Feature/more constexpr (#125)

* added constexpr

* fix

* improved stuff

* added const

* improvement

* fix

* fix

* patch
This commit is contained in:
2025-12-24 02:32:14 +03:00
committed by GitHub
parent 897484bea1
commit d935caf1a4
36 changed files with 543 additions and 399 deletions

2
.idea/omath.iml generated Normal file
View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CIDR" type="CPP_MODULE" version="4" />

View File

@@ -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)

View File

@@ -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;
@@ -16,7 +16,7 @@ TEST(AStarExtra, TrivialNeighbor)
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);
}
@@ -24,10 +24,10 @@ TEST(AStarExtra, TrivialNeighbor)
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);
}
@@ -35,13 +35,13 @@ TEST(AStarExtra, StartEqualsGoal)
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());
}
@@ -50,34 +50,37 @@ 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)
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}}};
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
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;
@@ -87,7 +90,7 @@ TEST(AstarTests, TrivialDirectNeighborPath)
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.
@@ -99,11 +102,11 @@ TEST(AstarTests, NoPathWhenDisconnected)
{
NavigationMesh nav;
Vector3<float> v1{0.f, 0.f, 0.f};
Vector3<float> v2{10.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.
@@ -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());
}

View File

@@ -13,11 +13,11 @@ 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
@@ -25,7 +25,7 @@ namespace
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);
}
@@ -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);
@@ -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));
@@ -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...

View File

@@ -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())

View File

