Compare commits

...

10 Commits

Author SHA1 Message Date
ea8f3d8d51 Adds contributing guidelines
Introduces a CONTRIBUTING.MD file to provide guidelines for
contributing to the project, including prerequisites, setup
instructions, pull request workflow, code style, and building
instructions.

xd

returned back

patch
2025-08-06 06:23:06 +03:00
08d2ccc03a Refactors Vector operations for type safety
Ensures type safety in Vector2, Vector3, and Vector4 operations by using static_cast(0) instead of relying on implicit conversions.
This prevents potential issues with different numeric types.

Adds from_im_vec2 and from_im_vec4 methods for creating vectors from ImVec2/ImVec4 types.
2025-08-06 06:06:42 +03:00
21ec23d77b patch 2025-08-06 05:56:09 +03:00
2c4ff37062 Merge pull request #52 from orange-cpp/feature/more_traits
Adds engine traits for projectile prediction
2025-08-06 05:49:25 +03:00
695a8035b5 Adds engine traits for projectile prediction
Implements engine-specific traits for projectile and target position prediction.
Each trait encapsulates logic tailored to a specific game engine (IW, OpenGL, Unity),
accounting for engine-specific coordinate systems and calculations.
This allows for accurate projectile prediction across different game environments.
2025-08-06 05:45:37 +03:00
d12b236e56 Refactors target position prediction
Moves target prediction logic into engine traits, improving modularity.

This change consolidates target position prediction within the engine traits,
allowing for a more flexible and maintainable design.

This eliminates redundant code and simplifies the core prediction engine by
delegating target movement calculations to the appropriate trait.
2025-08-04 03:16:04 +03:00
7a5090d9f6 Marks legacy engine class as final
Prevents further inheritance from the legacy projectile prediction engine class.
2025-08-04 01:12:22 +03:00
ec76a7239c Adds direct pitch angle calculation
Implements a direct pitch angle calculation for scenarios with zero gravity, ensuring accurate projectile trajectory predictions in such conditions.

Also marks several methods as noexcept for better performance and exception safety.
2025-08-04 01:11:11 +03:00
2758f549a3 Updates project version and removes legacy code
Updates the project version to prepare for a new release.

Removes the legacy projectile prediction engine, which is no longer needed.
2025-08-03 18:35:52 +03:00
493931ef0f Ignores vcpkg directory
Excludes the vcpkg directory from being tracked by Git.

This prevents accidental commits of external library files
managed by vcpkg, keeping the repository cleaner.
2025-08-03 18:31:02 +03:00
13 changed files with 340 additions and 46 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/cmake-build/ /cmake-build/
/.idea /.idea
/out /out
*.DS_Store *.DS_Store
/extlibs/vcpkg

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.26) cmake_minimum_required(VERSION 3.26)
project(omath VERSION 3.0.2 LANGUAGES CXX) project(omath VERSION 3.0.4.1 LANGUAGES CXX)
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)

