ParametricCurve.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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/Integration.h>
  9. #include <Mathematics/RootsBisection.h>
  10. #include <Mathematics/Vector.h>
  11. namespace WwiseGTE
  12. {
  13. template <int N, typename Real>
  14. class ParametricCurve
  15. {
  16. protected:
  17. // Abstract base class for a parameterized curve X(t), where t is the
  18. // parameter in [tmin,tmax] and X is an N-tuple position. The first
  19. // constructor is for single-segment curves. The second constructor is
  20. // for multiple-segment curves. The times must be strictly increasing.
  21. ParametricCurve(Real tmin, Real tmax)
  22. :
  23. mTime(2),
  24. mSegmentLength(1),
  25. mAccumulatedLength(1),
  26. mRombergOrder(DEFAULT_ROMBERG_ORDER),
  27. mMaxBisections(DEFAULT_MAX_BISECTIONS),
  28. mConstructed(false)
  29. {
  30. mTime[0] = tmin;
  31. mTime[1] = tmax;
  32. mSegmentLength[0] = (Real)0;
  33. mAccumulatedLength[0] = (Real)0;
  34. }
  35. ParametricCurve(int numSegments, Real const* times)
  36. :
  37. mTime(numSegments + 1),
  38. mSegmentLength(numSegments),
  39. mAccumulatedLength(numSegments),
  40. mRombergOrder(DEFAULT_ROMBERG_ORDER),
  41. mMaxBisections(DEFAULT_MAX_BISECTIONS),
  42. mConstructed(false)
  43. {
  44. std::copy(times, times + numSegments + 1, mTime.begin());
  45. mSegmentLength[0] = (Real)0;
  46. mAccumulatedLength[0] = (Real)0;
  47. }
  48. public:
  49. virtual ~ParametricCurve()
  50. {
  51. }
  52. // To validate construction, create an object as shown:
  53. // DerivedClassCurve<N, Real> curve(parameters);
  54. // if (!curve) { <constructor failed, handle accordingly>; }
  55. inline operator bool() const
  56. {
  57. return mConstructed;
  58. }
  59. // Member access.
  60. inline Real GetTMin() const
  61. {
  62. return mTime.front();
  63. }
  64. inline Real GetTMax() const
  65. {
  66. return mTime.back();
  67. }
  68. inline int GetNumSegments() const
  69. {
  70. return static_cast<int>(mSegmentLength.size());
  71. }
  72. inline Real const* GetTimes() const
  73. {
  74. return &mTime[0];
  75. }
  76. // This function applies only when the first constructor is used (two
  77. // times rather than a sequence of three or more times).
  78. void SetTimeInterval(Real tmin, Real tmax)
  79. {
  80. if (mTime.size() == 2)
  81. {
  82. mTime[0] = tmin;
  83. mTime[1] = tmax;
  84. }
  85. }
  86. // Parameters used in GetLength(...), GetTotalLength() and
  87. // GetTime(...).
  88. // The default value is 8.
  89. inline void SetRombergOrder(int order)
  90. {
  91. mRombergOrder = std::max(order, 1);
  92. }
  93. // The default value is 1024.
  94. inline void SetMaxBisections(unsigned int maxBisections)
  95. {
  96. mMaxBisections = std::max(maxBisections, 1u);
  97. }
  98. // Evaluation of the curve. The function supports derivative
  99. // calculation through order 3; that is, order <= 3 is required. If
  100. // you want/ only the position, pass in order of 0. If you want the
  101. // position and first derivative, pass in order of 1, and so on. The
  102. // output array 'jet' must have enough storage to support the maximum
  103. // order. The values are ordered as: position, first derivative,
  104. // second derivative, third derivative.
  105. enum { SUP_ORDER = 4 };
  106. virtual void Evaluate(Real t, unsigned int order, Vector<N, Real>* jet) const = 0;
  107. void Evaluate(Real t, unsigned int order, Real* values) const
  108. {
  109. Evaluate(t, order, reinterpret_cast<Vector<N, Real>*>(values));
  110. }
  111. // Differential geometric quantities.
  112. Vector<N, Real> GetPosition(Real t) const
  113. {
  114. Vector<N, Real> position;
  115. Evaluate(t, 0, &position);
  116. return position;
  117. }
  118. Vector<N, Real> GetTangent(Real t) const
  119. {
  120. std::array<Vector<N, Real>, 2> jet; // (position, tangent)
  121. Evaluate(t, 1, jet.data());
  122. Normalize(jet[1]);
  123. return jet[1];
  124. }
  125. Real GetSpeed(Real t) const
  126. {
  127. std::array<Vector<N, Real>, 2> jet; // (position, tangent)
  128. Evaluate(t, 1, jet.data());
  129. return Length(jet[1]);
  130. }
  131. Real GetLength(Real t0, Real t1) const
  132. {
  133. std::function<Real(Real)> speed = [this](Real t)
  134. {
  135. return GetSpeed(t);
  136. };
  137. if (mSegmentLength[0] == (Real)0)
  138. {
  139. // Lazy initialization of lengths of segments.
  140. int const numSegments = static_cast<int>(mSegmentLength.size());
  141. Real accumulated = (Real)0;
  142. for (int i = 0; i < numSegments; ++i)
  143. {
  144. mSegmentLength[i] = Integration<Real>::Romberg(mRombergOrder,
  145. mTime[i], mTime[i + 1], speed);
  146. accumulated += mSegmentLength[i];
  147. mAccumulatedLength[i] = accumulated;
  148. }
  149. }
  150. t0 = std::max(t0, GetTMin());
  151. t1 = std::min(t1, GetTMax());
  152. auto iter0 = std::lower_bound(mTime.begin(), mTime.end(), t0);
  153. int index0 = static_cast<int>(iter0 - mTime.begin());
  154. auto iter1 = std::lower_bound(mTime.begin(), mTime.end(), t1);
  155. int index1 = static_cast<int>(iter1 - mTime.begin());
  156. Real length;
  157. if (index0 < index1)
  158. {
  159. length = (Real)0;
  160. if (t0 < *iter0)
  161. {
  162. length += Integration<Real>::Romberg(mRombergOrder, t0,
  163. mTime[index0], speed);
  164. }
  165. int isup;
  166. if (t1 < *iter1)
  167. {
  168. length += Integration<Real>::Romberg(mRombergOrder,
  169. mTime[index1 - 1], t1, speed);
  170. isup = index1 - 1;
  171. }
  172. else
  173. {
  174. isup = index1;
  175. }
  176. for (int i = index0; i < isup; ++i)
  177. {
  178. length += mSegmentLength[i];
  179. }
  180. }
  181. else
  182. {
  183. length = Integration<Real>::Romberg(mRombergOrder, t0, t1, speed);
  184. }
  185. return length;
  186. }
  187. Real GetTotalLength() const
  188. {
  189. if (mAccumulatedLength.back() == (Real)0)
  190. {
  191. // Lazy evaluation of the accumulated length array.
  192. return GetLength(mTime.front(), mTime.back());
  193. }
  194. return mAccumulatedLength.back();
  195. }
  196. // Inverse mapping of s = Length(t) given by t = Length^{-1}(s). The
  197. // inverse length function generally cannot be written in closed form,
  198. // in which case it is not directly computable. Instead, we can
  199. // specify s and estimate the root t for F(t) = Length(t) - s. The
  200. // derivative is F'(t) = Speed(t) >= 0, so F(t) is nondecreasing. To
  201. // be robust, we use bisection to locate the root, although it is
  202. // possible to use a hybrid of Newton's method and bisection. For
  203. // details, see the document
  204. // https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf
  205. Real GetTime(Real length) const
  206. {
  207. if (length > (Real)0)
  208. {
  209. if (length < GetTotalLength())
  210. {
  211. std::function<Real(Real)> F = [this, &length](Real t)
  212. {
  213. return Integration<Real>::Romberg(mRombergOrder,
  214. mTime.front(), t, [this](Real z) { return GetSpeed(z); })
  215. - length;
  216. };
  217. // We know that F(tmin) < 0 and F(tmax) > 0, which allows us to
  218. // use bisection. Rather than bisect the entire interval, let's
  219. // narrow it down with a reasonable initial guess.
  220. Real ratio = length / GetTotalLength();
  221. Real omratio = (Real)1 - ratio;
  222. Real tmid = omratio * mTime.front() + ratio * mTime.back();
  223. Real fmid = F(tmid);
  224. if (fmid > (Real)0)
  225. {
  226. RootsBisection<Real>::Find(F, mTime.front(), tmid, (Real)-1,
  227. (Real)1, mMaxBisections, tmid);
  228. }
  229. else if (fmid < (Real)0)
  230. {
  231. RootsBisection<Real>::Find(F, tmid, mTime.back(), (Real)-1,
  232. (Real)1, mMaxBisections, tmid);
  233. }
  234. return tmid;
  235. }
  236. else
  237. {
  238. return mTime.back();
  239. }
  240. }
  241. else
  242. {
  243. return mTime.front();
  244. }
  245. }
  246. // Compute a subset of curve points according to the specified attribute.
  247. // The input 'numPoints' must be two or larger.
  248. void SubdivideByTime(int numPoints, Vector<N, Real>* points) const
  249. {
  250. Real delta = (mTime.back() - mTime.front()) / (Real)(numPoints - 1);
  251. for (int i = 0; i < numPoints; ++i)
  252. {
  253. Real t = mTime.front() + delta * i;
  254. points[i] = GetPosition(t);
  255. }
  256. }
  257. void SubdivideByLength(int numPoints, Vector<N, Real>* points) const
  258. {
  259. Real delta = GetTotalLength() / (Real)(numPoints - 1);
  260. for (int i = 0; i < numPoints; ++i)
  261. {
  262. Real length = delta * i;
  263. Real t = GetTime(length);
  264. points[i] = GetPosition(t);
  265. }
  266. }
  267. protected:
  268. enum
  269. {
  270. DEFAULT_ROMBERG_ORDER = 8,
  271. DEFAULT_MAX_BISECTIONS = 1024
  272. };
  273. std::vector<Real> mTime;
  274. mutable std::vector<Real> mSegmentLength;
  275. mutable std::vector<Real> mAccumulatedLength;
  276. int mRombergOrder;
  277. unsigned int mMaxBisections;
  278. bool mConstructed;
  279. };
  280. }