diff --git a/CMakeLists.txt b/CMakeLists.txt index 810cac1..5197e0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.26) -project(omath VERSION 1.0.1) +project(omath VERSION 1.0.1 LANGUAGES CXX) include(CMakePackageConfigHelpers) diff --git a/README.md b/README.md index bd5de4a..0bae67a 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ For detailed commands on installing different versions and more information, ple 3. Build the project using CMake: ``` cmake --preset windows-release -S . - cmake --build cmake-build/build/windows-release --target server -j 6 + cmake --build cmake-build/build/windows-release --target omath -j 6 ``` Use **\-\** preset to build siutable version for yourself. Like **windows-release** or **linux-release**. ## ❔ Usage diff --git a/include/omath/projectile_prediction/ProjPredEngine.hpp b/include/omath/projectile_prediction/ProjPredEngine.hpp new file mode 100644 index 0000000..9938db7 --- /dev/null +++ b/include/omath/projectile_prediction/ProjPredEngine.hpp @@ -0,0 +1,20 @@ +// +// Created by Vlad on 2/23/2025. +// +#pragma once +#include "Projectile.hpp" +#include "Target.hpp" +#include "omath/Vector3.hpp" + + +namespace omath::projectile_prediction +{ + class ProjPredEngine + { + public: + [[nodiscard]] + virtual std::optional MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const = 0; + virtual ~ProjPredEngine() = default; + }; +} // namespace omath::projectile_prediction diff --git a/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp b/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp new file mode 100644 index 0000000..0c0f5f3 --- /dev/null +++ b/include/omath/projectile_prediction/ProjPredEngineAVX2.hpp @@ -0,0 +1,26 @@ +// +// Created by Vlad on 2/23/2025. +// +#pragma once +#include "ProjPredEngine.hpp" + +namespace omath::projectile_prediction +{ + class ProjPredEngineAVX2 final : public ProjPredEngine + { + public: + [[nodiscard]] std::optional MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const override; + + + ProjPredEngineAVX2(float gravityConstant, float simulationTimeStep, float maximumSimulationTime); + ~ProjPredEngineAVX2() override = default; + + private: + [[nodiscard]] static std::optional CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, + float bulletGravity, float v0, float time); + const float m_gravityConstant; + const float m_simulationTimeStep; + const float m_maximumSimulationTime; + }; +} // namespace omath::projectile_prediction diff --git a/include/omath/prediction/Engine.hpp b/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp similarity index 50% rename from include/omath/prediction/Engine.hpp rename to include/omath/projectile_prediction/ProjPredEngineLegacy.hpp index 2571cc5..6c9a9e8 100644 --- a/include/omath/prediction/Engine.hpp +++ b/include/omath/projectile_prediction/ProjPredEngineLegacy.hpp @@ -6,19 +6,22 @@ #include #include "omath/Vector3.hpp" -#include "omath/prediction/Projectile.hpp" -#include "omath/prediction/Target.hpp" +#include "omath/projectile_prediction/ProjPredEngine.hpp" +#include "omath/projectile_prediction/Projectile.hpp" +#include "omath/projectile_prediction/Target.hpp" -namespace omath::prediction + +namespace omath::projectile_prediction { - class Engine final + class ProjPredEngineLegacy final : public ProjPredEngine { public: - explicit Engine(float gravityConstant, float simulationTimeStep, - float maximumSimulationTime, float distanceTolerance); + explicit ProjPredEngineLegacy(float gravityConstant, float simulationTimeStep, float maximumSimulationTime, + float distanceTolerance); [[nodiscard]] - std::optional MaybeCalculateAimPoint(const Projectile& projectile, const Target& target) const; + std::optional MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const override; private: const float m_gravityConstant; @@ -32,7 +35,7 @@ namespace omath::prediction [[nodiscard]] - bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, float time) const; - + bool IsProjectileReachedTarget(const Vector3& targetPosition, const Projectile& projectile, float pitch, + float time) const; }; -} \ No newline at end of file +} // namespace omath::projectile_prediction diff --git a/include/omath/prediction/Projectile.hpp b/include/omath/projectile_prediction/Projectile.hpp similarity index 89% rename from include/omath/prediction/Projectile.hpp rename to include/omath/projectile_prediction/Projectile.hpp index 5499815..4292100 100644 --- a/include/omath/prediction/Projectile.hpp +++ b/include/omath/projectile_prediction/Projectile.hpp @@ -5,7 +5,7 @@ #pragma once #include "omath/Vector3.hpp" -namespace omath::prediction +namespace omath::projectile_prediction { class Projectile final { diff --git a/include/omath/prediction/Target.hpp b/include/omath/projectile_prediction/Target.hpp similarity index 93% rename from include/omath/prediction/Target.hpp rename to include/omath/projectile_prediction/Target.hpp index f3a775e..7dfed42 100644 --- a/include/omath/prediction/Target.hpp +++ b/include/omath/projectile_prediction/Target.hpp @@ -5,7 +5,7 @@ #pragma once #include "omath/Vector3.hpp" -namespace omath::prediction +namespace omath::projectile_prediction { class Target final { diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index fe42cba..b7a84af 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -6,7 +6,7 @@ target_sources(omath PRIVATE Vector2.cpp ) -add_subdirectory(prediction) +add_subdirectory(projectile_prediction) add_subdirectory(pathfinding) add_subdirectory(projection) add_subdirectory(collision) diff --git a/source/prediction/CMakeLists.txt b/source/prediction/CMakeLists.txt deleted file mode 100644 index da6b5de..0000000 --- a/source/prediction/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -target_sources(omath PRIVATE Engine.cpp Projectile.cpp Target.cpp) \ No newline at end of file diff --git a/source/projectile_prediction/CMakeLists.txt b/source/projectile_prediction/CMakeLists.txt new file mode 100644 index 0000000..623aa65 --- /dev/null +++ b/source/projectile_prediction/CMakeLists.txt @@ -0,0 +1 @@ +target_sources(omath PRIVATE ProjPredEngineLegacy.cpp Projectile.cpp Target.cpp ProjPredEngineAVX2.cpp ProjPredEngine.cpp) \ No newline at end of file diff --git a/source/projectile_prediction/ProjPredEngine.cpp b/source/projectile_prediction/ProjPredEngine.cpp new file mode 100644 index 0000000..7be6708 --- /dev/null +++ b/source/projectile_prediction/ProjPredEngine.cpp @@ -0,0 +1,10 @@ +// +// Created by Vlad on 2/23/2025. +// +#include "omath/projectile_prediction/ProjPredEngine.hpp" + + +namespace omath::projectile_prediction +{ + +} // namespace omath::projectile_prediction diff --git a/source/projectile_prediction/ProjPredEngineAVX2.cpp b/source/projectile_prediction/ProjPredEngineAVX2.cpp new file mode 100644 index 0000000..9787e1a --- /dev/null +++ b/source/projectile_prediction/ProjPredEngineAVX2.cpp @@ -0,0 +1,138 @@ +// +// Created by Vlad on 2/23/2025. +// +#include "omath/projectile_prediction/ProjPredEngineAVX2.hpp" + + +namespace omath::projectile_prediction +{ + std::optional ProjPredEngineAVX2::MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const + { + const float bulletGravity = m_gravityConstant * projectile.m_gravityScale; + const float v0 = projectile.m_launchSpeed; + const float v0Sqr = v0 * v0; + const Vector3 projOrigin = projectile.m_origin; + + constexpr int SIMD_FACTOR = 8; + float currentTime = m_simulationTimeStep; + + for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep * SIMD_FACTOR) + { + const __m256 times = + _mm256_setr_ps(currentTime, currentTime + m_simulationTimeStep, + currentTime + m_simulationTimeStep * 2, currentTime + m_simulationTimeStep * 3, + currentTime + m_simulationTimeStep * 4, currentTime + m_simulationTimeStep * 5, + currentTime + m_simulationTimeStep * 6, currentTime + m_simulationTimeStep * 7); + + const __m256 targetX = + _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.x), times, _mm256_set1_ps(target.m_origin.x)); + const __m256 targetY = + _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.y), times, _mm256_set1_ps(target.m_origin.y)); + const __m256 timesSq = _mm256_mul_ps(times, times); + const __m256 targetZ = _mm256_fmadd_ps(_mm256_set1_ps(target.m_velocity.z), times, + _mm256_fnmadd_ps(_mm256_set1_ps(0.5f * m_gravityConstant), timesSq, + _mm256_set1_ps(target.m_origin.z))); + + const __m256 deltaX = _mm256_sub_ps(targetX, _mm256_set1_ps(projOrigin.x)); + const __m256 deltaY = _mm256_sub_ps(targetY, _mm256_set1_ps(projOrigin.y)); + const __m256 deltaZ = _mm256_sub_ps(targetZ, _mm256_set1_ps(projOrigin.z)); + + const __m256 dSqr = _mm256_add_ps(_mm256_mul_ps(deltaX, deltaX), _mm256_mul_ps(deltaY, deltaY)); + + const __m256 bgTimesSq = _mm256_mul_ps(_mm256_set1_ps(bulletGravity), timesSq); + const __m256 term = _mm256_add_ps(deltaZ, _mm256_mul_ps(_mm256_set1_ps(0.5f), bgTimesSq)); + const __m256 termSq = _mm256_mul_ps(term, term); + const __m256 numerator = _mm256_add_ps(dSqr, termSq); + const __m256 denominator = _mm256_add_ps(timesSq, _mm256_set1_ps(1e-8f)); // Avoid division by zero + const __m256 requiredV0Sqr = _mm256_div_ps(numerator, denominator); + + const __m256 v0SqrVec = _mm256_set1_ps(v0Sqr + 1e-3f); + const __m256 mask = _mm256_cmp_ps(requiredV0Sqr, v0SqrVec, _CMP_LE_OQ); + + const unsigned validMask = _mm256_movemask_ps(mask); + + if (!validMask) + continue; + + alignas(32) float validTimes[SIMD_FACTOR]; + _mm256_store_ps(validTimes, times); + + for (int i = 0; i < SIMD_FACTOR; ++i) + { + if (!(validMask & (1 << i))) + continue; + + const float candidateTime = validTimes[i]; + + if (candidateTime > m_maximumSimulationTime) + continue; + + // Fine search around candidate time + for (float fineTime = candidateTime - m_simulationTimeStep * 2; + fineTime <= candidateTime + m_simulationTimeStep * 2; fineTime += m_simulationTimeStep) + { + if (fineTime < 0) + continue; + + const Vector3 targetPos = target.PredictPosition(fineTime, m_gravityConstant); + const auto pitch = CalculatePitch(projOrigin, targetPos, bulletGravity, v0, fineTime); + if (!pitch) + continue; + + const Vector3 delta = targetPos - projOrigin; + const float d = std::sqrt(delta.x * delta.x + delta.y * delta.y); + const float height = d * std::tan(angles::DegreesToRadians(*pitch)); + return Vector3(targetPos.x, targetPos.y, projOrigin.z + height); + } + } + } + + // Fallback scalar processing for remaining times + for (; currentTime <= m_maximumSimulationTime; currentTime += m_simulationTimeStep) + { + const Vector3 targetPos = target.PredictPosition(currentTime, m_gravityConstant); + const auto pitch = CalculatePitch(projOrigin, targetPos, bulletGravity, v0, currentTime); + if (!pitch) + continue; + + const Vector3 delta = targetPos - projOrigin; + const float d = std::sqrt(delta.x * delta.x + delta.y * delta.y); + const float height = d * std::tan(angles::DegreesToRadians(*pitch)); + return Vector3(targetPos.x, targetPos.y, projOrigin.z + height); + } + + return std::nullopt; + } + ProjPredEngineAVX2::ProjPredEngineAVX2(const float gravityConstant, const float simulationTimeStep, + const float maximumSimulationTime) : + m_gravityConstant(gravityConstant), m_simulationTimeStep(maximumSimulationTime), + m_maximumSimulationTime(simulationTimeStep) + { + } + std::optional ProjPredEngineAVX2::CalculatePitch(const Vector3& projOrigin, const Vector3& targetPos, + const float bulletGravity, const float v0, const float time) + { + if (time <= 0.0f) + return std::nullopt; + + const Vector3 delta = targetPos - projOrigin; + const float dSqr = delta.x * delta.x + delta.y * delta.y; + const float h = delta.z; + + const float term = h + 0.5f * bulletGravity * time * time; + const float requiredV0Sqr = (dSqr + term * term) / (time * time); + const float v0Sqr = v0 * v0; + + if (requiredV0Sqr > v0Sqr + 1e-3f) + return std::nullopt; + + if (dSqr == 0.0f) + return term >= 0.0f ? 90.0f : -90.0f; + + + const float d = std::sqrt(dSqr); + const float tanTheta = term / d; + return angles::RadiansToDegrees(std::atan(tanTheta)); + } +} // namespace omath::projectile_prediction diff --git a/source/prediction/Engine.cpp b/source/projectile_prediction/ProjPredEngineLegacy.cpp similarity index 57% rename from source/prediction/Engine.cpp rename to source/projectile_prediction/ProjPredEngineLegacy.cpp index 4452a52..84c5e22 100644 --- a/source/prediction/Engine.cpp +++ b/source/projectile_prediction/ProjPredEngineLegacy.cpp @@ -1,25 +1,18 @@ -// -// Created by Vlad on 6/9/2024. -// - - -#include "omath/prediction/Engine.hpp" +#include "omath/projectile_prediction/ProjPredEngineLegacy.hpp" #include #include - -namespace omath::prediction +namespace omath::projectile_prediction { - Engine::Engine(const float gravityConstant, const float simulationTimeStep, - const float maximumSimulationTime, const float distanceTolerance) - : m_gravityConstant(gravityConstant), - m_simulationTimeStep(simulationTimeStep), - m_maximumSimulationTime(maximumSimulationTime), - m_distanceTolerance(distanceTolerance) + ProjPredEngineLegacy::ProjPredEngineLegacy(const float gravityConstant, const float simulationTimeStep, + const float maximumSimulationTime, const float distanceTolerance) : + m_gravityConstant(gravityConstant), m_simulationTimeStep(simulationTimeStep), + m_maximumSimulationTime(maximumSimulationTime), m_distanceTolerance(distanceTolerance) { } - std::optional Engine::MaybeCalculateAimPoint(const Projectile &projectile, const Target &target) const + std::optional ProjPredEngineLegacy::MaybeCalculateAimPoint(const Projectile& projectile, + const Target& target) const { for (float time = 0.f; time < m_maximumSimulationTime; time += m_simulationTimeStep) { @@ -28,7 +21,7 @@ namespace omath::prediction const auto projectilePitch = MaybeCalculateProjectileLaunchPitchAngle(projectile, predictedTargetPosition); if (!projectilePitch.has_value()) [[unlikely]] - continue; + continue; if (!IsProjectileReachedTarget(predictedTargetPosition, projectile, projectilePitch.value(), time)) continue; @@ -41,8 +34,9 @@ namespace omath::prediction return std::nullopt; } - std::optional Engine::MaybeCalculateProjectileLaunchPitchAngle(const Projectile &projectile, - const Vector3 &targetPosition) const + std::optional + ProjPredEngineLegacy::MaybeCalculateProjectileLaunchPitchAngle(const Projectile& projectile, + const Vector3& targetPosition) const { const auto bulletGravity = m_gravityConstant * projectile.m_gravityScale; const auto delta = targetPosition - projectile.m_origin; @@ -51,11 +45,11 @@ namespace omath::prediction const auto distance2dSqr = distance2d * distance2d; const auto launchSpeedSqr = projectile.m_launchSpeed * projectile.m_launchSpeed; - float root = launchSpeedSqr * launchSpeedSqr - bulletGravity * (bulletGravity * - distance2dSqr + 2.0f * delta.z * launchSpeedSqr); + float root = launchSpeedSqr * launchSpeedSqr - + bulletGravity * (bulletGravity * distance2dSqr + 2.0f * delta.z * launchSpeedSqr); if (root < 0.0f) [[unlikely]] - return std::nullopt; + return std::nullopt; root = std::sqrt(root); const float angle = std::atan((launchSpeedSqr - root) / (bulletGravity * distance2d)); @@ -63,12 +57,12 @@ namespace omath::prediction return angles::RadiansToDegrees(angle); } - bool Engine::IsProjectileReachedTarget(const Vector3 &targetPosition, const Projectile &projectile, - const float pitch, const float time) const + bool ProjPredEngineLegacy::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) <= m_distanceTolerance; } -} +} // namespace omath::projectile_prediction diff --git a/source/prediction/Projectile.cpp b/source/projectile_prediction/Projectile.cpp similarity index 88% rename from source/prediction/Projectile.cpp rename to source/projectile_prediction/Projectile.cpp index 281b327..c1ce156 100644 --- a/source/prediction/Projectile.cpp +++ b/source/projectile_prediction/Projectile.cpp @@ -2,11 +2,11 @@ // Created by Vlad on 6/9/2024. // -#include "omath/prediction/Projectile.hpp" -#include +#include "omath/projectile_prediction/Projectile.hpp" + #include -namespace omath::prediction +namespace omath::projectile_prediction { Vector3 Projectile::PredictPosition(const float pitch, const float yaw, const float time, const float gravity) const { diff --git a/source/prediction/Target.cpp b/source/projectile_prediction/Target.cpp similarity index 57% rename from source/prediction/Target.cpp rename to source/projectile_prediction/Target.cpp index 9b12395..5c30ee0 100644 --- a/source/prediction/Target.cpp +++ b/source/projectile_prediction/Target.cpp @@ -2,7 +2,7 @@ // Created by Vlad on 6/9/2024. // -#include "omath/prediction/Target.hpp" +#include "omath/projectile_prediction/Projectile.hpp" namespace omath::prediction diff --git a/tests/general/UnitTestPrediction.cpp b/tests/general/UnitTestPrediction.cpp index 5002d39..c77c438 100644 --- a/tests/general/UnitTestPrediction.cpp +++ b/tests/general/UnitTestPrediction.cpp @@ -1,15 +1,17 @@ #include -#include +#include TEST(UnitTestPrediction, PredictionTest) { - constexpr omath::prediction::Target target{ + constexpr omath::projectile_prediction::Target target{ .m_origin = {100, 0, 90}, .m_velocity = {0, 0, 0}, .m_isAirborne = false}; - constexpr omath::prediction::Projectile proj = {.m_origin = {3,2,1}, .m_launchSpeed = 5000, .m_gravityScale= 0.4}; - const auto viewPoint = omath::prediction::Engine(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); + constexpr omath::projectile_prediction::Projectile proj = { + .m_origin = {3, 2, 1}, .m_launchSpeed = 5000, .m_gravityScale = 0.4}; + const auto viewPoint = + omath::projectile_prediction::ProjPredEngineLegacy(400, 1.f / 1000.f, 50, 5.f).MaybeCalculateAimPoint(proj, target); const auto [pitch, yaw, _] = proj.m_origin.ViewAngleTo(viewPoint.value()).AsTuple(); - EXPECT_NEAR(42.547142, pitch, 0.0001f); - EXPECT_NEAR(-1.181189, yaw, 0.0001f); -} \ No newline at end of file + EXPECT_NEAR(42.547142, pitch, 0.01f); + EXPECT_NEAR(-1.181189, yaw, 0.01f); +}