From 65a97e5b00a41bb8e03d19e0f6939048af0d3143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9meth=20Noel?= Date: Tue, 6 Jul 2021 22:26:11 +0200 Subject: [PATCH] Initial collision update --- .../server/collision/BoundingBox.java | 86 ++++---- .../server/collision/CollisionUtils.java | 197 +++++++++--------- .../net/minestom/server/entity/Entity.java | 32 +-- .../server/entity/pathfinding/Navigator.java | 9 +- 4 files changed, 158 insertions(+), 166 deletions(-) diff --git a/src/main/java/net/minestom/server/collision/BoundingBox.java b/src/main/java/net/minestom/server/collision/BoundingBox.java index 1c946d6c5..8ef3ddcb9 100644 --- a/src/main/java/net/minestom/server/collision/BoundingBox.java +++ b/src/main/java/net/minestom/server/collision/BoundingBox.java @@ -2,8 +2,8 @@ package net.minestom.server.collision; import net.minestom.server.entity.Entity; import net.minestom.server.utils.BlockPosition; -import net.minestom.server.utils.Vector; import net.minestom.server.utils.coordinate.Point; +import net.minestom.server.utils.coordinate.Vec; import org.jetbrains.annotations.NotNull; /** @@ -199,92 +199,92 @@ public class BoundingBox { } /** - * Gets an array of {@link Vector} representing the points at the bottom of the {@link BoundingBox}. + * Gets an array of {@link Vec} representing the points at the bottom of the {@link BoundingBox}. * * @return the points at the bottom of the {@link BoundingBox} */ @NotNull - public Vector[] getBottomFace() { - return new Vector[]{ - new Vector(getMinX(), getMinY(), getMinZ()), - new Vector(getMaxX(), getMinY(), getMinZ()), - new Vector(getMaxX(), getMinY(), getMaxZ()), - new Vector(getMinX(), getMinY(), getMaxZ()), + public Vec[] getBottomFace() { + return new Vec[]{ + new Vec(getMinX(), getMinY(), getMinZ()), + new Vec(getMaxX(), getMinY(), getMinZ()), + new Vec(getMaxX(), getMinY(), getMaxZ()), + new Vec(getMinX(), getMinY(), getMaxZ()), }; } /** - * Gets an array of {@link Vector} representing the points at the top of the {@link BoundingBox}. + * Gets an array of {@link Vec} representing the points at the top of the {@link BoundingBox}. * * @return the points at the top of the {@link BoundingBox} */ @NotNull - public Vector[] getTopFace() { - return new Vector[]{ - new Vector(getMinX(), getMaxY(), getMinZ()), - new Vector(getMaxX(), getMaxY(), getMinZ()), - new Vector(getMaxX(), getMaxY(), getMaxZ()), - new Vector(getMinX(), getMaxY(), getMaxZ()), + public Vec[] getTopFace() { + return new Vec[]{ + new Vec(getMinX(), getMaxY(), getMinZ()), + new Vec(getMaxX(), getMaxY(), getMinZ()), + new Vec(getMaxX(), getMaxY(), getMaxZ()), + new Vec(getMinX(), getMaxY(), getMaxZ()), }; } /** - * Gets an array of {@link Vector} representing the points on the left face of the {@link BoundingBox}. + * Gets an array of {@link Vec} representing the points on the left face of the {@link BoundingBox}. * * @return the points on the left face of the {@link BoundingBox} */ @NotNull - public Vector[] getLeftFace() { - return new Vector[]{ - new Vector(getMinX(), getMinY(), getMinZ()), - new Vector(getMinX(), getMaxY(), getMinZ()), - new Vector(getMinX(), getMaxY(), getMaxZ()), - new Vector(getMinX(), getMinY(), getMaxZ()), + public Vec[] getLeftFace() { + return new Vec[]{ + new Vec(getMinX(), getMinY(), getMinZ()), + new Vec(getMinX(), getMaxY(), getMinZ()), + new Vec(getMinX(), getMaxY(), getMaxZ()), + new Vec(getMinX(), getMinY(), getMaxZ()), }; } /** - * Gets an array of {@link Vector} representing the points on the right face of the {@link BoundingBox}. + * Gets an array of {@link Vec} representing the points on the right face of the {@link BoundingBox}. * * @return the points on the right face of the {@link BoundingBox} */ @NotNull - public Vector[] getRightFace() { - return new Vector[]{ - new Vector(getMaxX(), getMinY(), getMinZ()), - new Vector(getMaxX(), getMaxY(), getMinZ()), - new Vector(getMaxX(), getMaxY(), getMaxZ()), - new Vector(getMaxX(), getMinY(), getMaxZ()), + public Vec[] getRightFace() { + return new Vec[]{ + new Vec(getMaxX(), getMinY(), getMinZ()), + new Vec(getMaxX(), getMaxY(), getMinZ()), + new Vec(getMaxX(), getMaxY(), getMaxZ()), + new Vec(getMaxX(), getMinY(), getMaxZ()), }; } /** - * Gets an array of {@link Vector} representing the points at the front of the {@link BoundingBox}. + * Gets an array of {@link Vec} representing the points at the front of the {@link BoundingBox}. * * @return the points at the front of the {@link BoundingBox} */ @NotNull - public Vector[] getFrontFace() { - return new Vector[]{ - new Vector(getMinX(), getMinY(), getMinZ()), - new Vector(getMaxX(), getMinY(), getMinZ()), - new Vector(getMaxX(), getMaxY(), getMinZ()), - new Vector(getMinX(), getMaxY(), getMinZ()), + public Vec[] getFrontFace() { + return new Vec[]{ + new Vec(getMinX(), getMinY(), getMinZ()), + new Vec(getMaxX(), getMinY(), getMinZ()), + new Vec(getMaxX(), getMaxY(), getMinZ()), + new Vec(getMinX(), getMaxY(), getMinZ()), }; } /** - * Gets an array of {@link Vector} representing the points at the back of the {@link BoundingBox}. + * Gets an array of {@link Vec} representing the points at the back of the {@link BoundingBox}. * * @return the points at the back of the {@link BoundingBox} */ @NotNull - public Vector[] getBackFace() { - return new Vector[]{ - new Vector(getMinX(), getMinY(), getMaxZ()), - new Vector(getMaxX(), getMinY(), getMaxZ()), - new Vector(getMaxX(), getMaxY(), getMaxZ()), - new Vector(getMinX(), getMaxY(), getMaxZ()), + public Vec[] getBackFace() { + return new Vec[]{ + new Vec(getMinX(), getMinY(), getMaxZ()), + new Vec(getMaxX(), getMinY(), getMaxZ()), + new Vec(getMaxX(), getMaxY(), getMaxZ()), + new Vec(getMinX(), getMaxY(), getMaxZ()), }; } diff --git a/src/main/java/net/minestom/server/collision/CollisionUtils.java b/src/main/java/net/minestom/server/collision/CollisionUtils.java index d87e4a4fc..cf385fcb0 100644 --- a/src/main/java/net/minestom/server/collision/CollisionUtils.java +++ b/src/main/java/net/minestom/server/collision/CollisionUtils.java @@ -5,67 +5,46 @@ import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; import net.minestom.server.instance.WorldBorder; import net.minestom.server.instance.block.Block; -import net.minestom.server.utils.BlockPosition; -import net.minestom.server.utils.Position; -import net.minestom.server.utils.Vector; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.coordinate.Point; +import net.minestom.server.utils.coordinate.Pos; import net.minestom.server.utils.coordinate.Vec; import org.jetbrains.annotations.NotNull; public class CollisionUtils { - private static final Vector Y_AXIS = new Vector(0, 1, 0); - private static final Vector X_AXIS = new Vector(1, 0, 0); - private static final Vector Z_AXIS = new Vector(0, 0, 1); + private static final Vec Y_AXIS = new Vec(0, 1, 0); + private static final Vec X_AXIS = new Vec(1, 0, 0); + private static final Vec Z_AXIS = new Vec(0, 0, 1); /** * Moves an entity with physics applied (ie checking against blocks) * * @param entity the entity to move - * @param deltaPosition - * @param positionOut the Position object in which the new position will be saved - * @param velocityOut the Vector object in which the new velocity will be saved - * @return whether this entity is on the ground + * @return the result of physics simulation */ - public static boolean handlePhysics(@NotNull Entity entity, - @NotNull Vector deltaPosition, - @NotNull Position positionOut, - @NotNull Vector velocityOut) { + public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec deltaPosition) { // TODO handle collisions with nearby entities (should it be done here?) final Instance instance = entity.getInstance(); final Chunk originChunk = entity.getChunk(); - final Position currentPosition = entity.getPosition(); + final Pos currentPosition = entity.getPosition(); final BoundingBox boundingBox = entity.getBoundingBox(); - Vector intermediaryPosition = new Vector(); - boolean yCollision = stepAxis(instance, originChunk, currentPosition.toVector(), Y_AXIS, deltaPosition.getY(), - intermediaryPosition, - deltaPosition.getY() > 0 ? boundingBox.getTopFace() : boundingBox.getBottomFace()); + final StepResult yCollision = stepAxis(instance, originChunk, currentPosition.asVec(), Y_AXIS, deltaPosition.y(), + deltaPosition.y() > 0 ? boundingBox.getTopFace() : boundingBox.getBottomFace()); - boolean xCollision = stepAxis(instance, originChunk, intermediaryPosition, X_AXIS, deltaPosition.getX(), - intermediaryPosition, - deltaPosition.getX() < 0 ? boundingBox.getLeftFace() : boundingBox.getRightFace()); + final StepResult xCollision = stepAxis(instance, originChunk, yCollision.newPosition, X_AXIS, deltaPosition.x(), + deltaPosition.x() < 0 ? boundingBox.getLeftFace() : boundingBox.getRightFace()); - boolean zCollision = stepAxis(instance, originChunk, intermediaryPosition, Z_AXIS, deltaPosition.getZ(), - intermediaryPosition, - deltaPosition.getZ() > 0 ? boundingBox.getBackFace() : boundingBox.getFrontFace()); + final StepResult zCollision = stepAxis(instance, originChunk, xCollision.newPosition, Z_AXIS, deltaPosition.z(), + deltaPosition.z() > 0 ? boundingBox.getBackFace() : boundingBox.getFrontFace()); - positionOut.setX(intermediaryPosition.getX()); - positionOut.setY(intermediaryPosition.getY()); - positionOut.setZ(intermediaryPosition.getZ()); - velocityOut.copy(deltaPosition); - if (xCollision) { - velocityOut.setX(0f); - } - if (yCollision) { - velocityOut.setY(0f); - } - if (zCollision) { - velocityOut.setZ(0f); - } - - return yCollision && deltaPosition.getY() < 0; + return new PhysicsResult(currentPosition.withCoord(zCollision.newPosition), + deltaPosition.with(((x, y, z) -> new Vec( + xCollision.foundCollision ? 0 : x, + yCollision.foundCollision ? 0 : y, + zCollision.foundCollision ? 0 : z + ))), yCollision.foundCollision && deltaPosition.y() < 0); } /** @@ -76,111 +55,82 @@ public class CollisionUtils { * @param startPosition starting position for stepping, can be intermediary position from last step * @param axis step direction. Works best if unit vector and aligned to an axis * @param stepAmount how much to step in the direction (in blocks) - * @param positionOut the vector in which to store the new position * @param corners the corners to check against - * @return true if a collision has been found + * @return result of the step */ - private static boolean stepAxis(Instance instance, - Chunk originChunk, - Vector startPosition, Vector axis, - double stepAmount, Vector positionOut, - Vector... corners) { - positionOut.copy(startPosition); + private static StepResult stepAxis(Instance instance, Chunk originChunk, Vec startPosition, Vec axis, double stepAmount, Vec... corners) { if (corners.length == 0) - return false; // avoid degeneracy in following computations - // perform copies to allow in place modifications - // prevents making a lot of new objects. Well at least it reduces the count - BlockPosition[] cornerPositions = new BlockPosition[corners.length]; - Vector[] cornersCopy = new Vector[corners.length]; - for (int i = 0; i < corners.length; i++) { - cornersCopy[i] = corners[i].clone(); - cornerPositions[i] = new BlockPosition(corners[i]); - } + return new StepResult(startPosition, false); // avoid degeneracy in following computations + final Vec[] originalCorners = corners.clone(); final double sign = Math.signum(stepAmount); final int blockLength = (int) stepAmount; final double remainingLength = stepAmount - blockLength; // used to determine if 'remainingLength' should be used boolean collisionFound = false; for (int i = 0; i < Math.abs(blockLength); i++) { - if (!stepOnce(instance, originChunk, axis, sign, cornersCopy, cornerPositions)) { - collisionFound = true; - } - if (collisionFound) { - break; - } + final OneStepResult oneStepResult = stepOnce(instance, originChunk, axis, sign, corners); + corners = oneStepResult.newCorners; + if (collisionFound = oneStepResult.foundCollision) break; } // add remainingLength if (!collisionFound) { - Vector direction = new Vector(); - direction.copy(axis); - collisionFound = !stepOnce(instance, originChunk, direction, remainingLength, cornersCopy, cornerPositions); + final OneStepResult oneStepResult = stepOnce(instance, originChunk, axis, remainingLength, corners); + corners = oneStepResult.newCorners; + collisionFound = oneStepResult.foundCollision; } // find the corner which moved the least double smallestDisplacement = Double.POSITIVE_INFINITY; for (int i = 0; i < corners.length; i++) { - final double displacement = corners[i].distance(cornersCopy[i]); + final double displacement = originalCorners[i].distance(corners[i]); if (displacement < smallestDisplacement) { smallestDisplacement = displacement; } } - positionOut.copy(startPosition); - positionOut.add(smallestDisplacement * axis.getX() * sign, smallestDisplacement * axis.getY() * sign, smallestDisplacement * axis.getZ() * sign); - return collisionFound; + return new StepResult(startPosition.add(new Vec(smallestDisplacement).mul(axis).mul(sign)), collisionFound); } /** * Steps once (by a length of 1 block) on the given axis. * - * @param instance instance to get blocks from - * @param axis the axis to move along - * @param amount - * @param cornersCopy the corners of the bounding box to consider (mutable) - * @param cornerPositions the corners, converted to BlockPosition (mutable) - * @return false if this method encountered a collision + * @param instance instance to get blocks from + * @param axis the axis to move along + * @param corners the corners of the bounding box to consider + * @return the result of one step */ - private static boolean stepOnce(Instance instance, - Chunk originChunk, - Vector axis, double amount, Vector[] cornersCopy, BlockPosition[] cornerPositions) { + private static OneStepResult stepOnce(Instance instance, Chunk originChunk, Vec axis, double amount, Vec[] corners) { final double sign = Math.signum(amount); - for (int cornerIndex = 0; cornerIndex < cornersCopy.length; cornerIndex++) { - Vector corner = cornersCopy[cornerIndex]; - BlockPosition blockPos = cornerPositions[cornerIndex]; - corner.add(axis.getX() * amount, axis.getY() * amount, axis.getZ() * amount); - blockPos.setX((int) Math.floor(corner.getX())); - blockPos.setY((int) Math.floor(corner.getY())); - blockPos.setZ((int) Math.floor(corner.getZ())); + Vec[] newCorners = new Vec[corners.length]; + for (int cornerIndex = 0; cornerIndex < corners.length; cornerIndex++) { + final Vec originalCorner = corners[cornerIndex]; + final Vec corner = originalCorner.add(axis.mul(amount)); - Chunk chunk = ChunkUtils.retrieve(instance, originChunk, blockPos); + Chunk chunk = ChunkUtils.retrieve(instance, originChunk, corner); if (!ChunkUtils.isLoaded(chunk)) { // Collision at chunk border - return false; + return new OneStepResult(corners, true); } - final Block block = chunk.getBlock(blockPos); + final Block block = chunk.getBlock(corner); // TODO: block collision boxes // TODO: for the moment, always consider a full block if (block.isSolid()) { - corner.subtract(axis.getX() * amount, axis.getY() * amount, axis.getZ() * amount); + newCorners[cornerIndex] = originalCorner.with(((x, y, z) -> new Vec( + Math.abs(axis.x()) > 10e-16 ? originalCorner.blockX() - axis.x() * sign : x, + Math.abs(axis.y()) > 10e-16 ? originalCorner.blockY() - axis.y() * sign : y, + Math.abs(axis.z()) > 10e-16 ? originalCorner.blockZ() - axis.z() * sign : z + ))); - if (Math.abs(axis.getX()) > 10e-16) { - corner.setX(blockPos.getX() - axis.getX() * sign); - } - if (Math.abs(axis.getY()) > 10e-16) { - corner.setY(blockPos.getY() - axis.getY() * sign); - } - if (Math.abs(axis.getZ()) > 10e-16) { - corner.setZ(blockPos.getZ() - axis.getZ() * sign); - } - - return false; + return new OneStepResult(newCorners, true); } + + newCorners[cornerIndex] = corner; } - return true; + return new OneStepResult(newCorners, false); } /** @@ -213,4 +163,47 @@ public class CollisionUtils { throw new IllegalStateException("Something weird happened..."); } + public static class PhysicsResult { + private final Pos newPosition; + private final Vec newVelocity; + private final boolean isOnGround; + + public PhysicsResult(Pos newPosition, Vec newVelocity, boolean isOnGround) { + this.newPosition = newPosition; + this.newVelocity = newVelocity; + this.isOnGround = isOnGround; + } + + public Pos getNewPosition() { + return newPosition; + } + + public Vec getNewVelocity() { + return newVelocity; + } + + public boolean isOnGround() { + return isOnGround; + } + } + + private static class StepResult { + private final Vec newPosition; + private final boolean foundCollision; + + public StepResult(Vec newPosition, boolean foundCollision) { + this.newPosition = newPosition; + this.foundCollision = foundCollision; + } + } + + private static class OneStepResult { + private final Vec[] newCorners; + private final boolean foundCollision; + + public OneStepResult(Vec[] newCorners, boolean foundCollision) { + this.newCorners = newCorners; + this.foundCollision = foundCollision; + } + } } diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index c716a9d8a..028bc3890 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -508,26 +508,30 @@ public class Entity implements Viewable, Tickable, EventHandler, Da if (applyVelocity) { final float tps = MinecraftServer.TICK_PER_SECOND; - final double newX = position.x() + velocity.x() / tps; - final double newY = position.y() + velocity.y() / tps; - final double newZ = position.z() + velocity.z() / tps; - Position newPosition = new Position(newX, newY, newZ); - - Vector newVelocityOut = new Vector(); + final Pos newPosition; + final Vec newVelocity; // Gravity force final double gravityY = hasNoGravity() ? 0 : gravityAcceleration; - final Vector deltaPos = new Vector( - getVelocity().getX() / tps, - getVelocity().getY() / tps - gravityY, - getVelocity().getZ() / tps + final Vec deltaPos = new Vec( + getVelocity().x() / tps, + getVelocity().y() / tps - gravityY, + getVelocity().z() / tps ); if (this.hasPhysics) { - this.onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut); + final CollisionUtils.PhysicsResult physicsResult = CollisionUtils.handlePhysics(this, deltaPos); + this.onGround = physicsResult.isOnGround(); + newPosition = physicsResult.getNewPosition(); + newVelocity = physicsResult.getNewVelocity(); } else { - newVelocityOut = deltaPos; + newVelocity = deltaPos; + newPosition = new Pos( + position.x() + velocity.x() / tps, + position.y() + velocity.y() / tps, + position.z() + velocity.z() / tps + ); } // World border collision @@ -545,8 +549,8 @@ public class Entity implements Viewable, Tickable, EventHandler, Da } // Update velocity - if (hasVelocity() || !newVelocityOut.isZero()) { - this.velocity.copy(newVelocityOut); + if (hasVelocity() || !newVelocity.isZero()) { + this.velocity.copy(newVelocity); this.velocity.multiply(tps); final Block block = finalChunk.getBlock(position); diff --git a/src/main/java/net/minestom/server/entity/pathfinding/Navigator.java b/src/main/java/net/minestom/server/entity/pathfinding/Navigator.java index 9150ddfc3..72135b060 100644 --- a/src/main/java/net/minestom/server/entity/pathfinding/Navigator.java +++ b/src/main/java/net/minestom/server/entity/pathfinding/Navigator.java @@ -75,16 +75,11 @@ public class Navigator { // Update 'position' view PositionUtils.lookAlong(position, dx, direction.y(), dz); - Position newPosition = new Position(); - Vector newVelocityOut = new Vector(); - // Prevent ghosting - CollisionUtils.handlePhysics(entity, - new Vector(speedX, speedY, speedZ), - newPosition, newVelocityOut); + final CollisionUtils.PhysicsResult physicsResult = CollisionUtils.handlePhysics(entity, new Vec(speedX, speedY, speedZ)); // Will move the entity during Entity#tick - position.copyCoordinates(newPosition); + entity.refreshPosition(physicsResult.getNewPosition()); } public void jump(float height) {