mirror of
https://github.com/orange-cpp/omath.git
synced 2026-04-19 14:23:27 +00:00
Compare commits
15 Commits
v5.1.0.rc5
...
7567501f00
| Author | SHA1 | Date | |
|---|---|---|---|
| 7567501f00 | |||
| 46d999f846 | |||
| b54601132b | |||
| 5c8ce2d163 | |||
| 04a86739b4 | |||
| 575b411863 | |||
| 5a91151bc0 | |||
| 66d4df0524 | |||
| 54e14760ca | |||
| ee61c47d7d | |||
| d737aee1c5 | |||
| ef422f0a86 | |||
| e99ca0bc2b | |||
| 5f94e36965 | |||
| 29510cf9e7 |
3
.github/workflows/cmake-multi-platform.yml
vendored
3
.github/workflows/cmake-multi-platform.yml
vendored
@@ -370,6 +370,8 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cmake --preset ${{ matrix.preset }} \
|
cmake --preset ${{ matrix.preset }} \
|
||||||
|
-DCMAKE_C_COMPILER=$(xcrun --find clang) \
|
||||||
|
-DCMAKE_CXX_COMPILER=$(xcrun --find clang++) \
|
||||||
-DOMATH_BUILD_TESTS=ON \
|
-DOMATH_BUILD_TESTS=ON \
|
||||||
-DOMATH_BUILD_BENCHMARK=OFF \
|
-DOMATH_BUILD_BENCHMARK=OFF \
|
||||||
-DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || 'OFF' }} \
|
-DOMATH_ENABLE_COVERAGE=${{ matrix.coverage == true && 'ON' || 'OFF' }} \
|
||||||
@@ -380,6 +382,7 @@ jobs:
|
|||||||
run: cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
run: cmake --build cmake-build/build/${{ matrix.preset }} --target unit_tests omath
|
||||||
|
|
||||||
- name: Run unit_tests
|
- name: Run unit_tests
|
||||||
|
if: ${{ matrix.coverage != true }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: ./out/Release/unit_tests
|
run: ./out/Release/unit_tests
|
||||||
|
|
||||||
|
|||||||
62
.github/workflows/docs.yml
vendored
Normal file
62
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: Documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
paths:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'mkdocs.yml'
|
||||||
|
- '.github/workflows/docs.yml'
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
paths:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'mkdocs.yml'
|
||||||
|
- '.github/workflows/docs.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: docs-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Documentation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Install mkdocs and dependencies
|
||||||
|
run: pip install mkdocs mkdocs-bootswatch
|
||||||
|
|
||||||
|
- name: Build documentation
|
||||||
|
run: mkdocs build --strict
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: site/
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy to GitHub Pages
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -12,6 +12,35 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
##############################################################################
|
||||||
|
# 0) Documentation – MkDocs
|
||||||
|
##############################################################################
|
||||||
|
docs-release:
|
||||||
|
name: Documentation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Install mkdocs and dependencies
|
||||||
|
run: pip install mkdocs mkdocs-bootswatch
|
||||||
|
|
||||||
|
- name: Build documentation
|
||||||
|
run: mkdocs build --strict
|
||||||
|
|
||||||
|
- name: Package
|
||||||
|
run: tar -czf omath-docs.tar.gz -C site .
|
||||||
|
|
||||||
|
- name: Upload release asset
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: gh release upload "${{ github.event.release.tag_name }}" omath-docs.tar.gz --clobber
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# 1) Linux – Clang / Ninja
|
# 1) Linux – Clang / Ninja
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
Thanks to everyone who made this possible, including:
|
Thanks to everyone who made this possible, including:
|
||||||
|
|
||||||
- Saikari aka luadebug for VCPKG port and awesome new initial logo design.
|
- Saikari aka luadebug for VCPKG port and awesome new initial logo design.
|
||||||
- AmbushedRaccoon for telegram post about omath to boost repository activity.
|
|
||||||
- Billy O'Neal aka BillyONeal for fixing compilation issues due to C math library compatibility.
|
- Billy O'Neal aka BillyONeal for fixing compilation issues due to C math library compatibility.
|
||||||
- Alex2772 for reference of AUI declarative interface design for omath::hud
|
- Alex2772 for reference of AUI declarative interface design for omath::hud
|
||||||
|
|
||||||
|
|||||||
23
INSTALL.md
23
INSTALL.md
@@ -28,6 +28,29 @@ target("...")
|
|||||||
add_packages("omath")
|
add_packages("omath")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## <img width="28px" src="https://conan.io/favicon.png" /> Using Conan
|
||||||
|
**Note**: Support Conan for package management
|
||||||
|
1. Install [Conan](https://conan.io/downloads)
|
||||||
|
2. Run the following command to install the omath package:
|
||||||
|
```
|
||||||
|
conan install --requires="omath/[*]" --build=missing
|
||||||
|
```
|
||||||
|
conanfile.txt
|
||||||
|
```ini
|
||||||
|
[requires]
|
||||||
|
omath/[*]
|
||||||
|
|
||||||
|
[generators]
|
||||||
|
CMakeDeps
|
||||||
|
CMakeToolchain
|
||||||
|
```
|
||||||
|
CMakeLists.txt
|
||||||
|
```cmake
|
||||||
|
find_package(omath CONFIG REQUIRED)
|
||||||
|
target_link_libraries(main PRIVATE omath::omath)
|
||||||
|
```
|
||||||
|
For more details, see the [Conan documentation](https://docs.conan.io/2/).
|
||||||
|
|
||||||
## <img width="28px" src="https://github.githubassets.com/favicons/favicon.svg" /> Using prebuilt binaries (GitHub Releases)
|
## <img width="28px" src="https://github.githubassets.com/favicons/favicon.svg" /> Using prebuilt binaries (GitHub Releases)
|
||||||
|
|
||||||
**Note**: This is the fastest option if you don’t want to build from source.
|
**Note**: This is the fastest option if you don’t want to build from source.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Installation
|
# Installation Guide
|
||||||
|
|
||||||
## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg
|
## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg (recomended)
|
||||||
**Note**: Support vcpkg for package management
|
**Note**: Support vcpkg for package management
|
||||||
1. Install [vcpkg](https://github.com/microsoft/vcpkg)
|
1. Install [vcpkg](https://github.com/microsoft/vcpkg)
|
||||||
2. Run the following command to install the orange-math package:
|
2. Run the following command to install the orange-math package:
|
||||||
@@ -28,6 +28,69 @@ target("...")
|
|||||||
add_packages("omath")
|
add_packages("omath")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## <img width="28px" src="https://conan.io/favicon.png" /> Using Conan
|
||||||
|
**Note**: Support Conan for package management
|
||||||
|
1. Install [Conan](https://conan.io/downloads)
|
||||||
|
2. Run the following command to install the omath package:
|
||||||
|
```
|
||||||
|
conan install --requires="omath/[*]" --build=missing
|
||||||
|
```
|
||||||
|
conanfile.txt
|
||||||
|
```ini
|
||||||
|
[requires]
|
||||||
|
omath/[*]
|
||||||
|
|
||||||
|
[generators]
|
||||||
|
CMakeDeps
|
||||||
|
CMakeToolchain
|
||||||
|
```
|
||||||
|
CMakeLists.txt
|
||||||
|
```cmake
|
||||||
|
find_package(omath CONFIG REQUIRED)
|
||||||
|
target_link_libraries(main PRIVATE omath::omath)
|
||||||
|
```
|
||||||
|
For more details, see the [Conan documentation](https://docs.conan.io/2/).
|
||||||
|
|
||||||
|
## <img width="28px" src="https://github.githubassets.com/favicons/favicon.svg" /> Using prebuilt binaries (GitHub Releases)
|
||||||
|
|
||||||
|
**Note**: This is the fastest option if you don’t want to build from source.
|
||||||
|
|
||||||
|
1. **Go to the Releases page**
|
||||||
|
- Open the project’s GitHub **Releases** page and choose the latest version.
|
||||||
|
|
||||||
|
2. **Download the correct asset for your platform**
|
||||||
|
- Pick the archive that matches your OS and architecture (for example: Windows x64 / Linux x64 / macOS arm64).
|
||||||
|
|
||||||
|
3. **Extract the archive**
|
||||||
|
- You should end up with something like:
|
||||||
|
- `include/` (headers)
|
||||||
|
- `lib/` or `bin/` (library files / DLLs)
|
||||||
|
- sometimes `cmake/` (CMake package config)
|
||||||
|
|
||||||
|
4. **Use it in your project**
|
||||||
|
|
||||||
|
### Option A: CMake package (recommended if the release includes CMake config files)
|
||||||
|
If the extracted folder contains something like `lib/cmake/omath` or `cmake/omath`, you can point CMake to it:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# Example: set this to the extracted prebuilt folder
|
||||||
|
list(APPEND CMAKE_PREFIX_PATH "path/to/omath-prebuilt")
|
||||||
|
|
||||||
|
find_package(omath CONFIG REQUIRED)
|
||||||
|
target_link_libraries(main PRIVATE omath::omath)
|
||||||
|
```
|
||||||
|
### Option B: Manual include + link (works with any layout)
|
||||||
|
If there’s no CMake package config, link it manually:
|
||||||
|
```cmake
|
||||||
|
target_include_directories(main PRIVATE "path/to/omath-prebuilt/include")
|
||||||
|
|
||||||
|
# Choose ONE depending on what you downloaded:
|
||||||
|
# - Static library: .lib / .a
|
||||||
|
# - Shared library: .dll + .lib import (Windows), .so (Linux), .dylib (macOS)
|
||||||
|
|
||||||
|
target_link_directories(main PRIVATE "path/to/omath-prebuilt/lib")
|
||||||
|
target_link_libraries(main PRIVATE omath) # or the actual library filename
|
||||||
|
```
|
||||||
## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake
|
## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake
|
||||||
1. **Preparation**
|
1. **Preparation**
|
||||||
|
|
||||||
@@ -62,7 +125,7 @@ target("...")
|
|||||||
Use **\<platform\>-\<build configuration\>** preset to build suitable version for yourself. Like **windows-release** or **linux-release**.
|
Use **\<platform\>-\<build configuration\>** preset to build suitable version for yourself. Like **windows-release** or **linux-release**.
|
||||||
|
|
||||||
| Platform Name | Build Config |
|
| Platform Name | Build Config |
|
||||||
|---------------|---------------|
|
|---------------|---------------|
|
||||||
| windows | release/debug |
|
| windows | release/debug |
|
||||||
| linux | release/debug |
|
| linux | release/debug |
|
||||||
| darwin | release/debug |
|
| darwin | release/debug |
|
||||||
|
|||||||
103
include/omath/algorithm/targeting.hpp
Normal file
103
include/omath/algorithm/targeting.hpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
//
|
||||||
|
// Created by Vladislav on 19.03.2026.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "omath/linear_algebra/vector3.hpp"
|
||||||
|
#include <functional>
|
||||||
|
#include <iterator>
|
||||||
|
#include <optional>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
namespace omath::algorithm
|
||||||
|
{
|
||||||
|
template<class CameraType, std::input_or_output_iterator IteratorType, class FilterT>
|
||||||
|
requires std::is_invocable_r_v<bool, std::function<FilterT>, std::iter_reference_t<IteratorType>>
|
||||||
|
[[nodiscard]]
|
||||||
|
IteratorType get_closest_target_by_fov(const IteratorType& begin, const IteratorType& end, const CameraType& camera,
|
||||||
|
auto get_position,
|
||||||
|
const std::optional<std::function<FilterT>>& filter_func = std::nullopt)
|
||||||
|
{
|
||||||
|
auto best_target = end;
|
||||||
|
const auto& camera_angles = camera.get_view_angles();
|
||||||
|
const Vector2<float> camera_angles_vec = {camera_angles.pitch.as_degrees(), camera_angles.yaw.as_degrees()};
|
||||||
|
|
||||||
|
for (auto current = begin; current != end; current = std::next(current))
|
||||||
|
{
|
||||||
|
if (filter_func && !filter_func.value()(*current))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (best_target == end)
|
||||||
|
{
|
||||||
|
best_target = current;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto current_target_angles = camera.calc_look_at_angles(get_position(*current));
|
||||||
|
const auto best_target_angles = camera.calc_look_at_angles(get_position(*best_target));
|
||||||
|
|
||||||
|
const Vector2<float> current_angles_vec = {current_target_angles.pitch.as_degrees(),
|
||||||
|
current_target_angles.yaw.as_degrees()};
|
||||||
|
const Vector2<float> best_angles_vec = {best_target_angles.pitch.as_degrees(),
|
||||||
|
best_target_angles.yaw.as_degrees()};
|
||||||
|
|
||||||
|
const auto current_target_distance = camera_angles_vec.distance_to(current_angles_vec);
|
||||||
|
const auto best_target_distance = camera_angles_vec.distance_to(best_angles_vec);
|
||||||
|
if (current_target_distance < best_target_distance)
|
||||||
|
best_target = current;
|
||||||
|
}
|
||||||
|
return best_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class CameraType, std::ranges::range RangeType, class FilterT>
|
||||||
|
requires std::is_invocable_r_v<bool, std::function<FilterT>,
|
||||||
|
std::ranges::range_reference_t<const RangeType>>
|
||||||
|
[[nodiscard]]
|
||||||
|
auto get_closest_target_by_fov(const RangeType& range, const CameraType& camera,
|
||||||
|
auto get_position,
|
||||||
|
const std::optional<std::function<FilterT>>& filter_func = std::nullopt)
|
||||||
|
{
|
||||||
|
return get_closest_target_by_fov<CameraType, decltype(std::ranges::begin(range)), FilterT>(
|
||||||
|
std::ranges::begin(range), std::ranges::end(range), camera, get_position, filter_func);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── By world-space distance ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
template<std::input_or_output_iterator IteratorType, class FilterT>
|
||||||
|
requires std::is_invocable_r_v<bool, std::function<FilterT>, std::iter_reference_t<IteratorType>>
|
||||||
|
[[nodiscard]]
|
||||||
|
IteratorType get_closest_target_by_distance(const IteratorType& begin, const IteratorType& end,
|
||||||
|
const Vector3<float>& origin, auto get_position,
|
||||||
|
const std::optional<std::function<FilterT>>& filter_func = std::nullopt)
|
||||||
|
{
|
||||||
|
auto best_target = end;
|
||||||
|
|
||||||
|
for (auto current = begin; current != end; current = std::next(current))
|
||||||
|
{
|
||||||
|
if (filter_func && !filter_func.value()(*current))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (best_target == end)
|
||||||
|
{
|
||||||
|
best_target = current;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (origin.distance_to(get_position(*current)) < origin.distance_to(get_position(*best_target)))
|
||||||
|
best_target = current;
|
||||||
|
}
|
||||||
|
return best_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::ranges::range RangeType, class FilterT>
|
||||||
|
requires std::is_invocable_r_v<bool, std::function<FilterT>,
|
||||||
|
std::ranges::range_reference_t<const RangeType>>
|
||||||
|
[[nodiscard]]
|
||||||
|
auto get_closest_target_by_distance(const RangeType& range, const Vector3<float>& origin,
|
||||||
|
auto get_position,
|
||||||
|
const std::optional<std::function<FilterT>>& filter_func = std::nullopt)
|
||||||
|
{
|
||||||
|
return get_closest_target_by_distance<decltype(std::ranges::begin(range)), FilterT>(
|
||||||
|
std::ranges::begin(range), std::ranges::end(range), origin, get_position, filter_func);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace omath::algorithm
|
||||||
@@ -82,6 +82,11 @@ namespace omath::projection
|
|||||||
m_view_projection_matrix = std::nullopt;
|
m_view_projection_matrix = std::nullopt;
|
||||||
m_view_matrix = std::nullopt;
|
m_view_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]]
|
||||||
|
ViewAnglesType calc_look_at_angles(const Vector3<float>& look_to) const
|
||||||
|
{
|
||||||
|
return TraitClass::calc_look_at_angle(m_origin, look_to);
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
Vector3<float> get_forward() const noexcept
|
Vector3<float> get_forward() const noexcept
|
||||||
@@ -138,16 +143,16 @@ namespace omath::projection
|
|||||||
m_projection_matrix = std::nullopt;
|
m_projection_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_near_plane(const float near) noexcept
|
void set_near_plane(const float near_plane) noexcept
|
||||||
{
|
{
|
||||||
m_near_plane_distance = near;
|
m_near_plane_distance = near_plane;
|
||||||
m_view_projection_matrix = std::nullopt;
|
m_view_projection_matrix = std::nullopt;
|
||||||
m_projection_matrix = std::nullopt;
|
m_projection_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_far_plane(const float far) noexcept
|
void set_far_plane(const float far_plane) noexcept
|
||||||
{
|
{
|
||||||
m_far_plane_distance = far;
|
m_far_plane_distance = far_plane;
|
||||||
m_view_projection_matrix = std::nullopt;
|
m_view_projection_matrix = std::nullopt;
|
||||||
m_projection_matrix = std::nullopt;
|
m_projection_matrix = std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -199,9 +204,9 @@ namespace omath::projection
|
|||||||
|
|
||||||
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 bool auto_clip = true) const noexcept
|
||||||
{
|
{
|
||||||
const auto normalized_cords = world_to_view_port(world_position);
|
const auto normalized_cords = world_to_view_port(world_position, auto_clip);
|
||||||
|
|
||||||
if (!normalized_cords.has_value())
|
if (!normalized_cords.has_value())
|
||||||
return std::unexpected{normalized_cords.error()};
|
return std::unexpected{normalized_cords.error()};
|
||||||
@@ -262,7 +267,7 @@ namespace omath::projection
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::expected<Vector3<float>, Error>
|
[[nodiscard]] std::expected<Vector3<float>, Error>
|
||||||
world_to_view_port(const Vector3<float>& world_position) const noexcept
|
world_to_view_port(const Vector3<float>& world_position, const bool auto_clip = true) const noexcept
|
||||||
{
|
{
|
||||||
auto projected = get_view_projection_matrix()
|
auto projected = get_view_projection_matrix()
|
||||||
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(world_position);
|
* mat_column_from_vector<float, Mat4X4Type::get_store_ordering()>(world_position);
|
||||||
@@ -273,13 +278,13 @@ namespace omath::projection
|
|||||||
|
|
||||||
projected /= w;
|
projected /= w;
|
||||||
|
|
||||||
if (is_ndc_out_of_bounds(projected))
|
if (auto_clip && is_ndc_out_of_bounds(projected))
|
||||||
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
||||||
|
|
||||||
return Vector3<float>{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)};
|
return Vector3<float>{projected.at(0, 0), projected.at(1, 0), projected.at(2, 0)};
|
||||||
}
|
}
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::expected<Vector3<float>, Error> view_port_to_screen(const Vector3<float>& ndc) const noexcept
|
std::expected<Vector3<float>, Error> view_port_to_world(const Vector3<float>& ndc) const noexcept
|
||||||
{
|
{
|
||||||
const auto inv_view_proj = get_view_projection_matrix().inverted();
|
const auto inv_view_proj = get_view_projection_matrix().inverted();
|
||||||
|
|
||||||
@@ -304,7 +309,7 @@ namespace omath::projection
|
|||||||
[[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_start>(screen_pos));
|
return view_port_to_world(screen_to_ndc<screen_start>(screen_pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||||
|
|||||||
@@ -16,15 +16,42 @@ echo "[*] Output dir: ${OUTPUT_DIR}"
|
|||||||
# Find llvm tools - handle versioned names (Linux) and xcrun (macOS)
|
# Find llvm tools - handle versioned names (Linux) and xcrun (macOS)
|
||||||
find_llvm_tool() {
|
find_llvm_tool() {
|
||||||
local tool_name="$1"
|
local tool_name="$1"
|
||||||
|
|
||||||
# macOS: use xcrun
|
# First priority: derive from the actual compiler used by cmake (CMakeCache.txt).
|
||||||
|
# This guarantees the profraw format version matches the instrumented binary.
|
||||||
|
local cache_file="${BINARY_DIR}/CMakeCache.txt"
|
||||||
|
if [[ -f "$cache_file" ]]; then
|
||||||
|
local cmake_cxx
|
||||||
|
cmake_cxx=$(grep '^CMAKE_CXX_COMPILER:' "$cache_file" | cut -d= -f2)
|
||||||
|
if [[ -n "$cmake_cxx" && -x "$cmake_cxx" ]]; then
|
||||||
|
local tool_path
|
||||||
|
tool_path="$(dirname "$cmake_cxx")/${tool_name}"
|
||||||
|
if [[ -x "$tool_path" ]]; then
|
||||||
|
echo "$tool_path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# macOS: derive from xcrun clang as fallback
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
local clang_path
|
||||||
|
clang_path=$(xcrun --find clang 2>/dev/null)
|
||||||
|
if [[ -n "$clang_path" ]]; then
|
||||||
|
local tool_path
|
||||||
|
tool_path="$(dirname "$clang_path")/${tool_name}"
|
||||||
|
if [[ -x "$tool_path" ]]; then
|
||||||
|
echo "$tool_path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Fallback: xcrun
|
||||||
if xcrun --find "${tool_name}" &>/dev/null; then
|
if xcrun --find "${tool_name}" &>/dev/null; then
|
||||||
echo "xcrun ${tool_name}"
|
echo "xcrun ${tool_name}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try versioned names (Linux with LLVM 21, 20, 19, etc.)
|
# Try versioned names (Linux with LLVM 21, 20, 19, etc.)
|
||||||
for version in 21 20 19 18 17 ""; do
|
for version in 21 20 19 18 17 ""; do
|
||||||
local versioned_name="${tool_name}${version:+-$version}"
|
local versioned_name="${tool_name}${version:+-$version}"
|
||||||
@@ -33,7 +60,7 @@ find_llvm_tool() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -51,6 +78,18 @@ fi
|
|||||||
echo "[*] Using: ${LLVM_PROFDATA}"
|
echo "[*] Using: ${LLVM_PROFDATA}"
|
||||||
echo "[*] Using: ${LLVM_COV}"
|
echo "[*] Using: ${LLVM_COV}"
|
||||||
|
|
||||||
|
# Print version info for debugging version mismatches
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
echo "[*] Default clang: $(xcrun clang --version 2>&1 | head -1)"
|
||||||
|
# Show actual compiler used by the build (from CMakeCache.txt if available)
|
||||||
|
CACHE_FILE="${BINARY_DIR}/CMakeCache.txt"
|
||||||
|
if [[ -f "$CACHE_FILE" ]]; then
|
||||||
|
ACTUAL_CXX=$(grep '^CMAKE_CXX_COMPILER:' "$CACHE_FILE" | cut -d= -f2)
|
||||||
|
echo "[*] Build compiler: ${ACTUAL_CXX} ($(${ACTUAL_CXX} --version 2>&1 | head -1))"
|
||||||
|
fi
|
||||||
|
echo "[*] profdata: $(${LLVM_PROFDATA} show --version 2>&1 | head -1 || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
# Find test binary
|
# Find test binary
|
||||||
if [[ -z "${TEST_BINARY}" ]]; then
|
if [[ -z "${TEST_BINARY}" ]]; then
|
||||||
for path in \
|
for path in \
|
||||||
|
|||||||
260
tests/general/unit_test_targeting.cpp
Normal file
260
tests/general/unit_test_targeting.cpp
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
//
|
||||||
|
// Created by claude on 19.03.2026.
|
||||||
|
//
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <omath/algorithm/targeting.hpp>
|
||||||
|
#include <omath/engines/source_engine/camera.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
using Camera = omath::source_engine::Camera;
|
||||||
|
using ViewAngles = omath::source_engine::ViewAngles;
|
||||||
|
using Targets = std::vector<omath::Vector3<float>>;
|
||||||
|
using Iter = Targets::const_iterator;
|
||||||
|
using FilterSig = bool(const omath::Vector3<float>&);
|
||||||
|
|
||||||
|
constexpr auto k_fov = omath::Angle<float, 0.f, 180.f, omath::AngleFlags::Clamped>::from_degrees(90.f);
|
||||||
|
|
||||||
|
Camera make_camera(const omath::Vector3<float>& origin, float pitch_deg, float yaw_deg)
|
||||||
|
{
|
||||||
|
ViewAngles angles{
|
||||||
|
omath::source_engine::PitchAngle::from_degrees(pitch_deg),
|
||||||
|
omath::source_engine::YawAngle::from_degrees(yaw_deg),
|
||||||
|
omath::source_engine::RollAngle::from_degrees(0.f),
|
||||||
|
};
|
||||||
|
return Camera{origin, angles, {1920.f, 1080.f}, k_fov, 0.01f, 1000.f};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto get_pos = [](const omath::Vector3<float>& v) -> const omath::Vector3<float>& { return v; };
|
||||||
|
|
||||||
|
Iter find_closest(const Iter begin, const Iter end, const Camera& camera)
|
||||||
|
{
|
||||||
|
return omath::algorithm::get_closest_target_by_fov<Camera, Iter, FilterSig>(
|
||||||
|
begin, end, camera, get_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iter find_nearest(const Iter begin, const Iter end, const omath::Vector3<float>& origin)
|
||||||
|
{
|
||||||
|
return omath::algorithm::get_closest_target_by_distance<Iter, FilterSig>(
|
||||||
|
begin, end, origin, get_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, returns_end_for_empty_range)
|
||||||
|
{
|
||||||
|
const auto camera = make_camera({0, 0, 0}, 0.f, 0.f);
|
||||||
|
Targets targets;
|
||||||
|
|
||||||
|
EXPECT_EQ(find_closest(targets.cbegin(), targets.cend(), camera), targets.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, single_target_returns_that_target)
|
||||||
|
{
|
||||||
|
const auto camera = make_camera({0, 0, 0}, 0.f, 0.f);
|
||||||
|
Targets targets = {{100.f, 0.f, 0.f}};
|
||||||
|
|
||||||
|
EXPECT_EQ(find_closest(targets.cbegin(), targets.cend(), camera), targets.cbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, picks_closest_to_crosshair)
|
||||||
|
{
|
||||||
|
// Camera looking forward along +X (yaw=0, pitch=0 in source engine)
|
||||||
|
const auto camera = make_camera({0, 0, 0}, 0.f, 0.f);
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{100.f, 50.f, 0.f}, // off to the side
|
||||||
|
{100.f, 1.f, 0.f}, // nearly on crosshair
|
||||||
|
{100.f, -30.f, 0.f}, // off to the other side
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_closest(targets.cbegin(), targets.cend(), camera);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, picks_closest_with_vertical_offset)
|
||||||
|
{
|
||||||
|
const auto camera = make_camera({0, 0, 0}, 0.f, 0.f);
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{100.f, 0.f, 50.f}, // high above
|
||||||
|
{100.f, 0.f, 2.f}, // slightly above
|
||||||
|
{100.f, 0.f, 30.f}, // moderately above
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_closest(targets.cbegin(), targets.cend(), camera);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, respects_camera_direction)
|
||||||
|
{
|
||||||
|
// Camera looking along +Y (yaw=90)
|
||||||
|
const auto camera = make_camera({0, 0, 0}, 0.f, 90.f);
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{100.f, 0.f, 0.f}, // to the side relative to camera facing +Y
|
||||||
|
{0.f, 100.f, 0.f}, // directly in front
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_closest(targets.cbegin(), targets.cend(), camera);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, equidistant_targets_returns_first)
|
||||||
|
{
|
||||||
|
const auto camera = make_camera({0, 0, 0}, 0.f, 0.f);
|
||||||
|
|
||||||
|
// Two targets symmetric about the forward axis — same angular distance
|
||||||
|
Targets targets = {
|
||||||
|
{100.f, 10.f, 0.f},
|
||||||
|
{100.f, -10.f, 0.f},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_closest(targets.cbegin(), targets.cend(), camera);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
// First target should be selected (strict < means first wins on tie)
|
||||||
|
EXPECT_EQ(result, targets.cbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, camera_pitch_affects_selection)
|
||||||
|
{
|
||||||
|
// Camera looking upward (pitch < 0)
|
||||||
|
const auto camera = make_camera({0, 0, 0}, -40.f, 0.f);
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{100.f, 0.f, 0.f}, // on the horizon
|
||||||
|
{100.f, 0.f, 40.f}, // above, closer to where camera is looking
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_closest(targets.cbegin(), targets.cend(), camera);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, many_targets_picks_best)
|
||||||
|
{
|
||||||
|
const auto camera = make_camera({0, 0, 0}, 0.f, 0.f);
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{100.f, 80.f, 80.f},
|
||||||
|
{100.f, 60.f, 60.f},
|
||||||
|
{100.f, 40.f, 40.f},
|
||||||
|
{100.f, 20.f, 20.f},
|
||||||
|
{100.f, 0.5f, 0.5f}, // closest to crosshair
|
||||||
|
{100.f, 10.f, 10.f},
|
||||||
|
{100.f, 30.f, 30.f},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_closest(targets.cbegin(), targets.cend(), camera);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── get_closest_target_by_distance tests ────────────────────────────────────
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, distance_returns_end_for_empty_range)
|
||||||
|
{
|
||||||
|
Targets targets;
|
||||||
|
|
||||||
|
EXPECT_EQ(find_nearest(targets.cbegin(), targets.cend(), {0, 0, 0}), targets.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, distance_single_target)
|
||||||
|
{
|
||||||
|
Targets targets = {{50.f, 0.f, 0.f}};
|
||||||
|
|
||||||
|
EXPECT_EQ(find_nearest(targets.cbegin(), targets.cend(), {0, 0, 0}), targets.cbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, distance_picks_nearest)
|
||||||
|
{
|
||||||
|
const omath::Vector3<float> origin{0.f, 0.f, 0.f};
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{100.f, 0.f, 0.f}, // distance = 100
|
||||||
|
{10.f, 0.f, 0.f}, // distance = 10 (closest)
|
||||||
|
{50.f, 0.f, 0.f}, // distance = 50
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_nearest(targets.cbegin(), targets.cend(), origin);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, distance_considers_all_axes)
|
||||||
|
{
|
||||||
|
const omath::Vector3<float> origin{0.f, 0.f, 0.f};
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{30.f, 30.f, 30.f}, // distance = sqrt(2700) ~ 51.96
|
||||||
|
{50.f, 0.f, 0.f}, // distance = 50
|
||||||
|
{0.f, 0.f, 10.f}, // distance = 10 (closest)
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_nearest(targets.cbegin(), targets.cend(), origin);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, distance_from_nonzero_origin)
|
||||||
|
{
|
||||||
|
const omath::Vector3<float> origin{100.f, 100.f, 100.f};
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{0.f, 0.f, 0.f}, // distance = sqrt(30000) ~ 173
|
||||||
|
{105.f, 100.f, 100.f}, // distance = 5 (closest)
|
||||||
|
{200.f, 200.f, 200.f}, // distance = sqrt(30000) ~ 173
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_nearest(targets.cbegin(), targets.cend(), origin);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, distance_equidistant_returns_first)
|
||||||
|
{
|
||||||
|
const omath::Vector3<float> origin{0.f, 0.f, 0.f};
|
||||||
|
|
||||||
|
// Both targets at distance 100, symmetric
|
||||||
|
Targets targets = {
|
||||||
|
{100.f, 0.f, 0.f},
|
||||||
|
{-100.f, 0.f, 0.f},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_nearest(targets.cbegin(), targets.cend(), origin);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unit_test_targeting, distance_many_targets)
|
||||||
|
{
|
||||||
|
const omath::Vector3<float> origin{0.f, 0.f, 0.f};
|
||||||
|
|
||||||
|
Targets targets = {
|
||||||
|
{500.f, 0.f, 0.f},
|
||||||
|
{200.f, 200.f, 0.f},
|
||||||
|
{100.f, 100.f, 100.f},
|
||||||
|
{50.f, 50.f, 50.f},
|
||||||
|
{1.f, 1.f, 1.f}, // distance = sqrt(3) ~ 1.73 (closest)
|
||||||
|
{10.f, 10.f, 10.f},
|
||||||
|
{80.f, 0.f, 0.f},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = find_nearest(targets.cbegin(), targets.cend(), origin);
|
||||||
|
|
||||||
|
ASSERT_NE(result, targets.cend());
|
||||||
|
EXPECT_EQ(result, targets.cbegin() + 4);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user