diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dfef16..87ccf28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/uml/ProjectilePredictor.h b/include/uml/ProjectilePredictor.h deleted file mode 100644 index 20a0a75..0000000 --- a/include/uml/ProjectilePredictor.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// Created by vlad on 11/6/23. -// - -#pragma once -#include -#include - - -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 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 - MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, - const Vector3& targetPosition) const; - - [[nodiscard]] - std::optional ProjectileTravelTime(const Vector3& end, - const Projectile& projectile, - float angle) const; - - [[nodiscard]] - bool IsTargetWasHit(const Vector3& end, const Projectile& projectile, float angle, float time) const; - }; - -}; diff --git a/include/uml/Vector3.h b/include/uml/Vector3.h index 06f6507..ea4348a 100644 --- a/include/uml/Vector3.h +++ b/include/uml/Vector3.h @@ -57,7 +57,7 @@ namespace uml { return *reinterpret_cast(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; diff --git a/include/uml/angles.h b/include/uml/angles.h index 6726896..39b9a07 100644 --- a/include/uml/angles.h +++ b/include/uml/angles.h @@ -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); } \ No newline at end of file diff --git a/include/uml/prediction/Engine.h b/include/uml/prediction/Engine.h new file mode 100644 index 0000000..9971271 --- /dev/null +++ b/include/uml/prediction/Engine.h @@ -0,0 +1,37 @@ +// +// Created by Vlad on 6/9/2024. +// + +#pragma once + +#include +#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 MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const; + + private: + const float m_gravityConstant; + const float m_simulationTimeStep; + const float m_maximumSimulationTime; + + [[nodiscard]] + std::optional MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, + const Vector3& targetPosition) const; + + + [[nodiscard]] + bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, float time) const; + + }; +} \ No newline at end of file diff --git a/include/uml/prediction/Projectile.h b/include/uml/prediction/Projectile.h new file mode 100644 index 0000000..58603bd --- /dev/null +++ b/include/uml/prediction/Projectile.h @@ -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{}; + }; +} \ No newline at end of file diff --git a/include/uml/prediction/Target.h b/include/uml/prediction/Target.h new file mode 100644 index 0000000..db5eb46 --- /dev/null +++ b/include/uml/prediction/Target.h @@ -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{}; + }; +} \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 80bd636..c8faebe 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -2,6 +2,7 @@ target_sources(uml PRIVATE Vector3.cpp matrix.cpp angles.cpp - ProjectilePredictor.cpp color.cpp - Vector4.cpp) \ No newline at end of file + Vector4.cpp) + +add_subdirectory(prediction) \ No newline at end of file diff --git a/source/ProjectilePredictor.cpp b/source/ProjectilePredictor.cpp deleted file mode 100644 index 7145452..0000000 --- a/source/ProjectilePredictor.cpp +++ /dev/null @@ -1,118 +0,0 @@ -// -// Created by vlad on 11/6/23. -// - -#include "uml/ProjectilePredictor.h" -#include "uml/Vector3.h" -#include "uml/angles.h" - -#include -#include - -namespace uml::prediction -{ - ProjectilePredictor::ProjectilePredictor(float gravityValue, - float maxTimeToTravel, - float timeStep) - : m_gravity(gravityValue), m_maxTravelTime(maxTimeToTravel), m_timeStepSize(timeStep) - { - - } - - std::optional 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 - 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 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; - } -} diff --git a/source/Vector3.cpp b/source/Vector3.cpp index 4769889..7fb27f7 100644 --- a/source/Vector3.cpp +++ b/source/Vector3.cpp @@ -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 }; } diff --git a/source/angles.cpp b/source/angles.cpp index 0195090..26b9f0f 100644 --- a/source/angles.cpp +++ b/source/angles.cpp @@ -8,13 +8,13 @@ namespace uml::angles { - float RadToDeg(float rads) + float RadiansToDegrees(const float radiands) { - return rads * 180.f / std::numbers::pi_v; + return radiands * (180.f / std::numbers::pi_v); } - float DegToRad(float degrees) + float DegreesToRadians(const float degrees) { - return degrees * std::numbers::pi_v / 180.f; + return degrees * (std::numbers::pi_v / 180.f); } } diff --git a/source/prediction/CMakeLists.txt b/source/prediction/CMakeLists.txt new file mode 100644 index 0000000..6964df7 --- /dev/null +++ b/source/prediction/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(uml PRIVATE Engine.cpp Projectile.cpp Target.cpp) \ No newline at end of file diff --git a/source/prediction/Engine.cpp b/source/prediction/Engine.cpp new file mode 100644 index 0000000..2205fce --- /dev/null +++ b/source/prediction/Engine.cpp @@ -0,0 +1,71 @@ +// +// Created by Vlad on 6/9/2024. +// + + +#include "uml/prediction/Engine.h" +#include +#include + + +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 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 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; + } +} diff --git a/source/prediction/Projectile.cpp b/source/prediction/Projectile.cpp new file mode 100644 index 0000000..e65144d --- /dev/null +++ b/source/prediction/Projectile.cpp @@ -0,0 +1,23 @@ +// +// Created by Vlad on 6/9/2024. +// + +#include "uml/prediction/Projectile.h" +#include + + +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; + } +} diff --git a/source/prediction/Target.cpp b/source/prediction/Target.cpp new file mode 100644 index 0000000..9783897 --- /dev/null +++ b/source/prediction/Target.cpp @@ -0,0 +1,20 @@ +// +// Created by Vlad on 6/9/2024. +// + +#include "uml/prediction/Target.h" +#include + + +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; + } +} diff --git a/tests/UnitTestColor.cpp b/tests/UnitTestColor.cpp index 3e45514..02700e5 100644 --- a/tests/UnitTestColor.cpp +++ b/tests/UnitTestColor.cpp @@ -1,13 +1,13 @@ #include -#include +#include 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); } \ No newline at end of file