mirror of
https://github.com/orange-cpp/omath.git
synced 2026-06-09 16:54:35 +00:00
Compare commits
2 Commits
v5.2.1
...
feature/air
| Author | SHA1 | Date | |
|---|---|---|---|
| 92f5378a70 | |||
| dc56400927 |
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/engines/rage_engine/constants.hpp"
|
||||
#include "omath/projection/camera.hpp"
|
||||
#include "traits/camera_trait.hpp"
|
||||
|
||||
namespace omath::rage_engine
|
||||
{
|
||||
using Camera = projection::Camera<Mat4X4, ViewAngles, CameraTrait, NDCDepthRange::ZERO_TO_ONE>;
|
||||
} // namespace omath::rage_engine
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
|
||||
#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::rage_engine
|
||||
{
|
||||
constexpr Vector3<float> k_abs_up = {0, 0, 1};
|
||||
constexpr Vector3<float> k_abs_right = {1, 0, 0};
|
||||
constexpr Vector3<float> k_abs_forward = {0, 1, 0};
|
||||
|
||||
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.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>;
|
||||
} // namespace omath::rage_engine
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/engines/rage_engine/constants.hpp"
|
||||
#include <type_traits>
|
||||
|
||||
namespace omath::rage_engine
|
||||
{
|
||||
[[nodiscard]]
|
||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept;
|
||||
|
||||
[[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<float> extract_origin(const Mat4X4& mat) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<float> extract_scale(const Mat4X4& mat) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
Mat4X4 calc_perspective_projection_matrix(float field_of_view, float aspect_ratio, float near, float far,
|
||||
NDCDepthRange ndc_depth_range = NDCDepthRange::ZERO_TO_ONE) noexcept;
|
||||
|
||||
template<class FloatingType>
|
||||
requires std::is_floating_point_v<FloatingType>
|
||||
[[nodiscard]]
|
||||
constexpr FloatingType units_to_centimeters(const FloatingType& units)
|
||||
{
|
||||
return units / static_cast<FloatingType>(100);
|
||||
}
|
||||
|
||||
template<class FloatingType>
|
||||
requires std::is_floating_point_v<FloatingType>
|
||||
[[nodiscard]]
|
||||
constexpr FloatingType units_to_meters(const FloatingType& units)
|
||||
{
|
||||
return units;
|
||||
}
|
||||
|
||||
template<class FloatingType>
|
||||
requires std::is_floating_point_v<FloatingType>
|
||||
[[nodiscard]]
|
||||
constexpr FloatingType units_to_kilometers(const FloatingType& units)
|
||||
{
|
||||
return units_to_meters(units) / static_cast<FloatingType>(1000);
|
||||
}
|
||||
|
||||
template<class FloatingType>
|
||||
requires std::is_floating_point_v<FloatingType>
|
||||
[[nodiscard]]
|
||||
constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
|
||||
{
|
||||
return centimeters * static_cast<FloatingType>(100);
|
||||
}
|
||||
|
||||
template<class FloatingType>
|
||||
requires std::is_floating_point_v<FloatingType>
|
||||
[[nodiscard]]
|
||||
constexpr FloatingType meters_to_units(const FloatingType& meters)
|
||||
{
|
||||
return meters;
|
||||
}
|
||||
|
||||
template<class FloatingType>
|
||||
requires std::is_floating_point_v<FloatingType>
|
||||
[[nodiscard]]
|
||||
constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
|
||||
{
|
||||
return meters_to_units(kilometers * static_cast<FloatingType>(1000));
|
||||
}
|
||||
} // namespace omath::rage_engine
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "constants.hpp"
|
||||
#include "omath/3d_primitives/mesh.hpp"
|
||||
#include "traits/mesh_trait.hpp"
|
||||
|
||||
namespace omath::rage_engine
|
||||
{
|
||||
using Mesh = primitives::Mesh<Mat4X4, ViewAngles, MeshTrait>;
|
||||
} // namespace omath::rage_engine
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/engines/rage_engine/formulas.hpp"
|
||||
#include "omath/projection/camera.hpp"
|
||||
|
||||
namespace omath::rage_engine
|
||||
{
|
||||
class CameraTrait final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static ViewAngles calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept;
|
||||
[[nodiscard]]
|
||||
static Mat4X4 calc_projection_matrix(const projection::FieldOfView& fov, const projection::ViewPort& view_port,
|
||||
float near, float far, NDCDepthRange ndc_depth_range) noexcept;
|
||||
};
|
||||
|
||||
} // namespace omath::rage_engine
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <omath/engines/rage_engine/constants.hpp>
|
||||
#include <omath/engines/rage_engine/formulas.hpp>
|
||||
|
||||
namespace omath::rage_engine
|
||||
{
|
||||
class MeshTrait final
|
||||
{
|
||||
public:
|
||||
[[nodiscard]]
|
||||
static Mat4X4 rotation_matrix(const ViewAngles& rotation)
|
||||
{
|
||||
return rage_engine::rotation_matrix(rotation);
|
||||
}
|
||||
};
|
||||
} // namespace omath::rage_engine
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "omath/engines/rage_engine/formulas.hpp"
|
||||
#include "omath/projectile_prediction/projectile.hpp"
|
||||
#include "omath/projectile_prediction/target.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace omath::rage_engine
|
||||
{
|
||||
class PredEngineTrait final
|
||||
{
|
||||
public:
|
||||
constexpr static Vector3<float>
|
||||
predict_projectile_position(const projectile_prediction::Projectile<float>& projectile, const float pitch,
|
||||
const float yaw, const float time, const float gravity) noexcept
|
||||
{
|
||||
const auto launch_pos = projectile.m_origin + projectile.m_launch_offset;
|
||||
auto current_pos = launch_pos
|
||||
+ forward_vector({PitchAngle::from_degrees(-pitch), YawAngle::from_degrees(yaw),
|
||||
RollAngle::from_degrees(0)})
|
||||
* projectile.m_launch_speed * time;
|
||||
current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
|
||||
|
||||
return current_pos;
|
||||
}
|
||||
[[nodiscard]]
|
||||
static constexpr Vector3<float> predict_target_position(const projectile_prediction::Target<float>& target,
|
||||
const float time, const float gravity) noexcept
|
||||
{
|
||||
auto predicted = target.m_origin + target.m_velocity * time;
|
||||
|
||||
if (target.m_is_airborne)
|
||||
predicted.z -= gravity * (time * time) * 0.5f;
|
||||
|
||||
return predicted;
|
||||
}
|
||||
[[nodiscard]]
|
||||
static float calc_vector_2d_distance(const Vector3<float>& delta) noexcept
|
||||
{
|
||||
return std::sqrt(delta.x * delta.x + delta.y * delta.y);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
|
||||
{
|
||||
return vec.z;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static Vector3<float> calc_viewpoint_from_angles(const projectile_prediction::Projectile<float>& projectile,
|
||||
Vector3<float> predicted_target_position,
|
||||
const std::optional<float> projectile_pitch) noexcept
|
||||
{
|
||||
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
|
||||
const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value()));
|
||||
|
||||
return {predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height};
|
||||
}
|
||||
[[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.z));
|
||||
}
|
||||
[[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.y));
|
||||
};
|
||||
};
|
||||
} // namespace omath::rage_engine
|
||||
@@ -27,7 +27,7 @@ namespace omath::hud
|
||||
const Color& tint = Color{1.f, 1.f, 1.f, 1.f}) override;
|
||||
void add_text(const Vector2<float>& position, const Color& color, const std::string_view& text) override;
|
||||
[[nodiscard]]
|
||||
virtual Vector2<float> calc_text_size(const std::string_view& text) override;
|
||||
Vector2<float> calc_text_size(const std::string_view& text) override;
|
||||
};
|
||||
} // namespace omath::hud
|
||||
#endif // OMATH_IMGUI_INTEGRATION
|
||||
@@ -87,6 +87,12 @@
|
||||
#include "omath/engines/frostbite_engine/traits/camera_trait.hpp"
|
||||
#include "omath/engines/frostbite_engine/traits/pred_engine_trait.hpp"
|
||||
|
||||
// RAGE Engine
|
||||
#include "omath/engines/rage_engine/constants.hpp"
|
||||
#include "omath/engines/rage_engine/formulas.hpp"
|
||||
#include "omath/engines/rage_engine/camera.hpp"
|
||||
#include "omath/engines/rage_engine/traits/camera_trait.hpp"
|
||||
#include "omath/engines/rage_engine/traits/pred_engine_trait.hpp"
|
||||
|
||||
// Unreal Engine
|
||||
#include "omath/engines/unreal_engine/constants.hpp"
|
||||
@@ -101,4 +107,4 @@
|
||||
|
||||
// Utility
|
||||
#include "omath/utility/pattern_scan.hpp"
|
||||
#include "omath/utility/pe_pattern_scan.hpp"
|
||||
#include "omath/utility/pe_pattern_scan.hpp"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "omath/projectile_prediction/proj_pred_engine.hpp"
|
||||
#include "omath/projectile_prediction/projectile.hpp"
|
||||
#include "omath/projectile_prediction/target.hpp"
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
|
||||
namespace omath::projectile_prediction
|
||||
@@ -96,7 +97,7 @@ namespace omath::projectile_prediction
|
||||
EngineTrait::predict_target_position(target, time, m_gravity_constant);
|
||||
|
||||
const auto projectile_pitch =
|
||||
maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position);
|
||||
maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position, time);
|
||||
|
||||
if (!projectile_pitch.has_value()) [[unlikely]]
|
||||
continue;
|
||||
@@ -130,7 +131,20 @@ namespace omath::projectile_prediction
|
||||
[[nodiscard]]
|
||||
std::optional<ArithmeticType>
|
||||
maybe_calculate_projectile_launch_pitch_angle(const Projectile<ArithmeticType>& projectile,
|
||||
const Vector3<ArithmeticType>& target_position) const noexcept
|
||||
const Vector3<ArithmeticType>& target_position,
|
||||
const ArithmeticType time) const noexcept
|
||||
{
|
||||
if (projectile.m_air_friction > ArithmeticType{0})
|
||||
return maybe_calculate_projectile_launch_pitch_angle_with_air_friction(projectile, target_position,
|
||||
time);
|
||||
|
||||
return maybe_calculate_projectile_launch_pitch_angle_without_air_friction(projectile, target_position);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::optional<ArithmeticType> maybe_calculate_projectile_launch_pitch_angle_without_air_friction(
|
||||
const Projectile<ArithmeticType>& projectile,
|
||||
const Vector3<ArithmeticType>& target_position) const noexcept
|
||||
{
|
||||
const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
|
||||
|
||||
@@ -160,6 +174,31 @@ namespace omath::projectile_prediction
|
||||
return angles::radians_to_degrees(angle);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::optional<ArithmeticType>
|
||||
maybe_calculate_projectile_launch_pitch_angle_with_air_friction(const Projectile<ArithmeticType>& projectile,
|
||||
const Vector3<ArithmeticType>& target_position,
|
||||
const ArithmeticType time) const noexcept
|
||||
{
|
||||
if (time <= ArithmeticType{0})
|
||||
return std::nullopt;
|
||||
|
||||
const auto launch_origin = projectile.m_origin + projectile.m_launch_offset;
|
||||
const auto velocity_factor = calculate_air_friction_velocity_factor(projectile.m_air_friction, time);
|
||||
|
||||
if (velocity_factor == ArithmeticType{0}) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
const auto gravity_acceleration = calculate_projectile_gravity_acceleration(projectile);
|
||||
const auto gravity_displacement =
|
||||
gravity_acceleration * ((time - velocity_factor) / projectile.m_air_friction);
|
||||
const auto required_velocity = (target_position - launch_origin - gravity_displacement) / velocity_factor;
|
||||
const auto horizontal_speed = EngineTrait::calc_vector_2d_distance(required_velocity);
|
||||
const auto vertical_speed = EngineTrait::get_vector_height_coordinate(required_velocity);
|
||||
|
||||
return angles::radians_to_degrees(std::atan2(vertical_speed, horizontal_speed));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool is_projectile_reached_target(const Vector3<ArithmeticType>& target_position,
|
||||
const Projectile<ArithmeticType>& projectile,
|
||||
@@ -167,10 +206,65 @@ namespace omath::projectile_prediction
|
||||
{
|
||||
const auto yaw = EngineTrait::calc_direct_yaw_angle(
|
||||
projectile.m_origin + projectile.m_launch_offset, target_position);
|
||||
const auto projectile_position =
|
||||
EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);
|
||||
const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time);
|
||||
|
||||
return projectile_position.distance_to(target_position) <= m_distance_tolerance;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<ArithmeticType> predict_projectile_position(const Projectile<ArithmeticType>& projectile,
|
||||
const ArithmeticType pitch, const ArithmeticType yaw,
|
||||
const ArithmeticType time) const noexcept
|
||||
{
|
||||
if (projectile.m_air_friction <= ArithmeticType{0})
|
||||
return EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);
|
||||
|
||||
const auto launch_origin = projectile.m_origin + projectile.m_launch_offset;
|
||||
const auto launch_velocity = calculate_projectile_launch_velocity(projectile, pitch, yaw);
|
||||
const auto gravity_acceleration = calculate_projectile_gravity_acceleration(projectile);
|
||||
const auto velocity_factor = calculate_air_friction_velocity_factor(projectile.m_air_friction, time);
|
||||
const auto gravity_factor = (time - velocity_factor) / projectile.m_air_friction;
|
||||
|
||||
return launch_origin + launch_velocity * velocity_factor + gravity_acceleration * gravity_factor;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static ArithmeticType calculate_air_friction_velocity_factor(const ArithmeticType air_friction,
|
||||
const ArithmeticType time) noexcept
|
||||
{
|
||||
return -std::expm1(-air_friction * time) / air_friction;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<ArithmeticType> calculate_projectile_launch_velocity(const Projectile<ArithmeticType>& projectile,
|
||||
const ArithmeticType pitch,
|
||||
const ArithmeticType yaw) const noexcept
|
||||
{
|
||||
const auto launch_origin = projectile.m_origin + projectile.m_launch_offset;
|
||||
auto projectile_without_air_friction = projectile;
|
||||
projectile_without_air_friction.m_air_friction = ArithmeticType{0};
|
||||
|
||||
return EngineTrait::predict_projectile_position(projectile_without_air_friction, pitch, yaw,
|
||||
ArithmeticType{1}, ArithmeticType{0})
|
||||
- launch_origin;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3<ArithmeticType>
|
||||
calculate_projectile_gravity_acceleration(const Projectile<ArithmeticType>& projectile) const noexcept
|
||||
{
|
||||
constexpr ArithmeticType test_time = ArithmeticType{1};
|
||||
constexpr ArithmeticType acceleration_multiplier = ArithmeticType{2};
|
||||
auto projectile_without_air_friction = projectile;
|
||||
projectile_without_air_friction.m_air_friction = ArithmeticType{0};
|
||||
const auto no_gravity_position = EngineTrait::predict_projectile_position(
|
||||
projectile_without_air_friction, ArithmeticType{0}, ArithmeticType{0}, test_time,
|
||||
ArithmeticType{0});
|
||||
const auto with_gravity_position = EngineTrait::predict_projectile_position(
|
||||
projectile_without_air_friction, ArithmeticType{0}, ArithmeticType{0}, test_time,
|
||||
m_gravity_constant);
|
||||
|
||||
return (with_gravity_position - no_gravity_position) * acceleration_multiplier;
|
||||
}
|
||||
};
|
||||
} // namespace omath::projectile_prediction
|
||||
|
||||
@@ -15,5 +15,6 @@ namespace omath::projectile_prediction
|
||||
Vector3<ArithmeticType> m_launch_offset{};
|
||||
ArithmeticType m_launch_speed{};
|
||||
ArithmeticType m_gravity_scale{};
|
||||
ArithmeticType m_air_friction{};
|
||||
};
|
||||
} // namespace omath::projectile_prediction
|
||||
} // namespace omath::projectile_prediction
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
#include "omath/engines/rage_engine/formulas.hpp"
|
||||
|
||||
namespace omath::rage_engine
|
||||
{
|
||||
Vector3<float> forward_vector(const ViewAngles& angles) noexcept
|
||||
{
|
||||
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_forward);
|
||||
|
||||
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
||||
}
|
||||
Vector3<float> right_vector(const ViewAngles& angles) noexcept
|
||||
{
|
||||
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_right);
|
||||
|
||||
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
||||
}
|
||||
Vector3<float> up_vector(const ViewAngles& angles) noexcept
|
||||
{
|
||||
const auto vec = rotation_matrix(angles) * mat_column_from_vector(k_abs_up);
|
||||
|
||||
return {vec.at(0, 0), vec.at(1, 0), vec.at(2, 0)};
|
||||
}
|
||||
Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
|
||||
{
|
||||
return mat_camera_view<float, MatStoreType::ROW_MAJOR>(forward_vector(angles), right_vector(angles),
|
||||
up_vector(angles), cam_origin);
|
||||
}
|
||||
Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
|
||||
{
|
||||
return mat_rotation_axis_z<float, MatStoreType::ROW_MAJOR>(angles.yaw)
|
||||
* mat_rotation_axis_y<float, MatStoreType::ROW_MAJOR>(angles.roll)
|
||||
* mat_rotation_axis_x<float, MatStoreType::ROW_MAJOR>(angles.pitch);
|
||||
}
|
||||
|
||||
Vector3<float> extract_origin(const Mat4X4& mat) noexcept
|
||||
{
|
||||
return mat_extract_origin(mat);
|
||||
}
|
||||
|
||||
Vector3<float> extract_scale(const Mat4X4& mat) noexcept
|
||||
{
|
||||
return mat_extract_scale(mat);
|
||||
}
|
||||
|
||||
ViewAngles extract_rotation_angles(const Mat4X4& mat) noexcept
|
||||
{
|
||||
const auto angles = mat_extract_rotation_zyx(mat);
|
||||
return {
|
||||
PitchAngle::from_degrees(angles.x),
|
||||
YawAngle::from_degrees(angles.z),
|
||||
RollAngle::from_degrees(angles.y),
|
||||
};
|
||||
}
|
||||
|
||||
Mat4X4 calc_perspective_projection_matrix(const float field_of_view, const float aspect_ratio, const float near,
|
||||
const float far, const NDCDepthRange ndc_depth_range) noexcept
|
||||
{
|
||||
if (ndc_depth_range == NDCDepthRange::ZERO_TO_ONE)
|
||||
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR, NDCDepthRange::ZERO_TO_ONE>(
|
||||
field_of_view, aspect_ratio, near, far);
|
||||
|
||||
if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
|
||||
return mat_perspective_left_handed_vertical_fov<float, MatStoreType::ROW_MAJOR,
|
||||
NDCDepthRange::NEGATIVE_ONE_TO_ONE>(
|
||||
field_of_view, aspect_ratio, near, far);
|
||||
std::unreachable();
|
||||
}
|
||||
} // namespace omath::rage_engine
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
#include "omath/engines/rage_engine/traits/camera_trait.hpp"
|
||||
|
||||
namespace omath::rage_engine
|
||||
{
|
||||
|
||||
ViewAngles CameraTrait::calc_look_at_angle(const Vector3<float>& cam_origin, const Vector3<float>& look_at) noexcept
|
||||
{
|
||||
const auto direction = (look_at - cam_origin).normalized();
|
||||
|
||||
return {PitchAngle::from_radians(std::asin(direction.z)),
|
||||
YawAngle::from_radians(-std::atan2(direction.x, direction.y)), RollAngle::from_radians(0.f)};
|
||||
}
|
||||
Mat4X4 CameraTrait::calc_view_matrix(const ViewAngles& angles, const Vector3<float>& cam_origin) noexcept
|
||||
{
|
||||
return rage_engine::calc_view_matrix(angles, cam_origin);
|
||||
}
|
||||
Mat4X4 CameraTrait::calc_projection_matrix(const projection::FieldOfView& fov,
|
||||
const projection::ViewPort& view_port, const float near, const float far,
|
||||
const NDCDepthRange ndc_depth_range) noexcept
|
||||
{
|
||||
return calc_perspective_projection_matrix(fov.as_degrees(), view_port.aspect_ratio(), near, far,
|
||||
ndc_depth_range);
|
||||
}
|
||||
} // namespace omath::rage_engine
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <omath/engines/frostbite_engine/camera.hpp>
|
||||
#include <omath/engines/iw_engine/camera.hpp>
|
||||
#include <omath/engines/opengl_engine/camera.hpp>
|
||||
#include <omath/engines/rage_engine/camera.hpp>
|
||||
#include <omath/engines/source_engine/camera.hpp>
|
||||
#include <omath/engines/unity_engine/camera.hpp>
|
||||
#include <omath/engines/unreal_engine/camera.hpp>
|
||||
@@ -181,6 +182,12 @@ namespace
|
||||
using ViewAngles = omath::source_engine::ViewAngles;
|
||||
using Camera = omath::source_engine::Camera;
|
||||
};
|
||||
struct RageEngineTraits
|
||||
{
|
||||
using PitchAngle = omath::rage_engine::PitchAngle;
|
||||
using ViewAngles = omath::rage_engine::ViewAngles;
|
||||
using Camera = omath::rage_engine::Camera;
|
||||
};
|
||||
struct UnityEngineTraits
|
||||
{
|
||||
using PitchAngle = omath::unity_engine::PitchAngle;
|
||||
@@ -254,6 +261,7 @@ namespace omath::lua
|
||||
register_engine<FrostbiteEngineTraits>(omath_table, "frostbite");
|
||||
register_engine<IWEngineTraits>(omath_table, "iw");
|
||||
register_engine<SourceEngineTraits>(omath_table, "source");
|
||||
register_engine<RageEngineTraits>(omath_table, "rage");
|
||||
register_engine<UnityEngineTraits>(omath_table, "unity");
|
||||
register_engine<UnrealEngineTraits, double>(omath_table, "unreal");
|
||||
register_engine<CryEngineTraits>(omath_table, "cry");
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Created by Codex on 6/3/2026.
|
||||
//
|
||||
#include <gtest/gtest.h>
|
||||
#include <omath/engines/rage_engine/camera.hpp>
|
||||
#include <omath/engines/rage_engine/constants.hpp>
|
||||
#include <omath/engines/rage_engine/formulas.hpp>
|
||||
#include <omath/engines/rage_engine/traits/mesh_trait.hpp>
|
||||
#include <omath/engines/rage_engine/traits/pred_engine_trait.hpp>
|
||||
#include <omath/projectile_prediction/projectile.hpp>
|
||||
#include <ranges>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace omath;
|
||||
|
||||
static_assert(std::is_same_v<rage_engine::Mat4X4::ContainedType, float>);
|
||||
|
||||
static void expect_rage_vector_near(const Vector3<float>& actual, const Vector3<float>& expected)
|
||||
{
|
||||
for (const auto& [result, etalon] : std::views::zip(actual.as_array(), expected.as_array()))
|
||||
EXPECT_NEAR(result, etalon, 0.0001f);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, ForwardVector)
|
||||
{
|
||||
const auto forward = rage_engine::forward_vector({});
|
||||
|
||||
EXPECT_EQ(forward, rage_engine::k_abs_forward);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, RightVector)
|
||||
{
|
||||
const auto right = rage_engine::right_vector({});
|
||||
|
||||
EXPECT_EQ(right, rage_engine::k_abs_right);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, UpVector)
|
||||
{
|
||||
const auto up = rage_engine::up_vector({});
|
||||
EXPECT_EQ(up, rage_engine::k_abs_up);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, LookAtForward)
|
||||
{
|
||||
const auto angles = rage_engine::CameraTrait::calc_look_at_angle({}, rage_engine::k_abs_forward);
|
||||
|
||||
expect_rage_vector_near(rage_engine::forward_vector(angles), rage_engine::k_abs_forward);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, LookAtRight)
|
||||
{
|
||||
const auto angles = rage_engine::CameraTrait::calc_look_at_angle({}, rage_engine::k_abs_right);
|
||||
|
||||
expect_rage_vector_near(rage_engine::forward_vector(angles), rage_engine::k_abs_right);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, LookAtUp)
|
||||
{
|
||||
const auto angles = rage_engine::CameraTrait::calc_look_at_angle({}, rage_engine::k_abs_up);
|
||||
|
||||
expect_rage_vector_near(rage_engine::forward_vector(angles), rage_engine::k_abs_up);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, ProjectTargetMovedFromCamera)
|
||||
{
|
||||
constexpr auto fov = projection::FieldOfView::from_degrees(60.f);
|
||||
const auto cam = rage_engine::Camera({0, 0, 0}, {}, {1280.f, 720.f}, fov, 0.01f, 1000.f);
|
||||
|
||||
const auto projected = cam.world_to_screen({0, 10.f, 0});
|
||||
|
||||
ASSERT_TRUE(projected.has_value());
|
||||
EXPECT_NEAR(projected->x, 640.f, 0.0001f);
|
||||
EXPECT_NEAR(projected->y, 360.f, 0.0001f);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, PredEngineTraitUsesZAsHeight)
|
||||
{
|
||||
projectile_prediction::Projectile<float> projectile;
|
||||
projectile.m_origin = {0.f, 0.f, 0.f};
|
||||
projectile.m_launch_speed = 10.f;
|
||||
projectile.m_gravity_scale = 1.f;
|
||||
|
||||
const auto pos = rage_engine::PredEngineTrait::predict_projectile_position(projectile, 0.f, 0.f, 1.f, 9.81f);
|
||||
|
||||
EXPECT_NEAR(pos.x, 0.f, 0.0001f);
|
||||
EXPECT_NEAR(pos.y, 10.f, 0.0001f);
|
||||
EXPECT_NEAR(pos.z, -9.81f * 0.5f, 0.0001f);
|
||||
EXPECT_NEAR(rage_engine::PredEngineTrait::get_vector_height_coordinate({1.f, 2.f, 3.f}), 3.f, 0.0001f);
|
||||
}
|
||||
|
||||
TEST(unit_test_rage_engine, MeshTraitForwardsRotationMatrix)
|
||||
{
|
||||
const rage_engine::ViewAngles angles{
|
||||
rage_engine::PitchAngle::from_degrees(20.f),
|
||||
rage_engine::YawAngle::from_degrees(-35.f),
|
||||
rage_engine::RollAngle::from_degrees(15.f),
|
||||
};
|
||||
|
||||
EXPECT_EQ(rage_engine::MeshTrait::rotation_matrix(angles), rage_engine::rotation_matrix(angles));
|
||||
}
|
||||
@@ -283,6 +283,34 @@ TEST(ProjectileSimulation, HitsNegativeYawTarget_WithOffset)
|
||||
expect_projectile_hits_target(proj, target, 400, 1.f / 1000.f, 50, 5.f, 10.f);
|
||||
}
|
||||
|
||||
TEST(ProjectileSimulation, HitsStaticTarget_WithAirFriction)
|
||||
{
|
||||
constexpr Target target{
|
||||
.m_origin = {75, 0, 0}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||
constexpr Projectile proj = {
|
||||
.m_origin = {0, 0, 0}, .m_launch_speed = 100.f, .m_gravity_scale = 0.f, .m_air_friction = 0.5f};
|
||||
|
||||
const Engine engine(0.f, 1.f / 1000.f, 10.f, 0.1f);
|
||||
const auto aim_angles = engine.maybe_calculate_aim_angles(proj, target);
|
||||
|
||||
ASSERT_TRUE(aim_angles.has_value());
|
||||
EXPECT_NEAR(aim_angles->pitch, 0.f, 0.1f);
|
||||
EXPECT_NEAR(aim_angles->yaw, 0.f, 0.1f);
|
||||
}
|
||||
|
||||
TEST(ProjectileSimulation, AirFrictionLimitsMaximumReach)
|
||||
{
|
||||
constexpr Target target{
|
||||
.m_origin = {150, 0, 0}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||
constexpr Projectile proj = {
|
||||
.m_origin = {0, 0, 0}, .m_launch_speed = 100.f, .m_gravity_scale = 0.f, .m_air_friction = 1.f};
|
||||
|
||||
const Engine engine(0.f, 1.f / 1000.f, 10.f, 0.1f);
|
||||
|
||||
EXPECT_FALSE(engine.maybe_calculate_aim_point(proj, target).has_value());
|
||||
EXPECT_FALSE(engine.maybe_calculate_aim_angles(proj, target).has_value());
|
||||
}
|
||||
|
||||
TEST(UnitTestPrediction, AimAnglesReturnsNulloptWhenNoSolution)
|
||||
{
|
||||
constexpr Target target{
|
||||
|
||||
Reference in New Issue
Block a user