From f2e28161a895831406deeb29c47f482e6d6cfcae Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Sun, 15 Nov 2020 20:58:17 +0300 Subject: [PATCH] Added human player model - Extracted NPedModel out of QuadripedModel - Both are now configurable - Added HumanoidModel - Added human model - Changed NPedModel animation logic - Added a few skins including first sketch of Pyotr by WarDreg - Decreased walking speed --- .../client/graphics/model/LambdaModel.java | 9 + .../graphics/texture/ComplexTexture.java | 4 +- .../client/world/entity/HumanoidModel.java | 116 +++++++ .../client/world/entity/NPedModel.java | 284 ++++++++++++++++++ .../client/world/entity/QuadripedModel.java | 200 ++---------- .../progressia/common/world/ChunkData.java | 36 ++- .../windcorp/progressia/test/TestContent.java | 11 +- .../test/TestEntityRenderHuman.java | 164 ++++++++++ .../progressia/test/TestPlayerControls.java | 2 +- .../assets/textures/entities/pyotr.png | Bin 0 -> 8036 bytes .../assets/textures/entities/test_skin.png | Bin 0 -> 16530 bytes .../textures/entities/test_skin_layout.png | Bin 0 -> 2850 bytes 12 files changed, 627 insertions(+), 199 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java create mode 100644 src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java create mode 100644 src/main/java/ru/windcorp/progressia/test/TestEntityRenderHuman.java create mode 100644 src/main/resources/assets/textures/entities/pyotr.png create mode 100644 src/main/resources/assets/textures/entities/test_skin.png create mode 100644 src/main/resources/assets/textures/entities/test_skin_layout.png diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/LambdaModel.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/LambdaModel.java index c6799c5..00d47b1 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/model/LambdaModel.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/LambdaModel.java @@ -121,5 +121,14 @@ public class LambdaModel extends DynamicModel { } } + + public static LambdaModel animate(Renderable model, TransformGetter transform) { + return new LambdaModel( + new Renderable[] { model }, + new Mat4[] { new Mat4() }, + new boolean[] { true }, + new TransformGetter[] { transform } + ); + } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java index d8f4c18..8aaf4d4 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/ComplexTexture.java @@ -19,10 +19,10 @@ public class ComplexTexture { this.primitive = primitive; this.assumedWidth = abstractWidth - * primitive.getWidth() / (float) primitive.getBufferWidth(); + / (float) primitive.getWidth() * primitive.getBufferWidth(); this.assumedHeight = abstractHeight - * primitive.getHeight() / (float) primitive.getBufferHeight(); + / (float) primitive.getHeight() * primitive.getBufferHeight(); } public Texture get(int x, int y, int width, int height) { diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java new file mode 100644 index 0000000..45817db --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java @@ -0,0 +1,116 @@ +package ru.windcorp.progressia.client.world.entity; + +import static java.lang.Math.*; +import static ru.windcorp.progressia.common.util.FloatMathUtils.*; + +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.progressia.common.world.entity.EntityData; + +public class HumanoidModel extends NPedModel { + + protected static abstract class Limb extends BodyPart { + private final float animationOffset; + + public Limb( + Renderable renderable, Vec3 joint, + float animationOffset + ) { + super(renderable, joint); + this.animationOffset = animationOffset; + } + + @Override + protected void applyTransform(Mat4 mat, NPedModel model) { + float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset; + float value = sin(phase); + float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter(); + mat.rotateY(value * amplitude); + } + + protected abstract float getSwingAmplitude(HumanoidModel model); + + } + + public static class Leg extends Limb { + public Leg( + Renderable renderable, Vec3 joint, + float animationOffset + ) { + super(renderable, joint, animationOffset); + } + + @Override + protected float getSwingAmplitude(HumanoidModel model) { + return model.walkingLegSwing; + } + } + + public static class Arm extends Limb { + public Arm( + Renderable renderable, Vec3 joint, + float animationOffset + ) { + super(renderable, joint, animationOffset); + } + + @Override + protected float getSwingAmplitude(HumanoidModel model) { + return model.walkingArmSwing; + } + } + + private final Arm leftArm; + private final Arm rightArm; + private final Leg leftLeg; + private final Leg rightLeg; + + private float walkingLegSwing; + private float walkingArmSwing; + + public HumanoidModel( + EntityData entity, + + Body body, Head head, + Arm leftArm, Arm rightArm, + Leg leftLeg, Leg rightLeg, + + float scale + ) { + super(entity, body, head, scale); + this.leftArm = leftArm; + this.rightArm = rightArm; + this.leftLeg = leftLeg; + this.rightLeg = rightLeg; + } + + @Override + protected void renderBodyParts(ShapeRenderHelper renderer) { + super.renderBodyParts(renderer); + leftArm.render(renderer, this); + rightArm.render(renderer, this); + leftLeg.render(renderer, this); + rightLeg.render(renderer, this); + } + + public float getWalkingArmSwing() { + return walkingArmSwing; + } + + public float getWalkingLegSwing() { + return walkingLegSwing; + } + + public HumanoidModel setWalkingLegSwing(float walkingLegSwing) { + this.walkingLegSwing = walkingLegSwing; + return this; + } + + public HumanoidModel setWalkingArmSwing(float walkingArmSwing) { + this.walkingArmSwing = walkingArmSwing; + return this; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java new file mode 100644 index 0000000..441ff6e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java @@ -0,0 +1,284 @@ +package ru.windcorp.progressia.client.world.entity; + +import static java.lang.Math.atan2; +import static java.lang.Math.min; +import static java.lang.Math.pow; +import static java.lang.Math.toRadians; +import static ru.windcorp.progressia.common.util.FloatMathUtils.normalizeAngle; + +import glm.Glm; +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import glm.vec._4.Vec4; +import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.progressia.common.Units; +import ru.windcorp.progressia.common.util.Matrices; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.entity.EntityData; + +public abstract class NPedModel extends EntityRenderable { + + protected static abstract class BodyPart { + private final Renderable renderable; + private final Vec3 translation = new Vec3(); + + public BodyPart(Renderable renderable, Vec3 joint) { + this.renderable = renderable; + if (joint != null) { + this.translation.set(joint); + } + } + + + protected void render( + ShapeRenderHelper renderer, NPedModel model + ) { + renderer.pushTransform().translate(translation); + applyTransform(renderer.pushTransform(), model); + renderable.render(renderer); + renderer.popTransform(); + renderer.popTransform(); + } + + protected abstract void applyTransform(Mat4 mat, NPedModel model); + + public Vec3 getTranslation() { + return translation; + } + } + + public static class Body extends BodyPart { + public Body(Renderable renderable) { + super(renderable, null); + } + + @Override + protected void applyTransform(Mat4 mat, NPedModel model) { + // Do nothing + } + } + + public static class Head extends BodyPart { + private final float maxYaw; + private final float maxPitch; + + private final Vec3 viewPoint; + + public Head( + Renderable renderable, Vec3 joint, + double maxYawDegrees, double maxPitchDegrees, + Vec3 viewPoint + ) { + super(renderable, joint); + this.maxYaw = (float) toRadians(maxYawDegrees); + this.maxPitch = (float) toRadians(maxPitchDegrees); + this.viewPoint = viewPoint; + } + + @Override + protected void applyTransform(Mat4 mat, NPedModel model) { + mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch()); + } + + public Vec3 getViewPoint() { + return viewPoint; + } + } + + protected final Body body; + protected final Head head; + + private float walkingParameter = 0; + private float velocityParameter = 0; + private float velocity = 0; + + /** + * If {@link #velocity} is greater than this value, {@link #velocityParameter} is 1.0. + */ + private float maxEffectiveVelocity = 5 * Units.METERS_PER_SECOND; + + /** + * If {@link #velocity} is less than {@link #maxEffectiveVelocity}, then + * {@code velocityCoeff = exp(velocity / maxEffectiveVelocity, velocityCoeffPower)}. + */ + private float velocityCoeffPower = 1; + + private final float scale; + + private float walkingFrequency; + + private float bodyYaw = Float.NaN; + private float headYaw; + private float headPitch; + + public NPedModel(EntityData data, Body body, Head head, float scale) { + super(data); + this.body = body; + this.head = head; + this.scale = scale; + } + + @Override + public void render(ShapeRenderHelper renderer) { + renderer.pushTransform().scale(scale).rotateZ(bodyYaw); + renderBodyParts(renderer); + renderer.popTransform(); + + accountForVelocity(); + evaluateAngles(); + } + + protected void renderBodyParts(ShapeRenderHelper renderer) { + body.render(renderer, this); + head.render(renderer, this); + } + + private void evaluateAngles() { + float globalYaw = normalizeAngle(getData().getYaw()); + + if (Float.isNaN(bodyYaw)) { + bodyYaw = globalYaw; + headYaw = 0; + } else { + headYaw = normalizeAngle(globalYaw - bodyYaw); + + if (headYaw > +head.maxYaw) { + bodyYaw += headYaw - +head.maxYaw; + headYaw = +head.maxYaw; + } else if (headYaw < -head.maxYaw) { + bodyYaw += headYaw - -head.maxYaw; + headYaw = -head.maxYaw; + } + } + + bodyYaw = normalizeAngle(bodyYaw); + + headPitch = Glm.clamp( + getData().getPitch(), + -head.maxPitch, head.maxPitch + ); + } + + private void accountForVelocity() { + Vec3 horizontal = Vectors.grab3(); + horizontal.set(getData().getVelocity()); + horizontal.z = 0; + + velocity = horizontal.length(); + + evaluateVelocityCoeff(); + + // TODO switch to world time + walkingParameter += velocity * GraphicsInterface.getFrameLength() * 1000; + + bodyYaw += velocityParameter * normalizeAngle( + (float) (atan2(horizontal.y, horizontal.x) - bodyYaw) + ) * min(1, GraphicsInterface.getFrameLength() * 10); + Vectors.release(horizontal); + } + + private void evaluateVelocityCoeff() { + if (velocity > maxEffectiveVelocity) { + velocityParameter = 1; + } else { + velocityParameter = (float) pow(velocity / maxEffectiveVelocity, velocityCoeffPower); + } + } + + @Override + public void getViewPoint(Vec3 output) { + Mat4 m = Matrices.grab4(); + Vec4 v = Vectors.grab4(); + + m.identity() + .scale(scale) + .rotateZ(bodyYaw) + .translate(head.getTranslation()) + .rotateZ(headYaw) + .rotateY(headPitch); + + v.set(head.getViewPoint(), 1); + m.mul(v); + + output.set(v.x, v.y, v.z); + + Vectors.release(v); + Matrices.release(m); + } + + public Body getBody() { + return body; + } + + public Head getHead() { + return head; + } + + public float getBodyYaw() { + return bodyYaw; + } + + public float getHeadYaw() { + return headYaw; + } + + public float getHeadPitch() { + return headPitch; + } + + /** + * Returns a number in the range [0; 1] that can be used to scale animation effects that depend on speed. + * This parameter is 0 when the entity is not moving and 1 when it's moving "fast". + * @return velocity parameter + */ + protected float getVelocityParameter() { + return velocityParameter; + } + + /** + * Returns a number that can be used to parameterize animation effects that depend on walking. + * This parameter increases when the entity moves (e.g. this can be total traveled distance). + * @return walking parameter + */ + protected float getWalkingParameter() { + return walkingParameter; + } + + protected float getVelocity() { + return velocity; + } + + public float getScale() { + return scale; + } + + protected float getWalkingFrequency() { + return walkingFrequency; + } + + public NPedModel setWalkingFrequency(float walkingFrequency) { + this.walkingFrequency = walkingFrequency; + return this; + } + + public float getMaxEffectiveVelocity() { + return maxEffectiveVelocity; + } + + public float getVelocityCoeffPower() { + return velocityCoeffPower; + } + + public NPedModel setMaxEffectiveVelocity(float maxEffectiveVelocity) { + this.maxEffectiveVelocity = maxEffectiveVelocity; + return this; + } + + public NPedModel setVelocityCoeffPower(float velocityCoeffPower) { + this.velocityCoeffPower = velocityCoeffPower; + return this; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java b/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java index c19a3b0..00311d4 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/QuadripedModel.java @@ -2,86 +2,15 @@ package ru.windcorp.progressia.client.world.entity; import static java.lang.Math.*; import static ru.windcorp.progressia.common.util.FloatMathUtils.*; +import static ru.windcorp.progressia.common.util.FloatMathUtils.sin; -import glm.Glm; import glm.mat._4.Mat4; import glm.vec._3.Vec3; -import glm.vec._4.Vec4; -import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; -import ru.windcorp.progressia.common.util.Matrices; -import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.entity.EntityData; -public class QuadripedModel extends EntityRenderable { - - private static abstract class BodyPart { - private final Renderable renderable; - private final Vec3 translation = new Vec3(); - - public BodyPart(Renderable renderable, Vec3 joint) { - this.renderable = renderable; - if (joint != null) { - this.translation.set(joint); - } - } - - - protected void render( - ShapeRenderHelper renderer, QuadripedModel model - ) { - renderer.pushTransform().translate(translation); - applyTransform(renderer.pushTransform(), model); - renderable.render(renderer); - renderer.popTransform(); - renderer.popTransform(); - } - - protected abstract void applyTransform(Mat4 mat, QuadripedModel model); - - public Vec3 getTranslation() { - return translation; - } - } - - public static class Body extends BodyPart { - public Body(Renderable renderable) { - super(renderable, null); - } - - @Override - protected void applyTransform(Mat4 mat, QuadripedModel model) { - // Do nothing - } - } - - public static class Head extends BodyPart { - private final float maxYaw; - private final float maxPitch; - - private final Vec3 viewPoint; - - public Head( - Renderable renderable, Vec3 joint, - double maxYawDegrees, double maxPitchDegrees, - Vec3 viewPoint - ) { - super(renderable, joint); - this.maxYaw = (float) toRadians(maxYawDegrees); - this.maxPitch = (float) toRadians(maxPitchDegrees); - this.viewPoint = viewPoint; - } - - @Override - protected void applyTransform(Mat4 mat, QuadripedModel model) { - mat.rotateZ(model.headYaw).rotateY(model.headPitch); - } - - public Vec3 getViewPoint() { - return viewPoint; - } - } +public class QuadripedModel extends NPedModel { public static class Leg extends BodyPart { private final float animationOffset; @@ -95,33 +24,19 @@ public class QuadripedModel extends EntityRenderable { } @Override - protected void applyTransform(Mat4 mat, QuadripedModel model) { - mat.rotateY(sin(model.walkingFrequency * model.walkingAnimationParameter + animationOffset) * model.walkingSwing * model.velocityCoeff); + protected void applyTransform(Mat4 mat, NPedModel model) { + float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset; + float value = sin(phase); + float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter(); + mat.rotateY(value * amplitude); } + } - private final Body body; - private final Head head; private final Leg leftForeLeg, rightForeLeg; private final Leg leftHindLeg, rightHindLeg; - private final float scale; - - private float walkingAnimationParameter = 0; - private float velocityCoeff = 0; - private float velocity = 0; - - /** - * Controls how quickly velocityCoeff approaches 1 - */ - private float velocityCutoff = 10; - - private float walkingFrequency = 0.15f / 60.0f; private float walkingSwing = (float) toRadians(30); - - private float bodyYaw = Float.NaN; - private float headYaw; - private float headPitch; public QuadripedModel( EntityData entity, @@ -132,105 +47,30 @@ public class QuadripedModel extends EntityRenderable { float scale ) { - super(entity); + super(entity, body, head, scale); - this.body = body; - this.head = head; this.leftForeLeg = leftForeLeg; this.rightForeLeg = rightForeLeg; this.leftHindLeg = leftHindLeg; this.rightHindLeg = rightHindLeg; - - this.scale = scale; } @Override - public void render(ShapeRenderHelper renderer) { - renderer.pushTransform().scale(scale).rotateZ(bodyYaw); - body.render(renderer, this); - head.render(renderer, this); - leftForeLeg.render(renderer, this); - rightForeLeg.render(renderer, this); - leftHindLeg.render(renderer, this); - rightHindLeg.render(renderer, this); - renderer.popTransform(); - - accountForVelocity(); - evaluateAngles(); - } - - private void evaluateAngles() { - float globalYaw = normalizeAngle(getData().getYaw()); - - if (Float.isNaN(bodyYaw)) { - bodyYaw = globalYaw; - headYaw = 0; - } else { - headYaw = normalizeAngle(globalYaw - bodyYaw); - - if (headYaw > +head.maxYaw) { - bodyYaw += headYaw - +head.maxYaw; - headYaw = +head.maxYaw; - } else if (headYaw < -head.maxYaw) { - bodyYaw += headYaw - -head.maxYaw; - headYaw = -head.maxYaw; - } - } - - bodyYaw = normalizeAngle(bodyYaw); - - headPitch = Glm.clamp( - getData().getPitch(), - -head.maxPitch, head.maxPitch - ); - } - - private void accountForVelocity() { - Vec3 horizontal = Vectors.grab3(); - horizontal.set(getData().getVelocity()); - horizontal.z = 0; - - velocity = horizontal.length(); - - evaluateVelocityCoeff(); - - // TODO switch to world time - walkingAnimationParameter += velocity * GraphicsInterface.getFrameLength() * 1000; - - bodyYaw += velocityCoeff * normalizeAngle( - (float) (atan2(horizontal.y, horizontal.x) - bodyYaw) - ) * min(1, GraphicsInterface.getFrameLength() * 10); - Vectors.release(horizontal); + protected void renderBodyParts(ShapeRenderHelper renderer) { + super.renderBodyParts(renderer); + this.leftForeLeg.render(renderer, this); + this.rightForeLeg.render(renderer, this); + this.leftHindLeg.render(renderer, this); + this.rightHindLeg.render(renderer, this); } - private void evaluateVelocityCoeff() { - if (velocity * velocityCutoff > 1) { - velocityCoeff = 1; - } else { - velocityCoeff = velocity * velocityCutoff; - velocityCoeff *= velocityCoeff; - } + public float getWalkingSwing() { + return walkingSwing; } - @Override - public void getViewPoint(Vec3 output) { - Mat4 m = Matrices.grab4(); - Vec4 v = Vectors.grab4(); - - m.identity() - .scale(scale) - .rotateZ(bodyYaw) - .translate(head.getTranslation()) - .rotateZ(headYaw) - .rotateY(headPitch); - - v.set(head.getViewPoint(), 1); - m.mul(v); - - output.set(v.x, v.y, v.z); - - Vectors.release(v); - Matrices.release(m); + public QuadripedModel setWalkingSwing(float walkingSwing) { + this.walkingSwing = walkingSwing; + return this; } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java index 18d44b0..f25ac0a 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/ChunkData.java @@ -80,7 +80,7 @@ public class ChunkData { TileData flowers = TileDataRegistry.getInstance().get("Test:YellowFlowers"); TileData sand = TileDataRegistry.getInstance().get("Test:Sand"); - Vec3i aPoint = new Vec3i(5, 0, BLOCKS_PER_CHUNK + BLOCKS_PER_CHUNK/2); + Vec3i aPoint = new Vec3i(5, 0, BLOCKS_PER_CHUNK + BLOCKS_PER_CHUNK/2).sub(getPosition()); Vec3i pos = new Vec3i(); for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) { @@ -132,18 +132,28 @@ public class ChunkData { } } - EntityData javapony = EntityDataRegistry.getInstance().create("Test:Javapony"); - javapony.setEntityId(0x42); - javapony.setPosition(new Vec3(-6, -6, 20)); - javapony.setDirection(new Vec2( - (float) Math.toRadians(40), (float) Math.toRadians(45) - )); - getEntities().add(javapony); - - EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie"); - statie.setEntityId(0xDEADBEEF); - statie.setPosition(new Vec3(0, 15, 16)); - getEntities().add(statie); + if (!getPosition().any()) { +// EntityData javapony = EntityDataRegistry.getInstance().create("Test:Javapony"); +// javapony.setEntityId(0x42); +// javapony.setPosition(new Vec3(-6, -6, 20)); +// javapony.setDirection(new Vec2( +// (float) Math.toRadians(40), (float) Math.toRadians(45) +// )); +// getEntities().add(javapony); + + EntityData player = EntityDataRegistry.getInstance().create("Test:Player"); + player.setEntityId(0x42); + player.setPosition(new Vec3(-6, -6, 20)); + player.setDirection(new Vec2( + (float) Math.toRadians(40), (float) Math.toRadians(45) + )); + getEntities().add(player); + + EntityData statie = EntityDataRegistry.getInstance().create("Test:Statie"); + statie.setEntityId(0xDEADBEEF); + statie.setPosition(new Vec3(0, 15, 16)); + getEntities().add(statie); + } } public BlockData getBlock(Vec3i posInChunk) { diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index 7b24efa..1b2d16e 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -87,9 +87,14 @@ public class TestContent { } private static void registerEntities() { - registerEntityData("Test", "Javapony", e -> e.setCollisionModel(new AABB(0, 0, -0.05f, 0.75f, 0.75f, 1.2f))); - register(new TestEntityRenderJavapony()); - register(new EntityLogic("Test", "Javapony")); +// registerEntityData("Test", "Javapony", e -> e.setCollisionModel(new AABB(0, 0, -0.05f, 0.75f, 0.75f, 1.2f))); +// register(new TestEntityRenderJavapony()); +// register(new EntityLogic("Test", "Javapony")); + + float scale = 1.8f / 8; + registerEntityData("Test", "Player", e -> e.setCollisionModel(new AABB(0, 0, 4*scale, 0.75f, 0.75f, 1.8f))); + register(new TestEntityRenderHuman()); + register(new EntityLogic("Test", "Player")); register("Test", "Statie", TestEntityDataStatie::new); register(new TestEntityRenderStatie()); diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderHuman.java b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderHuman.java new file mode 100644 index 0000000..cecfacf --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderHuman.java @@ -0,0 +1,164 @@ +package ru.windcorp.progressia.test; + +import static java.lang.Math.toRadians; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; +import ru.windcorp.progressia.client.graphics.model.LambdaModel; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.Shapes.PppBuilder; +import ru.windcorp.progressia.client.graphics.model.StaticModel; +import ru.windcorp.progressia.client.graphics.texture.ComplexTexture; +import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; +import ru.windcorp.progressia.client.world.entity.HumanoidModel; +import ru.windcorp.progressia.client.world.entity.EntityRender; +import ru.windcorp.progressia.client.world.entity.EntityRenderRegistry; +import ru.windcorp.progressia.client.world.entity.EntityRenderable; +import ru.windcorp.progressia.common.util.FloatMathUtils; +import ru.windcorp.progressia.common.world.entity.EntityData; + +import static java.lang.Math.*; + +public class TestEntityRenderHuman extends EntityRender { + + private static final float SECOND_LAYER_OFFSET = 1 / 12f; + + private final Renderable body; + private final Renderable head; + private final Renderable leftArm; + private final Renderable rightArm; + private final Renderable leftLeg; + private final Renderable rightLeg; + + public TestEntityRenderHuman() { + super("Test", "Player"); + + ComplexTexture texture = new ComplexTexture( + EntityRenderRegistry.getEntityTexture("pyotr"), + 16, 16 + ); + + this.body = createBody(texture); + this.head = createHead(texture); + + this.leftArm = createLimb(texture, 8, 0, 12, 0, true, true); + this.rightArm = createLimb(texture, 10, 8, 10, 4, true, false); + this.leftLeg = createLimb(texture, 4, 0, 0, 0, false, true); + this.rightLeg = createLimb(texture, 0, 8, 0, 4, false, false); + } + + private Renderable createBody(ComplexTexture texture) { + return createLayeredCuboid( + texture, + 4, 8, + 4, 4, + 2, 3, 1, + -0.5f, -1, 3, + 1, 2, 3 + ); + } + + private Renderable createHead(ComplexTexture texture) { + return createLayeredCuboid( + texture, + 0, 12, + 8, 12, + 2, 2, 2, + -1, -1, 0, + 2, 2, 2 + ); + } + + private Renderable createLimb( + ComplexTexture texture, + int tx, int ty, + int tx2, int ty2, + boolean isArm, boolean isLeft + ) { + Renderable model = createLayeredCuboid( + texture, + tx, ty, + tx2, ty2, + 1, 3, 1, + -0.5f, -0.5f, isArm ? -2.5f : -3f, + 1, 1, 3 + ); + + if (isArm) { + return LambdaModel.animate( + model, + mat -> { + double phase = GraphicsInterface.getTime() + (isLeft ? 0 : Math.PI / 3); + mat.rotateX((isLeft ? +1 : -1) * 1/40f * (sin(phase) + 1)); + mat.rotateY(1/20f * sin(Math.PI / 3 * phase)); + } + ); + } else { + return model; + } + } + + private Renderable createLayeredCuboid( + ComplexTexture texture, + int tx, int ty, + int tx2, int ty2, + int tw, int th, int td, + float ox, float oy, float oz, + float sx, float sy, float sz + ) { + WorldRenderProgram program = WorldRenderProgram.getDefault(); + StaticModel.Builder b = StaticModel.builder(); + + // First layer + b.addPart(new PppBuilder( + program, + texture.getCuboidTextures(tx, ty, tw, th, td) + ).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create()); + + ox -= SECOND_LAYER_OFFSET; + oy -= SECOND_LAYER_OFFSET; + oz -= SECOND_LAYER_OFFSET; + + sx += SECOND_LAYER_OFFSET * 2; + sy += SECOND_LAYER_OFFSET * 2; + sz += SECOND_LAYER_OFFSET * 2; + + // Second layer + b.addPart(new PppBuilder( + program, + texture.getCuboidTextures(tx2, ty2, tw, th, td) + ).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create()); + + return new StaticModel(b); + } + + @Override + public EntityRenderable createRenderable(EntityData entity) { + return new HumanoidModel( + entity, + + new HumanoidModel.Body(body), + new HumanoidModel.Head( + head, new Vec3(0, 0, 6), 70, 25, new Vec3(1.2f, 0, 1.5f) + ), + new HumanoidModel.Arm( + leftArm, new Vec3(0, +1.5f, 3 + 3 - 0.5f), 0.0f + ), + new HumanoidModel.Arm( + rightArm, new Vec3(0, -1.5f, 3 + 3 - 0.5f), FloatMathUtils.PI_F + ), + new HumanoidModel.Leg( + leftLeg, new Vec3(0, +0.5f, 3), FloatMathUtils.PI_F + ), + new HumanoidModel.Leg( + rightLeg, new Vec3(0, -0.5f, 3), 0.0f + ), + + 1.8f / (3 + 3 + 2) + ) + .setWalkingArmSwing((float) toRadians(30)) + .setWalkingLegSwing((float) toRadians(50)) + .setWalkingFrequency(0.15f / 60.0f); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java index a41a3c2..5ad784b 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java +++ b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java @@ -39,7 +39,7 @@ public class TestPlayerControls { private static final float FLYING_CONTROL_AUTHORITY = 0.05f; // Horizontal and vertical max control speed when walking - private static final float WALKING_SPEED = 5.0f * Units.METERS_PER_SECOND; + private static final float WALKING_SPEED = 4.0f * Units.METERS_PER_SECOND; // (0; 1], 1 is instant change, 0 is no control authority private static final float WALKING_CONTROL_AUTHORITY = 0.1f; diff --git a/src/main/resources/assets/textures/entities/pyotr.png b/src/main/resources/assets/textures/entities/pyotr.png new file mode 100644 index 0000000000000000000000000000000000000000..a0cff2024a873ec77f1b88361042a598baf375c5 GIT binary patch literal 8036 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/textures/entities/test_skin.png b/src/main/resources/assets/textures/entities/test_skin.png new file mode 100644 index 0000000000000000000000000000000000000000..e75b5e8b2fd5b9c2dd39526c397b3a43b954e965 GIT binary patch literal 16530 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 AYybcN literal 0 HcmV?d00001