// // 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::infinity () && scale != -std::numeric_limits::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= 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