Als ich dieses Problem bei der Arbeit an meinen Cubes hatte , fand ich die Arbeit "Ein schneller Voxel-Traversal-Algorithmus für die Raytracing" von John Amanatides und Andrew Woo, 1987, die einen Algorithmus beschreibt, der auf diese Aufgabe angewendet werden kann. es ist genau und benötigt nur eine Schleifeniteration pro durchschnittenem Voxel.
Ich habe eine Implementierung der relevanten Teile des Algorithmus des Papiers in JavaScript geschrieben. Meine Implementierung fügt zwei Funktionen hinzu: Sie ermöglicht das Festlegen einer Grenze für die Entfernung des Raycasts (nützlich zum Vermeiden von Leistungsproblemen sowie zum Definieren einer begrenzten Reichweite) und das Berechnen der Fläche jedes Voxels, in das der Ray eingegeben wurde.
Der Eingabevektor origin
muss so skaliert werden, dass die Seitenlänge eines Voxels 1 beträgt. Die Länge des direction
Vektors ist nicht signifikant, kann jedoch die numerische Genauigkeit des Algorithmus beeinflussen.
Der Algorithmus arbeitet unter Verwendung einer parametrisierten Darstellung des Strahls origin + t * direction
. Für jede Koordinatenachse, verfolgen wir den t
Wert, den wir haben würde , wenn wir ein Voxel Grenze entlang dieser Achse (dh Änderung der ganzzahlige Teil der Koordinate) in den Variablen einen Schritt ausreichend zu durchqueren hat tMaxX
, tMaxY
und tMaxZ
. Dann machen wir einen Schritt (unter Verwendung der Variablen step
und tDelta
) entlang derjenigen Achse, die die geringste hat tMax
- dh derjenigen, die der Voxelgrenze am nächsten liegt.
/**
* Call the callback with (x,y,z,value,face) of all blocks along the line
* segment from point 'origin' in vector direction 'direction' of length
* 'radius'. 'radius' may be infinite.
*
* 'face' is the normal vector of the face of that block that was entered.
* It should not be used after the callback returns.
*
* If the callback returns a true value, the traversal will be stopped.
*/
function raycast(origin, direction, radius, callback) {
// From "A Fast Voxel Traversal Algorithm for Ray Tracing"
// by John Amanatides and Andrew Woo, 1987
// <http://www.cse.yorku.ca/~amana/research/grid.pdf>
// <http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.42.3443>
// Extensions to the described algorithm:
// • Imposed a distance limit.
// • The face passed through to reach the current cube is provided to
// the callback.
// The foundation of this algorithm is a parameterized representation of
// the provided ray,
// origin + t * direction,
// except that t is not actually stored; rather, at any given point in the
// traversal, we keep track of the *greater* t values which we would have
// if we took a step sufficient to cross a cube boundary along that axis
// (i.e. change the integer part of the coordinate) in the variables
// tMaxX, tMaxY, and tMaxZ.
// Cube containing origin point.
var x = Math.floor(origin[0]);
var y = Math.floor(origin[1]);
var z = Math.floor(origin[2]);
// Break out direction vector.
var dx = direction[0];
var dy = direction[1];
var dz = direction[2];
// Direction to increment x,y,z when stepping.
var stepX = signum(dx);
var stepY = signum(dy);
var stepZ = signum(dz);
// See description above. The initial values depend on the fractional
// part of the origin.
var tMaxX = intbound(origin[0], dx);
var tMaxY = intbound(origin[1], dy);
var tMaxZ = intbound(origin[2], dz);
// The change in t when taking a step (always positive).
var tDeltaX = stepX/dx;
var tDeltaY = stepY/dy;
var tDeltaZ = stepZ/dz;
// Buffer for reporting faces to the callback.
var face = vec3.create();
// Avoids an infinite loop.
if (dx === 0 && dy === 0 && dz === 0)
throw new RangeError("Raycast in zero direction!");
// Rescale from units of 1 cube-edge to units of 'direction' so we can
// compare with 't'.
radius /= Math.sqrt(dx*dx+dy*dy+dz*dz);
while (/* ray has not gone past bounds of world */
(stepX > 0 ? x < wx : x >= 0) &&
(stepY > 0 ? y < wy : y >= 0) &&
(stepZ > 0 ? z < wz : z >= 0)) {
// Invoke the callback, unless we are not *yet* within the bounds of the
// world.
if (!(x < 0 || y < 0 || z < 0 || x >= wx || y >= wy || z >= wz))
if (callback(x, y, z, blocks[x*wy*wz + y*wz + z], face))
break;
// tMaxX stores the t-value at which we cross a cube boundary along the
// X axis, and similarly for Y and Z. Therefore, choosing the least tMax
// chooses the closest cube boundary. Only the first case of the four
// has been commented in detail.
if (tMaxX < tMaxY) {
if (tMaxX < tMaxZ) {
if (tMaxX > radius) break;
// Update which cube we are now in.
x += stepX;
// Adjust tMaxX to the next X-oriented boundary crossing.
tMaxX += tDeltaX;
// Record the normal vector of the cube face we entered.
face[0] = -stepX;
face[1] = 0;
face[2] = 0;
} else {
if (tMaxZ > radius) break;
z += stepZ;
tMaxZ += tDeltaZ;
face[0] = 0;
face[1] = 0;
face[2] = -stepZ;
}
} else {
if (tMaxY < tMaxZ) {
if (tMaxY > radius) break;
y += stepY;
tMaxY += tDeltaY;
face[0] = 0;
face[1] = -stepY;
face[2] = 0;
} else {
// Identical to the second case, repeated for simplicity in
// the conditionals.
if (tMaxZ > radius) break;
z += stepZ;
tMaxZ += tDeltaZ;
face[0] = 0;
face[1] = 0;
face[2] = -stepZ;
}
}
}
}
function intbound(s, ds) {
// Find the smallest positive t such that s+t*ds is an integer.
if (ds < 0) {
return intbound(-s, -ds);
} else {
s = mod(s, 1);
// problem is now s+t*ds = 1
return (1-s)/ds;
}
}
function signum(x) {
return x > 0 ? 1 : x < 0 ? -1 : 0;
}
function mod(value, modulus) {
return (value % modulus + modulus) % modulus;
}
Permanenter Link zu dieser Version der Quelle auf GitHub .