CNA-BUG-004: BoundingFrustum::Intersects(Ray) never computes an entry distance: rays starting outside always miss, and an origin on a plane throws

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

BoundingFrustum::Intersects(Ray) and Ray::Intersects(BoundingFrustum) report no hit for every ray whose origin is outside the frustum, whatever its direction, return 0 for an origin inside, and throw NotImplementedException for an origin on a plane.

Identifier
CNA-BUG-004
Category
Bug
Subsystem
Math & geometry
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Medium (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
std::optional<float> BoundingFrustum::Intersects(Ray) const, its output-parameter form, Ray::Intersects(const BoundingFrustum&); C ABI cna_bounding_frustum_intersects_ray and cna_ray_intersects_frustum

Expected behaviour

The header promises "the distance along a ray where it hits this frustum, or an empty optional if there is no hit". XNA 4.0 implements it fully (read from the IL of the genuine XNA 4.0 Microsoft.Xna.Framework assembly; not executed): it returns 0 when the origin is inside; otherwise it clips the ray against the six planes (a plane nearly parallel to the ray, with |N·d| below 1e-5, rejects the ray only when the origin is on its outer side), keeps the largest entry and the smallest exit parameter, and returns the entry distance, or the exit distance when the entry is negative, if it is not negative; it never throws. XNA 4.0 therefore does not share this limitation.

Actual behaviour at TARGET

BoundingFrustum::Intersects(const Ray&, std::optional<float>&) in BoundingFrustum.cpp classifies only the ray origin with Contains(ray.Position): Disjoint returns an empty optional, Contains returns 0, and Intersects throws System::NotImplementedException("BoundingFrustum::Intersects(Ray) is not implemented"). No entry distance is ever computed, so a ray from a camera or light outside a frustum that points straight into it is reported as a miss. The throwing case is reached only when Contains(Vector3) answers Intersects, which it does for a point lying exactly on a plane (see the related point-containment entry, whose tolerance also differs from XNA). The code is an unchanged port of FNA's C# implementation (read at FNA commit 1358793, 2026-08-01).

The C ABI barrier turns the exception into CNA_RESULT_NOT_SUPPORTED, and geometry.h documents that boundary case; the silent miss for outside origins is documented nowhere in the headers.

Source locations

Evidence

Checked by reading at 009d40f5; nothing was built or executed for this entry. The three outcomes follow from the function body. The XNA 4.0 algorithm was read from its IL; FNA was read to confirm that CNA's code is FNA's unfinished port. The throw is reachable only through an exact-zero plane distance, so it is rare in practice; the outside-origin miss is the common case.

Focused reproduction

Illustrative; not compiled or run for this entry.

BoundingFrustum f(Matrix::CreateLookAt(Vector3(0.0f, 0.0f, 10.0f), Vector3::Zero, Vector3::Up) *
                  Matrix::CreatePerspectiveFieldOfView(MathHelper::PiOver4, 1.0f, 1.0f, 100.0f));
Ray r(Vector3(0.0f, 0.0f, 20.0f), Vector3(0.0f, 0.0f, -1.0f));   // behind the camera, pointing into the frustum
std::optional<float> d = f.Intersects(r);
// XNA: 11 (the near plane lies at z = 9)     TARGET code: empty optional

Current tests

The three BoundingFrustumTest.IntersectsRay* cases in BoundingFrustumTests.cpp cover an origin inside (expects 0), an origin behind the camera pointing further away (an empty optional is the right answer there too) and the output-parameter form. No test casts a ray into the frustum from outside or starts one on a plane; RayTests.cpp records that its frustum test was left out.

Regression test

A ray from outside pointing into the frustum (the example above, expecting 11 within a tolerance), a ray from outside that misses, an origin exactly on the near plane (XNA answers 0), and the same three through Ray::Intersects(const BoundingFrustum&) and the C ABI route.

Blast radius

Affected: both BoundingFrustum::Intersects Ray overloads, Ray::Intersects(const BoundingFrustum&) and the two C ABI routes; any picking, line-of-sight or light-volume query that casts a ray into a frustum from outside receives a miss. Not affected: frustum tests against boxes, spheres, planes and other frustums, and the culling helpers in graphics-ext, which use box and sphere tests.

Workaround

Clip the ray against the six planes yourself (getNearProperty() to getBottomProperty()) with the slab method described above, or test against a proxy BoundingBox or BoundingSphere with Ray::Intersects.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index