From f9717be412376662070ef3f72e7cee7bc767deab Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Fri, 29 Jan 2021 23:19:22 +0300 Subject: [PATCH] Switched to using looking-at vectors instead of Euler angles Also fixed camera jittering and added some vector functions --- .../client/graphics/world/Camera.java | 79 +++---- .../client/graphics/world/EntityAnchor.java | 24 +- .../client/graphics/world/Selection.java | 5 +- .../client/world/entity/EntityRenderable.java | 60 ++++- .../client/world/entity/HumanoidModel.java | 1 + .../client/world/entity/NPedModel.java | 206 ++++++++++++------ .../client/world/entity/QuadripedModel.java | 1 + .../progressia/common/util/VectorUtil.java | 74 ++++++- .../progressia/common/world/BlockRay.java | 12 +- .../common/world/entity/EntityData.java | 129 ++++++++--- .../progressia/server/PlayerManager.java | 11 +- .../windcorp/progressia/test/LayerTestUI.java | 29 +-- .../test/TestEntityRenderStatie.java | 2 +- .../progressia/test/TestPlayerControls.java | 92 +++++--- 14 files changed, 517 insertions(+), 208 deletions(-) diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java index 46b551c..9c30a49 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/Camera.java @@ -29,6 +29,9 @@ import glm.mat._4.Mat4; import glm.vec._3.Vec3; import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; import ru.windcorp.progressia.client.graphics.world.Camera.Anchor.Mode; +import ru.windcorp.progressia.client.world.entity.NPedModel; +import ru.windcorp.progressia.common.util.Matrices; +import ru.windcorp.progressia.common.util.Vectors; public class Camera { @@ -60,13 +63,13 @@ public class Camera { } } - void getCameraPosition(Vec3 output); + Vec3 getCameraPosition(Vec3 output); - void getCameraVelocity(Vec3 output); + Vec3 getCameraVelocity(Vec3 output); - float getCameraYaw(); - - float getCameraPitch(); + Vec3 getLookingAt(Vec3 output); + + Vec3 getUpVector(Vec3 output); Collection getCameraModes(); @@ -84,14 +87,11 @@ public class Camera { */ private final Vec3 lastAnchorPosition = new Vec3(); - private float lastAnchorYaw; - private float lastAnchorPitch; + private final Vec3 lastAnchorLookingAt = new Vec3(); + private final Vec3 lastAnchorUpVector = new Vec3(); private final Mat4 lastCameraMatrix = new Mat4(); - private final Vec3 lastAnchorLookingAt = new Vec3(); - private final Vec3 lastAnchorUp = new Vec3(); - { invalidateCache(); } @@ -108,6 +108,9 @@ public class Camera { */ public void apply(WorldRenderHelper helper) { + if (NPedModel.flag) { +// System.out.println("Camera.apply()"); + } applyPerspective(helper); rotateCoordinateSystem(helper); @@ -149,26 +152,34 @@ public class Camera { } private void applyDirection(WorldRenderHelper helper) { - float pitch = anchor.getCameraPitch(); - float yaw = anchor.getCameraYaw(); + anchor.getLookingAt(lastAnchorLookingAt); + anchor.getUpVector(lastAnchorUpVector); - helper.pushViewTransform() - .rotateY(-pitch) - .rotateZ(-yaw); + lookAt(helper.pushViewTransform()); + } - this.lastAnchorYaw = yaw; - this.lastAnchorPitch = pitch; - - this.lastAnchorLookingAt.set( - cos(pitch) * cos(yaw), - cos(pitch) * sin(yaw), - sin(pitch) - ); - this.lastAnchorUp.set( - cos(pitch + PI_F / 2) * cos(yaw), - cos(pitch + PI_F / 2) * sin(yaw), - sin(pitch + PI_F / 2) + private void lookAt(Mat4 result) { + Vec3 f = this.lastAnchorLookingAt; + Vec3 s = Vectors.grab3(); + Vec3 u = Vectors.grab3(); + + f.cross(this.lastAnchorUpVector, s); + s.normalize(); + + s.cross(f, u); + + Mat4 workspace = Matrices.grab4(); + workspace.set( + +f.x, -s.x, +u.x, 0, + +f.y, -s.y, +u.y, 0, + +f.z, -s.z, +u.z, 0, + 0, 0, 0, 1 ); + result.mul(workspace); + Matrices.release(workspace); + + Vectors.release(s); + Vectors.release(u); } private void applyPosition(WorldRenderHelper helper) { @@ -247,8 +258,6 @@ public class Camera { private void invalidateCache() { this.lastAnchorPosition.set(Float.NaN); - this.lastAnchorYaw = Float.NaN; - this.lastAnchorPitch = Float.NaN; this.lastCameraMatrix.set( Float.NaN, @@ -270,7 +279,7 @@ public class Camera { ); this.lastAnchorLookingAt.set(Float.NaN); - this.lastAnchorUp.set(Float.NaN); + this.lastAnchorUpVector.set(Float.NaN); } public Anchor.Mode getMode() { @@ -289,14 +298,6 @@ public class Camera { return currentModeIndex; } - public float getLastAnchorYaw() { - return lastAnchorYaw; - } - - public float getLastAnchorPitch() { - return lastAnchorPitch; - } - public Vec3 getLastAnchorPosition() { return lastAnchorPosition; } @@ -310,7 +311,7 @@ public class Camera { } public Vec3 getLastAnchorUp() { - return lastAnchorUp; + return lastAnchorUpVector; } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java index d11ea92..cb578d9 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/EntityAnchor.java @@ -59,24 +59,32 @@ public class EntityAnchor implements Anchor { } @Override - public void getCameraPosition(Vec3 output) { + public Vec3 getCameraPosition(Vec3 output) { + if (output == null) output = new Vec3(); model.getViewPoint(output); - output.add(entity.getPosition()); + output.add(model.getPosition()); + return output; } @Override - public void getCameraVelocity(Vec3 output) { + public Vec3 getCameraVelocity(Vec3 output) { + if (output == null) output = new Vec3(); output.set(entity.getVelocity()); + return output; } @Override - public float getCameraYaw() { - return entity.getYaw(); + public Vec3 getLookingAt(Vec3 output) { + if (output == null) output = new Vec3(); + model.getLookingAt(output); + return output; } - + @Override - public float getCameraPitch() { - return entity.getPitch(); + public Vec3 getUpVector(Vec3 output) { + if (output == null) output = new Vec3(); + model.getUpVector(output); + return output; } @Override diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java index cd4ba29..e6cd57d 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/Selection.java @@ -38,10 +38,9 @@ public class Selection { private BlockRay ray = new BlockRay(); public void update(WorldRender world, EntityData player) { - Vec3 direction = new Vec3(); Vec3 start = new Vec3(); - - player.getLookingAtVector(direction); + Vec3 direction = player.getLookingAt(); + world.getEntityRenderable(player).getViewPoint(start); start.add(player.getPosition()); diff --git a/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderable.java b/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderable.java index eecaa16..24ea147 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderable.java +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/EntityRenderable.java @@ -15,22 +15,49 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.client.world.entity; import glm.vec._3.Vec3; +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.world.entity.EntityData; import ru.windcorp.progressia.common.world.generic.GenericEntity; public abstract class EntityRenderable implements Renderable, GenericEntity { private final EntityData data; + + private long stateComputedForFrame = -1; public EntityRenderable(EntityData data) { this.data = data; } + /** + * Updates the state of this model. This method is invoked exactly once per + * renderable per frame before this entity is queried for the first time. + */ + protected void update() { + // Do nothing + } + + private void updateIfNecessary() { + if (stateComputedForFrame != GraphicsInterface.getFramesRendered()) { + update(); + stateComputedForFrame = GraphicsInterface.getFramesRendered(); + } + } + + @Override + public final void render(ShapeRenderHelper renderer) { + updateIfNecessary(); + doRender(renderer); + } + + protected abstract void doRender(ShapeRenderHelper renderer); + public EntityData getData() { return data; } @@ -45,7 +72,36 @@ public abstract class EntityRenderable implements Renderable, GenericEntity { return getData().getId(); } - public void getViewPoint(Vec3 output) { + public final Vec3 getLookingAt(Vec3 output) { + if (output == null) output = new Vec3(); + updateIfNecessary(); + doGetLookingAt(output); + return output; + } + + protected void doGetLookingAt(Vec3 output) { + output.set(getData().getLookingAt()); + } + + public final Vec3 getUpVector(Vec3 output) { + if (output == null) output = new Vec3(); + updateIfNecessary(); + doGetUpVector(output); + return output; + } + + protected void doGetUpVector(Vec3 output) { + output.set(getData().getUpVector()); + } + + public final Vec3 getViewPoint(Vec3 output) { + if (output == null) output = new Vec3(); + updateIfNecessary(); + doGetViewPoint(output); + return output; + } + + protected void doGetViewPoint(Vec3 output) { output.set(0, 0, 0); } 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 index 1609bfc..c33356f 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/HumanoidModel.java @@ -43,6 +43,7 @@ public class HumanoidModel extends NPedModel { @Override protected void applyTransform(Mat4 mat, NPedModel model) { + super.applyTransform(mat, model); float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset; float value = sin(phase); float amplitude = getSwingAmplitude((HumanoidModel) model) * model.getVelocityParameter(); 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 index ead6565..ecefd06 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java +++ b/src/main/java/ru/windcorp/progressia/client/world/entity/NPedModel.java @@ -18,11 +18,8 @@ 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.FloatMathUtil.normalizeAngle; import glm.Glm; import glm.mat._4.Mat4; @@ -32,6 +29,9 @@ 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.FloatMathUtil; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.entity.EntityData; public abstract class NPedModel extends EntityRenderable { @@ -51,29 +51,30 @@ public abstract class NPedModel extends EntityRenderable { 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); + protected void applyTransform(Mat4 mat, NPedModel model) { + mat.translate(getTranslation()); + } public Vec3 getTranslation() { return translation; } + + public Mat4 getTransform(Mat4 output, NPedModel model) { + if (output == null) output = new Mat4().identity(); + applyTransform(output, model); + return output; + } } 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 { @@ -97,7 +98,8 @@ public abstract class NPedModel extends EntityRenderable { @Override protected void applyTransform(Mat4 mat, NPedModel model) { - mat.rotateZ(model.getHeadYaw()).rotateY(model.getHeadPitch()); + super.applyTransform(mat, model); + mat.rotateZ(-model.getHeadYaw()).rotateY(-model.getHeadPitch()); } public Vec3 getViewPoint() { @@ -105,6 +107,8 @@ public abstract class NPedModel extends EntityRenderable { } } + public static boolean flag; + protected final Body body; protected final Head head; @@ -128,7 +132,9 @@ public abstract class NPedModel extends EntityRenderable { private float walkingFrequency; - private float bodyYaw = Float.NaN; + private final Vec3 bodyLookingAt = new Vec3().set(0); + private final Mat4 bodyTransform = new Mat4(); + private float headYaw; private float headPitch; @@ -138,68 +144,122 @@ public abstract class NPedModel extends EntityRenderable { this.head = head; this.scale = scale; - evaluateAngles(); + computeRotations(); } @Override - public void render(ShapeRenderHelper renderer) { - renderer.pushTransform().scale(scale).rotateZ(bodyYaw); + protected void doRender(ShapeRenderHelper renderer) { + renderer.pushTransform().scale(scale).mul(bodyTransform); 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 - ); + + @Override + protected void update() { + advanceTime(); + computeRotations(); } - private void accountForVelocity() { - Vec3 horizontal = new Vec3(getData().getVelocity()); - horizontal.z = 0; + private void computeRotations() { + if (!bodyLookingAt.any()) { + getData().getForwardVector(bodyLookingAt); + headYaw = 0; + } else { + ensureBodyLookingAtIsPerpendicularToUpVector(); + computeDesiredHeadYaw(); + clampHeadYawAndChangeBodyLookingAt(); + } + + recomputeBodyTransform(); + + setHeadPitch(); + } + + private void ensureBodyLookingAtIsPerpendicularToUpVector() { + Vec3 up = getData().getUpVector(); + if (up.dot(bodyLookingAt) > 1 - 1e-4) return; + + Vec3 tmp = Vectors.grab3(); + + tmp.set(up).mul(-up.dot(bodyLookingAt)).add(bodyLookingAt); + + float tmpLength = tmp.length(); + if (tmpLength > 1e-4) { + bodyLookingAt.set(tmp).div(tmpLength); + } else { + // bodyLookingAt is suddenly parallel to up vector -- PANIC! ENTERING RESCUE MODE! + getData().getForwardVector(bodyLookingAt); + } + + Vectors.release(tmp); + } + + private void computeDesiredHeadYaw() { + Vec3 newDirection = getData().getForwardVector(null); + Vec3 oldDirection = bodyLookingAt; + Vec3 up = getData().getUpVector(); + + headYaw = (float) VectorUtil.getAngle(oldDirection, newDirection, up); + } + + private void clampHeadYawAndChangeBodyLookingAt() { + float bodyYawChange = 0; + + if (headYaw > +head.maxYaw) { + bodyYawChange = headYaw - +head.maxYaw; + headYaw = +head.maxYaw; + } else if (headYaw < -head.maxYaw) { + bodyYawChange = headYaw - -head.maxYaw; + headYaw = -head.maxYaw; + } + + if (bodyYawChange != 0) { + VectorUtil.rotate(bodyLookingAt, getData().getUpVector(), bodyYawChange); + } + } + + private void recomputeBodyTransform() { + Vec3 u = getData().getUpVector(); + Vec3 f = bodyLookingAt; + Vec3 s = Vectors.grab3(); + + s.set(u).cross(f); + + bodyTransform.identity().set( + +f.x, -s.x, +u.x, 0, + +f.y, -s.y, +u.y, 0, + +f.z, -s.z, +u.z, 0, + 0, 0, 0, 1 + ); + + Vectors.release(s); + } + + private void setHeadPitch() { + headPitch = Glm.clamp((float) getData().getPitch(), -head.maxPitch, +head.maxPitch); + } + + private void advanceTime() { + Vec3 horizontal = getData().getUpVector() + .mul_(-getData().getUpVector().dot(getData().getVelocity())) + .add(getData().getVelocity()); velocity = horizontal.length(); - evaluateVelocityCoeff(); + computeVelocityParameter(); // 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); + + rotateBodyWithMovement(horizontal); } - private void evaluateVelocityCoeff() { + private void computeVelocityParameter() { if (velocity > maxEffectiveVelocity) { velocityParameter = 1; } else { @@ -207,17 +267,39 @@ public abstract class NPedModel extends EntityRenderable { } } + private void rotateBodyWithMovement(Vec3 target) { + if (velocityParameter == 0 || !target.any() || Glm.equals(target, bodyLookingAt)) { + return; + } + + Vec3 axis = getData().getUpVector(); + + float yawDifference = FloatMathUtil.normalizeAngle( + (float) VectorUtil.getAngle( + bodyLookingAt, + target.normalize_(), + axis + ) + ); + + float bodyYawChange = + velocityParameter * + yawDifference * + (float) Math.expm1(GraphicsInterface.getFrameLength() * 10); + + VectorUtil.rotate(bodyLookingAt, axis, bodyYawChange); + } + @Override - public void getViewPoint(Vec3 output) { + protected void doGetViewPoint(Vec3 output) { Mat4 m = new Mat4(); Vec4 v = new Vec4(); m.identity() .scale(scale) - .rotateZ(bodyYaw) - .translate(head.getTranslation()) - .rotateZ(headYaw) - .rotateY(headPitch); + .mul(bodyTransform); + + head.getTransform(m, this); v.set(head.getViewPoint(), 1); m.mul(v); @@ -232,9 +314,9 @@ public abstract class NPedModel extends EntityRenderable { public Head getHead() { return head; } - - public float getBodyYaw() { - return bodyYaw; + + public Vec3 getBodyLookingAt() { + return bodyLookingAt; } public float getHeadYaw() { 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 8755b08..db6dbf4 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 @@ -44,6 +44,7 @@ public class QuadripedModel extends NPedModel { @Override protected void applyTransform(Mat4 mat, NPedModel model) { + super.applyTransform(mat, model); float phase = model.getWalkingFrequency() * model.getWalkingParameter() + animationOffset; float value = sin(phase); float amplitude = ((QuadripedModel) model).getWalkingSwing() * model.getVelocityParameter(); diff --git a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java index 00ede3e..1de0848 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java +++ b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java @@ -20,6 +20,8 @@ package ru.windcorp.progressia.common.util; import java.util.function.Consumer; +import glm.Glm; +import glm.mat._3.Mat3; import glm.mat._4.Mat4; import glm.vec._2.Vec2; import glm.vec._2.d.Vec2d; @@ -135,12 +137,72 @@ public class VectorUtil { } public static void applyMat4(Vec3 inOut, Mat4 mat) { - Vec4 vec4 = Vectors.grab4(); - vec4.set(inOut, 1f); - - mat.mul(vec4); - - inOut.set(vec4.x, vec4.y, vec4.z); + applyMat4(inOut, mat, inOut); + } + + public static void rotate(Vec3 in, Vec3 axis, float angle, Vec3 out) { + Mat3 mat = Matrices.grab3(); + + mat.identity().rotate(angle, axis); + mat.mul(in, out); + + Matrices.release(mat); + } + + public static void rotate(Vec3 inOut, Vec3 axis, float angle) { + rotate(inOut, axis, angle, inOut); + } + + public static double getAngle(Vec3 from, Vec3 to, Vec3 normal) { + Vec3 left = Vectors.grab3(); + + left.set(normal).cross(from); + double sign = Math.signum(left.dot(to)); + + double result = (float) Math.acos(Glm.clamp(from.dot(to), -1, +1)) * sign; + + Vectors.release(left); + return result; + } + + public static Vec3 projectOnSurface(Vec3 in, Vec3 normal, Vec3 out) { + if (in == out) { + return projectOnSurface(in, normal); + } + + if (out == null) { + out = new Vec3(); + } + + out.set(normal).mul(-normal.dot(in)).add(in); + + return out; + } + + public static Vec3 projectOnSurface(Vec3 inOut, Vec3 normal) { + Vec3 buffer = Vectors.grab3(); + + projectOnSurface(inOut, normal, buffer); + inOut.set(buffer); + + Vectors.release(buffer); + + return inOut; + } + + public static Vec3 projectOnVector(Vec3 in, Vec3 vector, Vec3 out) { + if (out == null) { + out = new Vec3(); + } + + float dot = vector.dot(in); + out.set(vector).mul(dot); + + return out; + } + + public static Vec3 projectOnVector(Vec3 inOut, Vec3 vector) { + return projectOnVector(inOut, vector); } public static Vec3 linearCombination( diff --git a/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java b/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java index 4d5f1f9..743d238 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java +++ b/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java @@ -39,9 +39,17 @@ public class BlockRay { private boolean isValid = false; public void start(Vec3 position, Vec3 direction) { - if (!direction.any()) { + if (direction.x == 0 && direction.y == 0 && direction.z == 0) { throw new IllegalArgumentException("Direction is a zero vector"); } + + if (Float.isNaN(direction.x) || Float.isNaN(direction.y) || Float.isNaN(direction.z)) { + throw new IllegalArgumentException("Direction contains NaN: " + direction); + } + + if (Float.isNaN(position.x) || Float.isNaN(position.y) || Float.isNaN(position.z)) { + throw new IllegalArgumentException("Position contains NaN: " + position); + } isValid = true; this.position.set(position).sub(0.5f); // Make sure lattice points are @@ -75,6 +83,8 @@ public class BlockRay { tMin = tz; axis = Axis.Z; } + + assert tMin > 0 : "tMin is not positive (" + tMin + ")"; // block.(axis) += signum(direction.(axis)) VectorUtil.set(block, axis, VectorUtil.get(block, axis) + (int) signum(VectorUtil.get(direction, axis))); diff --git a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java index 7b3325c..2dcdf3e 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java +++ b/src/main/java/ru/windcorp/progressia/common/world/entity/EntityData.java @@ -15,14 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.common.world.entity; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; -import glm.vec._2.Vec2; import glm.vec._3.Vec3; import ru.windcorp.jputil.chars.StringUtil; import ru.windcorp.progressia.common.collision.Collideable; @@ -36,7 +35,8 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn private final Vec3 position = new Vec3(); private final Vec3 velocity = new Vec3(); - private final Vec2 direction = new Vec2(); + private final Vec3 lookingAt = new Vec3(1, 0, 0); + private final Vec3 upVector = new Vec3(0, 0, 1); /** * The unique {@code long} value guaranteed to never be assigned to an @@ -79,22 +79,6 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn this.velocity.set(velocity); } - public Vec2 getDirection() { - return direction; - } - - public void setDirection(Vec2 direction) { - this.direction.set(direction.x, direction.y); - } - - public float getYaw() { - return getDirection().x; - } - - public float getPitch() { - return getDirection().y; - } - public long getEntityId() { return entityId; } @@ -152,16 +136,90 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn getVelocity().add(velocityChange); } - public Vec3 getLookingAtVector(Vec3 output) { - output.set( - Math.cos(getPitch()) * Math.cos(getYaw()), - Math.cos(getPitch()) * Math.sin(getYaw()), - -Math.sin(getPitch()) - ); + public Vec3 getLookingAt() { + return lookingAt; + } + public void setLookingAt(Vec3 lookingAt) { + float lengthSq = lookingAt.x * lookingAt.x + lookingAt.y * lookingAt.y + lookingAt.z * lookingAt.z; + if (lengthSq == 1) { + this.lookingAt.set(lookingAt); + } else if (lengthSq == 0) { + throw new IllegalArgumentException("lookingAt is zero-length"); + } else { + float length = (float) Math.sqrt(lengthSq); + this.lookingAt.set( + lookingAt.x / length, + lookingAt.y / length, + lookingAt.z / length + ); + } + } + + public Vec3 getUpVector() { + return upVector; + } + + /** + * Sets this entity's up vector without updating looking at-vector. + * + * @param upVector the Vec3 to copy up vector from + * @see #changeUpVector(Vec3) + */ + public void setUpVector(Vec3 upVector) { + float lengthSq = upVector.x * upVector.x + upVector.y * upVector.y + upVector.z * upVector.z; + if (lengthSq == 1) { + this.upVector.set(upVector); + } else if (lengthSq == 0) { + throw new IllegalArgumentException("upVector is zero-length"); + } else { + float length = (float) Math.sqrt(lengthSq); + this.upVector.set( + upVector.x / length, + upVector.y / length, + upVector.z / length + ); + } + } + + /** + * Computes the forward vector of this entity. An entity's forward vector is + * defined as a normalized projection of the looking at-vector onto the + * plane perpendicular to up vector, or {@code (NaN; NaN; NaN)} if looking + * at-vector is parallel to the up vector. + * + * @param output a {@link Vec3} where the result is stored. May be + * {@code null}. + * @return the computed forward vector or {@code (NaN; NaN; NaN)} + */ + public Vec3 getForwardVector(Vec3 output) { + if (output == null) + output = new Vec3(); + output.set(getUpVector()).mul(-getUpVector().dot(getLookingAt())).add(getLookingAt()).normalize(); return output; } + public double getPitch() { + return -Math.acos(getLookingAt().dot(getUpVector())) + Math.PI / 2; + } + + /** + * Updates this entity's up vector and alters looking at-vector to match the + * rotation of the up vector. + *

+ * This method assumes that the up vector has changed due to rotation around + * some axis. The axis and the angle are computed, after which the same + * rotation is applied to the looking at-vector. + * + * @param newUpVector the Vec3 to copy up vector from. May be equal to + * current up vector + * @see #setLookingAt(Vec3) + */ + public void changeUpVector(Vec3 newUpVector) { + // TODO + this.upVector.set(newUpVector); + } + @Override public String toString() { return new StringBuilder(super.toString()) @@ -189,8 +247,13 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn output.writeFloat(getVelocity().y); output.writeFloat(getVelocity().z); - output.writeFloat(getDirection().x); - output.writeFloat(getDirection().y); + output.writeFloat(getLookingAt().x); + output.writeFloat(getLookingAt().y); + output.writeFloat(getLookingAt().z); + + output.writeFloat(getUpVector().x); + output.writeFloat(getUpVector().y); + output.writeFloat(getUpVector().z); super.write(output, context); } @@ -209,14 +272,22 @@ public class EntityData extends StatefulObject implements Collideable, GenericEn input.readFloat() ); - Vec2 direction = new Vec2( + Vec3 lookingAt = new Vec3( + input.readFloat(), + input.readFloat(), + input.readFloat() + ); + + Vec3 upVector = new Vec3( + input.readFloat(), input.readFloat(), input.readFloat() ); setPosition(position); setVelocity(velocity); - setDirection(direction); + setLookingAt(lookingAt); + setUpVector(upVector); super.read(input, context); } diff --git a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java index 38d0f32..8e34b49 100644 --- a/src/main/java/ru/windcorp/progressia/server/PlayerManager.java +++ b/src/main/java/ru/windcorp/progressia/server/PlayerManager.java @@ -22,7 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import glm.vec._2.Vec2; +import glm.vec._3.Vec3; import ru.windcorp.progressia.common.util.crash.CrashReports; import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityDataRegistry; @@ -61,12 +61,9 @@ public class PlayerManager { player.setEntityId(TestContent.PLAYER_ENTITY_ID); player.setPosition(TestContent.SPAWN); - player.setDirection( - new Vec2( - Math.toRadians(40), - Math.toRadians(10) - ) - ); + + player.setUpVector(new Vec3(0, 0, 1)); + player.setLookingAt(new Vec3(2, 1, 0)); getServer().getWorld().getData().addEntity(player); diff --git a/src/main/java/ru/windcorp/progressia/test/LayerTestUI.java b/src/main/java/ru/windcorp/progressia/test/LayerTestUI.java index c93a055..7962ca1 100755 --- a/src/main/java/ru/windcorp/progressia/test/LayerTestUI.java +++ b/src/main/java/ru/windcorp/progressia/test/LayerTestUI.java @@ -24,7 +24,6 @@ import com.google.common.eventbus.Subscribe; import glm.mat._4.Mat4; import glm.vec._4.Vec4; -import ru.windcorp.progressia.client.ClientState; import ru.windcorp.progressia.client.graphics.Colors; import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; import ru.windcorp.progressia.client.graphics.flat.AssembledFlatLayer; @@ -34,7 +33,6 @@ import ru.windcorp.progressia.client.graphics.input.bus.Input; import ru.windcorp.progressia.client.graphics.model.LambdaModel; import ru.windcorp.progressia.client.graphics.texture.SimpleTextures; import ru.windcorp.progressia.client.graphics.texture.Texture; -import ru.windcorp.progressia.client.graphics.world.Camera; public class LayerTestUI extends AssembledFlatLayer { @@ -46,9 +44,12 @@ public class LayerTestUI extends AssembledFlatLayer { private boolean flag = false; - private static final int WIDTH = 80; - private static final int HEIGHT = 80; + private static final int SCALE = 4; + private static final int TEX_SIZE = 16 * SCALE; + private static final int TEX_SHADOW = SCALE / 2; private static final int BORDER = 5; + private static final int HEIGHT = TEX_SIZE + 4 * BORDER; + private static final int WIDTH = HEIGHT; @Override protected void assemble(RenderTarget target) { @@ -64,25 +65,22 @@ public class LayerTestUI extends AssembledFlatLayer { target.fill(x, y, WIDTH, HEIGHT, borderColor); target.fill(x + BORDER, y + BORDER, WIDTH - 2 * BORDER, HEIGHT - 2 * BORDER, boxColor); - final int texShadow = 2; - final int texSize = HEIGHT - 4 * BORDER; - target.pushTransform(new Mat4().identity().translate(x + 2 * BORDER, y + 2 * BORDER, 0)); final Texture compassBg = SimpleTextures.get("compass_icon"); final Texture compassFg = SimpleTextures.get("compass_icon_arrow"); - target.drawTexture(texShadow, -texShadow, texSize, texSize, Colors.BLACK, compassBg); - target.drawTexture(0, 0, texSize, texSize, compassBg); + target.drawTexture(TEX_SHADOW, -TEX_SHADOW, TEX_SIZE, TEX_SIZE, Colors.BLACK, compassBg); + target.drawTexture(0, 0, TEX_SIZE, TEX_SIZE, compassBg); target.addCustomRenderer( new LambdaModel( LambdaModel.lambdaBuilder() .addDynamicPart( - target.createRectagle(0, 0, texSize, texSize, Colors.WHITE, compassFg), - mat -> mat.translate(texSize / 2, texSize / 2, 0) + target.createRectagle(0, 0, TEX_SIZE, TEX_SIZE, Colors.WHITE, compassFg), + mat -> mat.translate(TEX_SIZE / 2, TEX_SIZE / 2, 0) .rotateZ(getCompassRotation()) - .translate(-texSize / 2, -texSize / 2, 0) + .translate(-TEX_SIZE / 2, -TEX_SIZE / 2, 0) ) ) ); @@ -92,12 +90,7 @@ public class LayerTestUI extends AssembledFlatLayer { } private double getCompassRotation() { - Camera.Anchor anchor = ClientState.getInstance().getCamera().getAnchor(); - - if (anchor == null) - return 0; - - return -anchor.getCameraYaw(); + return 0; } private void drawCross(RenderTarget target) { diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java index 47990fd..fa595e7 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderStatie.java @@ -44,7 +44,7 @@ public class TestEntityRenderStatie extends EntityRender { public EntityRenderable createRenderable(EntityData entity) { return new EntityRenderable(entity) { @Override - public void render(ShapeRenderHelper renderer) { + public void doRender(ShapeRenderHelper renderer) { renderer.pushTransform().scale( ((TestEntityDataStatie) entity).getSize() / 24.0f ); diff --git a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java index 0a0d3d3..1254399 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java +++ b/src/main/java/ru/windcorp/progressia/test/TestPlayerControls.java @@ -20,7 +20,7 @@ package ru.windcorp.progressia.test; import glm.Glm; import glm.mat._3.Mat3; -import glm.vec._2.Vec2; +import glm.mat._4.Mat4; import glm.vec._3.Vec3; import org.lwjgl.glfw.GLFW; import ru.windcorp.progressia.client.ClientState; @@ -36,7 +36,9 @@ import ru.windcorp.progressia.client.graphics.input.bus.Input; import ru.windcorp.progressia.client.graphics.world.LocalPlayer; import ru.windcorp.progressia.client.localization.Localizer; import ru.windcorp.progressia.common.Units; -import ru.windcorp.progressia.common.util.FloatMathUtil; +import ru.windcorp.progressia.common.util.Matrices; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.block.BlockData; import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.tile.TileData; @@ -108,17 +110,17 @@ public class TestPlayerControls { authority = WALKING_CONTROL_AUTHORITY; } - Mat3 angMat = new Mat3().identity().rotateZ(player.getYaw()); - Vec3 desiredVelocity = new Vec3(movementForward, -movementRight, 0); - - if (movementForward != 0 && movementRight != 0) + Mat3 movementTransform = getMovementTransform(player, null); + Vec3 desiredVelocity = new Vec3(movementForward, movementRight, 0); + if (movementForward != 0 && movementRight != 0) { desiredVelocity.normalize(); - angMat.mul_(desiredVelocity); // bug in jglm, .mul() and mul_() are - // swapped + } desiredVelocity.z = movementUp; + movementTransform.mul_(desiredVelocity); // bug in jglm, .mul() and mul_() are + // swapped desiredVelocity.mul(speed); - Vec3 change = new Vec3() + Vec3 newVelocity = new Vec3() .set(desiredVelocity) .sub(player.getVelocity()) .mul((float) Math.exp(-authority * GraphicsInterface.getFrameLength())) @@ -126,10 +128,12 @@ public class TestPlayerControls { .add(desiredVelocity); if (!isFlying) { - change.z = player.getVelocity().z; + Vec3 up = player.getUpVector(); + Vec3 wantedVertical = VectorUtil.projectOnVector(player.getVelocity(), up, null); + VectorUtil.projectOnSurface(newVelocity, up).add(wantedVertical); } - player.getVelocity().set(change); + player.getVelocity().set(newVelocity); // THIS IS TERRIBLE TEST EntityData serverEntity = ServerState.getInstance().getWorld().getData() @@ -140,6 +144,22 @@ public class TestPlayerControls { } + private Mat3 getMovementTransform(EntityData player, Mat3 mat) { + if (mat == null) { + mat = new Mat3(); + } + + Vec3 f = player.getForwardVector(null); + Vec3 u = player.getUpVector(); + Vec3 s = u.cross_(f); + + return mat.set( + +f.x, -s.x, +u.x, + +f.y, -s.y, +u.y, + +f.z, -s.z, +u.z + ); + } + public void handleInput(Input input) { InputEvent event = input.getEvent(); @@ -318,36 +338,44 @@ public class TestPlayerControls { } private void onMouseMoved(CursorMoveEvent event) { - if (!captureMouse) + if (!captureMouse) { return; + } if (ClientState.getInstance() == null || !ClientState.getInstance().isReady()) { return; } - final float yawScale = -0.002f; - final float pitchScale = yawScale; + final double yawScale = -0.002f; + final double pitchScale = -yawScale; + final double pitchExtremum = Math.PI/2 * 0.95f; + + double yawChange = event.getChangeX() * yawScale; + double pitchChange = event.getChangeY() * pitchScale; EntityData player = getEntity(); + + double startPitch = player.getPitch(); + double endPitch = startPitch + pitchChange; + endPitch = Glm.clamp(endPitch, -pitchExtremum, +pitchExtremum); + pitchChange = endPitch - startPitch; + + Mat4 mat = Matrices.grab4(); + Vec3 lookingAt = Vectors.grab3(); + Vec3 rightVector = Vectors.grab3(); - normalizeAngles( - player.getDirection().add( - (float) (event.getChangeX() * yawScale), - (float) (event.getChangeY() * pitchScale) - ) - ); - } - - private void normalizeAngles(Vec2 dir) { - // Normalize yaw - dir.x = FloatMathUtil.normalizeAngle(dir.x); - - // Clamp pitch - dir.y = Glm.clamp( - dir.y, - -FloatMathUtil.PI_F / 2, - +FloatMathUtil.PI_F / 2 - ); + rightVector.set(player.getLookingAt()).cross(player.getUpVector()).normalize(); + + mat.identity() + .rotate((float) yawChange, player.getUpVector()) + .rotate((float) pitchChange, rightVector); + + VectorUtil.applyMat4(player.getLookingAt(), mat, lookingAt); + player.setLookingAt(lookingAt); + + Vectors.release(rightVector); + Vectors.release(lookingAt); + Matrices.release(mat); } private void onWheelScroll(WheelScrollEvent event) {