Last active
July 10, 2021 10:23
-
-
Save Cadiboo/5c1d72fcb9d891a2e64119aaecce5de7 to your computer and use it in GitHub Desktop.
Minecraft's collisions are hard to wrap my head around
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Entity { | |
//... | |
// AKA 'adjustMovementForCollisions' | |
public static Vector3d collideBoundingBoxHeuristically(@Nullable Entity entity, Vector3d motion, AxisAlignedBB aabb, World world, ISelectionContext ctx, ReuseableStream<VoxelShape> nonBlockShapes) { | |
// 'nonBlockShapes' may include the shapes of other entities and the world border | |
boolean xStatic = motion.x == 0.0D; | |
boolean yStatic = motion.y == 0.0D; | |
boolean zStatic = motion.z == 0.0D; | |
boolean xOrYMoving = !xStatic || !yStatic; | |
boolean xOrZMoving = !xStatic || !zStatic; | |
boolean yOrZMoving = !yStatic || !zStatic; | |
if (xOrYMoving && xOrZMoving && yOrZMoving) { | |
ReuseableStream<VoxelShape> allShapes = new ReuseableStream<>(Stream.concat(nonBlockShapes.getStream(), world.getBlockCollisions(entity, aabb.expandTowards(motion)))); | |
return collideBoundingBoxLegacy(motion, aabb, allShapes); | |
} else | |
return collideBoundingBox(motion, aabb, world, ctx, nonBlockShapes); | |
} | |
public static Vector3d collideBoundingBoxLegacy(Vector3d motion, AxisAlignedBB aabb, ReuseableStream<VoxelShape> allShapes) { | |
double motionX = motion.x; | |
double motionY = motion.y; | |
double motionZ = motion.z; | |
if (motionY != 0.0D) { | |
motionY = VoxelShapes.collide(Direction.Axis.Y, aabb, allShapes.getStream(), motionY); | |
if (motionY != 0.0D) | |
aabb = aabb.move(0.0D, motionY, 0.0D); | |
} | |
// Runs the collision check for the axis (X/Z) with the greatest motion first | |
boolean motionMoreTowardsZ = Math.abs(motionX) < Math.abs(motionZ); | |
if (motionMoreTowardsZ && motionZ != 0.0D) { | |
motionZ = VoxelShapes.collide(Direction.Axis.Z, aabb, allShapes.getStream(), motionZ); | |
if (motionZ != 0.0D) | |
aabb = aabb.move(0.0D, 0.0D, motionZ); | |
} | |
if (motionX != 0.0D) { | |
motionX = VoxelShapes.collide(Direction.Axis.X, aabb, allShapes.getStream(), motionX); | |
if (!motionMoreTowardsZ && motionX != 0.0D) | |
aabb = aabb.move(motionX, 0.0D, 0.0D); | |
} | |
if (!motionMoreTowardsZ && motionZ != 0.0D) | |
motionZ = VoxelShapes.collide(Direction.Axis.Z, aabb, allShapes.getStream(), motionZ); | |
return new Vector3d(motionX, motionY, motionZ); | |
} | |
public static Vector3d collideBoundingBox(Vector3d motion, AxisAlignedBB aabb, IWorldReader world, ISelectionContext ctx, ReuseableStream<VoxelShape> nonBlockShapes) { | |
double motionX = motion.x; | |
double motionY = motion.y; | |
double motionZ = motion.z; | |
if (motionY != 0.0D) { | |
motionY = VoxelShapes.collide(Direction.Axis.Y, aabb, world, motionY, ctx, nonBlockShapes.getStream()); | |
if (motionY != 0.0D) | |
aabb = aabb.move(0.0D, motionY, 0.0D); | |
} | |
// Runs the collision check for the axis (X/Z) with the greatest motion first | |
boolean motionMoreTowardsZ = Math.abs(motionX) < Math.abs(motionZ); | |
if (motionMoreTowardsZ && motionZ != 0.0D) { | |
motionZ = VoxelShapes.collide(Direction.Axis.Z, aabb, world, motionZ, ctx, nonBlockShapes.getStream()); | |
if (motionZ != 0.0D) | |
aabb = aabb.move(0.0D, 0.0D, motionZ); | |
} | |
if (motionX != 0.0D) { | |
motionX = VoxelShapes.collide(Direction.Axis.X, aabb, world, motionX, ctx, nonBlockShapes.getStream()); | |
if (!motionMoreTowardsZ && motionX != 0.0D) | |
aabb = aabb.move(motionX, 0.0D, 0.0D); | |
} | |
if (!motionMoreTowardsZ && motionZ != 0.0D) | |
motionZ = VoxelShapes.collide(Direction.Axis.Z, aabb, world, motionZ, ctx, nonBlockShapes.getStream()); | |
return new Vector3d(motionX, motionY, motionZ); | |
} | |
//... | |
} | |
public final class VoxelShapes { | |
//... | |
public static double collide(Direction.Axis axis, AxisAlignedBB aabb, Stream<VoxelShape> allShapes, double motion) { | |
Iterator<VoxelShape> iterator = allShapes.iterator(); | |
while (iterator.hasNext()) { | |
if (Math.abs(motion) < 0.0000001) | |
return 0.0D; | |
motion = iterator.next().collide(axis, aabb, motion); | |
} | |
return motion; | |
} | |
public static double collide(Direction.Axis axis, AxisAlignedBB aabb, IWorldReader world, double motion, ISelectionContext ctx, Stream<VoxelShape> nonBlockShapes) { | |
return collide(aabb, world, motion, ctx, AxisRotation.between(axis, Direction.Axis.Z), nonBlockShapes); | |
} | |
// This code is *very* similar to the code in 'VoxelShapeSpliterator#collisionCheck' | |
private static double collide(AxisAlignedBB aabb, IWorldReader world, double motion, ISelectionContext ctx, AxisRotation _rotation_, Stream<VoxelShape> nonBlockShapes) { | |
if (aabb.getXsize() < 0.000001 || aabb.getYsize() < 0.000001 || aabb.getZsize() < 0.000001) | |
return motion; | |
if (Math.abs(motion) < 0.0000001) | |
return 0.0D; | |
AxisRotation _inverse_ = _rotation_.inverse(); | |
Direction.Axis cycledX = _inverse_.cycle(Direction.Axis.X); | |
Direction.Axis cycledY = _inverse_.cycle(Direction.Axis.Y); | |
Direction.Axis cycledZ = _inverse_.cycle(Direction.Axis.Z); | |
BlockPos.Mutable pos = new BlockPos.Mutable(); | |
int minX = MathHelper.floor(aabb.min(cycledX) - 0.0000001) - 1; | |
int maxX = MathHelper.floor(aabb.max(cycledX) + 0.0000001) + 1; | |
int minY = MathHelper.floor(aabb.min(cycledY) - 0.0000001) - 1; | |
int maxY = MathHelper.floor(aabb.max(cycledY) + 0.0000001) + 1; | |
double d0 = aabb.min(cycledZ) - 0.0000001; | |
double d1 = aabb.max(cycledZ) + 0.0000001; | |
boolean motionInitiallyPositive = motion > 0.0D; | |
int minZ = motionInitiallyPositive ? MathHelper.floor(aabb.max(cycledZ) - 0.0000001) - 1 : MathHelper.floor(aabb.min(cycledZ) + 0.0000001) + 1; | |
int maxZ = lastC(motion, d0, d1); | |
int zIncrement = motionInitiallyPositive ? 1 : -1; | |
int z = minZ; | |
// // Handle collisions for smooth blocks | |
// motion = io.github.cadiboo.nocubes.hooks.Hooks.collide(aabb, world, motion, ctx, _rotation_, _inverse_, pos, minX, maxX, minY, maxY, minZ, maxZ); | |
// if (Math.abs(motion) < 1.0E-7D) | |
// return 0.0D; | |
while (true) { | |
if (motionInitiallyPositive) { | |
if (z > maxZ) | |
break; | |
} else if (z < maxZ) | |
break; | |
for (int x = minX; x <= maxX; ++x) { | |
for (int y = minY; y <= maxY; ++y) { | |
int boundariesTouched = 0; | |
if (x == minX || x == maxX) | |
++boundariesTouched; | |
if (y == minY || y == maxY) | |
++boundariesTouched; | |
if (z == minZ || z == maxZ) | |
++boundariesTouched; | |
if (boundariesTouched >= 3) | |
continue; | |
pos.set(_inverse_, x, y, z); | |
BlockState blockstate = world.getBlockState(pos); | |
// // Cancel collisions for smooth blocks (we we handle them ourselves elsewhere) | |
// if (!io.github.cadiboo.nocubes.hooks.Hooks.canBlockStateCollide(blockstate)) | |
// continue; | |
if (boundariesTouched == 1 && !blockstate.hasLargeCollisionShape()) | |
continue; | |
if (boundariesTouched == 2 && !blockstate.is(Blocks.MOVING_PISTON)) | |
continue; | |
// 'getCollisionShape' returns a non-offset shape (e.g. (0, 0, 0) -> (1, 1, 1) for a full block) | |
// Instead of offsetting the shape by pos to make it be in world-space, the | |
// bounding box is offset negatively which has the same effect for the comparison | |
// NB: 'cycledZ' is the same Axis that was originally passed in to our caller 'collide' method | |
motion = blockstate.getCollisionShape(world, pos, ctx) | |
.collide(cycledZ, aabb.move(-pos.getX(), -pos.getY(), -pos.getZ()), motion); | |
if (Math.abs(motion) < 0.0000001) | |
return 0.0D; | |
maxZ = lastC(motion, d0, d1); | |
} | |
} | |
z += zIncrement; | |
} | |
double[] motionRef = {motion}; | |
nonBlockShapes.forEach((shape) -> motionRef[0] = shape.collide(cycledZ, aabb, motionRef[0])); | |
return motionRef[0]; | |
} | |
private static int lastC(double motion, double d0, double d1) { | |
return motion > 0.0D ? MathHelper.floor(d1 + motion) + 1 : MathHelper.floor(d0 + motion) - 1; | |
} | |
//... | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment