001/** 002 * Copyright (c) 2011-2012 Ardor Labs AB. 003 * 004 * This file is part of the ArdorCraft API, developed by Rikard Herlitz. 005 */ 006 007package com.ardorcraft.collision; 008 009import com.ardor3d.math.MathUtils; 010import com.ardor3d.math.Vector3; 011import com.ardor3d.math.type.ReadOnlyVector3; 012import com.ardorcraft.world.BlockProvider; 013 014/** 015 * Ray tracing for a block world. 016 */ 017public class Tracer { 018 private double mult = 1; 019 private final Vector3 tmax = new Vector3(); 020 private final Vector3 tdelta = new Vector3(); 021 private final Vector3 newPos = new Vector3(); 022 023 private static final class DefaultHitTester implements HitTester { 024 @Override 025 public boolean isHit(final int blockId) { 026 return blockId != 0; 027 } 028 } 029 030 private final int maxHeight; 031 private final HitTester hitTester; 032 private final BlockProvider provider; 033 034 public Tracer(final BlockProvider provider) { 035 this.provider = provider; 036 hitTester = new DefaultHitTester(); 037 maxHeight = -1; 038 } 039 040 public Tracer(final BlockProvider provider, final HitTester hitTester) { 041 this.provider = provider; 042 this.hitTester = hitTester; 043 maxHeight = -1; 044 } 045 046 public Tracer(final BlockProvider provider, final HitTester hitTester, final int maxHeight) { 047 this.provider = provider; 048 this.hitTester = hitTester; 049 this.maxHeight = maxHeight; 050 } 051 052 public void traceCollision(ReadOnlyVector3 curpos, ReadOnlyVector3 raydir, final int iterations, 053 final IntersectionResult result) { 054 tmax.set(0, 0, 0); 055 tdelta.set(0, 0, 0); 056 057 mult = 1; 058 boolean back = false; 059 060 result.hit = false; 061 result.length = 0; 062 063 if (curpos.getY() < 0) { 064 return; 065 } 066 if (maxHeight > 0 && curpos.getY() >= maxHeight - 1 && raydir.getY() >= 0) { 067 return; 068 } 069 070 if (maxHeight > 0 && curpos.getY() >= maxHeight - 1) { 071 final double diff = maxHeight - 1 - curpos.getY(); 072 final double t = diff / raydir.getY(); 073 newPos.set(raydir).multiplyLocal(t).addLocal(curpos); 074 curpos = newPos; 075 } 076 077 int X = (int) MathUtils.floor(curpos.getX()); 078 int Y = (int) MathUtils.floor(curpos.getY()); 079 int Z = (int) MathUtils.floor(curpos.getZ()); 080 081 final int block1 = provider.getBlock(X, Y, Z); 082 if (block1 != 0 && hitTester.isHit(block1)) { 083 raydir = new Vector3(raydir).negateLocal(); 084 mult = -1; 085 back = true; 086 } 087 088 int stepX, stepY, stepZ; 089 double cbx, cby, cbz; 090 091 if (raydir.getX() > 0.0) { 092 stepX = 1; 093 cbx = X + 1; 094 } else { 095 stepX = -1; 096 cbx = X; 097 } 098 if (raydir.getY() > 0.0) { 099 stepY = 1; 100 cby = Y + 1; 101 } else { 102 stepY = -1; 103 cby = Y; 104 } 105 if (raydir.getZ() > 0.0) { 106 stepZ = 1; 107 cbz = Z + 1; 108 } else { 109 stepZ = -1; 110 cbz = Z; 111 } 112 113 if (raydir.getX() != 0) { 114 final double rxr = 1.0 / raydir.getX(); 115 tmax.setX((cbx - curpos.getX()) * rxr); 116 tdelta.setX(stepX * rxr); 117 } else { 118 tmax.setX(1000000); 119 } 120 if (raydir.getY() != 0) { 121 final double ryr = 1.0 / raydir.getY(); 122 tmax.setY((cby - curpos.getY()) * ryr); 123 tdelta.setY(stepY * ryr); 124 } else { 125 tmax.setY(1000000); 126 } 127 if (raydir.getZ() != 0) { 128 final double rzr = 1.0 / raydir.getZ(); 129 tmax.setZ((cbz - curpos.getZ()) * rzr); 130 tdelta.setZ(stepZ * rzr); 131 } else { 132 tmax.setZ(1000000); 133 } 134 135 int oldX = X, oldY = Y, oldZ = Z; 136 137 for (int i = 0; i < iterations; i++) { 138 if (tmax.getX() < tmax.getY()) { 139 if (tmax.getX() < tmax.getZ()) { 140 X = X + stepX; 141 final int block = provider.getBlock(X, Y, Z); 142 final boolean isHit = block != 0 && hitTester.isHit(block); 143 if (back && !isHit || !back && isHit) { 144 gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ); 145 result.hit = true; 146 return; 147 } 148 tmax.setX(tmax.getX() + tdelta.getX()); 149 } else { 150 Z = Z + stepZ; 151 final int block = provider.getBlock(X, Y, Z); 152 final boolean isHit = block != 0 && hitTester.isHit(block); 153 if (back && !isHit || !back && isHit) { 154 gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ); 155 result.hit = true; 156 return; 157 } 158 tmax.setZ(tmax.getZ() + tdelta.getZ()); 159 } 160 } else { 161 if (tmax.getY() < tmax.getZ()) { 162 Y = Y + stepY; 163 if (maxHeight > 0 && Y >= maxHeight - 1) { 164 return; 165 } 166 final int block = provider.getBlock(X, Y, Z); 167 final boolean isHit = block != 0 && hitTester.isHit(block); 168 if (back && !isHit || !back && isHit) { 169 gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ); 170 result.hit = true; 171 return; 172 } 173 tmax.setY(tmax.getY() + tdelta.getY()); 174 } else { 175 Z = Z + stepZ; 176 final int block = provider.getBlock(X, Y, Z); 177 final boolean isHit = block != 0 && hitTester.isHit(block); 178 if (back && !isHit || !back && isHit) { 179 gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ); 180 result.hit = true; 181 return; 182 } 183 tmax.setZ(tmax.getZ() + tdelta.getZ()); 184 } 185 } 186 187 oldX = X; 188 oldY = Y; 189 oldZ = Z; 190 } 191 } 192 193 private void gatherMin(final IntersectionResult result, final Vector3 tmax, final int X, final int Y, final int Z, 194 final int oldX, final int oldY, final int oldZ) { 195 result.oldPos.set(oldX, oldY, oldZ); 196 result.pos.set(X, Y, Z); 197 198 double min = tmax.getX(); 199 if (tmax.getY() < min) { 200 min = tmax.getY(); 201 } 202 if (tmax.getZ() < min) { 203 min = tmax.getZ(); 204 } 205 206 final double epsilon = 0.0001; 207 208 double length = min * mult; 209 if (length > 0) { 210 length = Math.max(length - epsilon, 0.0); 211 } else if (length < 0) { 212 length = Math.min(length - epsilon, 0.0); 213 } 214 215 result.length = length; 216 } 217}