mirror of
https://github.com/orange-cpp/omath.git
synced 2026-04-18 21:03:27 +00:00
Compare commits
6 Commits
bcbb5c1a8d
...
v5.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| d8188de736 | |||
| 33cd3f64e4 | |||
| 67a07eed45 | |||
| 0b52b2847b | |||
| d38895e4d7 | |||
| 04203d46ff |
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(ls:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
10
.idea/editor.xml
generated
10
.idea/editor.xml
generated
@@ -17,7 +17,7 @@
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
@@ -103,14 +103,14 @@
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
@@ -202,7 +202,7 @@
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
@@ -216,7 +216,7 @@
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
|
||||
28
include/omath/3d_primitives/aabb.hpp
Normal file
28
include/omath/3d_primitives/aabb.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by Vladislav on 24.03.2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
|
||||
namespace omath::primitives
|
||||
{
|
||||
template<class Type>
|
||||
struct Aabb final
|
||||
{
|
||||
Vector3<Type> min;
|
||||
Vector3<Type> max;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Vector3<Type> center() const noexcept
|
||||
{
|
||||
return (min + max) / static_cast<Type>(2);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Vector3<Type> extents() const noexcept
|
||||
{
|
||||
return (max - min) / static_cast<Type>(2);
|
||||
}
|
||||
};
|
||||
} // namespace omath::primitives
|
||||
@@ -3,6 +3,7 @@
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "omath/3d_primitives/aabb.hpp"
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
|
||||
@@ -34,6 +35,7 @@ namespace omath::collision
|
||||
class LineTracer final
|
||||
{
|
||||
using TriangleType = Triangle<typename RayType::VectorType>;
|
||||
using AABBType = primitives::Aabb<typename RayType::VectorType::ContainedType>;
|
||||
|
||||
public:
|
||||
LineTracer() = delete;
|
||||
@@ -87,6 +89,54 @@ namespace omath::collision
|
||||
return ray.start + ray_dir * t_hit;
|
||||
}
|
||||
|
||||
// Slab method ray-AABB intersection
|
||||
// Returns the hit point on the AABB surface, or ray.end if no intersection
|
||||
[[nodiscard]]
|
||||
constexpr static auto get_ray_hit_point(const RayType& ray, const AABBType& aabb) noexcept
|
||||
{
|
||||
using T = typename RayType::VectorType::ContainedType;
|
||||
const auto dir = ray.direction_vector();
|
||||
|
||||
auto t_min = -std::numeric_limits<T>::infinity();
|
||||
auto t_max = std::numeric_limits<T>::infinity();
|
||||
|
||||
const auto process_axis = [&](const T& d, const T& origin, const T& box_min,
|
||||
const T& box_max) -> bool
|
||||
{
|
||||
constexpr T k_epsilon = std::numeric_limits<T>::epsilon();
|
||||
if (std::abs(d) < k_epsilon)
|
||||
return origin >= box_min && origin <= box_max;
|
||||
|
||||
const T inv = T(1) / d;
|
||||
T t0 = (box_min - origin) * inv;
|
||||
T t1 = (box_max - origin) * inv;
|
||||
if (t0 > t1)
|
||||
std::swap(t0, t1);
|
||||
|
||||
t_min = std::max(t_min, t0);
|
||||
t_max = std::min(t_max, t1);
|
||||
return t_min <= t_max;
|
||||
};
|
||||
|
||||
if (!process_axis(dir.x, ray.start.x, aabb.min.x, aabb.max.x))
|
||||
return ray.end;
|
||||
if (!process_axis(dir.y, ray.start.y, aabb.min.y, aabb.max.y))
|
||||
return ray.end;
|
||||
if (!process_axis(dir.z, ray.start.z, aabb.min.z, aabb.max.z))
|
||||
return ray.end;
|
||||
|
||||
// t_hit: use entry point if in front of origin, otherwise 0 (started inside)
|
||||
const T t_hit = std::max(T(0), t_min);
|
||||
|
||||
if (t_max < T(0))
|
||||
return ray.end; // box entirely behind origin
|
||||
|
||||
if (!ray.infinite_length && t_hit > T(1))
|
||||
return ray.end; // box beyond ray endpoint
|
||||
|
||||
return ray.start + dir * t_hit;
|
||||
}
|
||||
|
||||
template<class MeshType>
|
||||
[[nodiscard]]
|
||||
constexpr static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "omath/3d_primitives/aabb.hpp"
|
||||
#include "omath/linear_algebra/mat.hpp"
|
||||
#include "omath/linear_algebra/triangle.hpp"
|
||||
#include "omath/linear_algebra/vector3.hpp"
|
||||
@@ -308,6 +309,63 @@ namespace omath::projection
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_aabb_culled_by_frustum(const primitives::Aabb<float>& aabb) const noexcept
|
||||
{
|
||||
const auto& m = get_view_projection_matrix();
|
||||
|
||||
// Gribb-Hartmann: extract 6 frustum planes from the view-projection matrix.
|
||||
// Each plane is (a, b, c, d) such that ax + by + cz + d >= 0 means inside.
|
||||
// For a 4x4 matrix with rows r0..r3:
|
||||
// Left = r3 + r0
|
||||
// Right = r3 - r0
|
||||
// Bottom = r3 + r1
|
||||
// Top = r3 - r1
|
||||
// Near = r3 + r2 ([-1,1]) or r2 ([0,1])
|
||||
// Far = r3 - r2
|
||||
struct Plane final
|
||||
{
|
||||
float a, b, c, d;
|
||||
};
|
||||
|
||||
const auto extract_plane = [&m](const int sign, const int row) -> Plane
|
||||
{
|
||||
return {
|
||||
m.at(3, 0) + static_cast<float>(sign) * m.at(row, 0),
|
||||
m.at(3, 1) + static_cast<float>(sign) * m.at(row, 1),
|
||||
m.at(3, 2) + static_cast<float>(sign) * m.at(row, 2),
|
||||
m.at(3, 3) + static_cast<float>(sign) * m.at(row, 3),
|
||||
};
|
||||
};
|
||||
|
||||
std::array<Plane, 6> planes = {
|
||||
extract_plane(1, 0), // left
|
||||
extract_plane(-1, 0), // right
|
||||
extract_plane(1, 1), // bottom
|
||||
extract_plane(-1, 1), // top
|
||||
extract_plane(-1, 2), // far
|
||||
};
|
||||
|
||||
// Near plane depends on NDC depth range
|
||||
if constexpr (depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||
planes[5] = {m.at(2, 0), m.at(2, 1), m.at(2, 2), m.at(2, 3)};
|
||||
else
|
||||
planes[5] = extract_plane(1, 2);
|
||||
|
||||
// For each plane, find the AABB corner most in the direction of the plane normal
|
||||
// (the "positive vertex"). If it's outside, the entire AABB is outside.
|
||||
for (const auto& [a, b, c, d] : planes)
|
||||
{
|
||||
const float px = a >= 0.f ? aabb.max.x : aabb.min.x;
|
||||
const float py = b >= 0.f ? aabb.max.y : aabb.min.y;
|
||||
const float pz = c >= 0.f ? aabb.max.z : aabb.min.z;
|
||||
|
||||
if (a * px + b * py + c * pz + d < 0.f)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<Vector3<float>, Error>
|
||||
world_to_view_port(const Vector3<float>& world_position,
|
||||
const ViewPortClipping& clipping = ViewPortClipping::AUTO) const noexcept
|
||||
@@ -405,10 +463,10 @@ namespace omath::projection
|
||||
constexpr static bool is_ndc_z_value_out_of_bounds(const ZType& z_ndc) noexcept
|
||||
{
|
||||
constexpr auto eps = std::numeric_limits<float>::epsilon();
|
||||
if constexpr (depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||
return z_ndc < -1.0f - eps || z_ndc > 1.0f + eps;
|
||||
if constexpr (depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||
return z_ndc < 0.0f - eps || z_ndc > 1.0f + eps;
|
||||
if constexpr (depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||
return z_ndc < -1.0f - eps || z_ndc > 1.0f + eps;
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
125
tests/general/unit_test_line_tracer_aabb.cpp
Normal file
125
tests/general/unit_test_line_tracer_aabb.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// Created by Vlad on 3/25/2025.
|
||||
//
|
||||
#include "omath/collision/line_tracer.hpp"
|
||||
#include "omath/3d_primitives/aabb.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using Vec3 = omath::Vector3<float>;
|
||||
using Ray = omath::collision::Ray<>;
|
||||
using LineTracer = omath::collision::LineTracer<>;
|
||||
using AABB = omath::primitives::Aabb<float>;
|
||||
|
||||
static Ray make_ray(Vec3 start, Vec3 end, bool infinite = false)
|
||||
{
|
||||
Ray r;
|
||||
r.start = start;
|
||||
r.end = end;
|
||||
r.infinite_length = infinite;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Ray passing straight through the center along Z axis
|
||||
TEST(LineTracerAABBTests, HitCenterAlongZ)
|
||||
{
|
||||
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||
const auto ray = make_ray({0.f, 0.f, -5.f}, {0.f, 0.f, 5.f});
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_NE(hit, ray.end);
|
||||
EXPECT_NEAR(hit.z, -1.f, 1e-4f);
|
||||
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||
}
|
||||
|
||||
// Ray passing straight through the center along X axis
|
||||
TEST(LineTracerAABBTests, HitCenterAlongX)
|
||||
{
|
||||
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||
const auto ray = make_ray({-5.f, 0.f, 0.f}, {5.f, 0.f, 0.f});
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_NE(hit, ray.end);
|
||||
EXPECT_NEAR(hit.x, -1.f, 1e-4f);
|
||||
}
|
||||
|
||||
// Ray that misses entirely (too far in Y)
|
||||
TEST(LineTracerAABBTests, MissReturnsEnd)
|
||||
{
|
||||
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||
const auto ray = make_ray({0.f, 5.f, -5.f}, {0.f, 5.f, 5.f});
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
// Ray that stops short before reaching the box
|
||||
TEST(LineTracerAABBTests, RayTooShortReturnsEnd)
|
||||
{
|
||||
const AABB box{{3.f, -1.f, -1.f}, {5.f, 1.f, 1.f}};
|
||||
const auto ray = make_ray({0.f, 0.f, 0.f}, {2.f, 0.f, 0.f});
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
// Infinite ray that starts before the box should hit
|
||||
TEST(LineTracerAABBTests, InfiniteRayHits)
|
||||
{
|
||||
const AABB box{{3.f, -1.f, -1.f}, {5.f, 1.f, 1.f}};
|
||||
const auto ray = make_ray({0.f, 0.f, 0.f}, {2.f, 0.f, 0.f}, true);
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_NE(hit, ray.end);
|
||||
EXPECT_NEAR(hit.x, 3.f, 1e-4f);
|
||||
}
|
||||
|
||||
// Ray starting inside the box — t_min=0, so hit point equals ray.start
|
||||
TEST(LineTracerAABBTests, RayStartsInsideBox)
|
||||
{
|
||||
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||
const auto ray = make_ray({0.f, 0.f, 0.f}, {0.f, 0.f, 5.f});
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_NE(hit, ray.end);
|
||||
// t_min is clamped to 0, so hit == start
|
||||
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||
}
|
||||
|
||||
// Ray parallel to XY plane, pointing along X, at Z outside the box
|
||||
TEST(LineTracerAABBTests, ParallelRayOutsideSlabMisses)
|
||||
{
|
||||
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||
// Z component of ray is 3.0 — outside box's Z slab
|
||||
const auto ray = make_ray({-5.f, 0.f, 3.f}, {5.f, 0.f, 3.f});
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_EQ(hit, ray.end);
|
||||
}
|
||||
|
||||
// Ray parallel to XY plane, pointing along X, at Z inside the box
|
||||
TEST(LineTracerAABBTests, ParallelRayInsideSlabHits)
|
||||
{
|
||||
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
|
||||
const auto ray = make_ray({-5.f, 0.f, 0.f}, {5.f, 0.f, 0.f});
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_NE(hit, ray.end);
|
||||
EXPECT_NEAR(hit.x, -1.f, 1e-4f);
|
||||
}
|
||||
|
||||
// Diagonal ray hitting a corner region
|
||||
TEST(LineTracerAABBTests, DiagonalRayHits)
|
||||
{
|
||||
const AABB box{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}};
|
||||
const auto ray = make_ray({-1.f, -1.f, -1.f}, {3.f, 3.f, 3.f});
|
||||
|
||||
const auto hit = LineTracer::get_ray_hit_point(ray, box);
|
||||
EXPECT_NE(hit, ray.end);
|
||||
// Entry point should be at (0,0,0)
|
||||
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
|
||||
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
|
||||
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "omath/engines/unity_engine/camera.hpp"
|
||||
#include <complex>
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/3d_primitives/aabb.hpp>
|
||||
#include <omath/engines/opengl_engine/camera.hpp>
|
||||
#include <omath/engines/source_engine/camera.hpp>
|
||||
#include <omath/projection/camera.hpp>
|
||||
#include <print>
|
||||
@@ -216,4 +218,296 @@ TEST(UnitTestProjection, ScreenToWorldBottomLeftCorner)
|
||||
EXPECT_NEAR(screen_cords->x, initial_screen_cords.x, 0.001f);
|
||||
EXPECT_NEAR(screen_cords->y, initial_screen_cords.y, 0.001f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbInsideFrustumNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Small box directly in front of camera (Source Engine: +X forward, +Y left, +Z up)
|
||||
const omath::primitives::Aabb<float> aabb{{90.f, -1.f, -1.f}, {110.f, 1.f, 1.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbBehindCameraCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box entirely behind the camera
|
||||
const omath::primitives::Aabb<float> aabb{{-200.f, -1.f, -1.f}, {-100.f, 1.f, 1.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbBeyondFarPlaneCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box beyond far plane (1000)
|
||||
const omath::primitives::Aabb<float> aabb{{1500.f, -1.f, -1.f}, {2000.f, 1.f, 1.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbFarLeftCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box far to the side, outside the frustum
|
||||
const omath::primitives::Aabb<float> aabb{{90.f, 4000.f, -1.f}, {110.f, 5000.f, 1.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbFarRightCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box far to the other side, outside the frustum
|
||||
const omath::primitives::Aabb<float> aabb{{90.f, -5000.f, -1.f}, {110.f, -4000.f, 1.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbAboveCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box far above the frustum
|
||||
const omath::primitives::Aabb<float> aabb{{90.f, -1.f, 5000.f}, {110.f, 1.f, 6000.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbPartiallyInsideNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Large box that straddles the frustum boundary — partially inside
|
||||
const omath::primitives::Aabb<float> aabb{{50.f, -5000.f, -5000.f}, {500.f, 5000.f, 5000.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbStraddlesNearPlaneNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box that straddles the near plane — partially in front
|
||||
const omath::primitives::Aabb<float> aabb{{-5.f, -1.f, -1.f}, {5.f, 1.f, 1.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbStraddlesFarPlaneNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box that straddles the far plane
|
||||
const omath::primitives::Aabb<float> aabb{{900.f, -1.f, -1.f}, {1100.f, 1.f, 1.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbCulledUnityEngine)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
|
||||
const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f);
|
||||
|
||||
// Box in front — not culled
|
||||
const omath::primitives::Aabb<float> inside{{-1.f, -1.f, 50.f}, {1.f, 1.f, 100.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(inside));
|
||||
|
||||
// Box behind — culled
|
||||
const omath::primitives::Aabb<float> behind{{-1.f, -1.f, -200.f}, {1.f, 1.f, -100.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(behind));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbBelowCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box far below the frustum (Source Engine: +Z up)
|
||||
const omath::primitives::Aabb<float> aabb{{90.f, -1.f, -6000.f}, {110.f, 1.f, -5000.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbEnclosesCameraNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Huge box that fully encloses the camera
|
||||
const omath::primitives::Aabb<float> aabb{{-500.f, -500.f, -500.f}, {500.f, 500.f, 500.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbExactlyAtNearPlaneNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box starting exactly at the near plane distance
|
||||
const omath::primitives::Aabb<float> aabb{{0.01f, -1.f, -1.f}, {10.f, 1.f, 1.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbExactlyAtFarPlaneNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Box ending exactly at the far plane distance
|
||||
const omath::primitives::Aabb<float> aabb{{990.f, -1.f, -1.f}, {1000.f, 1.f, 1.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbNarrowFovCulledAtEdge)
|
||||
{
|
||||
// Narrow FOV — box that would be visible at 90 is culled at 30
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(30.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
const omath::primitives::Aabb<float> aabb{{100.f, 200.f, -1.f}, {110.f, 210.f, 1.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbWideFovNotCulledAtEdge)
|
||||
{
|
||||
// Wide FOV — same box is visible
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(120.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
const omath::primitives::Aabb<float> aabb{{100.f, 200.f, -1.f}, {110.f, 210.f, 1.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbCameraOffOrigin)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({500.f, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f},
|
||||
fov, 0.01f, 1000.f);
|
||||
|
||||
// Box in front of shifted camera
|
||||
const omath::primitives::Aabb<float> in_front{{600.f, -1.f, -1.f}, {700.f, 1.f, 1.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(in_front));
|
||||
|
||||
// Box behind shifted camera
|
||||
const omath::primitives::Aabb<float> behind{{0.f, -1.f, -1.f}, {100.f, 1.f, 1.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(behind));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbShortFarPlaneCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
// Very short far plane
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 50.f);
|
||||
|
||||
// Box at distance 100 — beyond the 50-unit far plane
|
||||
const omath::primitives::Aabb<float> aabb{{90.f, -1.f, -1.f}, {110.f, 1.f, 1.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
|
||||
// Box at distance 30 — within range
|
||||
const omath::primitives::Aabb<float> near_box{{25.f, -1.f, -1.f}, {35.f, 1.f, 1.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(near_box));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbPointSizedInsideNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
|
||||
0.01f, 1000.f);
|
||||
|
||||
// Degenerate zero-volume AABB (a point) inside the frustum
|
||||
const omath::primitives::Aabb<float> aabb{{100.f, 0.f, 0.f}, {100.f, 0.f, 0.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbOpenGlEngineInsideNotCulled)
|
||||
{
|
||||
// OpenGL: COLUMN_MAJOR, NEGATIVE_ONE_TO_ONE, inverted_z, forward = -Z
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f);
|
||||
|
||||
// Box in front of camera (OpenGL: -Z forward)
|
||||
const omath::primitives::Aabb<float> aabb{{-1.f, -1.f, -110.f}, {1.f, 1.f, -90.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbOpenGlEngineBehindCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f);
|
||||
|
||||
// Box behind (OpenGL: +Z is behind the camera)
|
||||
const omath::primitives::Aabb<float> aabb{{-1.f, -1.f, 100.f}, {1.f, 1.f, 200.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbOpenGlEngineBeyondFarCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f);
|
||||
|
||||
// Box beyond far plane along -Z
|
||||
const omath::primitives::Aabb<float> aabb{{-1.f, -1.f, -2000.f}, {1.f, 1.f, -1500.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbOpenGlEngineSideCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(90.f);
|
||||
const auto cam = omath::opengl_engine::Camera({0, 0, 0}, {}, {1920.f, 1080.f}, fov, 0.01f, 1000.f);
|
||||
|
||||
// Box far to the right (OpenGL: +X right)
|
||||
const omath::primitives::Aabb<float> aabb{{4000.f, -1.f, -110.f}, {5000.f, 1.f, -90.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbUnityEngineBeyondFarCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
|
||||
const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 500.f);
|
||||
|
||||
// Box beyond 500-unit far plane (Unity: +Z forward)
|
||||
const omath::primitives::Aabb<float> aabb{{-1.f, -1.f, 600.f}, {1.f, 1.f, 700.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbUnityEngineSideCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
|
||||
const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f);
|
||||
|
||||
// Box far above (Unity: +Y up)
|
||||
const omath::primitives::Aabb<float> aabb{{-1.f, 5000.f, 50.f}, {1.f, 6000.f, 100.f}};
|
||||
EXPECT_TRUE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
|
||||
TEST(UnitTestProjection, AabbUnityEngineStraddlesNearNotCulled)
|
||||
{
|
||||
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
|
||||
const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f);
|
||||
|
||||
// Box straddles near plane (Unity: +Z forward)
|
||||
const omath::primitives::Aabb<float> aabb{{-1.f, -1.f, -5.f}, {1.f, 1.f, 5.f}};
|
||||
EXPECT_FALSE(cam.is_aabb_culled_by_frustum(aabb));
|
||||
}
|
||||
Reference in New Issue
Block a user