Skip to content

Commit 45a37eb

Browse files
authored
Merge pull request #111 from orange-cpp/feature/frustum_culling_method
added check method
2 parents d7189eb + 90c4ea2 commit 45a37eb

File tree

6 files changed

+152
-73
lines changed

6 files changed

+152
-73
lines changed

CMakePresets.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@
3838
},
3939
{
4040
"name": "windows-debug-vcpkg",
41-
"displayName": "Debug",
41+
"displayName": "Windows Debug Vcpkg",
4242
"inherits": "windows-base-vcpkg",
4343
"cacheVariables": {
4444
"CMAKE_BUILD_TYPE": "Debug"
4545
}
4646
},
4747
{
4848
"name": "windows-release-vcpkg",
49-
"displayName": "Release",
49+
"displayName": "Windows Release Vcpkg",
5050
"inherits": "windows-base-vcpkg",
5151
"cacheVariables": {
5252
"CMAKE_BUILD_TYPE": "Release",
@@ -157,7 +157,7 @@
157157
},
158158
{
159159
"name": "darwin-debug-vcpkg",
160-
"displayName": "Darwin Debug",
160+
"displayName": "Darwin Debug Vcpkg",
161161
"inherits": "darwin-base-vcpkg",
162162
"cacheVariables": {
163163
"CMAKE_BUILD_TYPE": "Debug"
@@ -173,7 +173,7 @@
173173
},
174174
{
175175
"name": "darwin-release-vcpkg",
176-
"displayName": "Darwin Release",
176+
"displayName": "Darwin Release Vcpkg",
177177
"inherits": "darwin-base-vcpkg",
178178
"cacheVariables": {
179179
"CMAKE_BUILD_TYPE": "Release"

include/omath/3d_primitives/mesh.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ namespace omath::primitives
3838

3939
public:
4040
Vbo m_vertex_buffer;
41-
Ebo m_vertex_array_object;
41+
Ebo m_element_buffer_object;
4242

4343
Mesh(Vbo vbo, Ebo vao,
4444
const VectorType scale =
@@ -47,7 +47,7 @@ namespace omath::primitives
4747
1,
4848
1,
4949
})
50-
: m_vertex_buffer(std::move(vbo)), m_vertex_array_object(std::move(vao)), m_scale(std::move(scale))
50+
: m_vertex_buffer(std::move(vbo)), m_element_buffer_object(std::move(vao)), m_scale(std::move(scale))
5151
{
5252
}
5353
void set_origin(const VectorType& new_origin)

include/omath/collision/collider_interface.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Created by Vladislav on 06.12.2025.
33
//
44
#pragma once
5-
5+
#include <omath/linear_algebra/vector3.hpp>
66

77
namespace omath::collision
88
{

include/omath/collision/epa_algorithm.hpp

Lines changed: 95 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#pragma once
22
#include "simplex.hpp"
3-
#include <algorithm> // find_if
3+
#include <algorithm>
44
#include <array>
55
#include <cmath>
66
#include <cstdint>
@@ -19,7 +19,7 @@ namespace omath::collision
1919
{ a.cross(b) } -> std::same_as<V>;
2020
{ a.dot(b) } -> std::same_as<float>;
2121
{ -a } -> std::same_as<V>;
22-
{ a* s } -> std::same_as<V>;
22+
{ a * s } -> std::same_as<V>;
2323
{ a / s } -> std::same_as<V>;
2424
};
2525

@@ -45,29 +45,19 @@ namespace omath::collision
4545
int max_iterations{64};
4646
float tolerance{1e-4f}; // absolute tolerance on distance growth
4747
};
48-
4948
// Precondition: simplex.size()==4 and contains the origin.
5049
[[nodiscard]]
5150
static std::optional<Result> solve(const ColliderInterfaceType& a, const ColliderInterfaceType& b,
5251
const Simplex<VectorType>& simplex, const Params params = {},
53-
std::shared_ptr<std::pmr::memory_resource> mem_resource = {
54-
std::shared_ptr<void>{}, std::pmr::get_default_resource()})
52+
std::pmr::memory_resource& mem_resource = *std::pmr::get_default_resource())
5553
{
5654
// --- Build initial polytope from simplex (4 points) ---
57-
std::pmr::vector<VectorType> vertexes{mem_resource.get()};
58-
vertexes.reserve(simplex.size());
59-
for (std::size_t i = 0; i < simplex.size(); ++i)
60-
vertexes.emplace_back(simplex[i]);
55+
std::pmr::vector<VectorType> vertexes = build_initial_polytope_from_simplex(simplex, mem_resource);
6156

6257
// Initial tetra faces (windings corrected in make_face)
63-
std::pmr::vector<Face> faces{mem_resource.get()};
64-
faces.reserve(4);
65-
faces.emplace_back(make_face(vertexes, 0, 1, 2));
66-
faces.emplace_back(make_face(vertexes, 0, 2, 3));
67-
faces.emplace_back(make_face(vertexes, 0, 3, 1));
68-
faces.emplace_back(make_face(vertexes, 1, 3, 2));
58+
std::pmr::vector<Face> faces = create_initial_tetra_faces(mem_resource, vertexes);
6959

70-
auto heap = rebuild_heap(faces);
60+
auto heap = rebuild_heap(faces, mem_resource);
7161

7262
Result out{};
7363

@@ -80,7 +70,7 @@ namespace omath::collision
8070
// (We could keep face handles; this is fine for small Ns.)
8171

8272
if (const auto top = heap.top(); faces[top.idx].d != top.d)
83-
heap = rebuild_heap(faces);
73+
heap = rebuild_heap(faces, mem_resource);
8474

8575
if (heap.empty())
8676
break;
@@ -109,62 +99,35 @@ namespace omath::collision
10999
const int new_idx = static_cast<int>(vertexes.size());
110100
vertexes.emplace_back(p);
111101

112-
// Mark faces visible from p and collect their horizon
113-
std::pmr::vector<bool> to_delete(faces.size(), false, mem_resource.get()); // uses single bits
114-
std::pmr::vector<Edge> boundary{mem_resource.get()};
115-
boundary.reserve(faces.size() * 2);
116-
117-
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
118-
{
119-
if (to_delete[i])
120-
continue;
121-
if (visible_from(faces[i], p))
122-
{
123-
const auto& rf = faces[i];
124-
to_delete[i] = true;
125-
add_edge_boundary(boundary, rf.i0, rf.i1);
126-
add_edge_boundary(boundary, rf.i1, rf.i2);
127-
add_edge_boundary(boundary, rf.i2, rf.i0);
128-
}
129-
}
102+
const auto [to_delete, boundary] = mark_visible_and_collect_horizon(faces, p);
130103

131-
// Remove visible faces
132-
std::pmr::vector<Face> new_faces{mem_resource.get()};
133-
new_faces.reserve(faces.size() + boundary.size());
134-
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
135-
if (!to_delete[i])
136-
new_faces.emplace_back(faces[i]);
137-
faces.swap(new_faces);
104+
erase_marked(faces, to_delete);
138105

139106
// Stitch new faces around the horizon
140107
for (const auto& e : boundary)
141108
faces.emplace_back(make_face(vertexes, e.a, e.b, new_idx));
142109

143110
// Rebuild heap after topology change
144-
heap = rebuild_heap(faces);
111+
heap = rebuild_heap(faces, mem_resource);
145112

146113
if (!std::isfinite(vertexes.back().dot(vertexes.back())))
147114
break; // safety
148115
out.iterations = it + 1;
149116
}
150117

151-
// Fallback: pick closest face as best-effort answer
152-
if (!faces.empty())
153-
{
154-
auto best = faces[0];
155-
for (const auto& f : faces)
156-
if (f.d < best.d)
157-
best = f;
158-
out.normal = best.n;
159-
out.depth = best.d;
160-
out.num_vertices = static_cast<int>(vertexes.size());
161-
out.num_faces = static_cast<int>(faces.size());
162-
163-
out.penetration_vector = out.normal * out.depth;
164-
165-
return out;
166-
}
167-
return std::nullopt;
118+
if (faces.empty())
119+
return std::nullopt;
120+
121+
const auto best = *std::ranges::min_element(faces, [](const auto& first, const auto& second)
122+
{ return first.d < second.d; });
123+
out.normal = best.n;
124+
out.depth = best.d;
125+
out.num_vertices = static_cast<int>(vertexes.size());
126+
out.num_faces = static_cast<int>(faces.size());
127+
128+
out.penetration_vector = out.normal * out.depth;
129+
130+
return out;
168131
}
169132

