Compare commits

..

189 Commits

Author SHA1 Message Date
94ee8751af Merge pull request #116 from luadebug/wasm
Add WASM support
2025-12-17 14:02:58 +03:00
Saikari
82b9b671f6 Add WASM support 2025-12-17 13:38:14 +03:00
082b5f69b8 Merge pull request #115 from luadebug/iphone
Add iOS support
2025-12-16 19:16:52 +03:00
Saikari
735a565446 Add iOS support 2025-12-16 14:48:20 +03:00
852bf5c56f Merge pull request #114 from luadebug/ndk
Add NDK support
2025-12-15 07:08:56 +03:00
Saikari
de5c8bc84d Add NDK support 2025-12-15 00:04:11 +03:00
35d9de1550 Merge pull request #113 from luadebug/freebsd
Add FreeBSD support
2025-12-14 22:57:07 +03:00
Saikari
201d8f5547 Add FreeBSD support 2025-12-14 22:48:29 +03:00
5a7b9d2338 Merge pull request #112 from orange-cpp/feature/test
fix
2025-12-14 12:31:45 +03:00
3d67827704 fix 2025-12-14 12:20:05 +03:00
45a37eb413 Merge pull request #111 from orange-cpp/feature/frustum_culling_method
added check method
2025-12-14 11:15:17 +03:00
90c4ea2036 removed nesting 2025-12-14 11:08:06 +03:00
e10cbf9356 added const 2025-12-14 11:06:15 +03:00
4ad44badb9 removed double size reserve 2025-12-14 11:05:24 +03:00
adce4a808a improved readability 2025-12-14 10:59:38 +03:00
257b06c552 extracted to methdod 2025-12-14 10:42:23 +03:00
a94c78f834 added nodiscard 2025-12-14 10:39:23 +03:00
b6ac0a1d61 decomposed into method 2025-12-14 10:26:36 +03:00
f3b74fe433 removed useless comment 2025-12-14 10:18:58 +03:00
2ddf29b158 style fix 2025-12-14 09:53:12 +03:00
bf30957acf renamed stuff 2025-12-14 09:50:46 +03:00
c9ac61935e updated preset file 2025-12-13 23:34:15 +03:00
60a3a42140 improved pmr stuff 2025-12-13 00:06:45 +03:00
17e21cde4b added missing include 2025-12-12 18:34:56 +03:00
7fb5ea47dd added check method 2025-12-09 10:21:54 +03:00
d7189eb7d4 added extra test 2025-12-09 10:16:57 +03:00
ff35571231 reverted is out of bounds check + added extra test 2025-12-09 10:06:25 +03:00
3744a6cdec returned stuff back 2025-12-09 09:31:22 +03:00
0fd9a5aed8 hot fix 2025-12-09 09:26:55 +03:00
3dd792c2d5 Merge pull request #110 from orange-cpp/feature/bug_fix
fixed bug due to on screen ndc check
2025-12-09 08:08:05 +03:00
584969da44 fixed bug due to on screen ndc check 2025-12-09 07:53:52 +03:00
acf36c3e04 Merge pull request #109 from orange-cpp/feature/collider_interface
Feature/collider interface
2025-12-06 14:59:34 +03:00
27c1d147c5 fixed naming 2025-12-06 14:49:30 +03:00
3831bc0999 removed file 2025-12-06 14:34:20 +03:00
d23bc3204d update 2025-12-06 14:30:44 +03:00
c158f08430 fix 2025-12-06 13:56:25 +03:00
e05eba42c3 added collider interface 2025-12-06 13:56:25 +03:00
e97be8c142 Merge pull request #108 from orange-cpp/fearure/epa_pmr
Fearure/epa pmr
2025-12-04 08:41:55 +03:00
e97d097b2b fix 2025-12-04 05:08:01 +03:00
58aa03c4a9 patch 2025-12-04 05:02:54 +03:00
e1399d1814 fix 2025-12-04 05:01:52 +03:00
1964d3d36f replaced with bool 2025-12-04 04:52:22 +03:00
d7a009eb67 oops 2025-12-04 04:47:34 +03:00
0e03805439 added nodiscard + static 2025-12-04 04:46:00 +03:00
eafefb40ec fixed typo 2025-12-04 04:45:15 +03:00
9e4c778e8f tweak 2025-12-04 04:38:44 +03:00
0788fd6122 replaced with emplace 2025-12-03 14:11:29 +03:00
3685f13344 back to static 2025-12-03 13:34:35 +03:00
d4d8f70fff switched to shared_ptr 2025-12-03 13:30:05 +03:00
918858e255 added poly allocators 2025-12-03 09:52:53 +03:00
1aff083ef3 fixed uv type 2025-12-01 05:03:43 +03:00
6414922884 added mesh culling 2025-11-30 06:19:38 +03:00
57ba809076 Auto stash before merge of "main" and "origin/main" 2025-11-30 01:32:25 +03:00
6fd3a695cf cleaned up code 2025-11-29 22:00:07 +03:00
f6857cac90 updated read me 2025-11-29 21:45:09 +03:00
b994e47357 Merge pull request #107 from orange-cpp/feature/example_improvement
fixed somebugs, improved tests
2025-11-29 21:41:43 +03:00
82b21d0458 fixed rotation 2025-11-29 21:34:40 +03:00
daa1abc047 added rotation by pitch 2025-11-29 21:31:20 +03:00
3a66b66c6a fixed somebugs, improved tests 2025-11-29 21:24:45 +03:00
6c89c72041 Merge pull request #106 from orange-cpp/feature/mesh_upgrade
Feature/mesh upgrade
2025-11-29 16:55:31 +03:00
e54d5e7388 changed ebo type 2025-11-29 16:49:19 +03:00
9a89e2467e fix 2025-11-29 16:37:30 +03:00
48bf06f69c refactored using stuff 2025-11-29 16:35:43 +03:00
8feddf872a added conecpt to method 2025-11-29 16:33:31 +03:00
99ebdeb188 added template arg to Vertex struct 2025-11-29 16:31:24 +03:00
ba267cbcb8 improved mesh class 2025-11-29 16:28:06 +03:00
b98093b244 removed newline 2025-11-14 18:20:25 +03:00
58392144ca Update version to 4.4.0 2025-11-14 14:35:58 +03:00
f1394a24e5 Bump version from 4.2.0 to 4.4.0 2025-11-14 12:12:23 +03:00
e396e00016 Revise Code of Conduct with comprehensive updates
Updated the Code of Conduct to include a universal declaration, preamble, and detailed articles outlining community rights, responsibilities, and enforcement strategies.
2025-11-14 00:30:55 +03:00
0dc4890107 Updates theme with custom link styles
Adds custom CSS to style links and the navbar,
improving the visual appearance of the documentation.

Disables unnecessary code inspection warnings to reduce clutter.
2025-11-13 23:47:17 +03:00
dd8c41b19f Merge pull request #105 from orange-cpp/feature/epa_improvement
Feature/epa improvement
2025-11-13 22:13:26 +03:00
0283935918 added std::optional 2025-11-13 21:25:45 +03:00
12f888b8d4 Calculates penetration vector for EPA algorithm
Adds `penetration_vector` to the `epa_result` struct to represent the direction and magnitude of penetration.

This allows for more accurate collision response calculations and simplifies access to penetration information.

Updates both the early-exit and iterative EPA calculations within `epa_algorithm.hpp` to compute and store the penetration vector, factoring in the relative origin of the colliding meshes.
2025-11-13 21:19:09 +03:00
a5b24f90dc Merge pull request #104 from orange-cpp/copilot/update-docs-new-classes
Add documentation for GJK/EPA collision detection and mesh primitives
2025-11-13 19:00:11 +03:00
copilot-swe-agent[bot]
df4947ceb3 Update API overview with new collision detection and mesh classes
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2025-11-13 15:21:37 +00:00
copilot-swe-agent[bot]
190a8bf91e Add documentation for collision detection and mesh classes
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2025-11-13 15:19:36 +00:00
copilot-swe-agent[bot]
d118e88f6b Initial plan 2025-11-13 15:10:10 +00:00
1553139a80 Merge pull request #103 from orange-cpp/feature/epa_algorithm
Feature/epa algorithm
2025-11-13 18:08:18 +03:00
798caa2b0d made ref 2025-11-13 17:34:39 +03:00
88d4447b20 add another test 2025-11-13 17:22:54 +03:00
ee458a24f7 fixed formating 2025-11-13 16:56:48 +03:00
fa91f21e39 Refactors collision detection with GJK and EPA
This commit refactors the collision detection pipeline to utilize a more standard GJK algorithm and simplifies the EPA implementation.

Removes the custom `GjkAlgorithmWithSimplex` in favor of the standalone `GjkAlgorithm`. This streamlines the collision detection process and enhances code clarity.

Updates unit tests to align with the new GJK implementation, ensuring continued functionality and correctness.
2025-11-13 16:39:26 +03:00
873bdd2036 Improves EPA test coverage
Adds a comprehensive unit test for the EPA algorithm,
validating collision detection, depth calculation, and
separation axis determination with a cube overlap scenario.

Refactors the test for clarity and robustness.
2025-11-13 16:34:14 +03:00
20aecac2ae Refactors vertex parameter name
Updates the `make_face` function to use `vertexes` instead of `verts` for clarity and consistency in naming conventions.
2025-11-13 16:16:23 +03:00
09fd92ccad Refactor EPA algorithm for clarity
Improves code organization and readability within the EPA algorithm implementation.

Changes include renaming variables for better semantic meaning (e.g., `verts` to `vertexes`), adding `final` specifiers to structs for clarity, and enhancing function signatures with `[[nodiscard]]` where appropriate.

These refactorings aim to enhance maintainability and understanding of the code without altering its core functionality.
2025-11-13 16:15:45 +03:00
2b21caf58f Refactors face initialization
Replaces `push_back` with `emplace_back` when initializing faces in the EPA algorithm. This avoids unnecessary copying and improves performance during face creation.
2025-11-13 16:07:57 +03:00
40e26be72e Refactors EPA algorithm loop
Replaces the `for(;;)` loop in the EPA algorithm with a `while(true)` loop for improved readability and clarity.

This change enhances the maintainability of the code without altering its functionality.
2025-11-13 16:06:18 +03:00
2699053102 fixed formating 2025-11-13 16:01:42 +03:00
06b597f37c added epa 2025-11-13 15:57:38 +03:00
6d3b543648 Refactors triangle calculations for generic vectors
Updates the `Triangle` class to use a generic `Vector` type instead of `Vector3`, enhancing flexibility and reusability.

Changes include updating relevant function return types to use `Vector::ContainedType` and adapting length and distance calculations accordingly.

This refactoring supports the ongoing work on the EPA algorithm (feature/epa_algorithm) by providing a more adaptable foundation for geometric calculations.
2025-11-13 15:29:10 +03:00
c515dc89a9 Adds GjkHitInfo and refactors collision detection
Introduces `GjkHitInfo` to encapsulate collision results, including the simplex.

Refactors `is_collide` to return the `GjkHitInfo` struct, providing more detailed collision data. This prepares the codebase for integrating the EPA algorithm (as per the feature branch) which requires simplex information.
2025-11-13 13:33:18 +03:00
66919af46a Refactors GJK algorithm vertex type
Simplifies the GJK algorithm by using a type alias for the vertex type, improving code readability and reducing redundancy. Removes unnecessary includes.
2025-11-11 23:37:56 +03:00
28ef194586 Adds nodiscard attribute to simplex functions
Applies the `[[nodiscard]]` attribute to several functions within the `omath::collision` namespace to improve code safety and signal potential misuse when return values are ignored. This encourages developers to handle the results of these functions appropriately.
2025-11-11 23:36:19 +03:00
05bc7577b5 Refactors perpendicular vector selection
Simplifies logic for finding a non-zero perpendicular vector by iterating through candidate directions. This improves readability and maintainability.
2025-11-11 23:34:30 +03:00
9efabd8fb2 Merge pull request #102 from orange-cpp/feature/gjk_algorithm
Feature/gjk algorithm
2025-11-09 23:27:03 +03:00
8f225c5f8e forgot keyname 2025-11-09 23:23:13 +03:00
44682b6f2c added other collision types 2025-11-09 22:39:09 +03:00
a6cf043d79 style fix 2025-11-09 22:27:04 +03:00
3968d13a19 removed redundant headers 2025-11-09 22:25:29 +03:00
a3f45628aa fixed formating 2025-11-09 22:22:02 +03:00
6da44a5a51 added mesh class, added mesh trair 2025-11-09 22:10:56 +03:00
e5d8e1c953 add mesh class 2025-11-09 21:28:00 +03:00
e2378bfa8b 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
ca3dab855b 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
79482d56d1 fix 2025-11-09 17:09:46 +03:00
c4024663bb Auto stash before checking out "origin/feature/gjk_algorithm" 2025-11-09 17:08:57 +03:00
b2a512eafe 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
7b0e2127dc 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
0b663b73d5 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
afc0720f08 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
015fc9b1e7 made final 2025-11-09 15:51:28 +03:00
62d1a615ae fix 2025-11-09 15:50:07 +03:00
043b5c588d updated 2025-11-09 15:39:11 +03:00
cd18b088cb updated test 2025-11-09 15:38:38 +03:00
ebfdd0b70e 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
e7b82f441c patch 2025-11-09 14:23:34 +03:00
6e59957247 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
1e540862a0 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
31cc1116ae 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
338246a618 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
10ebf6ed04 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
ec9a15927a 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
1a0e55b4cf 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
b48160e1b7 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
d4a16a94e6 Update documentation links from HTTPS to HTTP 2025-11-08 07:43:28 +03:00
07c0eebf21 removed old banner 2025-11-03 17:17:54 +03:00
9ed18f27c3 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
86b1e8a00d Add macOS CI workflow and update CMake presets for vcpkg integration 2025-11-01 16:44:40 +03:00
db9200d7b5 upgraded version 2025-11-01 14:07:06 +03:00
d894b62773 patch 2025-11-01 13:39:13 +03:00
ade281cdd2 Adds const version of rev_bar method
Adds a const overload for the `rev_bar` virtual method.
This allows calling the method on const instances of the reverse engineered class.
2025-11-01 13:28:51 +03:00
9bb94ee33b Merge pull request #96 from orange-cpp/feature/docs
Feature/docs
2025-11-01 13:07:58 +03:00
754b370d8b Renames documentation titles for consistency
Updates several documentation titles to a more consistent format.
Changes include renaming "Frequently Asked Questions (FAQ)" to "FAQ",
"Getting Started with OMath" to "Getting Started",
"Troubleshooting Guide" to "Troubleshooting", and
"OMath Tutorials" to "Tutorials".
```
2025-11-01 13:00:32 +03:00
d3e379e71b Removes dark overrides CSS file
Deletes the `dark-overrides.css` file.

This file appears to have been superseded by other theming mechanisms and is no longer needed. Its presence was causing potential conflicts.
2025-11-01 12:57:33 +03:00
6ed40a7bc1 Merge pull request #98 from orange-cpp/copilot/sub-pr-96
Comprehensive documentation overhaul with guides, tutorials, and cross-references
2025-11-01 12:56:05 +03:00
copilot-swe-agent[bot]
f3d5f84d2c Improve documentation with cross-references and README enhancements
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2025-11-01 09:54:08 +00:00
copilot-swe-agent[bot]
0b89c1d36d Add comprehensive documentation improvements
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2025-11-01 09:51:38 +00:00
copilot-swe-agent[bot]
b8ed0bd5a5 Initial plan 2025-11-01 09:40:43 +00:00
b9ae356e57 Merge pull request #97 from orange-cpp/copilot/sub-pr-96
Add documentation for game engine math modules
2025-11-01 12:40:09 +03:00
copilot-swe-agent[bot]
e351b64355 Add comprehensive documentation for all game engines
Co-authored-by: orange-cpp <59374393+orange-cpp@users.noreply.github.com>
2025-11-01 09:37:11 +00:00
copilot-swe-agent[bot]
f4df88bb7a Initial plan 2025-11-01 09:22:33 +00:00
8142d800c7 Styles navbar for wider display and consistent spacing
Widens the navbar container to accommodate more content.
Adjusts padding of navbar items for tighter spacing.
Prevents line wrapping and enables horizontal scrolling on smaller screens.
2025-11-01 12:14:36 +03:00
95c0873b8c Documents view angle struct and related API
Adds documentation for the `omath::ViewAngles` struct,
clarifying its purpose, common usage patterns,
and the definition of the types of pitch, yaw and roll.

Also, adds short explanations of how to use ViewAngles and what tradeoffs exist
between using raw float types and strongly typed Angle<> types.
2025-11-01 09:12:04 +03:00
d12a2611b8 Moves images to improved documentation structure
Moves various image files demonstrating libomath’s usage and featuring artwork from showcases to the improved `docs/images` structure to reorganize the project's documentation in a logical and maintainable fashion.

This change ensures consistency and simplifies updating documentation assets.
2025-10-31 16:47:28 +03:00
9212b1f51f Documents pattern scanning API
Adds comprehensive documentation for the pattern scanning API.

Details parsing behavior, complexity, and usage examples.
Includes troubleshooting tips and minimal test sketches.
Clarifies edge-case handling and implementation notes.
2025-10-31 16:25:56 +03:00
56505cf3e1 Adds documentation for ray-triangle intersection
Documents the `omath::collision::Ray` and `LineTracer` types, detailing their purpose, usage, and API.

Includes a description of the Möller–Trumbore algorithm
and provides usage examples for segment-triangle
and infinite ray intersection tests.

Relates to feature/docs
2025-10-31 16:17:32 +03:00
0510dd8328 Adds documentation for Vector4 and incorporates ImGui integration
Adds comprehensive documentation for the `Vector4` class, outlining constructors, operators, and utility functions. Includes detailed notes on `clamp` functionality and potential ImGui integration caveats. Incorporates optional ImGui integration with explanations for correct usage and potential improvements.
2025-10-31 16:14:20 +03:00
ce9758c86b Adds dark theme support to the OM documentation
Applies a dark theme to the documentation site using the `darkly` theme.
Provides a visually appealing dark mode option.
Configures colors to ensure readability and aesthetic consistency.
2025-10-31 16:06:03 +03:00
7d194a5c36 Merge pull request #95 from orange-cpp/pathfinding_improvement
removed brackets
2025-10-30 05:44:00 +03:00
eec34c1efb removed brackets
Initial plan

Initial exploration and analysis complete

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

Optimize performance: A* pathfinding, Vector3 hash, pattern scanner, AVX2 code, and serialization

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

Add bounds check for navigation mesh serialization

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

Document serialization limitation for large neighbor counts

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

Add _codeql_build_dir to gitignore

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

Removes codeql detected source root

Eliminates the automatically generated file that was causing issues.

This file was added by codeql and no longer needed.

revert
cleaned up gitignore

moved to anon namespace

Improves navigation mesh serialization and clamping

Ensures correct serialization of navigation meshes by clamping neighbor counts to prevent data corruption when exceeding uint16_t limits.

Updates data types to `std::uint8_t` and `std::size_t` for consistency.
Uses `std::copy_n` instead of `std::memcpy` for deserialization.
2025-10-30 05:38:58 +03:00
765d5e7216 fixed typo 2025-10-30 05:37:34 +03:00
7cb8a4de52 Clarifies release process for stable builds
Explains that changes merged into master won't reach stable users unless the `stable` tag is updated.
2025-10-28 01:21:41 +03:00
d2fbc286b1 Merge pull request #93 from orange-cpp/feaure/small_improvement
removed useless option
2025-10-28 01:19:21 +03:00
64385757af fix 2025-10-28 01:15:36 +03:00
ea7b812645 removed 2025-10-28 01:11:02 +03:00
b214cdf3a1 removed useless option 2025-10-28 01:09:47 +03:00
dccc12ee30 added some comments 2025-10-27 11:25:31 +03:00
60e744978c force disabled tests 2025-10-27 11:22:24 +03:00
495a2a80e4 Merge pull request #92 from orange-cpp/feature/projection_utility
added vcpkg integration
2025-10-27 11:11:28 +03:00
6fbc010fa1 considered to switch to v4 2025-10-27 11:05:55 +03:00
c8f77de4f2 updated version 2025-10-27 10:59:30 +03:00
5c2d09b175 patch 2025-10-27 10:58:26 +03:00
b3646ab708 Adds default and artifact registries
Configures the default Vcpkg registry and adds an artifact registry
for the microsoft catalog. This enables the tool to find and use
pre-built packages from the Microsoft catalog.
2025-10-27 10:57:57 +03:00
d751eaf132 Adds Darwin build presets with Vcpkg support
Adds new CMake presets for Darwin platforms, including debug and release configurations with optional Vcpkg integration.
This allows for easier builds on Darwin systems utilizing pre-built libraries from Vcpkg.
2025-10-27 09:57:12 +03:00
62e7ccb054 Update CMake configuration for Vcpkg integration and feature enablement.
Enables features and sets build configurations via Vcpkg manifest.
Adds new presets for Windows and Linux debug/release builds using Vcpkg.
Conditionally enables features (imgui, avx2, tests, benchmark) based on Vcpkg manifest features.
2025-10-27 09:55:49 +03:00
4d9e055bb3 Fixes invalid NT header check in variant
Ensures the NT header signature check uses a capture to avoid potential issues with variable scope.
2025-10-27 09:22:00 +03:00
9be7ca886d Disables Vcpkg build override
Disables the automatic override of build options by Vcpkg.

This prevents unintended consequences when using Vcpkg and allows for more control over the build process.
2025-10-27 09:19:51 +03:00
30568f3633 Disables tests and benchmarks by default for VCPKG
Changes the default state of `OMATH_BUILD_TESTS` and `OMATH_BUILD_BENCHMARK` to OFF when built via VCPKG.

Previously, these flags were inherited from the project's top level. This change ensures that these are explicitly enabled via VCPKG manifest features, providing better control over build configuration.
2025-10-27 09:19:09 +03:00
5f22499b66 Links tests to gtest when available
Conditionally links the test target to gtest when it is available,
rather than requiring the OMATH_BUILD_VIA_VCPKG flag to be false.
This allows for a more flexible test setup.
2025-10-27 09:05:40 +03:00
f75afb8354 fix 2025-10-27 08:59:42 +03:00
e38df8bd23 Removes baseline from omath vcpkg.json
Eliminates the `builtin-baseline` setting from the omath vcpkg.json file.
This setting is no longer necessary.
2025-10-27 08:56:37 +03:00
ae347c8509 fix 2025-10-27 08:51:58 +03:00
ee40979a2d fix 2025-10-27 08:41:22 +03:00
69d0b7b829 fix 2025-10-27 08:37:55 +03:00
490ccfe983 fix 2025-10-27 08:36:48 +03:00
5630c2708e Update build system and enable VCPKG
Migrates to CMake presets and enables VCPKG to manage dependencies.
Removes explicit submodule configuration.
Updates benchmark and googletest to be integrated or linked properly.
The goal is to ease the build process and reduce complexity related to linking and dependency management.
2025-10-27 08:34:05 +03:00
ed7220dc9c Configures CMake for Vcpkg integration
Sets up CMake presets to utilize the Vcpkg toolchain.

Specifies the Vcpkg root directory and manifest features.

Defines the installation directory for Vcpkg packages.

Forces the usage of Ninja as the make program.
2025-10-27 08:11:13 +03:00
5e4fae9e42 Enables Vcpkg usage for CMake builds
Configures CMake to utilize vcpkg for dependency management on both Linux and Windows platforms.
This ensures consistent build environments across different operating systems and simplifies the integration of external libraries.
2025-10-27 08:07:25 +03:00
063f43e74c fix 2025-10-27 08:03:42 +03:00
171b4ca3da Adds Vcpkg setup to CI workflows
Adds vcpkg setup for Arch Linux and Windows CI jobs.

This ensures consistent dependency management and builds via Vcpkg for both platforms and enables OMath to build via vcpkg.
Clones the vcpkg repository and bootstraps it during the job execution.
2025-10-27 08:00:53 +03:00
878e3358c0 Adds vcpkg integration and updates build configuration
Configures CMakePresets.json to utilize vcpkg for dependency management.
Adds support for building with vcpkg.

Adds error message for missing VCPKG_ROOT environment variable.
Adds explicit VCPKG_MANIFEST_FEATURES and VCPKG_INSTALLED_DIR to CMakePresets.json.
Adds benchmark dependency to vcpkg.json.
2025-10-27 07:53:53 +03:00
81aef68143 Adds support for tests and benchmarks via Vcpkg features
Enables building unit tests using GTest and benchmarks using Benchmark,
through Vcpkg features.

Adds `tests` and `benchmark` features to `vcpkg.json`,
and adds corresponding CMake logic to control their build based on
Vcpkg manifest features.
2025-10-27 06:42:43 +03:00
e2a37f2c81 Fix formatting and spacing in LICENSE file
Corrected formatting and removed extra spaces in the LICENSE file.
2025-10-27 02:32:06 +03:00
69575c1cb3 added additional check 2025-10-23 06:43:09 +03:00
8adf5db409 added vcpkg manifest file 2025-10-23 06:35:22 +03:00
2c7e443f52 added contained type alias 2025-10-23 06:26:57 +03:00
6ad4ef29e9 fix 2025-10-23 01:08:19 +03:00
8feb805067 Remove 'future' from affiliation clause in LICENSE 2025-10-23 01:00:40 +03:00
147 changed files with 16088 additions and 281 deletions

View File

@@ -56,7 +56,7 @@ SpaceBeforeParensOptions:
AfterIfMacros: true
AfterOverloadedOperator: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

View File

@@ -1,4 +1,4 @@
name: Omath CI (Arch Linux / Windows)
name: Omath CI
on:
push:
@@ -12,34 +12,39 @@ concurrency:
##############################################################################
# 1) ARCH LINUX Clang / Ninja
# 1) Arch Linux Clang / Ninja
##############################################################################
jobs:
arch-build-and-test:
name: Arch Linux (Clang)
name: Arch Linux (Clang) (x64-linux)
runs-on: ubuntu-latest
container: archlinux:latest
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Install basic tool-chain with pacman
shell: bash
run: |
pacman -Sy --noconfirm archlinux-keyring
pacman -Syu --noconfirm --needed \
git base-devel clang cmake ninja
git base-devel clang cmake ninja zip unzip fmt
- 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 linux-release -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF
run: cmake --preset linux-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/linux-release --target unit_tests omath
run: cmake --build cmake-build/build/linux-release-vcpkg --target unit_tests omath
- name: Run unit_tests
shell: bash
@@ -47,12 +52,14 @@ jobs:
##############################################################################
# 2) Windows MSVC / Ninja
##############################################################################
##############################################################################
# 2) Windows MSVC / Ninja
##############################################################################
windows-build-and-test:
name: Windows (MSVC)
name: Windows (MSVC) (x64-windows)
runs-on: windows-latest
env:
OMATH_BUILD_VIA_VCPKG: ON
steps:
- name: Checkout repository (with sub-modules)
@@ -68,12 +75,235 @@ jobs:
- name: Configure (cmake --preset)
shell: bash
run: cmake --preset windows-release -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF
run: cmake --preset windows-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/windows-release --target unit_tests omath
run: cmake --build cmake-build/build/windows-release-vcpkg --target unit_tests omath
- name: Run unit_tests.exe
shell: bash
run: ./out/Release/unit_tests.exe
##############################################################################
# 3) macOS AppleClang / Ninja
##############################################################################
macosx-build-and-test:
name: macOS (AppleClang) (arm64-osx)
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
##############################################################################
# 4) iOS AppleClang / Xcode / arm64-ios
##############################################################################
ios-build:
name: iOS (AppleClang) (arm64-ios)
runs-on: macOS-latest
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Install CMake tooling
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"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset ios-release-vcpkg \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
-DOMATH_BUILD_TESTS=ON \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui;tests"
- name: Build
shell: bash
run: |
cmake --build cmake-build/build/ios-release-vcpkg --config Release --target unit_tests omath
##############################################################################
# 5) FreeBSD Clang / Ninja
##############################################################################
freebsd-build-and-test:
name: FreeBSD (Clang) (x64-freebsd)
runs-on: ubuntu-latest
steps:
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build and Test
uses: vmactions/freebsd-vm@v1
with:
usesh: true
sync: sshfs
mem: 12288
copyback: false
prepare: pkg install -y git curl zip unzip gmake llvm gsed bash perl5 openssl 7-zip coreutils cmake ninja pkgconf patchelf
run: |
git config --global --add safe.directory `pwd`
# Build vcpkg in /tmp to avoid sshfs timestamp sync issues
# This prevents PCH timestamp mismatches during parallel builds
export VCPKG_ROOT=/tmp/vcpkg
rm -rf "$VCPKG_ROOT"
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
# Bootstrap vcpkg - it will build from source on FreeBSD
# Building in /tmp avoids sshfs timestamp issues that break PCH
./bootstrap-vcpkg.sh
cd -
export VCPKG_FORCE_SYSTEM_BINARIES=0
# VCPKG_ROOT is set to /tmp/vcpkg, which CMake will use via the preset
cmake --preset freebsd-release-vcpkg -DOMATH_BUILD_TESTS=ON -DOMATH_BUILD_BENCHMARK=OFF -DVCPKG_MANIFEST_FEATURES="imgui;avx2;tests" -DVCPKG_INSTALL_OPTIONS="--allow-unsupported"
cmake --build cmake-build/build/freebsd-release-vcpkg --target unit_tests omath
./out/Release/unit_tests
##############################################################################
# 5) Android NDK Clang / Ninja / arm64-android
##############################################################################
android-build-and-test:
name: Android NDK (arm64-android)
runs-on: ubuntu-latest
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
ANDROID_NDK_HOME: ${{ github.workspace }}/android-ndk
steps:
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Android NDK
shell: bash
run: |
NDK_VERSION="r28c"
NDK_ZIP="android-ndk-${NDK_VERSION}-linux.zip"
wget -q "https://dl.google.com/android/repository/${NDK_ZIP}"
unzip -q "${NDK_ZIP}" -d "${{ github.workspace }}"
mv "${{ github.workspace }}/android-ndk-${NDK_VERSION}" "$ANDROID_NDK_HOME"
rm "${NDK_ZIP}"
echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}" >> $GITHUB_ENV
echo "Android NDK installed at: ${ANDROID_NDK_HOME}"
ls -la "${ANDROID_NDK_HOME}" | head -5
- name: Install basic tool-chain
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ninja-build cmake
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset android-release-vcpkg \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
-DOMATH_BUILD_TESTS=ON \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui;tests"
- name: Build
shell: bash
run: |
cmake --build cmake-build/build/android-release-vcpkg --target unit_tests omath
##############################################################################
# 6) WebAssembly (Emscripten) Clang / Ninja / wasm32-emscripten
##############################################################################
wasm-build-and-test:
name: WebAssembly (Emscripten) (wasm32-emscripten)
runs-on: ubuntu-latest
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg
steps:
- name: Checkout repository (with sub-modules)
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install basic tool-chain
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y ninja-build
- name: Setup Emscripten
uses: mymindstorm/setup-emsdk@v14
with:
version: 'latest'
- name: Verify Emscripten
shell: bash
run: |
echo "EMSDK=$EMSDK"
emcc --version
# Verify toolchain file exists
ls -la "$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
- name: Set up vcpkg
shell: bash
run: |
git clone https://github.com/microsoft/vcpkg "$VCPKG_ROOT"
cd "$VCPKG_ROOT"
./bootstrap-vcpkg.sh
- name: Configure (cmake --preset)
shell: bash
run: |
cmake --preset wasm-release-vcpkg \
-DVCPKG_INSTALL_OPTIONS="--allow-unsupported" \
-DOMATH_BUILD_TESTS=ON \
-DOMATH_BUILD_BENCHMARK=OFF \
-DVCPKG_MANIFEST_FEATURES="imgui;tests"
- name: Build
shell: bash
run: |
cmake --build cmake-build/build/wasm-release-vcpkg --target unit_tests omath

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "extlibs/googletest"]
path = extlibs/googletest
url = https://github.com/google/googletest.git
[submodule "extlibs/benchmark"]
path = extlibs/benchmark
url = https://github.com/google/benchmark.git

4
.idea/editor.xml generated
View File

@@ -201,7 +201,7 @@
<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/=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/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" 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/=CppUnmatchedPragmaRegionDirective/@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/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />

2
.idea/vcs.xml generated
View File

@@ -2,7 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/extlibs/benchmark" vcs="Git" />
<mapping directory="$PROJECT_DIR$/extlibs/googletest" vcs="Git" />
</component>
</project>

View File

@@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.26)
project(omath VERSION 3.9.2 LANGUAGES CXX)
file(READ VERSION OMATH_VERSION)
project(omath VERSION ${OMATH_VERSION} LANGUAGES CXX)
include(CMakePackageConfigHelpers)
include(CheckCXXCompilerFlag)
@@ -11,8 +12,8 @@ else ()
check_cxx_compiler_flag("-mavx2" COMPILER_SUPPORTS_AVX2)
endif ()
option(OMATH_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL})
option(OMATH_BUILD_BENCHMARK "Build benchmarks" ${PROJECT_IS_TOP_LEVEL})
option(OMATH_BUILD_TESTS "Build unit tests" OFF)
option(OMATH_BUILD_BENCHMARK "Build benchmarks" OFF)
option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON)
option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF)
option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ${COMPILER_SUPPORTS_AVX2})
@@ -21,7 +22,25 @@ option(OMATH_BUILD_EXAMPLES "Build example projects with you can learn & play" O
option(OMATH_STATIC_MSVC_RUNTIME_LIBRARY "Force Omath to link static runtime" OFF)
option(OMATH_SUPRESS_SAFETY_CHECKS "Supress some safety checks in release build to improve general performance" ON)
option(OMATH_USE_UNITY_BUILD "Will enable unity build to speed up compilation" OFF)
option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" OFF)
option(OMATH_ENABLE_LEGACY "Will enable legacy classes that MUST be used ONLY for backward compatibility" ON)
if (VCPKG_MANIFEST_FEATURES)
foreach (omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
if (omath_feature STREQUAL "imgui")
set(OMATH_IMGUI_INTEGRATION ON)
elseif (omath_feature STREQUAL "avx2")
set(OMATH_USE_AVX2 ${COMPILER_SUPPORTS_AVX2})
elseif (omath_feature STREQUAL "tests")
set(OMATH_BUILD_TESTS ON)
elseif (omath_feature STREQUAL "benchmark")
set(OMATH_BUILD_BENCHMARK ON)
elseif (omath_feature STREQUAL "examples")
set(OMATH_BUILD_EXAMPLES ON)
endif ()
endforeach ()
endif ()
if (OMATH_USE_AVX2 AND NOT COMPILER_SUPPORTS_AVX2)
message(WARNING "OMATH_USE_AVX2 requested, but compiler/target does not support AVX2. Disabling.")
@@ -40,6 +59,7 @@ if (${PROJECT_IS_TOP_LEVEL})
message(STATUS "[${PROJECT_NAME}]: AVX2 feature status ${OMATH_USE_AVX2}")
message(STATUS "[${PROJECT_NAME}]: ImGUI integration feature status ${OMATH_IMGUI_INTEGRATION}")
message(STATUS "[${PROJECT_NAME}]: Legacy features support ${OMATH_ENABLE_LEGACY}")
message(STATUS "[${PROJECT_NAME}]: Building using vcpkg ${OMATH_BUILD_VIA_VCPKG}")
endif ()
file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp")
@@ -84,7 +104,7 @@ if (OMATH_SUPRESS_SAFETY_CHECKS)
endif ()
if (OMATH_ENABLE_LEGACY)
target_compile_options(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_LEGACY)
endif ()
set_target_properties(${PROJECT_NAME} PROPERTIES
@@ -117,9 +137,6 @@ endif ()
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
add_subdirectory(extlibs)
if (OMATH_BUILD_TESTS)
add_subdirectory(tests)
target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_BUILD_TESTS)

View File

@@ -8,7 +8,8 @@
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "cl.exe"
"CMAKE_CXX_COMPILER": "cl.exe",
"CMAKE_MAKE_PROGRAM": "Ninja"
},
"condition": {
"type": "equals",
@@ -16,6 +17,17 @@
"rhs": "Windows"
}
},
{
"name": "windows-base-vcpkg",
"hidden": true,
"inherits": "windows-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;examples"
}
},
{
"name": "windows-debug",
"displayName": "Debug",
@@ -24,6 +36,23 @@
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "windows-debug-vcpkg",
"displayName": "Windows Debug Vcpkg",
"inherits": "windows-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "windows-release-vcpkg",
"displayName": "Windows Release Vcpkg",
"inherits": "windows-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"OMATH_BUILD_VIA_VCPKG": "ON"
}
},
{
"name": "windows-release",
"displayName": "Release",
@@ -39,7 +68,8 @@
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "clang++"
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_MAKE_PROGRAM": "ninja"
},
"condition": {
"type": "equals",
@@ -47,6 +77,17 @@
"rhs": "Linux"
}
},
{
"name": "linux-base-vcpkg",
"hidden": true,
"inherits": "linux-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": "linux-debug",
"displayName": "Linux Debug",
@@ -55,6 +96,14 @@
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "linux-debug-vcpkg",
"displayName": "Linux Debug",
"inherits": "linux-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "linux-release",
"displayName": "Linux Release",
@@ -63,6 +112,14 @@
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "linux-release-vcpkg",
"displayName": "Linux Release",
"inherits": "linux-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "darwin-base",
"hidden": true,
@@ -70,7 +127,8 @@
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "clang++"
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_MAKE_PROGRAM": "ninja"
},
"condition": {
"type": "equals",
@@ -78,6 +136,17 @@
"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;examples"
}
},
{
"name": "darwin-debug",
"displayName": "Darwin Debug",
@@ -86,10 +155,213 @@
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "darwin-debug-vcpkg",
"displayName": "Darwin Debug Vcpkg",
"inherits": "darwin-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "darwin-release",
"displayName": "Darwin Release",
"inherits": "darwin-debug",
"inherits": "darwin-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "darwin-release-vcpkg",
"displayName": "Darwin Release Vcpkg",
"inherits": "darwin-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "ios-base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "iOS",
"CMAKE_OSX_DEPLOYMENT_TARGET": "18.5",
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED": "NO",
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED": "NO"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
},
{
"name": "ios-base-vcpkg",
"hidden": true,
"inherits": "ios-base",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"VCPKG_TARGET_TRIPLET": "arm64-ios",
"VCPKG_HOST_TRIPLET": "arm64-osx"
}
},
{
"name": "ios-release-vcpkg",
"displayName": "iOS Release (vcpkg)",
"inherits": "ios-base-vcpkg"
},
{
"name": "freebsd-base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
"cacheVariables": {
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_MAKE_PROGRAM": "ninja"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "FreeBSD"
}
},
{
"name": "freebsd-base-vcpkg",
"hidden": true,
"inherits": "freebsd-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": "freebsd-debug",
"displayName": "FreeBSD Debug",
"inherits": "freebsd-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "freebsd-debug-vcpkg",
"displayName": "FreeBSD Debug Vcpkg",
"inherits": "freebsd-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "freebsd-release",
"displayName": "FreeBSD Release",
"inherits": "freebsd-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "freebsd-release-vcpkg",
"displayName": "FreeBSD Release Vcpkg",
"inherits": "freebsd-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "android-base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "Android",
"CMAKE_SYSTEM_VERSION": "24",
"CMAKE_ANDROID_ARCH_ABI": "arm64-v8a",
"CMAKE_ANDROID_NDK": "$env{ANDROID_NDK_HOME}",
"CMAKE_MAKE_PROGRAM": "ninja"
}
},
{
"name": "android-base-vcpkg",
"hidden": true,
"inherits": "android-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_TARGET_TRIPLET": "arm64-android",
"VCPKG_MANIFEST_FEATURES": "tests;imgui"
}
},
{
"name": "android-debug",
"displayName": "Android Debug",
"inherits": "android-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "android-debug-vcpkg",
"displayName": "Android Debug Vcpkg",
"inherits": "android-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "android-release",
"displayName": "Android Release",
"inherits": "android-base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "android-release-vcpkg",
"displayName": "Android Release Vcpkg",
"inherits": "android-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "wasm-base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/cmake-build/build/${presetName}",
"installDir": "${sourceDir}/cmake-build/install/${presetName}",
"cacheVariables": {
"CMAKE_MAKE_PROGRAM": "ninja"
}
},
{
"name": "wasm-base-vcpkg",
"hidden": true,
"inherits": "wasm-base",
"cacheVariables": {
"OMATH_BUILD_VIA_VCPKG": "ON",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "$env{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
"VCPKG_INSTALLED_DIR": "${sourceDir}/cmake-build/vcpkg_installed",
"VCPKG_TARGET_TRIPLET": "wasm32-emscripten"
}
},
{
"name": "wasm-debug-vcpkg",
"displayName": "WASM Debug Vcpkg",
"inherits": "wasm-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "wasm-release-vcpkg",
"displayName": "WASM Release Vcpkg",
"inherits": "wasm-base-vcpkg",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}

View File

@@ -1,93 +1,201 @@
## 🎯 Goal
# UNIVERSAL DECLARATION OF CODE OF CONDUCT
_Declaration of Community Rights and Responsibilities_
My goal is to provide a space where it is safe for everyone to contribute to,
and get support for, open-source software in a respectful and cooperative
manner.
## Preamble
I value all contributions and want to make this project and its
surrounding community a place for everyone.
Whereas the Orange++ community is founded on cooperation, mutual respect and support for the development of open-source software;
As members, contributors, and everyone else who may participate in the
development, I strive to keep the entire experience civil.
Whereas it is essential that all participants can contribute and seek assistance in an environment that is safe, inclusive and free from discrimination and harassment;
## 📜 Standards
Whereas the dignity and equality of all participants, regardless of their traits or background, must be respected and protected;
Our community standards exist in order to make sure everyone feels comfortable
contributing to the project(s) together.
Now, therefore, this Community Code of Conduct is proclaimed as a common standard of behaviour for all members, contributors and participants in projects led by Orange++ and its official communities.
Our standards are:
- Do not harass, attack, or in any other way discriminate against anyone, including
for their protected traits, including, but not limited to, sex, religion, race,
appearance, gender, identity, nationality, sexuality, etc.
- Do not go off-topic, do not post spam.
- Treat everyone with respect.
---
Examples of breaking each rule respectively include:
- Harassment, bullying or inappropriate jokes about another person.
- Posting distasteful imagery, trolling, or posting things unrelated to the topic at hand.
- Treating someone as worse because of their lack of understanding of an issue.
## Article 1
## ⚡ Enforcement
This Code of Conduct establishes standards of behaviour intended to:
Enforcement of this CoC is done by Orange++ and/or other core contributors.
1. Provide a safe and welcoming environment for all participants.
2. Encourage respectful and constructive collaboration.
3. Prevent harassment, discrimination, and other harmful conduct.
I, as the core developer, will strive my best to keep this community civil and
following the standards outlined above.
All individuals who participate in Orange++ projects or official communities, whether online or offline, are expected to adhere to this Code of Conduct.
### 🚩 Reporting incidents
---
If you believe an incident of breaking these standards has occurred, but nobody has
taken appropriate action, you can privately contact the people responsible for dealing
with such incidents in multiple ways:
## Article 2
All participants are equal in dignity and rights within the community.
No person shall be harassed, attacked, or discriminated against on the basis of protected or personal traits, including but not limited to:
- sex;
- religion or belief;
- race or ethnicity;
- appearance;
- gender or gender identity;
- nationality;
- sexual orientation;
- or any other similar characteristic.
Treating someone as lesser or unworthy because of their knowledge, experience, or level of understanding of an issue is incompatible with this Code.
---
## Article 3
Participants shall treat one another with respect at all times.
Participants shall:
1. Engage in discussion in good faith and assume good intent where reasonable.
2. Provide feedback and criticism in a constructive and considerate manner.
3. Recognize that people have different backgrounds, perspectives, and levels of expertise.
Examples of conduct contrary to this Article include, but are not limited to:
- harassment, bullying, personal attacks or degrading comments;
- inappropriate or offensive jokes or remarks about another person;
- persistent disruption of discussions or activities.
---
## Article 4
Participants shall remain on topic and avoid posting spam or irrelevant material.
Content that is distasteful, deliberately inflammatory, or unrelated to the project or discussion at hand is prohibited.
Examples of prohibited conduct under this Article include:
- posting trolling or inflammatory messages;
- sharing disturbing or inappropriate imagery unrelated to the topic;
- repeatedly derailing conversations away from their intended purpose.
---
## Article 5
The following standards shall guide all participation in Orange++ projects and official communities:
1. Do not harass, attack, or discriminate against any person.
2. Do not go off-topic and do not post spam.
3. Treat all participants with respect.
These standards apply equally to maintainers, contributors, and all other participants, regardless of status or seniority.
---
## Article 6
Enforcement of this Code of Conduct is carried out by Orange++ and/or other core contributors (hereinafter “members”).
Members shall strive to:
1. Act fairly, consistently, and transparently.
2. Consider the context and severity of each incident.
3. Maintain a civil and welcoming environment for the community as a whole.
Where appropriate, members may consult individuals with relevant lived experience, particularly when an incident concerns a marginalized group, while preserving confidentiality as required by this Code.
---
## Article 7
Any participant who believes that a breach of this Code of Conduct has occurred and has not been appropriately addressed may report the incident privately.
Reports may be submitted through any of the following channels:
**E-mail**
***E-Mail***
- `orange-cpp@yandex.ru`
***Discord***
**Discord**
- `@orange_cpp`
***Telegram***
**Telegram**
- `@orange_cpp`
I guarantee your privacy and will not share those reports with anyone.
The reporting partys privacy shall be respected, and reports shall not be shared beyond those responsible for handling them, except where required by law or with the explicit consent of the reporting party.
## ⚖️ Enforcement Strategy
---
Depending on the severity of the infraction, any action from the list below may be applied.
Please keep in mind cases are reviewed on a per-case basis and members are the ultimate
deciding factor in the type of punishment.
## Article 8
If the matter benefited from an outside opinion, a member might reach for more opinions
from people unrelated, however, the final decision regarding the action
to be taken is still up to the member.
Depending on the nature and severity of the infraction, and taking into account past behaviour, members may apply one or more of the following measures.
For example, if the matter at hand regards a representative of a marginalized group or minority,
the member might ask for a first-hand opinion from another representative of such group.
**1. Correction / Edit**
### ✏️ Correction/Edit
Where a message is misleading, poorly worded, or likely to cause misunderstanding, members may:
If your message is found to be misleading or poorly worded, a member might
edit your message.
1. Request that the author clarify or correct the message; or
2. Edit the message where the platform permits and such action is appropriate and transparent.
### ⚠️ Warning/Deletion
**2. Warning / Deletion**
If your message is found inappropriate, a member might give you a public or private warning,
and/or delete your message.
Where a message is inappropriate or in breach of the standards, members may:
### 🔇 Mute
1. Issue a public or private warning; and/or
2. Delete the message.
If your message is disruptive, or you have been repeatedly violating the standards,
a member might mute (or temporarily ban) you.
**3. Mute / Temporary Ban**
### ⛔ Ban
Where a participant is repeatedly violating the standards, or where their behaviour is significantly disruptive, members may:
If your message is hateful, very disruptive, or other, less serious infractions are repeated
ignoring previous punishments, a member might ban you permanently.
1. Temporarily mute the participant; or
2. Temporarily suspend or ban the participant from the community.
## 🔎 Scope
**4. Permanent Ban**
This CoC shall apply to all projects ran under the Orange++ lead and all _official_ communities
outside of GitHub.
Where a message is hateful or severely disruptive, or where less serious infractions are repeated despite prior measures, members may permanently ban the participant.
However, it is worth noting that official communities outside of GitHub might have their own,
additional sets of rules.
Each case shall be considered individually. The final decision regarding the appropriate measure lies with the members responsible for enforcement.
---
## Article 9
Reports of misconduct and information regarding enforcement actions shall be handled with care and confidentiality.
The personal data of reporters, witnesses, and involved parties shall not be disclosed to third parties, except:
1. Where such disclosure is required by law; or
2. Where explicit consent has been given by the person concerned.
The maintainer guarantees that every report will be treated with discretion and respect.
---
## Article 10
This Code of Conduct applies to:
1. All projects led under the Orange++ name or leadership; and
2. All official communities associated with Orange++ outside of GitHub.
Official communities outside of GitHub may maintain additional rules specific to their platform. In case of overlap, participants are expected to follow:
1. The rules of the platform or community; and
2. This Code of Conduct, insofar as it is applicable.
---
## Article 11
This Code of Conduct shall be interpreted in a manner consistent with its purpose: to promote a safe, respectful and inclusive community.
The maintainer and core contributors may review and revise this Code of Conduct periodically in light of community needs and experience.
Significant changes should be communicated to the community in a timely and clear manner.
---
## Article 12
Nothing in this Community Code of Conduct may be interpreted as granting to any maintainer, member, contributor, or participant any right to engage in any activity or to perform any act that aims at undermining, limiting, or destroying the rights, protections, and standards set forth herein.
No rule, policy, custom, or decision within the Orange++ projects or their official communities may be invoked to justify harassment, discrimination, retaliation, or any other conduct contrary to this Code of Conduct.

View File

@@ -18,7 +18,7 @@ In order to send code back to the official OMath repository, you must first crea
account ([fork](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)) and
then [create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) back to OMath.
OMath developement is performed on multiple branches. Changes are then pull requested into master. By default, changes
OMath development is performed on multiple branches. Changes are then pull requested into master. By default, changes
merged into master will not roll out to stable build users unless the `stable` tag is updated.
### 📜 Code-Style

38
LICENSE
View File

@@ -1,7 +1,7 @@
Copyright (C) 2024-2025 Orange++ <orange_github@proton.me>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
@@ -16,14 +16,44 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
4. If you are an employee, contractor, volunteer, representative,
or have any other affiliation (past, present, or future)
or have any other affiliation (past or present)
with any of the following entities:
* "Advertising Placement Services" LLC
* "NEW SOLUTIONS VERTICALS" LLC
* "Autoexpert" LLC
* "Creditit" LLC
* "Yandex.Taxi" LLC
* "Yandex.Eda" LLC
* "Yandex.Lavka" LLC
* "Yandex.Telecom" LLC
* "Yandex.Cloud" LLC
* "Micromobility" LLC
* "MM-Tech" LLC
* "Carsharing" LLC
* "Yandex.Drive" LLC
* "EDADIL PROMO" LLC
* "Kinopoisk" LLC
* "Yandex.Music" LLC
* "Refueling (Yandex.Zapravki)" LLC
* "Yandex.Pay" LLC
* "Financial and Payment Technologies" LLC
* "Yandex.Delivery" LLC
* "Delivery Club" LLC
* "Yandex.Check" LLC
* "SMB-Service" LLC
* "ADV-TECH" LLC
* "Yandex Fantech" LLC
* "Yandex Smena" LLC
* "Market.Operations" LLC
* "Yandex.Market" LLC
* "ID Tech" LLC
* "Yandex.Crowd" LLC
* "Yandex" LLC
* "Rutube" LLC
* "Kaspersky" LLC
Or if you represent or are associated with any legal, organizational, or
professional entity providing services to or on behalf of the aforementioned entities:
You are expressly forbidden from accessing, using, modifying, distributing, or
interacting with the Software and its source code in any form. You must immediately
delete or destroy any physical or digital copies of the Software and/or
interacting with the Software and its source code in any form. You must immediately
delete or destroy any physical or digital copies of the Software and/or
its source code, including any derivative works, tools, or information obtained from the Software.

View File

@@ -1,6 +1,6 @@
<div align = center>
![banner](.github/images/logos/omath_logo_macro.png)
![banner](docs/images/logos/omath_logo_macro.png)
![Static Badge](https://img.shields.io/badge/license-libomath-orange)
![GitHub contributors](https://img.shields.io/github/contributors/orange-cpp/omath)
@@ -43,25 +43,52 @@ It provides the latest features, is highly customizable, has all for cheat devel
</a>
</div>
# Features
- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
- **Versatility**: Includes a wide array of mathematical functions and algorithms.
- **Ease of Use**: Simplified interface for convenient integration into various projects.
- **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
- **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
- **Engine support**: Supports coordinate systems of Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL.
- **Cross platform**: Supports Windows, MacOS and Linux.
- **Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps.
## 🚀 Quick Example
```cpp
#include <omath/omath.hpp>
using namespace omath;
// 3D vector operations
Vector3<float> a{1, 2, 3};
Vector3<float> b{4, 5, 6};
auto dot = a.dot(b); // 32.0
auto cross = a.cross(b); // (-3, 6, -3)
auto distance = a.distance_to(b); // ~5.196
auto normalized = a.normalized(); // Unit vector
// World-to-screen projection (Source Engine example)
using namespace omath::source_engine;
Camera camera(position, angles, viewport, fov, near_plane, far_plane);
if (auto screen = camera.world_to_screen(world_position)) {
// Draw at screen->x, screen->y
}
```
**[➡️ See more examples and tutorials][TUTORIALS]**
# ✨ Features
- **🚀 Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
- **🎯 Versatility**: Includes a wide array of mathematical functions and algorithms.
- **✅ Ease of Use**: Simplified interface for convenient integration into various projects.
- **🎮 Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
- **📐 3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
- **💥 Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
- **📦 No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
- **🔧 Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
- **🎯 Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL**.
- **🌍 Cross platform**: Supports Windows, MacOS and Linux.
- **🔍 Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps.
<div align = center>
# Gallery
<br>
[![Youtube Video](.github/images/yt_previews/img.png)](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
[![Youtube Video](docs/images/yt_previews/img.png)](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
<br>
@@ -79,22 +106,45 @@ It provides the latest features, is highly customizable, has all for cheat devel
![TF2 Preview]
<br>
![OpenGL Preview]
<br>
<br>
</div>
## 📚 Documentation
- **[Getting Started Guide](http://libomath.org/getting_started/)** - Installation and first steps
- **[API Overview](http://libomath.org/api_overview/)** - Complete API reference
- **[Tutorials](http://libomath.org/tutorials/)** - Step-by-step guides
- **[FAQ](http://libomath.org/faq/)** - Common questions and answers
- **[Troubleshooting](http://libomath.org/troubleshooting/)** - Solutions to common issues
- **[Best Practices](http://libomath.org/best_practices/)** - Guidelines for effective usage
## 🤝 Community & Support
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
- **Issues**: [Report bugs or request features](https://github.com/orange-cpp/omath/issues)
- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines
# 💘 Acknowledgments
- [All contributors](https://github.com/orange-cpp/omath/graphs/contributors)
<!----------------------------------{ Images }--------------------------------->
[APEX Preview]: .github/images/showcase/apex.png
[BO2 Preview]: .github/images/showcase/cod_bo2.png
[CS2 Preview]: .github/images/showcase/cs2.jpeg
[TF2 Preview]: .github/images/showcase/tf2.jpg
[APEX Preview]: docs/images/showcase/apex.png
[BO2 Preview]: docs/images/showcase/cod_bo2.png
[CS2 Preview]: docs/images/showcase/cs2.jpeg
[TF2 Preview]: docs/images/showcase/tf2.jpg
[OpenGL Preview]: docs/images/showcase/opengl.png
<!----------------------------------{ Buttons }--------------------------------->
[QUICKSTART]: docs/getting_started.md
[INSTALL]: INSTALL.md
[DOCUMENTATION]: http://libomath.org
[TUTORIALS]: docs/tutorials.md
[CONTRIBUTING]: CONTRIBUTING.md
[EXAMPLES]: examples
[SPONSOR]: https://boosty.to/orangecpp/purchase/3568644?ssource=DIRECT&share=subscription_link

View File

@@ -1 +1 @@
3.9.2
4.4.0

View File

@@ -11,5 +11,9 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 23
CXX_STANDARD_REQUIRED ON)
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath)
if (TARGET benchmark::benchmark) # Benchmark is being linked as submodule
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath)
else()
find_package(benchmark CONFIG REQUIRED) # Benchmark is being linked as vcpkg package
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark omath)
endif ()

118
docs/3d_primitives/box.md Normal file
View File

@@ -0,0 +1,118 @@
# `omath::primitives::create_box` — Build an oriented box as 12 triangles
> Header: your projects `primitives/box.hpp` (declares `create_box`)
> Namespace: `omath::primitives`
> Depends on: `omath::Triangle<omath::Vector3<float>>`, `omath::Vector3<float>`
```cpp
[[nodiscard]]
std::array<Triangle<Vector3<float>>, 12>
create_box(const Vector3<float>& top,
const Vector3<float>& bottom,
const Vector3<float>& dir_forward,
const Vector3<float>& dir_right,
float ratio = 4.f) noexcept;
```
---
## What it does
Constructs a **rectangular cuboid (“box”)** oriented in 3D space and returns its surface as **12 triangles** (2 per face × 6 faces). The boxs central axis runs from `bottom``top`. The **up** direction is inferred from that segment; the **forward** and **right** directions define the boxs orientation around that axis.
The lateral half-extents are derived from the axis length and `ratio`:
> Let `H = |top - bottom|`. Lateral half-size ≈ `H / ratio` along both `dir_forward` and `dir_right`
> (i.e., the cross-section is a square of side `2H/ratio`).
> **Note:** This describes the intended behavior from the interface. If you rely on a different sizing rule, document it next to your implementation.
---
## Parameters
* `top`
Center of the **top face**.
* `bottom`
Center of the **bottom face**.
* `dir_forward`
A direction that orients the box around its up axis. Should be **non-zero** and **not collinear** with `top - bottom`.
* `dir_right`
A direction roughly orthogonal to both `dir_forward` and `top - bottom`. Used to fully fix orientation.
* `ratio` (default `4.0f`)
Controls thickness relative to height. Larger values → thinner box.
With the default rule above, half-extent = `|top-bottom|/ratio`.
---
## Return value
`std::array<Triangle<Vector3<float>>, 12>` — the six faces of the box, triangulated.
Winding is intended to be **outward-facing** (right-handed coordinates). Do not rely on a specific **face ordering**; treat the array as opaque unless your implementation guarantees an order.
---
## Expected math & robustness
* Define `u = normalize(top - bottom)`.
* Re-orthonormalize the basis to avoid skew:
```cpp
f = normalize(dir_forward - u * u.dot(dir_forward)); // drop any up component
r = normalize(u.cross(f)); // right-handed basis
// (Optionally recompute f = r.cross(u) for orthogonality)
```
* Half-extents: `h = length(top - bottom) / ratio; hf = h * f; hr = h * r`.
* Corners (top): `t±r±f = top ± hr ± hf`; (bottom): `b±r±f = bottom ± hr ± hf`.
* Triangulate each face with consistent CCW winding when viewed from outside.
---
## Example
```cpp
using omath::Vector3;
using omath::Triangle;
using omath::primitives::create_box;
// Axis from bottom to top (height 2)
Vector3<float> bottom{0, 0, 0};
Vector3<float> top {0, 2, 0};
// Orientation around the axis
Vector3<float> forward{0, 0, 1};
Vector3<float> right {1, 0, 0};
// Ratio 4 → lateral half-size = height/4 = 0.5
auto tris = create_box(top, bottom, forward, right, 4.0f);
// Use the triangles (normals, rendering, collision, etc.)
for (const auto& tri : tris) {
auto n = tri.calculate_normal();
(void)n;
}
```
---
## Usage notes & pitfalls
* **Degenerate axis**: If `top == bottom`, the box is undefined (zero height). Guard against this.
* **Directions**: Provide **non-zero**, **reasonably orthogonal** `dir_forward`/`dir_right`. A robust implementation should project/normalize internally, but callers should still pass sensible inputs.
* **Winding**: If your renderer or collision expects a specific winding, verify with a unit test and flip vertex order per face if necessary.
* **Thickness policy**: This doc assumes both lateral half-extents equal `|top-bottom|/ratio`. If your implementation diverges (e.g., separate forward/right ratios), document it.
---
## See also
* `omath::Triangle` (vertex utilities: normals, centroid, etc.)
* `omath::Vector3` (geometry operations used by the construction)
---
*Last updated: 31 Oct 2025*

465
docs/3d_primitives/mesh.md Normal file
View File

@@ -0,0 +1,465 @@
# `omath::primitives::Mesh` — 3D mesh with transformation support
> Header: `omath/3d_primitives/mesh.hpp`
> Namespace: `omath::primitives`
> Depends on: `omath::Vector3<T>`, `omath::Mat4X4`, `omath::Triangle<Vector3<T>>`
> Purpose: represent and transform 3D meshes in different engine coordinate systems
---
## Overview
`Mesh` represents a 3D polygonal mesh with vertex data and transformation capabilities. It stores:
* **Vertex buffer (VBO)** — array of 3D vertex positions
* **Index buffer (VAO)** — array of triangular faces (indices into VBO)
* **Transformation** — position, rotation, and scale with caching
The mesh supports transformation from local space to world space using engine-specific coordinate systems through the `MeshTrait` template parameter.
---
## Template Declaration
```cpp
template<class Mat4X4, class RotationAngles, class MeshTypeTrait, class Type = float>
class Mesh final;
```
### Template Parameters
* `Mat4X4` — Matrix type for transformations (typically `omath::Mat4X4`)
* `RotationAngles` — Rotation representation (e.g., `ViewAngles` with pitch/yaw/roll)
* `MeshTypeTrait` — Engine-specific transformation trait (see [Engine Traits](#engine-traits))
* `Type` — Scalar type for vertex coordinates (default `float`)
---
## Type Aliases
```cpp
using NumericType = Type;
```
Common engine-specific aliases:
```cpp
// Source Engine
using Mesh = omath::primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
// Unity Engine
using Mesh = omath::primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
// Unreal Engine
using Mesh = omath::primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
// Frostbite, IW Engine, OpenGL similar...
```
Use the pre-defined type aliases in engine namespaces:
```cpp
using namespace omath::source_engine;
Mesh my_mesh = /* ... */; // Uses SourceEngine::Mesh
```
---
## Data Members
### Vertex Data
```cpp
std::vector<Vector3<NumericType>> m_vertex_buffer; // VBO: vertex positions
std::vector<Vector3<std::size_t>> m_vertex_array_object; // VAO: face indices
```
* `m_vertex_buffer` — array of vertex positions in **local space**
* `m_vertex_array_object` — array of triangular faces, each containing 3 indices into `m_vertex_buffer`
**Public access**: These members are public for direct manipulation when needed.
---
## Constructor
```cpp
Mesh(std::vector<Vector3<NumericType>> vbo,
std::vector<Vector3<std::size_t>> vao,
Vector3<NumericType> scale = {1, 1, 1});
```
Creates a mesh from vertex and index data.
**Parameters**:
* `vbo` — vertex buffer (moved into mesh)
* `vao` — index buffer / vertex array object (moved into mesh)
* `scale` — initial scale (default `{1, 1, 1}`)
**Example**:
```cpp
std::vector<Vector3<float>> vertices = {
{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}
};
std::vector<Vector3<std::size_t>> faces = {
{0, 1, 2}, // Triangle 1
{0, 1, 3}, // Triangle 2
{0, 2, 3}, // Triangle 3
{1, 2, 3} // Triangle 4
};
using namespace omath::source_engine;
Mesh tetrahedron(std::move(vertices), std::move(faces));
```
---
## Transformation Methods
### Setting Transform Components
```cpp
void set_origin(const Vector3<NumericType>& new_origin);
void set_scale(const Vector3<NumericType>& new_scale);
void set_rotation(const RotationAngles& new_rotation_angles);
```
Update the mesh's transformation. **Side effect**: invalidates the cached transformation matrix, which will be recomputed on the next `get_to_world_matrix()` call.
**Example**:
```cpp
mesh.set_origin({10, 0, 5});
mesh.set_scale({2, 2, 2});
ViewAngles angles;
angles.pitch = PitchAngle::from_degrees(45.0f);
angles.yaw = YawAngle::from_degrees(30.0f);
mesh.set_rotation(angles);
```
### Getting Transform Components
```cpp
[[nodiscard]] const Vector3<NumericType>& get_origin() const;
[[nodiscard]] const Vector3<NumericType>& get_scale() const;
[[nodiscard]] const RotationAngles& get_rotation_angles() const;
```
Retrieve current transformation components.
### Transformation Matrix
```cpp
[[nodiscard]] const Mat4X4& get_to_world_matrix() const;
```
Returns the cached local-to-world transformation matrix. The matrix is computed lazily on first access after any transformation change:
```
M = Translation(origin) × Scale(scale) × Rotation(angles)
```
The rotation matrix is computed using the engine-specific `MeshTrait::rotation_matrix()` method.
**Caching**: The matrix is stored in a `mutable std::optional` and recomputed only when invalidated by `set_*` methods.
---
## Vertex Transformation
### `vertex_to_world_space`
```cpp
[[nodiscard]]
Vector3<float> vertex_to_world_space(const Vector3<float>& vertex) const;
```
Transforms a vertex from local space to world space by multiplying with the transformation matrix.
**Algorithm**:
1. Convert vertex to column matrix: `[x, y, z, 1]ᵀ`
2. Multiply by transformation matrix: `M × vertex`
3. Extract the resulting 3D position
**Usage**:
```cpp
Vector3<float> local_vertex{1, 0, 0};
Vector3<float> world_vertex = mesh.vertex_to_world_space(local_vertex);
```
**Note**: This is used internally by `MeshCollider` to provide world-space support functions for GJK/EPA.
---
## Face Transformation
### `make_face_in_world_space`
```cpp
[[nodiscard]]
Triangle<Vector3<float>> make_face_in_world_space(
const std::vector<Vector3<std::size_t>>::const_iterator vao_iterator
) const;
```
Creates a triangle in world space from a face index iterator.
**Parameters**:
* `vao_iterator` — iterator to an element in `m_vertex_array_object`
**Returns**: `Triangle` with all three vertices transformed to world space.
**Example**:
```cpp
for (auto it = mesh.m_vertex_array_object.begin();
it != mesh.m_vertex_array_object.end();
++it) {
Triangle<Vector3<float>> world_triangle = mesh.make_face_in_world_space(it);
// Render or process the triangle
}
```
---
## Usage Examples
### Creating a Box Mesh
```cpp
using namespace omath::source_engine;
std::vector<Vector3<float>> box_vbo = {
// Bottom face
{-0.5f, -0.5f, 0.0f}, { 0.5f, -0.5f, 0.0f},
{ 0.5f, 0.5f, 0.0f}, {-0.5f, 0.5f, 0.0f},
// Top face
{-0.5f, -0.5f, 1.0f}, { 0.5f, -0.5f, 1.0f},
{ 0.5f, 0.5f, 1.0f}, {-0.5f, 0.5f, 1.0f}
};
std::vector<Vector3<std::size_t>> box_vao = {
// Bottom
{0, 1, 2}, {0, 2, 3},
// Top
{4, 6, 5}, {4, 7, 6},
// Sides
{0, 4, 5}, {0, 5, 1},
{1, 5, 6}, {1, 6, 2},
{2, 6, 7}, {2, 7, 3},
{3, 7, 4}, {3, 4, 0}
};
Mesh box(std::move(box_vbo), std::move(box_vao));
box.set_origin({0, 0, 50});
box.set_scale({10, 10, 10});
```
### Transforming Mesh Over Time
```cpp
void update_mesh(Mesh& mesh, float delta_time) {
// Rotate mesh
auto rotation = mesh.get_rotation_angles();
rotation.yaw = YawAngle::from_degrees(
rotation.yaw.as_degrees() + 45.0f * delta_time
);
mesh.set_rotation(rotation);
// Oscillate position
auto origin = mesh.get_origin();
origin.z = 50.0f + 10.0f * std::sin(current_time * 2.0f);
mesh.set_origin(origin);
}
```
### Collision Detection
```cpp
using namespace omath::collision;
using namespace omath::source_engine;
Mesh mesh_a(vbo_a, vao_a);
mesh_a.set_origin({0, 0, 0});
Mesh mesh_b(vbo_b, vao_b);
mesh_b.set_origin({5, 0, 0});
MeshCollider collider_a(std::move(mesh_a));
MeshCollider collider_b(std::move(mesh_b));
auto result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
collider_a, collider_b
);
```
### Rendering Transformed Triangles
```cpp
void render_mesh(const Mesh& mesh) {
for (auto it = mesh.m_vertex_array_object.begin();
it != mesh.m_vertex_array_object.end();
++it) {
Triangle<Vector3<float>> tri = mesh.make_face_in_world_space(it);
// Draw triangle with your renderer
draw_triangle(tri.m_vertex1, tri.m_vertex2, tri.m_vertex3);
}
}
```
---
## Engine Traits
Each game engine has a corresponding `MeshTrait` that provides the `rotation_matrix` function:
```cpp
class MeshTrait final {
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
};
```
### Available Engines
| Engine | Namespace | Header |
|--------|-----------|--------|
| Source Engine | `omath::source_engine` | `engines/source_engine/mesh.hpp` |
| Unity | `omath::unity_engine` | `engines/unity_engine/mesh.hpp` |
| Unreal | `omath::unreal_engine` | `engines/unreal_engine/mesh.hpp` |
| Frostbite | `omath::frostbite_engine` | `engines/frostbite_engine/mesh.hpp` |
| IW Engine | `omath::iw_engine` | `engines/iw_engine/mesh.hpp` |
| OpenGL | `omath::opengl_engine` | `engines/opengl_engine/mesh.hpp` |
**Example** (Source Engine):
```cpp
using namespace omath::source_engine;
// Uses source_engine::MeshTrait automatically
Mesh my_mesh(vertices, indices);
```
See [MeshTrait Documentation](#mesh-trait-documentation) for engine-specific details.
---
## Performance Considerations
### Matrix Caching
The transformation matrix is computed lazily and cached:
* **First access**: O(matrix multiply) ≈ 64 float operations
* **Subsequent access**: O(1) — returns cached matrix
* **Cache invalidation**: Any `set_*` call invalidates the cache
**Best practice**: Batch transformation updates before accessing the matrix:
```cpp
// Good: single matrix recomputation
mesh.set_origin(new_origin);
mesh.set_rotation(new_rotation);
mesh.set_scale(new_scale);
auto matrix = mesh.get_to_world_matrix(); // Computes once
// Bad: three matrix recomputations
mesh.set_origin(new_origin);
auto m1 = mesh.get_to_world_matrix(); // Compute
mesh.set_rotation(new_rotation);
auto m2 = mesh.get_to_world_matrix(); // Compute again
mesh.set_scale(new_scale);
auto m3 = mesh.get_to_world_matrix(); // Compute again
```
### Memory Layout
* **VBO**: Contiguous `std::vector` for cache-friendly access
* **VAO**: Contiguous indices for cache-friendly face iteration
* **Matrix**: Cached in `std::optional` (no allocation)
### Transformation Cost
* `vertex_to_world_space`: ~15-20 FLOPs per vertex (4×4 matrix multiply)
* `make_face_in_world_space`: ~60 FLOPs (3 vertices)
For high-frequency transformations, consider:
* Caching transformed vertices if the mesh doesn't change
* Using simpler proxy geometry for collision
* Batching transformations
---
## Coordinate System Details
Different engines use different coordinate systems:
| Engine | Up Axis | Forward Axis | Handedness |
|--------|---------|--------------|------------|
| Source | +Z | +Y | Right |
| Unity | +Y | +Z | Left |
| Unreal | +Z | +X | Left |
| Frostbite | +Y | +Z | Right |
| IW Engine | +Z | +Y | Right |
| OpenGL | +Y | +Z | Right |
The `MeshTrait::rotation_matrix` function accounts for these differences, ensuring correct transformations in each engine's space.
---
## Limitations & Edge Cases
### Empty Mesh
A mesh with no vertices or faces is valid but not useful:
```cpp
Mesh empty_mesh({}, {}); // Valid but meaningless
```
For collision detection, ensure `m_vertex_buffer` is non-empty.
### Index Validity
No bounds checking is performed on indices in `m_vertex_array_object`. Ensure all indices are valid:
```cpp
assert(face.x < mesh.m_vertex_buffer.size());
assert(face.y < mesh.m_vertex_buffer.size());
assert(face.z < mesh.m_vertex_buffer.size());
```
### Degenerate Triangles
Faces with duplicate indices or collinear vertices will produce degenerate triangles. The mesh doesn't validate this; users must ensure clean geometry.
### Thread Safety
* **Read-only**: Safe to read from multiple threads (including const methods)
* **Modification**: Not thread-safe; synchronize `set_*` calls externally
* **Matrix cache**: Uses `mutable` member; not thread-safe even for const methods
---
## See Also
- [MeshCollider Documentation](../collision/mesh_collider.md) - Collision wrapper for meshes
- [GJK Algorithm Documentation](../collision/gjk_algorithm.md) - Uses mesh for collision detection
- [EPA Algorithm Documentation](../collision/epa_algorithm.md) - Penetration depth with meshes
- [Triangle Documentation](../linear_algebra/triangle.md) - Triangle primitive
- [Mat4X4 Documentation](../linear_algebra/mat.md) - Transformation matrices
- [Box Documentation](box.md) - Box primitive
- [Plane Documentation](plane.md) - Plane primitive
---
## Mesh Trait Documentation
For engine-specific `MeshTrait` details, see:
- [Source Engine MeshTrait](../engines/source_engine/mesh_trait.md)
- [Unity Engine MeshTrait](../engines/unity_engine/mesh_trait.md)
- [Unreal Engine MeshTrait](../engines/unreal_engine/mesh_trait.md)
- [Frostbite Engine MeshTrait](../engines/frostbite/mesh_trait.md)
- [IW Engine MeshTrait](../engines/iw_engine/mesh_trait.md)
- [OpenGL Engine MeshTrait](../engines/opengl_engine/mesh_trait.md)
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,98 @@
# `omath::primitives::create_plane` — Build an oriented quad (2 triangles)
> Header: your projects `primitives/plane.hpp`
> Namespace: `omath::primitives`
> Depends on: `omath::Triangle<omath::Vector3<float>>`, `omath::Vector3<float>`
```cpp
[[nodiscard]]
std::array<Triangle<Vector3<float>>, 2>
create_plane(const Vector3<float>& vertex_a,
const Vector3<float>& vertex_b,
const Vector3<float>& direction,
float size) noexcept;
```
---
## What it does
Creates a **rectangle (quad)** in 3D oriented by the edge **A→B** and a second in-plane **direction**. The quad is returned as **two triangles** suitable for rendering or collision.
* Edge axis: `e = vertex_b - vertex_a`
* Width axis: “direction”, **projected to be perpendicular to `e`** so the quad is planar and well-formed.
* Normal (by right-hand rule): `n ∝ e × width`.
> **Sizing convention**
> Typical construction uses **half-width = `size`** along the (normalized, orthogonalized) *direction*, i.e. the total width is `2*size`.
> If your implementation interprets `size` as full width, adjust your expectations accordingly.
---
## Parameters
* `vertex_a`, `vertex_b` — two adjacent quad vertices defining the **long edge** of the plane.
* `direction` — a vector indicating the **cross-edge direction** within the plane (does not need to be orthogonal or normalized).
* `size`**half-width** of the quad along the (processed) `direction`.
---
## Return
`std::array<Triangle<Vector3<float>>, 2>` — the quad triangulated (consistent CCW winding, outward normal per `e × width`).
---
## Robust construction (expected math)
1. `e = vertex_b - vertex_a`
2. Make `d` perpendicular to `e`:
```
d = direction - e * (e.dot(direction) / e.length_sqr());
if (d.length_sqr() == 0) pick an arbitrary perpendicular to e
d = d.normalized();
```
3. Offsets: `w = d * size`
4. Four corners:
```
A0 = vertex_a - w; A1 = vertex_a + w;
B0 = vertex_b - w; B1 = vertex_b + w;
```
5. Triangles (CCW when viewed from +normal):
```
T0 = Triangle{ A0, A1, B1 }
T1 = Triangle{ A0, B1, B0 }
```
---
## Example
```cpp
using omath::Vector3;
using omath::Triangle;
using omath::primitives::create_plane;
Vector3<float> a{ -1, 0, -1 }; // edge start
Vector3<float> b{ 1, 0, -1 }; // edge end
Vector3<float> dir{ 0, 0, 1 }; // cross-edge direction within the plane (roughly +Z)
float half_width = 2.0f;
auto quad = create_plane(a, b, dir, half_width);
// e.g., compute normals
for (const auto& tri : quad) {
auto n = tri.calculate_normal(); (void)n;
}
```
---
## Notes & edge cases
* **Degenerate edge**: if `vertex_a == vertex_b`, the plane is undefined.
* **Collinearity**: if `direction` is parallel to `vertex_b - vertex_a`, the function must choose an alternate perpendicular; expect a fallback.
* **Winding**: If your renderer expects a specific face order, verify and swap the two vertices in each triangle as needed.

577
docs/api_overview.md Normal file
View File

@@ -0,0 +1,577 @@
# API Overview
This document provides a high-level overview of OMath's API, organized by functionality area.
---
## Module Organization
OMath is organized into several logical modules:
### Core Mathematics
- **Linear Algebra** - Vectors, matrices, triangles
- **Trigonometry** - Angles, view angles, trigonometric functions
- **3D Primitives** - Boxes, planes, meshes, geometric shapes
### Game Development
- **Collision Detection** - Ray tracing, GJK/EPA algorithms, mesh collision, intersection tests
- **Projectile Prediction** - Ballistics and aim-assist calculations
- **Projection** - Camera systems and world-to-screen transformations
- **Pathfinding** - A* algorithm, navigation meshes
### Engine Support
- **Source Engine** - Valve's Source Engine (CS:GO, TF2, etc.)
- **Unity Engine** - Unity game engine
- **Unreal Engine** - Epic's Unreal Engine
- **Frostbite Engine** - EA's Frostbite Engine
- **IW Engine** - Infinity Ward's engine (Call of Duty)
- **OpenGL Engine** - Canonical OpenGL coordinate system
### Utilities
- **Color** - RGBA color representation
- **Pattern Scanning** - Memory pattern search (wildcards, PE files)
- **Reverse Engineering** - Internal/external memory manipulation
---
## Core Types
### Vectors
All vector types are template-based and support arithmetic types.
| Type | Description | Key Methods |
|------|-------------|-------------|
| `Vector2<T>` | 2D vector | `length()`, `normalized()`, `dot()`, `distance_to()` |
| `Vector3<T>` | 3D vector | `length()`, `normalized()`, `dot()`, `cross()`, `angle_between()` |
| `Vector4<T>` | 4D vector | Extends Vector3 with `w` component |
**Common aliases:**
```cpp
using Vec2f = Vector2<float>;
using Vec3f = Vector3<float>;
using Vec4f = Vector4<float>;
```
**Key features:**
- Component-wise arithmetic (+, -, *, /)
- Scalar multiplication/division
- Dot and cross products
- Safe normalization (returns original if length is zero)
- Distance calculations
- Angle calculations with error handling
- Hash support for `float` variants
- `std::formatter` support
### Matrices
| Type | Description | Key Methods |
|------|-------------|-------------|
| `Mat4X4` | 4×4 matrix | `identity()`, `transpose()`, `determinant()`, `inverse()` |
**Use cases:**
- Transformation matrices
- View matrices
- Projection matrices
- Model-view-projection pipelines
### Angles
Strong-typed angle system with compile-time range enforcement:
| Type | Range | Description |
|------|-------|-------------|
| `Angle<T, Min, Max, Flags>` | Custom | Generic angle type with bounds |
| `PitchAngle` | [-89°, 89°] | Vertical camera rotation |
| `YawAngle` | [-180°, 180°] | Horizontal camera rotation |
| `RollAngle` | [-180°, 180°] | Camera roll |
| `ViewAngles` | - | Composite pitch/yaw/roll |
**Features:**
- Automatic normalization/clamping based on flags
- Conversions between degrees and radians
- Type-safe arithmetic
- Prevents common angle bugs
---
## Projection System
### Camera
Generic camera template that works with any engine trait:
```cpp
template<class MatrixType, class AnglesType, class EngineTrait>
class Camera;
```
**Engine-specific cameras:**
```cpp
omath::source_engine::Camera // Source Engine
omath::unity_engine::Camera // Unity
omath::unreal_engine::Camera // Unreal
omath::frostbite_engine::Camera // Frostbite
omath::iw_engine::Camera // IW Engine
omath::opengl_engine::Camera // OpenGL
```
**Core methods:**
- `world_to_screen(Vector3<float>)` - Project 3D point to 2D screen
- `get_view_matrix()` - Get current view matrix
- `get_projection_matrix()` - Get current projection matrix
- `update(position, angles)` - Update camera state
**Supporting types:**
- `ViewPort` - Screen dimensions and aspect ratio
- `FieldOfView` - FOV in degrees with validation
- `ProjectionError` - Error codes for projection failures
---
## Collision Detection
### GJK/EPA Algorithms
Advanced convex shape collision detection using the Gilbert-Johnson-Keerthi and Expanding Polytope algorithms:
```cpp
namespace omath::collision {
template<class ColliderType>
class GjkAlgorithm;
template<class ColliderType>
class Epa;
}
```
**GJK (Gilbert-Johnson-Keerthi):**
* Detects collision between two convex shapes
* Returns a 4-point simplex when collision is detected
* O(k) complexity where k is typically < 20 iterations
* Works with any collider implementing `find_abs_furthest_vertex()`
**EPA (Expanding Polytope Algorithm):**
* Computes penetration depth and separation normal
* Takes GJK's output simplex as input
* Provides contact information for physics simulation
* Configurable iteration limit and convergence tolerance
**Supporting Types:**
| Type | Description | Key Features |
|------|-------------|--------------|
| `Simplex<VectorType>` | 1-4 point geometric simplex | Fixed capacity, GJK iteration support |
| `MeshCollider<MeshType>` | Convex mesh collider | Support function for GJK/EPA |
| `GjkHitInfo<VertexType>` | Collision result | Hit flag and simplex |
| `Epa::Result` | Penetration info | Depth, normal, iteration count |
### LineTracer
Ray-casting and line tracing utilities:
```cpp
namespace omath::collision {
class LineTracer;
}
```
**Features:**
- Ray-triangle intersection (Möller-Trumbore algorithm)
- Ray-plane intersection
- Ray-box intersection
- Distance calculations
- Normal calculations at hit points
### 3D Primitives
| Type | Description | Key Methods |
|------|-------------|-------------|
| `Plane` | Infinite plane | `intersects_ray()`, `distance_to_point()` |
| `Box` | Axis-aligned bounding box | `contains()`, `intersects()` |
| `Mesh` | Polygonal mesh with transforms | `vertex_to_world_space()`, `make_face_in_world_space()` |
**Mesh Features:**
* Vertex buffer (VBO) and index buffer (VAO/EBO) storage
* Position, rotation, and scale transformations
* Cached transformation matrix
* Engine-specific coordinate system support
* Compatible with `MeshCollider` for collision detection
---
## Projectile Prediction
### Interfaces
**`ProjPredEngineInterface`** - Base interface for all prediction engines
```cpp
virtual std::optional<Vector3<float>>
maybe_calculate_aim_point(const Projectile&, const Target&) const = 0;
```
### Implementations
| Engine | Description | Optimizations |
|--------|-------------|---------------|
| `ProjPredEngineLegacy` | Standard implementation | Portable, works everywhere |
| `ProjPredEngineAVX2` | AVX2 optimized | 2-4x faster on modern CPUs |
### Supporting Types
**`Projectile`** - Defines projectile properties:
```cpp
struct Projectile {
Vector3<float> origin;
float speed;
Vector3<float> gravity;
// ... additional properties
};
```
**`Target`** - Defines target state:
```cpp
struct Target {
Vector3<float> position;
Vector3<float> velocity;
// ... additional properties
};
```
---
## Pathfinding
### A* Algorithm
```cpp
namespace omath::pathfinding {
template<typename NodeType>
class AStar;
}
```
**Features:**
- Generic node type support
- Customizable heuristics
- Efficient priority queue implementation
- Path reconstruction
### Navigation Mesh
```cpp
namespace omath::pathfinding {
class NavigationMesh;
}
```
**Features:**
- Triangle-based navigation
- Neighbor connectivity
- Walkable area definitions
---
## Engine Traits
Each game engine has a trait system providing engine-specific math:
### CameraTrait
Implements camera math for an engine:
- `calc_look_at_angle()` - Calculate angles to look at a point
- `calc_view_matrix()` - Build view matrix from angles and position
- `calc_projection_matrix()` - Build projection matrix from FOV and viewport
### MeshTrait
Provides mesh transformation for an engine:
- `rotation_matrix()` - Build rotation matrix from engine-specific angles
- Handles coordinate system differences (Y-up vs Z-up, left/right-handed)
- Used by `Mesh` class for local-to-world transformations
### PredEngineTrait
Provides physics/ballistics specific to an engine:
- Gravity vectors
- Coordinate system conventions
- Unit conversions
- Physics parameters
### Available Traits
| Engine | Camera Trait | Mesh Trait | Pred Engine Trait | Constants | Formulas |
|--------|--------------|------------|-------------------|-----------|----------|
| Source Engine | ✓ | ✓ | ✓ | ✓ | ✓ |
| Unity Engine | ✓ | ✓ | ✓ | ✓ | ✓ |
| Unreal Engine | ✓ | ✓ | ✓ | ✓ | ✓ |
| Frostbite | ✓ | ✓ | ✓ | ✓ | ✓ |
| IW Engine | ✓ | ✓ | ✓ | ✓ | ✓ |
| OpenGL | ✓ | ✓ | ✓ | ✓ | ✓ |
**Documentation:**
- See `docs/engines/<engine_name>/` for detailed per-engine docs
- Each engine has separate docs for camera_trait, mesh_trait, pred_engine_trait, constants, and formulas
---
## Utility Functions
### Color
```cpp
struct Color {
uint8_t r, g, b, a;
// Conversions
static Color from_hsv(float h, float s, float v);
static Color from_hex(uint32_t hex);
uint32_t to_hex() const;
// Blending
Color blend(const Color& other, float t) const;
};
```
### Pattern Scanning
**Binary pattern search with wildcards:**
```cpp
// Pattern with wildcards (?? = any byte)
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
// Scan memory
auto result = pattern_scan(memory_buffer, pattern);
if (result) {
std::cout << "Found at offset: " << result->offset << "\n";
}
```
**PE file scanning:**
```cpp
PEPatternScanner scanner("target.exe");
if (auto addr = scanner.scan_pattern(pattern)) {
std::cout << "Found at RVA: " << *addr << "\n";
}
```
### Reverse Engineering
**External memory access:**
```cpp
ExternalRevObject process("game.exe");
Vector3<float> position = process.read<Vector3<float>>(address);
process.write(address, new_position);
```
**Internal memory access:**
```cpp
InternalRevObject memory;
auto value = memory.read<float>(address);
memory.write(address, new_value);
```
---
## Concepts and Constraints
OMath uses C++20 concepts for type safety:
```cpp
template<class T>
concept Arithmetic = std::is_arithmetic_v<T>;
template<class EngineTrait>
concept CameraEngineConcept = requires(EngineTrait t) {
{ t.calc_look_at_angle(...) } -> /* returns angles */;
{ t.calc_view_matrix(...) } -> /* returns matrix */;
{ t.calc_projection_matrix(...) } -> /* returns matrix */;
};
```
---
## Error Handling
OMath uses modern C++ error handling:
### std::expected (C++23)
```cpp
std::expected<Angle<...>, Vector3Error>
angle_between(const Vector3& other) const;
if (auto angle = v1.angle_between(v2)) {
// Success: use *angle
} else {
// Error: angle.error() gives Vector3Error
}
```
### std::optional
```cpp
std::optional<Vector2<float>>
world_to_screen(const Vector3<float>& world);
if (auto screen = camera.world_to_screen(pos)) {
// Success: use screen->x, screen->y
} else {
// Point not visible
}
```
### Error Codes
```cpp
enum class ProjectionError {
SUCCESS = 0,
POINT_BEHIND_CAMERA,
INVALID_VIEWPORT,
// ...
};
```
---
## Performance Considerations
### constexpr Support
Most operations are `constexpr` where possible:
```cpp
constexpr Vector3<float> v{1, 2, 3};
constexpr auto len_sq = v.length_sqr(); // Computed at compile time
```
### AVX2 Optimizations
Use AVX2 variants when available:
```cpp
// Standard: portable but slower
ProjPredEngineLegacy legacy_engine;
// AVX2: 2-4x faster on modern CPUs
ProjPredEngineAVX2 fast_engine;
```
**When to use AVX2:**
- Modern Intel/AMD processors (2013+)
- Performance-critical paths
- Batch operations
**When to use Legacy:**
- Older processors
- ARM platforms
- Guaranteed compatibility
### Cache Efficiency
```cpp
// Good: contiguous storage
std::vector<Vector3<float>> positions;
// Good: structure of arrays for SIMD
struct Particles {
std::vector<float> x, y, z;
};
```
---
## Platform Support
| Platform | Support | Notes |
|----------|---------|-------|
| Windows | ✓ | MSVC, Clang, GCC |
| Linux | ✓ | GCC, Clang |
| macOS | ✓ | Clang |
**Minimum requirements:**
- C++20 compiler
- C++23 recommended for `std::expected`
---
## Thread Safety
- **Vector/Matrix types**: Thread-safe (immutable operations)
- **Camera**: Not thread-safe (mutable state)
- **Pattern scanning**: Thread-safe (read-only operations)
- **Memory access**: Depends on OS/process synchronization
**Thread-safe example:**
```cpp
// Safe: each thread gets its own camera
std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([i]() {
Camera camera = /* create camera */;
// Use camera in this thread
});
}
```
---
## Best Practices
### 1. Use Type Aliases
```cpp
using Vec3f = omath::Vector3<float>;
using Mat4 = omath::Mat4X4;
```
### 2. Prefer constexpr When Possible
```cpp
constexpr auto compute_at_compile_time() {
Vector3<float> v{1, 2, 3};
return v.length_sqr();
}
```
### 3. Check Optional/Expected Results
```cpp
// Good
if (auto result = camera.world_to_screen(pos)) {
use(*result);
}
// Bad - may crash
auto result = camera.world_to_screen(pos);
use(result->x); // Undefined behavior if nullopt
```
### 4. Use Engine-Specific Types
```cpp
// Good: uses correct coordinate system
using namespace omath::source_engine;
Camera camera = /* ... */;
// Bad: mixing engine types
using UnityCamera = omath::unity_engine::Camera;
using SourceAngles = omath::source_engine::ViewAngles;
UnityCamera camera{pos, SourceAngles{}}; // Wrong!
```
---
## See Also
- [Getting Started Guide](getting_started.md)
- [Installation Instructions](install.md)
- [Examples Directory](../examples/)
- Individual module documentation in respective folders
---
*Last updated: 13 Nov 2025*

532
docs/best_practices.md Normal file
View File

@@ -0,0 +1,532 @@
# Best Practices
Guidelines for using OMath effectively and avoiding common pitfalls.
---
## Code Organization
### Use Type Aliases
Define clear type aliases for commonly used types:
```cpp
// Good: Clear and concise
using Vec3f = omath::Vector3<float>;
using Vec2f = omath::Vector2<float>;
using Mat4 = omath::Mat4X4;
Vec3f position{1.0f, 2.0f, 3.0f};
```
```cpp
// Avoid: Verbose and repetitive
omath::Vector3<float> position{1.0f, 2.0f, 3.0f};
omath::Vector3<float> velocity{0.0f, 0.0f, 0.0f};
```
### Namespace Usage
Be selective with `using namespace`:
```cpp
// Good: Specific namespace for your engine
using namespace omath::source_engine;
// Good: Import specific types
using omath::Vector3;
using omath::Vector2;
// Avoid: Too broad
using namespace omath; // Imports everything
```
### Include What You Use
```cpp
// Good: Include specific headers
#include <omath/linear_algebra/vector3.hpp>
#include <omath/projection/camera.hpp>
// Okay for development
#include <omath/omath.hpp>
// Production: Include only what you need
// to reduce compile times
```
---
## Error Handling
### Always Check Optional Results
```cpp
// Good: Check before using
if (auto screen = camera.world_to_screen(world_pos)) {
draw_at(screen->x, screen->y);
} else {
// Handle point not visible
}
// Bad: Unchecked access can crash
auto screen = camera.world_to_screen(world_pos);
draw_at(screen->x, screen->y); // Undefined behavior if nullopt!
```
### Handle Expected Errors
```cpp
// Good: Handle error case
if (auto angle = v1.angle_between(v2)) {
use_angle(*angle);
} else {
switch (angle.error()) {
case Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE:
// Handle zero-length vector
break;
}
}
// Bad: Assume success
auto angle = v1.angle_between(v2);
use_angle(*angle); // Throws if error!
```
### Validate Inputs
```cpp
// Good: Validate before expensive operations
bool is_valid_projectile(const Projectile& proj) {
return proj.speed > 0.0f &&
std::isfinite(proj.speed) &&
std::isfinite(proj.origin.length());
}
if (is_valid_projectile(proj) && is_valid_target(target)) {
auto aim = engine.maybe_calculate_aim_point(proj, target);
}
```
---
## Performance
### Use constexpr When Possible
```cpp
// Good: Computed at compile time
constexpr Vector3<float> gravity{0.0f, 0.0f, -9.81f};
constexpr float max_range = 1000.0f;
constexpr float max_range_sq = max_range * max_range;
// Use in runtime calculations
if (position.length_sqr() < max_range_sq) {
// ...
}
```
### Prefer Squared Distance
```cpp
// Good: Avoids expensive sqrt
constexpr float max_dist_sq = 100.0f * 100.0f;
for (const auto& entity : entities) {
if (entity.pos.distance_to_sqr(player_pos) < max_dist_sq) {
// Process nearby entity
}
}
// Avoid: Unnecessary sqrt calls
constexpr float max_dist = 100.0f;
for (const auto& entity : entities) {
if (entity.pos.distance_to(player_pos) < max_dist) {
// More expensive
}
}
```
### Cache Expensive Calculations
```cpp
// Good: Update camera once per frame
void update_frame() {
camera.update(current_position, current_angles);
// All projections use cached matrices
for (const auto& entity : entities) {
if (auto screen = camera.world_to_screen(entity.pos)) {
draw_entity(screen->x, screen->y);
}
}
}
// Bad: Camera recreated each call
for (const auto& entity : entities) {
Camera cam(pos, angles, viewport, fov, near, far); // Expensive!
auto screen = cam.world_to_screen(entity.pos);
}
```
### Choose the Right Engine
```cpp
// Good: Use AVX2 when available
#ifdef __AVX2__
using Engine = ProjPredEngineAVX2;
#else
using Engine = ProjPredEngineLegacy;
#endif
Engine prediction_engine;
// Or runtime detection
Engine* create_best_engine() {
if (cpu_supports_avx2()) {
return new ProjPredEngineAVX2();
}
return new ProjPredEngineLegacy();
}
```
### Minimize Allocations
```cpp
// Good: Reuse vectors
std::vector<Vector3<float>> positions;
positions.reserve(expected_count);
// In loop
positions.clear(); // Doesn't deallocate
for (...) {
positions.push_back(compute_position());
}
// Bad: Allocate every time
for (...) {
std::vector<Vector3<float>> positions; // Allocates each iteration
// ...
}
```
---
## Type Safety
### Use Strong Angle Types
```cpp
// Good: Type-safe angles
PitchAngle pitch = PitchAngle::from_degrees(45.0f);
YawAngle yaw = YawAngle::from_degrees(90.0f);
ViewAngles angles{pitch, yaw, RollAngle::from_degrees(0.0f)};
// Bad: Raw floats lose safety
float pitch = 45.0f; // No range checking
float yaw = 90.0f; // Can go out of bounds
```
### Match Engine Types
```cpp
// Good: Use matching types from same engine
using namespace omath::source_engine;
Camera camera = /* ... */;
ViewAngles angles = /* ... */;
// Bad: Mixing engine types
using UnityCamera = omath::unity_engine::Camera;
using SourceAngles = omath::source_engine::ViewAngles;
UnityCamera camera{pos, SourceAngles{}, ...}; // May cause issues!
```
### Template Type Parameters
```cpp
// Good: Explicit and clear
Vector3<float> position;
Vector3<double> high_precision_pos;
// Okay: Use default float
Vector3<> position; // Defaults to float
// Avoid: Mixing types unintentionally
Vector3<float> a;
Vector3<double> b;
auto result = a + b; // Type mismatch!
```
---
## Testing & Validation
### Test Edge Cases
```cpp
void test_projection() {
Camera camera = setup_camera();
// Test normal case
assert(camera.world_to_screen({100, 100, 100}).has_value());
// Test edge cases
assert(!camera.world_to_screen({0, 0, -100}).has_value()); // Behind
assert(!camera.world_to_screen({1e10, 0, 0}).has_value()); // Too far
// Test boundaries
Vector3<float> at_near{0, 0, camera.near_plane() + 0.1f};
assert(camera.world_to_screen(at_near).has_value());
}
```
### Validate Assumptions
```cpp
void validate_game_data() {
// Validate FOV
float fov = read_game_fov();
assert(fov > 1.0f && fov < 179.0f);
// Validate positions
Vector3<float> pos = read_player_position();
assert(std::isfinite(pos.x));
assert(std::isfinite(pos.y));
assert(std::isfinite(pos.z));
// Validate viewport
ViewPort vp = read_viewport();
assert(vp.width > 0 && vp.height > 0);
}
```
### Use Assertions
```cpp
// Good: Catch errors early in development
void shoot_projectile(const Projectile& proj) {
assert(proj.speed > 0.0f && "Projectile speed must be positive");
assert(std::isfinite(proj.origin.length()) && "Invalid projectile origin");
// Continue with logic
}
// Add debug-only checks
#ifndef NDEBUG
if (!is_valid_input(data)) {
std::cerr << "Warning: Invalid input detected\n";
}
#endif
```
---
## Memory & Resources
### RAII for Resources
```cpp
// Good: Automatic cleanup
class GameOverlay {
Camera camera_;
std::vector<Entity> entities_;
public:
GameOverlay(/* ... */) : camera_(/* ... */) {
entities_.reserve(1000);
}
// Resources cleaned up automatically
~GameOverlay() = default;
};
```
### Avoid Unnecessary Copies
```cpp
// Good: Pass by const reference
void draw_entities(const std::vector<Vector3<float>>& positions) {
for (const auto& pos : positions) {
// Process position
}
}
// Bad: Copies entire vector
void draw_entities(std::vector<Vector3<float>> positions) {
// Expensive copy!
}
// Good: Move when transferring ownership
std::vector<Vector3<float>> compute_positions();
auto positions = compute_positions(); // Move, not copy
```
### Use Structured Bindings
```cpp
// Good: Clear and concise
if (auto [success, screen_pos] = try_project(world_pos); success) {
draw_at(screen_pos.x, screen_pos.y);
}
// Good: Decompose results
auto [x, y, z] = position.as_tuple();
```
---
## Documentation
### Document Assumptions
```cpp
// Good: Clear documentation
/**
* Projects world position to screen space.
*
* @param world_pos Position in world coordinates (meters)
* @return Screen position if visible, nullopt if behind camera or out of view
*
* @note Assumes camera.update() was called this frame
* @note Screen coordinates are in viewport space [0, width] x [0, height]
*/
std::optional<Vector2<float>> project(const Vector3<float>& world_pos);
```
### Explain Non-Obvious Code
```cpp
// Good: Explain the math
// Use squared distance to avoid expensive sqrt
// max_range = 100.0 → max_range_sq = 10000.0
constexpr float max_range_sq = 100.0f * 100.0f;
if (dist_sq < max_range_sq) {
// Entity is in range
}
// Explain engine-specific quirks
// Source Engine uses Z-up coordinates, but angles are in degrees
// Pitch: [-89, 89], Yaw: [-180, 180], Roll: [-180, 180]
ViewAngles angles{pitch, yaw, roll};
```
---
## Debugging
### Add Debug Visualization
```cpp
#ifndef NDEBUG
void debug_draw_projection() {
// Draw camera frustum
draw_frustum(camera);
// Draw world axes
draw_line({0,0,0}, {100,0,0}, Color::Red); // X
draw_line({0,0,0}, {0,100,0}, Color::Green); // Y
draw_line({0,0,0}, {0,0,100}, Color::Blue); // Z
// Draw projected points
for (const auto& entity : entities) {
if (auto screen = camera.world_to_screen(entity.pos)) {
draw_cross(screen->x, screen->y);
}
}
}
#endif
```
### Log Important Values
```cpp
void debug_projection_failure(const Vector3<float>& pos) {
std::cerr << "Projection failed for position: "
<< pos.x << ", " << pos.y << ", " << pos.z << "\n";
auto view_matrix = camera.get_view_matrix();
std::cerr << "View matrix:\n";
// Print matrix...
std::cerr << "Camera position: "
<< camera.position().x << ", "
<< camera.position().y << ", "
<< camera.position().z << "\n";
}
```
### Use Debug Builds
```cmake
# CMakeLists.txt
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(your_target PRIVATE
DEBUG_PROJECTION=1
VALIDATE_INPUTS=1
)
endif()
```
```cpp
#ifdef DEBUG_PROJECTION
std::cout << "Projecting: " << world_pos << "\n";
#endif
#ifdef VALIDATE_INPUTS
assert(std::isfinite(world_pos.length()));
#endif
```
---
## Platform Considerations
### Cross-Platform Code
```cpp
// Good: Platform-agnostic
constexpr float PI = 3.14159265359f;
// Avoid: Platform-specific
#ifdef _WIN32
// Windows-only code
#endif
```
### Handle Different Compilers
```cpp
// Good: Compiler-agnostic
#if defined(_MSC_VER)
// MSVC-specific
#elif defined(__GNUC__)
// GCC/Clang-specific
#endif
// Use OMath's built-in compatibility
// It handles compiler differences automatically
```
---
## Summary
**Key principles:**
1. **Safety first**: Always check optional/expected results
2. **Performance matters**: Use constexpr, avoid allocations, cache results
3. **Type safety**: Use strong types, match engine types
4. **Clear code**: Use aliases, document assumptions, explain non-obvious logic
5. **Test thoroughly**: Validate inputs, test edge cases, add assertions
6. **Debug effectively**: Add visualization, log values, use debug builds
---
## See Also
- [Troubleshooting Guide](troubleshooting.md)
- [FAQ](faq.md)
- [API Overview](api_overview.md)
- [Tutorials](tutorials.md)
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,322 @@
# `omath::collision::Epa` — Expanding Polytope Algorithm for penetration depth
> Header: `omath/collision/epa_algorithm.hpp`
> Namespace: `omath::collision`
> Depends on: `Simplex<VertexType>`, collider types with `find_abs_furthest_vertex` method
> Algorithm: **EPA** (Expanding Polytope Algorithm) for penetration depth and contact normal
---
## Overview
The **EPA (Expanding Polytope Algorithm)** calculates the **penetration depth** and **separation normal** between two intersecting convex shapes. It is typically used as a follow-up to the GJK algorithm after a collision has been detected.
EPA takes a 4-point simplex containing the origin (from GJK) and iteratively expands it to find the point on the Minkowski difference closest to the origin. This point gives both:
* **Depth**: minimum translation distance to separate the shapes
* **Normal**: direction of separation (pointing from shape B to shape A)
`Epa` is a template class working with any collider type that implements the support function interface.
---
## `Epa::Result`
```cpp
struct Result final {
bool success{false}; // true if EPA converged
Vertex normal{}; // outward normal (from B to A)
float depth{0.0f}; // penetration depth
int iterations{0}; // number of iterations performed
int num_vertices{0}; // final polytope vertex count
int num_faces{0}; // final polytope face count
};
```
### Fields
* `success``true` if EPA successfully computed depth and normal; `false` if it failed to converge
* `normal` — unit vector pointing from shape B toward shape A (separation direction)
* `depth` — minimum distance to move shape A along `normal` to separate the shapes
* `iterations` — actual iteration count (useful for performance tuning)
* `num_vertices`, `num_faces` — final polytope size (for diagnostics)
---
## `Epa::Params`
```cpp
struct Params final {
int max_iterations{64}; // maximum iterations before giving up
float tolerance{1e-4f}; // absolute tolerance on distance growth
};
```
### Fields
* `max_iterations` — safety limit to prevent infinite loops (default 64)
* `tolerance` — convergence threshold: stop when distance grows less than this (default 1e-4)
---
## `Epa` Template Class
```cpp
template<class ColliderType>
class Epa final {
public:
using Vertex = typename ColliderType::VertexType;
static_assert(EpaVector<Vertex>, "VertexType must satisfy EpaVector concept");
// Solve for penetration depth and normal
[[nodiscard]]
static Result solve(
const ColliderType& a,
const ColliderType& b,
const Simplex<Vertex>& simplex,
const Params params = {}
);
};
```
### Precondition
The `simplex` parameter must:
* Have exactly 4 points (`simplex.size() == 4`)
* Contain the origin (i.e., be a valid GJK result with `hit == true`)
Violating this precondition leads to undefined behavior.
---
## Collider Requirements
Any type used as `ColliderType` must provide:
```cpp
// Type alias for vertex type (typically Vector3<float>)
using VertexType = /* ... */;
// Find the farthest point in world space along the given direction
[[nodiscard]]
VertexType find_abs_furthest_vertex(const VertexType& direction) const;
```
---
## Algorithm Details
### Expanding Polytope
EPA maintains a convex polytope (polyhedron) in Minkowski difference space `A - B`. Starting from the 4-point tetrahedron (simplex from GJK), it repeatedly:
1. **Find closest face** to the origin
2. **Support query** in the direction of the face normal
3. **Expand polytope** by adding the new support point
4. **Update faces** to maintain convexity
The algorithm terminates when:
* **Convergence**: the distance from origin to polytope stops growing (within tolerance)
* **Max iterations**: safety limit reached
* **Failure cases**: degenerate polytope or numerical issues
### Minkowski Difference
Like GJK, EPA operates in Minkowski difference space where `point = a - b` for points in shapes A and B. The closest point on this polytope to the origin gives the minimum separation.
### Face Winding
Faces are stored with outward-pointing normals. The algorithm uses a priority queue to efficiently find the face closest to the origin.
---
## Vertex Type Requirements
The `VertexType` must satisfy the `EpaVector` concept:
```cpp
template<class V>
concept EpaVector = requires(const V& a, const V& b, float s) {
{ a - b } -> std::same_as<V>;
{ a.cross(b) } -> std::same_as<V>;
{ a.dot(b) } -> std::same_as<float>;
{ -a } -> std::same_as<V>;
{ a * s } -> std::same_as<V>;
{ a / s } -> std::same_as<V>;
};
```
`omath::Vector3<float>` satisfies this concept.
---
## Usage Examples
### Basic EPA Usage
```cpp
using namespace omath::collision;
using namespace omath::source_engine;
// First, run GJK to detect collision
MeshCollider<Mesh> collider_a(mesh_a);
MeshCollider<Mesh> collider_b(mesh_b);
auto gjk_result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
collider_a,
collider_b
);
if (gjk_result.hit) {
// Collision detected, use EPA to get penetration info
auto epa_result = Epa<MeshCollider<Mesh>>::solve(
collider_a,
collider_b,
gjk_result.simplex
);
if (epa_result.success) {
std::cout << "Penetration depth: " << epa_result.depth << "\n";
std::cout << "Separation normal: "
<< "(" << epa_result.normal.x << ", "
<< epa_result.normal.y << ", "
<< epa_result.normal.z << ")\n";
// Apply separation: move A away from B
Vector3<float> correction = epa_result.normal * epa_result.depth;
mesh_a.set_origin(mesh_a.get_origin() + correction);
}
}
```
### Custom Parameters
```cpp
// Use custom convergence settings
Epa<Collider>::Params params;
params.max_iterations = 128; // Allow more iterations for complex shapes
params.tolerance = 1e-5f; // Tighter tolerance for more accuracy
auto result = Epa<Collider>::solve(a, b, simplex, params);
```
### Physics Integration
```cpp
void resolve_collision(PhysicsBody& body_a, PhysicsBody& body_b) {
auto gjk_result = GjkAlgorithm<Collider>::check_collision(
body_a.collider, body_b.collider
);
if (!gjk_result.hit)
return; // No collision
auto epa_result = Epa<Collider>::solve(
body_a.collider,
body_b.collider,
gjk_result.simplex
);
if (epa_result.success) {
// Separate bodies
float mass_sum = body_a.mass + body_b.mass;
float ratio_a = body_b.mass / mass_sum;
float ratio_b = body_a.mass / mass_sum;
body_a.position += epa_result.normal * (epa_result.depth * ratio_a);
body_b.position -= epa_result.normal * (epa_result.depth * ratio_b);
// Apply collision response
apply_impulse(body_a, body_b, epa_result.normal);
}
}
```
---
## Performance Characteristics
* **Time complexity**: O(k × f) where k is iterations and f is faces per iteration (typically f grows slowly)
* **Space complexity**: O(n) where n is the number of polytope vertices (typically < 100)
* **Typical iterations**: 4-20 for most collisions
* **Worst case**: 64 iterations (configurable limit)
### Performance Tips
1. **Adjust max_iterations**: Balance accuracy vs. performance for your use case
2. **Tolerance tuning**: Larger tolerance = faster convergence but less accurate
3. **Shape complexity**: Simpler shapes (fewer faces) converge faster
4. **Deep penetrations**: Require more iterations; consider broad-phase separation
---
## Limitations & Edge Cases
* **Requires valid simplex**: Must be called with a 4-point simplex containing the origin (from successful GJK)
* **Convex shapes only**: Like GJK, EPA only works with convex colliders
* **Convergence failure**: Can fail to converge for degenerate or very thin shapes (check `result.success`)
* **Numerical precision**: Extreme scale differences or very small shapes may cause issues
* **Deep penetration**: Very deep intersections may require many iterations or fail to converge
### Error Handling
```cpp
auto result = Epa<Collider>::solve(a, b, simplex);
if (!result.success) {
// EPA failed to converge
// Fallback options:
// 1. Use a default separation (e.g., axis between centers)
// 2. Increase max_iterations and retry
// 3. Log a warning and skip this collision
std::cerr << "EPA failed after " << result.iterations << " iterations\n";
}
```
---
## Theory & Background
### Why EPA after GJK?
GJK determines **if** shapes intersect but doesn't compute penetration depth. EPA extends GJK's final simplex to find the exact depth and normal needed for:
* **Collision response** — separating objects realistically
* **Contact manifolds** — generating contact points for physics
* **Constraint solving** — iterative physics solvers
### Comparison with SAT
| Feature | EPA | SAT (Separating Axis Theorem) |
|---------|-----|-------------------------------|
| Works with | Any convex shape | Polytopes (faces/edges) |
| Penetration depth | Yes | Yes |
| Complexity | Iterative | Per-axis projection |
| Best for | General convex | Boxes, prisms |
| Typical speed | Moderate | Fast (few axes) |
EPA is more general; SAT is faster for axis-aligned shapes.
---
## Implementation Details
The EPA implementation in OMath:
* Uses a **priority queue** to efficiently find the closest face
* Maintains face winding for consistent normals
* Handles **edge cases**: degenerate faces, numerical instability
* Prevents infinite loops with iteration limits
* Returns detailed diagnostics (iteration count, polytope size)
---
## See Also
- [GJK Algorithm Documentation](gjk_algorithm.md) - Collision detection (required before EPA)
- [Simplex Documentation](simplex.md) - Input simplex structure
- [MeshCollider Documentation](mesh_collider.md) - Mesh-based collider
- [Mesh Documentation](../3d_primitives/mesh.md) - Mesh primitive
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
- [API Overview](../api_overview.md) - High-level API reference
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,216 @@
# `omath::collision::GjkAlgorithm` — Gilbert-Johnson-Keerthi collision detection
> Header: `omath/collision/gjk_algorithm.hpp`
> Namespace: `omath::collision`
> Depends on: `Simplex<VertexType>`, collider types with `find_abs_furthest_vertex` method
> Algorithm: **GJK** (Gilbert-Johnson-Keerthi) for convex shape collision detection
---
## Overview
The **GJK algorithm** determines whether two convex shapes intersect by iteratively constructing a simplex in Minkowski difference space. The algorithm is widely used in physics engines and collision detection systems due to its efficiency and robustness.
`GjkAlgorithm` is a template class that works with any collider type implementing the required support function interface:
* `find_abs_furthest_vertex(direction)` — returns the farthest point in the collider along the given direction.
The algorithm returns a `GjkHitInfo` containing:
* `hit` — boolean indicating whether the shapes intersect
* `simplex` — a 4-point simplex containing the origin (valid only when `hit == true`)
---
## `GjkHitInfo`
```cpp
template<class VertexType>
struct GjkHitInfo final {
bool hit{false}; // true if collision detected
Simplex<VertexType> simplex; // 4-point simplex (valid only if hit == true)
};
```
The `simplex` field is only meaningful when `hit == true` and contains 4 points. This simplex can be passed to the EPA algorithm for penetration depth calculation.
---
## `GjkAlgorithm`
```cpp
template<class ColliderType>
class GjkAlgorithm final {
using VertexType = typename ColliderType::VertexType;
public:
// Find support vertex in Minkowski difference
[[nodiscard]]
static VertexType find_support_vertex(
const ColliderType& collider_a,
const ColliderType& collider_b,
const VertexType& direction
);
// Check if two convex shapes intersect
[[nodiscard]]
static GjkHitInfo<VertexType> check_collision(
const ColliderType& collider_a,
const ColliderType& collider_b
);
};
```
---
## Collider Requirements
Any type used as `ColliderType` must provide:
```cpp
// Type alias for vertex type (typically Vector3<float>)
using VertexType = /* ... */;
// Find the farthest point in world space along the given direction
[[nodiscard]]
VertexType find_abs_furthest_vertex(const VertexType& direction) const;
```
Common collider types:
* `MeshCollider<MeshType>` — for arbitrary triangle meshes
* Custom colliders for spheres, boxes, capsules, etc.
---
## Algorithm Details
### Minkowski Difference
GJK operates in the **Minkowski difference** space `A - B`, where a point in this space represents the difference between points in shapes A and B. The shapes intersect if and only if the origin lies within this Minkowski difference.
### Support Function
The support function finds the point in the Minkowski difference farthest along a given direction:
```cpp
support(A, B, dir) = A.furthest(dir) - B.furthest(-dir)
```
This is computed by `find_support_vertex`.
### Simplex Iteration
The algorithm builds a simplex incrementally:
1. Start with an initial direction (typically vector between shape centers)
2. Add support vertices in directions that move the simplex toward the origin
3. Simplify the simplex to keep only points closest to the origin
4. Repeat until either:
* Origin is contained (collision detected, returns 4-point simplex)
* No progress can be made (no collision)
Maximum 64 iterations are performed to prevent infinite loops in edge cases.
---
## Usage Examples
### Basic Collision Check
```cpp
using namespace omath::collision;
using namespace omath::source_engine;
// Create mesh colliders
Mesh mesh_a = /* ... */;
Mesh mesh_b = /* ... */;
MeshCollider collider_a(mesh_a);
MeshCollider collider_b(mesh_b);
// Check for collision
auto result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
collider_a,
collider_b
);
if (result.hit) {
std::cout << "Collision detected!\n";
// Can pass result.simplex to EPA for penetration depth
}
```
### Combined with EPA
```cpp
auto gjk_result = GjkAlgorithm<Collider>::check_collision(a, b);
if (gjk_result.hit) {
// Get penetration depth and normal using EPA
auto epa_result = Epa<Collider>::solve(
a, b, gjk_result.simplex
);
if (epa_result.success) {
std::cout << "Penetration depth: " << epa_result.depth << "\n";
std::cout << "Separation normal: " << epa_result.normal << "\n";
}
}
```
---
## Performance Characteristics
* **Time complexity**: O(k) where k is the number of iterations (typically < 20 for most cases)
* **Space complexity**: O(1) — only stores a 4-point simplex
* **Best case**: 4-8 iterations for well-separated objects
* **Worst case**: 64 iterations (hard limit)
* **Cache efficient**: operates on small fixed-size data structures
### Optimization Tips
1. **Initial direction**: Use vector between shape centers for faster convergence
2. **Early exit**: GJK quickly rejects non-intersecting shapes
3. **Warm starting**: Reuse previous simplex for continuous collision detection
4. **Broad phase**: Use spatial partitioning before GJK (AABB trees, grids)
---
## Limitations & Edge Cases
* **Convex shapes only**: GJK only works with convex colliders. For concave shapes, decompose into convex parts or use a mesh collider wrapper.
* **Degenerate simplices**: The algorithm handles degenerate cases, but numerical precision can cause issues with very thin or flat shapes.
* **Iteration limit**: Hard limit of 64 iterations prevents infinite loops but may miss collisions in extreme cases.
* **Zero-length directions**: The simplex update logic guards against zero-length vectors, returning safe fallbacks.
---
## Vertex Type Requirements
The `VertexType` must satisfy the `GjkVector` concept (defined in `simplex.hpp`):
```cpp
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>;
};
```
`omath::Vector3<float>` satisfies this concept.
---
## See Also
- [EPA Algorithm Documentation](epa_algorithm.md) - Penetration depth calculation
- [Simplex Documentation](simplex.md) - Simplex data structure
- [MeshCollider Documentation](mesh_collider.md) - Mesh-based collider
- [Mesh Documentation](../3d_primitives/mesh.md) - Mesh primitive
- [LineTracer Documentation](line_tracer.md) - Ray-triangle intersection
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,181 @@
# `omath::collision::Ray` & `LineTracer` — RayTriangle intersection (MöllerTrumbore)
> Headers: your projects `ray.hpp` (includes `omath/linear_algebra/triangle.hpp`, `omath/linear_algebra/vector3.hpp`)
> Namespace: `omath::collision`
> Depends on: `omath::Vector3<float>`, `omath::Triangle<Vector3<float>>`
> Algorithm: **MöllerTrumbore** raytriangle intersection (no allocation)
---
## Overview
These types provide a minimal, fast path to test and compute intersections between a **ray or line segment** and a **single triangle**:
* `Ray` — start/end points plus a flag to treat the ray as **infinite** (half-line) or a **finite segment**.
* `LineTracer` — static helpers:
* `can_trace_line(ray, triangle)``true` if they intersect.
* `get_ray_hit_point(ray, triangle)` → the hit point (precondition: intersection exists).
---
## `Ray`
```cpp
class Ray {
public:
omath::Vector3<float> start; // ray origin
omath::Vector3<float> end; // end point (for finite segment) or a point along the direction
bool infinite_length = false;
[[nodiscard]] omath::Vector3<float> direction_vector() const noexcept;
[[nodiscard]] omath::Vector3<float> direction_vector_normalized() const noexcept;
};
```
### Semantics
* **Direction**: `direction_vector() == end - start`.
The normalized variant returns a unit vector (or `{0,0,0}` if the direction length is zero).
* **Extent**:
* `infinite_length == true` → treat as a **semi-infinite ray** from `start` along `direction`.
* `infinite_length == false` → treat as a **closed segment** from `start` to `end`.
> Tip: For an infinite ray that points along some vector `d`, set `end = start + d`.
---
## `LineTracer`
```cpp
class LineTracer {
public:
LineTracer() = delete;
[[nodiscard]]
static bool can_trace_line(
const Ray& ray,
const omath::Triangle<omath::Vector3<float>>& triangle
) noexcept;
// MöllerTrumbore intersection
[[nodiscard]]
static omath::Vector3<float> get_ray_hit_point(
const Ray& ray,
const omath::Triangle<omath::Vector3<float>>& triangle
) noexcept;
};
```
### Behavior & contract
* **Intersection test**: `can_trace_line` returns `true` iff the ray/segment intersects the triangle (within the rays extent).
* **Hit point**: `get_ray_hit_point` **assumes** there is an intersection.
Call **only after** `can_trace_line(...) == true`. Otherwise the result is unspecified.
* **Triangle winding**: Standard MöllerTrumbore works with either winding; no backface culling is implied here.
* **Degenerate inputs**: A zero-length ray or degenerate triangle yields **no hit** under typical MöllerTrumbore tolerances.
---
## Quick examples
### 1) Segment vs triangle
```cpp
using omath::Vector3;
using omath::Triangle;
using omath::collision::Ray;
using omath::collision::LineTracer;
Triangle<Vector3<float>> tri(
Vector3<float>{0, 0, 0},
Vector3<float>{1, 0, 0},
Vector3<float>{0, 1, 0}
);
Ray seg;
seg.start = {0.25f, 0.25f, 1.0f};
seg.end = {0.25f, 0.25f,-1.0f};
seg.infinite_length = false; // finite segment
if (LineTracer::can_trace_line(seg, tri)) {
Vector3<float> hit = LineTracer::get_ray_hit_point(seg, tri);
// use hit
}
```
### 2) Infinite ray
```cpp
Ray ray;
ray.start = {0.5f, 0.5f, 1.0f};
ray.end = ray.start + Vector3<float>{0, 0, -1}; // direction only
ray.infinite_length = true;
bool hit = LineTracer::can_trace_line(ray, tri);
```
---
## Notes & edge cases
* **Normalization**: `direction_vector_normalized()` returns `{0,0,0}` for a zero-length direction (safe, but unusable for tracing).
* **Precision**: The underlying algorithm uses EPS thresholds to reject nearly parallel cases; results near edges can be sensitive to floating-point error. If you need robust edge inclusion/exclusion, document and enforce a policy (e.g., inclusive barycentric range with small epsilon).
* **Hit location**: The point returned by `get_ray_hit_point` lies **on the triangle plane** and within its area by construction (when `can_trace_line` is `true`).
---
## API summary
```cpp
namespace omath::collision {
class Ray {
public:
Vector3<float> start, end;
bool infinite_length = false;
[[nodiscard]] Vector3<float> direction_vector() const noexcept;
[[nodiscard]] Vector3<float> direction_vector_normalized() const noexcept;
};
class LineTracer {
public:
LineTracer() = delete;
[[nodiscard]] static bool can_trace_line(
const Ray&,
const omath::Triangle<omath::Vector3<float>>&
) noexcept;
[[nodiscard]] static Vector3<float> get_ray_hit_point(
const Ray&,
const omath::Triangle<omath::Vector3<float>>&
) noexcept; // precondition: can_trace_line(...) == true
};
} // namespace omath::collision
```
---
## Implementation hints (if you extend it)
* Expose a variant that returns **barycentric coordinates** `(u, v, w)` alongside the hit point to support texture lookup or edge tests.
* Provide an overload returning `std::optional<Vector3<float>>` (or `expected`) for safer one-shot queries without a separate test call.
* If you need backface culling, add a flag or dedicated function (reject hits where the signed distance is negative with respect to triangle normal).
---
## See Also
- [Plane Documentation](../3d_primitives/plane.md) - Ray-plane intersection
- [Box Documentation](../3d_primitives/box.md) - AABB collision detection
- [Triangle Documentation](../linear_algebra/triangle.md) - Triangle primitives
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,371 @@
# `omath::collision::MeshCollider` — Convex hull collider for meshes
> Header: `omath/collision/mesh_collider.hpp`
> Namespace: `omath::collision`
> Depends on: `omath::primitives::Mesh`, `omath::Vector3<T>`
> Purpose: wrap a mesh to provide collision detection support for GJK/EPA
---
## Overview
`MeshCollider` wraps a `Mesh` object to provide the **support function** interface required by the GJK and EPA collision detection algorithms. The support function finds the vertex of the mesh farthest along a given direction, which is essential for constructing Minkowski difference simplices.
**Important**: `MeshCollider` assumes the mesh represents a **convex hull**. For non-convex shapes, you must either:
* Decompose into convex parts
* Use the convex hull of the mesh
* Use a different collision detection algorithm
---
## Template Declaration
```cpp
template<class MeshType>
class MeshCollider;
```
### MeshType Requirements
The `MeshType` must be an instantiation of `omath::primitives::Mesh` or provide:
```cpp
struct MeshType {
using NumericType = /* float, double, etc. */;
std::vector<Vector3<NumericType>> m_vertex_buffer;
// Transform vertex from local to world space
Vector3<NumericType> vertex_to_world_space(const Vector3<NumericType>&) const;
};
```
Common types:
* `omath::source_engine::Mesh`
* `omath::unity_engine::Mesh`
* `omath::unreal_engine::Mesh`
* `omath::frostbite_engine::Mesh`
* `omath::iw_engine::Mesh`
* `omath::opengl_engine::Mesh`
---
## Type Aliases
```cpp
using NumericType = typename MeshType::NumericType;
using VertexType = Vector3<NumericType>;
```
* `NumericType` — scalar type (typically `float`)
* `VertexType` — 3D vector type for vertices
---
## Constructor
```cpp
explicit MeshCollider(MeshType mesh);
```
Creates a collider from a mesh. The mesh is **moved** into the collider, so pass by value:
```cpp
omath::source_engine::Mesh my_mesh = /* ... */;
MeshCollider collider(std::move(my_mesh));
```
---
## Methods
### `find_furthest_vertex`
```cpp
[[nodiscard]]
const VertexType& find_furthest_vertex(const VertexType& direction) const;
```
Finds the vertex in the mesh's **local space** that has the maximum dot product with `direction`.
**Algorithm**: Linear search through all vertices (O(n) where n is vertex count).
**Returns**: Const reference to the vertex in `m_vertex_buffer`.
---
### `find_abs_furthest_vertex`
```cpp
[[nodiscard]]
VertexType find_abs_furthest_vertex(const VertexType& direction) const;
```
Finds the vertex farthest along `direction` and transforms it to **world space**. This is the primary method used by GJK/EPA.
**Steps**:
1. Find furthest vertex in local space using `find_furthest_vertex`
2. Transform to world space using `mesh.vertex_to_world_space()`
**Returns**: Vertex position in world coordinates.
**Usage in GJK**:
```cpp
// GJK support function for Minkowski difference
VertexType support = collider_a.find_abs_furthest_vertex(direction)
- collider_b.find_abs_furthest_vertex(-direction);
```
---
## Usage Examples
### Basic Collision Detection
```cpp
using namespace omath::collision;
using namespace omath::source_engine;
// Create meshes with vertex data
std::vector<Vector3<float>> vbo_a = {
{-1, -1, -1}, {1, -1, -1}, {1, 1, -1}, {-1, 1, -1},
{-1, -1, 1}, {1, -1, 1}, {1, 1, 1}, {-1, 1, 1}
};
std::vector<Vector3<std::size_t>> vao_a = /* face indices */;
Mesh mesh_a(vbo_a, vao_a);
mesh_a.set_origin({0, 0, 0});
Mesh mesh_b(vbo_b, vao_b);
mesh_b.set_origin({5, 0, 0}); // Positioned away from mesh_a
// Wrap in colliders
MeshCollider<Mesh> collider_a(std::move(mesh_a));
MeshCollider<Mesh> collider_b(std::move(mesh_b));
// Run GJK
auto result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
collider_a, collider_b
);
if (result.hit) {
std::cout << "Collision detected!\n";
}
```
### With EPA for Penetration Depth
```cpp
auto gjk_result = GjkAlgorithm<MeshCollider<Mesh>>::check_collision(
collider_a, collider_b
);
if (gjk_result.hit) {
auto epa_result = Epa<MeshCollider<Mesh>>::solve(
collider_a, collider_b, gjk_result.simplex
);
if (epa_result.success) {
std::cout << "Penetration: " << epa_result.depth << " units\n";
std::cout << "Normal: " << epa_result.normal << "\n";
}
}
```
### Custom Mesh Creation
```cpp
// Create a simple box mesh
std::vector<Vector3<float>> box_vertices = {
{-0.5f, -0.5f, -0.5f}, { 0.5f, -0.5f, -0.5f},
{ 0.5f, 0.5f, -0.5f}, {-0.5f, 0.5f, -0.5f},
{-0.5f, -0.5f, 0.5f}, { 0.5f, -0.5f, 0.5f},
{ 0.5f, 0.5f, 0.5f}, {-0.5f, 0.5f, 0.5f}
};
std::vector<Vector3<std::size_t>> box_indices = {
{0, 1, 2}, {0, 2, 3}, // Front face
{4, 6, 5}, {4, 7, 6}, // Back face
{0, 4, 5}, {0, 5, 1}, // Bottom face
{2, 6, 7}, {2, 7, 3}, // Top face
{0, 3, 7}, {0, 7, 4}, // Left face
{1, 5, 6}, {1, 6, 2} // Right face
};
using namespace omath::source_engine;
Mesh box_mesh(box_vertices, box_indices);
box_mesh.set_origin({10, 0, 0});
box_mesh.set_scale({2, 2, 2});
MeshCollider<Mesh> box_collider(std::move(box_mesh));
```
### Oriented Collision
```cpp
// Create rotated mesh
Mesh mesh(vertices, indices);
mesh.set_origin({5, 5, 5});
mesh.set_scale({1, 1, 1});
// Set rotation (engine-specific angles)
ViewAngles rotation;
rotation.pitch = PitchAngle::from_degrees(45.0f);
rotation.yaw = YawAngle::from_degrees(30.0f);
mesh.set_rotation(rotation);
// Collider automatically handles transformation
MeshCollider<Mesh> collider(std::move(mesh));
// Support function returns world-space vertices
auto support = collider.find_abs_furthest_vertex({0, 1, 0});
```
---
## Performance Considerations
### Linear Search
`find_furthest_vertex` performs a **linear search** through all vertices:
* **Time complexity**: O(n) per support query
* **GJK iterations**: ~10-20 support queries per collision test
* **Total cost**: O(k × n) where k is GJK iterations
For meshes with many vertices (>1000), consider:
* Using simpler proxy geometry (bounding box, convex hull with fewer vertices)
* Pre-computing hierarchical structures
* Using specialized collision shapes when possible
### Caching Opportunities
The implementation uses `std::ranges::max_element`, which is cache-friendly for contiguous vertex buffers. For optimal performance:
* Store vertices contiguously in memory
* Avoid pointer-based or scattered vertex storage
* Consider SoA (Structure of Arrays) layout for SIMD optimization
### World Space Transformation
The `vertex_to_world_space` call involves matrix multiplication:
* **Cost**: ~15-20 floating-point operations per vertex
* **Optimization**: The mesh caches its transformation matrix
* **Update cost**: Only recomputed when origin/rotation/scale changes
---
## Limitations & Edge Cases
### Convex Hull Requirement
**Critical**: GJK/EPA only work with **convex shapes**. If your mesh is concave:
#### Option 1: Convex Decomposition
```cpp
// Decompose concave mesh into convex parts
std::vector<Mesh> convex_parts = decompose_mesh(concave_mesh);
for (const auto& part : convex_parts) {
MeshCollider collider(part);
// Test each part separately
}
```
#### Option 2: Use Convex Hull
```cpp
// Compute convex hull of vertices
auto hull_vertices = compute_convex_hull(mesh.m_vertex_buffer);
Mesh hull_mesh(hull_vertices, hull_indices);
MeshCollider collider(std::move(hull_mesh));
```
#### Option 3: Different Algorithm
Use triangle-based collision (e.g., LineTracer) for true concave support.
### Empty Mesh
Behavior is undefined if `m_vertex_buffer` is empty. Always ensure:
```cpp
assert(!mesh.m_vertex_buffer.empty());
MeshCollider collider(std::move(mesh));
```
### Degenerate Meshes
* **Single vertex**: Treated as a point (degenerates to sphere collision)
* **Two vertices**: Line segment (may cause GJK issues)
* **Coplanar vertices**: Flat mesh; EPA may have convergence issues
**Recommendation**: Use at least 4 non-coplanar vertices for robustness.
---
## Coordinate Systems
`MeshCollider` supports different engine coordinate systems through the `MeshTrait`:
| Engine | Up Axis | Handedness | Rotation Order |
|--------|---------|------------|----------------|
| Source Engine | Z | Right-handed | Pitch/Yaw/Roll |
| Unity | Y | Left-handed | Pitch/Yaw/Roll |
| Unreal | Z | Left-handed | Roll/Pitch/Yaw |
| Frostbite | Y | Right-handed | Pitch/Yaw/Roll |
| IW Engine | Z | Right-handed | Pitch/Yaw/Roll |
| OpenGL | Y | Right-handed | Pitch/Yaw/Roll |
The `vertex_to_world_space` method handles these differences transparently.
---
## Advanced Usage
### Custom Support Function
For specialized collision shapes, implement a custom collider:
```cpp
class SphereCollider {
public:
using VertexType = Vector3<float>;
Vector3<float> center;
float radius;
VertexType find_abs_furthest_vertex(const VertexType& direction) const {
auto normalized = direction.normalized();
return center + normalized * radius;
}
};
// Use with GJK/EPA
auto result = GjkAlgorithm<SphereCollider>::check_collision(sphere_a, sphere_b);
```
### Debugging Support Queries
```cpp
class DebugMeshCollider : public MeshCollider<Mesh> {
public:
using MeshCollider::MeshCollider;
VertexType find_abs_furthest_vertex(const VertexType& direction) const {
auto result = MeshCollider::find_abs_furthest_vertex(direction);
std::cout << "Support query: direction=" << direction
<< " -> vertex=" << result << "\n";
return result;
}
};
```
---
## See Also
- [GJK Algorithm Documentation](gjk_algorithm.md) - Uses `MeshCollider` for collision detection
- [EPA Algorithm Documentation](epa_algorithm.md) - Uses `MeshCollider` for penetration depth
- [Simplex Documentation](simplex.md) - Data structure used by GJK
- [Mesh Documentation](../3d_primitives/mesh.md) - Underlying mesh primitive
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Complete collision tutorial
---
*Last updated: 13 Nov 2025*

327
docs/collision/simplex.md Normal file
View File

@@ -0,0 +1,327 @@
# `omath::collision::Simplex` — Fixed-capacity simplex for GJK/EPA
> Header: `omath/collision/simplex.hpp`
> Namespace: `omath::collision`
> Depends on: `Vector3<float>` (or any type satisfying `GjkVector` concept)
> Purpose: store and manipulate simplices in GJK and EPA algorithms
---
## Overview
`Simplex` is a lightweight container for up to 4 points, used internally by the GJK and EPA collision detection algorithms. A simplex in this context is a geometric shape defined by 1 to 4 vertices:
* **1 point** — a single vertex
* **2 points** — a line segment
* **3 points** — a triangle
* **4 points** — a tetrahedron
The GJK algorithm builds simplices incrementally to detect collisions, and EPA extends a 4-point simplex to compute penetration depth.
---
## Template & Concepts
```cpp
template<GjkVector VectorType = Vector3<float>>
class Simplex final;
```
### `GjkVector` Concept
The vertex type must satisfy:
```cpp
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>;
};
```
`omath::Vector3<float>` satisfies this concept and is the default.
---
## Constructors & Assignment
```cpp
constexpr Simplex() = default;
constexpr Simplex& operator=(std::initializer_list<VectorType> list) noexcept;
```
### Initialization
```cpp
// Empty simplex
Simplex<Vector3<float>> s;
// Initialize with points
Simplex<Vector3<float>> s2;
s2 = {v1, v2, v3}; // 3-point simplex (triangle)
```
**Constraint**: Maximum 4 points. Passing more triggers an assertion in debug builds.
---
## Core Methods
### Adding Points
```cpp
constexpr void push_front(const VectorType& p) noexcept;
```
Inserts a point at the **front** (index 0), shifting existing points back. If the simplex is already at capacity (4 points), the last point is discarded.
**Usage pattern in GJK**:
```cpp
simplex.push_front(new_support_point);
// Now simplex[0] is the newest point
```
### Size & Capacity
```cpp
[[nodiscard]] constexpr std::size_t size() const noexcept;
[[nodiscard]] static constexpr std::size_t capacity = 4;
```
* `size()` — current number of points (0-4)
* `capacity` — maximum points (always 4)
### Element Access
```cpp
[[nodiscard]] constexpr VectorType& operator[](std::size_t index) noexcept;
[[nodiscard]] constexpr const VectorType& operator[](std::size_t index) const noexcept;
```
Access points by index. **No bounds checking** — index must be `< size()`.
```cpp
if (simplex.size() >= 2) {
auto edge = simplex[1] - simplex[0];
}
```
### Iterators
```cpp
[[nodiscard]] constexpr auto begin() noexcept;
[[nodiscard]] constexpr auto end() noexcept;
[[nodiscard]] constexpr auto begin() const noexcept;
[[nodiscard]] constexpr auto end() const noexcept;
```
Standard iterator support for range-based loops:
```cpp
for (const auto& vertex : simplex) {
std::cout << vertex << "\n";
}
```
---
## GJK-Specific Methods
These methods implement the core logic for simplifying simplices in the GJK algorithm.
### `contains_origin`
```cpp
[[nodiscard]] constexpr bool contains_origin() noexcept;
```
Determines if the origin lies within the current simplex. This is the **core GJK test**: if true, the shapes intersect.
* For a **1-point** simplex, always returns `false` (can't contain origin)
* For a **2-point** simplex (line), checks if origin projects onto the segment
* For a **3-point** simplex (triangle), checks if origin projects onto the triangle
* For a **4-point** simplex (tetrahedron), checks if origin is inside
**Side effect**: Simplifies the simplex by removing points not needed to maintain proximity to the origin. After calling, `size()` may have decreased.
**Return value**:
* `true` — origin is contained (collision detected)
* `false` — origin not contained; simplex has been simplified toward origin
### `next_direction`
```cpp
[[nodiscard]] constexpr VectorType next_direction() const noexcept;
```
Computes the next search direction for GJK. This is the direction from the simplex toward the origin, used to query the next support point.
* Must be called **after** `contains_origin()` returns `false`
* Behavior is **undefined** if called when `size() == 0` or when origin is already contained
---
## Usage Examples
### GJK Iteration (Simplified)
```cpp
Simplex<Vector3<float>> simplex;
Vector3<float> direction{1, 0, 0}; // Initial search direction
for (int i = 0; i < 64; ++i) {
// Get support point in current direction
auto support = find_support_vertex(collider_a, collider_b, direction);
// Check if we made progress
if (support.dot(direction) <= 0)
break; // No collision possible
simplex.push_front(support);
// Check if simplex contains origin
if (simplex.contains_origin()) {
// Collision detected!
assert(simplex.size() == 4);
return GjkHitInfo{true, simplex};
}
// Get next search direction
direction = simplex.next_direction();
}
// No collision
return GjkHitInfo{false, {}};
```
### Manual Simplex Construction
```cpp
using Vec3 = Vector3<float>;
Simplex<Vec3> simplex;
simplex = {
Vec3{0.0f, 0.0f, 0.0f},
Vec3{1.0f, 0.0f, 0.0f},
Vec3{0.0f, 1.0f, 0.0f},
Vec3{0.0f, 0.0f, 1.0f}
};
assert(simplex.size() == 4);
// Check if origin is inside this tetrahedron
bool has_collision = simplex.contains_origin();
```
### Iterating Over Points
```cpp
void print_simplex(const Simplex<Vector3<float>>& s) {
std::cout << "Simplex with " << s.size() << " points:\n";
for (std::size_t i = 0; i < s.size(); ++i) {
const auto& p = s[i];
std::cout << " [" << i << "] = ("
<< p.x << ", " << p.y << ", " << p.z << ")\n";
}
}
```
---
## Implementation Details
### Simplex Simplification
The `contains_origin()` method implements different tests based on simplex size:
#### Line Segment (2 points)
Checks if origin projects onto segment `[A, B]`:
* If yes, keeps both points
* If no, keeps only the closer point
#### Triangle (3 points)
Tests the origin against the triangle plane and edges using cross products. Simplifies to:
* The full triangle if origin projects onto its surface
* An edge if origin is closest to that edge
* A single vertex otherwise
#### Tetrahedron (4 points)
Tests origin against all four faces:
* If origin is inside, returns `true` (collision)
* If outside, reduces to the face/edge/vertex closest to origin
### Direction Calculation
The `next_direction()` method computes:
* For **line**: perpendicular from line toward origin
* For **triangle**: perpendicular from triangle toward origin
* Implementation uses cross products and projections to avoid sqrt when possible
---
## Performance Characteristics
* **Storage**: Fixed 4 × `sizeof(VectorType)` + size counter
* **Push front**: O(n) where n is current size (max 4, so effectively O(1))
* **Contains origin**: O(1) for each case (line, triangle, tetrahedron)
* **Next direction**: O(1) — simple cross products and subtractions
* **No heap allocations**: All storage is inline
**constexpr**: All methods are `constexpr`, enabling compile-time usage where feasible.
---
## Edge Cases & Constraints
### Degenerate Simplices
* **Zero-length edges**: Can occur if support points coincide. The algorithm handles this by checking `point_to_same_direction` before divisions.
* **Collinear points**: Triangle simplification detects and handles collinear cases by reducing to a line.
* **Flat tetrahedron**: If the 4th point is coplanar with the first 3, the origin containment test may have reduced precision.
### Assertions
* **Capacity**: `operator=` asserts `list.size() <= 4` in debug builds
* **Index bounds**: No bounds checking in release builds — ensure `index < size()`
### Thread Safety
* **Read-only**: Safe to read from multiple threads
* **Modification**: Not thread-safe; synchronize writes externally
---
## Relationship to GJK & EPA
### In GJK
* Starts empty or with an initial point
* Grows via `push_front` as support points are added
* Shrinks via `contains_origin` as it's simplified
* Once it reaches 4 points and contains origin, GJK succeeds
### In EPA
* Takes a 4-point simplex from GJK as input
* Uses the tetrahedron as the initial polytope
* Does not directly use the `Simplex` class for expansion (EPA maintains a more complex polytope structure)
---
## See Also
- [GJK Algorithm Documentation](gjk_algorithm.md) - Uses `Simplex` for collision detection
- [EPA Algorithm Documentation](epa_algorithm.md) - Takes 4-point `Simplex` as input
- [MeshCollider Documentation](mesh_collider.md) - Provides support function for GJK/EPA
- [Vector3 Documentation](../linear_algebra/vector3.md) - Default vertex type
- [Tutorials - Collision Detection](../tutorials.md#tutorial-4-collision-detection) - Collision tutorial
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,108 @@
# `omath::frostbite_engine::CameraTrait` — plug-in trait for `projection::Camera`
> Header: `omath/engines/frostbite_engine/traits/camera_trait.hpp` • Impl: `omath/engines/frostbite_engine/traits/camera_trait.cpp`
> Namespace: `omath::frostbite_engine`
> Purpose: provide Frostbite-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
---
## Summary
`CameraTrait` exposes three `static` functions:
* `calc_look_at_angle(origin, look_at)` computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `-asin(dir.y)` and **yaw** as `atan2(dir.x, dir.z)`; **roll** is `0`. Pitch/yaw are returned using the projects strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
* `calc_view_matrix(angles, origin)` delegates to Frostbite formulas `frostbite_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
* `calc_projection_matrix(fov, viewport, near, far)` builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
The traits types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Frostbite engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
---
## API
```cpp
namespace omath::frostbite_engine {
class CameraTrait final {
public:
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
static ViewAngles
calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept;
// Build view matrix for given angles and origin.
static Mat4X4
calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection from FOV (deg), viewport, near/far.
static Mat4X4
calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port,
float near, float far) noexcept;
};
} // namespace omath::frostbite_engine
```
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
---
## Behavior & conventions
* **Angles from look-at**:
```
dir = normalize(look_at - origin)
pitch = -asin(dir.y) // +Y is up
yaw = atan2(dir.x, dir.z)
roll = 0
```
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
* **View matrix**: built by the Frostbite engine helper `frostbite_engine::calc_view_matrix(angles, origin)` to match the engines handedness and axis conventions.
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
---
## Using with `projection::Camera`
Create a camera whose math is driven by this trait:
```cpp
using Mat4 = Mat4X4; // from Frostbite math headers
using Angs = ViewAngles; // pitch/yaw/roll type
using FBcam = omath::projection::Camera<Mat4, Angs, omath::frostbite_engine::CameraTrait>;
omath::projection::ViewPort vp{1920.f, 1080.f};
auto fov = omath::projection::FieldOfView::from_degrees(70.f);
FBcam cam(
/*position*/ {0.f, 1.7f, -3.f},
/*angles*/ omath::frostbite_engine::CameraTrait::calc_look_at_angle({0,1.7f,-3},{0,1.7f,0}),
/*viewport*/ vp,
/*fov*/ fov,
/*near*/ 0.1f,
/*far*/ 1000.f
);
```
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
---
## Notes & tips
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the projects angle policy (ranges/normalization). The implementation constructs them **from radians**.
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
---
## See also
* Frostbite math helpers in `omath/engines/frostbite_engine/formulas.hpp` (view/projection builders used above).
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).

View File

@@ -0,0 +1,59 @@
Nice! A clean little “types + constants” header. A few quick fixes and polish:
## Issues / suggestions
1. **Mat3X3 alias is wrong**
* You wrote `using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;`
* That should be `Mat<3, 3, ...>`.
2. **`constexpr` globals in a header → make them `inline constexpr`**
* Since this is in a header included by multiple TUs, use `inline constexpr` to avoid ODR/link issues (C++17+).
3. **Consider column-major vs row-major**
* Most game/graphics stacks (GLSL/HLSL, many engines) lean column-major and column vectors. If the rest of your math lib or shaders assume column-major, align these typedefs now to avoid silent transposes later. If row-major is intentional, all good—just be consistent.
4. **Naming consistency**
* If you prefer `k_` prefix, keep it; otherwise consider `kAbsUp`/`ABS_UP` to match your codebases conventions.
5. **`Mat1X3` as a “row vector”**
* If you actually use it as a 3-component vector, consider just `Vector3<float>` (clearer) and reserve `Mat1X3` for real row-vector math.
---
## Tidied version
```cpp
// Created by Vlad on 10/21/2025.
#pragma once
#include "omath/linear_algebra/mat.hpp"
#include "omath/linear_algebra/vector3.hpp"
#include <omath/trigonometry/angle.hpp>
#include <omath/trigonometry/view_angles.hpp>
namespace omath::frostbite_engine
{
// Inline to avoid ODR across translation units
inline constexpr Vector3<float> k_abs_up = {0.0f, 1.0f, 0.0f};
inline constexpr Vector3<float> k_abs_right = {1.0f, 0.0f, 0.0f};
inline constexpr Vector3<float> k_abs_forward = {0.0f, 0.0f, 1.0f};
// NOTE: verify row/column major matches the rest of your engine
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat3X3 = Mat<3, 3, float, MatStoreType::ROW_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
using PitchAngle = Angle<float, -90.0f, 90.0f, AngleFlags::Clamped >;
using YawAngle = Angle<float,-180.0f, 180.0f, AngleFlags::Normalized>;
using RollAngle = Angle<float,-180.0f, 180.0f, AngleFlags::Normalized>;
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
} // namespace omath::frostbite_engine
```
If you share how your matrices multiply vectors (row vs column) and your world handedness, I can double-check the axis constants and angle normalization to make sure yaw/pitch signs line up with your camera and `atan2` usage.

View File

View File

@@ -0,0 +1,119 @@
# `omath::frostbite_engine::MeshTrait` — mesh transformation trait for Frostbite Engine
> Header: `omath/engines/frostbite_engine/traits/mesh_trait.hpp`
> Namespace: `omath::frostbite_engine`
> Purpose: provide Frostbite Engine-specific rotation matrix computation for `omath::primitives::Mesh`
---
## Summary
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Frostbite's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
---
## Coordinate System
**Frostbite Engine** uses:
* **Up axis**: +Y
* **Forward axis**: +Z
* **Right axis**: +X
* **Handedness**: Right-handed
* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z)
---
## API
```cpp
namespace omath::frostbite_engine {
class MeshTrait final {
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
};
} // namespace omath::frostbite_engine
```
---
## Method: `rotation_matrix`
```cpp
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
```
Computes a 4×4 rotation matrix from Frostbite-style Euler angles.
**Parameters**:
* `rotation``ViewAngles` containing pitch, yaw, and roll angles
**Returns**: 4×4 rotation matrix suitable for mesh transformation
**Implementation**: Delegates to `frostbite_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
---
## Usage
### With Mesh
```cpp
using namespace omath::frostbite_engine;
// Create mesh (MeshTrait is used automatically)
Mesh my_mesh(vertices, indices);
// Set rotation using ViewAngles
ViewAngles angles;
angles.pitch = PitchAngle::from_degrees(30.0f);
angles.yaw = YawAngle::from_degrees(45.0f);
angles.roll = RollAngle::from_degrees(0.0f);
my_mesh.set_rotation(angles);
// The rotation matrix is computed using MeshTrait::rotation_matrix
auto matrix = my_mesh.get_to_world_matrix();
```
---
## Rotation Conventions
Frostbite uses a right-handed Y-up coordinate system:
1. **Pitch** (rotation around X-axis / right axis)
* Positive pitch looks upward (+Y direction)
* Range: typically [-89°, 89°]
2. **Yaw** (rotation around Y-axis / up axis)
* Positive yaw rotates counterclockwise when viewed from above (right-handed)
* Range: [-180°, 180°]
3. **Roll** (rotation around Z-axis / forward axis)
* Positive roll tilts right
* Range: [-180°, 180°]
---
## Type Alias
```cpp
namespace omath::frostbite_engine {
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## See Also
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
- [Formulas Documentation](formulas.md) - Frostbite rotation formula
- [CameraTrait Documentation](camera_trait.md) - Camera trait
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,105 @@
// Created by Vlad on 8/6/2025.
#pragma once
#include <cmath> // sqrt, hypot, tan, asin, atan2
#include <optional>
#include "omath/engines/frostbite_engine/formulas.hpp"
#include "omath/projectile_prediction/projectile.hpp"
#include "omath/projectile_prediction/target.hpp"
namespace omath::frostbite_engine
{
class PredEngineTrait final
{
public:
// Predict projectile position given launch angles (degrees), time (s), and world gravity (m/s^2).
// Note: kept runtime function; remove constexpr to avoid CTAD surprises across toolchains.
static Vector3<float> predict_projectile_position(
const projectile_prediction::Projectile& projectile,
float pitch_deg, float yaw_deg,
float time, float gravity) noexcept
{
// Engine convention: negative pitch looks up (your original used -pitch).
const auto fwd = forward_vector({
PitchAngle::from_degrees(-pitch_deg),
YawAngle::from_degrees(yaw_deg),
RollAngle::from_degrees(0.0f)
});
Vector3<float> pos =
projectile.m_origin +
fwd * (projectile.m_launch_speed * time);
// s = 1/2 a t^2 downward
pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
return pos;
}
[[nodiscard]]
static Vector3<float> predict_target_position(
const projectile_prediction::Target& target,
float time, float gravity) noexcept
{
Vector3<float> predicted = target.m_origin + target.m_velocity * time;
if (target.m_is_airborne) {
// If targets also have a gravity scale in your model, multiply here.
predicted.y -= gravity * (time * time) * 0.5f;
}
return predicted;
}
[[nodiscard]]
static float calc_vector_2d_distance(const Vector3<float>& delta) noexcept
{
// More stable than sqrt(x*x + z*z)
return std::hypot(delta.x, delta.z);
}
[[nodiscard]]
static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
{
return vec.y;
}
// Computes a viewpoint above the predicted target, using an optional projectile pitch.
// If pitch is absent, we leave Y unchanged (or you can choose a sensible default).
[[nodiscard]]
static Vector3<float> calc_viewpoint_from_angles(
const projectile_prediction::Projectile& projectile,
const Vector3<float>& predicted_target_position,
const std::optional<float> projectile_pitch_deg) noexcept
{
// Lateral separation from projectile to target (X/Z plane).
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
float y = predicted_target_position.y;
if (projectile_pitch_deg.has_value()) {
const float pitch_rad = angles::degrees_to_radians(*projectile_pitch_deg);
const float height = delta2d * std::tan(pitch_rad);
y += height;
}
// Use the target's Z, not the projectile's Z (likely bugfix).
return { predicted_target_position.x, y, predicted_target_position.z };
}
// Due to maybe_calculate_projectile_launch_pitch_angle spec: +89° up, -89° down.
[[nodiscard]]
static float calc_direct_pitch_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept
{
const auto direction = (view_to - origin).normalized();
return angles::radians_to_degrees(std::asin(direction.y));
}
[[nodiscard]]
static float calc_direct_yaw_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept
{
const auto direction = (view_to - origin).normalized();
return angles::radians_to_degrees(std::atan2(direction.x, direction.z));
}
};
} // namespace omath::frostbite_engine

View File

@@ -0,0 +1,109 @@
# `omath::iw_engine::CameraTrait` — plug-in trait for `projection::Camera`
> Header: `omath/engines/iw_engine/traits/camera_trait.hpp` • Impl: `omath/engines/iw_engine/traits/camera_trait.cpp`
> Namespace: `omath::iw_engine`
> Purpose: provide IW Engine (Call of Duty)-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
---
## Summary
`CameraTrait` exposes three `static` functions:
* `calc_look_at_angle(origin, look_at)` computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
* `calc_view_matrix(angles, origin)` delegates to IW Engine formulas `iw_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
* `calc_projection_matrix(fov, viewport, near, far)` builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the IW Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
---
## API
```cpp
namespace omath::iw_engine {
class CameraTrait final {
public:
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
static ViewAngles
calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept;
// Build view matrix for given angles and origin.
static Mat4X4
calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection from FOV (deg), viewport, near/far.
static Mat4X4
calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port,
float near, float far) noexcept;
};
} // namespace omath::iw_engine
```
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
---
## Behavior & conventions
* **Angles from look-at** (Z-up coordinate system):
```
dir = normalize(look_at - origin)
pitch = asin(dir.z) // +Z is up
yaw = atan2(dir.y, dir.x) // horizontal rotation
roll = 0
```
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
* **View matrix**: built by the IW Engine helper `iw_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
---
## Using with `projection::Camera`
Create a camera whose math is driven by this trait:
```cpp
using Mat4 = Mat4X4; // from IW Engine math headers
using Angs = ViewAngles; // pitch/yaw/roll type
using IWcam = omath::projection::Camera<Mat4, Angs, omath::iw_engine::CameraTrait>;
omath::projection::ViewPort vp{1920.f, 1080.f};
auto fov = omath::projection::FieldOfView::from_degrees(65.f);
IWcam cam(
/*position*/ {500.f, 200.f, 100.f},
/*angles*/ omath::iw_engine::CameraTrait::calc_look_at_angle({500,200,100},{0,0,100}),
/*viewport*/ vp,
/*fov*/ fov,
/*near*/ 0.1f,
/*far*/ 5000.f
);
```
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
---
## Notes & tips
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
* IW Engine uses **Z-up**: pitch angles control vertical look, positive = up.
---
## See also
* IW Engine math helpers in `omath/engines/iw_engine/formulas.hpp` (view/projection builders used above).
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).

View File

@@ -0,0 +1,77 @@
# `omath::iw_engine` — types & constants
> Header: `omath/engines/iw_engine/constants.hpp`
> Namespace: `omath::iw_engine`
> Purpose: define IW Engine (Call of Duty) coordinate system, matrix types, and angle ranges
---
## Summary
The **IW Engine** (Infinity Ward Engine, used in Call of Duty series) uses a **Z-up, right-handed** coordinate system:
* **Up** = `{0, 0, 1}` (Z-axis)
* **Right** = `{0, -1, 0}` (negative Y-axis)
* **Forward** = `{1, 0, 0}` (X-axis)
Matrices are **row-major**. Angles are **clamped pitch** (±89°) and **normalized yaw/roll** (±180°).
---
## Constants
```cpp
namespace omath::iw_engine {
constexpr Vector3<float> k_abs_up = {0, 0, 1};
constexpr Vector3<float> k_abs_right = {0, -1, 0};
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
}
```
These basis vectors define the engine's **world coordinate frame**.
---
## Matrix types
```cpp
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
```
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
---
## Angle types
```cpp
using PitchAngle = Angle<float, -89.f, 89.f, AngleFlags::Clamped>;
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
```
* **PitchAngle**: clamped to **[-89°, +89°]** (looking down vs. up)
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
`ViewAngles` bundles all three into a single type for camera/view transforms.
---
## Coordinate system notes
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
* **Right-handed**: cross product `forward × right = up` holds
* This matches **IW Engine** (Call of Duty series: Modern Warfare, Black Ops, etc.) conventions
---
## See also
* `omath/engines/iw_engine/formulas.hpp` — view/projection matrix builders
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper

View File

@@ -0,0 +1,135 @@
# `omath::iw_engine` — formulas & matrix helpers
> Header: `omath/engines/iw_engine/formulas.hpp`
> Namespace: `omath::iw_engine`
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for IW Engine (Call of Duty)
---
## Summary
This header provides **IW Engine**-specific math for:
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
* **Rotation matrices** from Euler angles
* **View matrices** (camera transforms)
* **Perspective projection** matrices
All functions respect IW Engine's **Z-up, right-handed** coordinate system.
---
## API
```cpp
namespace omath::iw_engine {
// Compute forward direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
// Compute right direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
// Compute up direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
// Build 3x3 rotation matrix from angles
[[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
// Build view matrix (camera space transform)
[[nodiscard]]
Mat4X4 calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection matrix
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
float aspect_ratio,
float near, float far) noexcept;
} // namespace omath::iw_engine
```
---
## Direction vectors
Given camera angles (pitch/yaw/roll):
* `forward_vector(angles)` → unit vector pointing where the camera looks
* `right_vector(angles)` → unit vector pointing to the camera's right
* `up_vector(angles)` → unit vector pointing upward relative to the camera
These are used for movement, aim direction, and building coordinate frames.
---
## Rotation & view matrices
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
* `calc_view_matrix(angles, origin)` → camera view matrix
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
---
## Perspective projection
```cpp
Mat4X4 proj = calc_perspective_projection_matrix(
fov_degrees, // vertical field of view (e.g., 65)
aspect_ratio, // width / height (e.g., 16/9)
near_plane, // e.g., 0.1
far_plane // e.g., 5000.0
);
```
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
---
## Usage example
```cpp
using namespace omath::iw_engine;
// Camera setup
ViewAngles angles = {
PitchAngle::from_degrees(-10.0f),
YawAngle::from_degrees(90.0f),
RollAngle::from_degrees(0.0f)
};
Vector3<float> cam_pos{500.0f, 200.0f, 100.0f};
// Compute direction
auto forward = forward_vector(angles);
auto right = right_vector(angles);
auto up = up_vector(angles);
// Build matrices
auto view_mat = calc_view_matrix(angles, cam_pos);
auto proj_mat = calc_perspective_projection_matrix(65.0f, 16.0f/9.0f, 0.1f, 5000.0f);
// Use view_mat and proj_mat for rendering...
```
---
## Conventions
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
* **Pitch**: positive = looking up, negative = looking down
* **Yaw**: increases counter-clockwise from the +X axis
* **Coordinate system**: Z-up, X-forward, Y-right (negative in code convention)
---
## See also
* `omath/engines/iw_engine/constants.hpp` — coordinate frame & angle types
* `omath/engines/iw_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas

View File

@@ -0,0 +1,119 @@
# `omath::iw_engine::MeshTrait` — mesh transformation trait for IW Engine
> Header: `omath/engines/iw_engine/traits/mesh_trait.hpp`
> Namespace: `omath::iw_engine`
> Purpose: provide IW Engine-specific rotation matrix computation for `omath::primitives::Mesh`
---
## Summary
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in IW Engine's (Infinity Ward) coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
---
## Coordinate System
**IW Engine** (Call of Duty) uses:
* **Up axis**: +Z
* **Forward axis**: +Y
* **Right axis**: +X
* **Handedness**: Right-handed
* **Rotation order**: Pitch (X) → Yaw (Z) → Roll (Y)
---
## API
```cpp
namespace omath::iw_engine {
class MeshTrait final {
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
};
} // namespace omath::iw_engine
```
---
## Method: `rotation_matrix`
```cpp
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
```
Computes a 4×4 rotation matrix from IW Engine-style Euler angles.
**Parameters**:
* `rotation``ViewAngles` containing pitch, yaw, and roll angles
**Returns**: 4×4 rotation matrix suitable for mesh transformation
**Implementation**: Delegates to `iw_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
---
## Usage
### With Mesh
```cpp
using namespace omath::iw_engine;
// Create mesh (MeshTrait is used automatically)
Mesh my_mesh(vertices, indices);
// Set rotation using ViewAngles
ViewAngles angles;
angles.pitch = PitchAngle::from_degrees(30.0f);
angles.yaw = YawAngle::from_degrees(45.0f);
angles.roll = RollAngle::from_degrees(0.0f);
my_mesh.set_rotation(angles);
// The rotation matrix is computed using MeshTrait::rotation_matrix
auto matrix = my_mesh.get_to_world_matrix();
```
---
## Rotation Conventions
IW Engine uses a right-handed Z-up coordinate system (similar to Source Engine):
1. **Pitch** (rotation around X-axis / right axis)
* Positive pitch looks upward (+Z direction)
* Range: typically [-89°, 89°]
2. **Yaw** (rotation around Z-axis / up axis)
* Positive yaw rotates counterclockwise when viewed from above
* Range: [-180°, 180°]
3. **Roll** (rotation around Y-axis / forward axis)
* Positive roll tilts right
* Range: [-180°, 180°]
---
## Type Alias
```cpp
namespace omath::iw_engine {
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## See Also
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
- [Formulas Documentation](formulas.md) - IW Engine rotation formula
- [CameraTrait Documentation](camera_trait.md) - Camera trait
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,198 @@
# `omath::iw_engine::PredEngineTrait` — projectile prediction trait
> Header: `omath/engines/iw_engine/traits/pred_engine_trait.hpp`
> Namespace: `omath::iw_engine`
> Purpose: provide IW Engine (Call of Duty)-specific projectile and target prediction for ballistic calculations
---
## Summary
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
* `predict_projectile_position` computes where a projectile will be after `time` seconds
* `predict_target_position` computes where a moving target will be after `time` seconds
* `calc_vector_2d_distance` horizontal distance (X/Y plane, ignoring Z)
* `get_vector_height_coordinate` extracts vertical coordinate (Z in IW Engine)
* `calc_viewpoint_from_angles` computes aim point given pitch angle
* `calc_direct_pitch_angle` pitch angle to look from origin to target
* `calc_direct_yaw_angle` yaw angle to look from origin to target
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
---
## API
```cpp
namespace omath::iw_engine {
class PredEngineTrait final {
public:
// Predict projectile position after `time` seconds
static constexpr Vector3<float>
predict_projectile_position(const projectile_prediction::Projectile& projectile,
float pitch, float yaw, float time,
float gravity) noexcept;
// Predict target position after `time` seconds
static constexpr Vector3<float>
predict_target_position(const projectile_prediction::Target& target,
float time, float gravity) noexcept;
// Compute horizontal (2D) distance
static float
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
// Get vertical coordinate (Z in IW Engine)
static constexpr float
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
// Compute aim point from angles
static Vector3<float>
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
Vector3<float> predicted_target_position,
std::optional<float> projectile_pitch) noexcept;
// Compute pitch angle to look at target
static float
calc_direct_pitch_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
// Compute yaw angle to look at target
static float
calc_direct_yaw_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
};
} // namespace omath::iw_engine
```
---
## Projectile prediction
```cpp
auto pos = PredEngineTrait::predict_projectile_position(
projectile, // initial position, speed, gravity scale
pitch_deg, // launch pitch (positive = up)
yaw_deg, // launch yaw
time, // time in seconds
gravity // gravity constant (e.g., 800 units/s²)
);
```
Computes:
1. Forward vector from pitch/yaw (using `forward_vector`)
2. Initial velocity: `forward * launch_speed`
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Z component only)
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
---
## Target prediction
```cpp
auto pos = PredEngineTrait::predict_target_position(
target, // position, velocity, airborne flag
time, // time in seconds
gravity // gravity constant
);
```
Simple linear extrapolation plus gravity if target is airborne:
```
predicted = origin + velocity * time
if (airborne)
predicted.z -= 0.5 * gravity * time²
```
---
## Distance & height helpers
* `calc_vector_2d_distance(delta)``sqrt(delta.x² + delta.y²)` (horizontal distance)
* `get_vector_height_coordinate(vec)``vec.z` (vertical coordinate in IW Engine)
Used to compute ballistic arc parameters.
---
## Aim angle calculation
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
- Formula: `asin(Δz / distance)` converted to degrees
- Positive = looking up, negative = looking down
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
- Formula: `atan2(Δy, Δx)` converted to degrees
- Horizontal rotation around Z-axis
---
## Viewpoint from angles
```cpp
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
projectile,
predicted_target_pos,
optional_pitch_deg
);
```
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
---
## Conventions
* **Coordinate system**: Z-up (height increases with Z)
* **Angles**: pitch in [-89°, +89°], yaw in [-180°, +180°]
* **Gravity**: applied downward along -Z axis
* **Pitch convention**: +89° = straight up, -89° = straight down
---
## Usage example
```cpp
using namespace omath::iw_engine;
using namespace omath::projectile_prediction;
Projectile proj{
.m_origin = {0, 0, 100},
.m_launch_speed = 1200.0f,
.m_gravity_scale = 1.0f
};
Target tgt{
.m_origin = {800, 300, 100},
.m_velocity = {15, 8, 0},
.m_is_airborne = false
};
float gravity = 800.0f;
float time = 0.5f;
// Predict where target will be
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
// Compute aim angles
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
// Predict projectile position with those angles
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
```
---
## See also
* `omath/engines/iw_engine/formulas.hpp` — direction vectors and matrix builders
* `omath/projectile_prediction/projectile.hpp``Projectile` struct
* `omath/projectile_prediction/target.hpp``Target` struct
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`

View File

@@ -0,0 +1,110 @@
# `omath::opengl_engine::CameraTrait` — plug-in trait for `projection::Camera`
> Header: `omath/engines/opengl_engine/traits/camera_trait.hpp` • Impl: `omath/engines/opengl_engine/traits/camera_trait.cpp`
> Namespace: `omath::opengl_engine`
> Purpose: provide OpenGL-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
---
## Summary
`CameraTrait` exposes three `static` functions:
* `calc_look_at_angle(origin, look_at)` computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.y)` and **yaw** as `-atan2(dir.x, -dir.z)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
* `calc_view_matrix(angles, origin)` delegates to OpenGL formulas `opengl_engine::calc_view_matrix`, producing a `Mat4X4` view matrix (column-major) for the given angles and origin.
* `calc_projection_matrix(fov, viewport, near, far)` builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the OpenGL math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
---
## API
```cpp
namespace omath::opengl_engine {
class CameraTrait final {
public:
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
static ViewAngles
calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept;
// Build view matrix for given angles and origin (column-major).
static Mat4X4
calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection from FOV (deg), viewport, near/far (column-major).
static Mat4X4
calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port,
float near, float far) noexcept;
};
} // namespace omath::opengl_engine
```
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
---
## Behavior & conventions
* **Angles from look-at** (Y-up, -Z forward coordinate system):
```
dir = normalize(look_at - origin)
pitch = asin(dir.y) // +Y is up
yaw = -atan2(dir.x, -dir.z) // horizontal rotation
roll = 0
```
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
* **View matrix**: built by the OpenGL helper `opengl_engine::calc_view_matrix(angles, origin)` to match OpenGL's right-handed, Y-up, -Z forward conventions. Matrix is **column-major**.
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix (column-major).
---
## Using with `projection::Camera`
Create a camera whose math is driven by this trait:
```cpp
using Mat4 = Mat4X4; // from OpenGL math headers (column-major)
using Angs = ViewAngles; // pitch/yaw/roll type
using GLcam = omath::projection::Camera<Mat4, Angs, omath::opengl_engine::CameraTrait>;
omath::projection::ViewPort vp{1920.f, 1080.f};
auto fov = omath::projection::FieldOfView::from_degrees(45.f);
GLcam cam(
/*position*/ {5.f, 3.f, 5.f},
/*angles*/ omath::opengl_engine::CameraTrait::calc_look_at_angle({5,3,5},{0,0,0}),
/*viewport*/ vp,
/*fov*/ fov,
/*near*/ 0.1f,
/*far*/ 100.f
);
```
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
---
## Notes & tips
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
* OpenGL uses **Y-up, -Z forward**: pitch angles control vertical look (positive = up), yaw controls horizontal rotation.
* Matrices are **column-major** (no transpose needed for OpenGL shaders).
---
## See also
* OpenGL math helpers in `omath/engines/opengl_engine/formulas.hpp` (view/projection builders used above).
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).

View File

@@ -0,0 +1,78 @@
# `omath::opengl_engine` — types & constants
> Header: `omath/engines/opengl_engine/constants.hpp`
> Namespace: `omath::opengl_engine`
> Purpose: define OpenGL coordinate system, matrix types, and angle ranges
---
## Summary
The **OpenGL Engine** uses a **Y-up, right-handed** coordinate system:
* **Up** = `{0, 1, 0}` (Y-axis)
* **Right** = `{1, 0, 0}` (X-axis)
* **Forward** = `{0, 0, -1}` (negative Z-axis)
Matrices are **column-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
---
## Constants
```cpp
namespace omath::opengl_engine {
constexpr Vector3<float> k_abs_up = {0, 1, 0};
constexpr Vector3<float> k_abs_right = {1, 0, 0};
constexpr Vector3<float> k_abs_forward = {0, 0, -1};
}
```
These basis vectors define the engine's **world coordinate frame**.
---
## Matrix types
```cpp
using Mat4X4 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>;
using Mat3X3 = Mat<4, 4, float, MatStoreType::COLUMN_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::COLUMN_MAJOR>;
```
**Column-major** storage means columns are contiguous in memory. This matches OpenGL's native matrix layout and shader expectations (GLSL).
---
## Angle types
```cpp
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
```
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
`ViewAngles` bundles all three into a single type for camera/view transforms.
---
## Coordinate system notes
* **Y-up**: gravity points along `-Y`, height increases with `+Y`
* **Right-handed**: cross product `right × up = forward` (forward is `-Z`)
* **Forward = -Z**: the camera looks down the negative Z-axis (OpenGL convention)
* This matches **OpenGL** conventions for 3D graphics pipelines
---
## See also
* `omath/engines/opengl_engine/formulas.hpp` — view/projection matrix builders
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper

View File

@@ -0,0 +1,140 @@
# `omath::opengl_engine` — formulas & matrix helpers
> Header: `omath/engines/opengl_engine/formulas.hpp`
> Namespace: `omath::opengl_engine`
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for OpenGL
---
## Summary
This header provides **OpenGL**-specific math for:
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
* **Rotation matrices** from Euler angles
* **View matrices** (camera transforms)
* **Perspective projection** matrices
All functions respect OpenGL's **Y-up, right-handed** coordinate system with **forward = -Z**.
---
## API
```cpp
namespace omath::opengl_engine {
// Compute forward direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
// Compute right direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
// Compute up direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
// Build 3x3 rotation matrix from angles
[[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
// Build view matrix (camera space transform)
[[nodiscard]]
Mat4X4 calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection matrix
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
float aspect_ratio,
float near, float far) noexcept;
} // namespace omath::opengl_engine
```
---
## Direction vectors
Given camera angles (pitch/yaw/roll):
* `forward_vector(angles)` → unit vector pointing where the camera looks (typically `-Z` direction)
* `right_vector(angles)` → unit vector pointing to the camera's right (`+X` direction)
* `up_vector(angles)` → unit vector pointing upward relative to the camera (`+Y` direction)
These are used for movement, aim direction, and building coordinate frames.
---
## Rotation & view matrices
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles (column-major)
* `calc_view_matrix(angles, origin)` → camera view matrix (column-major)
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
**Note**: Matrices are **column-major** to match OpenGL/GLSL conventions. No transpose needed when uploading to shaders.
---
## Perspective projection
```cpp
Mat4X4 proj = calc_perspective_projection_matrix(
fov_degrees, // vertical field of view (e.g., 45)
aspect_ratio, // width / height (e.g., 16/9)
near_plane, // e.g., 0.1
far_plane // e.g., 100.0
);
```
Produces a **perspective projection matrix** suitable for OpenGL rendering. Combined with the view matrix, this implements the standard camera transform chain.
---
## Usage example
```cpp
using namespace omath::opengl_engine;
// Camera setup
ViewAngles angles = {
PitchAngle::from_degrees(-20.0f),
YawAngle::from_degrees(135.0f),
RollAngle::from_degrees(0.0f)
};
Vector3<float> cam_pos{5.0f, 3.0f, 5.0f};
// Compute direction
auto forward = forward_vector(angles);
auto right = right_vector(angles);
auto up = up_vector(angles);
// Build matrices (column-major for OpenGL)
auto view_mat = calc_view_matrix(angles, cam_pos);
auto proj_mat = calc_perspective_projection_matrix(45.0f, 16.0f/9.0f, 0.1f, 100.0f);
// Upload to OpenGL shaders (no transpose needed)
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view_mat.data());
glUniformMatrix4fv(proj_loc, 1, GL_FALSE, proj_mat.data());
```
---
## Conventions
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
* **Pitch**: positive = looking up, negative = looking down
* **Yaw**: increases counter-clockwise from the -Z axis
* **Coordinate system**: Y-up, -Z-forward, X-right (right-handed)
* **Matrix storage**: column-major (matches OpenGL/GLSL)
---
## See also
* `omath/engines/opengl_engine/constants.hpp` — coordinate frame & angle types
* `omath/engines/opengl_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas

View File

@@ -0,0 +1,121 @@
# `omath::opengl_engine::MeshTrait` — mesh transformation trait for OpenGL
> Header: `omath/engines/opengl_engine/traits/mesh_trait.hpp`
> Namespace: `omath::opengl_engine`
> Purpose: provide OpenGL-specific rotation matrix computation for `omath::primitives::Mesh`
---
## Summary
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in OpenGL's canonical coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
---
## Coordinate System
**OpenGL** (canonical) uses:
* **Up axis**: +Y
* **Forward axis**: +Z (toward viewer)
* **Right axis**: +X
* **Handedness**: Right-handed
* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z)
---
## API
```cpp
namespace omath::opengl_engine {
class MeshTrait final {
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
};
} // namespace omath::opengl_engine
```
---
## Method: `rotation_matrix`
```cpp
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
```
Computes a 4×4 rotation matrix from OpenGL-style Euler angles.
**Parameters**:
* `rotation``ViewAngles` containing pitch, yaw, and roll angles
**Returns**: 4×4 rotation matrix suitable for mesh transformation
**Implementation**: Delegates to `opengl_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
---
## Usage
### With Mesh
```cpp
using namespace omath::opengl_engine;
// Create mesh (MeshTrait is used automatically)
Mesh my_mesh(vertices, indices);
// Set rotation using ViewAngles
ViewAngles angles;
angles.pitch = PitchAngle::from_degrees(30.0f);
angles.yaw = YawAngle::from_degrees(45.0f);
angles.roll = RollAngle::from_degrees(0.0f);
my_mesh.set_rotation(angles);
// The rotation matrix is computed using MeshTrait::rotation_matrix
auto matrix = my_mesh.get_to_world_matrix();
```
---
## Rotation Conventions
OpenGL uses a right-handed Y-up coordinate system:
1. **Pitch** (rotation around X-axis / right axis)
* Positive pitch looks upward (+Y direction)
* Range: typically [-89°, 89°]
2. **Yaw** (rotation around Y-axis / up axis)
* Positive yaw rotates counterclockwise when viewed from above (right-handed)
* Range: [-180°, 180°]
3. **Roll** (rotation around Z-axis / depth axis)
* Positive roll tilts right
* Range: [-180°, 180°]
**Note**: In OpenGL, +Z points toward the viewer in view space, but away from the viewer in world space.
---
## Type Alias
```cpp
namespace omath::opengl_engine {
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## See Also
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
- [Formulas Documentation](formulas.md) - OpenGL rotation formula
- [CameraTrait Documentation](camera_trait.md) - Camera trait
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,199 @@
# `omath::opengl_engine::PredEngineTrait` — projectile prediction trait
> Header: `omath/engines/opengl_engine/traits/pred_engine_trait.hpp`
> Namespace: `omath::opengl_engine`
> Purpose: provide OpenGL-specific projectile and target prediction for ballistic calculations
---
## Summary
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
* `predict_projectile_position` computes where a projectile will be after `time` seconds
* `predict_target_position` computes where a moving target will be after `time` seconds
* `calc_vector_2d_distance` horizontal distance (X/Z plane, ignoring Y)
* `get_vector_height_coordinate` extracts vertical coordinate (Y in OpenGL)
* `calc_viewpoint_from_angles` computes aim point given pitch angle
* `calc_direct_pitch_angle` pitch angle to look from origin to target
* `calc_direct_yaw_angle` yaw angle to look from origin to target
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
---
## API
```cpp
namespace omath::opengl_engine {
class PredEngineTrait final {
public:
// Predict projectile position after `time` seconds
static constexpr Vector3<float>
predict_projectile_position(const projectile_prediction::Projectile& projectile,
float pitch, float yaw, float time,
float gravity) noexcept;
// Predict target position after `time` seconds
static constexpr Vector3<float>
predict_target_position(const projectile_prediction::Target& target,
float time, float gravity) noexcept;
// Compute horizontal (2D) distance
static float
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
// Get vertical coordinate (Y in OpenGL)
static constexpr float
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
// Compute aim point from angles
static Vector3<float>
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
Vector3<float> predicted_target_position,
std::optional<float> projectile_pitch) noexcept;
// Compute pitch angle to look at target
static float
calc_direct_pitch_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
// Compute yaw angle to look at target
static float
calc_direct_yaw_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
};
} // namespace omath::opengl_engine
```
---
## Projectile prediction
```cpp
auto pos = PredEngineTrait::predict_projectile_position(
projectile, // initial position, speed, gravity scale
pitch_deg, // launch pitch (positive = up)
yaw_deg, // launch yaw
time, // time in seconds
gravity // gravity constant (e.g., 9.81 m/s²)
);
```
Computes:
1. Forward vector from pitch/yaw (using `forward_vector`)
2. Initial velocity: `forward * launch_speed`
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component only)
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
---
## Target prediction
```cpp
auto pos = PredEngineTrait::predict_target_position(
target, // position, velocity, airborne flag
time, // time in seconds
gravity // gravity constant
);
```
Simple linear extrapolation plus gravity if target is airborne:
```
predicted = origin + velocity * time
if (airborne)
predicted.y -= 0.5 * gravity * time²
```
---
## Distance & height helpers
* `calc_vector_2d_distance(delta)``sqrt(delta.x² + delta.z²)` (horizontal distance)
* `get_vector_height_coordinate(vec)``vec.y` (vertical coordinate in OpenGL)
Used to compute ballistic arc parameters.
---
## Aim angle calculation
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
- Formula: `asin(Δy / distance)` converted to degrees (direction normalized first)
- Positive = looking up, negative = looking down
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
- Formula: `-atan2(Δx, -Δz)` converted to degrees (direction normalized first)
- Horizontal rotation around Y-axis (accounts for -Z forward convention)
---
## Viewpoint from angles
```cpp
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
projectile,
predicted_target_pos,
optional_pitch_deg
);
```
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset. Result has adjusted Y coordinate.
---
## Conventions
* **Coordinate system**: Y-up, -Z forward (height increases with Y)
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
* **Gravity**: applied downward along -Y axis
* **Pitch convention**: +90° = straight up, -90° = straight down
* **Forward direction**: negative Z-axis
---
## Usage example
```cpp
using namespace omath::opengl_engine;
using namespace omath::projectile_prediction;
Projectile proj{
.m_origin = {0, 2, 0},
.m_launch_speed = 30.0f,
.m_gravity_scale = 1.0f
};
Target tgt{
.m_origin = {10, 2, -15},
.m_velocity = {0.5f, 0, -1.0f},
.m_is_airborne = false
};
float gravity = 9.81f;
float time = 0.5f;
// Predict where target will be
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
// Compute aim angles
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
// Predict projectile position with those angles
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
```
---
## See also
* `omath/engines/opengl_engine/formulas.hpp` — direction vectors and matrix builders
* `omath/projectile_prediction/projectile.hpp``Projectile` struct
* `omath/projectile_prediction/target.hpp``Target` struct
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`

View File

@@ -0,0 +1,113 @@
# `omath::source_engine::CameraTrait` — plug-in trait for `projection::Camera`
> Header: `omath/engines/source_engine/traits/camera_trait.hpp` • Impl: `omath/engines/source_engine/traits/camera_trait.cpp`
> Namespace: `omath::source_engine`
> Purpose: provide Source Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
---
## Summary
`CameraTrait` exposes three `static` functions:
* `calc_look_at_angle(origin, look_at)` computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
* `calc_view_matrix(angles, origin)` delegates to Source Engine formulas `source_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
* `calc_projection_matrix(fov, viewport, near, far)` builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Source Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
---
## API
```cpp
namespace omath::source_engine {
class CameraTrait final {
public:
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
static ViewAngles
calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept;
// Build view matrix for given angles and origin.
static Mat4X4
calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection from FOV (deg), viewport, near/far.
static Mat4X4
calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port,
float near, float far) noexcept;
};
} // namespace omath::source_engine
```
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
---
## Behavior & conventions
* **Angles from look-at** (Z-up coordinate system):
```
dir = normalize(look_at - origin)
pitch = asin(dir.z) // +Z is up
yaw = atan2(dir.y, dir.x) // horizontal rotation
roll = 0
```
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
* **View matrix**: built by the Source Engine helper `source_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
---
## Using with `projection::Camera`
Create a camera whose math is driven by this trait:
```cpp
using Mat4 = Mat4X4; // from Source Engine math headers
using Angs = ViewAngles; // pitch/yaw/roll type
using SEcam = omath::projection::Camera<Mat4, Angs, omath::source_engine::CameraTrait>;
omath::projection::ViewPort vp{1920.f, 1080.f};
auto fov = omath::projection::FieldOfView::from_degrees(90.f);
SEcam cam(
/*position*/ {100.f, 50.f, 80.f},
/*angles*/ omath::source_engine::CameraTrait::calc_look_at_angle({100,50,80},{0,0,80}),
/*viewport*/ vp,
/*fov*/ fov,
/*near*/ 0.1f,
/*far*/ 1000.f
);
```
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
---
## Notes & tips
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
* Source Engine uses **Z-up**: pitch angles control vertical look, positive = up.
---
## See also
* [Source Engine Formulas](formulas.md) - View/projection matrix builders
* [Source Engine Constants](constants.md) - Engine-specific constants
* [Source Engine Pred Engine Trait](pred_engine_trait.md) - Projectile prediction for Source Engine
* [Generic Camera Documentation](../../projection/camera.md) - Camera base class
* [Getting Started Guide](../../getting_started.md) - Quick start with OMath
* [Tutorials - World-to-Screen](../../tutorials.md#tutorial-2-world-to-screen-projection) - Projection tutorial

View File

@@ -0,0 +1,77 @@
# `omath::source_engine` — types & constants
> Header: `omath/engines/source_engine/constants.hpp`
> Namespace: `omath::source_engine`
> Purpose: define Source Engine coordinate system, matrix types, and angle ranges
---
## Summary
The **Source Engine** uses a **Z-up, right-handed** coordinate system:
* **Up** = `{0, 0, 1}` (Z-axis)
* **Right** = `{0, -1, 0}` (negative Y-axis)
* **Forward** = `{1, 0, 0}` (X-axis)
Matrices are **row-major**. Angles are **clamped pitch** (±89°) and **normalized yaw/roll** (±180°).
---
## Constants
```cpp
namespace omath::source_engine {
constexpr Vector3<float> k_abs_up = {0, 0, 1};
constexpr Vector3<float> k_abs_right = {0, -1, 0};
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
}
```
These basis vectors define the engine's **world coordinate frame**.
---
## Matrix types
```cpp
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
```
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
---
## Angle types
```cpp
using PitchAngle = Angle<float, -89.f, 89.f, AngleFlags::Clamped>;
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
```
* **PitchAngle**: clamped to **[-89°, +89°]** (looking down vs. up)
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
`ViewAngles` bundles all three into a single type for camera/view transforms.
---
## Coordinate system notes
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
* **Right-handed**: cross product `forward × right = up` holds
* This matches **Source Engine** (Half-Life 2, TF2, CS:GO, etc.) conventions
---
## See also
* `omath/engines/source_engine/formulas.hpp` — view/projection matrix builders
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper

View File

@@ -0,0 +1,135 @@
# `omath::source_engine` — formulas & matrix helpers
> Header: `omath/engines/source_engine/formulas.hpp`
> Namespace: `omath::source_engine`
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Source Engine
---
## Summary
This header provides **Source Engine**-specific math for:
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
* **Rotation matrices** from Euler angles
* **View matrices** (camera transforms)
* **Perspective projection** matrices
All functions respect Source Engine's **Z-up, right-handed** coordinate system.
---
## API
```cpp
namespace omath::source_engine {
// Compute forward direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
// Compute right direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
// Compute up direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
// Build 3x3 rotation matrix from angles
[[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
// Build view matrix (camera space transform)
[[nodiscard]]
Mat4X4 calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection matrix
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
float aspect_ratio,
float near, float far) noexcept;
} // namespace omath::source_engine
```
---
## Direction vectors
Given camera angles (pitch/yaw/roll):
* `forward_vector(angles)` → unit vector pointing where the camera looks
* `right_vector(angles)` → unit vector pointing to the camera's right
* `up_vector(angles)` → unit vector pointing upward relative to the camera
These are used for movement, aim direction, and building coordinate frames.
---
## Rotation & view matrices
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
* `calc_view_matrix(angles, origin)` → camera view matrix
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
---
## Perspective projection
```cpp
Mat4X4 proj = calc_perspective_projection_matrix(
fov_degrees, // vertical field of view (e.g., 90)
aspect_ratio, // width / height (e.g., 16/9)
near_plane, // e.g., 0.1
far_plane // e.g., 1000.0
);
```
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
---
## Usage example
```cpp
using namespace omath::source_engine;
// Camera setup
ViewAngles angles = {
PitchAngle::from_degrees(-15.0f),
YawAngle::from_degrees(45.0f),
RollAngle::from_degrees(0.0f)
};
Vector3<float> cam_pos{100.0f, 50.0f, 80.0f};
// Compute direction
auto forward = forward_vector(angles);
auto right = right_vector(angles);
auto up = up_vector(angles);
// Build matrices
auto view_mat = calc_view_matrix(angles, cam_pos);
auto proj_mat = calc_perspective_projection_matrix(90.0f, 16.0f/9.0f, 0.1f, 1000.0f);
// Use view_mat and proj_mat for rendering...
```
---
## Conventions
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
* **Pitch**: positive = looking up, negative = looking down
* **Yaw**: increases counter-clockwise from the +X axis
* **Coordinate system**: Z-up, X-forward, Y-right (negative in code convention)
---
## See also
* `omath/engines/source_engine/constants.hpp` — coordinate frame & angle types
* `omath/engines/source_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas

View File

@@ -0,0 +1,182 @@
# `omath::source_engine::MeshTrait` — mesh transformation trait for Source Engine
> Header: `omath/engines/source_engine/traits/mesh_trait.hpp`
> Namespace: `omath::source_engine`
> Purpose: provide Source Engine-specific rotation matrix computation for `omath::primitives::Mesh`
---
## Summary
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Source Engine's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
---
## Coordinate System
**Source Engine** uses:
* **Up axis**: +Z
* **Forward axis**: +Y
* **Right axis**: +X
* **Handedness**: Right-handed
* **Rotation order**: Pitch (X) → Yaw (Z) → Roll (Y)
---
## API
```cpp
namespace omath::source_engine {
class MeshTrait final {
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
};
} // namespace omath::source_engine
```
---
## Method: `rotation_matrix`
```cpp
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
```
Computes a 4×4 rotation matrix from Source Engine-style Euler angles.
**Parameters**:
* `rotation``ViewAngles` containing pitch, yaw, and roll angles
**Returns**: 4×4 rotation matrix suitable for mesh transformation
**Implementation**: Delegates to `source_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
---
## Usage
### With Mesh
```cpp
using namespace omath::source_engine;
// Create mesh (MeshTrait is used automatically)
Mesh my_mesh(vertices, indices);
// Set rotation using ViewAngles
ViewAngles angles;
angles.pitch = PitchAngle::from_degrees(30.0f);
angles.yaw = YawAngle::from_degrees(45.0f);
angles.roll = RollAngle::from_degrees(0.0f);
my_mesh.set_rotation(angles);
// The rotation matrix is computed using MeshTrait::rotation_matrix
auto matrix = my_mesh.get_to_world_matrix();
```
### Direct Usage
```cpp
using namespace omath::source_engine;
ViewAngles angles;
angles.pitch = PitchAngle::from_degrees(45.0f);
angles.yaw = YawAngle::from_degrees(90.0f);
angles.roll = RollAngle::from_degrees(0.0f);
Mat4X4 rot_matrix = MeshTrait::rotation_matrix(angles);
// Use the matrix directly
Vector3<float> local_point{1, 0, 0};
auto rotated = rot_matrix * mat_column_from_vector(local_point);
```
---
## Rotation Conventions
The rotation matrix is built following Source Engine's conventions:
1. **Pitch** (rotation around X-axis / right axis)
* Positive pitch looks upward (+Z direction)
* Range: typically [-89°, 89°]
2. **Yaw** (rotation around Z-axis / up axis)
* Positive yaw rotates counterclockwise when viewed from above
* Range: [-180°, 180°]
3. **Roll** (rotation around Y-axis / forward axis)
* Positive roll tilts right
* Range: [-180°, 180°]
**Composition**: The matrices are combined in the order Pitch × Yaw × Roll, producing a rotation that:
* First applies roll around the forward axis
* Then applies yaw around the up axis
* Finally applies pitch around the right axis
This matches Source Engine's internal rotation order.
---
## Related Functions
The trait delegates to the formula defined in `formulas.hpp`:
```cpp
[[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
```
See [Formulas Documentation](formulas.md) for details on the rotation matrix computation.
---
## Type Alias
The Source Engine mesh type is pre-defined:
```cpp
namespace omath::source_engine {
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
Use this alias to ensure correct trait usage:
```cpp
using namespace omath::source_engine;
// Correct: uses Source Engine trait
Mesh my_mesh(vbo, vao);
// Avoid: manually specifying template parameters
primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float> verbose_mesh(vbo, vao);
```
---
## Notes
* **Angle ranges**: Ensure angles are within valid ranges (pitch: [-89°, 89°], yaw/roll: [-180°, 180°])
* **Performance**: Matrix computation is O(1) with ~64 floating-point operations
* **Caching**: The mesh caches the transformation matrix; recomputed only when rotation changes
* **Compatibility**: Works with all Source Engine games (CS:GO, TF2, Portal, Half-Life 2, etc.)
---
## See Also
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive using this trait
- [MeshCollider Documentation](../../collision/mesh_collider.md) - Collision wrapper for meshes
- [Formulas Documentation](formulas.md) - Source Engine rotation formula
- [CameraTrait Documentation](camera_trait.md) - Camera transformation trait
- [Constants Documentation](constants.md) - Source Engine constants
- [API Overview](../../api_overview.md) - High-level API reference
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,198 @@
# `omath::source_engine::PredEngineTrait` — projectile prediction trait
> Header: `omath/engines/source_engine/traits/pred_engine_trait.hpp`
> Namespace: `omath::source_engine`
> Purpose: provide Source Engine-specific projectile and target prediction for ballistic calculations
---
## Summary
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
* `predict_projectile_position` computes where a projectile will be after `time` seconds
* `predict_target_position` computes where a moving target will be after `time` seconds
* `calc_vector_2d_distance` horizontal distance (X/Y plane, ignoring Z)
* `get_vector_height_coordinate` extracts vertical coordinate (Z in Source Engine)
* `calc_viewpoint_from_angles` computes aim point given pitch angle
* `calc_direct_pitch_angle` pitch angle to look from origin to target
* `calc_direct_yaw_angle` yaw angle to look from origin to target
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
---
## API
```cpp
namespace omath::source_engine {
class PredEngineTrait final {
public:
// Predict projectile position after `time` seconds
static constexpr Vector3<float>
predict_projectile_position(const projectile_prediction::Projectile& projectile,
float pitch, float yaw, float time,
float gravity) noexcept;
// Predict target position after `time` seconds
static constexpr Vector3<float>
predict_target_position(const projectile_prediction::Target& target,
float time, float gravity) noexcept;
// Compute horizontal (2D) distance
static float
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
// Get vertical coordinate (Z in Source Engine)
static constexpr float
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
// Compute aim point from angles
static Vector3<float>
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
Vector3<float> predicted_target_position,
std::optional<float> projectile_pitch) noexcept;
// Compute pitch angle to look at target
static float
calc_direct_pitch_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
// Compute yaw angle to look at target
static float
calc_direct_yaw_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
};
} // namespace omath::source_engine
```
---
## Projectile prediction
```cpp
auto pos = PredEngineTrait::predict_projectile_position(
projectile, // initial position, speed, gravity scale
pitch_deg, // launch pitch (positive = up)
yaw_deg, // launch yaw
time, // time in seconds
gravity // gravity constant (e.g., 800 units/s²)
);
```
Computes:
1. Forward vector from pitch/yaw (using `forward_vector`)
2. Initial velocity: `forward * launch_speed`
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Z component only)
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
---
## Target prediction
```cpp
auto pos = PredEngineTrait::predict_target_position(
target, // position, velocity, airborne flag
time, // time in seconds
gravity // gravity constant
);
```
Simple linear extrapolation plus gravity if target is airborne:
```
predicted = origin + velocity * time
if (airborne)
predicted.z -= 0.5 * gravity * time²
```
---
## Distance & height helpers
* `calc_vector_2d_distance(delta)``sqrt(delta.x² + delta.y²)` (horizontal distance)
* `get_vector_height_coordinate(vec)``vec.z` (vertical coordinate in Source Engine)
Used to compute ballistic arc parameters.
---
## Aim angle calculation
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
- Formula: `asin(Δz / distance)` converted to degrees
- Positive = looking up, negative = looking down
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
- Formula: `atan2(Δy, Δx)` converted to degrees
- Horizontal rotation around Z-axis
---
## Viewpoint from angles
```cpp
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
projectile,
predicted_target_pos,
optional_pitch_deg
);
```
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
---
## Conventions
* **Coordinate system**: Z-up (height increases with Z)
* **Angles**: pitch in [-89°, +89°], yaw in [-180°, +180°]
* **Gravity**: applied downward along -Z axis
* **Pitch convention**: +89° = straight up, -89° = straight down
---
## Usage example
```cpp
using namespace omath::source_engine;
using namespace omath::projectile_prediction;
Projectile proj{
.m_origin = {0, 0, 100},
.m_launch_speed = 1000.0f,
.m_gravity_scale = 1.0f
};
Target tgt{
.m_origin = {500, 200, 100},
.m_velocity = {10, 5, 0},
.m_is_airborne = false
};
float gravity = 800.0f;
float time = 0.5f;
// Predict where target will be
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
// Compute aim angles
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
// Predict projectile position with those angles
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
```
---
## See also
* `omath/engines/source_engine/formulas.hpp` — direction vectors and matrix builders
* `omath/projectile_prediction/projectile.hpp``Projectile` struct
* `omath/projectile_prediction/target.hpp``Target` struct
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`

View File

@@ -0,0 +1,109 @@
# `omath::unity_engine::CameraTrait` — plug-in trait for `projection::Camera`
> Header: `omath/engines/unity_engine/traits/camera_trait.hpp` • Impl: `omath/engines/unity_engine/traits/camera_trait.cpp`
> Namespace: `omath::unity_engine`
> Purpose: provide Unity Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
---
## Summary
`CameraTrait` exposes three `static` functions:
* `calc_look_at_angle(origin, look_at)` computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.y)` and **yaw** as `atan2(dir.x, dir.z)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
* `calc_view_matrix(angles, origin)` delegates to Unity Engine formulas `unity_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
* `calc_projection_matrix(fov, viewport, near, far)` builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Unity Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
---
## API
```cpp
namespace omath::unity_engine {
class CameraTrait final {
public:
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
static ViewAngles
calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept;
// Build view matrix for given angles and origin.
static Mat4X4
calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection from FOV (deg), viewport, near/far.
static Mat4X4
calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port,
float near, float far) noexcept;
};
} // namespace omath::unity_engine
```
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
---
## Behavior & conventions
* **Angles from look-at** (Y-up coordinate system):
```
dir = normalize(look_at - origin)
pitch = asin(dir.y) // +Y is up
yaw = atan2(dir.x, dir.z) // horizontal rotation
roll = 0
```
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
* **View matrix**: built by the Unity Engine helper `unity_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
---
## Using with `projection::Camera`
Create a camera whose math is driven by this trait:
```cpp
using Mat4 = Mat4X4; // from Unity Engine math headers
using Angs = ViewAngles; // pitch/yaw/roll type
using UEcam = omath::projection::Camera<Mat4, Angs, omath::unity_engine::CameraTrait>;
omath::projection::ViewPort vp{1920.f, 1080.f};
auto fov = omath::projection::FieldOfView::from_degrees(60.f);
UEcam cam(
/*position*/ {10.f, 5.f, -10.f},
/*angles*/ omath::unity_engine::CameraTrait::calc_look_at_angle({10,5,-10},{0,5,0}),
/*viewport*/ vp,
/*fov*/ fov,
/*near*/ 0.3f,
/*far*/ 1000.f
);
```
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
---
## Notes & tips
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
* Unity Engine uses **Y-up**: pitch angles control vertical look, positive = up.
---
## See also
* Unity Engine math helpers in `omath/engines/unity_engine/formulas.hpp` (view/projection builders used above).
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).

View File

@@ -0,0 +1,77 @@
# `omath::unity_engine` — types & constants
> Header: `omath/engines/unity_engine/constants.hpp`
> Namespace: `omath::unity_engine`
> Purpose: define Unity Engine coordinate system, matrix types, and angle ranges
---
## Summary
The **Unity Engine** uses a **Y-up, left-handed** coordinate system:
* **Up** = `{0, 1, 0}` (Y-axis)
* **Right** = `{1, 0, 0}` (X-axis)
* **Forward** = `{0, 0, 1}` (Z-axis)
Matrices are **row-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
---
## Constants
```cpp
namespace omath::unity_engine {
constexpr Vector3<float> k_abs_up = {0, 1, 0};
constexpr Vector3<float> k_abs_right = {1, 0, 0};
constexpr Vector3<float> k_abs_forward = {0, 0, 1};
}
```
These basis vectors define the engine's **world coordinate frame**.
---
## Matrix types
```cpp
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
```
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
---
## Angle types
```cpp
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
```
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
`ViewAngles` bundles all three into a single type for camera/view transforms.
---
## Coordinate system notes
* **Y-up**: gravity points along `-Y`, height increases with `+Y`
* **Left-handed**: cross product `forward × right = up` with left-hand rule
* This matches **Unity Engine** conventions for 3D games and simulations
---
## See also
* `omath/engines/unity_engine/formulas.hpp` — view/projection matrix builders
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper

View File

@@ -0,0 +1,135 @@
# `omath::unity_engine` — formulas & matrix helpers
> Header: `omath/engines/unity_engine/formulas.hpp`
> Namespace: `omath::unity_engine`
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Unity Engine
---
## Summary
This header provides **Unity Engine**-specific math for:
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
* **Rotation matrices** from Euler angles
* **View matrices** (camera transforms)
* **Perspective projection** matrices
All functions respect Unity Engine's **Y-up, left-handed** coordinate system.
---
## API
```cpp
namespace omath::unity_engine {
// Compute forward direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
// Compute right direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
// Compute up direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
// Build 3x3 rotation matrix from angles
[[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
// Build view matrix (camera space transform)
[[nodiscard]]
Mat4X4 calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection matrix
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
float aspect_ratio,
float near, float far) noexcept;
} // namespace omath::unity_engine
```
---
## Direction vectors
Given camera angles (pitch/yaw/roll):
* `forward_vector(angles)` → unit vector pointing where the camera looks
* `right_vector(angles)` → unit vector pointing to the camera's right
* `up_vector(angles)` → unit vector pointing upward relative to the camera
These are used for movement, aim direction, and building coordinate frames.
---
## Rotation & view matrices
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
* `calc_view_matrix(angles, origin)` → camera view matrix
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
---
## Perspective projection
```cpp
Mat4X4 proj = calc_perspective_projection_matrix(
fov_degrees, // vertical field of view (e.g., 60)
aspect_ratio, // width / height (e.g., 16/9)
near_plane, // e.g., 0.3
far_plane // e.g., 1000.0
);
```
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
---
## Usage example
```cpp
using namespace omath::unity_engine;
// Camera setup
ViewAngles angles = {
PitchAngle::from_degrees(-15.0f),
YawAngle::from_degrees(45.0f),
RollAngle::from_degrees(0.0f)
};
Vector3<float> cam_pos{10.0f, 5.0f, -10.0f};
// Compute direction
auto forward = forward_vector(angles);
auto right = right_vector(angles);
auto up = up_vector(angles);
// Build matrices
auto view_mat = calc_view_matrix(angles, cam_pos);
auto proj_mat = calc_perspective_projection_matrix(60.0f, 16.0f/9.0f, 0.3f, 1000.0f);
// Use view_mat and proj_mat for rendering...
```
---
## Conventions
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
* **Pitch**: positive = looking up, negative = looking down
* **Yaw**: increases counter-clockwise from the +Z axis
* **Coordinate system**: Y-up, Z-forward, X-right (left-handed)
---
## See also
* `omath/engines/unity_engine/constants.hpp` — coordinate frame & angle types
* `omath/engines/unity_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas

View File

@@ -0,0 +1,119 @@
# `omath::unity_engine::MeshTrait` — mesh transformation trait for Unity Engine
> Header: `omath/engines/unity_engine/traits/mesh_trait.hpp`
> Namespace: `omath::unity_engine`
> Purpose: provide Unity Engine-specific rotation matrix computation for `omath::primitives::Mesh`
---
## Summary
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Unity's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
---
## Coordinate System
**Unity Engine** uses:
* **Up axis**: +Y
* **Forward axis**: +Z
* **Right axis**: +X
* **Handedness**: Left-handed
* **Rotation order**: Pitch (X) → Yaw (Y) → Roll (Z)
---
## API
```cpp
namespace omath::unity_engine {
class MeshTrait final {
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
};
} // namespace omath::unity_engine
```
---
## Method: `rotation_matrix`
```cpp
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
```
Computes a 4×4 rotation matrix from Unity-style Euler angles.
**Parameters**:
* `rotation``ViewAngles` containing pitch, yaw, and roll angles
**Returns**: 4×4 rotation matrix suitable for mesh transformation
**Implementation**: Delegates to `unity_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
---
## Usage
### With Mesh
```cpp
using namespace omath::unity_engine;
// Create mesh (MeshTrait is used automatically)
Mesh my_mesh(vertices, indices);
// Set rotation using ViewAngles
ViewAngles angles;
angles.pitch = PitchAngle::from_degrees(30.0f);
angles.yaw = YawAngle::from_degrees(45.0f);
angles.roll = RollAngle::from_degrees(0.0f);
my_mesh.set_rotation(angles);
// The rotation matrix is computed using MeshTrait::rotation_matrix
auto matrix = my_mesh.get_to_world_matrix();
```
---
## Rotation Conventions
Unity uses a left-handed coordinate system with Y-up:
1. **Pitch** (rotation around X-axis / right axis)
* Positive pitch looks upward (+Y direction)
* Range: typically [-89°, 89°]
2. **Yaw** (rotation around Y-axis / up axis)
* Positive yaw rotates clockwise when viewed from above (left-handed)
* Range: [-180°, 180°]
3. **Roll** (rotation around Z-axis / forward axis)
* Positive roll tilts right
* Range: [-180°, 180°]
---
## Type Alias
```cpp
namespace omath::unity_engine {
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## See Also
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
- [Formulas Documentation](formulas.md) - Unity rotation formula
- [CameraTrait Documentation](camera_trait.md) - Camera trait
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,198 @@
# `omath::unity_engine::PredEngineTrait` — projectile prediction trait
> Header: `omath/engines/unity_engine/traits/pred_engine_trait.hpp`
> Namespace: `omath::unity_engine`
> Purpose: provide Unity Engine-specific projectile and target prediction for ballistic calculations
---
## Summary
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
* `predict_projectile_position` computes where a projectile will be after `time` seconds
* `predict_target_position` computes where a moving target will be after `time` seconds
* `calc_vector_2d_distance` horizontal distance (X/Z plane, ignoring Y)
* `get_vector_height_coordinate` extracts vertical coordinate (Y in Unity Engine)
* `calc_viewpoint_from_angles` computes aim point given pitch angle
* `calc_direct_pitch_angle` pitch angle to look from origin to target
* `calc_direct_yaw_angle` yaw angle to look from origin to target
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
---
## API
```cpp
namespace omath::unity_engine {
class PredEngineTrait final {
public:
// Predict projectile position after `time` seconds
static constexpr Vector3<float>
predict_projectile_position(const projectile_prediction::Projectile& projectile,
float pitch, float yaw, float time,
float gravity) noexcept;
// Predict target position after `time` seconds
static constexpr Vector3<float>
predict_target_position(const projectile_prediction::Target& target,
float time, float gravity) noexcept;
// Compute horizontal (2D) distance
static float
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
// Get vertical coordinate (Y in Unity Engine)
static constexpr float
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
// Compute aim point from angles
static Vector3<float>
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
Vector3<float> predicted_target_position,
std::optional<float> projectile_pitch) noexcept;
// Compute pitch angle to look at target
static float
calc_direct_pitch_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
// Compute yaw angle to look at target
static float
calc_direct_yaw_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
};
} // namespace omath::unity_engine
```
---
## Projectile prediction
```cpp
auto pos = PredEngineTrait::predict_projectile_position(
projectile, // initial position, speed, gravity scale
pitch_deg, // launch pitch (positive = up)
yaw_deg, // launch yaw
time, // time in seconds
gravity // gravity constant (e.g., 9.81 m/s²)
);
```
Computes:
1. Forward vector from pitch/yaw (using `forward_vector`)
2. Initial velocity: `forward * launch_speed`
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component only)
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
---
## Target prediction
```cpp
auto pos = PredEngineTrait::predict_target_position(
target, // position, velocity, airborne flag
time, // time in seconds
gravity // gravity constant
);
```
Simple linear extrapolation plus gravity if target is airborne:
```
predicted = origin + velocity * time
if (airborne)
predicted.y -= 0.5 * gravity * time²
```
---
## Distance & height helpers
* `calc_vector_2d_distance(delta)``sqrt(delta.x² + delta.z²)` (horizontal distance)
* `get_vector_height_coordinate(vec)``vec.y` (vertical coordinate in Unity Engine)
Used to compute ballistic arc parameters.
---
## Aim angle calculation
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
- Formula: `asin(Δy / distance)` converted to degrees (direction normalized first)
- Positive = looking up, negative = looking down
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
- Formula: `atan2(Δx, Δz)` converted to degrees (direction normalized first)
- Horizontal rotation around Y-axis
---
## Viewpoint from angles
```cpp
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
projectile,
predicted_target_pos,
optional_pitch_deg
);
```
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset. Result has adjusted Y coordinate.
---
## Conventions
* **Coordinate system**: Y-up (height increases with Y)
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
* **Gravity**: applied downward along -Y axis
* **Pitch convention**: +90° = straight up, -90° = straight down
---
## Usage example
```cpp
using namespace omath::unity_engine;
using namespace omath::projectile_prediction;
Projectile proj{
.m_origin = {0, 2, 0},
.m_launch_speed = 50.0f,
.m_gravity_scale = 1.0f
};
Target tgt{
.m_origin = {20, 2, 15},
.m_velocity = {1, 0, 0.5f},
.m_is_airborne = false
};
float gravity = 9.81f;
float time = 0.5f;
// Predict where target will be
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
// Compute aim angles
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
// Predict projectile position with those angles
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
```
---
## See also
* `omath/engines/unity_engine/formulas.hpp` — direction vectors and matrix builders
* `omath/projectile_prediction/projectile.hpp``Projectile` struct
* `omath/projectile_prediction/target.hpp``Target` struct
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`

View File

@@ -0,0 +1,109 @@
# `omath::unreal_engine::CameraTrait` — plug-in trait for `projection::Camera`
> Header: `omath/engines/unreal_engine/traits/camera_trait.hpp` • Impl: `omath/engines/unreal_engine/traits/camera_trait.cpp`
> Namespace: `omath::unreal_engine`
> Purpose: provide Unreal Engine-style **look-at**, **view**, and **projection** math to the generic `omath::projection::Camera` (satisfies `CameraEngineConcept`).
---
## Summary
`CameraTrait` exposes three `static` functions:
* `calc_look_at_angle(origin, look_at)` computes Euler angles so the camera at `origin` looks at `look_at`. Implementation normalizes the direction, computes **pitch** as `asin(dir.z)` and **yaw** as `atan2(dir.y, dir.x)`; **roll** is `0`. Pitch/yaw are returned using the project's strong angle types (`PitchAngle`, `YawAngle`, `RollAngle`).
* `calc_view_matrix(angles, origin)` delegates to Unreal Engine formulas `unreal_engine::calc_view_matrix`, producing a `Mat4X4` view matrix for the given angles and origin.
* `calc_projection_matrix(fov, viewport, near, far)` builds a perspective projection by calling `calc_perspective_projection_matrix(fov_degrees, aspect, near, far)`, where `aspect = viewport.aspect_ratio()`. Accepts `FieldOfView` (degrees).
The trait's types (`ViewAngles`, `Mat4X4`, angle aliases) and helpers live in the Unreal Engine math headers included by the trait (`formulas.hpp`) and the shared projection header (`projection/camera.hpp`).
---
## API
```cpp
namespace omath::unreal_engine {
class CameraTrait final {
public:
// Compute Euler angles (pitch/yaw/roll) to look from cam_origin to look_at.
static ViewAngles
calc_look_at_angle(const Vector3<float>& cam_origin,
const Vector3<float>& look_at) noexcept;
// Build view matrix for given angles and origin.
static Mat4X4
calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection from FOV (deg), viewport, near/far.
static Mat4X4
calc_projection_matrix(const projection::FieldOfView& fov,
const projection::ViewPort& view_port,
float near, float far) noexcept;
};
} // namespace omath::unreal_engine
```
Uses: `Vector3<float>`, `ViewAngles` (pitch/yaw/roll), `Mat4X4`, `projection::FieldOfView`, `projection::ViewPort`.
---
## Behavior & conventions
* **Angles from look-at** (Z-up coordinate system):
```
dir = normalize(look_at - origin)
pitch = asin(dir.z) // +Z is up
yaw = atan2(dir.y, dir.x) // horizontal rotation
roll = 0
```
Returned as `PitchAngle::from_radians(...)`, `YawAngle::from_radians(...)`, etc.
* **View matrix**: built by the Unreal Engine helper `unreal_engine::calc_view_matrix(angles, origin)` to match the engine's handedness and axis conventions.
* **Projection**: uses `calc_perspective_projection_matrix(fov.as_degrees(), viewport.aspect_ratio(), near, far)`. Pass your **vertical FOV** in degrees via `FieldOfView`; the helper computes a standard perspective matrix.
---
## Using with `projection::Camera`
Create a camera whose math is driven by this trait:
```cpp
using Mat4 = Mat4X4; // from Unreal Engine math headers
using Angs = ViewAngles; // pitch/yaw/roll type
using UEcam = omath::projection::Camera<Mat4, Angs, omath::unreal_engine::CameraTrait>;
omath::projection::ViewPort vp{1920.f, 1080.f};
auto fov = omath::projection::FieldOfView::from_degrees(90.f);
UEcam cam(
/*position*/ {1000.f, 500.f, 200.f},
/*angles*/ omath::unreal_engine::CameraTrait::calc_look_at_angle({1000,500,200},{0,0,200}),
/*viewport*/ vp,
/*fov*/ fov,
/*near*/ 10.f,
/*far*/ 100000.f
);
```
This satisfies `CameraEngineConcept` expected by `projection::Camera` (look-at, view, projection) as declared in the trait header.
---
## Notes & tips
* Ensure your `ViewAngles` aliases (`PitchAngle`, `YawAngle`, `RollAngle`) match the project's angle policy (ranges/normalization). The implementation constructs them **from radians**.
* `aspect_ratio()` is taken directly from `ViewPort` (`width / height`), so keep both positive and non-zero.
* `near` must be > 0 and `< far` for a valid projection matrix (enforced by your math helpers).
* Unreal Engine uses **Z-up**: pitch angles control vertical look, positive = up.
---
## See also
* Unreal Engine math helpers in `omath/engines/unreal_engine/formulas.hpp` (view/projection builders used above).
* Generic camera wrapper `omath::projection::Camera` and its `CameraEngineConcept` (this trait is designed to plug straight into it).

View File

@@ -0,0 +1,77 @@
# `omath::unreal_engine` — types & constants
> Header: `omath/engines/unreal_engine/constants.hpp`
> Namespace: `omath::unreal_engine`
> Purpose: define Unreal Engine coordinate system, matrix types, and angle ranges
---
## Summary
The **Unreal Engine** uses a **Z-up, left-handed** coordinate system:
* **Up** = `{0, 0, 1}` (Z-axis)
* **Right** = `{0, 1, 0}` (Y-axis)
* **Forward** = `{1, 0, 0}` (X-axis)
Matrices are **row-major**. Angles are **clamped pitch** (±90°) and **normalized yaw/roll** (±180°).
---
## Constants
```cpp
namespace omath::unreal_engine {
constexpr Vector3<float> k_abs_up = {0, 0, 1};
constexpr Vector3<float> k_abs_right = {0, 1, 0};
constexpr Vector3<float> k_abs_forward = {1, 0, 0};
}
```
These basis vectors define the engine's **world coordinate frame**.
---
## Matrix types
```cpp
using Mat4X4 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat3X3 = Mat<4, 4, float, MatStoreType::ROW_MAJOR>;
using Mat1X3 = Mat<1, 3, float, MatStoreType::ROW_MAJOR>;
```
**Row-major** storage means rows are contiguous in memory. Suitable for CPU-side transforms and typical C++ math libraries.
---
## Angle types
```cpp
using PitchAngle = Angle<float, -90.f, 90.f, AngleFlags::Clamped>;
using YawAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using RollAngle = Angle<float, -180.f, 180.f, AngleFlags::Normalized>;
using ViewAngles = omath::ViewAngles<PitchAngle, YawAngle, RollAngle>;
```
* **PitchAngle**: clamped to **[-90°, +90°]** (looking down vs. up)
* **YawAngle**: normalized to **[-180°, +180°]** (horizontal rotation)
* **RollAngle**: normalized to **[-180°, +180°]** (camera roll)
`ViewAngles` bundles all three into a single type for camera/view transforms.
---
## Coordinate system notes
* **Z-up**: gravity points along `-Z`, height increases with `+Z`
* **Left-handed**: cross product `forward × right = up` with left-hand rule
* This matches **Unreal Engine** conventions for 3D games and simulations
---
## See also
* `omath/engines/unreal_engine/formulas.hpp` — view/projection matrix builders
* `omath/trigonometry/angle.hpp` — angle normalization & clamping helpers
* `omath/trigonometry/view_angles.hpp` — generic pitch/yaw/roll wrapper

View File

@@ -0,0 +1,135 @@
# `omath::unreal_engine` — formulas & matrix helpers
> Header: `omath/engines/unreal_engine/formulas.hpp`
> Namespace: `omath::unreal_engine`
> Purpose: compute direction vectors, rotation matrices, view matrices, and perspective projections for Unreal Engine
---
## Summary
This header provides **Unreal Engine**-specific math for:
* **Direction vectors** (`forward`, `right`, `up`) from `ViewAngles`
* **Rotation matrices** from Euler angles
* **View matrices** (camera transforms)
* **Perspective projection** matrices
All functions respect Unreal Engine's **Z-up, left-handed** coordinate system.
---
## API
```cpp
namespace omath::unreal_engine {
// Compute forward direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
// Compute right direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
// Compute up direction from pitch/yaw/roll
[[nodiscard]]
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
// Build 3x3 rotation matrix from angles
[[nodiscard]]
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
// Build view matrix (camera space transform)
[[nodiscard]]
Mat4X4 calc_view_matrix(const ViewAngles& angles,
const Vector3<float>& cam_origin) noexcept;
// Build perspective projection matrix
[[nodiscard]]
Mat4X4 calc_perspective_projection_matrix(float field_of_view,
float aspect_ratio,
float near, float far) noexcept;
} // namespace omath::unreal_engine
```
---
## Direction vectors
Given camera angles (pitch/yaw/roll):
* `forward_vector(angles)` → unit vector pointing where the camera looks
* `right_vector(angles)` → unit vector pointing to the camera's right
* `up_vector(angles)` → unit vector pointing upward relative to the camera
These are used for movement, aim direction, and building coordinate frames.
---
## Rotation & view matrices
* `rotation_matrix(angles)` → 3×3 (or 4×4) rotation matrix from Euler angles
* `calc_view_matrix(angles, origin)` → camera view matrix
The view matrix transforms world coordinates into camera space (origin at camera, axes aligned with camera orientation).
---
## Perspective projection
```cpp
Mat4X4 proj = calc_perspective_projection_matrix(
fov_degrees, // vertical field of view (e.g., 90)
aspect_ratio, // width / height (e.g., 16/9)
near_plane, // e.g., 10.0
far_plane // e.g., 100000.0
);
```
Produces a **perspective projection matrix** suitable for 3D rendering pipelines. Combined with the view matrix, this implements the standard camera transform chain.
---
## Usage example
```cpp
using namespace omath::unreal_engine;
// Camera setup
ViewAngles angles = {
PitchAngle::from_degrees(-20.0f),
YawAngle::from_degrees(45.0f),
RollAngle::from_degrees(0.0f)
};
Vector3<float> cam_pos{1000.0f, 500.0f, 200.0f};
// Compute direction
auto forward = forward_vector(angles);
auto right = right_vector(angles);
auto up = up_vector(angles);
// Build matrices
auto view_mat = calc_view_matrix(angles, cam_pos);
auto proj_mat = calc_perspective_projection_matrix(90.0f, 16.0f/9.0f, 10.0f, 100000.0f);
// Use view_mat and proj_mat for rendering...
```
---
## Conventions
* **Angles**: pitch (up/down), yaw (left/right), roll (tilt)
* **Pitch**: positive = looking up, negative = looking down
* **Yaw**: increases counter-clockwise from the +X axis
* **Coordinate system**: Z-up, X-forward, Y-right (left-handed)
---
## See also
* `omath/engines/unreal_engine/constants.hpp` — coordinate frame & angle types
* `omath/engines/unreal_engine/traits/camera_trait.hpp` — plug-in for generic `Camera`
* `omath/projection/camera.hpp` — generic camera wrapper using these formulas

View File

@@ -0,0 +1,121 @@
# `omath::unreal_engine::MeshTrait` — mesh transformation trait for Unreal Engine
> Header: `omath/engines/unreal_engine/traits/mesh_trait.hpp`
> Namespace: `omath::unreal_engine`
> Purpose: provide Unreal Engine-specific rotation matrix computation for `omath::primitives::Mesh`
---
## Summary
`MeshTrait` is a trait class that provides the `rotation_matrix` function for transforming meshes in Unreal Engine's coordinate system. It serves as a template parameter to `omath::primitives::Mesh`, enabling engine-specific rotation behavior.
---
## Coordinate System
**Unreal Engine** uses:
* **Up axis**: +Z
* **Forward axis**: +X
* **Right axis**: +Y
* **Handedness**: Left-handed
* **Rotation order**: Roll (Y) → Pitch (X) → Yaw (Z)
---
## API
```cpp
namespace omath::unreal_engine {
class MeshTrait final {
public:
[[nodiscard]]
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
};
} // namespace omath::unreal_engine
```
---
## Method: `rotation_matrix`
```cpp
static Mat4X4 rotation_matrix(const ViewAngles& rotation);
```
Computes a 4×4 rotation matrix from Unreal-style Euler angles.
**Parameters**:
* `rotation``ViewAngles` containing pitch, yaw, and roll angles
**Returns**: 4×4 rotation matrix suitable for mesh transformation
**Implementation**: Delegates to `unreal_engine::rotation_matrix(rotation)` defined in `formulas.hpp`.
---
## Usage
### With Mesh
```cpp
using namespace omath::unreal_engine;
// Create mesh (MeshTrait is used automatically)
Mesh my_mesh(vertices, indices);
// Set rotation using ViewAngles
ViewAngles angles;
angles.pitch = PitchAngle::from_degrees(30.0f);
angles.yaw = YawAngle::from_degrees(45.0f);
angles.roll = RollAngle::from_degrees(0.0f);
my_mesh.set_rotation(angles);
// The rotation matrix is computed using MeshTrait::rotation_matrix
auto matrix = my_mesh.get_to_world_matrix();
```
---
## Rotation Conventions
Unreal uses a left-handed Z-up coordinate system:
1. **Roll** (rotation around Y-axis / right axis)
* Positive roll rotates forward axis upward
* Range: [-180°, 180°]
2. **Pitch** (rotation around X-axis / forward axis)
* Positive pitch looks upward
* Range: typically [-89°, 89°]
3. **Yaw** (rotation around Z-axis / up axis)
* Positive yaw rotates clockwise when viewed from above (left-handed)
* Range: [-180°, 180°]
**Note**: Unreal applies rotations in Roll-Pitch-Yaw order, different from most other engines.
---
## Type Alias
```cpp
namespace omath::unreal_engine {
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait, float>;
}
```
---
## See Also
- [Mesh Documentation](../../3d_primitives/mesh.md) - Mesh primitive
- [Formulas Documentation](formulas.md) - Unreal rotation formula
- [CameraTrait Documentation](camera_trait.md) - Camera trait
---
*Last updated: 13 Nov 2025*

View File

@@ -0,0 +1,200 @@
# `omath::unreal_engine::PredEngineTrait` — projectile prediction trait
> Header: `omath/engines/unreal_engine/traits/pred_engine_trait.hpp`
> Namespace: `omath::unreal_engine`
> Purpose: provide Unreal Engine-specific projectile and target prediction for ballistic calculations
---
## Summary
`PredEngineTrait` implements engine-specific helpers for **projectile prediction**:
* `predict_projectile_position` computes where a projectile will be after `time` seconds
* `predict_target_position` computes where a moving target will be after `time` seconds
* `calc_vector_2d_distance` horizontal distance (X/Y plane, ignoring Z)
* `get_vector_height_coordinate` extracts vertical coordinate (Y in Unreal Engine, note: code uses Z)
* `calc_viewpoint_from_angles` computes aim point given pitch angle
* `calc_direct_pitch_angle` pitch angle to look from origin to target
* `calc_direct_yaw_angle` yaw angle to look from origin to target
These methods satisfy the `PredEngineTraitConcept` required by generic projectile prediction algorithms.
---
## API
```cpp
namespace omath::unreal_engine {
class PredEngineTrait final {
public:
// Predict projectile position after `time` seconds
static constexpr Vector3<float>
predict_projectile_position(const projectile_prediction::Projectile& projectile,
float pitch, float yaw, float time,
float gravity) noexcept;
// Predict target position after `time` seconds
static constexpr Vector3<float>
predict_target_position(const projectile_prediction::Target& target,
float time, float gravity) noexcept;
// Compute horizontal (2D) distance
static float
calc_vector_2d_distance(const Vector3<float>& delta) noexcept;
// Get vertical coordinate (implementation returns Y, but UE is Z-up)
static constexpr float
get_vector_height_coordinate(const Vector3<float>& vec) noexcept;
// Compute aim point from angles
static Vector3<float>
calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
Vector3<float> predicted_target_position,
std::optional<float> projectile_pitch) noexcept;
// Compute pitch angle to look at target
static float
calc_direct_pitch_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
// Compute yaw angle to look at target
static float
calc_direct_yaw_angle(const Vector3<float>& origin,
const Vector3<float>& view_to) noexcept;
};
} // namespace omath::unreal_engine
```
---
## Projectile prediction
```cpp
auto pos = PredEngineTrait::predict_projectile_position(
projectile, // initial position, speed, gravity scale
pitch_deg, // launch pitch (positive = up)
yaw_deg, // launch yaw
time, // time in seconds
gravity // gravity constant (e.g., 980 cm/s²)
);
```
Computes:
1. Forward vector from pitch/yaw (using `forward_vector`)
2. Initial velocity: `forward * launch_speed`
3. Position after `time`: `origin + velocity*time - 0.5*gravity*gravityScale*time²` (Y component per implementation, though UE is Z-up)
**Note**: Negative pitch in `forward_vector` convention → positive pitch looks up.
---
## Target prediction
```cpp
auto pos = PredEngineTrait::predict_target_position(
target, // position, velocity, airborne flag
time, // time in seconds
gravity // gravity constant
);
```
Simple linear extrapolation plus gravity if target is airborne:
```
predicted = origin + velocity * time
if (airborne)
predicted.y -= 0.5 * gravity * time² // Note: implementation uses Y
```
---
## Distance & height helpers
* `calc_vector_2d_distance(delta)``sqrt(delta.x² + delta.z²)` (horizontal distance in X/Z plane)
* `get_vector_height_coordinate(vec)``vec.y` (implementation returns Y; UE convention is Z-up)
Used to compute ballistic arc parameters.
---
## Aim angle calculation
* `calc_direct_pitch_angle(origin, target)` → pitch in degrees to look from `origin` to `target`
- Formula: `asin(Δz / distance)` converted to degrees (direction normalized first)
- Positive = looking up, negative = looking down
* `calc_direct_yaw_angle(origin, target)` → yaw in degrees to look from `origin` to `target`
- Formula: `atan2(Δy, Δx)` converted to degrees (direction normalized first)
- Horizontal rotation around Z-axis
---
## Viewpoint from angles
```cpp
auto aim_point = PredEngineTrait::calc_viewpoint_from_angles(
projectile,
predicted_target_pos,
optional_pitch_deg
);
```
Computes where to aim in 3D space given a desired pitch angle. Uses horizontal distance and `tan(pitch)` to compute height offset.
---
## Conventions
* **Coordinate system**: Z-up (height increases with Z)
* **Angles**: pitch in [-90°, +90°], yaw in [-180°, +180°]
* **Gravity**: applied downward (implementation uses Y component, but UE is Z-up)
* **Pitch convention**: +90° = straight up, -90° = straight down
**Note**: Some implementation details (gravity application to Y coordinate) may need adjustment for full Unreal Engine Z-up consistency.
---
## Usage example
```cpp
using namespace omath::unreal_engine;
using namespace omath::projectile_prediction;
Projectile proj{
.m_origin = {0, 0, 200},
.m_launch_speed = 5000.0f,
.m_gravity_scale = 1.0f
};
Target tgt{
.m_origin = {2000, 1000, 200},
.m_velocity = {50, 20, 0},
.m_is_airborne = false
};
float gravity = 980.0f; // cm/s² in Unreal units
float time = 0.5f;
// Predict where target will be
auto target_pos = PredEngineTrait::predict_target_position(tgt, time, gravity);
// Compute aim angles
float pitch = PredEngineTrait::calc_direct_pitch_angle(proj.m_origin, target_pos);
float yaw = PredEngineTrait::calc_direct_yaw_angle(proj.m_origin, target_pos);
// Predict projectile position with those angles
auto proj_pos = PredEngineTrait::predict_projectile_position(proj, pitch, yaw, time, gravity);
```
---
## See also
* `omath/engines/unreal_engine/formulas.hpp` — direction vectors and matrix builders
* `omath/projectile_prediction/projectile.hpp``Projectile` struct
* `omath/projectile_prediction/target.hpp``Target` struct
* Generic projectile prediction algorithms that use `PredEngineTraitConcept`

406
docs/faq.md Normal file
View File

@@ -0,0 +1,406 @@
# FAQ
Common questions and answers about OMath.
---
## General Questions
### What is OMath?
OMath is a modern C++ math library designed for game development, graphics programming, and high-performance computing. It provides:
- Vector and matrix operations
- 3D projection and camera systems
- Projectile prediction
- Collision detection
- Support for multiple game engines (Source, Unity, Unreal, etc.)
- Pattern scanning utilities
### Why choose OMath over other math libraries?
- **Modern C++**: Uses C++20/23 features (concepts, `constexpr`, `std::expected`)
- **No legacy code**: Built from scratch without legacy baggage
- **Game engine support**: Pre-configured for Source, Unity, Unreal, Frostbite, etc.
- **Zero dependencies**: No external dependencies needed (except for testing)
- **Performance**: AVX2 optimizations available
- **Type safety**: Strong typing prevents common errors
- **Cross-platform**: Works on Windows, Linux, and macOS
### Is OMath suitable for production use?
Yes! OMath is production-ready and used in various projects. It has:
- Comprehensive test coverage
- Clear error handling
- Well-documented API
- Active maintenance and community support
---
## Installation & Setup
### How do I install OMath?
Three main methods:
**vcpkg (recommended):**
```bash
vcpkg install orange-math
```
**xrepo:**
```bash
xrepo install omath
```
**From source:**
See [Installation Guide](install.md)
### What are the minimum requirements?
- **Compiler**: C++20 support required
- GCC 10+
- Clang 11+
- MSVC 2019 16.10+
- **CMake**: 3.15+ (if building from source)
- **Platform**: Windows, Linux, or macOS
### Do I need C++23?
C++23 is **recommended** but not required. Some features like `std::expected` work better with C++23, but fallbacks are available for C++20.
### Can I use OMath in a C++17 project?
No, OMath requires C++20 minimum due to use of concepts, `constexpr` enhancements, and other C++20 features.
---
## Usage Questions
### How do I include OMath in my project?
**Full library:**
```cpp
#include <omath/omath.hpp>
```
**Specific components:**
```cpp
#include <omath/linear_algebra/vector3.hpp>
#include <omath/engines/source_engine/camera.hpp>
```
### Which game engine should I use?
Choose based on your target game or application:
| Engine | Use For |
|--------|---------|
| **Source Engine** | CS:GO, TF2, CS2, Half-Life, Portal, L4D |
| **Unity Engine** | Unity games (many indie and mobile games) |
| **Unreal Engine** | Fortnite, Unreal games |
| **Frostbite** | Battlefield, Star Wars games (EA titles) |
| **IW Engine** | Call of Duty series |
| **OpenGL** | Custom OpenGL applications, generic 3D |
### How do I switch between engines?
Just change the namespace:
```cpp
// Source Engine
using namespace omath::source_engine;
Camera cam = /* ... */;
// Unity Engine
using namespace omath::unity_engine;
Camera cam = /* ... */;
```
Each engine has the same API but different coordinate system handling.
### What if my game isn't listed?
Use the **OpenGL engine** as a starting point - it uses canonical OpenGL conventions. You may need to adjust coordinate transformations based on your specific game.
---
## Performance Questions
### Should I use the AVX2 or Legacy engine?
**Use AVX2 if:**
- Target modern CPUs (2013+)
- Need maximum performance
- Can accept reduced compatibility
**Use Legacy if:**
- Need broad compatibility
- Target older CPUs or ARM
- Unsure about target hardware
The API is identical - just change the class:
```cpp
// Legacy (compatible)
ProjPredEngineLegacy engine;
// AVX2 (faster)
ProjPredEngineAVX2 engine;
```
### How much faster is AVX2?
Typically 2-4x faster for projectile prediction calculations, depending on the CPU and specific use case.
### Are vector operations constexpr?
Yes! Most operations are `constexpr` and can be evaluated at compile-time:
```cpp
constexpr Vector3<float> v{1, 2, 3};
constexpr auto len_sq = v.length_sqr(); // Computed at compile time
```
### Is OMath thread-safe?
- **Immutable operations** (vector math, etc.) are thread-safe
- **Mutable state** (Camera updates) is NOT thread-safe
- Use separate instances per thread or synchronize access
---
## Troubleshooting
### `world_to_screen()` always returns `nullopt`
Check:
1. **Is the point behind the camera?** Points behind the camera cannot be projected.
2. **Are near/far planes correct?** Ensure `near < far` and both are positive.
3. **Is FOV valid?** FOV should be between 1° and 179°.
4. **Are camera angles normalized?** Use engine-provided angle types.
### Angles are wrapping incorrectly
Use the correct angle type:
```cpp
// Good: uses proper angle type
PitchAngle pitch = PitchAngle::from_degrees(45.0f);
// Bad: raw float loses normalization
float pitch = 45.0f;
```
### Projection seems mirrored or inverted
You may be using the wrong engine trait. Each engine has different coordinate conventions:
- **Source/Unity**: Z-up
- **Unreal**: Z-up, different handedness
- **OpenGL**: Y-up
Ensure you're using the trait matching your game.
### Pattern scanning finds multiple matches
This is normal! Patterns may appear multiple times. Solutions:
1. Make the pattern more specific (more bytes, fewer wildcards)
2. Use additional context (nearby code patterns)
3. Verify each match programmatically
### Projectile prediction returns `nullopt`
Common reasons:
1. **Target too fast**: Target velocity exceeds projectile speed
2. **Out of range**: Distance exceeds max flight time
3. **Invalid input**: Check projectile speed > 0
4. **Gravity too strong**: Projectile can't reach target height
### Compilation errors about `std::expected`
If using C++20 (not C++23), you may need a backport library like `tl::expected`:
```cmake
# CMakeLists.txt
find_package(tl-expected CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE tl::expected)
```
Or upgrade to C++23 if possible.
---
## Feature Questions
### Can I use OMath with DirectX/OpenGL/Vulkan?
Yes! OMath matrices and vectors work with all graphics APIs. Use:
- **OpenGL**: `opengl_engine` traits
- **DirectX**: Use appropriate engine trait or OpenGL as base
- **Vulkan**: Use OpenGL traits as starting point
### Does OMath support quaternions?
Not currently. Quaternion support may be added in future versions. For now, use euler angles (ViewAngles) or convert manually.
### Can I extend OMath with custom engine traits?
Yes! Implement the `CameraEngineConcept`:
```cpp
class MyEngineTrait {
public:
static ViewAngles calc_look_at_angle(
const Vector3<float>& origin,
const Vector3<float>& target
);
static Mat4X4 calc_view_matrix(
const ViewAngles& angles,
const Vector3<float>& origin
);
static Mat4X4 calc_projection_matrix(
const FieldOfView& fov,
const ViewPort& viewport,
float near, float far
);
};
// Use with Camera
using MyCamera = Camera<Mat4X4, ViewAngles, MyEngineTrait>;
```
### Does OMath support SIMD for vector operations?
AVX2 support is available for projectile prediction. General vector SIMD may be added in future versions. The library already compiles to efficient code with compiler optimizations enabled.
### Can I use OMath for machine learning?
OMath is optimized for game development and graphics, not ML. For machine learning, consider libraries like Eigen or xtensor which are designed for that domain.
---
## Debugging Questions
### How do I print vectors?
OMath provides `std::formatter` support:
```cpp
#include <format>
#include <iostream>
Vector3<float> v{1, 2, 3};
std::cout << std::format("{}", v) << "\n"; // Prints: [1, 2, 3]
```
### How do I visualize projection problems?
1. Check if `world_to_screen()` succeeds
2. Print camera matrices:
```cpp
auto view = camera.get_view_matrix();
auto proj = camera.get_projection_matrix();
// Print matrix values
```
3. Test with known good points (e.g., origin, simple positions)
4. Verify viewport and FOV values
### How can I debug pattern scanning?
```cpp
PatternView pattern{"48 8B 05 ?? ?? ?? ??"};
// Print pattern details
std::cout << "Pattern length: " << pattern.size() << "\n";
std::cout << "Pattern bytes: ";
for (auto byte : pattern) {
if (byte.has_value()) {
std::cout << std::hex << (int)*byte << " ";
} else {
std::cout << "?? ";
}
}
std::cout << "\n";
```
---
## Contributing
### How can I contribute to OMath?
See [CONTRIBUTING.md](https://github.com/orange-cpp/omath/blob/master/CONTRIBUTING.md) for guidelines. Contributions welcome:
- Bug fixes
- New features
- Documentation improvements
- Test coverage
- Examples
### Where do I report bugs?
[GitHub Issues](https://github.com/orange-cpp/omath/issues)
Please include:
- OMath version
- Compiler and version
- Minimal reproducible example
- Expected vs actual behavior
### How do I request a feature?
Open a GitHub issue with:
- Use case description
- Proposed API (if applicable)
- Why existing features don't meet your needs
---
## License & Legal
### What license does OMath use?
OMath uses a custom "libomath" license. See [LICENSE](https://github.com/orange-cpp/omath/blob/master/LICENSE) for full details.
### Can I use OMath in commercial projects?
Check the LICENSE file for commercial use terms.
### Can I use OMath for game cheating/hacking?
OMath is a math library and can be used for various purposes. However:
- Using it to cheat in online games may violate game ToS
- Creating cheats may be illegal in your jurisdiction
- The developers do not condone cheating in online games
Use responsibly and ethically.
---
## Getting Help
### Where can I get help?
- **Documentation**: [http://libomath.org](http://libomath.org)
- **Discord**: [Join community](https://discord.gg/eDgdaWbqwZ)
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
- **GitHub Issues**: [Report bugs/ask questions](https://github.com/orange-cpp/omath/issues)
### Is there a Discord/community?
Yes! Join our Discord: [https://discord.gg/eDgdaWbqwZ](https://discord.gg/eDgdaWbqwZ)
### Are there video tutorials?
Check our [YouTube channel](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ) for demonstrations and tutorials.
---
## Didn't find your answer?
- Search the [documentation](index.md)
- Check [tutorials](tutorials.md)
- Ask on [Discord](https://discord.gg/eDgdaWbqwZ)
- Open a [GitHub issue](https://github.com/orange-cpp/omath/issues)
---
*Last updated: 1 Nov 2025*

305
docs/getting_started.md Normal file
View File

@@ -0,0 +1,305 @@
# Getting Started
Welcome to OMath! This guide will help you get up and running with the library quickly.
## What is OMath?
OMath is a modern, blazingly fast C++ math library designed for:
- **Game development** and cheat development
- **Graphics programming** (DirectX/OpenGL/Vulkan)
- **3D applications** with support for multiple game engines
- **High-performance computing** with AVX2 optimizations
Key features:
- 100% independent, no legacy C++ code
- Fully `constexpr` template-based design
- Zero additional dependencies (except for unit tests)
- Cross-platform (Windows, macOS, Linux)
- Built-in support for Source, Unity, Unreal, Frostbite, IWEngine, and OpenGL coordinate systems
---
## Installation
Choose one of the following methods to install OMath:
### Using vcpkg (Recommended)
```bash
vcpkg install orange-math
```
Then in your CMakeLists.txt:
```cmake
find_package(omath CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE omath::omath)
```
### Using xrepo
```bash
xrepo install omath
```
Then in your xmake.lua:
```lua
add_requires("omath")
target("your_target")
add_packages("omath")
```
### Building from Source
See the detailed [Installation Guide](install.md) for complete instructions.
---
## Quick Example
Here's a simple example to get you started:
```cpp
#include <omath/omath.hpp>
#include <iostream>
int main() {
using namespace omath;
// Create 3D vectors
Vector3<float> a{1.0f, 2.0f, 3.0f};
Vector3<float> b{4.0f, 5.0f, 6.0f};
// Vector operations
auto sum = a + b; // Vector addition
auto dot_product = a.dot(b); // Dot product: 32.0
auto cross_product = a.cross(b); // Cross product: (-3, 6, -3)
auto length = a.length(); // Length: ~3.74
auto normalized = a.normalized(); // Unit vector
std::cout << "Sum: [" << sum.x << ", " << sum.y << ", " << sum.z << "]\n";
std::cout << "Dot product: " << dot_product << "\n";
std::cout << "Length: " << length << "\n";
return 0;
}
```
---
## Core Concepts
### 1. Vectors
OMath provides 2D, 3D, and 4D vector types:
```cpp
using namespace omath;
Vector2<float> vec2{1.0f, 2.0f};
Vector3<float> vec3{1.0f, 2.0f, 3.0f};
Vector4<float> vec4{1.0f, 2.0f, 3.0f, 4.0f};
```
All vector types support:
- Arithmetic operations (+, -, *, /)
- Dot and cross products (where applicable)
- Length and distance calculations
- Normalization
- Component-wise operations
See: [Vector2](linear_algebra/vector2.md), [Vector3](linear_algebra/vector3.md), [Vector4](linear_algebra/vector4.md)
### 2. Matrices
4x4 matrices for transformations:
```cpp
using namespace omath;
Mat4X4 matrix = Mat4X4::identity();
// Use for transformations, projections, etc.
```
See: [Matrix Documentation](linear_algebra/mat.md)
### 3. Angles
Strong-typed angle system with automatic range management:
```cpp
using namespace omath;
auto angle = Angle<float, 0.0f, 360.0f>::from_degrees(45.0f);
auto radians = angle.as_radians();
// View angles for camera systems
ViewAngles view{
PitchAngle::from_degrees(-10.0f),
YawAngle::from_degrees(90.0f),
RollAngle::from_degrees(0.0f)
};
```
See: [Angle](trigonometry/angle.md), [View Angles](trigonometry/view_angles.md)
### 4. 3D Projection
Built-in camera and projection systems:
```cpp
using namespace omath;
using namespace omath::projection;
ViewPort viewport{1920.0f, 1080.0f};
auto fov = FieldOfView::from_degrees(90.0f);
// Example using Source Engine
using namespace omath::source_engine;
Camera cam(
Vector3<float>{0, 0, 100}, // Position
ViewAngles{}, // Angles
viewport,
fov,
0.1f, // near plane
1000.0f // far plane
);
// Project 3D point to 2D screen
Vector3<float> world_pos{100, 50, 75};
if (auto screen_pos = cam.world_to_screen(world_pos)) {
std::cout << "Screen: " << screen_pos->x << ", " << screen_pos->y << "\n";
}
```
See: [Camera](projection/camera.md)
### 5. Game Engine Support
OMath provides pre-configured traits for major game engines:
```cpp
// Source Engine
#include <omath/engines/source_engine/camera.hpp>
using SourceCamera = omath::source_engine::Camera;
// Unity Engine
#include <omath/engines/unity_engine/camera.hpp>
using UnityCamera = omath::unity_engine::Camera;
// Unreal Engine
#include <omath/engines/unreal_engine/camera.hpp>
using UnrealCamera = omath::unreal_engine::Camera;
// And more: OpenGL, Frostbite, IWEngine
```
Each engine has its own coordinate system conventions automatically handled.
See: Engine-specific docs in [engines/](engines/) folder
---
## Common Use Cases
### World-to-Screen Projection
```cpp
using namespace omath;
using namespace omath::source_engine;
Camera cam = /* initialize camera */;
Vector3<float> enemy_position{100, 200, 50};
if (auto screen = cam.world_to_screen(enemy_position)) {
// Draw ESP box at screen->x, screen->y
std::cout << "Enemy on screen at: " << screen->x << ", " << screen->y << "\n";
} else {
// Enemy not visible (behind camera or outside frustum)
}
```
### Projectile Prediction
```cpp
using namespace omath::projectile_prediction;
Projectile bullet{
Vector3<float>{0, 0, 0}, // shooter position
1000.0f, // muzzle velocity (m/s)
Vector3<float>{0, 0, -9.81f} // gravity
};
Target enemy{
Vector3<float>{100, 200, 50}, // position
Vector3<float>{10, 0, 0} // velocity
};
// Calculate where to aim
ProjPredEngineLegacy engine;
if (auto aim_point = engine.maybe_calculate_aim_point(bullet, enemy)) {
// Aim at *aim_point to hit moving target
}
```
See: [Projectile Prediction](projectile_prediction/projectile_engine.md)
### Collision Detection
```cpp
using namespace omath;
// Ray-plane intersection
Plane ground{
Vector3<float>{0, 0, 0}, // point on plane
Vector3<float>{0, 0, 1} // normal (pointing up)
};
Vector3<float> ray_origin{0, 0, 100};
Vector3<float> ray_direction{0, 0, -1};
if (auto hit = ground.intersects_ray(ray_origin, ray_direction)) {
std::cout << "Hit ground at: " << hit->x << ", " << hit->y << ", " << hit->z << "\n";
}
```
See: [Collision Detection](collision/line_tracer.md)
### Pattern Scanning
```cpp
#include <omath/utility/pattern_scan.hpp>
using namespace omath;
std::vector<uint8_t> memory = /* ... */;
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
if (auto result = pattern_scan(memory, pattern)) {
std::cout << "Pattern found at offset: " << result->offset << "\n";
}
```
See: [Pattern Scanning](utility/pattern_scan.md)
---
## Next Steps
Now that you have the basics, explore these topics:
1. **[API Reference](index.md)** - Complete API documentation
2. **[Examples](../examples/)** - Working code examples
3. **[Engine-Specific Features](engines/)** - Deep dive into game engine support
4. **[Advanced Topics](#)** - Performance optimization, custom traits, etc.
---
## Getting Help
- **Documentation**: [http://libomath.org](http://libomath.org)
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
- **Issues**: [GitHub Issues](https://github.com/orange-cpp/omath/issues)
---
*Last updated: 1 Nov 2025*

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 454 KiB

After

Width:  |  Height:  |  Size: 454 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 644 KiB

After

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View File

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 324 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,17 +1,239 @@
# Welcome to MkDocs
<div class="center-text">
<!-- Banner -->
<p>
<img src="images/logos/omath_logo_macro.png" alt="omath banner">
</p>
For full documentation visit [mkdocs.org](https://www.mkdocs.org).
<!-- Badges -->
<p>
<img src="https://img.shields.io/badge/license-libomath-orange" alt="license: libomath">
<img src="https://img.shields.io/github/contributors/orange-cpp/omath" alt="GitHub contributors">
<img src="https://img.shields.io/github/languages/top/orange-cpp/omath" alt="Top language">
<a href="https://www.codefactor.io/repository/github/orange-cpp/omath">
<img src="https://www.codefactor.io/repository/github/orange-cpp/omath/badge" alt="CodeFactor">
</a>
<img src="https://img.shields.io/github/actions/workflow/status/orange-cpp/omath/cmake-multi-platform.yml" alt="GitHub Actions Workflow Status">
<a href="https://repology.org/project/orange-math/versions">
<img src="https://repology.org/badge/version-for-repo/vcpkg/orange-math.svg" alt="Vcpkg package">
</a>
<img src="https://img.shields.io/github/forks/orange-cpp/omath" alt="GitHub forks">
<a href="https://discord.gg/eDgdaWbqwZ">
<img src="https://dcbadge.limes.pink/api/server/https://discord.gg/eDgdaWbqwZ?style=flat" alt="Join us on Discord">
</a>
<a href="https://t.me/orangennotes">
<img src="https://img.shields.io/badge/Telegram-2CA5E0?style=flat-squeare&logo=telegram&logoColor=white" alt="Telegram">
</a>
</p>
</div>
## Commands
OMath is a 100% independent, constexpr template blazingly fast math library that doesn't have legacy C++ code.
* `mkdocs new [dir-name]` - Create a new project.
* `mkdocs serve` - Start the live-reloading docs server.
* `mkdocs build` - Build the documentation site.
* `mkdocs -h` - Print help message and exit.
It provides the latest features, is highly customizable, has all for cheat development, DirectX/OpenGL/Vulkan support, premade support for different game engines, much more constexpr stuff than in other libraries and more...
## Project layout
---
mkdocs.yml # The configuration file.
docs/
index.md # The documentation homepage.
... # Other markdown pages, images and other files.
## 🚀 Quick Start
**New to OMath?** Start here:
- **[Getting Started Guide](getting_started.md)** - Installation and first steps
- **[API Overview](api_overview.md)** - High-level API reference
- **[Installation Instructions](install.md)** - Detailed setup guide
**Quick example:**
```cpp
#include <omath/omath.hpp>
using namespace omath;
Vector3<float> a{1, 2, 3};
Vector3<float> b{4, 5, 6};
auto dot = a.dot(b); // 32.0
auto cross = a.cross(b); // (-3, 6, -3)
auto distance = a.distance_to(b); // ~5.196
```
---
## 📚 Documentation Structure
### Core Mathematics
**Linear Algebra**
- [Vector2](linear_algebra/vector2.md) - 2D vectors with full operator support
- [Vector3](linear_algebra/vector3.md) - 3D vectors, dot/cross products, angles
- [Vector4](linear_algebra/vector4.md) - 4D vectors (homogeneous coordinates)
- [Mat4X4](linear_algebra/mat.md) - 4×4 matrices for transformations
- [Triangle](linear_algebra/triangle.md) - Triangle primitive and utilities
**Trigonometry**
- [Angle](trigonometry/angle.md) - Strong-typed angle system with range enforcement
- [Angles](trigonometry/angles.md) - Angle utilities and conversions
- [View Angles](trigonometry/view_angles.md) - Pitch/Yaw/Roll for camera systems
**3D Primitives**
- [Box](3d_primitives/box.md) - Axis-aligned bounding boxes
- [Plane](3d_primitives/plane.md) - Infinite planes and intersections
### Game Development Features
**Projection & Camera**
- [Camera](projection/camera.md) - Generic camera system with engine traits
- [Error Codes](projection/error_codes.md) - Projection error handling
**Collision Detection**
- [Line Tracer](collision/line_tracer.md) - Ray-triangle, ray-plane intersections
**Projectile Prediction**
- [Projectile Engine Interface](projectile_prediction/projectile_engine.md) - Base interface
- [Projectile](projectile_prediction/projectile.md) - Projectile properties
- [Target](projectile_prediction/target.md) - Target state representation
- [Legacy Engine](projectile_prediction/proj_pred_engine_legacy.md) - Standard implementation
- [AVX2 Engine](projectile_prediction/proj_pred_engine_avx2.md) - Optimized implementation
**Pathfinding**
- [A* Algorithm](pathfinding/a_star.md) - A* pathfinding implementation
- [Navigation Mesh](pathfinding/navigation_mesh.md) - Triangle-based navigation
### Game Engine Support
OMath provides built-in support for multiple game engines with proper coordinate system handling:
**Source Engine** (Valve - CS:GO, TF2, etc.)
- [Camera Trait](engines/source_engine/camera_trait.md)
- [Pred Engine Trait](engines/source_engine/pred_engine_trait.md)
- [Constants](engines/source_engine/constants.md)
- [Formulas](engines/source_engine/formulas.md)
**Unity Engine**
- [Camera Trait](engines/unity_engine/camera_trait.md)
- [Pred Engine Trait](engines/unity_engine/pred_engine_trait.md)
- [Constants](engines/unity_engine/constants.md)
- [Formulas](engines/unity_engine/formulas.md)
**Unreal Engine** (Epic Games)
- [Camera Trait](engines/unreal_engine/camera_trait.md)
- [Pred Engine Trait](engines/unreal_engine/pred_engine_trait.md)
- [Constants](engines/unreal_engine/constants.md)
- [Formulas](engines/unreal_engine/formulas.md)
**Frostbite Engine** (EA - Battlefield, etc.)
- [Camera Trait](engines/frostbite/camera_trait.md)
- [Pred Engine Trait](engines/frostbite/pred_engine_trait.md)
- [Constants](engines/frostbite/constants.md)
- [Formulas](engines/frostbite/formulas.md)
**IW Engine** (Infinity Ward - Call of Duty)
- [Camera Trait](engines/iw_engine/camera_trait.md)
- [Pred Engine Trait](engines/iw_engine/pred_engine_trait.md)
- [Constants](engines/iw_engine/constants.md)
- [Formulas](engines/iw_engine/formulas.md)
**OpenGL Engine** (Canonical OpenGL)
- [Camera Trait](engines/opengl_engine/camera_trait.md)
- [Pred Engine Trait](engines/opengl_engine/pred_engine_trait.md)
- [Constants](engines/opengl_engine/constants.md)
- [Formulas](engines/opengl_engine/formulas.md)
### Utilities
**Color**
- [Color](utility/color.md) - RGBA color with conversions
**Pattern Scanning & Memory**
- [Pattern Scan](utility/pattern_scan.md) - Binary pattern search with wildcards
- [PE Pattern Scan](utility/pe_pattern_scan.md) - PE file pattern scanning
**Reverse Engineering**
- [External Rev Object](rev_eng/external_rev_object.md) - External process memory access
- [Internal Rev Object](rev_eng/internal_rev_object.md) - Internal memory access
---
## ✨ Key Features
- **Efficiency**: Optimized for performance, ensuring quick computations using AVX2.
- **Versatility**: Includes a wide array of mathematical functions and algorithms.
- **Ease of Use**: Simplified interface for convenient integration into various projects.
- **Projectile Prediction**: Projectile prediction engine with O(N) algo complexity, that can power you projectile aim-bot.
- **3D Projection**: No need to find view-projection matrix anymore you can make your own projection pipeline.
- **Collision Detection**: Production ready code to handle collision detection by using simple interfaces.
- **No Additional Dependencies**: No additional dependencies need to use OMath except unit test execution
- **Ready for meta-programming**: Omath use templates for common types like Vectors, Matrixes etc, to handle all types!
- **Engine support**: Supports coordinate systems of **Source, Unity, Unreal, Frostbite, IWEngine and canonical OpenGL**.
- **Cross platform**: Supports Windows, MacOS and Linux.
- **Algorithms**: Has ability to scan for byte pattern with wildcards in PE files/modules, binary slices, works even with Wine apps.
---
## 📖 Common Use Cases
### World-to-Screen Projection
Project 3D world coordinates to 2D screen space for ESP overlays, UI elements, or visualization.
### Projectile Prediction
Calculate aim points for moving targets considering projectile speed, gravity, and target velocity.
### Collision Detection
Perform ray-casting, line tracing, and intersection tests for hit detection and physics.
### Pattern Scanning
Search for byte patterns in memory for reverse engineering, modding, or tool development.
### Pathfinding
Find optimal paths through 3D spaces using A* algorithm and navigation meshes.
---
## 🎮 Gallery
<br>
[![Youtube Video](images/yt_previews/img.png)](https://youtu.be/lM_NJ1yCunw?si=-Qf5yzDcWbaxAXGQ)
<br>
![APEX Preview]
<br>
![BO2 Preview]
<br>
![CS2 Preview]
<br>
![TF2 Preview]
<br>
<br>
---
## 🤝 Community & Support
- **Documentation**: [http://libomath.org](http://libomath.org)
- **GitHub**: [orange-cpp/omath](https://github.com/orange-cpp/omath)
- **Discord**: [Join our community](https://discord.gg/eDgdaWbqwZ)
- **Telegram**: [@orangennotes](https://t.me/orangennotes)
- **Issues**: [Report bugs or request features](https://github.com/orange-cpp/omath/issues)
---
## 💡 Contributing
OMath is open source and welcomes contributions! See [CONTRIBUTING.md](https://github.com/orange-cpp/omath/blob/master/CONTRIBUTING.md) for guidelines.
---
*Last updated: 1 Nov 2025*
<!----------------------------------{ Images }--------------------------------->
[APEX Preview]: images/showcase/apex.png
[BO2 Preview]: images/showcase/cod_bo2.png
[CS2 Preview]: images/showcase/cs2.jpeg
[TF2 Preview]: images/showcase/tf2.jpg

68
docs/install.md Normal file
View File

@@ -0,0 +1,68 @@
# Installation
## <img width="28px" src="https://vcpkg.io/assets/mark/mark.svg" /> Using vcpkg
**Note**: Support vcpkg for package management
1. Install [vcpkg](https://github.com/microsoft/vcpkg)
2. Run the following command to install the orange-math package:
```
vcpkg install orange-math
```
CMakeLists.txt
```cmake
find_package(omath CONFIG REQUIRED)
target_link_libraries(main PRIVATE omath::omath)
```
For detailed commands on installing different versions and more information, please refer to Microsoft's [official instructions](https://learn.microsoft.com/en-us/vcpkg/get_started/overview).
## <img width="28px" src="https://xmake.io/assets/img/logo.svg" /> Using xrepo
**Note**: Support xrepo for package management
1. Install [xmake](https://xmake.io/)
2. Run the following command to install the omath package:
```
xrepo install omath
```
xmake.lua
```xmake
add_requires("omath")
target("...")
add_packages("omath")
```
## <img width="28px" src="https://upload.wikimedia.org/wikipedia/commons/e/ef/CMake_logo.svg?" /> Build from source using CMake
1. **Preparation**
Install needed tools: cmake, clang, git, msvc (windows only).
1. **Linux:**
```bash
sudo pacman -Sy cmake ninja clang git
```
2. **MacOS:**
```bash
brew install llvm git cmake ninja
```
3. **Windows:**
Install Visual Studio from [here](https://visualstudio.microsoft.com/downloads/) and Git from [here](https://git-scm.com/downloads).
Use x64 Native Tools shell to execute needed commands down below.
2. **Clone the repository:**
```bash
git clone https://github.com/orange-cpp/omath.git
```
3. **Navigate to the project directory:**
```bash
cd omath
```
4. **Build the project using CMake:**
```bash
cmake --preset windows-release -S .
cmake --build cmake-build/build/windows-release --target omath -j 6
```
Use **\<platform\>-\<build configuration\>** preset to build suitable version for yourself. Like **windows-release** or **linux-release**.
| Platform Name | Build Config |
|---------------|---------------|
| windows | release/debug |
| linux | release/debug |
| darwin | release/debug |

428
docs/linear_algebra/mat.md Normal file
View File

@@ -0,0 +1,428 @@
# `omath::Mat` — Matrix class (C++20/23)
> Header: your projects `mat.hpp` (requires `vector3.hpp`)
> Namespace: `omath`
> Requires: **C++23** (uses multi-parameter `operator[]`)
> SIMD (optional): define **`OMATH_USE_AVX2`** to enable AVX2-accelerated multiplication for `float`/`double`.
---
## Overview
`omath::Mat<Rows, Columns, Type, StoreType>` is a compile-time, fixed-size matrix with:
* **Row/column counts** as template parameters (no heap allocations).
* **Row-major** or **column-major** storage (compile-time via `MatStoreType`).
* **Arithmetic** and **linear algebra**: matrix × matrix, scalar ops, transpose, determinant, inverse (optional), etc.
* **Transform helpers**: translation, axis rotations, look-at, perspective & orthographic projections.
* **I/O helpers**: `to_string`/`to_wstring`/`to_u8string` and `std::formatter` specializations.
---
## Template parameters
| Parameter | Description | Default |
| ----------- | ------------------------------------------------------------------------ | ----------- |
| `Rows` | Number of rows (size_t, compile-time) | — |
| `Columns` | Number of columns (size_t, compile-time) | — |
| `Type` | Element type (arithmetic) | `float` |
| `StoreType` | Storage order: `MatStoreType::ROW_MAJOR` or `MatStoreType::COLUMN_MAJOR` | `ROW_MAJOR` |
```cpp
enum class MatStoreType : uint8_t { ROW_MAJOR = 0, COLUMN_MAJOR };
```
---
## Quick start
```cpp
#include "mat.hpp"
using omath::Mat;
// 4x4 float, row-major
Mat<4,4> I = {
{1,0,0,0},
{0,1,0,0},
{0,0,1,0},
{0,0,0,1},
};
// Multiply 4x4 transforms
Mat<4,4> A = { {1,2,3,0},{0,1,4,0},{5,6,0,0},{0,0,0,1} };
Mat<4,4> B = { {2,0,0,0},{0,2,0,0},{0,0,2,0},{0,0,0,1} };
Mat<4,4> C = A * B; // matrix × matrix
// Scalar ops
auto D = C * 0.5f; // scale all entries
// Indexing (C++23 multi-parameter operator[])
float a03 = A[0,3]; // same as A.at(0,3)
A[1,2] = 42.0f;
// Transpose, determinant, inverse
auto AT = A.transposed();
float det = A.determinant(); // only for square matrices
auto inv = A.inverted(); // std::optional<Mat>; std::nullopt if non-invertible
```
> **Note**
> Multiplication requires the **same** `StoreType` and `Type` on both operands, and dimensions must match at compile time.
---
## Construction
```cpp
Mat(); // zero-initialized
Mat(std::initializer_list<std::initializer_list<Type>> rows);
explicit Mat(const Type* raw_data); // copies Rows*Columns elements
Mat(const Mat&); Mat(Mat&&);
```
* **Zeroing/setting**
```cpp
m.clear(); // set all entries to 0
m.set(3.14f); // set all entries to a value
```
* **Shape & metadata**
```cpp
Mat<>::row_count(); // constexpr size_t
Mat<>::columns_count(); // constexpr size_t
Mat<>::size(); // constexpr MatSize {rows, columns}
Mat<>::get_store_ordering(); // constexpr MatStoreType
using ContainedType = Type; // alias
```
---
## Element access
```cpp
T& at(size_t r, size_t c);
T const& at(size_t r, size_t c) const;
T& operator[](size_t r, size_t c); // C++23
T const& operator[](size_t r, size_t c) const; // C++23
```
> **Bounds checking**
> In debug builds you may enable/disable range checks via your compile-time macros (see the source guard around `at()`).
---
## Arithmetic
* **Matrix × matrix**
```cpp
// (Rows x Columns) * (Columns x OtherColumns) -> (Rows x OtherColumns)
template<size_t OtherColumns>
Mat<Rows, OtherColumns, Type, StoreType>
operator*(const Mat<Columns, OtherColumns, Type, StoreType>&) const;
```
* Complexity: `O(Rows * Columns * OtherColumns)`.
* AVX2-accelerated when `OMATH_USE_AVX2` is defined and `Type` is `float` or `double`.
* **Scalars**
```cpp
Mat operator*(const Type& s) const; Mat& operator*=(const Type& s);
Mat operator/(const Type& s) const; Mat& operator/=(const Type& s);
```
* **Transpose**
```cpp
Mat<Columns, Rows, Type, StoreType> transposed() const noexcept;
```
* **Determinant (square only)**
```cpp
Type determinant() const; // 1x1, 2x2 fast path; larger uses Laplace expansion
```
* **Inverse (square only)**
```cpp
std::optional<Mat> inverted() const; // nullopt if det == 0
```
* **Minors & cofactors (square only)**
```cpp
Mat<Rows-1, Columns-1, Type, StoreType> strip(size_t r, size_t c) const;
Type minor(size_t r, size_t c) const;
Type alg_complement(size_t r, size_t c) const; // cofactor
```
* **Utilities**
```cpp
Type sum() const noexcept;
auto& raw_array(); // std::array<Type, Rows*Columns>&
auto const& raw_array() const;
```
* **Comparison / formatting**
```cpp
bool operator==(const Mat&) const;
bool operator!=(const Mat&) const;
std::string to_string() const noexcept;
std::wstring to_wstring() const noexcept;
std::u8string to_u8string() const noexcept;
```
// std::formatter specialization provided for char, wchar_t, char8_t
````
---
## Storage order notes
- **Row-major**: `index = row * Columns + column`
- **Column-major**: `index = row + column * Rows`
Choose one **consistently** across your math types and shader conventions. Mixed orders are supported by the type system but not for cross-multiplying (store types must match).
---
## Transform helpers
### From vectors
```cpp
template<class T=float, MatStoreType St=ROW_MAJOR>
Mat<1,4,T,St> mat_row_from_vector(const Vector3<T>& v);
template<class T=float, MatStoreType St=ROW_MAJOR>
Mat<4,1,T,St> mat_column_from_vector(const Vector3<T>& v);
````
### Translation
```cpp
template<class T=float, MatStoreType St=ROW_MAJOR>
Mat<4,4,T,St> mat_translation(const Vector3<T>& d) noexcept;
```
### Axis rotations
```cpp
// Angle type must provide angle.cos() and angle.sin()
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
Mat<4,4,T,St> mat_rotation_axis_x(const Angle& a) noexcept;
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
Mat<4,4,T,St> mat_rotation_axis_y(const Angle& a) noexcept;
template<class T=float, MatStoreType St=ROW_MAJOR, class Angle>
Mat<4,4,T,St> mat_rotation_axis_z(const Angle& a) noexcept;
```
### Camera/view
```cpp
template<class T=float, MatStoreType St=ROW_MAJOR>
Mat<4,4,T,St> mat_camera_view(const Vector3<T>& forward,
const Vector3<T>& right,
const Vector3<T>& up,
const Vector3<T>& camera_origin) noexcept;
```
### Perspective projections
```cpp
template<class T=float, MatStoreType St=ROW_MAJOR>
Mat<4,4,T,St> mat_perspective_left_handed (float fov_deg, float aspect, float near, float far) noexcept;
template<class T=float, MatStoreType St=ROW_MAJOR>
Mat<4,4,T,St> mat_perspective_right_handed(float fov_deg, float aspect, float near, float far) noexcept;
```
### Orthographic projections
```cpp
template<class T=float, MatStoreType St=ROW_MAJOR>
Mat<4,4,T,St> mat_ortho_left_handed (T left, T right, T bottom, T top, T near, T far) noexcept;
template<class T=float, MatStoreType St=ROW_MAJOR>
Mat<4,4,T,St> mat_ortho_right_handed(T left, T right, T bottom, T top, T near, T far) noexcept;
```
### Look-at matrices
```cpp
template<class T=float, MatStoreType St=COLUMN_MAJOR>
Mat<4,4,T,St> mat_look_at_left_handed (const Vector3<T>& eye,
const Vector3<T>& center,
const Vector3<T>& up);
template<class T=float, MatStoreType St=COLUMN_MAJOR>
Mat<4,4,T,St> mat_look_at_right_handed(const Vector3<T>& eye,
const Vector3<T>& center,
const Vector3<T>& up);
```
---
## Screen-space helper
```cpp
template<class Type=float>
static constexpr Mat<4,4> to_screen_mat(const Type& screen_w, const Type& screen_h) noexcept;
// Maps NDC to screen space (origin top-left, y down)
```
---
## Examples
### 1) Building a left-handed camera and perspective
```cpp
using V3 = omath::Vector3<float>;
using M4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
V3 eye{0, 1, -5}, center{0, 0, 0}, up{0, 1, 0};
M4 view = omath::mat_look_at_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(eye, center, up);
float fov = 60.f, aspect = 16.f/9.f, n = 0.1f, f = 100.f;
M4 proj = omath::mat_perspective_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(fov, aspect, n, f);
// final VP
M4 vp = proj * view;
```
### 2) Inverting a transform safely
```cpp
omath::Mat<4,4> T = omath::mat_translation(omath::Vector3<float>{2,3,4});
if (auto inv = T.inverted()) {
// use *inv
} else {
// handle non-invertible
}
```
### 3) Formatting for logs
```cpp
omath::Mat<2,2> A = { {1,2},{3,4} };
std::string s = A.to_string(); // "[[ 1.000, 2.000]\n [ 3.000, 4.000]]"
std::string f = std::format("A = {}", A); // uses std::formatter
```
---
## Performance
* **Cache-friendly kernels** per storage order when AVX2 is not enabled.
* **AVX2 path** (`OMATH_USE_AVX2`) for `float`/`double` implements FMAs with 256-bit vectors for both row-major and column-major multiplication.
* Complexity for `A(R×K) * B(K×C)`: **O(RKC)** regardless of storage order.
---
## Constraints & concepts
```cpp
template<typename M1, typename M2>
concept MatTemplateEqual =
(M1::rows == M2::rows) &&
(M1::columns == M2::columns) &&
std::is_same_v<typename M1::value_type, typename M2::value_type> &&
(M1::store_type == M2::store_type);
```
> Use this concept to constrain generic functions that operate on like-shaped matrices.
---
## Exceptions
* `std::invalid_argument` — initializer list dimensions mismatch.
* `std::out_of_range` — out-of-bounds in `at()` when bounds checking is active (see source guard).
* `inverted()` does **not** throw; returns `std::nullopt` if `determinant() == 0`.
---
## Build switches
* **`OMATH_USE_AVX2`** — enable AVX2 vectorized multiplication paths (`<immintrin.h>` required).
* **Debug checks** — the `at()` method contains a conditional range check; refer to the preprocessor guard in the code to enable/disable in your configuration.
---
## Known requirements & interoperability
* **C++23** is required for multi-parameter `operator[]`. If you target pre-C++23, use `at(r,c)` instead.
* All binary operations require matching `Type` and `StoreType`. Convert explicitly if needed.
---
## See also
* `omath::Vector3<T>`
* Projection helpers: `mat_perspective_*`, `mat_ortho_*`
* View helpers: `mat_look_at_*`, `mat_camera_view`
* Construction helpers: `mat_row_from_vector`, `mat_column_from_vector`, `mat_translation`, `mat_rotation_axis_*`
---
## Appendix: API summary (signatures)
```cpp
// Core
Mat(); Mat(const Mat&); Mat(Mat&&);
Mat(std::initializer_list<std::initializer_list<Type>>);
explicit Mat(const Type* raw);
Mat& operator=(const Mat&); Mat& operator=(Mat&&);
static constexpr size_t row_count();
static constexpr size_t columns_count();
static consteval MatSize size();
static constexpr MatStoreType get_store_ordering();
T& at(size_t r, size_t c);
T const& at(size_t r, size_t c) const;
T& operator[](size_t r, size_t c);
T const& operator[](size_t r, size_t c) const;
void clear();
void set(const Type& v);
Type sum() const noexcept;
template<size_t OC> Mat<Rows,OC,Type,StoreType> operator*(const Mat<Columns,OC,Type,StoreType>&) const;
Mat& operator*=(const Type&); Mat operator*(const Type&) const;
Mat& operator/=(const Type&); Mat operator/(const Type&) const;
Mat<Columns,Rows,Type,StoreType> transposed() const noexcept;
Type determinant() const; // square only
std::optional<Mat> inverted() const; // square only
Mat<Rows-1,Columns-1,Type,StoreType> strip(size_t r, size_t c) const;
Type minor(size_t r, size_t c) const;
Type alg_complement(size_t r, size_t c) const;
auto& raw_array(); auto const& raw_array() const;
std::string to_string() const noexcept;
std::wstring to_wstring() const noexcept;
std::u8string to_u8string() const noexcept;
bool operator==(const Mat&) const;
bool operator!=(const Mat&) const;
// Helpers (see sections above)
```
---
*Last updated: 31 Oct 2025*

View File

@@ -0,0 +1,173 @@
# `omath::Triangle` — Simple 3D triangle utility
> Header: your projects `triangle.hpp`
> Namespace: `omath`
> Depends on: `omath::Vector3<float>` (from `vector3.hpp`)
A tiny helper around three `Vector3<float>` vertices with convenience methods for normals, edge vectors/lengths, a right-angle test (at **`v2`**), and the triangle centroid.
> **Note on the template parameter**
>
> The class is declared as `template<class Vector> class Triangle`, but the stored vertices are concretely `Vector3<float>`. In practice this type is currently **fixed to `Vector3<float>`**. You can ignore the template parameter or refactor to store `Vector` if you intend true genericity.
---
## Vertex layout & naming
```
v1
|\
| \
a | \ hypot = |v1 - v3|
| \
v2 -- v3
b
a = |v1 - v2| (side_a_length)
b = |v3 - v2| (side_b_length)
```
* **`side_a_vector()`** = `v1 - v2` (points from v2 → v1)
* **`side_b_vector()`** = `v3 - v2` (points from v2 → v3)
* **Right-angle check** uses `a² + b² ≈ hypot²` with an epsilon of `1e-4`.
---
## Quick start
```cpp
#include "triangle.hpp"
using omath::Vector3;
using omath::Triangle;
Triangle<void> tri( // template arg unused; any placeholder ok
Vector3<float>{0,0,0}, // v1
Vector3<float>{0,0,1}, // v2 (right angle is tested at v2)
Vector3<float>{1,0,1} // v3
);
auto n = tri.calculate_normal(); // unit normal (right-handed: (v3-v2) × (v1-v2))
float a = tri.side_a_length(); // |v1 - v2|
float b = tri.side_b_length(); // |v3 - v2|
float hyp = tri.hypot(); // |v1 - v3|
bool rect = tri.is_rectangular(); // true if ~right triangle at v2
auto C = tri.mid_point(); // centroid (average of v1,v2,v3)
```
---
## Data members
```cpp
Vector3<float> m_vertex1; // v1
Vector3<float> m_vertex2; // v2 (the corner tested by is_rectangular)
Vector3<float> m_vertex3; // v3
```
---
## Constructors
```cpp
constexpr Triangle() = default;
constexpr Triangle(const Vector3<float>& v1,
const Vector3<float>& v2,
const Vector3<float>& v3);
```
---
## Methods
```cpp
// Normal (unit) using right-handed cross product:
// n = (v3 - v2) × (v1 - v2), then normalized()
[[nodiscard]] constexpr Vector3<float> calculate_normal() const;
// Edge lengths with the naming from the diagram
[[nodiscard]] float side_a_length() const; // |v1 - v2|
[[nodiscard]] float side_b_length() const; // |v3 - v2|
// Edge vectors (from v2 to the other vertex)
[[nodiscard]] constexpr Vector3<float> side_a_vector() const; // v1 - v2
[[nodiscard]] constexpr Vector3<float> side_b_vector() const; // v3 - v2
// Hypotenuse length between v1 and v3
[[nodiscard]] constexpr float hypot() const; // |v1 - v3|
// Right-triangle check at vertex v2 (Pythagoras with epsilon 1e-4)
[[nodiscard]] constexpr bool is_rectangular() const;
// Centroid of the triangle (average of the 3 vertices)
[[nodiscard]] constexpr Vector3<float> mid_point() const; // actually the centroid
```
### Notes & edge cases
* **Normal direction** follows the right-hand rule for the ordered vertices `{v2 → v3} × {v2 → v1}`.
Swap vertex order to flip the normal.
* **Degenerate triangles** (collinear or overlapping vertices) yield a **zero vector** normal (since `normalized()` of the zero vector returns the zero vector in your math types).
* **`mid_point()` is the centroid**, not the midpoint of any single edge. If you need the midpoint of edge `v1v2`, use `(m_vertex1 + m_vertex2) * 0.5f`.
---
## Examples
### Area and plane from existing API
```cpp
const auto a = tri.side_a_vector();
const auto b = tri.side_b_vector();
const auto n = b.cross(a); // unnormalized normal
float area = 0.5f * n.length(); // triangle area
// Plane equation n̂·(x - v2) = 0
auto nhat = n.length() > 0 ? n / n.length() : n;
float d = -nhat.dot(tri.m_vertex2);
```
### Project a point onto the triangles plane
```cpp
Vector3<float> p{0.3f, 1.0f, 0.7f};
auto n = tri.calculate_normal();
float t = n.dot(tri.m_vertex2 - p); // signed distance along normal
auto projected = p + n * t; // on-plane projection
```
---
## API summary (signatures)
```cpp
class Triangle final {
public:
constexpr Triangle();
constexpr Triangle(const Vector3<float>& v1,
const Vector3<float>& v2,
const Vector3<float>& v3);
Vector3<float> m_vertex1, m_vertex2, m_vertex3;
[[nodiscard]] constexpr Vector3<float> calculate_normal() const;
[[nodiscard]] float side_a_length() const;
[[nodiscard]] float side_b_length() const;
[[nodiscard]] constexpr Vector3<float> side_a_vector() const;
[[nodiscard]] constexpr Vector3<float> side_b_vector() const;
[[nodiscard]] constexpr float hypot() const;
[[nodiscard]] constexpr bool is_rectangular() const;
[[nodiscard]] constexpr Vector3<float> mid_point() const;
};
```
---
## Suggestions (optional improvements)
* If generic vectors are intended, store `Vector m_vertex*;` and constrain `Vector` to the required ops (`-`, `cross`, `normalized`, `distance_to`, `+`, `/`).
* Consider renaming `mid_point()``centroid()` to avoid ambiguity.
* Expose an `area()` helper and (optionally) a barycentric coordinate routine if you plan to use this in rasterization or intersection tests.
---
*Last updated: 31 Oct 2025*

View File

@@ -0,0 +1,300 @@
# `omath::Vector2` — 2D vector (C++20/23)
> Header: your projects `vector2.hpp`
> Namespace: `omath`
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
`Vector2<Type>` is a lightweight, POD-like 2D math type with arithmetic, geometry helpers, comparisons, hashing (for `float`), optional ImGui interop, and `std::formatter` support.
---
## Quick start
```cpp
#include "vector2.hpp"
using omath::Vector2;
using Vec2f = Vector2<float>;
Vec2f a{3.f, 4.f};
Vec2f b{1.f, 2.f};
auto d = a.distance_to(b); // ≈ 3.1623
auto dot = a.dot(b); // 11
auto len = a.length(); // 5
auto unit_a = a.normalized(); // (0.6, 0.8)
// Component-wise mutate
Vec2f c{2, 3};
c *= b; // c -> (2*1, 3*2) = (2, 6)
// Scalar ops (non-mutating + mutating)
auto scaled = a * 0.5f; // (1.5, 2)
a *= 2.f; // (6, 8)
// Ordering by length()
bool shorter = (b < a);
// Formatted printing
std::string s = std::format("a = {}", a); // "a = [6, 8]"
```
---
## Members
```cpp
Type x{0};
Type y{0};
```
---
## Constructors
```cpp
constexpr Vector2(); // (0,0)
constexpr Vector2(const Type& x, const Type& y) noexcept;
```
---
## Equality & ordering
```cpp
constexpr bool operator==(const Vector2&) const noexcept; // component-wise equality
constexpr bool operator!=(const Vector2&) const noexcept;
bool operator< (const Vector2&) const noexcept; // compares by length()
bool operator> (const Vector2&) const noexcept;
bool operator<=(const Vector2&) const noexcept;
bool operator>=(const Vector2&) const noexcept;
```
> **Note:** `<`, `>`, `<=`, `>=` order vectors by **magnitude** (not lexicographically).
---
## Arithmetic
### With another vector (component-wise, **mutating**)
```cpp
Vector2& operator+=(const Vector2&) noexcept;
Vector2& operator-=(const Vector2&) noexcept;
Vector2& operator*=(const Vector2&) noexcept; // Hadamard product (x*=x, y*=y)
Vector2& operator/=(const Vector2&) noexcept;
```
> Non-mutating `v * u` / `v / u` (vector × vector) are **not** provided.
> Use `v *= u` (mutating) or build a new vector explicitly.
### With a scalar
```cpp
Vector2& operator*=(const Type& v) noexcept;
Vector2& operator/=(const Type& v) noexcept;
Vector2& operator+=(const Type& v) noexcept;
Vector2& operator-=(const Type& v) noexcept;
constexpr Vector2 operator*(const Type& v) const noexcept;
constexpr Vector2 operator/(const Type& v) const noexcept;
```
### Binary (+/) with another vector (non-mutating)
```cpp
constexpr Vector2 operator+(const Vector2&) const noexcept;
constexpr Vector2 operator-(const Vector2&) const noexcept;
```
### Unary
```cpp
constexpr Vector2 operator-() const noexcept; // negation
```
---
## Geometry & helpers
```cpp
Type distance_to (const Vector2&) const noexcept; // sqrt of squared distance
constexpr Type distance_to_sqr(const Vector2&) const noexcept;
constexpr Type dot(const Vector2&) const noexcept;
#ifndef _MSC_VER
constexpr Type length() const noexcept; // uses std::hypot; constexpr on non-MSVC
constexpr Vector2 normalized() const noexcept; // returns *this if length==0
#else
Type length() const noexcept;
Vector2 normalized() const noexcept;
#endif
constexpr Type length_sqr() const noexcept; // x*x + y*y
Vector2& abs() noexcept; // component-wise absolute (constexpr-friendly impl)
constexpr Type sum() const noexcept; // x + y
constexpr std::tuple<Type, Type> as_tuple() const noexcept;
```
---
## ImGui integration (optional)
Define `OMATH_IMGUI_INTEGRATION` **before** including the header.
```cpp
#ifdef OMATH_IMGUI_INTEGRATION
constexpr ImVec2 to_im_vec2() const noexcept; // {float(x), float(y)}
static Vector2 from_im_vec2(const ImVec2&) noexcept;
#endif
```
---
## Hashing & formatting
* **Hash (for `Vector2<float>`)**
```cpp
template<> struct std::hash<omath::Vector2<float>> {
std::size_t operator()(const omath::Vector2<float>&) const noexcept;
};
```
Example:
```cpp
std::unordered_set<omath::Vector2<float>> set;
set.insert({1.f, 2.f});
```
* **`std::formatter`** (for any `Type`)
```cpp
// prints "[x, y]" for char / wchar_t / char8_t
template<class Type>
struct std::formatter<omath::Vector2<Type>>;
```
---
## Notes & invariants
* `Type` must be arithmetic (e.g., `float`, `double`, `int`, …).
* `normalized()` returns the input unchanged if `length() == 0`.
* `abs()` uses a constexpr-friendly implementation (not `std::abs`) to allow compile-time evaluation.
* On MSVC, `length()`/`normalized()` are not `constexpr` due to library constraints; theyre still `noexcept`.
---
## Examples
### Component-wise operations and scalar scaling
```cpp
omath::Vector2<float> u{2, 3}, v{4, 5};
u += v; // (6, 8)
u -= v; // (2, 3)
u *= v; // (8, 15) Hadamard product (mutates u)
auto w = v * 2.0f; // (8, 10) non-mutating scalar multiply
```
### Geometry helpers
```cpp
omath::Vector2<double> p{0.0, 0.0}, q{3.0, 4.0};
auto dsq = p.distance_to_sqr(q); // 25
auto d = p.distance_to(q); // 5
auto dot = p.dot(q); // 0
auto uq = q.normalized(); // (0.6, 0.8)
```
### Using as a key in unordered containers (`float`)
```cpp
std::unordered_map<omath::Vector2<float>, int> counts;
counts[{1.f, 2.f}] = 42;
```
### ImGui interop
```cpp
#define OMATH_IMGUI_INTEGRATION
#include "vector2.hpp"
omath::Vector2<float> v{10, 20};
ImVec2 iv = v.to_im_vec2();
v = omath::Vector2<float>::from_im_vec2(iv);
```
---
## API summary (signatures)
```cpp
// Constructors
constexpr Vector2();
constexpr Vector2(const Type& x, const Type& y) noexcept;
// Equality & ordering
constexpr bool operator==(const Vector2&) const noexcept;
constexpr bool operator!=(const Vector2&) const noexcept;
bool operator< (const Vector2&) const noexcept;
bool operator> (const Vector2&) const noexcept;
bool operator<=(const Vector2&) const noexcept;
bool operator>=(const Vector2&) const noexcept;
// Compound (vector/vector and scalar)
Vector2& operator+=(const Vector2&) noexcept;
Vector2& operator-=(const Vector2&) noexcept;
Vector2& operator*=(const Vector2&) noexcept;
Vector2& operator/=(const Vector2&) noexcept;
Vector2& operator*=(const Type&) noexcept;
Vector2& operator/=(const Type&) noexcept;
Vector2& operator+=(const Type&) noexcept;
Vector2& operator-=(const Type&) noexcept;
// Non-mutating arithmetic
constexpr Vector2 operator+(const Vector2&) const noexcept;
constexpr Vector2 operator-(const Vector2&) const noexcept;
constexpr Vector2 operator*(const Type&) const noexcept;
constexpr Vector2 operator/(const Type&) const noexcept;
constexpr Vector2 operator-() const noexcept;
// Geometry
Type distance_to(const Vector2&) const noexcept;
constexpr Type distance_to_sqr(const Vector2&) const noexcept;
constexpr Type dot(const Vector2&) const noexcept;
Type length() const noexcept; // constexpr on non-MSVC
Vector2 normalized() const noexcept; // constexpr on non-MSVC
constexpr Type length_sqr() const noexcept;
Vector2& abs() noexcept;
constexpr Type sum() const noexcept;
constexpr std::tuple<Type,Type> as_tuple() const noexcept;
// ImGui (optional)
#ifdef OMATH_IMGUI_INTEGRATION
constexpr ImVec2 to_im_vec2() const noexcept;
static Vector2 from_im_vec2(const ImVec2&) noexcept;
#endif
// Hash (float) and formatter are specialized in the header
```
---
## See Also
- [Vector3 Documentation](vector3.md) - 3D vector operations
- [Vector4 Documentation](vector4.md) - 4D vector operations
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
- [Tutorials](../tutorials.md) - Step-by-step examples
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,307 @@
# `omath::Vector3` — 3D vector (C++20/23)
> Header: your projects `vector3.hpp`
> Namespace: `omath`
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
> Depends on: `omath::Vector2<Type>` (base class), `omath::Angle` (for `angle_between`)
> C++: uses `std::expected` ⇒ **C++23** recommended (or a backport)
`Vector3<Type>` extends `Vector2<Type>` with a `z` component and 3D operations: arithmetic, geometry (dot, cross, distance), normalization, angle-between with robust error signaling, hashing (for `float`) and `std::formatter` support.
---
## Quick start
```cpp
#include "vector3.hpp"
using omath::Vector3;
using Vec3f = Vector3<float>;
Vec3f a{3, 4, 0};
Vec3f b{1, 2, 2};
auto d = a.distance_to(b); // Euclidean distance
auto dot = a.dot(b); // 3*1 + 4*2 + 0*2 = 11
auto cr = a.cross(b); // (8, -6, 2)
auto len = a.length(); // hypot(x,y,z)
auto unit = a.normalized(); // safe normalize (returns a if length==0)
if (auto ang = a.angle_between(b)) {
float deg = ang->as_degrees(); // [0, 180], clamped
} else {
// vectors have zero length -> no defined angle
}
```
---
## Data members
```cpp
Type x{0}; // inherited from Vector2<Type>
Type y{0}; // inherited from Vector2<Type>
Type z{0};
```
---
## Constructors
```cpp
constexpr Vector3() noexcept;
constexpr Vector3(const Type& x, const Type& y, const Type& z) noexcept;
```
---
## Equality & ordering
```cpp
constexpr bool operator==(const Vector3&) const noexcept; // component-wise
constexpr bool operator!=(const Vector3&) const noexcept;
bool operator< (const Vector3&) const noexcept; // compare by length()
bool operator> (const Vector3&) const noexcept;
bool operator<=(const Vector3&) const noexcept;
bool operator>=(const Vector3&) const noexcept;
```
> **Note:** Ordering uses **magnitude**, not lexicographic order.
---
## Arithmetic (mutating)
Component-wise with another vector:
```cpp
Vector3& operator+=(const Vector3&) noexcept;
Vector3& operator-=(const Vector3&) noexcept;
Vector3& operator*=(const Vector3&) noexcept; // Hadamard product
Vector3& operator/=(const Vector3&) noexcept;
```
With a scalar:
```cpp
Vector3& operator*=(const Type& v) noexcept;
Vector3& operator/=(const Type& v) noexcept;
Vector3& operator+=(const Type& v) noexcept;
Vector3& operator-=(const Type& v) noexcept;
```
---
## Arithmetic (non-mutating)
```cpp
constexpr Vector3 operator-() const noexcept;
constexpr Vector3 operator+(const Vector3&) const noexcept;
constexpr Vector3 operator-(const Vector3&) const noexcept;
constexpr Vector3 operator*(const Vector3&) const noexcept; // Hadamard
constexpr Vector3 operator/(const Vector3&) const noexcept; // Hadamard
constexpr Vector3 operator*(const Type& scalar) const noexcept;
constexpr Vector3 operator/(const Type& scalar) const noexcept;
```
---
## Geometry & helpers
```cpp
// Distances & lengths
Type distance_to(const Vector3&) const; // sqrt of squared distance
constexpr Type distance_to_sqr(const Vector3&) const noexcept;
#ifndef _MSC_VER
constexpr Type length() const; // hypot(x,y,z)
constexpr Type length_2d() const; // 2D length from base
constexpr Vector3 normalized() const; // returns *this if length==0
#else
Type length() const noexcept;
Type length_2d() const noexcept;
Vector3 normalized() const noexcept;
#endif
constexpr Type length_sqr() const noexcept;
// Products
constexpr Type dot(const Vector3&) const noexcept;
constexpr Vector3 cross(const Vector3&) const noexcept; // right-handed
// Sums & tuples
constexpr Type sum() const noexcept; // x + y + z
constexpr Type sum_2d() const noexcept; // x + y
constexpr auto as_tuple() const noexcept -> std::tuple<Type,Type,Type>;
// Utilities
Vector3& abs() noexcept; // component-wise absolute
```
---
## Angles & orthogonality
```cpp
enum class Vector3Error { IMPOSSIBLE_BETWEEN_ANGLE };
// Angle in degrees, clamped to [0,180]. Error if any vector has zero length.
std::expected<
omath::Angle<float, 0.f, 180.f, AngleFlags::Clamped>,
Vector3Error
> angle_between(const Vector3& other) const noexcept;
bool is_perpendicular(const Vector3& other) const noexcept; // true if angle == 90°
```
---
## Hashing & formatting
* **Hash (for `Vector3<float>`)**
```cpp
template<> struct std::hash<omath::Vector3<float>> {
std::size_t operator()(const omath::Vector3<float>&) const noexcept;
};
```
Example:
```cpp
std::unordered_map<omath::Vector3<float>, int> counts;
counts[{1.f, 2.f, 3.f}] = 7;
```
* **`std::formatter`** (all character types)
```cpp
template<class Type>
struct std::formatter<omath::Vector3<Type>>; // prints "[x, y, z]"
```
---
## Error handling
* `angle_between()` returns `std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE)` if either vector length is zero.
* Other operations are total for arithmetic `Type` (no throwing behavior in this class).
---
## Examples
### Cross product & perpendicular check
```cpp
omath::Vector3<float> x{1,0,0}, y{0,1,0};
auto z = x.cross(y); // (0,0,1)
bool perp = x.is_perpendicular(y); // true
```
### Safe normalization and angle
```cpp
omath::Vector3<float> u{0,0,0}, v{1,1,0};
auto nu = u.normalized(); // returns {0,0,0}
if (auto ang = u.angle_between(v)) {
// won't happen: u has zero length → error
} else {
// handle degenerate case
}
```
### Hadamard vs scalar multiply
```cpp
omath::Vector3<float> a{2,3,4}, b{5,6,7};
auto h = a * b; // (10, 18, 28) component-wise
auto s = a * 2.f; // (4, 6, 8) scalar
```
---
## API summary (signatures)
```cpp
// Ctors
constexpr Vector3() noexcept;
constexpr Vector3(const Type& x, const Type& y, const Type& z) noexcept;
// Equality & ordering
constexpr bool operator==(const Vector3&) const noexcept;
constexpr bool operator!=(const Vector3&) const noexcept;
bool operator< (const Vector3&) const noexcept;
bool operator> (const Vector3&) const noexcept;
bool operator<=(const Vector3&) const noexcept;
bool operator>=(const Vector3&) const noexcept;
// Mutating arithmetic
Vector3& operator+=(const Vector3&) noexcept;
Vector3& operator-=(const Vector3&) noexcept;
Vector3& operator*=(const Vector3&) noexcept;
Vector3& operator/=(const Vector3&) noexcept;
Vector3& operator*=(const Type&) noexcept;
Vector3& operator/=(const Type&) noexcept;
Vector3& operator+=(const Type&) noexcept;
Vector3& operator-=(const Type&) noexcept;
// Non-mutating arithmetic
constexpr Vector3 operator-() const noexcept;
constexpr Vector3 operator+(const Vector3&) const noexcept;
constexpr Vector3 operator-(const Vector3&) const noexcept;
constexpr Vector3 operator*(const Vector3&) const noexcept;
constexpr Vector3 operator/(const Vector3&) const noexcept;
constexpr Vector3 operator*(const Type&) const noexcept;
constexpr Vector3 operator/(const Type&) const noexcept;
// Geometry
Type distance_to(const Vector3&) const;
constexpr Type distance_to_sqr(const Vector3&) const noexcept;
#ifndef _MSC_VER
constexpr Type length() const;
constexpr Type length_2d() const;
constexpr Vector3 normalized() const;
#else
Type length() const noexcept;
Type length_2d() const noexcept;
Vector3 normalized() const noexcept;
#endif
constexpr Type length_sqr() const noexcept;
constexpr Type dot(const Vector3&) const noexcept;
constexpr Vector3 cross(const Vector3&) const noexcept;
Vector3& abs() noexcept;
constexpr Type sum() const noexcept;
constexpr Type sum_2d() const noexcept;
constexpr auto as_tuple() const noexcept -> std::tuple<Type,Type,Type>;
// Angles
std::expected<omath::Angle<float,0.f,180.f,AngleFlags::Clamped>, omath::Vector3Error>
angle_between(const Vector3&) const noexcept;
bool is_perpendicular(const Vector3&) const noexcept;
// Hash (float) and formatter specializations provided below the class
```
---
## Notes
* Inherits all public API of `Vector2<Type>` (including `x`, `y`, many operators, and helpers used internally).
* `normalized()` returns the original vector if its length is zero (no NaNs).
* `cross()` uses the standard right-handed definition.
* `length()`/`normalized()` are `constexpr` on non-MSVC; MSVC builds provide `noexcept` runtime versions.
---
## See Also
- [Vector2 Documentation](vector2.md) - 2D vector operations
- [Vector4 Documentation](vector4.md) - 4D vector operations
- [Angle Documentation](../trigonometry/angle.md) - Working with angles
- [Getting Started Guide](../getting_started.md) - Quick start with OMath
- [Tutorials](../tutorials.md) - Practical examples including vector math
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,253 @@
# `omath::Vector4` — 4D vector (C++20/23)
> Header: your projects `vector4.hpp`
> Namespace: `omath`
> Template: `template<class Type> requires std::is_arithmetic_v<Type>`
> Inherits: `omath::Vector3<Type>` (brings `x`, `y`, `z` and most scalar ops)
`Vector4<Type>` extends `Vector3<Type>` with a `w` component and 4D operations: component-wise arithmetic, scalar ops, dot/length helpers, clamping, hashing (for `float`) and `std::formatter` support. Optional ImGui interop is available behind a macro.
---
## Quick start
```cpp
#include "vector4.hpp"
using omath::Vector4;
using Vec4f = Vector4<float>;
Vec4f a{1, 2, 3, 1};
Vec4f b{4, 5, 6, 2};
// Component-wise & scalar ops
auto c = a + b; // (5, 7, 9, 3)
c *= 0.5f; // (2.5, 3.5, 4.5, 1.5)
auto h = a * b; // Hadamard: (4, 10, 18, 2)
// Dot / length
float d = a.dot(b); // 1*4 + 2*5 + 3*6 + 1*2 = 32
float L = a.length(); // sqrt(x²+y²+z²+w²)
// Clamp (x,y,z only; see notes)
Vec4f col{1.4f, -0.2f, 0.7f, 42.f};
col.clamp(0.f, 1.f); // -> (1, 0, 0.7, w unchanged)
```
---
## Data members
```cpp
// Inherited from Vector3<Type>:
Type x{0};
Type y{0};
Type z{0};
// Added in Vector4:
Type w{0};
```
---
## Constructors
```cpp
constexpr Vector4() noexcept; // (0,0,0,0)
constexpr Vector4(const Type& x, const Type& y,
const Type& z, const Type& w); // value-init
```
---
## Equality & ordering
```cpp
constexpr bool operator==(const Vector4&) const noexcept; // component-wise
constexpr bool operator!=(const Vector4&) const noexcept;
bool operator< (const Vector4&) const noexcept; // compare by length()
bool operator> (const Vector4&) const noexcept;
bool operator<=(const Vector4&) const noexcept;
bool operator>=(const Vector4&) const noexcept;
```
> **Note:** Ordering uses **magnitude** (Euclidean norm), not lexicographic order.
---
## Arithmetic (mutating)
With another vector (component-wise):
```cpp
Vector4& operator+=(const Vector4&) noexcept;
Vector4& operator-=(const Vector4&) noexcept;
Vector4& operator*=(const Vector4&) noexcept; // Hadamard
Vector4& operator/=(const Vector4&) noexcept;
```
With a scalar:
```cpp
Vector4& operator*=(const Type& v) noexcept;
Vector4& operator/=(const Type& v) noexcept;
// From base class (inherited):
Vector4& operator+=(const Type& v) noexcept; // adds v to x,y,z (and w via base? see notes)
Vector4& operator-=(const Type& v) noexcept;
```
---
## Arithmetic (non-mutating)
```cpp
constexpr Vector4 operator-() const noexcept;
constexpr Vector4 operator+(const Vector4&) const noexcept;
constexpr Vector4 operator-(const Vector4&) const noexcept;
constexpr Vector4 operator*(const Vector4&) const noexcept; // Hadamard
constexpr Vector4 operator/(const Vector4&) const noexcept; // Hadamard
constexpr Vector4 operator*(const Type& scalar) const noexcept;
constexpr Vector4 operator/(const Type& scalar) const noexcept;
```
---
## Geometry & helpers
```cpp
constexpr Type length_sqr() const noexcept; // x² + y² + z² + w²
Type length() const noexcept; // std::sqrt(length_sqr())
constexpr Type dot(const Vector4& rhs) const noexcept;
Vector4& abs() noexcept; // component-wise absolute
Vector4& clamp(const Type& min, const Type& max) noexcept;
// clamps x,y,z; leaves w unchanged (see notes)
constexpr Type sum() const noexcept; // x + y + z + w
```
---
## ImGui integration (optional)
Guarded by `OMATH_IMGUI_INTEGRATION`:
```cpp
#ifdef OMATH_IMGUI_INTEGRATION
constexpr ImVec4 to_im_vec4() const noexcept;
// NOTE: Provided signature returns Vector4<float> and (in current code) sets only x,y,z.
// See "Notes & caveats" for a corrected version you may prefer.
static Vector4<float> from_im_vec4(const ImVec4& other) noexcept;
#endif
```
---
## Hashing & formatting
* **Hash specialization** (only for `Vector4<float>`):
```cpp
template<> struct std::hash<omath::Vector4<float>> {
std::size_t operator()(const omath::Vector4<float>&) const noexcept;
};
```
Example:
```cpp
std::unordered_map<omath::Vector4<float>, int> counts;
counts[{1.f, 2.f, 3.f, 1.f}] = 7;
```
* **`std::formatter`** (for any `Type`, all character kinds):
```cpp
template<class Type>
struct std::formatter<omath::Vector4<Type>>; // -> "[x, y, z, w]"
```
---
## Notes & caveats (as implemented)
* `clamp(min,max)` **clamps only `x`, `y`, `z`** and **does not clamp `w`**. This may be intentional (e.g., when `w` is a homogeneous coordinate) — document your intent in your codebase.
If you want to clamp `w` too:
```cpp
w = std::clamp(w, min, max);
```
* **ImGui interop**:
* The header references `ImVec4` but does not include `<imgui.h>` itself. Ensure its included **before** this header whenever `OMATH_IMGUI_INTEGRATION` is defined.
* `from_im_vec4` currently returns `Vector4<float>` and (in the snippet shown) initializes **only x,y,z**. A more consistent version would be:
```cpp
#ifdef OMATH_IMGUI_INTEGRATION
static Vector4<Type> from_im_vec4(const ImVec4& v) noexcept {
return {static_cast<Type>(v.x), static_cast<Type>(v.y),
static_cast<Type>(v.z), static_cast<Type>(v.w)};
}
#endif
```
* Many scalar compound operators (`+= Type`, `-= Type`) are inherited from `Vector3<Type>`.
---
## API summary (signatures)
```cpp
// Ctors
constexpr Vector4() noexcept;
constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w);
// Equality & ordering
constexpr bool operator==(const Vector4&) const noexcept;
constexpr bool operator!=(const Vector4&) const noexcept;
bool operator< (const Vector4&) const noexcept;
bool operator> (const Vector4&) const noexcept;
bool operator<=(const Vector4&) const noexcept;
bool operator>=(const Vector4&) const noexcept;
// Mutating arithmetic
Vector4& operator+=(const Vector4&) noexcept;
Vector4& operator-=(const Vector4&) noexcept;
Vector4& operator*=(const Vector4&) noexcept;
Vector4& operator/=(const Vector4&) noexcept;
Vector4& operator*=(const Type&) noexcept;
Vector4& operator/=(const Type&) noexcept;
// (inherited) Vector4& operator+=(const Type&) noexcept;
// (inherited) Vector4& operator-=(const Type&) noexcept;
// Non-mutating arithmetic
constexpr Vector4 operator-() const noexcept;
constexpr Vector4 operator+(const Vector4&) const noexcept;
constexpr Vector4 operator-(const Vector4&) const noexcept;
constexpr Vector4 operator*(const Vector4&) const noexcept;
constexpr Vector4 operator/(const Vector4&) const noexcept;
constexpr Vector4 operator*(const Type&) const noexcept;
constexpr Vector4 operator/(const Type&) const noexcept;
// Geometry & helpers
constexpr Type length_sqr() const noexcept;
Type length() const noexcept;
constexpr Type dot(const Vector4&) const noexcept;
Vector4& abs() noexcept;
Vector4& clamp(const Type& min, const Type& max) noexcept;
constexpr Type sum() const noexcept;
// ImGui (optional)
#ifdef OMATH_IMGUI_INTEGRATION
constexpr ImVec4 to_im_vec4() const noexcept;
static Vector4<float> from_im_vec4(const ImVec4&) noexcept; // see note for preferred template version
#endif
// Hash (float) and formatter specializations provided below the class
```
---
*Last updated: 31 Oct 2025*

188
docs/pathfinding/a_star.md Normal file
View File

@@ -0,0 +1,188 @@
# `omath::pathfinding::Astar` — Pathfinding over a navigation mesh
> Header: your projects `pathfinding/astar.hpp`
> Namespace: `omath::pathfinding`
> Inputs: start/end as `Vector3<float>`, a `NavigationMesh`
> Output: ordered list of waypoints `std::vector<Vector3<float>>`
`Astar` exposes a single public function, `find_path`, that computes a path of 3D waypoints on a navigation mesh. Internally it reconstructs the result with `reconstruct_final_path` from a closed set keyed by `Vector3<float>`.
---
## API
```cpp
namespace omath::pathfinding {
struct PathNode; // holds per-node search data (see "Expected PathNode fields")
class Astar final {
public:
[[nodiscard]]
static std::vector<Vector3<float>>
find_path(const Vector3<float>& start,
const Vector3<float>& end,
const NavigationMesh& nav_mesh) noexcept;
private:
[[nodiscard]]
static std::vector<Vector3<float>>
reconstruct_final_path(
const std::unordered_map<Vector3<float>, PathNode>& closed_list,
const Vector3<float>& current) noexcept;
};
} // namespace omath::pathfinding
```
### Semantics
* Returns a **polyline** of 3D points from `start` to `end`.
* If no path exists, the function typically returns an **empty vector** (behavior depends on implementation details; keep this contract in unit tests).
---
## What `NavigationMesh` is expected to provide
The header doesnt constrain `NavigationMesh`, but for A* it commonly needs:
* **Neighborhood queries**: given a position or node key → iterable neighbors.
* **Traversal cost**: `g(u,v)` (often Euclidean distance or edge weight).
* **Heuristic**: `h(x,end)` (commonly straight-line distance on the mesh).
* **Projection / snap**: the ability to map `start`/`end` to valid nodes/points on the mesh (if they are off-mesh).
> If your `NavigationMesh` doesnt directly expose these, `Astar::find_path` likely does the adapter work (snapping to the nearest convex polygon/portal nodes and expanding across adjacency).
---
## Expected `PathNode` fields
Although not visible here, `PathNode` typically carries:
* `Vector3<float> parent;` — predecessor position or key for backtracking
* `float g;` — cost from `start`
* `float h;` — heuristic to `end`
* `float f;``g + h`
`reconstruct_final_path(closed_list, current)` walks `parent` links from `current` back to the start, **reverses** the chain, and returns the path.
---
## Heuristic & optimality
* Use an **admissible** heuristic (never overestimates true cost) to keep A* optimal.
The usual choice is **Euclidean distance** in 3D:
```cpp
h(x, goal) = (goal - x).length();
```
* For best performance, make it **consistent** (triangle inequality holds). Euclidean distance is consistent on standard navmeshes.
---
## Complexity
Let `V` be explored vertices (or portal nodes) and `E` the traversed edges.
* With a binary heap open list: **O(E log V)** time, **O(V)** memory.
* With a d-ary heap or pairing heap you may reduce practical constants.
---
## Typical usage
```cpp
#include "omath/pathfinding/astar.hpp"
#include "omath/pathfinding/navigation_mesh.hpp"
using omath::Vector3;
using omath::pathfinding::Astar;
NavigationMesh nav = /* ... load/build mesh ... */;
Vector3<float> start{2.5f, 0.0f, -1.0f};
Vector3<float> goal {40.0f, 0.0f, 12.0f};
auto path = Astar::find_path(start, goal, nav);
if (!path.empty()) {
// feed to your agent/renderer
for (const auto& p : path) {
// draw waypoint p or push to steering
}
} else {
// handle "no path" (e.g., unreachable or disconnected mesh)
}
```
---
## Notes & recommendations
* **Start/end snapping**: If `start` or `end` are outside the mesh, decide whether to snap to the nearest polygon/portal or fail early. Keep this behavior consistent and document it where `NavigationMesh` is defined.
* **Numerical stability**: Prefer squared distances when only comparing (`dist2`) to avoid unnecessary `sqrt`.
* **Tie-breaking**: When `f` ties are common (grid-like graphs), bias toward larger `g` or smaller `h` to reduce zig-zagging.
* **Smoothing**: A* returns a polyline that may hug polygon edges. Consider:
* **String pulling / Funnel algorithm** over the corridor of polygons to get a straightened path.
* **Raycast smoothing** (visibility checks) to remove redundant interior points.
* **Hashing `Vector3<float>`**: Your repo defines `std::hash<omath::Vector3<float>>`. Ensure equality/precision rules for using float keys are acceptable (or use discrete node IDs instead).
---
## Testing checklist
* Start/end on the **same polygon** → direct path of 2 points.
* **Disconnected components** → empty result.
* **Narrow corridors** → path stays inside.
* **Obstacles blocking** → no path.
* **Floating-point noise** → still reconstructs a valid chain from parents.
---
## Minimal pseudo-implementation outline (for reference)
```cpp
// Pseudocode only — matches the headers intent
std::vector<Vec3> find_path(start, goal, mesh) {
auto [snode, gnode] = mesh.snap_to_nodes(start, goal);
OpenSet open; // min-heap by f
std::unordered_map<Vec3, PathNode> closed;
open.push({snode, g=0, h=heuristic(snode, gnode)});
parents.clear();
while (!open.empty()) {
auto current = open.pop_min(); // node with lowest f
if (current.pos == gnode.pos)
return reconstruct_final_path(closed, current.pos);
for (auto [nbr, cost] : mesh.neighbors(current.pos)) {
float tentative_g = current.g + cost;
if (auto it = closed.find(nbr); it == closed.end() || tentative_g < it->second.g) {
closed[nbr] = { .parent = current.pos,
.g = tentative_g,
.h = heuristic(nbr, gnode.pos),
.f = tentative_g + heuristic(nbr, gnode.pos) };
open.push(closed[nbr]);
}
}
}
return {}; // no path
}
```
---
## FAQ
* **Why return `std::vector<Vector3<float>>` and not polygon IDs?**
Waypoints are directly usable by agents/steering and for rendering. If you also need the corridor (polygon chain), extend the API or `PathNode` to store it.
* **Does `find_path` modify the mesh?**
No; it should be a read-only search over `NavigationMesh`.
---
*Last updated: 31 Oct 2025*

View File

@@ -0,0 +1,113 @@
# `omath::pathfinding::NavigationMesh` — Lightweight vertex graph for A*
> Header: your projects `pathfinding/navigation_mesh.hpp`
> Namespace: `omath::pathfinding`
> Nodes: `Vector3<float>` (3D points)
> Storage: adjacency map `unordered_map<Vector3<float>, std::vector<Vector3<float>>>`
A minimal navigation mesh represented as a **vertex/edge graph**. Each vertex is a `Vector3<float>` and neighbors are stored in an adjacency list. Designed to pair with `Astar::find_path`.
---
## API
```cpp
class NavigationMesh final {
public:
// Nearest graph vertex to an arbitrary 3D point.
// On success -> closest vertex; on failure -> std::string error (e.g., empty mesh).
[[nodiscard]]
std::expected<Vector3<float>, std::string>
get_closest_vertex(const Vector3<float>& point) const noexcept;
// Read-only neighbor list for a vertex key.
// If vertex is absent, implementation should return an empty list (see notes).
[[nodiscard]]
const std::vector<Vector3<float>>&
get_neighbors(const Vector3<float>& vertex) const noexcept;
// True if the graph has no vertices/edges.
[[nodiscard]]
bool empty() const;
// Serialize/deserialize the graph (opaque binary).
[[nodiscard]] std::vector<uint8_t> serialize() const noexcept;
void deserialize(const std::vector<uint8_t>& raw) noexcept;
// Public adjacency (vertex -> neighbors)
std::unordered_map<Vector3<float>, std::vector<Vector3<float>>> m_vertex_map;
};
```
---
## Quick start
```cpp
using omath::Vector3;
using omath::pathfinding::NavigationMesh;
// Build a tiny mesh (triangle)
NavigationMesh nav;
nav.m_vertex_map[ {0,0,0} ] = { {1,0,0}, {0,0,1} };
nav.m_vertex_map[ {1,0,0} ] = { {0,0,0}, {0,0,1} };
nav.m_vertex_map[ {0,0,1} ] = { {0,0,0}, {1,0,0} };
// Query the closest node to an arbitrary point
auto q = nav.get_closest_vertex({0.3f, 0.0f, 0.2f});
if (q) {
const auto& v = *q;
const auto& nbrs = nav.get_neighbors(v);
(void)nbrs;
}
```
---
## Semantics & expectations
* **Nearest vertex**
`get_closest_vertex(p)` should scan known vertices and return the one with minimal Euclidean distance to `p`. If the mesh is empty, expect an error (`unexpected` with a message).
* **Neighbors**
`get_neighbors(v)` returns the adjacency list for `v`. If `v` is not present, a conventional behavior is to return a **reference to a static empty vector** (since the API is `noexcept` and returns by reference). Verify in your implementation.
* **Graph invariants** (recommended)
* Neighbor links are **symmetric** for undirected navigation (if `u` has `v`, then `v` has `u`).
* No self-loops unless explicitly desired.
* Vertices are unique keys; hashing uses `std::hash<Vector3<float>>` (be mindful of floating-point equality).
---
## Serialization
* `serialize()` → opaque, implementation-defined binary of the current `m_vertex_map`.
* `deserialize(raw)` → restores the internal map from `raw`.
Keep versioning in mind if you evolve the format (e.g., add a header/magic/version).
---
## Performance
Let `V = m_vertex_map.size()` and `E = Σ|neighbors(v)|`.
* `get_closest_vertex`: **O(V)** (linear scan) unless you back it with a spatial index (KD-tree, grid, etc.).
* `get_neighbors`: **O(1)** average (hash lookup).
* Memory: **O(V + E)**.
---
## Usage notes
* **Floating-point keys**: Using `Vector3<float>` as an unordered_map key relies on your `std::hash<omath::Vector3<float>>` and exact `operator==`. Avoid building meshes with numerically “close but not equal” duplicates; consider canonicalizing or using integer IDs if needed.
* **Pathfinding**: Pair with `Astar::find_path(start, end, nav)`; the A* heuristic can use straight-line distance between vertex positions.
---
## Minimal test ideas
* Empty mesh → `get_closest_vertex` returns error; `empty() == true`.
* Single vertex → nearest always that vertex; neighbors empty.
* Symmetric edges → `get_neighbors(a)` contains `b` and vice versa.
* Serialization round-trip preserves vertex/edge counts and neighbor lists.

View File

@@ -0,0 +1,161 @@
# `omath::projectile_prediction::ProjPredEngineAvx2` — AVX2-accelerated ballistic aim solver
> Header: your projects `projectile_prediction/proj_pred_engine_avx2.hpp`
> Namespace: `omath::projectile_prediction`
> Inherits: `ProjPredEngineInterface`
> Depends on: `Vector3<float>`, `Projectile`, `Target`
> CPU: Uses AVX2 when available; falls back to scalar elsewhere (fields are marked `[[maybe_unused]]` for non-x86/AVX2 builds).
This engine computes a **world-space aim point** (and implicitly the firing **yaw/pitch**) to intersect a moving target under a **constant gravity** model and **constant muzzle speed**. It typically scans candidate times of flight and solves for the elevation (`pitch`) that makes the vertical and horizontal kinematics meet at the same time.
---
## API
```cpp
class ProjPredEngineAvx2 final : public ProjPredEngineInterface {
public:
[[nodiscard]]
std::optional<Vector3<float>>
maybe_calculate_aim_point(const Projectile& projectile,
const Target& target) const override;
ProjPredEngineAvx2(float gravity_constant,
float simulation_time_step,
float maximum_simulation_time);
~ProjPredEngineAvx2() override = default;
private:
// Solve for pitch at a fixed time-of-flight t.
[[nodiscard]]
static std::optional<float>
calculate_pitch(const Vector3<float>& proj_origin,
const Vector3<float>& target_pos,
float bullet_gravity, float v0, float time);
// Tunables (may be unused on non-AVX2 builds)
[[maybe_unused]] const float m_gravity_constant; // |g| (e.g., 9.81)
[[maybe_unused]] const float m_simulation_time_step; // Δt (e.g., 1/240 s)
[[maybe_unused]] const float m_maximum_simulation_time; // Tmax (e.g., 3 s)
};
```
### Parameters (constructor)
* `gravity_constant` — magnitude of gravity (units consistent with your world, e.g., **m/s²**).
* `simulation_time_step` — Δt used to scan candidate intercept times.
* `maximum_simulation_time` — cap on time of flight; larger allows longer-range solutions but increases cost.
### Return (solver)
* `maybe_calculate_aim_point(...)`
* **`Vector3<float>`**: a world-space **aim point** yielding an intercept under the model.
* **`std::nullopt`**: no feasible solution (e.g., target receding too fast, out of range, or kinematics inconsistent).
---
## How it solves (expected flow)
1. **Predict target at time `t`** (constant-velocity model unless your `Target` carries more):
```
T(t) = target.position + target.velocity * t
```
2. **Horizontal/vertical kinematics at fixed `t`** with muzzle speed `v0` and gravity `g`:
* Let `Δ = T(t) - proj_origin`, `d = length(Δ.xz)`, `h = Δ.y`.
* Required initial components:
```
cosθ = d / (v0 * t)
sinθ = (h + 0.5 * g * t^2) / (v0 * t)
```
* If `cosθ` ∈ [1,1] and `sinθ` ∈ [1,1] and `sin²θ + cos²θ ≈ 1`, then
```
θ = atan2(sinθ, cosθ)
```
That is what `calculate_pitch(...)` returns on success.
3. **Yaw** is the azimuth toward `Δ.xz`.
4. **Pick the earliest feasible `t`** in `[Δt, Tmax]` (scanned in steps of `Δt`; AVX2 batches several `t` at once).
5. **Return the aim point.** Common choices:
* The **impact point** `T(t*)` (useful as a HUD marker), or
* A point along the **initial firing ray** at some convenient range using `(yaw, pitch)`; both are consistent—pick the convention your caller expects.
> The private `calculate_pitch(...)` matches step **2** and returns `nullopt` if the trigonometric constraints are violated for that `t`.
---
## AVX2 notes
* On x86/x64 with AVX2, candidate times `t` can be evaluated **8 at a time** using FMA (great for dense scans).
* On ARM/ARM64 (no AVX2), code falls back to scalar math; the `[[maybe_unused]]` members acknowledge compilation without SIMD.
---
## Usage example
```cpp
using namespace omath::projectile_prediction;
ProjPredEngineAvx2 solver(
/*gravity*/ 9.81f,
/*dt*/ 1.0f/240.0f,
/*Tmax*/ 3.0f
);
Projectile proj; // fill: origin, muzzle_speed, etc.
Target tgt; // fill: position, velocity
if (auto aim = solver.maybe_calculate_aim_point(proj, tgt)) {
// Aim your weapon at *aim and fire with muzzle speed proj.v0
// If you need yaw/pitch explicitly, replicate the pitch solve and azimuth.
} else {
// No solution (out of envelope) — pick a fallback
}
```
---
## Edge cases & failure modes
* **Zero or tiny `v0`** → no solution.
* **Target collinear & receding faster than `v0`** → no solution.
* **`t` constraints**: if viable solutions exist only beyond `Tmax`, youll get `nullopt`.
* **Geometric infeasibility** at a given `t` (e.g., `d > v0*t`) causes `calculate_pitch` to fail that sample.
* **Numerical tolerance**: check `sin²θ + cos²θ` against 1 with a small epsilon (e.g., `1e-3`).
---
## Performance & tuning
* Work is roughly `O(Nt)` where `Nt ≈ Tmax / Δt`.
* Smaller `Δt` → better accuracy, higher cost. With AVX2 you can afford smaller steps.
* If you frequently miss solutions **between** steps, consider:
* **Coarse-to-fine**: coarse scan, then local refine around the best `t`.
* **Newton on time**: root-find `‖horizontal‖ v0 t cosθ(t) = 0` shaped from the kinematics.
---
## Testing checklist
* **Stationary target** at same height → θ ≈ 0, aim point ≈ target.
* **Higher target** → positive pitch; **lower target** → negative pitch.
* **Perpendicular moving target** → feasible at moderate speeds.
* **Very fast receding target** → `nullopt`.
* **Boundary**: `d ≈ v0*Tmax` and `h` large → verify pass/fail around thresholds.
---
## See also
* `ProjPredEngineInterface` — base interface and general contract
* `Projectile`, `Target` — data carriers for solver inputs (speed, origin, position, velocity, etc.)
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,184 @@
# `omath::projectile_prediction::ProjPredEngineLegacy` — Legacy trait-based aim solver
> Header: `omath/projectile_prediction/proj_pred_engine_legacy.hpp`
> Namespace: `omath::projectile_prediction`
> Inherits: `ProjPredEngineInterface`
> Template param (default): `EngineTrait = source_engine::PredEngineTrait`
> Purpose: compute a world-space **aim point** to hit a (possibly moving) target using a **discrete time scan** and a **closed-form ballistic pitch** under constant gravity.
---
## Overview
`ProjPredEngineLegacy` is a portable, trait-driven projectile lead solver. At each simulation time step `t` it:
1. **Predicts target position** with `EngineTrait::predict_target_position(target, t, g)`.
2. **Computes launch pitch** via a gravity-aware closed form (or a direct angle if gravity is zero).
3. **Validates** that a projectile fired with that pitch (and direct yaw) actually reaches the predicted target within a **distance tolerance** at time `t`.
4. On success, **returns an aim point** computed by `EngineTrait::calc_viewpoint_from_angles(...)`.
If no time step yields a feasible solution up to `maximum_simulation_time`, returns `std::nullopt`.
---
## API
```cpp
template<class EngineTrait = source_engine::PredEngineTrait>
requires PredEngineConcept<EngineTrait>
class ProjPredEngineLegacy final : public ProjPredEngineInterface {
public:
ProjPredEngineLegacy(float gravity_constant,
float simulation_time_step,
float maximum_simulation_time,
float distance_tolerance);
[[nodiscard]]
std::optional<Vector3<float>>
maybe_calculate_aim_point(const Projectile& projectile,
const Target& target) const override;
private:
// Closed-form ballistic pitch solver (internal)
std::optional<float>
maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile,
const Vector3<float>& target_position) const noexcept;
bool is_projectile_reached_target(const Vector3<float>& target_position,
const Projectile& projectile,
float pitch, float time) const noexcept;
const float m_gravity_constant;
const float m_simulation_time_step;
const float m_maximum_simulation_time;
const float m_distance_tolerance;
};
```
### Constructor parameters
* `gravity_constant` — magnitude of gravity (e.g., `9.81f`), world units/s².
* `simulation_time_step` — Δt for the scan (e.g., `1/240.f`).
* `maximum_simulation_time` — search horizon in seconds.
* `distance_tolerance` — max allowed miss distance at time `t` to accept a solution.
---
## Trait requirements (`PredEngineConcept`)
Your `EngineTrait` must expose **noexcept** static methods with these signatures:
```cpp
Vector3<float> predict_projectile_position(const Projectile&, float pitch_deg, float yaw_deg,
float time, float gravity) noexcept;
Vector3<float> predict_target_position(const Target&, float time, float gravity) noexcept;
float calc_vector_2d_distance(const Vector3<float>& v) noexcept; // typically length in XZ plane
float get_vector_height_coordinate(const Vector3<float>& v) noexcept; // typically Y
Vector3<float> calc_viewpoint_from_angles(const Projectile&, Vector3<float> target,
std::optional<float> maybe_pitch_deg) noexcept;
float calc_direct_pitch_angle(const Vector3<float>& from, const Vector3<float>& to) noexcept;
float calc_direct_yaw_angle (const Vector3<float>& from, const Vector3<float>& to) noexcept;
```
> This design lets you adapt different game/physics conventions (axes, units, handedness) without changing the solver.
---
## Algorithm details
### Time scan
For `t = 0 .. maximum_simulation_time` in steps of `simulation_time_step`:
1. `T = EngineTrait::predict_target_position(target, t, g)`
2. `pitch = maybe_calculate_projectile_launch_pitch_angle(projectile, T)`
* If `std::nullopt`: continue
3. `yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin, T)`
4. `P = EngineTrait::predict_projectile_position(projectile, pitch, yaw, t, g)`
5. Accept if `|P - T| <= distance_tolerance`
6. Return `EngineTrait::calc_viewpoint_from_angles(projectile, T, pitch)`
### Closed-form pitch (gravity on)
Implements the classic ballistic formula (low-arc branch), where:
* `v` = muzzle speed,
* `g` = `gravity_constant * projectile.m_gravity_scale`,
* `x` = horizontal (2D) distance to target,
* `y` = vertical offset to target.
[
\theta ;=; \arctan!\left(\frac{v^{2} ;-; \sqrt{v^{4}-g!\left(gx^{2}+2yv^{2}\right)}}{gx}\right)
]
* If the **discriminant** ( v^{4}-g(gx^{2}+2yv^{2}) < 0 ) ⇒ **no real solution**.
* If `g == 0`, falls back to `EngineTrait::calc_direct_pitch_angle(...)`.
* Returns **degrees** (internally converts from radians).
---
## Usage example
```cpp
using namespace omath::projectile_prediction;
ProjPredEngineLegacy solver(
/*gravity*/ 9.81f,
/*dt*/ 1.f / 240.f,
/*Tmax*/ 3.0f,
/*tol*/ 0.05f
);
Projectile proj; // fill: m_origin, m_launch_speed, m_gravity_scale, etc.
Target tgt; // fill: position/velocity as required by your trait
if (auto aim = solver.maybe_calculate_aim_point(proj, tgt)) {
// Drive your turret/reticle toward *aim
} else {
// No feasible intercept in the given horizon
}
```
---
## Behavior & edge cases
* **Zero gravity or zero distance**: uses direct pitch toward the target.
* **Negative discriminant** in the pitch formula: returns `std::nullopt` for that time step.
* **Very small `x`** (horizontal distance): the formulas denominator `gx` approaches zero; your traits direct pitch helper provides a stable fallback.
* **Tolerance**: `distance_tolerance` controls acceptance; tighten for accuracy, loosen for robustness.
---
## Complexity & tuning
* Time: **O(T)** where ( T \approx \frac{\text{maximum_simulation_time}}{\text{simulation_time_step}} )
plus trait costs for prediction and angle math per step.
* Smaller `simulation_time_step` improves precision but increases runtime.
* If needed, do a **coarse-to-fine** search: coarse Δt scan, then refine around the best hit time.
---
## Testing checklist
* Stationary, level target → pitch ≈ 0 for short ranges; accepted within tolerance.
* Elevated/depressed targets → pitch positive/negative as expected.
* Receding fast target → unsolved within horizon ⇒ `nullopt`.
* Gravity scale = 0 → identical to straight-line solution.
* Near-horizon shots (large range, small arc) → discriminant near zero; verify stability.
---
## Notes
* All angles produced/consumed by the trait in this implementation are **degrees**.
* `calc_viewpoint_from_angles` defines what “aim point” means in your engine (e.g., a point along the initial ray or the predicted impact point). Keep this consistent with your HUD/reticle.
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,96 @@
# `omath::projectile_prediction::Projectile` — Projectile parameters for aim solvers
> Header: `omath/projectile_prediction/projectile.hpp`
> Namespace: `omath::projectile_prediction`
> Used by: `ProjPredEngineInterface` implementations (e.g., `ProjPredEngineLegacy`, `ProjPredEngineAvx2`)
`Projectile` is a tiny data holder that describes how a projectile is launched: **origin** (world position), **launch speed**, and a **gravity scale** (multiplier applied to the engines gravity constant).
---
## API
```cpp
namespace omath::projectile_prediction {
class Projectile final {
public:
Vector3<float> m_origin; // Launch position (world space)
float m_launch_speed{}; // Initial speed magnitude (units/sec)
float m_gravity_scale{}; // Multiplier for global gravity (dimensionless)
};
} // namespace omath::projectile_prediction
```
---
## Field semantics
* **`m_origin`**
World-space position where the projectile is spawned (e.g., muzzle or emitter point).
* **`m_launch_speed`**
Initial speed **magnitude** in your world units per second. Direction is determined by the solver (from yaw/pitch).
* Must be **non-negative**. Zero disables meaningful ballistic solutions.
* **`m_gravity_scale`**
Multiplies the engines gravity constant provided to the solver (e.g., `g = gravity_constant * m_gravity_scale`).
* Use `1.0f` for normal gravity, `0.0f` for no-drop projectiles, other values to simulate heavier/lighter rounds.
> Units must be consistent across your project (e.g., meters & seconds). If `gravity_constant = 9.81f`, then `m_launch_speed` is in m/s and positions are in meters.
---
## Typical usage
```cpp
using namespace omath::projectile_prediction;
Projectile proj;
proj.m_origin = { 0.0f, 1.6f, 0.0f }; // player eye / muzzle height
proj.m_launch_speed = 850.0f; // e.g., 850 m/s
proj.m_gravity_scale = 1.0f; // normal gravity
// With an aim solver:
auto aim = engine->maybe_calculate_aim_point(proj, target);
if (aim) {
// rotate/aim toward *aim and fire
}
```
---
## With gravity-aware solver (outline)
Engines typically compute the firing angles to reach a predicted target position:
* Horizontal distance `x` and vertical offset `y` are derived from `target - m_origin`.
* Gravity used is `g = gravity_constant * m_gravity_scale`.
* Launch direction has speed `m_launch_speed` and angles solved by the engine.
If `m_gravity_scale == 0`, engines usually fall back to straight-line (no-drop) solutions.
---
## Validation & tips
* Keep `m_launch_speed ≥ 0`. Negative values are nonsensical.
* If your weapon can vary muzzle speed (charge-up, attachments), update `m_launch_speed` per shot.
* For different ammo types (tracers, grenades), prefer tweaking **`m_gravity_scale`** (and possibly the engines gravity constant) to match observed arc.
---
## See also
* `ProjPredEngineInterface` — common interface for aim solvers
* `ProjPredEngineLegacy` — trait-based, time-stepped ballistic solver
* `ProjPredEngineAvx2` — AVX2-accelerated solver with fixed-time pitch solve
* `Target` — target state consumed by the solvers
* `Vector3<float>` — math type used for positions and directions
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,162 @@
# `omath::projectile_prediction::ProjPredEngineInterface` — Aim-point solver interface
> Header: your projects `projectile_prediction/proj_pred_engine_interface.hpp`
> Namespace: `omath::projectile_prediction`
> Depends on: `Vector3<float>`, `Projectile`, `Target`
> Purpose: **contract** for engines that compute a lead/aim point to hit a moving target.
---
## Overview
`ProjPredEngineInterface` defines a single pure-virtual method that attempts to compute the **world-space aim point** where a projectile should be launched to intersect a target under the engines physical model (e.g., constant projectile speed, gravity, drag, max flight time, etc.).
If a valid solution exists, the engine returns the 3D aim point. Otherwise, it returns `std::nullopt` (no feasible intercept).
---
## API
```cpp
namespace omath::projectile_prediction {
class ProjPredEngineInterface {
public:
[[nodiscard]]
virtual std::optional<Vector3<float>>
maybe_calculate_aim_point(const Projectile& projectile,
const Target& target) const = 0;
virtual ~ProjPredEngineInterface() = default;
};
} // namespace omath::projectile_prediction
```
### Semantics
* **Input**
* `Projectile` — engine-specific projectile properties (typical: muzzle speed, gravity vector, drag flag/coeff, max range / flight time).
* `Target` — target state (typical: position, velocity, possibly acceleration).
* **Output**
* `std::optional<Vector3<float>>`
* `value()` — world-space point to aim at **now** so that the projectile intersects the target under the model.
* `std::nullopt` — no solution (e.g., target outruns projectile, blocked by constraints, numerical failure).
* **No side effects**: method is `const` and should not modify inputs.
---
## Typical usage
```cpp
using namespace omath::projectile_prediction;
std::unique_ptr<ProjPredEngineInterface> engine = /* your implementation */;
Projectile proj = /* fill from weapon config */;
Target tgt = /* read from tracking system */;
if (auto aim = engine->maybe_calculate_aim_point(proj, tgt)) {
// Rotate/steer to (*aim)
} else {
// Fall back: no-lead, predictive UI, or do not fire
}
```
---
## Implementation guidance (for engine authors)
**Common models:**
1. **No gravity, constant speed**
Closed form intersect time `t` solves `‖p_t + v_t t p_0‖ = v_p t`.
Choose the smallest non-negative real root; aim point = `p_t + v_t t`.
2. **Gravity (constant g), constant speed**
Solve ballistics with vertical drop: either numerical (NewtonRaphson on time) or 2D elevation + azimuth decomposition. Ensure convergence caps and time bounds.
3. **Drag**
Typically requires numeric integration (e.g., RK4) wrapped in a root find on time-of-flight.
**Robustness tips:**
* **Feasibility checks:** return `nullopt` when:
* projectile speed ≤ 0; target too fast in receding direction; solution time outside `[0, t_max]`.
* **Bounds:** clamp search time to reasonable `[t_min, t_max]` (e.g., `[0, max_flight_time]` or by range).
* **Tolerances:** use epsilons for convergence (e.g., `|f(t)| < 1e-4`, `|Δt| < 1e-4 s`).
* **Determinism:** fix iteration counts or seeds if needed for replayability.
---
## Example: constant-speed, no-gravity intercept (closed form)
```cpp
// Solve ||p + v t|| = s t where p = target_pos - shooter_pos, v = target_vel, s = projectile_speed
// Quadratic: (v·v - s^2) t^2 + 2 (p·v) t + (p·p) = 0
inline std::optional<float> intercept_time_no_gravity(const Vector3<float>& p,
const Vector3<float>& v,
float s) {
const float a = v.dot(v) - s*s;
const float b = 2.f * p.dot(v);
const float c = p.dot(p);
if (std::abs(a) < 1e-6f) { // near linear
if (std::abs(b) < 1e-6f) return std::nullopt;
float t = -c / b;
return t >= 0.f ? std::optional{t} : std::nullopt;
}
const float disc = b*b - 4.f*a*c;
if (disc < 0.f) return std::nullopt;
const float sqrtD = std::sqrt(disc);
float t1 = (-b - sqrtD) / (2.f*a);
float t2 = (-b + sqrtD) / (2.f*a);
float t = (t1 >= 0.f ? t1 : t2);
return t >= 0.f ? std::optional{t} : std::nullopt;
}
```
Aim point (given shooter origin `S`, target pos `T`, vel `V`):
```
p = T - S
t* = intercept_time_no_gravity(p, V, speed)
aim = T + V * t*
```
Return `nullopt` if `t*` is absent.
---
## Testing checklist
* **Stationary target**: aim point equals target position when `s > 0`.
* **Target perpendicular motion**: lead equals lateral displacement `V⊥ * t`.
* **Receding too fast**: expect `nullopt`.
* **Gravity model**: verify arc solutions exist for short & long trajectories (if implemented).
* **Numerics**: convergence within max iterations; monotonic improvement of residuals.
---
## Notes
* This is an **interface** only; concrete engines (e.g., `SimpleNoGravityEngine`, `BallisticGravityEngine`) should document their assumptions (gravity, drag, wind, bounds) and units (meters, seconds).
* The coordinate system and handedness should be consistent with `Vector3<float>` and the rest of your math stack.
---
## See Also
- [Projectile Documentation](projectile.md) - Projectile properties
- [Target Documentation](target.md) - Target state representation
- [Legacy Implementation](proj_pred_engine_legacy.md) - Standard projectile prediction engine
- [AVX2 Implementation](proj_pred_engine_avx2.md) - Optimized AVX2 engine
- [Tutorials - Projectile Prediction](../tutorials.md#tutorial-3-projectile-prediction-aim-bot) - Complete aim-bot tutorial
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,70 @@
# `omath::projectile_prediction::Target` — Target state for aim solvers
> Header: `omath/projectile_prediction/target.hpp`
> Namespace: `omath::projectile_prediction`
> Used by: `ProjPredEngineInterface` implementations (e.g., Legacy/AVX2 engines)
A small POD-style container describing a targets **current pose** and **motion** for projectile lead/aim computations.
---
## API
```cpp
namespace omath::projectile_prediction {
class Target final {
public:
Vector3<float> m_origin; // Current world-space position of the target
Vector3<float> m_velocity; // World-space linear velocity (units/sec)
bool m_is_airborne{}; // Domain hint (e.g., ignore ground snapping)
};
} // namespace omath::projectile_prediction
```
---
## Field semantics
* **`m_origin`** — target position in world coordinates (same units as your `Vector3<float>` grid).
* **`m_velocity`** — instantaneous linear velocity. Solvers commonly assume **constant velocity** between “now” and impact unless your trait injects gravity/accel.
* **`m_is_airborne`** — optional hint for engine/trait logic (e.g., apply gravity to the target, skip ground friction/snap). Exact meaning is engine-dependent.
> Keep units consistent with your projectile model (e.g., meters & seconds). If projectiles use `g = 9.81 m/s²`, velocity should be in m/s and positions in meters.
---
## Typical usage
```cpp
using namespace omath::projectile_prediction;
Target tgt;
tgt.m_origin = { 42.0f, 1.8f, -7.5f };
tgt.m_velocity = { 3.0f, 0.0f, 0.0f }; // moving +X at 3 units/s
tgt.m_is_airborne = false;
// Feed into an aim solver with a Projectile
auto aim = engine->maybe_calculate_aim_point(projectile, tgt);
```
---
## Notes & tips
* If you track acceleration (e.g., gravity on ragdolls), your **EngineTrait** may derive it from `m_is_airborne` and world gravity; otherwise most solvers treat the targets motion as linear.
* For highly agile targets, refresh `m_origin`/`m_velocity` every tick and re-solve; dont reuse stale aim points.
* Precision: `Vector3<float>` is typically enough; if you need sub-millimeter accuracy over long ranges, consider double-precision internally in your trait.
---
## See also
* `Projectile` — shooter origin, muzzle speed, gravity scale
* `ProjPredEngineInterface` — common interface for aim solvers
* `ProjPredEngineLegacy`, `ProjPredEngineAvx2` — concrete solvers using this data
---
*Last updated: 1 Nov 2025*

270
docs/projection/camera.md Normal file
View File

@@ -0,0 +1,270 @@
# `omath::projection::Camera` — Generic, trait-driven camera with screen/world conversion
> Header: `omath/projection/camera.hpp` (this header)
> Namespace: `omath::projection`
> Template: `Camera<Mat4X4Type, ViewAnglesType, TraitClass>`
> Requires: `CameraEngineConcept<TraitClass, Mat4X4Type, ViewAnglesType>`
> Key features: **lazy view-projection caching**, world↔screen helpers, pluggable math via a **Trait**
---
## Overview
`Camera` is a small, zero-allocation camera wrapper. It delegates the math for **view**, **projection**, and **look-at** to a **Trait** (`TraitClass`), which lets you plug in different coordinate systems or conventions without changing the camera code. The class caches the **View×Projection** matrix and invalidates it when any parameter changes.
Alongside the camera, the header defines:
* `struct ViewPort { float m_width, m_height; float aspect_ratio() const; }`
* `using FieldOfView = Angle<float, 0.f, 180.f, AngleFlags::Clamped>;`
---
## Template & trait requirements
```cpp
template<class T, class MatType, class ViewAnglesType>
concept CameraEngineConcept = requires(
const omath::Vector3<float>& cam_origin,
const omath::Vector3<float>& look_at,
const ViewAnglesType& angles,
const omath::projection::FieldOfView& fov,
const omath::projection::ViewPort& viewport,
float znear, float zfar
) {
{ T::calc_look_at_angle(cam_origin, look_at) } noexcept -> std::same_as<ViewAnglesType>;
{ T::calc_view_matrix(angles, cam_origin) } noexcept -> std::same_as<MatType>;
{ T::calc_projection_matrix(fov, viewport, znear, zfar)}noexcept -> std::same_as<MatType>;
};
```
Your `Mat4X4Type` must behave like the librarys `Mat<4,4,...>` (supports `*`, `/`, `inverted()`, `.at(r,c)`, `.raw_array()`, and `static constexpr get_store_ordering()`).
---
## Quick start
```cpp
using Mat4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
// Example trait (sketch): assumes Y-up, column-major, left-handed
struct MyCamTrait {
static ViewAnglesType calc_look_at_angle(const Vector3<float>& eye,
const Vector3<float>& at) noexcept;
static Mat4 calc_view_matrix(const ViewAnglesType& ang,
const Vector3<float>& eye) noexcept;
static Mat4 calc_projection_matrix(const FieldOfView& fov,
const ViewPort& vp,
float znear, float zfar) noexcept;
};
using Camera = omath::projection::Camera<Mat4, MyViewAngles, MyCamTrait>;
omath::projection::ViewPort vp{1920, 1080};
omath::projection::FieldOfView fov = omath::angles::degrees(70.f);
Camera cam(/*position*/ {0,1.7f, -3},
/*angles*/ MyViewAngles{/*...*/},
/*viewport*/ vp, fov,
/*near*/ 0.1f,
/*far*/ 1000.f);
// Project world → screen (origin top-left)
auto s = cam.world_to_screen<Camera::ScreenStart::TOP_LEFT_CORNER>({1, 1, 0});
if (s) {
// s->x, s->y in pixels; s->z in NDC depth
}
```
---
## API
```cpp
enum class ScreenStart { TOP_LEFT_CORNER, BOTTOM_LEFT_CORNER };
class Camera final {
public:
~Camera() = default;
Camera(const Vector3<float>& position,
const ViewAnglesType& view_angles,
const ViewPort& view_port,
const FieldOfView& fov,
float near, float far) noexcept;
void look_at(const Vector3<float>& target); // recomputes view angles; invalidates cache
// Lazily computed and cached:
const Mat4X4Type& get_view_projection_matrix() const noexcept;
// Setters (all invalidate cached VP):
void set_field_of_view(const FieldOfView&) noexcept;
void set_near_plane(float) noexcept;
void set_far_plane(float) noexcept;
void set_view_angles(const ViewAnglesType&) noexcept;
void set_origin(const Vector3<float>&) noexcept;
void set_view_port(const ViewPort&) noexcept;
// Getters:
const FieldOfView& get_field_of_view() const noexcept;
const float& get_near_plane() const noexcept;
const float& get_far_plane() const noexcept;
const ViewAnglesType& get_view_angles() const noexcept;
const Vector3<float>& get_origin() const noexcept;
// World → Screen (pixels) via NDC; choose screen origin:
template<ScreenStart screen_start = ScreenStart::TOP_LEFT_CORNER>
std::expected<Vector3<float>, Error>
world_to_screen(const Vector3<float>& world) const noexcept;
// World → NDC (aka “viewport” in this code) ∈ [-1,1]^3
std::expected<Vector3<float>, Error>
world_to_view_port(const Vector3<float>& world) const noexcept;
// NDC → World (uses inverse VP)
std::expected<Vector3<float>, Error>
view_port_to_screen(const Vector3<float>& ndc) const noexcept;
// Screen (pixels) → World
std::expected<Vector3<float>, Error>
screen_to_world(const Vector3<float>& screen) const noexcept;
// 2D overload (z defaults to 1, i.e., far plane ray-end in NDC)
std::expected<Vector3<float>, Error>
screen_to_world(const Vector2<float>& screen) const noexcept;
protected:
ViewPort m_view_port{};
FieldOfView m_field_of_view;
mutable std::optional<Mat4X4Type> m_view_projection_matrix;
float m_far_plane_distance{};
float m_near_plane_distance{};
ViewAnglesType m_view_angles;
Vector3<float> m_origin;
private:
static constexpr bool is_ndc_out_of_bounds(const Mat4X4Type& ndc) noexcept;
Vector3<float> ndc_to_screen_position_from_top_left_corner(const Vector3<float>& ndc) const noexcept;
Vector3<float> ndc_to_screen_position_from_bottom_left_corner(const Vector3<float>& ndc) const noexcept;
Vector3<float> screen_to_ndc(const Vector3<float>& screen) const noexcept;
};
```
### Error handling
All conversions return `std::expected<..., Error>` with errors from `error_codes.hpp`, notably:
* `Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS` — clip space W=0 or NDC outside `[-1,1]`.
* `Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO` — non-invertible View×Projection matrix.
---
## Coordinate spaces & conversions
### World → NDC (`world_to_view_port`)
1. Build (or reuse cached) `VP = P * V` (projection * view).
2. Multiply by homogeneous column from the world point.
3. Reject if `w == 0`.
4. Perspective divide → NDC in `[-1,1]^3`.
5. Reject if any component is out of range.
Returns `{x_ndc, y_ndc, z_ndc}`.
### NDC → Screen (pixels)
The class offers two origins:
* **Top-left (default)**
```
x_px = (x_ndc + 1)/2 * width
y_px = ( -y_ndc/2 + 0.5) * height // flips Y
```
* **Bottom-left**
```
x_px = (x_ndc + 1)/2 * width
y_px = ( y_ndc/2 + 0.5) * height
```
### Screen (pixels) → NDC
```
x_ndc = screen_x / width * 2 - 1
y_ndc = 1 - screen_y / height * 2 // Top-left screen origin assumed here
z_ndc = screen_z // Caller-provided (e.g., 0..1 depth)
```
### NDC → World (`view_port_to_screen`)
Despite the method name, this function **unprojects** an NDC point back to world space:
1. Compute `VP^{-1}`; if not invertible → error.
2. Multiply by NDC (homogeneous 4D) and divide by `w`.
3. Return world point.
> Tip: to build a **world-space ray** from a screen pixel, unproject at `z=0` (near) and `z=1` (far).
---
## Caching & invalidation
* `get_view_projection_matrix()` computes `P*V` once and caches it.
* Any setter (`set_*`) or `look_at()` clears the cache (`m_view_projection_matrix = std::nullopt`).
---
## Notes & gotchas
* **Matrix order**: The camera multiplies `P * V`. Make sure your **Trait** matches this convention.
* **Store ordering**: The `Mat4X4Type::get_store_ordering()` is used when building homogeneous columns; ensure its consistent with your matrix implementation.
* **Naming quirk**: `view_port_to_screen()` returns a **world** point from **NDC** (its an unproject). Consider renaming to `ndc_to_world()` in your codebase for clarity.
* **FOV units**: `FieldOfView` uses the projects `Angle` type; pass degrees via `angles::degrees(...)`.
---
## Minimal trait sketch (column-major, left-handed)
```cpp
struct LHCTrait {
static MyAngles calc_look_at_angle(const Vector3<float>& eye,
const Vector3<float>& at) noexcept { /* ... */ }
static Mat4 calc_view_matrix(const MyAngles& ang,
const Vector3<float>& eye) noexcept {
// Build from forward/right/up and translation
}
static Mat4 calc_projection_matrix(const FieldOfView& fov,
const ViewPort& vp,
float zn, float zf) noexcept {
return omath::mat_perspective_left_handed<float, omath::MatStoreType::COLUMN_MAJOR>(
fov.as_degrees(), vp.aspect_ratio(), zn, zf
);
}
};
```
---
## Testing checklist
* World point centered in view projects to **screen center**.
* Points outside frustum → `WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS`.
* Inverting `VP` fails gracefully for singular matrices.
* `ScreenStart` switch flips Y as expected.
* Screen→World ray: unproject `(x,y,0)` and `(x,y,1)` and verify direction passes through the camera frustum.
---
## See Also
- [Engine-Specific Camera Traits](../engines/) - Camera implementations for different game engines
- [View Angles Documentation](../trigonometry/view_angles.md) - Understanding pitch/yaw/roll
- [Getting Started Guide](../getting_started.md) - Quick start with projection
- [Tutorials - World-to-Screen](../tutorials.md#tutorial-2-world-to-screen-projection) - Complete projection tutorial
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,79 @@
# `omath::projection::Error` — Error codes for world/screen projection
> Header: `omath/projection/error_codes.hpp`
> Namespace: `omath::projection`
> Type: `enum class Error : uint16_t`
These error codes are returned by camera/projection helpers (e.g., `Camera::world_to_screen`, `Camera::screen_to_world`) wrapped in `std::expected<..., Error>`. Use them to distinguish **clipping/visibility** problems from **matrix/math** failures.
---
## Enum values
```cpp
enum class Error : uint16_t {
WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS,
INV_VIEW_PROJ_MAT_DET_EQ_ZERO,
};
```
* **`WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS`**
The input point cannot produce a valid on-screen coordinate:
* Clip-space `w == 0` (point at/infinite or behind camera plane), or
* After projection, any NDC component is outside `[-1, 1]`.
* **`INV_VIEW_PROJ_MAT_DET_EQ_ZERO`**
The **View × Projection** matrix is not invertible (determinant ≈ 0).
Unprojection (`screen_to_world` / `view_port_to_screen`) requires an invertible matrix.
---
## Typical usage
```cpp
using omath::projection::Error;
auto pix = cam.world_to_screen(point);
if (!pix) {
switch (pix.error()) {
case Error::WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS:
// Cull label/marker; point is off-screen or behind camera.
break;
case Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO:
// Investigate camera/projection setup; near/far/FOV or trait bug.
break;
}
}
// Unproject a screen pixel (top-left origin) at depth 1.0
if (auto world = cam.screen_to_world({sx, sy, 1.0f})) {
// use *world
} else if (world.error() == Error::INV_VIEW_PROJ_MAT_DET_EQ_ZERO) {
// handle singular VP matrix
}
```
---
## When you might see these errors
* **Out-of-bounds**
* The world point lies outside the camera frustum.
* The point is behind the camera (clip `w <= 0`).
* Extremely large coordinates cause overflow and fail NDC bounds.
* **Non-invertible VP**
* Degenerate projection settings (e.g., `near == far`, zero FOV).
* Trait builds `P` or `V` incorrectly (wrong handedness/order).
* Numerical issues from nearly singular configurations.
---
## Recommendations
* Validate camera setup: `near > 0`, `far > near`, sensible FOV (e.g., 30°120°).
* For UI markers: treat `WORLD_POSITION_IS_OUT_OF_SCREEN_BOUNDS` as a simple **cull** signal.
* Log `INV_VIEW_PROJ_MAT_DET_EQ_ZERO` — it usually indicates a configuration or math bug worth fixing rather than hiding.

View File

@@ -0,0 +1,164 @@
# `omath::rev_eng::ExternalReverseEngineeredObject` — typed offsets over external memory
> Header: `omath/rev_eng/external_reverse_engineered_object.hpp`
> Namespace: `omath::rev_eng`
> Pattern: **CRTP-style wrapper** around a user-provided *ExternalMemoryManagementTrait* that actually reads/writes another process or devices memory.
A tiny base class for reverse-engineered objects that live **outside** your address space. You pass an absolute base address and a trait with `read_memory` / `write_memory`. Your derived types then expose strongly-typed getters/setters that delegate into the trait using **byte offsets**.
---
## Quick look
```cpp
template<class ExternalMemoryManagementTrait>
class ExternalReverseEngineeredObject {
public:
explicit ExternalReverseEngineeredObject(std::uintptr_t addr)
: m_object_address(addr) {}
protected:
template<class Type>
[[nodiscard]] Type get_by_offset(std::ptrdiff_t offset) const {
return ExternalMemoryManagementTrait::read_memory(m_object_address + offset);
}
template<class Type>
void set_by_offset(std::ptrdiff_t offset, const Type& value) const {
ExternalMemoryManagementTrait::write_memory(m_object_address + offset, value);
}
private:
std::uintptr_t m_object_address{};
};
```
---
## Trait requirements
Your `ExternalMemoryManagementTrait` must provide:
```cpp
// Reads sizeof(T) bytes starting at absolute address and returns T.
template<class T>
static T read_memory(std::uintptr_t absolute_address);
// Writes sizeof(T) bytes to absolute address.
template<class T>
static void write_memory(std::uintptr_t absolute_address, const T& value);
```
> Tip: If your implementation prefers returning `bool`/`expected<>` for writes, either:
>
> * make `write_memory` `void` and throw/log internally, or
> * adjust `set_by_offset` in your fork to surface the status.
### Common implementations
* **Windows**: wrap `ReadProcessMemory` / `WriteProcessMemory` with a stored `HANDLE` (often captured via a singleton or embedded in the trait).
* **Linux**: `/proc/<pid>/mem`, `process_vm_readv/writev`, `ptrace`.
* **Device/FPGA**: custom MMIO/driver APIs.
---
## How to use (derive and map fields)
Create a concrete type for your target structure and map known offsets:
```cpp
struct WinRPMTrait {
template<class T>
static T read_memory(std::uintptr_t addr) {
T out{};
SIZE_T n{};
if (!ReadProcessMemory(g_handle, reinterpret_cast<LPCVOID>(addr), &out, sizeof(T), &n) || n != sizeof(T))
throw std::runtime_error("RPM failed");
return out;
}
template<class T>
static void write_memory(std::uintptr_t addr, const T& val) {
SIZE_T n{};
if (!WriteProcessMemory(g_handle, reinterpret_cast<LPVOID>(addr), &val, sizeof(T), &n) || n != sizeof(T))
throw std::runtime_error("WPM failed");
}
};
class Player final : public omath::rev_eng::ExternalReverseEngineeredObject<WinRPMTrait> {
using Base = omath::rev_eng::ExternalReverseEngineeredObject<WinRPMTrait>;
public:
using Base::Base; // inherit ctor (takes base address)
// Offsets taken from your RE notes (in bytes)
Vector3<float> position() const { return get_by_offset<Vector3<float>>(0x30); }
void set_position(const Vector3<float>& p) const { set_by_offset(0x30, p); }
float health() const { return get_by_offset<float>(0x100); }
void set_health(float h) const { set_by_offset(0x100, h); }
};
```
Then:
```cpp
Player p{ /* base address you discovered */ 0x7FF6'1234'0000ull };
auto pos = p.position();
p.set_health(100.f);
```
---
## Design notes & constraints
* **Offsets are byte offsets** from the objects **base address** passed to the constructor.
* **Type safety is on you**: `Type` must match the external layout at that offset (endian, packing, alignment).
* **No lifetime tracking**: if the target object relocates/frees, you must update/recreate the wrapper with the new base address.
* **Thread safety**: the class itself is stateless; thread safety depends on your trait implementation.
* **Endianness**: assumes the host and target endianness agree, or your trait handles conversion.
* **Error handling**: this header doesnt prescribe it; adopt exceptions/expected/logging inside the trait.
---
## Best practices
* Centralize offsets in one place (constexprs or a small struct) and **comment source/version** (e.g., *game v1.2.3*).
* Wrap fragile multi-field writes in a trait-level **transaction** if your platform supports it.
* Validate pointers/guards (e.g., vtable signature, canary) before trusting offsets.
* Prefer **plain old data** (`struct` without virtuals) for `Type` to ensure trivial byte copies.
---
## Minimal trait sketch (POSIX, `process_vm_readv`)
```cpp
struct LinuxPvmTrait {
static pid_t pid;
template<class T> static T read_memory(std::uintptr_t addr) {
T out{};
iovec local{ &out, sizeof(out) }, remote{ reinterpret_cast<void*>(addr), sizeof(out) };
if (process_vm_readv(pid, &local, 1, &remote, 1, 0) != ssize_t(sizeof(out)))
throw std::runtime_error("pvm_readv failed");
return out;
}
template<class T> static void write_memory(std::uintptr_t addr, const T& val) {
iovec local{ const_cast<T*>(&val), sizeof(val) }, remote{ reinterpret_cast<void*>(addr), sizeof(val) };
if (process_vm_writev(pid, &local, 1, &remote, 1, 0) != ssize_t(sizeof(val)))
throw std::runtime_error("pvm_writev failed");
}
};
```
---
## Troubleshooting
* **Garbled values** → wrong offset/Type, or targets structure changed between versions.
* **Access denied** → missing privileges (admin/root), wrong process handle, or page protections.
* **Crashes in trait** → add bounds/sanity checks; many APIs fail on unmapped pages.
* **Writes “stick” only briefly** → the target may constantly overwrite (server authority / anti-cheat / replication).
---
*Last updated: 1 Nov 2025*

View File

@@ -0,0 +1,142 @@
# `omath::rev_eng::InternalReverseEngineeredObject` — raw in-process offset/VTABLE access
> Header: `omath/rev_eng/internal_reverse_engineered_object.hpp`
> Namespace: `omath::rev_eng`
> Purpose: Convenience base for **internal** (same-process) RE wrappers that:
>
> * read/write fields by **byte offset** from `this`
> * call **virtual methods** by **vtable index**
---
## At a glance
```cpp
class InternalReverseEngineeredObject {
protected:
template<class Type>
[[nodiscard]] Type& get_by_offset(std::ptrdiff_t offset);
template<class Type>
[[nodiscard]] const Type& get_by_offset(std::ptrdiff_t offset) const;
template<std::size_t id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list);
};
```
* `get_by_offset<T>(off)` — returns a **reference** to `T` located at `reinterpret_cast<uintptr_t>(this) + off`.
* `call_virtual_method<id, Ret>(args...)` — fetches the function pointer from `(*reinterpret_cast<void***>(this))[id]` and invokes it as a free function with implicit `this` passed explicitly.
On MSVC builds the function pointer type uses `__thiscall`; on non-MSVC it uses a plain function pointer taking `void*` as the first parameter (the typical Itanium ABI shape).
---
## Example: wrapping a reverse-engineered class
```cpp
struct Player : omath::rev_eng::InternalReverseEngineeredObject {
// Field offsets (document game/app version!)
static constexpr std::ptrdiff_t kHealth = 0x100;
static constexpr std::ptrdiff_t kPosition = 0x30;
// Accessors
float& health() { return get_by_offset<float>(kHealth); }
const float& health() const { return get_by_offset<float>(kHealth); }
Vector3<float>& position() { return get_by_offset<Vector3<float>>(kPosition); }
const Vector3<float>& position() const { return get_by_offset<Vector3<float>>(kPosition); }
// Virtuals (vtable indices discovered via RE)
int getTeam() { return call_virtual_method<27, int>(); }
void setArmor(float val) { call_virtual_method<42, void>(val); } // signature must match!
};
```
Usage:
```cpp
auto* p = /* pointer to live Player instance within the same process */;
p->health() = 100.f;
int team = p->getTeam();
```
---
## How `call_virtual_method` resolves the signature
```cpp
template<std::size_t id, class ReturnType>
ReturnType call_virtual_method(auto... arg_list) {
#ifdef _MSC_VER
using Fn = ReturnType(__thiscall*)(void*, decltype(arg_list)...);
#else
using Fn = ReturnType(*)(void*, decltype(arg_list)...);
#endif
return (*reinterpret_cast<Fn**>(this))[id](this, arg_list...);
}
```
* The **first parameter** is always `this` (`void*`).
* Remaining parameter types are deduced from the **actual arguments** (`decltype(arg_list)...`).
Ensure you pass arguments with the correct types (e.g., `int32_t` vs `int`, pointer/ref qualifiers), or define thin wrappers that cast to the exact signature you recovered.
> ⚠ On 32-bit MSVC the `__thiscall` distinction matters; on 64-bit MSVC its ignored (all member funcs use the common x64 calling convention).
---
## Safety notes (read before using!)
Working at this level is inherently unsafe; be deliberate:
1. **Correct offsets & alignment**
* `get_by_offset<T>` assumes `this + offset` is **properly aligned** for `T` and points to an object of type `T`.
* Wrong offsets or misalignment ⇒ **undefined behavior** (UB), crashes, silent corruption.
2. **Object layout assumptions**
* The vtable pointer is assumed to be at the **start of the most-derived subobject at `this`**.
* With **multiple/virtual inheritance**, the desired subobjects vptr may be at a non-zero offset. If so, adjust `this` to that subobject before calling, e.g.:
```cpp
auto* sub = reinterpret_cast<void*>(reinterpret_cast<std::uintptr_t>(this) + kSubobjectOffset);
// … then reinterpret sub instead of this inside a custom helper
```
3. **ABI & calling convention**
* Indices and signatures are **compiler/ABI-specific**. Recheck after updates or different builds (MSVC vs Clang/LLVM-MSVC vs MinGW).
4. **Strict aliasing**
* Reinterpreting memory as unrelated `T` can violate aliasing rules. Prefer **trivially copyable** PODs and exact original types where possible.
5. **Const-correctness**
* The `const` overload returns `const T&` but still reinterprets memory; do not write through it. Use the non-const overload to mutate.
6. **Thread safety**
* No synchronization is provided. Ensure the underlying object isnt concurrently mutated in incompatible ways.
---
## Tips & patterns
* **Centralize offsets** in `constexpr` with comments (`// game v1.2.3, sig XYZ`).
* **Guard reads**: if you have a canary or RTTI/vtable hash, check it before relying on offsets.
* **Prefer accessors** returning references**:** lets you both read and write with natural syntax.
* **Wrap tricky virtuals**: if a method takes complex/reference params, wrap `call_virtual_method` in a strongly typed member that casts exactly as needed.
---
## Troubleshooting
* **Crash on virtual call** → wrong index or wrong `this` (subobject), or mismatched signature (args/ret or calling conv).
* **Weird field values** → wrong offset, wrong type size/packing, stale layout after an update.
* **Only in 32-bit** → double-check `__thiscall` and parameter passing (register vs stack).
---
*Last updated: 1 Nov 2025*

4
docs/styles/center.css Normal file
View File

@@ -0,0 +1,4 @@
/* docs/css/custom.css */
.center-text {
text-align: center;
}

View File

@@ -0,0 +1,11 @@
/* Widen the navbar container */
.navbar .container {
max-width: 100%; /* adjust to your target width */
width: 95%;
}
/* Tighter spacing between navbar items */
.navbar-nav > li > a {
padding-left: 8px;
padding-right: 8px;
}

65
docs/styles/links.css Normal file
View File

@@ -0,0 +1,65 @@
/* Normal links */
a {
color: orange;
}
/* On hover/focus */
a:hover,
a:focus {
color: #ff9900; /* a slightly different orange, optional */
}
/* Navbar background */
.navbar,
.navbar-default,
.navbar-inverse {
background-color: #a26228 !important; /* your orange */
border-color: #ff6600 !important;
}
/* Navbar brand + links */
.navbar .navbar-brand,
.navbar .navbar-nav > li > a {
color: #ffffff !important;
}
/* Active and hover states */
.navbar .navbar-nav > .active > a,
.navbar .navbar-nav > .active > a:focus,
.navbar .navbar-nav > .active > a:hover,
.navbar .navbar-nav > li > a:hover,
.navbar .navbar-nav > li > a:focus {
color: #ffffff !important;
}
/* === DROPDOWN MENU BACKGROUND === */
.navbar .dropdown-menu {
border-color: #ff6600 !important;
}
/* Caret icon (the little triangle) */
.navbar .dropdown-toggle .caret {
border-top-color: #ffffff !important;
border-bottom-color: #ffffff !important;
}
/* === BOOTSTRAP 3 STYLE ITEMS (mkdocs + bootswatch darkly often use this) === */
.navbar .dropdown-menu > li > a {
color: #ffffff !important;
}
.navbar .dropdown-menu > li > a:hover,
.navbar .dropdown-menu > li > a:focus {
background-color: #e65c00 !important; /* darker orange on hover */
color: #ffffff !important;
}
/* === BOOTSTRAP 4+ STYLE ITEMS (if your theme uses .dropdown-item) === */
.navbar .dropdown-item {
color: #ffffff !important;
}
.navbar .dropdown-item:hover,
.navbar .dropdown-item:focus {
background-color: #e65c00 !important;
color: #ffffff !important;
}

165
docs/trigonometry/angle.md Normal file
View File

@@ -0,0 +1,165 @@
# `omath::Angle` — templated angle with normalize/clamper + trig
> Header: `omath/trigonometry/angle.hpp`
> Namespace: `omath`
> Template: `Angle<Type = float, min = 0, max = 360, flags = AngleFlags::Normalized>`
> Requires: `std::is_arithmetic_v<Type>`
> Formatters: `std::formatter` for `char`, `wchar_t`, `char8_t` → `"{}deg"`
---
## Overview
`Angle` is a tiny value-type that stores an angle in **degrees** and automatically **normalizes** or **clamps** it into a compile-time range. It exposes conversions to/from radians, common trig (`sin/cos/tan/cot`), arithmetic with wrap/clamp semantics, and lightweight formatting.
Two behaviors via `AngleFlags`:
* `AngleFlags::Normalized` (default): values are wrapped into `[min, max]` using `angles::wrap_angle`.
* `AngleFlags::Clamped`: values are clamped to `[min, max]` using `std::clamp`.
---
## API
```cpp
namespace omath {
enum class AngleFlags { Normalized = 0, Clamped = 1 };
template<class Type = float, Type min = Type(0), Type max = Type(360),
AngleFlags flags = AngleFlags::Normalized>
requires std::is_arithmetic_v<Type>
class Angle {
public:
// Construction
static constexpr Angle from_degrees(const Type& deg) noexcept;
static constexpr Angle from_radians(const Type& rad) noexcept;
constexpr Angle() noexcept; // 0 deg, adjusted by flags/range
// Accessors / conversions (degrees stored internally)
constexpr const Type& operator*() const noexcept; // raw degrees reference
constexpr Type as_degrees() const noexcept;
constexpr Type as_radians() const noexcept;
// Trig (computed from radians)
Type sin() const noexcept;
Type cos() const noexcept;
Type tan() const noexcept;
Type atan() const noexcept; // atan(as_radians()) (rarely used)
Type cot() const noexcept; // cos()/sin() (watch sin≈0)
// Arithmetic (wraps or clamps per flags and [min,max])
constexpr Angle& operator+=(const Angle&) noexcept;
constexpr Angle& operator-=(const Angle&) noexcept;
constexpr Angle operator+(const Angle&) noexcept;
constexpr Angle operator-(const Angle&) noexcept;
constexpr Angle operator-() const noexcept;
// Comparison (partial ordering)
constexpr std::partial_ordering operator<=>(const Angle&) const noexcept = default;
};
} // namespace omath
```
### Formatting
```cpp
std::format("{}", Angle<float>::from_degrees(45)); // "45deg"
```
Formatters exist for `char`, `wchar_t`, and `char8_t`.
---
## Usage examples
### Defaults (0360, normalized)
```cpp
using Deg = omath::Angle<>; // float, [0,360], Normalized
auto a = Deg::from_degrees(370); // -> 10deg
auto b = Deg::from_radians(omath::angles::pi); // -> 180deg
a += Deg::from_degrees(355); // 10 + 355 -> 365 -> wraps -> 5deg
float s = a.sin(); // sin(5°)
```
### Clamped range
```cpp
using Fov = omath::Angle<float, 1.f, 179.f, omath::AngleFlags::Clamped>;
auto fov = Fov::from_degrees(200.f); // -> 179deg (clamped)
```
### Signed, normalized range
```cpp
using SignedDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
auto x = SignedDeg::from_degrees(190.f); // -> -170deg
auto y = SignedDeg::from_degrees(-200.f); // -> 160deg
auto z = x + y; // -170 + 160 = -10deg (wrapped if needed)
```
### Get/set raw degrees
```cpp
auto yaw = SignedDeg::from_degrees(-45.f);
float deg = *yaw; // same as yaw.as_degrees()
```
---
## Semantics & notes
* **Storage & units:** Internally stores **degrees** (`Type m_angle`). `as_radians()`/`from_radians()` use the project helpers in `omath::angles`.
* **Arithmetic honors policy:** `operator+=`/`-=` and the binary `+`/`-` apply **wrap** or **clamp** in `[min,max]`, mirroring construction behavior.
* **`atan()`**: returns `std::atan(as_radians())` (the arctangent of the *radian value*). This is mathematically unusual for an angle type and is rarely useful; prefer `tan()`/`atan2` in client code when solving geometry problems.
* **`cot()` / `tan()` singularities:** Near multiples where `sin() ≈ 0` or `cos() ≈ 0`, results blow up. Guard in your usage if inputs can approach these points.
* **Comparison:** `operator<=>` is defaulted. With normalization, distinct representatives can compare as expected (e.g., `-180` vs `180` in signed ranges are distinct endpoints).
* **No implicit numeric conversion:** Theres **no `operator Type()`**. Use `as_degrees()`/`as_radians()` (or `*angle`) explicitly—this intentional friction avoids unit mistakes.
---
## Customization patterns
* **Radians workflow:** Keep angles in degrees internally but wrap helper creators:
```cpp
inline Deg degf(float d) { return Deg::from_degrees(d); }
inline Deg radf(float r) { return Deg::from_radians(r); }
```
* **Compile-time policy:** Pick ranges/flags at the type level to enforce invariants (e.g., `YawDeg = Angle<float,-180,180,Normalized>`; `FovDeg = Angle<float,1,179,Clamped>`).
---
## Pitfalls & gotchas
* Ensure `min < max` at compile time for meaningful wrap/clamp behavior.
* For normalized signed ranges, decide whether your `wrap_angle(min,max)` treats endpoints half-open (e.g., `[-180,180)`) to avoid duplicate representations; the formatter will print the stored value verbatim.
* If you need **sum of many angles**, accumulating in radians then converting back can improve numeric stability at extreme values.
---
## Minimal tests
```cpp
using A = omath::Angle<>;
REQUIRE(A::from_degrees(360).as_degrees() == 0.f);
REQUIRE(A::from_degrees(-1).as_degrees() == 359.f);
using S = omath::Angle<float,-180.f,180.f, omath::AngleFlags::Normalized>;
REQUIRE(S::from_degrees( 181).as_degrees() == -179.f);
REQUIRE(S::from_degrees(-181).as_degrees() == 179.f);
using C = omath::Angle<float, 10.f, 20.f, omath::AngleFlags::Clamped>;
REQUIRE(C::from_degrees(5).as_degrees() == 10.f);
REQUIRE(C::from_degrees(25).as_degrees() == 20.f);
```
---
*Last updated: 1 Nov 2025*

107
docs/trigonometry/angles.md Normal file
View File

@@ -0,0 +1,107 @@
# `omath::angles` — angle conversions, FOV helpers, and wrapping
> Header: `omath/trigonometry/angles.hpp`
> Namespace: `omath::angles`
> All functions are `[[nodiscard]]` and `noexcept` where applicable.
A small set of constexpr-friendly utilities for converting between degrees/radians, converting horizontal/vertical field of view, and wrapping angles into a closed interval.
---
## API
```cpp
// Degrees ↔ Radians (Type must be floating-point)
template<class Type>
requires std::is_floating_point_v<Type>
constexpr Type radians_to_degrees(const Type& radians) noexcept;
template<class Type>
requires std::is_floating_point_v<Type>
constexpr Type degrees_to_radians(const Type& degrees) noexcept;
// FOV conversion (inputs/outputs in degrees, aspect = width/height)
template<class Type>
requires std::is_floating_point_v<Type>
Type horizontal_fov_to_vertical(const Type& horizontal_fov, const Type& aspect) noexcept;
template<class Type>
requires std::is_floating_point_v<Type>
Type vertical_fov_to_horizontal(const Type& vertical_fov, const Type& aspect) noexcept;
// Wrap angle into [min, max] (any arithmetic type)
template<class Type>
requires std::is_arithmetic_v<Type>
Type wrap_angle(const Type& angle, const Type& min, const Type& max) noexcept;
```
---
## Usage
### Degrees ↔ Radians
```cpp
float rad = omath::angles::degrees_to_radians(180.0f); // π
double deg = omath::angles::radians_to_degrees(std::numbers::pi); // 180
```
### Horizontal ↔ Vertical FOV
* `aspect` = **width / height**.
* Inputs/outputs are **degrees**.
```cpp
float hdeg = 90.0f;
float aspect = 16.0f / 9.0f;
float vdeg = omath::angles::horizontal_fov_to_vertical(hdeg, aspect); // ~58.0°
float hdeg2 = omath::angles::vertical_fov_to_horizontal(vdeg, aspect); // ≈ 90.0°
```
Formulas (in radians):
* `v = 2 * atan( tan(h/2) / aspect )`
* `h = 2 * atan( tan(v/2) * aspect )`
### Wrapping angles (or any periodic value)
Wrap any numeric `angle` into `[min, max]`:
```cpp
// Wrap degrees into [0, 360]
float a = omath::angles::wrap_angle( 370.0f, 0.0f, 360.0f); // 10
float b = omath::angles::wrap_angle( -15.0f, 0.0f, 360.0f); // 345
// Signed range [-180,180]
float c = omath::angles::wrap_angle( 200.0f, -180.0f, 180.0f); // -160
```
---
## Notes & edge cases
* **Type requirements**
* Converters & FOV helpers require **floating-point** `Type`.
* `wrap_angle` accepts any arithmetic `Type` (floats or integers).
* **Aspect ratio** must be **positive** and finite. For `aspect == 0` the FOV helpers are undefined.
* **Units**: FOV functions accept/return **degrees** but compute internally in radians.
* **Wrapping interval**: Behavior assumes `max > min`. The result lies in the **closed interval** `[min, max]` with modulo arithmetic; if you need half-open behavior (e.g., `[min,max)`), adjust your range or post-process endpoint cases.
* **constexpr**: Converters are `constexpr`; FOV helpers are runtime constexpr-compatible except for `std::atan/std::tan` constraints on some standard libraries.
---
## Quick tests
```cpp
using namespace omath::angles;
static_assert(degrees_to_radians(180.0) == std::numbers::pi);
static_assert(radians_to_degrees(std::numbers::pi_v<float>) == 180.0f);
float v = horizontal_fov_to_vertical(90.0f, 16.0f/9.0f);
float h = vertical_fov_to_horizontal(v, 16.0f/9.0f);
assert(std::abs(h - 90.0f) < 1e-5f);
assert(wrap_angle(360.0f, 0.0f, 360.0f) == 0.0f || wrap_angle(360.0f, 0.0f, 360.0f) == 360.0f);
```

View File

@@ -0,0 +1,87 @@
# `omath::ViewAngles` — tiny POD for pitch/yaw/roll
> Header: your projects `view_angles.hpp`
> Namespace: `omath`
> Kind: **aggregate struct** (POD), no methods, no allocation
A minimal container for Euler angles. You choose the types for each component (e.g., raw `float` or the strong `omath::Angle<>` type), and plug it into systems like `projection::Camera`.
---
## API
```cpp
namespace omath {
template<class PitchType, class YawType, class RollType>
struct ViewAngles {
PitchType pitch;
YawType yaw;
RollType roll;
};
}
```
* Aggregate: supports brace-init, aggregate copying, and `constexpr` usage when the component types do.
* Semantics (units/handedness/ranges) are **entirely defined by your chosen types**.
---
## Common aliases
```cpp
// Simple, raw degrees as floats (be careful with wrapping!)
using ViewAnglesF = omath::ViewAngles<float, float, float>;
// Safer, policy-based angles (recommended)
using PitchDeg = omath::Angle<float, -89.f, 89.f, omath::AngleFlags::Clamped>;
using YawDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
using RollDeg = omath::Angle<float, -180.f, 180.f, omath::AngleFlags::Normalized>;
using ViewAnglesDeg = omath::ViewAngles<PitchDeg, YawDeg, RollDeg>;
```
---
## Examples
### Basic construction
```cpp
omath::ViewAngles<float,float,float> a{ 10.f, 45.f, 0.f }; // pitch, yaw, roll in degrees
```
### With `omath::Angle<>` (automatic wrap/clamper)
```cpp
ViewAnglesDeg v{
PitchDeg::from_degrees( 95.f), // -> 89deg (clamped)
YawDeg::from_degrees (-190.f), // -> 170deg (wrapped)
RollDeg::from_degrees ( 30.f)
};
```
### Using with `projection::Camera`
```cpp
using Mat4 = omath::Mat<4,4,float, omath::MatStoreType::COLUMN_MAJOR>;
using Cam = omath::projection::Camera<Mat4, ViewAnglesDeg, MyCameraTrait>;
omath::projection::ViewPort vp{1920,1080};
auto fov = omath::angles::degrees_to_radians(70.f); // or your Angle type
Cam cam(/*position*/ {0,1.7f,-3},
/*angles*/ ViewAnglesDeg{ PitchDeg::from_degrees(0),
YawDeg::from_degrees(0),
RollDeg::from_degrees(0) },
/*viewport*/ vp,
/*fov*/ omath::Angle<float,0.f,180.f,omath::AngleFlags::Clamped>::from_degrees(70.f),
/*near*/ 0.1f,
/*far*/ 1000.f);
```
---
## Notes & tips
* **Ranges/units**: pick types that encode your policy (e.g., signed yaw in `[-180,180]`, pitch clamped to avoid gimbal flips).
* **Handedness & order**: this struct doesnt impose rotation order. Your math/trait layer (e.g., `MyCameraTrait`) must define how `(pitch, yaw, roll)` map to a view matrix (common orders: ZYX or XYZ).
* **Zero-cost**: with plain `float`s this is as cheap as three scalars; with `Angle<>` you gain safety at the cost of tiny wrap/clamp logic on construction/arithmetic.

525
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,525 @@
# Troubleshooting
Solutions to common problems when using OMath.
---
## Build & Compilation Issues
### Error: C++20 features not available
**Problem:** Compiler doesn't support C++20.
**Solution:**
Upgrade your compiler:
- **GCC**: Version 10 or newer
- **Clang**: Version 11 or newer
- **MSVC**: Visual Studio 2019 16.10 or newer
Set C++20 in CMakeLists.txt:
```cmake
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
```
### Error: `std::expected` not found
**Problem:** Using C++20 without C++23's `std::expected`.
**Solutions:**
1. **Upgrade to C++23** (recommended):
```cmake
set(CMAKE_CXX_STANDARD 23)
```
2. **Use a backport library**:
```cmake
find_package(tl-expected CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE tl::expected)
```
### Error: `omath/omath.hpp` not found
**Problem:** OMath not installed or not in include path.
**Solution:**
Check installation:
```bash
# vcpkg
vcpkg list | grep omath
# Check if files exist
ls /path/to/vcpkg/installed/x64-linux/include/omath
```
In CMakeLists.txt:
```cmake
find_package(omath CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE omath::omath)
```
### Linker errors with AVX2 engine
**Problem:** Undefined references to AVX2 functions.
**Solution:**
Enable AVX2 in your build:
```cmake
if(MSVC)
target_compile_options(your_target PRIVATE /arch:AVX2)
else()
target_compile_options(your_target PRIVATE -mavx2)
endif()
```
Or use the legacy engine instead:
```cpp
// Use this instead of ProjPredEngineAVX2
ProjPredEngineLegacy engine;
```
---
## Runtime Issues
### `world_to_screen()` always returns `nullopt`
**Common causes:**
1. **Point behind camera**
```cpp
// Point is behind the camera
Vector3<float> behind = camera_pos - Vector3<float>{0, 0, 100};
auto result = camera.world_to_screen(behind); // Returns nullopt
```
**Fix:** Only project points in front of camera. Check Z-coordinate in view space.
2. **Invalid near/far planes**
```cpp
// Bad: near >= far
Camera camera(pos, angles, viewport, fov, 100.0f, 1.0f);
// Good: near < far
Camera camera(pos, angles, viewport, fov, 0.1f, 1000.0f);
```
3. **Invalid FOV**
```cpp
// Bad: FOV out of range
auto fov = FieldOfView::from_degrees(0.0f); // Too small
auto fov = FieldOfView::from_degrees(180.0f); // Too large
// Good: FOV in valid range
auto fov = FieldOfView::from_degrees(90.0f);
```
4. **Uninitialized camera**
```cpp
// Make sure camera is properly initialized
camera.update(current_position, current_angles);
```
**Debugging:**
```cpp
Vector3<float> world_pos{100, 100, 100};
// Check projection step by step
std::cout << "World pos: " << world_pos.x << ", "
<< world_pos.y << ", " << world_pos.z << "\n";
auto view_matrix = camera.get_view_matrix();
// Transform to view space manually and check if Z > 0
if (auto screen = camera.world_to_screen(world_pos)) {
std::cout << "Success: " << screen->x << ", " << screen->y << "\n";
} else {
std::cout << "Failed - check if point is behind camera\n";
}
```
### Angles wrapping incorrectly
**Problem:** Angles not normalizing to expected ranges.
**Solution:**
Use proper angle types:
```cpp
// Wrong: using raw floats
float pitch = 95.0f; // Out of valid range!
// Right: using typed angles
auto pitch = PitchAngle::from_degrees(89.0f); // Clamped to valid range
```
For custom ranges:
```cpp
// Define custom angle with wrapping
auto angle = Angle<float, -180.0f, 180.0f, AngleFlags::Normalized>::from_degrees(270.0f);
// Result: -90° (wrapped)
```
### Projection appears mirrored or inverted
**Problem:** Using wrong engine trait for your game.
**Solution:**
Different engines have different coordinate systems:
| Symptom | Likely Issue | Fix |
|---------|-------------|-----|
| Upside down | Y-axis inverted | Try different engine or negate Y |
| Left-right flipped | Wrong handedness | Check engine documentation |
| Rotated 90° | Axis swap | Verify engine coordinate system |
```cpp
// Try different engine traits
using namespace omath::source_engine; // Z-up, left-handed
using namespace omath::unity_engine; // Y-up, left-handed
using namespace omath::unreal_engine; // Z-up, left-handed (different conventions)
using namespace omath::opengl_engine; // Y-up, right-handed
```
If still wrong, manually transform coordinates:
```cpp
// Example: swap Y and Z for Y-up to Z-up conversion
Vector3<float> convert_y_up_to_z_up(const Vector3<float>& pos) {
return Vector3<float>{pos.x, pos.z, pos.y};
}
```
---
## Projectile Prediction Issues
### `maybe_calculate_aim_point()` returns `nullopt`
**Common causes:**
1. **Target moving too fast**
```cpp
Target target;
target.velocity = Vector3<float>{1000, 0, 0}; // Very fast!
Projectile proj;
proj.speed = 500.0f; // Too slow to catch target
// Returns nullopt - projectile can't catch target
```
**Fix:** Check if projectile speed > target speed in the direction of motion.
2. **Zero projectile speed**
```cpp
Projectile proj;
proj.speed = 0.0f; // Invalid!
// Returns nullopt
```
**Fix:** Ensure `proj.speed > 0`.
3. **Invalid positions**
```cpp
// NaN or infinite values
target.position = Vector3<float>{NAN, 0, 0};
// Returns nullopt
```
**Fix:** Validate all input values are finite.
4. **Target out of range**
```cpp
// Target very far away
float distance = shooter_pos.distance_to(target.position);
float max_range = proj.speed * max_flight_time;
if (distance > max_range) {
// Will return nullopt
}
```
**Debugging:**
```cpp
Projectile proj{/* ... */};
Target target{/* ... */};
// Check inputs
assert(proj.speed > 0);
assert(std::isfinite(target.position.length()));
assert(std::isfinite(target.velocity.length()));
// Check if target is reachable
float distance = proj.origin.distance_to(target.position);
float target_speed = target.velocity.length();
std::cout << "Distance: " << distance << "\n";
std::cout << "Projectile speed: " << proj.speed << "\n";
std::cout << "Target speed: " << target_speed << "\n";
if (target_speed >= proj.speed) {
std::cout << "Target may be too fast!\n";
}
```
### Aim point is inaccurate
**Problem:** Calculated aim point doesn't hit target.
**Possible causes:**
1. **Unit mismatch**
```cpp
// All units must match!
proj.speed = 800.0f; // meters per second
target.velocity = Vector3<float>{2, 1, 0}; // Must also be m/s!
// If using different units (e.g., game units vs meters), convert:
float game_units_to_meters = 0.01905f; // Example for Source
target.velocity = game_velocity * game_units_to_meters;
```
2. **Wrong gravity vector**
```cpp
// Source Engine: Z-up
proj.gravity = Vector3<float>{0, 0, -9.81f};
// Unity: Y-up
proj.gravity = Vector3<float>{0, -9.81f, 0};
```
3. **Target velocity not updated**
```cpp
// Update target velocity each frame
target.velocity = current_velocity; // Not last frame's velocity!
```
---
## Pattern Scanning Issues
### Pattern not found when it should be
**Problem:** `pattern_scan()` returns `nullopt` but pattern exists.
**Solutions:**
1. **Pattern syntax error**
```cpp
// Wrong: missing spaces
PatternView pattern{"488B05????????"};
// Right: spaces between bytes
PatternView pattern{"48 8B 05 ?? ?? ?? ??"};
```
2. **Pattern too specific**
```cpp
// May fail if any byte is different
PatternView pattern{"48 8B 05 01 02 03 04 48 85 C0"};
// Better: use wildcards for variable bytes
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
```
3. **Searching wrong memory region**
```cpp
// Make sure you're scanning the right memory
std::vector<uint8_t> code_section = get_code_section();
auto result = pattern_scan(code_section, pattern);
```
4. **Pattern might have multiple matches**
```cpp
// Find all matches instead of just first
size_t offset = 0;
while (offset < memory.size()) {
auto result = pattern_scan(
std::span(memory.begin() + offset, memory.end()),
pattern
);
if (result) {
std::cout << "Match at: " << offset + result->offset << "\n";
offset += result->offset + 1;
} else {
break;
}
}
```
### Pattern found at wrong location
**Problem:** Pattern matches unintended code.
**Solutions:**
1. **Make pattern more specific**
```cpp
// Too generic
PatternView pattern{"48 8B"};
// More specific - include more context
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0 74 ??"};
```
2. **Verify found address**
```cpp
if (auto result = pattern_scan(memory, pattern)) {
// Verify by checking nearby bytes
size_t offset = result->offset;
// Check if instruction makes sense
if (memory[offset] == 0x48 && memory[offset + 1] == 0x8B) {
// Looks good
}
}
```
3. **Use multiple patterns**
```cpp
// Find reference function first
auto ref_pattern = PatternView{"E8 ?? ?? ?? ?? 85 C0"};
auto ref_result = pattern_scan(memory, ref_pattern);
// Then search near that location
// This provides context validation
```
---
## Vector & Math Issues
### `normalized()` returns zero vector
**Problem:** Normalizing a zero-length vector.
**Behavior:**
```cpp
Vector3<float> zero{0, 0, 0};
auto result = zero.normalized(); // Returns {0, 0, 0}
```
This is **intentional** to avoid NaN. Check vector length first:
```cpp
if (v.length() > 0.001f) {
auto normalized = v.normalized();
// Use normalized vector
} else {
// Handle zero-length case
}
```
### `angle_between()` returns error
**Problem:** One or both vectors have zero length.
**Solution:**
```cpp
auto angle_result = v1.angle_between(v2);
if (angle_result) {
float degrees = angle_result->as_degrees();
} else {
// Handle error - one or both vectors have zero length
std::cerr << "Cannot compute angle between zero-length vectors\n";
}
```
### Cross product seems wrong
**Problem:** Unexpected cross product result.
**Check:**
1. **Right-handed system**
```cpp
Vector3<float> x{1, 0, 0};
Vector3<float> y{0, 1, 0};
auto z = x.cross(y); // Should be {0, 0, 1} in right-handed system
```
2. **Order matters**
```cpp
auto cross1 = a.cross(b); // {x1, y1, z1}
auto cross2 = b.cross(a); // {-x1, -y1, -z1} (opposite direction!)
```
---
## Performance Issues
### Code is slower than expected
**Solutions:**
1. **Enable optimizations**
```cmake
# CMakeLists.txt
target_compile_options(your_target PRIVATE
$<$<CONFIG:Release>:-O3>
$<$<CONFIG:Release>:-march=native>
)
```
2. **Use AVX2 engine**
```cpp
// Instead of
ProjPredEngineLegacy engine;
// Use
ProjPredEngineAVX2 engine;
```
3. **Avoid unnecessary operations**
```cpp
// Bad: recompute every frame
for (auto& entity : entities) {
float dist = entity.pos.distance_to(player_pos); // Expensive sqrt!
if (dist < 100.0f) { /* ... */ }
}
// Good: use squared distance
constexpr float max_dist_sq = 100.0f * 100.0f;
for (auto& entity : entities) {
float dist_sq = entity.pos.distance_to_sqr(player_pos); // No sqrt!
if (dist_sq < max_dist_sq) { /* ... */ }
}
```
4. **Cache matrices**
```cpp
// Bad: recompute matrix every call
for (auto& pos : positions) {
auto screen = camera.world_to_screen(pos); // Recomputes matrices!
}
// Good: matrices are cached in camera automatically
camera.update(pos, angles); // Updates matrices once
for (auto& pos : positions) {
auto screen = camera.world_to_screen(pos); // Uses cached matrices
}
```
---
## Getting More Help
If your issue isn't covered here:
1. **Check the docs**: [API Overview](api_overview.md), [Tutorials](tutorials.md)
2. **Search GitHub issues**: [Issues page](https://github.com/orange-cpp/omath/issues)
3. **Ask on Discord**: [Join community](https://discord.gg/eDgdaWbqwZ)
4. **Open a new issue**: Include:
- OMath version
- Compiler and version
- Minimal reproducible example
- What you expected vs what happened
---
*Last updated: 1 Nov 2025*

616
docs/tutorials.md Normal file
View File

@@ -0,0 +1,616 @@
# Tutorials
This page provides step-by-step tutorials for common OMath use cases.
---
## Tutorial 1: Basic Vector Math
Learn the fundamentals of vector operations in OMath.
### Step 1: Include OMath
```cpp
#include <omath/omath.hpp>
#include <iostream>
using namespace omath;
```
### Step 2: Create Vectors
```cpp
// 2D vectors
Vector2<float> v2a{3.0f, 4.0f};
Vector2<float> v2b{1.0f, 2.0f};
// 3D vectors
Vector3<float> v3a{1.0f, 2.0f, 3.0f};
Vector3<float> v3b{4.0f, 5.0f, 6.0f};
// 4D vectors (often used for homogeneous coordinates)
Vector4<float> v4{1.0f, 2.0f, 3.0f, 1.0f};
```
### Step 3: Perform Operations
```cpp
// Addition
auto sum = v3a + v3b; // {5, 7, 9}
// Subtraction
auto diff = v3a - v3b; // {-3, -3, -3}
// Scalar multiplication
auto scaled = v3a * 2.0f; // {2, 4, 6}
// Dot product
float dot = v3a.dot(v3b); // 32.0
// Cross product (3D only)
auto cross = v3a.cross(v3b); // {-3, 6, -3}
// Length
float len = v3a.length(); // ~3.74
// Normalization (safe - returns original if length is zero)
auto normalized = v3a.normalized();
// Distance between vectors
float dist = v3a.distance_to(v3b); // ~5.196
```
### Step 4: Angle Calculations
```cpp
if (auto angle = v3a.angle_between(v3b)) {
std::cout << "Angle in degrees: " << angle->as_degrees() << "\n";
std::cout << "Angle in radians: " << angle->as_radians() << "\n";
} else {
std::cout << "Cannot compute angle (zero-length vector)\n";
}
// Check if perpendicular
if (v3a.is_perpendicular(v3b)) {
std::cout << "Vectors are perpendicular\n";
}
```
**Key takeaways:**
- All vector operations are type-safe and constexpr-friendly
- Safe normalization never produces NaN
- Angle calculations use `std::expected` for error handling
---
## Tutorial 2: World-to-Screen Projection
Project 3D coordinates to 2D screen space for overlays and ESP.
### Step 1: Choose Your Game Engine
```cpp
#include <omath/omath.hpp>
// For Source Engine games (CS:GO, TF2, etc.)
using namespace omath::source_engine;
// Or for other engines:
// using namespace omath::unity_engine;
// using namespace omath::unreal_engine;
// using namespace omath::frostbite_engine;
```
### Step 2: Set Up the Camera
```cpp
using namespace omath;
using namespace omath::projection;
// Define viewport (screen dimensions)
ViewPort viewport{1920.0f, 1080.0f};
// Define field of view
auto fov = FieldOfView::from_degrees(90.0f);
// Camera position and angles
Vector3<float> camera_pos{0.0f, 0.0f, 100.0f};
ViewAngles camera_angles{
PitchAngle::from_degrees(0.0f),
YawAngle::from_degrees(0.0f),
RollAngle::from_degrees(0.0f)
};
// Create camera (using Source Engine in this example)
Camera camera(
camera_pos,
camera_angles,
viewport,
fov,
0.1f, // near plane
1000.0f // far plane
);
```
### Step 3: Project 3D Points
```cpp
// 3D world position (e.g., enemy player position)
Vector3<float> enemy_pos{150.0f, 200.0f, 75.0f};
// Project to screen
if (auto screen = camera.world_to_screen(enemy_pos)) {
std::cout << "Enemy on screen at: "
<< screen->x << ", " << screen->y << "\n";
// Draw ESP box or marker at screen->x, screen->y
// Note: screen coordinates are in viewport space (0-width, 0-height)
} else {
// Enemy is not visible (behind camera or outside frustum)
std::cout << "Enemy not visible\n";
}
```
### Step 4: Update Camera for Each Frame
```cpp
void render_frame() {
// Read current camera data from game
Vector3<float> new_pos = read_camera_position();
ViewAngles new_angles = read_camera_angles();
// Update camera
camera.update(new_pos, new_angles);
// Project all entities
for (const auto& entity : entities) {
if (auto screen = camera.world_to_screen(entity.position)) {
draw_esp_box(screen->x, screen->y);
}
}
}
```
**Key takeaways:**
- Choose the engine trait that matches your target game
- `world_to_screen()` returns `std::optional` - always check the result
- Update camera each frame for accurate projections
- Screen coordinates are in the viewport space you defined
---
## Tutorial 3: Projectile Prediction (Aim-Bot)
Calculate where to aim to hit a moving target.
### Step 1: Define Projectile Properties
```cpp
#include <omath/omath.hpp>
#include <omath/projectile_prediction/proj_pred_engine_legacy.hpp>
using namespace omath;
using namespace omath::projectile_prediction;
// Define your weapon's projectile
Projectile bullet;
bullet.origin = Vector3<float>{0, 0, 0}; // Shooter position
bullet.speed = 800.0f; // Muzzle velocity (m/s or game units/s)
bullet.gravity = Vector3<float>{0, 0, -9.81f}; // Gravity vector
```
### Step 2: Define Target State
```cpp
// Target information (enemy player)
Target enemy;
enemy.position = Vector3<float>{100, 200, 50}; // Current position
enemy.velocity = Vector3<float>{10, 5, 0}; // Current velocity
```
### Step 3: Calculate Aim Point
```cpp
// Create prediction engine
// Use AVX2 version if available for better performance:
// ProjPredEngineAVX2 engine;
ProjPredEngineLegacy engine;
// Calculate where to aim
if (auto aim_point = engine.maybe_calculate_aim_point(bullet, enemy)) {
std::cout << "Aim at: "
<< aim_point->x << ", "
<< aim_point->y << ", "
<< aim_point->z << "\n";
// Calculate angles to aim_point
Vector3<float> aim_direction = (*aim_point - bullet.origin).normalized();
// Convert to view angles (engine-specific)
// ViewAngles angles = calculate_angles_to_direction(aim_direction);
// set_aim_angles(angles);
} else {
// Cannot hit target (too fast, out of range, etc.)
std::cout << "Target cannot be hit\n";
}
```
### Step 4: Handle Different Scenarios
```cpp
// Stationary target
Target stationary;
stationary.position = Vector3<float>{100, 100, 100};
stationary.velocity = Vector3<float>{0, 0, 0};
// aim_point will equal position for stationary targets
// Fast-moving target
Target fast;
fast.position = Vector3<float>{100, 100, 100};
fast.velocity = Vector3<float>{50, 0, 0}; // Moving very fast
// May return nullopt if target is too fast
// Target at different heights
Target aerial;
aerial.position = Vector3<float>{100, 100, 200}; // High up
aerial.velocity = Vector3<float>{5, 5, -10}; // Falling
// Gravity will be factored into the calculation
```
### Step 5: Performance Optimization
```cpp
// For better performance on modern CPUs, use AVX2:
#include <omath/projectile_prediction/proj_pred_engine_avx2.hpp>
ProjPredEngineAVX2 fast_engine; // 2-4x faster than legacy
// Use the same way as legacy engine
if (auto aim = fast_engine.maybe_calculate_aim_point(bullet, enemy)) {
// Process aim point
}
```
**Key takeaways:**
- Always check if aim point exists before using
- Velocity must be in same units as position/speed
- Gravity vector points down (typically negative Z or Y depending on engine)
- Use AVX2 engine when possible for better performance
- Returns `nullopt` when target is unreachable
---
## Tutorial 4: Collision Detection
Perform ray-casting and intersection tests.
### Step 1: Ray-Plane Intersection
```cpp
#include <omath/omath.hpp>
using namespace omath;
// Define a ground plane (Z=0, normal pointing up)
Plane ground{
Vector3<float>{0, 0, 0}, // Point on plane
Vector3<float>{0, 0, 1} // Normal vector (Z-up)
};
// Define a ray (e.g., looking downward from above)
Vector3<float> ray_origin{10, 20, 100};
Vector3<float> ray_direction{0, 0, -1}; // Pointing down
// Test intersection
if (auto hit = ground.intersects_ray(ray_origin, ray_direction)) {
std::cout << "Hit ground at: "
<< hit->x << ", " << hit->y << ", " << hit->z << "\n";
// Expected: (10, 20, 0)
} else {
std::cout << "Ray does not intersect plane\n";
}
```
### Step 2: Distance to Plane
```cpp
// Calculate signed distance from point to plane
Vector3<float> point{10, 20, 50};
float distance = ground.distance_to_point(point);
std::cout << "Distance to ground: " << distance << "\n";
// Expected: 50.0 (50 units above ground)
// Negative distance means point is below the plane
Vector3<float> below{10, 20, -5};
float dist_below = ground.distance_to_point(below);
// Expected: -5.0
```
### Step 3: Axis-Aligned Bounding Box
```cpp
#include <omath/3d_primitives/box.hpp>
// Create a bounding box
Box bbox{
Vector3<float>{0, 0, 0}, // Min corner
Vector3<float>{100, 100, 100} // Max corner
};
// Test if point is inside
Vector3<float> inside{50, 50, 50};
if (bbox.contains(inside)) {
std::cout << "Point is inside box\n";
}
Vector3<float> outside{150, 50, 50};
if (!bbox.contains(outside)) {
std::cout << "Point is outside box\n";
}
// Box-box intersection
Box other{
Vector3<float>{50, 50, 50},
Vector3<float>{150, 150, 150}
};
if (bbox.intersects(other)) {
std::cout << "Boxes overlap\n";
}
```
### Step 4: Line Tracing
```cpp
#include <omath/collision/line_tracer.hpp>
using namespace omath::collision;
// Ray-triangle intersection
Vector3<float> v0{0, 0, 0};
Vector3<float> v1{100, 0, 0};
Vector3<float> v2{0, 100, 0};
Vector3<float> ray_start{25, 25, 100};
Vector3<float> ray_dir{0, 0, -1};
LineTracer tracer;
if (auto hit = tracer.ray_triangle_intersect(ray_start, ray_dir, v0, v1, v2)) {
std::cout << "Hit triangle at: "
<< hit->point.x << ", "
<< hit->point.y << ", "
<< hit->point.z << "\n";
std::cout << "Hit distance: " << hit->distance << "\n";
std::cout << "Surface normal: "
<< hit->normal.x << ", "
<< hit->normal.y << ", "
<< hit->normal.z << "\n";
}
```
**Key takeaways:**
- Plane normals should be unit vectors
- Ray direction should typically be normalized
- Signed distance indicates which side of plane a point is on
- AABB tests are very fast for broad-phase collision detection
- Line tracer provides hit point, distance, and surface normal
---
## Tutorial 5: Pattern Scanning
Search for byte patterns in memory.
### Step 1: Basic Pattern Scanning
```cpp
#include <omath/utility/pattern_scan.hpp>
#include <vector>
using namespace omath;
// Memory to search (e.g., from a loaded module)
std::vector<uint8_t> memory = {
0x48, 0x8B, 0x05, 0xAA, 0xBB, 0xCC, 0xDD,
0x48, 0x85, 0xC0, 0x74, 0x10,
// ... more bytes
};
// Pattern with wildcards (?? = match any byte)
PatternView pattern{"48 8B 05 ?? ?? ?? ?? 48 85 C0"};
// Scan for pattern
if (auto result = pattern_scan(memory, pattern)) {
std::cout << "Pattern found at offset: " << result->offset << "\n";
// Extract wildcard values if needed
// result->wildcards contains the matched bytes at ?? positions
} else {
std::cout << "Pattern not found\n";
}
```
### Step 2: PE File Scanning
```cpp
#include <omath/utility/pe_pattern_scan.hpp>
// Scan a PE file (EXE or DLL)
PEPatternScanner scanner("game.exe");
PatternView pattern{"E8 ?? ?? ?? ?? 85 C0 75 ??"};
if (auto rva = scanner.scan_pattern(pattern)) {
std::cout << "Pattern found at RVA: 0x"
<< std::hex << *rva << std::dec << "\n";
// Convert RVA to absolute address if needed
uintptr_t base_address = get_module_base("game.exe");
uintptr_t absolute = base_address + *rva;
} else {
std::cout << "Pattern not found in PE file\n";
}
```
### Step 3: Multiple Patterns
```cpp
// Search for multiple patterns
std::vector<PatternView> patterns{
PatternView{"48 8B 05 ?? ?? ?? ??"},
PatternView{"E8 ?? ?? ?? ?? 85 C0"},
PatternView{"FF 15 ?? ?? ?? ?? 48 8B"}
};
for (size_t i = 0; i < patterns.size(); ++i) {
if (auto result = pattern_scan(memory, patterns[i])) {
std::cout << "Pattern " << i << " found at: "
<< result->offset << "\n";
}
}
```
### Step 4: Pattern with Masks
```cpp
// Alternative: use mask-based patterns
// Pattern: bytes to match
std::vector<uint8_t> pattern_bytes{0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00};
// Mask: 'x' = must match, '?' = wildcard
std::string mask{"xxx????"};
// Custom scan function
auto scan_with_mask = [&](const std::vector<uint8_t>& data) {
for (size_t i = 0; i < data.size() - pattern_bytes.size(); ++i) {
bool match = true;
for (size_t j = 0; j < pattern_bytes.size(); ++j) {
if (mask[j] == 'x' && data[i + j] != pattern_bytes[j]) {
match = false;
break;
}
}
if (match) return i;
}
return size_t(-1);
};
```
**Key takeaways:**
- Use `??` in pattern strings for wildcards
- PE scanner works with files and modules
- Pattern scanning is useful for finding functions, vtables, or data
- Always validate found addresses before use
- Patterns may have multiple matches - consider context
---
## Tutorial 6: Angles and View Angles
Work with game camera angles properly.
### Step 1: Understanding Angle Types
```cpp
#include <omath/omath.hpp>
using namespace omath;
// Generic angle with custom range
auto angle1 = Angle<float, 0.0f, 360.0f>::from_degrees(45.0f);
auto angle2 = Angle<float, -180.0f, 180.0f>::from_degrees(270.0f);
// Specialized camera angles
auto pitch = PitchAngle::from_degrees(-10.0f); // Looking down
auto yaw = YawAngle::from_degrees(90.0f); // Looking right
auto roll = RollAngle::from_degrees(0.0f); // No tilt
```
### Step 2: Angle Conversions
```cpp
// Create from degrees
auto deg_angle = PitchAngle::from_degrees(45.0f);
// Get as radians
float radians = deg_angle.as_radians();
std::cout << "45° = " << radians << " radians\n";
// Get as degrees
float degrees = deg_angle.as_degrees();
std::cout << "Value: " << degrees << "°\n";
```
### Step 3: View Angles (Camera)
```cpp
// Pitch: vertical rotation (-89° to 89°)
// Yaw: horizontal rotation (-180° to 180°)
// Roll: camera tilt (-180° to 180°)
ViewAngles camera_angles{
PitchAngle::from_degrees(-15.0f), // Looking slightly down
YawAngle::from_degrees(45.0f), // Facing northeast
RollAngle::from_degrees(0.0f) // No tilt
};
// Access individual components
float pitch_val = camera_angles.pitch.as_degrees();
float yaw_val = camera_angles.yaw.as_degrees();
float roll_val = camera_angles.roll.as_degrees();
```
### Step 4: Calculating Look-At Angles
```cpp
using namespace omath::source_engine; // Or your game's engine
Vector3<float> camera_pos{0, 0, 100};
Vector3<float> target_pos{100, 100, 100};
// Calculate angles to look at target
ViewAngles look_at = CameraTrait::calc_look_at_angle(camera_pos, target_pos);
std::cout << "Pitch: " << look_at.pitch.as_degrees() << "°\n";
std::cout << "Yaw: " << look_at.yaw.as_degrees() << "°\n";
std::cout << "Roll: " << look_at.roll.as_degrees() << "°\n";
```
### Step 5: Angle Arithmetic
```cpp
// Angles support arithmetic with automatic normalization
auto angle1 = YawAngle::from_degrees(170.0f);
auto angle2 = YawAngle::from_degrees(20.0f);
// Addition (wraps around)
auto sum = angle1 + angle2; // 190° → normalized to -170°
// Subtraction
auto diff = angle2 - angle1; // -150°
// Scaling
auto scaled = angle1 * 2.0f;
```
**Key takeaways:**
- Use specialized angle types for camera angles (PitchAngle, YawAngle, RollAngle)
- Angles automatically normalize to their valid ranges
- Each game engine may have different angle conventions
- Use engine traits to calculate look-at angles correctly
---
## Next Steps
Now that you've completed these tutorials, explore:
- **[API Overview](api_overview.md)** - Complete API reference
- **[Engine Documentation](engines/)** - Engine-specific features
- **[Examples](../examples/)** - More code examples
- **[Getting Started](getting_started.md)** - Quick start guide
---
*Last updated: 1 Nov 2025*

190
docs/utility/color.md Normal file
View File

@@ -0,0 +1,190 @@
# `omath::Color` — RGBA color with HSV helpers (C++20/23)
> Header: your projects `color.hpp`
> Namespace: `omath`
> Inherits: `Vector4<float>` (`x=r`, `y=g`, `z=b`, `w=a`)
> Depends on: `<cstdint>`, `Vector4`, optionally ImGui (`OMATH_IMGUI_INTEGRATION`)
> Formatting: provides `std::formatter<omath::Color>`
`Color` is a tiny RGBA utility on top of `Vector4<float>`. It offers sRGB-style channel construction, HSV↔RGB conversion, in-place HSV setters, linear blending, and string/formatter helpers.
---
## Quick start
```cpp
#include "color.hpp"
using omath::Color;
// RGBA in [0,1] (r,g,b clamped to [0,1] on construction)
Color c{0.2f, 0.4f, 0.8f, 0.5f};
// From 8-bit channels
auto red = Color::from_rgba(255, 0, 0, 255);
auto green = Color::from_rgba(0, 255, 0, 160);
// From HSV (h ∈ [0,1], s ∈ [0,1], v ∈ [0,1])
auto cyan = Color::from_hsv(0.5f, 1.0f, 1.0f); // a = 1
// Read/modify via HSV
auto hsv = cyan.to_hsv(); // hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1]
cyan.set_value(0.6f); // converts back to RGB (alpha becomes 1)
// Blend linearly (lerp)
auto mid = red.blend(green, 0.5f);
// Printable (0255 per channel)
std::string s = std::format("{}", mid); // "[r:128, g:128, b:0, a:207]" for example
```
---
## Data model
* Inherits `Vector4<float>`:
* `x` = **red**, `y` = **green**, `z` = **blue**, `w` = **alpha**.
* Construction clamps **RGB** to `[0,1]` (via `Vector4::clamp(0,1)`), **alpha is not clamped** by that call (see notes).
---
## Construction & factories
```cpp
// RGBA in [0,1] (RGB clamped to [0,1]; alpha untouched by clamp)
constexpr Color(float r, float g, float b, float a) noexcept;
// Default
constexpr Color() noexcept;
// From 8-bit RGBA (0255) → normalized to [0,1]
constexpr static Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
// From HSV where hue ∈ [0,1], saturation ∈ [0,1], value ∈ [0,1]
struct Hsv { float hue{}, saturation{}, value{}; };
constexpr static Color from_hsv(float hue, float saturation, float value) noexcept;
constexpr static Color from_hsv(const Hsv& hsv) noexcept; // delegates to the above
// Construct from a Vector4 (RGB clamped, alpha not clamped)
constexpr explicit Color(const Vector4& vec) noexcept;
```
**HSV details**
* `from_hsv(h, s, v)`: `h` is **normalized** (`[0,1]`); it is clamped, then mapped to the 6 hue sectors; **alpha = 1.0**.
* `to_hsv()`: returns `Hsv{h,s,v}` with **`h ∈ [0,1]`** (internally computes degrees and divides by 360), `s,v ∈ [0,1]`.
---
## Mutators
```cpp
constexpr void set_hue(float h) noexcept; // h ∈ [0,1] recommended
constexpr void set_saturation(float s) noexcept; // s ∈ [0,1]
constexpr void set_value(float v) noexcept; // v ∈ [0,1]
// Linear blend: (1-ratio)*this + ratio*other, ratio clamped to [0,1]
constexpr Color blend(const Color& other, float ratio) const noexcept;
```
> ⚠️ **Alpha reset on HSV setters:** each `set_*` converts HSV→RGB using `from_hsv(...)`, which **sets alpha to 1.0** (overwriting previous `w`). If you need to preserve alpha:
>
> ```cpp
> float a = col.w;
> col.set_value(0.5f);
> col.w = a;
> ```
---
## Constants
```cpp
static constexpr Color red(); // (1,0,0,1)
static constexpr Color green(); // (0,1,0,1)
static constexpr Color blue(); // (0,0,1,1)
```
---
## String & formatting
```cpp
// "[r:R, g:G, b:B, a:A]" with each channel shown as 0255 integer
std::string to_string() const noexcept;
std::wstring to_wstring() const noexcept;
std::u8string to_u8string() const noexcept;
// Formatter forwards to the above (char/wchar_t/char8_t)
template<> struct std::formatter<omath::Color>;
```
---
## ImGui (optional)
```cpp
#ifdef OMATH_IMGUI_INTEGRATION
ImColor to_im_color() const noexcept; // constructs from Vector4's to_im_vec4()
#endif
```
Ensure `<imgui.h>` is included somewhere before this header when the macro is enabled.
---
## Notes & caveats
* **Alpha clamping:** `Vector4::clamp(min,max)` (called by `Color` ctors) clamps **x,y,z** only in the provided `Vector4` implementation; `w` is **left unchanged**. If you require strict `[0,1]` alpha, clamp it yourself:
```cpp
col.w = std::clamp(col.w, 0.0f, 1.0f);
```
* **HSV range:** The API consistently uses **normalized hue** (`[0,1]`). Convert degrees ↔ normalized as `h_norm = h_deg / 360.f`.
* **Blend space:** `blend` is a **linear** interpolation in RGBA; it is not perceptually uniform.
---
## API summary
```cpp
struct Hsv { float hue{}, saturation{}, value{}; };
class Color final : public Vector4<float> {
public:
constexpr Color(float r, float g, float b, float a) noexcept;
constexpr Color() noexcept;
constexpr explicit Color(const Vector4& vec) noexcept;
static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
static constexpr Color from_hsv(float hue, float saturation, float value) noexcept;
static constexpr Color from_hsv(const Hsv& hsv) noexcept;
constexpr Hsv to_hsv() const noexcept;
constexpr void set_hue(float h) noexcept;
constexpr void set_saturation(float s) noexcept;
constexpr void set_value(float v) noexcept;
constexpr Color blend(const Color& other, float ratio) const noexcept;
static constexpr Color red();
static constexpr Color green();
static constexpr Color blue();
#ifdef OMATH_IMGUI_INTEGRATION
ImColor to_im_color() const noexcept;
#endif
std::string to_string() const noexcept;
std::wstring to_wstring() const noexcept;
std::u8string to_u8string() const noexcept;
};
// formatter<omath::Color> provided
```
---
*Last updated: 31 Oct 2025*

View File

@@ -0,0 +1,194 @@
# `omath::PatternScanner` — Fast byte-pattern search with wildcards
> Header: your projects `pattern_scanner.hpp`
> Namespace: `omath`
> Core API: `scan_for_pattern(...)` (span or iterators)
> Errors: `PatternScanError::INVALID_PATTERN_STRING` (from `parse_pattern`)
> C++: uses `std::span`, `std::expected`, `std::byte`
`PatternScanner` scans a contiguous byte range for a **hex pattern** that may include **wildcards**. It returns an iterator to the **first match** or the end iterator if not found (or if the pattern string is invalid).
---
## Quick start
```cpp
#include "pattern_scanner.hpp"
using omath::PatternScanner;
std::vector<std::byte> buf = /* ... bytes ... */;
std::span<std::byte> s{buf.data(), buf.size()};
// Example pattern: "48 8B ?? ?? 89" (hex bytes with '?' as any byte)
auto it = PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89");
if (it != s.end()) {
// Found at offset:
auto offset = static_cast<std::size_t>(it - s.begin());
}
```
Or with iterators:
```cpp
auto it = PatternScanner::scan_for_pattern(buf.begin(), buf.end(), "DE AD BE EF");
if (it != buf.end()) { /* ... */ }
```
---
## Pattern string grammar
The pattern string is parsed into a sequence of byte **tokens**:
* **Hex byte**: two hexadecimal digits form one byte.
Examples: `90`, `4F`, `00`, `ff`
* **Wildcard byte**: `?` or `??` matches **any single byte**.
* **Separators**: any ASCII whitespace (space, tab, newline) is **ignored** and may be used to group tokens.
> ✔️ Valid: `"48 8B ?? 05 00"`, `"90 90 90"`, `"??"`
> ❌ Invalid: odd number of hex digits in a token, non-hex characters (besides `?` and whitespace)
If the string cannot be parsed into a clean sequence of tokens, `parse_pattern()` returns `std::unexpected(PatternScanError::INVALID_PATTERN_STRING)`, and the public scan function returns **end**.
---
## API
```cpp
namespace omath {
enum class PatternScanError { INVALID_PATTERN_STRING };
class PatternScanner final {
public:
// Contiguous range (span) overload
[[nodiscard]]
static std::span<std::byte>::iterator
scan_for_pattern(const std::span<std::byte>& range,
const std::string_view& pattern);
// Deleted rvalue-span overload (prevents dangling)
static std::span<std::byte>::iterator
scan_for_pattern(std::span<std::byte>&&,
const std::string_view&) = delete;
// Iterator overload
template<class IteratorType>
requires std::input_or_output_iterator<std::remove_cvref_t<IteratorType>>
static IteratorType
scan_for_pattern(const IteratorType& begin,
const IteratorType& end,
const std::string_view& pattern);
private:
[[nodiscard]]
static std::expected<std::vector<std::optional<std::byte>>, PatternScanError>
parse_pattern(const std::string_view& pattern_string);
};
} // namespace omath
```
### Return value
* On success: iterator to the **first** matching position.
* On failure / not found / invalid pattern: **`end`**.
---
## Complexity
Let `N` be the number of bytes in the range and `M` the number of **pattern tokens**.
* Time: **O(N × M)** (simple sliding window with early break).
* Space: **O(M)** for the parsed pattern vector.
---
## Examples
### Find a function prologue
```cpp
// x86-64: push rbp; mov rbp, rsp
auto it = PatternScanner::scan_for_pattern(s, "55 48 89 E5");
if (it != s.end()) {
// ... process
}
```
### Skip variable bytes with wildcards
```cpp
// mov rax, <imm32>; call <rel32>
auto it = PatternScanner::scan_for_pattern(s, "48 B8 ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ??");
```
### Iterator-based scan (subrange)
```cpp
auto sub_begin = buf.begin() + 1024;
auto sub_end = buf.begin() + 4096;
auto it = PatternScanner::scan_for_pattern(sub_begin, sub_end, "DE AD ?? BE EF");
```
---
## Behavior & edge cases
* **Empty or too-short**: If the effective pattern token count `M` is `0` or `M > N`, the function returns `end`.
* **Wildcards**: Each `?`/`??` token matches **exactly one** byte.
* **No exceptions**: Invalid pattern → parsed as `unexpected`, public API returns `end`.
* **No ownership**: The span overload takes `const std::span<std::byte>&` and returns a **mutable iterator** into that span. You must ensure the underlying memory stays alive. The rvalue-span overload is **deleted** to prevent dangling.
---
## Notes & caveats (implementation-sensitive)
* Although the iterator overload is constrained with `std::input_or_output_iterator`, the implementation uses `*(begin + i + j)` and arithmetic on iterators. In practice this means you should pass **random-access / contiguous iterators** (e.g., from `std::vector<std::byte>` or `std::span<std::byte>`). Using non-random-access iterators would be ill-formed.
* The inner matching loop compares a parsed token (`std::optional<std::byte>`) to the candidate byte; `std::nullopt` is treated as a **wildcard** (always matches).
* **Implementation note:** the outer scan currently derives its scan bound from the **pattern string length**; conceptually it should use the **number of parsed tokens** (hex/wildcard bytes). If you plan to accept spaces (or other non-byte characters) in the pattern string, ensure the scan window uses the parsed token count to avoid false negatives near the end of the buffer.
---
## Testing hooks
The class befriends several unit tests:
```
unit_test_pattern_scan_read_test_Test
unit_test_pattern_scan_corner_case_1_Test
unit_test_pattern_scan_corner_case_2_Test
unit_test_pattern_scan_corner_case_3_Test
unit_test_pattern_scan_corner_case_4_Test
```
Use these to validate parsing correctness, wildcard handling, and boundary conditions.
---
## Troubleshooting
* **Always returns end**: verify the pattern string is valid hex/wildcards and that youre scanning the intended subrange. Try a simpler pattern (e.g., a single known byte) to sanity-check.
* **Crashes or compile errors with iterator overload**: use iterators that support random access (e.g., from `std::vector`), or prefer the `std::span` overload.
* **Ambiguous wildcards**: this scanner treats `?` and `??` as **byte-wide** wildcards (not per-nibble). If you need nibble-level masks, extend `parse_pattern` to support patterns like `A?`/`?F` with bitmask matching.
---
## Minimal unit test sketch
```cpp
TEST(pattern, basic) {
std::array<std::byte, 8> data{
std::byte{0x48}, std::byte{0x8B}, std::byte{0x01}, std::byte{0x02},
std::byte{0x89}, std::byte{0x50}, std::byte{0x90}, std::byte{0xCC}
};
std::span<std::byte> s{data.data(), data.size()};
auto it = omath::PatternScanner::scan_for_pattern(s, "48 8B ?? ?? 89");
ASSERT_NE(it, s.end());
EXPECT_EQ(static_cast<size_t>(it - s.begin()), 0U);
}
```
---
*Last updated: 31 Oct 2025*

View File

@@ -0,0 +1,155 @@
# `omath::PePatternScanner` — Scan PE images for byte patterns
> Header: your projects `pe_pattern_scanner.hpp`
> Namespace: `omath`
> Platform: **Windows / PE (Portable Executable) images**
> Depends on: `<filesystem>`, `<optional>`, `<cstdint>`
> Companion: works well with `omath::PatternScanner` (same pattern grammar)
`PePatternScanner` searches **Portable Executable (PE)** binaries for a hex pattern (with wildcards). You can scan:
* a **loaded module** in the current process, or
* a **PE file on disk** (by section name; defaults to **`.text`**).
---
## Pattern string grammar (same as `PatternScanner`)
* **Hex byte**: two hex digits → one byte (`90`, `4F`, `00`, `ff`).
* **Wildcard byte**: `?` or `??` matches **any byte**.
* **Whitespace**: ignored (use to group tokens).
✔️ `"48 8B ?? ?? 89"`, `"55 8B EC"`, `"??"`
❌ odd digit counts, non-hex characters (besides `?` and whitespace)
---
## API
```cpp
namespace omath {
struct PeSectionScanResult {
std::uint64_t virtual_base_addr; // RVA base of the scanned section (ImageBase + SectionRVA)
std::uint64_t raw_base_addr; // file offset (start of section in the file)
std::ptrdiff_t target_offset; // offset from section base to the first matched byte
};
class PePatternScanner final {
public:
// Scan a module already loaded in *this* process.
// module_base_address: HMODULE / ImageBase (e.g., from GetModuleHandle)
// Returns absolute address (process VA) of the first match, or nullopt.
static std::optional<std::uintptr_t>
scan_for_pattern_in_loaded_module(const void* module_base_address,
const std::string_view& pattern);
// Scan a PE file on disk, by section name (default ".text").
// Returns section bases (virtual + raw) and match offset within the section, or nullopt.
static std::optional<PeSectionScanResult>
scan_for_pattern_in_file(const std::filesystem::path& path_to_file,
const std::string_view& pattern,
const std::string_view& target_section_name = ".text");
};
} // namespace omath
```
---
## Return values
* **Loaded module**: `std::optional<std::uintptr_t>`
* `value()` = **process virtual address** (ImageBase + SectionRVA + match offset).
* `nullopt` = no match or parse/PE error.
* **File scan**: `std::optional<PeSectionScanResult>`
* `virtual_base_addr` = **ImageBase + SectionRVA** of the scanned section (as if mapped).
* `raw_base_addr` = **file offset** of section start.
* `target_offset` = offset from the section base to the **first matched byte**.
* To get addresses:
* **Virtual (RVA)** of hit = `virtual_base_addr + target_offset`
* **Raw file offset** of hit = `raw_base_addr + target_offset`
---
## Usage examples
### Scan a loaded module (current process)
```cpp
#include <windows.h>
#include "pe_pattern_scanner.hpp"
using omath::PePatternScanner;
auto hMod = ::GetModuleHandleW(L"kernel32.dll");
if (hMod) {
auto addr = PePatternScanner::scan_for_pattern_in_loaded_module(
hMod, "48 8B ?? ?? 89" // hex + wildcards
);
if (addr) {
// Use the absolute process VA:
std::uintptr_t hit_va = *addr;
// ...
}
}
```
### Scan a PE file on disk (default section “.text”)
```cpp
#include "pe_pattern_scanner.hpp"
using omath::PePatternScanner;
auto res = PePatternScanner::scan_for_pattern_in_file(
R"(C:\Windows\System32\kernel32.dll)", "55 8B EC"
);
if (res) {
auto rva_hit = res->virtual_base_addr + res->target_offset;
auto raw_hit = res->raw_base_addr + res->target_offset;
// rva_hit: where it would be in memory; raw_hit: file offset
}
```
### Scan another section (e.g., “.rdata”)
```cpp
auto res = PePatternScanner::scan_for_pattern_in_file(
"foo.dll", "48 8D 0D ?? ?? ?? ??", ".rdata"
);
```
---
## Notes & edge cases
* **PE only**: these functions assume a valid **PE/COFF** layout. Non-PE files or corrupted headers yield `nullopt`.
* **Section name**: `scan_for_pattern_in_file` defaults to **`.text`**; pass a different name to target other sections.
* **Alignment & RVAs**: `virtual_base_addr` is computed from section headers (RVA aligned per section alignment). The returned “virtual” base is suitable for RVA math; the **process VA** returned by the “loaded module” API already includes the image base.
* **Architecture**: works for 32-bit and 64-bit PEs; `std::uintptr_t` size matches the build architecture.
* **Performance**: Pattern matching is **O(N × M)** (sliding window with wildcards). For large images, prefer scanning only necessary sections.
* **Wildcards**: Each `?` matches **one byte** (no nibble masks). If you need `A?`-style nibble wildcards, extend the parser (see `PatternScanner`).
* **Safety**: For loaded modules, you must have access to the memory; scanning read-only sections is fine, but never write. For file scans, ensure the file path is accessible.
---
## Troubleshooting
* **`nullopt`**: Verify the pattern (valid tokens), correct **section**, and that youre scanning the intended module/file (check bitness and version).
* **“Loaded module” address math**: If you need an **offset from the module base**, compute `offset = hit_va - reinterpret_cast<std::uintptr_t>(module_base_address)`.
* **Multiple matches**: Only the **first** match is returned. If you need all matches, extend the implementation to continue scanning from `target_offset + 1`.
---
## See also
* `omath::PatternScanner` — raw buffer/iterator scanning with the same pattern grammar.
* `omath::Triangle`, `omath::Vector3` — math types used elsewhere in the library.
---
*Last updated: 31 Oct 2025*

View File

@@ -1,4 +1,32 @@
project(examples)
add_executable(ExampleProjectionMatrixBuilder example_proj_mat_builder.cpp)
target_link_libraries(ExampleProjectionMatrixBuilder PRIVATE omath::omath)
add_executable(example_projection_matrix_builder example_proj_mat_builder.cpp)
set_target_properties(example_projection_matrix_builder PROPERTIES
CXX_STANDARD 26
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
)
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
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
)
target_link_libraries(example_signature_scan PRIVATE omath::omath)
add_executable(example_glfw3 example_glfw3.cpp)
set_target_properties(example_glfw3 PROPERTIES CXX_STANDARD 26
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
)
find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(example_glfw3 PRIVATE omath::omath GLEW::GLEW glfw)

339
examples/example_glfw3.cpp Normal file
View File

@@ -0,0 +1,339 @@
// main.cpp
#include <cstdint>
#include <iostream>
#include <vector>
// --- OpenGL / windowing ---
#include <GL/glew.h> // GLEW must come before GLFW
#include <GLFW/glfw3.h>
// --- your math / engine stuff ---
#include "omath/3d_primitives/mesh.hpp"
#include "omath/engines/opengl_engine/camera.hpp"
#include "omath/engines/opengl_engine/constants.hpp"
#include "omath/engines/opengl_engine/mesh.hpp"
#include "omath/linear_algebra/vector3.hpp"
using omath::Vector3;
// ---------------- TYPE ALIASES (ADAPT TO YOUR LIB) ----------------
// Your 4x4 matrix type
using Mat4x4 = omath::opengl_engine::Mat4X4;
// Rotation angles for the Mesh
using RotationAngles = omath::opengl_engine::ViewAngles;
// For brevity, alias the templates instantiated with your types
using VertexType = omath::primitives::Vertex<Vector3<float>>;
using CubeMesh = omath::opengl_engine::Mesh;
using MyCamera = omath::opengl_engine::Camera;
// ---------------- SHADERS ----------------
static const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec3 aUv;
uniform mat4 uMVP;
uniform mat4 uModel;
out vec3 vNormal;
out vec3 vUv;
void main() {
vNormal = aNormal;
vUv = aUv;
gl_Position = uMVP * uModel * vec4(aPos, 1.0);
}
)";
static const char* fragmentShaderSource = R"(
#version 330 core
in vec3 vNormal;
in vec3 vUv;
out vec4 FragColor;
void main() {
vec3 baseColor = normalize(abs(vNormal));
FragColor = vec4(baseColor, 1.0);
}
)";
GLuint compileShader(GLenum type, const char* src)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, nullptr);
glCompileShader(shader);
GLint ok = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok)
{
char log[1024];
glGetShaderInfoLog(shader, sizeof(log), nullptr, log);
std::cerr << "Shader compile error: " << log << std::endl;
}
return shader;
}
GLuint createShaderProgram()
{
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
GLint ok = GL_FALSE;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok)
{
char log[1024];
glGetProgramInfoLog(prog, sizeof(log), nullptr, log);
std::cerr << "Program link error: " << log << std::endl;
}
glDeleteShader(vs);
glDeleteShader(fs);
return prog;
}
void framebuffer_size_callback(GLFWwindow* /*window*/, int w, int h)
{
glViewport(0, 0, w, h);
}
// ---------------- MAIN ----------------
int main()
{
// ---------- GLFW init ----------
if (!glfwInit())
{
std::cerr << "Failed to init GLFW\n";
return -1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
const int SCR_WIDTH = 800;
const int SCR_HEIGHT = 600;
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "omath cube + camera (GLEW)", nullptr, nullptr);
if (!window)
{
std::cerr << "Failed to create GLFW window\n";
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// ---------- GLEW init ----------
glewExperimental = GL_TRUE;
GLenum glewErr = glewInit();
if (glewErr != GLEW_OK)
{
std::cerr << "Failed to initialize GLEW: " << reinterpret_cast<const char*>(glewGetErrorString(glewErr))
<< "\n";
glfwTerminate();
return -1;
}
// ---------- GL state ----------
glEnable(GL_DEPTH_TEST);
// Face culling
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK); // cull back faces
glFrontFace(GL_CCW); // counter-clockwise is front
// ---------- Build Cube Mesh (CPU side) ----------
std::vector<VertexType> vbo;
vbo.reserve(8);
Vector3<float> p000{-0.5f, -0.5f, -0.5f};
Vector3<float> p001{-0.5f, -0.5f, 0.5f};
Vector3<float> p010{-0.5f, 0.5f, -0.5f};
Vector3<float> p011{-0.5f, 0.5f, 0.5f};
Vector3<float> p100{0.5f, -0.5f, -0.5f};
Vector3<float> p101{0.5f, -0.5f, 0.5f};
Vector3<float> p110{0.5f, 0.5f, -0.5f};
Vector3<float> p111{0.5f, 0.5f, 0.5f};
VertexType v0{p000, Vector3<float>{-1, -1, -1}, omath::Vector2<float>{0, 0}};
VertexType v1{p001, Vector3<float>{-1, -1, 1}, omath::Vector2<float>{0, 1}};
VertexType v2{p010, Vector3<float>{-1, 1, -1}, omath::Vector2<float>{1, 0}};
VertexType v3{p011, Vector3<float>{-1, 1, 1}, omath::Vector2<float>{1, 1}};
VertexType v4{p100, Vector3<float>{1, -1, -1}, omath::Vector2<float>{0, 0}};
VertexType v5{p101, Vector3<float>{1, -1, 1}, omath::Vector2<float>{0, 1}};
VertexType v6{p110, Vector3<float>{1, 1, -1}, omath::Vector2<float>{1, 0}};
VertexType v7{p111, Vector3<float>{1, 1, 1}, omath::Vector2<float>{1, 1}};
vbo.push_back(v0); // 0
vbo.push_back(v1); // 1
vbo.push_back(v2); // 2
vbo.push_back(v3); // 3
vbo.push_back(v4); // 4
vbo.push_back(v5); // 5
vbo.push_back(v6); // 6
vbo.push_back(v7); // 7
using Idx = Vector3<std::uint32_t>;
std::vector<Idx> ebo;
ebo.reserve(12);
// front (z+)
ebo.emplace_back(1, 5, 7);
ebo.emplace_back(1, 7, 3);
// back (z-)
ebo.emplace_back(0, 2, 6);
ebo.emplace_back(0, 6, 4);
// left (x-)
ebo.emplace_back(0, 1, 3);
ebo.emplace_back(0, 3, 2);
// right (x+)
ebo.emplace_back(4, 6, 7);
ebo.emplace_back(4, 7, 5);
// bottom (y-)
ebo.emplace_back(0, 4, 5);
ebo.emplace_back(0, 5, 1);
// top (y+)
ebo.emplace_back(2, 3, 7);
ebo.emplace_back(2, 7, 6);
CubeMesh cube{std::move(vbo), std::move(ebo)};
cube.set_origin({0.f, 0.f, 0.f});
cube.set_scale({2.f, 2.f, 2.f});
cube.set_rotation(RotationAngles{});
// ---------- OpenGL buffers ----------
GLuint VAO = 0, VBO = 0, EBO_GL = 0;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO_GL);
glBindVertexArray(VAO);
// upload vertex buffer
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, cube.m_vertex_buffer.size() * sizeof(VertexType), cube.m_vertex_buffer.data(),
GL_STATIC_DRAW);
// flatten EBO to GL indices
std::vector<GLuint> flatIndices;
flatIndices.reserve(cube.m_vertex_array_object.size() * 3);
for (const auto& tri : cube.m_vertex_array_object)
{
flatIndices.push_back(tri.x);
flatIndices.push_back(tri.y);
flatIndices.push_back(tri.z);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO_GL);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, flatIndices.size() * sizeof(GLuint), flatIndices.data(), GL_STATIC_DRAW);
// vertex layout: position / normal / uv (each Vector3<float>)
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, normal));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(VertexType), (void*)offsetof(VertexType, uv));
glBindVertexArray(0);
// ---------- Camera setup ----------
omath::projection::ViewPort viewPort{static_cast<float>(SCR_WIDTH), static_cast<float>(SCR_HEIGHT)};
Vector3<float> camPos{0.f, 0.f, 3.f};
float nearPlane = 0.1f;
float farPlane = 100.f;
auto fov = omath::projection::FieldOfView::from_degrees(90.f);
MyCamera camera{camPos, {}, viewPort, fov, nearPlane, farPlane};
// ---------- Shader ----------
GLuint shaderProgram = createShaderProgram();
GLint uMvpLoc = glGetUniformLocation(shaderProgram, "uMVP");
GLint uModel = glGetUniformLocation(shaderProgram, "uModel");
static float old_frame_time = glfwGetTime();
// ---------- Main loop ----------
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
float currentTime = glfwGetTime();
float deltaTime = currentTime - old_frame_time;
old_frame_time = currentTime;
int fbW = 0, fbH = 0;
glfwGetFramebufferSize(window, &fbW, &fbH);
glViewport(0, 0, fbW, fbH);
viewPort.m_width = static_cast<float>(fbW);
viewPort.m_height = static_cast<float>(fbH);
camera.set_view_port(viewPort);
glClearColor(0.1f, 0.15f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
RotationAngles rot = cube.get_rotation_angles();
rot.yaw += omath::opengl_engine::YawAngle ::from_degrees(40.f * deltaTime);
rot.roll += omath::opengl_engine::RollAngle::from_degrees(40.f * deltaTime);
if (rot.pitch.as_degrees() == 90.f)
rot.pitch = omath::opengl_engine::PitchAngle::from_degrees(-90.f);
rot.pitch += omath::opengl_engine::PitchAngle::from_degrees(40.f * deltaTime);
cube.set_rotation(rot);
const Mat4x4& viewProj = camera.get_view_projection_matrix();
const auto& model = cube.get_to_world_matrix();
glUseProgram(shaderProgram);
// Send matrices to GPU
const float* mvpPtr = viewProj.raw_array().data();
const float* modelPtr = model.raw_array().data();
glUniformMatrix4fv(uMvpLoc, 1, GL_FALSE, mvpPtr);
glUniformMatrix4fv(uModel, 1, GL_FALSE, modelPtr);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(flatIndices.size()), GL_UNSIGNED_INT, nullptr);
glfwSwapBuffers(window);
}
// ---------- Cleanup ----------
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO_GL);
glDeleteProgram(shaderProgram);
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

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;
}

View File

@@ -1,9 +0,0 @@
if (OMATH_BUILD_TESTS)
add_subdirectory(googletest)
endif ()
if (OMATH_BUILD_BENCHMARK)
set(BENCHMARK_ENABLE_TESTING OFF)
add_subdirectory(benchmark)
endif ()

Some files were not shown because too many files have changed in this diff Show More