Skip to content

Commit 9e19909

Browse files
committed
Refactors projectile prediction engine
Migrates projectile prediction logic to leverage engine traits for improved flexibility and testability. This change decouples core prediction algorithms from specific engine implementations, allowing for easier adaptation to different game engines or simulation environments.
1 parent f1984fb commit 9e19909

File tree

4 files changed

+109
-116
lines changed

4 files changed

+109
-116
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ project(omath VERSION 3.0.2 LANGUAGES CXX)
55
include(CMakePackageConfigHelpers)
66

77

8-
option(OMATH_BUILD_TESTS "Build unit tests" OFF)
8+
option(OMATH_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL})
99
option(OMATH_THREAT_WARNING_AS_ERROR "Set highest level of warnings and force compiler to treat them as errors" ON)
1010
option(OMATH_BUILD_AS_SHARED_LIBRARY "Build Omath as .so or .dll" OFF)
1111
option(OMATH_USE_AVX2 "Omath will use AVX2 to boost performance" ON)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// Created by Vlad on 8/3/2025.
3+
//
4+
5+
#pragma once
6+
#include "omath/engines/source_engine/formulas.hpp"
7+
#include "omath/projectile_prediction/projectile.hpp"
8+
#include <optional>
9+
10+
namespace omath::projectile_prediction::traits
11+
{
12+
class SourceEngineTrait final
13+
{
14+
public:
15+
constexpr static Vector3<float> predict_projectile_position(const Projectile& projectile, const float pitch,
16+
const float yaw, const float time,
17+
const float gravity) noexcept
18+
{
19+
auto current_pos = projectile.m_origin
20+
+ source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch),
21+
source_engine::YawAngle::from_degrees(yaw),
22+
source_engine::RollAngle::from_degrees(0)})
23+
* projectile.m_launch_speed * time;
24+
current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
25+
26+
return current_pos;
27+
}
28+
29+
static bool is_projectile_reached_target(const Vector3<float>& target_position,
30+
const Projectile& projectile, const float pitch,
31+
const float time, const float gravity,
32+
const float tolerance) noexcept
33+
{
34+
const auto yaw = projectile.m_origin.view_angle_to(target_position).y;
35+
const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time, gravity);
36+
37+
return projectile_position.distance_to(target_position) <= tolerance;
38+
}
39+
[[nodiscard]]
40+
static float calc_vector_2d_distance(const Vector3<float>& delta)
41+
{
42+
return std::sqrt(delta.x * delta.x + delta.y * delta.y);
43+
}
44+
45+
[[nodiscard]]
46+
constexpr static float get_vector_height_coordinate(const Vector3<float>& vec)
47+
{
48+
return vec.z;
49+
}
50+
51+
[[nodiscard]]
52+
static Vector3<float> calc_viewpoint_from_angles(const Projectile& projectile,
53+
Vector3<float> predicted_target_position,
54+
const std::optional<float> projectile_pitch)
55+
{
56+
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
57+
const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value()));
58+
59+
return {predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height};
60+
}
61+
};
62+
} // namespace omath::projectile_prediction::traits

include/omath/projectile_prediction/proj_pred_engine_legacy.hpp

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#pragma once
66

