mirror of
https://github.com/orange-cpp/omath.git
synced 2026-04-19 23:23:28 +00:00
Compare commits
15 Commits
66d4df0524
...
v5.1.0.rc6
| Author | SHA1 | Date | |
|---|---|---|---|
| 308f7ed481 | |||
| 8802ad9af1 | |||
| 2ac508d6e8 | |||
| eb1ca6055b | |||
| b528e41de3 | |||
| 8615ab2b7c | |||
| 5a4c042fec | |||
| 8063c1697a | |||
| 7567501f00 | |||
| 46d999f846 | |||
| b54601132b | |||
| 5c8ce2d163 | |||
| 04a86739b4 | |||
| 575b411863 | |||
| 5a91151bc0 |
7
.claude/settings.local.json
Normal file
7
.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(ls:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
4
.github/workflows/cmake-multi-platform.yml
vendored
4
.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' }} \
|
||||||
@@ -389,8 +391,6 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
brew install lcov
|
brew install lcov
|
||||||
# Clean stale profraw files before running coverage
|
|
||||||
find cmake-build/build/${{ matrix.preset }} -name "*.profraw" -delete 2>/dev/null || true
|
|
||||||
chmod +x scripts/coverage-llvm.sh
|
chmod +x scripts/coverage-llvm.sh
|
||||||
./scripts/coverage-llvm.sh \
|
./scripts/coverage-llvm.sh \
|
||||||
"${{ github.workspace }}" \
|
"${{ github.workspace }}" \
|
||||||
|
|||||||
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
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
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**
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,11 @@ namespace omath::projection
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
using FieldOfView = Angle<float, 0.f, 180.f, AngleFlags::Clamped>;
|
using FieldOfView = Angle<float, 0.f, 180.f, AngleFlags::Clamped>;
|
||||||
|
enum class ViewPortClipping
|
||||||
|
{
|
||||||
|
AUTO,
|
||||||
|
MANUAL,
|
||||||
|
};
|
||||||
template<class T, class MatType, class ViewAnglesType>
|
template<class T, class MatType, class ViewAnglesType>
|
||||||
concept CameraEngineConcept =
|
concept CameraEngineConcept =
|
||||||
requires(const Vector3<float>& cam_origin, const Vector3<float>& look_at, const ViewAnglesType& angles,
|
requires(const Vector3<float>& cam_origin, const Vector3<float>& look_at, const ViewAnglesType& angles,
|
||||||
@@ -218,6 +222,22 @@ namespace omath::projection
|
|||||||
else
|
else
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
|
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
|
||||||
|
[[nodiscard]] std::expected<Vector3<float>, Error>
|
||||||
|
world_to_screen_unclipped(const Vector3<float>& world_position) const noexcept
|
||||||
|
{
|
||||||
|
const auto normalized_cords = world_to_view_port(world_position, ViewPortClipping::MANUAL);
|
||||||
|
|
||||||
|
if (!normalized_cords.has_value())
|
||||||
|
return std::unexpected{normalized_cords.error()};
|
||||||
|
|
||||||
|
if constexpr (screen_start == ScreenStart::TOP_LEFT_CORNER)
|
||||||
|
return ndc_to_screen_position_from_top_left_corner(*normalized_cords);
|
||||||
|
else if constexpr (screen_start == ScreenStart::BOTTOM_LEFT_CORNER)
|
||||||
|
return ndc_to_screen_position_from_bottom_left_corner(*normalized_cords);
|
||||||
|
else
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool is_culled_by_frustum(const Triangle<Vector3<float>>& triangle) const noexcept
|
[[nodiscard]] bool is_culled_by_frustum(const Triangle<Vector3<float>>& triangle) const noexcept
|
||||||
{
|
{
|
||||||
@@ -267,24 +287,34 @@ 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 ViewPortClipping& clipping = ViewPortClipping::AUTO) 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);
|
||||||
|
|
||||||
const auto& w = projected.at(3, 0);
|
const auto& w = projected.at(3, 0);
|
||||||
if (w <= std::numeric_limits<float>::epsilon())
|
constexpr auto eps = std::numeric_limits<float>::epsilon();
|
||||||
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
if (w <= eps)
|
||||||
|
return std::unexpected(Error::PERSPECTIVE_DIVIDER_LESS_EQ_ZERO);
|
||||||
|
|
||||||
projected /= w;
|
projected /= w;
|
||||||
|
|
||||||
if (is_ndc_out_of_bounds(projected))
|
// ReSharper disable once CppTooWideScope
|
||||||
|
const auto clipped_automatically = clipping == ViewPortClipping::AUTO && is_ndc_out_of_bounds(projected);
|
||||||
|
if (clipped_automatically)
|
||||||
|
return std::unexpected(Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS);
|
||||||
|
|
||||||
|
// ReSharper disable once CppTooWideScope
|
||||||
|
const auto clipped_manually = clipping == ViewPortClipping::MANUAL && (projected.at(2, 0) < 0.0f - eps
|
||||||
|
|| projected.at(2, 0) > 1.0f + eps);
|
||||||
|
if (clipped_manually)
|
||||||
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();
|
||||||
|
|
||||||
@@ -309,7 +339,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>
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ namespace omath::projection
|
|||||||
{
|
{
|
||||||
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
|
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
|
||||||
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
|
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
|
||||||
|
PERSPECTIVE_DIVIDER_LESS_EQ_ZERO,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,23 @@ echo "[*] Output dir: ${OUTPUT_DIR}"
|
|||||||
find_llvm_tool() {
|
find_llvm_tool() {
|
||||||
local tool_name="$1"
|
local tool_name="$1"
|
||||||
|
|
||||||
# macOS: derive from the same directory as the active clang to guarantee
|
# First priority: derive from the actual compiler used by cmake (CMakeCache.txt).
|
||||||
# the profraw format version matches the compiler that instrumented the binary.
|
# 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
|
local clang_path
|
||||||
clang_path=$(xcrun --find clang 2>/dev/null)
|
clang_path=$(xcrun --find clang 2>/dev/null)
|
||||||
@@ -65,7 +80,13 @@ echo "[*] Using: ${LLVM_COV}"
|
|||||||
|
|
||||||
# Print version info for debugging version mismatches
|
# Print version info for debugging version mismatches
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
echo "[*] Compiler: $(xcrun clang --version | head -1)"
|
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)"
|
echo "[*] profdata: $(${LLVM_PROFDATA} show --version 2>&1 | head -1 || true)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
//
|
//
|
||||||
#ifdef OMATH_ENABLE_LUA
|
#ifdef OMATH_ENABLE_LUA
|
||||||
#include "omath/lua/lua.hpp"
|
#include "omath/lua/lua.hpp"
|
||||||
|
#include "omath/omath.hpp"
|
||||||
|
#include "omath/projection/error_codes.hpp"
|
||||||
#include <omath/engines/cry_engine/camera.hpp>
|
#include <omath/engines/cry_engine/camera.hpp>
|
||||||
#include <omath/engines/frostbite_engine/camera.hpp>
|
#include <omath/engines/frostbite_engine/camera.hpp>
|
||||||
#include <omath/engines/iw_engine/camera.hpp>
|
#include <omath/engines/iw_engine/camera.hpp>
|
||||||
@@ -33,6 +35,8 @@ namespace
|
|||||||
return "world position is out of screen bounds";
|
return "world position is out of screen bounds";
|
||||||
case omath::projection::Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
|
case omath::projection::Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
|
||||||
return "inverse view-projection matrix determinant is zero";
|
return "inverse view-projection matrix determinant is zero";
|
||||||
|
case omath::projection::Error::PERSPECTIVE_DIVIDER_LESS_EQ_ZERO:
|
||||||
|
return "perspective divider is less or equal to zero";
|
||||||
}
|
}
|
||||||
return "unknown error";
|
return "unknown error";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,126 @@ TEST(UnitTestProjection, ScreenToNdcBottomLeft)
|
|||||||
EXPECT_NEAR(ndc_bottom_left.y, 0.519615293f, 0.0001f);
|
EXPECT_NEAR(ndc_bottom_left.y, 0.519615293f, 0.0001f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestProjection, UnclippedWorldToScreenInBounds)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
const auto projected = cam.world_to_screen_unclipped({1000.f, 0, 50.f});
|
||||||
|
ASSERT_TRUE(projected.has_value());
|
||||||
|
EXPECT_NEAR(projected->x, 960.f, 0.001f);
|
||||||
|
EXPECT_NEAR(projected->y, 504.f, 0.001f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestProjection, UnclippedWorldToScreenMatchesWorldToScreenWhenInBounds)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
const auto w2s = cam.world_to_screen({1000.f, 0, 50.f});
|
||||||
|
const auto no_clip = cam.world_to_screen_unclipped({1000.f, 0, 50.f});
|
||||||
|
|
||||||
|
ASSERT_TRUE(w2s.has_value());
|
||||||
|
ASSERT_TRUE(no_clip.has_value());
|
||||||
|
EXPECT_NEAR(w2s->x, no_clip->x, 0.001f);
|
||||||
|
EXPECT_NEAR(w2s->y, no_clip->y, 0.001f);
|
||||||
|
EXPECT_NEAR(w2s->z, no_clip->z, 0.001f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestProjection, UnclippedWorldToScreenRejectsBehindCamera)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
const auto projected = cam.world_to_screen_unclipped({-1000.f, 0, 0});
|
||||||
|
EXPECT_FALSE(projected.has_value());
|
||||||
|
EXPECT_EQ(projected.error(), omath::projection::Error::PERSPECTIVE_DIVIDER_LESS_EQ_ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestProjection, UnclippedWorldToScreenAllowsOutOfBoundsNdc)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Point far to the side exceeds NDC [-1,1] bounds but unclipped returns it anyway
|
||||||
|
const auto projected = cam.world_to_screen_unclipped({100.f, 5000.f, 0});
|
||||||
|
EXPECT_TRUE(projected.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestProjection, WorldToScreenRejectsOutOfBoundsNdc)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Same point that unclipped allows — clipped world_to_screen rejects it
|
||||||
|
const auto projected = cam.world_to_screen({100.f, 5000.f, 0});
|
||||||
|
EXPECT_FALSE(projected.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestProjection, UnclippedWorldToScreenBottomLeftCorner)
|
||||||
|
{
|
||||||
|
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 top_left = cam.world_to_screen_unclipped<ScreenStart::TOP_LEFT_CORNER>({1000.f, 0, 50.f});
|
||||||
|
const auto bottom_left = cam.world_to_screen_unclipped<ScreenStart::BOTTOM_LEFT_CORNER>({1000.f, 0, 50.f});
|
||||||
|
|
||||||
|
ASSERT_TRUE(top_left.has_value());
|
||||||
|
ASSERT_TRUE(bottom_left.has_value());
|
||||||
|
// X should be identical, Y should differ (mirrored around center)
|
||||||
|
EXPECT_NEAR(top_left->x, bottom_left->x, 0.001f);
|
||||||
|
EXPECT_NEAR(top_left->y + bottom_left->y, 1080.f, 0.001f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestProjection, UnclippedWorldToScreenRoundTrip)
|
||||||
|
{
|
||||||
|
std::mt19937 gen(42);
|
||||||
|
std::uniform_real_distribution dist_fwd(100.f, 900.f);
|
||||||
|
std::uniform_real_distribution dist_side(-400.f, 400.f);
|
||||||
|
std::uniform_real_distribution dist_up(-200.f, 200.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);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
const omath::Vector3<float> world_pos{dist_fwd(gen), dist_side(gen), dist_up(gen)};
|
||||||
|
const auto screen = cam.world_to_screen_unclipped(world_pos);
|
||||||
|
if (!screen.has_value())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto back_to_world = cam.screen_to_world(screen.value());
|
||||||
|
ASSERT_TRUE(back_to_world.has_value());
|
||||||
|
|
||||||
|
const auto back_to_screen = cam.world_to_screen_unclipped(back_to_world.value());
|
||||||
|
ASSERT_TRUE(back_to_screen.has_value());
|
||||||
|
|
||||||
|
EXPECT_NEAR(screen->x, back_to_screen->x, 0.01f);
|
||||||
|
EXPECT_NEAR(screen->y, back_to_screen->y, 0.01f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UnitTestProjection, UnclippedWorldToScreenUnityEngine)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Point directly in front
|
||||||
|
const auto projected = cam.world_to_screen_unclipped<ScreenStart::BOTTOM_LEFT_CORNER>({0, 0, 500.f});
|
||||||
|
ASSERT_TRUE(projected.has_value());
|
||||||
|
EXPECT_NEAR(projected->x, 640.f, 0.5f);
|
||||||
|
EXPECT_NEAR(projected->y, 360.f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(UnitTestProjection, ScreenToWorldTopLeftCorner)
|
TEST(UnitTestProjection, ScreenToWorldTopLeftCorner)
|
||||||
{
|
{
|
||||||
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
|
std::mt19937 gen(std::random_device{}()); // Seed with a non-deterministic source
|
||||||
|
|||||||
Reference in New Issue
Block a user