PolynomialCurve.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. //
  2. // PolynomialCurve.cpp
  3. // cocos2d_libs
  4. //
  5. // Created by 徐俊杰 on 2020/4/24.
  6. //
  7. #include "rparticle/PolynomialCurve.h"
  8. //#include "UnityPrefix.h"
  9. #include "rparticle/PolynomialCurve.h"
  10. //#include "Runtime/Math/Vector2.h"
  11. #include "rparticle/Math/Polynomials.h"
  12. #include "rparticle/Math/AnimationCurve.h"
  13. #define zero ZERO
  14. NS_RRP_BEGIN
  15. static void DoubleIntegrateSegment (float* coeff)
  16. {
  17. coeff[0] /= 20.0F;
  18. coeff[1] /= 12.0F;
  19. coeff[2] /= 6.0F;
  20. coeff[3] /= 2.0F;
  21. }
  22. static void IntegrateSegment (float* coeff)
  23. {
  24. coeff[0] /= 4.0F;
  25. coeff[1] /= 3.0F;
  26. coeff[2] /= 2.0F;
  27. coeff[3] /= 1.0F;
  28. }
  29. void CalculateMinMax(Vector2f& minmax, float value)
  30. {
  31. minmax.x = std::min(minmax.x, value);
  32. minmax.y = std::max(minmax.y, value);
  33. }
  34. void ConstrainToPolynomialCurve (AnimationCurve& curve)
  35. {
  36. const int max = OptimizedPolynomialCurve::kMaxPolynomialKeyframeCount;
  37. // Maximum 3 keys
  38. if (curve.GetKeyCount () > max)
  39. curve.RemoveKeys(curve.begin() + max, curve.end());
  40. // Clamp begin and end to 0...1 range
  41. if (curve.GetKeyCount () >= 2)
  42. {
  43. curve.GetKey(0).time = 0;
  44. curve.GetKey(curve.GetKeyCount ()-1).time = 1;
  45. }
  46. }
  47. bool IsValidPolynomialCurve (const AnimationCurve& curve)
  48. {
  49. // Maximum 3 keys
  50. if (curve.GetKeyCount () > OptimizedPolynomialCurve::kMaxPolynomialKeyframeCount)
  51. return false;
  52. // One constant key can always be representated
  53. else if (curve.GetKeyCount () <= 1)
  54. return true;
  55. // First and last keyframe must be at 0 and 1 time
  56. else
  57. {
  58. float beginTime = curve.GetKey(0).time;
  59. float endTime = curve.GetKey(curve.GetKeyCount ()-1).time;
  60. return CompareApproximately(beginTime, 0.0F, 0.0001F) && CompareApproximately(endTime, 1.0F, 0.0001F);
  61. }
  62. }
  63. void SetPolynomialCurveToValue (AnimationCurve& a, OptimizedPolynomialCurve& c, float value)
  64. {
  65. AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, value), AnimationCurve::Keyframe(1.0f, value) };
  66. a.Assign(keys, keys + 2);
  67. c.BuildOptimizedCurve(a, 1.0f);
  68. }
  69. void SetPolynomialCurveToLinear (AnimationCurve& a, OptimizedPolynomialCurve& c)
  70. {
  71. AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, 0.0f), AnimationCurve::Keyframe(1.0f, 1.0f) };
  72. keys[0].inSlope = 0.0f; keys[0].outSlope = 1.0f;
  73. keys[1].inSlope = 1.0f; keys[1].outSlope = 0.0f;
  74. a.Assign(keys, keys + 2);
  75. c.BuildOptimizedCurve(a, 1.0f);
  76. }
  77. bool OptimizedPolynomialCurve::BuildOptimizedCurve (const AnimationCurve& editorCurve, float scale)
  78. {
  79. if (!IsValidPolynomialCurve(editorCurve))
  80. return false;
  81. const size_t keyCount = editorCurve.GetKeyCount ();
  82. timeValue = 1.0F;
  83. memset(segments, 0, sizeof(segments));
  84. // Handle corner case 1 & 0 keyframes
  85. if (keyCount == 0)
  86. ;
  87. else if (keyCount == 1)
  88. {
  89. // Set constant value coefficient
  90. for (int i=0;i<kSegmentCount;i++)
  91. segments[i].coeff[3] = editorCurve.GetKey(0).value * scale;
  92. }
  93. else
  94. {
  95. float segmentStartTime[kSegmentCount];
  96. for (int i=0;i<kSegmentCount;i++)
  97. {
  98. bool hasSegment = i+1 < keyCount;
  99. if (hasSegment)
  100. {
  101. AnimationCurve::Cache cache;
  102. editorCurve.CalculateCacheData(cache, i, i + 1, 0.0F);
  103. memcpy(segments[i].coeff, cache.coeff, sizeof(Polynomial));
  104. segmentStartTime[i] = editorCurve.GetKey(i).time;
  105. }
  106. else
  107. {
  108. memcpy(segments[i].coeff, segments[i-1].coeff, sizeof(Polynomial));
  109. segmentStartTime[i] = 1.0F;//timeValue[i-1];
  110. }
  111. }
  112. // scale curve
  113. for (int i=0;i<kSegmentCount;i++)
  114. {
  115. segments[i].coeff[0] *= scale;
  116. segments[i].coeff[1] *= scale;
  117. segments[i].coeff[2] *= scale;
  118. segments[i].coeff[3] *= scale;
  119. }
  120. // Timevalue 0 is always 0.0F. No need to store it.
  121. timeValue = segmentStartTime[1];
  122. #if !UNITY_RELEASE
  123. // Check that UI editor curve matches polynomial curve except if the
  124. // scale happens to be infinite (trying to subtract infinity values from
  125. // each other yields NaN with IEEE floats).
  126. if (scale != std::numeric_limits<float>::infinity () &&
  127. scale != -std::numeric_limits<float>::infinity())
  128. {
  129. for (int i=0;i<=50;i++)
  130. {
  131. // The very last element at 1.0 can be different when using step curves.
  132. // The AnimationCurve implementation will sample the position of the last key.
  133. // The OptimizedPolynomialCurve will keep value of the previous key (Continuing the trajectory of the segment)
  134. // In practice this is probably not a problem, since you don't sample 1.0 because then the particle will be dead.
  135. // thus we just don't do the automatic assert when the curves are not in sync in that case.
  136. float t = std::min(i / 50.0F, 0.99999F);
  137. float dif;
  138. DebugAssert((dif = Abs(Evaluate(t) - editorCurve.Evaluate(t) * scale)) < 0.01F);
  139. }
  140. }
  141. #endif
  142. }
  143. return true;
  144. }
  145. void OptimizedPolynomialCurve::Integrate ()
  146. {
  147. for (int i=0;i<kSegmentCount;i++)
  148. IntegrateSegment(segments[i].coeff);
  149. }
  150. void OptimizedPolynomialCurve::DoubleIntegrate ()
  151. {
  152. Polynomial velocity0 = segments[0];
  153. IntegrateSegment (velocity0.coeff);
  154. velocityValue = Polynomial::EvalSegment(timeValue, velocity0.coeff) * timeValue;
  155. for (int i=0;i<kSegmentCount;i++)
  156. DoubleIntegrateSegment(segments[i].coeff);
  157. }
  158. Vector2f OptimizedPolynomialCurve::FindMinMaxDoubleIntegrated() const
  159. {
  160. // Because of velocityValue * t, this becomes a quartic polynomial (4th order polynomial).
  161. // TODO: Find all roots of quartic polynomial
  162. Vector2f result = Vector2f::zero;
  163. const int numSteps = 20;
  164. const float delta = 1.0f / float(numSteps);
  165. float acc = delta;
  166. for(int i = 0; i < numSteps; i++)
  167. {
  168. CalculateMinMax(result, EvaluateDoubleIntegrated(acc));
  169. acc += delta;
  170. }
  171. return result;
  172. }
  173. // Find the maximum of the integrated curve (x: min, y: max)
  174. Vector2f OptimizedPolynomialCurve::FindMinMaxIntegrated() const
  175. {
  176. Vector2f result = Vector2f::zero;
  177. float start[kSegmentCount] = {0.0f, timeValue};
  178. float end[kSegmentCount] = {timeValue, 1.0f};
  179. for(int i = 0; i < kSegmentCount; i++)
  180. {
  181. // Differentiate coefficients
  182. float a = 4.0f*segments[i].coeff[0];
  183. float b = 3.0f*segments[i].coeff[1];
  184. float c = 2.0f*segments[i].coeff[2];
  185. float d = 1.0f*segments[i].coeff[3];
  186. float roots[3];
  187. int numRoots = CubicPolynomialRootsGeneric(roots, a, b, c, d);
  188. for(int r = 0; r < numRoots; r++)
  189. {
  190. float root = roots[r] + start[i];
  191. if((root >= start[i]) && (root < end[i]))
  192. CalculateMinMax(result, EvaluateIntegrated(root));
  193. }
  194. // TODO: Don't use eval integrated, use eval segment (and integrate in loop)
  195. CalculateMinMax(result, EvaluateIntegrated(end[i]));
  196. }
  197. return result;
  198. }
  199. // Find the maximum of a double integrated curve (x: min, y: max)
  200. Vector2f PolynomialCurve::FindMinMaxDoubleIntegrated() const
  201. {
  202. // Because of velocityValue * t, this becomes a quartic polynomial (4th order polynomial).
  203. // TODO: Find all roots of quartic polynomial
  204. Vector2f result = Vector2f::zero;
  205. const int numSteps = 20;
  206. const float delta = 1.0f / float(numSteps);
  207. float acc = delta;
  208. for(int i = 0; i < numSteps; i++)
  209. {
  210. CalculateMinMax(result, EvaluateDoubleIntegrated(acc));
  211. acc += delta;
  212. }
  213. return result;
  214. }
  215. Vector2f PolynomialCurve::FindMinMaxIntegrated() const
  216. {
  217. Vector2f result = Vector2f::zero;
  218. float prevTimeValue = 0.0f;
  219. for(int i = 0; i < segmentCount; i++)
  220. {
  221. // Differentiate coefficients
  222. float a = 4.0f*segments[i].coeff[0];
  223. float b = 3.0f*segments[i].coeff[1];
  224. float c = 2.0f*segments[i].coeff[2];
  225. float d = 1.0f*segments[i].coeff[3];
  226. float roots[3];
  227. int numRoots = CubicPolynomialRootsGeneric(roots, a, b, c, d);
  228. for(int r = 0; r < numRoots; r++)
  229. {
  230. float root = roots[r] + prevTimeValue;
  231. if((root >= prevTimeValue) && (root < times[i]))
  232. CalculateMinMax(result, EvaluateIntegrated(root));
  233. }
  234. // TODO: Don't use eval integrated, use eval segment (and integrate in loop)
  235. CalculateMinMax(result, EvaluateIntegrated(times[i]));
  236. prevTimeValue = times[i];
  237. }
  238. return result;
  239. }
  240. bool PolynomialCurve::IsValidCurve(const AnimationCurve& editorCurve)
  241. {
  242. int keyCount = editorCurve.GetKeyCount();
  243. int segmentCount = keyCount - 1;
  244. if(editorCurve.GetKey(0).time != 0.0f)
  245. segmentCount++;
  246. if(editorCurve.GetKey(keyCount-1).time != 1.0f)
  247. segmentCount++;
  248. return segmentCount <= kMaxNumSegments;
  249. }
  250. bool PolynomialCurve::BuildCurve(const AnimationCurve& editorCurve, float scale)
  251. {
  252. int keyCount = editorCurve.GetKeyCount();
  253. segmentCount = 1;
  254. const float kMaxTime = 1.01f;
  255. memset(segments, 0, sizeof(segments));
  256. memset(integrationCache, 0, sizeof(integrationCache));
  257. memset(doubleIntegrationCache, 0, sizeof(doubleIntegrationCache));
  258. memset(times, 0, sizeof(times));
  259. times[0] = kMaxTime;
  260. // Handle corner case 1 & 0 keyframes
  261. if (keyCount == 0)
  262. ;
  263. else if (keyCount == 1)
  264. {
  265. // Set constant value coefficient
  266. segments[0].coeff[3] = editorCurve.GetKey(0).value * scale;
  267. }
  268. else
  269. {
  270. segmentCount = keyCount - 1;
  271. int segmentOffset = 0;
  272. // Add extra key to start if it doesn't match up
  273. if(editorCurve.GetKey(0).time != 0.0f)
  274. {
  275. segments[0].coeff[3] = editorCurve.GetKey(0).value;
  276. times[0] = editorCurve.GetKey(0).time;
  277. segmentOffset = 1;
  278. }
  279. for (int i = 0;i<segmentCount;i++)
  280. {
  281. DebugAssert(i+1 < keyCount);
  282. AnimationCurve::Cache cache;
  283. editorCurve.CalculateCacheData(cache, i, i + 1, 0.0F);
  284. memcpy(segments[i+segmentOffset].coeff, cache.coeff, 4 * sizeof(float));
  285. times[i+segmentOffset] = editorCurve.GetKey(i+1).time;
  286. }
  287. segmentCount += segmentOffset;
  288. // Add extra key to start if it doesn't match up
  289. if(editorCurve.GetKey(keyCount-1).time != 1.0f)
  290. {
  291. segments[segmentCount].coeff[3] = editorCurve.GetKey(keyCount-1).value;
  292. segmentCount++;
  293. }
  294. // Fixup last key time value
  295. times[segmentCount-1] = kMaxTime;
  296. for (int i = 0;i<segmentCount;i++)
  297. {
  298. segments[i].coeff[0] *= scale;
  299. segments[i].coeff[1] *= scale;
  300. segments[i].coeff[2] *= scale;
  301. segments[i].coeff[3] *= scale;
  302. }
  303. }
  304. DebugAssert(segmentCount <= kMaxNumSegments);
  305. #if !UNITY_RELEASE
  306. // Check that UI editor curve matches polynomial curve
  307. for (int i=0;i<=10;i++)
  308. {
  309. // The very last element at 1.0 can be different when using step curves.
  310. // The AnimationCurve implementation will sample the position of the last key.
  311. // The PolynomialCurve will keep value of the previous key (Continuing the trajectory of the segment)
  312. // In practice this is probably not a problem, since you don't sample 1.0 because then the particle will be dead.
  313. // thus we just don't do the automatic assert when the curves are not in sync in that case.
  314. float t = std::min(i / 50.0F, 0.99999F);
  315. float dif;
  316. DebugAssert((dif = Abs(Evaluate(t) - editorCurve.Evaluate(t) * scale)) < 0.01F);
  317. }
  318. #endif
  319. return true;
  320. }
  321. void GenerateIntegrationCache(PolynomialCurve& curve)
  322. {
  323. curve.integrationCache[0] = 0.0f;
  324. float prevTimeValue0 = curve.times[0];
  325. float prevTimeValue1 = 0.0f;
  326. for (int i=1;i<curve.segmentCount;i++)
  327. {
  328. float coeff[4];
  329. memcpy(coeff, curve.segments[i-1].coeff, 4*sizeof(float));
  330. IntegrateSegment (coeff);
  331. float time = prevTimeValue0 - prevTimeValue1;
  332. curve.integrationCache[i] = curve.integrationCache[i-1] + Polynomial::EvalSegment(time, coeff) * time;
  333. prevTimeValue1 = prevTimeValue0;
  334. prevTimeValue0 = curve.times[i];
  335. }
  336. }
  337. // Expects double integrated segments and valid integration cache
  338. void GenerateDoubleIntegrationCache(PolynomialCurve& curve)
  339. {
  340. float sum = 0.0f;
  341. float prevTimeValue = 0.0f;
  342. for(int i = 0; i < curve.segmentCount; i++)
  343. {
  344. curve.doubleIntegrationCache[i] = sum;
  345. float time = curve.times[i] - prevTimeValue;
  346. time = std::max(time, 0.0f);
  347. sum += Polynomial::EvalSegment(time, curve.segments[i].coeff) * time * time + curve.integrationCache[i] * time;
  348. prevTimeValue = curve.times[i];
  349. }
  350. }
  351. void PolynomialCurve::Integrate ()
  352. {
  353. GenerateIntegrationCache(*this);
  354. for (int i=0;i<segmentCount;i++)
  355. IntegrateSegment(segments[i].coeff);
  356. }
  357. void PolynomialCurve::DoubleIntegrate ()
  358. {
  359. GenerateIntegrationCache(*this);
  360. for (int i=0;i<segmentCount;i++)
  361. DoubleIntegrateSegment(segments[i].coeff);
  362. GenerateDoubleIntegrationCache(*this);
  363. }
  364. NS_RRP_END