123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- //
- // PolynomialCurve.cpp
- // cocos2d_libs
- //
- // Created by 徐俊杰 on 2020/4/24.
- //
- #include "rparticle/PolynomialCurve.h"
- //#include "UnityPrefix.h"
- #include "rparticle/PolynomialCurve.h"
- //#include "Runtime/Math/Vector2.h"
- #include "rparticle/Math/Polynomials.h"
- #include "rparticle/Math/AnimationCurve.h"
- #define zero ZERO
- NS_RRP_BEGIN
- static void DoubleIntegrateSegment (float* coeff)
- {
- coeff[0] /= 20.0F;
- coeff[1] /= 12.0F;
- coeff[2] /= 6.0F;
- coeff[3] /= 2.0F;
- }
- static void IntegrateSegment (float* coeff)
- {
- coeff[0] /= 4.0F;
- coeff[1] /= 3.0F;
- coeff[2] /= 2.0F;
- coeff[3] /= 1.0F;
- }
- void CalculateMinMax(Vector2f& minmax, float value)
- {
- minmax.x = std::min(minmax.x, value);
- minmax.y = std::max(minmax.y, value);
- }
- void ConstrainToPolynomialCurve (AnimationCurve& curve)
- {
- const int max = OptimizedPolynomialCurve::kMaxPolynomialKeyframeCount;
-
- // Maximum 3 keys
- if (curve.GetKeyCount () > max)
- curve.RemoveKeys(curve.begin() + max, curve.end());
-
- // Clamp begin and end to 0...1 range
- if (curve.GetKeyCount () >= 2)
- {
- curve.GetKey(0).time = 0;
- curve.GetKey(curve.GetKeyCount ()-1).time = 1;
- }
- }
- bool IsValidPolynomialCurve (const AnimationCurve& curve)
- {
- // Maximum 3 keys
- if (curve.GetKeyCount () > OptimizedPolynomialCurve::kMaxPolynomialKeyframeCount)
- return false;
- // One constant key can always be representated
- else if (curve.GetKeyCount () <= 1)
- return true;
- // First and last keyframe must be at 0 and 1 time
- else
- {
- float beginTime = curve.GetKey(0).time;
- float endTime = curve.GetKey(curve.GetKeyCount ()-1).time;
-
- return CompareApproximately(beginTime, 0.0F, 0.0001F) && CompareApproximately(endTime, 1.0F, 0.0001F);
- }
- }
- void SetPolynomialCurveToValue (AnimationCurve& a, OptimizedPolynomialCurve& c, float value)
- {
- AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, value), AnimationCurve::Keyframe(1.0f, value) };
- a.Assign(keys, keys + 2);
- c.BuildOptimizedCurve(a, 1.0f);
- }
- void SetPolynomialCurveToLinear (AnimationCurve& a, OptimizedPolynomialCurve& c)
- {
- AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, 0.0f), AnimationCurve::Keyframe(1.0f, 1.0f) };
- keys[0].inSlope = 0.0f; keys[0].outSlope = 1.0f;
- keys[1].inSlope = 1.0f; keys[1].outSlope = 0.0f;
- a.Assign(keys, keys + 2);
- c.BuildOptimizedCurve(a, 1.0f);
- }
- bool OptimizedPolynomialCurve::BuildOptimizedCurve (const AnimationCurve& editorCurve, float scale)
- {
- if (!IsValidPolynomialCurve(editorCurve))
- return false;
-
- const size_t keyCount = editorCurve.GetKeyCount ();
-
- timeValue = 1.0F;
- memset(segments, 0, sizeof(segments));
-
- // Handle corner case 1 & 0 keyframes
- if (keyCount == 0)
- ;
- else if (keyCount == 1)
- {
- // Set constant value coefficient
- for (int i=0;i<kSegmentCount;i++)
- segments[i].coeff[3] = editorCurve.GetKey(0).value * scale;
- }
- else
- {
- float segmentStartTime[kSegmentCount];
-
- for (int i=0;i<kSegmentCount;i++)
- {
- bool hasSegment = i+1 < keyCount;
- if (hasSegment)
- {
- AnimationCurve::Cache cache;
- editorCurve.CalculateCacheData(cache, i, i + 1, 0.0F);
-
- memcpy(segments[i].coeff, cache.coeff, sizeof(Polynomial));
- segmentStartTime[i] = editorCurve.GetKey(i).time;
- }
- else
- {
- memcpy(segments[i].coeff, segments[i-1].coeff, sizeof(Polynomial));
- segmentStartTime[i] = 1.0F;//timeValue[i-1];
- }
- }
-
- // scale curve
- for (int i=0;i<kSegmentCount;i++)
- {
- segments[i].coeff[0] *= scale;
- segments[i].coeff[1] *= scale;
- segments[i].coeff[2] *= scale;
- segments[i].coeff[3] *= scale;
- }
-
- // Timevalue 0 is always 0.0F. No need to store it.
- timeValue = segmentStartTime[1];
-
- #if !UNITY_RELEASE
- // Check that UI editor curve matches polynomial curve except if the
- // scale happens to be infinite (trying to subtract infinity values from
- // each other yields NaN with IEEE floats).
- if (scale != std::numeric_limits<float>::infinity () &&
- scale != -std::numeric_limits<float>::infinity())
- {
- for (int i=0;i<=50;i++)
- {
- // The very last element at 1.0 can be different when using step curves.
- // The AnimationCurve implementation will sample the position of the last key.
- // The OptimizedPolynomialCurve will keep value of the previous key (Continuing the trajectory of the segment)
- // In practice this is probably not a problem, since you don't sample 1.0 because then the particle will be dead.
- // thus we just don't do the automatic assert when the curves are not in sync in that case.
- float t = std::min(i / 50.0F, 0.99999F);
- float dif;
-
- DebugAssert((dif = Abs(Evaluate(t) - editorCurve.Evaluate(t) * scale)) < 0.01F);
- }
- }
- #endif
- }
-
- return true;
- }
- void OptimizedPolynomialCurve::Integrate ()
- {
- for (int i=0;i<kSegmentCount;i++)
- IntegrateSegment(segments[i].coeff);
- }
- void OptimizedPolynomialCurve::DoubleIntegrate ()
- {
- Polynomial velocity0 = segments[0];
- IntegrateSegment (velocity0.coeff);
-
- velocityValue = Polynomial::EvalSegment(timeValue, velocity0.coeff) * timeValue;
-
- for (int i=0;i<kSegmentCount;i++)
- DoubleIntegrateSegment(segments[i].coeff);
- }
- Vector2f OptimizedPolynomialCurve::FindMinMaxDoubleIntegrated() const
- {
- // Because of velocityValue * t, this becomes a quartic polynomial (4th order polynomial).
- // TODO: Find all roots of quartic polynomial
- Vector2f result = Vector2f::zero;
- const int numSteps = 20;
- const float delta = 1.0f / float(numSteps);
- float acc = delta;
- for(int i = 0; i < numSteps; i++)
- {
- CalculateMinMax(result, EvaluateDoubleIntegrated(acc));
- acc += delta;
- }
- return result;
- }
- // Find the maximum of the integrated curve (x: min, y: max)
- Vector2f OptimizedPolynomialCurve::FindMinMaxIntegrated() const
- {
- Vector2f result = Vector2f::zero;
-
- float start[kSegmentCount] = {0.0f, timeValue};
- float end[kSegmentCount] = {timeValue, 1.0f};
- for(int i = 0; i < kSegmentCount; i++)
- {
- // Differentiate coefficients
- float a = 4.0f*segments[i].coeff[0];
- float b = 3.0f*segments[i].coeff[1];
- float c = 2.0f*segments[i].coeff[2];
- float d = 1.0f*segments[i].coeff[3];
-
- float roots[3];
- int numRoots = CubicPolynomialRootsGeneric(roots, a, b, c, d);
- for(int r = 0; r < numRoots; r++)
- {
- float root = roots[r] + start[i];
- if((root >= start[i]) && (root < end[i]))
- CalculateMinMax(result, EvaluateIntegrated(root));
- }
-
- // TODO: Don't use eval integrated, use eval segment (and integrate in loop)
- CalculateMinMax(result, EvaluateIntegrated(end[i]));
- }
- return result;
- }
- // Find the maximum of a double integrated curve (x: min, y: max)
- Vector2f PolynomialCurve::FindMinMaxDoubleIntegrated() const
- {
- // Because of velocityValue * t, this becomes a quartic polynomial (4th order polynomial).
- // TODO: Find all roots of quartic polynomial
- Vector2f result = Vector2f::zero;
- const int numSteps = 20;
- const float delta = 1.0f / float(numSteps);
- float acc = delta;
- for(int i = 0; i < numSteps; i++)
- {
- CalculateMinMax(result, EvaluateDoubleIntegrated(acc));
- acc += delta;
- }
- return result;
- }
- Vector2f PolynomialCurve::FindMinMaxIntegrated() const
- {
- Vector2f result = Vector2f::zero;
-
- float prevTimeValue = 0.0f;
- for(int i = 0; i < segmentCount; i++)
- {
- // Differentiate coefficients
- float a = 4.0f*segments[i].coeff[0];
- float b = 3.0f*segments[i].coeff[1];
- float c = 2.0f*segments[i].coeff[2];
- float d = 1.0f*segments[i].coeff[3];
-
- float roots[3];
- int numRoots = CubicPolynomialRootsGeneric(roots, a, b, c, d);
- for(int r = 0; r < numRoots; r++)
- {
- float root = roots[r] + prevTimeValue;
- if((root >= prevTimeValue) && (root < times[i]))
- CalculateMinMax(result, EvaluateIntegrated(root));
- }
-
- // TODO: Don't use eval integrated, use eval segment (and integrate in loop)
- CalculateMinMax(result, EvaluateIntegrated(times[i]));
- prevTimeValue = times[i];
- }
- return result;
- }
- bool PolynomialCurve::IsValidCurve(const AnimationCurve& editorCurve)
- {
- int keyCount = editorCurve.GetKeyCount();
- int segmentCount = keyCount - 1;
- if(editorCurve.GetKey(0).time != 0.0f)
- segmentCount++;
- if(editorCurve.GetKey(keyCount-1).time != 1.0f)
- segmentCount++;
- return segmentCount <= kMaxNumSegments;
- }
- bool PolynomialCurve::BuildCurve(const AnimationCurve& editorCurve, float scale)
- {
- int keyCount = editorCurve.GetKeyCount();
- segmentCount = 1;
-
- const float kMaxTime = 1.01f;
-
- memset(segments, 0, sizeof(segments));
- memset(integrationCache, 0, sizeof(integrationCache));
- memset(doubleIntegrationCache, 0, sizeof(doubleIntegrationCache));
- memset(times, 0, sizeof(times));
- times[0] = kMaxTime;
-
- // Handle corner case 1 & 0 keyframes
- if (keyCount == 0)
- ;
- else if (keyCount == 1)
- {
- // Set constant value coefficient
- segments[0].coeff[3] = editorCurve.GetKey(0).value * scale;
- }
- else
- {
- segmentCount = keyCount - 1;
- int segmentOffset = 0;
-
- // Add extra key to start if it doesn't match up
- if(editorCurve.GetKey(0).time != 0.0f)
- {
- segments[0].coeff[3] = editorCurve.GetKey(0).value;
- times[0] = editorCurve.GetKey(0).time;
- segmentOffset = 1;
- }
-
- for (int i = 0;i<segmentCount;i++)
- {
- DebugAssert(i+1 < keyCount);
- AnimationCurve::Cache cache;
- editorCurve.CalculateCacheData(cache, i, i + 1, 0.0F);
- memcpy(segments[i+segmentOffset].coeff, cache.coeff, 4 * sizeof(float));
- times[i+segmentOffset] = editorCurve.GetKey(i+1).time;
- }
- segmentCount += segmentOffset;
-
- // Add extra key to start if it doesn't match up
- if(editorCurve.GetKey(keyCount-1).time != 1.0f)
- {
- segments[segmentCount].coeff[3] = editorCurve.GetKey(keyCount-1).value;
- segmentCount++;
- }
-
- // Fixup last key time value
- times[segmentCount-1] = kMaxTime;
-
- for (int i = 0;i<segmentCount;i++)
- {
- segments[i].coeff[0] *= scale;
- segments[i].coeff[1] *= scale;
- segments[i].coeff[2] *= scale;
- segments[i].coeff[3] *= scale;
- }
- }
-
- DebugAssert(segmentCount <= kMaxNumSegments);
-
- #if !UNITY_RELEASE
- // Check that UI editor curve matches polynomial curve
- for (int i=0;i<=10;i++)
- {
- // The very last element at 1.0 can be different when using step curves.
- // The AnimationCurve implementation will sample the position of the last key.
- // The PolynomialCurve will keep value of the previous key (Continuing the trajectory of the segment)
- // In practice this is probably not a problem, since you don't sample 1.0 because then the particle will be dead.
- // thus we just don't do the automatic assert when the curves are not in sync in that case.
- float t = std::min(i / 50.0F, 0.99999F);
- float dif;
- DebugAssert((dif = Abs(Evaluate(t) - editorCurve.Evaluate(t) * scale)) < 0.01F);
- }
- #endif
- return true;
- }
- void GenerateIntegrationCache(PolynomialCurve& curve)
- {
- curve.integrationCache[0] = 0.0f;
- float prevTimeValue0 = curve.times[0];
- float prevTimeValue1 = 0.0f;
- for (int i=1;i<curve.segmentCount;i++)
- {
- float coeff[4];
- memcpy(coeff, curve.segments[i-1].coeff, 4*sizeof(float));
- IntegrateSegment (coeff);
- float time = prevTimeValue0 - prevTimeValue1;
- curve.integrationCache[i] = curve.integrationCache[i-1] + Polynomial::EvalSegment(time, coeff) * time;
- prevTimeValue1 = prevTimeValue0;
- prevTimeValue0 = curve.times[i];
- }
- }
- // Expects double integrated segments and valid integration cache
- void GenerateDoubleIntegrationCache(PolynomialCurve& curve)
- {
- float sum = 0.0f;
- float prevTimeValue = 0.0f;
- for(int i = 0; i < curve.segmentCount; i++)
- {
- curve.doubleIntegrationCache[i] = sum;
- float time = curve.times[i] - prevTimeValue;
- time = std::max(time, 0.0f);
- sum += Polynomial::EvalSegment(time, curve.segments[i].coeff) * time * time + curve.integrationCache[i] * time;
- prevTimeValue = curve.times[i];
- }
- }
- void PolynomialCurve::Integrate ()
- {
- GenerateIntegrationCache(*this);
- for (int i=0;i<segmentCount;i++)
- IntegrateSegment(segments[i].coeff);
- }
- void PolynomialCurve::DoubleIntegrate ()
- {
- GenerateIntegrationCache(*this);
- for (int i=0;i<segmentCount;i++)
- DoubleIntegrateSegment(segments[i].coeff);
- GenerateDoubleIntegrationCache(*this);
- }
- NS_RRP_END
|