7+
#include "engine_traits/source_engine_trait.hpp"
78
#include "omath/projectile_prediction/proj_pred_engine.hpp"
89
#include "omath/projectile_prediction/projectile.hpp"
910
#include "omath/projectile_prediction/target.hpp"
@@ -13,15 +14,40 @@
1314
namespace omath::projectile_prediction
1415
{
1516
// ReSharper disable once CppClassCanBeFinal
17+
template<class EngineTrait = traits::SourceEngineTrait>
1618
class ProjPredEngineLegacy : public ProjPredEngineInterface
1719
{
1820
public:
19-
explicit ProjPredEngineLegacy(float gravity_constant, float simulation_time_step, float maximum_simulation_time,
20-
float distance_tolerance);
21+
explicit ProjPredEngineLegacy(const float gravity_constant, const float simulation_time_step,
22+
const float maximum_simulation_time, const float distance_tolerance)
23+
: m_gravity_constant(gravity_constant), m_simulation_time_step(simulation_time_step),
24+
m_maximum_simulation_time(maximum_simulation_time), m_distance_tolerance(distance_tolerance)
25+
{
26+
}
2127

2228
[[nodiscard]]
2329
std::optional<Vector3<float>> maybe_calculate_aim_point(const Projectile& projectile,
24-
const Target& target) const override;
30+
const Target& target) const override
31+
{
32+
for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step)
33+
{
34+
const auto predicted_target_position = target.predict_position(time, m_gravity_constant);
35+
36+
const auto projectile_pitch =
37+
maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position);
38+
39+
if (!projectile_pitch.has_value()) [[unlikely]]
40+
continue;
41+
42+
if (!EngineTrait::is_projectile_reached_target(predicted_target_position, projectile,
43+
projectile_pitch.value(), time, m_gravity_constant,
44+
m_distance_tolerance))
45+
continue;
46+
47+
return EngineTrait::calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch);
48+
}
49+
return std::nullopt;
50+
}
2551

2652
private:
2753
const float m_gravity_constant;
@@ -44,30 +70,27 @@ namespace omath::projectile_prediction
4470
[[nodiscard]]
4571
std::optional<float>
4672
maybe_calculate_projectile_launch_pitch_angle(const Projectile& projectile,
47-
const Vector3<float>& target_position) const noexcept;
73+
const Vector3<float>& target_position) const noexcept
74+
{
75+
const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
76+
const auto delta = target_position - projectile.m_origin;
4877

49-
[[nodiscard]]
50-
bool is_projectile_reached_target(const Vector3<float>& target_position, const Projectile& projectile,
51-
float pitch, float time) const noexcept;
78+
const auto distance2d = EngineTrait::calc_vector_2d_distance(delta);
79+
const auto distance2d_sqr = distance2d * distance2d;
80+
const auto launch_speed_sqr = projectile.m_launch_speed * projectile.m_launch_speed;
5281

53-
protected:
54-
// NOTE: Override this if you need to use engine with different coordinate system
55-
// Like where Z is not height coordinate
56-
// ===============================================================================================
57-
[[nodiscard]]
58-
virtual float calc_vector_2d_distance(const Vector3<float>& delta) const;
82+
float root = launch_speed_sqr * launch_speed_sqr
83+
- bullet_gravity
84+
* (bullet_gravity * distance2d_sqr
85+
+ 2.0f * EngineTrait::get_vector_height_coordinate(delta) * launch_speed_sqr);
5986

60-
[[nodiscard]]
61-
virtual float get_vector_height_coordinate(const Vector3<float>& vec) const;
87+
if (root < 0.0f) [[unlikely]]
88+
return std::nullopt;
6289

63-
[[nodiscard]]
64-
virtual Vector3<float> calc_viewpoint_from_angles(const Projectile& projectile,
65-
Vector3<float> predicted_target_position,
66-
std::optional<float> projectile_pitch) const;
90+
root = std::sqrt(root);
91+
const float angle = std::atan((launch_speed_sqr - root) / (bullet_gravity * distance2d));
6792

68-
[[nodiscard]]
69-
virtual Vector3<float> predict_projectile_position(const Projectile& projectile, float pitch, float yaw,
70-
float time, float gravity) const;
71-
// ===============================================================================================
93+
return angles::radians_to_degrees(angle);
94+
}
7295
};
7396
} // namespace omath::projectile_prediction

source/projectile_prediction/proj_pred_engine_legacy.cpp

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -4,97 +4,5 @@
44

