// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #pragma once #include "AnimationUtils.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Types/Span.h" // @formatter:off /// /// A single keyframe that can be injected into an animation curve (step). /// PACK_BEGIN() template struct StepCurveKeyframe { public: /// /// The time of the keyframe. /// float Time; /// /// The value of the curve at keyframe. /// T Value; public: StepCurveKeyframe() { } StepCurveKeyframe(float time, const T& value) { Time = time; Value = value; } public: FORCE_INLINE static void Interpolate(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, T& result) { result = a.Value; } FORCE_INLINE static void InterpolateFirstDerivative(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, T& result) { result = AnimationUtils::GetZero(); } FORCE_INLINE static void InterpolateKey(const StepCurveKeyframe& a, const StepCurveKeyframe& b, float alpha, float length, StepCurveKeyframe& result) { result = a; } bool operator==(const StepCurveKeyframe& other) const { return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); } } PACK_END(); /// /// A single keyframe that can be injected into an animation curve (linear). /// PACK_BEGIN() template struct LinearCurveKeyframe { public: /// /// The time of the keyframe. /// float Time; /// /// The value of the curve at keyframe. /// T Value; public: LinearCurveKeyframe() { } LinearCurveKeyframe(float time, const T& value) { Time = time; Value = value; } public: FORCE_INLINE static void Interpolate(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, T& result) { AnimationUtils::Interpolate(a.Value, b.Value, alpha, result); } FORCE_INLINE static void InterpolateFirstDerivative(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, T& result) { result = b.Value - a.Value; } FORCE_INLINE static void InterpolateKey(const LinearCurveKeyframe& a, const LinearCurveKeyframe& b, float alpha, float length, LinearCurveKeyframe& result) { result.Time = a.Time + (b.Time - a.Time) * alpha; AnimationUtils::Interpolate(a.Value, b.Value, alpha, result.Value); } bool operator==(const LinearCurveKeyframe& other) const { return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value); } } PACK_END(); /// /// A single keyframe that can be injected into cubic hermite curve. /// PACK_BEGIN() template struct HermiteCurveKeyframe { public: /// /// The time of the keyframe. /// float Time; /// /// The value of the curve at keyframe. /// T Value; /// /// The input tangent (going from the previous key to this one) of the key. /// T TangentIn; /// /// The output tangent (going from this key to next one) of the key. /// T TangentOut; public: HermiteCurveKeyframe() { } HermiteCurveKeyframe(float time, const T& value) { Time = time; Value = value; TangentIn = TangentOut = AnimationUtils::GetZero(); } public: static void Interpolate(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, T& result) { T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; AnimationUtils::CubicHermite( a.Value, b.Value, leftTangent, rightTangent, alpha, result); } static void InterpolateFirstDerivative(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, T& result) { T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; AnimationUtils::CubicHermiteFirstDerivative( a.Value, b.Value, leftTangent, rightTangent, alpha, result); } static void InterpolateKey(const HermiteCurveKeyframe& a, const HermiteCurveKeyframe& b, float alpha, float length, HermiteCurveKeyframe& result) { result.Time = a.Time + length * alpha; T leftTangent = a.Value + a.TangentOut * length; T rightTangent = b.Value + b.TangentIn * length; AnimationUtils::CubicHermite(a.Value, b.Value, leftTangent, rightTangent, alpha, result.Value); AnimationUtils::CubicHermiteFirstDerivative(a.Value, b.Value, leftTangent, rightTangent, alpha, result.TangentIn); result.TangentIn /= length; result.TangentOut = result.TangentIn; } bool operator==(const HermiteCurveKeyframe& other) const { return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); } } PACK_END(); /// /// A single keyframe that can be injected into Bezier curve. /// PACK_BEGIN() template struct BezierCurveKeyframe { public: /// /// The time of the keyframe. /// float Time; /// /// The value of the curve at keyframe. /// T Value; /// /// The input tangent (going from the previous key to this one) of the key. /// T TangentIn; /// /// The output tangent (going from this key to next one) of the key. /// T TangentOut; public: BezierCurveKeyframe() { } BezierCurveKeyframe(float time, const T& value) { Time = time; Value = value; TangentIn = TangentOut = AnimationUtils::GetZero(); } BezierCurveKeyframe(float time, const T& value, const T& tangentIn, const T& tangentOut) { Time = time; Value = value; TangentIn = tangentIn; TangentOut = tangentOut; } public: static void Interpolate(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result) { T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result); } static void InterpolateFirstDerivative(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, T& result) { T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); AnimationUtils::BezierFirstDerivative(a.Value, leftTangent, rightTangent, b.Value, alpha, result); } static void InterpolateKey(const BezierCurveKeyframe& a, const BezierCurveKeyframe& b, float alpha, float length, BezierCurveKeyframe& result) { result.Time = a.Time + length * alpha; T leftTangent, rightTangent; AnimationUtils::GetTangent(a.Value, a.TangentOut, length, leftTangent); AnimationUtils::GetTangent(b.Value, b.TangentIn, length, rightTangent); AnimationUtils::Bezier(a.Value, leftTangent, rightTangent, b.Value, alpha, result.Value); result.TangentIn = a.TangentOut; result.TangentOut = b.TangentIn; } bool operator==(const BezierCurveKeyframe& other) const { return Math::NearEqual(Time, other.Time) && Math::NearEqual(Value, other.Value) && Math::NearEqual(TangentIn, other.TangentIn) && Math::NearEqual(TangentOut, other.TangentOut); } } PACK_END(); // @formatter:on /// /// An animation spline represented by a set of read-only keyframes, each representing an endpoint of a curve. /// template> class CurveBase { public: typedef Span KeyFrameData; protected: T _default; public: /// /// Initializes a new instance of the class. /// CurveBase() : _default(AnimationUtils::GetZero()) { } /// /// Initializes a new instance of the class. /// /// The default keyframe value. CurveBase(const T& defaultValue) : _default(defaultValue) { } /// /// Finalizes an instance of the class. /// ~CurveBase() { } public: /// /// Gets the default value for the keyframes. /// FORCE_INLINE const T& GetDefaultValue() const { return _default; } /// /// Gets the default value for the keyframes. /// FORCE_INLINE T GetDefaultValue() { return _default; } public: /// /// Evaluates the animation curve value at the specified time. /// /// The keyframes data container. /// The interpolated value from the curve at provided time. /// The time to evaluate the curve at. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void Evaluate(const KeyFrameData& data, T& result, float time, bool loop = true) const { const int32 count = data.Length(); if (count == 0) { result = _default; return; } const float start = Math::Min(data[0].Time, 0.0f); const float end = data[count - 1].Time; AnimationUtils::WrapTime(time, start, end, loop); int32 leftKeyIdx; int32 rightKeyIdx; FindKeys(data, time, leftKeyIdx, rightKeyIdx); const KeyFrame& leftKey = data[leftKeyIdx]; const KeyFrame& rightKey = data[rightKeyIdx]; if (leftKeyIdx == rightKeyIdx) { result = leftKey.Value; return; } const float length = rightKey.Time - leftKey.Time; // Scale from arbitrary range to [0, 1] float t = Math::NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length; // Evaluate the value at the curve KeyFrame::Interpolate(leftKey, rightKey, t, length, result); } /// /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). /// /// The keyframes data container. /// The calculated first derivative from the curve at provided time. /// The time to evaluate the curve at. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void EvaluateFirstDerivative(const KeyFrameData& data, T& result, float time, bool loop = true) const { const int32 count = data.Length(); if (count == 0) { result = _default; return; } const float start = Math::Min(data[0].Time, 0.0f); const float end = data[count - 1].Time; AnimationUtils::WrapTime(time, start, end, loop); int32 leftKeyIdx; int32 rightKeyIdx; FindKeys(data, time, leftKeyIdx, rightKeyIdx); const KeyFrame& leftKey = data[leftKeyIdx]; const KeyFrame& rightKey = data[rightKeyIdx]; if (leftKeyIdx == rightKeyIdx) { result = leftKey.Value; return; } const float length = rightKey.Time - leftKey.Time; // Scale from arbitrary range to [0, 1] float t = Math::NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length; // Evaluate the derivative at the curve KeyFrame::InterpolateFirstDerivative(leftKey, rightKey, t, length, result); } /// /// Evaluates the animation curve key at the specified time. /// /// The keyframes data container. /// The interpolated key from the curve at provided time. /// The time to evaluate the curve at. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void EvaluateKey(const KeyFrameData& data, KeyFrame& result, float time, bool loop = true) const { const int32 count = data.Length(); if (count == 0) { result = KeyFrame(time, _default); return; } const float start = Math::Min(data[0].Time, 0.0f); const float end = data[count - 1].Time; AnimationUtils::WrapTime(time, start, end, loop); int32 leftKeyIdx; int32 rightKeyIdx; FindKeys(data, time, leftKeyIdx, rightKeyIdx); const KeyFrame& leftKey = data[leftKeyIdx]; const KeyFrame& rightKey = data[rightKeyIdx]; if (leftKeyIdx == rightKeyIdx) { result = leftKey; return; } const float length = rightKey.Time - leftKey.Time; // Scale from arbitrary range to [0, 1] float t = Math::NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length; // Evaluate the key at the curve KeyFrame::InterpolateKey(leftKey, rightKey, t, length, result); } protected: /// /// Returns a pair of keys that can be used for interpolating to field the value at the provided time. /// /// The keyframes data container. /// The time for which to find the relevant keys from. It is expected to be clamped to a valid range within the curve. /// The index of the key to interpolate from. /// The index of the key to interpolate to. void FindKeys(const KeyFrameData& data, float time, int32& leftKey, int32& rightKey) const { int32 start = 0; int32 searchLength = data.Length(); while (searchLength > 0) { const int32 half = searchLength >> 1; int32 mid = start + half; if (time < data[mid].Time) { searchLength = half; } else { start = mid + 1; searchLength -= half + 1; } } leftKey = Math::Max(0, start - 1); rightKey = Math::Min(start, data.Length() - 1); } }; /// /// An animation spline represented by a set of keyframes, each representing an endpoint of a curve. /// template> class Curve : public CurveBase { public: typedef CurveBase Base; using KeyFrameCollection = Array; private: KeyFrameCollection _keyframes; public: /// /// Initializes a new instance of the class. /// Curve() : Base() { } /// /// Initializes a new instance of the class. /// /// The default keyframe value. Curve(const T& defaultValue) : Base(defaultValue) { } /// /// Initializes a new instance of the class. /// /// The keyframes to initialize the curve with. Curve(const KeyFrameCollection& keyframes) : Base() { SetKeyframes(keyframes); } /// /// Finalizes an instance of the class. /// ~Curve() { } public: /// /// Gets the length of the animation curve, from time zero to last keyframe. /// float GetLength() const { return _keyframes.HasItems() ? _keyframes.Last().Time : 0.0f; } /// /// Gets the keyframes collection (for read-only). /// FORCE_INLINE const KeyFrameCollection& GetKeyframes() const { return _keyframes; } /// /// Gets the keyframes collection. /// FORCE_INLINE KeyFrameCollection& GetKeyframes() { return _keyframes; } /// /// Determines whether this curve is empty (has no keyframes). /// FORCE_INLINE bool IsEmpty() const { return _keyframes.IsEmpty(); } /// /// Clears this keyframes collection. /// FORCE_INLINE void Clear() { _keyframes.Resize(0); } /// /// Resizes the keyframes collection specified amount. Drops the data. /// /// The count. /// The keyframes memory pointer. KeyFrame* Resize(int32 count) { _keyframes.Resize(count, false); return _keyframes.Get(); } /// /// Sets the keyframes collection. /// /// The keyframes collection to assign. void SetKeyframes(const KeyFrameCollection& keyframes) { _keyframes = keyframes; #if BUILD_DEBUG // Ensure that keyframes are sorted if (keyframes.HasItems()) { float time = keyframes[0].Time; for (int32 i = 1; i < keyframes.Count(); i++) { ASSERT(keyframes[i].Time >= time); time = keyframes[i].Time; } } #endif } public: /// /// Evaluates the animation curve value at the specified time. /// /// The interpolated value from the curve at provided time. /// The time to evaluate the curve at. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void Evaluate(T& result, float time, bool loop = true) const { typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::Evaluate(data, result, time, loop); } /// /// Evaluates the first derivative of the animation curve at the specified time (aka velocity). /// /// The calculated first derivative from the curve at provided time. /// The time to evaluate the curve at. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void EvaluateFirstDerivative(T& result, float time, bool loop = true) const { typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::EvaluateFirstDerivative(data, result, time, loop); } /// /// Evaluates the animation curve key at the specified time. /// /// The interpolated key from the curve at provided time. /// The time to evaluate the curve at. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. void EvaluateKey(KeyFrame& result, float time, bool loop = true) const { typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); Base::EvaluateKey(data, result, time, loop); } /// /// Trims the curve keyframes to the specified time range. /// /// The time start. /// The time end. void Trim(float start, float end) { // Early out if (_keyframes.IsEmpty() || (_keyframes.First().Time >= start && _keyframes.Last().Time <= end)) return; if (end - start <= ZeroTolerance) { // Erase the curve _keyframes.Clear(); return; } typename Base::KeyFrameData data(_keyframes.Get(), _keyframes.Count()); KeyFrame startValue, endValue; Base::EvaluateKey(data, startValue, start, false); Base::EvaluateKey(data, endValue, end, false); // Begin for (int32 i = 0; i < _keyframes.Count() && _keyframes.HasItems(); i++) { if (_keyframes[i].Time < start) { _keyframes.RemoveAtKeepOrder(i); i--; } else { break; } } if (_keyframes.IsEmpty() || Math::NotNearEqual(_keyframes.First().Time, start)) { KeyFrame key = startValue; key.Time = start; _keyframes.Insert(0, key); } // End for (int32 i = _keyframes.Count() - 1; i >= 0 && _keyframes.HasItems(); i--) { if (_keyframes[i].Time > end) { _keyframes.RemoveAtKeepOrder(i); } else { break; } } if (_keyframes.IsEmpty() || Math::NotNearEqual(_keyframes.Last().Time, end)) { KeyFrame key = endValue; key.Time = end; _keyframes.Add(key); } // Rebase the keyframes time if (!Math::IsZero(start)) { for (int32 i = 0; i < _keyframes.Count(); i++) _keyframes[i].Time -= start; } } /// /// Applies the linear transformation (scale and offset) to the keyframes time values. /// /// The time scale. /// The time offset. void TransformTime(float timeScale, float timeOffset) { for (int32 i = 0; i < _keyframes.Count(); i++) _keyframes[i].Time = _keyframes[i].Time * timeScale + timeOffset;; } uint64 GetMemoryUsage() const { return _keyframes.Capacity() * sizeof(KeyFrame); } public: FORCE_INLINE KeyFrame& operator[](int32 index) { return _keyframes[index]; } FORCE_INLINE const KeyFrame& operator[](int32 index) const { return _keyframes[index]; } bool operator==(const Curve& other) const { if (_keyframes.Count() != other._keyframes.Count()) return false; for (int32 i = 0; i < _keyframes.Count(); i++) { if (!(_keyframes[i] == other._keyframes[i])) return false; } return true; } }; /// /// An animation spline represented by a set of keyframes, each representing a value point. /// template using StepCurve = Curve>; /// /// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve. /// template using LinearCurve = Curve>; /// /// An animation spline represented by a set of keyframes, each representing an endpoint of a cubic hermite curve. /// template using HermiteCurve = Curve>; /// /// An animation spline represented by a set of keyframes, each representing an endpoint of Bezier curve. /// template using BezierCurve = Curve>;