CNA-BUG-004: BoundingFrustum::Intersects(Ray) never computes an entry distance: rays starting outside always miss, and an origin on a plane throws
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
modules/math/src/BoundingFrustum.cpp— BoundingFrustum::Intersects(const Ray&, std::optional<float>&): origin-only classification and the NotImplementedExceptionmodules/math/include/Microsoft/Xna/Framework/BoundingFrustum.hpp— the documented Intersects(Ray) contractmodules/math/src/Ray.cpp— Ray::Intersects(const BoundingFrustum&) delegates to the frustummodules/math/tests/Microsoft/Xna/Framework/BoundingFrustumTests.cpp— IntersectsRayOriginInsideReturnZero, IntersectsRayOriginOutsideReturnsNullopt, IntersectsRayOutRefmodules/math/tests/Microsoft/Xna/Framework/RayTests.cpp— comment: the Ray-versus-frustum test is omitted until the function is portedmodules/c-api/src/CnaCApiGeometry.cpp— cna_bounding_frustum_intersects_ray and cna_ray_intersects_frustummodules/c-api/include/CNA/C/geometry.h— documents only the not-supported boundary case
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.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Math types: BoundingFrustum
- Maintainer workflow
- Add a regression test: the math case
- Reference
- C API route families
- Known issues
- Bug index