mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-30 21:17:53 +01:00
Merge pull request #428 from RinesThaix/getLineOfSightEntity
Entity#getLineOfSightEntity and BoundingBox#intersect(Point, Point)
This commit is contained in:
commit
1b55bd0137
@ -102,16 +102,111 @@ public class BoundingBox {
|
||||
return intersectWithBlock(blockPosition.blockX(), blockPosition.blockY(), blockPosition.blockZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to know if the bounding box intersects (contains) a point.
|
||||
*
|
||||
* @param x x-coord of a point
|
||||
* @param y y-coord of a point
|
||||
* @param z z-coord of a point
|
||||
* @return true if the bounding box intersects (contains) with the point, false otherwise
|
||||
*/
|
||||
public boolean intersect(double x, double y, double z) {
|
||||
return (x >= getMinX() && x <= getMaxX()) &&
|
||||
(y >= getMinY() && y <= getMaxY()) &&
|
||||
(z >= getMinZ() && z <= getMaxZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to know if the bounding box intersects (contains) a point.
|
||||
*
|
||||
* @param point the point to check
|
||||
* @return true if the bounding box intersects (contains) with the point, false otherwise
|
||||
*/
|
||||
public boolean intersect(@NotNull Point point) {
|
||||
return intersect(point.x(), point.y(), point.z());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to know if the bounding box intersects a line segment.
|
||||
*
|
||||
* @param x1 x-coord of first line segment point
|
||||
* @param y1 y-coord of first line segment point
|
||||
* @param z1 z-coord of first line segment point
|
||||
* @param x2 x-coord of second line segment point
|
||||
* @param y2 y-coord of second line segment point
|
||||
* @param z2 z-coord of second line segment point
|
||||
* @return true if the bounding box intersects with the line segment, false otherwise.
|
||||
*/
|
||||
public boolean intersect(double x1, double y1, double z1, double x2, double y2, double z2) {
|
||||
// originally from http://www.3dkingdoms.com/weekly/weekly.php?a=3
|
||||
|
||||
double x3 = getMinX();
|
||||
double x4 = getMaxX();
|
||||
double y3 = getMinY();
|
||||
double y4 = getMaxY();
|
||||
double z3 = getMinZ();
|
||||
double z4 = getMaxZ();
|
||||
if (x1 > x3 && x1 < x4 && y1 > y3 && y1 < y4 && z1 > z3 && z1 < z4) {
|
||||
return true;
|
||||
}
|
||||
if (x1 < x3 && x2 < x3 || x1 > x4 && x2 > x4 ||
|
||||
y1 < y3 && y2 < y3 || y1 > y4 && y2 > y4 ||
|
||||
z1 < z3 && z2 < z3 || z1 > z4 && z2 > z4) {
|
||||
return false;
|
||||
}
|
||||
return isInsideBoxWithAxis(Axis.X, getSegmentIntersection(x1 - x3, x2 - x3, x1, y1, z1, x2, y2, z2)) ||
|
||||
isInsideBoxWithAxis(Axis.X, getSegmentIntersection(x1 - x4, x2 - x4, x1, y1, z1, x2, y2, z2)) ||
|
||||
isInsideBoxWithAxis(Axis.Y, getSegmentIntersection(y1 - y3, y2 - y3, x1, y1, z1, x2, y2, z2)) ||
|
||||
isInsideBoxWithAxis(Axis.Y, getSegmentIntersection(y1 - y4, y2 - y4, x1, y1, z1, x2, y2, z2)) ||
|
||||
isInsideBoxWithAxis(Axis.Z, getSegmentIntersection(z1 - z3, z2 - z3, x1, y1, z1, x2, y2, z2)) ||
|
||||
isInsideBoxWithAxis(Axis.Z, getSegmentIntersection(z1 - z4, z2 - z4, x1, y1, z1, x2, y2, z2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to know if the bounding box intersects a line segment.
|
||||
*
|
||||
* @param start first line segment point
|
||||
* @param end second line segment point
|
||||
* @return true if the bounding box intersects with the line segment, false otherwise.
|
||||
*/
|
||||
public boolean intersect(@NotNull Point start, @NotNull Point end) {
|
||||
return intersect(
|
||||
Math.min(start.x(), end.x()),
|
||||
Math.min(start.y(), end.y()),
|
||||
Math.min(start.z(), end.z()),
|
||||
Math.max(start.x(), end.x()),
|
||||
Math.max(start.y(), end.y()),
|
||||
Math.max(start.z(), end.z())
|
||||
);
|
||||
}
|
||||
|
||||
private @Nullable Vec getSegmentIntersection(double dst1, double dst2, double x1, double y1, double z1, double x2, double y2, double z2) {
|
||||
if (dst1 == dst2 || dst1 * dst2 >= 0D) {
|
||||
return null;
|
||||
}
|
||||
double delta = dst1 / (dst1 - dst2);
|
||||
return new Vec(
|
||||
x1 + (x2 - x1) * delta,
|
||||
y1 + (y2 - y1) * delta,
|
||||
z1 + (z2 - z1) * delta
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isInsideBoxWithAxis(Axis axis, @Nullable Vec intersection) {
|
||||
if (intersection == null) {
|
||||
return false;
|
||||
}
|
||||
double x1 = getMinX();
|
||||
double x2 = getMaxX();
|
||||
double y1 = getMinY();
|
||||
double y2 = getMaxY();
|
||||
double z1 = getMinZ();
|
||||
double z2 = getMaxZ();
|
||||
return axis == Axis.X && intersection.z() > z1 && intersection.z() < z2 && intersection.y() > y1 && intersection.y() < y2 ||
|
||||
axis == Axis.Y && intersection.z() > z1 && intersection.z() < z2 && intersection.x() > x1 && intersection.x() < x2 ||
|
||||
axis == Axis.Z && intersection.x() > x1 && intersection.x() < x2 && intersection.y() > y1 && intersection.y() < y2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link BoundingBox} linked to the same {@link Entity} with expanded size.
|
||||
*
|
||||
@ -324,4 +419,9 @@ public class BoundingBox {
|
||||
this.lastPosition = entityPos;
|
||||
return vecSupplier.get();
|
||||
}
|
||||
|
||||
private enum Axis {
|
||||
X, Y, Z
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import net.minestom.server.tag.Tag;
|
||||
import net.minestom.server.tag.TagHandler;
|
||||
import net.minestom.server.thread.ThreadProvider;
|
||||
import net.minestom.server.utils.async.AsyncUtils;
|
||||
import net.minestom.server.utils.block.BlockIterator;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.entity.EntityUtils;
|
||||
import net.minestom.server.utils.player.PlayerUtils;
|
||||
@ -52,7 +53,9 @@ import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Could be a player, a monster, or an object.
|
||||
@ -1452,6 +1455,111 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the line of sight of the entity.
|
||||
*
|
||||
* @param maxDistance The max distance to scan
|
||||
* @return A list of {@link Point poiints} in this entities line of sight
|
||||
*/
|
||||
public List<Point> getLineOfSight(int maxDistance) {
|
||||
Instance instance = getInstance();
|
||||
if (instance == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Point> blocks = new ArrayList<>();
|
||||
var it = new BlockIterator(this, maxDistance);
|
||||
while (it.hasNext()) {
|
||||
final Point position = it.next();
|
||||
if (!instance.getBlock(position).isAir()) blocks.add(position);
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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) {
|
||||
Instance instance = getInstance();
|
||||
if (instance == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Vec start = getPosition().asVec().add(0D, getEyeHeight(), 0D);
|
||||
final Vec end = entity.getPosition().asVec().add(0D, getEyeHeight(), 0D);
|
||||
final Vec direction = end.sub(start);
|
||||
final int maxDistance = (int) Math.ceil(direction.length());
|
||||
|
||||
var it = new BlockIterator(start, direction.normalize(), 0D, maxDistance);
|
||||
while (it.hasNext()) {
|
||||
Block block = instance.getBlock(it.next());
|
||||
if (!block.isAir() && !block.isLiquid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 predicate optional predicate
|
||||
* @return resulting entity whether there're any, null otherwise.
|
||||
*/
|
||||
public @Nullable Entity getLineOfSightEntity(double range, Predicate<Entity> predicate) {
|
||||
Instance instance = getInstance();
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Vec start = new Vec(position.x(), position.y() + getEyeHeight(), position.z());
|
||||
Vec end = start.add(position.direction().mul(range));
|
||||
|
||||
List<Entity> nearby = instance.getNearbyEntities(position, range).stream()
|
||||
.filter(e -> e != this && e.boundingBox.intersect(start, end) && predicate.test(e))
|
||||
.collect(Collectors.toList());
|
||||
if (nearby.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Vec direction = end.sub(start);
|
||||
int maxDistance = (int) Math.ceil(direction.length());
|
||||
double maxVisibleDistanceSquared = direction.lengthSquared();
|
||||
|
||||
var iterator = new BlockIterator(start, direction.normalize(), 0D, maxDistance);
|
||||
while (iterator.hasNext()) {
|
||||
Point blockPos = iterator.next();
|
||||
Block block = instance.getBlock(blockPos);
|
||||
if (!block.isAir() && !block.isLiquid()) {
|
||||
maxVisibleDistanceSquared = blockPos.distanceSquared(position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Entity result = null;
|
||||
double minDistanceSquared = 0D;
|
||||
for (Entity entity : nearby) {
|
||||
double distanceSquared = entity.getDistanceSquared(this);
|
||||
if (result == null || minDistanceSquared > distanceSquared) {
|
||||
result = entity;
|
||||
minDistanceSquared = distanceSquared;
|
||||
}
|
||||
}
|
||||
if (minDistanceSquared < maxVisibleDistanceSquared) {
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Pose {
|
||||
STANDING,
|
||||
FALL_FLYING,
|
||||
|
@ -37,6 +37,7 @@ import java.time.Duration;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class LivingEntity extends Entity implements EquipmentHandler {
|
||||
|
||||
@ -711,46 +712,6 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||
return team;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line of sight of the entity.
|
||||
*
|
||||
* @param maxDistance The max distance to scan
|
||||
* @return A list of {@link Point poiints} in this entities line of sight
|
||||
*/
|
||||
public List<Point> getLineOfSight(int maxDistance) {
|
||||
List<Point> blocks = new ArrayList<>();
|
||||
Iterator<Point> it = new BlockIterator(this, maxDistance);
|
||||
while (it.hasNext()) {
|
||||
final Point position = it.next();
|
||||
if (!getInstance().getBlock(position).isAir()) blocks.add(position);
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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) {
|
||||
final var start = getPosition().asVec().add(0D, getEyeHeight(), 0D);
|
||||
final var end = entity.getPosition().asVec().add(0D, getEyeHeight(), 0D);
|
||||
final var direction = end.sub(start);
|
||||
final int maxDistance = (int) Math.ceil(direction.length());
|
||||
|
||||
Iterator<Point> it = new BlockIterator(start, direction.normalize(), 0D, maxDistance);
|
||||
while (it.hasNext()) {
|
||||
Block block = getInstance().getBlock(it.next());
|
||||
if (!block.isAir() && !block.isLiquid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the target (not-air) block position of the entity.
|
||||
*
|
||||
|
@ -489,6 +489,32 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
|
||||
return Collections.unmodifiableSet(entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets nearby entities to the given position.
|
||||
*
|
||||
* @param point position to look at
|
||||
* @param range max range from the given point to collect entities at
|
||||
* @return entities that are not further than the specified distance from the transmitted position.
|
||||
*/
|
||||
public @NotNull Collection<Entity> getNearbyEntities(@NotNull Point point, double range) {
|
||||
int minX = ChunkUtils.getChunkCoordinate(point.x() - range);
|
||||
int maxX = ChunkUtils.getChunkCoordinate(point.x() + range);
|
||||
int minZ = ChunkUtils.getChunkCoordinate(point.z() - range);
|
||||
int maxZ = ChunkUtils.getChunkCoordinate(point.z() + range);
|
||||
List<Entity> result = new ArrayList<>();
|
||||
synchronized (entitiesLock) {
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
for (int z = minZ; z <= maxZ; ++z) {
|
||||
Chunk chunk = getChunk(x, z);
|
||||
if (chunk != null) {
|
||||
result.addAll(getChunkEntities(chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Block getBlock(int x, int y, int z, @NotNull Condition condition) {
|
||||
final Chunk chunk = getChunkAt(x, z);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.utils.block;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.LivingEntity;
|
||||
import net.minestom.server.instance.block.BlockFace;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
@ -268,7 +269,7 @@ public class BlockIterator implements Iterator<Point> {
|
||||
* unloaded chunks. A value of 0 indicates no limit
|
||||
*/
|
||||
|
||||
public BlockIterator(@NotNull LivingEntity entity, int maxDistance) {
|
||||
public BlockIterator(@NotNull Entity entity, int maxDistance) {
|
||||
this(entity.getPosition(), entity.getEyeHeight(), maxDistance);
|
||||
}
|
||||
|
||||
@ -280,7 +281,7 @@ public class BlockIterator implements Iterator<Point> {
|
||||
* @param entity Information from the entity is used to set up the trace
|
||||
*/
|
||||
|
||||
public BlockIterator(@NotNull LivingEntity entity) {
|
||||
public BlockIterator(@NotNull Entity entity) {
|
||||
this(entity, 0);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user