Remove dependency from instance/entity for collisions

This commit is contained in:
themode 2022-03-13 23:28:31 +01:00
parent 60f583b179
commit df64ce9653
5 changed files with 84 additions and 75 deletions

View File

@ -3,11 +3,7 @@ package net.minestom.server.collision;
import net.minestom.server.coordinate.Point; 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.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -20,13 +16,12 @@ final class BlockCollision {
* <p> * <p>
* Works by getting all the full blocks that an entity could interact with. * Works by getting all the full blocks that an entity could interact with.
* All bounding boxes inside the full blocks are checked for collisions with the entity. * All bounding boxes inside the full blocks are checked for collisions with the entity.
*
* @param entity the entity to move
* @return the result of physics simulation
*/ */
static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity, static PhysicsResult handlePhysics(@NotNull BoundingBox boundingBox,
@NotNull Vec entityVelocity, @NotNull Pos entityPosition,
@NotNull Block.Getter getter,
@Nullable PhysicsResult lastPhysicsResult) { @Nullable PhysicsResult lastPhysicsResult) {
final var faces = entity.getBoundingBox().faces(); final var faces = boundingBox.faces();
Vec remainingMove = entityVelocity; Vec remainingMove = entityVelocity;
// Allocate once and update values // Allocate once and update values
@ -39,14 +34,13 @@ final class BlockCollision {
// Check cache to see if the entity is standing on a block without moving. // Check cache to see if the entity is standing on a block without moving.
// If the entity isn't moving and the block below hasn't changed, return // If the entity isn't moving and the block below hasn't changed, return
if (lastPhysicsResult != null && entity.getInstance() != null) { if (lastPhysicsResult != null) {
if (lastPhysicsResult.collisionY() if (lastPhysicsResult.collisionY()
&& Math.signum(remainingMove.y()) == Math.signum(lastPhysicsResult.originalDelta().y()) && Math.signum(remainingMove.y()) == Math.signum(lastPhysicsResult.originalDelta().y())
&& lastPhysicsResult.collidedBlockY() != null && lastPhysicsResult.collidedBlockY() != null
&& entity.getInstance().getChunk(lastPhysicsResult.collidedBlockY().chunkX(), lastPhysicsResult.collidedBlockY().chunkZ()) != null && getter.getBlock(lastPhysicsResult.collidedBlockY(), Block.Getter.Condition.TYPE) == lastPhysicsResult.blockTypeY()
&& entity.getInstance().getBlock(lastPhysicsResult.collidedBlockY(), Block.Getter.Condition.TYPE) == lastPhysicsResult.blockTypeY()
&& remainingMove.x() == 0 && remainingMove.z() == 0 && remainingMove.x() == 0 && remainingMove.z() == 0
&& entity.getPosition().samePoint(lastPhysicsResult.newPosition()) && entityPosition.samePoint(lastPhysicsResult.newPosition())
&& lastPhysicsResult.blockTypeY() != Block.AIR) { && lastPhysicsResult.blockTypeY() != Block.AIR) {
remainingMove = remainingMove.withY(0); remainingMove = remainingMove.withY(0);
foundCollisionY = true; foundCollisionY = true;
@ -65,16 +59,16 @@ final class BlockCollision {
if (remainingMove.isZero()) if (remainingMove.isZero())
if (lastPhysicsResult != null) if (lastPhysicsResult != null)
return new PhysicsResult(entity.getPosition(), Vec.ZERO, lastPhysicsResult.isOnGround(), return new PhysicsResult(entityPosition, Vec.ZERO, lastPhysicsResult.isOnGround(),
lastPhysicsResult.collisionX(), lastPhysicsResult.collisionY(), lastPhysicsResult.collisionZ(), lastPhysicsResult.collisionX(), lastPhysicsResult.collisionY(), lastPhysicsResult.collisionZ(),
entityVelocity, lastPhysicsResult.collidedBlockY(), lastPhysicsResult.blockTypeY()); entityVelocity, lastPhysicsResult.collidedBlockY(), lastPhysicsResult.blockTypeY());
else else
return new PhysicsResult(entity.getPosition(), Vec.ZERO, false, false, false, false, entityVelocity, null, Block.AIR); return new PhysicsResult(entityPosition, Vec.ZERO, false, false, false, false, entityVelocity, null, Block.AIR);
// Query faces to get the points needed for collision // Query faces to get the points needed for collision
Vec[] allFaces = faces.get(new Vec(Math.signum(remainingMove.x()), Math.signum(remainingMove.y()), Math.signum(remainingMove.z()))); Vec[] allFaces = faces.get(new Vec(Math.signum(remainingMove.x()), Math.signum(remainingMove.y()), Math.signum(remainingMove.z())));
PhysicsResult res = handlePhysics(entity, remainingMove, entity.getPosition(), allFaces, finalResult); PhysicsResult res = handlePhysics(boundingBox, remainingMove, entityPosition, getter, allFaces, finalResult);
// Loop until no collisions are found. // Loop until no collisions are found.
// When collisions are found, the collision axis is set to 0 // When collisions are found, the collision axis is set to 0
@ -107,7 +101,7 @@ final class BlockCollision {
allFaces = faces.get(new Vec(Math.signum(remainingMove.x()), Math.signum(remainingMove.y()), Math.signum(remainingMove.z()))); allFaces = faces.get(new Vec(Math.signum(remainingMove.x()), Math.signum(remainingMove.y()), Math.signum(remainingMove.z())));
res = handlePhysics(entity, res.newVelocity(), res.newPosition(), allFaces, finalResult); res = handlePhysics(boundingBox, res.newVelocity(), res.newPosition(), getter, allFaces, finalResult);
} }
final double newDeltaX = foundCollisionX ? 0 : entityVelocity.x(); final double newDeltaX = foundCollisionX ? 0 : entityVelocity.x();
@ -119,21 +113,11 @@ final class BlockCollision {
foundCollisionX, foundCollisionY, foundCollisionZ, entityVelocity, collisionYBlock, blockYType); foundCollisionX, foundCollisionY, foundCollisionZ, entityVelocity, collisionYBlock, blockYType);
} }
/** private static PhysicsResult handlePhysics(@NotNull BoundingBox boundingBox,
* Does a physics step until a boundary is found @NotNull Vec deltaPosition, Pos entityPosition,
* @NotNull Block.Getter getter,
* @param entity the entity to move @NotNull Vec[] allFaces,
* @param deltaPosition the movement vector @NotNull SweepResult finalResult) {
* @param entityPosition the position of the entity
* @param allFaces point list to use for collision checking
* @param finalResult place to store final result of collision
* @return result of physics calculation
*/
private static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec deltaPosition, Pos entityPosition,
@NotNull Vec[] allFaces, @NotNull SweepResult finalResult) {
final Instance instance = entity.getInstance();
final Chunk originChunk = entity.getChunk();
final BoundingBox boundingBox = entity.getBoundingBox();
double remainingX = deltaPosition.x(); double remainingX = deltaPosition.x();
double remainingY = deltaPosition.y(); double remainingY = deltaPosition.y();
@ -153,47 +137,47 @@ final class BlockCollision {
// Checks can be limited by checking if we moved across an axis line // Checks can be limited by checking if we moved across an axis line
// Pass through (0, 0, 0) // Pass through (0, 0, 0)
checkBoundingBox(pointBefore.blockX(), pointBefore.blockY(), pointBefore.blockZ(), deltaPosition, entityPosition, boundingBox, instance, originChunk, finalResult); checkBoundingBox(pointBefore.blockX(), pointBefore.blockY(), pointBefore.blockZ(), deltaPosition, entityPosition, boundingBox, getter, finalResult);
if (pointBefore.blockX() != pointAfter.blockX()) { if (pointBefore.blockX() != pointAfter.blockX()) {
// Pass through (+1, 0, 0) // Pass through (+1, 0, 0)
checkBoundingBox(pointAfter.blockX(), pointBefore.blockY(), pointBefore.blockZ(), deltaPosition, entityPosition, boundingBox, instance, originChunk, finalResult); checkBoundingBox(pointAfter.blockX(), pointBefore.blockY(), pointBefore.blockZ(), deltaPosition, entityPosition, boundingBox, getter, finalResult);
// Checks for moving through 4 blocks // Checks for moving through 4 blocks
if (pointBefore.blockY() != pointAfter.blockY()) if (pointBefore.blockY() != pointAfter.blockY())
// Pass through (+1, +1, 0) // Pass through (+1, +1, 0)
checkBoundingBox(pointAfter.blockX(), pointAfter.blockY(), pointBefore.blockZ(), deltaPosition, entityPosition, boundingBox, instance, originChunk, finalResult); checkBoundingBox(pointAfter.blockX(), pointAfter.blockY(), pointBefore.blockZ(), deltaPosition, entityPosition, boundingBox, getter, finalResult);
if (pointBefore.blockZ() != pointAfter.blockZ()) if (pointBefore.blockZ() != pointAfter.blockZ())
// Pass through (+1, 0, +1) // Pass through (+1, 0, +1)
checkBoundingBox(pointAfter.blockX(), pointBefore.blockY(), pointAfter.blockZ(), deltaPosition, entityPosition, boundingBox, instance, originChunk, finalResult); checkBoundingBox(pointAfter.blockX(), pointBefore.blockY(), pointAfter.blockZ(), deltaPosition, entityPosition, boundingBox, getter, finalResult);
} }
if (pointBefore.blockY() != pointAfter.blockY()) { if (pointBefore.blockY() != pointAfter.blockY()) {
// Pass through (0, +1, 0) // Pass through (0, +1, 0)
checkBoundingBox(pointBefore.blockX(), pointAfter.blockY(), pointBefore.blockZ(), deltaPosition, entityPosition, boundingBox, instance, originChunk, finalResult); checkBoundingBox(pointBefore.blockX(), pointAfter.blockY(), pointBefore.blockZ(), deltaPosition, entityPosition, boundingBox, getter, finalResult);
// Checks for moving through 4 blocks // Checks for moving through 4 blocks
if (pointBefore.blockZ() != pointAfter.blockZ()) if (pointBefore.blockZ() != pointAfter.blockZ())
// Pass through (0, +1, +1) // Pass through (0, +1, +1)
checkBoundingBox(pointBefore.blockX(), pointAfter.blockY(), pointAfter.blockZ(), deltaPosition, entityPosition, boundingBox, instance, originChunk, finalResult); checkBoundingBox(pointBefore.blockX(), pointAfter.blockY(), pointAfter.blockZ(), deltaPosition, entityPosition, boundingBox, getter, finalResult);
} }
if (pointBefore.blockZ() != pointAfter.blockZ()) { if (pointBefore.blockZ() != pointAfter.blockZ()) {
// Pass through (0, 0, +1) // Pass through (0, 0, +1)
checkBoundingBox(pointBefore.blockX(), pointBefore.blockY(), pointAfter.blockZ(), deltaPosition, entityPosition, boundingBox, instance, originChunk, finalResult); checkBoundingBox(pointBefore.blockX(), pointBefore.blockY(), pointAfter.blockZ(), deltaPosition, entityPosition, boundingBox, getter, finalResult);
} }
// Pass through (+1, +1, +1) // Pass through (+1, +1, +1)
if (pointBefore.blockX() != pointAfter.blockX() if (pointBefore.blockX() != pointAfter.blockX()
&& pointBefore.blockY() != pointAfter.blockY() && pointBefore.blockY() != pointAfter.blockY()
&& pointBefore.blockZ() != pointAfter.blockZ()) && pointBefore.blockZ() != pointAfter.blockZ())
checkBoundingBox(pointAfter.blockX(), pointAfter.blockY(), pointAfter.blockZ(), deltaPosition, entityPosition, boundingBox, instance, originChunk, finalResult); checkBoundingBox(pointAfter.blockX(), pointAfter.blockY(), pointAfter.blockZ(), deltaPosition, entityPosition, boundingBox, getter, finalResult);
} }
} else { } else {
// When large moves are done we need to ray-cast to find all blocks that could intersect with the movement // When large moves are done we need to ray-cast to find all blocks that could intersect with the movement
for (Vec point : allFaces) { for (Vec point : allFaces) {
RayUtils.RaycastCollision(deltaPosition, point.add(entityPosition), instance, originChunk, boundingBox, entityPosition, finalResult); RayUtils.RaycastCollision(deltaPosition, point.add(entityPosition), getter, boundingBox, entityPosition, finalResult);
} }
} }
@ -244,22 +228,15 @@ final class BlockCollision {
* @param entityVelocity entity movement vector * @param entityVelocity entity movement vector
* @param entityPosition entity position * @param entityPosition entity position
* @param boundingBox entity bounding box * @param boundingBox entity bounding box
* @param instance entity instance * @param getter block getter
* @param originChunk entity chunk
* @param finalResult place to store final result of collision * @param finalResult place to store final result of collision
* @return true if entity finds collision, other false * @return true if entity finds collision, other false
*/ */
static boolean checkBoundingBox(int blockX, int blockY, int blockZ, static boolean checkBoundingBox(int blockX, int blockY, int blockZ,
Vec entityVelocity, Pos entityPosition, BoundingBox boundingBox, Vec entityVelocity, Pos entityPosition, BoundingBox boundingBox,
Instance instance, Chunk originChunk, SweepResult finalResult) { Block.Getter getter, SweepResult finalResult) {
final Chunk c = ChunkUtils.retrieve(instance, originChunk, blockX, blockZ);
// Don't step if chunk isn't loaded yet // Don't step if chunk isn't loaded yet
final Block checkBlock; final Block checkBlock = getter.getBlock(blockX, blockY, blockZ, Block.Getter.Condition.TYPE);
if (ChunkUtils.isLoaded(c)) {
checkBlock = c.getBlock(blockX, blockY, blockZ, Block.Getter.Condition.TYPE);
} else {
checkBlock = Block.STONE; // Generic full block
}
boolean hitBlock = false; boolean hitBlock = false;
if (checkBlock.isSolid()) { if (checkBlock.isSolid()) {
final Vec blockPos = new Vec(blockX, blockY, blockZ); final Vec blockPos = new Vec(blockX, blockY, blockZ);

View File

@ -3,12 +3,11 @@ package net.minestom.server.collision;
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;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkCache;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -30,7 +29,12 @@ 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) {
return BlockCollision.handlePhysics(entity, entityVelocity, lastPhysicsResult); final BoundingBox boundingBox = entity.getBoundingBox();
final Pos currentPosition = entity.getPosition();
final Block.Getter getter = new ChunkCache(entity.getInstance(), entity.getChunk(), Block.STONE);
return BlockCollision.handlePhysics(boundingBox,
entityVelocity, currentPosition,
getter, lastPhysicsResult);
} }
public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity) { public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity) {

View File

@ -3,20 +3,10 @@ package net.minestom.server.collision;
import net.minestom.server.coordinate.Point; 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.instance.Chunk; import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.Instance;
class RayUtils { final class RayUtils {
/** public static void RaycastCollision(Vec rayDirection, Point rayStart, Block.Getter getter, BoundingBox boundingBox, Pos entityCentre, SweepResult finalResult) {
* @param rayDirection Ray vector
* @param rayStart Ray start point
* @param instance entity instance
* @param originChunk entity chunk
* @param boundingBox entity bounding box
* @param entityCentre position of entity
* @param finalResult place to store final result of collision
*/
public static void RaycastCollision(Vec rayDirection, Point rayStart, Instance instance, Chunk originChunk, 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 // 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 // Finding all the intersections will give us all the full blocks that are traversed by the ray
@ -53,7 +43,7 @@ class RayUtils {
// Check for collisions with the found block // Check for collisions with the found block
// If a collision was found, break // If a collision was found, break
if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, instance, originChunk, finalResult)) if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, getter, finalResult))
break; break;
} }
} }
@ -79,7 +69,7 @@ class RayUtils {
zi -= zFix; zi -= zFix;
zStepsCompleted++; zStepsCompleted++;
if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, instance, originChunk, finalResult)) if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, getter, finalResult))
break; break;
} }
} }
@ -105,7 +95,7 @@ class RayUtils {
yi -= yFix; yi -= yFix;
yStepsCompleted++; yStepsCompleted++;
if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, instance, originChunk, finalResult)) if (BlockCollision.checkBoundingBox(xi, yi, zi, rayDirection, entityCentre, boundingBox, getter, finalResult))
break; break;
} }
} }

