diff --git a/CMakePresets.json b/CMakePresets.json index d9f232d..5b8b205 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -38,7 +38,7 @@ }, { "name": "windows-debug-vcpkg", - "displayName": "Debug", + "displayName": "Windows Debug Vcpkg", "inherits": "windows-base-vcpkg", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" @@ -46,7 +46,7 @@ }, { "name": "windows-release-vcpkg", - "displayName": "Release", + "displayName": "Windows Release Vcpkg", "inherits": "windows-base-vcpkg", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", @@ -157,7 +157,7 @@ }, { "name": "darwin-debug-vcpkg", - "displayName": "Darwin Debug", + "displayName": "Darwin Debug Vcpkg", "inherits": "darwin-base-vcpkg", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" @@ -173,7 +173,7 @@ }, { "name": "darwin-release-vcpkg", - "displayName": "Darwin Release", + "displayName": "Darwin Release Vcpkg", "inherits": "darwin-base-vcpkg", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" diff --git a/include/omath/3d_primitives/mesh.hpp b/include/omath/3d_primitives/mesh.hpp index cc2e5e4..bac5513 100644 --- a/include/omath/3d_primitives/mesh.hpp +++ b/include/omath/3d_primitives/mesh.hpp @@ -38,7 +38,7 @@ namespace omath::primitives public: Vbo m_vertex_buffer; - Ebo m_vertex_array_object; + Ebo m_element_buffer_object; Mesh(Vbo vbo, Ebo vao, const VectorType scale = @@ -47,7 +47,7 @@ namespace omath::primitives 1, 1, }) - : m_vertex_buffer(std::move(vbo)), m_vertex_array_object(std::move(vao)), m_scale(std::move(scale)) + : m_vertex_buffer(std::move(vbo)), m_element_buffer_object(std::move(vao)), m_scale(std::move(scale)) { } void set_origin(const VectorType& new_origin) diff --git a/include/omath/collision/collider_interface.hpp b/include/omath/collision/collider_interface.hpp index b27487b..7047d7c 100644 --- a/include/omath/collision/collider_interface.hpp +++ b/include/omath/collision/collider_interface.hpp @@ -2,7 +2,7 @@ // Created by Vladislav on 06.12.2025. // #pragma once - +#include namespace omath::collision { diff --git a/include/omath/collision/epa_algorithm.hpp b/include/omath/collision/epa_algorithm.hpp index e98cf05..cd5ed36 100644 --- a/include/omath/collision/epa_algorithm.hpp +++ b/include/omath/collision/epa_algorithm.hpp @@ -1,6 +1,6 @@ #pragma once #include "simplex.hpp" -#include // find_if +#include #include #include #include @@ -19,7 +19,7 @@ namespace omath::collision { a.cross(b) } -> std::same_as; { a.dot(b) } -> std::same_as; { -a } -> std::same_as; - { a* s } -> std::same_as; + { a * s } -> std::same_as; { a / s } -> std::same_as; }; @@ -45,29 +45,19 @@ namespace omath::collision int max_iterations{64}; float tolerance{1e-4f}; // absolute tolerance on distance growth }; - // Precondition: simplex.size()==4 and contains the origin. [[nodiscard]] static std::optional solve(const ColliderInterfaceType& a, const ColliderInterfaceType& b, const Simplex& simplex, const Params params = {}, - std::shared_ptr mem_resource = { - std::shared_ptr{}, std::pmr::get_default_resource()}) + std::pmr::memory_resource& mem_resource = *std::pmr::get_default_resource()) { // --- Build initial polytope from simplex (4 points) --- - std::pmr::vector vertexes{mem_resource.get()}; - vertexes.reserve(simplex.size()); - for (std::size_t i = 0; i < simplex.size(); ++i) - vertexes.emplace_back(simplex[i]); + std::pmr::vector vertexes = build_initial_polytope_from_simplex(simplex, mem_resource); // Initial tetra faces (windings corrected in make_face) - std::pmr::vector faces{mem_resource.get()}; - faces.reserve(4); - faces.emplace_back(make_face(vertexes, 0, 1, 2)); - faces.emplace_back(make_face(vertexes, 0, 2, 3)); - faces.emplace_back(make_face(vertexes, 0, 3, 1)); - faces.emplace_back(make_face(vertexes, 1, 3, 2)); + std::pmr::vector faces = create_initial_tetra_faces(mem_resource, vertexes); - auto heap = rebuild_heap(faces); + auto heap = rebuild_heap(faces, mem_resource); Result out{}; @@ -80,7 +70,7 @@ namespace omath::collision // (We could keep face handles; this is fine for small Ns.) if (const auto top = heap.top(); faces[top.idx].d != top.d) - heap = rebuild_heap(faces); + heap = rebuild_heap(faces, mem_resource); if (heap.empty()) break; @@ -109,62 +99,35 @@ namespace omath::collision const int new_idx = static_cast(vertexes.size()); vertexes.emplace_back(p); - // Mark faces visible from p and collect their horizon - std::pmr::vector to_delete(faces.size(), false, mem_resource.get()); // uses single bits - std::pmr::vector boundary{mem_resource.get()}; - boundary.reserve(faces.size() * 2); - - for (int i = 0; i < static_cast(faces.size()); ++i) - { - if (to_delete[i]) - continue; - if (visible_from(faces[i], p)) - { - const auto& rf = faces[i]; - to_delete[i] = true; - add_edge_boundary(boundary, rf.i0, rf.i1); - add_edge_boundary(boundary, rf.i1, rf.i2); - add_edge_boundary(boundary, rf.i2, rf.i0); - } - } + const auto [to_delete, boundary] = mark_visible_and_collect_horizon(faces, p); - // Remove visible faces - std::pmr::vector new_faces{mem_resource.get()}; - new_faces.reserve(faces.size() + boundary.size()); - for (int i = 0; i < static_cast(faces.size()); ++i) - if (!to_delete[i]) - new_faces.emplace_back(faces[i]); - faces.swap(new_faces); + erase_marked(faces, to_delete); // Stitch new faces around the horizon for (const auto& e : boundary) faces.emplace_back(make_face(vertexes, e.a, e.b, new_idx)); // Rebuild heap after topology change - heap = rebuild_heap(faces); + heap = rebuild_heap(faces, mem_resource); if (!std::isfinite(vertexes.back().dot(vertexes.back()))) break; // safety out.iterations = it + 1; } - // Fallback: pick closest face as best-effort answer - if (!faces.empty()) - { - auto best = faces[0]; - for (const auto& f : faces) - if (f.d < best.d) - best = f; - out.normal = best.n; - out.depth = best.d; - out.num_vertices = static_cast(vertexes.size()); - out.num_faces = static_cast(faces.size()); - - out.penetration_vector = out.normal * out.depth; - - return out; - } - return std::nullopt; + if (faces.empty()) + return std::nullopt; + + const auto best = *std::ranges::min_element(faces, [](const auto& first, const auto& second) + { return first.d < second.d; }); + out.normal = best.n; + out.depth = best.d; + out.num_vertices = static_cast(vertexes.size()); + out.num_faces = static_cast(faces.size()); + + out.penetration_vector = out.normal * out.depth; + + return out; } private: @@ -193,15 +156,21 @@ namespace omath::collision return lhs.d > rhs.d; // min-heap by distance } }; - using Heap = std::priority_queue, HeapCmp>; + + using Heap = std::priority_queue, HeapCmp>; [[nodiscard]] - static Heap rebuild_heap(const std::pmr::vector& faces) + static Heap rebuild_heap(const std::pmr::vector& faces, auto& memory_resource) { - Heap h; + std::pmr::vector storage{&memory_resource}; + storage.reserve(faces.size()); // optional but recommended + + Heap h{HeapCmp{}, std::move(storage)}; + for (int i = 0; i < static_cast(faces.size()); ++i) h.emplace(faces[i].d, i); - return h; + + return h; // allocator is preserved } [[nodiscard]] @@ -267,5 +236,67 @@ namespace omath::collision return d; return V{1, 0, 0}; } + [[nodiscard]] + static std::pmr::vector create_initial_tetra_faces(std::pmr::memory_resource& mem_resource, + const std::pmr::vector& vertexes) + { + std::pmr::vector faces{&mem_resource}; + faces.reserve(4); + faces.emplace_back(make_face(vertexes, 0, 1, 2)); + faces.emplace_back(make_face(vertexes, 0, 2, 3)); + faces.emplace_back(make_face(vertexes, 0, 3, 1)); + faces.emplace_back(make_face(vertexes, 1, 3, 2)); + return faces; + } + + [[nodiscard]] + static std::pmr::vector build_initial_polytope_from_simplex(const Simplex& simplex, + std::pmr::memory_resource& mem_resource) + { + std::pmr::vector vertexes{&mem_resource}; + vertexes.reserve(simplex.size()); + + for (std::size_t i = 0; i < simplex.size(); ++i) + vertexes.emplace_back(simplex[i]); + + return vertexes; + } + static void erase_marked(std::pmr::vector& faces, const std::pmr::vector& to_delete) + { + auto* mr = faces.get_allocator().resource(); // keep same resource + std::pmr::vector kept{mr}; + kept.reserve(faces.size()); + + for (std::size_t i = 0; i < faces.size(); ++i) + if (!to_delete[i]) + kept.emplace_back(faces[i]); + + faces.swap(kept); + } + struct Horizon + { + std::pmr::vector to_delete; + std::pmr::vector boundary; + }; + + static Horizon mark_visible_and_collect_horizon(const std::pmr::vector& faces, const VectorType& p) + { + auto* mr = faces.get_allocator().resource(); + + Horizon horizon{std::pmr::vector(faces.size(), false, mr), std::pmr::vector(mr)}; + horizon.boundary.reserve(faces.size()); + + for (std::size_t i = 0; i < faces.size(); ++i) + if (visible_from(faces[i], p)) + { + const auto& rf = faces[i]; + horizon.to_delete[i] = true; + add_edge_boundary(horizon.boundary, rf.i0, rf.i1); + add_edge_boundary(horizon.boundary, rf.i1, rf.i2); + add_edge_boundary(horizon.boundary, rf.i2, rf.i0); + } + + return horizon; + } }; } // namespace omath::collision diff --git a/include/omath/projection/camera.hpp b/include/omath/projection/camera.hpp index 35bb1f0..291f189 100644 --- a/include/omath/projection/camera.hpp +++ b/include/omath/projection/camera.hpp @@ -5,6 +5,7 @@ #pragma once #include "omath/linear_algebra/mat.hpp" +#include "omath/linear_algebra/triangle.hpp" #include "omath/linear_algebra/vector3.hpp" #include "omath/projection/error_codes.hpp" #include @@ -175,6 +176,53 @@ namespace omath::projection std::unreachable(); } + [[nodiscard]] bool is_culled_by_frustum(const Triangle>& triangle) const noexcept + { + // Transform to clip space (before perspective divide) + auto to_clip = [this](const Vector3& point) + { + auto clip = get_view_projection_matrix() + * mat_column_from_vector(point); + return std::array{ + clip.at(0, 0), // x + clip.at(1, 0), // y + clip.at(2, 0), // z + clip.at(3, 0) // w + }; + }; + + const auto c0 = to_clip(triangle.m_vertex1); + const auto c1 = to_clip(triangle.m_vertex2); + const auto c2 = to_clip(triangle.m_vertex3); + + // If all vertices are behind the camera (w <= 0), trivially reject + if (c0[3] <= 0.f && c1[3] <= 0.f && c2[3] <= 0.f) + return true; + + // Helper: all three vertices outside the same clip plane + auto all_outside_plane = [](const int axis, const std::array& a, const std::array& b, + const std::array& c, const bool positive_side) + { + if (positive_side) + return a[axis] > a[3] && b[axis] > b[3] && c[axis] > c[3]; + return a[axis] < -a[3] && b[axis] < -b[3] && c[axis] < -c[3]; + }; + + // Clip volume in clip space (OpenGL-style): + // -w <= x <= w + // -w <= y <= w + // -w <= z <= w + + for (int i = 0; i < 3; i++) + { + if (all_outside_plane(i, c0, c1, c2, false)) + return true; // x < -w (left) + if (all_outside_plane(i, c0, c1, c2, true)) + return true; // x > w (right) + } + return false; + } + [[nodiscard]] std::expected, Error> world_to_view_port(const Vector3& world_position) const noexcept { diff --git a/tests/general/unit_test_epa.cpp b/tests/general/unit_test_epa.cpp index 3e8d0ad..97838d3 100644 --- a/tests/general/unit_test_epa.cpp +++ b/tests/general/unit_test_epa.cpp @@ -45,7 +45,7 @@ TEST(UnitTestEpa, TestCollisionTrue) auto pool = std::make_shared(1024); params.max_iterations = 64; params.tolerance = 1e-4f; - auto epa = EPA::solve(A, B, gjk.simplex, params, pool); + auto epa = EPA::solve(A, B, gjk.simplex, params, *pool); ASSERT_TRUE(epa.has_value()) << "EPA should converge"; // Normal is unit @@ -119,7 +119,7 @@ TEST(UnitTestEpa, TestCollisionTrue2) params.max_iterations = 64; params.tolerance = 1e-4f; auto pool = std::make_shared(1024); - auto epa = EPA::solve(A, B, gjk.simplex, params, pool); + auto epa = EPA::solve(A, B, gjk.simplex, params, *pool); ASSERT_TRUE(epa.has_value()) << "EPA should converge"; // Normal is unit-length