Keep adjusting time and offsets in raytracing.

* Not actually a fix for anything we encountered.
* Nailed down blockinteract.visible raytracing issues to bad end-points
for raytracing.
* Also test/prepare logging test-cases for raytracing in general. Not
enabled, because we should have some flag/permission/command to check
before logging ~ 5KB per interact event.
This commit is contained in:
asofold 2015-06-30 00:59:48 +02:00
parent ab88b98704
commit 14049200a2
6 changed files with 207 additions and 98 deletions

View File

@ -140,7 +140,7 @@ public class Visible extends Check {
tMinZ > tMaxX && tMaxZ > tMaxY) {
// TODO: Option to tolerate a minimal difference in t and use a corrected position then.
tags.add("block_miss");
// Bukkit.getServer().broadcastMessage("visible: " + tMinX + "," + tMaxX + " | " + tMinY + "," + tMaxY + " | " + tMinZ + "," + tMaxZ);
// Bukkit.getServer().broadcastMessage("visible: " + tMinX + "," + tMaxX + " | " + tMinY + "," + tMaxY + " | " + tMinZ + "," + tMaxZ);
return true;
}
@ -170,7 +170,7 @@ public class Visible extends Check {
// Perform ray-tracing.
rayTracing.set(eyeX, eyeY, eyeZ, collideX, collideY, collideZ, blockX, blockY, blockZ);
rayTracing.loop();
final boolean collides;
if (rayTracing.collides()) {
tags.add("raytracing");
@ -188,7 +188,8 @@ public class Visible extends Check {
* Consider using a configuration setting for extended debugging
* (e.g. make DEBUG_LEVEL accessible by API and config).
*/
// TODO: public static void InteractRayTracing.logTestCase(...).
// TEST: Log as a false positive (!).
//NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, "blockinteract.visible test case:\n" + rayTracing.getTestCase(1.05, false));
}
return collides;
}
@ -233,7 +234,7 @@ public class Visible extends Check {
// Just the time within range.
return tMin + 1.0 / Math.abs(dir);
}
/**
* Correct the coordinate to be on the block (only if outside, for
* correcting inside-block to edge tMin has to be checked.

View File

@ -2,6 +2,7 @@ package fr.neatmonster.nocheatplus.utilities;
import java.util.Iterator;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Entity;
@ -26,6 +27,42 @@ public class FakeBlockCache extends BlockCache {
/** Cached shape values. */
private final CoordMap<double[]> boundsMapStored = new CoordMap<double[]>(23);
/**
* Convenience method to copy a cuboid region given by two endpoints without any order specified.
* @param other
* @param x0
* @param y0
* @param z0
* @param x1
* @param y1
* @param z1
* @param margin
*/
public void set(BlockCache other, double x0, double y0, double z0, double x1, double y1, double z1, double margin) {
set(other, Location.locToBlock(Math.min(x0, x1) - margin), Location.locToBlock(Math.min(y0, y1) - margin), Location.locToBlock(Math.min(z0, z1) - margin),
Location.locToBlock(Math.max(x0, x1) + margin), Location.locToBlock(Math.max(y0, y1) + margin), Location.locToBlock(Math.max(z0, z1) + margin));
}
/**
* Copy a cuboid region from the other BlockCache instance.
* @param other
* @param minX
* @param minY
* @param minZ
* @param maxX
* @param maxY
* @param maxZ
*/
public void set(BlockCache other, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y ++) {
for (int z = minZ; z <= maxZ; z ++) {
set(x, y, z, other.getTypeId(x, y, z), other.getData(x, y, z), other.getBounds(x, y, z));
}
}
}
}
/**
* Set with data=0 and bounds=full.
* @param x
@ -79,7 +116,7 @@ public class FakeBlockCache extends BlockCache {
* @param z
* @param typeId
* @param data
* @param bounds
* @param bounds Stores the given bounds directly.
*/
public void set(int x, int y, int z, int typeId, int data, double[] bounds) {
idMapStored.put(x, y, z, typeId);
@ -219,9 +256,17 @@ public class FakeBlockCache extends BlockCache {
* Return a line of java code to construct a new FakeBlockCache with the same content (no newlines).
* @param builder
* @param fbcName Variable name of the FakeBlockCache instance.
* @param boundsPrefix A prefix for bounds variables for the case of repeated content. If set to null, no optimization will be performed.
*/
public void toJava(final StringBuilder builder, final String fbcName) {
public void toJava(final StringBuilder builder, final String fbcName, final String boundsPrefix) {
builder.append("FakeBlockCache " + fbcName + " = new FakeBlockCache();");
final String fullBounds;
if (boundsPrefix != null) {
fullBounds = boundsPrefix + "_fb";
builder.append(" double[] " + fullBounds + " = new double[]{0.0, 0.0, 0.0, 1.0, 1.0, 1.0};" );
} else {
fullBounds = null;
}
// Assume id is always set.
final Iterator<Entry<Integer>> it = idMapStored.iterator();
final int airId = BlockProperties.getId(Material.AIR);
@ -233,16 +278,22 @@ public class FakeBlockCache extends BlockCache {
final Integer id = entry.getValue();
if (id == airId) {
builder.append(fbcName + ".set(" + x + ", " + y + ", " + z + ", " + id + ");");
} else {
}
else {
final Integer data = dataMapStored.get(x, y, z);
final double[] bounds = boundsMapStored.get(x, y, z);
if (bounds == null) {
if (data == null) { // Consider 0 too.
builder.append(fbcName + ".set(" + x + ", " + y + ", " + z + ", " + id + ");");
} else {
}
else {
builder.append(fbcName + ".set(" + x + ", " + y + ", " + z + ", " + id + ", " + data + ");");
}
} else {
}
else if (boundsPrefix != null && BlockCache.isFullBounds(bounds)) {
builder.append(fbcName + ".set(" + x + ", " + y + ", " + z + ", " + id + ", " + data + ", " + fullBounds + ");");;
}
else {
builder.append(fbcName + ".set(" + x + ", " + y + ", " + z + ", " + id + ", " + data + ", ");
DebugUtil.toJava(bounds, builder);
builder.append(");");

View File

@ -11,32 +11,33 @@ import org.bukkit.Location;
*/
public class InteractRayTracing extends RayTracing {
// private static final int[][] incr = new int[][]{
// {1, 0, 0},
// {0, 1, 0},
// {0, 0, 1},
// {-1, 0, 0},
// {0, -1, 0},
// {0, 0, -1},
// };
// private static final int[][] incr = new int[][]{
// {1, 0, 0},
// {0, 1, 0},
// {0, 0, 1},
// {-1, 0, 0},
// {0, -1, 0},
// {0, 0, -1},
// };
protected BlockCache blockCache = null;
protected boolean collides = false;
protected boolean strict = false;
protected final boolean strict;
protected int lastBx, lastBy, lastBz;
protected int targetX, targetY, targetZ;
public InteractRayTracing() {
super();
this(false);
}
public InteractRayTracing(boolean strict) {
super();
this.strict = strict;
this.forceStepEndPos = false; // Not needed here.
}
public BlockCache getBlockCache() {
@ -123,53 +124,53 @@ public class InteractRayTracing extends RayTracing {
return targetX != Integer.MAX_VALUE && blockX == targetX && blockY == targetY && blockZ == targetZ;
}
// /**
// * Check if the block may be interacted through by use of some workaround.
// *
// * @param blockX
// * @param blockY
// * @param blockZ
// * @return
// */
// private final boolean allowsWorkaround(final int blockX, final int blockY, final int blockZ) {
//
// // TODO: Recode this/other.
//
// // TODO: This could allow some bypasses for "strange" setups.
// // TODO: Consider using distance to target as heuristic ? [should not get smaller !?]
// // TODO: Consider (min/max) offset for distance.
// final int dX = blockX - lastBx;
// final int dY = blockY - lastBy;
// final int dZ = blockZ - lastBz;
// final double dSq = dX * dX + dY * dY + dZ * dZ;
// // TODO: Limit distance more here !?
// for (int i = 0; i < 6; i++) {
// final int[] dir = incr[i];
// final int rX = blockX + dir[0];
// if (Math.abs(lastBx - rX) > 1) {
// continue;
// }
// final int rY = blockY + dir[1];
// if (Math.abs(lastBy - rY) > 1) {
// continue;
// }
// final int rZ = blockZ + dir[2];
// if (Math.abs(lastBz - rZ) > 1) {
// continue;
// }
// final int dRx = rX - lastBx;
// final int dRy = rY - lastBy;
// final int dRz = rZ - lastBz;
// if (dRx * dRx + dRy * dRy + dRz * dRz <= dSq) {
// continue;
// }
// if (!doesCollide(rX, rY, rZ)) {
// // NOTE: Don't check "rX == targetBx && rZ == targetBz && rY == targetBy".
// return true;
// }
// }
// return false;
// }
// /**
// * Check if the block may be interacted through by use of some workaround.
// *
// * @param blockX
// * @param blockY
// * @param blockZ
// * @return
// */
// private final boolean allowsWorkaround(final int blockX, final int blockY, final int blockZ) {
//
// // TODO: Recode this/other.
//
// // TODO: This could allow some bypasses for "strange" setups.
// // TODO: Consider using distance to target as heuristic ? [should not get smaller !?]
// // TODO: Consider (min/max) offset for distance.
// final int dX = blockX - lastBx;
// final int dY = blockY - lastBy;
// final int dZ = blockZ - lastBz;
// final double dSq = dX * dX + dY * dY + dZ * dZ;
// // TODO: Limit distance more here !?
// for (int i = 0; i < 6; i++) {
// final int[] dir = incr[i];
// final int rX = blockX + dir[0];
// if (Math.abs(lastBx - rX) > 1) {
// continue;
// }
// final int rY = blockY + dir[1];
// if (Math.abs(lastBy - rY) > 1) {
// continue;
// }
// final int rZ = blockZ + dir[2];
// if (Math.abs(lastBz - rZ) > 1) {
// continue;
// }
// final int dRx = rX - lastBx;
// final int dRy = rY - lastBy;
// final int dRz = rZ - lastBz;
// if (dRx * dRx + dRy * dRy + dRz * dRz <= dSq) {
// continue;
// }
// if (!doesCollide(rX, rY, rZ)) {
// // NOTE: Don't check "rX == targetBx && rZ == targetBz && rY == targetBy".
// return true;
// }
// }
// return false;
// }
@Override
protected boolean step(final int blockX, final int blockY, final int blockZ, final double oX, final double oY, final double oZ, final double dT, final boolean isPrimary) {
@ -184,20 +185,43 @@ public class InteractRayTracing extends RayTracing {
}
return true;
}
// if (strict || blockX == lastBx && blockZ == lastBz && blockY == lastBy) {
// collides = true;
// return false;
// }
// // Check workarounds...
// if (isPrimary && allowsWorkaround(blockX, blockY, blockZ)) {
// lastBx = blockX;
// lastBy = blockY;
// lastBz = blockZ;
// return true;
// }
// if (strict || blockX == lastBx && blockZ == lastBz && blockY == lastBy) {
// collides = true;
// return false;
// }
// // Check workarounds...
// if (isPrimary && allowsWorkaround(blockX, blockY, blockZ)) {
// lastBx = blockX;
// lastBy = blockY;
// lastBz = blockZ;
// return true;
// }
// No workaround found.
collides = true;
return false;
}
/**
* Get a directly usable test case (in a closure), for copy and paste to TestInteractRayTracing.
* @param captureMargin
* @param expectCollide
* @return
*/
public String getTestCase(double captureMargin, boolean expectCollide) {
FakeBlockCache recorder = new FakeBlockCache();
recorder.set(this.blockCache, x0, y0, z0, x0 + dX, y0 + dY, z0 + dZ, captureMargin);
StringBuilder builder = new StringBuilder(10000);
// Add everything inside a closure for direct copy and paste.
builder.append('{');
// Set up the block cache.
recorder.toJava(builder, "fbc", "");
// Add the test case code.
builder.append("InteractRayTracing rt = new CenteredInteractRayTracing(false, " + targetX + ", " + targetY + ", " + targetZ + "); rt.setBlockCache(fbc);");
builder.append("TestRayTracing.runCoordinates(rt, new double[]{" + x0 + ", " + y0 + ", " + z0 + ", " + (x0 + dX) + ", " + (y0 + dY) + ", " + (z0 + dZ) + "}, " + expectCollide + ", " + !expectCollide + ", 0.0, false, \"ingame\");");
builder.append("rt.cleanup(); fbc.cleanup();");
builder.append('}');
recorder.cleanup();
return builder.toString();
}
}

View File

@ -10,7 +10,7 @@ import org.bukkit.Location;
public abstract class RayTracing {
// /** End point coordinates (from, to) */
// protected double x0, y0, z0, x1, y1, z1;
protected double x0, y0, z0; // x1, y1, z1;
// /** Total distance between end points. */
// protected double d;
@ -33,6 +33,9 @@ public abstract class RayTracing {
/** Tolerance for time, for checking the abort condition: 1.0 - t <= tol . */
protected double tol = 0.0;
/** Force calling step at the end position, for the case it is reached with block transitions. */
protected boolean forceStepEndPos = true;
/**
* Counting the number of steps along the primary line. Step is incremented
* before calling step(), and is 0 after set(...). Checking this from within
@ -65,9 +68,9 @@ public abstract class RayTracing {
* @param z1
*/
public void set(double x0, double y0, double z0, double x1, double y1, double z1) {
// this.x0 = x0;
// this.y0 = y0;
// this.z0 = z0;
this.x0 = x0;
this.y0 = y0;
this.z0 = z0;
// this.x1 = x1;
// this.y1 = y1;
// this.z1 = z1;
@ -136,7 +139,7 @@ public abstract class RayTracing {
* "off by x-th digit" or "t=0 transition"). Consider correcting t on
* base of the block coordinates in use.
*/
while (1.0 - t > tol) {
while (t + tol < 1.0) {
// Determine smallest time to block edge, per axis.
tX = tDiff(dX, oX, blockX == endBlockX);
tY = tDiff(dY, oY, blockY == endBlockY);
@ -167,7 +170,7 @@ public abstract class RayTracing {
}
// Abort if arrived.
if (t + tMin >= 1.0 - tol && isEndBlock()) {
if (t + tMin + tol >= 1.0 && isEndBlock()) {
break;
}
@ -201,10 +204,18 @@ public abstract class RayTracing {
if (!handleTransitions(transitions, transX, transY, transZ, tMin)) {
break;
}
// Check conditions for abort/end.
if (forceStepEndPos && t + tol >= 1.0) {
// Reached the end with transitions, ensure we check the end block.
step(blockX, blockY, blockZ, oX, oY, oZ, 0.0, true);
break;
}
} else {
// No transitions, finished.
break;
}
// Abort if done or exceeded maxSteps.
if (transitions == 0 || step >= maxSteps) {
// Ensure not to go beyond maxSteps.
if (step >= maxSteps) {
break;
}
}
@ -228,36 +239,48 @@ public abstract class RayTracing {
}
// Apply all transitions to the primary line.
double tcMin = 1.0; // Corrected absolute time to reach the resulting block position.
if (transX) {
if (dX > 0.0) {
blockX ++;
oX = 0.0;
tcMin = Math.min(tcMin, ((double) blockX - x0) / dX);
}
else {
blockX --;
oX = 1.0;
tcMin = Math.min(tcMin, (1.0 + (double) blockX - x0) / dX);
}
}
if (transY) {
if (dY > 0.0) {
blockY ++;
oY = 0.0;
tcMin = Math.min(tcMin, ((double) blockY - y0) / dY);
}
else {
blockY --;
oY = 1.0;
tcMin = Math.min(tcMin, (1.0 + (double) blockY - y0) / dY);
}
}
if (transZ) {
if (dZ > 0.0) {
blockZ ++;
oZ = 0.0;
tcMin = Math.min(tcMin, ((double) blockZ - z0) / dZ);
}
else {
blockZ --;
oZ = 1.0;
tcMin = Math.min(tcMin, (1.0 + (double) blockZ - z0) / dZ);
}
}
// Correct time and offsets based on tcMin.
oX = x0 + tcMin * dX - (double) blockX;
oY = y0 + tcMin * dY - (double) blockY;
oZ = z0 + tcMin * dZ - (double) blockZ;
t = tcMin;
return true; // Continue loop.
}

View File

@ -142,4 +142,14 @@ public class TestInteractRayTracing {
bc.cleanup();
}
/**
* Test cases taken from ingame logging.
*/
@Test
public void testIngame() {
// Circle around the corners of 4 blocks with left button pressed down (random sample).
// Bad end coords (should fail):
{FakeBlockCache fbc = new FakeBlockCache(); double[] _fb = new double[]{0.0, 0.0, 0.0, 1.0, 1.0, 1.0};fbc.set(142, 67, 221, 3, 0, _fb);fbc.set(142, 67, 217, 3, 0, _fb);fbc.set(142, 69, 219, 0);fbc.set(142, 68, 218, 2, 0, _fb);fbc.set(142, 70, 220, 0);fbc.set(142, 71, 217, 0);fbc.set(142, 71, 221, 0);fbc.set(143, 67, 218, 3, 0, _fb);fbc.set(143, 68, 217, 2, 0, _fb);fbc.set(143, 68, 221, 2, 0, _fb);fbc.set(143, 69, 220, 0);fbc.set(143, 70, 219, 0);fbc.set(143, 71, 218, 0);fbc.set(144, 67, 219, 3, 0, _fb);fbc.set(144, 68, 220, 2, 0, _fb);fbc.set(144, 69, 217, 0);fbc.set(144, 69, 221, 31, 1, new double[] {0.09999999403953552, 0.0, 0.09999999403953552, 0.8999999761581421, 0.800000011920929, 0.8999999761581421});fbc.set(144, 70, 218, 0);fbc.set(144, 71, 219, 0);fbc.set(145, 67, 220, 3, 0, _fb);fbc.set(145, 68, 219, 2, 0, _fb);fbc.set(145, 69, 218, 0);fbc.set(145, 70, 217, 0);fbc.set(145, 70, 221, 0);fbc.set(145, 71, 220, 0);fbc.set(142, 68, 217, 2, 0, _fb);fbc.set(142, 68, 221, 2, 0, _fb);fbc.set(142, 67, 218, 3, 0, _fb);fbc.set(142, 69, 220, 0);fbc.set(142, 70, 219, 0);fbc.set(142, 71, 218, 0);fbc.set(143, 67, 217, 3, 0, _fb);fbc.set(143, 67, 221, 3, 0, _fb);fbc.set(143, 68, 218, 49, 0, _fb);fbc.set(143, 69, 219, 0);fbc.set(143, 70, 220, 0);fbc.set(143, 71, 217, 0);fbc.set(143, 71, 221, 0);fbc.set(144, 67, 220, 3, 0, _fb);fbc.set(144, 68, 219, 49, 0, _fb);fbc.set(144, 69, 218, 0);fbc.set(144, 70, 217, 0);fbc.set(144, 70, 221, 0);fbc.set(144, 71, 220, 0);fbc.set(145, 67, 219, 3, 0, _fb);fbc.set(145, 68, 220, 2, 0, _fb);fbc.set(145, 69, 217, 0);fbc.set(145, 69, 221, 50, 5, new double[] {0.4000000059604645, 0.0, 0.4000000059604645, 0.6000000238418579, 0.6000000238418579, 0.6000000238418579});fbc.set(145, 70, 218, 0);fbc.set(145, 71, 219, 0);fbc.set(142, 67, 219, 3, 0, _fb);fbc.set(142, 70, 218, 0);fbc.set(142, 69, 221, 31, 1, new double[] {0.09999999403953552, 0.0, 0.09999999403953552, 0.8999999761581421, 0.800000011920929, 0.8999999761581421});fbc.set(142, 69, 217, 0);fbc.set(142, 68, 220, 2, 0, _fb);fbc.set(142, 71, 219, 0);fbc.set(143, 67, 220, 3, 0, _fb);fbc.set(143, 68, 219, 49, 0, _fb);fbc.set(143, 69, 218, 0);fbc.set(143, 70, 217, 0);fbc.set(143, 70, 221, 0);fbc.set(143, 71, 220, 0);fbc.set(144, 67, 217, 3, 0, _fb);fbc.set(144, 67, 221, 3, 0, _fb);fbc.set(144, 68, 218, 49, 0, _fb);fbc.set(144, 69, 219, 0);fbc.set(144, 70, 220, 0);fbc.set(144, 71, 217, 0);fbc.set(144, 71, 221, 0);fbc.set(145, 67, 218, 3, 0, _fb);fbc.set(145, 68, 217, 2, 0, _fb);fbc.set(145, 68, 221, 2, 0, _fb);fbc.set(145, 69, 220, 0);fbc.set(145, 70, 219, 0);fbc.set(145, 71, 218, 0);fbc.set(142, 68, 219, 2, 0, _fb);fbc.set(142, 70, 217, 0);fbc.set(142, 67, 220, 3, 0, _fb);fbc.set(142, 69, 218, 31, 1, new double[] {0.09999999403953552, 0.0, 0.09999999403953552, 0.8999999761581421, 0.800000011920929, 0.8999999761581421});fbc.set(142, 70, 221, 0);fbc.set(142, 71, 220, 0);fbc.set(143, 67, 219, 3, 0, _fb);fbc.set(143, 68, 220, 2, 0, _fb);fbc.set(143, 69, 217, 0);fbc.set(143, 69, 221, 0);fbc.set(143, 70, 218, 0);fbc.set(143, 71, 219, 0);fbc.set(144, 67, 218, 3, 0, _fb);fbc.set(144, 68, 217, 2, 0, _fb);fbc.set(144, 68, 221, 2, 0, _fb);fbc.set(144, 69, 220, 0);fbc.set(144, 70, 219, 0);fbc.set(144, 71, 218, 0);fbc.set(145, 67, 217, 3, 0, _fb);fbc.set(145, 67, 221, 3, 0, _fb);fbc.set(145, 68, 218, 2, 0, _fb);fbc.set(145, 69, 219, 0);fbc.set(145, 70, 220, 0);fbc.set(145, 71, 217, 0);fbc.set(145, 71, 221, 0);InteractRayTracing rt = new CenteredInteractRayTracing(false, 144, 68, 218); rt.setBlockCache(fbc);TestRayTracing.runCoordinates(rt, new double[]{144.01901074886095, 70.62, 220.1221052415879, 144.07776715103876, 68.99423513239826, 219.0}, true, false, 0.0, false, "ingame");rt.cleanup(); fbc.cleanup();}
}
}

View File

@ -111,16 +111,16 @@ public class TestRayTracing {
doFail("dT < 0 at t = " + StringUtil.fdec3.format(t), coords);
}
// TODO: Check if this check makes sense at all (dT=0 happens during multi-transitions.)l.
// TODO: Check if this check makes sense at all (dT=0 happens during multi-transitions).
// if (dT == 0.0 && 1.0 - (t + dT) > tol) {
// if (!ignEdge(oX, dX) && !ignEdge(oY, dY) && !ignEdge(oZ, dZ)) {
// doFail("Premature dT = 0 at t = " + StringUtil.fdec3.format(t), coords);
// }
// }
checkOffset(oX, "x");
checkOffset(oY, "y");
checkOffset(oZ, "z");
// checkOffset(oX, "x");
// checkOffset(oY, "y");
// checkOffset(oZ, "z");
// TODO: check with last block coordinates
if (lbx == blockX && lby == blockY && lbz == blockZ) {
@ -143,11 +143,11 @@ public class TestRayTracing {
return true;
}
private void checkOffset(double offset, String name) {
if (offset < 0.0 || offset > 1.0) {
doFail("Bad " + name + "-offset: " + offset, coords);
}
}
// private void checkOffset(double offset, String name) {
// if (offset < 0.0 || offset > 1.0) {
// doFail("Bad " + name + "-offset: " + offset, coords);
// }
// }
@Override
public void loop() {
@ -193,17 +193,17 @@ public class TestRayTracing {
return crt;
}
public static void dump(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double t, double dT) {
public static void dump(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double t, double dT, boolean isPrimary) {
String sdt = StringUtil.fdec3.format(dT);
if ("0".equals(sdt) && dT > 0) sdt = "0.X";
System.out.println(StringUtil.fdec3.format(t) + " (+" + sdt + "): " + blockX + ", "+blockY + ", " + blockZ + " / " + StringUtil.fdec3.format(oX) + ", " + StringUtil.fdec3.format(oY)+ ", " + StringUtil.fdec3.format(oZ));
System.out.println(StringUtil.fdec3.format(t) + " (+" + sdt + "): " + blockX + ", "+blockY + ", " + blockZ + " / " + StringUtil.fdec3.format(oX) + ", " + StringUtil.fdec3.format(oY)+ ", " + StringUtil.fdec3.format(oZ) + (isPrimary ? " (primary)" : ""));
}
public static RayTracing dumpRawRayTracing(final double[] coords) {
RayTracing rt = new RayTracing(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]) {
@Override
protected boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT, boolean isPrimary) {
dump(blockX, blockY, blockZ, oX, oY, oZ, t, dT);
dump(blockX, blockY, blockZ, oX, oY, oZ, t, dT, isPrimary);
if (step > maxSteps(dX, dY, dZ)) {
System.out.println("[WARNING] Max steps exceeded: " + maxSteps(dX, dY, dZ));
return false;