Line data Source code
1 : // Uses a simplified clip matrix, not a full gluPerspective-style projection.
2 : // With identity + data[10]=-1 (column-major), extractFromProjectionMatrix
3 : // produces six axis-aligned planes that bound the unit cube [-1,1]^3:
4 : // left (1,0,0,1), right (-1,0,0,1), bottom (0,1,0,1),
5 : // top (0,-1,0,1), far (0,0,1,1), near (0,0,-1,1)
6 : // All plane distances from the origin equal 1, so testSphere results are
7 : // analytically predictable and are asserted as specific codes below.
8 : #include "catch.hpp"
9 : #include "Frustum.h"
10 : #include "Matrix.h"
11 : #include "Sphere.h"
12 : #include <cmath>
13 :
14 10 : SCENARIO( "Frustum", "[Frustum]" ) {
15 18 : GIVEN( "Frustum from a non-degenerate projection-like matrix" ) {
16 9 : Matrix proj;
17 9 : proj.loadIdentity();
18 : // Simple symmetric adjustment so clip planes are well-defined (like glOrtho depth).
19 9 : proj.data[10] = -1.0f;
20 :
21 9 : Frustum f;
22 9 : f.extractFromProjectionMatrix(proj);
23 :
24 10 : WHEN( "reading plane definitions" ) {
25 2 : THEN( "all six planes are finite and normalized" ) {
26 7 : for (int i = 0; i < NUM_PLANES; ++i) {
27 : float a, b, c, d;
28 6 : f.getPlaneDefinition(i, a, b, c, d);
29 6 : REQUIRE(std::isfinite(a));
30 6 : REQUIRE(std::isfinite(b));
31 6 : REQUIRE(std::isfinite(c));
32 6 : REQUIRE(std::isfinite(d));
33 6 : const float len = std::sqrt(a * a + b * b + c * c);
34 6 : REQUIRE(len == Approx(1.0f));
35 6 : }
36 1 : }
37 1 : }
38 :
39 : // getPlaneDefinition silently does nothing for out-of-range indices,
40 : // leaving output parameters unchanged (both above and below valid range).
41 10 : WHEN( "reading an out-of-range plane index (too high)" ) {
42 1 : float a = 99.0f, b = 99.0f, c = 99.0f, d = 99.0f;
43 1 : f.getPlaneDefinition(NUM_PLANES, a, b, c, d);
44 2 : THEN( "output parameters are left unchanged" ) {
45 1 : REQUIRE(a == Approx(99.0f));
46 1 : REQUIRE(b == Approx(99.0f));
47 1 : REQUIRE(c == Approx(99.0f));
48 1 : REQUIRE(d == Approx(99.0f));
49 1 : }
50 1 : }
51 :
52 10 : WHEN( "reading an out-of-range plane index (negative)" ) {
53 1 : float a = 99.0f, b = 99.0f, c = 99.0f, d = 99.0f;
54 1 : f.getPlaneDefinition(-1, a, b, c, d);
55 2 : THEN( "output parameters are left unchanged" ) {
56 1 : REQUIRE(a == Approx(99.0f));
57 1 : REQUIRE(b == Approx(99.0f));
58 1 : REQUIRE(c == Approx(99.0f));
59 1 : REQUIRE(d == Approx(99.0f));
60 1 : }
61 1 : }
62 :
63 10 : WHEN( "testBoundingBox stub" ) {
64 2 : THEN( "returns zero" ) {
65 1 : REQUIRE(f.testBoundingBox() == 0);
66 1 : }
67 1 : }
68 :
69 : // testSphere return codes: 1 = inside all planes, 0 = intersecting one or
70 : // more planes, -1 = outside at least one plane. Both engine callers
71 : // (ModelGameObject::animate and QuadTree::buildDisplayList) treat r != -1
72 : // as "visible" — only -1 triggers culling.
73 : //
74 : // Note: testSphere is conservative. It returns 0 on the first intersecting
75 : // plane without checking remaining planes, so a sphere that intersects one
76 : // plane but is outside another may return 0 (render) instead of -1 (cull).
77 :
78 : // With this matrix every plane is distance 1 from the origin.
79 : // A sphere at the origin with radius 0.5: all distances = 1 > 0.5 → inside.
80 10 : WHEN( "sphere at origin with radius 0.5" ) {
81 1 : Sphere s;
82 1 : s.setSphere(0.0f, 0.0f, 0.0f, 0.5f);
83 2 : THEN( "returns 1 (fully inside, r != -1 so rendered)" ) {
84 1 : REQUIRE(f.testSphere(s) == 1);
85 1 : }
86 1 : }
87 :
88 : // Sphere far outside on X: right-plane distance = -1*5+1 = -4 < -0.1 → outside.
89 10 : WHEN( "sphere at (5,0,0) with radius 0.1" ) {
90 1 : Sphere s;
91 1 : s.setSphere(5.0f, 0.0f, 0.0f, 0.1f);
92 2 : THEN( "returns -1 (outside, culled)" ) {
93 1 : REQUIRE(f.testSphere(s) == -1);
94 1 : }
95 1 : }
96 :
97 : // Sphere straddling the right face: right-plane distance = -1*0.95+1 = 0.05 < radius 0.1.
98 10 : WHEN( "sphere at (0.95,0,0) with radius 0.1" ) {
99 1 : Sphere s;
100 1 : s.setSphere(0.95f, 0.0f, 0.0f, 0.1f);
101 2 : THEN( "returns 0 (intersecting, r != -1 so rendered)" ) {
102 1 : REQUIRE(f.testSphere(s) == 0);
103 1 : }
104 1 : }
105 :
106 : // A zero-radius sphere is a point. |distance| < 0 is never true, so a
107 : // point can never return 0 (intersecting) — it is either inside or outside.
108 10 : WHEN( "zero-radius sphere (point) inside the frustum" ) {
109 1 : Sphere s;
110 1 : s.setSphere(0.0f, 0.0f, 0.0f, 0.0f);
111 2 : THEN( "returns 1 (inside, not intersecting)" ) {
112 1 : REQUIRE(f.testSphere(s) == 1);
113 1 : }
114 1 : }
115 :
116 10 : WHEN( "zero-radius sphere (point) outside the frustum" ) {
117 1 : Sphere s;
118 1 : s.setSphere(5.0f, 0.0f, 0.0f, 0.0f);
119 2 : THEN( "returns -1 (outside, not intersecting)" ) {
120 1 : REQUIRE(f.testSphere(s) == -1);
121 1 : }
122 1 : }
123 9 : }
124 9 : }
|