Compare commits

...

20 Commits

Author SHA1 Message Date
6ae3e37172 Merge pull request #148 from orange-cpp/feature/engine-units-to-metric
Feature/engine units to metric
2026-02-08 03:28:54 +03:00
afc613fcc0 added tests 2026-02-08 03:15:21 +03:00
d7a721f62e added frostbite tests 2026-02-08 03:03:23 +03:00
5aae9d6842 added for other engines 2026-02-08 02:58:59 +03:00
3e4598313d improved naming 2026-02-08 02:51:48 +03:00
d231139b83 added for source 2026-02-08 02:43:10 +03:00
9c4e2a3319 Merge pull request #146 from orange-cpp/feature/line_tracer_template
improvement
2026-02-06 00:14:15 +03:00
7597d95778 fixed warnings 2026-02-06 00:02:00 +03:00
5aa0e2e949 added noexcept 2026-02-05 23:45:41 +03:00
b7b1154f29 simplified shit 2026-02-05 23:43:17 +03:00
b10e26e6ba added constexpr 2026-02-05 23:38:51 +03:00
ba23fee243 removed uselss c++ file 2026-02-05 23:31:14 +03:00
32e0f9e636 improvement 2026-02-05 23:27:31 +03:00
63b4327c91 Merge pull request #145 from orange-cpp/feature/macho_improvement
Feature/macho improvement
2026-02-04 19:27:44 +03:00
dbad87de0f fixed bug 2026-02-04 19:10:06 +03:00
8dd044fa1e removed nesting 2026-02-04 18:35:04 +03:00
c25a3da196 removed nesting 2026-02-04 18:33:05 +03:00
d64d60cfcd fixed codestyle 2026-02-04 18:30:45 +03:00
2ef25b0ce8 added resharper ignore segment 2026-02-04 18:29:55 +03:00
Copilot
775949887a Add Mach-O pattern scanner (#144)
* Initial plan

* Add Mach-O pattern scanner implementation and unit tests

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

* Add Mach-O pattern scanner

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

* Remove CodeQL build artifacts from PR

Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2026-02-04 17:30:20 +03:00
23 changed files with 1728 additions and 119 deletions

View File

@@ -8,43 +8,96 @@
namespace omath::collision
{
class Ray
template<class T = Vector3<float>>
class Ray final
{
public:
Vector3<float> start;
Vector3<float> end;
using VectorType = T;
VectorType start;
VectorType end;
bool infinite_length = false;
[[nodiscard]]
Vector3<float> direction_vector() const noexcept;
constexpr VectorType direction_vector() const noexcept
{
return end - start;
}
[[nodiscard]]
Vector3<float> direction_vector_normalized() const noexcept;
constexpr VectorType direction_vector_normalized() const noexcept
{
return direction_vector().normalized();
}
};
class LineTracer
template<class RayType = Ray<>>
class LineTracer final
{
using TriangleType = Triangle<typename RayType::VectorType>;
public:
LineTracer() = delete;
[[nodiscard]]
static bool can_trace_line(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept;
constexpr static bool can_trace_line(const RayType& ray, const TriangleType& triangle) noexcept
{
return get_ray_hit_point(ray, triangle) == ray.end;
}
// Realization of MöllerTrumbore intersection algorithm
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
[[nodiscard]]
static Vector3<float> get_ray_hit_point(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept;
constexpr static auto get_ray_hit_point(const RayType& ray, const TriangleType& triangle) noexcept
{
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
const auto side_a = triangle.side_a_vector();
const auto side_b = triangle.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);
if (std::abs(det) < k_epsilon)
return ray.end;
const auto inv_det = 1 / det;
const auto t = ray.start - triangle.m_vertex2;
const auto u = t.dot(p) * inv_det;
if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon))
return ray.end;
const auto q = t.cross(side_a);
// ReSharper disable once CppTooWideScopeInitStatement
const auto v = ray_dir.dot(q) * inv_det;
if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon))
return ray.end;
const auto t_hit = side_b.dot(q) * inv_det;
if (ray.infinite_length && t_hit <= k_epsilon)
return ray.end;
if (t_hit <= k_epsilon || t_hit > 1 - k_epsilon)
return ray.end;
return ray.start + ray_dir * t_hit;
}
template<class MeshType>
[[nodiscard]]
static Vector3<float> get_ray_hit_point(const Ray& ray, const MeshType& mesh) noexcept
constexpr static auto get_ray_hit_point(const RayType& ray, const MeshType& mesh) noexcept
{
Vector3<float> mesh_hit = ray.end;
auto mesh_hit = ray.end;
auto begin = mesh.m_element_buffer_object.cbegin();
auto end = mesh.m_element_buffer_object.cend();
const auto begin = mesh.m_element_buffer_object.cbegin();
const auto end = mesh.m_element_buffer_object.cend();
for (auto current = begin; current < end; current = std::next(current))
{
auto face = mesh.make_face_in_world_space(current);
const auto face = mesh.make_face_in_world_space(current);
auto ray_stop_point = get_ray_hit_point(ray, face);
if (ray_stop_point.distance_to(ray.start) < mesh_hit.distance_to(ray.start))

View File

@@ -130,7 +130,7 @@ namespace omath::collision
template<class V>
[[nodiscard]]
static constexpr bool near_zero(const V& v, const float eps = 1e-7f)
static constexpr bool near_zero(const V& v, const float eps = 1e-7f) noexcept
{
return v.dot(v) <= eps * eps;
}
@@ -146,7 +146,7 @@ namespace omath::collision
}
[[nodiscard]]
constexpr bool handle_line(VectorType& direction)
constexpr bool handle_line(VectorType& direction) noexcept
{
const auto& a = m_points[0];
const auto& b = m_points[1];
@@ -158,21 +158,11 @@ namespace omath::collision
{
// ReSharper disable once CppTooWideScopeInitStatement
auto n = ab.cross(ao); // Needed to valid handle collision if colliders placed at same origin pos
if (near_zero(n))
{
// collinear: origin lies on ray AB (often on segment), pick any perp to escape
direction = any_perp(ab);
}
else
{
direction = n.cross(ab);
}
}
else
{
*this = {a};
direction = ao;
direction = near_zero(n) ? any_perp(ab) : n.cross(ab);
return false;
}
*this = {a};
direction = ao;
return false;
}

View File

@@ -23,4 +23,52 @@ namespace omath::frostbite_engine
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
} // namespace omath::unity_engine
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::frostbite_engine

View File

