diff --git a/README.md b/README.md
index 9b7cf852..695344d8 100644
--- a/README.md
+++ b/README.md
@@ -81,7 +81,7 @@ if (auto screen = camera.world_to_screen(world_position)) {
- **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, CryEngine and canonical OpenGL**.
+- **Engine support**: Supports coordinate systems of **Source, Rage, Unity, Unreal, Frostbite, IWEngine, CryEngine and canonical OpenGL**.
- **Cross platform**: Supports Windows, MacOS and Linux.
- **Algorithms**: Has ability to scan for byte pattern with wildcards in ELF/Mach-O/PE files/modules, binary slices, works even with Wine apps.
- **Scripting**: Supports to make scripts in Lua out of box.
@@ -113,6 +113,10 @@ if (auto screen = camera.world_to_screen(world_position)) {
+![GTA5 Preview]
+
+
+
![OpenGL Preview]
@@ -144,6 +148,7 @@ if (auto screen = camera.world_to_screen(world_position)) {
[BO2 Preview]: docs/images/showcase/cod_bo2.png
[CS2 Preview]: docs/images/showcase/cs2.jpeg
[TF2 Preview]: docs/images/showcase/tf2.jpg
+[GTA5 Preview]: https://i.imgur.com/W7T8RhZ.png
[OpenGL Preview]: docs/images/showcase/opengl.png
[QUICKSTART]: docs/getting_started.md
diff --git a/include/omath/engines/rage_engine/camera.hpp b/include/omath/engines/rage_engine/camera.hpp
new file mode 100644
index 00000000..2000bdc3
--- /dev/null
+++ b/include/omath/engines/rage_engine/camera.hpp
@@ -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;
+} // namespace omath::rage_engine
diff --git a/include/omath/engines/rage_engine/constants.hpp b/include/omath/engines/rage_engine/constants.hpp
new file mode 100644
index 00000000..b60860bf
--- /dev/null
+++ b/include/omath/engines/rage_engine/constants.hpp
@@ -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
+#include
+
+namespace omath::rage_engine
+{
+ constexpr Vector3 k_abs_up = {0, 0, 1};
+ constexpr Vector3 k_abs_right = {1, 0, 0};
+ constexpr Vector3 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;
+ using YawAngle = Angle;
+ using RollAngle = Angle;
+
+ using ViewAngles = omath::ViewAngles;
+} // namespace omath::rage_engine
diff --git a/include/omath/engines/rage_engine/formulas.hpp b/include/omath/engines/rage_engine/formulas.hpp
new file mode 100644
index 00000000..a0a888c7
--- /dev/null
+++ b/include/omath/engines/rage_engine/formulas.hpp
@@ -0,0 +1,85 @@
+//
+// Created by Codex on 6/3/2026.
+//
+
+#pragma once
+#include "omath/engines/rage_engine/constants.hpp"
+#include
+
+namespace omath::rage_engine
+{
+ [[nodiscard]]
+ Vector3 forward_vector(const ViewAngles& angles) noexcept;
+
+ [[nodiscard]]
+ Vector3 right_vector(const ViewAngles& angles) noexcept;
+
+ [[nodiscard]]
+ Vector3 up_vector(const ViewAngles& angles) noexcept;
+
+ [[nodiscard]] Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& cam_origin) noexcept;
+
+ [[nodiscard]]
+ Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept;
+
+ [[nodiscard]]
+ Vector3 extract_origin(const Mat4X4& mat) noexcept;
+
+ [[nodiscard]]
+ Vector3 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
+ requires std::is_floating_point_v
+ [[nodiscard]]
+ constexpr FloatingType units_to_centimeters(const FloatingType& units)
+ {
+ return units / static_cast(100);
+ }
+
+ template
+ requires std::is_floating_point_v
+ [[nodiscard]]
+ constexpr FloatingType units_to_meters(const FloatingType& units)
+ {
+ return units;
+ }
+
+ template
+ requires std::is_floating_point_v
+ [[nodiscard]]
+ constexpr FloatingType units_to_kilometers(const FloatingType& units)
+ {
+ return units_to_meters(units) / static_cast(1000);
+ }
+
+ template
+ requires std::is_floating_point_v
+ [[nodiscard]]
+ constexpr FloatingType centimeters_to_units(const FloatingType& centimeters)
+ {
+ return centimeters * static_cast(100);
+ }
+
+ template
+ requires std::is_floating_point_v
+ [[nodiscard]]
+ constexpr FloatingType meters_to_units(const FloatingType& meters)
+ {
+ return meters;
+ }
+
+ template
+ requires std::is_floating_point_v
+ [[nodiscard]]
+ constexpr FloatingType kilometers_to_units(const FloatingType& kilometers)
+ {
+ return meters_to_units(kilometers * static_cast(1000));
+ }
+} // namespace omath::rage_engine
diff --git a/include/omath/engines/rage_engine/mesh.hpp b/include/omath/engines/rage_engine/mesh.hpp
new file mode 100644
index 00000000..27ad1baa
--- /dev/null
+++ b/include/omath/engines/rage_engine/mesh.hpp
@@ -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;
+} // namespace omath::rage_engine
diff --git a/include/omath/engines/rage_engine/traits/camera_trait.hpp b/include/omath/engines/rage_engine/traits/camera_trait.hpp
new file mode 100644
index 00000000..773c38e7
--- /dev/null
+++ b/include/omath/engines/rage_engine/traits/camera_trait.hpp
@@ -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& cam_origin, const Vector3& look_at) noexcept;
+
+ [[nodiscard]]
+ static Mat4X4 calc_view_matrix(const ViewAngles& angles, const Vector3& 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
diff --git a/include/omath/engines/rage_engine/traits/mesh_trait.hpp b/include/omath/engines/rage_engine/traits/mesh_trait.hpp
new file mode 100644
index 00000000..5cc57e9d
--- /dev/null
+++ b/include/omath/engines/rage_engine/traits/mesh_trait.hpp
@@ -0,0 +1,20 @@
+//
+// Created by Codex on 6/3/2026.
+//
+
+#pragma once
+#include
+#include
+
+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
diff --git a/include/omath/engines/rage_engine/traits/pred_engine_trait.hpp b/include/omath/engines/rage_engine/traits/pred_engine_trait.hpp
new file mode 100644
index 00000000..8fb2e712
--- /dev/null
+++ b/include/omath/engines/rage_engine/traits/pred_engine_trait.hpp
@@ -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
+
+namespace omath::rage_engine
+{
+ class PredEngineTrait final
+ {
+ public:
+ constexpr static Vector3
+ predict_projectile_position(const projectile_prediction::Projectile& 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 predict_target_position(const projectile_prediction::Target& 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& delta) noexcept
+ {
+ return std::sqrt(delta.x * delta.x + delta.y * delta.y);
+ }
+
+ [[nodiscard]]
+ constexpr static float get_vector_height_coordinate(const Vector3& vec) noexcept
+ {
+ return vec.z;
+ }
+
+ [[nodiscard]]
+ static Vector3 calc_viewpoint_from_angles(const projectile_prediction::Projectile& projectile,
+ Vector3 predicted_target_position,
+ const std::optional 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& origin, const Vector3& 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& origin, const Vector3& 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
diff --git a/include/omath/hud/renderer_realizations/imgui_renderer.hpp b/include/omath/hud/renderer_realizations/imgui_renderer.hpp
index 8b8190b4..a7e13647 100644
--- a/include/omath/hud/renderer_realizations/imgui_renderer.hpp
+++ b/include/omath/hud/renderer_realizations/imgui_renderer.hpp
@@ -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& position, const Color& color, const std::string_view& text) override;
[[nodiscard]]
- virtual Vector2 calc_text_size(const std::string_view& text) override;
+ Vector2 calc_text_size(const std::string_view& text) override;
};
} // namespace omath::hud
#endif // OMATH_IMGUI_INTEGRATION
\ No newline at end of file
diff --git a/include/omath/omath.hpp b/include/omath/omath.hpp
index bcf503a3..f249eacb 100644
--- a/include/omath/omath.hpp
+++ b/include/omath/omath.hpp
@@ -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"
\ No newline at end of file
+#include "omath/utility/pe_pattern_scan.hpp"
diff --git a/source/engines/rage_engine/formulas.cpp b/source/engines/rage_engine/formulas.cpp
new file mode 100644
index 00000000..ff2b3ced
--- /dev/null
+++ b/source/engines/rage_engine/formulas.cpp
@@ -0,0 +1,71 @@
+//
+// Created by Codex on 6/3/2026.
+//
+#include "omath/engines/rage_engine/formulas.hpp"
+
+namespace omath::rage_engine
+{
+ Vector3 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 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 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& cam_origin) noexcept
+ {
+ return mat_camera_view(forward_vector(angles), right_vector(angles),
+ up_vector(angles), cam_origin);
+ }
+ Mat4X4 rotation_matrix(const ViewAngles& angles) noexcept
+ {
+ return mat_rotation_axis_z(angles.yaw)
+ * mat_rotation_axis_y(angles.roll)
+ * mat_rotation_axis_x(angles.pitch);
+ }
+
+ Vector3 extract_origin(const Mat4X4& mat) noexcept
+ {
+ return mat_extract_origin(mat);
+ }
+
+ Vector3 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(
+ field_of_view, aspect_ratio, near, far);
+
+ if (ndc_depth_range == NDCDepthRange::NEGATIVE_ONE_TO_ONE)
+ return mat_perspective_left_handed_vertical_fov(
+ field_of_view, aspect_ratio, near, far);
+ std::unreachable();
+ }
+} // namespace omath::rage_engine
diff --git a/source/engines/rage_engine/traits/camera_trait.cpp b/source/engines/rage_engine/traits/camera_trait.cpp
new file mode 100644
index 00000000..8e1f5cd5
--- /dev/null
+++ b/source/engines/rage_engine/traits/camera_trait.cpp
@@ -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& cam_origin, const Vector3& 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& 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
diff --git a/source/lua/lua_engines.cpp b/source/lua/lua_engines.cpp
index 31a49994..f64cae29 100644
--- a/source/lua/lua_engines.cpp
+++ b/source/lua/lua_engines.cpp
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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(omath_table, "frostbite");
register_engine(omath_table, "iw");
register_engine(omath_table, "source");
+ register_engine(omath_table, "rage");
register_engine(omath_table, "unity");
register_engine(omath_table, "unreal");
register_engine(omath_table, "cry");
diff --git a/tests/engines/unit_test_rage_engine.cpp b/tests/engines/unit_test_rage_engine.cpp
new file mode 100644
index 00000000..59cbaa61
--- /dev/null
+++ b/tests/engines/unit_test_rage_engine.cpp
@@ -0,0 +1,101 @@
+//
+// Created by Codex on 6/3/2026.
+//
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace omath;
+
+static_assert(std::is_same_v);
+
+static void expect_rage_vector_near(const Vector3& actual, const Vector3& 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 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));
+}