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:
OLEGSHA 2020-11-08 18:26:33 +03:00
parent 1daec9a4df
commit 09af026137
16 changed files with 969 additions and 28 deletions

View File

@ -17,6 +17,8 @@
*******************************************************************************/ *******************************************************************************/
package ru.windcorp.progressia.client.graphics; package ru.windcorp.progressia.client.graphics;
import glm.vec._3.Vec3;
public class Colors { public class Colors {
public static final int public static final int
@ -34,5 +36,17 @@ public class Colors {
DEBUG_CYAN = 0x00FFFF, DEBUG_CYAN = 0x00FFFF,
DEBUG_MAGENTA = 0xFF00FF, DEBUG_MAGENTA = 0xFF00FF,
DEBUG_YELLOW = 0xFFFF00; 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;
}
} }

View File

@ -227,7 +227,7 @@ public class RenderTarget {
return Faces.createRectangle( return Faces.createRectangle(
FlatRenderProgram.getDefault(), FlatRenderProgram.getDefault(),
texture, texture,
createVectorFromRGBInt(color), Colors.toVector(color),
new Vec3(x, y, depth), new Vec3(x, y, depth),
new Vec3(width, 0, 0), new Vec3(width, 0, 0),
new Vec3(0, height, 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);
}
} }

View File

@ -17,6 +17,9 @@
*******************************************************************************/ *******************************************************************************/
package ru.windcorp.progressia.client.graphics.world; package ru.windcorp.progressia.client.graphics.world;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import glm.Glm; 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.InputEvent;
import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input; 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.FloatMathUtils;
import ru.windcorp.progressia.common.util.Vectors; import ru.windcorp.progressia.common.util.Vectors;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
import ru.windcorp.progressia.test.AABBRenderer;
public class LayerWorld extends Layer { public class LayerWorld extends Layer {
private final Vec3 velocity = new Vec3();
private final Mat3 angMat = new Mat3(); private final Mat3 angMat = new Mat3();
private int movementForward = 0; private int movementForward = 0;
@ -98,20 +105,11 @@ public class LayerWorld extends Layer {
angMat.mul_(movement); // bug in jglm, .mul() and mul_() are swapped angMat.mul_(movement); // bug in jglm, .mul() and mul_() are swapped
movement.z = movementUp; movement.z = movementUp;
movement.mul(movementSpeed); movement.mul(movementSpeed);
movement.sub(velocity); movement.sub(player.getVelocity());
movement.mul(controlAuthority); movement.mul(controlAuthority);
velocity.add(movement); player.getVelocity().add(movement);
Vectors.release(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() { private void renderWorld() {
@ -126,8 +124,51 @@ public class LayerWorld extends Layer {
helper.reset(); 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() { 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 @Override

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -0,0 +1,8 @@
package ru.windcorp.progressia.common.collision;
public interface CollisionClock {
float getTime();
void advanceTime(float change);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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() {}
}

View File

@ -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() {}
}

View File

@ -2,7 +2,10 @@ package ru.windcorp.progressia.common.util;
import java.util.function.Consumer; import java.util.function.Consumer;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i; import glm.vec._3.i.Vec3i;
import glm.vec._4.Vec4;
public class VectorUtil { public class VectorUtil {
@ -25,6 +28,25 @@ public class VectorUtil {
Vectors.release(cursor); 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() {} private VectorUtil() {}
} }

View File

@ -2,9 +2,12 @@ package ru.windcorp.progressia.common.world.entity;
import glm.vec._2.Vec2; import glm.vec._2.Vec2;
import glm.vec._3.Vec3; 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.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 position = new Vec3();
private final Vec3 velocity = new Vec3(); private final Vec3 velocity = new Vec3();
@ -13,6 +16,8 @@ public class EntityData extends StatefulObject {
private long entityId; private long entityId;
private CollisionModel collisionModel = null;
private double age = 0; private double age = 0;
public EntityData(String namespace, String name) { public EntityData(String namespace, String name) {
@ -24,7 +29,17 @@ public class EntityData extends StatefulObject {
} }
public void setPosition(Vec3 position) { 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() { public Vec3 getVelocity() {
@ -70,5 +85,39 @@ public class EntityData extends StatefulObject {
public void incrementAge(double increment) { public void incrementAge(double increment) {
this.age += 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);
}
} }

View 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();
}
}

View File

@ -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.block.BlockRenderRegistry.getBlockTexture;
import static ru.windcorp.progressia.client.world.tile.TileRenderRegistry.getTileTexture; import static ru.windcorp.progressia.client.world.tile.TileRenderRegistry.getTileTexture;
import java.util.function.Consumer;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import glm.vec._3.i.Vec3i; 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.block.*;
import ru.windcorp.progressia.client.world.entity.*; import ru.windcorp.progressia.client.world.entity.*;
import ru.windcorp.progressia.client.world.tile.*; 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.comms.controls.*;
import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory; import ru.windcorp.progressia.common.state.StatefulObjectRegistry.Factory;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
@ -78,7 +81,7 @@ public class TestContent {
} }
private static void registerEntities() { 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 TestEntityRenderJavapony());
register(new EntityLogic("Test", "Javapony")); register(new EntityLogic("Test", "Javapony"));
@ -125,9 +128,17 @@ public class TestContent {
} }
private static void registerEntityData( 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) { private static void register(BlockRender x) {

View File

@ -1,5 +1,6 @@
package ru.windcorp.progressia.test; package ru.windcorp.progressia.test;
import ru.windcorp.progressia.common.collision.AABB;
import ru.windcorp.progressia.common.state.IntStateField; import ru.windcorp.progressia.common.state.IntStateField;
import ru.windcorp.progressia.common.world.entity.EntityData; import ru.windcorp.progressia.common.world.entity.EntityData;
@ -10,6 +11,7 @@ public class TestEntityDataStatie extends EntityData {
public TestEntityDataStatie() { public TestEntityDataStatie() {
super("Test", "Statie"); super("Test", "Statie");
setCollisionModel(new AABB(0, 0, 0, 16f/24, 16f/24, 16f/24));
setSizeNow(16); setSizeNow(16);
} }
@ -20,5 +22,10 @@ public class TestEntityDataStatie extends EntityData {
public void setSizeNow(int size) { public void setSizeNow(int size) {
this.size.setNow(this, size); this.size.setNow(this, size);
} }
@Override
public float getCollisionMass() {
return 50f;
}
} }