diff --git a/docs/engines/frostbite/camera_trait.md b/docs/engines/frostbite/camera_trait.md new file mode 100644 index 0000000..bb03cd0 --- /dev/null +++ b/docs/engines/frostbite/camera_trait.md @@ -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 project’s 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 trait’s 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& cam_origin, + const Vector3& look_at) noexcept; + + // Build view matrix for given angles and origin. + static Mat4X4 + calc_view_matrix(const ViewAngles& angles, + const Vector3& 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`, `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 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 Frostbite math headers +using Angs = ViewAngles; // pitch/yaw/roll type +using FBcam = omath::projection::Camera; + +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 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). + +--- + +## 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). diff --git a/docs/engines/frostbite/constants.md b/docs/engines/frostbite/constants.md new file mode 100644 index 0000000..61d1fbe --- /dev/null +++ b/docs/engines/frostbite/constants.md @@ -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 codebase’s conventions. + +5. **`Mat1X3` as a “row vector”** + +* If you actually use it as a 3-component vector, consider just `Vector3` (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 +#include + +namespace omath::frostbite_engine +{ + // Inline to avoid ODR across translation units + inline constexpr Vector3 k_abs_up = {0.0f, 1.0f, 0.0f}; + inline constexpr Vector3 k_abs_right = {1.0f, 0.0f, 0.0f}; + inline constexpr Vector3 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; + using YawAngle = Angle; + using RollAngle = Angle; + + using ViewAngles = omath::ViewAngles; +} // 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. diff --git a/docs/engines/frostbite/formulas.md b/docs/engines/frostbite/formulas.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/engines/frostbite/pred_engine_trait.md b/docs/engines/frostbite/pred_engine_trait.md new file mode 100644 index 0000000..a5b7751 --- /dev/null +++ b/docs/engines/frostbite/pred_engine_trait.md @@ -0,0 +1,105 @@ +// Created by Vlad on 8/6/2025. +#pragma once + +#include // sqrt, hypot, tan, asin, atan2 +#include + +#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 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 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 predict_target_position( + const projectile_prediction::Target& target, + float time, float gravity) noexcept + { + Vector3 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& 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& 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 calc_viewpoint_from_angles( + const projectile_prediction::Projectile& projectile, + const Vector3& predicted_target_position, + const std::optional 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& origin, + const Vector3& 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& origin, + const Vector3& 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 diff --git a/docs/styles/custom-header.css b/docs/styles/custom-header.css new file mode 100644 index 0000000..f3b6931 --- /dev/null +++ b/docs/styles/custom-header.css @@ -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; +} diff --git a/mkdocs.yml b/mkdocs.yml index 2534924..cabaeb5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,4 +2,5 @@ site_name: OMATH Docs theme: name: darkly extra_css: - - styles/center.css \ No newline at end of file + - styles/center.css + - styles/custom-header.css \ No newline at end of file