32
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,32 @@
## 🤝 Contributing to OMath or other Orange's Projects
### ❕ Prerequisites
- A working up-to-date OMath installation
- C++ knowledge
- Git knowledge
- Ability to ask for help (Feel free to create empty pull-request or PM a maintainer
in [Telegram](https://t.me/orange_cpp))
### ⏬ Setting up OMath
Please read INSTALL.md file in repository
### 🔀 Pull requests and Branches
In order to send code back to the official OMath repository, you must first create a copy of OMath on your github
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
merged into master will not roll out to stable build users unless the `stable` tag is updated.
### 📜 Code-Style
The orange code-style can be found in `.clang-format`.
### 📦 Building
OMath has already created the `cmake-build` and `out` directories where cmake/bin files are located. By default, you
can build OMath by running `cmake --build cmake-build/build/windows-release --target omath -j 6` in the source
directory.

View File

@@ -0,0 +1,80 @@
//
// Created by Vlad on 8/6/2025.
//
#pragma once
#include "omath/engines/iw_engine/formulas.hpp"
#include "omath/projectile_prediction/projectile.hpp"
#include "omath/projectile_prediction/target.hpp"
#include <optional>
namespace omath::projectile_prediction::traits
{
class IwEngineTrait final
{
public:
constexpr static Vector3<float> predict_projectile_position(const Projectile& projectile, const float pitch,
const float yaw, const float time,
const float gravity) noexcept
{
auto current_pos = projectile.m_origin
+ iw_engine::forward_vector({iw_engine::PitchAngle::from_degrees(-pitch),
iw_engine::YawAngle::from_degrees(yaw),
iw_engine::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 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<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& 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};
}
// Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be:
// 89 look up, -89 look down
[[nodiscard]]
static float calc_direct_pitch_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto distance = origin.distance_to(view_to);
const auto delta = view_to - origin;
return angles::radians_to_degrees(std::asin(delta.z / distance));
}
[[nodiscard]]
static float calc_direct_yaw_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto delta = view_to - origin;
return angles::radians_to_degrees(std::atan2(delta.y, delta.x));
};
};
} // namespace omath::projectile_prediction::traits

View File

@@ -0,0 +1,79 @@
//
// Created by Vlad on 8/6/2025.
//
#pragma once
#include "omath/engines/opengl_engine/formulas.hpp"
#include "omath/projectile_prediction/projectile.hpp"
#include "omath/projectile_prediction/target.hpp"
#include <optional>
namespace omath::projectile_prediction::traits
{
class OpenGlEngineTrait final
{
public:
constexpr static Vector3<float> predict_projectile_position(const Projectile& projectile, const float pitch,
const float yaw, const float time,
const float gravity) noexcept
{
auto current_pos = projectile.m_origin
+ opengl_engine::forward_vector({opengl_engine::PitchAngle::from_degrees(-pitch),
opengl_engine::YawAngle::from_degrees(yaw),
opengl_engine::RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;
current_pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
return current_pos;
}
[[nodiscard]]
static constexpr Vector3<float> predict_target_position(const Target& target, const float time,
const float gravity) noexcept
{
auto predicted = target.m_origin + target.m_velocity * time;
if (target.m_is_airborne)
predicted.y -= 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.z * delta.z);
}
[[nodiscard]]
constexpr static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
{
return vec.y;
}
[[nodiscard]]
static Vector3<float> calc_viewpoint_from_angles(const Projectile& 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 + height, projectile.m_origin.z};
}
// Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be:
// 89 look up, -89 look down
[[nodiscard]]
static float calc_direct_pitch_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto distance = origin.distance_to(view_to);
const auto delta = view_to - origin;
return angles::radians_to_degrees(std::asin(delta.y / distance));
}
[[nodiscard]]
static float calc_direct_yaw_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto delta = view_to - origin;
return angles::radians_to_degrees(std::atan2(delta.z, delta.x));
};
};
} // namespace omath::projectile_prediction::traits

View File

@@ -5,6 +5,7 @@
#pragma once #pragma once
#include "omath/engines/source_engine/formulas.hpp" #include "omath/engines/source_engine/formulas.hpp"
#include "omath/projectile_prediction/projectile.hpp" #include "omath/projectile_prediction/projectile.hpp"
#include "omath/projectile_prediction/target.hpp"
#include <optional> #include <optional>
namespace omath::projectile_prediction::traits namespace omath::projectile_prediction::traits
@@ -25,25 +26,25 @@ namespace omath::projectile_prediction::traits
return current_pos; return current_pos;
} }
[[nodiscard]]
static bool is_projectile_reached_target(const Vector3<float>& target_position, static constexpr Vector3<float> predict_target_position(const Target& target, const float time,
const Projectile& projectile, const float pitch, const float gravity) noexcept
const float time, const float gravity,
const float tolerance) noexcept
{ {
const auto yaw = projectile.m_origin.view_angle_to(target_position).y; auto predicted = target.m_origin + target.m_velocity * time;
const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time, gravity);
return projectile_position.distance_to(target_position) <= tolerance; if (target.m_is_airborne)
predicted.z -= gravity * (time * time) * 0.5f;
return predicted;
} }
[[nodiscard]] [[nodiscard]]
static float calc_vector_2d_distance(const Vector3<float>& delta) static float calc_vector_2d_distance(const Vector3<float>& delta) noexcept
{ {
return std::sqrt(delta.x * delta.x + delta.y * delta.y); return std::sqrt(delta.x * delta.x + delta.y * delta.y);
} }
[[nodiscard]] [[nodiscard]]
constexpr static float get_vector_height_coordinate(const Vector3<float>& vec) constexpr static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
{ {
return vec.z; return vec.z;
} }
@@ -51,12 +52,29 @@ namespace omath::projectile_prediction::traits
[[nodiscard]] [[nodiscard]]
static Vector3<float> calc_viewpoint_from_angles(const Projectile& projectile, static Vector3<float> calc_viewpoint_from_angles(const Projectile& projectile,
Vector3<float> predicted_target_position, Vector3<float> predicted_target_position,
const std::optional<float> projectile_pitch) const std::optional<float> projectile_pitch) noexcept
{ {
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin); 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())); 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}; return {predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height};
} }
// Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be:
// 89 look up, -89 look down
[[nodiscard]]
static float calc_direct_pitch_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto distance = origin.distance_to(view_to);
const auto delta = view_to - origin;
return angles::radians_to_degrees(std::asin(delta.z / distance));
}
[[nodiscard]]
static float calc_direct_yaw_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto delta = view_to - origin;
return angles::radians_to_degrees(std::atan2(delta.y, delta.x));
};
}; };
} // namespace omath::projectile_prediction::traits } // namespace omath::projectile_prediction::traits

