diff --git a/src/main/java/net/minestom/server/collision/BoundingBox.java b/src/main/java/net/minestom/server/collision/BoundingBox.java index ea42524c4..d8e55c6d3 100644 --- a/src/main/java/net/minestom/server/collision/BoundingBox.java +++ b/src/main/java/net/minestom/server/collision/BoundingBox.java @@ -11,6 +11,9 @@ import org.jetbrains.annotations.NotNull; * See https://wiki.vg/Entity_metadata#Mobs_2 */ public final class BoundingBox implements Shape { + + final static BoundingBox ZERO = new BoundingBox(0, 0, 0); + private final double width, height, depth; private final Point offset; private Point relativeEnd; diff --git a/src/main/java/net/minestom/server/collision/CollisionUtils.java b/src/main/java/net/minestom/server/collision/CollisionUtils.java index 5e5b2cb73..df8e73411 100644 --- a/src/main/java/net/minestom/server/collision/CollisionUtils.java +++ b/src/main/java/net/minestom/server/collision/CollisionUtils.java @@ -4,6 +4,7 @@ import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Entity; +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; @@ -30,14 +31,53 @@ public final class CollisionUtils { */ public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity, @Nullable PhysicsResult lastPhysicsResult) { - final BoundingBox boundingBox = entity.getBoundingBox(); - final Pos currentPosition = entity.getPosition(); - final Block.Getter getter = new ChunkCache(entity.getInstance(), entity.getChunk(), Block.STONE); + assert entity.getInstance() != null; + return handlePhysics(entity.getInstance(), entity.getChunk(), + entity.getBoundingBox(), + entity.getPosition(), entityVelocity, + lastPhysicsResult); + } + + /** + * Moves bounding box with physics applied (ie checking against blocks) + *
+ * Works by getting all the full blocks that a bounding box could interact with.
+ * All bounding boxes inside the full blocks are checked for collisions with the given bounding box.
+ *
+ * @param boundingBox the bounding box to move
+ * @return the result of physics simulation
+ */
+ public static PhysicsResult handlePhysics(@NotNull Instance instance, @Nullable Chunk chunk,
+ @NotNull BoundingBox boundingBox,
+ @NotNull Pos position, @NotNull Vec velocity,
+ @Nullable PhysicsResult lastPhysicsResult) {
+ final Block.Getter getter = new ChunkCache(instance, chunk != null ? chunk : instance.getChunkAt(position), Block.STONE);
return BlockCollision.handlePhysics(boundingBox,
- entityVelocity, currentPosition,
+ velocity, position,
getter, lastPhysicsResult);
}
+ /**
+ * Checks whether shape is reachable by the given line of sight
+ * (ie there are no blocks colliding with it).
+ *
+ * @param instance the instance.
+ * @param chunk optional chunk reference for speedup purposes.
+ * @param start start of the line of sight.
+ * @param end end of the line of sight.
+ * @param shape shape to check.
+ * @return true is shape is reachable by the given line of sight; false otherwise.
+ */
+ public static boolean isLineOfSightReachingShape(@NotNull Instance instance, @Nullable Chunk chunk,
+ @NotNull Point start, @NotNull Point end,
+ @NotNull Shape shape) {
+ final PhysicsResult result = handlePhysics(instance, chunk,
+ BoundingBox.ZERO,
+ Pos.fromPoint(start), Vec.fromPoint(end.sub(start)),
+ null);
+ return shape.intersectBox(end.sub(result.newPosition()), BoundingBox.ZERO);
+ }
+
public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity) {
return handlePhysics(entity, entityVelocity, null);
}
diff --git a/src/main/java/net/minestom/server/collision/RayUtils.java b/src/main/java/net/minestom/server/collision/RayUtils.java
index 4ceba2af0..11cc00921 100644
--- a/src/main/java/net/minestom/server/collision/RayUtils.java
+++ b/src/main/java/net/minestom/server/collision/RayUtils.java
@@ -345,7 +345,6 @@ final class RayUtils {
}
public static boolean BoundingBoxRayIntersectionCheck(Vec start, Vec direction, BoundingBox boundingBox, Pos position) {
- // TODO: BoundingBox.ZERO?
- return BoundingBoxIntersectionCheck(new BoundingBox(0, 0, 0), start, direction, boundingBox, position);
+ return BoundingBoxIntersectionCheck(BoundingBox.ZERO, start, direction, boundingBox, position);
}
}
diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java
index 302e8c074..ff6960e8f 100644
--- a/src/main/java/net/minestom/server/entity/Entity.java
+++ b/src/main/java/net/minestom/server/entity/Entity.java
@@ -1610,27 +1610,41 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
}
/**
- * Checks whether the current entity has line of sight to the given one.
- * If so, it doesn't mean that the given entity is IN line of sight of the current,
- * but the current one can rotate so that it will be true.
+ * Raycasts current entity's eye position to target eye position.
*
* @param entity the entity to be checked.
- * @return if the current entity has line of sight to the given one.
+ * @param exactView if set to TRUE, checks whether target is IN the line of sight of the current one;
+ * otherwise checks if the current entity can rotate so that target will be in its line of sight.
+ * @return true if the ray reaches the target bounding box before hitting a block.
*/
- public boolean hasLineOfSight(Entity entity) {
+ public boolean hasLineOfSight(Entity entity, boolean exactView) {
Instance instance = getInstance();
if (instance == null) {
return false;
}
- final Vec start = new Vec(position.x(), position.y() + getEyeHeight(), position.z());
- return entity.boundingBox.boundingBoxRayIntersectionCheck(start, position.direction(), entity.getPosition());
+ final Pos start = position.withY(position.y() + getEyeHeight());
+ final Pos end = entity.position.withY(entity.position.y() + entity.getEyeHeight());
+ final Vec direction = exactView ? position.direction() : end.sub(start).asVec().normalize();
+ if (!entity.boundingBox.boundingBoxRayIntersectionCheck(start.asVec(), direction, entity.getPosition())) {
+ return false;
+ }
+ return CollisionUtils.isLineOfSightReachingShape(instance, currentChunk, start, end, entity.boundingBox);
+ }
+
+ /**
+ * @see Entity#hasLineOfSight(Entity, boolean)
+ * @param entity the entity to be checked.
+ * @return if the current entity has line of sight to the given one.
+ */
+ public boolean hasLineOfSight(Entity entity) {
+ return hasLineOfSight(entity, false);
}
/**
* Gets first entity on the line of sight of the current one that matches the given predicate.
*
- * @param range max length of the line of sight of the current entity to be checked.
+ * @param range max length of the line of sight of the current entity to be checked.
* @param predicate optional predicate
* @return resulting entity whether there're any, null otherwise.
*/
@@ -1640,12 +1654,16 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
return null;
}
- final Vec start = new Vec(position.x(), position.y() + getEyeHeight(), position.z());
+ final Pos start = position.withY(position.y() + getEyeHeight());
+ final Vec startAsVec = start.asVec();
+ final Predicate