I've tried to reimplement the Fast Graphics Gems Ray/AABB Intersection Method in C#:
// Based on "Fast Ray-Box Intersection" algorithm by Andrew Woo, "Graphics Gems", Academic Press, 1990
public unsafe Vector? IntersectionWith(Cuboid other) {
const int NUM_DIMENSIONS = 3;
Assure.Equal(NUM_DIMENSIONS, 3); // If that value is ever changed, this algorithm will need some maintenance
const byte QUADRANT_MIN = 0;
const byte QUADRANT_MAX = 1;
const byte QUADRANT_BETWEEN = 2;
// Step 1: Work out which direction from the start point to test for intersection for all 3 dimensions, and the distance
byte* quadrants = stackalloc byte[NUM_DIMENSIONS];
float* candidatePlanes = stackalloc float[NUM_DIMENSIONS];
float* cuboidMinPoints = stackalloc float[NUM_DIMENSIONS];
float* cuboidMaxPoints = stackalloc float[NUM_DIMENSIONS];
float maxDistance = Single.NegativeInfinity;
byte maxDistanceDimension = 0;
bool startPointIsInsideCuboid = true;
cuboidMinPoints[0] = other.X;
cuboidMinPoints[1] = other.Y;
cuboidMinPoints[2] = other.Z;
cuboidMaxPoints[0] = other.X + other.Width;
cuboidMaxPoints[1] = other.Y + other.Height;
cuboidMaxPoints[2] = other.Z + other.Depth;
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
if (StartPoint[i] < cuboidMinPoints[i]) {
quadrants[i] = QUADRANT_MIN;
candidatePlanes[i] = cuboidMinPoints[i];
startPointIsInsideCuboid = false;
}
else if (StartPoint[i] > cuboidMaxPoints[i]) {
quadrants[i] = QUADRANT_MAX;
candidatePlanes[i] = cuboidMaxPoints[i];
startPointIsInsideCuboid = false;
}
else {
quadrants[i] = QUADRANT_BETWEEN;
}
}
if (startPointIsInsideCuboid) return StartPoint;
// Step 2: Find farthest dimension from cuboid
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
// ReSharper disable once CompareOfFloatsByEqualityOperator Exact check is desired here: Anything other than 0f is usable
if (quadrants[i] != QUADRANT_BETWEEN && Orientation[i] != 0f) {
float thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];
if (thisDimensionDist > maxDistance) {
maxDistance = thisDimensionDist;
maxDistanceDimension = i;
}
}
}
if (maxDistance < 0f) return null;
if (maxDistance - Length > MathUtils.FlopsErrorMargin) return null;
float* intersectionPoint = stackalloc float[NUM_DIMENSIONS];
for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
if (maxDistanceDimension == i) {
intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
if (cuboidMinPoints[i] - intersectionPoint[i] > MathUtils.FlopsErrorMargin || intersectionPoint[i] - cuboidMaxPoints[i] > MathUtils.FlopsErrorMargin) return null;
}
else intersectionPoint[i] = candidatePlanes[i];
}
Vector result = new Vector(intersectionPoint[0], intersectionPoint[1], intersectionPoint[2]);
if (!IsInfiniteLength && Vector.DistanceSquared(StartPoint, result) > Length * Length) return null;
else return result;
}
However, although it sort of works, I'm getting incorrect results on the following part of a unit test:
Cuboid cuboid = new Cuboid(frontBottomLeft: new Vector(0f, 7.1f, 0f), width: 0f, height: 5f, depth: 0f);
Ray testRayC = new Ray(startPoint: new Vector(30f, 30f, 30f), orientation: new Vector(-1f, -1f, -1f));
Assert.AreEqual(
null,
testRayC.IntersectionWith(cuboid)
);
I am expecting null
from the call to testRayC.IntersectionWith(cuboid)
, but instead it returns a Vector(0, 12.1, 0)
, which is not a point on the ray at all.
So is it just a case of adding a final check that the calculated point is on the ray? Or (and this is what I suspect), have I made an error in transcribing the code? I have double and triple checked but didn't see anything obvious...