@@ -23,4 +23,54 @@ namespace omath::iw_engine
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
constexpr auto centimeter_in_unit = static_cast<FloatingType>(2.54);
return units * centimeter_in_unit;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units_to_centimeters(units) / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
constexpr auto centimeter_in_unit = static_cast<FloatingType>(2.54);
return centimeters / centimeter_in_unit;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return centimeters_to_units(meters * static_cast<FloatingType>(100));
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::iw_engine

View File

@@ -4,7 +4,6 @@
#pragma once
#include "omath/engines/opengl_engine/constants.hpp"
namespace omath::opengl_engine
{
[[nodiscard]]
@@ -23,4 +22,52 @@ namespace omath::opengl_engine
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::opengl_engine

View File

@@ -22,4 +22,54 @@ namespace omath::source_engine
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
constexpr auto centimeter_in_unit = static_cast<FloatingType>(2.54);
return units * centimeter_in_unit;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units_to_centimeters(units) / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
constexpr auto centimeter_in_unit = static_cast<FloatingType>(2.54);
return centimeters / centimeter_in_unit;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return centimeters_to_units(meters * static_cast<FloatingType>(100));
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::source_engine

View File

@@ -23,4 +23,52 @@ namespace omath::unity_engine
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::unity_engine

View File

@@ -23,4 +23,52 @@ namespace omath::unreal_engine
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far) noexcept;
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_centimeters(const FloatingType& units)
{
return units;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_meters(const FloatingType& units)
{
return units / static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType units_to_kilometers(const FloatingType& units)
{
return units_to_meters(units) / static_cast<FloatingType>(1000);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
{
return centimeters;
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType meters_to_units(const FloatingType& meters)
{
return meters * static_cast<FloatingType>(100);
}
template<class FloatingType>
requires std::is_floating_point_v<FloatingType>
[[nodiscard]]
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
{
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
}
} // namespace omath::unreal_engine

View File

@@ -0,0 +1,25 @@
//
// Created by Copilot on 04.02.2026.
//
#pragma once
#include <cstdint>
#include <filesystem>
#include <optional>
#include <string_view>
#include "section_scan_result.hpp"
namespace omath
{
class MachOPatternScanner final
{
public:
[[nodiscard]]
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
[[nodiscard]]
static std::optional<SectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file, const std::string_view& pattern,
const std::string_view& target_section_name = "__text");
};
} // namespace omath

View File

@@ -1,61 +0,0 @@
//
// Created by Orange on 11/13/2024.
//
#include "omath/collision/line_tracer.hpp"
namespace omath::collision
{
bool LineTracer::can_trace_line(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept
{
return get_ray_hit_point(ray, triangle) == ray.end;
}
Vector3<float> Ray::direction_vector() const noexcept
{
return end - start;
}
Vector3<float> Ray::direction_vector_normalized() const noexcept
{
return direction_vector().normalized();
}
Vector3<float> LineTracer::get_ray_hit_point(const Ray& ray, const Triangle<Vector3<float>>& triangle) noexcept
{
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
const auto side_a = triangle.side_a_vector();
const auto side_b = triangle.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);
if (std::abs(det) < k_epsilon)
return ray.end;
const auto inv_det = 1.0f / det;
const auto t = ray.start - triangle.m_vertex2;
const auto u = t.dot(p) * inv_det;
if ((u < 0 && std::abs(u) > k_epsilon) || (u > 1 && std::abs(u - 1) > k_epsilon))
return ray.end;
const auto q = t.cross(side_a);
// ReSharper disable once CppTooWideScopeInitStatement
const auto v = ray_dir.dot(q) * inv_det;
if ((v < 0 && std::abs(v) > k_epsilon) || (u + v > 1 && std::abs(u + v - 1) > k_epsilon))
return ray.end;
const auto t_hit = side_b.dot(q) * inv_det;
if (ray.infinite_length && t_hit <= k_epsilon)
return ray.end;
if (t_hit <= k_epsilon || t_hit > 1.0f - k_epsilon)
return ray.end;
return ray.start + ray_dir * t_hit;
}
} // namespace omath::collision

View File

@@ -0,0 +1,349 @@
//
// Created by Copilot on 04.02.2026.
//
#include "omath/utility/macho_pattern_scan.hpp"
#include "omath/utility/pattern_scan.hpp"
#include <cstring>
#include <fstream>
#include <variant>
#include <vector>
#pragma pack(push, 1)
namespace
{
// Mach-O magic numbers
constexpr std::uint32_t mh_magic_32 = 0xFEEDFACE;
constexpr std::uint32_t mh_magic_64 = 0xFEEDFACF;
constexpr std::uint32_t mh_cigam_32 = 0xCEFAEDFE; // Byte-swapped 32-bit
constexpr std::uint32_t mh_cigam_64 = 0xCFFAEDFE; // Byte-swapped 64-bit
// Load command types
constexpr std::uint32_t lc_segment = 0x1;
constexpr std::uint32_t lc_segment_64 = 0x19;
// ReSharper disable CppDeclaratorNeverUsed
// Mach-O header for 32-bit
struct MachHeader32 final
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
};
// Mach-O header for 64-bit
struct MachHeader64 final
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
std::uint32_t reserved;
};
// Load command header
struct LoadCommand final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
};
// Segment command for 32-bit
struct SegmentCommand32 final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint32_t vmaddr;
std::uint32_t vmsize;
std::uint32_t fileoff;
std::uint32_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
// Segment command for 64-bit
struct SegmentCommand64 final
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint64_t vmaddr;
std::uint64_t vmsize;
std::uint64_t fileoff;
std::uint64_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
// Section for 32-bit
struct Section32 final
{
char sectname[16];
char segname[16];
std::uint32_t addr;
std::uint32_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
};
// Section for 64-bit
struct Section64 final
{
char sectname[16];
char segname[16];
std::uint64_t addr;
std::uint64_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
std::uint32_t reserved3;
};
// ReSharper enable CppDeclaratorNeverUsed
#pragma pack(pop)
enum class MachOArch : std::int8_t
{
x32,
x64,
};
struct ExtractedSection final
{
std::uintptr_t virtual_base_addr{};
std::uintptr_t raw_base_addr{};
std::vector<std::byte> data;
};
[[nodiscard]]
std::optional<MachOArch> get_macho_arch(std::fstream& file)
{
std::uint32_t magic{};
const std::streampos backup_pos = file.tellg();
file.seekg(0, std::ios_base::beg);
file.read(reinterpret_cast<char*>(&magic), sizeof(magic));
file.seekg(backup_pos, std::ios_base::beg);
if (magic == mh_magic_64 || magic == mh_cigam_64)
return MachOArch::x64;
if (magic == mh_magic_32 || magic == mh_cigam_32)
return MachOArch::x32;
return std::nullopt;
}
[[nodiscard]]
bool is_macho_file(std::fstream& file)
{
return get_macho_arch(file).has_value();
}
[[nodiscard]]
std::string_view get_section_name(const char* sectname)
{
// Mach-O section names are fixed 16-byte arrays, not necessarily null-terminated
return std::string_view(sectname, std::min(std::strlen(sectname), std::size_t{16}));
}
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
std::optional<ExtractedSection> extract_section_impl(std::fstream& file, const std::string_view& section_name)
{
HeaderType header{};
file.seekg(0, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&header), sizeof(header))) [[unlikely]]
return std::nullopt;
std::streamoff cmd_offset = sizeof(header);
for (std::uint32_t i = 0; i < header.ncmds; ++i)
{
LoadCommand lc{};
file.seekg(cmd_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&lc), sizeof(lc))) [[unlikely]]
return std::nullopt;
if (lc.cmd != segment_cmd)
{
cmd_offset += static_cast<std::streamoff>(lc.cmdsize);
continue;
}
SegmentType segment{};
file.seekg(cmd_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&segment), sizeof(segment))) [[unlikely]]
return std::nullopt;
if (!segment.nsects)
{
cmd_offset += static_cast<std::streamoff>(lc.cmdsize);
continue;
}
std::streamoff sect_offset = cmd_offset + static_cast<std::streamoff>(sizeof(segment));
for (std::uint32_t j = 0; j < segment.nsects; ++j)
{
SectionType section{};
file.seekg(sect_offset, std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(&section), sizeof(section))) [[unlikely]]
return std::nullopt;
if (get_section_name(section.sectname) != section_name)
{
sect_offset += static_cast<std::streamoff>(sizeof(section));
continue;
}
ExtractedSection out;
out.virtual_base_addr = static_cast<std::uintptr_t>(section.addr);
out.raw_base_addr = static_cast<std::uintptr_t>(section.offset);
out.data.resize(static_cast<std::size_t>(section.size));
file.seekg(static_cast<std::streamoff>(section.offset), std::ios_base::beg);
if (!file.read(reinterpret_cast<char*>(out.data.data()), static_cast<std::streamsize>(out.data.size())))
[[unlikely]]
return std::nullopt;
return out;
}
}
return std::nullopt;
}
[[nodiscard]]
std::optional<ExtractedSection> get_macho_section_by_name(const std::filesystem::path& path,
const std::string_view& section_name)
{
std::fstream file(path, std::ios::binary | std::ios::in);
if (!file.is_open()) [[unlikely]]
return std::nullopt;
if (!is_macho_file(file)) [[unlikely]]
return std::nullopt;
const auto arch = get_macho_arch(file);
if (!arch.has_value()) [[unlikely]]
return std::nullopt;
if (arch.value() == MachOArch::x64)
return extract_section_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(file, section_name);
return extract_section_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(file, section_name);
}
template<typename HeaderType, typename SegmentType, typename SectionType, std::uint32_t segment_cmd>
std::optional<std::uintptr_t> scan_in_module_impl(const std::byte* base, const std::string_view pattern,
const std::string_view target_section_name)
{
const auto* header = reinterpret_cast<const HeaderType*>(base);
std::size_t cmd_offset = sizeof(HeaderType);
for (std::uint32_t i = 0; i < header->ncmds; ++i)
{
const auto* lc = reinterpret_cast<const LoadCommand*>(base + cmd_offset);
if (lc->cmd != segment_cmd)
{
cmd_offset += lc->cmdsize;
continue;
}
const auto* segment = reinterpret_cast<const SegmentType*>(base + cmd_offset);
std::size_t sect_offset = cmd_offset + sizeof(SegmentType);
for (std::uint32_t j = 0; j < segment->nsects; ++j)
{
const auto* section = reinterpret_cast<const SectionType*>(base + sect_offset);
if (get_section_name(section->sectname) != target_section_name && section->size > 0)
{
sect_offset += sizeof(SectionType);
continue;
}
const auto* section_begin = base + static_cast<std::size_t>(section->addr);
const auto* section_end = section_begin + static_cast<std::size_t>(section->size);
const auto scan_result = omath::PatternScanner::scan_for_pattern(section_begin, section_end, pattern);
if (scan_result != section_end)
return reinterpret_cast<std::uintptr_t>(scan_result);
}
}
return std::nullopt;
}
} // namespace
namespace omath
{
std::optional<std::uintptr_t>
MachOPatternScanner::scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
if (module_base_address == nullptr) [[unlikely]]
return std::nullopt;
const auto* base = static_cast<const std::byte*>(module_base_address);
// Read magic to determine architecture
std::uint32_t magic{};
std::memcpy(&magic, base, sizeof(magic));
if (magic == mh_magic_64 || magic == mh_cigam_64)
return scan_in_module_impl<MachHeader64, SegmentCommand64, Section64, lc_segment_64>(base, pattern,
target_section_name);
if (magic == mh_magic_32 || magic == mh_cigam_32)
return scan_in_module_impl<MachHeader32, SegmentCommand32, Section32, lc_segment>(base, pattern,
target_section_name);
return std::nullopt;
}
std::optional<SectionScanResult>
MachOPatternScanner::scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
const std::string_view& target_section_name)
{
const auto macho_section = get_macho_section_by_name(path_to_file, target_section_name);
if (!macho_section.has_value()) [[unlikely]]
return std::nullopt;
const auto scan_result =
PatternScanner::scan_for_pattern(macho_section->data.cbegin(), macho_section->data.cend(), pattern);
if (scan_result == macho_section->data.cend())
return std::nullopt;
const auto offset = std::distance(macho_section->data.begin(), scan_result);
return SectionScanResult{.virtual_base_addr = macho_section->virtual_base_addr,
.raw_base_addr = macho_section->raw_base_addr,
.target_offset = offset};
}
} // namespace omath

