CNA-BUG-066: BoundingFrustum::Contains(Vector3) answers Intersects for a point exactly on a plane and skips the remaining planes
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::Contains(point) returns Disjoint for any positive plane distance and, for a distance of exactly zero, Intersects without testing the remaining planes; XNA never answers Intersects for a point and uses a 1e-5 tolerance.
- Identifier
CNA-BUG-066- Category
- Bug
- Subsystem
- Math & geometry
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (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
- ContainmentType BoundingFrustum::Contains(Vector3) const and its output-parameter form; C ABI cna_bounding_frustum_contains_point
Expected behaviour
XNA 4.0 (read from the IL of the genuine XNA 4.0 Microsoft.Xna.Framework assembly; not executed) computes d = Dot(plane.Normal, point) + plane.D for each of the six planes, returns Disjoint as soon as d > 1E-05, and otherwise returns Contains. A point on a plane, or within the tolerance outside it, is contained; Intersects is never returned for a point.
Actual behaviour at TARGET
BoundingFrustum::Contains(const Vector3&, ContainmentType&) in BoundingFrustum.cpp returns Disjoint for d > 0.0f and, when d == 0.0f, sets Intersects and breaks out of the loop. So:
- a point lying on one plane's extension but outside a later plane (plane order: near, far, left, right, top, bottom) is reported as
Intersectsinstead ofDisjoint; - a point exactly on the boundary is
Intersectswhere XNA saysContains(this is the answer that makesBoundingFrustum::Intersects(Ray)throw, CNA-BUG-004); - a point up to 1e-5 outside a plane is
Disjointwhere XNA saysContains.
The code is an unchanged port of FNA's C# implementation (read at FNA commit 1358793, 2026-08-01).
Source locations
modules/math/src/BoundingFrustum.cpp— BoundingFrustum::Contains(const Vector3&, ContainmentType&): the exact-zero breakmodules/math/tests/Microsoft/Xna/Framework/BoundingFrustumTests.cpp— ContainsPointInsideFrustum, ContainsPointBehindCamera, ContainsPointBeyondFarPlane, ContainsPointOutRefmodules/c-api/tests/pure_c/GeometrySmoke.c— asserts INTERSECTS for the origin, which lies on the near plane of the identity frustummodules/c-api/src/CnaCApiGeometry.cpp— cna_bounding_frustum_contains_point
Evidence
Checked by reading at 009d40f5; nothing was built or executed for this entry. The loop was read; XNA's point test and tolerance were read from its IL. The Intersects(Ray) consequence is recorded in CNA-BUG-004.
Focused reproduction
Illustrative; not compiled or run for this entry.
BoundingFrustum f(Matrix::getIdentityProperty()); // planes z = 0 (near), z = 1 (far), x = +-1, y = +-1
ContainmentType a = f.Contains(Vector3(0.0f, 0.0f, 0.0f)); // on the near plane: XNA Contains, TARGET Intersects
ContainmentType b = f.Contains(Vector3(5.0f, 0.0f, 0.0f)); // on the near plane, outside the right plane:
// XNA Disjoint, TARGET Intersects
Current tests
The four point cases in BoundingFrustumTests.cpp use points clearly inside or outside. The C ABI GeometrySmoke.c asserts CNA_CONTAINMENT_INTERSECTS for the origin against the identity frustum, pinning the current on-plane answer; it must change with a fix.
Regression test
The two cases above (expecting Contains and Disjoint), a point 5e-6 outside a plane (Contains), and a point 2e-5 outside (Disjoint); update the C ABI smoke expectation for the origin.
Blast radius
Affected: both Contains(Vector3) overloads, cna_bounding_frustum_contains_point, and through it BoundingFrustum::Intersects(Ray). Not affected: frustum containment of boxes, spheres and frustums (they classify volumes against planes) and BoundingBox::Contains(const BoundingFrustum&) (it uses the box's own point test).
Workaround
Test the six planes yourself with a tolerance, treating a point as outside only when Dot(N, p) + D exceeds it.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Math types: BoundingFrustum
- Internals
- Math internals: BoundingFrustum
- Maintainer workflow
- Add a regression test: the math case
- Known issues
- Bug index