transfered from codeberg
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
#ifndef private_frustum_h
|
||||
#define private_frustum_h
|
||||
|
||||
enum {
|
||||
left = 0,
|
||||
right = 1,
|
||||
bottom = 2,
|
||||
top = 3,
|
||||
near = 4,
|
||||
far = 5
|
||||
};
|
||||
|
||||
#endif
|
||||
112
source_code/frustum_module/frustum/frustum.c
Normal file
112
source_code/frustum_module/frustum/frustum.c
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "frustum.h"
|
||||
#include "_internal/private_frustum.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Extracts the six clipping planes from a combined View-Projection matrix.
|
||||
* * This function utilizes the Gribb-Hartmann method to derive plane equations
|
||||
* directly from the matrix rows. The resulting planes are normalized to ensure
|
||||
* that the distance calculations in visibility tests are accurate.
|
||||
*/
|
||||
void extract_frustum_planes(frustum_plane_struct planes[number_of_planes],
|
||||
Matrix view_projection)
|
||||
{
|
||||
/* Left Clipping Plane */
|
||||
planes[left].normal.x = view_projection.m3 + view_projection.m0;
|
||||
planes[left].normal.y = view_projection.m7 + view_projection.m4;
|
||||
planes[left].normal.z = view_projection.m11 + view_projection.m8;
|
||||
planes[left].distance = view_projection.m15 + view_projection.m12;
|
||||
|
||||
/* Right Clipping Plane */
|
||||
planes[right].normal.x = view_projection.m3 - view_projection.m0;
|
||||
planes[right].normal.y = view_projection.m7 - view_projection.m4;
|
||||
planes[right].normal.z = view_projection.m11 - view_projection.m8;
|
||||
planes[right].distance = view_projection.m15 - view_projection.m12;
|
||||
|
||||
/* Bottom Clipping Plane */
|
||||
planes[bottom].normal.x = view_projection.m3 + view_projection.m1;
|
||||
planes[bottom].normal.y = view_projection.m7 + view_projection.m5;
|
||||
planes[bottom].normal.z = view_projection.m11 + view_projection.m9;
|
||||
planes[bottom].distance = view_projection.m15 + view_projection.m13;
|
||||
|
||||
/* Top Clipping Plane */
|
||||
planes[top].normal.x = view_projection.m3 - view_projection.m1;
|
||||
planes[top].normal.y = view_projection.m7 - view_projection.m5;
|
||||
planes[top].normal.z = view_projection.m11 - view_projection.m9;
|
||||
planes[top].distance = view_projection.m15 - view_projection.m13;
|
||||
|
||||
/* Near Clipping Plane */
|
||||
planes[near].normal.x = view_projection.m3 + view_projection.m2;
|
||||
planes[near].normal.y = view_projection.m7 + view_projection.m6;
|
||||
planes[near].normal.z = view_projection.m11 + view_projection.m10;
|
||||
planes[near].distance = view_projection.m15 + view_projection.m14;
|
||||
|
||||
/* Far Clipping Plane */
|
||||
planes[far].normal.x = view_projection.m3 - view_projection.m2;
|
||||
planes[far].normal.y = view_projection.m7 - view_projection.m6;
|
||||
planes[far].normal.z = view_projection.m11 - view_projection.m10;
|
||||
planes[far].distance = view_projection.m15 - view_projection.m14;
|
||||
|
||||
/* Normalize all planes so that normal length equals 1.0 */
|
||||
for (uint8_t plane_index = 0; plane_index < number_of_planes; plane_index++)
|
||||
{
|
||||
float magnitude = sqrtf(
|
||||
planes[plane_index].normal.x * planes[plane_index].normal.x +
|
||||
planes[plane_index].normal.y * planes[plane_index].normal.y +
|
||||
planes[plane_index].normal.z * planes[plane_index].normal.z
|
||||
);
|
||||
|
||||
if (magnitude > 0.0f)
|
||||
{
|
||||
float inverse_magnitude = 1.0f / magnitude;
|
||||
planes[plane_index].normal.x *= inverse_magnitude;
|
||||
planes[plane_index].normal.y *= inverse_magnitude;
|
||||
planes[plane_index].normal.z *= inverse_magnitude;
|
||||
planes[plane_index].distance *= inverse_magnitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an Axis-Aligned Bounding Box (AABB) is within the view frustum.
|
||||
* * This function performs a fast visibility test by finding the "positive vertex"
|
||||
* of the box (the corner most aligned with the plane normal). If this vertex
|
||||
* is outside any of the six planes, the entire box is considered culled.
|
||||
*/
|
||||
bool is_axis_aligned_bounding_box_in_frustum( const frustum_plane_struct planes
|
||||
[
|
||||
number_of_planes
|
||||
],
|
||||
BoundingBox bounds)
|
||||
{
|
||||
for (uint8_t plane_index = 0; plane_index < number_of_planes; plane_index++)
|
||||
{
|
||||
Vector3 positive_vertex;
|
||||
|
||||
positive_vertex.x =
|
||||
(planes[plane_index].normal.x >= 0.0f) ? bounds.max.x : bounds.min.x;
|
||||
|
||||
positive_vertex.y =
|
||||
(planes[plane_index].normal.y >= 0.0f) ? bounds.max.y : bounds.min.y;
|
||||
|
||||
positive_vertex.z =
|
||||
(planes[plane_index].normal.z >= 0.0f) ? bounds.max.z : bounds.min.z;
|
||||
|
||||
/* Calculate the signed distance from the plane to the positive vertex */
|
||||
float vertex_distance =
|
||||
planes[plane_index].normal.x * positive_vertex.x +
|
||||
planes[plane_index].normal.y * positive_vertex.y +
|
||||
planes[plane_index].normal.z * positive_vertex.z +
|
||||
planes[plane_index].distance;
|
||||
|
||||
/* If the most positive point is behind the plane, the box is outside the frustum */
|
||||
if (vertex_distance < 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
38
source_code/frustum_module/frustum/frustum.h
Normal file
38
source_code/frustum_module/frustum/frustum.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef frustum_h
|
||||
#define frustum_h
|
||||
|
||||
#include "raylib.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#define number_of_planes 6
|
||||
|
||||
typedef struct frustum_plane_struct
|
||||
{
|
||||
Vector3 normal;
|
||||
float distance;
|
||||
} frustum_plane_struct;
|
||||
|
||||
/**
|
||||
* Extracts the six clipping planes from a combined View-Projection matrix.
|
||||
* * This function utilizes the Gribb-Hartmann method to derive plane equations
|
||||
* directly from the matrix rows. The resulting planes are normalized to ensure
|
||||
* that the distance calculations in visibility tests are accurate.
|
||||
*/
|
||||
void extract_frustum_planes(frustum_plane_struct planes[number_of_planes],
|
||||
Matrix view_projection);
|
||||
|
||||
|
||||
/**
|
||||
* Checks if an Axis-Aligned Bounding Box (AABB) is within the view frustum.
|
||||
* * This function performs a fast visibility test by finding the "positive vertex"
|
||||
* of the box (the corner most aligned with the plane normal). If this vertex
|
||||
* is outside any of the six planes, the entire box is considered culled.
|
||||
*/
|
||||
bool is_axis_aligned_bounding_box_in_frustum( const frustum_plane_struct planes
|
||||
[
|
||||
number_of_planes
|
||||
],
|
||||
BoundingBox bounds);
|
||||
|
||||
#endif
|
||||
|
||||
112
source_code/frustum_module/frustum/tests/test_frustum.c
Normal file
112
source_code/frustum_module/frustum/tests/test_frustum.c
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "test_frustum.h"
|
||||
#include "frustum.h"
|
||||
#include "_internal/private_frustum.h"
|
||||
|
||||
#include "unity.h"
|
||||
|
||||
#include "raylib.h"
|
||||
#include "raymath.h"
|
||||
|
||||
/* Verify that an identity matrix results in planes that face inward correctly */
|
||||
void test_extract_frustum_planes_from_identity_matrix(void)
|
||||
{
|
||||
frustum_plane_struct planes[number_of_planes];
|
||||
Matrix identity_matrix = MatrixIdentity();
|
||||
|
||||
extract_frustum_planes(planes, identity_matrix);
|
||||
|
||||
/* With an identity matrix, the 'Left' plane normal should point along +X
|
||||
* because the plane equation is Ax + By + Cz + D = 0 and it faces inward.
|
||||
*/
|
||||
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 1.0f, planes[left].normal.x);
|
||||
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 0.0f, planes[left].normal.y);
|
||||
TEST_ASSERT_FLOAT_WITHIN(0.0001f, 0.0f, planes[left].normal.z);
|
||||
}
|
||||
|
||||
/* Verify that a box inside the view area is correctly identified as visible */
|
||||
void test_bounding_box_inside_frustum_is_not_culled(void)
|
||||
{
|
||||
frustum_plane_struct planes[number_of_planes];
|
||||
|
||||
/* Create a standard perspective projection matrix
|
||||
* (FOV: 90, Aspect: 1.0, Near: 0.1, Far: 100.0)
|
||||
*/
|
||||
Matrix projection = MatrixPerspective(90.0f * DEG2RAD, 1.0f, 0.1f, 100.0f);
|
||||
|
||||
Matrix view = MatrixLookAt((Vector3){ 0, 0, 0 },
|
||||
(Vector3){ 0, 0, -1 },
|
||||
(Vector3){ 0, 1, 0 });
|
||||
|
||||
Matrix view_projection = MatrixMultiply(view, projection);
|
||||
|
||||
extract_frustum_planes(planes, view_projection);
|
||||
|
||||
/* Define a box directly in front of the camera (negative Z axis) */
|
||||
BoundingBox box_inside;
|
||||
box_inside.min = (Vector3){ -1.0f, -1.0f, -10.0f };
|
||||
box_inside.max = (Vector3){ 1.0f, 1.0f, -5.0f };
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(is_axis_aligned_bounding_box_in_frustum(planes,
|
||||
box_inside),
|
||||
"Box directly in front of camera should be visible");
|
||||
}
|
||||
|
||||
/* Verify that a box behind the camera is correctly culled */
|
||||
void test_bounding_box_behind_camera_is_culled(void)
|
||||
{
|
||||
frustum_plane_struct planes[number_of_planes];
|
||||
|
||||
Matrix projection = MatrixPerspective(90.0f * DEG2RAD, 1.0f, 0.1f, 100.0f);
|
||||
|
||||
Matrix view = MatrixLookAt((Vector3){ 0, 0, 0 },
|
||||
(Vector3){ 0, 0, -1 },
|
||||
(Vector3){ 0, 1, 0 });
|
||||
|
||||
Matrix view_projection = MatrixMultiply(view, projection);
|
||||
|
||||
extract_frustum_planes(planes, view_projection);
|
||||
|
||||
/* Define a box behind the camera (positive Z axis) */
|
||||
BoundingBox box_behind;
|
||||
box_behind.min = (Vector3){ -1.0f, -1.0f, 5.0f };
|
||||
box_behind.max = (Vector3){ 1.0f, 1.0f, 10.0f };
|
||||
|
||||
TEST_ASSERT_FALSE_MESSAGE(is_axis_aligned_bounding_box_in_frustum(planes,
|
||||
box_behind),
|
||||
"Box behind the camera should be culled");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Verify that a box far beyond the far clipping plane is culled */
|
||||
void test_bounding_box_beyond_far_plane_is_culled(void)
|
||||
{
|
||||
frustum_plane_struct planes[number_of_planes];
|
||||
|
||||
/* Far plane is set to 100.0 */
|
||||
Matrix projection = MatrixPerspective(90.0f * DEG2RAD, 1.0f, 0.1f, 100.0f);
|
||||
Matrix view = MatrixLookAt((Vector3){ 0, 0, 0 },
|
||||
(Vector3){ 0, 0, -1 },
|
||||
(Vector3){ 0, 1, 0 });
|
||||
|
||||
Matrix view_projection = MatrixMultiply(view, projection);
|
||||
|
||||
extract_frustum_planes(planes, view_projection);
|
||||
|
||||
/* Define a box at Z = -150 (outside the 100 unit limit) */
|
||||
BoundingBox box_too_far;
|
||||
box_too_far.min = (Vector3){ -1.0f, -1.0f, -160.0f };
|
||||
box_too_far.max = (Vector3){ 1.0f, 1.0f, -150.0f };
|
||||
|
||||
TEST_ASSERT_FALSE_MESSAGE(is_axis_aligned_bounding_box_in_frustum(planes,
|
||||
box_too_far),
|
||||
"Box beyond the far clipping plane should be culled");
|
||||
}
|
||||
|
||||
void run_frustum_tests(void)
|
||||
{
|
||||
RUN_TEST(test_extract_frustum_planes_from_identity_matrix);
|
||||
RUN_TEST(test_bounding_box_inside_frustum_is_not_culled);
|
||||
RUN_TEST(test_bounding_box_behind_camera_is_culled);
|
||||
RUN_TEST(test_bounding_box_beyond_far_plane_is_culled);
|
||||
}
|
||||
15
source_code/frustum_module/frustum/tests/test_frustum.h
Normal file
15
source_code/frustum_module/frustum/tests/test_frustum.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef test_frustum_h
|
||||
#define test_frustum_h
|
||||
|
||||
void test_extract_frustum_planes_from_identity_matrix(void);
|
||||
|
||||
void test_bounding_box_inside_frustum_is_not_culled(void);
|
||||
|
||||
void test_bounding_box_behind_camera_is_culled(void);
|
||||
|
||||
void test_bounding_box_beyond_far_plane_is_culled(void);
|
||||
|
||||
void run_frustum_tests(void);
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user