View File

@@ -8,6 +8,125 @@
#include <print>
#include <random>
TEST(unit_test_frostbite_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_frostbite_engine, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_frostbite_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_frostbite_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::frostbite_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_frostbite_engine, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::frostbite_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_frostbite_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::frostbite_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
const auto cm_f = omath::frostbite_engine::units_to_centimeters(units_f);
const auto units_f_back = omath::frostbite_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
const auto cm_d = omath::frostbite_engine::units_to_centimeters(units_d);
const auto units_d_back = omath::frostbite_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::frostbite_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::frostbite_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::frostbite_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::frostbite_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_frostbite_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::frostbite_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::frostbite_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::frostbite_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::frostbite_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_frostbite_engine, ConversionChainConsistency)
{
const double units = 424242.42;
const auto cm_direct = omath::frostbite_engine::units_to_centimeters(units);
const auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
const auto km_direct = omath::frostbite_engine::units_to_kilometers(units);
const auto km_via_meters = omath::frostbite_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_frostbite_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::frostbite_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::frostbite_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_frostbite_engine, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::frostbite_engine::units_to_centimeters(units);
constexpr double m = omath::frostbite_engine::units_to_meters(units);
constexpr double km = omath::frostbite_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_frostbite_engine, ForwardVector)
{
const auto forward = omath::frostbite_engine::forward_vector({});

View File

@@ -7,6 +7,125 @@
#include <omath/engines/opengl_engine/formulas.hpp>
#include <random>
TEST(unit_test_opengl, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_opengl, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::opengl_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_opengl, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_opengl, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::opengl_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_opengl, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::opengl_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_opengl, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::opengl_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_opengl, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
const auto cm_f = omath::opengl_engine::units_to_centimeters(units_f);
const auto units_f_back = omath::opengl_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
const auto cm_d = omath::opengl_engine::units_to_centimeters(units_d);
const auto units_d_back = omath::opengl_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_opengl, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
const auto m_f = omath::opengl_engine::units_to_meters(units_f);
const auto units_f_back = omath::opengl_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
const auto m_d = omath::opengl_engine::units_to_meters(units_d);
const auto units_d_back = omath::opengl_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_opengl, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
const auto km_f = omath::opengl_engine::units_to_kilometers(units_f);
const auto units_f_back = omath::opengl_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
const auto km_d = omath::opengl_engine::units_to_kilometers(units_d);
const auto units_d_back = omath::opengl_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_opengl, ConversionChainConsistency)
{
const double units = 424242.42;
const auto cm_direct = omath::opengl_engine::units_to_centimeters(units);
const auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
const auto km_direct = omath::opengl_engine::units_to_kilometers(units);
const auto km_via_meters = omath::opengl_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_opengl, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::opengl_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::opengl_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_opengl, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::opengl_engine::units_to_centimeters(units);
constexpr double m = omath::opengl_engine::units_to_meters(units);
constexpr double km = omath::opengl_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_opengl, ForwardVector)
{
const auto forward = omath::opengl_engine::forward_vector({});

View File

@@ -7,6 +7,129 @@
#include <omath/engines/source_engine/formulas.hpp>
#include <random>
TEST(unit_test_source_engine_units, HammerUnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(1.0f), 2.54f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(10.0f), 25.4f);
EXPECT_FLOAT_EQ(omath::source_engine::units_to_centimeters(-2.0f), -5.08f);
}
TEST(unit_test_source_engine_units, HammerUnitsToMeters_BasicValues)
{
EXPECT_NEAR(omath::source_engine::units_to_meters(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(1.0), 0.0254, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(100.0), 2.54, 1e-12);
EXPECT_NEAR(omath::source_engine::units_to_meters(-4.0), -0.1016, 1e-12);
}
TEST(unit_test_source_engine_units, HammerUnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::source_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(1.0), 0.0000254, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(1000.0), 0.0254, 1e-15);
EXPECT_NEAR(omath::source_engine::units_to_kilometers(-10.0), -0.000254, 1e-15);
}
TEST(unit_test_source_engine_units, CentimetersToHammerUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::source_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(2.54f), 1.0f, 1e-6f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(25.4f), 10.0f, 1e-5f);
EXPECT_NEAR(omath::source_engine::centimeters_to_units(-5.08f), -2.0f, 1e-6f);
}
TEST(unit_test_source_engine_units, MetersToHammerUnits_BasicValues)
{
EXPECT_NEAR(omath::source_engine::meters_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::source_engine::meters_to_units(0.0254), 1.0, 1e-12);
EXPECT_NEAR(omath::source_engine::meters_to_units(2.54), 100.0, 1e-10);
EXPECT_NEAR(omath::source_engine::meters_to_units(-0.0508), -2.0, 1e-12);
}
TEST(unit_test_source_engine_units, KilometersToHammerUnits_BasicValues)
{
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.0), 0.0, 1e-9);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.0000254), 1.0, 1e-9);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(0.00254), 100.0, 1e-7);
EXPECT_NEAR(omath::source_engine::kilometers_to_units(-0.0000508), -2.0, 1e-9);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToCentimetersToHammer)
{
constexpr float hu_f = 123.456f;
constexpr auto cm_f = omath::source_engine::units_to_centimeters(hu_f);
constexpr auto hu_f_back = omath::source_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-5f);
constexpr double hu_d = -98765.4321;
constexpr auto cm_d = omath::source_engine::units_to_centimeters(hu_d);
constexpr auto hu_d_back = omath::source_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-10);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToMetersToHammer)
{
constexpr float hu_f = 2500.25f;
constexpr auto m_f = omath::source_engine::units_to_meters(hu_f);
constexpr auto hu_f_back = omath::source_engine::meters_to_units(m_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-4f);
constexpr double hu_d = -42000.125;
constexpr auto m_d = omath::source_engine::units_to_meters(hu_d);
constexpr auto hu_d_back = omath::source_engine::meters_to_units(m_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-10);
}
TEST(unit_test_source_engine_units, RoundTrip_HammerToKilometersToHammer)
{
constexpr float hu_f = 100000.0f;
constexpr auto km_f = omath::source_engine::units_to_kilometers(hu_f);
constexpr auto hu_f_back = omath::source_engine::kilometers_to_units(km_f);
EXPECT_NEAR(hu_f_back, hu_f, 1e-2f); // looser due to float scaling
constexpr double hu_d = -1234567.89;
constexpr auto km_d = omath::source_engine::units_to_kilometers(hu_d);
constexpr auto hu_d_back = omath::source_engine::kilometers_to_units(km_d);
EXPECT_NEAR(hu_d_back, hu_d, 1e-7);
}
TEST(unit_test_source_engine_units, ConversionChainConsistency)
{
// hu -> cm -> m -> km should match direct helpers
constexpr auto hu = 54321.123;
constexpr auto cm = omath::source_engine::units_to_centimeters(hu);
constexpr auto m_via_cm = cm / 100.0;
constexpr auto km_via_cm = m_via_cm / 1000.0;
constexpr auto m_direct = omath::source_engine::units_to_meters(hu);
constexpr auto km_direct = omath::source_engine::units_to_kilometers(hu);
EXPECT_NEAR(m_direct, m_via_cm, 1e-12);
EXPECT_NEAR(km_direct, km_via_cm, 1e-15);
}
TEST(unit_test_source_engine_units, SupportsFloatAndDoubleTypes)
{
static_assert(std::is_same_v<decltype(omath::source_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::source_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::source_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::source_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_source_engine_units, ConstexprEvaluation)
{
constexpr double hu = 10.0;
constexpr double cm = omath::source_engine::units_to_centimeters(hu);
constexpr double m = omath::source_engine::units_to_meters(hu);
constexpr double km = omath::source_engine::units_to_kilometers(hu);
static_assert(cm == 25.4, "constexpr hu->cm failed");
static_assert(m == 0.254, "constexpr hu->m failed");
static_assert(km == 0.000254, "constexpr hu->km failed");
}
TEST(unit_test_source_engine, ForwardVector)
{
const auto forward = omath::source_engine::forward_vector({});

View File

@@ -8,6 +8,126 @@
#include <print>
#include <random>
TEST(unit_test_unity_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(1.0f), 0.01f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(100.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::units_to_centimeters(-250.0f), -2.5f);
}
TEST(unit_test_unity_engine, UnitsToMeters_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::unity_engine::units_to_meters(-42.0), -42.0);
}
TEST(unit_test_unity_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(1.0), 0.001, 1e-15);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(1000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::units_to_kilometers(-2500.0), -2.5, 1e-12);
}
TEST(unit_test_unity_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(0.01f), 1.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(1.0f), 100.0f);
EXPECT_FLOAT_EQ(omath::unity_engine::centimeters_to_units(-2.5f), -250.0f);
}
TEST(unit_test_unity_engine, MetersToUnits_BasicValues)
{
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(0.0), 0.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(1.0), 1.0);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(123.456), 123.456);
EXPECT_DOUBLE_EQ(omath::unity_engine::meters_to_units(-42.0), -42.0);
}
TEST(unit_test_unity_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(0.001), 1.0, 1e-12);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(1.0), 1000.0, 1e-9);
EXPECT_NEAR(omath::unity_engine::kilometers_to_units(-2.5), -2500.0, 1e-9);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
constexpr auto cm_f = omath::unity_engine::units_to_centimeters(units_f);
constexpr auto units_f_back = omath::unity_engine::centimeters_to_units(cm_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -987654.321;
constexpr auto cm_d = omath::unity_engine::units_to_centimeters(units_d);
constexpr auto units_d_back = omath::unity_engine::centimeters_to_units(cm_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::unity_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::unity_engine::meters_to_units(m_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::unity_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::unity_engine::meters_to_units(m_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_unity_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::unity_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::unity_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::unity_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::unity_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_unity_engine, ConversionChainConsistency)
{
constexpr double units = 424242.42;
constexpr auto cm_direct = omath::unity_engine::units_to_centimeters(units);
constexpr auto cm_via_units = units / 100.0;
EXPECT_NEAR(cm_direct, cm_via_units, 1e-12);
constexpr auto km_direct = omath::unity_engine::units_to_kilometers(units);
constexpr auto km_via_meters = omath::unity_engine::units_to_meters(units) / 1000.0;
EXPECT_NEAR(km_direct, km_via_meters, 1e-12);
}
TEST(unit_test_unity_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::unity_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unity_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::unity_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unity_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_unity_engine, ConstexprConversions)
{
constexpr double units = 1000.0;
constexpr double cm = omath::unity_engine::units_to_centimeters(units);
constexpr double m = omath::unity_engine::units_to_meters(units);
constexpr double km = omath::unity_engine::units_to_kilometers(units);
static_assert(cm == 10.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}
TEST(unit_test_unity_engine, ForwardVector)
{
const auto forward = omath::unity_engine::forward_vector({});

View File

@@ -238,4 +238,127 @@ TEST(unit_test_unreal_engine, loook_at_random_z_axis)
failed_points++;
}
EXPECT_LE(failed_points, 100);
}
TEST(unit_test_unreal_engine, UnitsToCentimeters_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(1.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(250.0f), 250.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::units_to_centimeters(-42.5f), -42.5f);
}
TEST(unit_test_unreal_engine, UnitsToMeters_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::units_to_meters(0.0), 0.0, 1e-15);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(1.0), 0.01, 1e-15);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(100.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::units_to_meters(-250.0), -2.5, 1e-12);
}
TEST(unit_test_unreal_engine, UnitsToKilometers_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(0.0), 0.0, 1e-18);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(1.0), 0.00001, 1e-18);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(100000.0), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::units_to_kilometers(-250000.0), -2.5, 1e-12);
}
TEST(unit_test_unreal_engine, CentimetersToUnits_BasicValues)
{
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(0.0f), 0.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(1.0f), 1.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(250.0f), 250.0f);
EXPECT_FLOAT_EQ(omath::unreal_engine::centimeters_to_units(-42.5f), -42.5f);
}
TEST(unit_test_unreal_engine, MetersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::meters_to_units(0.0), 0.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(0.01), 1.0, 1e-12);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(1.0), 100.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::meters_to_units(-2.5), -250.0, 1e-9);
}
TEST(unit_test_unreal_engine, KilometersToUnits_BasicValues)
{
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(0.0), 0.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(0.00001), 1.0, 1e-9);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(1.0), 100000.0, 1e-6);
EXPECT_NEAR(omath::unreal_engine::kilometers_to_units(-2.5), -250000.0, 1e-3);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsCentimeters)
{
constexpr float units_f = 12345.678f;
constexpr auto cm_f = omath::unreal_engine::units_to_centimeters(units_f);
constexpr auto units_f_back = omath::unreal_engine::centimeters_to_units(cm_f);
EXPECT_FLOAT_EQ(units_f_back, units_f);
constexpr double units_d = -987654.321;
constexpr auto cm_d = omath::unreal_engine::units_to_centimeters(units_d);
constexpr auto units_d_back = omath::unreal_engine::centimeters_to_units(cm_d);
EXPECT_DOUBLE_EQ(units_d_back, units_d);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsMeters)
{
constexpr float units_f = 5432.125f;
constexpr auto m_f = omath::unreal_engine::units_to_meters(units_f);
constexpr auto units_f_back = omath::unreal_engine::meters_to_units(m_f);
EXPECT_NEAR(units_f_back, units_f, 1e-3f);
constexpr double units_d = -123456.789;
constexpr auto m_d = omath::unreal_engine::units_to_meters(units_d);
constexpr auto units_d_back = omath::unreal_engine::meters_to_units(m_d);
EXPECT_NEAR(units_d_back, units_d, 1e-9);
}
TEST(unit_test_unreal_engine, RoundTrip_UnitsKilometers)
{
constexpr float units_f = 100000.0f;
constexpr auto km_f = omath::unreal_engine::units_to_kilometers(units_f);
constexpr auto units_f_back = omath::unreal_engine::kilometers_to_units(km_f);
EXPECT_NEAR(units_f_back, units_f, 1e-2f);
constexpr double units_d = -7654321.123;
constexpr auto km_d = omath::unreal_engine::units_to_kilometers(units_d);
constexpr auto units_d_back = omath::unreal_engine::kilometers_to_units(km_d);
EXPECT_NEAR(units_d_back, units_d, 1e-6);
}
TEST(unit_test_unreal_engine, ConversionChainConsistency)
{
constexpr double units = 424242.42;
constexpr auto cm_direct = omath::unreal_engine::units_to_centimeters(units);
constexpr auto cm_expected = units; // 1 uu == 1 cm
EXPECT_NEAR(cm_direct, cm_expected, 1e-12);
constexpr auto m_direct = omath::unreal_engine::units_to_meters(units);
constexpr auto m_via_cm = cm_direct / 100.0;
EXPECT_NEAR(m_direct, m_via_cm, 1e-12);
constexpr auto km_direct = omath::unreal_engine::units_to_kilometers(units);
constexpr auto km_via_m = m_direct / 1000.0;
EXPECT_NEAR(km_direct, km_via_m, 1e-15);
}
TEST(unit_test_unreal_engine, SupportsFloatAndDouble)
{
static_assert(std::is_same_v<decltype(omath::unreal_engine::units_to_centimeters(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::units_to_centimeters(1.0)), double>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::meters_to_units(1.0f)), float>);
static_assert(std::is_same_v<decltype(omath::unreal_engine::kilometers_to_units(1.0)), double>);
}
TEST(unit_test_unreal_engine, ConstexprConversions)
{
constexpr double units = 100000.0;
constexpr double cm = omath::unreal_engine::units_to_centimeters(units);
constexpr double m = omath::unreal_engine::units_to_meters(units);
constexpr double km = omath::unreal_engine::units_to_kilometers(units);
static_assert(cm == 100000.0, "units_to_centimeters constexpr failed");
static_assert(m == 1000.0, "units_to_meters constexpr failed");
static_assert(km == 1.0, "units_to_kilometers constexpr failed");
}

