LCOV - code coverage report
Current view: top level - test - frustum.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 82 82
Test Date: 2026-04-03 02:26:39 Functions: 100.0 % 2 2

            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 : }
        

Generated by: LCOV version 2.4-0