diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/Colors.java b/src/main/java/ru/windcorp/progressia/client/graphics/Colors.java index 5be4a4a..0ee371c 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/Colors.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/Colors.java @@ -17,6 +17,8 @@ *******************************************************************************/ package ru.windcorp.progressia.client.graphics; +import glm.vec._3.Vec3; + public class Colors { public static final int @@ -34,5 +36,17 @@ public class Colors { DEBUG_CYAN = 0x00FFFF, DEBUG_MAGENTA = 0xFF00FF, DEBUG_YELLOW = 0xFFFF00; + + public static Vec3 toVector(int rgb) { + return toVector(rgb, new Vec3()); + } + + public static Vec3 toVector(int rgb, Vec3 output) { + output.x = ((rgb & 0xFF0000) >> 16) / 256f; + output.y = ((rgb & 0x00FF00) >> 8 ) / 256f; + output.z = ((rgb & 0x0000FF) ) / 256f; + + return output; + } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/flat/RenderTarget.java b/src/main/java/ru/windcorp/progressia/client/graphics/flat/RenderTarget.java index 1c9737b..77e0341 100755 --- a/src/main/java/ru/windcorp/progressia/client/graphics/flat/RenderTarget.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/flat/RenderTarget.java @@ -227,7 +227,7 @@ public class RenderTarget { return Faces.createRectangle( FlatRenderProgram.getDefault(), texture, - createVectorFromRGBInt(color), + Colors.toVector(color), new Vec3(x, y, depth), new Vec3(width, 0, 0), new Vec3(0, height, 0), @@ -244,12 +244,4 @@ public class RenderTarget { ); } - private static Vec3 createVectorFromRGBInt(int rgb) { - int r = (rgb & 0xFF0000) >> 16; - int g = (rgb & 0x00FF00) >> 8; - int b = (rgb & 0x0000FF); - - return new Vec3(r / 256f, g / 256f, b / 256f); - } - } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java index 1efa6ca..09d6db4 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java @@ -17,6 +17,9 @@ *******************************************************************************/ package ru.windcorp.progressia.client.graphics.world; +import java.util.ArrayList; +import java.util.List; + import org.lwjgl.glfw.GLFW; import glm.Glm; @@ -33,14 +36,18 @@ import ru.windcorp.progressia.client.graphics.input.CursorMoveEvent; import ru.windcorp.progressia.client.graphics.input.InputEvent; import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.bus.Input; +import ru.windcorp.progressia.common.collision.AABB; +import ru.windcorp.progressia.common.collision.Collideable; +import ru.windcorp.progressia.common.collision.CollisionClock; +import ru.windcorp.progressia.common.collision.CollisionModel; +import ru.windcorp.progressia.common.collision.colliders.Collider; import ru.windcorp.progressia.common.util.FloatMathUtils; import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.world.entity.EntityData; +import ru.windcorp.progressia.test.AABBRenderer; public class LayerWorld extends Layer { - private final Vec3 velocity = new Vec3(); - private final Mat3 angMat = new Mat3(); private int movementForward = 0; @@ -98,20 +105,11 @@ public class LayerWorld extends Layer { angMat.mul_(movement); // bug in jglm, .mul() and mul_() are swapped movement.z = movementUp; movement.mul(movementSpeed); - movement.sub(velocity); + movement.sub(player.getVelocity()); movement.mul(controlAuthority); - velocity.add(movement); + player.getVelocity().add(movement); Vectors.release(movement); - - Vec3 velCopy = Vectors.grab3().set(velocity); - - velCopy.mul((float) GraphicsInterface.getFrameLength()); - - player.move(velCopy); - player.getVelocity().set(velocity); - - Vectors.release(velCopy); } private void renderWorld() { @@ -126,8 +124,51 @@ public class LayerWorld extends Layer { helper.reset(); } + private final Collider.ColliderWorkspace tmp_colliderWorkspace = new Collider.ColliderWorkspace(); + private final List tmp_collideableList = new ArrayList<>(); + + private static final boolean RENDER_AABBS = false; + private void tmp_doEveryFrame() { - // Stub for the future + try { + if (RENDER_AABBS) { + for (EntityData data : this.client.getWorld().getData().getEntities()) { + CollisionModel model = data.getCollisionModel(); + if (model instanceof AABB) { + AABBRenderer.renderAABB((AABB) model, helper); + } + } + } + + tmp_collideableList.clear(); + tmp_collideableList.addAll(this.client.getWorld().getData().getEntities()); + + Collider.performCollisions( + tmp_collideableList, + new CollisionClock() { + private float t = 0; + @Override + public float getTime() { + return t; + } + + @Override + public void advanceTime(float change) { + t += change; + } + }, + (float) GraphicsInterface.getFrameLength(), + tmp_colliderWorkspace + ); + + final float frictionCoeff = 1 - 1e-2f; + + for (EntityData data : this.client.getWorld().getData().getEntities()) { + data.getVelocity().mul(frictionCoeff); + } + } catch (Exception e) { + System.exit(31337); + } } @Override diff --git a/src/main/java/ru/windcorp/progressia/common/collision/AABB.java b/src/main/java/ru/windcorp/progressia/common/collision/AABB.java new file mode 100644 index 0000000..7188e33 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/AABB.java @@ -0,0 +1,93 @@ +package ru.windcorp.progressia.common.collision; + +import java.util.Collection; +import java.util.Map; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.world.block.BlockFace; + +public class AABB implements CollisionModel { + + private final Map faces = BlockFace.mapToFaces( + new CollisionWall(-0.5f, -0.5f, -0.5f, +1, 0, 0, 0, 0, +1), + new CollisionWall(+0.5f, -0.5f, -0.5f, 0, +1, 0, 0, 0, +1), + new CollisionWall(+0.5f, +0.5f, -0.5f, -1, 0, 0, 0, 0, +1), + new CollisionWall(-0.5f, +0.5f, -0.5f, 0, -1, 0, 0, 0, +1), + + new CollisionWall(-0.5f, -0.5f, +0.5f, +1, 0, 0, 0, +1, 0), + new CollisionWall(-0.5f, -0.5f, -0.5f, 0, +1, 0, +1, 0, 0) + ); + + private final Vec3 origin = new Vec3(); + private final Vec3 size = new Vec3(); + + public AABB(Vec3 origin, Vec3 size) { + this.origin.set(origin); + this.size.set(size); + + for (CollisionWall wall : getFaces()) { + wall.moveOrigin(origin); + wall.getWidth().mul(size); + wall.getHeight().mul(size); + } + } + + public AABB( + float ox, float oy, float oz, + float xSize, float ySize, float zSize + ) { + this.origin.set(ox, oy, oz); + this.size.set(xSize, ySize, zSize); + + for (CollisionWall wall : getFaces()) { + wall.moveOrigin(ox, oy, oz); + wall.getWidth().mul(xSize, ySize, zSize); + wall.getHeight().mul(xSize, ySize, zSize); + } + } + + public Collection getFaces() { + return faces.values(); + } + + public Vec3 getOrigin() { + return origin; + } + + @Override + public void setOrigin(Vec3 origin) { + for (CollisionWall wall : getFaces()) { + wall.getOrigin().sub(this.origin).add(origin); + } + + this.origin.set(origin); + } + + @Override + public void moveOrigin(Vec3 displacement) { + for (CollisionWall wall : getFaces()) { + wall.getOrigin().add(displacement); + } + + this.origin.add(displacement); + } + + public Vec3 getSize() { + return size; + } + + public void setSize(Vec3 size) { + setSize(size.x, size.y, size.z); + } + + public void setSize(float xSize, float ySize, float zSize) { + for (CollisionWall wall : getFaces()) { + wall.getWidth().div(this.size).mul(xSize, ySize, zSize); + wall.getHeight().div(this.size).mul(xSize, ySize, zSize); + wall.getOrigin().sub(getOrigin()).div(this.size).mul(xSize, ySize, zSize).add(getOrigin()); + } + + this.size.set(xSize, ySize, zSize); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/Collideable.java b/src/main/java/ru/windcorp/progressia/common/collision/Collideable.java new file mode 100644 index 0000000..9b6d5e6 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/Collideable.java @@ -0,0 +1,30 @@ +package ru.windcorp.progressia.common.collision; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.collision.colliders.Collider; + +public interface Collideable { + + CollisionModel getCollisionModel(); + + /** + * Invoked by {@link Collider} when two entities are about to collide. + * The world is at the moment of collision. + * @param other the colliding object + * @return {@code true} iff the collision should not be handled normally (e.g. this object has disappeared) + */ + boolean onCollision(Collideable other); + + /** + * Returns the mass of this {@link Collideable} that should be used to calculate collisions. + * Collision mass must be a positive number. Positive infinity is allowed. + * @return this object's collision mass + */ + float getCollisionMass(); + + void moveAsCollideable(Vec3 displacement); + + void getCollideableVelocity(Vec3 output); + void changeVelocityOnCollision(Vec3 velocityChange); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/CollisionClock.java b/src/main/java/ru/windcorp/progressia/common/collision/CollisionClock.java new file mode 100644 index 0000000..cf42614 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/CollisionClock.java @@ -0,0 +1,8 @@ +package ru.windcorp.progressia.common.collision; + +public interface CollisionClock { + + float getTime(); + void advanceTime(float change); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/CollisionModel.java b/src/main/java/ru/windcorp/progressia/common/collision/CollisionModel.java new file mode 100644 index 0000000..39f7ab7 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/CollisionModel.java @@ -0,0 +1,10 @@ +package ru.windcorp.progressia.common.collision; + +import glm.vec._3.Vec3; + +public interface CollisionModel { + + public void setOrigin(Vec3 origin); + public void moveOrigin(Vec3 displacement); + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/CollisionWall.java b/src/main/java/ru/windcorp/progressia/common/collision/CollisionWall.java new file mode 100644 index 0000000..8963ffd --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/CollisionWall.java @@ -0,0 +1,55 @@ +package ru.windcorp.progressia.common.collision; + +import glm.vec._3.Vec3; + +public class CollisionWall { + + private final Vec3 origin = new Vec3(); + private final Vec3 width = new Vec3(); + private final Vec3 height = new Vec3(); + + public CollisionWall(Vec3 origin, Vec3 width, Vec3 height) { + this.origin.set(origin); + this.width.set(width); + this.height.set(height); + } + + public CollisionWall( + float ox, float oy, float oz, + float wx, float wy, float wz, + float hx, float hy, float hz + ) { + this.origin.set(ox, oy, oz); + this.width.set(wx, wy, wz); + this.height.set(hx, hy, hz); + } + + public Vec3 getOrigin() { + return origin; + } + + public Vec3 getWidth() { + return width; + } + + public Vec3 getHeight() { + return height; + } + + public void setOrigin(Vec3 origin) { + setOrigin(origin.x, origin.y, origin.z); + } + + public void setOrigin(float x, float y, float z) { + this.origin.set(x, y, z); + } + + public void moveOrigin(Vec3 displacement) { + moveOrigin(displacement.x, displacement.y, displacement.z); + } + + public void moveOrigin(float dx, float dy, float dz) { + this.origin.add(dx, dy, dz); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/TransformedCollisionModel.java b/src/main/java/ru/windcorp/progressia/common/collision/TransformedCollisionModel.java new file mode 100644 index 0000000..13681b0 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/TransformedCollisionModel.java @@ -0,0 +1,53 @@ +package ru.windcorp.progressia.common.collision; + +import java.util.function.Consumer; + +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.util.Vectors; + +public class TransformedCollisionModel implements CollisionModel { + + private final CollisionModel parent; + + private final Mat4 transform = new Mat4(); + private final Mat4 inverseTransform = new Mat4(); + + public TransformedCollisionModel(CollisionModel parent, Mat4 transform) { + this.parent = parent; + setTransform(transform); + } + + public TransformedCollisionModel(CollisionModel parent, Consumer transform) { + this.parent = parent; + transform.accept(this.transform); + this.inverseTransform.set(this.transform).inverse(); + } + + public Mat4 getTransform() { + return transform; + } + + public void setTransform(Mat4 newTransform) { + this.transform.set(newTransform); + this.inverseTransform.set(newTransform).inverse(); + } + + @Override + public void setOrigin(Vec3 origin) { + Vec3 vec3 = Vectors.grab3(); + VectorUtil.applyMat4(origin, inverseTransform, vec3); + this.parent.setOrigin(vec3); + Vectors.release(vec3); + } + + @Override + public void moveOrigin(Vec3 displacement) { + Vec3 vec3 = Vectors.grab3(); + VectorUtil.applyMat4(displacement, inverseTransform, vec3); + this.parent.moveOrigin(vec3); + Vectors.release(vec3); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBWithAABBCollider.java b/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBWithAABBCollider.java new file mode 100644 index 0000000..7b961e1 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/colliders/AABBWithAABBCollider.java @@ -0,0 +1,198 @@ +package ru.windcorp.progressia.common.collision.colliders; + +import glm.mat._3.Mat3; +import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.collision.AABB; +import ru.windcorp.progressia.common.collision.Collideable; +import ru.windcorp.progressia.common.collision.CollisionWall; +import ru.windcorp.progressia.common.collision.colliders.Collider.ColliderWorkspace; +import ru.windcorp.progressia.common.collision.colliders.Collider.Collision; +import ru.windcorp.progressia.common.util.Matrices; +import ru.windcorp.progressia.common.util.Vectors; + +class AABBWithAABBCollider { + + static Collider.Collision computeModelCollision( + Collideable aBody, Collideable bBody, + AABB aModel, AABB bModel, + float tickLength, + ColliderWorkspace workspace + ) { + Collideable obstacleBody = bBody; + Collideable colliderBody = aBody; + AABB obstacleModel = bModel; + AABB colliderModel = aModel; + + Collision result = null; + + AABB originCollisionSpace = + createOriginCollisionSpace(obstacleModel, colliderModel, workspace.dummyAABB); + + Vec3 collisionVelocity = Vectors.grab3(); + computeCollisionVelocity(collisionVelocity, obstacleBody, colliderBody); + + // For every wall of collision space + for (CollisionWall wall : originCollisionSpace.getFaces()) { + + Collision collision = computeWallCollision( + wall, colliderModel, + collisionVelocity, + tickLength, workspace, + aBody, bBody + ); + + // Update result + if (collision != null) { + Collision second; + + if (result == null || collision.time < result.time) { + second = result; + result = collision; + } else { + second = collision; + } + + // Release Collision that is no longer used + if (second != null) workspace.release(second); + } + + } + + Vectors.release(collisionVelocity); + + return result; + } + + private static void computeCollisionVelocity( + Vec3 output, + Collideable obstacleBody, + Collideable colliderBody + ) { + Vec3 obstacleVelocity = Vectors.grab3(); + Vec3 colliderVelocity = Vectors.grab3(); + + obstacleBody.getCollideableVelocity(obstacleVelocity); + colliderBody.getCollideableVelocity(colliderVelocity); + + output.set(colliderVelocity).sub(obstacleVelocity); + + Vectors.release(obstacleVelocity); + Vectors.release(colliderVelocity); + } + + private static AABB createOriginCollisionSpace(AABB obstacle, AABB collider, AABB output) { + output.setOrigin(obstacle.getOrigin()); + + Vec3 size = Vectors.grab3().set(obstacle.getSize()).add(collider.getSize()); + output.setSize(size); + Vectors.release(size); + + return output; + } + + /* + * Here we determine whether a collision has actually happened, and if it did, at what moment. + * + * The basic idea is to compute the moment of collision and impact coordinates in wall coordinate space. + * Then, we can check impact coordinates to determine if we actually hit the wall or flew by and then + * check time to make sure the collision is not too far in the future and not in the past. + * + * DETAILED EXPLANATION: + * + * Consider a surface defined by an origin r_wall and two noncollinear nonzero vectors w and h. + * Consider a line defined by an origin r_line and a nonzero vector v. + * + * Then, a collision occurs if there exist x, y and t such that + * ______ _ + * r_line + v * t + * + * and + * ______ _ _ + * r_wall + w * x + h * y + * + * describe the same location (indeed, this corresponds to a collision at moment t0 + t + * with a point on the wall with coordinates (x; y) in (w; h) coordinate system). + * + * Therefore, + * ______ _ ______ _ _ + * r_line + v*t = r_wall + w*x + h*y; + * + * _ ⎡w_x h_x -v_x⎤ ⎡x⎤ _ ______ ______ + * r = ⎢w_y h_y -v_y⎥ * ⎢y⎥, where r = r_line - r_wall; + * ⎣w_z h_z -v_z⎦ ⎣t⎦ + * + * ⎡x⎤ ⎡w_x h_x -v_x⎤ -1 _ + * ⎢y⎥ = ⎢w_y h_y -v_y⎥ * r, if the matrix is invertible. + * ⎣t⎦ ⎣w_z h_z -v_z⎦ + * + * Then, one only needs to ensure that: + * 0 < x < 1, + * 0 < y < 1, and + * 0 < t < T, where T is remaining tick time. + * + * If the matrix is not invertible or any of the conditions are not met, no collision happened. + * If all conditions are satisfied, then the moment of impact is t0 + t. + */ + private static Collision computeWallCollision( + CollisionWall obstacleWall, + AABB colliderModel, + Vec3 collisionVelocity, + float tickLength, ColliderWorkspace workspace, + Collideable aBody, Collideable bBody + ) { + Vec3 w = obstacleWall.getWidth(); + Vec3 h = obstacleWall.getHeight(); + Vec3 v = Vectors.grab3(); + Mat3 m = Matrices.grab3(); // The matrix [w h -v] + Vec3 r = Vectors.grab3(); + Vec3 xyt = Vectors.grab3(); + + try { + v.set(collisionVelocity); + + if (isExiting(v, w, h)) { + return null; + } + + r.set(colliderModel.getOrigin()).sub(obstacleWall.getOrigin()); + m.c0(w).c1(h).c2(v.negate()); + + if (Math.abs(m.det()) < 1e-6) { + return null; + } + + m.inverse().mul(r, xyt); + + float x = xyt.x; + float y = xyt.y; + float t = xyt.z; + + if (x <= 0 || x >= 1 || y <= 0 || y >= 1) { + // We're missing the wall + return null; + } + + if (t < 0 || t > tickLength) { + // We're colliding at the wrong moment + return null; + } + + return workspace.grab().set(aBody, bBody, obstacleWall, t); + } finally { + Vectors.release(v); + Vectors.release(r); + Matrices.release(m); + Vectors.release(xyt); + } + } + + private static boolean isExiting(Vec3 v, Vec3 w, Vec3 h) { + Vec3 tmp = Vectors.grab3().set(w).cross(h); + boolean result = Vec3.dot(tmp, v) >= 0; + Vectors.release(tmp); + return result; + } + + private AABBWithAABBCollider() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java b/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java new file mode 100644 index 0000000..4c040ba --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/collision/colliders/Collider.java @@ -0,0 +1,338 @@ +package ru.windcorp.progressia.common.collision.colliders; + +import java.util.Collection; +import java.util.List; + +import org.apache.logging.log4j.LogManager; + +import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.collision.AABB; +import ru.windcorp.progressia.common.collision.Collideable; +import ru.windcorp.progressia.common.collision.CollisionClock; +import ru.windcorp.progressia.common.collision.CollisionModel; +import ru.windcorp.progressia.common.collision.CollisionWall; +import ru.windcorp.progressia.common.util.LowOverheadCache; +import ru.windcorp.progressia.common.util.Vectors; + +public class Collider { + + private static final int MAX_COLLISIONS_PER_ENTITY = 64; + + public static void performCollisions( + List colls, + CollisionClock clock, + float tickLength, + ColliderWorkspace workspace + ) { + int collisionCount = 0; + int maxCollisions = colls.size() * MAX_COLLISIONS_PER_ENTITY; + + while (true) { + if (collisionCount > maxCollisions) { + LogManager.getLogger().warn( + "Attempted to handle more than {} collisions", + maxCollisions + ); + return; + } + + Collision firstCollision = getFirstCollision(colls, tickLength, workspace); + + if (firstCollision == null) { + break; + } else { + collide(firstCollision, colls, clock, tickLength, workspace); + workspace.release(firstCollision); + collisionCount++; + + tickLength -= firstCollision.time; + } + } + + advanceTime(colls, clock, tickLength); + } + + private static Collision getFirstCollision( + List colls, + float tickLength, + ColliderWorkspace workspace + ) { + Collision result = null; + + // For every pair of colls + for (int i = 0; i < colls.size(); ++i) { + Collideable a = colls.get(i); + + for (int j = i + 1; j < colls.size(); ++j) { + Collideable b = colls.get(j); + + Collision collision = getCollision(a, b, tickLength, workspace); + + // Update result + if (collision != null) { + Collision second; + + if (result == null || collision.time < result.time) { + second = result; + result = collision; + } else { + second = collision; + } + + // Release Collision that is no longer used + if (second != null) workspace.release(second); + } + } + } + + return result; + } + + private static Collision getCollision( + Collideable a, + Collideable b, + float tickLength, + ColliderWorkspace workspace + ) { + CollisionModel aModel = a.getCollisionModel(); + CollisionModel bModel = b.getCollisionModel(); + + if (aModel instanceof AABB && bModel instanceof AABB) { + return AABBWithAABBCollider.computeModelCollision( + a, b, + (AABB) aModel, (AABB) bModel, + tickLength, + workspace + ); + } + + throw new UnsupportedOperationException( + "Collisions between " + aModel + " and " + bModel + " are not yet implemented" + ); + } + + private static void collide( + Collision collision, + + Collection colls, + CollisionClock clock, + float tickLength, + ColliderWorkspace workspace + ) { + advanceTime(colls, clock, collision.time); + + boolean doNotHandle = false; + + doNotHandle |= collision.a.onCollision(collision.b); + doNotHandle |= collision.b.onCollision(collision.a); + + if (doNotHandle) { + return; + } + + handlePhysics(collision); + } + + /* + * Here we compute the change in body velocities due to a collision. + * + * We make the following simplifications: + * 1) The bodies are perfectly rigid; + * 2) The collision is perfectly inelastic + * (no bouncing); + * 3) The bodies are spherical; + * 4) No tangential friction exists + * (bodies do not experience friction when sliding against each other); + * 5) Velocities are not relativistic. + * + * Angular momentum is ignored per 3) and 4), + * e.g. when something pushes an end of a long stick, the stick does not rotate. + * + * DETAILED EXPLANATION: + * + * Two spherical (sic) bodies, a and b, experience a perfectly inelastic collision + * along a unit vector + * _ _ _ _ _ + * n = (w ⨯ h) / (|w ⨯ h|), + * _ _ + * where w and h are two noncollinear nonzero vectors on the dividing plane. + * ___ ___ + * Body masses and velocities are M_a, M_b and v_a, v_b, respectively. + * ___ ___ + * After the collision desired velocities are u_a and u_b, respectively. + * _ + * (Notation convention: suffix 'n' denotes a vector projection onto vector n, + * and suffix 't' denotes a vector projection onto the dividing plane.) + * + * Consider the law of conservation of momentum for axis n and the dividing plane: + * ____________ ____________ ________________ + * n: ⎧ p_a_before_n + p_b_before_n = p_common_after_n; + * ⎨ ___________ ____________ + * t: ⎩ p_i_after_t = p_i_before_t for any i in {a, b}. + * + * Expressing all p_* in given terms: + * ___ _ ___ _ ___ ___ ____ ____ + * n: ⎧ M_a * (v_a ⋅ n) + M_b * (v_b ⋅ n) = (M_a + M_b) * u_n, where u_n ≡ u_an = u_bn; + * ⎨ ____ ___ _ ___ _ + * t: ⎩ u_it = v_i - n * (v_i ⋅ n) for any i in {a, b}. + * + * Therefore: + * ___ _ ___ _ ___ _ + * u_n = n * ( M_a/(M_a + M_b) * v_a ⋅ n + M_b/(M_a + M_b) * v_b ⋅ n ); + * + * or, equivalently, + * ___ _ ___ _ ___ _ + * u_n = n * ( m_a * v_a ⋅ n + m_b * v_b ⋅ n ), + * + * where m_a and m_b are relative masses (see below). + * + * Finally, + * ___ ____ ___ + * u_i = u_it + u_n for any i in {a, b}. + * + * The usage of relative masses m_i permits a convenient generalization of the algorithm + * for infinite masses, signifying masses "significantly greater" than finite masses: + * + * 1) If both M_a and M_b are finite, let m_i = M_i / (M_a + M_b) for any i in {a, b}. + * 2) If M_i is finite but M_j is infinite, let m_i = 0 and m_j = 1. + * 3) If both M_a and M_b are infinite, let m_i = 1/2 for any i in {a, b}. + */ + private static void handlePhysics(Collision collision) { + // Fuck JGLM + Vec3 n = Vectors.grab3(); + Vec3 v_a = Vectors.grab3(); + Vec3 v_b = Vectors.grab3(); + Vec3 u_n = Vectors.grab3(); + Vec3 u_at = Vectors.grab3(); + Vec3 u_bt = Vectors.grab3(); + Vec3 du_a = Vectors.grab3(); + Vec3 du_b = Vectors.grab3(); + + n.set(collision.wall.getWidth()).cross(collision.wall.getHeight()).normalize(); + collision.a.getCollideableVelocity(v_a); + collision.b.getCollideableVelocity(v_b); + + float M_a = collision.a.getCollisionMass(); + float M_b = collision.b.getCollisionMass(); + + float m_a, m_b; + + if (Float.isFinite(M_a)) { + if (Float.isFinite(M_b)) { + float sum = M_a + M_b; + m_a = M_a / sum; + m_b = M_b / sum; + } else { + m_a = 0; + m_b = 1; + } + } else { + if (Float.isFinite(M_b)) { + m_a = 1; + m_b = 0; + } else { + m_a = 0.5f; + m_b = 0.5f; + } + } + + u_n.set(n).mul(m_a * Vec3.dot(v_a, n) + m_b * Vec3.dot(v_b, n)); + u_at.set(n).mul(Vec3.dot(v_a, n)).negate().add(v_a); + u_bt.set(n).mul(Vec3.dot(v_b, n)).negate().add(v_b); + du_a.set(u_n).add(u_at).sub(v_a); + du_b.set(u_n).add(u_bt).sub(v_b); + + collision.a.changeVelocityOnCollision(du_a); + collision.b.changeVelocityOnCollision(du_b); + + separate(collision, n, m_a, m_b); + + // JGML is still to fuck + Vectors.release(n); + Vectors.release(v_a); + Vectors.release(v_b); + Vectors.release(u_n); + Vectors.release(u_at); + Vectors.release(u_bt); + Vectors.release(du_a); + Vectors.release(du_b); + } + + private static void separate( + Collision collision, + Vec3 normal, float aRelativeMass, float bRelativeMass + ) { + final float margin = 1e-4f; + + Vec3 displacement = Vectors.grab3(); + + displacement.set(normal).mul(margin).mul(bRelativeMass); + collision.a.moveAsCollideable(displacement); + + displacement.set(normal).mul(margin).mul(aRelativeMass).negate(); + collision.b.moveAsCollideable(displacement); + + Vectors.release(displacement); + } + + private static void advanceTime( + Collection colls, + CollisionClock clock, + float step + ) { + clock.advanceTime(step); + + Vec3 tmp = Vectors.grab3(); + + for (Collideable coll : colls) { + coll.getCollideableVelocity(tmp); + tmp.mul(step); + coll.moveAsCollideable(tmp); + } + + Vectors.release(tmp); + } + + public static class ColliderWorkspace { + + private final LowOverheadCache collisionCache = + new LowOverheadCache<>(Collision::new); + + AABB dummyAABB = new AABB(0, 0, 0, 1, 1, 1); + + Collision grab() { + return collisionCache.grab(); + } + + void release(Collision object) { + collisionCache.release(object); + } + + } + + static class Collision { + public Collideable a; + public Collideable b; + public final CollisionWall wall = new CollisionWall(0, 0, 0, 0, 0, 0, 0, 0, 0); + + /** + * Time offset from the start of the tick. + * 0 means right now, tickLength means at the end of the tick. + */ + public float time; + + public Collision set(Collideable a, Collideable b, CollisionWall wall, float time) { + this.a = a; + this.b = b; + this.wall.getOrigin().set(wall.getOrigin()); + this.wall.getWidth().set(wall.getWidth()); + this.wall.getHeight().set(wall.getHeight()); + this.time = time; + + return this; + } + } + + private Collider() {} + +} 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 d84e2f1..712a9cf 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java +++ b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java @@ -2,7 +2,10 @@ package ru.windcorp.progressia.common.util; import java.util.function.Consumer; +import glm.mat._4.Mat4; +import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; +import glm.vec._4.Vec4; public class VectorUtil { @@ -25,6 +28,25 @@ public class VectorUtil { Vectors.release(cursor); } + public static void applyMat4(Vec3 in, Mat4 mat, Vec3 out) { + Vec4 vec4 = Vectors.grab4(); + vec4.set(in, 1f); + + mat.mul(vec4); + + out.set(vec4.x, vec4.y, vec4.z); + Vectors.release(vec4); + } + + 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); + } + private VectorUtil() {} } 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 1c8e7e8..0b3b73c 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 @@ -2,9 +2,12 @@ package ru.windcorp.progressia.common.world.entity; import glm.vec._2.Vec2; import glm.vec._3.Vec3; +import ru.windcorp.progressia.common.collision.Collideable; +import ru.windcorp.progressia.common.collision.CollisionModel; import ru.windcorp.progressia.common.state.StatefulObject; +import ru.windcorp.progressia.common.util.Vectors; -public class EntityData extends StatefulObject { +public class EntityData extends StatefulObject implements Collideable { private final Vec3 position = new Vec3(); private final Vec3 velocity = new Vec3(); @@ -13,6 +16,8 @@ public class EntityData extends StatefulObject { private long entityId; + private CollisionModel collisionModel = null; + private double age = 0; public EntityData(String namespace, String name) { @@ -24,7 +29,17 @@ public class EntityData extends StatefulObject { } public void setPosition(Vec3 position) { - this.position.set(position); + Vec3 displacement = Vectors.grab3(); + displacement.set(position).sub(getPosition()); + move(displacement); + Vectors.release(displacement); + } + + public void move(Vec3 displacement) { + this.position.add(displacement); + if (getCollisionModel() != null) { + getCollisionModel().moveOrigin(displacement); + } } public Vec3 getVelocity() { @@ -70,5 +85,39 @@ public class EntityData extends StatefulObject { public void incrementAge(double increment) { this.age += increment; } + + @Override + public CollisionModel getCollisionModel() { + return collisionModel; + } + + public void setCollisionModel(CollisionModel collisionModel) { + this.collisionModel = collisionModel; + } + + @Override + public boolean onCollision(Collideable other) { + return false; + } + + @Override + public float getCollisionMass() { + return 1.0f; + } + + @Override + public void moveAsCollideable(Vec3 displacement) { + move(displacement); + } + + @Override + public void getCollideableVelocity(Vec3 output) { + output.set(getVelocity()); + } + + @Override + public void changeVelocityOnCollision(Vec3 velocityChange) { + getVelocity().add(velocityChange); + } } diff --git a/src/main/java/ru/windcorp/progressia/test/AABBRenderer.java b/src/main/java/ru/windcorp/progressia/test/AABBRenderer.java new file mode 100644 index 0000000..0afbb09 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/AABBRenderer.java @@ -0,0 +1,20 @@ +package ru.windcorp.progressia.test; + +import ru.windcorp.progressia.client.graphics.model.Shape; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.progressia.client.graphics.model.Shapes; +import ru.windcorp.progressia.client.graphics.texture.Texture; +import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; +import ru.windcorp.progressia.common.collision.AABB; + +public class AABBRenderer { + + private static final Shape CUBE = new Shapes.PppBuilder(WorldRenderProgram.getDefault(), (Texture) null).setColorMultiplier(1.0f, 0.7f, 0.2f).create(); + + public static void renderAABB(AABB aabb, ShapeRenderHelper helper) { + helper.pushTransform().translate(aabb.getOrigin()).scale(aabb.getSize()); + CUBE.render(helper); + helper.popTransform(); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index 3463525..3c890bc 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -3,6 +3,8 @@ package ru.windcorp.progressia.test; import static ru.windcorp.progressia.client.world.block.BlockRenderRegistry.getBlockTexture; import static ru.windcorp.progressia.client.world.tile.TileRenderRegistry.getTileTexture; +import java.util.function.Consumer; + import org.lwjgl.glfw.GLFW; import glm.vec._3.i.Vec3i; @@ -11,6 +13,7 @@ import ru.windcorp.progressia.client.graphics.input.KeyMatcher; import ru.windcorp.progressia.client.world.block.*; import ru.windcorp.progressia.client.world.entity.*; import ru.windcorp.progressia.client.world.tile.*; +import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.comms.controls.*; import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory; import ru.windcorp.progressia.common.world.ChunkData; @@ -78,7 +81,7 @@ public class TestContent { } private static void registerEntities() { - registerEntityData("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")); @@ -125,9 +128,17 @@ public class TestContent { } private static void registerEntityData( - String namespace, String name + String namespace, String name, + Consumer transform ) { - EntityDataRegistry.getInstance().register(namespace, name); + EntityDataRegistry.getInstance().register(namespace, name, new Factory() { + @Override + public EntityData build() { + EntityData entity = new EntityData(namespace, name); + transform.accept(entity); + return entity; + } + }); } private static void register(BlockRender x) { diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityDataStatie.java b/src/main/java/ru/windcorp/progressia/test/TestEntityDataStatie.java index 2ff45c8..fb29df1 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestEntityDataStatie.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityDataStatie.java @@ -1,5 +1,6 @@ package ru.windcorp.progressia.test; +import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.state.IntStateField; import ru.windcorp.progressia.common.world.entity.EntityData; @@ -10,6 +11,7 @@ public class TestEntityDataStatie extends EntityData { public TestEntityDataStatie() { super("Test", "Statie"); + setCollisionModel(new AABB(0, 0, 0, 16f/24, 16f/24, 16f/24)); setSizeNow(16); } @@ -20,5 +22,10 @@ public class TestEntityDataStatie extends EntityData { public void setSizeNow(int size) { this.size.setNow(this, size); } + + @Override + public float getCollisionMass() { + return 50f; + } }