Add basic ray-tracing support.

This commit is contained in:
asofold 2013-02-13 21:26:26 +01:00
parent 7d5e5deb24
commit 9f7d3c6cfc
2 changed files with 337 additions and 0 deletions

View File

@ -0,0 +1,160 @@
package fr.neatmonster.nocheatplus.utilities;
import org.bukkit.Location;
/**
* Ray tracing for block coordinates with entry point offsets.
* @author mc_dev
*
*/
public abstract class RayTracing {
// /** End point coordinates (from, to) */
// protected double x0, y0, z0, x1, y1, z1;
// /** Total distance between end points. */
// protected double d;
/** Distance per axis. */
protected double dX, dY, dZ;
/** Current block. */
protected int blockX, blockY, blockZ;
/** Offset within current block. */
protected double oX, oY, oZ;
/** Current "time" in [0..1]. */
protected double t = Double.MIN_VALUE;
/** Tolerance for time, for checking the abort condition: 1.0 - t <= tol . */
protected double tol = 0.0;
public RayTracing(double x0, double y0, double z0, double x1, double y1, double z1){
set(x0, y0, z0, x1, y1, z1);
}
public void set(double x0, double y0, double z0, double x1, double y1, double z1){
// // TODO: Consider not using end-points at all.
// this.x0 = x0;
// this.y0 = y0;
// this.z0 = z0;
// this.x1 = x1;
// this.y1 = y1;
// this.z1 = z1;
// // Set the "runtime" info.
// d = CheckUtils.distance(x0, y0, z0, x1, y1, z1);
dX = x1 - x0;
dY = y1 - y0;
dZ = z1 - z0;
blockX = Location.locToBlock(x0);
blockY = Location.locToBlock(y0);
blockZ = Location.locToBlock(z0);
oX = (double) (x0 - blockX);
oY = (double) (y0 - blockY);
oZ = (double) (z0 - blockZ);
t = 0.0;
}
private static final double tDiff(final double dTotal, final double offset){
if (dTotal > 0.0){
return (1 - offset) / dTotal;
}
else if (dTotal < 0.0){
return offset / -dTotal;
}
else{
return Double.MAX_VALUE;
}
}
/**
* Loop through blocks.
*/
public void loop(){
// TODO: Might intercept 0 dist ?
// Time to block edge.
double tX, tY, tZ, tMin;
while (1.0 - t > tol){
// Determine smallest time to block edge.
tX = tDiff(dX, oX);
tY = tDiff(dY, oY);
tZ = tDiff(dZ, oZ);
tMin = Math.min(tX, Math.min(tY, tZ));
if (tMin == Double.MAX_VALUE || t + tMin > 1.0) tMin = 1.0 - t;
// Call step with appropriate arguments.
if (!step(blockX, blockY, blockZ, oX, oY, oZ, tMin)) break; // || tMin == 0) break;
if (t + tMin >= 1.0 - tol) break;
// Advance (add to t etc.).
// x
oX += tMin * dX;
if (tX == tMin){
if (dX < 0){
oX = 1;
blockX --;
}
else{
oX = 0;
blockX ++;
}
}
else if (oX >= 1){
oX -= 1;
blockX ++;
}
else if (oX < 0){
oX += 1;
blockX --;
}
// y
oY += tMin * dY;
if (tY == tMin){
if (dY < 0){
oY = 1;
blockY --;
}
else{
oY = 0;
blockY ++;
}
}
else if (oY >= 1){
oY -= 1;
blockY ++;
}
else if (oY < 0){
oY += 1;
blockY --;
}
// z
oZ += tMin * dZ;
if (tZ == tMin){
if (dZ < 0){
oZ = 1;
blockZ --;
}
else{
oZ = 0;
blockZ ++;
}
}
else if (oZ >= 1){
oZ -= 1;
blockZ ++;
}
else if (oZ < 0){
oZ += 1;
blockZ --;
}
t += tMin;
}
}
/**
* One step in the loop.
* @return If to continue loop.
*/
protected abstract boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT);
}

View File

