Avoid creating SliceShape when retrieving Voxelshape faces
SliceShape is not always the ideal shape, as sometimes the face result is empty or full cube - but is represented as SliceShape. Additionally, sometimes SliceShape should be empty but isEmpty() is false.
This commit is contained in:
@@ -9,12 +9,12 @@ import org.spongepowered.asm.mixin.Mixin;
|
|||||||
@Mixin(ClientSuggestionProvider.class)
|
@Mixin(ClientSuggestionProvider.class)
|
||||||
abstract class ClientSuggestionProviderMixin implements CommandClientCommandSource, FabricClientCommandSource {
|
abstract class ClientSuggestionProviderMixin implements CommandClientCommandSource, FabricClientCommandSource {
|
||||||
@Override
|
@Override
|
||||||
public void moonrise$sendSuccess(final Component message) {
|
public final void moonrise$sendSuccess(final Component message) {
|
||||||
this.sendFeedback(message);
|
this.sendFeedback(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moonrise$sendFailure(final Component message) {
|
public final void moonrise$sendFailure(final Component message) {
|
||||||
this.sendError(message);
|
this.sendError(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package ca.spottedleaf.moonrise.neoforge;
|
|||||||
|
|
||||||
import ca.spottedleaf.moonrise.common.PlatformHooks;
|
import ca.spottedleaf.moonrise.common.PlatformHooks;
|
||||||
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
|
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 ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
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.EmptyBlockGetter;
|
||||||
import net.minecraft.world.level.Explosion;
|
import net.minecraft.world.level.Explosion;
|
||||||
import net.minecraft.world.level.Level;
|
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.block.state.BlockState;
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
import net.minecraft.world.level.chunk.ImposterProtoChunk;
|
import net.minecraft.world.level.chunk.ImposterProtoChunk;
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ abstract class ClientCommandSourceStackMixin extends CommandSourceStack implemen
|
|||||||
public abstract void sendSuccess(Supplier<Component> message, boolean sendToAdmins);
|
public abstract void sendSuccess(Supplier<Component> message, boolean sendToAdmins);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moonrise$sendFailure(final Component message) {
|
public final void moonrise$sendFailure(final Component message) {
|
||||||
this.sendFailure(message);
|
this.sendFailure(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moonrise$sendSuccess(final Component message) {
|
public final void moonrise$sendSuccess(final Component message) {
|
||||||
this.sendSuccess(() -> message, true);
|
this.sendSuccess(() -> message, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import org.spongepowered.asm.mixin.Shadow;
|
|||||||
@Mixin(ServerChunkCache.MainThreadExecutor.class)
|
@Mixin(ServerChunkCache.MainThreadExecutor.class)
|
||||||
abstract class ServerChunkCache$MainThreadExecutorMixin extends BlockableEventLoop<Runnable> {
|
abstract class ServerChunkCache$MainThreadExecutorMixin extends BlockableEventLoop<Runnable> {
|
||||||
|
|
||||||
@Shadow(aliases = "this$0")
|
@Shadow(aliases = "this$0") // Neoforge
|
||||||
@Final
|
@Final
|
||||||
ServerChunkCache field_18810;
|
ServerChunkCache field_18810; // Fabric
|
||||||
|
|
||||||
protected ServerChunkCache$MainThreadExecutorMixin(String string) {
|
protected ServerChunkCache$MainThreadExecutorMixin(String string) {
|
||||||
super(string);
|
super(string);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ abstract class StructureTemplate$PaletteMixin {
|
|||||||
value = "RETURN"
|
value = "RETURN"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
private <K, V> void makeCacheCHM(final CallbackInfo ci) {
|
private void makeCacheCHM(final CallbackInfo ci) {
|
||||||
this.cache = new ConcurrentHashMap<>();
|
this.cache = new ConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,13 +65,19 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
|
|||||||
private AABB constantAABBCollision;
|
private AABB constantAABBCollision;
|
||||||
|
|
||||||
@Unique
|
@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$isFullBlock();
|
||||||
((CollisionVoxelShape)shape).moonrise$occludesFullBlock();
|
((CollisionVoxelShape)shape).moonrise$occludesFullBlock();
|
||||||
shape.toAabbs();
|
shape.toAabbs();
|
||||||
if (!shape.isEmpty()) {
|
if (!shape.isEmpty()) {
|
||||||
shape.bounds();
|
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<Block, BlockState> implem
|
|||||||
this.occludesFullBlock = ((CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock();
|
this.occludesFullBlock = ((CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock();
|
||||||
this.emptyCollisionShape = collisionShape.isEmpty();
|
this.emptyCollisionShape = collisionShape.isEmpty();
|
||||||
// init caches
|
// init caches
|
||||||
initCaches(collisionShape);
|
initCaches(collisionShape, true);
|
||||||
if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) {
|
if (this.constantCollisionShape != null) {
|
||||||
for (final Direction direction : DIRECTIONS_CACHED) {
|
initCaches(this.constantCollisionShape, true);
|
||||||
// initialise the directional face shape cache as well
|
|
||||||
final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction);
|
|
||||||
initCaches(shape);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (this.cache.occlusionShapes != null) {
|
if (this.cache.occlusionShapes != null) {
|
||||||
for (final VoxelShape shape : this.cache.occlusionShapes) {
|
for (final VoxelShape shape : this.cache.occlusionShapes) {
|
||||||
initCaches(shape);
|
initCaches(shape, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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 minFullX = discreteVoxelShape.firstFull(Direction.Axis.X);
|
||||||
final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y);
|
final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y);
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import net.minecraft.world.phys.shapes.BooleanOp;
|
|||||||
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
||||||
import net.minecraft.world.phys.shapes.OffsetDoubleList;
|
import net.minecraft.world.phys.shapes.OffsetDoubleList;
|
||||||
import net.minecraft.world.phys.shapes.Shapes;
|
import net.minecraft.world.phys.shapes.Shapes;
|
||||||
import net.minecraft.world.phys.shapes.SliceShape;
|
|
||||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
@@ -208,13 +207,13 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape {
|
|||||||
|
|
||||||
if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
|
if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
|
||||||
if (DoubleMath.fuzzyEquals(this.max(axis), 1.0, CollisionUtil.COLLISION_EPSILON)) {
|
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 {
|
} else {
|
||||||
ret = Shapes.empty();
|
ret = Shapes.empty();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (DoubleMath.fuzzyEquals(this.min(axis), 0.0, CollisionUtil.COLLISION_EPSILON)) {
|
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 {
|
} else {
|
||||||
ret = Shapes.empty();
|
ret = Shapes.empty();
|
||||||
}
|
}
|
||||||
@@ -225,24 +224,6 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape {
|
|||||||
return ret;
|
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
|
@Unique
|
||||||
private boolean computeOccludesFullBlock() {
|
private boolean computeOccludesFullBlock() {
|
||||||
if (this.isEmpty) {
|
if (this.isEmpty) {
|
||||||
@@ -353,6 +334,115 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape {
|
|||||||
return this.isEmpty;
|
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
|
* @author Spottedleaf
|
||||||
* @reason Route to optimized collision method
|
* @reason Route to optimized collision method
|
||||||
@@ -382,11 +472,12 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private static DoubleList offsetList(final DoubleList src, final double by) {
|
private static DoubleList offsetList(final double[] src, final double by) {
|
||||||
if (src instanceof OffsetDoubleList offsetDoubleList) {
|
final DoubleArrayList wrap = DoubleArrayList.wrap(src);
|
||||||
return new OffsetDoubleList(offsetDoubleList.delegate, by + offsetDoubleList.offset);
|
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(
|
final ArrayVoxelShape ret = new ArrayVoxelShape(
|
||||||
this.shape,
|
this.shape,
|
||||||
offsetList(this.getCoords(Direction.Axis.X), x),
|
offsetList(this.rootCoordinatesX, this.offsetX + x),
|
||||||
offsetList(this.getCoords(Direction.Axis.Y), y),
|
offsetList(this.rootCoordinatesY, this.offsetY + y),
|
||||||
offsetList(this.getCoords(Direction.Axis.Z), z)
|
offsetList(this.rootCoordinatesZ, this.offsetZ + z)
|
||||||
);
|
);
|
||||||
|
|
||||||
final CachedToAABBs cachedToAABBs = this.cachedToAABBs;
|
final CachedToAABBs cachedToAABBs = this.cachedToAABBs;
|
||||||
@@ -416,10 +507,12 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape {
|
|||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private List<AABB> toAabbsUncached() {
|
private List<AABB> toAabbsUncached() {
|
||||||
final List<AABB> ret = new ArrayList<>();
|
final List<AABB> ret;
|
||||||
if (this.singleAABBRepresentation != null) {
|
if (this.singleAABBRepresentation != null) {
|
||||||
|
ret = new ArrayList<>(1);
|
||||||
ret.add(this.singleAABBRepresentation);
|
ret.add(this.singleAABBRepresentation);
|
||||||
} else {
|
} else {
|
||||||
|
ret = new ArrayList<>();
|
||||||
final double[] coordsX = this.rootCoordinatesX;
|
final double[] coordsX = this.rootCoordinatesX;
|
||||||
final double[] coordsY = this.rootCoordinatesY;
|
final double[] coordsY = this.rootCoordinatesY;
|
||||||
final double[] coordsZ = this.rootCoordinatesZ;
|
final double[] coordsZ = this.rootCoordinatesZ;
|
||||||
@@ -718,6 +811,11 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape {
|
|||||||
|
|
||||||
final List<AABB> aabbs = this.toAabbs();
|
final List<AABB> 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) {
|
if (aabbs.size() == 1) {
|
||||||
final AABB singleAABB = aabbs.get(0);
|
final AABB singleAABB = aabbs.get(0);
|
||||||
final VoxelShape ret = Shapes.create(singleAABB);
|
final VoxelShape ret = Shapes.create(singleAABB);
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import net.minecraft.client.multiplayer.resolver.ServerAddressResolver;
|
|||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
import java.net.Inet4Address;
|
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
@@ -29,9 +27,10 @@ interface ServerAddressResolverMixin {
|
|||||||
private static InetAddress eliminateRDNS(final String name) throws UnknownHostException {
|
private static InetAddress eliminateRDNS(final String name) throws UnknownHostException {
|
||||||
final InetAddress ret = InetAddress.getByName(name);
|
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
|
// pass name to prevent rDNS
|
||||||
return InetAddress.getByAddress(name, ret.getAddress());
|
return InetAddress.getByAddress(name, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
|||||||
import net.minecraft.world.phys.shapes.EntityCollisionContext;
|
import net.minecraft.world.phys.shapes.EntityCollisionContext;
|
||||||
import net.minecraft.world.phys.shapes.OffsetDoubleList;
|
import net.minecraft.world.phys.shapes.OffsetDoubleList;
|
||||||
import net.minecraft.world.phys.shapes.Shapes;
|
import net.minecraft.world.phys.shapes.Shapes;
|
||||||
|
import net.minecraft.world.phys.shapes.SliceShape;
|
||||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@@ -162,7 +164,8 @@ public final class CollisionUtil {
|
|||||||
|
|
||||||
// startIndex and endIndex inclusive
|
// startIndex and endIndex inclusive
|
||||||
// assumes indices are in range of array
|
// 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 {
|
do {
|
||||||
final int middle = (startIndex + endIndex) >>> 1;
|
final int middle = (startIndex + endIndex) >>> 1;
|
||||||
final double middleVal = values[middle];
|
final double middleVal = values[middle];
|
||||||
@@ -177,6 +180,214 @@ public final class CollisionUtil {
|
|||||||
return startIndex - 1;
|
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) {
|
public static boolean voxelShapeIntersectNoEmpty(final VoxelShape voxel, final AABB aabb) {
|
||||||
if (voxel.isEmpty()) {
|
if (voxel.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1306,7 +1517,7 @@ public final class CollisionUtil {
|
|||||||
return true;
|
return true;
|
||||||
} else if (isEmpty1 ^ isEmpty2) {
|
} else if (isEmpty1 ^ isEmpty2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
} // else: isEmpty1 = isEmpty2 = false
|
||||||
|
|
||||||
if (cachedShapeData1.hasSingleAABB() != cachedShapeData2.hasSingleAABB()) {
|
if (cachedShapeData1.hasSingleAABB() != cachedShapeData2.hasSingleAABB()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1327,6 +1538,12 @@ public final class CollisionUtil {
|
|||||||
|
|
||||||
// useful only for testing
|
// useful only for testing
|
||||||
public static boolean equals(final VoxelShape shape1, final VoxelShape shape2) {
|
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)) {
|
if (!equals(shape1.shape, shape2.shape)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1336,6 +1553,84 @@ public final class CollisionUtil {
|
|||||||
shape1.getCoords(Direction.Axis.Z).equals(shape2.getCoords(Direction.Axis.Z));
|
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) {
|
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);
|
return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user