Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .idea/omath.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/example_glfw3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ int main()
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

const int SCR_WIDTH = 800;
const int SCR_HEIGHT = 600;
constexpr int SCR_WIDTH = 800;
constexpr int SCR_HEIGHT = 600;

GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "omath cube + camera (GLEW)", nullptr, nullptr);
if (!window)
Expand Down
67 changes: 35 additions & 32 deletions tests/general/unit_test_a_star.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Extra unit tests for the project's A* implementation
#include <array>
#include <gtest/gtest.h>
#include <omath/pathfinding/a_star.hpp>
#include <omath/pathfinding/navigation_mesh.hpp>
#include <array>
#include <utility>

using namespace omath;
Expand All @@ -11,37 +11,37 @@ using namespace omath::pathfinding;
TEST(AStarExtra, TrivialNeighbor)
{
NavigationMesh nav;
Vector3<float> v1{0.f,0.f,0.f};
Vector3<float> v2{1.f,0.f,0.f};
Vector3<float> v1{0.f, 0.f, 0.f};
Vector3<float> v2{1.f, 0.f, 0.f};
nav.m_vertex_map[v1] = {v2};
nav.m_vertex_map[v2] = {v1};

auto path = Astar::find_path(v1, v2, nav);
const auto path = Astar::find_path(v1, v2, nav);
ASSERT_EQ(path.size(), 1u);
EXPECT_EQ(path.front(), v2);
}

TEST(AStarExtra, StartEqualsGoal)
{
NavigationMesh nav;
Vector3<float> v{1.f,1.f,0.f};
constexpr Vector3<float> v{1.f, 1.f, 0.f};
nav.m_vertex_map[v] = {};

auto path = Astar::find_path(v, v, nav);
const auto path = Astar::find_path(v, v, nav);
ASSERT_EQ(path.size(), 1u);
EXPECT_EQ(path.front(), v);
}

TEST(AStarExtra, BlockedNoPathBetweenTwoVertices)
{
NavigationMesh nav;
Vector3<float> left{0.f,0.f,0.f};
Vector3<float> right{2.f,0.f,0.f};
constexpr Vector3<float> left{0.f, 0.f, 0.f};
constexpr Vector3<float> right{2.f, 0.f, 0.f};
// both vertices present but no connections
nav.m_vertex_map[left] = {};
nav.m_vertex_map[right] = {};

auto path = Astar::find_path(left, right, nav);
const auto path = Astar::find_path(left, right, nav);
// disconnected vertices -> empty result
EXPECT_TRUE(path.empty());
}
Expand All @@ -50,44 +50,47 @@ TEST(AStarExtra, LongerPathAvoidsBlock)
{
NavigationMesh nav;
// build 3x3 grid of vertices, block center (1,1)
auto idx = [&](int x, int y){ return Vector3<float>{static_cast<float>(x), static_cast<float>(y), 0.f}; };
auto idx = [&](const int x, const int y)
{ return Vector3<float>{static_cast<float>(x), static_cast<float>(y), 0.f}; };
for (int y = 0; y < 3; ++y)
{
for (int x = 0; x < 3; ++x)
{
Vector3<float> v = idx(x,y);
if (x==1 && y==1) continue; // center is omitted (blocked)
Vector3<float> v = idx(x, y);
if (x == 1 && y == 1)
continue; // center is omitted (blocked)
std::vector<Vector3<float>> neigh;
const std::array<std::pair<int,int>,4> offs{{{1,0},{-1,0},{0,1},{0,-1}}};
for (auto [dx,dy]: offs)
constexpr std::array<std::pair<int, int>, 4> offs{{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}};
for (auto [dx, dy] : offs)
{
int nx = x + dx, ny = y + dy;
if (nx < 0 || nx >= 3 || ny < 0 || ny >= 3) continue;
if (nx==1 && ny==1) continue; // neighbor is the blocked center
neigh.push_back(idx(nx,ny));
const int nx = x + dx, ny = y + dy;
if (nx < 0 || nx >= 3 || ny < 0 || ny >= 3)
continue;
if (nx == 1 && ny == 1)
continue; // neighbor is the blocked center
neigh.push_back(idx(nx, ny));
}
nav.m_vertex_map[v] = neigh;
}
}

Vector3<float> start = idx(0,1);
Vector3<float> goal = idx(2,1);
auto path = Astar::find_path(start, goal, nav);
constexpr Vector3<float> start = idx(0, 1);
constexpr Vector3<float> goal = idx(2, 1);
const auto path = Astar::find_path(start, goal, nav);
ASSERT_FALSE(path.empty());
EXPECT_EQ(path.front(), goal); // Astar convention: single-element or endpoint present
}