View File

@@ -47,7 +47,7 @@ namespace
// -----------------------------------------------------------------------------
struct TraceCase
{
Ray ray;
Ray<> ray;
bool expected_clear; // true => segment does NOT hit the triangle
friend std::ostream& operator<<(std::ostream& os, const TraceCase& tc)
{
@@ -66,7 +66,7 @@ namespace
TEST_P(CanTraceLineParam, VariousRays)
{
const auto& [ray, expected_clear] = GetParam();
EXPECT_EQ(LineTracer::can_trace_line(ray, triangle), expected_clear);
EXPECT_EQ(LineTracer<>::can_trace_line(ray, triangle), expected_clear);
}
INSTANTIATE_TEST_SUITE_P(
@@ -91,7 +91,7 @@ namespace
constexpr Ray ray{{0.3f, 0.3f, -1.f}, {0.3f, 0.3f, 1.f}};
constexpr Vec3 expected{0.3f, 0.3f, 0.f};
const Vec3 hit = LineTracer::get_ray_hit_point(ray, triangle);
const Vec3 hit = LineTracer<>::get_ray_hit_point(ray, triangle);
ASSERT_FALSE(vec_equal(hit, ray.end));
EXPECT_TRUE(vec_equal(hit, expected));
}
@@ -106,7 +106,7 @@ namespace
{1001.f, 1000.f, 1000.f},
{1000.f, 1001.f, 1000.f}};
EXPECT_TRUE(LineTracer::can_trace_line(short_ray, distant));
EXPECT_TRUE(LineTracer<>::can_trace_line(short_ray, distant));
}
TEST(unit_test_unity_engine, CantHit)
@@ -115,13 +115,13 @@ namespace
constexpr Ray ray{{}, {1.0, 0, 0}, false};
EXPECT_TRUE(omath::collision::LineTracer::can_trace_line(ray, triangle));
EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, triangle));
}
TEST(unit_test_unity_engine, CanHit)
{
constexpr omath::Triangle<Vector3<float>> triangle{{2, 0, 0}, {2, 2, 0}, {2, 2, 2}};
constexpr Ray ray{{}, {2.1, 0, 0}, false};
EXPECT_FALSE(omath::collision::LineTracer::can_trace_line(ray, triangle));
EXPECT_FALSE(omath::collision::LineTracer<>::can_trace_line(ray, triangle));
}
} // namespace

