mirror of
https://github.com/orange-cpp/omath.git
synced 2026-04-18 19:03:28 +00:00
added stuff
This commit is contained in:
@@ -53,6 +53,47 @@ TEST(PredEngineTrait, CalcViewpointFromAngles)
|
||||
EXPECT_NEAR(vp.z, 10.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(PredEngineTrait, PredictProjectilePositionWithLaunchOffset)
|
||||
{
|
||||
projectile_prediction::Projectile p;
|
||||
p.m_origin = {0.f, 0.f, 0.f};
|
||||
p.m_launch_offset = {5.f, 3.f, -2.f};
|
||||
p.m_launch_speed = 10.f;
|
||||
p.m_gravity_scale = 1.f;
|
||||
|
||||
// At time=0, projectile should be at launch_pos = origin + offset
|
||||
const auto pos_t0 = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 0.f, 9.81f);
|
||||
EXPECT_NEAR(pos_t0.x, 5.f, 1e-4f);
|
||||
EXPECT_NEAR(pos_t0.y, 3.f, 1e-4f);
|
||||
EXPECT_NEAR(pos_t0.z, -2.f, 1e-4f);
|
||||
|
||||
// At time=1 with zero pitch/yaw, should travel along X from the offset position
|
||||
const auto pos_t1 = PredEngineTrait::predict_projectile_position(p, 0.f, 0.f, 1.f, 9.81f);
|
||||
EXPECT_NEAR(pos_t1.x, 5.f + 10.f, 1e-3f);
|
||||
EXPECT_NEAR(pos_t1.y, 3.f, 1e-3f);
|
||||
EXPECT_NEAR(pos_t1.z, -2.f - 9.81f * 0.5f, 1e-3f);
|
||||
}
|
||||
|
||||
TEST(PredEngineTrait, ZeroLaunchOffsetMatchesOriginalBehavior)
|
||||
{
|
||||
projectile_prediction::Projectile p;
|
||||
p.m_origin = {10.f, 20.f, 30.f};
|
||||
p.m_launch_offset = {0.f, 0.f, 0.f};
|
||||
p.m_launch_speed = 15.f;
|
||||
p.m_gravity_scale = 0.5f;
|
||||
|
||||
projectile_prediction::Projectile p_no_offset;
|
||||
p_no_offset.m_origin = {10.f, 20.f, 30.f};
|
||||
p_no_offset.m_launch_speed = 15.f;
|
||||
p_no_offset.m_gravity_scale = 0.5f;
|
||||
|
||||
const auto pos1 = PredEngineTrait::predict_projectile_position(p, 30.f, 45.f, 2.f, 9.81f);
|
||||
const auto pos2 = PredEngineTrait::predict_projectile_position(p_no_offset, 30.f, 45.f, 2.f, 9.81f);
|
||||
EXPECT_NEAR(pos1.x, pos2.x, 1e-6f);
|
||||
EXPECT_NEAR(pos1.y, pos2.y, 1e-6f);
|
||||
EXPECT_NEAR(pos1.z, pos2.z, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(PredEngineTrait, DirectAngles)
|
||||
{
|
||||
constexpr Vector3<float> origin{0.f, 0.f, 0.f};
|
||||
|
||||
@@ -16,3 +16,107 @@ TEST(UnitTestPrediction, PredictionTest)
|
||||
EXPECT_NEAR(-42.547142, pitch.as_degrees(), 0.01f);
|
||||
EXPECT_NEAR(-1.181189, yaw.as_degrees(), 0.01f);
|
||||
}
|
||||
|
||||
// Helper: verify aim_angles match angles derived from aim_point via CameraTrait
|
||||
static void expect_angles_match_aim_point(const omath::projectile_prediction::Projectile& proj,
|
||||
const omath::projectile_prediction::Target& target,
|
||||
float gravity, float step, float max_time, float tolerance,
|
||||
float angle_eps = 0.01f)
|
||||
{
|
||||
const omath::projectile_prediction::ProjPredEngineLegacy engine(gravity, step, max_time, tolerance);
|
||||
|
||||
const auto aim_point = engine.maybe_calculate_aim_point(proj, target);
|
||||
const auto aim_angles = engine.maybe_calculate_aim_angles(proj, target);
|
||||
|
||||
ASSERT_TRUE(aim_point.has_value()) << "aim_point should have a solution";
|
||||
ASSERT_TRUE(aim_angles.has_value()) << "aim_angles should have a solution";
|
||||
|
||||
// Source engine CameraTrait: pitch = -asin(dir.z), yaw = atan2(dir.y, dir.x)
|
||||
// PredEngineTrait: pitch = asin(delta.z / dist), yaw = atan2(delta.y, delta.x)
|
||||
// So aim_angles.pitch == -camera_pitch, aim_angles.yaw == camera_yaw
|
||||
const auto [cam_pitch, cam_yaw, cam_roll] =
|
||||
omath::source_engine::CameraTrait::calc_look_at_angle(proj.m_origin, aim_point.value());
|
||||
|
||||
EXPECT_NEAR(aim_angles->pitch, -cam_pitch.as_degrees(), angle_eps)
|
||||
<< "pitch from aim_angles must match pitch derived from aim_point";
|
||||
EXPECT_NEAR(aim_angles->yaw, cam_yaw.as_degrees(), angle_eps)
|
||||
<< "yaw from aim_angles must match yaw derived from aim_point";
|
||||
}
|
||||
|
||||
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_StaticTarget)
|
||||
{
|
||||
constexpr omath::projectile_prediction::Target target{
|
||||
.m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||
constexpr omath::projectile_prediction::Projectile proj = {
|
||||
.m_origin = {3, 2, 1}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||
|
||||
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 5.f);
|
||||
}
|
||||
|
||||
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_MovingTarget)
|
||||
{
|
||||
constexpr omath::projectile_prediction::Target target{
|
||||
.m_origin = {500, 100, 0}, .m_velocity = {-50, 20, 0}, .m_is_airborne = false};
|
||||
constexpr omath::projectile_prediction::Projectile proj = {
|
||||
.m_origin = {0, 0, 0}, .m_launch_speed = 3000, .m_gravity_scale = 1.0};
|
||||
|
||||
expect_angles_match_aim_point(proj, target, 800, 1.f / 500.f, 30, 10.f);
|
||||
}
|
||||
|
||||
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_AirborneTarget)
|
||||
{
|
||||
constexpr omath::projectile_prediction::Target target{
|
||||
.m_origin = {200, 50, 300}, .m_velocity = {10, -5, -20}, .m_is_airborne = true};
|
||||
constexpr omath::projectile_prediction::Projectile proj = {
|
||||
.m_origin = {0, 0, 0}, .m_launch_speed = 4000, .m_gravity_scale = 0.5};
|
||||
|
||||
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 10.f);
|
||||
}
|
||||
|
||||
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_HighArc)
|
||||
{
|
||||
// Target nearly directly above — high pitch angle
|
||||
constexpr omath::projectile_prediction::Target target{
|
||||
.m_origin = {10, 0, 500}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||
constexpr omath::projectile_prediction::Projectile proj = {
|
||||
.m_origin = {0, 0, 0}, .m_launch_speed = 5000, .m_gravity_scale = 0.3};
|
||||
|
||||
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 5.f);
|
||||
}
|
||||
|
||||
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_NegativeYaw)
|
||||
{
|
||||
// Target behind and to the left — negative yaw quadrant
|
||||
constexpr omath::projectile_prediction::Target target{
|
||||
.m_origin = {-200, -150, 10}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||
constexpr omath::projectile_prediction::Projectile proj = {
|
||||
.m_origin = {0, 0, 0}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||
|
||||
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 5.f);
|
||||
}
|
||||
|
||||
TEST(UnitTestPrediction, AimAnglesMatchAimPoint_WithLaunchOffset)
|
||||
{
|
||||
constexpr omath::projectile_prediction::Target target{
|
||||
.m_origin = {200, 0, 50}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||
const omath::projectile_prediction::Projectile proj = {
|
||||
.m_origin = {0, 0, 0}, .m_launch_offset = {5, 0, -3}, .m_launch_speed = 5000, .m_gravity_scale = 0.4};
|
||||
|
||||
expect_angles_match_aim_point(proj, target, 400, 1.f / 1000.f, 50, 5.f);
|
||||
}
|
||||
|
||||
TEST(UnitTestPrediction, AimAnglesReturnsNulloptWhenNoSolution)
|
||||
{
|
||||
constexpr omath::projectile_prediction::Target target{
|
||||
.m_origin = {100000, 0, 0}, .m_velocity = {0, 0, 0}, .m_is_airborne = false};
|
||||
constexpr omath::projectile_prediction::Projectile proj = {
|
||||
.m_origin = {0, 0, 0}, .m_launch_speed = 1, .m_gravity_scale = 1};
|
||||
|
||||
const omath::projectile_prediction::ProjPredEngineLegacy engine(9.81f, 0.1f, 2.f, 5.f);
|
||||
|
||||
const auto aim_point = engine.maybe_calculate_aim_point(proj, target);
|
||||
const auto aim_angles = engine.maybe_calculate_aim_angles(proj, target);
|
||||
|
||||
EXPECT_FALSE(aim_point.has_value());
|
||||
EXPECT_FALSE(aim_angles.has_value());
|
||||
}
|
||||
|
||||
@@ -46,6 +46,22 @@ TEST(ProjPredLegacyMore, ZeroGravityUsesDirectPitchAndReturnsViewpoint)
|
||||
EXPECT_NEAR(v.z, 3.f, 1e-6f);
|
||||
}
|
||||
|
||||
TEST(ProjPredLegacyMore, ZeroGravityAimAnglesReturnsPitchAndYaw)
|
||||
{
|
||||
constexpr Projectile proj{ .m_origin = {0.f, 0.f, 0.f}, .m_launch_speed = 10.f, .m_gravity_scale = 0.f };
|
||||
constexpr Target target{ .m_origin = {100.f, 0.f, 0.f}, .m_velocity = {0.f,0.f,0.f}, .m_is_airborne = false };
|
||||
|
||||
using Engine = omath::projectile_prediction::ProjPredEngineLegacy<FakeEngineZeroGravity>;
|
||||
const Engine engine(9.8f, 0.1f, 5.f, 1e-3f);
|
||||
|
||||
const auto res = engine.maybe_calculate_aim_angles(proj, target);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
// FakeEngineZeroGravity::calc_direct_pitch_angle returns 12.5f
|
||||
EXPECT_NEAR(res->pitch, 12.5f, 1e-6f);
|
||||
// FakeEngineZeroGravity::calc_direct_yaw_angle returns 0.f
|
||||
EXPECT_NEAR(res->yaw, 0.f, 1e-6f);
|
||||
}
|
||||
|
||||
// Fake trait producing no valid launch angle (root < 0)
|
||||
struct FakeEngineNoSolution
|
||||
{
|
||||
@@ -69,6 +85,9 @@ TEST(ProjPredLegacyMore, NoSolutionRootReturnsNullopt)
|
||||
|
||||
const auto res = engine.maybe_calculate_aim_point(proj, target);
|
||||
EXPECT_FALSE(res.has_value());
|
||||
|
||||
const auto angles_res = engine.maybe_calculate_aim_angles(proj, target);
|
||||
EXPECT_FALSE(angles_res.has_value());
|
||||
}
|
||||
|
||||
// Fake trait where an angle exists but the projectile does not reach target (miss)
|
||||
|
||||
Reference in New Issue
Block a user