PathConstraintPathHermite.cpp 10 KB

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