@@ -113,50 +113,50 @@ TEST_F(UnitTestColorGrouped, BlendVector3)
TEST(UnitTestColorGrouped_Extra, SetHueSaturationValue)
{
Color c = Color::red();
auto h1 = c.to_hsv();
const auto h1 = c.to_hsv();
EXPECT_FLOAT_EQ(h1.hue, 0.f);
c.set_hue(0.5f);
auto h2 = c.to_hsv();
const auto h2 = c.to_hsv();
EXPECT_NEAR(h2.hue, 0.5f, 1e-3f);
c = Color::from_hsv(0.25f, 0.8f, 0.6f);
c.set_saturation(0.3f);
auto h3 = c.to_hsv();
const auto h3 = c.to_hsv();
EXPECT_NEAR(h3.saturation, 0.3f, 1e-3f);
c.set_value(1.0f);
auto h4 = c.to_hsv();
const auto h4 = c.to_hsv();
EXPECT_NEAR(h4.value, 1.0f, 1e-3f);
}
TEST(UnitTestColorGrouped_Extra, ToStringVariants)
{
Color c = Color::from_rgba(10, 20, 30, 255);
constexpr Color c = Color::from_rgba(10, 20, 30, 255);
auto s = c.to_string();
EXPECT_NE(s.find("r:"), std::string::npos);
auto ws = c.to_wstring();
const auto ws = c.to_wstring();
EXPECT_FALSE(ws.empty());
auto u8 = c.to_u8string();
const auto u8 = c.to_u8string();
EXPECT_FALSE(u8.empty());
}
TEST(UnitTestColorGrouped_Extra, BlendEdgeCases)
{
Color a = Color::red();
Color b = Color::blue();
auto r0 = a.blend(b, 0.f);
constexpr Color a = Color::red();
constexpr Color b = Color::blue();
constexpr auto r0 = a.blend(b, 0.f);
EXPECT_FLOAT_EQ(r0.x, a.x);
auto r1 = a.blend(b, 1.f);
constexpr auto r1 = a.blend(b, 1.f);
EXPECT_FLOAT_EQ(r1.x, b.x);
}
// From unit_test_color_more.cpp
TEST(UnitTestColorGrouped_More, DefaultCtorIsZero)
{
Color c;
constexpr Color c;
EXPECT_FLOAT_EQ(c.x, 0.0f);
EXPECT_FLOAT_EQ(c.y, 0.0f);
EXPECT_FLOAT_EQ(c.z, 0.0f);
@@ -165,7 +165,7 @@ TEST(UnitTestColorGrouped_More, DefaultCtorIsZero)
TEST(UnitTestColorGrouped_More, FloatCtorAndClampForRGB)
{
Color c(1.2f, -0.5f, 0.5f, 2.0f);
constexpr Color c(1.2f, -0.5f, 0.5f, 2.0f);
EXPECT_FLOAT_EQ(c.x, 1.0f);
EXPECT_FLOAT_EQ(c.y, 0.0f);
EXPECT_FLOAT_EQ(c.z, 0.5f);
@@ -174,7 +174,7 @@ TEST(UnitTestColorGrouped_More, FloatCtorAndClampForRGB)
TEST(UnitTestColorGrouped_More, FromRgbaProducesScaledComponents)
{
Color c = Color::from_rgba(25u, 128u, 230u, 64u);
constexpr Color c = Color::from_rgba(25u, 128u, 230u, 64u);
EXPECT_NEAR(c.x, 25.0f/255.0f, 1e-6f);
EXPECT_NEAR(c.y, 128.0f/255.0f, 1e-6f);
EXPECT_NEAR(c.z, 230.0f/255.0f, 1e-6f);
@@ -183,9 +183,9 @@ TEST(UnitTestColorGrouped_More, FromRgbaProducesScaledComponents)
TEST(UnitTestColorGrouped_More, BlendProducesIntermediate)
{
Color c0(0.0f, 0.0f, 0.0f, 1.0f);
Color c1(1.0f, 1.0f, 1.0f, 0.0f);
Color mid = c0.blend(c1, 0.5f);
constexpr Color c0(0.0f, 0.0f, 0.0f, 1.0f);
constexpr Color c1(1.0f, 1.0f, 1.0f, 0.0f);
constexpr Color mid = c0.blend(c1, 0.5f);
EXPECT_FLOAT_EQ(mid.x, 0.5f);
EXPECT_FLOAT_EQ(mid.y, 0.5f);
EXPECT_FLOAT_EQ(mid.z, 0.5f);
@@ -194,9 +194,9 @@ TEST(UnitTestColorGrouped_More, BlendProducesIntermediate)
TEST(UnitTestColorGrouped_More, HsvRoundTrip)
{
Color red = Color::red();
auto hsv = red.to_hsv();
Color back = Color::from_hsv(hsv);
constexpr Color red = Color::red();
const auto hsv = red.to_hsv();
const Color back = Color::from_hsv(hsv);
EXPECT_NEAR(back.x, 1.0f, 1e-6f);
EXPECT_NEAR(back.y, 0.0f, 1e-6f);
EXPECT_NEAR(back.z, 0.0f, 1e-6f);
@@ -204,7 +204,7 @@ TEST(UnitTestColorGrouped_More, HsvRoundTrip)
TEST(UnitTestColorGrouped_More, ToStringContainsComponents)
{
Color c = Color::from_rgba(10, 20, 30, 40);
constexpr Color c = Color::from_rgba(10, 20, 30, 40);
std::string s = c.to_string();
EXPECT_NE(s.find("r:"), std::string::npos);
EXPECT_NE(s.find("g:"), std::string::npos);
@@ -215,7 +215,7 @@ TEST(UnitTestColorGrouped_More, ToStringContainsComponents)
// From unit_test_color_more2.cpp
TEST(UnitTestColorGrouped_More2, FromRgbaAndToString)
{
auto c = Color::from_rgba(255, 128, 0, 64);
constexpr auto c = Color::from_rgba(255, 128, 0, 64);
const auto s = c.to_string();
EXPECT_NE(s.find("r:255"), std::string::npos);
EXPECT_NE(s.find("g:128"), std::string::npos);
@@ -225,7 +225,7 @@ TEST(UnitTestColorGrouped_More2, FromRgbaAndToString)
TEST(UnitTestColorGrouped_More2, FromHsvCases)
{
const float eps = 1e-5f;
constexpr float eps = 1e-5f;
auto check_hue = [&](float h) {
SCOPED_TRACE(::testing::Message() << "h=" << h);
@@ -257,7 +257,7 @@ TEST(UnitTestColorGrouped_More2, FromHsvCases)
TEST(UnitTestColorGrouped_More2, ToHsvAndSetters)
{
Color c{0.2f, 0.4f, 0.6f, 1.f};
auto hsv = c.to_hsv();
const auto hsv = c.to_hsv();
EXPECT_NEAR(hsv.value, 0.6f, 1e-6f);
c.set_hue(0.0f);
@@ -272,16 +272,16 @@ TEST(UnitTestColorGrouped_More2, ToHsvAndSetters)
TEST(UnitTestColorGrouped_More2, BlendAndStaticColors)
{
Color a = Color::red();
Color b = Color::blue();
auto mid = a.blend(b, 0.5f);
constexpr Color a = Color::red();
constexpr Color b = Color::blue();
constexpr auto mid = a.blend(b, 0.5f);
EXPECT_GT(mid.x, 0.f);
EXPECT_GT(mid.z, 0.f);
auto all_a = a.blend(b, -1.f);
constexpr auto all_a = a.blend(b, -1.f);
EXPECT_NEAR(all_a.x, a.x, 1e-6f);
auto all_b = a.blend(b, 2.f);
constexpr auto all_b = a.blend(b, 2.f);
EXPECT_NEAR(all_b.z, b.z, 1e-6f);
}

View File

@@ -60,7 +60,7 @@ TEST(UnitTestEpa, TestCollisionTrue)
EXPECT_NEAR(epa->normal.z, 0.0f, 1e-3f);
// Try both signs with a tiny margin (avoid grazing contacts)
const float margin = 1.0f + 1e-3f;
constexpr float margin = 1.0f + 1e-3f;
const auto pen = epa->penetration_vector;
Mesh b_plus = b;

View File

@@ -9,7 +9,8 @@ using Vector3f = omath::Vector3<float>;
struct DummyCollider
{
using VectorType = Vector3f;
VectorType find_abs_furthest_vertex_position(const VectorType& dir) const noexcept
[[nodiscard]]
static VectorType find_abs_furthest_vertex_position(const VectorType& dir) noexcept
{
// map direction to a small point so support_point is finite
return Vector3f{dir.x * 0.01f, dir.y * 0.01f, dir.z * 0.01f};
@@ -25,12 +26,13 @@ TEST(EpaInternal, SolveHandlesSmallPolytope)
Simplex s;
s = { Vector3f{0.01f, 0.f, 0.f}, Vector3f{0.f, 0.01f, 0.f}, Vector3f{0.f, 0.f, 0.01f}, Vector3f{-0.01f, -0.01f, -0.01f} };
DummyCollider a, b;
constexpr DummyCollider a;
constexpr DummyCollider b;
EpaDummy::Params params;
params.max_iterations = 16;
params.tolerance = 1e-6f;
auto result = EpaDummy::solve(a, b, s, params);
const auto result = EpaDummy::solve(a, b, s, params);
// Should either return a valid result or gracefully return nullopt
if (result)

View File

@@ -30,12 +30,13 @@ TEST(EpaExtra, DegenerateFaceHandled)
Simplex s;
s = { Vector3f{0.01f, 0.f, 0.f}, Vector3f{0.02f, 0.f, 0.f}, Vector3f{0.03f, 0.f, 0.f}, Vector3f{0.0f, 0.0f, 0.01f} };
DegenerateCollider a, b;
constexpr DegenerateCollider a;
constexpr DegenerateCollider b;
Epa::Params params;
params.max_iterations = 4;
params.tolerance = 1e-6f;
auto result = Epa::solve(a, b, s, params);
const auto result = Epa::solve(a, b, s, params);
// The algorithm should either return a valid result or gracefully exit (not crash)
if (result)

View File

@@ -19,9 +19,9 @@ namespace
// -----------------------------------------------------------------------------
// Constants & helpers
// -----------------------------------------------------------------------------
constexpr float kTol = 1e-5f;
constexpr float k_tol = 1e-5f;
bool VecEqual(const Vec3& a, const Vec3& b, float tol = kTol)
bool vec_equal(const Vec3& a, const Vec3& b, const float tol = k_tol)
{
return std::fabs(a.x - b.x) < tol &&
std::fabs(a.y - b.y) < tol &&
@@ -58,8 +58,8 @@ namespace
TEST_P(CanTraceLineParam, VariousRays)
{
const auto& p = GetParam();
EXPECT_EQ(LineTracer::can_trace_line(p.ray, triangle), p.expected_clear);
const auto& [ray, expected_clear] = GetParam();
EXPECT_EQ(LineTracer::can_trace_line(ray, triangle), expected_clear);
}
INSTANTIATE_TEST_SUITE_P(
@@ -85,8 +85,8 @@ namespace
constexpr Vec3 expected{0.3f, 0.3f, 0.f};
const Vec3 hit = LineTracer::get_ray_hit_point(ray, triangle);
ASSERT_FALSE(VecEqual(hit, ray.end));
EXPECT_TRUE(VecEqual(hit, expected));
ASSERT_FALSE(vec_equal(hit, ray.end));
EXPECT_TRUE(vec_equal(hit, expected));
}
// -----------------------------------------------------------------------------

View File

@@ -8,51 +8,51 @@ using omath::Vector3;
TEST(LineTracerTests, ParallelRayReturnsEnd)
{
// Triangle in XY plane
omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
constexpr omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
omath::collision::Ray ray;
ray.start = Vector3<float>{0.f,0.f,1.f};
ray.end = Vector3<float>{1.f,1.f,2.f}; // direction parallel to plane normal (z) -> but choose parallel to plane? make direction parallel to triangle plane
ray.end = Vector3<float>{1.f,1.f,1.f};
// For a ray parallel to the triangle plane the algorithm should return ray.end
auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end);
EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, tri));
}
TEST(LineTracerTests, MissesTriangleReturnsEnd)
{
omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
constexpr omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
omath::collision::Ray ray;
ray.start = Vector3<float>{2.f,2.f,-1.f};
ray.end = Vector3<float>{2.f,2.f,1.f}; // passes above the triangle area
auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end);
}
TEST(LineTracerTests, HitTriangleReturnsPointInsideSegment)
{
omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {2.f,0.f,0.f}, {0.f,2.f,0.f} };
constexpr omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {2.f,0.f,0.f}, {0.f,2.f,0.f} };
omath::collision::Ray ray;
ray.start = Vector3<float>{0.25f,0.25f,-1.f};
ray.end = Vector3<float>{0.25f,0.25f,1.f};
auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
// Should return a point between start and end (z approximately 0)
EXPECT_NE(hit, ray.end);
EXPECT_NEAR(hit.z, 0.f, 1e-4f);
// t_hit should be between 0 and 1 along the ray direction
auto dir = ray.direction_vector();
const auto dir = ray.direction_vector();
// find t such that start + dir * t == hit (only check z comp for stability)
float t = (hit.z - ray.start.z) / dir.z;
const float t = (hit.z - ray.start.z) / dir.z;
EXPECT_GT(t, 0.f);
EXPECT_LT(t, 1.f);
}
TEST(LineTracerTests, InfiniteLengthEarlyOut)
{
omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
constexpr omath::Triangle<Vector3<float>> tri{ {0.f,0.f,0.f}, {1.f,0.f,0.f}, {0.f,1.f,0.f} };
omath::collision::Ray ray;
ray.start = Vector3<float>{0.25f,0.25f,0.f};
ray.end = Vector3<float>{0.25f,0.25f,1.f};
@@ -60,6 +60,6 @@ TEST(LineTracerTests, InfiniteLengthEarlyOut)
// If t_hit <= epsilon the algorithm should return ray.end when infinite_length is true.
// Using start on the triangle plane should produce t_hit <= epsilon.
auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
const auto hit = omath::collision::LineTracer::get_ray_hit_point(ray, tri);
EXPECT_TRUE(hit == ray.end);
}

View File

@@ -8,17 +8,17 @@ using namespace omath::collision;
TEST(LineTracerExtra, MissParallel)
{
Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false };// parallel above triangle
auto hit = LineTracer::get_ray_hit_point(ray, tri);
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.3f,0.3f,1.f}, {0.3f,0.3f,2.f}, false }; // parallel above triangle
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerExtra, HitCenter)
{
Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
Ray ray{ {0.3f,0.3f,-1.f}, {0.3f,0.3f,1.f}, false };
auto hit = LineTracer::get_ray_hit_point(ray, tri);
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.3f,0.3f,-1.f}, {0.3f,0.3f,1.f}, false };
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
ASSERT_FALSE(hit == ray.end);
EXPECT_NEAR(hit.x, 0.3f, 1e-6f);
EXPECT_NEAR(hit.y, 0.3f, 1e-6f);
@@ -27,9 +27,9 @@ TEST(LineTracerExtra, HitCenter)
TEST(LineTracerExtra, HitOnEdge)
{
Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
Ray ray{ {0.0f,0.0f,1.f}, {0.0f,0.0f,0.f}, false };
auto hit = LineTracer::get_ray_hit_point(ray, tri);
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Ray ray{ {0.0f,0.0f,1.f}, {0.0f,0.0f,0.f}, false };
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
// hitting exact vertex/edge may be considered miss; ensure function handles without crash
if (hit != ray.end)
{
@@ -40,9 +40,9 @@ TEST(LineTracerExtra, HitOnEdge)
TEST(LineTracerExtra, InfiniteRayIgnoredIfBehind)
{
Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
// Ray pointing away but infinite_length true should be ignored
Ray ray{ {0.5f,0.5f,-1.f}, {0.5f,0.5f,-2.f}, true };
auto hit = LineTracer::get_ray_hit_point(ray, tri);
constexpr Ray ray{ {0.5f,0.5f,-1.f}, {0.5f,0.5f,-2.f}, true };
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}

View File

@@ -11,53 +11,53 @@ using Triangle3 = omath::Triangle<Vector3<float>>;
TEST(LineTracerMore, ParallelRayReturnsEnd)
{
// Ray parallel to triangle plane: construct triangle in XY plane and ray along X axis
Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.f,0.f,1.f}; ray.end = {1.f,0.f,1.f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerMore, UOutOfRangeReturnsEnd)
{
// Construct a ray that misses due to u < 0
Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {-1.f,-1.f,-1.f}; ray.end = {-0.5f,-1.f,1.f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerMore, VOutOfRangeReturnsEnd)
{
// Construct ray that has v < 0
Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {2.f,2.f,-1.f}; ray.end = {2.f,2.f,1.f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerMore, THitTooSmallReturnsEnd)
{
Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.f,0.f,0.0000000001f}; ray.end = {0.f,0.f,1.f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerMore, THitGreaterThanOneReturnsEnd)
{
Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
// Choose a ray and compute t_hit locally to assert consistency
Ray ray; ray.start = {0.f,0.f,-1.f}; ray.end = {0.f,0.f,-0.5f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const float k_epsilon = std::numeric_limits<float>::epsilon();
const auto side_a = tri.side_a_vector();
const auto side_b = tri.side_b_vector();
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
constexpr auto side_a = tri.side_a_vector();
constexpr auto side_b = tri.side_b_vector();
const auto ray_dir = ray.direction_vector();
const auto p = ray_dir.cross(side_b);
const auto det = side_a.dot(p);
@@ -82,21 +82,21 @@ TEST(LineTracerMore, THitGreaterThanOneReturnsEnd)
TEST(LineTracerMore, InfiniteLengthWithSmallTHitReturnsEnd)
{
Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Triangle3 tri2(Vector3<float>{0.f,0.f,-1e-8f}, Vector3<float>{1.f,0.f,-1e-8f}, Vector3<float>{0.f,1.f,-1e-8f});
constexpr Triangle3 tri2(Vector3<float>{0.f,0.f,-1e-8f}, Vector3<float>{1.f,0.f,-1e-8f}, Vector3<float>{0.f,1.f,-1e-8f});
Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,1.f}; ray.infinite_length = true;
// Create triangle slightly behind so t_hit <= eps
tri = tri2;
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerMore, SuccessfulHitReturnsPoint)
{
Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
constexpr Triangle3 tri(Vector3<float>{0.f,0.f,0.f}, Vector3<float>{1.f,0.f,0.f}, Vector3<float>{0.f,1.f,0.f});
Ray ray; ray.start = {0.1f,0.1f,-1.f}; ray.end = {0.1f,0.1f,1.f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_NE(hit, ray.end);
// Hit should be on plane z=0 and near x=0.1,y=0.1
EXPECT_NEAR(hit.z, 0.f, 1e-6f);

View File

@@ -10,48 +10,48 @@ using Triangle3 = omath::Triangle<Vector3<float>>;
TEST(LineTracerMore2, UGreaterThanOneReturnsEnd)
{
Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
constexpr Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
// choose ray so barycentric u > 1
Ray ray; ray.start = {2.f, -1.f, -1.f}; ray.end = {2.f, -1.f, 1.f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerMore2, VGreaterThanOneReturnsEnd)
{
Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
constexpr Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
// choose ray so barycentric v > 1
Ray ray; ray.start = {-1.f, 2.f, -1.f}; ray.end = {-1.f, 2.f, 1.f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerMore2, UPlusVGreaterThanOneReturnsEnd)
{
Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
constexpr Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
// Ray aimed so u+v > 1 (outside triangle region)
Ray ray; ray.start = {1.f, 1.f, -1.f}; ray.end = {1.f, 1.f, 1.f};
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
TEST(LineTracerMore2, DirectionVectorNormalizedProducesUnitLength)
{
Ray r; r.start = {0.f,0.f,0.f}; r.end = {0.f,3.f,4.f};
auto dir = r.direction_vector_normalized();
auto len = dir.length();
const auto dir = r.direction_vector_normalized();
const auto len = dir.length();
EXPECT_NEAR(len, 1.f, 1e-6f);
}
TEST(LineTracerMore2, ZeroLengthRayHandled)
{
Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
constexpr Triangle3 tri({0.f,0.f,0.f},{1.f,0.f,0.f},{0.f,1.f,0.f});
Ray ray; ray.start = {0.f,0.f,0.f}; ray.end = {0.f,0.f,0.f};
// Zero-length ray: direction length == 0; algorithm should handle without crash
auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}

View File

@@ -31,16 +31,16 @@ TEST(Vector3ScalarOps, InPlaceScalarOperators)
TEST(Vector4BinaryOps, ElementWiseMulDiv)
{
Vector4<float> a{2.f, 4.f, 6.f, 8.f};
Vector4<float> b{1.f, 2.f, 3.f, 4.f};
constexpr Vector4<float> a{2.f, 4.f, 6.f, 8.f};
constexpr Vector4<float> b{1.f, 2.f, 3.f, 4.f};
auto m = a * b;
constexpr auto m = a * b;
EXPECT_FLOAT_EQ(m.x, 2.f);
EXPECT_FLOAT_EQ(m.y, 8.f);
EXPECT_FLOAT_EQ(m.z, 18.f);
EXPECT_FLOAT_EQ(m.w, 32.f);
auto d = a / b;
constexpr auto d = a / b;
EXPECT_FLOAT_EQ(d.x, 2.f);
EXPECT_FLOAT_EQ(d.y, 2.f);
EXPECT_FLOAT_EQ(d.z, 2.f);

View File

@@ -10,23 +10,21 @@ using namespace omath;
static void make_bad_mat_rows()
{
// wrong number of rows -> should throw inside initializer-list ctor
Mat<2, 2, float> m{{1.f, 2.f}};
(void)m;
[[maybe_unused]] const Mat<2, 2, float> m{{1.f, 2.f}};
}
static void make_bad_mat_cols()
{
// row with wrong number of columns -> should throw
Mat<2, 2, float> m{{1.f, 2.f}, {1.f}};
(void)m;
[[maybe_unused]] const Mat<2, 2, float> m{{1.f, 2.f}, {1.f}};
}
TEST(Vector4Operator, Subtraction)
{
Vector4<float> a{5.f, 6.f, 7.f, 8.f};
Vector4<float> b{1.f, 2.f, 3.f, 4.f};
constexpr Vector4<float> a{5.f, 6.f, 7.f, 8.f};
constexpr Vector4<float> b{1.f, 2.f, 3.f, 4.f};
auto r = a - b;
constexpr auto r = a - b;
EXPECT_FLOAT_EQ(r.x, 4.f);
EXPECT_FLOAT_EQ(r.y, 4.f);
EXPECT_FLOAT_EQ(r.z, 4.f);

View File

@@ -11,12 +11,12 @@ using namespace omath;
TEST(LinearAlgebraExtra, FormatterAndHashVector2)
{
Vector2<float> v{1.0f, 2.0f};
std::string s = std::format("{}", v);
const std::string s = std::format("{}", v);
EXPECT_EQ(s, "[1, 2]");
std::size_t h1 = std::hash<Vector2<float>>{}(v);
std::size_t h2 = std::hash<Vector2<float>>{}(Vector2<float>{1.0f, 2.0f});
std::size_t h3 = std::hash<Vector2<float>>{}(Vector2<float>{2.0f, 3.0f});
const std::size_t h1 = std::hash<Vector2<float>>{}(v);
const std::size_t h2 = std::hash<Vector2<float>>{}(Vector2<float>{1.0f, 2.0f});
const std::size_t h3 = std::hash<Vector2<float>>{}(Vector2<float>{2.0f, 3.0f});
EXPECT_EQ(h1, h2);
EXPECT_NE(h1, h3);
@@ -25,11 +25,11 @@ TEST(LinearAlgebraExtra, FormatterAndHashVector2)
TEST(LinearAlgebraExtra, FormatterAndHashVector3)
{
Vector3<float> v{1.0f, 2.0f, 3.0f};
std::string s = std::format("{}", v);
const std::string s = std::format("{}", v);
EXPECT_EQ(s, "[1, 2, 3]");
std::size_t h1 = std::hash<Vector3<float>>{}(v);
std::size_t h2 = std::hash<Vector3<float>>{}(Vector3<float>{1.0f, 2.0f, 3.0f});
const std::size_t h1 = std::hash<Vector3<float>>{}(v);
const std::size_t h2 = std::hash<Vector3<float>>{}(Vector3<float>{1.0f, 2.0f, 3.0f});
EXPECT_EQ(h1, h2);
// point_to_same_direction
@@ -40,18 +40,18 @@ TEST(LinearAlgebraExtra, FormatterAndHashVector3)
TEST(LinearAlgebraExtra, FormatterAndHashVector4)
{
Vector4<float> v{1.0f, 2.0f, 3.0f, 4.0f};
std::string s = std::format("{}", v);
const std::string s = std::format("{}", v);
EXPECT_EQ(s, "[1, 2, 3, 4]");
std::size_t h1 = std::hash<Vector4<float>>{}(v);
std::size_t h2 = std::hash<Vector4<float>>{}(Vector4<float>{1.0f, 2.0f, 3.0f, 4.0f});
const std::size_t h1 = std::hash<Vector4<float>>{}(v);
const std::size_t h2 = std::hash<Vector4<float>>{}(Vector4<float>{1.0f, 2.0f, 3.0f, 4.0f});
EXPECT_EQ(h1, h2);
}
TEST(LinearAlgebraExtra, MatRawArrayAndOperators)
{
Mat<2,2> m{{1.0f, 2.0f},{3.0f,4.0f}};
auto raw = m.raw_array();
const auto raw = m.raw_array();
EXPECT_EQ(raw.size(), 4);
EXPECT_FLOAT_EQ(raw[0], 1.0f);
EXPECT_FLOAT_EQ(raw[3], 4.0f);

View File

@@ -12,14 +12,14 @@ using namespace omath;
TEST(LinearAlgebraHelpers, Vector3NoInlineHelpersExecute)
{
Vector3<float> a{1.f, 2.f, 3.f};
Vector3<float> b{4.f, 5.f, 6.f};
constexpr Vector3<float> a{1.f, 2.f, 3.f};
constexpr Vector3<float> b{4.f, 5.f, 6.f};
// Execute helpers that were made non-inlined
auto l = a.length();
auto ang = a.angle_between(b);
auto perp = a.is_perpendicular(b);
auto norm = a.normalized();
const auto l = a.length();
const auto ang = a.angle_between(b);
const auto perp = a.is_perpendicular(b);
const auto norm = a.normalized();
(void)l; (void)ang; (void)perp; (void)norm;
SUCCEED();
@@ -27,17 +27,17 @@ TEST(LinearAlgebraHelpers, Vector3NoInlineHelpersExecute)
TEST(LinearAlgebraHelpers, TriangleNoInlineHelpersExecute)
{
Vector3<float> v1{0.f,0.f,0.f};
Vector3<float> v2{3.f,0.f,0.f};
Vector3<float> v3{3.f,4.f,0.f};
constexpr Vector3<float> v1{0.f,0.f,0.f};
constexpr Vector3<float> v2{3.f,0.f,0.f};
constexpr Vector3<float> v3{3.f,4.f,0.f};
Triangle<Vector3<float>> t{v1, v2, v3};
constexpr Triangle<Vector3<float>> t{v1, v2, v3};
auto n = t.calculate_normal();
auto a = t.side_a_length();
auto b = t.side_b_length();
auto h = t.hypot();
auto r = t.is_rectangular();
const auto n = t.calculate_normal();
const auto a = t.side_a_length();
const auto b = t.side_b_length();
const auto h = t.hypot();
const auto r = t.is_rectangular();
(void)n; (void)a; (void)b; (void)h; (void)r;
SUCCEED();
@@ -47,8 +47,8 @@ TEST(LinearAlgebraHelpers, Vector4NoInlineHelpersExecute)
{
Vector4<float> v{1.f,2.f,3.f,4.f};
auto l = v.length();
auto s = v.sum();
const auto l = v.length();
const auto s = v.sum();
v.clamp(-10.f, 10.f);
(void)l; (void)s;

View File

@@ -7,32 +7,32 @@ using namespace omath;
TEST(LinearAlgebraMore, Vector3EdgeCases)
{
Vector3<float> zero{0.f,0.f,0.f};
Vector3<float> v{1.f,0.f,0.f};
constexpr Vector3<float> zero{0.f,0.f,0.f};
constexpr Vector3<float> v{1.f,0.f,0.f};
// angle_between should be unexpected when one vector has zero length
auto angle = zero.angle_between(v);
const auto angle = zero.angle_between(v);
EXPECT_FALSE(static_cast<bool>(angle));
// normalized of zero should return zero
auto nz = zero.normalized();
const auto nz = zero.normalized();
EXPECT_EQ(nz.x, 0.f);
EXPECT_EQ(nz.y, 0.f);
EXPECT_EQ(nz.z, 0.f);
// perpendicular case: x-axis and y-axis
Vector3<float> x{1.f,0.f,0.f};
Vector3<float> y{0.f,1.f,0.f};
constexpr Vector3<float> x{1.f,0.f,0.f};
constexpr Vector3<float> y{0.f,1.f,0.f};
EXPECT_TRUE(x.is_perpendicular(y));
}
TEST(LinearAlgebraMore, TriangleRectangularAndDegenerate)
{
Vector3<float> v1{0.f,0.f,0.f};
Vector3<float> v2{3.f,0.f,0.f};
Vector3<float> v3{3.f,4.f,0.f}; // 3-4-5 triangle, rectangular at v2
constexpr Vector3<float> v1{0.f,0.f,0.f};
constexpr Vector3<float> v2{3.f,0.f,0.f};
constexpr Vector3<float> v3{3.f,4.f,0.f}; // 3-4-5 triangle, rectangular at v2
Triangle<Vector3<float>> t{v1,v2,v3};
constexpr Triangle<Vector3<float>> t{v1,v2,v3};
EXPECT_NEAR(t.side_a_length(), 3.f, 1e-6f);
EXPECT_NEAR(t.side_b_length(), 4.f, 1e-6f);
@@ -40,7 +40,7 @@ TEST(LinearAlgebraMore, TriangleRectangularAndDegenerate)
EXPECT_TRUE(t.is_rectangular());
// Degenerate: all points same
Triangle<Vector3<float>> d{v1,v1,v1};
constexpr Triangle<Vector3<float>> d{v1,v1,v1};
EXPECT_NEAR(d.side_a_length(), 0.f, 1e-6f);
EXPECT_NEAR(d.side_b_length(), 0.f, 1e-6f);
EXPECT_NEAR(d.hypot(), 0.f, 1e-6f);
@@ -49,7 +49,7 @@ TEST(LinearAlgebraMore, TriangleRectangularAndDegenerate)
TEST(LinearAlgebraMore, Vector4ClampAndComparisons)
{
Vector4<float> v{10.f, -20.f, 30.f, -40.f};
auto s = v.sum();
const auto s = v.sum();
EXPECT_NEAR(s, -20.f, 1e-6f);
v.clamp(-10.f, 10.f);
@@ -58,7 +58,7 @@ TEST(LinearAlgebraMore, Vector4ClampAndComparisons)
EXPECT_LE(v.y, 10.f);
EXPECT_GE(v.y, -10.f);
Vector4<float> a{1.f,2.f,3.f,4.f};
Vector4<float> b{2.f,2.f,2.f,2.f};
constexpr Vector4<float> a{1.f,2.f,3.f,4.f};
constexpr Vector4<float> b{2.f,2.f,2.f,2.f};
EXPECT_TRUE(a < b || a > b || a == b); // just exercise comparisons
}

View File

@@ -47,7 +47,7 @@ TEST(LinearAlgebraMore2, Vector4NonInlinedHelpers)
EXPECT_GE(v.x, 0.f);
EXPECT_LE(v.z, 2.5f);
Vector4<float> shorter{0.1f,0.1f,0.1f,0.1f};
constexpr Vector4<float> shorter{0.1f,0.1f,0.1f,0.1f};
EXPECT_TRUE(shorter < v);
EXPECT_FALSE(v < shorter);
}
@@ -59,7 +59,7 @@ TEST(LinearAlgebraMore2, MatNonInlinedAndStringHelpers)
auto maybe_inv = m.inverted();
EXPECT_TRUE(maybe_inv.has_value());
auto inv = maybe_inv.value();
const auto& inv = maybe_inv.value();
// m * inv should be identity (approximately)
auto prod = m * inv;
@@ -72,14 +72,14 @@ TEST(LinearAlgebraMore2, MatNonInlinedAndStringHelpers)
EXPECT_EQ(t.at(0,1), m.at(1,0));
auto raw = m.raw_array();
EXPECT_EQ(raw.size(), size_t(4));
EXPECT_EQ(raw.size(), static_cast<size_t>(4));
auto s = m.to_string();
EXPECT_NE(s.size(), 0u);
auto ws = m.to_wstring();
EXPECT_NE(ws.size(), 0u);
auto u8s = m.to_u8string();
EXPECT_NE(u8s.size(), 0u);
auto u8_s = m.to_u8string();
EXPECT_NE(u8_s.size(), 0u);
// to_screen_mat static helper
auto screen = Mat<4,4,float>::to_screen_mat(800.f, 600.f);

View File

@@ -154,12 +154,12 @@ TEST_F(UnitTestMat, AssignmentOperator_Move)
// Test static methods
TEST_F(UnitTestMat, StaticMethod_ToScreenMat)
{
Mat<4, 4> screenMat = Mat<4, 4>::to_screen_mat(800.0f, 600.0f);
EXPECT_FLOAT_EQ(screenMat.at(0, 0), 400.0f);
EXPECT_FLOAT_EQ(screenMat.at(1, 1), -300.0f);
EXPECT_FLOAT_EQ(screenMat.at(3, 0), 400.0f);
EXPECT_FLOAT_EQ(screenMat.at(3, 1), 300.0f);
EXPECT_FLOAT_EQ(screenMat.at(3, 3), 1.0f);
Mat<4, 4> screen_mat = Mat<4, 4>::to_screen_mat(800.0f, 600.0f);
EXPECT_FLOAT_EQ(screen_mat.at(0, 0), 400.0f);
EXPECT_FLOAT_EQ(screen_mat.at(1, 1), -300.0f);
EXPECT_FLOAT_EQ(screen_mat.at(3, 0), 400.0f);
EXPECT_FLOAT_EQ(screen_mat.at(3, 1), 300.0f);
EXPECT_FLOAT_EQ(screen_mat.at(3, 3), 1.0f);
}
@@ -220,8 +220,8 @@ TEST(UnitTestMatStandalone, Equanity)
constexpr omath::Vector3<float> left_handed = {0, 2, 10};
constexpr omath::Vector3<float> right_handed = {0, 2, -10};
auto proj_left_handed = omath::mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1, 1000);
auto proj_right_handed = omath::mat_perspective_right_handed(90.f, 16.f / 9.f, 0.1, 1000);
const auto proj_left_handed = omath::mat_perspective_left_handed(90.f, 16.f / 9.f, 0.1, 1000);
const auto proj_right_handed = omath::mat_perspective_right_handed(90.f, 16.f / 9.f, 0.1, 1000);
auto ndc_left_handed = proj_left_handed * omath::mat_column_from_vector(left_handed);
auto ndc_right_handed = proj_right_handed * omath::mat_column_from_vector(right_handed);
@@ -233,7 +233,7 @@ TEST(UnitTestMatStandalone, Equanity)
}
TEST(UnitTestMatStandalone, MatPerspectiveLeftHanded)
{
auto perspective_proj = mat_perspective_left_handed(90.f, 16.f/9.f, 0.1f, 1000.f);
const auto perspective_proj = mat_perspective_left_handed(90.f, 16.f/9.f, 0.1f, 1000.f);
auto projected = perspective_proj
* mat_column_from_vector<float>({0, 0, 0.1001});

View File

@@ -16,9 +16,9 @@ TEST(MatCoverageExtra, InitListColumnsMismatchThrows) {
TEST(MatCoverageExtra, DeterminantFallbackIsCallable) {
// Call determinant for 1x1 and 2x2 matrices to cover determinant paths
Mat<1,1> m1{{3.14f}};
const Mat<1,1> m1{{3.14f}};
EXPECT_FLOAT_EQ(m1.determinant(), 3.14f);
Mat<2,2> m2{{{1.0f,2.0f},{3.0f,4.0f}}};
const Mat<2,2> m2{{{1.0f,2.0f},{3.0f,4.0f}}};
EXPECT_FLOAT_EQ(m2.determinant(), -2.0f);
}

View File

@@ -15,7 +15,7 @@ TEST(MatMore, InitListAndMultiply)
TEST(MatMore, Determinant)
{
Mat<2,2,double> m{{{1.0,2.0},{2.0,4.0}}}; // singular
double det = m.determinant();
const Mat<2,2,double> m{{{1.0,2.0},{2.0,4.0}}}; // singular
const double det = m.determinant();
EXPECT_DOUBLE_EQ(det, 0.0);
}

View File

@@ -26,8 +26,8 @@ TEST(NavigationMeshTests, SerializeDeserializeRoundTrip)
TEST(NavigationMeshTests, GetClosestVertexWhenEmpty)
{
NavigationMesh nav;
Vector3<float> p{5.f,5.f,5.f};
auto res = nav.get_closest_vertex(p);
const NavigationMesh nav;
constexpr Vector3<float> p{5.f,5.f,5.f};
const auto res = nav.get_closest_vertex(p);
EXPECT_FALSE(res.has_value());
}

View File

@@ -6,23 +6,26 @@ using namespace omath;
TEST(unit_test_pattern_scan_extra, IteratorScanFound)
{
std::vector<std::byte> buf = {std::byte(0xDE), std::byte(0xAD), std::byte(0xBE), std::byte(0xEF), std::byte(0x00)};
auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "DE AD BE EF");
std::vector<std::byte> buf = {static_cast<std::byte>(0xDE), static_cast<std::byte>(0xAD),
static_cast<std::byte>(0xBE), static_cast<std::byte>(0xEF),
static_cast<std::byte>(0x00)};
const auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "DE AD BE EF");
EXPECT_NE(it, buf.end());
EXPECT_EQ(std::distance(buf.begin(), it), 0);
}
TEST(unit_test_pattern_scan_extra, IteratorScanNotFound)
{
std::vector<std::byte> buf = {std::byte(0x00), std::byte(0x11), std::byte(0x22)};
auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "FF EE DD");
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11),
static_cast<std::byte>(0x22)};
const auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "FF EE DD");
EXPECT_EQ(it, buf.end());
}
TEST(unit_test_pattern_scan_extra, ParseInvalidPattern)
{
// invalid hex token should cause the public scan to return end (no match)
std::vector<std::byte> buf = {std::byte(0x00), std::byte(0x11)};
auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "GG HH");
std::vector<std::byte> buf = {static_cast<std::byte>(0x00), static_cast<std::byte>(0x11)};
const auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "GG HH");
EXPECT_EQ(it, buf.end());
}

View File

@@ -64,7 +64,7 @@ static bool write_minimal_pe_file(const std::string& path, const std::vector<std
f.write(name, 8);
// Write placeholder bytes for the rest of the section header and remember its start
const std::uint32_t section_header_rest = 36u;
constexpr std::uint32_t section_header_rest = 36u;
const std::streampos header_rest_pos = f.tellp();
std::vector<char> placeholder(section_header_rest, 0);
f.write(placeholder.data(), placeholder.size());
@@ -75,7 +75,7 @@ static bool write_minimal_pe_file(const std::string& path, const std::vector<std
// Patch section header fields: virtual_size, virtual_address, size_raw_data, ptr_raw_data
const std::uint32_t virtual_size = static_cast<std::uint32_t>(section_bytes.size());
const std::uint32_t virtual_address = 0x1000u;
constexpr std::uint32_t virtual_address = 0x1000u;
const std::uint32_t size_raw_data = static_cast<std::uint32_t>(section_bytes.size());
const std::uint32_t ptr_raw_data = static_cast<std::uint32_t>(data_pos);
@@ -95,9 +95,9 @@ static bool write_minimal_pe_file(const std::string& path, const std::vector<std
TEST(unit_test_pe_pattern_scan_file, ScanFindsPattern)
{
const std::string path = "./test_minimal_pe.bin";
std::vector<std::uint8_t> bytes = {0x55, 0x8B, 0xEC, 0x90, 0x90}; // pattern at offset 0
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
constexpr std::string_view path = "./test_minimal_pe.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x8B, 0xEC, 0x90, 0x90}; // pattern at offset 0
ASSERT_TRUE(write_minimal_pe_file(path.data(), bytes));
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text");
EXPECT_TRUE(res.has_value());
@@ -105,9 +105,9 @@ TEST(unit_test_pe_pattern_scan_file, ScanFindsPattern)
TEST(unit_test_pe_pattern_scan_file, ScanMissingPattern)
{
const std::string path = "./test_minimal_pe_2.bin";
std::vector<std::uint8_t> bytes = {0x00, 0x01, 0x02, 0x03};
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
constexpr std::string_view path = "./test_minimal_pe_2.bin";
const std::vector<std::uint8_t> bytes = {0x00, 0x01, 0x02, 0x03};
ASSERT_TRUE(write_minimal_pe_file(path.data(), bytes));
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "FF EE DD", ".text");
EXPECT_FALSE(res.has_value());

View File

@@ -11,23 +11,23 @@ static std::vector<std::uint8_t> make_fake_module(std::uint32_t base_of_code,
std::uint32_t size_code,
const std::vector<std::uint8_t>& code_bytes)
{
const std::uint32_t e_lfanew = 0x80;
constexpr std::uint32_t e_lfanew = 0x80;
const std::uint32_t total_size = e_lfanew + 0x200 + size_code + 0x100;
std::vector<std::uint8_t> buf(total_size, 0);
// DOS header: e_magic at 0, e_lfanew at offset 0x3C
buf[0] = 0x4D; buf[1] = 0x5A; // 'M' 'Z' (little-endian 0x5A4D)
std::uint32_t le = e_lfanew;
constexpr std::uint32_t le = e_lfanew;
std::memcpy(buf.data() + 0x3C, &le, sizeof(le));
// NT signature at e_lfanew
const std::uint32_t nt_sig = 0x4550; // 'PE\0\0'
constexpr std::uint32_t nt_sig = 0x4550; // 'PE\0\0'
std::memcpy(buf.data() + e_lfanew, &nt_sig, sizeof(nt_sig));
// FileHeader is 20 bytes: we only need to ensure its size is present; leave zeros
// OptionalHeader magic (optional header begins at e_lfanew + 4 + sizeof(FileHeader) == e_lfanew + 24)
const std::uint16_t opt_magic = 0x020B; // x64
constexpr std::uint16_t opt_magic = 0x020B; // x64
std::memcpy(buf.data() + e_lfanew + 24, &opt_magic, sizeof(opt_magic));
// size_code is at offset 4 inside OptionalHeader -> absolute e_lfanew + 28
@@ -45,25 +45,25 @@ static std::vector<std::uint8_t> make_fake_module(std::uint32_t base_of_code,
TEST(PePatternScanLoaded, FindsPatternAtBase)
{
std::vector<std::uint8_t> code = {0x90, 0x01, 0x02, 0x03, 0x04};
const std::vector<std::uint8_t> code = {0x90, 0x01, 0x02, 0x03, 0x04};
auto buf = make_fake_module(0x200, static_cast<std::uint32_t>(code.size()), code);
auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "90 01 02");
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "90 01 02");
ASSERT_TRUE(res.has_value());
// address should point somewhere in our buffer; check offset
uintptr_t addr = res.value();
uintptr_t base = reinterpret_cast<uintptr_t>(buf.data());
const uintptr_t addr = res.value();
const uintptr_t base = reinterpret_cast<uintptr_t>(buf.data());
EXPECT_EQ(addr - base, 0x200u);
}
TEST(PePatternScanLoaded, WildcardMatches)
{
std::vector<std::uint8_t> code = {0xDE, 0xAD, 0xBE, 0xEF};
const std::vector<std::uint8_t> code = {0xDE, 0xAD, 0xBE, 0xEF};
auto buf = make_fake_module(0x300, static_cast<std::uint32_t>(code.size()), code);
auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE ?? BE");
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE ?? BE");
ASSERT_TRUE(res.has_value());
uintptr_t addr = res.value();
uintptr_t base = reinterpret_cast<uintptr_t>(buf.data());
const uintptr_t addr = res.value();
const uintptr_t base = reinterpret_cast<uintptr_t>(buf.data());
EXPECT_EQ(addr - base, 0x300u);
}

View File

@@ -1,73 +1,111 @@
// Additional tests for PePatternScanner to exercise edge cases and loaded-module scanning
#include <gtest/gtest.h>
#include <omath/utility/pe_pattern_scan.hpp>
#include <fstream>
#include <vector>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <gtest/gtest.h>
#include <omath/utility/pe_pattern_scan.hpp>
#include <vector>
using namespace omath;
static bool write_bytes(const std::string& path, const std::vector<std::uint8_t>& data)
{
std::ofstream f(path, std::ios::binary);
if (!f.is_open()) return false;
if (!f.is_open())
return false;
f.write(reinterpret_cast<const char*>(data.data()), data.size());
return true;
}
TEST(unit_test_pe_pattern_scan_more, InvalidDosHeader)
{
const std::string path = "./test_bad_dos.bin";
constexpr std::string_view path = "./test_bad_dos.bin";
std::vector<std::uint8_t> data(128, 0);
// write wrong magic
data[0] = 'N'; data[1] = 'Z';
ASSERT_TRUE(write_bytes(path, data));
data[0] = 'N';
data[1] = 'Z';
ASSERT_TRUE(write_bytes(path.data(), data));
auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text");
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text");
EXPECT_FALSE(res.has_value());
}
TEST(unit_test_pe_pattern_scan_more, InvalidNtSignature)
{
const std::string path = "./test_bad_nt.bin";
constexpr std::string_view path = "./test_bad_nt.bin";
std::vector<std::uint8_t> data(256, 0);
// valid DOS header
data[0] = 'M'; data[1] = 'Z';
data[0] = 'M';
data[1] = 'Z';
// point e_lfanew to 0x80
std::uint32_t e_lfanew = 0x80;
constexpr std::uint32_t e_lfanew = 0x80;
std::memcpy(data.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
// write garbage at e_lfanew (not 'PE\0\0')
data[e_lfanew + 0] = 'X'; data[e_lfanew + 1] = 'Y'; data[e_lfanew + 2] = 'Z'; data[e_lfanew + 3] = 'W';
ASSERT_TRUE(write_bytes(path, data));
data[e_lfanew + 0] = 'X';
data[e_lfanew + 1] = 'Y';
data[e_lfanew + 2] = 'Z';
data[e_lfanew + 3] = 'W';
ASSERT_TRUE(write_bytes(path.data(), data));
auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text");
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "55 8B EC", ".text");
EXPECT_FALSE(res.has_value());
}
TEST(unit_test_pe_pattern_scan_more, SectionNotFound)
{
// reuse minimal writer but with section named .data and search .text
const std::string path = "./test_section_not_found.bin";
std::ofstream f(path, std::ios::binary);
constexpr std::string_view path = "./test_section_not_found.bin";
std::ofstream f(path.data(), std::ios::binary);
ASSERT_TRUE(f.is_open());
// DOS
std::vector<std::uint8_t> dos(64, 0); dos[0]='M'; dos[1]='Z'; std::uint32_t e_lfanew=0x80; std::memcpy(dos.data()+0x3C,&e_lfanew,sizeof(e_lfanew)); f.write(reinterpret_cast<char*>(dos.data()), dos.size());
std::vector<std::uint8_t> dos(64, 0);
dos[0] = 'M';
dos[1] = 'Z';
std::uint32_t e_lfanew = 0x80;
std::memcpy(dos.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
f.write(reinterpret_cast<char*>(dos.data()), dos.size());
// pad
std::vector<char> pad(e_lfanew - static_cast<std::uint32_t>(f.tellp()), 0); f.write(pad.data(), pad.size());
std::vector<char> pad(e_lfanew - static_cast<std::uint32_t>(f.tellp()), 0);
f.write(pad.data(), pad.size());
// NT sig
f.put('P'); f.put('E'); f.put('\0'); f.put('\0');
f.put('P');
f.put('E');
f.put('\0');
f.put('\0');
// FileHeader minimal
std::uint16_t machine=0x8664; std::uint16_t num_sections=1; std::uint32_t z=0; std::uint32_t z2=0; std::uint32_t numsym=0; std::uint16_t size_opt=0xF0; std::uint16_t ch=0;
f.write(reinterpret_cast<char*>(&machine), sizeof(machine)); f.write(reinterpret_cast<char*>(&num_sections), sizeof(num_sections)); f.write(reinterpret_cast<char*>(&z), sizeof(z)); f.write(reinterpret_cast<char*>(&z2), sizeof(z2)); f.write(reinterpret_cast<char*>(&numsym), sizeof(numsym)); f.write(reinterpret_cast<char*>(&size_opt), sizeof(size_opt)); f.write(reinterpret_cast<char*>(&ch), sizeof(ch));
std::uint16_t machine = 0x8664;
std::uint16_t num_sections = 1;
std::uint32_t z = 0;
std::uint32_t z2 = 0;
std::uint32_t numsym = 0;
std::uint16_t size_opt = 0xF0;
std::uint16_t ch = 0;
f.write(reinterpret_cast<char*>(&machine), sizeof(machine));
f.write(reinterpret_cast<char*>(&num_sections), sizeof(num_sections));
f.write(reinterpret_cast<char*>(&z), sizeof(z));
f.write(reinterpret_cast<char*>(&z2), sizeof(z2));
f.write(reinterpret_cast<char*>(&numsym), sizeof(numsym));
f.write(reinterpret_cast<char*>(&size_opt), sizeof(size_opt));
f.write(reinterpret_cast<char*>(&ch), sizeof(ch));
// Optional header magic
std::uint16_t magic = 0x20b; f.write(reinterpret_cast<char*>(&magic), sizeof(magic)); std::vector<std::uint8_t> opt(size_opt - sizeof(magic),0); f.write(reinterpret_cast<char*>(opt.data()), opt.size());
std::uint16_t magic = 0x20b;
f.write(reinterpret_cast<char*>(&magic), sizeof(magic));
std::vector<std::uint8_t> opt(size_opt - sizeof(magic), 0);
f.write(reinterpret_cast<char*>(opt.data()), opt.size());
// Section header named .data
char name[8] = {'.','d','a','t','a',0,0,0}; f.write(name,8);
std::uint32_t vs=4, va=0x1000, srd=4, prd=0x200; f.write(reinterpret_cast<char*>(&vs),4); f.write(reinterpret_cast<char*>(&va),4); f.write(reinterpret_cast<char*>(&srd),4); f.write(reinterpret_cast<char*>(&prd),4);
std::vector<char> rest(16,0); f.write(rest.data(), rest.size());
char name[8] = {'.', 'd', 'a', 't', 'a', 0, 0, 0};
f.write(name, 8);
std::uint32_t vs = 4, va = 0x1000, srd = 4, prd = 0x200;
f.write(reinterpret_cast<char*>(&vs), 4);
f.write(reinterpret_cast<char*>(&va), 4);
f.write(reinterpret_cast<char*>(&srd), 4);
f.write(reinterpret_cast<char*>(&prd), 4);
std::vector<char> rest(16, 0);
f.write(rest.data(), rest.size());
// section bytes
std::vector<std::uint8_t> sec={0x00,0x01,0x02,0x03}; f.write(reinterpret_cast<char*>(sec.data()), sec.size()); f.close();
std::vector<std::uint8_t> sec = {0x00, 0x01, 0x02, 0x03};
f.write(reinterpret_cast<char*>(sec.data()), sec.size());
f.close();
auto res = PePatternScanner::scan_for_pattern_in_file(path, "00 01", ".text");
EXPECT_FALSE(res.has_value());
@@ -77,24 +115,76 @@ TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds)
{
// Create an in-memory buffer that mimics loaded module layout
// Define local header structs matching those in source
struct DosHeader { std::uint16_t e_magic; std::uint16_t e_cblp; std::uint16_t e_cp; std::uint16_t e_crlc; std::uint16_t e_cparhdr; std::uint16_t e_minalloc; std::uint16_t e_maxalloc; std::uint16_t e_ss; std::uint16_t e_sp; std::uint16_t e_csum; std::uint16_t e_ip; std::uint16_t e_cs; std::uint16_t e_lfarlc; std::uint16_t e_ovno; std::uint16_t e_res[4]; std::uint16_t e_oemid; std::uint16_t e_oeminfo; std::uint16_t e_res2[10]; std::uint32_t e_lfanew; };
struct FileHeader { std::uint16_t machine; std::uint16_t num_sections; std::uint32_t timedate_stamp; std::uint32_t ptr_symbols; std::uint32_t num_symbols; std::uint16_t size_optional_header; std::uint16_t characteristics; };
struct OptionalHeaderX64 { std::uint16_t magic; std::uint16_t linker_version; std::uint32_t size_code; std::uint32_t size_init_data; std::uint32_t size_uninit_data; std::uint32_t entry_point; std::uint32_t base_of_code; std::uint64_t image_base; std::uint32_t section_alignment; std::uint32_t file_alignment; /* rest omitted */ std::uint32_t size_image; std::uint32_t size_headers; /* keep space */ std::uint8_t pad[200]; };
struct ImageNtHeadersX64 { std::uint32_t signature; FileHeader file_header; OptionalHeaderX64 optional_header; };
struct DosHeader
{
std::uint16_t e_magic;
std::uint16_t e_cblp;
std::uint16_t e_cp;
std::uint16_t e_crlc;
std::uint16_t e_cparhdr;
std::uint16_t e_minalloc;
std::uint16_t e_maxalloc;
std::uint16_t e_ss;
std::uint16_t e_sp;
std::uint16_t e_csum;
std::uint16_t e_ip;
std::uint16_t e_cs;
std::uint16_t e_lfarlc;
std::uint16_t e_ovno;
std::uint16_t e_res[4];
std::uint16_t e_oemid;
std::uint16_t e_oeminfo;
std::uint16_t e_res2[10];
std::uint32_t e_lfanew;
};
struct FileHeader
{
std::uint16_t machine;
std::uint16_t num_sections;
std::uint32_t timedate_stamp;
std::uint32_t ptr_symbols;
std::uint32_t num_symbols;
std::uint16_t size_optional_header;
std::uint16_t characteristics;
};
struct OptionalHeaderX64
{
std::uint16_t magic;
std::uint16_t linker_version;
std::uint32_t size_code;
std::uint32_t size_init_data;
std::uint32_t size_uninit_data;
std::uint32_t entry_point;
std::uint32_t base_of_code;
std::uint64_t image_base;
std::uint32_t section_alignment;
std::uint32_t file_alignment; /* rest omitted */
std::uint32_t size_image;
std::uint32_t size_headers; /* keep space */
std::uint8_t pad[200];
};
struct ImageNtHeadersX64
{
std::uint32_t signature;
FileHeader file_header;
OptionalHeaderX64 optional_header;
};
const std::vector<std::uint8_t> pattern_bytes = {0xDE, 0xAD, 0xBE, 0xEF, 0x90};
const std::uint32_t base_of_code = 0x200; // will place bytes at offset 0x200
constexpr std::uint32_t base_of_code = 0x200; // will place bytes at offset 0x200
const std::uint32_t size_code = static_cast<std::uint32_t>(pattern_bytes.size());
const std::uint32_t bufsize = 0x400 + size_code;
std::vector<std::uint8_t> buf(bufsize, 0);
// DOS header
auto dos = reinterpret_cast<DosHeader*>(buf.data());
dos->e_magic = 0x5A4D; dos->e_lfanew = 0x80;
const auto dos = reinterpret_cast<DosHeader*>(buf.data());
dos->e_magic = 0x5A4D;
dos->e_lfanew = 0x80;
// NT headers
auto nt = reinterpret_cast<ImageNtHeadersX64*>(buf.data() + dos->e_lfanew);
const auto nt = reinterpret_cast<ImageNtHeadersX64*>(buf.data() + dos->e_lfanew);
nt->signature = 0x4550; // 'PE\0\0'
nt->file_header.machine = 0x8664; nt->file_header.num_sections = 1;
nt->file_header.machine = 0x8664;
nt->file_header.num_sections = 1;
nt->optional_header.magic = 0x020B; // x64
nt->optional_header.base_of_code = base_of_code;
nt->optional_header.size_code = size_code;
@@ -102,6 +192,6 @@ TEST(unit_test_pe_pattern_scan_more, LoadedModuleScanFinds)
// place code at base_of_code
std::memcpy(buf.data() + base_of_code, pattern_bytes.data(), pattern_bytes.size());
auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD BE EF");
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD BE EF");
EXPECT_TRUE(res.has_value());
}

View File

@@ -1,19 +1,29 @@
#include <gtest/gtest.h>
#include <omath/utility/pe_pattern_scan.hpp>
#include <fstream>
#include <vector>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <gtest/gtest.h>
#include <omath/utility/pe_pattern_scan.hpp>
#include <vector>
using namespace omath;
// Local minimal FileHeader used by tests when constructing raw NT headers
struct TestFileHeader { std::uint16_t machine; std::uint16_t num_sections; std::uint32_t timedate_stamp; std::uint32_t ptr_symbols; std::uint32_t num_symbols; std::uint16_t size_optional_header; std::uint16_t characteristics; };
struct TestFileHeader
{
std::uint16_t machine;
std::uint16_t num_sections;
std::uint32_t timedate_stamp;
std::uint32_t ptr_symbols;
std::uint32_t num_symbols;
std::uint16_t size_optional_header;
std::uint16_t characteristics;
};
static bool write_bytes(const std::string& path, const std::vector<std::uint8_t>& data)
{
std::ofstream f(path, std::ios::binary);
if (!f.is_open()) return false;
if (!f.is_open())
return false;
f.write(reinterpret_cast<const char*>(data.data()), data.size());
return true;
}
@@ -22,11 +32,13 @@ static bool write_bytes(const std::string &path, const std::vector<std::uint8_t>
static bool write_minimal_pe_file(const std::string& path, const std::vector<std::uint8_t>& section_bytes)
{
std::ofstream f(path, std::ios::binary);
if (!f.is_open()) return false;
if (!f.is_open())
return false;
// Write DOS header (e_magic = 0x5A4D, e_lfanew at offset 0x3C)
std::vector<std::uint8_t> dos(64, 0);
dos[0] = 'M'; dos[1] = 'Z';
dos[0] = 'M';
dos[1] = 'Z';
std::uint32_t e_lfanew = 0x80;
std::memcpy(dos.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
f.write(reinterpret_cast<const char*>(dos.data()), dos.size());
@@ -39,7 +51,10 @@ static bool write_minimal_pe_file(const std::string& path, const std::vector<std
}
// NT headers signature 'PE\0\0'
f.put('P'); f.put('E'); f.put('\0'); f.put('\0');
f.put('P');
f.put('E');
f.put('\0');
f.put('\0');
// FileHeader minimal
std::uint16_t machine = 0x8664; // x64
@@ -67,7 +82,7 @@ static bool write_minimal_pe_file(const std::string& path, const std::vector<std
char name[8] = {'.', 't', 'e', 'x', 't', 0, 0, 0};
f.write(name, 8);
const std::uint32_t section_header_rest = 36u;
constexpr std::uint32_t section_header_rest = 36u;
const std::streampos header_rest_pos = f.tellp();
std::vector<char> placeholder(section_header_rest, 0);
f.write(placeholder.data(), placeholder.size());
@@ -78,7 +93,7 @@ static bool write_minimal_pe_file(const std::string& path, const std::vector<std
// Patch section header fields
const std::uint32_t virtual_size = static_cast<std::uint32_t>(section_bytes.size());
const std::uint32_t virtual_address = 0x1000u;
constexpr std::uint32_t virtual_address = 0x1000u;
const std::uint32_t size_raw_data = static_cast<std::uint32_t>(section_bytes.size());
const std::uint32_t ptr_raw_data = static_cast<std::uint32_t>(data_pos);
@@ -95,7 +110,7 @@ static bool write_minimal_pe_file(const std::string& path, const std::vector<std
TEST(unit_test_pe_pattern_scan_more2, LoadedModuleNullBaseReturnsNull)
{
auto res = PePatternScanner::scan_for_pattern_in_loaded_module(nullptr, "DE AD");
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(nullptr, "DE AD");
EXPECT_FALSE(res.has_value());
}
@@ -103,35 +118,46 @@ TEST(unit_test_pe_pattern_scan_more2, LoadedModuleInvalidOptionalHeaderReturnsNu
{
// Construct in-memory buffer with DOS header but invalid optional header magic
std::vector<std::uint8_t> buf(0x200, 0);
struct DosHeader { std::uint16_t e_magic; std::uint8_t pad[0x3A]; std::uint32_t e_lfanew; };
auto dos = reinterpret_cast<DosHeader*>(buf.data());
dos->e_magic = 0x5A4D; dos->e_lfanew = 0x80;
struct DosHeader
{
std::uint16_t e_magic;
std::uint8_t pad[0x3A];
std::uint32_t e_lfanew;
};
const auto dos = reinterpret_cast<DosHeader*>(buf.data());
dos->e_magic = 0x5A4D;
dos->e_lfanew = 0x80;
// Place an NT header with wrong optional magic at e_lfanew
auto nt_ptr = buf.data() + dos->e_lfanew;
const auto nt_ptr = buf.data() + dos->e_lfanew;
// write signature
nt_ptr[0] = 'P'; nt_ptr[1] = 'E'; nt_ptr[2] = 0; nt_ptr[3] = 0;
nt_ptr[0] = 'P';
nt_ptr[1] = 'E';
nt_ptr[2] = 0;
nt_ptr[3] = 0;
// craft FileHeader with size_optional_header large enough
std::uint16_t size_opt = 0xE0;
constexpr std::uint16_t size_opt = 0xE0;
// file header starts at offset 4
std::memcpy(nt_ptr + 4 + 12, &size_opt, sizeof(size_opt)); // size_optional_header located after 12 bytes into FileHeader
std::memcpy(nt_ptr + 4 + 12, &size_opt,
sizeof(size_opt)); // size_optional_header located after 12 bytes into FileHeader
// write optional header magic to be invalid value
std::uint16_t bad_magic = 0x9999;
std::memcpy(nt_ptr + 4 + sizeof(std::uint32_t) + sizeof(std::uint16_t) + sizeof(std::uint16_t), &bad_magic, sizeof(bad_magic));
constexpr std::uint16_t bad_magic = 0x9999;
std::memcpy(nt_ptr + 4 + sizeof(std::uint32_t) + sizeof(std::uint16_t) + sizeof(std::uint16_t), &bad_magic,
sizeof(bad_magic));
auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD");
const auto res = PePatternScanner::scan_for_pattern_in_loaded_module(buf.data(), "DE AD");
EXPECT_FALSE(res.has_value());
}
TEST(unit_test_pe_pattern_scan_more2, FileX86OptionalHeaderScanFindsPattern)
{
const std::string path = "./test_pe_x86.bin";
constexpr std::string_view path = "./test_pe_x86.bin";
const std::vector<std::uint8_t> pattern = {0xDE, 0xAD, 0xBE, 0xEF};
// Use helper from this file to write a consistent minimal PE file with .text section
ASSERT_TRUE(write_minimal_pe_file(path, pattern));
ASSERT_TRUE(write_minimal_pe_file(path.data(), pattern));
auto res = PePatternScanner::scan_for_pattern_in_file(path, "DE AD BE EF", ".text");
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "DE AD BE EF", ".text");
ASSERT_TRUE(res.has_value());
EXPECT_GE(res->virtual_base_addr, 0u);
EXPECT_GE(res->raw_base_addr, 0u);
@@ -143,21 +169,36 @@ TEST(unit_test_pe_pattern_scan_more2, FilePatternNotFoundReturnsNull)
const std::string path = "./test_pe_no_pattern.bin";
std::vector<std::uint8_t> data(512, 0);
// minimal DOS/NT headers to make extract_section fail earlier or return empty data
data[0] = 'M'; data[1] = 'Z'; std::uint32_t e_lfanew = 0x80; std::memcpy(data.data()+0x3C, &e_lfanew, sizeof(e_lfanew));
data[0] = 'M';
data[1] = 'Z';
constexpr std::uint32_t e_lfanew = 0x80;
std::memcpy(data.data() + 0x3C, &e_lfanew, sizeof(e_lfanew));
// NT signature
data[e_lfanew + 0] = 'P'; data[e_lfanew + 1] = 'E'; data[e_lfanew + 2] = 0; data[e_lfanew + 3] = 0;
data[e_lfanew + 0] = 'P';
data[e_lfanew + 1] = 'E';
data[e_lfanew + 2] = 0;
data[e_lfanew + 3] = 0;
// FileHeader: one section, size_optional_header set low
std::uint16_t num_sections = 1; std::uint16_t size_optional_header = 0xE0; std::memcpy(data.data() + e_lfanew + 6, &num_sections, sizeof(num_sections)); std::memcpy(data.data() + e_lfanew + 4 + 12, &size_optional_header, sizeof(size_optional_header));
constexpr std::uint16_t num_sections = 1;
constexpr std::uint16_t size_optional_header = 0xE0;
std::memcpy(data.data() + e_lfanew + 6, &num_sections, sizeof(num_sections));
std::memcpy(data.data() + e_lfanew + 4 + 12, &size_optional_header, sizeof(size_optional_header));
// Optional header magic x64
std::uint16_t magic = 0x020B; std::memcpy(data.data() + e_lfanew + 4 + sizeof(TestFileHeader), &magic, sizeof(magic));
constexpr std::uint16_t magic = 0x020B;
std::memcpy(data.data() + e_lfanew + 4 + sizeof(TestFileHeader), &magic, sizeof(magic));
// Section header .text with small data that does not contain the pattern
const std::size_t offset_to_segment_table = e_lfanew + 4 + sizeof(TestFileHeader) + size_optional_header;
const char name[8] = {'.','t','e','x','t',0,0,0}; std::memcpy(data.data() + offset_to_segment_table, name, 8);
std::uint32_t vs = 4, va = 0x1000, srd = 4, prd = 0x200; std::memcpy(data.data() + offset_to_segment_table + 8, &vs, 4); std::memcpy(data.data() + offset_to_segment_table + 12, &va, 4); std::memcpy(data.data() + offset_to_segment_table + 16, &srd, 4); std::memcpy(data.data() + offset_to_segment_table + 20, &prd, 4);
constexpr std::size_t offset_to_segment_table = e_lfanew + 4 + sizeof(TestFileHeader) + size_optional_header;
constexpr char name[8] = {'.', 't', 'e', 'x', 't', 0, 0, 0};
std::memcpy(data.data() + offset_to_segment_table, name, 8);
std::uint32_t vs = 4, va = 0x1000, srd = 4, prd = 0x200;
std::memcpy(data.data() + offset_to_segment_table + 8, &vs, 4);
std::memcpy(data.data() + offset_to_segment_table + 12, &va, 4);
std::memcpy(data.data() + offset_to_segment_table + 16, &srd, 4);
std::memcpy(data.data() + offset_to_segment_table + 20, &prd, 4);
// write file
ASSERT_TRUE(write_bytes(path, data));
auto res = PePatternScanner::scan_for_pattern_in_file(path, "AA BB CC", ".text");
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "AA BB CC", ".text");
EXPECT_FALSE(res.has_value());
}
// Extra tests for pe_pattern_scan edge cases (on-disk API)
@@ -165,7 +206,7 @@ TEST(unit_test_pe_pattern_scan_more2, FilePatternNotFoundReturnsNull)
TEST(PePatternScanMore2, PatternAtStartFound)
{
const std::string path = "./test_pe_more_start.bin";
std::vector<std::uint8_t> bytes = {0x90, 0x01, 0x02, 0x03, 0x04};
const std::vector<std::uint8_t> bytes = {0x90, 0x01, 0x02, 0x03, 0x04};
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "90 01 02", ".text");
@@ -202,7 +243,9 @@ TEST(PePatternScanMore2, PatternAtEndFound)
std::memcpy(&size_raw_data, filebuf.data() + meta_off + 8, sizeof(size_raw_data));
std::memcpy(&ptr_raw_data, filebuf.data() + meta_off + 12, sizeof(ptr_raw_data));
std::cerr << "Parsed section header: virtual_size=" << virtual_size << " virtual_address=0x" << std::hex << virtual_address << std::dec << " size_raw_data=" << size_raw_data << " ptr_raw_data=" << ptr_raw_data << "\n";
std::cerr << "Parsed section header: virtual_size=" << virtual_size << " virtual_address=0x" << std::hex
<< virtual_address << std::dec << " size_raw_data=" << size_raw_data
<< " ptr_raw_data=" << ptr_raw_data << "\n";
if (ptr_raw_data + size_raw_data <= filebuf.size())
{
@@ -223,7 +266,7 @@ TEST(PePatternScanMore2, PatternAtEndFound)
TEST(PePatternScanMore2, WildcardMatches)
{
const std::string path = "./test_pe_more_wild.bin";
std::vector<std::uint8_t> bytes = {0xDE, 0xAD, 0xBE, 0xEF};
const std::vector<std::uint8_t> bytes = {0xDE, 0xAD, 0xBE, 0xEF};
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "DE ?? BE", ".text");
@@ -233,7 +276,7 @@ TEST(PePatternScanMore2, WildcardMatches)
TEST(PePatternScanMore2, PatternLongerThanBuffer)
{
const std::string path = "./test_pe_more_small.bin";
std::vector<std::uint8_t> bytes = {0xAA, 0xBB};
const std::vector<std::uint8_t> bytes = {0xAA, 0xBB};
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "AA BB CC", ".text");
@@ -243,10 +286,9 @@ TEST(PePatternScanMore2, PatternLongerThanBuffer)
TEST(PePatternScanMore2, InvalidPatternParse)
{
const std::string path = "./test_pe_more_invalid.bin";
std::vector<std::uint8_t> bytes = {0x01, 0x02, 0x03};
const std::vector<std::uint8_t> bytes = {0x01, 0x02, 0x03};
ASSERT_TRUE(write_minimal_pe_file(path, bytes));
const auto res = PePatternScanner::scan_for_pattern_in_file(path, "01 GG 03", ".text");
EXPECT_FALSE(res.has_value());
}

View File

@@ -14,7 +14,8 @@ TEST(PredEngineTrait, PredictProjectilePositionBasic)
p.m_launch_speed = 10.f;
p.m_gravity_scale = 1.f;
auto pos = PredEngineTrait::predict_projectile_position(p, /*pitch*/0.f, /*yaw*/0.f, /*time*/1.f, /*gravity*/9.81f);
const auto pos = PredEngineTrait::predict_projectile_position(p, /*pitch*/ 0.f, /*yaw*/ 0.f, /*time*/ 1.f,
/*gravity*/ 9.81f);
// With zero pitch and yaw forward vector is along X; expect x ~10, z reduced by gravity*0.5
EXPECT_NEAR(pos.x, 10.f, 1e-3f);
EXPECT_NEAR(pos.z, -9.81f * 0.5f, 1e-3f);
@@ -27,7 +28,7 @@ TEST(PredEngineTrait, PredictTargetPositionAirborne)
t.m_velocity = {1.f, 0.f, 0.f};
t.m_is_airborne = true;
auto pred = PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
const auto pred = PredEngineTrait::predict_target_position(t, 2.f, 9.81f);
EXPECT_NEAR(pred.x, 2.f, 1e-6f);
// z should have been reduced by gravity* t^2
EXPECT_NEAR(pred.z, 10.f - 9.81f * 4.f * 0.5f, 1e-6f);
@@ -35,7 +36,7 @@ TEST(PredEngineTrait, PredictTargetPositionAirborne)
TEST(PredEngineTrait, CalcVector2dDistance)
{
Vector3<float> d{3.f,4.f,0.f};
constexpr Vector3<float> d{3.f, 4.f, 0.f};
EXPECT_NEAR(PredEngineTrait::calc_vector_2d_distance(d), 5.f, 1e-6f);
}
@@ -45,20 +46,21 @@ TEST(PredEngineTrait, CalcViewpointFromAngles)
p.m_origin = {0.f, 0.f, 0.f};
p.m_launch_speed = 10.f;
Vector3<float> predicted{10.f, 0.f, 0.f};
std::optional<float> pitch = 45.f;
auto vp = PredEngineTrait::calc_viewpoint_from_angles(p, predicted, pitch);
constexpr Vector3<float> predicted{10.f, 0.f, 0.f};
constexpr std::optional<float> pitch = 45.f;
const auto vp = PredEngineTrait::calc_viewpoint_from_angles(p, predicted, pitch);
// For 45 degrees, height = delta2d * tan(45deg) = 10 * 1 = 10
EXPECT_NEAR(vp.z, 10.f, 1e-6f);
}
TEST(PredEngineTrait, DirectAngles)
{
Vector3<float> origin{0.f,0.f,0.f};
Vector3<float> target{0.f,1.f,1.f};
constexpr Vector3<float> origin{0.f, 0.f, 0.f};
constexpr Vector3<float> target{0.f, 1.f, 1.f};
// yaw should be 90 degrees (pointing along y)
EXPECT_NEAR(PredEngineTrait::calc_direct_yaw_angle(origin, target), 90.f, 1e-3f);
// pitch should be asin(z/distance)
const float dist = origin.distance_to(target);
EXPECT_NEAR(PredEngineTrait::calc_direct_pitch_angle(origin, target), angles::radians_to_degrees(std::asin((target.z-origin.z)/dist)), 1e-3f);
EXPECT_NEAR(PredEngineTrait::calc_direct_pitch_angle(origin, target),
angles::radians_to_degrees(std::asin((target.z - origin.z) / dist)), 1e-3f);
}

View File

@@ -32,11 +32,11 @@ struct FakeEngineZeroGravity
TEST(ProjPredLegacyMore, ZeroGravityUsesDirectPitchAndReturnsViewpoint)
{
Projectile proj{ .m_origin = {0.f, 0.f, 0.f}, .m_launch_speed = 10.f, .m_gravity_scale = 0.f };
Target target{ .m_origin = {100.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
constexpr Projectile proj{ .m_origin = {0.f, 0.f, 0.f}, .m_launch_speed = 10.f, .m_gravity_scale = 0.f };
constexpr Target target{ .m_origin = {100.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
using Engine = omath::projectile_prediction::ProjPredEngineLegacy<FakeEngineZeroGravity>;
Engine engine(9.8f, 0.1f, 5.f, 1e-3f);
const Engine engine(9.8f, 0.1f, 5.f, 1e-3f);
const auto res = engine.maybe_calculate_aim_point(proj, target);
ASSERT_TRUE(res.has_value());
@@ -61,11 +61,11 @@ struct FakeEngineNoSolution
TEST(ProjPredLegacyMore, NoSolutionRootReturnsNullopt)
{
// Very slow projectile and large distance -> quadratic root negative
Projectile proj{ .m_origin = {0.f,0.f,0.f}, .m_launch_speed = 1.f, .m_gravity_scale = 1.f };
Target target{ .m_origin = {10000.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
constexpr Projectile proj{ .m_origin = {0.f,0.f,0.f}, .m_launch_speed = 1.f, .m_gravity_scale = 1.f };
constexpr Target target{ .m_origin = {10000.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
using Engine = omath::projectile_prediction::ProjPredEngineLegacy<FakeEngineNoSolution>;
Engine engine(9.8f, 0.5f, 2.f, 1.f);
const Engine engine(9.8f, 0.5f, 2.f, 1.f);
const auto res = engine.maybe_calculate_aim_point(proj, target);
EXPECT_FALSE(res.has_value());
@@ -89,11 +89,11 @@ struct FakeEngineAngleButMiss
TEST(ProjPredLegacyMore, AngleComputedButMissReturnsNullopt)
{
Projectile proj{ .m_origin = {0.f,0.f,0.f}, .m_launch_speed = 100.f, .m_gravity_scale = 1.f };
Target target{ .m_origin = {10.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
constexpr Projectile proj{ .m_origin = {0.f,0.f,0.f}, .m_launch_speed = 100.f, .m_gravity_scale = 1.f };
constexpr Target target{ .m_origin = {10.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
using Engine = omath::projectile_prediction::ProjPredEngineLegacy<FakeEngineAngleButMiss>;
Engine engine(9.8f, 0.1f, 1.f, 0.1f);
const Engine engine(9.8f, 0.1f, 1.f, 0.1f);
const auto res = engine.maybe_calculate_aim_point(proj, target);
EXPECT_FALSE(res.has_value());

View File

@@ -23,7 +23,7 @@ TEST(SimplexExtra, HandleLine_CollinearProducesPerp)
EXPECT_TRUE(std::isfinite(dir.x));
EXPECT_TRUE(std::isfinite(dir.y));
EXPECT_TRUE(std::isfinite(dir.z));
auto zero = Vector3<float>{0.f, 0.f, 0.f};
constexpr auto zero = Vector3<float>{0.f, 0.f, 0.f};
EXPECT_FALSE(dir == zero);
// Ensure direction is (approximately) perpendicular to ab
@@ -94,11 +94,11 @@ TEST(SimplexMore, PushFrontAndAccess)
s.push_front(omath::Vector3<float>{3.f, 0.f, 0.f});
EXPECT_EQ(s.size(), 3u);
omath::Vector3<float> exp_front{3.f,0.f,0.f};
omath::Vector3<float> exp_back{1.f,0.f,0.f};
constexpr omath::Vector3<float> exp_front{3.f, 0.f, 0.f};
constexpr omath::Vector3<float> exp_back{1.f, 0.f, 0.f};
EXPECT_TRUE(s.front() == exp_front);
EXPECT_TRUE(s.back() == exp_back);
auto d = s.data();
const auto d = s.data();
EXPECT_TRUE(d[0] == exp_front);
}
@@ -123,15 +123,15 @@ TEST(SimplexMore, HandleLineCollinearProducesPerp)
TEST(SimplexMore, HandleTriangleFlipWinding)
{
const omath::Vector3<float> a{1.f,0.f,0.f};
const omath::Vector3<float> b{0.f,1.f,0.f};
const omath::Vector3<float> c{0.f,0.f,1.f};
constexpr omath::Vector3<float> a{1.f, 0.f, 0.f};
constexpr omath::Vector3<float> b{0.f, 1.f, 0.f};
constexpr omath::Vector3<float> c{0.f, 0.f, 1.f};
omath::collision::Simplex<omath::Vector3<float>> s;
s = {a, b, c};
omath::Vector3<float> dir{0.f, 0.f, 0.f};
const auto ab = b - a;
const auto ac = c - a;
constexpr auto ab = b - a;
constexpr auto ac = c - a;
const auto abc = ab.cross(ac);
const bool res = s.handle(dir);
@@ -145,7 +145,8 @@ TEST(SimplexMore, HandleTriangleFlipWinding)
TEST(SimplexMore, HandleTetrahedronInsideTrue)
{
omath::collision::Simplex<omath::Vector3<float>> s;
s = { omath::Vector3<float>{1.f,0.f,0.f}, omath::Vector3<float>{0.f,1.f,0.f}, omath::Vector3<float>{0.f,0.f,1.f}, omath::Vector3<float>{-1.f,-1.f,-1.f} };
s = {omath::Vector3<float>{1.f, 0.f, 0.f}, omath::Vector3<float>{0.f, 1.f, 0.f},
omath::Vector3<float>{0.f, 0.f, 1.f}, omath::Vector3<float>{-1.f, -1.f, -1.f}};
omath::Vector3<float> dir{0.f, 0.f, 0.f};
const bool inside = s.handle(dir);
EXPECT_TRUE(inside);

View File

@@ -99,15 +99,15 @@ TEST_F(UnitTestTriangle, SideLengths)
// Test side vectors
TEST_F(UnitTestTriangle, SideVectors)
{
const Vector3 sideA_t1 = t1.side_a_vector(); // m_vertex1 - m_vertex2
EXPECT_FLOAT_EQ(sideA_t1.x, 0.0f - 1.0f);
EXPECT_FLOAT_EQ(sideA_t1.y, 0.0f - 0.0f);
EXPECT_FLOAT_EQ(sideA_t1.z, 0.0f - 0.0f);
const Vector3 side_a_t1 = t1.side_a_vector(); // m_vertex1 - m_vertex2
EXPECT_FLOAT_EQ(side_a_t1.x, 0.0f - 1.0f);
EXPECT_FLOAT_EQ(side_a_t1.y, 0.0f - 0.0f);
EXPECT_FLOAT_EQ(side_a_t1.z, 0.0f - 0.0f);
const Vector3 sideB_t1 = t1.side_b_vector(); // m_vertex3 - m_vertex2
EXPECT_FLOAT_EQ(sideB_t1.x, 0.0f - 1.0f);
EXPECT_FLOAT_EQ(sideB_t1.y, 1.0f - 0.0f);
EXPECT_FLOAT_EQ(sideB_t1.z, 0.0f - 0.0f);
const Vector3 side_b_t1 = t1.side_b_vector(); // m_vertex3 - m_vertex2
EXPECT_FLOAT_EQ(side_b_t1.x, 0.0f - 1.0f);
EXPECT_FLOAT_EQ(side_b_t1.y, 1.0f - 0.0f);
EXPECT_FLOAT_EQ(side_b_t1.z, 0.0f - 0.0f);
}
TEST_F(UnitTestTriangle, IsRectangular)

View File

@@ -306,7 +306,7 @@ TEST_F(UnitTestVector2, DivisionAssignmentOperator_VectorWithZero)
// Test operations with infinity and NaN
TEST_F(UnitTestVector2, Operator_WithInfinity)
{
constexpr Vector2 v_inf(INFINITY, INFINITY);
const Vector2 v_inf(INFINITY, INFINITY);
const Vector2 result = v1 + v_inf;
EXPECT_TRUE(std::isinf(result.x));
EXPECT_TRUE(std::isinf(result.y));

View File

@@ -12,55 +12,55 @@ using namespace omath;
TEST(Vector3More, ConstructorsAndEquality)
{
Vector3<float> a;
constexpr Vector3<float> a;
EXPECT_EQ(a.x, 0.f);
EXPECT_EQ(a.y, 0.f);
EXPECT_EQ(a.z, 0.f);
Vector3<float> b{1.f, 2.f, 3.f};
constexpr Vector3<float> b{1.f, 2.f, 3.f};
EXPECT_EQ(b.x, 1.f);
EXPECT_EQ(b.y, 2.f);
EXPECT_EQ(b.z, 3.f);
Vector3<float> c = b;
const Vector3<float> c = b;
EXPECT_EQ(c, b);
}
TEST(Vector3More, ArithmeticAndDotCross)
{
Vector3<float> a{1.f, 0.f, 0.f};
Vector3<float> b{0.f, 1.f, 0.f};
auto c = a + b;
const Vector3<float> expect_c{1.f,1.f,0.f};
constexpr Vector3<float> a{1.f, 0.f, 0.f};
constexpr Vector3<float> b{0.f, 1.f, 0.f};
const auto c = a + b;
constexpr Vector3<float> expect_c{1.f,1.f,0.f};
EXPECT_EQ(c, expect_c);
auto d = a - b;
const Vector3<float> expect_d{1.f,-1.f,0.f};
const auto d = a - b;
constexpr Vector3<float> expect_d{1.f,-1.f,0.f};
EXPECT_EQ(d, expect_d);
auto e = a * 2.f;
const Vector3<float> expect_e{2.f,0.f,0.f};
const auto e = a * 2.f;
constexpr Vector3<float> expect_e{2.f,0.f,0.f};
EXPECT_EQ(e, expect_e);
EXPECT_FLOAT_EQ(a.dot(b), 0.f);
// manual cross product check
auto cr = Vector3<float>{ a.y * b.z - a.z * b.y,
const auto cr = Vector3<float>{ a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x };
const Vector3<float> expect_cr{0.f,0.f,1.f};
constexpr Vector3<float> expect_cr{0.f,0.f,1.f};
EXPECT_EQ(cr, expect_cr);
}
TEST(Vector3More, NormalizationEdgeCases)
{
Vector3<double> z{0.0,0.0,0.0};
auto zn = z.normalized();
constexpr Vector3<double> z{0.0,0.0,0.0};
const auto zn = z.normalized();
EXPECT_DOUBLE_EQ(zn.x, 0.0);
EXPECT_DOUBLE_EQ(zn.y, 0.0);
EXPECT_DOUBLE_EQ(zn.z, 0.0);
Vector3<double> v{3.0,4.0,0.0};
auto vn = v.normalized();
constexpr Vector3<double> v{3.0,4.0,0.0};
const auto vn = v.normalized();
EXPECT_NEAR(vn.x, 0.6, 1e-12);
EXPECT_NEAR(vn.y, 0.8, 1e-12);
}
@@ -315,7 +315,7 @@ TEST_F(UnitTestVector3, Division_ByZeroScalar)
// Test operations with infinity
TEST_F(UnitTestVector3, Addition_WithInfinity)
{
constexpr Vector3 v_inf(INFINITY, INFINITY, INFINITY);
const Vector3 v_inf(INFINITY, INFINITY, INFINITY);
const Vector3 result = v1 + v_inf;
EXPECT_TRUE(std::isinf(result.x));
EXPECT_TRUE(std::isinf(result.y));

View File

@@ -13,7 +13,7 @@ using namespace omath;
TEST(Vector4More, ConstructorsAndClamp)
{
Vector4<float> a;
constexpr Vector4<float> a;
EXPECT_EQ(a.x, 0.f);
EXPECT_EQ(a.y, 0.f);
EXPECT_EQ(a.z, 0.f);
@@ -28,8 +28,8 @@ TEST(Vector4More, ConstructorsAndClamp)
TEST(Vector4More, ComparisonsAndHashFormatter)
{
Vector4<int> a{1,2,3,4};
Vector4<int> b{1,2,3,5};
constexpr Vector4<int> a{1,2,3,4};
constexpr Vector4<int> b{1,2,3,5};
EXPECT_NE(a, b);
// exercise to_string via formatting if available by converting via std::format