PathConstraintPathHermite.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <Jolt/Jolt.h>
  4. #include <Jolt/Physics/Constraints/PathConstraintPathHermite.h>
  5. #include <Jolt/Core/Profiler.h>
  6. #include <Jolt/ObjectStream/TypeDeclarations.h>
  7. #include <Jolt/Core/StreamIn.h>
  8. #include <Jolt/Core/StreamOut.h>
  9. JPH_NAMESPACE_BEGIN
  10. JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(PathConstraintPathHermite::Point)
  11. {
  12. JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mPosition)
  13. JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mTangent)
  14. JPH_ADD_ATTRIBUTE(PathConstraintPathHermite::Point, mNormal)
  15. }
  16. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(PathConstraintPathHermite)
  17. {
  18. JPH_ADD_BASE_CLASS(PathConstraintPathHermite, PathConstraintPath)
  19. JPH_ADD_ATTRIBUTE(PathConstraintPathHermite, mPoints)
  20. }
  21. // Calculate position and tangent for a Cubic Hermite Spline segment
  22. static inline void sCalculatePositionAndTangent(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float inT, Vec3 &outPosition, Vec3 &outTangent)
  23. {
  24. // Calculate factors for Cubic Hermite Spline
  25. // See: https://en.wikipedia.org/wiki/Cubic_Hermite_spline
  26. float t2 = inT * inT;
  27. float t3 = inT * t2;
  28. float h00 = 2.0f * t3 - 3.0f * t2 + 1.0f;
  29. float h10 = t3 - 2.0f * t2 + inT;
  30. float h01 = -2.0f * t3 + 3.0f * t2;
  31. float h11 = t3 - t2;
  32. // Calculate d/dt for factors to calculate the tangent
  33. float ddt_h00 = 6.0f * (t2 - inT);
  34. float ddt_h10 = 3.0f * t2 - 4.0f * inT + 1.0f;
  35. float ddt_h01 = -ddt_h00;
  36. float ddt_h11 = 3.0f * t2 - 2.0f * inT;
  37. outPosition = h00 * inP1 + h10 * inM1 + h01 * inP2 + h11 * inM2;
  38. outTangent = ddt_h00 * inP1 + ddt_h10 * inM1 + ddt_h01 * inP2 + ddt_h11 * inM2;
  39. }
  40. // Calculate the closest point to the origin for a Cubic Hermite Spline segment
  41. // This is used to get an estimate for the interval in which the closest point can be found,
  42. // the interval [0, 1] is too big for Newton Raphson to work on because it is solving a 5th degree polynomial which may
  43. // have multiple local minima that are not the root. This happens especially when the path is straight (tangents aligned with inP2 - inP1).
  44. // Based on the bisection method: https://en.wikipedia.org/wiki/Bisection_method
  45. static inline void sCalculateClosestPointThroughBisection(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float &outTMin, float &outTMax)
  46. {
  47. outTMin = 0.0f;
  48. outTMax = 1.0f;
  49. // To get the closest point of the curve to the origin we need to solve:
  50. // d/dt P(t) . P(t) = 0 for t, where P(t) is the point on the curve segment
  51. // Using d/dt (a(t) . b(t)) = d/dt a(t) . b(t) + a(t) . d/dt b(t)
  52. // See: https://proofwiki.org/wiki/Derivative_of_Dot_Product_of_Vector-Valued_Functions
  53. // d/dt P(t) . P(t) = 2 P(t) d/dt P(t) = 2 P(t) . Tangent(t)
  54. // Calculate the derivative at t = 0, we know P(0) = inP1 and Tangent(0) = inM1
  55. float ddt_min = inP1.Dot(inM1); // Leaving out factor 2, we're only interested in the root
  56. if (abs(ddt_min) < 1.0e-6f)
  57. {
  58. // Derivative is near zero, we found our root
  59. outTMax = 0.0f;
  60. return;
  61. }
  62. bool ddt_min_negative = ddt_min < 0.0f;
  63. // Calculate derivative at t = 1, we know P(1) = inP2 and Tangent(1) = inM2
  64. float ddt_max = inP2.Dot(inM2);
  65. if (abs(ddt_max) < 1.0e-6f)
  66. {
  67. // Derivative is near zero, we found our root
  68. outTMin = 1.0f;
  69. return;
  70. }
  71. bool ddt_max_negative = ddt_max < 0.0f;
  72. // If the signs of the derivative are not different, this algorithm can't find the root
  73. if (ddt_min_negative == ddt_max_negative)
  74. return;
  75. // With 4 iterations we'll get a result accurate to 1 / 2^4 = 0.0625
  76. for (int iteration = 0; iteration < 4; ++iteration)
  77. {
  78. float t_mid = 0.5f * (outTMin + outTMax);
  79. Vec3 position, tangent;
  80. sCalculatePositionAndTangent(inP1, inM1, inP2, inM2, t_mid, position, tangent);
  81. float ddt_mid = position.Dot(tangent);
  82. if (abs(ddt_mid) < 1.0e-6f)
  83. {
  84. // Derivative is near zero, we found our root
  85. outTMin = outTMax = t_mid;
  86. return;
  87. }
  88. bool ddt_mid_negative = ddt_mid < 0.0f;
  89. // Update the search interval so that the signs of the derivative at both ends of the interval are still different
  90. if (ddt_mid_negative == ddt_min_negative)
  91. outTMin = t_mid;
  92. else
  93. outTMax = t_mid;
  94. }
  95. }
  96. // Calculate the closest point to the origin for a Cubic Hermite Spline segment
  97. // Only considers the range t e [inTMin, inTMax] and will stop as soon as the closest point falls outside of that range
  98. static inline float sCalculateClosestPointThroughNewtonRaphson(Vec3Arg inP1, Vec3Arg inM1, Vec3Arg inP2, Vec3Arg inM2, float inTMin, float inTMax, float &outDistanceSq)
  99. {
  100. // This is the closest position on the curve to the origin that we found
  101. Vec3 position;
  102. // Calculate the size of the interval
  103. float interval = inTMax - inTMin;
  104. // Start in the middle of the interval
  105. float t = 0.5f * (inTMin + inTMax);
  106. // Do max 10 iterations to prevent taking too much CPU time
  107. for (int iteration = 0; iteration < 10; ++iteration)
  108. {
  109. // Calculate derivative at t, see comment at sCalculateClosestPointThroughBisection for derivation of the equations
  110. Vec3 tangent;
  111. sCalculatePositionAndTangent(inP1, inM1, inP2, inM2, t, position, tangent);
  112. float ddt = position.Dot(tangent); // Leaving out factor 2, we're only interested in the root
  113. // Calculate derivative of ddt: d^2/dt P(t) . P(t) = d/dt (2 P(t) . Tangent(t))
  114. // = 2 (d/dt P(t)) . Tangent(t) + P(t) . d/dt Tangent(t)) = 2 (Tangent(t) . Tangent(t) + P(t) . d/dt Tangent(t))
  115. float d2dt_h00 = 12.0f * t - 6.0f;
  116. float d2dt_h10 = 6.0f * t - 4.0f;
  117. float d2dt_h01 = -d2dt_h00;
  118. float d2dt_h11 = 6.0f * t - 2.0f;
  119. Vec3 ddt_tangent = d2dt_h00 * inP1 + d2dt_h10 * inM1 + d2dt_h01 * inP2 + d2dt_h11 * inM2;
  120. float d2dt = tangent.Dot(tangent) + position.Dot(ddt_tangent); // Leaving out factor 2, because we left it out above too
  121. // If d2dt is zero, the curve is flat and there are multiple t's for which we are closest to the origin, stop now
  122. if (d2dt == 0.0f)
  123. break;
  124. // Do a Newton Raphson step
  125. // See: https://en.wikipedia.org/wiki/Newton%27s_method
  126. // Clamp against [-interval, interval] to avoid overshooting too much, we're not interested outside the interval
  127. float delta = Clamp(-ddt / d2dt, -interval, interval);
  128. // If we're stepping away further from t e [inTMin, inTMax] stop now
  129. if ((t > inTMax && delta > 0.0f) || (t < inTMin && delta < 0.0f))
  130. break;
  131. // If we've converged, stop now
  132. t += delta;
  133. if (abs(delta) < 1.0e-4f)
  134. break;
  135. }
  136. // Calculate the distance squared for the origin to the curve
  137. outDistanceSq = position.LengthSq();
  138. return t;
  139. }
  140. void PathConstraintPathHermite::GetIndexAndT(float inFraction, int &outIndex, float &outT) const
  141. {
  142. int num_points = int(mPoints.size());
  143. // Start by truncating the fraction to get the index and storing the remainder in t
  144. int index = int(trunc(inFraction));
  145. float t = inFraction - float(index);
  146. if (IsLooping())
  147. {
  148. JPH_ASSERT(!mPoints.front().mPosition.IsClose(mPoints.back().mPosition), "A looping path should have a different first and last point!");
  149. // Make sure index is positive by adding a multiple of num_points
  150. if (index < 0)
  151. index += (-index / num_points + 1) * num_points;
  152. // Index needs to be modulo num_points
  153. index = index % num_points;
  154. }
  155. else
  156. {
  157. // Clamp against range of points
  158. if (index < 0)
  159. {
  160. index = 0;
  161. t = 0.0f;
  162. }
  163. else if (index >= num_points - 1)
  164. {
  165. index = num_points - 2;
  166. t = 1.0f;
  167. }
  168. }
  169. outIndex = index;
  170. outT = t;
  171. }
  172. float PathConstraintPathHermite::GetClosestPoint(Vec3Arg inPosition) const
  173. {
  174. JPH_PROFILE_FUNCTION();
  175. int num_points = int(mPoints.size());
  176. // Start with last point on the path, in the non-looping case we won't be visiting this point
  177. float best_dist_sq = (mPoints[num_points - 1].mPosition - inPosition).LengthSq();
  178. float best_t = float(num_points - 1);
  179. // Loop over all points
  180. for (int i = 0, max_i = IsLooping()? num_points : num_points - 1; i < max_i; ++i)
  181. {
  182. const Point &p1 = mPoints[i];
  183. const Point &p2 = mPoints[(i + 1) % num_points];
  184. // Make the curve relative to inPosition
  185. Vec3 p1_pos = p1.mPosition - inPosition;
  186. Vec3 p2_pos = p2.mPosition - inPosition;
  187. // Get distance to p1
  188. float dist_sq = p1_pos.LengthSq();
  189. if (dist_sq < best_dist_sq)
  190. {
  191. best_t = float(i);
  192. best_dist_sq = dist_sq;
  193. }
  194. // First find an interval for the closest point so that we can start doing Newton Raphson steps
  195. float t_min, t_max;
  196. sCalculateClosestPointThroughBisection(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, t_max);
  197. if (t_min == t_max)
  198. {
  199. // If the function above returned no interval then it found the root already and we can just calculate the distance
  200. Vec3 position, tangent;
  201. sCalculatePositionAndTangent(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, position, tangent);
  202. dist_sq = position.LengthSq();
  203. if (dist_sq < best_dist_sq)
  204. {
  205. best_t = float(i) + t_min;
  206. best_dist_sq = dist_sq;
  207. }
  208. }
  209. else
  210. {
  211. // Get closest distance along curve segment
  212. float t = sCalculateClosestPointThroughNewtonRaphson(p1_pos, p1.mTangent, p2_pos, p2.mTangent, t_min, t_max, dist_sq);
  213. if (t >= 0.0f && t <= 1.0f && dist_sq < best_dist_sq)
  214. {
  215. best_t = float(i) + t;
  216. best_dist_sq = dist_sq;
  217. }
  218. }
  219. }
  220. return best_t;
  221. }
  222. void PathConstraintPathHermite::GetPointOnPath(float inFraction, Vec3 &outPathPosition, Vec3 &outPathTangent, Vec3 &outPathNormal, Vec3 &outPathBinormal) const
  223. {
  224. JPH_PROFILE_FUNCTION();
  225. // Determine which hermite spline segment we need
  226. int index;
  227. float t;
  228. GetIndexAndT(inFraction, index, t);
  229. // Get the points on the segment
  230. const Point &p1 = mPoints[index];
  231. const Point &p2 = mPoints[(index + 1) % int(mPoints.size())];
  232. // Calculate the position and tangent on the path
  233. Vec3 tangent;
  234. sCalculatePositionAndTangent(p1.mPosition, p1.mTangent, p2.mPosition, p2.mTangent, t, outPathPosition, tangent);
  235. outPathTangent = tangent.Normalized();
  236. // Just linearly interpolate the normal
  237. Vec3 normal = (1.0f - t) * p1.mNormal + t * p2.mNormal;
  238. // Calculate binormal
  239. outPathBinormal = normal.Cross(outPathTangent).Normalized();
  240. // Recalculate normal so it is perpendicular to both (linear interpolation will cause it not to be)
  241. outPathNormal = outPathTangent.Cross(outPathBinormal);
  242. JPH_ASSERT(outPathNormal.IsNormalized());
  243. }
  244. void PathConstraintPathHermite::SaveBinaryState(StreamOut &inStream) const
  245. {
  246. PathConstraintPath::SaveBinaryState(inStream);
  247. inStream.Write(mPoints);
  248. }
  249. void PathConstraintPathHermite::RestoreBinaryState(StreamIn &inStream)
  250. {
  251. PathConstraintPath::RestoreBinaryState(inStream);
  252. inStream.Read(mPoints);
  253. }
  254. JPH_NAMESPACE_END