mirror of
https://github.com/orange-cpp/omath.git
synced 2026-02-12 22:53:27 +00:00
refactored projectile prediction
This commit is contained in:
@@ -3,7 +3,8 @@ project(uml)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 26)
|
||||
option(BUILD_TESTS "Build test programs" ON)
|
||||
add_library(uml STATIC source/Vector3.cpp)
|
||||
add_library(uml STATIC source/Vector3.cpp
|
||||
include/uml/prediction/engine.h)
|
||||
|
||||
add_subdirectory(source)
|
||||
add_subdirectory(extlibs)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// Created by vlad on 11/6/23.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <optional>
|
||||
#include <uml/Vector3.h>
|
||||
|
||||
|
||||
namespace uml::prediction
|
||||
{
|
||||
struct Projectile
|
||||
{
|
||||
Vector3 m_origin;
|
||||
float m_velocity{};
|
||||
float m_gravityMultiplier = 1.f;
|
||||
};
|
||||
struct Target
|
||||
{
|
||||
Vector3 m_origin;
|
||||
Vector3 m_vecVelocity;
|
||||
bool m_IsAirborne;
|
||||
};
|
||||
class ProjectilePredictor
|
||||
{
|
||||
public:
|
||||
explicit ProjectilePredictor(float gravityValue,
|
||||
float maxTimeToTravel = 3.f,
|
||||
float timeStep = 0.1f);
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
std::optional<Vector3> PredictPointToAim(const Target& target,
|
||||
const Projectile& projectile) const;
|
||||
|
||||
private:
|
||||
const float m_gravity;
|
||||
const float m_maxTravelTime;
|
||||
const float m_timeStepSize;
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3 LinearPrediction(const Target& target, float time) const;
|
||||
|
||||
[[nodiscard]]
|
||||
std::optional<float>
|
||||
MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile,
|
||||
const Vector3& targetPosition) const;
|
||||
|
||||
[[nodiscard]]
|
||||
std::optional<float> ProjectileTravelTime(const Vector3& end,
|
||||
const Projectile& projectile,
|
||||
float angle) const;
|
||||
|
||||
[[nodiscard]]
|
||||
bool IsTargetWasHit(const Vector3& end, const Projectile& projectile, float angle, float time) const;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -57,7 +57,7 @@ namespace uml
|
||||
{
|
||||
return *reinterpret_cast<type*>(this);
|
||||
}
|
||||
[[nodiscard]] static Vector3 CreateVelocity(const Vector3& angles, float length);
|
||||
[[nodiscard]] static Vector3 CreateVelocity(float pitch, float yaw, float speed);
|
||||
[[nodiscard]] float Sum() const;
|
||||
[[nodiscard]] float Sum2D() const;
|
||||
[[nodiscard]] Vector3 ViewAngleTo(const Vector3& other) const;
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
|
||||
namespace uml::angles
|
||||
{
|
||||
[[nodiscard]] float RadToDeg(float rads);
|
||||
[[nodiscard]] float DegToRad(float degrees);
|
||||
[[nodiscard]] float RadiansToDegrees(float rads);
|
||||
[[nodiscard]] float DegreesToRadians(float degrees);
|
||||
}
|
||||
37
include/uml/prediction/Engine.h
Normal file
37
include/uml/prediction/Engine.h
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by Vlad on 6/9/2024.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include "uml/Vector3.h"
|
||||
#include "uml/prediction/Projectile.h"
|
||||
#include "uml/prediction/Target.h"
|
||||
|
||||
|
||||
namespace uml::prediction
|
||||
{
|
||||
class Engine
|
||||
{
|
||||
public:
|
||||
explicit Engine(float gravityConstant, float simulationTimeStep, float maximumSimulationTime);
|
||||
|
||||
[[nodiscard]]
|
||||
std::optional<Vector3> MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const;
|
||||
|
||||
private:
|
||||
const float m_gravityConstant;
|
||||
const float m_simulationTimeStep;
|
||||
const float m_maximumSimulationTime;
|
||||
|
||||
[[nodiscard]]
|
||||
std::optional<float> MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile,
|
||||
const Vector3& targetPosition) const;
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, float time) const;
|
||||
|
||||
};
|
||||
}
|
||||
25
include/uml/prediction/Projectile.h
Normal file
25
include/uml/prediction/Projectile.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by Vlad on 6/9/2024.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "uml/Vector3.h"
|
||||
|
||||
|
||||
namespace uml::prediction
|
||||
{
|
||||
class Projectile final
|
||||
{
|
||||
public:
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3 CalculateVelocity(float pitch, float yaw) const;
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3 PredictPosition(float pitch, float yaw, float time, float gravity) const;
|
||||
|
||||
Vector3 m_origin;
|
||||
float m_launchSpeed{};
|
||||
float m_gravityScale{};
|
||||
};
|
||||
}
|
||||
22
include/uml/prediction/Target.h
Normal file
22
include/uml/prediction/Target.h
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by Vlad on 6/9/2024.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "uml/Vector3.h"
|
||||
|
||||
|
||||
namespace uml::prediction
|
||||
{
|
||||
class Target final
|
||||
{
|
||||
public:
|
||||
|
||||
[[nodiscard]]
|
||||
Vector3 PredictPosition(float time, float gravity) const;
|
||||
|
||||
Vector3 m_origin;
|
||||
Vector3 m_velocity;
|
||||
bool m_isAirborne{};
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@ target_sources(uml PRIVATE
|
||||
Vector3.cpp
|
||||
matrix.cpp
|
||||
angles.cpp
|
||||
ProjectilePredictor.cpp
|
||||
color.cpp
|
||||
Vector4.cpp)
|
||||
Vector4.cpp)
|
||||
|
||||
add_subdirectory(prediction)
|
||||
@@ -1,118 +0,0 @@
|
||||
//
|
||||
// Created by vlad on 11/6/23.
|
||||
//
|
||||
|
||||
#include "uml/ProjectilePredictor.h"
|
||||
#include "uml/Vector3.h"
|
||||
#include "uml/angles.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
namespace uml::prediction
|
||||
{
|
||||
ProjectilePredictor::ProjectilePredictor(float gravityValue,
|
||||
float maxTimeToTravel,
|
||||
float timeStep)
|
||||
: m_gravity(gravityValue), m_maxTravelTime(maxTimeToTravel), m_timeStepSize(timeStep)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
std::optional<Vector3> ProjectilePredictor::PredictPointToAim(
|
||||
const Target &target, const Projectile &projectile) const
|
||||
{
|
||||
for (float time = 0.0f; time <= m_maxTravelTime; time += m_timeStepSize)
|
||||
{
|
||||
auto predictedTargetPosition = LinearPrediction(target, time);
|
||||
|
||||
const auto projectilePitch =
|
||||
MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition);
|
||||
|
||||
if (!projectilePitch.has_value()) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
if (!IsTargetWasHit(predictedTargetPosition, projectile, projectilePitch.value(), time))
|
||||
continue;
|
||||
|
||||
const auto delta2d = (predictedTargetPosition - projectile.m_origin).Length2D();
|
||||
const auto height = delta2d * std::tan(angles::DegToRad(projectilePitch.value()));
|
||||
|
||||
predictedTargetPosition.z = projectile.m_origin.z + height;
|
||||
|
||||
return predictedTargetPosition;
|
||||
}
|
||||
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Vector3 ProjectilePredictor::LinearPrediction(const Target &target, float time) const
|
||||
{
|
||||
auto predicted = target.m_origin + target.m_vecVelocity * time;
|
||||
|
||||
if (target.m_IsAirborne)
|
||||
predicted.z -= m_gravity * std::pow(time, 2.f) * 0.5f;
|
||||
|
||||
return predicted;
|
||||
}
|
||||
|
||||
std::optional<float>
|
||||
ProjectilePredictor::MaybeCalculateProjectileLaunchPitchAngle(const Projectile &projectile,
|
||||
const Vector3 &targetPosition)
|
||||
const
|
||||
{
|
||||
const auto bulletGravity = m_gravity * projectile.m_gravityMultiplier;
|
||||
const auto delta = targetPosition - projectile.m_origin;;
|
||||
|
||||
const auto distance2d = delta.Length2D();
|
||||
|
||||
|
||||
float root = std::pow(projectile.m_velocity, 4.f) - bulletGravity * (bulletGravity *
|
||||
std::pow(distance2d, 2.f) + 2.0f * delta.z * std::pow(projectile.m_velocity, 2.f));
|
||||
|
||||
if (root < 0.0f) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
root = std::sqrt(root);
|
||||
const float angle = std::atan((std::pow(projectile.m_velocity, 2.f) - root) / (bulletGravity * distance2d));
|
||||
|
||||
return angles::RadToDeg(angle);
|
||||
}
|
||||
|
||||
std::optional<float> ProjectilePredictor::ProjectileTravelTime(const Vector3 &end,
|
||||
const Projectile &projectile,
|
||||
const float angle) const
|
||||
{
|
||||
auto launchAngles = projectile.m_origin.ViewAngleTo(end);
|
||||
launchAngles.x = angle;
|
||||
|
||||
const auto velocity = Vector3::CreateVelocity(launchAngles, projectile.m_velocity);
|
||||
|
||||
for (float time = 0.0f; time <= m_maxTravelTime; time += m_timeStepSize)
|
||||
{
|
||||
auto currentPos = projectile.m_origin + velocity * time;
|
||||
currentPos.z -= m_gravity * projectile.m_gravityMultiplier * std::pow(time, 2.f) * 0.5f;
|
||||
|
||||
if (currentPos.DistTo(end) <= 10.f)
|
||||
return time;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool ProjectilePredictor::IsTargetWasHit(const Vector3 &end, const Projectile &projectile, const float angle,
|
||||
const float time) const
|
||||
{
|
||||
auto launchAngles = projectile.m_origin.ViewAngleTo(end);
|
||||
launchAngles.x = angle;
|
||||
|
||||
const auto velocity = Vector3::CreateVelocity(launchAngles, projectile.m_velocity);
|
||||
|
||||
auto currentPos = projectile.m_origin + velocity * time;
|
||||
currentPos.z -= m_gravity * projectile.m_gravityMultiplier * std::pow(time, 2.f) * 0.5f;
|
||||
|
||||
|
||||
return currentPos.DistTo(end) <= 10.f;
|
||||
}
|
||||
}
|
||||
@@ -177,13 +177,13 @@ namespace uml
|
||||
return {x / v.x, y / v.y, z / v.z};
|
||||
}
|
||||
|
||||
Vector3 Vector3::CreateVelocity(const Vector3 &angles, const float length)
|
||||
Vector3 Vector3::CreateVelocity(const float pitch, const float yaw, const float speed)
|
||||
{
|
||||
return
|
||||
{
|
||||
std::cos(angles::DegToRad(angles.x)) * std::cos(angles::DegToRad(angles.y)) * length,
|
||||
std::cos(angles::DegToRad(angles.x)) * std::sin(angles::DegToRad(angles.y)) * length,
|
||||
std::sin(angles::DegToRad(angles.x)) * length,
|
||||
std::cos(angles::DegreesToRadians(pitch)) * std::cos(angles::DegreesToRadians(yaw)) * speed,
|
||||
std::cos(angles::DegreesToRadians(pitch)) * std::sin(angles::DegreesToRadians(yaw)) * speed,
|
||||
std::sin(angles::DegreesToRadians(pitch)) * speed,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -205,8 +205,8 @@ namespace uml
|
||||
// Make x negative since -89 is top and 89 is bottom
|
||||
return
|
||||
{
|
||||
-angles::RadToDeg(asinf(delta.z / distance)),
|
||||
angles::RadToDeg(atan2f(delta.y, delta.x)),
|
||||
-angles::RadiansToDegrees(asinf(delta.z / distance)),
|
||||
angles::RadiansToDegrees(atan2f(delta.y, delta.x)),
|
||||
0.f
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
namespace uml::angles
|
||||
{
|
||||
float RadToDeg(float rads)
|
||||
float RadiansToDegrees(const float radiands)
|
||||
{
|
||||
return rads * 180.f / std::numbers::pi_v<float>;
|
||||
return radiands * (180.f / std::numbers::pi_v<float>);
|
||||
}
|
||||
|
||||
float DegToRad(float degrees)
|
||||
float DegreesToRadians(const float degrees)
|
||||
{
|
||||
return degrees * std::numbers::pi_v<float> / 180.f;
|
||||
return degrees * (std::numbers::pi_v<float> / 180.f);
|
||||
}
|
||||
}
|
||||
|
||||
1
source/prediction/CMakeLists.txt
Normal file
1
source/prediction/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
target_sources(uml PRIVATE Engine.cpp Projectile.cpp Target.cpp)
|
||||
71
source/prediction/Engine.cpp
Normal file
71
source/prediction/Engine.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Created by Vlad on 6/9/2024.
|
||||
//
|
||||
|
||||
|
||||
#include "uml/prediction/Engine.h"
|
||||
#include <cmath>
|
||||
#include <uml/angles.h>
|
||||
|
||||
|
||||
namespace uml::prediction
|
||||
{
|
||||
Engine::Engine(const float gravityConstant, const float simulationTimeStep, const float maximumSimulationTime)
|
||||
: m_gravityConstant(gravityConstant),
|
||||
m_simulationTimeStep(simulationTimeStep),
|
||||
m_maximumSimulationTime(maximumSimulationTime)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<Vector3> Engine::MaybeCalculateAimPoint(const Projectile &projectile, const Target &target) const
|
||||
{
|
||||
for (float time = 0.f; time < m_maximumSimulationTime; time += m_simulationTimeStep)
|
||||
{
|
||||
const auto predictedTargetPosition = target.PredictPosition(time, m_gravityConstant);
|
||||
|
||||
const auto projectilePitch = MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition);
|
||||
|
||||
if (!projectilePitch.has_value()) [[unlikely]]
|
||||
continue;
|
||||
|
||||
if (!IsProjectileReachedTarget(predictedTargetPosition, projectile, projectilePitch.value(), time))
|
||||
continue;
|
||||
|
||||
const auto delta2d = (predictedTargetPosition - projectile.m_origin).Length2D();
|
||||
const auto height = delta2d * std::tan(angles::DegreesToRadians(projectilePitch.value()));
|
||||
|
||||
return Vector3(predictedTargetPosition.x, predictedTargetPosition.y, projectile.m_origin.z + height);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<float> Engine::MaybeCalculateProjectileLaunchPitchAngle(const Projectile &projectile,
|
||||
const Vector3 &targetPosition) const
|
||||
{
|
||||
const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale;
|
||||
const auto delta = targetPosition - projectile.m_origin;;
|
||||
|
||||
const auto distance2d = delta.Length2D();
|
||||
|
||||
|
||||
float root = std::pow(projectile.m_launchSpeed, 4.f) - bulletGravity * (bulletGravity *
|
||||
std::pow(distance2d, 2.f) + 2.0f * delta.z * std::pow(projectile.m_launchSpeed, 2.f));
|
||||
|
||||
if (root < 0.0f) [[unlikely]]
|
||||
return std::nullopt;
|
||||
|
||||
root = std::sqrt(root);
|
||||
const float angle = std::atan((std::pow(projectile.m_launchSpeed, 2.f) - root) / (bulletGravity * distance2d));
|
||||
|
||||
return angles::RadiansToDegrees(angle);
|
||||
}
|
||||
|
||||
bool Engine::IsProjectileReachedTarget(const Vector3 &targetPosition, const Projectile &projectile,
|
||||
const float pitch, const float time) const
|
||||
{
|
||||
const auto yaw = projectile.m_origin.ViewAngleTo(targetPosition).y;
|
||||
const auto projectilePosition = projectile.PredictPosition(pitch, yaw, time, m_gravityConstant);
|
||||
|
||||
return projectilePosition.DistTo(targetPosition) <= 10.f;
|
||||
}
|
||||
}
|
||||
23
source/prediction/Projectile.cpp
Normal file
23
source/prediction/Projectile.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by Vlad on 6/9/2024.
|
||||
//
|
||||
|
||||
#include "uml/prediction/Projectile.h"
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace uml::prediction
|
||||
{
|
||||
Vector3 Projectile::CalculateVelocity(const float pitch, const float yaw) const
|
||||
{
|
||||
return Vector3::CreateVelocity(pitch, yaw, m_launchSpeed);
|
||||
}
|
||||
|
||||
Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const
|
||||
{
|
||||
auto currentPos = m_origin + Vector3::CreateVelocity(pitch, yaw, m_launchSpeed) * time;
|
||||
currentPos.z -= (gravity * m_gravityScale) * std::pow(time, 2.f) * 0.5f;
|
||||
|
||||
return currentPos;
|
||||
}
|
||||
}
|
||||
20
source/prediction/Target.cpp
Normal file
20
source/prediction/Target.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Created by Vlad on 6/9/2024.
|
||||
//
|
||||
|
||||
#include "uml/prediction/Target.h"
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace uml::prediction
|
||||
{
|
||||
Vector3 Target::PredictPosition(const float time, const float gravity) const
|
||||
{
|
||||
auto predicted = m_origin + m_velocity * time;
|
||||
|
||||
if (m_isAirborne)
|
||||
predicted.z -= gravity * std::pow(time, 2.f) * 0.5f;
|
||||
|
||||
return predicted;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <uml/ProjectilePredictor.h>
|
||||
#include <uml/prediction/Engine.h>
|
||||
|
||||
TEST(x,x)
|
||||
{
|
||||
uml::prediction::Target target{.m_origin = {100, 0, 60}, .m_vecVelocity = {0,0, 0}, .m_IsAirborne = false};
|
||||
uml::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_velocity = 400, .m_gravityMultiplier= 0.4};
|
||||
auto vel = uml::prediction::ProjectilePredictor(400).PredictPointToAim(target, proj);
|
||||
uml::prediction::Target target{.m_origin = {100, 0, 60}, .m_velocity = {0, 0, 0}, .m_isAirborne = false};
|
||||
uml::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 400, .m_gravityScale= 0.4};
|
||||
auto vel = uml::prediction::Engine(400, 1.f / 10000.f, 50).MaybeCalculateAimPoint(proj, target);
|
||||
|
||||
auto pitch = proj.m_origin.ViewAngleTo(vel.value()).x;
|
||||
|
||||
// printf("pitch: %f", pitch);
|
||||
printf("pitch: %f", pitch);
|
||||
}
|
||||
Reference in New Issue
Block a user