View File

@@ -15,9 +15,9 @@ TEST(LineTracerTests, ParallelRayReturnsEnd)
ray.end = Vector3<float>{1.f,1.f,1.f};
// For a ray parallel to the triangle plane the algorithm should return ray.end
const 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));
EXPECT_TRUE(omath::collision::LineTracer<>::can_trace_line(ray, tri));
}
TEST(LineTracerTests, MissesTriangleReturnsEnd)
@@ -27,7 +27,7 @@ TEST(LineTracerTests, MissesTriangleReturnsEnd)
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
const 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);
}
@@ -38,7 +38,7 @@ TEST(LineTracerTests, HitTriangleReturnsPointInsideSegment)
ray.start = Vector3<float>{0.25f,0.25f,-1.f};
ray.end = Vector3<float>{0.25f,0.25f,1.f};
const 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);
@@ -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.
const 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

@@ -10,7 +10,7 @@ TEST(LineTracerExtra, MissParallel)
{
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);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -18,7 +18,7 @@ TEST(LineTracerExtra, HitCenter)
{
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);
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);
@@ -30,7 +30,7 @@ TEST(LineTracerExtra, HitOnEdge)
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 };
// hitting exact vertex/edge may be considered miss; ensure function handles without crash
if (const auto hit = LineTracer::get_ray_hit_point(ray, tri); hit != ray.end)
if (const auto hit = LineTracer<>::get_ray_hit_point(ray, tri); hit != ray.end)
{
EXPECT_NEAR(hit.x, 0.0f, 1e-6f);
EXPECT_NEAR(hit.y, 0.0f, 1e-6f);
@@ -42,6 +42,6 @@ TEST(LineTracerExtra, InfiniteRayIgnoredIfBehind)
constexpr Triangle<Vector3<float>> tri({0,0,0},{1,0,0},{0,1,0});
// Ray pointing away but infinite_length true should be ignored
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);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}

