From 6bad6400c4cc22ec9be6225e900c7c9b50f667b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio?= <94169007+JulioJPinto@users.noreply.github.com> Date: Sat, 25 May 2024 17:22:23 +0100 Subject: [PATCH] Frustsum Culling (#34) * apply transformation to bouding sphere * working on frustsum, add right and real up to camera, add python script to calculate frustsum * finish frustsum culling --- engine/include/Camera.hpp | 14 ++-- engine/include/curves.hpp | 4 +- engine/include/frustsum.hpp | 38 +++++++-- engine/include/model.hpp | 3 +- engine/src/Camera.cpp | 18 ++--- engine/src/curves.cpp | 23 ++++-- engine/src/frustsum.cpp | 152 +++++++++++++++++++++++------------- engine/src/group.cpp | 20 +++-- engine/src/main.cpp | 6 +- engine/src/model.cpp | 12 +-- engine/src/parse.cpp | 6 +- scenes/test/test_3_1.xml | 2 +- scripts/frustsum.py | 94 ++++++++++++++++++++++ 13 files changed, 281 insertions(+), 111 deletions(-) create mode 100644 scripts/frustsum.py diff --git a/engine/include/Camera.hpp b/engine/include/Camera.hpp index 219cafc..640c5a2 100644 --- a/engine/include/Camera.hpp +++ b/engine/include/Camera.hpp @@ -2,27 +2,27 @@ #define CAMERA_HPP #include - +#include #include "Camera.hpp" #include "utils.hpp" class Camera { public: - Point position; - Point lookAt; - Point up; + glm::vec3 position; + glm::vec3 lookAt; + glm::vec3 up; + glm::vec3 right; + glm::vec3 real_up; int fov; float near; float far; Camera(); - Camera(Point position, Point lookAt, Point up, int fov, float near, + Camera(glm::vec3 position, glm::vec3 lookAt, glm::vec3 up, int fov, float near, float far); Camera(const Camera& other); - void changeVectors(Point position, Point lookAt, Point up); - // std::string toString(); }; diff --git a/engine/include/curves.hpp b/engine/include/curves.hpp index bb83d20..72fdb6d 100644 --- a/engine/include/curves.hpp +++ b/engine/include/curves.hpp @@ -22,7 +22,7 @@ class TimeRotations { TimeRotations(); TimeRotations(float time, float x, float y, float z); - void applyTimeRotation(float elapsed_time); + glm::mat4 applyTimeRotation(float elapsed_time); }; class TimeTranslations { @@ -35,7 +35,7 @@ class TimeTranslations { TimeTranslations(); TimeTranslations(float time, bool align, std::vector curve); - void applyTimeTranslations(float elapsed_time); + glm::mat4 applyTimeTranslations(float elapsed_time); void renderCatmullRomCurve(); diff --git a/engine/include/frustsum.hpp b/engine/include/frustsum.hpp index 7b8e590..30686e5 100644 --- a/engine/include/frustsum.hpp +++ b/engine/include/frustsum.hpp @@ -2,23 +2,31 @@ #define FRUSTSUM_CPP #include - +#include #include - +#include "Window.hpp" #include "Camera.hpp" struct Plane { - float distance = 0.f; + glm::vec3 point = { 0.f, 0.f, 0.f }; glm::vec3 normal = { 0.f, 1.f, 0.f }; + glm::vec4 abcd = { 0.f, 0.f, 0.f, 0.f }; Plane() = default; Plane(const Plane& other) = default; - Plane(const glm::vec3& normal, float distance) : normal(normal), distance(distance) {} + Plane(const glm::vec3& normal, glm::vec3 point); + // rethink distance to point float distanceToPoint(const glm::vec3& point) const { - return glm::dot(normal, point) + distance; + return abcd.x * point.x + abcd.y * point.y + abcd.z * point.z + abcd.w; + } + + + void printPlane() { + std::cout << "Point: " << point.x << " " << point.y << " " << point.z << std::endl; + std::cout << "Normal: " << normal.x << " " << normal.y << " " << normal.z << std::endl; } @@ -36,7 +44,22 @@ struct Frustsum { Frustsum(const Frustsum& other) = default; Frustsum(const Plane& nearFace, const Plane& farFace, const Plane& rightFace, const Plane& leftFace, const Plane& topFace, const Plane& bottomFace) : nearFace(nearFace), farFace(farFace), rightFace(rightFace), leftFace(leftFace), topFace(topFace), bottomFace(bottomFace) {} - Frustsum(const Camera& cam); + Frustsum(const Camera& cam, const Window& window); + + void printFrustsum() { + std::cout << "Near" << std::endl; + nearFace.printPlane(); + std::cout << "Far" << std::endl; + farFace.printPlane(); + std::cout << "right" << std::endl; + rightFace.printPlane(); + std::cout << "left" << std::endl; + leftFace.printPlane(); + std::cout << "top" << std::endl; + topFace.printPlane(); + std::cout << "bottom" << std::endl; + bottomFace.printPlane(); + } }; @@ -49,8 +72,9 @@ struct BoundingSphere { BoundingSphere() = default; BoundingSphere(const BoundingSphere& other) = default; BoundingSphere(const glm::vec3& center, float radius) : center(center), radius(radius) {} + BoundingSphere(std::vector points); - bool isInsideFrustsum(const Frustsum& frustsum) const; + bool isInsideFrustsum(const Frustsum& frustsum, glm::mat4 transformations) const; }; diff --git a/engine/include/model.hpp b/engine/include/model.hpp index 80ac3a5..39fabdd 100644 --- a/engine/include/model.hpp +++ b/engine/include/model.hpp @@ -41,10 +41,9 @@ class Model { Model(std::string filename, std::vector points); void initModel(); - void drawModel(const Frustsum& f); + void drawModel(); void setupModel(); bool loadTexture(); - bool isInsideFrustsum(const Frustsum& frustsum) const; std::vector getPoints(); diff --git a/engine/src/Camera.cpp b/engine/src/Camera.cpp index 3fd7e4b..a039cbc 100644 --- a/engine/src/Camera.cpp +++ b/engine/src/Camera.cpp @@ -4,29 +4,29 @@ #include Camera::Camera() { - this->position = Point(); - this->lookAt = Point(); - this->up = Point(); + this->position = glm::vec3(); + this->lookAt = glm::vec3(); + this->up = glm::vec3(); this->fov = 0; this->near = 0; this->far = 0; } -Camera::Camera(Point position, Point lookAt, Point up, int fov, float near, +Camera::Camera(glm::vec3 position, glm::vec3 lookAt, glm::vec3 up, int fov, float near, float far) { this->position = position; this->lookAt = lookAt; this->up = up; + + glm::vec3 direction = glm::normalize(position - lookAt); + this->right = glm::normalize(glm::cross(up, direction)); + this->real_up = glm::normalize(glm::cross(direction, right)); + this->fov = fov; this->near = near; this->far = far; } -void Camera::changeVectors(Point position, Point lookAt, Point up) { - this->position = position; - this->lookAt = lookAt; - this->up = up; -} Camera::Camera(const Camera& other) { // Copy member variables from 'other' to the current object diff --git a/engine/src/curves.cpp b/engine/src/curves.cpp index d91d05b..eced39f 100644 --- a/engine/src/curves.cpp +++ b/engine/src/curves.cpp @@ -77,12 +77,12 @@ TimeRotations::TimeRotations(float time, float x, float y, float z) { this->z = z; } -void TimeRotations::applyTimeRotation(float elapsed_time) { +glm::mat4 TimeRotations::applyTimeRotation(float elapsed_time) { if (this->time == 0) { - return; + return glm::mat4(1.0f); } float angle = 360 * (elapsed_time / this->time); - glRotatef(angle, this->x, this->y, this->z); + return glm::rotate(glm::mat4(1.0f), glm::radians(angle), glm::vec3(this->x, this->y, this->z)); } TimeTranslations::TimeTranslations() { @@ -129,12 +129,12 @@ std::array TimeTranslations::rotationMatrix(Point x, Point y, Point z } -void TimeTranslations::applyTimeTranslations(float elapsed_time) { +glm::mat4 TimeTranslations::applyTimeTranslations(float elapsed_time) { if (this->time == 0) { - return; + return glm::mat4(1.0f); } - // this->renderCatmullRomCurve(); + this->renderCatmullRomCurve(); float time = elapsed_time / this->time; @@ -143,14 +143,21 @@ void TimeTranslations::applyTimeTranslations(float elapsed_time) { Point pos = position_dir.first; Point dir = position_dir.second; - glTranslatef(pos.x, pos.y, pos.z); + + glm::mat4 matrix = glm::mat4(1.0f); + matrix = glm::translate(matrix, glm::vec3(pos.x, pos.y, pos.z)); if (this->align) { Point x = dir.normalize(); Point z = Point(x).cross(this->y_axis).normalize(); Point y = Point(z).cross(x).normalize(); - glMultMatrixf(rotationMatrix(x, y, z).data()); + std::array matrixR = this->rotationMatrix(x, y, z); + glm::mat4 rotation_matrix = glm::make_mat4(matrixR.data()); + + matrix *= rotation_matrix; } + + return matrix; } void TimeTranslations::renderCatmullRomCurve() { diff --git a/engine/src/frustsum.cpp b/engine/src/frustsum.cpp index 5a08b67..8752acc 100644 --- a/engine/src/frustsum.cpp +++ b/engine/src/frustsum.cpp @@ -1,70 +1,116 @@ #include #include +#include "vertex.hpp" #include "frustsum.hpp" -Frustsum::Frustsum(const Camera& cam) { - glm::vec3 lookAt; - glm::vec3 position; - glm::vec3 up; +/* +{ + Frustum frustum; + const float halfVSide = zFar * tanf(fovY * .5f); + const float halfHSide = halfVSide * aspect; + const glm::vec3 frontMultFar = zFar * front; - lookAt.x = cam.lookAt.x; - lookAt.y = cam.lookAt.y; - lookAt.z = cam.lookAt.z; + frustum.nearFace = { position + near * front, front }; + frustum.farFace = { position + frontMultFar, -front }; + frustum.rightFace = { position, + glm::cross(frontMultFar - cam.Right * halfHSide, cam.Up) }; + frustum.leftFace = { position, + glm::cross(cam.Up,frontMultFar + cam.Right * halfHSide) }; + frustum.topFace = { position, + glm::cross(cam.Right, frontMultFar - cam.Up * halfVSide) }; + frustum.bottomFace = { position, + glm::cross(frontMultFar + cam.Up * halfVSide, cam.Right) }; - position.x = cam.position.x; - position.y = cam.position.y; - position.z = cam.position.z; + return frustum; +} +*/ + +Plane::Plane(const glm::vec3& normal, glm::vec3 point) { + this->normal = glm::normalize(normal); + this->point = point; + float d = -normal.x * point.x - normal.y * point.y - normal.z * point.z; + + this->abcd = glm::vec4(this->normal, d); +} + + +Frustsum::Frustsum(const Camera& cam, const Window& window) { + float aspect = static_cast(window.width) / static_cast(window.height); - up.x = cam.up.x; - up.y = cam.up.y; - up.z = cam.up.z; + glm::vec3 front = glm::normalize(cam.lookAt - cam.position); - glm::vec3 zAxis = glm::normalize(lookAt - position); - glm::vec3 xAxis = glm::normalize(glm::cross(up, zAxis)); - glm::vec3 yAxis = glm::normalize(glm::cross(zAxis, xAxis)); + // Ensure FOV is treated as a floating-point number + float fovRadians = glm::radians(static_cast(cam.fov)); - // near plane - glm::vec3 nearCenter = position + zAxis * cam.near; - glm::vec3 nearNormal = -zAxis; - nearFace = Plane(nearNormal, -glm::dot(nearNormal, nearCenter)); + const float halfVSide = cam.far * tanf(fovRadians * 0.5f); + const float halfHSide = halfVSide * aspect; + const glm::vec3 frontMultFar = cam.far * front; + + // Near and far planes + glm::vec3 nearNormal = front; + glm::vec3 nearPoint = cam.position + cam.near * front; + + glm::vec3 farNormal = -front; + glm::vec3 farPoint = cam.position + frontMultFar; + + // Right and left planes + glm::vec3 rightNormal = glm::normalize(glm::cross(frontMultFar - cam.right * halfHSide, cam.up)); + glm::vec3 rightPoint = cam.position; + + glm::vec3 leftNormal = glm::normalize(glm::cross(cam.up, frontMultFar + cam.right * halfHSide)); + glm::vec3 leftPoint = cam.position; + + // Top and bottom planes + glm::vec3 topNormal = glm::normalize(glm::cross(cam.right, frontMultFar - cam.up * halfVSide)); + glm::vec3 topPoint = cam.position; - // far plane - glm::vec3 farCenter = position + zAxis * cam.far; - glm::vec3 farNormal = zAxis; - farFace = Plane(farNormal, -glm::dot(farNormal, farCenter)); - - // right plane - glm::vec3 rightCenter = nearCenter + xAxis * cam.near * glm::tan(cam.fov / 2.f); - glm::vec3 rightNormal = glm::normalize(glm::cross(yAxis, rightCenter - position)); - rightFace = Plane(rightNormal, -glm::dot(rightNormal, rightCenter)); - - // left plane - glm::vec3 leftCenter = nearCenter - xAxis * cam.near * glm::tan(cam.fov / 2.f); - glm::vec3 leftNormal = -glm::normalize(glm::cross(yAxis, leftCenter - position)); - leftFace = Plane(leftNormal, -glm::dot(leftNormal, leftCenter)); - - // top plane - glm::vec3 topCenter = nearCenter + yAxis * cam.near * glm::tan(cam.fov / 2.f); - glm::vec3 topNormal = glm::normalize(glm::cross(xAxis, topCenter - position)); - topFace = Plane(topNormal, -glm::dot(topNormal, topCenter)); - - // bottom plane - glm::vec3 bottomCenter = nearCenter - yAxis * cam.near * glm::tan(cam.fov / 2.f); - glm::vec3 bottomNormal = -glm::normalize(glm::cross(xAxis, bottomCenter - position)); - bottomFace = Plane(bottomNormal, -glm::dot(bottomNormal, bottomCenter)); + glm::vec3 bottomNormal = glm::normalize(glm::cross(frontMultFar + cam.up * halfVSide, cam.right)); + glm::vec3 bottomPoint = cam.position; + nearFace = { nearNormal, nearPoint }; + farFace = { farNormal, farPoint }; + rightFace = { rightNormal, rightPoint }; + leftFace = { leftNormal, leftPoint }; + topFace = { topNormal, topPoint }; + bottomFace = { bottomNormal, bottomPoint }; } -bool BoundingSphere::isInsideFrustsum(const Frustsum& frustsum) const { - return true; - // return frustsum.nearFace.distanceToPoint(center) > -radius && - // frustsum.farFace.distanceToPoint(center) > -radius && - // frustsum.rightFace.distanceToPoint(center) > -radius && - // frustsum.leftFace.distanceToPoint(center) > -radius && - // frustsum.topFace.distanceToPoint(center) > -radius && - // frustsum.bottomFace.distanceToPoint(center) > -radius; + + +BoundingSphere::BoundingSphere(std::vector points) { + glm::vec3 max = {-INFINITY, -INFINITY, -INFINITY}; + + for (const Vertex& point : points) { + + if (point.position.x > max.x) { + max.x = point.position.x; + } + + if (point.position.y > max.y) { + max.y = point.position.y; + } + + if (point.position.z > max.z) { + max.z = point.position.z; + } + } + + center = glm::vec3(0.0f); + radius = glm::distance(center, max); + } - +bool BoundingSphere::isInsideFrustsum(const Frustsum& frustsum, glm::mat4 transformations) const { + + glm::vec3 center = glm::vec3(transformations * glm::vec4(this->center, 1.0f)); + float radius = this->radius * glm::length(glm::vec3(transformations[0])) / 2; + return frustsum.nearFace.distanceToPoint(center) > -radius && + frustsum.farFace.distanceToPoint(center) > -radius && + frustsum.rightFace.distanceToPoint(center) > -radius && + frustsum.leftFace.distanceToPoint(center) > -radius && + frustsum.topFace.distanceToPoint(center) > -radius && + frustsum.bottomFace.distanceToPoint(center) > -radius; +} + diff --git a/engine/src/group.cpp b/engine/src/group.cpp index ca66986..efc38b4 100644 --- a/engine/src/group.cpp +++ b/engine/src/group.cpp @@ -25,7 +25,7 @@ Group::Group(std::vector models, std::vector subgroups, } -void applyTimeTransformations(std::vector order, +glm::mat4 applyTransformations(std::vector order, std::vector static_transformations, std::vector rotations, std::vector translates) { @@ -34,28 +34,33 @@ void applyTimeTransformations(std::vector order, int r = 0; int s = 0; + glm::mat4 matrix = glm::mat4(1.0f); + for (Transformations type : order) { switch (type) { case TIMEROTATION: - rotations[r].applyTimeRotation(elapsed_time); + matrix *= rotations[r].applyTimeRotation(elapsed_time); r++; break; case TIMETRANSLATE: - translates[t].applyTimeTranslations(elapsed_time); + matrix *= translates[t].applyTimeTranslations(elapsed_time); t++; break; case STATIC: - glMultMatrixf(&static_transformations[s][0][0]); + matrix *= static_transformations[s]; s++; break; } } + + glMultMatrixf(&matrix[0][0]); + return matrix; } void Group::drawGroup(bool lights, const Frustsum& frustsum) { glPushMatrix(); - applyTimeTransformations(this->order, this->static_transformations, this->rotations, this->translates); + glm::mat4 matrix = applyTransformations(this->order, this->static_transformations, this->rotations, this->translates); for (Model& model : this->models) { @@ -63,7 +68,10 @@ void Group::drawGroup(bool lights, const Frustsum& frustsum) { setupMaterial(model.material); } - model.drawModel(frustsum); + if(model.bounding_sphere.isInsideFrustsum(frustsum, matrix)) { + std::cout << model.filename << std::endl; + model.drawModel(); + } } for (Group& sub : this->subgroups) { diff --git a/engine/src/main.cpp b/engine/src/main.cpp index 4c00d3a..5eb0935 100644 --- a/engine/src/main.cpp +++ b/engine/src/main.cpp @@ -107,7 +107,8 @@ void renderScene(void) { glRotatef(cameraAngle, 1.0f, 0.0f, 1.0f); glScalef(zoom, zoom, zoom); - Frustsum frustsum = Frustsum(c.camera); + Window currentW = Window(glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)); + Frustsum frustsum = Frustsum(c.camera, currentW); drawAxis(); @@ -115,7 +116,7 @@ void renderScene(void) { if (lights) { drawLights(c.lights); } - + c.group.drawGroup(lights, frustsum); frameCounter(); @@ -209,7 +210,6 @@ void setupConfig(char* arg) { filename.assign(arg); if (filename.substr(filename.size() - 4) == ".xml") { - printf("XML\n"); c = parseConfig(filename); } else { std::cout << "Invalid file format\n"; diff --git a/engine/src/model.cpp b/engine/src/model.cpp index f2072f1..865e748 100644 --- a/engine/src/model.cpp +++ b/engine/src/model.cpp @@ -94,6 +94,7 @@ Model::Model(std::string filename, std::vector points) { this->id = counter; this->vbo = generateVBO(points); this->ibo = generateIBO(points, this->vbo); + this->bounding_sphere = BoundingSphere(points); this->initialized = false; this->_points = points; counter++; @@ -181,12 +182,7 @@ void Model::setupModel() { this->ibo.data(), GL_STATIC_DRAW); } -void Model::drawModel(const Frustsum& f) { - if(!this->isInsideFrustsum(f)) { - std::cout << "Model not inside frustsum " << this->filename << std::endl; - return; - } - +void Model::drawModel() { initModel(); glBindTexture(GL_TEXTURE_2D, this->_texture_id); @@ -207,8 +203,4 @@ void Model::drawModel(const Frustsum& f) { glBindTexture(GL_TEXTURE_2D, 0); } -bool Model::isInsideFrustsum(const Frustsum& frustsum) const { - return this->bounding_sphere.isInsideFrustsum(frustsum); -} - std::vector Model::getPoints() { return this->_points; } diff --git a/engine/src/parse.cpp b/engine/src/parse.cpp index edf7526..e89caae 100644 --- a/engine/src/parse.cpp +++ b/engine/src/parse.cpp @@ -38,17 +38,17 @@ Configuration parseConfig(std::string filename) { rapidxml::xml_node<>* camera = root->first_node("camera"); rapidxml::xml_node<>* position_n = camera->first_node("position"); - Point position = Point(std::stof(position_n->first_attribute("x")->value()), + glm::vec3 position = glm::vec3(std::stof(position_n->first_attribute("x")->value()), std::stof(position_n->first_attribute("y")->value()), std::stof(position_n->first_attribute("z")->value())); rapidxml::xml_node<>* lookAt_n = camera->first_node("lookAt"); - Point lookAt = Point(std::stof(lookAt_n->first_attribute("x")->value()), + glm::vec3 lookAt = glm::vec3(std::stof(lookAt_n->first_attribute("x")->value()), std::stof(lookAt_n->first_attribute("y")->value()), std::stof(lookAt_n->first_attribute("z")->value())); rapidxml::xml_node<>* up_n = camera->first_node("up"); - Point up = Point(std::stof(up_n->first_attribute("x")->value()), + glm::vec3 up = glm::vec3(std::stof(up_n->first_attribute("x")->value()), std::stof(up_n->first_attribute("y")->value()), std::stof(up_n->first_attribute("z")->value())); diff --git a/scenes/test/test_3_1.xml b/scenes/test/test_3_1.xml index 2869fdf..b0225e6 100644 --- a/scenes/test/test_3_1.xml +++ b/scenes/test/test_3_1.xml @@ -22,7 +22,7 @@ - + diff --git a/scripts/frustsum.py b/scripts/frustsum.py new file mode 100644 index 0000000..2bfd386 --- /dev/null +++ b/scripts/frustsum.py @@ -0,0 +1,94 @@ +import xml.etree.ElementTree as ET +import numpy as np + +def parse_camera_params(xml_file): + tree = ET.parse(xml_file) + root = tree.getroot() + + camera = root.find('camera') + + position = camera.find('position') + pos = np.array([float(position.get('x')), float(position.get('y')), float(position.get('z'))]) + + look_at = camera.find('lookAt') + look = np.array([float(look_at.get('x')), float(look_at.get('y')), float(look_at.get('z'))]) + + up = camera.find('up') + up_vector = np.array([float(up.get('x')), float(up.get('y')), float(up.get('z'))]) + + projection = camera.find('projection') + fov = float(projection.get('fov')) + near = float(projection.get('near')) + far = float(projection.get('far')) + + return pos, look, up_vector, fov, near, far + +def normalize_plane(plane): + norm = np.linalg.norm(plane[:3]) + return plane / norm + +def extract_frustum_planes(view_proj_matrix): + planes = [] + planes.append(view_proj_matrix[3] + view_proj_matrix[0]) # Left + planes.append(view_proj_matrix[3] - view_proj_matrix[0]) # Right + planes.append(view_proj_matrix[3] + view_proj_matrix[1]) # Bottom + planes.append(view_proj_matrix[3] - view_proj_matrix[1]) # Top + planes.append(view_proj_matrix[3] + view_proj_matrix[2]) # Near + planes.append(view_proj_matrix[3] - view_proj_matrix[2]) # Far + + return [normalize_plane(plane) for plane in planes] + +def compute_view_matrix(pos, look, up): + forward = look - pos + forward /= np.linalg.norm(forward) + + right = np.cross(up, forward) + right /= np.linalg.norm(right) + + up = np.cross(forward, right) + + view = np.eye(4) + view[0, :3] = right + view[1, :3] = up + view[2, :3] = -forward + view[:3, 3] = -np.dot(view[:3, :3], pos) + + return view + +def compute_projection_matrix(fov, aspect_ratio, near, far): + f = 1.0 / np.tan(np.radians(fov) / 2.0) + + proj = np.zeros((4, 4)) + proj[0, 0] = f / aspect_ratio + proj[1, 1] = f + proj[2, 2] = (far + near) / (near - far) + proj[2, 3] = (2 * far * near) / (near - far) + proj[3, 2] = -1.0 + + return proj + +def calculate_frustum_planes(xml_file): + pos, look, up, fov, near, far = parse_camera_params(xml_file) + + aspect_ratio = 1.0 # Assuming square aspect ratio based on window dimensions + + view_matrix = compute_view_matrix(pos, look, up) + projection_matrix = compute_projection_matrix(fov, aspect_ratio, near, far) + + view_proj_matrix = np.dot(projection_matrix, view_matrix) + + frustum_planes = extract_frustum_planes(view_proj_matrix) + + return frustum_planes + +def main(): + xml_file = 'scenes/test/test_4_6.xml' # Replace with the path to your XML file + frustum_planes = calculate_frustum_planes(xml_file) + + for i, plane in enumerate(frustum_planes): + normal = plane[:3] + distance = plane[3] + print(f"Plane {i}: Normal = {normal}, Distance = {distance}") + +if __name__ == "__main__": + main()