@ -0,0 +1,177 @@
package fr.neatmonster.nocheatplus.test;
import static org.junit.Assert.fail;
import java.util.Random;
import org.bukkit.Location;
import org.junit.Test;
import fr.neatmonster.nocheatplus.utilities.RayTracing;
import fr.neatmonster.nocheatplus.utilities.StringUtil;
public class TestRayTracing {
protected static final Random random = new Random(System.nanoTime() + 13391);
public static class CountRayTracing extends RayTracing{
public CountRayTracing(double x0, double y0, double z0, double x1, double y1, double z1) {
super(x0, y0, z0, x1, y1, z1);
}
protected int done = 0;
@Override
protected boolean step(int blockX, int blockY, int blockZ, double oX,
double oY, double oZ, double dT) {
done ++;
return true;
}
public int loopCount() {
super.loop();
return done;
}
}
public static double[] randomCoords(double max){
double[] res = new double[6];
for (int i = 0; i < 6 ; i++){
res[i] = (random.nextDouble() * 2.0 - 1.0 ) * max;
}
return res;
}
public static void doFail(String message, double[] coords) {
System.out.println("---- Failure trace ----");
System.out.println(message);
if (coords != null){
System.out.println("{" + coords[0] + ", " + coords[1]+ ", " + coords[2] + " , " + coords[3] + ", " + coords[4]+ ", " + coords[5] + "}");
dumpRawRayTracing(coords);
}
fail(message);
}
/**
* Mostly block-coordinate consistency checking.
* @param coords
* @return
*/
public static RayTracing checkConsistency(final double[] coords){
RayTracing rt = new RayTracing(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]) {
protected int lbx, lby, lbz;
protected int step = 0;
/* (non-Javadoc)
* @see fr.neatmonster.nocheatplus.utilities.RayTracing#set(double, double, double, double, double, double)
*/
@Override
public void set(double x0, double y0, double z0, double x1, double y1, double z1) {
super.set(x0, y0, z0, x1, y1, z1);
lbx = blockX - 1;
lby = blockY - 1;
lbz = blockZ - 1;
}
private boolean ignEdge(double offset, double dTotal){
return offset == 1 && dTotal > 0 || offset == 0 && dTotal < 0;
}
@Override
protected boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT) {
// TODO: This does not check last step for some occasions where it should.
step ++;
if (dT == 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);
}
}
// TODO: check with last block coordinates
if (lbx == blockX && lby == blockY && lbz == blockZ){
if (1.0 - (t + dT) > tol){
doFail("Expect block coordinates to change with each step (step=" + step + ", t=" + StringUtil.fdec3.format(t) +").", coords);
}
}
// TODO: check offsets
// Set to current.
lbx = blockX;
lby = blockY;
lbz = blockZ;
return true;
}
@Override
public void loop() {
super.loop();
checkBlockTarget(coords[3], blockX, oX, dX, "x");
checkBlockTarget(coords[4], blockY, oY, dY, "y");
checkBlockTarget(coords[5], blockZ, oZ, dZ, "z");
}
private void checkBlockTarget(double target, int current, double offset, double dTotal, String name){
int b = Location.locToBlock(target);
if (current != b){
// 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;
// Failure.
doFail("Bad target " + name + "-coordinate: " + current + " instead of " + b, coords);
}
}
};
rt.loop();
return rt;
}
public static RayTracing checkNumberOfSteps(double[] coords, int steps) {
CountRayTracing crt = new CountRayTracing(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
int done = crt.loopCount();
if (done != steps) doFail("Wrong number of steps: " + done + " instead of " + steps, coords);
return crt;
}
public static void dump(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double t, double dT) {
System.out.println(StringUtil.fdec3.format(t) + " (+" + StringUtil.fdec3.format(dT) + "): " + blockX + ", "+blockY + ", " + blockZ + " / " + StringUtil.fdec3.format(oX) + ", " + StringUtil.fdec3.format(oY)+ ", " + StringUtil.fdec3.format(oZ));
}
public static RayTracing dumpRawRayTracing(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) {
dump(blockX, blockY, blockZ, oX, oY, oZ, t, dT);
return true;
}
};
rt.loop();
return rt;
}
@Test
public void testNumberOfSteps(){
// Hand picked stuff.
checkNumberOfSteps(new double[]{0.5, 0.5, 0.5, 1.5, -0.5, 1.5}, 2);
}
@Test
public void testConsistency(){
// 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},
}){
checkConsistency(coords);
}
// Random tests.
for (int i = 0; i < 100000; i++){
checkConsistency(randomCoords(10.0));
}
}
}