View File

@@ -14,7 +14,7 @@ TEST(LineTracerMore, ParallelRayReturnsEnd)
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};
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -24,7 +24,7 @@ TEST(LineTracerMore, UOutOfRangeReturnsEnd)
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};
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -34,7 +34,7 @@ TEST(LineTracerMore, VOutOfRangeReturnsEnd)
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};
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -43,7 +43,7 @@ TEST(LineTracerMore, THitTooSmallReturnsEnd)
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};
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -53,7 +53,7 @@ TEST(LineTracerMore, THitGreaterThanOneReturnsEnd)
// 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};
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
constexpr float k_epsilon = std::numeric_limits<float>::epsilon();
constexpr auto side_a = tri.side_a_vector();
@@ -87,7 +87,7 @@ TEST(LineTracerMore, InfiniteLengthWithSmallTHitReturnsEnd)
// Create triangle slightly behind so t_hit <= eps
tri = tri2;
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -96,7 +96,7 @@ TEST(LineTracerMore, SuccessfulHitReturnsPoint)
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};
const 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

@@ -14,7 +14,7 @@ TEST(LineTracerMore2, UGreaterThanOneReturnsEnd)
// choose ray so barycentric u > 1
Ray ray; ray.start = {2.f, -1.f, -1.f}; ray.end = {2.f, -1.f, 1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -24,7 +24,7 @@ TEST(LineTracerMore2, VGreaterThanOneReturnsEnd)
// choose ray so barycentric v > 1
Ray ray; ray.start = {-1.f, 2.f, -1.f}; ray.end = {-1.f, 2.f, 1.f};
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -34,7 +34,7 @@ TEST(LineTracerMore2, UPlusVGreaterThanOneReturnsEnd)
// 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};
const auto hit = LineTracer::get_ray_hit_point(ray, tri);
const auto hit = LineTracer<>::get_ray_hit_point(ray, tri);
EXPECT_EQ(hit, ray.end);
}
@@ -52,6 +52,6 @@ TEST(LineTracerMore2, ZeroLengthRayHandled)
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
const 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

