diff --git a/fabric/src/main/java/ca/spottedleaf/moonrise/fabric/mixin/command/ClientSuggestionProviderMixin.java b/fabric/src/main/java/ca/spottedleaf/moonrise/fabric/mixin/command/ClientSuggestionProviderMixin.java index 5a5da3a..a5401ff 100644 --- a/fabric/src/main/java/ca/spottedleaf/moonrise/fabric/mixin/command/ClientSuggestionProviderMixin.java +++ b/fabric/src/main/java/ca/spottedleaf/moonrise/fabric/mixin/command/ClientSuggestionProviderMixin.java @@ -9,12 +9,12 @@ import org.spongepowered.asm.mixin.Mixin; @Mixin(ClientSuggestionProvider.class) abstract class ClientSuggestionProviderMixin implements CommandClientCommandSource, FabricClientCommandSource { @Override - public void moonrise$sendSuccess(final Component message) { + public final void moonrise$sendSuccess(final Component message) { this.sendFeedback(message); } @Override - public void moonrise$sendFailure(final Component message) { + public final void moonrise$sendFailure(final Component message) { this.sendError(message); } } diff --git a/neoforge/src/main/java/ca/spottedleaf/moonrise/neoforge/NeoForgeHooks.java b/neoforge/src/main/java/ca/spottedleaf/moonrise/neoforge/NeoForgeHooks.java index de99e10..6b06e62 100644 --- a/neoforge/src/main/java/ca/spottedleaf/moonrise/neoforge/NeoForgeHooks.java +++ b/neoforge/src/main/java/ca/spottedleaf/moonrise/neoforge/NeoForgeHooks.java @@ -2,7 +2,6 @@ package ca.spottedleaf.moonrise.neoforge; import ca.spottedleaf.moonrise.common.PlatformHooks; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; -import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; @@ -15,7 +14,6 @@ import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.EmptyBlockGetter; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ImposterProtoChunk; diff --git a/neoforge/src/main/java/ca/spottedleaf/moonrise/neoforge/mixin/command/ClientCommandSourceStackMixin.java b/neoforge/src/main/java/ca/spottedleaf/moonrise/neoforge/mixin/command/ClientCommandSourceStackMixin.java index 9ac1bfd..1140502 100644 --- a/neoforge/src/main/java/ca/spottedleaf/moonrise/neoforge/mixin/command/ClientCommandSourceStackMixin.java +++ b/neoforge/src/main/java/ca/spottedleaf/moonrise/neoforge/mixin/command/ClientCommandSourceStackMixin.java @@ -25,12 +25,12 @@ abstract class ClientCommandSourceStackMixin extends CommandSourceStack implemen public abstract void sendSuccess(Supplier message, boolean sendToAdmins); @Override - public void moonrise$sendFailure(final Component message) { + public final void moonrise$sendFailure(final Component message) { this.sendFailure(message); } @Override - public void moonrise$sendSuccess(final Component message) { + public final void moonrise$sendSuccess(final Component message) { this.sendSuccess(() -> message, true); } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCache$MainThreadExecutorMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCache$MainThreadExecutorMixin.java index 5af3dc9..3ac4b93 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCache$MainThreadExecutorMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCache$MainThreadExecutorMixin.java @@ -11,9 +11,9 @@ import org.spongepowered.asm.mixin.Shadow; @Mixin(ServerChunkCache.MainThreadExecutor.class) abstract class ServerChunkCache$MainThreadExecutorMixin extends BlockableEventLoop { - @Shadow(aliases = "this$0") + @Shadow(aliases = "this$0") // Neoforge @Final - ServerChunkCache field_18810; + ServerChunkCache field_18810; // Fabric protected ServerChunkCache$MainThreadExecutorMixin(String string) { super(string); diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/StructureTemplate$PaletteMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/StructureTemplate$PaletteMixin.java index 8fbb018..8e6f73d 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/StructureTemplate$PaletteMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/StructureTemplate$PaletteMixin.java @@ -27,7 +27,7 @@ abstract class StructureTemplate$PaletteMixin { value = "RETURN" ) ) - private void makeCacheCHM(final CallbackInfo ci) { + private void makeCacheCHM(final CallbackInfo ci) { this.cache = new ConcurrentHashMap<>(); } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/BlockStateBaseMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/BlockStateBaseMixin.java index 236a8ce..c05b5df 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/BlockStateBaseMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/BlockStateBaseMixin.java @@ -65,13 +65,19 @@ abstract class BlockStateBaseMixin extends StateHolder implem private AABB constantAABBCollision; @Unique - private static void initCaches(final VoxelShape shape) { + private static void initCaches(final VoxelShape shape, final boolean neighbours) { ((CollisionVoxelShape)shape).moonrise$isFullBlock(); ((CollisionVoxelShape)shape).moonrise$occludesFullBlock(); shape.toAabbs(); if (!shape.isEmpty()) { shape.bounds(); } + if (neighbours) { + for (final Direction direction : DIRECTIONS_CACHED) { + initCaches(Shapes.getFaceShape(shape, direction), false); + initCaches(shape.getFaceShape(direction), false); + } + } } /** @@ -98,17 +104,13 @@ abstract class BlockStateBaseMixin extends StateHolder implem this.occludesFullBlock = ((CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock(); this.emptyCollisionShape = collisionShape.isEmpty(); // init caches - initCaches(collisionShape); - if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) { - for (final Direction direction : DIRECTIONS_CACHED) { - // initialise the directional face shape cache as well - final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction); - initCaches(shape); - } + initCaches(collisionShape, true); + if (this.constantCollisionShape != null) { + initCaches(this.constantCollisionShape, true); } if (this.cache.occlusionShapes != null) { for (final VoxelShape shape : this.cache.occlusionShapes) { - initCaches(shape); + initCaches(shape, false); } } } else { diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/DiscreteVoxelShapeMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/DiscreteVoxelShapeMixin.java index e907ebb..6baa862 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/DiscreteVoxelShapeMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/DiscreteVoxelShapeMixin.java @@ -60,7 +60,7 @@ abstract class DiscreteVoxelShapeMixin implements CollisionDiscreteVoxelShape { } } - final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && discreteVoxelShape.isFull(0, 0, 0); + final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && (voxelSet[0] & 1L) != 0L; final int minFullX = discreteVoxelShape.firstFull(Direction.Axis.X); final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y); diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/VoxelShapeMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/VoxelShapeMixin.java index 2bc46c4..9027ef7 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/VoxelShapeMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/VoxelShapeMixin.java @@ -23,7 +23,6 @@ import net.minecraft.world.phys.shapes.BooleanOp; import net.minecraft.world.phys.shapes.DiscreteVoxelShape; import net.minecraft.world.phys.shapes.OffsetDoubleList; import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.world.phys.shapes.SliceShape; import net.minecraft.world.phys.shapes.VoxelShape; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -208,13 +207,13 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape { if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { if (DoubleMath.fuzzyEquals(this.max(axis), 1.0, CollisionUtil.COLLISION_EPSILON)) { - ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1)); + ret = CollisionUtil.sliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1); } else { ret = Shapes.empty(); } } else { if (DoubleMath.fuzzyEquals(this.min(axis), 0.0, CollisionUtil.COLLISION_EPSILON)) { - ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, 0)); + ret = CollisionUtil.sliceShape((VoxelShape)(Object)this, axis, 0); } else { ret = Shapes.empty(); } @@ -225,24 +224,6 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape { return ret; } - @Unique - private static VoxelShape tryForceBlock(final VoxelShape other) { - if (other == Shapes.block()) { - return other; - } - - final AABB otherAABB = ((CollisionVoxelShape)other).moonrise$getSingleAABBRepresentation(); - if (otherAABB == null) { - return other; - } - - if (((CollisionVoxelShape)Shapes.block()).moonrise$getSingleAABBRepresentation().equals(otherAABB)) { - return Shapes.block(); - } - - return other; - } - @Unique private boolean computeOccludesFullBlock() { if (this.isEmpty) { @@ -353,6 +334,115 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape { return this.isEmpty; } + /** + * @author Spottedleaf + * @reason Use cached bounds + */ + @Overwrite + public VoxelShape singleEncompassing() { + if (this.isEmpty) { + return Shapes.empty(); + } + return Shapes.create(this.bounds()); + } + + /** + * @author Spottedleaf + * @reason Optimise implementation to avoid indirection + */ + @Overwrite + protected double get(final Direction.Axis axis, final int idx) { + switch (axis) { + case X: { + return this.rootCoordinatesX[idx] + this.offsetX; + } + case Y: { + return this.rootCoordinatesY[idx] + this.offsetY; + } + case Z: { + return this.rootCoordinatesZ[idx] + this.offsetZ; + } + default: { + throw new IllegalStateException("Unknown axis: " + axis); + } + } + } + + /** + * @author Spottedleaf + * @reason Optimise implementation to avoid indirection + */ + @Overwrite + public int findIndex(final Direction.Axis axis, final double value) { + switch (axis) { + case X: { + final double[] values = this.rootCoordinatesX; + return CollisionUtil.findFloor( + values, value - this.offsetX, 0, values.length - 1 + ); + } + case Y: { + final double[] values = this.rootCoordinatesY; + return CollisionUtil.findFloor( + values, value - this.offsetY, 0, values.length - 1 + ); + } + case Z: { + final double[] values = this.rootCoordinatesZ; + return CollisionUtil.findFloor( + values, value - this.offsetZ, 0, values.length - 1 + ); + } + default: { + throw new IllegalStateException("Unknown axis: " + axis); + } + } + } + + @Unique + private VoxelShape calculateFaceDirect(final Direction direction, final Direction.Axis axis, final double[] coords, final double offset) { + if (coords.length == 2 && + DoubleMath.fuzzyEquals(coords[0] + offset, 0.0, CollisionUtil.COLLISION_EPSILON) && + DoubleMath.fuzzyEquals(coords[1] + offset, 1.0, CollisionUtil.COLLISION_EPSILON)) { + return (VoxelShape)(Object)this; + } + + final boolean positiveDir = direction.getAxisDirection() == Direction.AxisDirection.POSITIVE; + + // see findIndex + final int index = CollisionUtil.findFloor( + coords, (positiveDir ? (1.0 - CollisionUtil.COLLISION_EPSILON) : (0.0 + CollisionUtil.COLLISION_EPSILON)) - offset, + 0, coords.length - 1 + ); + + return CollisionUtil.sliceShape( + (VoxelShape)(Object)this, axis, index + ); + } + + /** + * @author Spottedleaf + * @reason Avoid creating SliceShape + */ + @Overwrite + public VoxelShape calculateFace(final Direction direction) { + final Direction.Axis axis = direction.getAxis(); + switch (axis) { + case X: { + return this.calculateFaceDirect(direction, axis, this.rootCoordinatesX, this.offsetX); + } + case Y: { + return this.calculateFaceDirect(direction, axis, this.rootCoordinatesY, this.offsetY); + } + case Z: { + return this.calculateFaceDirect(direction, axis, this.rootCoordinatesZ, this.offsetZ); + } + default: { + throw new IllegalStateException("Unknown axis: " + axis); + } + } + } + /** * @author Spottedleaf * @reason Route to optimized collision method @@ -382,11 +472,12 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape { } @Unique - private static DoubleList offsetList(final DoubleList src, final double by) { - if (src instanceof OffsetDoubleList offsetDoubleList) { - return new OffsetDoubleList(offsetDoubleList.delegate, by + offsetDoubleList.offset); + private static DoubleList offsetList(final double[] src, final double by) { + final DoubleArrayList wrap = DoubleArrayList.wrap(src); + if (by == 0.0) { + return wrap; } - return new OffsetDoubleList(src, by); + return new OffsetDoubleList(wrap, by); } /** @@ -400,10 +491,10 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape { } final ArrayVoxelShape ret = new ArrayVoxelShape( - this.shape, - offsetList(this.getCoords(Direction.Axis.X), x), - offsetList(this.getCoords(Direction.Axis.Y), y), - offsetList(this.getCoords(Direction.Axis.Z), z) + this.shape, + offsetList(this.rootCoordinatesX, this.offsetX + x), + offsetList(this.rootCoordinatesY, this.offsetY + y), + offsetList(this.rootCoordinatesZ, this.offsetZ + z) ); final CachedToAABBs cachedToAABBs = this.cachedToAABBs; @@ -416,10 +507,12 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape { @Unique private List toAabbsUncached() { - final List ret = new ArrayList<>(); + final List ret; if (this.singleAABBRepresentation != null) { + ret = new ArrayList<>(1); ret.add(this.singleAABBRepresentation); } else { + ret = new ArrayList<>(); final double[] coordsX = this.rootCoordinatesX; final double[] coordsY = this.rootCoordinatesY; final double[] coordsZ = this.rootCoordinatesZ; @@ -718,6 +811,11 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape { final List aabbs = this.toAabbs(); + if (aabbs.isEmpty()) { + // We are a SliceShape, which does not properly fill isEmpty for every case + return Shapes.empty(); + } + if (aabbs.size() == 1) { final AABB singleAABB = aabbs.get(0); final VoxelShape ret = Shapes.create(singleAABB); diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/serverlist/ServerAddressResolverMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/serverlist/ServerAddressResolverMixin.java index 5f43007..e6dfe6f 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/serverlist/ServerAddressResolverMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/serverlist/ServerAddressResolverMixin.java @@ -4,8 +4,6 @@ import net.minecraft.client.multiplayer.resolver.ServerAddressResolver; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; -import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; @@ -29,9 +27,10 @@ interface ServerAddressResolverMixin { private static InetAddress eliminateRDNS(final String name) throws UnknownHostException { final InetAddress ret = InetAddress.getByName(name); - if (ret instanceof Inet4Address || ret instanceof Inet6Address) { + final byte[] address = ret.getAddress(); + if (address != null) { // pass name to prevent rDNS - return InetAddress.getByAddress(name, ret.getAddress()); + return InetAddress.getByAddress(name, address); } return ret; diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java index c1b95c8..f5b4c3c 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java @@ -35,9 +35,11 @@ import net.minecraft.world.phys.shapes.DiscreteVoxelShape; import net.minecraft.world.phys.shapes.EntityCollisionContext; import net.minecraft.world.phys.shapes.OffsetDoubleList; import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.SliceShape; import net.minecraft.world.phys.shapes.VoxelShape; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -162,7 +164,8 @@ public final class CollisionUtil { // startIndex and endIndex inclusive // assumes indices are in range of array - private static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { + public static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { + Objects.checkFromToIndex(startIndex, endIndex + 1, values.length); do { final int middle = (startIndex + endIndex) >>> 1; final double middleVal = values[middle]; @@ -177,6 +180,214 @@ public final class CollisionUtil { return startIndex - 1; } + private static VoxelShape sliceShapeVanilla(final VoxelShape src, final Direction.Axis axis, + final int index) { + return new SliceShape(src, axis, index); + } + + private static DoubleList offsetList(final double[] src, final double by) { + final DoubleArrayList wrap = DoubleArrayList.wrap(src); + if (by == 0.0) { + return wrap; + } + return new OffsetDoubleList(wrap, by); + } + + private static VoxelShape sliceShapeOptimised(final VoxelShape src, final Direction.Axis axis, + final int index) { + // assume index in range + final double off_x = ((CollisionVoxelShape)src).moonrise$offsetX(); + final double off_y = ((CollisionVoxelShape)src).moonrise$offsetY(); + final double off_z = ((CollisionVoxelShape)src).moonrise$offsetZ(); + + final double[] coords_x = ((CollisionVoxelShape)src).moonrise$rootCoordinatesX(); + final double[] coords_y = ((CollisionVoxelShape)src).moonrise$rootCoordinatesY(); + final double[] coords_z = ((CollisionVoxelShape)src).moonrise$rootCoordinatesZ(); + + final CachedShapeData cached_shape_data = ((CollisionVoxelShape)src).moonrise$getCachedVoxelData(); + + // note: size = coords.length - 1 + final int size_x = cached_shape_data.sizeX(); + final int size_y = cached_shape_data.sizeY(); + final int size_z = cached_shape_data.sizeZ(); + + final long[] bitset = cached_shape_data.voxelSet(); + + final DoubleList list_x; + final DoubleList list_y; + final DoubleList list_z; + final int shape_sx; + final int shape_ex; + final int shape_sy; + final int shape_ey; + final int shape_sz; + final int shape_ez; + + switch (axis) { + case X: { + // validate index + if (index < 0 || index >= size_x) { + return Shapes.empty(); + } + + // test if input is already "sliced" + if (coords_x.length == 2 && (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0) { + return src; + } + + // test if result would be full box + if (coords_y.length == 2 && coords_z.length == 2 && + (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0 && + (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { + // note: size_y == size_z == 1 + final int bitIdx = 0 + 0*size_z + index*(size_z*size_y); + return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); + } + + list_x = ZERO_ONE; + list_y = offsetList(coords_y, off_y); + list_z = offsetList(coords_z, off_z); + shape_sx = index; + shape_ex = index + 1; + shape_sy = 0; + shape_ey = size_y; + shape_sz = 0; + shape_ez = size_z; + + break; + } + case Y: { + // validate index + if (index < 0 || index >= size_y) { + return Shapes.empty(); + } + + // test if input is already "sliced" + if (coords_y.length == 2 && (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0) { + return src; + } + + // test if result would be full box + if (coords_x.length == 2 && coords_z.length == 2 && + (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0 && + (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { + // note: size_x == size_z == 1 + final int bitIdx = 0 + index*size_z + 0*(size_z*size_y); + return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); + } + + list_x = offsetList(coords_x, off_x); + list_y = ZERO_ONE; + list_z = offsetList(coords_z, off_z); + shape_sx = 0; + shape_ex = size_x; + shape_sy = index; + shape_ey = index + 1; + shape_sz = 0; + shape_ez = size_z; + + break; + } + case Z: { + // validate index + if (index < 0 || index >= size_z) { + return Shapes.empty(); + } + + // test if input is already "sliced" + if (coords_z.length == 2 && (coords_z[0] + off_z) == 0.0 && (coords_z[1] + off_z) == 1.0) { + return src; + } + + // test if result would be full box + if (coords_x.length == 2 && coords_y.length == 2 && + (coords_x[0] + off_x) == 0.0 && (coords_x[1] + off_x) == 1.0 && + (coords_y[0] + off_y) == 0.0 && (coords_y[1] + off_y) == 1.0) { + // note: size_x == size_y == 1 + final int bitIdx = index + 0*size_z + 0*(size_z*size_y); + return (bitset[bitIdx >>> 6] & (1L << bitIdx)) == 0L ? Shapes.empty() : Shapes.block(); + } + + list_x = offsetList(coords_x, off_x); + list_y = offsetList(coords_y, off_y); + list_z = ZERO_ONE; + shape_sx = 0; + shape_ex = size_x; + shape_sy = 0; + shape_ey = size_y; + shape_sz = index; + shape_ez = index + 1; + + break; + } + default: { + throw new IllegalStateException("Unknown axis: " + axis); + } + } + + final int local_len_x = shape_ex - shape_sx; + final int local_len_y = shape_ey - shape_sy; + final int local_len_z = shape_ez - shape_sz; + + final BitSetDiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(local_len_x, local_len_y, local_len_z); + + final int idx_off = shape_sz + shape_sy*size_z + shape_sx*(size_z*size_y); + for (int x = 0; x < local_len_x; ++x) { + boolean setX = false; + for (int y = 0; y < local_len_y; ++y) { + boolean setY = false; + for (int z = 0; z < local_len_z; ++z) { + final int unslicedIdx = idx_off + z + y*size_z + x*(size_z*size_y); + if ((bitset[unslicedIdx >>> 6] & (1L << unslicedIdx)) == 0L) { + continue; + } + + setY = true; + setX = true; + shape.zMin = Math.min(shape.zMin, z); + shape.zMax = Math.max(shape.zMax, z + 1); + + shape.storage.set( + z + y*local_len_z + x*(local_len_y*local_len_z) + ); + } + + if (setY) { + shape.yMin = Math.min(shape.yMin, y); + shape.yMax = Math.max(shape.yMax, y + 1); + } + } + if (setX) { + shape.xMin = Math.min(shape.xMin, x); + shape.xMax = Math.max(shape.xMax, x + 1); + } + } + + return shape.isEmpty() ? Shapes.empty() : new ArrayVoxelShape( + shape, list_x, list_y, list_z + ); + } + + private static final boolean DEBUG_SLICE_SHAPE = false; + + public static VoxelShape sliceShape(final VoxelShape src, final Direction.Axis axis, + final int index) { + final VoxelShape ret = sliceShapeOptimised(src, axis, index); + if (DEBUG_SLICE_SHAPE) { + final VoxelShape vanilla = sliceShapeVanilla(src, axis, index); + if (!equals(ret, vanilla)) { + // special case: SliceShape is not empty when it should be! + if (areAnyFull(ret.shape) || areAnyFull(vanilla.shape)) { + equals(ret, vanilla); + sliceShapeOptimised(src, axis, index); + throw new IllegalStateException("Slice shape mismatch"); + } + } + } + + return ret; + } + public static boolean voxelShapeIntersectNoEmpty(final VoxelShape voxel, final AABB aabb) { if (voxel.isEmpty()) { return false; @@ -1306,7 +1517,7 @@ public final class CollisionUtil { return true; } else if (isEmpty1 ^ isEmpty2) { return false; - } + } // else: isEmpty1 = isEmpty2 = false if (cachedShapeData1.hasSingleAABB() != cachedShapeData2.hasSingleAABB()) { return false; @@ -1327,6 +1538,12 @@ public final class CollisionUtil { // useful only for testing public static boolean equals(final VoxelShape shape1, final VoxelShape shape2) { + if (shape1.isEmpty() & shape2.isEmpty()) { + return true; + } else if (shape1.isEmpty() ^ shape2.isEmpty()) { + return false; + } + if (!equals(shape1.shape, shape2.shape)) { return false; } @@ -1336,6 +1553,84 @@ public final class CollisionUtil { shape1.getCoords(Direction.Axis.Z).equals(shape2.getCoords(Direction.Axis.Z)); } + public static boolean areAnyFull(final DiscreteVoxelShape shape) { + if (shape.isEmpty()) { + return false; + } + + final int sizeX = shape.getXSize(); + final int sizeY = shape.getYSize(); + final int sizeZ = shape.getZSize(); + + for (int x = 0; x < sizeX; ++x) { + for (int y = 0; y < sizeY; ++y) { + for (int z = 0; z < sizeZ; ++z) { + if (shape.isFull(x, y, z)) { + return true; + } + } + } + } + + return false; + } + + public static String shapeMismatch(final DiscreteVoxelShape shape1, final DiscreteVoxelShape shape2) { + final CachedShapeData cachedShapeData1 = ((CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData(); + final CachedShapeData cachedShapeData2 = ((CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData(); + + final boolean isEmpty1 = cachedShapeData1.isEmpty(); + final boolean isEmpty2 = cachedShapeData2.isEmpty(); + + if (isEmpty1 & isEmpty2) { + return null; + } else if (isEmpty1 ^ isEmpty2) { + return null; + } // else: isEmpty1 = isEmpty2 = false + + if (cachedShapeData1.sizeX() != cachedShapeData2.sizeX()) { + return "size x: " + cachedShapeData1.sizeX() + " != " + cachedShapeData2.sizeX(); + } + if (cachedShapeData1.sizeY() != cachedShapeData2.sizeY()) { + return "size y: " + cachedShapeData1.sizeY() + " != " + cachedShapeData2.sizeY(); + } + if (cachedShapeData1.sizeZ() != cachedShapeData2.sizeZ()) { + return "size z: " + cachedShapeData1.sizeZ() + " != " + cachedShapeData2.sizeZ(); + } + + final StringBuilder ret = new StringBuilder(); + + final int sizeX = cachedShapeData1.sizeX();; + final int sizeY = cachedShapeData1.sizeY(); + final int sizeZ = cachedShapeData1.sizeZ(); + + boolean first = true; + + for (int x = 0; x < sizeX; ++x) { + for (int y = 0; y < sizeY; ++y) { + for (int z = 0; z < sizeZ; ++z) { + final boolean isFull1 = shape1.isFull(x, y, z); + final boolean isFull2 = shape2.isFull(x, y, z); + + if (isFull1 == isFull2) { + continue; + } + + if (first) { + first = false; + } else { + ret.append(", "); + } + + ret.append("(").append(x).append(",").append(y).append(",").append(z) + .append("): shape1: ").append(isFull1).append(", shape2: ").append(isFull2); + } + } + } + + return ret.isEmpty() ? null : ret.toString(); + } + public static AABB offsetX(final AABB box, final double dx) { return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); }