mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-25 02:27:38 +01:00
Entity's line of sight methods improvements (#842)
This commit is contained in:
parent
eebdb4a7a3
commit
8df0d37107
@ -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;
|
||||
|
@ -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)
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Entity> finalPredicate = e -> e != this
|
||||
&& e.boundingBox.boundingBoxRayIntersectionCheck(startAsVec, position.direction(), e.getPosition())
|
||||
&& predicate.test(e)
|
||||
&& CollisionUtils.isLineOfSightReachingShape(instance, currentChunk, start,
|
||||
e.position.withY(e.position.y() + e.getEyeHeight()), e.boundingBox);
|
||||
|
||||
Optional<Entity> nearby = instance.getNearbyEntities(position, range).stream()
|
||||
.filter(e -> e != this
|
||||
&& e.boundingBox.boundingBoxRayIntersectionCheck(start, position.direction(), e.getPosition())
|
||||
&& predicate.test(e))
|
||||
.filter(finalPredicate)
|
||||
.min(Comparator.comparingDouble(e -> e.getDistance(this.position)));
|
||||
|
||||
return nearby.orElse(null);
|
||||
|
@ -3,10 +3,10 @@ package net.minestom.server.entity;
|
||||
import net.minestom.server.api.Env;
|
||||
import net.minestom.server.api.EnvTest;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@EnvTest
|
||||
public class EntityLineOfSightIntegrationTest {
|
||||
@ -21,8 +21,17 @@ public class EntityLineOfSightIntegrationTest {
|
||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||
entity2.setInstance(instance, new Pos(10, 42, 0)).join();
|
||||
|
||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
||||
assertEquals(res, entity2);
|
||||
assertEquals(entity2, entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, true));
|
||||
|
||||
for (int z = -1; z <= 1; ++z) {
|
||||
for (int y = 40; y <= 44; ++y) {
|
||||
instance.setBlock(5, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
|
||||
assertNull(entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertFalse(entity.hasLineOfSight(entity2, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -36,8 +45,17 @@ public class EntityLineOfSightIntegrationTest {
|
||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||
entity2.setInstance(instance, new Pos(-10, 42, 0)).join();
|
||||
|
||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
||||
assertNull(res);
|
||||
assertNull(entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertFalse(entity.hasLineOfSight(entity2, true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, false));
|
||||
|
||||
for (int z = -1; z <= 1; ++z) {
|
||||
for (int y = 40; y <= 44; ++y) {
|
||||
instance.setBlock(-5, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
|
||||
assertFalse(entity.hasLineOfSight(entity2, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -51,8 +69,17 @@ public class EntityLineOfSightIntegrationTest {
|
||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||
entity2.setInstance(instance, new Pos(10, 42, 0.31)).join();
|
||||
|
||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
||||
assertNull(res);
|
||||
assertNull(entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertFalse(entity.hasLineOfSight(entity2, true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, false));
|
||||
|
||||
for (int z = -1; z <= 1; ++z) {
|
||||
for (int y = 40; y <= 44; ++y) {
|
||||
instance.setBlock(5, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
|
||||
assertFalse(entity.hasLineOfSight(entity2, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -66,8 +93,19 @@ public class EntityLineOfSightIntegrationTest {
|
||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||
entity2.setInstance(instance, new Pos(10, 42, 0.3)).join();
|
||||
|
||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
||||
assertEquals(res, entity2);
|
||||
assertEquals(entity2, entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, false));
|
||||
|
||||
for (int z = -1; z <= 1; ++z) {
|
||||
for (int y = 40; y <= 44; ++y) {
|
||||
instance.setBlock(5, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
|
||||
assertNull(entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertFalse(entity.hasLineOfSight(entity2, true));
|
||||
assertFalse(entity.hasLineOfSight(entity2, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -84,8 +122,11 @@ public class EntityLineOfSightIntegrationTest {
|
||||
var entity3 = new Entity(EntityTypes.ZOMBIE);
|
||||
entity3.setInstance(instance, new Pos(5, 42, 0)).join();
|
||||
|
||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
||||
assertEquals(res, entity3);
|
||||
assertEquals(entity3, entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, false));
|
||||
assertTrue(entity.hasLineOfSight(entity3, true));
|
||||
assertTrue(entity.hasLineOfSight(entity3, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -99,7 +140,30 @@ public class EntityLineOfSightIntegrationTest {
|
||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||
entity2.setInstance(instance, new Pos(10, 42, 10)).join();
|
||||
|
||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
||||
assertNull(res);
|
||||
assertNull(entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertFalse(entity.hasLineOfSight(entity2, true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, false));
|
||||
}
|
||||
@Test
|
||||
public void entityPhysicsCheckLineOfSightLargeBoundingBox(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
|
||||
var entity = new Entity(EntityTypes.ZOMBIE);
|
||||
entity.setInstance(instance, new Pos(0, 42, 0)).join();
|
||||
entity.setView(-90, 0);
|
||||
|
||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||
entity2.setInstance(instance, new Pos(6, 42, 0)).join();
|
||||
entity2.setBoundingBox(4.0, 2.0, 4.0);
|
||||
|
||||
for (int z = -1; z <= 1; ++z) {
|
||||
for (int y = 40; y <= 44; ++y) {
|
||||
instance.setBlock(5, y, z, Block.STONE);
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(entity2, entity.getLineOfSightEntity(20, (e) -> true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, true));
|
||||
assertTrue(entity.hasLineOfSight(entity2, false));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user