Skip to content
Merged

Temp #123

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
23 changes: 6 additions & 17 deletions .github/workflows/cmake-multi-platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true


##############################################################################
# 1) Linux – Clang / Ninja
##############################################################################
Expand All @@ -32,8 +31,8 @@ jobs:
sudo apt-get install -y git build-essential cmake ninja-build \
zip unzip curl pkg-config ca-certificates \
clang-21 lld-21 libc++-21-dev libc++abi-21-dev
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-21 100
- name: Linux (Clang) (x86-linux)
triplet: x86-linux
Expand All @@ -56,8 +55,8 @@ jobs:
# Install GCC 15 with multilib support
sudo apt-get install -y gcc-15-multilib g++-15-multilib
# Set up alternatives for Clang
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-21 100
# Set up alternatives for GCC
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 100
Expand All @@ -73,8 +72,8 @@ jobs:
sudo apt-get install -y git build-essential cmake ninja-build \
zip unzip curl pkg-config ca-certificates \
clang-21 lld-21 libc++-21-dev libc++abi-21-dev
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-21 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-21 100
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-21 100
fail-fast: false
env:
Expand All @@ -84,16 +83,6 @@ jobs:
shell: bash
run: ${{ matrix.install_cmd }}

- name: Verify compiler versions
shell: bash
run: |
echo "=== Clang ==="
clang --version
clang++ --version
echo "=== GCC ==="
gcc --version || true
g++ --version || true

- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
/out
*.DS_Store
/extlibs/vcpkg
.idea/workspace.xml
.idea/workspace.xml
/build/
*.gcov
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ if (OMATH_USE_AVX2)
endif ()
endif ()

if(EMSCRIPTEN)
target_compile_options(${PROJECT_NAME} PUBLIC -fexceptions)
target_link_options(${PROJECT_NAME} PUBLIC -fexceptions)
endif()

target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)

if (OMATH_BUILD_TESTS)
Expand All @@ -150,6 +155,7 @@ if (OMATH_BUILD_EXAMPLES)
add_subdirectory(examples)
endif ()


if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND OMATH_THREAT_WARNING_AS_ERROR)
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX)
elseif (OMATH_THREAT_WARNING_AS_ERROR)
Expand Down Expand Up @@ -188,7 +194,6 @@ install(EXPORT ${PROJECT_NAME}Targets
DESTINATION lib/cmake/${PROJECT_NAME} COMPONENT ${PROJECT_NAME}
)


# Generate the omathConfigVersion.cmake file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/omathConfigVersion.cmake"
Expand Down
4 changes: 0 additions & 4 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,6 @@
"name": "linux-base",
"hidden": true,
"inherits": "base",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang-21",
"CMAKE_CXX_COMPILER": "clang++-21"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
Expand Down
4 changes: 2 additions & 2 deletions include/omath/utility/color.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ template<>
struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp)
{
[[nodiscard]]
static constexpr auto parse(std::format_parse_context& ctx)
static constexpr auto parse(const std::format_parse_context& ctx)
{
return ctx.begin();
}
Expand All @@ -207,6 +207,6 @@ struct std::formatter<omath::Color> // NOLINT(*-dcl58-cpp)
if constexpr (std::is_same_v<typename FormatContext::char_type, char8_t>)
return std::format_to(ctx.out(), u8"{}", col.to_u8string());

return std::unreachable();
std::unreachable();
}
};
8 changes: 6 additions & 2 deletions include/omath/utility/pattern_scan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ namespace omath

const auto whole_range_size = static_cast<std::ptrdiff_t>(std::distance(begin, end));

const std::ptrdiff_t scan_size = whole_range_size - static_cast<std::ptrdiff_t>(pattern.size());
const auto pattern_size = static_cast<std::ptrdiff_t>(parsed_pattern->size());
const std::ptrdiff_t scan_size = whole_range_size - pattern_size;

for (std::ptrdiff_t i = 0; i < scan_size; i++)
if (scan_size < 0)
return end;

for (std::ptrdiff_t i = 0; i <= scan_size; i++)
{
bool found = true;

Expand Down
4 changes: 1 addition & 3 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON)



if (TARGET gtest) # GTest is being linked as submodule
target_link_libraries(${PROJECT_NAME} PRIVATE gtest gtest_main omath::omath)
else() # GTest is being linked as vcpkg package
Expand All @@ -24,6 +22,6 @@ else() # GTest is being linked as vcpkg package
endif()