View File

@@ -0,0 +1,79 @@
//
// Created by Vlad on 8/6/2025.
//
#pragma once
#include "omath/engines/unity_engine/formulas.hpp"
#include "omath/projectile_prediction/projectile.hpp"
#include "omath/projectile_prediction/target.hpp"
#include <optional>
namespace omath::projectile_prediction::traits
{
class UnityEngineTrait final
{
public:
constexpr static Vector3<float> predict_projectile_position(const Projectile& projectile, const float pitch,
const float yaw, const float time,
const float gravity) noexcept
{
auto current_pos = projectile.m_origin
+ unity_engine::forward_vector({unity_engine::PitchAngle::from_degrees(-pitch),
unity_engine::YawAngle::from_degrees(yaw),
unity_engine::RollAngle::from_degrees(0)})
* projectile.m_launch_speed * time;
current_pos.y -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
return current_pos;
}
[[nodiscard]]
static constexpr Vector3<float> predict_target_position(const Target& target, const float time,
const float gravity) noexcept
{
auto predicted = target.m_origin + target.m_velocity * time;
if (target.m_is_airborne)
predicted.y -= 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.z * delta.z);
}
[[nodiscard]]
constexpr static float get_vector_height_coordinate(const Vector3<float>& vec) noexcept
{
return vec.y;
}
[[nodiscard]]
static Vector3<float> calc_viewpoint_from_angles(const Projectile& 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 + height, projectile.m_origin.z};
}
// Due to specification of maybe_calculate_projectile_launch_pitch_angle, pitch angle must be:
// 89 look up, -89 look down
[[nodiscard]]
static float calc_direct_pitch_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto distance = origin.distance_to(view_to);
const auto delta = view_to - origin;
return angles::radians_to_degrees(std::asin(delta.y / distance));
}
[[nodiscard]]
static float calc_direct_yaw_angle(const Vector3<float>& origin, const Vector3<float>& view_to) noexcept
{
const auto delta = view_to - origin;
return angles::radians_to_degrees(std::atan2(delta.z, delta.x));
};
};
} // namespace omath::projectile_prediction::traits

View File

@@ -13,9 +13,8 @@
namespace omath::projectile_prediction namespace omath::projectile_prediction
{ {
// ReSharper disable once CppClassCanBeFinal
template<class EngineTrait = traits::SourceEngineTrait> template<class EngineTrait = traits::SourceEngineTrait>
class ProjPredEngineLegacy : public ProjPredEngineInterface class ProjPredEngineLegacy final : public ProjPredEngineInterface
{ {
public: public:
explicit ProjPredEngineLegacy(const float gravity_constant, const float simulation_time_step, explicit ProjPredEngineLegacy(const float gravity_constant, const float simulation_time_step,
@@ -31,7 +30,8 @@ namespace omath::projectile_prediction
{ {
for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step) for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step)
{ {
const auto predicted_target_position = target.predict_position(time, m_gravity_constant); const auto predicted_target_position =
EngineTrait::predict_target_position(target, time, m_gravity_constant);
const auto projectile_pitch = const auto projectile_pitch =
maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position); maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position);
@@ -39,9 +39,8 @@ namespace omath::projectile_prediction
if (!projectile_pitch.has_value()) [[unlikely]] if (!projectile_pitch.has_value()) [[unlikely]]
continue; continue;
if (!EngineTrait::is_projectile_reached_target(predicted_target_position, projectile, if (!is_projectile_reached_target(predicted_target_position, projectile, projectile_pitch.value(),
projectile_pitch.value(), time, m_gravity_constant, time))
m_distance_tolerance))
continue; continue;
return EngineTrait::calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch); return EngineTrait::calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch);
@@ -73,6 +72,10 @@ namespace omath::projectile_prediction
const Vector3<float>& target_position) const noexcept const Vector3<float>& target_position) const noexcept
{ {
const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale; const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
if (bullet_gravity == 0.f)
return EngineTrait::calc_direct_pitch_angle(projectile.m_origin, target_position);
const auto delta = target_position - projectile.m_origin; const auto delta = target_position - projectile.m_origin;
const auto distance2d = EngineTrait::calc_vector_2d_distance(delta); const auto distance2d = EngineTrait::calc_vector_2d_distance(delta);
@@ -92,5 +95,15 @@ namespace omath::projectile_prediction
return angles::radians_to_degrees(angle); return angles::radians_to_degrees(angle);
} }
[[nodiscard]]
bool is_projectile_reached_target(const Vector3<float>& target_position, const Projectile& projectile,
const float pitch, const float time) const noexcept
{
const auto yaw = EngineTrait::calc_direct_yaw_angle(projectile.m_origin, target_position);
const auto projectile_position =
EngineTrait::predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);
return projectile_position.distance_to(target_position) <= m_distance_tolerance;
}
}; };
} // namespace omath::projectile_prediction } // namespace omath::projectile_prediction

