rayTriangle function
Möller–Trumbore ray/triangle intersection.
Returns the distance along ray, or kNoHit. When it hits, outUv receives
the barycentric coordinates (u, v) of the second and third vertices, which
is what interpolating a normal or a texture coordinate at the hit needs.
The classic formulation: no plane equation, no separate inside test, and no precomputed per-triangle data — which matters because the alternative would mean building and invalidating an acceleration structure per mesh.
Implementation
double rayTriangle(
Ray ray,
Vector3 a,
Vector3 b,
Vector3 c, {
Vector2? outUv,
bool cullBackFace = false,
}) {
final e1x = b.x - a.x, e1y = b.y - a.y, e1z = b.z - a.z;
final e2x = c.x - a.x, e2y = c.y - a.y, e2z = c.z - a.z;
final d = ray.direction;
// p = direction x edge2
final px = d.y * e2z - d.z * e2y;
final py = d.z * e2x - d.x * e2z;
final pz = d.x * e2y - d.y * e2x;
final determinant = e1x * px + e1y * py + e1z * pz;
// A determinant at zero means the ray is parallel to the triangle's plane, or
// the triangle is degenerate. Both are misses, and both would divide by zero.
if (cullBackFace) {
if (determinant < 1e-12) return kNoHit;
} else if (determinant.abs() < 1e-12) {
return kNoHit;
}
final inverse = 1.0 / determinant;
final tx = ray.origin.x - a.x;
final ty = ray.origin.y - a.y;
final tz = ray.origin.z - a.z;
final u = (tx * px + ty * py + tz * pz) * inverse;
if (u < 0.0 || u > 1.0) return kNoHit;
// q = t x edge1
final qx = ty * e1z - tz * e1y;
final qy = tz * e1x - tx * e1z;
final qz = tx * e1y - ty * e1x;
final v = (d.x * qx + d.y * qy + d.z * qz) * inverse;
if (v < 0.0 || u + v > 1.0) return kNoHit;
final t = (e2x * qx + e2y * qy + e2z * qz) * inverse;
if (t < 0.0) return kNoHit;
outUv?.setValues(u, v);
return t;
}