170133
private:
@@ -193,15 +156,21 @@ namespace omath::collision
193156
return lhs.d > rhs.d; // min-heap by distance
194157
}
195158
};
196-
using Heap = std::priority_queue<HeapItem, std::vector<HeapItem>, HeapCmp>;
159+
160+
using Heap = std::priority_queue<HeapItem, std::pmr::vector<HeapItem>, HeapCmp>;
197161

198162
[[nodiscard]]
199-
static Heap rebuild_heap(const std::pmr::vector<Face>& faces)
163+
static Heap rebuild_heap(const std::pmr::vector<Face>& faces, auto& memory_resource)
200164
{
201-
Heap h;
165+
std::pmr::vector<HeapItem> storage{&memory_resource};
166+
storage.reserve(faces.size()); // optional but recommended
167+
168+
Heap h{HeapCmp{}, std::move(storage)};
169+
202170
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
203171
h.emplace(faces[i].d, i);
204-
return h;
172+
173+
return h; // allocator is preserved
205174
}
206175

207176
[[nodiscard]]
@@ -267,5 +236,67 @@ namespace omath::collision
267236
return d;
268237
return V{1, 0, 0};
269238
}
239+
[[nodiscard]]
240+
static std::pmr::vector<Face> create_initial_tetra_faces(std::pmr::memory_resource& mem_resource,
241+
const std::pmr::vector<VectorType>& vertexes)
242+
{
243+
std::pmr::vector<Face> faces{&mem_resource};
244+
faces.reserve(4);
245+
faces.emplace_back(make_face(vertexes, 0, 1, 2));
246+
faces.emplace_back(make_face(vertexes, 0, 2, 3));
247+
faces.emplace_back(make_face(vertexes, 0, 3, 1));
248+
faces.emplace_back(make_face(vertexes, 1, 3, 2));
249+
return faces;
250+
}
251+
252+
[[nodiscard]]
253+
static std::pmr::vector<VectorType> build_initial_polytope_from_simplex(const Simplex<VectorType>& simplex,
254+
std::pmr::memory_resource& mem_resource)
255+
{
256+
std::pmr::vector<VectorType> vertexes{&mem_resource};
257+
vertexes.reserve(simplex.size());
258+
259+
for (std::size_t i = 0; i < simplex.size(); ++i)
260+
vertexes.emplace_back(simplex[i]);
261+
262+
return vertexes;
263+
}
264+
static void erase_marked(std::pmr::vector<Face>& faces, const std::pmr::vector<bool>& to_delete)
265+
{
266+
auto* mr = faces.get_allocator().resource(); // keep same resource
267+
std::pmr::vector<Face> kept{mr};
268+
kept.reserve(faces.size());
269+
270+
for (std::size_t i = 0; i < faces.size(); ++i)
271+
if (!to_delete[i])
272+
kept.emplace_back(faces[i]);
273+
274+
faces.swap(kept);
275+
}
276+
struct Horizon
277+
{
278+
std::pmr::vector<bool> to_delete;
279+
std::pmr::vector<Edge> boundary;
280+
};
281+
282+
static Horizon mark_visible_and_collect_horizon(const std::pmr::vector<Face>& faces, const VectorType& p)
283+
{
284+
auto* mr = faces.get_allocator().resource();
285+
286+
Horizon horizon{std::pmr::vector<bool>(faces.size(), false, mr), std::pmr::vector<Edge>(mr)};
287+
horizon.boundary.reserve(faces.size());
288+
289+
for (std::size_t i = 0; i < faces.size(); ++i)
290+
if (visible_from(faces[i], p))
291+
{
292+
const auto& rf = faces[i];
293+
horizon.to_delete[i] = true;
294+
add_edge_boundary(horizon.boundary, rf.i0, rf.i1);
295+
add_edge_boundary(horizon.boundary, rf.i1, rf.i2);
296+
add_edge_boundary(horizon.boundary, rf.i2, rf.i0);
297+
}
298+
299+
return horizon;
300+
}
270301
};
271302
} // namespace omath::collision

