RiemannianGeodesic.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. // David Eberly, Geometric Tools, Redmond WA 98052
  2. // Copyright (c) 1998-2020
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // https://www.boost.org/LICENSE_1_0.txt
  5. // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
  6. // Version: 4.0.2019.08.13
  7. #pragma once
  8. #include <Mathematics/GMatrix.h>
  9. #include <Mathematics/LinearSystem.h>
  10. #include <functional>
  11. // Computing geodesics on a surface is a differential geometric topic that
  12. // involves Riemannian geometry. The algorithm for constructing geodesics
  13. // that is implemented here uses a multiresolution approach. A description
  14. // of the algorithm is in the document
  15. // https://www.geometrictools.com/Documentation/RiemannianGeodesics.pdf
  16. namespace WwiseGTE
  17. {
  18. template <typename Real>
  19. class RiemannianGeodesic
  20. {
  21. public:
  22. // Construction and destruction. The input dimension must be two or
  23. // larger.
  24. RiemannianGeodesic(int dimension)
  25. :
  26. integralSamples(16),
  27. searchSamples(32),
  28. derivativeStep((Real)1e-04),
  29. subdivisions(7),
  30. refinements(8),
  31. searchRadius((Real)1),
  32. refineCallback([]() {}),
  33. mDimension(dimension >= 2 ? dimension : 2),
  34. mMetric(mDimension, mDimension),
  35. mMetricInverse(mDimension, mDimension),
  36. mChristoffel1(mDimension),
  37. mChristoffel2(mDimension),
  38. mMetricDerivative(mDimension),
  39. mMetricInverseExists(true),
  40. mSubdivide(0),
  41. mRefine(0),
  42. mCurrentQuantity(0),
  43. mIntegralStep((Real)1 / (Real)(integralSamples - 1)),
  44. mSearchStep((Real)1 / (Real)searchSamples),
  45. mDerivativeFactor((Real)0.5 / derivativeStep)
  46. {
  47. LogAssert(dimension >= 2, "Dimension must be at least 2.");
  48. for (int i = 0; i < mDimension; ++i)
  49. {
  50. mChristoffel1[i].SetSize(mDimension, mDimension);
  51. mChristoffel2[i].SetSize(mDimension, mDimension);
  52. mMetricDerivative[i].SetSize(mDimension, mDimension);
  53. }
  54. }
  55. virtual ~RiemannianGeodesic()
  56. {
  57. }
  58. // Tweakable parameters.
  59. // 1. The integral samples are the number of samples used in the
  60. // Trapezoid Rule numerical integrator.
  61. // 2. The search samples are the number of samples taken along a ray
  62. // for the steepest descent algorithm used to refine the vertices
  63. // of the polyline approximation to the geodesic curve.
  64. // 3. The derivative step is the value of h used for centered
  65. // difference approximations df/dx = (f(x+h)-f(x-h))/(2*h) in the
  66. // steepest descent algorithm.
  67. // 4. The number of subdivisions indicates how many times the polyline
  68. // segments should be subdivided. The number of polyline vertices
  69. // will be pow(2,subdivisions)+1.
  70. // 5. The number of refinements per subdivision. Setting this to a
  71. // positive value appears necessary when the geodesic curve has a
  72. // large length.
  73. // 6. The search radius is the distance over which the steepest
  74. // descent algorithm searches for a minimum on the line whose
  75. // direction is the estimated gradient. The default of 1 means the
  76. // search interval is [-L,L], where L is the length of the gradient.
  77. // If the search radius is r, then the interval is [-r*L,r*L].
  78. int integralSamples; // default = 16
  79. int searchSamples; // default = 32
  80. Real derivativeStep; // default = 0.0001
  81. int subdivisions; // default = 7
  82. int refinements; // default = 8
  83. Real searchRadius; // default = 1.0
  84. // The dimension of the manifold.
  85. inline int GetDimension() const
  86. {
  87. return mDimension;
  88. }
  89. // Returns the length of the line segment connecting the points.
  90. Real ComputeSegmentLength(GVector<Real> const& point0, GVector<Real> const& point1)
  91. {
  92. // The Trapezoid Rule is used for integration of the length
  93. // integral. The ComputeMetric function internally modifies
  94. // mMetric, which means the qForm values are actually varying
  95. // even though diff does not.
  96. GVector<Real> diff = point1 - point0;
  97. GVector<Real> temp(mDimension);
  98. // Evaluate the integrand at point0.
  99. ComputeMetric(point0);
  100. Real qForm = Dot(diff, mMetric * diff);
  101. LogAssert(qForm > (Real)0, "Unexpected condition.");
  102. Real length = std::sqrt(qForm);
  103. // Evaluate the integrand at point1.
  104. ComputeMetric(point1);
  105. qForm = Dot(diff, mMetric * diff);
  106. LogAssert(qForm > (Real)0, "Unexpected condition.");
  107. length += std::sqrt(qForm);
  108. length *= (Real)0.5;
  109. int imax = integralSamples - 2;
  110. for (int i = 1; i <= imax; ++i)
  111. {
  112. // Evaluate the integrand at point0+t*(point1-point0).
  113. Real t = mIntegralStep * static_cast<Real>(i);
  114. temp = point0 + t * diff;
  115. ComputeMetric(temp);
  116. qForm = Dot(diff, mMetric * diff);
  117. LogAssert(qForm > (Real)0, "Unexpected condition.");
  118. length += std::sqrt(qForm);
  119. }
  120. length *= mIntegralStep;
  121. return length;
  122. }
  123. // Compute the total length of the polyline. The lengths of the
  124. // segments are computed relative to the metric tensor.
  125. Real ComputeTotalLength(int quantity, std::vector<GVector<Real>> const& path)
  126. {
  127. LogAssert(quantity >= 2, "Path must have at least two points.");
  128. Real length = ComputeSegmentLength(path[0], path[1]);
  129. for (int i = 1; i <= quantity - 2; ++i)
  130. {
  131. length += ComputeSegmentLength(path[i], path[i + 1]);
  132. }
  133. return length;
  134. }
  135. // Returns a polyline approximation to a geodesic curve connecting the
  136. // points.
  137. void ComputeGeodesic(GVector<Real> const& end0, GVector<Real> const& end1,
  138. int& quantity, std::vector<GVector<Real>>& path)
  139. {
  140. LogAssert(subdivisions < 32, "Exceeds maximum iterations.");
  141. quantity = (1 << subdivisions) + 1;
  142. path.resize(quantity);
  143. for (int i = 0; i < quantity; ++i)
  144. {
  145. path[i].SetSize(mDimension);
  146. }
  147. mCurrentQuantity = 2;
  148. path[0] = end0;
  149. path[1] = end1;
  150. for (mSubdivide = 1; mSubdivide <= subdivisions; ++mSubdivide)
  151. {
  152. // A subdivision essentially doubles the number of points.
  153. int newQuantity = 2 * mCurrentQuantity - 1;
  154. LogAssert(newQuantity <= quantity, "Unexpected condition.");
  155. // Copy the old points so that there are slots for the
  156. // midpoints during the subdivision, the slots interleaved
  157. // between the old points.
  158. for (int i = mCurrentQuantity - 1; i > 0; --i)
  159. {
  160. path[2 * i] = path[i];
  161. }
  162. // Subdivide the polyline.
  163. for (int i = 0; i <= mCurrentQuantity - 2; ++i)
  164. {
  165. Subdivide(path[2 * i], path[2 * i + 1], path[2 * i + 2]);
  166. }
  167. mCurrentQuantity = newQuantity;
  168. // Refine the current polyline vertices.
  169. for (mRefine = 1; mRefine <= refinements; ++mRefine)
  170. {
  171. for (int i = 1; i <= mCurrentQuantity - 2; ++i)
  172. {
  173. Refine(path[i - 1], path[i], path[i + 1]);
  174. }
  175. }
  176. }
  177. LogAssert(mCurrentQuantity == quantity, "Unexpected condition.");
  178. mSubdivide = 0;
  179. mRefine = 0;
  180. mCurrentQuantity = 0;
  181. }
  182. // Start with the midpoint M of the line segment (E0,E1) and use a
  183. // steepest descent algorithm to move M so that
  184. // Length(E0,M) + Length(M,E1) < Length(E0,E1)
  185. // This is essentially a relaxation scheme that inserts points into
  186. // the current polyline approximation to the geodesic curve.
  187. bool Subdivide(GVector<Real> const& end0, GVector<Real>& mid, GVector<Real> const& end1)
  188. {
  189. mid = (Real)0.5 * (end0 + end1);
  190. auto save = refineCallback;
  191. refineCallback = []() {};
  192. bool changed = Refine(end0, mid, end1);
  193. refineCallback = save;
  194. return changed;
  195. }
  196. // Apply the steepest descent algorithm to move the midpoint M of the
  197. // line segment (E0,E1) so that
  198. // Length(E0,M) + Length(M,E1) < Length(E0,E1)
  199. // This is essentially a relaxation scheme that inserts points into
  200. // the current polyline approximation to the geodesic curve.
  201. bool Refine(GVector<Real> const& end0, GVector<Real>& mid, GVector<Real> const& end1)
  202. {
  203. // Estimate the gradient vector for the function
  204. // F(m) = Length(e0,m) + Length(m,e1).
  205. GVector<Real> temp = mid;
  206. GVector<Real> gradient(mDimension);
  207. for (int i = 0; i < mDimension; ++i)
  208. {
  209. temp[i] = mid[i] + derivativeStep;
  210. gradient[i] = ComputeSegmentLength(end0, temp);
  211. gradient[i] += ComputeSegmentLength(temp, end1);
  212. temp[i] = mid[i] - derivativeStep;
  213. gradient[i] -= ComputeSegmentLength(end0, temp);
  214. gradient[i] -= ComputeSegmentLength(temp, end1);
  215. temp[i] = mid[i];
  216. gradient[i] *= mDerivativeFactor;
  217. }
  218. // Compute the length sum for the current midpoint.
  219. Real length0 = ComputeSegmentLength(end0, mid);
  220. Real length1 = ComputeSegmentLength(mid, end1);
  221. Real oldLength = length0 + length1;
  222. Real tRay, newLength;
  223. GVector<Real> pRay(mDimension);
  224. Real multiplier = mSearchStep * searchRadius;
  225. Real minLength = oldLength;
  226. GVector<Real> minPoint = mid;
  227. for (int i = -searchSamples; i <= searchSamples; ++i)
  228. {
  229. tRay = multiplier * static_cast<Real>(i);
  230. pRay = mid - tRay * gradient;
  231. length0 = ComputeSegmentLength(end0, pRay);
  232. length1 = ComputeSegmentLength(end1, pRay);
  233. newLength = length0 + length1;
  234. if (newLength < minLength)
  235. {
  236. minLength = newLength;
  237. minPoint = pRay;
  238. }
  239. }
  240. mid = minPoint;
  241. refineCallback();
  242. return minLength < oldLength;
  243. }
  244. // A callback that is executed during each call of Refine.
  245. std::function<void(void)> refineCallback;
  246. // Information to be used during the callback.
  247. inline int GetSubdivisionStep() const
  248. {
  249. return mSubdivide;
  250. }
  251. inline int GetRefinementStep() const
  252. {
  253. return mRefine;
  254. }
  255. inline int GetCurrentQuantity() const
  256. {
  257. return mCurrentQuantity;
  258. }
  259. // Curvature computations to measure how close the approximating
  260. // polyline is to a geodesic.
  261. // Returns the total curvature of the line segment connecting the
  262. // points.
  263. Real ComputeSegmentCurvature(GVector<Real> const& point0, GVector<Real> const& point1)
  264. {
  265. // The Trapezoid Rule is used for integration of the curvature
  266. // integral. The ComputeIntegrand function internally modifies
  267. // mMetric, which means the curvature values are actually varying
  268. // even though diff does not.
  269. GVector<Real> diff = point1 - point0;
  270. GVector<Real> temp(mDimension);
  271. // Evaluate the integrand at point0.
  272. Real curvature = ComputeIntegrand(point0, diff);
  273. // Evaluate the integrand at point1.
  274. curvature += ComputeIntegrand(point1, diff);
  275. curvature *= (Real)0.5;
  276. int imax = integralSamples - 2;
  277. for (int i = 1; i <= imax; ++i)
  278. {
  279. // Evaluate the integrand at point0+t*(point1-point0).
  280. Real t = mIntegralStep * static_cast<Real>(i);
  281. temp = point0 + t * diff;
  282. curvature += ComputeIntegrand(temp, diff);
  283. }
  284. curvature *= mIntegralStep;
  285. return curvature;
  286. }
  287. // Compute the total curvature of the polyline. The curvatures of the
  288. // segments are computed relative to the metric tensor.
  289. Real ComputeTotalCurvature(int quantity, std::vector<GVector<Real>> const& path)
  290. {
  291. LogAssert(quantity >= 2, "Path must have at least two points.");
  292. Real curvature = ComputeSegmentCurvature(path[0], path[1]);
  293. for (int i = 1; i <= quantity - 2; ++i)
  294. {
  295. curvature += ComputeSegmentCurvature(path[i], path[i + 1]);
  296. }
  297. return curvature;
  298. }
  299. protected:
  300. // Support for ComputeSegmentCurvature.
  301. Real ComputeIntegrand(GVector<Real> const& pos, GVector<Real> const& der)
  302. {
  303. ComputeMetric(pos);
  304. ComputeChristoffel1(pos);
  305. ComputeMetricInverse();
  306. ComputeChristoffel2();
  307. // g_{ij}*der_{i}*der_{j}
  308. Real qForm0 = Dot(der, mMetric * der);
  309. LogAssert(qForm0 > (Real)0, "Unexpected condition.");
  310. // gamma_{kij}*der_{k}*der_{i}*der_{j}
  311. GMatrix<Real> mat(mDimension, mDimension);
  312. for (int k = 0; k < mDimension; ++k)
  313. {
  314. mat += der[k] * mChristoffel1[k];
  315. }
  316. // This product can be negative because mat is not guaranteed to
  317. // be positive semidefinite. No assertion is added.
  318. Real qForm1 = Dot(der, mat * der);
  319. Real ratio = -qForm1 / qForm0;
  320. // Compute the acceleration.
  321. GVector<Real> acc = ratio * der;
  322. for (int k = 0; k < mDimension; ++k)
  323. {
  324. acc[k] += Dot(der, mChristoffel2[k] * der);
  325. }
  326. // Compute the curvature.
  327. Real curvature = std::sqrt(Dot(acc, mMetric * acc));
  328. return curvature;
  329. }
  330. // Compute the metric tensor for the specified point. Derived classes
  331. // are responsible for implementing this function.
  332. virtual void ComputeMetric(GVector<Real> const& point) = 0;
  333. // Compute the Christoffel symbols of the first kind for the current
  334. // point. Derived classes are responsible for implementing this
  335. // function.
  336. virtual void ComputeChristoffel1(GVector<Real> const& point) = 0;
  337. // Compute the inverse of the current metric tensor. The function
  338. // returns 'true' iff the inverse exists.
  339. bool ComputeMetricInverse()
  340. {
  341. mMetricInverse = Inverse(mMetric, &mMetricInverseExists);
  342. return mMetricInverseExists;
  343. }
  344. // Compute the derivative of the metric tensor for the current state.
  345. // This is a triply indexed quantity, the values computed using the
  346. // Christoffel symbols of the first kind.
  347. void ComputeMetricDerivative()
  348. {
  349. for (int derivative = 0; derivative < mDimension; ++derivative)
  350. {
  351. for (int i0 = 0; i0 < mDimension; ++i0)
  352. {
  353. for (int i1 = 0; i1 < mDimension; ++i1)
  354. {
  355. mMetricDerivative[derivative](i0, i1) =
  356. mChristoffel1[derivative](i0, i1) +
  357. mChristoffel1[derivative](i1, i0);
  358. }
  359. }
  360. }
  361. }
  362. // Compute the Christoffel symbols of the second kind for the current
  363. // state. The values depend on the inverse of the metric tensor, so
  364. // they may be computed only when the inverse exists. The function
  365. // returns 'true' whenever the inverse metric tensor exists.
  366. bool ComputeChristoffel2()
  367. {
  368. for (int i2 = 0; i2 < mDimension; ++i2)
  369. {
  370. for (int i0 = 0; i0 < mDimension; ++i0)
  371. {
  372. for (int i1 = 0; i1 < mDimension; ++i1)
  373. {
  374. Real fValue = (Real)0;
  375. for (int j = 0; j < mDimension; ++j)
  376. {
  377. fValue += mMetricInverse(i2, j) * mChristoffel1[j](i0, i1);
  378. }
  379. mChristoffel2[i2](i0, i1) = fValue;
  380. }
  381. }
  382. }
  383. return mMetricInverseExists;
  384. }
  385. int mDimension;
  386. GMatrix<Real> mMetric;
  387. GMatrix<Real> mMetricInverse;
  388. std::vector<GMatrix<Real>> mChristoffel1;
  389. std::vector<GMatrix<Real>> mChristoffel2;
  390. std::vector<GMatrix<Real>> mMetricDerivative;
  391. bool mMetricInverseExists;
  392. // Progress parameters that are useful to mRefineCallback.
  393. int mSubdivide, mRefine, mCurrentQuantity;
  394. // Derived tweaking parameters.
  395. Real mIntegralStep; // = 1/(mIntegralQuantity-1)
  396. Real mSearchStep; // = 1/mSearchQuantity
  397. Real mDerivativeFactor; // = 1/(2*mDerivativeStep)
  398. };
  399. }