Optimise BitSetDiscreteVoxelShape#forAllBoxes
This should speed up general shape joining, as VoxelShape#optimize() uses this method when performing an uncached toAabbs()
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class FlatBitsetUtil {
|
||||
|
||||
private static final int LOG2_LONG = 6;
|
||||
private static final long ALL_SET = -1L;
|
||||
private static final int BITS_PER_LONG = Long.SIZE;
|
||||
|
||||
// from inclusive
|
||||
// to exclusive
|
||||
public static int firstSet(final long[] bitset, final int from, final int to) {
|
||||
if ((from | to | (to - from)) < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
int bitsetIdx = from >>> LOG2_LONG;
|
||||
int bitIdx = from & ~(BITS_PER_LONG - 1);
|
||||
|
||||
long tmp = bitset[bitsetIdx] & (ALL_SET << from);
|
||||
for (;;) {
|
||||
if (tmp != 0L) {
|
||||
final int ret = bitIdx | Long.numberOfTrailingZeros(tmp);
|
||||
return ret >= to ? -1 : ret;
|
||||
}
|
||||
|
||||
bitIdx += BITS_PER_LONG;
|
||||
|
||||
if (bitIdx >= to) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = bitset[++bitsetIdx];
|
||||
}
|
||||
}
|
||||
|
||||
// from inclusive
|
||||
// to exclusive
|
||||
public static int firstClear(final long[] bitset, final int from, final int to) {
|
||||
if ((from | to | (to - from)) < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
// like firstSet, but invert the bitset
|
||||
|
||||
int bitsetIdx = from >>> LOG2_LONG;
|
||||
int bitIdx = from & ~(BITS_PER_LONG - 1);
|
||||
|
||||
long tmp = (~bitset[bitsetIdx]) & (ALL_SET << from);
|
||||
for (;;) {
|
||||
if (tmp != 0L) {
|
||||
final int ret = bitIdx | Long.numberOfTrailingZeros(tmp);
|
||||
return ret >= to ? -1 : ret;
|
||||
}
|
||||
|
||||
bitIdx += BITS_PER_LONG;
|
||||
|
||||
if (bitIdx >= to) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = ~bitset[++bitsetIdx];
|
||||
}
|
||||
}
|
||||
|
||||
// from inclusive
|
||||
// to exclusive
|
||||
public static void clearRange(final long[] bitset, final int from, int to) {
|
||||
if ((from | to | (to - from)) < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
|
||||
--to;
|
||||
|
||||
final int fromBitsetIdx = from >>> LOG2_LONG;
|
||||
final int toBitsetIdx = to >>> LOG2_LONG;
|
||||
|
||||
final long keepFirst = ALL_SET >>> ((BITS_PER_LONG - 1) ^ from);
|
||||
final long keepLast = ALL_SET << to;
|
||||
|
||||
Objects.checkFromToIndex(fromBitsetIdx, toBitsetIdx, bitset.length);
|
||||
|
||||
if (fromBitsetIdx == toBitsetIdx) {
|
||||
// special case: need to keep both first and last
|
||||
bitset[fromBitsetIdx] &= (keepFirst | keepLast);
|
||||
} else {
|
||||
bitset[fromBitsetIdx] &= keepFirst;
|
||||
|
||||
for (int i = fromBitsetIdx + 1; i < toBitsetIdx; ++i) {
|
||||
bitset[toBitsetIdx] = 0L;
|
||||
}
|
||||
|
||||
bitset[toBitsetIdx] &= keepLast;
|
||||
}
|
||||
}
|
||||
|
||||
// from inclusive
|
||||
// to exclusive
|
||||
public static boolean isRangeSet(final long[] bitset, final int from, final int to) {
|
||||
return firstClear(bitset, from, to) == -1;
|
||||
}
|
||||
|
||||
|
||||
private FlatBitsetUtil() {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package ca.spottedleaf.moonrise.common.util;
|
||||
|
||||
public final class MixinWorkarounds {
|
||||
|
||||
// mixins tries to find the owner of the clone() method, which doesn't exist and NPEs
|
||||
public static long[] clone(final long[] values) {
|
||||
return values.clone();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.FlatBitsetUtil;
|
||||
import ca.spottedleaf.moonrise.common.util.MixinWorkarounds;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape;
|
||||
import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
|
||||
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
@Mixin(BitSetDiscreteVoxelShape.class)
|
||||
public abstract class BitSetDiscreteVoxelShapeMixin extends DiscreteVoxelShape {
|
||||
|
||||
protected BitSetDiscreteVoxelShapeMixin(int i, int j, int k) {
|
||||
super(i, j, k);
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason avoid creating temporary shape, interact directly with the bitset
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
public static void forAllBoxes(final DiscreteVoxelShape shape, final DiscreteVoxelShape.IntLineConsumer consumer, final boolean mergeAdjacent) {
|
||||
// called with the shape of a VoxelShape, so we can expect the cache to exist
|
||||
final CachedShapeData cache = ((CollisionDiscreteVoxelShape)shape).getOrCreateCachedShapeData();
|
||||
|
||||
final int sizeX = cache.sizeX();
|
||||
final int sizeY = cache.sizeY();
|
||||
final int sizeZ = cache.sizeZ();
|
||||
|
||||
int indexX;
|
||||
int indexY = 0;
|
||||
int indexZ;
|
||||
|
||||
int incY = sizeZ;
|
||||
int incX = sizeZ*sizeY;
|
||||
|
||||
long[] bitset = cache.voxelSet();
|
||||
|
||||
// index = z + y*size_z + x*(size_z*size_y)
|
||||
|
||||
if (!mergeAdjacent) {
|
||||
// due to the odd selection of loop order (which does affect behavior, unfortunately) we can't simply
|
||||
// increment an index in the Z loop, and have to perform this trash (keeping track of 3 counters) to avoid
|
||||
// the multiplication
|
||||
for (int y = 0; y < sizeY; ++y, indexY += incY) {
|
||||
indexX = indexY;
|
||||
for (int x = 0; x < sizeX; ++x, indexX += incX) {
|
||||
indexZ = indexX;
|
||||
for (int z = 0; z < sizeZ; ++z, ++indexZ) {
|
||||
if ((bitset[indexZ >>> 6] & (1L << indexZ)) != 0L) {
|
||||
consumer.consume(x, y, z, x + 1, y + 1, z + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// same notes about loop order as the above
|
||||
// this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize())
|
||||
|
||||
// only clone when we may write to it
|
||||
bitset = MixinWorkarounds.clone(bitset);
|
||||
|
||||
for (int y = 0; y < sizeY; ++y, indexY += incY) {
|
||||
indexX = indexY;
|
||||
for (int x = 0; x < sizeX; ++x, indexX += incX) {
|
||||
final int startIndex = indexX;
|
||||
final int endIndex = indexX + sizeZ;
|
||||
|
||||
final int firstSetZ = FlatBitsetUtil.firstSet(bitset, startIndex, endIndex);
|
||||
|
||||
if (firstSetZ == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int lastSetZ = FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex);
|
||||
if (lastSetZ == -1) {
|
||||
lastSetZ = endIndex;
|
||||
}
|
||||
|
||||
FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ);
|
||||
|
||||
// try to merge neighbouring on the X axis
|
||||
int endX = x + 1; // exclusive
|
||||
for (int neighbourIdxStart = firstSetZ + incX, neighbourIdxEnd = lastSetZ + incX;
|
||||
endX < sizeX && FlatBitsetUtil.isRangeSet(bitset, neighbourIdxStart, neighbourIdxEnd);
|
||||
neighbourIdxStart += incX, neighbourIdxEnd += incX) {
|
||||
|
||||
++endX;
|
||||
FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd);
|
||||
}
|
||||
|
||||
// try to merge neighbouring on the Y axis
|
||||
|
||||
int endY; // exclusive
|
||||
int firstSetZY, lastSetZY;
|
||||
y_merge:
|
||||
for (endY = y + 1, firstSetZY = firstSetZ + incY, lastSetZY = lastSetZ + incY; endY < sizeY;
|
||||
++endY, firstSetZY += incY, lastSetZY += incY) {
|
||||
|
||||
// test the whole XZ range
|
||||
for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX;
|
||||
++testX, start += incX, end += incX) {
|
||||
if (!FlatBitsetUtil.isRangeSet(bitset, start, end)) {
|
||||
break y_merge;
|
||||
}
|
||||
}
|
||||
|
||||
// passed, so we can clear it
|
||||
for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX;
|
||||
++testX, start += incX, end += incX) {
|
||||
FlatBitsetUtil.clearRange(bitset, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
consumer.consume(x, y, firstSetZ - startIndex, endX, endY, lastSetZ - startIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.Overwrite;
|
||||
public abstract class BlockMixin {
|
||||
|
||||
/**
|
||||
* @reason Replace with an implementation that does not use join AND one that caches the result
|
||||
* @reason Replace with an implementation that does not use join AND one that caches the result per VoxelShape instance
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Overwrite
|
||||
|
||||
@@ -3,9 +3,11 @@ package ca.spottedleaf.moonrise.mixin.collisions;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData;
|
||||
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
|
||||
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Mixin(DiscreteVoxelShape.class)
|
||||
public abstract class DiscreteVoxelShapeMixin implements CollisionDiscreteVoxelShape {
|
||||
@@ -29,19 +31,29 @@ public abstract class DiscreteVoxelShapeMixin implements CollisionDiscreteVoxelS
|
||||
final int maxIndex = sizeX * sizeY * sizeZ; // exclusive
|
||||
|
||||
final int longsRequired = (maxIndex + (Long.SIZE - 1)) >>> 6;
|
||||
final long[] voxelSet = new long[longsRequired];
|
||||
long[] voxelSet;
|
||||
|
||||
final boolean isEmpty = discreteVoxelShape.isEmpty();
|
||||
if (!isEmpty) {
|
||||
final int mulX = sizeZ * sizeY;
|
||||
for (int x = 0; x < sizeX; ++x) {
|
||||
for (int y = 0; y < sizeY; ++y) {
|
||||
for (int z = 0; z < sizeZ; ++z) {
|
||||
if (discreteVoxelShape.isFull(x, y, z)) {
|
||||
// index = z + y*size_z + x*(size_z*size_y)
|
||||
final int index = z + y*sizeZ + x*mulX;
|
||||
|
||||
voxelSet[index >>> 6] |= 1L << index;
|
||||
if (discreteVoxelShape instanceof BitSetDiscreteVoxelShape bitsetShape) {
|
||||
voxelSet = bitsetShape.storage.toLongArray();
|
||||
if (voxelSet.length < longsRequired) {
|
||||
// happens when the later long values are 0L, so we need to resize
|
||||
voxelSet = Arrays.copyOf(voxelSet, longsRequired);
|
||||
}
|
||||
} else {
|
||||
voxelSet = new long[longsRequired];
|
||||
if (!isEmpty) {
|
||||
final int mulX = sizeZ * sizeY;
|
||||
for (int x = 0; x < sizeX; ++x) {
|
||||
for (int y = 0; y < sizeY; ++y) {
|
||||
for (int z = 0; z < sizeZ; ++z) {
|
||||
if (discreteVoxelShape.isFull(x, y, z)) {
|
||||
// index = z + y*size_z + x*(size_z*size_y)
|
||||
final int index = z + y * sizeZ + x * mulX;
|
||||
|
||||
voxelSet[index >>> 6] |= 1L << index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ public abstract class LevelMixin implements CollisionLevel, CollisionEntityGette
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Use optimised merge strategy, avoid streams
|
||||
* @reason Use optimised OR operator join strategy, avoid streams
|
||||
* @author Spottedleaf
|
||||
*/
|
||||
@Override
|
||||
@@ -464,7 +464,8 @@ public abstract class LevelMixin implements CollisionLevel, CollisionEntityGette
|
||||
final VoxelShape joined = Shapes.or(first, rest);
|
||||
|
||||
// find free space
|
||||
final VoxelShape freeSpace = Shapes.join(boundsShape, joined, BooleanOp.ONLY_FIRST);
|
||||
// don't need optimized join, as closestPointTo uses toAabbs()
|
||||
final VoxelShape freeSpace = Shapes.joinUnoptimized(boundsShape, joined, BooleanOp.ONLY_FIRST);
|
||||
|
||||
return freeSpace.closestPointTo(fromPosition);
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public abstract class ShapesMixin {
|
||||
final VoxelShape first = tmp[i];
|
||||
final VoxelShape second = tmp[next];
|
||||
|
||||
tmp[newSize++] = Shapes.join(first, second, BooleanOp.OR);
|
||||
tmp[newSize++] = Shapes.or(first, second);
|
||||
}
|
||||
}
|
||||
size = newSize;
|
||||
@@ -269,10 +269,11 @@ public abstract class ShapesMixin {
|
||||
final AABB bounds2 = shape2.bounds();
|
||||
|
||||
final double minX = Math.min(bounds1.minX, bounds2.minX);
|
||||
final double maxX = Math.max(bounds1.maxX, bounds2.maxX);
|
||||
final double minY = Math.min(bounds1.minY, bounds2.minY);
|
||||
final double maxY = Math.max(bounds1.maxY, bounds2.maxY);
|
||||
final double minZ = Math.min(bounds1.minZ, bounds2.minZ);
|
||||
|
||||
final double maxX = Math.max(bounds1.maxX, bounds2.maxX);
|
||||
final double maxY = Math.max(bounds1.maxY, bounds2.maxY);
|
||||
final double maxZ = Math.max(bounds1.maxZ, bounds2.maxZ);
|
||||
|
||||
return (minX <= CollisionUtil.COLLISION_EPSILON && maxX >= (1 - CollisionUtil.COLLISION_EPSILON)) &&
|
||||
|
||||
@@ -657,6 +657,7 @@ public abstract class VoxelShapeMixin implements CollisionVoxelShape {
|
||||
}
|
||||
|
||||
if (this.singleAABBRepresentation != null) {
|
||||
// note: the isFullBlock() is fuzzy, and Shapes.create() is also fuzzy which would return block()
|
||||
return this.isFullBlock() ? Shapes.block() : (VoxelShape)(Object)this;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ public abstract class HopperBlockEntityMixin extends RandomizableContainerBlockE
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
((HopperBlockEntityMixin)(Object)hopperBlockEntity).setCooldown(8);
|
||||
((HopperBlockEntityMixin)(Object)hopperBlockEntity).setCooldown(HopperBlockEntity.MOVE_ITEM_SPEED);
|
||||
setChanged(level, blockPos, blockState);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public final class VisibilityGraph {
|
||||
ret.setAll(true);
|
||||
return ret;
|
||||
}
|
||||
if (this.opaqueEdgeCount >= (TOTAL_BLOCKS_FACES - 1)) {
|
||||
if (this.opaqueEdgeCount == TOTAL_BLOCKS_FACES) {
|
||||
// impossible for there to be any visibility from one edge to another
|
||||
ret.setAll(false);
|
||||
return ret;
|
||||
@@ -218,12 +218,13 @@ public final class VisibilityGraph {
|
||||
final int directionOrdinal = Integer.numberOfTrailingZeros(negatedBitset);
|
||||
final int add = INDEX_ADD_BY_DIRECTION_ORDINAL[directionOrdinal];
|
||||
|
||||
negatedBitset ^= -negatedBitset & negatedBitset; // remove trailing bit
|
||||
|
||||
final int neighbourIndex = add + queuedIndex;
|
||||
|
||||
final long bitsetValue = opaque[neighbourIndex >>> LOG2_LONG];
|
||||
final int directionBitsetForNeighbour = DIRECTIONS_BITSET_BY_INDEX[neighbourIndex];
|
||||
|
||||
negatedBitset ^= -negatedBitset & negatedBitset; // remove trailing bit
|
||||
|
||||
final long neighbourMask = 1L << neighbourIndex;
|
||||
|
||||
final long newBitsetValue = bitsetValue | neighbourMask;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"chunk_getblock.LevelChunkMixin",
|
||||
"collisions.ArmorStandMixin",
|
||||
"collisions.ArrayVoxelShapeMixin",
|
||||
"collisions.BitSetDiscreteVoxelShapeMixin",
|
||||
"collisions.BlockMixin",
|
||||
"collisions.BlockStateBaseMixin",
|
||||
"collisions.CollisionGetterMixin",
|
||||
|
||||
Reference in New Issue
Block a user