View File

@ -53,6 +53,7 @@ import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils; import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockIterator; import net.minestom.server.utils.block.BlockIterator;
import net.minestom.server.utils.chunk.ChunkCache;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
import net.minestom.server.utils.entity.EntityUtils; import net.minestom.server.utils.entity.EntityUtils;
import net.minestom.server.utils.player.PlayerUtils; import net.minestom.server.utils.player.PlayerUtils;
@ -630,8 +631,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
// TODO do not call every tick (it is pretty expensive) // TODO do not call every tick (it is pretty expensive)
final Pos position = this.position; final Pos position = this.position;
final BoundingBox boundingBox = this.boundingBox; final BoundingBox boundingBox = this.boundingBox;
final Instance instance = this.instance; ChunkCache cache = new ChunkCache(instance, currentChunk);
Chunk chunk = currentChunk;
final int minX = (int) Math.floor(boundingBox.minX() + position.x()); final int minX = (int) Math.floor(boundingBox.minX() + position.x());
final int maxX = (int) Math.ceil(boundingBox.maxX() + position.x()); final int maxX = (int) Math.ceil(boundingBox.maxX() + position.x());
@ -643,9 +643,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
for (int y = minY; y <= maxY; y++) { for (int y = minY; y <= maxY; y++) {
for (int x = minX; x <= maxX; x++) { for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) { for (int z = minZ; z <= maxZ; z++) {
chunk = ChunkUtils.retrieve(instance, chunk, x, z); final Block block = cache.getBlock(x, y, z, Block.Getter.Condition.CACHED);
if (!ChunkUtils.isLoaded(chunk)) continue;
final Block block = chunk.getBlock(x, y, z, Block.Getter.Condition.CACHED);
if (block == null) continue; if (block == null) continue;
final BlockHandler handler = block.handler(); final BlockHandler handler = block.handler();
if (handler != null) { if (handler != null) {

View File

@ -0,0 +1,40 @@
package net.minestom.server.utils.chunk;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import static net.minestom.server.utils.chunk.ChunkUtils.getChunkCoordinate;
@ApiStatus.Internal
public final class ChunkCache implements Block.Getter {
private final Instance instance;
private Chunk chunk;
private final Block defaultBlock;
public ChunkCache(Instance instance, Chunk chunk,
Block defaultBlock) {
this.instance = instance;
this.chunk = chunk;
this.defaultBlock = defaultBlock;
}
public ChunkCache(Instance instance, Chunk chunk) {
this(instance, chunk, Block.AIR);
}
@Override
public @UnknownNullability Block getBlock(int x, int y, int z, @NotNull Condition condition) {
Chunk chunk = this.chunk;
final int chunkX = getChunkCoordinate(x);
final int chunkZ = getChunkCoordinate(z);
if (chunk == null || chunk.getChunkX() != chunkX || chunk.getChunkZ() != chunkZ) {
this.chunk = chunk = this.instance.getChunk(chunkX, chunkZ);
}
return chunk != null ? chunk.getBlock(x, y, z, condition) : defaultBlock;
}
}