From 9f43b35a13198548f651fecf88907304fc3a384f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 24 Aug 2026 09:50:54 +0200 Subject: [PATCH] feat(core): add camera view-frustum culling planes --- core/include/vf/Frustum.h | 28 ++++++++++++++++++++ core/src/Frustum.cpp | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 core/include/vf/Frustum.h create mode 100644 core/src/Frustum.cpp diff --git a/core/include/vf/Frustum.h b/core/include/vf/Frustum.h new file mode 100644 index 0000000..7b53370 --- /dev/null +++ b/core/include/vf/Frustum.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Math.h" +#include "Physics.h" + +namespace vf { + +struct Plane { + float nx = 0.0f, ny = 0.0f, nz = 0.0f, d = 0.0f; +}; + +class Frustum { +public: + // Extracts the six clip planes from a view-projection matrix + // (Gribb and Hartmann). Planes point inward; a point is inside when its + // signed distance to every plane is non-negative. + static Frustum fromMatrix(const Mat4& viewProjection); + + bool containsPoint(const Vec3& p) const; + bool intersectsAabb(const Aabb& box) const; + + const Plane& plane(int i) const { return planes[i]; } + +private: + Plane planes[6]; +}; + +} // namespace vf diff --git a/core/src/Frustum.cpp b/core/src/Frustum.cpp new file mode 100644 index 0000000..292f75f --- /dev/null +++ b/core/src/Frustum.cpp @@ -0,0 +1,55 @@ +#include "vf/Frustum.h" + +#include +#include + +namespace vf { + +namespace { + +Plane normalized(float a, float b, float c, float d) { + float len = std::sqrt(a * a + b * b + c * c); + if (len <= 1e-8f) return Plane{a, b, c, d}; + float inv = 1.0f / len; + return Plane{a * inv, b * inv, c * inv, d * inv}; +} + +} // namespace + +Frustum Frustum::fromMatrix(const Mat4& vp) { + auto el = [&](int row, int col) { return vp.at(col, row); }; + auto row = [&](int r) { + return std::array{el(r, 0), el(r, 1), el(r, 2), el(r, 3)}; + }; + auto r0 = row(0); + auto r1 = row(1); + auto r2 = row(2); + auto r3 = row(3); + + Frustum f; + f.planes[0] = normalized(r3[0] + r0[0], r3[1] + r0[1], r3[2] + r0[2], r3[3] + r0[3]); // left + f.planes[1] = normalized(r3[0] - r0[0], r3[1] - r0[1], r3[2] - r0[2], r3[3] - r0[3]); // right + f.planes[2] = normalized(r3[0] + r1[0], r3[1] + r1[1], r3[2] + r1[2], r3[3] + r1[3]); // bottom + f.planes[3] = normalized(r3[0] - r1[0], r3[1] - r1[1], r3[2] - r1[2], r3[3] - r1[3]); // top + f.planes[4] = normalized(r3[0] + r2[0], r3[1] + r2[1], r3[2] + r2[2], r3[3] + r2[3]); // near + f.planes[5] = normalized(r3[0] - r2[0], r3[1] - r2[1], r3[2] - r2[2], r3[3] - r2[3]); // far + return f; +} + +bool Frustum::containsPoint(const Vec3& p) const { + for (const Plane& pl : planes) + if (pl.nx * p.x + pl.ny * p.y + pl.nz * p.z + pl.d < 0.0f) return false; + return true; +} + +bool Frustum::intersectsAabb(const Aabb& box) const { + for (const Plane& pl : planes) { + float px = pl.nx >= 0.0f ? box.max.x : box.min.x; + float py = pl.ny >= 0.0f ? box.max.y : box.min.y; + float pz = pl.nz >= 0.0f ? box.max.z : box.min.z; + if (pl.nx * px + pl.ny * py + pl.nz * pz + pl.d < 0.0f) return false; + } + return true; +} + +} // namespace vf