Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ bin/
/run/
/common/run/
/fabric/run/
/fabric/config/
/fabric/logs/
/neoforge/run/
/neoforge/runs/

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package net.caffeinemc.mods.lithium.common.entity;

import com.google.common.collect.AbstractIterator;
import net.caffeinemc.mods.lithium.common.entity.movement.ChunkAwareBlockCollisionSweeper;
import net.caffeinemc.mods.lithium.common.entity.movement.ChunkAwareBlockCollisionSweeperVoxelShape;
import net.caffeinemc.mods.lithium.common.util.Pos;
import net.caffeinemc.mods.lithium.common.world.WorldHelper;
import net.minecraft.core.BlockPos;
Expand Down Expand Up @@ -37,14 +37,14 @@ public class LithiumEntityCollisions {
* VoxelShape system.
*/
public static List<VoxelShape> getBlockCollisions(Level world, Entity entity, AABB box) {
return new ChunkAwareBlockCollisionSweeper(world, entity, box).collectAll();
return new ChunkAwareBlockCollisionSweeperVoxelShape(world, entity, box).collectAll();
}

/***
* @return True if the box (possibly that of an entity's) collided with any blocks
*/
public static boolean doesBoxCollideWithBlocks(Level world, @Nullable Entity entity, AABB box) {
final ChunkAwareBlockCollisionSweeper sweeper = new ChunkAwareBlockCollisionSweeper(world, entity, box);
final ChunkAwareBlockCollisionSweeperVoxelShape sweeper = new ChunkAwareBlockCollisionSweeperVoxelShape(world, entity, box);

final VoxelShape shape = sweeper.computeNext();

Expand Down Expand Up @@ -213,7 +213,7 @@ public static VoxelShape getWorldBorderCollision(CollisionGetter collisionView,
// that cancels the downwards motion, but usually it is, and this is only for a quick, additional test.
//TODO: This may lead to the movement attempt not creating any chunk load tickets.
// Entities and pistons **probably** create these tickets elsewhere anyways. This probably also applies
// to usages of ChunkAwareBlockCollisionSweeper and others
// to usages of ChunkAwareBlockCollisionSweeperVoxelShape and others
VoxelShape voxelShape = supportingBlockCollisionShapeProvider.lithium$getCollisionShapeBelow();
if (voxelShape != null) {
return voxelShape;
Expand All @@ -238,7 +238,7 @@ private static VoxelShape getCollisionShapeBelowEntityFallback(Level world, Enti
return null;
}

public static boolean addLastBlockCollisionIfRequired(boolean addLastBlockCollision, ChunkAwareBlockCollisionSweeper blockCollisionSweeper, List<VoxelShape> list) {
public static boolean addLastBlockCollisionIfRequired(boolean addLastBlockCollision, ChunkAwareBlockCollisionSweeperVoxelShape blockCollisionSweeper, List<VoxelShape> list) {
if (addLastBlockCollision) {
VoxelShape lastCollision = blockCollisionSweeper.getLastCollision();
if (lastCollision != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,50 @@
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import static net.caffeinemc.mods.lithium.common.entity.LithiumEntityCollisions.EPSILON;

/**
* ChunkAwareBlockCollisionSweeper iterates over blocks in one chunk section at a time. Together with the chunk
* section keeping track of the amount of oversized blocks inside the number of iterations can often be reduced.
*/
public class ChunkAwareBlockCollisionSweeper extends AbstractIterator<VoxelShape> {

private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
public abstract class ChunkAwareBlockCollisionSweeper<T> extends AbstractIterator<T> {
protected final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();

/**
* The collision box being swept through the world.
*/
private final AABB box;
protected final AABB box;

/**
* The VoxelShape of the collision box being swept through the world.
*/
private final VoxelShape shape;
protected final VoxelShape shape;

private final Level world;
protected final Level world;

private final CollisionContext context;
protected final CollisionContext context;

//limits of the area without extension for oversized blocks
private final int minX, minY, minZ, maxX, maxY, maxZ;
protected final int minX, minY, minZ, maxX, maxY, maxZ;

//variables prefixed with c refer to the iteration of the currently cached chunk section
private int chunkX, chunkYIndex, chunkZ;
private int cStartX, cStartZ;
private int cEndX, cEndZ;
private int cX, cY, cZ;

private int maxHitX;
private int maxHitY;
private int maxHitZ;
private VoxelShape maxShape;
private final boolean hideLastCollision;
protected int cStartX, cStartZ;
protected int cEndX, cEndZ;
protected int cX, cY, cZ;

protected int maxHitX, maxHitY, maxHitZ;
protected VoxelShape maxShape;
protected final boolean hideLastCollision;


private int cTotalSize;
private int cIterated;
protected int cTotalSize;
protected int cIterated;

private boolean sectionOversizedBlocks;
protected boolean sectionOversizedBlocks;
private ChunkAccess cachedChunk;
private LevelChunkSection cachedChunkSection;
protected LevelChunkSection cachedChunkSection;

public ChunkAwareBlockCollisionSweeper(Level world, @Nullable Entity entity, AABB box) {
this(world, entity, box, false);
}
public ChunkAwareBlockCollisionSweeper(Level world, @Nullable Entity entity, AABB box, boolean hideLastCollision) {
this.box = box;
this.shape = Shapes.create(box);
Expand Down Expand Up @@ -105,39 +94,15 @@ public ChunkAwareBlockCollisionSweeper(Level world, @Nullable Entity entity, AAB
this.chunkX--;
}


public VoxelShape getLastCollision() {
return this.maxShape;
}

public Iterator<VoxelShape> getLastCollisionIterator() {
return new Iterator<>() {
@Override
public boolean hasNext() {
return hideLastCollision && maxShape != null;
}

@Override
public VoxelShape next() {
if (this.hasNext()) {
VoxelShape previousMaxShape = maxShape;
maxShape = null;
return previousMaxShape;
}
throw new NoSuchElementException();
}
};
}

private boolean nextSection() {
final protected boolean nextSection() {
do {
do {
//find the coordinates of the next section inside the area expanded by 1 block on all sides
//note: this.minX, maxX etc are not expanded, so there are lots of +1 and -1 around.
if (
this.cachedChunk != null &&
this.chunkYIndex < Pos.SectionYIndex.getMaxYSectionIndexInclusive(this.world) &&
this.chunkYIndex < Pos.SectionYIndex.fromBlockCoord(this.world,expandMax(this.maxY))
this.chunkYIndex < Pos.SectionYIndex.getMaxYSectionIndexInclusive(this.world) &&
this.chunkYIndex < Pos.SectionYIndex.fromBlockCoord(this.world,expandMax(this.maxY))
) {
this.chunkYIndex++;
this.cachedChunkSection = this.cachedChunk.getSections()[this.chunkYIndex];
Expand Down Expand Up @@ -192,102 +157,13 @@ private boolean nextSection() {
return true;
}

/**
* Advances the sweep forward until finding a block with a box-colliding VoxelShape
*
* @return null if no VoxelShape is left in the area, otherwise the next VoxelShape
*/
@Override
public VoxelShape computeNext() {
while (true) {
if (this.cIterated >= this.cTotalSize) {
if (!this.nextSection()) {
break;
}
}

this.cIterated++;


final int x = this.cX;
final int y = this.cY;
final int z = this.cZ;

//The iteration order within a chunk section is chosen so that it causes a mostly linear array access in the storage.
//In net.minecraft.world.chunk.PalettedContainer.toIndex x gets the 4 least significant bits, z the 4 above, and y the 4 even higher ones.
//Linearly accessing arrays is faster than other access patterns.
if (this.cX < this.cEndX) {
this.cX++;
} else if (this.cZ < this.cEndZ) {
this.cX = this.cStartX;
this.cZ++;
} else {
this.cX = this.cStartX;
this.cZ = this.cStartZ;
this.cY++;
//stop condition was already checked using this.cIterated at the start of the method
}

//using < minX and > maxX instead of <= and >= in vanilla, because minX, maxX are the coordinates
//of the box that wasn't extended for oversized blocks yet.
final int edgesHit = this.sectionOversizedBlocks ?
(x < this.minX || x > this.maxX ? 1 : 0) +
(y < this.minY || y > this.maxY ? 1 : 0) +
(z < this.minZ || z > this.maxZ ? 1 : 0) : 0;

if (edgesHit == 3) {
continue;
}

final BlockState state = this.cachedChunkSection.getBlockState(x & 15, y & 15, z & 15);

if (!canInteractWithBlock(state, edgesHit)) {
continue;
}

this.pos.set(x, y, z);

VoxelShape collisionShape = this.context.getCollisionShape(state, this.world, this.pos);

//noinspection ConstantValue
if (collisionShape != Shapes.empty() && collisionShape != null /*collisionShape should never be null, but we received crash reports.*/) {
VoxelShape collidedShape = getCollidedShape(this.box, this.shape, collisionShape, x, y, z);
if (collidedShape != null) {
if (z >= this.maxHitZ && (z > this.maxHitZ || y >= this.maxHitY && (y > this.maxHitY || x > this.maxHitX))) {
this.maxHitX = x;
this.maxHitY = y;
this.maxHitZ = z;
//Always make sure the shape at the maximum position is the last one returned, because
// the last shape has a different 1e-7 behavior (no next shape that clips movement to 0).
// This does affect certain contraptions: https://github.com/CaffeineMC/lithium-fabric/issues/443
VoxelShape previousMaxShape = this.maxShape;
this.maxShape = collidedShape;
if (previousMaxShape != null) {
return previousMaxShape;
}
} else {
return collidedShape;
}
}
}
}

if (!this.hideLastCollision && this.maxShape != null) {
VoxelShape previousMaxShape = this.maxShape;
this.maxShape = null;
return previousMaxShape;
}

return this.endOfData();
}

/**
* This is an artifact from vanilla which is used to avoid testing shapes in the extended portion of a volume
* unless they are a shape which exceeds their voxel. Pistons must be special-cased here.
*
* @return True if the shape can be interacted with at the given edge boundary
*/
private static boolean canInteractWithBlock(BlockState state, int edgesHit) {
protected static boolean canInteractWithBlock(BlockState state, int edgesHit) {
return (edgesHit != 1 || state.hasLargeCollisionShape()) && (edgesHit != 2 || state.getBlock() == Blocks.MOVING_PISTON);
}

Expand All @@ -298,7 +174,7 @@ private static boolean canInteractWithBlock(BlockState state, int edgesHit) {
*
* @return A {@link VoxelShape} which contains the shape representing that which was collided with, otherwise null
*/
private static VoxelShape getCollidedShape(AABB entityBox, VoxelShape entityShape, VoxelShape shape, int x, int y, int z) {
protected static VoxelShape getCollidedShape(AABB entityBox, VoxelShape entityShape, VoxelShape shape, int x, int y, int z) {
if (shape == Shapes.block()) {
return entityBox.intersects(x, y, z, x + 1.0, y + 1.0, z + 1.0) ? shape.move(x, y, z) : null;
}
Expand Down Expand Up @@ -338,14 +214,4 @@ private static boolean hasChunkSectionOversizedBlocks(ChunkAccess chunk, int chu
}
return true; //like vanilla, assume that a chunk section has oversized blocks, when the section mixin isn't loaded
}

public List<VoxelShape> collectAll() {
ArrayList<VoxelShape> collisions = new ArrayList<>();

while (this.hasNext()) {
collisions.add(this.next());
}

return collisions;
}
}
Loading