View File

@@ -10,17 +10,6 @@ namespace omath::projectile_prediction
class Target final class Target final
{ {
public: public:
[[nodiscard]]
constexpr Vector3<float> predict_position(const float time, const float gravity) const noexcept
{
auto predicted = m_origin + m_velocity * time;
if (m_is_airborne)
predicted.z -= gravity * (time*time) * 0.5f;
return predicted;
}
Vector3<float> m_origin; Vector3<float> m_origin;
Vector3<float> m_velocity; Vector3<float> m_velocity;
bool m_is_airborne{}; bool m_is_airborne{};

View File

@@ -142,7 +142,7 @@ namespace omath
[[nodiscard]] Vector2 normalized() const noexcept [[nodiscard]] Vector2 normalized() const noexcept
{ {
const Type len = length(); const Type len = length();
return len > 0.f ? *this / len : *this; return len > static_cast<Type>(0) ? *this / len : *this;
} }
#endif #endif
[[nodiscard]] constexpr Type length_sqr() const noexcept [[nodiscard]] constexpr Type length_sqr() const noexcept
@@ -153,8 +153,8 @@ namespace omath
constexpr Vector2& abs() noexcept constexpr Vector2& abs() noexcept
{ {
// FIXME: Replace with std::abs, if it will become constexprable // FIXME: Replace with std::abs, if it will become constexprable
x = x < 0 ? -x : x; x = x < static_cast<Type>(0) ? -x : x;
y = y < 0 ? -y : y; y = y < static_cast<Type>(0) ? -y : y;
return *this; return *this;
} }
@@ -202,6 +202,11 @@ namespace omath
{ {
return {static_cast<float>(this->x), static_cast<float>(this->y)}; return {static_cast<float>(this->x), static_cast<float>(this->y)};
} }
[[nodiscard]]
static Vector3<float> from_im_vec2(const ImVec2& other) noexcept
{
return {static_cast<Type>(other.x), static_cast<Type>(other.y)};
}
#endif #endif
}; };
} // namespace omath } // namespace omath

View File

@@ -151,7 +151,7 @@ namespace omath
{ {
const Type len = this->length(); const Type len = this->length();
return len != 0 ? *this / len : *this; return len != static_cast<Type>(0) ? *this / len : *this;
} }
[[nodiscard]] Type length_2d() const noexcept [[nodiscard]] Type length_2d() const noexcept
@@ -221,7 +221,7 @@ namespace omath
{ {
const auto bottom = length() * other.length(); const auto bottom = length() * other.length();
if (bottom == 0.f) if (bottom == static_cast<Type>(0))
return std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE); return std::unexpected(Vector3Error::IMPOSSIBLE_BETWEEN_ANGLE);
return Angle<float, 0.f, 180.f, AngleFlags::Clamped>::from_radians(std::acos(dot(other) / bottom)); return Angle<float, 0.f, 180.f, AngleFlags::Clamped>::from_radians(std::acos(dot(other) / bottom));
@@ -230,7 +230,7 @@ namespace omath
[[nodiscard]] bool is_perpendicular(const Vector3& other) const noexcept [[nodiscard]] bool is_perpendicular(const Vector3& other) const noexcept
{ {
if (const auto angle = angle_between(other)) if (const auto angle = angle_between(other))
return angle->as_degrees() == 90.f; return angle->as_degrees() == static_cast<Type>(90);
return false; return false;
} }

View File

@@ -18,7 +18,7 @@ namespace omath
constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w): Vector3<Type>(x, y, z), w(w) constexpr Vector4(const Type& x, const Type& y, const Type& z, const Type& w): Vector3<Type>(x, y, z), w(w)
{ {
} }
constexpr Vector4() noexcept : Vector3<Type>(), w(0) {}; constexpr Vector4() noexcept: Vector3<Type>(), w(static_cast<Type>(0)) {};
[[nodiscard]] [[nodiscard]]
constexpr bool operator==(const Vector4& other) const noexcept constexpr bool operator==(const Vector4& other) const noexcept
@@ -169,6 +169,12 @@ namespace omath
static_cast<float>(w), static_cast<float>(w),
}; };
} }
[[nodiscard]]
static Vector4<float> from_im_vec4(const ImVec4& other) noexcept
{
return {static_cast<Type>(other.x), static_cast<Type>(other.y), static_cast<Type>(other.z)};
}
}
#endif #endif
}; };
} // namespace omath } // namespace omath

View File

@@ -1,8 +0,0 @@
#include "omath/projectile_prediction/proj_pred_engine_legacy.hpp"
#include <cmath>
#include <omath/angles.hpp>
namespace omath::projectile_prediction
{
} // namespace omath::projectile_prediction