catmull_rom.qbk 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. [/
  2. Copyright 2017 Nick Thompson
  3. Distributed under the Boost Software License, Version 1.0.
  4. (See accompanying file LICENSE_1_0.txt or copy at
  5. http://www.boost.org/LICENSE_1_0.txt).
  6. ]
  7. [section:catmull_rom Catmull-Rom Splines]
  8. [heading Synopsis]
  9. ``
  10. #include <boost/math/interpolators/catmull_rom.hpp>
  11. namespace boost{ namespace math{
  12. template<class Point, class RandomAccessContainer = std::vector<Point> >
  13. class catmull_rom
  14. {
  15. public:
  16. catmull_rom(RandomAccessContainer&& points, bool closed = false, Real alpha = (Real) 1/ (Real) 2)
  17. catmull_rom(std::initializer_list<Point> l, bool closed = false, typename Point::value_type alpha = (typename Point::value_type) 1/ (typename Point::value_type) 2);
  18. Real operator()(Real s) const;
  19. Real max_parameter() const;
  20. Real parameter_at_point(size_t i) const;
  21. Point prime(Real s) const;
  22. };
  23. }}
  24. ``
  25. [heading Description]
  26. Catmull-Rom splines are a family of interpolating curves which are commonly used in computer graphics and animation.
  27. Catmull-Rom splines enjoy the following properties:
  28. * Affine invariance: The interpolant commutes with affine transformations.
  29. * Local support of the basis functions: This gives stability and fast evaluation.
  30. * /C/[super 2]-smoothness
  31. * Interpolation of control points-this means the curve passes through the control points.
  32. Many curves (such as B[eacute]zier) are /approximating/ - they do not pass through their control points.
  33. This makes them more difficult to use than interpolating splines.
  34. The `catmull_rom` class provided by Boost.Math creates a cubic Catmull-Rom spline from an array of points in any dimension.
  35. Since there are numerous ways to represent a point in /n/-dimensional space,
  36. the class attempts to be flexible by templating on the point type.
  37. The requirements on the point type are discussing in more detail below, but roughly, it must have a dereference operator defined (e.g., `p[0]` is not a syntax error),
  38. it must be able to be dereferenced up to `dimension -1`, and `p[i]` is of type `Real`, define `value_type`, and the free function `size()`.
  39. These requirements are met by `std::vector` and `std::array`.
  40. The basic usage is shown here:
  41. std::vector<std::array<Real, 3>> points(4);
  42. points[0] = {0,0,0};
  43. points[1] = {1,0,0};
  44. points[2] = {0,1,0};
  45. points[3] = {0,0,1};
  46. catmull_rom<std::array<Real, 3>> cr(std::move(points));
  47. // Interpolate at s = 0.1:
  48. auto point = cr(0.1);
  49. The spline can be either open or /closed/, closed meaning that there is some /s > 0/ such that /P(s) = P(0)/.
  50. The default is open, but this can be easily changed:
  51. // closed = true
  52. catmull_rom<std::array<Real, 3>> cr(std::move(points), true);
  53. In either case, evaluating the interpolator at /s=0/ returns the first point in the list.
  54. If the curve is open, then the first and last segments may have strange behavior.
  55. The traditional solution is to prepend a carefully selected control point to the data so that the first data segment (second interpolator segment) has reasonable tangent vectors, and simply ignore the first interpolator segment.
  56. A control point is appended to the data using similar criteria.
  57. However, we recommend not going through this effort until it proves to be necessary: For most use-cases, the curve is good enough without prepending and appending control points, and responsible selection of non-data control points is difficult.
  58. Inside `catmull_rom`, the curve is represented as closed.
  59. This is because an open Catmull-Rom curve is /implicitly closed/, but the closing point is the zero vector.
  60. There is no reason to suppose that the zero vector is a better closing point than the endpoint (or any other point, for that matter),
  61. so traditionally Catmull-Rom splines leave the segment between the first and second point undefined,
  62. as well as the segment between the second-to-last and last point.
  63. We find this property of the traditional implementation of Catmull-Rom splines annoying and confusing to the user.
  64. Hence internally, we close the curve so that the first and last segments are defined.
  65. Of course, this causes the /tangent/ vectors to the first and last points to be bizarre.
  66. This is a "pick your poison" design decision-either the curve cannot interpolate in its first and last segments,
  67. or the tangents along the first and last segments are meaningless.
  68. In the vast majority of cases, this will be no problem to the user.
  69. However, if it becomes a problem, then the user should add one extra point in a position they believe is reasonable and close the curve.
  70. Since the routine internally represents the curve as closed,
  71. a question arises: Why does the user have to specify if the curve is open or closed?
  72. The answer is that the parameterization is chosen by the routine,
  73. so it is of interest to the user to understand the values where a meaningful result is returned.
  74. Real max_s = cr.max_parameter();
  75. If you attempt to interpolate for `s > max_s`, an exception is thrown.
  76. If the curve is closed, then `cr(max_s) = p0`, where `p0` is the first point on the curve.
  77. If the curve is open, then `cr(max_s) = pf`, where `pf` is the final point on the curve.
  78. The Catmull-Rom curve admits an infinite number of parameterizations.
  79. The default parameterization of the `catmull_rom` class is the so-called /centripedal/ parameterization.
  80. This parameterization has been shown to be the only parameterization that does not form cusps or self-intersections within segments.
  81. However, for advanced users, other parameterizations can be chosen using the /alpha/ parameter:
  82. // alpha = 1 is the "chordal" parameterization.
  83. catmull_rom<std::array<double, 3>> cr(std::move(points), false, 1.0);
  84. The alpha parameter must always be in the range `[0,1]`.
  85. Finally, the tangent vector to any point of the curve can be computed via
  86. double s = 0.1;
  87. Point tangent = cr.prime(s);
  88. Since the magnitude of the tangent vector is dependent on the parameterization,
  89. it is not meaningful (unless the user chooses the chordal parameterization /alpha = 1/ which parameterizes by Euclidean distance between points.)
  90. However, its direction is meaningful no matter the parameterization, so the user may wish to normalize this result.
  91. [heading Examples]
  92. [import ../../example/catmull_rom_example.cpp]
  93. [heading Performance]
  94. The following performance numbers were generated for a call to the Catmull-Rom interpolation method.
  95. The number that follows the slash is the number of points passed to the interpolant.
  96. We see that evaluation of the interpolant is [bigo](/log/(/N/)).
  97. Run on 2700 MHz CPU
  98. CPU Caches:
  99. L1 Data 32K (x2)
  100. L1 Instruction 32K (x2)
  101. L2 Unified 262K (x2)
  102. L3 Unified 3145K (x1)
  103. ---------------------------------------------------------
  104. Benchmark Time CPU
  105. ---------------------------------------------------------
  106. BM_CatmullRom<double>/4 20 ns 20 ns
  107. BM_CatmullRom<double>/8 21 ns 21 ns
  108. BM_CatmullRom<double>/16 23 ns 23 ns
  109. BM_CatmullRom<double>/32 24 ns 24 ns
  110. BM_CatmullRom<double>/64 27 ns 27 ns
  111. BM_CatmullRom<double>/128 27 ns 27 ns
  112. BM_CatmullRom<double>/256 30 ns 30 ns
  113. BM_CatmullRom<double>/512 32 ns 31 ns
  114. BM_CatmullRom<double>/1024 33 ns 33 ns
  115. BM_CatmullRom<double>/2048 34 ns 34 ns
  116. BM_CatmullRom<double>/4096 36 ns 36 ns
  117. BM_CatmullRom<double>/8192 38 ns 38 ns
  118. BM_CatmullRom<double>/16384 39 ns 39 ns
  119. BM_CatmullRom<double>/32768 40 ns 40 ns
  120. BM_CatmullRom<double>/65536 45 ns 44 ns
  121. BM_CatmullRom<double>/131072 46 ns 46 ns
  122. BM_CatmullRom<double>/262144 50 ns 50 ns
  123. BM_CatmullRom<double>/524288 53 ns 52 ns
  124. BM_CatmullRom<double>/1048576 58 ns 57 ns
  125. BM_CatmullRom<double>_BigO 2.97 lgN 2.97 lgN
  126. BM_CatmullRom<double>_RMS 19 % 19 %
  127. [heading Point types]
  128. We have already discussed that certain conditions on the `Point` type template argument must be obeyed.
  129. The following shows a custom point type in 3D which can be used as a template argument to Catmull-Rom:
  130. template<class Real>
  131. class mypoint3d
  132. {
  133. public:
  134. // Must define a value_type:
  135. typedef Real value_type;
  136. // Regular constructor--need not be of this form.
  137. mypoint3d(Real x, Real y, Real z) {m_vec[0] = x; m_vec[1] = y; m_vec[2] = z; }
  138. // Must define a default constructor:
  139. mypoint3d() {}
  140. // Must define array access:
  141. Real operator[](size_t i) const
  142. {
  143. return m_vec[i];
  144. }
  145. // Must define array element assignment:
  146. Real& operator[](size_t i)
  147. {
  148. return m_vec[i];
  149. }
  150. private:
  151. std::array<Real, 3> m_vec;
  152. };
  153. // Must define the free function "size()":
  154. template<class Real>
  155. constexpr size_t size(const mypoint3d<Real>& c)
  156. {
  157. return 3;
  158. }
  159. These conditions are satisfied by both `std::array` and `std::vector`, but it may nonetheless be useful to define your own point class so that (say) you can define geometric distance between them.
  160. [heading Caveats]
  161. The Catmull-Rom interpolator requires memory for three more points than is provided by the user.
  162. This causes the class to call a `resize()` on the input vector.
  163. If `v.capacity() >= v.size() + 3`, then no problems arise; there are no reallocs, and in practice this condition is almost always satisfied.
  164. However, if `v.capacity() < v.size() + 3`, the `realloc` causes a performance penalty of roughly 20%.
  165. [heading Generic Containers]
  166. The `Point` type may be stored in a different container than `std::vector`.
  167. For example, here is how to store the points in a Boost.uBLAS vector:
  168. mypoint3d<Real> p0(0.1, 0.2, 0.3);
  169. mypoint3d<Real> p1(0.2, 0.3, 0.4);
  170. mypoint3d<Real> p2(0.3, 0.4, 0.5);
  171. mypoint3d<Real> p3(0.4, 0.5, 0.6);
  172. mypoint3d<Real> p4(0.5, 0.6, 0.7);
  173. mypoint3d<Real> p5(0.6, 0.7, 0.8);
  174. boost::numeric::ublas::vector<mypoint3d<Real>> u(6);
  175. u[0] = p0;
  176. u[1] = p1;
  177. u[2] = p2;
  178. u[3] = p3;
  179. u[4] = p4;
  180. u[5] = p5;
  181. // Tests initializer_list:
  182. catmull_rom<mypoint3d<Real>, decltype(u)> cat(std::move(u));
  183. [heading References]
  184. * Cem Yuksel, Scott Schaefer, and John Keyser, ['Parameterization and applications of Catmull–Rom curves], Computer-Aided Design 43 (2011) 747–755.
  185. * Phillip J. Barry and Ronald N. Goldman, ['A Recursive Evaluation Algorithm for a Class of Catmull-Rom Splines], Computer Graphics, Volume 22, Number 4, August 1988
  186. [endsect] [/section:catmull_rom Catmull-Rom Splines]