Compare commits

...

34 Commits

Author SHA1 Message Date
orange ee955a7ac2 Merge pull request #102 from orange-cpp/feature/gjk_algorithm
Feature/gjk algorithm
2025-11-09 23:27:03 +03:00
orange 9783a63c9b forgot keyname 2025-11-09 23:23:13 +03:00
orange 369f2ee582 added other collision types 2025-11-09 22:39:09 +03:00
orange 7d6309c0dd style fix 2025-11-09 22:27:04 +03:00
orange 8f1998b0c5 removed redundant headers 2025-11-09 22:25:29 +03:00
orange 8e332f3979 fixed formating 2025-11-09 22:22:02 +03:00
orange 353fb290b7 added mesh class, added mesh trair 2025-11-09 22:10:56 +03:00
orange 4fe001b102 add mesh class 2025-11-09 21:28:00 +03:00
orange 71f59406a9 Refactors Simplex class for GJK algorithm
Simplifies Simplex initialization and accessors.

Ensures correct handling of collinearity within the simplex calculation, preventing issues when colliders share the same origin. This improves stability and reliability of the GJK algorithm.
2025-11-09 17:29:50 +03:00
orange a9d60675f3 Corrects transform order for collider
Reorders the transform application to translation, scale, then rotation.

This ensures the collider’s world matrix is constructed correctly, preventing potential scaling or rotation issues in the GJK algorithm being developed on this branch.
2025-11-09 17:15:01 +03:00
orange 14296e45c4 fix 2025-11-09 17:09:46 +03:00
orange 7f62cb6db3 Auto stash before checking out "origin/feature/gjk_algorithm" 2025-11-09 17:08:57 +03:00
orange e1ff9efc91 Adds mesh scaling to mesh collider
Updates the mesh collider to include a scale parameter, allowing for non-uniform scaling of the collision mesh.

This provides more flexibility in defining collision shapes and supports a wider range of scenarios.
2025-11-09 17:02:07 +03:00
orange c20bfe1f8c Adds GJK collision test with equal origins
Implements a new test case for the GJK algorithm
to verify collision detection when colliders share the same origin.
This enhances the robustness of collision detection in scenarios
where objects are positioned at the same location.
2025-11-09 16:57:52 +03:00
orange 1dbaa4d53b Handles collinear cases in simplex collision
Adds helper functions to address near-zero vectors and find perpendicular directions.

This prevents potential crashes and improves robustness when the origin lies on the line defined by the simplex edges during GJK collision detection.
2025-11-09 16:56:38 +03:00
orange 8875157c79 Refactor: Simplify GJK simplex handling
Removes the separate `Simplex` class and integrates its functionality directly into the `GjkAlgorithm`. This simplifies the code and reduces unnecessary overhead.

Updates tests to align with refactored implementation.
2025-11-09 16:02:13 +03:00
orange 82e78016e3 made final 2025-11-09 15:51:28 +03:00
orange 1aa0360ac4 fix 2025-11-09 15:50:07 +03:00
orange bbd181f12f updated 2025-11-09 15:39:11 +03:00
orange c0353cd9be updated test 2025-11-09 15:38:38 +03:00
orange da2d51be6d Refactors simplex handling in GJK algorithm
Updates simplex handling to use references for vertex access, avoiding unnecessary copies. This improves performance and clarity within the GJK algorithm.
2025-11-09 14:40:22 +03:00
orange 727a9cc07e patch 2025-11-09 14:23:34 +03:00
orange b33288555a Adds mat_scale function
Introduces a utility function to create a scaling matrix from a Vector3.
This simplifies the creation of scale transformations, particularly useful for the GJK algorithm implementation.
2025-11-09 14:19:08 +03:00
orange 4c525d5c31 Refactors GJK tetrahedron handling
Updates the `handle_tetrahedron` function to use const references for simplex points, improving efficiency and readability.

Corrects a potential bug where the `simplex` variable wasn't being correctly updated when recursively calling `handle_triangle`.

Also, const-qualifies `point_to_same_direction` for better safety.
2025-11-09 14:15:32 +03:00
orange 5c81533c06 Updates simplex iterator and size access
Changes the index type for accessing simplex points to `std::size_t` for consistency and safety.

Adds `[[nodiscard]]` attribute to `size()` and iterator functions to signal potential misuse and enable static analysis.

These updates are part of the GJK algorithm implementation.
2025-11-09 14:05:46 +03:00
orange 8f054316fc Implements GJK collision detection
Adds GJK algorithm implementation for detecting collisions between mesh colliders.

Includes mesh collider definition and unit tests for basic collision detection.

Provides a foundation for more complex collision handling and physics interactions.
2025-11-09 14:04:01 +03:00
orange 6873f355b8 Merge pull request #101 from orange-cpp/feature/added_pe_scan_example
Adds PE signature scanner example
2025-11-08 14:38:17 +03:00
orange 47b16a4fea Adds PE signature scanner example
Implements a basic example demonstrating how to scan for a given pattern within a PE file.

