Let RayTracing test all combinations of transitions for all axes.

Previously only "random" transitions were taken, for simplicity. For the
sake of better debugging and consistency we check all combinations of
transitions now, calling the iteration with all transitions done at once
the "primary line", while calling step with a subset of transitions done
would be the "secondary line".

Currently an iteration might still end x-th digit off the target, so it
does not necessarily end on the target block itself. This is not a
problem for passable, but might be one for interaction and other
applications, thus this should be fixed at some point.
This commit is contained in:
asofold 2015-03-09 02:15:02 +01:00
parent e0f7e53c57
commit e871d205f6
5 changed files with 496 additions and 309 deletions

View File

@ -2242,7 +2242,7 @@ public class BlockProperties {
* @param z
* @param id
* @param bounds Not null: bounds of the block at x, y, z.
* @param flags Block flags for the block at x, y, z.
* @param flags Block flags for the block at x, y, z. Mix in F_COLLIDE_EDGES to disallow the "high edges" of blocks.
* @return
*/
public static final boolean collidesBlock(final BlockCache access, final double minX, double minY, final double minZ, final double maxX, final double maxY, final double maxZ, final int x, final int y, final int z, final int id, final double[] bounds, final long flags) {

View File

@ -1,6 +1,5 @@
package fr.neatmonster.nocheatplus.utilities;
import org.bukkit.Location;
/**
* Rough ray-tracing for interaction with something. This does not do any smart end-point guessing.
@ -26,8 +25,6 @@ public class InteractRayTracing extends RayTracing {
protected int lastBx, lastBy, lastBz;
protected int targetBx, targetBy, targetBz;
public InteractRayTracing(){
super();
}
@ -55,9 +52,6 @@ public class InteractRayTracing extends RayTracing {
lastBx = blockX;
lastBy = blockY;
lastBz = blockZ;
targetBx = Location.locToBlock(x1);
targetBy = Location.locToBlock(y1);
targetBz = Location.locToBlock(z1);
}
public boolean collides(){
@ -131,9 +125,11 @@ public class InteractRayTracing extends RayTracing {
}
@Override
protected boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT) {
protected boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT, final boolean isPrimary) {
// TODO: Make an optional, more precise check (like passable) ?
if (blockX == targetBx && blockZ == targetBz && blockY == targetBy || !doesCollide(blockX, blockY, blockZ)){
// TODO: Account for primary line vs. secondary.
// TODO: isEndBlock -> blockInteractedWith, because the offset edge might be on the next block.
if (isEndBlock() || !doesCollide(blockX, blockY, blockZ)){
lastBx = blockX;
lastBy = blockY;
lastBz = blockZ;

View File

@ -65,7 +65,7 @@ public class PassableRayTracing extends RayTracing{
}
@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) {
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) {
// Just delegate.
if (step == 1 && ignorefirst){
return true;

View File

@ -33,9 +33,20 @@ public abstract class RayTracing {
/** Tolerance for time, for checking the abort condition: 1.0 - t <= tol . */
protected double tol = 0.0;
/** Counting the number of steps. Step is incremented before calling step(), and is 0 after set(...). Checking this from within step means to get the current step number, checking after loop gets the number of steps done. */
/**
* Counting the number of steps along the primary line. Step is incremented
* before calling step(), and is 0 after set(...). Checking this from within
* step means to get the current step number, checking after loop gets the
* number of steps done.
*/
protected int step = 0;
/** If to call stepSecondary at all (secondary transitions).*/
protected boolean secondaryStep = true;
/** If to pass secondary transitions if ANY sub-call succeeds. */
protected boolean secondaryPassSingular = false;
/** Maximum steps that will be done. */
private int maxSteps = Integer.MAX_VALUE;
@ -80,11 +91,19 @@ public abstract class RayTracing {
step = 0;
}
private static final double tDiff(final double dTotal, final double offset, final int block, final int endBlock) {
/**
*
* @param dTotal
* @param offset
* @param isEndBlock If the end block coordinate is reached for this axis.
* @return
*/
private static final double tDiff(final double dTotal, final double offset, final boolean isEndBlock) {
// TODO: endBlock check only for == not </> ?
if (dTotal > 0.0) {
if (offset >= 1.0) {
// Static block change (e.g. diagonal move).
return 0.0; // block == endBlock ? Double.MAX_VALUE : 0.0;
return isEndBlock ? Double.MAX_VALUE : 0.0;
} else {
return (1.0 - offset) / dTotal;
}
@ -92,7 +111,7 @@ public abstract class RayTracing {
else if (dTotal < 0.0) {
if (offset <= 0.0) {
// Static block change (e.g. diagonal move).
return 0.0; //block == endBlock ? Double.MAX_VALUE : 0.0;
return isEndBlock ? Double.MAX_VALUE : 0.0;
} else {
return offset / -dTotal;
}
@ -106,23 +125,31 @@ public abstract class RayTracing {
* Loop through blocks.
*/
public void loop() {
// TODO: Might intercept 0 dist ?
// Time to block edge.
double tX, tY, tZ, tMin;
boolean changed;
// Number of axes to make a transition for.
int transitions;
// Transition direction per axis.
boolean transX, transY, transZ;
// Actual loop.
// TODO: Fix last transition not taken sometimes (with "off by x-th digit" or "t=0 transition").
while (1.0 - t > tol) {
// Determine smallest time to block edge.
tX = tDiff(dX, oX, blockX, endBlockX);
tY = tDiff(dY, oY, blockY, endBlockY);
tZ = tDiff(dZ, oZ, blockZ, endBlockZ);
tMin = Math.min(tX, Math.min(tY, tZ));
// Determine smallest time to block edge, per axis.
// TODO: if all coords are in the end block: ensure the full distance is taken towards the end coordinate.
tX = tDiff(dX, oX, blockX == endBlockX);
tY = tDiff(dY, oY, blockY == endBlockY);
tZ = tDiff(dZ, oZ, blockZ == endBlockZ);
// Adjust time.
tMin = Math.max(0.0, Math.min(tX, Math.min(tY, tZ)));
if (tMin == Double.MAX_VALUE) {
// All differences are 0 (no progress).
if (step < 1) {
// Allow one step.
// Allow one step always.
tMin = 0.0;
} else {
}
else {
break;
}
}
@ -130,69 +157,202 @@ public abstract class RayTracing {
// Set to the remaining distance (does trigger).
tMin = 1.0 - t;
}
// Call step with appropriate arguments.
// Step for the primary line.
step ++;
if (!step(blockX, blockY, blockZ, oX, oY, oZ, tMin)) {
if (!step(blockX, blockY, blockZ, oX, oY, oZ, tMin, true)) {
break;
}
if (t + tMin >= 1.0 - tol) { // && isEndBlock()) {
// Abort if arrived.
if (t + tMin >= 1.0 - tol && isEndBlock()) {
break;
}
// Advance (add to t etc.).
changed = false;
// Determine transitions, per axis.
transitions = 0;
transX = transY = transZ = false;
if (tX == tMin && blockX != endBlockX && dX != 0.0) {
transX = true;
transitions ++;
}
if (tY == tMin && blockY != endBlockY && dY != 0.0) {
transY = true;
transitions ++;
}
if (tZ == tMin && blockZ != endBlockZ && dZ != 0.0) {
transZ = true;
transitions ++;
}
// Advance on-block origin based on this move.
// TODO: Calculate "directly" based on this/next block or and/t?
oX = Math.min(1.0, Math.max(0.0, oX + tMin * dX));
oY = Math.min(1.0, Math.max(0.0, oY + tMin * dY));
oZ = Math.min(1.0, Math.max(0.0, oZ + tMin * dZ));
// TODO: Consider Heuristic change of the checking order for dy > 0 vs. dy < 0.
// x
if (tX == tMin && blockX != endBlockX) {
if (dX < 0) {
oX = 1.0;
blockX --;
changed = true;
}
else if (dX > 0) {
oX = 0.0;
blockX ++;
changed = true;
}
}
if (!changed) {
// y
if (tY == tMin && blockY != endBlockY) {
if (dY < 0) {
oY = 1.0;
blockY --;
changed = true;
}
else if (dY > 0) {
oY = 0.0;
blockY ++;
changed = true;
}
}
if (!changed) {
// z
if (tZ == tMin && blockZ != endBlockZ) {
if (dZ < 0) {
oZ = 1.0;
blockZ --;
changed = true;
}
else if (dZ > 0) {
oZ = 0.0;
blockZ ++;
changed = true;
}
}
}
}
t += tMin;
if (!changed || step >= maxSteps) {
// Advance time.
t = Math.min(1.0, t + tMin);
// Handle block transitions.
if (transitions > 0) {
if (!handleTransitions(transitions, transX, transY, transZ, tMin)) {
break;
}
}
// TODO: Catch special case with going beyond coordinates.
// Abort if done or exceeded maxSteps.
if (transitions == 0 || step >= maxSteps) {
break;
}
}
}
/**
*
* @param transitions
* @param transX
* @param transY
* @param transZ
* @param tMin
* @return If to continue at all.
*/
protected boolean handleTransitions(final int transitions, final boolean transX, final boolean transY, final boolean transZ, final double tMin) {
// Secondary transitions.
if (transitions > 1 && secondaryStep) {
if (!handleSecondaryTransitions(transitions, transX, transY, transZ, tMin)) {
return false;
}
}
// Apply all transitions to the primary line.
if (transX) {
if (dX > 0.0) {
blockX ++;
oX = 0.0;
}
else {
blockX --;
oX = 1.0;
}
}
if (transY) {
if (dY > 0.0) {
blockY ++;
oY = 0.0;
}
else {
blockY --;
oY = 1.0;
}
}
if (transZ) {
if (dZ > 0.0) {
blockZ ++;
oZ = 0.0;
}
else {
blockZ --;
oZ = 1.0;
}
}
return true; // Continue loop.
}
/**
*
* @param transitions
* @param transX
* @param transY
* @param transZ
* @param tMin
* @return If to continue at all.
*/
protected boolean handleSecondaryTransitions(final int transitions, final boolean transX, final boolean transY, final boolean transZ, final double tMin) {
// Handle one transition.
if (transX) {
if (step(blockX + (dX > 0 ? 1 : -1), blockY, blockZ, dX > 0 ? 0.0 : 1.0, oY, oZ, 0.0, false)) {
if (secondaryPassSingular) {
return true;
}
}
else if (!secondaryPassSingular) {
return false;
}
}
if (transY) {
if (step(blockX, blockY + (dY > 0 ? 1 : -1), blockZ, oX, dY > 0 ? 0.0 : 1.0, oZ, 0.0, false)) {
if (secondaryPassSingular) {
return true;
}
}
else if (!secondaryPassSingular) {
return false;
}
}
if (transZ) {
if (step(blockX, blockY, blockZ + (dZ > 0 ? 1 : -1), oX, oY, dZ > 0 ? 0.0 : 1.0, 0.0, false)) {
if (secondaryPassSingular) {
return true;
}
}
else if (!secondaryPassSingular) {
return false;
}
}
// Handle two transitions.
if (transitions == 3) {
if (!handleSecondaryDoubleTransitions(transitions, transX, transY, transZ, tMin)) {
return false;
}
}
// If secondaryPassSingular is true, all returned false, otherwise none returned false.
return !secondaryPassSingular;
}
/**
*
* @param transitions
* @param transX
* @param transY
* @param transZ
* @param tMin
* @return
*/
protected boolean handleSecondaryDoubleTransitions(final int transitions, final boolean transX, final boolean transY, final boolean transZ, final double tMin) {
// Two transitions at once, thus step directly.
// X and Y.
if (step(blockX + (dX > 0 ? 1 : -1), blockY + (dY > 0 ? 1 : -1), blockZ, dX > 0 ? 0.0 : 1.0, dY > 0 ? 0.0 : 1.0, oZ, 0.0, false)) {
if (secondaryPassSingular) {
return true;
}
}
else if (!secondaryPassSingular) {
return false;
}
// X and Z.
if (step(blockX + (dX > 0 ? 1 : -1), blockY, blockZ + (dZ > 0 ? 1 : -1), dX > 0 ? 0.0 : 1.0, oY, dZ > 0 ? 0.0 : 1.0, 0.0, false)) {
if (secondaryPassSingular) {
return true;
}
}
else if (!secondaryPassSingular) {
return false;
}
// Y and Z.
if (step(blockX, blockY + (dY > 0 ? 1 : -1), blockZ + (dZ > 0 ? 1 : -1), oX, dY > 0 ? 0.0 : 1.0, dZ > 0 ? 0.0 : 1.0, 0.0, false)) {
if (secondaryPassSingular) {
return true;
}
}
else if (!secondaryPassSingular) {
return false;
}
// If secondaryPassSingular is true, all returned false, otherwise none returned false.
return !secondaryPassSingular;
}
/**
@ -203,6 +363,10 @@ public abstract class RayTracing {
return false;
}
/**
* (Might later get changed to protected visibility.)
* @return
*/
public boolean isEndBlock() {
return blockX == endBlockX && blockY == endBlockY && blockZ == endBlockZ;
}
@ -236,8 +400,24 @@ public abstract class RayTracing {
/**
* One step in the loop.
* @return If to continue loop.
*
* @param blockX
* The block coordinates regarded in this step.
* @param blockY
* @param blockZ
* @param oX
* Origin relative to the block coordinates.
* @param oY
* @param oZ
* @param dT
* Amount of time regarded in this step (note that 0.0 is
* possible for transitions).
* @param isPrimary
* If this is along the primary line, for which all transitions
* are done at once. The secondary line would cover all
* combinations of transitions off the primary line.
* @return If to continue processing at all.
*/
protected abstract boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT);
protected abstract boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT, boolean isPrimary);
}

View File

@ -21,6 +21,7 @@ public class TestRayTracing {
protected static double maxFactor = 9.0;
protected static int maxSteps(double dX, double dY, double dZ) {
// TODO: Better calculation.
return (int) (maxFactor * (1 + Math.abs(dX) + Math.abs(dY) + Math.abs(dZ)));
}
@ -32,7 +33,7 @@ public class TestRayTracing {
@Override
protected boolean step(int blockX, int blockY, int blockZ, double oX,
double oY, double oZ, double dT) {
double oY, double oZ, double dT, boolean isPrimary) {
if (step > maxSteps(dX, dY, dZ)) {
System.out.println("[WARNING] Max steps exceeded: " + maxSteps(dX, dY, dZ));
return false;
@ -80,7 +81,7 @@ public class TestRayTracing {
protected double ldt = 0;
protected int step = 0;
protected double lox, loy, loz;
/* (non-Javadoc)
* @see fr.neatmonster.nocheatplus.utilities.RayTracing#set(double, double, double, double, double, double)
@ -91,8 +92,10 @@ public class TestRayTracing {
lbx = blockX - 1;
lby = blockY - 1;
lbz = blockZ - 1;
lox = oX;
loy = oY;
loz = oZ;
ldt = 0;
step = 0;
}
// private boolean ignEdge(double offset, double dTotal) {
@ -100,9 +103,8 @@ public class TestRayTracing {
// }
@Override
protected boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT) {
protected boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT, boolean isPrimary) {
// TODO: This does not check last step for some occasions where it should.
step ++;
if (dT < 0.0) {
doFail("dT < 0 at t = " + StringUtil.fdec3.format(t), coords);
@ -130,21 +132,28 @@ public class TestRayTracing {
lbx = blockX;
lby = blockY;
lbz = blockZ;
lox = oX;
loy = oY;
loz = oZ;
ldt = dT;
if (step > maxSteps(dX, dY, dZ)) doFail("max steps exceeded: " + maxSteps(dX, dY, dZ), coords);
if (step > maxSteps(dX, dY, dZ)) {
doFail("max steps exceeded: " + maxSteps(dX, dY, dZ), coords);
}
return true;
}
private void checkOffset(double offset, String name) {
if (offset < 0.0 || offset > 1.0) doFail("Bad " + name + "-offset: " + offset, coords);
if (offset < 0.0 || offset > 1.0) {
doFail("Bad " + name + "-offset: " + offset, coords);
}
}
@Override
public void loop() {
super.loop();
// checkBlockTarget(coords[3], blockX, oX, dX, ldt, "x");
// checkBlockTarget(coords[4], blockY, oY, dY, ldt, "y");
// checkBlockTarget(coords[5], blockZ, oZ, dZ, ldt, "z");
checkBlockTarget(coords[3], lbx, lox, dX, ldt, "x");
checkBlockTarget(coords[4], lby, loy, dY, ldt, "y");
checkBlockTarget(coords[5], lbz, loz, dZ, ldt, "z");
}
private void checkBlockTarget(double target, int current, double offset, double dTotal, double dT, String name) {
@ -153,18 +162,21 @@ public class TestRayTracing {
// TODO: Might do with or without these ?
// if (current == b + 1 && dTotal > 0 && offset == 0) return;
// if (current == b - 1 && dTotal < 0 && offset == 1) return;
if (Math.abs(dT * dTotal + offset + (double) current - target) <= 0.001) {
double diff = Math.abs(dT * dTotal + offset + (double) current - target);
if (diff <= 0.001) {
// TODO: Test how far off this usually is...
// TODO: Narrow down by edge coordinates or so.
return;
}
System.out.println(target + "|" + current + "|" + offset + "|" + dT * dTotal);
// Failure.
doFail("Bad target " + name + "-coordinate: " + current + " instead of " + b, coords);
doFail("Bad target " + name + "-coordinate: " + current + " instead of " + b + " (" + diff + " off)", coords);
}
}
};
rt.loop();
if (!rt.isEndBlock()) {
// TODO: Fix last transition not taken sometimes (with "off by x-th digit" or "t=0 transition").
// doFail("Incorrect end block.", coords);
}
return rt;
@ -177,9 +189,6 @@ public class TestRayTracing {
if (done != steps) {
doFail("Wrong number of steps: " + done + " instead of " + steps, coords);
}
if (!crt.isEndBlock()) {
//doFail("Incorrect end block.", coords);
}
return crt;
}
@ -192,7 +201,7 @@ public class TestRayTracing {
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) {
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);
if (step > maxSteps(dX, dY, dZ)) {
System.out.println("[WARNING] Max steps exceeded: " + maxSteps(dX, dY, dZ));
@ -211,7 +220,7 @@ public class TestRayTracing {
@Test
public void testNumberOfSteps() {
// Hand picked stuff.
checkNumberOfSteps(new double[]{0.5, 0.5, 0.5, 1.5, -0.5, 1.5}, 4);
checkNumberOfSteps(new double[]{0.5, 0.5, 0.5, 1.5, -0.5, 1.5}, 2);
}
@Test
@ -219,13 +228,16 @@ public class TestRayTracing {
// Past failures / making a difference.
for (double[] coords : new double[][] {
// Sort by x0.
new double[]{-9.873, -4.773, -3.387, -0.161, -1.879, -7.079},
new double[]{-3.0066423238842366, 0.8056808285866079, 5.359238045631369 , 2.0000000356757375, -2.3002237817433757, -5.889349195033338},
new double[]{2.5619753859456917, -5.010424935746547, -7.39326637860553 , -4.678643570182639, -2.0000000105642313, -4.634727842675916},
new double[]{7.388348424961977, -8.000000029346532, -2.5365675909347507 , 2.17126848312847, 3.236994108042559, -8.423292642985071},
new double[]{7.525633617461991, 2.654408573114717, 3.5119744782127893 , 9.99999995904821, 9.599753890871172, 6.721727939686946},
new double[] {-6.0, -4.0, -3.0 , -4.0, -3.0, -2.0},
new double[]{-3.0, 3.0, -6.0 , 2.0, -3.0, 4.0},
{-9.873, -4.773, -3.387, -0.161, -1.879, -7.079},
{-3.0066423238842366, 0.8056808285866079, 5.359238045631369 , 2.0000000356757375, -2.3002237817433757, -5.889349195033338},
{2.5619753859456917, -5.010424935746547, -7.39326637860553 , -4.678643570182639, -2.0000000105642313, -4.634727842675916},
{7.388348424961977, -8.000000029346532, -2.5365675909347507 , 2.17126848312847, 3.236994108042559, -8.423292642985071},
{7.525633617461991, 2.654408573114717, 3.5119744782127893 , 9.99999995904821, 9.599753890871172, 6.721727939686946},
{1.1, 1.1, 1.1 , 1.3, 1.3, 1.3},
{1.3, 1.3, 1.3 , 1.1, 1.1, 1.1},
{1.1, 1.1, 1.1 , 1.1, 1.1, 1.1},
{-6.0, -4.0, -3.0 , -4.0, -3.0, -2.0},
{-3.0, 3.0, -6.0 , 2.0, -3.0, 4.0},
}) {
checkConsistency(coords);
}
@ -237,7 +249,6 @@ public class TestRayTracing {
checkConsistency(randomCoords(10.0));
}
// TODO: make these work.
for (int i = 0; i < (e ? 10000000 : 1000); i++) {
checkConsistency(randomBlockCoords(6));
}