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.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.block.Block;
import net.minestom.server.utils.chunk.ChunkUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -20,13 +16,12 @@ final class BlockCollision {
* <p>
* 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.
*
* @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) {
final var faces = entity.getBoundingBox().faces();
final var faces = boundingBox.faces();
Vec remainingMove = entityVelocity;
// 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.
// 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()
&& Math.signum(remainingMove.y()) == Math.signum(lastPhysicsResult.originalDelta().y())
&& lastPhysicsResult.collidedBlockY() != null
&& entity.getInstance().getChunk(lastPhysicsResult.collidedBlockY().chunkX(), lastPhysicsResult.collidedBlockY().chunkZ()) != null
&& entity.getInstance().getBlock(lastPhysicsResult.collidedBlockY(), Block.Getter.Condition.TYPE) == lastPhysicsResult.blockTypeY()
&& getter.getBlock(lastPhysicsResult.collidedBlockY(), Block.Getter.Condition.TYPE) == lastPhysicsResult.blockTypeY()
&& remainingMove.x() == 0 && remainingMove.z() == 0
&& entity.getPosition().samePoint(lastPhysicsResult.newPosition())
&& entityPosition.samePoint(lastPhysicsResult.newPosition())
&& lastPhysicsResult.blockTypeY() != Block.AIR) {
remainingMove = remainingMove.withY(0);
foundCollisionY = true;
@ -65,16 +59,16 @@ final class BlockCollision {
if (remainingMove.isZero())
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(),
entityVelocity, lastPhysicsResult.collidedBlockY(), lastPhysicsResult.blockTypeY());
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
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.
// 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())));
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();
@ -119,21 +113,11 @@ final class BlockCollision {
foundCollisionX, foundCollisionY, foundCollisionZ, entityVelocity, collisionYBlock, blockYType);
}
/**
* Does a physics step until a boundary is found
*
* @param entity the entity to move
* @param deltaPosition the movement vector
* @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();
private static PhysicsResult handlePhysics(@NotNull BoundingBox boundingBox,
@NotNull Vec deltaPosition, Pos entityPosition,
@NotNull Block.Getter getter,
@NotNull Vec[] allFaces,
@NotNull SweepResult finalResult) {
double remainingX = deltaPosition.x();
double remainingY = deltaPosition.y();
@ -153,47 +137,47 @@ final class BlockCollision {
// Checks can be limited by checking if we moved across an axis line
// 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()) {
// 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
if (pointBefore.blockY() != pointAfter.blockY())
// 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())
// 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()) {
// 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
if (pointBefore.blockZ() != pointAfter.blockZ())
// 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()) {
// 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)
if (pointBefore.blockX() != pointAfter.blockX()
&& pointBefore.blockY() != pointAfter.blockY()
&& 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 {
// When large moves are done we need to ray-cast to find all blocks that could intersect with the movement
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 entityPosition entity position
* @param boundingBox entity bounding box
* @param instance entity instance
* @param originChunk entity chunk
* @param getter block getter
* @param finalResult place to store final result of collision
* @return true if entity finds collision, other false
*/
static boolean checkBoundingBox(int blockX, int blockY, int blockZ,
Vec entityVelocity, Pos entityPosition, BoundingBox boundingBox,
Instance instance, Chunk originChunk, SweepResult finalResult) {
final Chunk c = ChunkUtils.retrieve(instance, originChunk, blockX, blockZ);
Block.Getter getter, SweepResult finalResult) {
// Don't step if chunk isn't loaded yet
final Block checkBlock;
if (ChunkUtils.isLoaded(c)) {
checkBlock = c.getBlock(blockX, blockY, blockZ, Block.Getter.Condition.TYPE);
} else {
checkBlock = Block.STONE; // Generic full block
}
final Block checkBlock = getter.getBlock(blockX, blockY, blockZ, Block.Getter.Condition.TYPE);
boolean hitBlock = false;
if (checkBlock.isSolid()) {
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.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;
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.NotNull;
import org.jetbrains.annotations.Nullable;
@ -30,7 +29,12 @@ public final class CollisionUtils {
*/
public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity,
@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) {

View File

@ -3,20 +3,10 @@ package net.minestom.server.collision;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
class RayUtils {
/**
* @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) {
final class RayUtils {
public static void RaycastCollision(Vec rayDirection, Point rayStart, Block.Getter getter, 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
// 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
// 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;
}
}
@ -79,7 +69,7 @@ class RayUtils {
zi -= zFix;
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;
}
}
@ -105,7 +95,7 @@ class RayUtils {
yi -= yFix;
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;
}
}

View File

@ -53,6 +53,7 @@ import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils;
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.entity.EntityUtils;
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)
final Pos position = this.position;
final BoundingBox boundingBox = this.boundingBox;
final Instance instance = this.instance;
Chunk chunk = currentChunk;
ChunkCache cache = new ChunkCache(instance, currentChunk);
final int minX = (int) Math.floor(boundingBox.minX() + 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 x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
chunk = ChunkUtils.retrieve(instance, chunk, x, z);
if (!ChunkUtils.isLoaded(chunk)) continue;
final Block block = chunk.getBlock(x, y, z, Block.Getter.Condition.CACHED);
final Block block = cache.getBlock(x, y, z, Block.Getter.Condition.CACHED);
if (block == null) continue;
final BlockHandler handler = block.handler();
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;
}
}