Skip to content

Instantly share code, notes, and snippets.

@Cadiboo
Last active July 10, 2021 10:23
Show Gist options
  • Save Cadiboo/5c1d72fcb9d891a2e64119aaecce5de7 to your computer and use it in GitHub Desktop.
Save Cadiboo/5c1d72fcb9d891a2e64119aaecce5de7 to your computer and use it in GitHub Desktop.
Minecraft's collisions are hard to wrap my head around
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