From 11769aa7def2b81ac0f4a5afdaaa5f5549eff865 Mon Sep 17 00:00:00 2001 From: Danilo Date: Sat, 18 May 2024 16:09:33 -0300 Subject: [PATCH] Implementa Curve --- framework/CMakeLists.txt | 2 +- inc/common/curve.hpp | 333 +++++++++++++++++++++++++++++++++++++++ inc/enums.hpp | 20 +++ inc/forward.hpp | 1 - inc/xna.hpp | 2 +- 5 files changed, 355 insertions(+), 3 deletions(-) create mode 100644 inc/common/curve.hpp diff --git a/framework/CMakeLists.txt b/framework/CMakeLists.txt index da8dfe6..6175eea 100644 --- a/framework/CMakeLists.txt +++ b/framework/CMakeLists.txt @@ -51,7 +51,7 @@ add_executable (xna WIN32 "common/quaternion.cpp" "common/collision.cpp" "common/gjk.cpp" -) + ) if (CMAKE_VERSION VERSION_GREATER 3.12) set_property(TARGET xna PROPERTY CXX_STANDARD 20) diff --git a/inc/common/curve.hpp b/inc/common/curve.hpp new file mode 100644 index 0000000..c2f28a3 --- /dev/null +++ b/inc/common/curve.hpp @@ -0,0 +1,333 @@ +#ifndef XNA_COMMON_CURVE_HPP +#define XNA_COMMON_CURVE_HPP + +#include "../default.hpp" +#include + +namespace xna { + struct CurveKey { + float Position{ 0 }; + float Value{ 0 }; + float TangentOut{ 0 }; + float TangentIn{ 0 }; + CurveContinuity Continuity{ CurveContinuity::Smooth }; + + constexpr CurveKey() = default; + constexpr CurveKey(float position, float value) : + Position(position), Value(value) {} + constexpr CurveKey(float position, float value, float tangentIn, float tangentOut) : + Position(position), Value(value), TangentIn(tangentIn), TangentOut(tangentOut) {} + constexpr CurveKey(float position, float value, float tangentIn, float tangentOut, CurveContinuity continuity) : + Position(position), Value(value), TangentIn(tangentIn), TangentOut(tangentOut), Continuity(continuity) {} + + constexpr bool operator==(const CurveKey& other) const { + return Position == other.Position + && Value == other.Value + && TangentOut == other.TangentOut + && TangentIn == other.TangentIn + && Continuity == other.Continuity; + } + + constexpr bool operator<(CurveKey const& other) const { + return Position < other.Position; + } + }; + + struct CurveKeyCollection { + constexpr CurveKeyCollection() = default; + + constexpr size_t IndexOf(CurveKey const& item) { + auto it = std::find(Keys.begin(), Keys.end(), item); + auto value = std::distance(it, Keys.begin()); + + return static_cast(value); + } + + constexpr void RemoveAt(size_t index) { + if (index >= Keys.size()) + return; + + Keys.erase(Keys.begin() + index); + IsCachedAvailable = false; + } + + constexpr CurveKey& operator[](size_t index) { + if (index >= Keys.size()) + index = Keys.size() - 1; + + return Keys[index]; + } + + constexpr void Add(CurveKey const& item, bool sortItems = true) { + Keys.push_back(item); + + if (sortItems) + std::sort(Keys.begin(), Keys.end()); + + IsCachedAvailable = false; + } + + constexpr void Sort() { + std::sort(Keys.begin(), Keys.end()); + } + + constexpr void Clear() { + Keys.clear(); + TimeRange = 0.0f; + InvTimeRange = 0.0f; + IsCachedAvailable = false; + } + + constexpr bool Contains(CurveKey const& item) const { + auto it = std::find(Keys.begin(), Keys.end(), item); + + return it == Keys.end(); + } + + constexpr size_t Count() const { return Keys.size(); } + + constexpr bool Remove(CurveKey const& item) { + auto it = std::find(Keys.begin(), Keys.end(), item); + + if (it == Keys.end()) + return false; + + Keys.erase(it); + + IsCachedAvailable = false; + } + + constexpr void ComputeCacheValues() { + TimeRange = 0.0F; + InvTimeRange = 0.0f; + + if (Keys.size() > 1) { + TimeRange = Keys[Keys.size() - 1].Position - Keys[0].Position; + + if (TimeRange > 1.4012984643248171E-45) + InvTimeRange = 1.0f / TimeRange; + } + + IsCachedAvailable = true; + } + + public: + std::vector Keys; + + private: + friend class Curve; + + float TimeRange{ 0.0F }; + float InvTimeRange{ 0.0F }; + bool IsCachedAvailable{ true }; + }; + + struct Curve { + CurveLoopType PreLoop{ CurveLoopType::Constant }; + CurveLoopType PostLoop{ CurveLoopType::Constant }; + CurveKeyCollection Keys{}; + + constexpr Curve() = default; + + constexpr bool IsConstant() const { return Keys.Count() <= 1; } + + constexpr void ComputeTangent(size_t keyIndex, CurveTangent tangentType) { + ComputeTangent(keyIndex, tangentType, tangentType); + } + + constexpr void ComputeTangent(size_t keyIndex, CurveTangent tangentInType, CurveTangent tangentOutType) { + if (Keys.Count() <= keyIndex) + return; + + auto& key = Keys[keyIndex]; + auto num1 = key.Position; + auto num2 = key.Position; + auto num3 = key.Position; + auto num5 = key.Value; + auto num6 = key.Value; + auto num7 = key.Value; + + if (keyIndex > 0) + { + num3 = Keys[keyIndex - 1].Position; + num7 = Keys[keyIndex - 1].Value; + } + if (keyIndex + 1 < Keys.Count()) + { + num1 = Keys[keyIndex + 1].Position; + num5 = Keys[keyIndex + 1].Value; + } + + switch (tangentInType) + { + case CurveTangent::Linear: { + key.TangentIn = num6 - num7; + break; + } + case CurveTangent::Smooth: { + auto num8 = num1 - num3; + auto num9 = num5 - num7; + key.TangentIn = std::abs(num9) >= 1.1920928955078125E-07 ? num9 * std::abs(num3 - num2) / num8 : 0.0f; + break; + } + default: { + key.TangentIn = 0.0f; + break; + } + } + + switch (tangentOutType) + { + case CurveTangent::Linear: { + key.TangentOut = num5 - num6; + break; + } + case CurveTangent::Smooth: { + auto num10 = num1 - num3; + auto num11 = num5 - num7; + if (std::abs(num11) < 1.1920928955078125E-07) + { + key.TangentOut = 0.0f; + break; + } + key.TangentOut = num11 * std::abs(num1 - num2) / num10; + break; + } + default: { + key.TangentOut = 0.0f; + break; + } + } + } + + constexpr void ComputeTangents(CurveTangent tangentType) { + ComputeTangents(tangentType, tangentType); + } + + constexpr void ComputeTangents(CurveTangent tangentInType, CurveTangent tangentOutType) { + for (size_t keyIndex = 0; keyIndex < Keys.Count(); ++keyIndex) + ComputeTangent(keyIndex, tangentInType, tangentOutType); + } + + constexpr float Evaluate(float position) { + if (Keys.Count() == 0) + return 0.0f; + if (Keys.Count() == 1) + return Keys[0].Value; + + auto& key1 = Keys[0]; + auto& key2 = Keys[Keys.Count() - 1]; + float t = position; + float num1 = 0.0f; + + if (t < key1.Position) + { + if (PreLoop == CurveLoopType::Constant) + return key1.Value; + + if (PreLoop == CurveLoopType::Linear) + return key1.Value - key1.TangentIn * (key1.Position - t); + + if (!Keys.IsCachedAvailable) + Keys.ComputeCacheValues(); + + float num2 = CalcCycle(t); + float num3 = t - (key1.Position + num2 * Keys.TimeRange); + + if (PreLoop == CurveLoopType::Cycle) + t = key1.Position + num3; + + else if (PreLoop == CurveLoopType::CycleOffset) { + t = key1.Position + num3; + num1 = (key2.Value - key1.Value) * num2; + } + else { + t = (static_cast(num2) & 1) != 0 ? key2.Position - num3 : key1.Position + num3; + } + } + else if (key2.Position < t) + { + if (PostLoop == CurveLoopType::Constant) + return key2.Value; + + if (PostLoop == CurveLoopType::Linear) + return key2.Value - key2.TangentOut * (key2.Position - t); + + if (!Keys.IsCachedAvailable) + Keys.ComputeCacheValues(); + + float num4 = CalcCycle(t); + float num5 = t - (key1.Position + num4 * Keys.TimeRange); + + if (PostLoop == CurveLoopType::Cycle) + t = key1.Position + num5; + + else if (PostLoop == CurveLoopType::CycleOffset) + { + t = key1.Position + num5; + num1 = (key2.Value - key1.Value) * num4; + } + else { + t = (static_cast(num4) & 1) != 0 ? key2.Position - num5 : key1.Position + num5; + } + } + + CurveKey k0; + CurveKey k1; + float segment = FindSegment(t, k0, k1); + return num1 + Curve::Hermite(k0, k1, segment); + } + + private: + constexpr float CalcCycle(float t) { + auto num = (t - Keys[0].Position) * Keys.InvTimeRange; + + if (num < 0.0) + --num; + + return static_cast(num); + } + + constexpr float FindSegment(float t, CurveKey& k0, CurveKey& k1) { + float segment = t; + k0 = Keys[0]; + + for (size_t index = 1; index < Keys.Count(); ++index) + { + k1 = Keys[index]; + if (k1.Position >= t) + { + const auto position1 = k0.Position; + const auto position2 = k1.Position; + const auto num1 = t; + const auto num2 = position2 - position1; + segment = 0.0f; + if (num2 > 1E-10) + { + segment = ((num1 - position1) / num2); + break; + } + break; + } + k0 = k1; + } + return segment; + } + + static constexpr float Hermite(CurveKey k0, CurveKey k1, float t) { + + if (k0.Continuity == CurveContinuity::Step) + return t >= 1.0 ? k1.Value : k0.Value; + + const auto num1 = t * t; + const auto num2 = num1 * t; + const auto internalValue1 = k0.Value; + const auto internalValue2 = k1.Value; + const auto tangentOut = k0.TangentOut; + const auto tangentIn = k1.TangentIn; + return (internalValue1 * (2.0F * num2 - 3.0F * num1 + 1.0F) + internalValue2 * (-2.0F * num2 + 3.0F * num1) + tangentOut * (num2 - 2.0F * num1 + t) + tangentIn * (num2 - num1)); + } + }; +} + +#endif \ No newline at end of file diff --git a/inc/enums.hpp b/inc/enums.hpp index 06323a3..d86d559 100644 --- a/inc/enums.hpp +++ b/inc/enums.hpp @@ -132,6 +132,26 @@ namespace xna { GreaterEqual, Always }; + + enum class CurveContinuity { + Smooth, + Step, + }; + + enum class CurveTangent + { + Flat, + Linear, + Smooth, + }; + + enum class CurveLoopType { + Constant, + Cycle, + CycleOffset, + Oscillate, + Linear, + }; enum class CullMode { None, diff --git a/inc/forward.hpp b/inc/forward.hpp index 1019762..49f7324 100644 --- a/inc/forward.hpp +++ b/inc/forward.hpp @@ -30,7 +30,6 @@ namespace xna { struct BoundingSphere; struct Color; struct Curve; - struct CurveContinuity; struct CurveKey; struct CurveKeyCollection; struct Matrix; diff --git a/inc/xna.hpp b/inc/xna.hpp index d4deadc..d55d385 100644 --- a/inc/xna.hpp +++ b/inc/xna.hpp @@ -1,11 +1,11 @@ #include "xnaerror.hpp" #include "types.hpp" #include "helpers.hpp" -#include "forward.hpp" #include "enums.hpp" #include "audio/audioengine.hpp" #include "audio/soundeffect.hpp" #include "common/color.hpp" +#include "common/curve.hpp" #include "common/gjk.hpp" #include "common/math.hpp" #include "common/matrix.hpp"