TEST(AstarTests, TrivialDirectNeighborPath)
{
NavigationMesh nav;
// create two vertices directly connected
Vector3<float> v1{0.f,0.f,0.f};
Vector3<float> v2{1.f,0.f,0.f};
Vector3<float> v1{0.f, 0.f, 0.f};
Vector3<float> v2{1.f, 0.f, 0.f};
nav.m_vertex_map.emplace(v1, std::vector<Vector3<float>>{v2});
nav.m_vertex_map.emplace(v2, std::vector<Vector3<float>>{v1});

auto path = Astar::find_path(v1, v2, nav);
const auto path = Astar::find_path(v1, v2, nav);
// Current A* implementation returns the end vertex as the reconstructed
// path (single-element) in the simple neighbor scenario. Assert that the
// endpoint is present and reachable.
Expand All @@ -98,12 +101,12 @@ TEST(AstarTests, TrivialDirectNeighborPath)
TEST(AstarTests, NoPathWhenDisconnected)
{
NavigationMesh nav;
Vector3<float> v1{0.f,0.f,0.f};
Vector3<float> v2{10.f,0.f,0.f};
Vector3<float> v1{0.f, 0.f, 0.f};
constexpr Vector3<float> v2{10.f, 0.f, 0.f};
// nav has only v1
nav.m_vertex_map.emplace(v1, std::vector<Vector3<float>>{});

auto path = Astar::find_path(v1, v2, nav);
const auto path = Astar::find_path(v1, v2, nav);
// When the nav mesh contains only the start vertex, the closest
// vertex for both start and end will be the same vertex. In that
// case Astar returns a single-element path with the start vertex.
Expand All @@ -113,11 +116,11 @@ TEST(AstarTests, NoPathWhenDisconnected)

TEST(AstarTests, EmptyNavReturnsNoPath)
{
NavigationMesh nav;
Vector3<float> v1{0.f,0.f,0.f};
Vector3<float> v2{1.f,0.f,0.f};
const NavigationMesh nav;
constexpr Vector3<float> v1{0.f, 0.f, 0.f};
constexpr Vector3<float> v2{1.f, 0.f, 0.f};

auto path = Astar::find_path(v1, v2, nav);
const auto path = Astar::find_path(v1, v2, nav);
EXPECT_TRUE(path.empty());
}

Expand Down
20 changes: 10 additions & 10 deletions tests/general/unit_test_angle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ namespace
{

// Handy aliases (defaults: Type=float, [0,360], Normalized)
using Deg = Angle<float, float(0), float(360), AngleFlags::Normalized>;
using Pitch = Angle<float, float(-90), float(90), AngleFlags::Clamped>;
using Turn = Angle<float, float(-180), float(180), AngleFlags::Normalized>;
using Deg = Angle<float, static_cast<float>(0), static_cast<float>(360), AngleFlags::Normalized>;
using Pitch = Angle<float, static_cast<float>(-90), static_cast<float>(90), AngleFlags::Clamped>;
using Turn = Angle<float, static_cast<float>(-180), static_cast<float>(180), AngleFlags::Normalized>;

constexpr float kEps = 1e-5f;
constexpr float k_eps = 1e-5f;

} // namespace

// ---------- Construction / factories ----------

TEST(UnitTestAngle, DefaultConstructor_IsZeroDegrees)
{
Deg a; // default ctor
constexpr Deg a; // default ctor
EXPECT_FLOAT_EQ(*a, 0.0f);
EXPECT_FLOAT_EQ(a.as_degrees(), 0.0f);
}
Expand All @@ -44,8 +44,8 @@ TEST(UnitTestAngle, FromDegrees_Normalized_WrapsBelowMin)

TEST(UnitTestAngle, FromDegrees_Clamped_ClampsToRange)
{
const Pitch hi = Pitch::from_degrees(100.0f);
const Pitch lo = Pitch::from_degrees(-120.0f);
constexpr Pitch hi = Pitch::from_degrees(100.0f);
constexpr Pitch lo = Pitch::from_degrees(-120.0f);

EXPECT_FLOAT_EQ(hi.as_degrees(), 90.0f);
EXPECT_FLOAT_EQ(lo.as_degrees(), -90.0f);
Expand Down Expand Up @@ -80,8 +80,8 @@ TEST(UnitTestAngle, DereferenceReturnsDegrees)
TEST(UnitTestAngle, SinCosTanCot_BasicCases)
{
const Deg a0 = Deg::from_degrees(0.0f);
EXPECT_NEAR(a0.sin(), 0.0f, kEps);
EXPECT_NEAR(a0.cos(), 1.0f, kEps);
EXPECT_NEAR(a0.sin(), 0.0f, k_eps);
EXPECT_NEAR(a0.cos(), 1.0f, k_eps);
// cot(0) -> cos/sin -> div by 0: allow inf or nan
const float cot0 = a0.cot();
EXPECT_TRUE(std::isinf(cot0) || std::isnan(cot0));
Expand All @@ -99,7 +99,7 @@ TEST(UnitTestAngle, Atan_IsAtanOfRadians)
{
// atan(as_radians). For 0° -> atan(0)=0.
const Deg a0 = Deg::from_degrees(0.0f);
EXPECT_NEAR(a0.atan(), 0.0f, kEps);
EXPECT_NEAR(a0.atan(), 0.0f, k_eps);

const Deg a45 = Deg::from_degrees(45.0f);
// atan(pi/4) ≈ 0.665773...
Expand Down
10 changes: 5 additions & 5 deletions tests/general/unit_test_collision_extra.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,18 @@ TEST(CollisionExtra, EPAConvergesOnSimpleCase)
std::vector<omath::primitives::Vertex<>>{{ {0.f,0.f,0.f}, {}, {} }, { {1.f,0.f,0.f}, {}, {} } },
{}
};
omath::source_engine::Mesh meshB = meshA;
meshB.set_origin({0.5f, 0.f, 0.f}); // translate to overlap
omath::source_engine::Mesh mesh_b = meshA;
mesh_b.set_origin({0.5f, 0.f, 0.f}); // translate to overlap

omath::source_engine::MeshCollider A(meshA);
omath::source_engine::MeshCollider B(meshB);
omath::source_engine::MeshCollider a(meshA);
omath::source_engine::MeshCollider b(mesh_b);

// Create a simplex that approximately contains the origin in Minkowski space
Simplex<omath::Vector3<float>> simplex;
simplex = { omath::Vector3<float>{0.5f,0.f,0.f}, omath::Vector3<float>{-0.5f,0.f,0.f}, omath::Vector3<float>{0.f,0.5f,0.f}, omath::Vector3<float>{0.f,-0.5f,0.f} };

auto pool = std::pmr::monotonic_buffer_resource(1024);
auto res = Epa<omath::source_engine::MeshCollider>::solve(A, B, simplex, {}, pool);
auto res = Epa<omath::source_engine::MeshCollider>::solve(a, b, simplex, {}, pool);
// EPA may or may not converge depending on numerics; ensure it returns optionally
// but if it does, fields should be finite
if (res.has_value())
Expand Down
Loading
Loading