55
namespace omath::projectile_prediction
66
{
7-
ProjPredEngineLegacy::ProjPredEngineLegacy(const float gravity_constant, const float simulation_time_step,
8-
const float maximum_simulation_time, const float distance_tolerance)
9-
: m_gravity_constant(gravity_constant), m_simulation_time_step(simulation_time_step),
10-
m_maximum_simulation_time(maximum_simulation_time), m_distance_tolerance(distance_tolerance)
11-
{
12-
}
137

14-
std::optional<Vector3<float>> ProjPredEngineLegacy::maybe_calculate_aim_point(const Projectile& projectile,
15-
const Target& target) const
16-
{
17-
for (float time = 0.f; time < m_maximum_simulation_time; time += m_simulation_time_step)
18-
{
19-
const auto predicted_target_position = target.predict_position(time, m_gravity_constant);
20-
21-
const auto projectile_pitch =
22-
maybe_calculate_projectile_launch_pitch_angle(projectile, predicted_target_position);
23-
24-
if (!projectile_pitch.has_value()) [[unlikely]]
25-
continue;
26-
27-
if (!is_projectile_reached_target(predicted_target_position, projectile, projectile_pitch.value(), time))
28-
continue;
29-
30-
return calc_viewpoint_from_angles(projectile, predicted_target_position, projectile_pitch);
31-
}
32-
return std::nullopt;
33-
}
34-
35-
std::optional<float> ProjPredEngineLegacy::maybe_calculate_projectile_launch_pitch_angle(
36-
const Projectile& projectile, const Vector3<float>& target_position) const noexcept
37-
{
38-
const auto bullet_gravity = m_gravity_constant * projectile.m_gravity_scale;
39-
const auto delta = target_position - projectile.m_origin;
40-
41-
const auto distance2d = calc_vector_2d_distance(delta);
42-
const auto distance2d_sqr = distance2d * distance2d;
43-
const auto launch_speed_sqr = projectile.m_launch_speed * projectile.m_launch_speed;
44-
45-
float root = launch_speed_sqr * launch_speed_sqr
46-
- bullet_gravity
47-
* (bullet_gravity * distance2d_sqr
48-
+ 2.0f * get_vector_height_coordinate(delta) * launch_speed_sqr);
49-
50-
if (root < 0.0f) [[unlikely]]
51-
return std::nullopt;
52-
53-
root = std::sqrt(root);
54-
const float angle = std::atan((launch_speed_sqr - root) / (bullet_gravity * distance2d));
55-
56-
return angles::radians_to_degrees(angle);
57-
}
58-
59-
bool ProjPredEngineLegacy::is_projectile_reached_target(const Vector3<float>& target_position,
60-
const Projectile& projectile, const float pitch,
61-
const float time) const noexcept
62-
{
63-
const auto yaw = projectile.m_origin.view_angle_to(target_position).y;
64-
const auto projectile_position = predict_projectile_position(projectile, pitch, yaw, time, m_gravity_constant);
65-
66-
return projectile_position.distance_to(target_position) <= m_distance_tolerance;
67-
}
68-
69-
float ProjPredEngineLegacy::calc_vector_2d_distance(const Vector3<float>& delta) const
70-
{
71-
return std::sqrt(delta.x * delta.x + delta.y * delta.y);
72-
}
73-
74-
float ProjPredEngineLegacy::get_vector_height_coordinate(const Vector3<float>& vec) const
75-
{
76-
return vec.z;
77-
}
78-
Vector3<float> ProjPredEngineLegacy::calc_viewpoint_from_angles(const Projectile& projectile,
79-
const Vector3<float> predicted_target_position,
80-
const std::optional<float> projectile_pitch) const
81-
{
82-
const auto delta2d = calc_vector_2d_distance(predicted_target_position - projectile.m_origin);
83-
const auto height = delta2d * std::tan(angles::degrees_to_radians(projectile_pitch.value()));
84-
85-
return {predicted_target_position.x, predicted_target_position.y, projectile.m_origin.z + height};
86-
}
87-
Vector3<float> ProjPredEngineLegacy::predict_projectile_position(const Projectile& projectile, const float pitch,
88-
const float yaw, const float time,
89-
const float gravity) const
90-
{
91-
auto current_pos = projectile.m_origin
92-
+ source_engine::forward_vector({source_engine::PitchAngle::from_degrees(-pitch),
93-
source_engine::YawAngle::from_degrees(yaw),
94-
source_engine::RollAngle::from_degrees(0)})
95-
* projectile.m_launch_speed * time;
96-
current_pos.z -= (gravity * projectile.m_gravity_scale) * (time * time) * 0.5f;
97-
98-
return current_pos;
99-
}
1008
} // namespace omath::projectile_prediction

0 commit comments

Comments
 (0)