Skip to content

Commit 33cd3f6

Browse files
authored
Merge pull request #180 from orange-cpp/feature/aabb-linetrace
added aabb line trace
2 parents 0b52b28 + 67a07ee commit 33cd3f6

5 files changed

Lines changed: 214 additions & 12 deletions

File tree

.claude/settings.local.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

.idea/editor.xml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

include/omath/collision/line_tracer.hpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44
#pragma once
55

6+
#include "omath/linear_algebra/aabb.hpp"
67
#include "omath/linear_algebra/triangle.hpp"
78
#include "omath/linear_algebra/vector3.hpp"
89

@@ -34,6 +35,7 @@ namespace omath::collision
3435
class LineTracer final
3536
{
3637
using TriangleType = Triangle<typename RayType::VectorType>;
38+
using AABBType = AABB<typename RayType::VectorType>;
3739

3840
public:
3941
LineTracer() = delete;
@@ -87,6 +89,54 @@ namespace omath::collision
8789
return ray.start + ray_dir * t_hit;
8890
}
8991

92+
// Slab method ray-AABB intersection
93+
// Returns the hit point on the AABB surface, or ray.end if no intersection
94+
[[nodiscard]]
95+
constexpr static auto get_ray_hit_point(const RayType& ray, const AABBType& aabb) noexcept
96+
{
97+
using T = typename RayType::VectorType::ContainedType;
98+
const auto dir = ray.direction_vector();
99+
100+
auto t_min = -std::numeric_limits<T>::infinity();
101+
auto t_max = std::numeric_limits<T>::infinity();
102+
103+
const auto process_axis = [&](const T& d, const T& origin, const T& box_min,
104+
const T& box_max) -> bool
105+
{
106+
constexpr T k_epsilon = std::numeric_limits<T>::epsilon();
107+
if (std::abs(d) < k_epsilon)
108+
return origin >= box_min && origin <= box_max;
109+
110+
const T inv = T(1) / d;
111+
T t0 = (box_min - origin) * inv;
112+
T t1 = (box_max - origin) * inv;
113+
if (t0 > t1)
114+
std::swap(t0, t1);
115+
116+
t_min = std::max(t_min, t0);
117+
t_max = std::min(t_max, t1);
118+
return t_min <= t_max;
119+
};
120+
121+
if (!process_axis(dir.x, ray.start.x, aabb.min.x, aabb.max.x))
122+
return ray.end;
123+
if (!process_axis(dir.y, ray.start.y, aabb.min.y, aabb.max.y))
124+
return ray.end;
125+
if (!process_axis(dir.z, ray.start.z, aabb.min.z, aabb.max.z))
126+
return ray.end;
127+
128+
// t_hit: use entry point if in front of origin, otherwise 0 (started inside)
129+
const T t_hit = std::max(T(0), t_min);
130+
131+
if (t_max < T(0))
132+
return ray.end; // box entirely behind origin
133+
134+
if (!ray.infinite_length && t_hit > T(1))
135+
return ray.end; // box beyond ray endpoint
136+
137+
return ray.start + dir * t_hit;
138+
}
139+
90140
template<class MeshType>
91141
[[nodiscard]]
92142
constexpr static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Created by Vlad on 3/25/2025.
3+
//
4+
#pragma once
5+
6+
#include "omath/linear_algebra/vector3.hpp"
7+
8+
namespace omath
9+
{
10+
template<class Vector = Vector3<float>>
11+
class AABB final
12+
{
13+
public:
14+
using VectorType = Vector;
15+
16+
VectorType min;
17+
VectorType max;
18+
19+
constexpr AABB(const VectorType& min, const VectorType& max) noexcept : min(min), max(max) {}
20+
21+
[[nodiscard]]
22+
constexpr VectorType center() const noexcept
23+
{
24+
return (min + max) / static_cast<typename VectorType::ContainedType>(2);
25+
}
26+
27+
[[nodiscard]]
28+
constexpr VectorType extents() const noexcept
29+
{
30+
return (max - min) / static_cast<typename VectorType::ContainedType>(2);
31+
}
32+
};
33+
} // namespace omath
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//
2+
// Created by Vlad on 3/25/2025.
3+
//
4+
#include "omath/collision/line_tracer.hpp"
5+
#include "omath/linear_algebra/aabb.hpp"
6+
#include "omath/linear_algebra/vector3.hpp"
7+
#include <gtest/gtest.h>
8+
9+
using Vec3 = omath::Vector3<float>;
10+
using Ray = omath::collision::Ray<>;
11+
using LineTracer = omath::collision::LineTracer<>;
12+
using AABB = omath::AABB<Vec3>;
13+
14+
static Ray make_ray(Vec3 start, Vec3 end, bool infinite = false)
15+
{
16+
Ray r;
17+
r.start = start;
18+
r.end = end;
19+
r.infinite_length = infinite;
20+
return r;
21+
}
22+
23+
// Ray passing straight through the center along Z axis
24+
TEST(LineTracerAABBTests, HitCenterAlongZ)
25+
{
26+
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
27+
const auto ray = make_ray({0.f, 0.f, -5.f}, {0.f, 0.f, 5.f});
28+
29+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
30+
EXPECT_NE(hit, ray.end);
31+
EXPECT_NEAR(hit.z, -1.f, 1e-4f);
32+
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
33+
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
34+
}
35+
36+
// Ray passing straight through the center along X axis
37+
TEST(LineTracerAABBTests, HitCenterAlongX)
38+
{
39+
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
40+
const auto ray = make_ray({-5.f, 0.f, 0.f}, {5.f, 0.f, 0.f});
41+
42+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
43+
EXPECT_NE(hit, ray.end);
44+
EXPECT_NEAR(hit.x, -1.f, 1e-4f);
45+
}
46+
47+
// Ray that misses entirely (too far in Y)
48+
TEST(LineTracerAABBTests, MissReturnsEnd)
49+
{
50+
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
51+
const auto ray = make_ray({0.f, 5.f, -5.f}, {0.f, 5.f, 5.f});
52+
53+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
54+
EXPECT_EQ(hit, ray.end);
55+
}
56+
57+
// Ray that stops short before reaching the box
58+
TEST(LineTracerAABBTests, RayTooShortReturnsEnd)
59+
{
60+
const AABB box{{3.f, -1.f, -1.f}, {5.f, 1.f, 1.f}};
61+
const auto ray = make_ray({0.f, 0.f, 0.f}, {2.f, 0.f, 0.f});
62+
63+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
64+
EXPECT_EQ(hit, ray.end);
65+
}
66+
67+
// Infinite ray that starts before the box should hit
68+
TEST(LineTracerAABBTests, InfiniteRayHits)
69+
{
70+
const AABB box{{3.f, -1.f, -1.f}, {5.f, 1.f, 1.f}};
71+
const auto ray = make_ray({0.f, 0.f, 0.f}, {2.f, 0.f, 0.f}, true);
72+
73+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
74+
EXPECT_NE(hit, ray.end);
75+
EXPECT_NEAR(hit.x, 3.f, 1e-4f);
76+
}
77+
78+
// Ray starting inside the box — t_min=0, so hit point equals ray.start
79+
TEST(LineTracerAABBTests, RayStartsInsideBox)
80+
{
81+
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
82+
const auto ray = make_ray({0.f, 0.f, 0.f}, {0.f, 0.f, 5.f});
83+
84+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
85+
EXPECT_NE(hit, ray.end);
86+
// t_min is clamped to 0, so hit == start
87+
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
88+
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
89+
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
90+
}
91+
92+
// Ray parallel to XY plane, pointing along X, at Z outside the box
93+
TEST(LineTracerAABBTests, ParallelRayOutsideSlabMisses)
94+
{
95+
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
96+
// Z component of ray is 3.0 — outside box's Z slab
97+
const auto ray = make_ray({-5.f, 0.f, 3.f}, {5.f, 0.f, 3.f});
98+
99+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
100+
EXPECT_EQ(hit, ray.end);
101+
}
102+
103+
// Ray parallel to XY plane, pointing along X, at Z inside the box
104+
TEST(LineTracerAABBTests, ParallelRayInsideSlabHits)
105+
{
106+
const AABB box{{-1.f, -1.f, -1.f}, {1.f, 1.f, 1.f}};
107+
const auto ray = make_ray({-5.f, 0.f, 0.f}, {5.f, 0.f, 0.f});
108+
109+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
110+
EXPECT_NE(hit, ray.end);
111+
EXPECT_NEAR(hit.x, -1.f, 1e-4f);
112+
}
113+
114+
// Diagonal ray hitting a corner region
115+
TEST(LineTracerAABBTests, DiagonalRayHits)
116+
{
117+
const AABB box{{0.f, 0.f, 0.f}, {2.f, 2.f, 2.f}};
118+
const auto ray = make_ray({-1.f, -1.f, -1.f}, {3.f, 3.f, 3.f});
119+
120+
const auto hit = LineTracer::get_ray_hit_point(ray, box);
121+
EXPECT_NE(hit, ray.end);
122+
// Entry point should be at (0,0,0)
123+
EXPECT_NEAR(hit.x, 0.f, 1e-4f);
124+
EXPECT_NEAR(hit.y, 0.f, 1e-4f);
125+
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
126+
}

0 commit comments

Comments
 (0)