@@ -0,0 +1,358 @@
//
// Created by Copilot on 04.02.2026.
//
// Unit tests for MachOPatternScanner
#include <gtest/gtest.h>
#include <omath/utility/macho_pattern_scan.hpp>
#include <cstdint>
#include <cstring>
#include <fstream>
#include <vector>
using namespace omath;
namespace
{
// Mach-O magic numbers
constexpr std::uint32_t mh_magic_64 = 0xFEEDFACF;
constexpr std::uint32_t mh_magic_32 = 0xFEEDFACE;
constexpr std::uint32_t lc_segment = 0x1;
constexpr std::uint32_t lc_segment_64 = 0x19;
constexpr std::string_view segment_name = "__TEXT";
constexpr std::string_view section_name = "__text";
#pragma pack(push, 1)
struct MachHeader64
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
std::uint32_t reserved;
};
struct MachHeader32
{
std::uint32_t magic;
std::uint32_t cputype;
std::uint32_t cpusubtype;
std::uint32_t filetype;
std::uint32_t ncmds;
std::uint32_t sizeofcmds;
std::uint32_t flags;
};
struct SegmentCommand64
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint64_t vmaddr;
std::uint64_t vmsize;
std::uint64_t fileoff;
std::uint64_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
struct SegmentCommand32
{
std::uint32_t cmd;
std::uint32_t cmdsize;
char segname[16];
std::uint32_t vmaddr;
std::uint32_t vmsize;
std::uint32_t fileoff;
std::uint32_t filesize;
std::uint32_t maxprot;
std::uint32_t initprot;
std::uint32_t nsects;
std::uint32_t flags;
};
struct Section64
{
char sectname[16];
char segname[16];
std::uint64_t addr;
std::uint64_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
std::uint32_t reserved3;
};
struct Section32
{
char sectname[16];
char segname[16];
std::uint32_t addr;
std::uint32_t size;
std::uint32_t offset;
std::uint32_t align;
std::uint32_t reloff;
std::uint32_t nreloc;
std::uint32_t flags;
std::uint32_t reserved1;
std::uint32_t reserved2;
};
#pragma pack(pop)
// Helper function to create a minimal 64-bit Mach-O file with a __text section
bool write_minimal_macho64_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;
// Calculate sizes
constexpr std::size_t header_size = sizeof(MachHeader64);
constexpr std::size_t segment_size = sizeof(SegmentCommand64);
constexpr std::size_t section_size = sizeof(Section64);
constexpr std::size_t load_cmd_size = segment_size + section_size;
// Section data will start after headers
const std::size_t section_offset = header_size + load_cmd_size;
// Create Mach-O header
MachHeader64 header{};
header.magic = mh_magic_64;
header.cputype = 0x01000007; // CPU_TYPE_X86_64
header.cpusubtype = 0x3; // CPU_SUBTYPE_X86_64_ALL
header.filetype = 0x2; // MH_EXECUTE
header.ncmds = 1;
header.sizeofcmds = static_cast<std::uint32_t>(load_cmd_size);
header.flags = 0;
header.reserved = 0;
f.write(reinterpret_cast<const char*>(&header), sizeof(header));
// Create segment command
SegmentCommand64 segment{};
segment.cmd = lc_segment_64;
segment.cmdsize = static_cast<std::uint32_t>(load_cmd_size);
std::ranges::copy(segment_name, segment.segname);
segment.vmaddr = 0x100000000;
segment.vmsize = section_bytes.size();
segment.fileoff = section_offset;
segment.filesize = section_bytes.size();
segment.maxprot = 7; // VM_PROT_ALL
segment.initprot = 5; // VM_PROT_READ | VM_PROT_EXECUTE
segment.nsects = 1;
segment.flags = 0;
f.write(reinterpret_cast<const char*>(&segment), sizeof(segment));
// Create section
Section64 section{};
std::ranges::copy(section_name, section.sectname);
std::ranges::copy(segment_name, segment.segname);
section.addr = 0x100000000;
section.size = section_bytes.size();
section.offset = static_cast<std::uint32_t>(section_offset);
section.align = 0;
section.reloff = 0;
section.nreloc = 0;
section.flags = 0;
section.reserved1 = 0;
section.reserved2 = 0;
section.reserved3 = 0;
f.write(reinterpret_cast<const char*>(&section), sizeof(section));
// Write section data
f.write(reinterpret_cast<const char*>(section_bytes.data()), static_cast<std::streamsize>(section_bytes.size()));
f.close();
return true;
}
// Helper function to create a minimal 32-bit Mach-O file with a __text section
bool write_minimal_macho32_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;
// Calculate sizes
constexpr std::size_t header_size = sizeof(MachHeader32);
constexpr std::size_t segment_size = sizeof(SegmentCommand32);
constexpr std::size_t section_size = sizeof(Section32);
constexpr std::size_t load_cmd_size = segment_size + section_size;
// Section data will start after headers
constexpr std::size_t section_offset = header_size + load_cmd_size;
// Create Mach-O header
MachHeader32 header{};
header.magic = mh_magic_32;
header.cputype = 0x7; // CPU_TYPE_X86
header.cpusubtype = 0x3; // CPU_SUBTYPE_X86_ALL
header.filetype = 0x2; // MH_EXECUTE
header.ncmds = 1;
header.sizeofcmds = static_cast<std::uint32_t>(load_cmd_size);
header.flags = 0;
f.write(reinterpret_cast<const char*>(&header), sizeof(header));
// Create segment command
SegmentCommand32 segment{};
segment.cmd = lc_segment;
segment.cmdsize = static_cast<std::uint32_t>(load_cmd_size);
std::ranges::copy(segment_name, segment.segname);
segment.vmaddr = 0x1000;
segment.vmsize = static_cast<std::uint32_t>(section_bytes.size());
segment.fileoff = static_cast<std::uint32_t>(section_offset);
segment.filesize = static_cast<std::uint32_t>(section_bytes.size());
segment.maxprot = 7; // VM_PROT_ALL
segment.initprot = 5; // VM_PROT_READ | VM_PROT_EXECUTE
segment.nsects = 1;
segment.flags = 0;
f.write(reinterpret_cast<const char*>(&segment), sizeof(segment));
// Create section
Section32 section{};
std::ranges::copy(section_name, section.sectname);
std::ranges::copy(segment_name, segment.segname);
section.addr = 0x1000;
section.size = static_cast<std::uint32_t>(section_bytes.size());
section.offset = static_cast<std::uint32_t>(section_offset);
section.align = 0;
section.reloff = 0;
section.nreloc = 0;
section.flags = 0;
section.reserved1 = 0;
section.reserved2 = 0;
f.write(reinterpret_cast<const char*>(&section), sizeof(section));
// Write section data
f.write(reinterpret_cast<const char*>(section_bytes.data()), static_cast<std::streamsize>(section_bytes.size()));
f.close();
return true;
}
} // namespace
// Test scanning for a pattern that exists in a 64-bit Mach-O file
TEST(unit_test_macho_pattern_scan_file, ScanFindsPattern64)
{
constexpr std::string_view path = "./test_minimal_macho64.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5, 0x90, 0x90}; // push rbp; mov rbp, rsp; nop; nop
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 0);
}
}
// Test scanning for a pattern that exists in a 32-bit Mach-O file
TEST(unit_test_macho_pattern_scan_file, ScanFindsPattern32)
{
constexpr std::string_view path = "./test_minimal_macho32.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x89, 0xE5, 0x90, 0x90}; // push ebp; mov ebp, esp; nop; nop
ASSERT_TRUE(write_minimal_macho32_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 0);
}
}
// Test scanning for a pattern that does not exist
TEST(unit_test_macho_pattern_scan_file, ScanMissingPattern)
{
constexpr std::string_view path = "./test_minimal_macho_missing.bin";
const std::vector<std::uint8_t> bytes = {0x00, 0x01, 0x02, 0x03};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "FF EE DD", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning for a pattern at a non-zero offset
TEST(unit_test_macho_pattern_scan_file, ScanPatternAtOffset)
{
constexpr std::string_view path = "./test_minimal_macho_offset.bin";
const std::vector<std::uint8_t> bytes = {0x90, 0x90, 0x90, 0x55, 0x48, 0x89, 0xE5}; // nops then pattern
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48 89 E5", "__text");
EXPECT_TRUE(res.has_value());
if (res.has_value())
{
EXPECT_EQ(res->target_offset, 3);
}
}
// Test scanning with wildcards
TEST(unit_test_macho_pattern_scan_file, ScanWithWildcard)
{
constexpr std::string_view path = "./test_minimal_macho_wildcard.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5, 0x90};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 ? 89 E5", "__text");
EXPECT_TRUE(res.has_value());
}
// Test scanning a non-existent file
TEST(unit_test_macho_pattern_scan_file, ScanNonExistentFile)
{
const auto res = MachOPatternScanner::scan_for_pattern_in_file("/non/existent/file.bin", "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning an invalid (non-Mach-O) file
TEST(unit_test_macho_pattern_scan_file, ScanInvalidFile)
{
constexpr std::string_view path = "./test_invalid_macho.bin";
std::ofstream f(path.data(), std::ios::binary);
const std::vector<std::uint8_t> garbage = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
f.write(reinterpret_cast<const char*>(garbage.data()), static_cast<std::streamsize>(garbage.size()));
f.close();
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning for a non-existent section
TEST(unit_test_macho_pattern_scan_file, ScanNonExistentSection)
{
constexpr std::string_view path = "./test_minimal_macho_nosect.bin";
const std::vector<std::uint8_t> bytes = {0x55, 0x48, 0x89, 0xE5};
ASSERT_TRUE(write_minimal_macho64_file(path.data(), bytes));
const auto res = MachOPatternScanner::scan_for_pattern_in_file(path, "55 48", "__nonexistent");
EXPECT_FALSE(res.has_value());
}
// Test scanning with null module base address
TEST(unit_test_macho_pattern_scan_loaded, ScanNullModule)
{
const auto res = MachOPatternScanner::scan_for_pattern_in_loaded_module(nullptr, "55 48", "__text");
EXPECT_FALSE(res.has_value());
}
// Test scanning in loaded module with invalid magic
TEST(unit_test_macho_pattern_scan_loaded, ScanInvalidMagic)
{
std::vector<std::uint8_t> invalid_data(256, 0x00);
const auto res = MachOPatternScanner::scan_for_pattern_in_loaded_module(invalid_data.data(), "55 48", "__text");
EXPECT_FALSE(res.has_value());
}

View File

@@ -12,5 +12,5 @@ TEST(test, test)
{0.f, 30.f, 0.f}, {}, omath::opengl_engine::k_abs_forward, omath::opengl_engine::k_abs_right);
omath::collision::Ray ray{.start = {0, 0, 0}, .end = {-100, 0, 0}};
std::ignore = omath::collision::LineTracer::get_ray_hit_point(ray, result);
std::ignore = omath::collision::LineTracer<>::get_ray_hit_point(ray, result);
}