Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: optimize VoxelShapes.matchesAnywhere using simplicity of cuboid shapes #148

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private static double calculatePenetration(double aMin, double aMax, final int s
}

@Override
protected DoubleList getPointPositions(Direction.Axis axis) {
public DoubleList getPointPositions(Direction.Axis axis) {
return new FractionalDoubleList(axis.choose(this.xSegments, this.ySegments, this.zSegments));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private static double calculatePenetration(double aMin, double aMax, final int s
}

@Override
protected DoubleList getPointPositions(Direction.Axis axis) {
public DoubleList getPointPositions(Direction.Axis axis) {
return new OffsetFractionalDoubleList(axis.choose(this.xSegments, this.ySegments, this.zSegments),
axis.choose(this.xOffset, this.yOffset, this.zOffset));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public VoxelShapeEmpty(VoxelSet voxels) {
}

@Override
protected DoubleList getPointPositions(Direction.Axis axis) {
public DoubleList getPointPositions(Direction.Axis axis) {
return EMPTY_LIST;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package me.jellysquid.mods.lithium.common.shapes;

import it.unimi.dsi.fastutil.doubles.DoubleList;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.shape.VoxelSet;
import net.minecraft.util.shape.VoxelShape;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import static net.minecraft.util.math.Direction.Axis.*;

public class VoxelShapeMatchesAnywhere {

public static void cuboidMatchesAnywhere(VoxelShape shapeA, VoxelShape shapeB, BooleanBiFunction predicate, CallbackInfoReturnable<Boolean> cir) {
//calling this method only if both shapes are not empty and have bounding box overlap

if (shapeA instanceof VoxelShapeSimpleCube && shapeB instanceof VoxelShapeSimpleCube) {
if (((VoxelShapeSimpleCube) shapeA).isTiny || ((VoxelShapeSimpleCube) shapeB).isTiny) {
//vanilla fallback: Handling this special case would mean using the whole
//pointPosition merging code in SimplePairList. A tiny shape can have very odd effects caused by
//having three pointPositions within 2e-7 or even 1e-7. Vanilla merges the point positions
//by always taking the most negative one and skipping those with less than 1e-7 distance to it.
//The optimization partially relies on only having to check the previous point position, which is
//not possible when 3 or more are within 2e-7 of another, as the previous point position could have
//been skipped by the merging code.
return;
}
//both shapes are simple cubes, matching two cubes anywhere is really simple. Also handle epsilon margins.
if (predicate.apply(true, true)) {
if (intersects((VoxelShapeSimpleCube) shapeA, (VoxelShapeSimpleCube) shapeB)) {
cir.setReturnValue(true);
return;
}
cir.setReturnValue(predicate.apply(true, false) || predicate.apply(false, true));
} else if (predicate.apply(true, false) &&
exceedsShape((VoxelShapeSimpleCube) shapeA, (VoxelShapeSimpleCube) shapeB)) {
cir.setReturnValue(true);
return;
} else if (predicate.apply(false, true) &&
exceedsShape((VoxelShapeSimpleCube) shapeB, (VoxelShapeSimpleCube) shapeA)) {
cir.setReturnValue(true);
return;
}
cir.setReturnValue(false);
}
else if (shapeA instanceof VoxelShapeSimpleCube || shapeB instanceof VoxelShapeSimpleCube) {
//only one of the two shapes is a simple cube, but there are still some shortcuts that can be taken
VoxelShapeSimpleCube simpleCube = (VoxelShapeSimpleCube) (shapeA instanceof VoxelShapeSimpleCube ? shapeA : shapeB);
VoxelShape otherShape = simpleCube == shapeA ? shapeB : shapeA;

if (simpleCube.isTiny || isTiny(otherShape)) {
//vanilla fallback, same reason as above
return;
}

boolean acceptSimpleCubeAlone = predicate.apply(shapeA == simpleCube, shapeB == simpleCube);
//test the area outside otherShape
if (acceptSimpleCubeAlone && exceedsCube(simpleCube,
otherShape.getMin(X), otherShape.getMin(Y), otherShape.getMin(Z),
otherShape.getMax(X), otherShape.getMax(Y), otherShape.getMax(Z))) {
cir.setReturnValue(true);
return;
}
boolean acceptAnd = predicate.apply(true, true);
boolean acceptOtherShapeAlone = predicate.apply(shapeA == otherShape, shapeB == otherShape);

//test the area inside otherShape
VoxelSet voxelSet = otherShape.voxels;
DoubleList pointPositionsX = otherShape.getPointPositions(X);
DoubleList pointPositionsY = otherShape.getPointPositions(Y);
DoubleList pointPositionsZ = otherShape.getPointPositions(Z);

int xMax = voxelSet.getMax(X); // xMax <= pointPositionsX.size()
int yMax = voxelSet.getMax(Y);
int zMax = voxelSet.getMax(Z);

//keep the cube positions in local vars to avoid looking them up all the time
double simpleCubeMaxX = simpleCube.getMax(X);
double simpleCubeMinX = simpleCube.getMin(X);
double simpleCubeMaxY = simpleCube.getMax(Y);
double simpleCubeMinY = simpleCube.getMin(Y);
double simpleCubeMaxZ = simpleCube.getMax(Z);
double simpleCubeMinZ = simpleCube.getMin(Z);

//iterate over all entries of the VoxelSet
for (int x = voxelSet.getMin(X); x < xMax; x++) {
//all of the positions of +1e-7 and -1e-7 and >, >=, <, <= are carefully chosen:
//for example for the following line: >= here fails the test
// moving the - 1e-7 here to the other side of > as + 1e-7 fails the test
boolean simpleCubeIntersectsXSlice = (simpleCubeMaxX - 1e-7 > pointPositionsX.getDouble(x) && simpleCubeMinX < pointPositionsX.getDouble(x + 1) - 1e-7);
if (!acceptOtherShapeAlone && !simpleCubeIntersectsXSlice) {
//if we cannot return when the simple cube is not intersecting the area, skip forward
continue;
}
boolean xSliceExceedsCube = acceptOtherShapeAlone && !((simpleCubeMaxX >= pointPositionsX.getDouble(x + 1) - 1e-7 && simpleCubeMinX - 1e-7 <= pointPositionsX.getDouble(x)));
for (int y = voxelSet.getMin(Y); y < yMax; y++) {
boolean simpleCubeIntersectsYSlice = (simpleCubeMaxY - 1e-7 > pointPositionsY.getDouble(y) && simpleCubeMinY < pointPositionsY.getDouble(y + 1) - 1e-7);
if (!acceptOtherShapeAlone && !simpleCubeIntersectsYSlice) {
//if we cannot return when the simple cube is not intersecting the area, skip forward
continue;
}
boolean ySliceExceedsCube = acceptOtherShapeAlone && !((simpleCubeMaxY >= pointPositionsY.getDouble(y + 1) - 1e-7 && simpleCubeMinY - 1e-7 <= pointPositionsY.getDouble(y)));
for (int z = voxelSet.getMin(Z); z < zMax; z++) {
boolean simpleCubeIntersectsZSlice = (simpleCubeMaxZ - 1e-7 > pointPositionsZ.getDouble(z) && simpleCubeMinZ < pointPositionsZ.getDouble(z + 1) - 1e-7);
if (!acceptOtherShapeAlone && !simpleCubeIntersectsZSlice) {
//if we cannot return when the simple cube is not intersecting the area, skip forward
continue;
}
boolean zSliceExceedsCube = acceptOtherShapeAlone && !((simpleCubeMaxZ >= pointPositionsZ.getDouble(z + 1) - 1e-7 && simpleCubeMinZ - 1e-7 <= pointPositionsZ.getDouble(z)));

boolean o = voxelSet.inBoundsAndContains(x, y, z);
boolean s = simpleCubeIntersectsXSlice && simpleCubeIntersectsYSlice && simpleCubeIntersectsZSlice;
if (acceptAnd && o && s || acceptSimpleCubeAlone && !o && s || acceptOtherShapeAlone && o && (xSliceExceedsCube || ySliceExceedsCube || zSliceExceedsCube)) {
cir.setReturnValue(true);
return;
}
}
}
}
cir.setReturnValue(false);
}
}

private static boolean isTiny(VoxelShape shapeA) {
//avoid properties of SimplePairList, really close point positions are subject to special merging behavior
return shapeA.getMin(X) > shapeA.getMax(X) - 3e-7 ||
shapeA.getMin(Y) > shapeA.getMax(Y) - 3e-7 ||
shapeA.getMin(Z) > shapeA.getMax(Z) - 3e-7;
}

private static boolean exceedsCube(VoxelShapeSimpleCube a, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
return a.getMin(X) < minX - 1e-7 || a.getMax(X) > maxX + 1e-7 ||
a.getMin(Y) < minY - 1e-7 || a.getMax(Y) > maxY + 1e-7 ||
a.getMin(Z) < minZ - 1e-7 || a.getMax(Z) > maxZ + 1e-7;
}

private static boolean exceedsShape(VoxelShapeSimpleCube a, VoxelShapeSimpleCube b) {
return a.getMin(X) < b.getMin(X) - 1e-7 || a.getMax(X) > b.getMax(X) + 1e-7 ||
a.getMin(Y) < b.getMin(Y) - 1e-7 || a.getMax(Y) > b.getMax(Y) + 1e-7 ||
a.getMin(Z) < b.getMin(Z) - 1e-7 || a.getMax(Z) > b.getMax(Z) + 1e-7;
}

private static boolean intersects(VoxelShapeSimpleCube a, VoxelShapeSimpleCube b) {
return a.getMin(X) < b.getMax(X) - 1e-7 && a.getMax(X) > b.getMin(X) + 1e-7 &&
a.getMin(Y) < b.getMax(Y) - 1e-7 && a.getMax(Y) > b.getMin(Y) + 1e-7 &&
a.getMin(Z) < b.getMax(Z) - 1e-7 && a.getMax(Z) > b.getMin(Z) + 1e-7;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class VoxelShapeSimpleCube extends VoxelShape implements VoxelShapeCaster
static final double EPSILON = 1.0E-7D;

final double minX, minY, minZ, maxX, maxY, maxZ;
public final boolean isTiny;

public VoxelShapeSimpleCube(VoxelSet voxels, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
super(voxels);
Expand All @@ -35,6 +36,11 @@ public VoxelShapeSimpleCube(VoxelSet voxels, double minX, double minY, double mi
this.maxX = maxX;
this.maxY = maxY;
this.maxZ = maxZ;

this.isTiny =
this.minX + 3 * EPSILON >= this.maxX ||
this.minY + 3 * EPSILON >= this.maxY ||
this.minZ + 3 * EPSILON >= this.maxZ;
}

@Override
Expand Down Expand Up @@ -146,7 +152,7 @@ protected double getPointPosition(Direction.Axis axis, int index) {
}

@Override
protected DoubleList getPointPositions(Direction.Axis axis) {
public DoubleList getPointPositions(Direction.Axis axis) {
switch (axis) {
case X:
return DoubleArrayList.wrap(new double[]{this.minX, this.maxX});
Expand All @@ -166,7 +172,7 @@ protected boolean contains(double x, double y, double z) {

@Override
public boolean isEmpty() {
return ((this.minX + EPSILON) > this.maxX) || ((this.minY + EPSILON) > this.maxY) || ((this.minZ + EPSILON) > this.maxZ);
return (this.minX >= this.maxX) || (this.minY >= this.maxY) || (this.minZ >= this.maxZ);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package me.jellysquid.mods.lithium.mixin.shapes.optimized_matching;

import me.jellysquid.mods.lithium.common.shapes.VoxelShapeMatchesAnywhere;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(VoxelShapes.class)
public class VoxelShapesMixin {
@Inject(method = "matchesAnywhere(Lnet/minecraft/util/shape/VoxelShape;Lnet/minecraft/util/shape/VoxelShape;Lnet/minecraft/util/function/BooleanBiFunction;)Z",
at = @At(value = "INVOKE", shift = At.Shift.BEFORE,
target = "Lnet/minecraft/util/shape/VoxelShape;getPointPositions(Lnet/minecraft/util/math/Direction$Axis;)Lit/unimi/dsi/fastutil/doubles/DoubleList;",
ordinal = 0),
cancellable = true)
private static void cuboidMatchesAnywhere(VoxelShape shapeA, VoxelShape shapeB, BooleanBiFunction predicate, CallbackInfoReturnable<Boolean> cir) {
VoxelShapeMatchesAnywhere.cuboidMatchesAnywhere(shapeA, shapeB, predicate, cir);
}
}
5 changes: 4 additions & 1 deletion src/main/resources/lithium.accesswidener
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ accessible method net/minecraft/server/world/ChunkTicket isExpired (J)Z
accessible method net/minecraft/util/shape/VoxelShapes findRequiredBitResolution (DD)I
accessible method net/minecraft/util/shape/FractionalDoubleList <init> (I)V

accessible method net/minecraft/entity/Entity checkWaterState ()V
accessible method net/minecraft/entity/Entity checkWaterState ()V

accessible method net/minecraft/util/shape/VoxelShape getPointPositions (Lnet/minecraft/util/math/Direction$Axis;)Lit/unimi/dsi/fastutil/doubles/DoubleList;
accessible field net/minecraft/util/shape/VoxelShape voxels Lnet/minecraft/util/shape/VoxelSet;
1 change: 1 addition & 0 deletions src/main/resources/lithium.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"math.fast_util.BoxMixin",
"math.fast_util.DirectionMixin",
"shapes.blockstate_cache.BlockMixin",
"shapes.optimized_matching.VoxelShapesMixin",
"shapes.precompute_shape_arrays.FractionalDoubleListMixin",
"shapes.precompute_shape_arrays.SimpleVoxelShapeMixin",
"shapes.shape_merging.VoxelShapesMixin",
Expand Down
Loading