diff --git a/src/main/java/net/minestom/server/collision/BlockCollision.java b/src/main/java/net/minestom/server/collision/BlockCollision.java index a2f65b2c7..7fe67308c 100644 --- a/src/main/java/net/minestom/server/collision/BlockCollision.java +++ b/src/main/java/net/minestom/server/collision/BlockCollision.java @@ -10,6 +10,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.entity.metadata.other.ArmorStandMeta; import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; +import net.minestom.server.utils.block.BlockIterator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -132,7 +133,7 @@ final class BlockCollision { @NotNull Block.Getter getter, @Nullable PhysicsResult lastPhysicsResult) { // Allocate once and update values - SweepResult finalResult = new SweepResult(1, 0, 0, 0, null); + SweepResult finalResult = new SweepResult(1 - Vec.EPSILON, 0, 0, 0, null); boolean foundCollisionX = false, foundCollisionY = false, foundCollisionZ = false; Point collisionYBlock = null; @@ -148,7 +149,6 @@ final class BlockCollision { && velocity.x() == 0 && velocity.z() == 0 && entityPosition.samePoint(lastPhysicsResult.newPosition()) && lastPhysicsResult.blockTypeY() != Block.AIR) { - velocity = velocity.withY(0); foundCollisionY = true; collisionYBlock = lastPhysicsResult.collidedBlockY(); blockYType = lastPhysicsResult.blockTypeY(); @@ -171,7 +171,7 @@ final class BlockCollision { // Looping until there are no collisions will allow the entity to move in axis other than the collision axis after a collision. while (res.collisionX() || res.collisionY() || res.collisionZ()) { // Reset final result - finalResult.res = 1; + finalResult.res = 1 - Vec.EPSILON; finalResult.normalX = 0; finalResult.normalY = 0; finalResult.normalZ = 0; @@ -261,16 +261,31 @@ final class BlockCollision { } else { // When large moves are done we need to ray-cast to find all blocks that could intersect with the movement for (Vec point : allFaces) { - RayUtils.RaycastCollision(velocity, point.add(entityPosition), getter, boundingBox, entityPosition, finalResult); + BlockIterator iterator = new BlockIterator(Vec.fromPoint(point.add(entityPosition)), velocity, 0, (int) Math.ceil(velocity.length())); + while (iterator.hasNext()) { + Point p = iterator.next(); + + // sqrt 3 (1.733) is the maximum error + if (Vec.fromPoint(p.sub(entityPosition)).length() > (finalResult.res * velocity.length() + 1.733)) + break; + + if (checkBoundingBox(p.blockX(), p.blockY(), p.blockZ(), velocity, entityPosition, boundingBox, getter, finalResult)) + break; + } } } - final boolean collisionX = finalResult.normalX != 0; - final boolean collisionY = finalResult.normalY != 0; - final boolean collisionZ = finalResult.normalZ != 0; - final double deltaX = finalResult.res * velocity.x(); - final double deltaY = finalResult.res * velocity.y(); - final double deltaZ = finalResult.res * velocity.z(); + boolean collisionX = finalResult.normalX != 0; + boolean collisionY = finalResult.normalY != 0; + boolean collisionZ = finalResult.normalZ != 0; + + double deltaX = finalResult.res * velocity.x(); + double deltaY = finalResult.res * velocity.y(); + double deltaZ = finalResult.res * velocity.z(); + + if (Math.abs(deltaX) < Vec.EPSILON) deltaX = 0; + if (Math.abs(deltaY) < Vec.EPSILON) deltaY = 0; + if (Math.abs(deltaZ) < Vec.EPSILON) deltaZ = 0; final Pos finalPos = entityPosition.add(deltaX, deltaY, deltaZ); diff --git a/src/main/java/net/minestom/server/collision/BoundingBox.java b/src/main/java/net/minestom/server/collision/BoundingBox.java index f214d7ad0..64925fb1b 100644 --- a/src/main/java/net/minestom/server/collision/BoundingBox.java +++ b/src/main/java/net/minestom/server/collision/BoundingBox.java @@ -36,9 +36,9 @@ public final class BoundingBox implements Shape { @Override @ApiStatus.Experimental public boolean intersectBox(@NotNull Point positionRelative, @NotNull BoundingBox boundingBox) { - return (minX() + positionRelative.x() <= boundingBox.maxX() && maxX() + positionRelative.x() >= boundingBox.minX()) && - (minY() + positionRelative.y() <= boundingBox.maxY() && maxY() + positionRelative.y() >= boundingBox.minY()) && - (minZ() + positionRelative.z() <= boundingBox.maxZ() && maxZ() + positionRelative.z() >= boundingBox.minZ()); + return (minX() + positionRelative.x() <= boundingBox.maxX() - Vec.EPSILON / 2 && maxX() + positionRelative.x() >= boundingBox.minX() + Vec.EPSILON / 2) && + (minY() + positionRelative.y() <= boundingBox.maxY() - Vec.EPSILON / 2 && maxY() + positionRelative.y() >= boundingBox.minY() + Vec.EPSILON / 2) && + (minZ() + positionRelative.z() <= boundingBox.maxZ() - Vec.EPSILON / 2 && maxZ() + positionRelative.z() >= boundingBox.minZ() + Vec.EPSILON / 2); } @Override diff --git a/src/main/java/net/minestom/server/collision/CollisionUtils.java b/src/main/java/net/minestom/server/collision/CollisionUtils.java index 22c9c9954..43c34a592 100644 --- a/src/main/java/net/minestom/server/collision/CollisionUtils.java +++ b/src/main/java/net/minestom/server/collision/CollisionUtils.java @@ -74,7 +74,7 @@ public final class CollisionUtils { BoundingBox.ZERO, Pos.fromPoint(start), Vec.fromPoint(end.sub(start)), null); - return shape.intersectBox(end.sub(result.newPosition()), BoundingBox.ZERO); + return shape.intersectBox(end.sub(result.newPosition()).sub(Vec.EPSILON), BoundingBox.ZERO); } public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity) { diff --git a/src/main/java/net/minestom/server/collision/RayUtils.java b/src/main/java/net/minestom/server/collision/RayUtils.java index 46ccb7f1a..478f76d40 100644 --- a/src/main/java/net/minestom/server/collision/RayUtils.java +++ b/src/main/java/net/minestom/server/collision/RayUtils.java @@ -3,104 +3,8 @@ package net.minestom.server.collision; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; -import net.minestom.server.instance.block.Block; final class RayUtils { - public static void RaycastCollision(Vec rayDirection, Point rayStart, Block.Getter getter, BoundingBox boundingBox, Pos entityCentre, SweepResult finalResult) { - // This works by finding all the x, y and z grid line intersections and calculating the value of the point at that intersection - // Finding all the intersections will give us all the full blocks that are traversed by the ray - - if (rayDirection.x() != 0) { - // Which direction we're stepping the block boundary in - double xStep = rayDirection.x() < 0 ? -1 : 1; - - // If we are going in the positive direction, the block that we stepped over is the one we want - int xFix = rayDirection.x() > 0 ? 1 : 0; - - // Total number of axis block boundaries that will be passed - int xStepCount = (int) Math.ceil((rayDirection.x()) / xStep) + xFix; - - int xStepsCompleted = xFix; - - while (xStepsCompleted <= xStepCount) { - // Get the axis value - int xi = (int) (xStepsCompleted * xStep + rayStart.blockX()); - double factor = (xi - rayStart.x()) / rayDirection.x(); - - if (Math.abs(rayDirection.x() * finalResult.res) - Math.abs(rayStart.x() - (xi)) < -2) break; - - // Solve for y and z - int yi = (int) Math.floor(rayDirection.y() * factor + rayStart.y()); - - // If the y distance is much greater than the collision point that is currently being used, break - if (Math.abs(rayDirection.y() * finalResult.res) - Math.abs(rayStart.y() - (yi)) < -2) break; - - int zi = (int) Math.floor(rayDirection.z() * factor + rayStart.z()); - if (Math.abs(rayDirection.z() * finalResult.res) - Math.abs(rayStart.z() - (zi)) < -2) break; - - xi -= xFix; - xStepsCompleted++; - - // Check for collisions with the found block - // If a collision was found, break - if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, getter, finalResult)) - break; - } - } - - if (rayDirection.z() != 0) { - double zStep = rayDirection.z() < 0 ? -1 : 1; - int zFix = rayDirection.z() > 0 ? 1 : 0; - int zStepsCompleted = zFix; - int zStepCount = (int) Math.ceil((rayDirection.z()) / zStep) + zFix; - - while (zStepsCompleted <= zStepCount) { - int zi = (int) (zStepsCompleted * zStep + rayStart.blockZ()); - double factor = (zi - rayStart.z()) / rayDirection.z(); - - if (Math.abs(rayDirection.z() * finalResult.res) - Math.abs(rayStart.z() - (zi)) < -2) break; - - int xi = (int) Math.floor(rayDirection.x() * factor + rayStart.x()); - if (Math.abs(rayDirection.x() * finalResult.res) - Math.abs(rayStart.x() - (xi)) < -2) break; - - int yi = (int) Math.floor(rayDirection.y() * factor + rayStart.y()); - if (Math.abs(rayDirection.y() * finalResult.res) - Math.abs(rayStart.y() - (yi)) < -2) break; - - zi -= zFix; - zStepsCompleted++; - - if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, getter, finalResult)) - break; - } - } - - if (rayDirection.y() != 0) { - int yFix = rayDirection.y() > 0 ? 1 : 0; - double yStep = rayDirection.y() < 0 ? -1 : 1; - int yStepsCompleted = yFix; - int yStepCount = (int) Math.ceil((rayDirection.y()) / yStep) + yFix; - - while (yStepsCompleted <= yStepCount) { - int yi = (int) (yStepsCompleted * yStep + rayStart.blockY()); - double factor = (yi - rayStart.y()) / rayDirection.y(); - - if (Math.abs(rayDirection.y() * finalResult.res) - Math.abs(rayStart.y() - (yi)) < -2) break; - - int xi = (int) Math.floor(rayDirection.x() * factor + rayStart.x()); - if (Math.abs(rayDirection.x() * finalResult.res) - Math.abs(rayStart.x() - (xi)) < -2) break; - - int zi = (int) Math.floor(rayDirection.z() * factor + rayStart.z()); - if (Math.abs(rayDirection.z() * finalResult.res) - Math.abs(rayStart.z() - (zi)) < -2) break; - - yi -= yFix; - yStepsCompleted++; - - if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, getter, finalResult)) - break; - } - } - } - /** * Check if a bounding box intersects a ray * @@ -110,12 +14,12 @@ final class RayUtils { * @return true if an intersection between the ray and the bounding box was found */ public static boolean BoundingBoxIntersectionCheck(BoundingBox moving, Point rayStart, Point rayDirection, BoundingBox collidableStatic, Point staticCollidableOffset) { - Point bbCentre = new Pos(moving.minX() + moving.width() / 2, moving.minY() + moving.height() / 2, moving.minZ() + moving.depth() / 2); + Point bbCentre = new Vec(moving.minX() + moving.width() / 2, moving.minY() + moving.height() / 2 + Vec.EPSILON, moving.minZ() + moving.depth() / 2); Point rayCentre = rayStart.add(bbCentre); // Translate bounding box - Vec bbOffMin = Vec.Operator.EPSILON.apply(collidableStatic.minX() - rayCentre.x() + staticCollidableOffset.x() - moving.width() / 2, collidableStatic.minY() - rayCentre.y() + staticCollidableOffset.y() - moving.height() / 2, collidableStatic.minZ() - rayCentre.z() + staticCollidableOffset.z() - moving.depth() / 2); - Vec bbOffMax = Vec.Operator.EPSILON.apply(collidableStatic.maxX() - rayCentre.x() + staticCollidableOffset.x() + moving.width() / 2, collidableStatic.maxY() - rayCentre.y() + staticCollidableOffset.y() + moving.height() / 2, collidableStatic.maxZ() - rayCentre.z() + staticCollidableOffset.z() + moving.depth() / 2); + Vec bbOffMin = new Vec(collidableStatic.minX() - rayCentre.x() + staticCollidableOffset.x() - moving.width() / 2, collidableStatic.minY() - rayCentre.y() + staticCollidableOffset.y() - moving.height() / 2, collidableStatic.minZ() - rayCentre.z() + staticCollidableOffset.z() - moving.depth() / 2); + Vec bbOffMax = new Vec(collidableStatic.maxX() - rayCentre.x() + staticCollidableOffset.x() + moving.width() / 2, collidableStatic.maxY() - rayCentre.y() + staticCollidableOffset.y() + moving.height() / 2, collidableStatic.maxZ() - rayCentre.z() + staticCollidableOffset.z() + moving.depth() / 2); // This check is done in 2d. it can be visualised as a rectangle (the face we are checking), and a point. // If the point is within the rectangle, we know the vector intersects the face. @@ -127,14 +31,13 @@ final class RayUtils { // Intersect X if (rayDirection.x() != 0) { // Left side of bounding box - { + if (rayDirection.x() > 0) { double xFac = bbOffMin.x() / rayDirection.x(); double yix = rayDirection.y() * xFac + rayCentre.y(); double zix = rayDirection.z() * xFac + rayCentre.z(); // Check if ray passes through y/z plane - if (rayDirection.x() > 0 - && ((yix - rayCentre.y()) * signumRayY) >= 0 + if (((yix - rayCentre.y()) * signumRayY) >= 0 && ((zix - rayCentre.z()) * signumRayZ) >= 0 && yix >= collidableStatic.minY() + staticCollidableOffset.y() - moving.height() / 2 && yix <= collidableStatic.maxY() + staticCollidableOffset.y() + moving.height() / 2 @@ -144,13 +47,12 @@ final class RayUtils { } } // Right side of bounding box - { + if (rayDirection.x() < 0) { double xFac = bbOffMax.x() / rayDirection.x(); double yix = rayDirection.y() * xFac + rayCentre.y(); double zix = rayDirection.z() * xFac + rayCentre.z(); - if (rayDirection.x() < 0 - && ((yix - rayCentre.y()) * signumRayY) >= 0 + if (((yix - rayCentre.y()) * signumRayY) >= 0 && ((zix - rayCentre.z()) * signumRayZ) >= 0 && yix >= collidableStatic.minY() + staticCollidableOffset.y() - moving.height() / 2 && yix <= collidableStatic.maxY() + staticCollidableOffset.y() + moving.height() / 2 @@ -163,13 +65,12 @@ final class RayUtils { // Intersect Z if (rayDirection.z() != 0) { - { + if (rayDirection.z() > 0) { double zFac = bbOffMin.z() / rayDirection.z(); double xiz = rayDirection.x() * zFac + rayCentre.x(); double yiz = rayDirection.y() * zFac + rayCentre.y(); - if (rayDirection.z() > 0 - && ((yiz - rayCentre.y()) * signumRayY) >= 0 + if (((yiz - rayCentre.y()) * signumRayY) >= 0 && ((xiz - rayCentre.x()) * signumRayX) >= 0 && xiz >= collidableStatic.minX() + staticCollidableOffset.x() - moving.width() / 2 && xiz <= collidableStatic.maxX() + staticCollidableOffset.x() + moving.width() / 2 @@ -178,13 +79,12 @@ final class RayUtils { return true; } } - { + if (rayDirection.z() < 0) { double zFac = bbOffMax.z() / rayDirection.z(); double xiz = rayDirection.x() * zFac + rayCentre.x(); double yiz = rayDirection.y() * zFac + rayCentre.y(); - if (rayDirection.z() < 0 - && ((yiz - rayCentre.y()) * signumRayY) >= 0 + if (((yiz - rayCentre.y()) * signumRayY) >= 0 && ((xiz - rayCentre.x()) * signumRayX) >= 0 && xiz >= collidableStatic.minX() + staticCollidableOffset.x() - moving.width() / 2 && xiz <= collidableStatic.maxX() + staticCollidableOffset.x() + moving.width() / 2 @@ -197,13 +97,12 @@ final class RayUtils { // Intersect Y if (rayDirection.y() != 0) { - { + if (rayDirection.y() > 0) { double yFac = bbOffMin.y() / rayDirection.y(); double xiy = rayDirection.x() * yFac + rayCentre.x(); double ziy = rayDirection.z() * yFac + rayCentre.z(); - if (rayDirection.y() > 0 - && ((ziy - rayCentre.z()) * signumRayZ) >= 0 + if (((ziy - rayCentre.z()) * signumRayZ) >= 0 && ((xiy - rayCentre.x()) * signumRayX) >= 0 && xiy >= collidableStatic.minX() + staticCollidableOffset.x() - moving.width() / 2 && xiy <= collidableStatic.maxX() + staticCollidableOffset.x() + moving.width() / 2 @@ -212,13 +111,12 @@ final class RayUtils { return true; } } - { + if (rayDirection.y() < 0) { double yFac = bbOffMax.y() / rayDirection.y(); double xiy = rayDirection.x() * yFac + rayCentre.x(); double ziy = rayDirection.z() * yFac + rayCentre.z(); - if (rayDirection.y() < 0 - && ((ziy - rayCentre.z()) * signumRayZ) >= 0 + if (((ziy - rayCentre.z()) * signumRayZ) >= 0 && ((xiy - rayCentre.x()) * signumRayX) >= 0 && xiy >= collidableStatic.minX() + staticCollidableOffset.x() - moving.width() / 2 && xiy <= collidableStatic.maxX() + staticCollidableOffset.x() + moving.width() / 2 diff --git a/src/test/java/net/minestom/server/collision/EntityBlockPhysicsIntegrationTest.java b/src/test/java/net/minestom/server/collision/EntityBlockPhysicsIntegrationTest.java index 669de98cc..e4b1989ad 100644 --- a/src/test/java/net/minestom/server/collision/EntityBlockPhysicsIntegrationTest.java +++ b/src/test/java/net/minestom/server/collision/EntityBlockPhysicsIntegrationTest.java @@ -11,6 +11,7 @@ import net.minestom.server.entity.metadata.other.SlimeMeta; import net.minestom.server.instance.block.Block; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -33,6 +34,16 @@ public class EntityBlockPhysicsIntegrationTest { assertEquals(expected.z(), actual.z(), PRECISION.z()); } + private static void assertPossiblePoints(List expected, Point actual) { + for (Point point : expected) { + if (checkPoints(point, actual)) { + return; + } + } + + fail("Expected one of the following points: " + expected); + } + @Test public void entityPhysicsCheckCollision(Env env) { var instance = env.createFlatInstance(); @@ -251,13 +262,8 @@ public class EntityBlockPhysicsIntegrationTest { PhysicsResult res = CollisionUtils.handlePhysics(entity, new Vec(10, 0, 10)); - boolean isFirst = checkPoints(new Pos(10, 42, 0.7), res.newPosition()); - boolean isSecond = checkPoints(new Pos(0.7, 42, 10), res.newPosition()); - // First and second are both valid, it depends on the implementation - // If x collision is checked first then isFirst will be true - // If z collision is checked first then isSecond will be true - assertTrue(isFirst || isSecond); + assertPossiblePoints(List.of(new Pos(10, 42, 0.7), new Pos(0.7, 42, 10)), res.newPosition()); } @Test @@ -395,13 +401,8 @@ public class EntityBlockPhysicsIntegrationTest { PhysicsResult res = CollisionUtils.handlePhysics(entity, new Vec(0.702, 0, 0.702)); - boolean isFirst = checkPoints(new Pos(1.402, 42, 0.7), res.newPosition()); - boolean isSecond = checkPoints(new Pos(0.7, 42, 1.402), res.newPosition()); - // First and second are both valid, it depends on the implementation - // If x collision is checked first then isFirst will be true - // If z collision is checked first then isSecond will be true - assertTrue(isFirst || isSecond); + assertPossiblePoints(List.of(new Pos(1.402, 42, 0.7), new Pos(0.7, 42, 1.402)), res.newPosition()); } @Test @@ -592,10 +593,8 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(1, 42, 0, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(0.7, 42, 0.5)).join(); assertEquals(instance, entity.getInstance()); @@ -609,10 +608,8 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(0, 42, 1, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(0.5, 42, 0.7)).join(); assertEquals(instance, entity.getInstance()); @@ -626,10 +623,8 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(1, 42, 1, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(0.8, 42, 1.3)).join(); assertEquals(instance, entity.getInstance()); @@ -643,10 +638,8 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(0, 42, 0, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(0.7, 42, 1.1)).join(); assertEquals(instance, entity.getInstance()); @@ -660,10 +653,8 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(0, 42, 1, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(1.1, 42, 1.3)).join(); assertEquals(instance, entity.getInstance()); @@ -677,10 +668,8 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(1, 42, 0, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(1.3, 42, 1.1)).join(); assertEquals(instance, entity.getInstance()); @@ -694,10 +683,8 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(0, 42, 0, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(1.1, 42, 0.7)).join(); assertEquals(instance, entity.getInstance()); @@ -711,10 +698,8 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(1, 42, 1, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(1.3, 42, 0.8)).join(); assertEquals(instance, entity.getInstance()); @@ -729,16 +714,14 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(1, 43, 0, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(0.51, 42.51, 0.5)).join(); assertEquals(instance, entity.getInstance()); PhysicsResult res = CollisionUtils.handlePhysics(entity, new Vec(0.57, 0.57, 0.57)); - assertEqualsPoint(new Pos(1.08, 43, 1.07), res.newPosition()); + assertPossiblePoints(List.of(new Pos(1.08, 43, 1.07), new Pos(1.0, 43.08, 1.07)), res.newPosition()); } @Test @@ -746,16 +729,14 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(0, 43, 1, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(0.50, 42.51, 0.51)).join(); assertEquals(instance, entity.getInstance()); PhysicsResult res = CollisionUtils.handlePhysics(entity, new Vec(0.57, 0.57, 0.57)); - assertEqualsPoint(new Pos(1.07, 43, 1.08), res.newPosition()); + assertPossiblePoints(List.of(new Pos(1.07, 43, 1.08), new Pos(1.07, 43.08, 1.0)), res.newPosition()); } @Test @@ -763,16 +744,15 @@ public class EntityBlockPhysicsIntegrationTest { var instance = env.createFlatInstance(); instance.setBlock(1, 43, 1, Block.STONE); - BoundingBox bb = new BoundingBox(0, 0, 0); - var entity = new Entity(EntityType.ZOMBIE); - entity.setBoundingBox(bb); + entity.setBoundingBox(BoundingBox.ZERO); entity.setInstance(instance, new Pos(0.51, 42.50, 0.51)).join(); assertEquals(instance, entity.getInstance()); PhysicsResult res = CollisionUtils.handlePhysics(entity, new Vec(0.57, 0.57, 0.57)); - assertEqualsPoint(new Pos(1.08, 43, 1.08), res.newPosition()); + + assertPossiblePoints(List.of(new Pos(1.0, 43.08, 1.08), new Pos(1.08, 43.0, 1.08), new Pos(1.08, 43.08, 1.0)), res.newPosition()); } @Test @@ -827,7 +807,7 @@ public class EntityBlockPhysicsIntegrationTest { PhysicsResult nz = CollisionUtils.handlePhysics(entity, new Vec(0, 0, -10)); assertEqualsPoint(new Pos(0.7, 42, 0.5), px.newPosition()); - assertEqualsPoint(new Pos(0.5, 42.04, 0.5), py.newPosition()); + assertEqualsPoint(new Pos(0.5, 42.05, 0.5), py.newPosition()); assertEqualsPoint(new Pos(0.5, 42, 0.7), pz.newPosition()); assertEqualsPoint(new Pos(0.3, 42, 0.5), nx.newPosition()); @@ -878,4 +858,36 @@ public class EntityBlockPhysicsIntegrationTest { PhysicsResult res = CollisionUtils.handlePhysics(entity, Vec.ZERO); assertEqualsPoint(new Pos(5, 42, 5), res.newPosition()); } + + @Test + public void entityPhysicsRepeatedCollision(Env env) { + var instance = env.createFlatInstance(); + PhysicsResult previousResult = null; + + instance.setBlock(0, 41, 0, Block.STONE); + + instance.setBlock(1, 42, 0, Block.STONE); + instance.setBlock(0, 42, 1, Block.STONE); + instance.setBlock(0, 42, -1, Block.STONE); + instance.setBlock(-1, 42, 0, Block.STONE); + + instance.setBlock(1, 43, 0, Block.STONE); + instance.setBlock(0, 43, 1, Block.STONE); + instance.setBlock(0, 43, -1, Block.STONE); + instance.setBlock(-1, 43, 0, Block.STONE); + + var entity = new Entity(EntityType.ZOMBIE); + entity.setInstance(instance, new Pos(0.5, 43.1, 0.5)).join(); + + PhysicsResult res = CollisionUtils.handlePhysics(entity, new Vec(0, 0, 0)); + entity.teleport(res.newPosition()).join(); + + while ((previousResult == null || !previousResult.newPosition().samePoint(res.newPosition())) && entity.getPosition().y() >= 42) { + previousResult = res; + res = CollisionUtils.handlePhysics(entity, new Vec(0.1, -0.01, 0)); + entity.teleport(res.newPosition()).join(); + } + + assertEqualsPoint(new Pos(0.7, 42, 0.5), res.newPosition()); + } } \ No newline at end of file diff --git a/src/test/java/net/minestom/server/collision/RayUtilsTest.java b/src/test/java/net/minestom/server/collision/RayUtilsTest.java deleted file mode 100644 index bb490a3fd..000000000 --- a/src/test/java/net/minestom/server/collision/RayUtilsTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.minestom.server.collision; - -import net.minestom.server.coordinate.Pos; -import net.minestom.server.coordinate.Vec; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -public class RayUtilsTest { - @Test - void manyHeightIntersectionChecks() { - final Pos rayStart = new Pos(0.5, 1.0, 0.5); - final Vec rayDirection = new Vec(0.273, -0.0784, 0.0); - final BoundingBox collidableStatic = new BoundingBox(1, 1, 1); - final Vec staticCollidableOffset = new Vec(1, 0.0, 0.0); - - for(double y = 1; y < 10; y += 0.001D) { - final BoundingBox moving = new BoundingBox(0.6, y, 0.6); - assertTrue(RayUtils.BoundingBoxIntersectionCheck(moving, rayStart, rayDirection, collidableStatic, - staticCollidableOffset), moving.toString()); - } - } -} \ No newline at end of file diff --git a/src/test/java/net/minestom/server/entity/EntityVelocityIntegrationTest.java b/src/test/java/net/minestom/server/entity/EntityVelocityIntegrationTest.java index f175ca642..b68bf6b29 100644 --- a/src/test/java/net/minestom/server/entity/EntityVelocityIntegrationTest.java +++ b/src/test/java/net/minestom/server/entity/EntityVelocityIntegrationTest.java @@ -106,7 +106,7 @@ public class EntityVelocityIntegrationTest { var player = env.createPlayer(instance, new Pos(0, 42, 0)); env.tick(); - final double epsilon = 0.0000001; + final double epsilon = 0.00001; assertEquals(player.getVelocity().y(), -1.568, epsilon); double previousVelocity = player.getVelocity().y();