include/omath/projection/camera.hpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#pragma once
66

77
#include "omath/linear_algebra/mat.hpp"
8+
#include "omath/linear_algebra/triangle.hpp"
89
#include "omath/linear_algebra/vector3.hpp"
910
#include "omath/projection/error_codes.hpp"
1011
#include <expected>
@@ -175,6 +176,53 @@ namespace omath::projection
175176
std::unreachable();
176177
}
177178

179+
[[nodiscard]] bool is_culled_by_frustum(const Triangle<Vector3<float>>& triangle) const noexcept
180+
{
181+
// Transform to clip space (before perspective divide)
182+
auto to_clip = [this](const Vector3<float>& point)
183+
{
184+
auto clip = get_view_projection_matrix()
185+
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(point);
186+
return std::array<float, 4>{
187+
clip.at(0, 0), // x
188+
clip.at(1, 0), // y
189+
clip.at(2, 0), // z
190+
clip.at(3, 0) // w
191+
};
192+
};
193+
194+
const auto c0 = to_clip(triangle.m_vertex1);
195+
const auto c1 = to_clip(triangle.m_vertex2);
196+
const auto c2 = to_clip(triangle.m_vertex3);
197+
198+
// If all vertices are behind the camera (w <= 0), trivially reject
199+
if (c0[3] <= 0.f && c1[3] <= 0.f && c2[3] <= 0.f)
200+
return true;
201+
202+
// Helper: all three vertices outside the same clip plane
203+
auto all_outside_plane = [](const int axis, const std::array<float, 4>& a, const std::array<float, 4>& b,
204+
const std::array<float, 4>& c, const bool positive_side)
205+
{
206+
if (positive_side)
207+
return a[axis] > a[3] && b[axis] > b[3] && c[axis] > c[3];
208+
return a[axis] < -a[3] && b[axis] < -b[3] && c[axis] < -c[3];
209+
};
210+
211+
// Clip volume in clip space (OpenGL-style):
212+
// -w <= x <= w
213+
// -w <= y <= w
214+
// -w <= z <= w
215+
216+
for (int i = 0; i < 3; i++)
217+
{
218+
if (all_outside_plane(i, c0, c1, c2, false))
219+
return true; // x < -w (left)
220+
if (all_outside_plane(i, c0, c1, c2, true))
221+
return true; // x > w (right)
222+
}
223+
return false;
224+
}
225+
178226
[[nodiscard]] std::expected<Vector3<float>, Error>
179227
world_to_view_port(const Vector3<float>& world_position) const noexcept
180228
{

tests/general/unit_test_epa.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ TEST(UnitTestEpa, TestCollisionTrue)
4545
auto pool = std::make_shared<std::pmr::monotonic_buffer_resource>(1024);
4646
params.max_iterations = 64;
4747
params.tolerance = 1e-4f;
48-
auto epa = EPA::solve(A, B, gjk.simplex, params, pool);
48+
auto epa = EPA::solve(A, B, gjk.simplex, params, *pool);
4949
ASSERT_TRUE(epa.has_value()) << "EPA should converge";
5050

5151
// Normal is unit
@@ -119,7 +119,7 @@ TEST(UnitTestEpa, TestCollisionTrue2)
119119
params.max_iterations = 64;
120120
params.tolerance = 1e-4f;
121121
auto pool = std::make_shared<std::pmr::monotonic_buffer_resource>(1024);
122-
auto epa = EPA::solve(A, B, gjk.simplex, params, pool);
122+
auto epa = EPA::solve(A, B, gjk.simplex, params, *pool);
123123
ASSERT_TRUE(epa.has_value()) << "EPA should converge";
124124

125125
// Normal is unit-length

0 commit comments

Comments
 (0)