mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-14 07:23:26 +00:00
Merge pull request #111 from orange-cpp/feature/frustum_culling_method
added check method
This commit is contained in:
@@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "windows-debug-vcpkg",
|
"name": "windows-debug-vcpkg",
|
||||||
"displayName": "Debug",
|
"displayName": "Windows Debug Vcpkg",
|
||||||
"inherits": "windows-base-vcpkg",
|
"inherits": "windows-base-vcpkg",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "Debug"
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "windows-release-vcpkg",
|
"name": "windows-release-vcpkg",
|
||||||
"displayName": "Release",
|
"displayName": "Windows Release Vcpkg",
|
||||||
"inherits": "windows-base-vcpkg",
|
"inherits": "windows-base-vcpkg",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "Release",
|
"CMAKE_BUILD_TYPE": "Release",
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "darwin-debug-vcpkg",
|
"name": "darwin-debug-vcpkg",
|
||||||
"displayName": "Darwin Debug",
|
"displayName": "Darwin Debug Vcpkg",
|
||||||
"inherits": "darwin-base-vcpkg",
|
"inherits": "darwin-base-vcpkg",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "Debug"
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
@@ -173,7 +173,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "darwin-release-vcpkg",
|
"name": "darwin-release-vcpkg",
|
||||||
"displayName": "Darwin Release",
|
"displayName": "Darwin Release Vcpkg",
|
||||||
"inherits": "darwin-base-vcpkg",
|
"inherits": "darwin-base-vcpkg",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "Release"
|
"CMAKE_BUILD_TYPE": "Release"
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace omath::primitives
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
Vbo m_vertex_buffer;
|
Vbo m_vertex_buffer;
|
||||||
Ebo m_vertex_array_object;
|
Ebo m_element_buffer_object;
|
||||||
|
|
||||||
Mesh(Vbo vbo, Ebo vao,
|
Mesh(Vbo vbo, Ebo vao,
|
||||||
const VectorType scale =
|
const VectorType scale =
|
||||||
@@ -47,7 +47,7 @@ namespace omath::primitives
|
|||||||
1,
|
1,
|
||||||
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)
|
void set_origin(const VectorType& new_origin)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Created by Vladislav on 06.12.2025.
|
// Created by Vladislav on 06.12.2025.
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <omath/linear_algebra/vector3.hpp>
|
||||||
|
|
||||||
namespace omath::collision
|
namespace omath::collision
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "simplex.hpp"
|
#include "simplex.hpp"
|
||||||
#include <algorithm> // find_if
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -45,29 +45,19 @@ namespace omath::collision
|
|||||||
int max_iterations{64};
|
int max_iterations{64};
|
||||||
float tolerance{1e-4f}; // absolute tolerance on distance growth
|
float tolerance{1e-4f}; // absolute tolerance on distance growth
|
||||||
};
|
};
|
||||||
|
|
||||||
// Precondition: simplex.size()==4 and contains the origin.
|
// Precondition: simplex.size()==4 and contains the origin.
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static std::optional<Result> solve(const ColliderInterfaceType& a, const ColliderInterfaceType& b,
|
static std::optional<Result> solve(const ColliderInterfaceType& a, const ColliderInterfaceType& b,
|
||||||
const Simplex<VectorType>& simplex, const Params params = {},
|
const Simplex<VectorType>& simplex, const Params params = {},
|
||||||
std::shared_ptr<std::pmr::memory_resource> mem_resource = {
|
std::pmr::memory_resource& mem_resource = *std::pmr::get_default_resource())
|
||||||
std::shared_ptr<void>{}, std::pmr::get_default_resource()})
|
|
||||||
{
|
{
|
||||||
// --- Build initial polytope from simplex (4 points) ---
|
// --- Build initial polytope from simplex (4 points) ---
|
||||||
std::pmr::vector<VectorType> vertexes{mem_resource.get()};
|
std::pmr::vector<VectorType> vertexes = build_initial_polytope_from_simplex(simplex, mem_resource);
|
||||||
vertexes.reserve(simplex.size());
|
|
||||||
for (std::size_t i = 0; i < simplex.size(); ++i)
|
|
||||||
vertexes.emplace_back(simplex[i]);
|
|
||||||
|
|
||||||
// Initial tetra faces (windings corrected in make_face)
|
// Initial tetra faces (windings corrected in make_face)
|
||||||
std::pmr::vector<Face> faces{mem_resource.get()};
|
std::pmr::vector<Face> faces = create_initial_tetra_faces(mem_resource, vertexes);
|
||||||
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));
|
|
||||||
|
|
||||||
auto heap = rebuild_heap(faces);
|
auto heap = rebuild_heap(faces, mem_resource);
|
||||||
|
|
||||||
Result out{};
|
Result out{};
|
||||||
|
|
||||||
@@ -80,7 +70,7 @@ namespace omath::collision
|
|||||||
// (We could keep face handles; this is fine for small Ns.)
|
// (We could keep face handles; this is fine for small Ns.)
|
||||||
|
|
||||||
if (const auto top = heap.top(); faces[top.idx].d != top.d)
|
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())
|
if (heap.empty())
|
||||||
break;
|
break;
|
||||||
@@ -109,52 +99,27 @@ namespace omath::collision
|
|||||||
const int new_idx = static_cast<int>(vertexes.size());
|
const int new_idx = static_cast<int>(vertexes.size());
|
||||||
vertexes.emplace_back(p);
|
vertexes.emplace_back(p);
|
||||||
|
|
||||||
// Mark faces visible from p and collect their horizon
|
const auto [to_delete, boundary] = mark_visible_and_collect_horizon(faces, p);
|
||||||
std::pmr::vector<bool> to_delete(faces.size(), false, mem_resource.get()); // uses single bits
|
|
||||||
std::pmr::vector<Edge> boundary{mem_resource.get()};
|
|
||||||
boundary.reserve(faces.size() * 2);
|
|
||||||
|
|
||||||
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
|
erase_marked(faces, to_delete);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove visible faces
|
|
||||||
std::pmr::vector<Face> new_faces{mem_resource.get()};
|
|
||||||
new_faces.reserve(faces.size() + boundary.size());
|
|
||||||
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
|
|
||||||
if (!to_delete[i])
|
|
||||||
new_faces.emplace_back(faces[i]);
|
|
||||||
faces.swap(new_faces);
|
|
||||||
|
|
||||||
// Stitch new faces around the horizon
|
// Stitch new faces around the horizon
|
||||||
for (const auto& e : boundary)
|
for (const auto& e : boundary)
|
||||||
faces.emplace_back(make_face(vertexes, e.a, e.b, new_idx));
|
faces.emplace_back(make_face(vertexes, e.a, e.b, new_idx));
|
||||||
|
|
||||||
// Rebuild heap after topology change
|
// Rebuild heap after topology change
|
||||||
heap = rebuild_heap(faces);
|
heap = rebuild_heap(faces, mem_resource);
|
||||||
|
|
||||||
if (!std::isfinite(vertexes.back().dot(vertexes.back())))
|
if (!std::isfinite(vertexes.back().dot(vertexes.back())))
|
||||||
break; // safety
|
break; // safety
|
||||||
out.iterations = it + 1;
|
out.iterations = it + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: pick closest face as best-effort answer
|
if (faces.empty())
|
||||||
if (!faces.empty())
|
return std::nullopt;
|
||||||
{
|
|
||||||
auto best = faces[0];
|
const auto best = *std::ranges::min_element(faces, [](const auto& first, const auto& second)
|
||||||
for (const auto& f : faces)
|
{ return first.d < second.d; });
|
||||||
if (f.d < best.d)
|
|
||||||
best = f;
|
|
||||||
out.normal = best.n;
|
out.normal = best.n;
|
||||||
out.depth = best.d;
|
out.depth = best.d;
|
||||||
out.num_vertices = static_cast<int>(vertexes.size());
|
out.num_vertices = static_cast<int>(vertexes.size());
|
||||||
@@ -164,8 +129,6 @@ namespace omath::collision
|
|||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Face final
|
struct Face final
|
||||||
@@ -193,15 +156,21 @@ namespace omath::collision
|
|||||||
return lhs.d > rhs.d; // min-heap by distance
|
return lhs.d > rhs.d; // min-heap by distance
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
using Heap = std::priority_queue<HeapItem, std::vector<HeapItem>, HeapCmp>;
|
|
||||||
|
using Heap = std::priority_queue<HeapItem, std::pmr::vector<HeapItem>, HeapCmp>;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
static Heap rebuild_heap(const std::pmr::vector<Face>& faces)
|
static Heap rebuild_heap(const std::pmr::vector<Face>& faces, auto& memory_resource)
|
||||||
{
|
{
|
||||||
Heap h;
|
std::pmr::vector<HeapItem> storage{&memory_resource};
|
||||||
|
storage.reserve(faces.size()); // optional but recommended
|
||||||
|
|
||||||
|
Heap h{HeapCmp{}, std::move(storage)};
|
||||||
|
|
||||||
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
|
for (int i = 0; i < static_cast<int>(faces.size()); ++i)
|
||||||
h.emplace(faces[i].d, i);
|
h.emplace(faces[i].d, i);
|
||||||
return h;
|
|
||||||
|
return h; // allocator is preserved
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
@@ -267,5 +236,67 @@ namespace omath::collision
|
|||||||
return d;
|
return d;
|
||||||
return V{1, 0, 0};
|
return V{1, 0, 0};
|
||||||
}
|
}
|
||||||
|
[[nodiscard]]
|
||||||
|
static std::pmr::vector<Face> create_initial_tetra_faces(std::pmr::memory_resource& mem_resource,
|
||||||
|
const std::pmr::vector<VectorType>& vertexes)
|
||||||
|
{
|
||||||
|
std::pmr::vector<Face> 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<VectorType> build_initial_polytope_from_simplex(const Simplex<VectorType>& simplex,
|
||||||
|
std::pmr::memory_resource& mem_resource)
|
||||||
|
{
|
||||||
|
std::pmr::vector<VectorType> 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<Face>& faces, const std::pmr::vector<bool>& to_delete)
|
||||||
|
{
|
||||||
|
auto* mr = faces.get_allocator().resource(); // keep same resource
|
||||||
|
std::pmr::vector<Face> 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<bool> to_delete;
|
||||||
|
std::pmr::vector<Edge> boundary;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Horizon mark_visible_and_collect_horizon(const std::pmr::vector<Face>& faces, const VectorType& p)
|
||||||
|
{
|
||||||
|
auto* mr = faces.get_allocator().resource();
|
||||||
|
|
||||||
|
Horizon horizon{std::pmr::vector<bool>(faces.size(), false, mr), std::pmr::vector<Edge>(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
|
} // namespace omath::collision
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "omath/linear_algebra/mat.hpp"
|
#include "omath/linear_algebra/mat.hpp"
|
||||||
|
#include "omath/linear_algebra/triangle.hpp"
|
||||||
#include "omath/linear_algebra/vector3.hpp"
|
#include "omath/linear_algebra/vector3.hpp"
|
||||||
#include "omath/projection/error_codes.hpp"
|
#include "omath/projection/error_codes.hpp"
|
||||||
#include <expected>
|
#include <expected>
|
||||||
@@ -175,6 +176,53 @@ namespace omath::projection
|
|||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_culled_by_frustum(const Triangle<Vector3<float>>& triangle) const noexcept
|
||||||
|
{
|
||||||
|
// Transform to clip space (before perspective divide)
|
||||||
|
auto to_clip = [this](const Vector3<float>& point)
|
||||||
|
{
|
||||||
|
auto clip = get_view_projection_matrix()
|
||||||
|
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(point);
|
||||||
|
return std::array<float, 4>{
|
||||||
|
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<float, 4>& a, const std::array<float, 4>& b,
|
||||||
|
const std::array<float, 4>& 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<Vector3<float>, Error>
|
[[nodiscard]] std::expected<Vector3<float>, Error>
|
||||||
world_to_view_port(const Vector3<float>& world_position) const noexcept
|
world_to_view_port(const Vector3<float>& world_position) const noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ TEST(UnitTestEpa, TestCollisionTrue)
|
|||||||
auto pool = std::make_shared<std::pmr::monotonic_buffer_resource>(1024);
|
auto pool = std::make_shared<std::pmr::monotonic_buffer_resource>(1024);
|
||||||
params.max_iterations = 64;
|
params.max_iterations = 64;
|
||||||
params.tolerance = 1e-4f;
|
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";
|
ASSERT_TRUE(epa.has_value()) << "EPA should converge";
|
||||||
|
|
||||||
// Normal is unit
|
// Normal is unit
|
||||||
@@ -119,7 +119,7 @@ TEST(UnitTestEpa, TestCollisionTrue2)
|
|||||||
params.max_iterations = 64;
|
params.max_iterations = 64;
|
||||||
params.tolerance = 1e-4f;
|
params.tolerance = 1e-4f;
|
||||||
auto pool = std::make_shared<std::pmr::monotonic_buffer_resource>(1024);
|
auto pool = std::make_shared<std::pmr::monotonic_buffer_resource>(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";
|
ASSERT_TRUE(epa.has_value()) << "EPA should converge";
|
||||||
|
|
||||||
// Normal is unit-length
|
// Normal is unit-length
|
||||||
|
|||||||
Reference in New Issue
Block a user