Implemented first version of collider
- Entities now can have an AABB collision model - Collisions of pairs of entities are handled properly - Added debug display of AABBs
This commit is contained in:
parent
1daec9a4df
commit
09af026137
@ -17,6 +17,8 @@
|
||||
*******************************************************************************/
|
||||
package ru.windcorp.progressia.client.graphics;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
|
||||
public class Colors {
|
||||
|
||||
public static final int
|
||||
@ -35,4 +37,16 @@ public class Colors {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Collideable> 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
|
||||
|
@ -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<BlockFace, CollisionWall> 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<CollisionWall> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package ru.windcorp.progressia.common.collision;
|
||||
|
||||
public interface CollisionClock {
|
||||
|
||||
float getTime();
|
||||
void advanceTime(float change);
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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<Mat4> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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<? extends Collideable> 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<? extends Collideable> 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<? extends Collideable> 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<? extends Collideable> 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<Collision> 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() {}
|
||||
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
||||
|
@ -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() {
|
||||
@ -71,4 +86,38 @@ public class EntityData extends StatefulObject {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
20
src/main/java/ru/windcorp/progressia/test/AABBRenderer.java
Normal file
20
src/main/java/ru/windcorp/progressia/test/AABBRenderer.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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<EntityData> transform
|
||||
) {
|
||||
EntityDataRegistry.getInstance().register(namespace, name);
|
||||
EntityDataRegistry.getInstance().register(namespace, name, new Factory<EntityData>() {
|
||||
@Override
|
||||
public EntityData build() {
|
||||
EntityData entity = new EntityData(namespace, name);
|
||||
transform.accept(entity);
|
||||
return entity;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void register(BlockRender x) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
@ -21,4 +23,9 @@ public class TestEntityDataStatie extends EntityData {
|
||||
this.size.setNow(this, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getCollisionMass() {
|
||||
return 50f;
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user