The example takes a file path, section, and signature as input and utilizes the `omath::PePatternScanner` to locate the signature within the specified section of the PE file.
2025-11-08 14:34:14 +03:00
orange fd889aabcc Merge pull request #100 from orange-cpp/feature/improved_scree_to_world
Improves screen to world conversion accuracy
2025-11-08 13:54:44 +03:00
orange 8aa2dca456 Improves screen to world conversion accuracy
Adds support for different screen origin configurations.

This change allows for more accurate conversion from screen coordinates to world coordinates by correctly handling different screen origin positions (top-left and bottom-left). Includes new unit tests to verify the functionality with both configurations.
2025-11-08 13:51:56 +03:00
orange 3d4d9c0bbf Update documentation links from HTTPS to HTTP 2025-11-08 07:43:28 +03:00
orange dc799281dd removed old banner 2025-11-03 17:17:54 +03:00
orange b6304b79f5 Merge pull request #99 from luadebug/macosx
Add macOS CI workflow and update CMake presets for vcpkg integration
2025-11-01 16:53:27 +03:00
Saikari be56fbf408 Add macOS CI workflow and update CMake presets for vcpkg integration 2025-11-01 16:44:40 +03:00
32 changed files with 956 additions and 34 deletions
@@ -84,3 +84,39 @@ jobs:
- name: Run unit_tests.exe - name: Run unit_tests.exe
shell: bash shell: bash
run: ./out/Release/unit_tests.exe run: ./out/Release/unit_tests.exe
##############################################################################
# 3) macOS AppleClang / Ninja
##############################################################################
macosx-build-and-test:
name: macOS (AppleClang)
runs-on: macOS-latest
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Install basic tool-chain with Homebrew
shell: bash
run: |
brew install cmake ninja
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
- name: Configure (cmake --preset)
shell: bash
run: cmake --preset darwin-release-vcpkg -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests"
- name: Build
shell: bash
run: cmake --build cmake-build/build/darwin-release-vcpkg --target unit_tests omath
- name: Run unit_tests
shell: bash
run: ./out/Release/unit_tests
+2 -2
View File
@@ -201,7 +201,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
@@ -215,7 +215,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
+16 -4
View File
@@ -127,7 +127,8 @@
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}", "binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
"installDir": "${sourceDir}/cmake-build/install/${presetName}", "installDir": "${sourceDir}/cmake-build/install/${presetName}",
"cacheVariables": { "cacheVariables": {
"CMAKE_CXX_COMPILER": "clang++" "CMAKE_CXX_COMPILER": "clang++",
"CMAKE_MAKE_PROGRAM": "ninja"
}, },
"condition": { "condition": {
"type": "equals", "type": "equals",
@@ -135,6 +136,17 @@
"rhs": "Darwin" "rhs": "Darwin"
} }
}, },
{
"name": "darwin-base-vcpkg",
"hidden": true,
"inherits": "darwin-base",
"cacheVariables": {
"OMATH_BUILD_VIA_VCPKG": "ON",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"VCPKG_INSTALLED_DIR": "${sourceDir}/cmake-build/vcpkg_installed",
"VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2"
}
},
{ {
"name": "darwin-debug", "name": "darwin-debug",
"displayName": "Darwin Debug", "displayName": "Darwin Debug",
@@ -146,7 +158,7 @@
{ {
"name": "darwin-debug-vcpkg", "name": "darwin-debug-vcpkg",
"displayName": "Darwin Debug", "displayName": "Darwin Debug",
"inherits": "darwin-base", "inherits": "darwin-base-vcpkg",
"cacheVariables": { "cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug" "CMAKE_BUILD_TYPE": "Debug"
} }
@@ -154,7 +166,7 @@
{ {
"name": "darwin-release", "name": "darwin-release",
"displayName": "Darwin Release", "displayName": "Darwin Release",
"inherits": "darwin-debug", "inherits": "darwin-base",
"cacheVariables": { "cacheVariables": {
"CMAKE_BUILD_TYPE": "Release" "CMAKE_BUILD_TYPE": "Release"
} }
@@ -162,7 +174,7 @@
{ {
"name": "darwin-release-vcpkg", "name": "darwin-release-vcpkg",
"displayName": "Darwin Release", "displayName": "Darwin Release",
"inherits": "darwin-debug", "inherits": "darwin-base-vcpkg",
"cacheVariables": { "cacheVariables": {
"CMAKE_BUILD_TYPE": "Release" "CMAKE_BUILD_TYPE": "Release"
} }
+6 -6
View File
@@ -113,12 +113,12 @@ if (auto screen = camera.world_to_screen(world_position)) {
## 📚 Documentation ## 📚 Documentation
- **[Getting Started Guide](https://libomath.org/getting_started/)** - Installation and first steps - **[Getting Started Guide](http://libomath.org/getting_started/)** - Installation and first steps
- **[API Overview](https://libomath.org/api_overview/)** - Complete API reference - **[API Overview](http://libomath.org/api_overview/)** - Complete API reference
- **[Tutorials](https://libomath.org/tutorials/)** - Step-by-step guides - **[Tutorials](http://libomath.org/tutorials/)** - Step-by-step guides
- **[FAQ](https://libomath.org/faq/)** - Common questions and answers - **[FAQ](http://libomath.org/faq/)** - Common questions and answers
- **[Troubleshooting](https://libomath.org/troubleshooting/)** - Solutions to common issues - **[Troubleshooting](http://libomath.org/troubleshooting/)** - Solutions to common issues
- **[Best Practices](https://libomath.org/best_practices/)** - Guidelines for effective usage - **[Best Practices](http://libomath.org/best_practices/)** - Guidelines for effective usage
## 🤝 Community & Support ## 🤝 Community & Support
BIN
View File
Binary file not shown.
+7 -2
View File
@@ -1,4 +1,9 @@
project(examples) project(examples)
add_executable(ExampleProjectionMatrixBuilder example_proj_mat_builder.cpp) add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp)
target_link_libraries(ExampleProjectionMatrixBuilder PRIVATE omath::omath) set_target_properties(example_projection_matrix_builder PROPERTIES CXX_STANDARD 26)
target_link_libraries(example_projection_matrix_builder PRIVATE omath::omath)
add_executable(example_signature_scan example_signature_scan.cpp)
set_target_properties(example_signature_scan PROPERTIES CXX_STANDARD 26)
target_link_libraries(example_signature_scan PRIVATE omath::omath)
+39
View File
@@ -0,0 +1,39 @@
//
// Created by Vlad on 11/8/2025.
//
#include <iostream>
#include <omath/utility/pe_pattern_scan.hpp>
#include <print>
int main()
{
std::println("OMATH Signature Scanner");
std::print("Enter path to PE file: ");
std::string file_path;
std::getline(std::cin, file_path); // allows spaces
std::print("Enter target section: ");
std::string section;
std::getline(std::cin, section);
std::print("Enter signature: ");
std::string signature;
std::getline(std::cin, signature);
std::println("[LOG] Performing scan....");
const auto result = omath::PePatternScanner::scan_for_pattern_in_file(file_path, signature, section);
if (!result)
{
std::println("[ERROR] Scan failed or signature was not found");
return -1;
}
std::println("Found at virtual 0x{:x} , raw 0x{:x}", result->virtual_base_addr + result->target_offset,
result->raw_base_addr + result->target_offset);
return 0;
}
+102
View File
@@ -0,0 +1,102 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "omath/linear_algebra/triangle.hpp"
#include <omath/linear_algebra/mat.hpp>
#include <omath/linear_algebra/vector3.hpp>
#include <utility>
#include <vector>
namespace omath::primitives
{
template<class Mat4X4, class RotationAngles, class MeshTypeTrait, class Type = float>
class Mesh final
{
public:
using NumericType = Type;
private:
using Vbo = std::vector<Vector3<NumericType>>;
using Vao = std::vector<Vector3<std::size_t>>;
public:
Vbo m_vertex_buffer;
Vao m_vertex_array_object;
Mesh(Vbo vbo, Vao vao, const Vector3<NumericType> scale = {1, 1, 1,})
: m_vertex_buffer(std::move(vbo)), m_vertex_array_object(std::move(vao)), m_scale(scale)
{
}
void set_origin(const Vector3<NumericType>& new_origin)
{
m_origin = new_origin;
m_to_world_matrix = std::nullopt;
}
void set_scale(const Vector3<NumericType>& new_scale)
{
m_scale = new_scale;
m_to_world_matrix = std::nullopt;
}
void set_rotation(const RotationAngles& new_rotation_angles)
{
m_rotation_angles = new_rotation_angles;
m_to_world_matrix = std::nullopt;
}
[[nodiscard]]
const Vector3<NumericType>& get_origin() const
{
return m_origin;
}
[[nodiscard]]
const Vector3<NumericType>& get_scale() const
{
return m_scale;
}
[[nodiscard]]
const RotationAngles& get_rotation_angles() const
{
return m_rotation_angles;
}
[[nodiscard]]
const Mat4X4& get_to_world_matrix() const
{
if (m_to_world_matrix)
return m_to_world_matrix.value();
m_to_world_matrix =
mat_translation(m_origin) * mat_scale(m_scale) * MeshTypeTrait::rotation_matrix(m_rotation_angles);
return m_to_world_matrix.value();
}
[[nodiscard]]
Vector3<float> vertex_to_world_space(const Vector3<float>& vertex) const
{
auto abs_vec = get_to_world_matrix() * mat_column_from_vector(vertex);
return {abs_vec.at(0, 0), abs_vec.at(1, 0), abs_vec.at(2, 0)};
}
[[nodiscard]]
Triangle<Vector3<float>> make_face_in_world_space(const Vao::const_iterator vao_iterator) const
{
return {vertex_to_world_space(m_vertex_buffer.at(vao_iterator->x)),
vertex_to_world_space(m_vertex_buffer.at(vao_iterator->y)),
vertex_to_world_space(m_vertex_buffer.at(vao_iterator->z))};
}
private:
Vector3<NumericType> m_origin;
Vector3<NumericType> m_scale;
RotationAngles m_rotation_angles;
mutable std::optional<Mat4X4> m_to_world_matrix;
};
} // namespace omath::primitives
+49
View File
@@ -0,0 +1,49 @@
//
// Created by Vlad on 11/9/2025.
//
#pragma once
#include "mesh_collider.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include "simplex.hpp"
namespace omath::collision
{
template<class ColliderType>
class GjkAlgorithm final
{
public:
[[nodiscard]]
static ColliderType::VertexType find_support_vertex(const ColliderType& collider_a,
const ColliderType& collider_b,
const ColliderType::VertexType& direction)
{
return collider_a.find_abs_furthest_vertex(direction) - collider_b.find_abs_furthest_vertex(-direction);
}
[[nodiscard]]
static bool is_collide(const ColliderType& collider_a, const ColliderType& collider_b)
{
// Get initial support point in any direction
auto support = find_support_vertex(collider_a, collider_b, {1, 0, 0});
Simplex<typename ColliderType::VertexType> simplex;
simplex.push_front(support);
auto direction = -support;
while (true)
{
support = find_support_vertex(collider_a, collider_b, direction);
if (support.dot(direction) <= 0.f)
return false;
simplex.push_front(support);
if (simplex.handle(direction))
return true;
}
}
};
} // namespace omath::collision
+37
View File
@@ -0,0 +1,37 @@
//
// Created by Vlad on 11/9/2025.
//
#pragma once
#include "omath/linear_algebra/vector3.hpp"
namespace omath::collision
{
template<class MeshType>
class MeshCollider
{
public:
using NumericType = typename MeshType::NumericType;
using VertexType = Vector3<NumericType>;
explicit MeshCollider(MeshType mesh): m_mesh(std::move(mesh))
{
}
[[nodiscard]]
const Vector3<float>& find_furthest_vertex(const Vector3<float>& direction) const
{
return *std::ranges::max_element(m_mesh.m_vertex_buffer, [&direction](const auto& first, const auto& second)
{ return first.dot(direction) < second.dot(direction); });
}
[[nodiscard]]
Vector3<float> find_abs_furthest_vertex(const Vector3<float>& direction) const
{
return m_mesh.vertex_to_world_space(find_furthest_vertex(direction));
}
private:
MeshType m_mesh;
};
} // namespace omath::collision
+257
View File
@@ -0,0 +1,257 @@
#pragma once
#include "omath/linear_algebra/vector3.hpp"
#include <array>
#include <cassert>
#include <initializer_list>
#include <type_traits>
namespace omath::collision
{
// Minimal structural contract for the vector type used by GJK.
template<class V>
concept GjkVector = requires(const V& a, const V& b) {
{ -a } -> std::same_as<V>;
{ a - b } -> std::same_as<V>;
{ a.cross(b) } -> std::same_as<V>;
{ a.point_to_same_direction(b) } -> std::same_as<bool>;
};
template<GjkVector VectorType = Vector3<float>>
class Simplex final
{
std::array<VectorType, 4> m_points{};
std::size_t m_size{0};
public:
static constexpr std::size_t capacity = 4;
constexpr Simplex() = default;
constexpr Simplex& operator=(std::initializer_list<VectorType> list) noexcept
{
assert(list.size() <= capacity && "Simplex can have at most 4 points");
m_size = 0;
for (const auto& p : list)
m_points[m_size++] = p;
return *this;
}
constexpr void push_front(const VectorType& p) noexcept
{
const std::size_t limit = (m_size < capacity) ? m_size : capacity - 1;
for (std::size_t i = limit; i > 0; --i)
m_points[i] = m_points[i - 1];
m_points[0] = p;
if (m_size < capacity)
++m_size;
}
constexpr const VectorType& operator[](std::size_t i) const noexcept
{
return m_points[i];
}
constexpr VectorType& operator[](std::size_t i) noexcept
{
return m_points[i];
}
[[nodiscard]] constexpr std::size_t size() const noexcept
{
return m_size;
}
[[nodiscard]] constexpr bool empty() const noexcept
{
return m_size == 0;
}
[[nodiscard]] constexpr const VectorType& front() const noexcept
{
return m_points[0];
}
[[nodiscard]] constexpr const VectorType& back() const noexcept
{
return m_points[m_size - 1];
}
[[nodiscard]] constexpr const VectorType* data() const noexcept
{
return m_points.data();
}
[[nodiscard]] constexpr auto begin() const noexcept
{
return m_points.begin();
}
[[nodiscard]] constexpr auto end() const noexcept
{
return m_points.begin() + m_size;
}
constexpr void clear() noexcept
{
m_size = 0;
}
// GJK step: updates simplex + next search direction.
// Returns true iff the origin lies inside the tetrahedron.
[[nodiscard]] constexpr bool handle(VectorType& direction) noexcept
{
switch (m_size)
{
case 0:
return false;
case 1:
return handle_point(direction);
case 2:
return handle_line(direction);
case 3:
return handle_triangle(direction);
case 4:
return handle_tetrahedron(direction);
default:
std::unreachable();
}
}
private:
[[nodiscard]] constexpr bool handle_point(VectorType& direction) noexcept
{
const auto& a = m_points[0];
direction = -a;
return false;
}
template<class V>
static constexpr bool near_zero(const V& v, const float eps = 1e-7f)
{
return v.dot(v) <= eps * eps;
}
template<class V>
static constexpr V any_perp(const V& v)
{
// try cross with axes until non-zero
V d = v.cross(V{1, 0, 0});
if (near_zero(d))
d = v.cross(V{0, 1, 0});
if (near_zero(d))
d = v.cross(V{0, 0, 1});
return d;
}
constexpr bool handle_line(VectorType& direction)
{
const auto& a = m_points[0];
const auto& b = m_points[1];
const auto ab = b - a;
const auto ao = -a;
if (ab.point_to_same_direction(ao))
{
// 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;
}
return false;
}
[[nodiscard]] constexpr bool handle_triangle(VectorType& direction) noexcept
{
const auto& a = m_points[0];
const auto& b = m_points[1];
const auto& c = m_points[2];
const auto ab = b - a;
const auto ac = c - a;
const auto ao = -a;
const auto abc = ab.cross(ac);
// Region AC
if (abc.cross(ac).point_to_same_direction(ao))
{
if (ac.point_to_same_direction(ao))
{
*this = {a, c};
direction = ac.cross(ao).cross(ac);
return false;
}
*this = {a, b};
return handle_line(direction);
}
// Region AB
if (ab.cross(abc).point_to_same_direction(ao))
{
*this = {a, b};
return handle_line(direction);
}
// Above or below triangle
if (abc.point_to_same_direction(ao))
{
direction = abc;
}
else
{
*this = {a, c, b}; // flip winding
direction = -abc;
}
return false;
}
[[nodiscard]] constexpr bool handle_tetrahedron(VectorType& direction) noexcept
{
const auto& a = m_points[0];
const auto& b = m_points[1];
const auto& c = m_points[2];
const auto& d = m_points[3];
const auto ab = b - a;
const auto ac = c - a;
const auto ad = d - a;
const auto ao = -a;
const auto abc = ab.cross(ac);
const auto acd = ac.cross(ad);
const auto adb = ad.cross(ab);
if (abc.point_to_same_direction(ao))
{
*this = {a, b, c};
return handle_triangle(direction);
}
if (acd.point_to_same_direction(ao))
{
*this = {a, c, d};
return handle_triangle(direction);
}
if (adb.point_to_same_direction(ao))
{
*this = {a, d, b};
return handle_triangle(direction);
}
// Origin inside tetrahedron
return true;
}
};
} // namespace omath::collision
@@ -0,0 +1,12 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "constants.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "traits/mesh_trait.hpp"
namespace omath::frostbite_engine
{
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
@@ -0,0 +1,19 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include <omath/engines/frostbite_engine/constants.hpp>
#include <omath/engines/frostbite_engine/formulas.hpp>
namespace omath::frostbite_engine
{
class MeshTrait final
{
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
{
return frostbite_engine::rotation_matrix(rotation);
}
};
} // namespace omath::frostbite_engine
+12
View File
@@ -0,0 +1,12 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "constants.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "traits/mesh_trait.hpp"
namespace omath::iw_engine
{
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
@@ -0,0 +1,19 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include <omath/engines/iw_engine/constants.hpp>
#include <omath/engines/iw_engine/formulas.hpp>
namespace omath::iw_engine
{
class MeshTrait final
{
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
{
return iw_engine::rotation_matrix(rotation);
}
};
} // namespace omath::iw_engine
@@ -0,0 +1,12 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "constants.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "traits/mesh_trait.hpp"
namespace omath::opengl_engine
{
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
@@ -0,0 +1,19 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include <omath/engines/opengl_engine/constants.hpp>
#include <omath/engines/opengl_engine/formulas.hpp>
namespace omath::opengl_engine
{
class MeshTrait final
{
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
{
return opengl_engine::rotation_matrix(rotation);
}
};
} // namespace omath::opengl_engine
@@ -0,0 +1,13 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "mesh.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "omath/collision/mesh_collider.hpp"
namespace omath::source_engine
{
using MeshCollider = collision::MeshCollider<Mesh>;
}
@@ -0,0 +1,12 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "constants.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "traits/mesh_trait.hpp"
namespace omath::source_engine
{
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
@@ -0,0 +1,19 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include <omath/engines/source_engine/constants.hpp>
#include <omath/engines/source_engine/formulas.hpp>
namespace omath::source_engine
{
class MeshTrait final
{
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
{
return source_engine::rotation_matrix(rotation);
}
};
} // namespace omath::source_engine
@@ -0,0 +1,12 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "constants.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "traits/mesh_trait.hpp"
namespace omath::unity_engine
{
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
@@ -0,0 +1,19 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include <omath/engines/unity_engine/constants.hpp>
#include <omath/engines/unity_engine/formulas.hpp>
namespace omath::unity_engine
{
class MeshTrait final
{
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
{
return unity_engine::rotation_matrix(rotation);
}
};
} // namespace omath::unity_engine
@@ -0,0 +1,12 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include "constants.hpp"
#include "omath/3d_primitives/mesh.hpp"
#include "traits/mesh_trait.hpp"
namespace omath::unreal_engine
{
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
@@ -0,0 +1,19 @@
//
// Created by Vladislav on 09.11.2025.
//
#pragma once
#include <omath/engines/unreal_engine/constants.hpp>
#include <omath/engines/unreal_engine/formulas.hpp>
namespace omath::unreal_engine
{
class MeshTrait final
{
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
{
return unreal_engine::rotation_matrix(rotation);
}
};
} // namespace omath::unreal_engine
+11
View File
@@ -586,6 +586,17 @@ namespace omath
{0, 0, 0, 1}, {0, 0, 0, 1},
}; };
} }
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR>
[[nodiscard]]
constexpr Mat<4, 4, Type, St> mat_scale(const Vector3<Type>& scale) noexcept
{
return {
{scale.x, 0, 0, 0},
{0, scale.y, 0, 0},
{0, 0, scale.z, 0},
{0, 0, 0, 1},
};
}
template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle> template<class Type = float, MatStoreType St = MatStoreType::ROW_MAJOR, class Angle>
[[nodiscard]] [[nodiscard]]
+5
View File
@@ -216,6 +216,11 @@ namespace omath
return sum_2d() + z; return sum_2d() + z;
} }
[[nodiscard]]
bool point_to_same_direction(const Vector3& other) const
{
return dot(other) > static_cast<Type>(0);
}
[[nodiscard]] std::expected<Angle<float, 0.f, 180.f, AngleFlags::Clamped>, Vector3Error> [[nodiscard]] std::expected<Angle<float, 0.f, 180.f, AngleFlags::Clamped>, Vector3Error>
angle_between(const Vector3& other) const noexcept angle_between(const Vector3& other) const noexcept
{ {
+19 -5
View File
@@ -7,13 +7,17 @@
#include "omath/linear_algebra/mat.hpp" #include "omath/linear_algebra/mat.hpp"
#include "omath/linear_algebra/vector3.hpp" #include "omath/linear_algebra/vector3.hpp"
#include "omath/projection/error_codes.hpp" #include "omath/projection/error_codes.hpp"
#include <omath/trigonometry/angle.hpp>
#include <expected> #include <expected>
#include <omath/trigonometry/angle.hpp>
#include <type_traits> #include <type_traits>
#ifdef OMATH_BUILD_TESTS #ifdef OMATH_BUILD_TESTS
// ReSharper disable once CppInconsistentNaming // ReSharper disable CppInconsistentNaming
class UnitTestProjection_Projection_Test; class UnitTestProjection_Projection_Test;
class UnitTestProjection_ScreenToNdcTopLeft_Test;
class UnitTestProjection_ScreenToNdcBottomLeft_Test;
// ReSharper restore CppInconsistentNaming
#endif #endif
namespace omath::projection namespace omath::projection
@@ -52,6 +56,8 @@ namespace omath::projection
{ {
#ifdef OMATH_BUILD_TESTS #ifdef OMATH_BUILD_TESTS
friend UnitTestProjection_Projection_Test; friend UnitTestProjection_Projection_Test;
friend UnitTestProjection_ScreenToNdcTopLeft_Test;
friend UnitTestProjection_ScreenToNdcBottomLeft_Test;
#endif #endif
public: public:
enum class ScreenStart enum class ScreenStart
@@ -152,7 +158,6 @@ namespace omath::projection
return m_origin; return m_origin;
} }
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER> template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
[[nodiscard]] std::expected<Vector3<float>, Error> [[nodiscard]] std::expected<Vector3<float>, Error>
world_to_screen(const Vector3<float>& world_position) const noexcept world_to_screen(const Vector3<float>& world_position) const noexcept
@@ -206,17 +211,19 @@ namespace omath::projection
inverted_projection.at(2, 0)}; inverted_projection.at(2, 0)};
} }
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
[[nodiscard]] [[nodiscard]]
std::expected<Vector3<float>, Error> screen_to_world(const Vector3<float>& screen_pos) const noexcept std::expected<Vector3<float>, Error> screen_to_world(const Vector3<float>& screen_pos) const noexcept
{ {
return view_port_to_screen(screen_to_ndc(screen_pos)); return view_port_to_screen(screen_to_ndc<screen_start>(screen_pos));
} }
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
[[nodiscard]] [[nodiscard]]
std::expected<Vector3<float>, Error> screen_to_world(const Vector2<float>& screen_pos) const noexcept std::expected<Vector3<float>, Error> screen_to_world(const Vector2<float>& screen_pos) const noexcept
{ {
const auto& [x, y] = screen_pos; const auto& [x, y] = screen_pos;
return screen_to_world({x, y, 1.f}); return screen_to_world<screen_start>({x, y, 1.f});
} }
protected: protected:
@@ -286,10 +293,17 @@ namespace omath::projection
return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (ndc.y / 2.f + 0.5f) * m_view_port.m_height, ndc.z}; return {(ndc.x + 1.f) / 2.f * m_view_port.m_width, (ndc.y / 2.f + 0.5f) * m_view_port.m_height, ndc.z};
} }
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
[[nodiscard]] Vector3<float> screen_to_ndc(const Vector3<float>& screen_pos) const noexcept [[nodiscard]] Vector3<float> screen_to_ndc(const Vector3<float>& screen_pos) const noexcept
{ {
if constexpr (screen_start == ScreenStart::TOP_LEFT_CORNER)
return {screen_pos.x / m_view_port.m_width * 2.f - 1.f, 1.f - screen_pos.y / m_view_port.m_height * 2.f, return {screen_pos.x / m_view_port.m_width * 2.f - 1.f, 1.f - screen_pos.y / m_view_port.m_height * 2.f,
screen_pos.z}; screen_pos.z};
else if (screen_start == ScreenStart::BOTTOM_LEFT_CORNER)
return {screen_pos.x / m_view_port.m_width * 2.f - 1.f,
(screen_pos.y / m_view_port.m_height - 0.5f) * 2.f, screen_pos.z};
else
std::unreachable();
} }
}; };
} // namespace omath::projection } // namespace omath::projection
@@ -23,27 +23,26 @@ namespace omath::rev_eng
return *reinterpret_cast<Type*>(reinterpret_cast<std::uintptr_t>(this) + offset); return *reinterpret_cast<Type*>(reinterpret_cast<std::uintptr_t>(this) + offset);
} }
template<std::size_t id, class ReturnType, class... Args> template<std::size_t id, class ReturnType>
ReturnType call_virtual_method(Args&&... arg_list) ReturnType call_virtual_method(auto... arg_list)
{ {
#ifdef _MSC_VER #ifdef _MSC_VER
using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...); using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
#else #else
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...); using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
#endif #endif
return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, std::forward<Args>(arg_list)...); return (*reinterpret_cast<VirtualMethodType**>(this))[id](this, arg_list...);
} }
template<std::size_t id, class ReturnType, class... Args> template<std::size_t id, class ReturnType>
ReturnType call_virtual_method(Args&&... arg_list) const ReturnType call_virtual_method(auto... arg_list) const
{ {
using This = std::remove_cv_t<std::remove_pointer_t<decltype(this)>>;
#ifdef _MSC_VER #ifdef _MSC_VER
using VirtualMethodType = ReturnType(__thiscall*)(const void*, decltype(arg_list)...); using VirtualMethodType = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
#else #else
using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...); using VirtualMethodType = ReturnType (*)(void*, decltype(arg_list)...);
#endif #endif
return (*reinterpret_cast<VirtualMethodType**>(const_cast<This*>(this)))[id]( return (*static_cast<VirtualMethodType**>((void*)(this)))[id](
const_cast<void*>(static_cast<const void*>(this)), std::forward<Args>(arg_list)...); const_cast<void*>(static_cast<const void*>(this)), arg_list...);
} }
}; };
} // namespace omath::rev_eng } // namespace omath::rev_eng
+1 -1
View File
@@ -9,7 +9,7 @@
namespace omath namespace omath
{ {
struct Hsv struct Hsv final
{ {
float hue{}; float hue{};
float saturation{}; float saturation{};
+28
View File
@@ -0,0 +1,28 @@
//
// Created by Vlad on 11/9/2025.
//
#include "omath/engines/source_engine/collider.hpp"
#include <gtest/gtest.h>
#include <omath/collision/mesh_collider.hpp>
TEST(UnitTestColider, CheckToWorld)
{
omath::source_engine::Mesh mesh = {std::vector<omath::Vector3<float>>{{1.f, 1.f, 1.f}, {-1.f, -1.f, -1.f}}, {}};
mesh.set_origin({0, 2, 0});
const omath::source_engine::MeshCollider collider(mesh);
const auto vertex = collider.find_abs_furthest_vertex({1.f, 0.f, 0.f});
EXPECT_EQ(vertex, omath::Vector3<float>(1.f, 3.f, 1.f));
}
TEST(UnitTestColider, FindFurthestVertex)
{
const omath::source_engine::Mesh mesh = {{{1.f, 1.f, 1.f}, {-1.f, -1.f, -1.f}}, {}};
const omath::source_engine::MeshCollider collider(mesh);
const auto vertex = collider.find_furthest_vertex({1.f, 0.f, 0.f});
EXPECT_EQ(vertex, omath::Vector3<float>(1.f, 1.f, 1.f));
}
+58
View File
@@ -0,0 +1,58 @@
//
// Created by Vlad on 11/9/2025.
//
#include "omath/engines/source_engine/collider.hpp"
#include <gtest/gtest.h>
#include <omath/collision/gjk_algorithm.hpp>
#include <omath/engines/source_engine/mesh.hpp>
namespace
{
const omath::source_engine::Mesh mesh = {
{{-1.f, -1.f, -1.f},
{-1.f, -1.f, 1.f},
{-1.f, 1.f, -1.f},
{-1.f, 1.f, 1.f},
{1.f, 1.f, 1.f},
{1.f, 1.f, -1.f},
{1.f, -1.f, 1.f},
{1.f, -1.f, -1.f}},
{}};
}
TEST(UnitTestGjk, TestCollisionTrue)
{
const omath::source_engine::MeshCollider collider_a(mesh);
auto mesh_b = mesh;
mesh_b.set_origin({0.f, 0.5f, 0.f});
const omath::source_engine::MeshCollider collider_b(mesh_b);
using GjkAlgorithm = omath::collision::GjkAlgorithm<omath::source_engine::MeshCollider>;
const auto result = GjkAlgorithm::is_collide(collider_a, collider_b);
EXPECT_TRUE(result);
}
TEST(UnitTestGjk, TestCollisionFalse)
{
const omath::source_engine::MeshCollider collider_a(mesh);
auto mesh_b = mesh;
mesh_b.set_origin({0.f, 2.1f, 0.f});
const omath::source_engine::MeshCollider collider_b(mesh_b);
const auto result =
omath::collision::GjkAlgorithm<omath::source_engine::MeshCollider>::is_collide(collider_a, collider_b);
EXPECT_FALSE(result);
}
TEST(UnitTestGjk, TestCollisionEqualOrigin)
{
const omath::source_engine::MeshCollider collider_a(mesh);
const omath::source_engine::MeshCollider collider_b(mesh);
const auto result =
omath::collision::GjkAlgorithm<omath::source_engine::MeshCollider>::is_collide(collider_a, collider_b);
EXPECT_TRUE(result);
}
+74
View File
@@ -1,11 +1,13 @@
// //
// Created by Vlad on 27.08.2024. // Created by Vlad on 27.08.2024.
// //
#include "omath/engines/unity_engine/camera.hpp"
#include <complex> #include <complex>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <omath/engines/source_engine/camera.hpp> #include <omath/engines/source_engine/camera.hpp>
#include <omath/projection/camera.hpp> #include <omath/projection/camera.hpp>
#include <print> #include <print>
#include <random>
TEST(UnitTestProjection, Projection) TEST(UnitTestProjection, Projection)
{ {
@@ -23,3 +25,75 @@ TEST(UnitTestProjection, Projection)
EXPECT_NEAR(projected->y, 504.f, 0.001f); EXPECT_NEAR(projected->y, 504.f, 0.001f);
EXPECT_NEAR(projected->z, 1.f, 0.001f); EXPECT_NEAR(projected->z, 1.f, 0.001f);
} }
TEST(UnitTestProjection, ScreenToNdcTopLeft)
{
constexpr auto fov = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>::from_degrees(90.f);
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
0.01f, 1000.f);
using ScreenStart = omath::source_engine::Camera::ScreenStart;
const auto ndc_top_left = cam.screen_to_ndc<ScreenStart::TOP_LEFT_CORNER>({1500, 300, 1.f});
EXPECT_NEAR(ndc_top_left.x, 0.5625f, 0.0001f);
EXPECT_NEAR(ndc_top_left.y, 0.4444f, 0.0001f);
}
TEST(UnitTestProjection, ScreenToNdcBottomLeft)
{
constexpr auto fov = omath::projection::FieldOfView::from_degrees(60.f);
const auto cam = omath::unity_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.03f, 1000.f);
using ScreenStart = omath::unity_engine::Camera::ScreenStart;
const auto ndc_bottom_left =
cam.screen_to_ndc<ScreenStart::BOTTOM_LEFT_CORNER>({1263.53833f, 547.061523f, 0.99405992f});
EXPECT_NEAR(ndc_bottom_left.x, 0.974278628f, 0.0001f);
EXPECT_NEAR(ndc_bottom_left.y, 0.519615293f, 0.0001f);
}
TEST(UnitTestProjection, ScreenToWorldTopLeftCorner)
{
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
std::uniform_real_distribution dist_x(1.f, 1900.f);
std::uniform_real_distribution dist_y(1.f, 1070.f);
constexpr auto fov = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>::from_degrees(90.f);
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
0.01f, 1000.f);
using ScreenStart = omath::source_engine::Camera::ScreenStart;
for (int i = 0; i < 100; i++)
{
const auto initial_screen_cords = omath::Vector2{dist_x(gen), dist_y(gen)};
const auto world_cords = cam.screen_to_world<ScreenStart::TOP_LEFT_CORNER>(initial_screen_cords);
const auto screen_cords = cam.world_to_screen<ScreenStart::TOP_LEFT_CORNER>(world_cords.value());
EXPECT_NEAR(screen_cords->x, initial_screen_cords.x, 0.001f);
EXPECT_NEAR(screen_cords->y, initial_screen_cords.y, 0.001f);
}
}
TEST(UnitTestProjection, ScreenToWorldBottomLeftCorner)
{
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
std::uniform_real_distribution dist_x(1.f, 1900.f);
std::uniform_real_distribution dist_y(1.f, 1070.f);
constexpr auto fov = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>::from_degrees(90.f);
const auto cam = omath::source_engine::Camera({0, 0, 0}, omath::source_engine::ViewAngles{}, {1920.f, 1080.f}, fov,
0.01f, 1000.f);
using ScreenStart = omath::source_engine::Camera::ScreenStart;
for (int i = 0; i < 100; i++)
{
const auto initial_screen_cords = omath::Vector2{dist_x(gen), dist_y(gen)};
const auto world_cords = cam.screen_to_world<ScreenStart::BOTTOM_LEFT_CORNER>(initial_screen_cords);
const auto screen_cords = cam.world_to_screen<ScreenStart::BOTTOM_LEFT_CORNER>(world_cords.value());
EXPECT_NEAR(screen_cords->x, initial_screen_cords.x, 0.001f);
EXPECT_NEAR(screen_cords->y, initial_screen_cords.y, 0.001f);
}
}