mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-26 02:57:37 +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
|
* See https://wiki.vg/Entity_metadata#Mobs_2
|
||||||
*/
|
*/
|
||||||
public final class BoundingBox implements Shape {
|
public final class BoundingBox implements Shape {
|
||||||
|
|
||||||
|
final static BoundingBox ZERO = new BoundingBox(0, 0, 0);
|
||||||
|
|
||||||
private final double width, height, depth;
|
private final double width, height, depth;
|
||||||
private final Point offset;
|
private final Point offset;
|
||||||
private Point relativeEnd;
|
private Point relativeEnd;
|
||||||
|
@ -4,6 +4,7 @@ import net.minestom.server.coordinate.Point;
|
|||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
import net.minestom.server.coordinate.Vec;
|
import net.minestom.server.coordinate.Vec;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
|
import net.minestom.server.instance.Chunk;
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
import net.minestom.server.instance.WorldBorder;
|
import net.minestom.server.instance.WorldBorder;
|
||||||
import net.minestom.server.instance.block.Block;
|
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,
|
public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity,
|
||||||
@Nullable PhysicsResult lastPhysicsResult) {
|
@Nullable PhysicsResult lastPhysicsResult) {
|
||||||
final BoundingBox boundingBox = entity.getBoundingBox();
|
assert entity.getInstance() != null;
|
||||||
final Pos currentPosition = entity.getPosition();
|
return handlePhysics(entity.getInstance(), entity.getChunk(),
|
||||||
final Block.Getter getter = new ChunkCache(entity.getInstance(), entity.getChunk(), Block.STONE);
|
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,
|
return BlockCollision.handlePhysics(boundingBox,
|
||||||
entityVelocity, currentPosition,
|
velocity, position,
|
||||||
getter, lastPhysicsResult);
|
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) {
|
public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity) {
|
||||||
return handlePhysics(entity, entityVelocity, null);
|
return handlePhysics(entity, entityVelocity, null);
|
||||||
}
|
}
|
||||||
|
@ -345,7 +345,6 @@ final class RayUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean BoundingBoxRayIntersectionCheck(Vec start, Vec direction, BoundingBox boundingBox, Pos position) {
|
public static boolean BoundingBoxRayIntersectionCheck(Vec start, Vec direction, BoundingBox boundingBox, Pos position) {
|
||||||
// TODO: BoundingBox.ZERO?
|
return BoundingBoxIntersectionCheck(BoundingBox.ZERO, start, direction, boundingBox, position);
|
||||||
return BoundingBoxIntersectionCheck(new BoundingBox(0, 0, 0), start, direction, boundingBox, position);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1610,21 +1610,35 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the current entity has line of sight to the given one.
|
* Raycasts current entity's eye position to target eye position.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* @param entity the entity to be checked.
|
* @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();
|
Instance instance = getInstance();
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Vec start = new Vec(position.x(), position.y() + getEyeHeight(), position.z());
|
final Pos start = position.withY(position.y() + getEyeHeight());
|
||||||
return entity.boundingBox.boundingBoxRayIntersectionCheck(start, position.direction(), entity.getPosition());
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1640,12 +1654,16 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
|||||||
return null;
|
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()
|
Optional<Entity> nearby = instance.getNearbyEntities(position, range).stream()
|
||||||
.filter(e -> e != this
|
.filter(finalPredicate)
|
||||||
&& e.boundingBox.boundingBoxRayIntersectionCheck(start, position.direction(), e.getPosition())
|
|
||||||
&& predicate.test(e))
|
|
||||||
.min(Comparator.comparingDouble(e -> e.getDistance(this.position)));
|
.min(Comparator.comparingDouble(e -> e.getDistance(this.position)));
|
||||||
|
|
||||||
return nearby.orElse(null);
|
return nearby.orElse(null);
|
||||||
|
@ -3,10 +3,10 @@ package net.minestom.server.entity;
|
|||||||
import net.minestom.server.api.Env;
|
import net.minestom.server.api.Env;
|
||||||
import net.minestom.server.api.EnvTest;
|
import net.minestom.server.api.EnvTest;
|
||||||
import net.minestom.server.coordinate.Pos;
|
import net.minestom.server.coordinate.Pos;
|
||||||
|
import net.minestom.server.instance.block.Block;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
|
|
||||||
@EnvTest
|
@EnvTest
|
||||||
public class EntityLineOfSightIntegrationTest {
|
public class EntityLineOfSightIntegrationTest {
|
||||||
@ -21,8 +21,17 @@ public class EntityLineOfSightIntegrationTest {
|
|||||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||||
entity2.setInstance(instance, new Pos(10, 42, 0)).join();
|
entity2.setInstance(instance, new Pos(10, 42, 0)).join();
|
||||||
|
|
||||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
assertEquals(entity2, entity.getLineOfSightEntity(20, (e) -> true));
|
||||||
assertEquals(res, entity2);
|
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
|
@Test
|
||||||
@ -36,8 +45,17 @@ public class EntityLineOfSightIntegrationTest {
|
|||||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||||
entity2.setInstance(instance, new Pos(-10, 42, 0)).join();
|
entity2.setInstance(instance, new Pos(-10, 42, 0)).join();
|
||||||
|
|
||||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
assertNull(entity.getLineOfSightEntity(20, (e) -> true));
|
||||||
assertNull(res);
|
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
|
@Test
|
||||||
@ -51,8 +69,17 @@ public class EntityLineOfSightIntegrationTest {
|
|||||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||||
entity2.setInstance(instance, new Pos(10, 42, 0.31)).join();
|
entity2.setInstance(instance, new Pos(10, 42, 0.31)).join();
|
||||||
|
|
||||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
assertNull(entity.getLineOfSightEntity(20, (e) -> true));
|
||||||
assertNull(res);
|
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
|
@Test
|
||||||
@ -66,8 +93,19 @@ public class EntityLineOfSightIntegrationTest {
|
|||||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||||
entity2.setInstance(instance, new Pos(10, 42, 0.3)).join();
|
entity2.setInstance(instance, new Pos(10, 42, 0.3)).join();
|
||||||
|
|
||||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
assertEquals(entity2, entity.getLineOfSightEntity(20, (e) -> true));
|
||||||
assertEquals(res, entity2);
|
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
|
@Test
|
||||||
@ -84,8 +122,11 @@ public class EntityLineOfSightIntegrationTest {
|
|||||||
var entity3 = new Entity(EntityTypes.ZOMBIE);
|
var entity3 = new Entity(EntityTypes.ZOMBIE);
|
||||||
entity3.setInstance(instance, new Pos(5, 42, 0)).join();
|
entity3.setInstance(instance, new Pos(5, 42, 0)).join();
|
||||||
|
|
||||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
assertEquals(entity3, entity.getLineOfSightEntity(20, (e) -> true));
|
||||||
assertEquals(res, entity3);
|
assertTrue(entity.hasLineOfSight(entity2, true));
|
||||||
|
assertTrue(entity.hasLineOfSight(entity2, false));
|
||||||
|
assertTrue(entity.hasLineOfSight(entity3, true));
|
||||||
|
assertTrue(entity.hasLineOfSight(entity3, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -99,7 +140,30 @@ public class EntityLineOfSightIntegrationTest {
|
|||||||
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
var entity2 = new Entity(EntityTypes.ZOMBIE);
|
||||||
entity2.setInstance(instance, new Pos(10, 42, 10)).join();
|
entity2.setInstance(instance, new Pos(10, 42, 10)).join();
|
||||||
|
|
||||||
Entity res = entity.getLineOfSightEntity(20, (e) -> true);
|
assertNull(entity.getLineOfSightEntity(20, (e) -> true));
|
||||||
assertNull(res);
|
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