# Skip test discovery for Android/iOS builds or when cross-compiling - binaries cannot run on host
if (NOT (ANDROID OR IOS))
if (NOT (ANDROID OR IOS OR EMSCRIPTEN))
gtest_discover_tests(${PROJECT_NAME})
endif()
123 changes: 120 additions & 3 deletions tests/general/unit_test_a_star.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,125 @@
//
// Created by Vlad on 18.08.2024.
//
// Extra unit tests for the project's A* implementation
#include <gtest/gtest.h>
#include <omath/pathfinding/a_star.hpp>
#include <omath/pathfinding/navigation_mesh.hpp>
#include <array>
#include <utility>

using namespace omath;
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};
nav.m_vertex_map[v1] = {v2};
nav.m_vertex_map[v2] = {v1};

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};
nav.m_vertex_map[v] = {};

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};
// both vertices present but no connections
nav.m_vertex_map[left] = {};
nav.m_vertex_map[right] = {};

auto path = Astar::find_path(left, right, nav);
// disconnected vertices -> empty result
EXPECT_TRUE(path.empty());
}

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}; };
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)
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)
{
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);
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};
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);
// 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.
ASSERT_EQ(path.size(), 1u);
EXPECT_EQ(path.front(), v2);
}

TEST(AstarTests, NoPathWhenDisconnected)
{
NavigationMesh nav;
Vector3<float> v1{0.f,0.f,0.f};
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);
// 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.
ASSERT_EQ(path.size(), 1u);
EXPECT_EQ(path.front(), v1);
}

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

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

TEST(unit_test_a_star, finding_right_path)
{
Expand Down
89 changes: 89 additions & 0 deletions tests/general/unit_test_collision_extra.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Extra collision tests: Simplex, MeshCollider, EPA
#include <gtest/gtest.h>
#include <omath/collision/simplex.hpp>
#include <omath/collision/mesh_collider.hpp>
#include <omath/collision/epa_algorithm.hpp>
#include <omath/engines/source_engine/collider.hpp>

using namespace omath;
using namespace omath::collision;

TEST(CollisionExtra, SimplexLineHandle)
{
Simplex<Vector3<float>> s;
s = { Vector3<float>{1.f,0.f,0.f}, Vector3<float>{2.f,0.f,0.f} };
Vector3<float> dir{0,0,0};
EXPECT_FALSE(s.handle(dir));
// direction should not be zero
EXPECT_GT(dir.length_sqr(), 0.0f);
}

TEST(CollisionExtra, SimplexTriangleHandle)
{
Simplex<Vector3<float>> s;
s = { Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}, Vector3<float>{0.f,0.f,1.f} };
Vector3<float> dir{0,0,0};
EXPECT_FALSE(s.handle(dir));
EXPECT_GT(dir.length_sqr(), 0.0f);
}

TEST(CollisionExtra, SimplexTetrahedronInside)
{
Simplex<Vector3<float>> s;
// tetra that surrounds origin roughly
s = { Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f}, Vector3<float>{0.f,0.f,1.f}, Vector3<float>{-1.f,-1.f,-1.f} };
Vector3<float> dir{0,0,0};
// if origin inside, handle returns true
const bool inside = s.handle(dir);
EXPECT_TRUE(inside);
}

TEST(CollisionExtra, MeshColliderOriginAndFurthest)
{
omath::source_engine::Mesh mesh = {
std::vector<omath::primitives::Vertex<>>{
{ { 1.f, 1.f, 1.f }, {}, {} },
{ {-1.f, -1.f, -1.f }, {}, {} }
},
{}
};
mesh.set_origin({0, 2, 0});
omath::source_engine::MeshCollider collider(mesh);

EXPECT_EQ(collider.get_origin(), omath::Vector3<float>(0,2,0));
collider.set_origin({1,2,3});
EXPECT_EQ(collider.get_origin(), omath::Vector3<float>(1,2,3));

const auto v = collider.find_abs_furthest_vertex_position({1.f,0.f,0.f});
// the original vertex at (1,1,1) translated by origin (1,2,3) becomes (2,3,4)
EXPECT_EQ(v, omath::Vector3<float>(2.f,3.f,4.f));
}

TEST(CollisionExtra, EPAConvergesOnSimpleCase)
{
// Build two simple colliders using simple meshes that overlap
omath::source_engine::Mesh meshA = {
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::MeshCollider A(meshA);
omath::source_engine::MeshCollider B(meshB);

// 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);
// 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())
{
auto r = *res;
EXPECT_TRUE(std::isfinite(r.depth));
EXPECT_GT(r.normal.length_sqr(), 0.0f);
}
}
Loading
Loading