Blockiterator fix 4 (#1513)

This commit is contained in:
iam 2022-11-10 00:31:04 -05:00 committed by GitHub
parent d496fcfe4a
commit 1a013728fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 263 additions and 100 deletions

View File

@ -14,18 +14,84 @@ import java.util.NoSuchElementException;
* This class performs ray tracing and iterates along blocks on a line
*/
public class BlockIterator implements Iterator<Point> {
private final Vec direction;
private final Point start;
private final Point[] points = new Point[3];
private final double[] distances = new double[3];
private final short[] signums = new short[3];
private final Vec end;
private final boolean smooth;
private boolean foundEnd = false;
//length of ray from current position to next x or y-side
double sideDistX;
double sideDistY;
double sideDistZ;
//length of ray from one x or y-side to next x or y-side
private final double deltaDistX;
private final double deltaDistY;
private final double deltaDistZ;
//which box of the map we're in
int mapX;
int mapY;
int mapZ;
private final ArrayDeque<Point> extraPoints = new ArrayDeque<>();
/**
* Constructs the BlockIterator.
* <p>
* This considers all blocks as 1x1x1 in size.
*
* @param start A Vector giving the initial position for the trace
* @param direction A Vector pointing in the direction for the trace
* @param yOffset The trace begins vertically offset from the start vector
* by this value
* @param smooth A boolean indicating whether the cast should be smooth.
* Smooth casts will only include one block when intersecting multiple axis lines.
* @param maxDistance This is the maximum distance in blocks for the
* trace. Setting this value above 140 may lead to problems with
* unloaded chunks. A value of 0 indicates no limit
*/
public BlockIterator(@NotNull Vec start, @NotNull Vec direction, double yOffset, double maxDistance, boolean smooth) {
start = start.add(0, yOffset, 0);
end = start.add(direction.normalize().mul(maxDistance));
if (direction.isZero()) this.foundEnd = true;
this.smooth = smooth;
Vec ray = direction.normalize();
//which box of the map we're in
mapX = start.blockX();
mapY = start.blockY();
mapZ = start.blockZ();
signums[0] = (short) Math.signum(direction.x());
signums[1] = (short) Math.signum(direction.y());
signums[2] = (short) Math.signum(direction.z());
deltaDistX = (ray.x() == 0) ? 1e30 : Math.abs(1 / ray.x());
deltaDistY = (ray.y() == 0) ? 1e30 : Math.abs(1 / ray.y()); // Find grid intersections for x, y, z
deltaDistZ = (ray.z() == 0) ? 1e30 : Math.abs(1 / ray.z()); // This works by calculating and storing the distance to the next grid intersection on the x, y and z axis
//calculate step and initial sideDist
if (ray.x() < 0) {
sideDistX = (start.x() - mapX) * deltaDistX;
} else {
sideDistX = (mapX + 1.0 - start.x()) * deltaDistX;
}
if (ray.y() < 0) {
sideDistY = (start.y() - mapY) * deltaDistY;
} else {
sideDistY = (mapY + 1.0 - start.y()) * deltaDistY;
}
if (ray.z() < 0) {
sideDistZ = (start.z() - mapZ) * deltaDistZ;
} else {
sideDistZ = (mapZ + 1.0 - start.z()) * deltaDistZ;
}
}
/**
* Constructs the BlockIterator.
* <p>
@ -40,27 +106,7 @@ public class BlockIterator implements Iterator<Point> {
* unloaded chunks. A value of 0 indicates no limit
*/
public BlockIterator(@NotNull Vec start, @NotNull Vec direction, double yOffset, double maxDistance) {
this.direction = direction;
this.start = start.add(0, yOffset, 0);
this.end = start.add(0, yOffset, 0).add(direction.normalize().mul(maxDistance)).apply(Vec.Operator.FLOOR);
if (this.direction.isZero()) this.foundEnd = true;
signums[0] = (short) Math.signum(direction.x());
signums[1] = (short) Math.signum(direction.y());
signums[2] = (short) Math.signum(direction.z());
// Find grid intersections for x, y, z
// This works by calculating and storing the distance to the next grid intersection on the x, y and z axis
// On every iteration, we return the nearest grid intersection and update it
calculateIntersectionX(start, direction, signums[0] > 0 ? 1 : 0);
calculateIntersectionY(start, direction, signums[1] > 0 ? 1 : 0);
calculateIntersectionZ(start, direction, signums[2] > 0 ? 1 : 0);
// If directions are 0, set distances to max to stop the intersection point from being used
if (direction.x() == 0) distances[0] = Double.MAX_VALUE;
if (direction.y() == 0) distances[1] = Double.MAX_VALUE;
if (direction.z() == 0) distances[2] = Double.MAX_VALUE;
this(start, direction, yOffset, maxDistance, false);
}
/**
@ -77,7 +123,7 @@ public class BlockIterator implements Iterator<Point> {
*/
public BlockIterator(@NotNull Pos pos, double yOffset, int maxDistance) {
this(pos.asVec(), pos.direction(), yOffset, maxDistance);
this(pos.asVec(), pos.direction(), yOffset, maxDistance, false);
}
/**
@ -91,7 +137,7 @@ public class BlockIterator implements Iterator<Point> {
*/
public BlockIterator(@NotNull Pos pos, double yOffset) {
this(pos.asVec(), pos.direction(), yOffset, 0);
this(pos.asVec(), pos.direction(), yOffset, 0, false);
}
/**
@ -156,89 +202,54 @@ public class BlockIterator implements Iterator<Point> {
@Override
public Point next() {
if (foundEnd) throw new NoSuchElementException();
// If we have entries in the extra points queue, return those first
var res = extraPoints.isEmpty() ? updateClosest() : extraPoints.poll();
// If we have reached the end, set the flag
if (res.sameBlock(end)) foundEnd = true;
return new Vec(res.blockX(), res.blockY(), res.blockZ());
}
private void calculateIntersectionX(Point start, Vec direction, int signum) {
double x = Math.floor(start.x()) + signum;
double y = start.y() + (x - start.x()) * direction.y() / direction.x();
double z = start.z() + (x - start.x()) * direction.z() / direction.x();
points[0] = new Vec(x, y, z);
distances[0] = this.start.distance(points[0]);
}
private void calculateIntersectionY(Point start, Vec direction, int signum) {
double y = Math.floor(start.y()) + signum;
double x = start.x() + (y - start.y()) * direction.x() / direction.y();
double z = start.z() + (y - start.y()) * direction.z() / direction.y();
points[1] = new Vec(x, y, z);
distances[1] = this.start.distance(points[1]);
}
private void calculateIntersectionZ(Point start, Vec direction, int signum) {
double z = Math.floor(start.z()) + signum;
double x = start.x() + (z - start.z()) * direction.x() / direction.z();
double y = start.y() + (z - start.z()) * direction.y() / direction.z();
points[2] = new Vec(x, y, z);
distances[2] = this.start.distance(points[2]);
}
private Point updateClosest() {
// Find minimum distance
double minDistance = Double.MAX_VALUE;
for (int i = 0; i < 3; i++) {
if (distances[i] < minDistance) {
minDistance = distances[i];
}
if (!extraPoints.isEmpty()) {
var res = extraPoints.poll();
if (res.sameBlock(end)) foundEnd = true;
return res;
}
int[] sub = new int[3];
boolean needsX = Math.abs(distances[0] - minDistance) <= Vec.EPSILON;
boolean needsY = Math.abs(distances[1] - minDistance) <= Vec.EPSILON;
boolean needsZ = Math.abs(distances[2] - minDistance) <= Vec.EPSILON;
var current = new Vec(mapX, mapY, mapZ);
if (current.sameBlock(end)) foundEnd = true;
double closest = Math.min(sideDistX, Math.min(sideDistY, sideDistZ));
boolean needsX = sideDistX - closest < 1e-10;
boolean needsY = sideDistY - closest < 1e-10;
boolean needsZ = sideDistZ - closest < 1e-10;
// Update all points that are minimum distance
Point closest = null;
if (needsX) {
closest = points[0];
if (signums[0] == 1) sub[0] = 1;
calculateIntersectionX(points[0], direction, signums[0]);
}
if (needsY) {
closest = points[1];
if (signums[1] == 1) sub[1] = 1;
calculateIntersectionY(points[1], direction, signums[1]);
}
if (needsZ) {
closest = points[2];
if (signums[2] == 1) sub[2] = 1;
calculateIntersectionZ(points[2], direction, signums[2]);
sideDistZ += deltaDistZ;
mapZ += signums[2];
}
// If we pass a grid line in the positive direction, we subtract 1 to get the block we just passed over
closest = closest.sub(sub[0], sub[1], sub[2]);
if (needsX) {
sideDistX += deltaDistX;
mapX += signums[0];
}
if (needsY) {
sideDistY += deltaDistY;
mapY += signums[1];
}
// If multiple grid lines are cross at the same time, we need to add the blocks that are missed
if (needsX && needsY && needsZ) {
extraPoints.add(closest.add(signums[0], 0, 0));
extraPoints.add(closest.add(0, signums[1], 0));
extraPoints.add(closest.add(0, 0, signums[2]));
extraPoints.add(new Vec(signums[0] + current.x(), current.y(), current.z()));
if (smooth) return current;
extraPoints.add(new Vec(current.x(), signums[1] + current.y(), current.z()));
extraPoints.add(new Vec(current.x(), current.y(), signums[2] + current.z()));
} else if (needsX && needsY) {
extraPoints.add(closest.add(signums[0], 0, 0));
extraPoints.add(closest.add(0, signums[1], 0));
extraPoints.add(new Vec(signums[0] + current.x(), current.y(), current.z()));
if (smooth) return current;
extraPoints.add(new Vec(current.x(), signums[1] + current.y(), current.z()));
} else if (needsX && needsZ) {
extraPoints.add(closest.add(signums[0], 0, 0));
extraPoints.add(closest.add(0, 0, signums[2]));
extraPoints.add(new Vec(signums[0] + current.x(), current.y(), current.z()));
if (smooth) return current;
extraPoints.add(new Vec(current.x(), current.y(), signums[2] + current.z()));
} else if (needsY && needsZ) {
extraPoints.add(closest.add(0, signums[1], 0));
extraPoints.add(closest.add(0, 0, signums[2]));
extraPoints.add(new Vec(current.x(), signums[1] + current.y(), current.z()));
if (smooth) return current;
extraPoints.add(new Vec(current.x(), current.y(), signums[2] + current.z()));
}
return closest;
return current;
}
}

View File

@ -82,6 +82,158 @@ public class BlockIteratorTest {
assertFalse(iterator.hasNext());
}
@Test
public void testLongDistance() {
Vec s = new Vec(42.5, 0, 51.5);
Vec e = new Vec(-12, 0, -36);
BlockIterator iterator = new BlockIterator(s, e, 0, 37);
List<Point> points = new ArrayList<>();
while (iterator.hasNext()) {
points.add(iterator.next());
}
Point[] validPoints = new Point[] {
new Vec(42.0, 0.0, 51.0),
new Vec(42.0, 0.0, 50.0),
new Vec(41.0, 0.0, 50.0),
new Vec(42.0, 0.0, 49.0),
new Vec(41.0, 0.0, 49.0),
new Vec(41.0, 0.0, 48.0),
new Vec(41.0, 0.0, 47.0),
new Vec(40.0, 0.0, 47.0),
new Vec(41.0, 0.0, 46.0),
new Vec(40.0, 0.0, 46.0),
new Vec(40.0, 0.0, 45.0),
new Vec(40.0, 0.0, 44.0),
new Vec(39.0, 0.0, 44.0),
new Vec(40.0, 0.0, 43.0),
new Vec(39.0, 0.0, 43.0),
new Vec(39.0, 0.0, 42.0),
new Vec(39.0, 0.0, 41.0),
new Vec(38.0, 0.0, 41.0),
new Vec(39.0, 0.0, 40.0),
new Vec(38.0, 0.0, 40.0),
new Vec(38.0, 0.0, 39.0),
new Vec(38.0, 0.0, 38.0),
new Vec(37.0, 0.0, 38.0),
new Vec(38.0, 0.0, 37.0),
new Vec(37.0, 0.0, 37.0),
new Vec(37.0, 0.0, 36.0),
new Vec(37.0, 0.0, 35.0),
new Vec(36.0, 0.0, 35.0),
new Vec(37.0, 0.0, 34.0),
new Vec(36.0, 0.0, 34.0),
new Vec(36.0, 0.0, 33.0),
new Vec(36.0, 0.0, 32.0),
new Vec(35.0, 0.0, 32.0),
new Vec(36.0, 0.0, 31.0),
new Vec(35.0, 0.0, 31.0),
new Vec(35.0, 0.0, 30.0),
new Vec(35.0, 0.0, 29.0),
new Vec(34.0, 0.0, 29.0),
new Vec(35.0, 0.0, 28.0),
new Vec(34.0, 0.0, 28.0),
new Vec(34.0, 0.0, 27.0),
new Vec(34.0, 0.0, 26.0),
new Vec(33.0, 0.0, 26.0),
new Vec(34.0, 0.0, 25.0),
new Vec(33.0, 0.0, 25.0),
new Vec(33.0, 0.0, 24.0),
new Vec(33.0, 0.0, 23.0),
new Vec(32.0, 0.0, 23.0),
new Vec(33.0, 0.0, 22.0),
new Vec(32.0, 0.0, 22.0),
new Vec(32.0, 0.0, 21.0),
new Vec(32.0, 0.0, 20.0),
new Vec(31.0, 0.0, 20.0),
new Vec(32.0, 0.0, 19.0),
new Vec(31.0, 0.0, 19.0),
new Vec(31.0, 0.0, 18.0),
new Vec(31.0, 0.0, 17.0),
new Vec(30.0, 0.0, 17.0),
new Vec(31.0, 0.0, 16.0),
new Vec(30.0, 0.0, 16.0)
};
for (Point p : validPoints) {
assertContains(points, p);
}
assertEquals(validPoints.length, points.size());
}
@Test
public void testSkipping() {
Vec s = new Vec(0.5, 40, 0.5);
Vec e = new Vec(27, 0, 21);
BlockIterator iterator = new BlockIterator(s, e, 0, 34);
List<Point> points = new ArrayList<>();
while (iterator.hasNext()) {
points.add(iterator.next());
}
Point[] validPoints = new Point[] {
new Vec(0.0, 40.0, 0.0),
new Vec(1.0, 40.0, 0.0),
new Vec(1.0, 40.0, 1.0),
new Vec(2.0, 40.0, 1.0),
new Vec(2.0, 40.0, 2.0),
new Vec(3.0, 40.0, 2.0),
new Vec(3.0, 40.0, 3.0),
new Vec(4.0, 40.0, 3.0),
new Vec(5.0, 40.0, 3.0),
new Vec(4.0, 40.0, 4.0),
new Vec(5.0, 40.0, 4.0),
new Vec(6.0, 40.0, 4.0),
new Vec(6.0, 40.0, 5.0),
new Vec(7.0, 40.0, 5.0),
new Vec(7.0, 40.0, 6.0),
new Vec(8.0, 40.0, 6.0),
new Vec(8.0, 40.0, 7.0),
new Vec(9.0, 40.0, 7.0),
new Vec(10.0, 40.0, 7.0),
new Vec(10.0, 40.0, 8.0),
new Vec(11.0, 40.0, 8.0),
new Vec(11.0, 40.0, 9.0),
new Vec(12.0, 40.0, 9.0),
new Vec(12.0, 40.0, 10.0),
new Vec(13.0, 40.0, 10.0),
new Vec(14.0, 40.0, 10.0),
new Vec(13.0, 40.0, 11.0),
new Vec(14.0, 40.0, 11.0),
new Vec(15.0, 40.0, 11.0),
new Vec(15.0, 40.0, 12.0),
new Vec(16.0, 40.0, 12.0),
new Vec(16.0, 40.0, 13.0),
new Vec(17.0, 40.0, 13.0),
new Vec(17.0, 40.0, 14.0),
new Vec(18.0, 40.0, 14.0),
new Vec(19.0, 40.0, 14.0),
new Vec(19.0, 40.0, 15.0),
new Vec(20.0, 40.0, 15.0),
new Vec(20.0, 40.0, 16.0),
new Vec(21.0, 40.0, 16.0),
new Vec(21.0, 40.0, 17.0),
new Vec(22.0, 40.0, 17.0),
new Vec(23.0, 40.0, 17.0),
new Vec(22.0, 40.0, 18.0),
new Vec(23.0, 40.0, 18.0),
new Vec(24.0, 40.0, 18.0),
new Vec(24.0, 40.0, 19.0),
new Vec(25.0, 40.0, 19.0),
new Vec(25.0, 40.0, 20.0),
new Vec(26.0, 40.0, 20.0),
new Vec(26.0, 40.0, 21.0),
new Vec(27.0, 40.0, 21.0)
};
for (Point p : validPoints) {
assertContains(points, p);
}
assertEquals(validPoints.length, points.size());
}
@Test
public void testExactEnd() {
Vec s = new Vec(0.5, 0, 0.5);