spline.h 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // ---------------------------------------------------------------------------
  2. // This file is part of reSID, a MOS6581 SID emulator engine.
  3. // Copyright (C) 2004 Dag Lem <resid@nimrod.no>
  4. //
  5. // This program is free software; you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation; either version 2 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with this program; if not, write to the Free Software
  17. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  18. // ---------------------------------------------------------------------------
  19. #ifndef __SPLINE_H__
  20. #define __SPLINE_H__
  21. // Our objective is to construct a smooth interpolating single-valued function
  22. // y = f(x).
  23. //
  24. // Catmull-Rom splines are widely used for interpolation, however these are
  25. // parametric curves [x(t) y(t) ...] and can not be used to directly calculate
  26. // y = f(x).
  27. // For a discussion of Catmull-Rom splines see Catmull, E., and R. Rom,
  28. // "A Class of Local Interpolating Splines", Computer Aided Geometric Design.
  29. //
  30. // Natural cubic splines are single-valued functions, and have been used in
  31. // several applications e.g. to specify gamma curves for image display.
  32. // These splines do not afford local control, and a set of linear equations
  33. // including all interpolation points must be solved before any point on the
  34. // curve can be calculated. The lack of local control makes the splines
  35. // more difficult to handle than e.g. Catmull-Rom splines, and real-time
  36. // interpolation of a stream of data points is not possible.
  37. // For a discussion of natural cubic splines, see e.g. Kreyszig, E., "Advanced
  38. // Engineering Mathematics".
  39. //
  40. // Our approach is to approximate the properties of Catmull-Rom splines for
  41. // piecewice cubic polynomials f(x) = ax^3 + bx^2 + cx + d as follows:
  42. // Each curve segment is specified by four interpolation points,
  43. // p0, p1, p2, p3.
  44. // The curve between p1 and p2 must interpolate both p1 and p2, and in addition
  45. // f'(p1.x) = k1 = (p2.y - p0.y)/(p2.x - p0.x) and
  46. // f'(p2.x) = k2 = (p3.y - p1.y)/(p3.x - p1.x).
  47. //
  48. // The constraints are expressed by the following system of linear equations
  49. //
  50. // [ 1 xi xi^2 xi^3 ] [ d ] [ yi ]
  51. // [ 1 2*xi 3*xi^2 ] * [ c ] = [ ki ]
  52. // [ 1 xj xj^2 xj^3 ] [ b ] [ yj ]
  53. // [ 1 2*xj 3*xj^2 ] [ a ] [ kj ]
  54. //
  55. // Solving using Gaussian elimination and back substitution, setting
  56. // dy = yj - yi, dx = xj - xi, we get
  57. //
  58. // a = ((ki + kj) - 2*dy/dx)/(dx*dx);
  59. // b = ((kj - ki)/dx - 3*(xi + xj)*a)/2;
  60. // c = ki - (3*xi*a + 2*b)*xi;
  61. // d = yi - ((xi*a + b)*xi + c)*xi;
  62. //
  63. // Having calculated the coefficients of the cubic polynomial we have the
  64. // choice of evaluation by brute force
  65. //
  66. // for (x = x1; x <= x2; x += res) {
  67. // y = ((a*x + b)*x + c)*x + d;
  68. // plot(x, y);
  69. // }
  70. //
  71. // or by forward differencing
  72. //
  73. // y = ((a*x1 + b)*x1 + c)*x1 + d;
  74. // dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res;
  75. // d2y = (6*a*(x1 + res) + 2*b)*res*res;
  76. // d3y = 6*a*res*res*res;
  77. //
  78. // for (x = x1; x <= x2; x += res) {
  79. // plot(x, y);
  80. // y += dy; dy += d2y; d2y += d3y;
  81. // }
  82. //
  83. // See Foley, Van Dam, Feiner, Hughes, "Computer Graphics, Principles and
  84. // Practice" for a discussion of forward differencing.
  85. //
  86. // If we have a set of interpolation points p0, ..., pn, we may specify
  87. // curve segments between p0 and p1, and between pn-1 and pn by using the
  88. // following constraints:
  89. // f''(p0.x) = 0 and
  90. // f''(pn.x) = 0.
  91. //
  92. // Substituting the results for a and b in
  93. //
  94. // 2*b + 6*a*xi = 0
  95. //
  96. // we get
  97. //
  98. // ki = (3*dy/dx - kj)/2;
  99. //
  100. // or by substituting the results for a and b in
  101. //
  102. // 2*b + 6*a*xj = 0
  103. //
  104. // we get
  105. //
  106. // kj = (3*dy/dx - ki)/2;
  107. //
  108. // Finally, if we have only two interpolation points, the cubic polynomial
  109. // will degenerate to a straight line if we set
  110. //
  111. // ki = kj = dy/dx;
  112. //
  113. #if SPLINE_BRUTE_FORCE
  114. #define interpolate_segment interpolate_brute_force
  115. #else
  116. #define interpolate_segment interpolate_forward_difference
  117. #endif
  118. // ----------------------------------------------------------------------------
  119. // Calculation of coefficients.
  120. // ----------------------------------------------------------------------------
  121. inline
  122. void cubic_coefficients(double x1, double y1, double x2, double y2,
  123. double k1, double k2,
  124. double& a, double& b, double& c, double& d)
  125. {
  126. double dx = x2 - x1, dy = y2 - y1;
  127. a = ((k1 + k2) - 2*dy/dx)/(dx*dx);
  128. b = ((k2 - k1)/dx - 3*(x1 + x2)*a)/2;
  129. c = k1 - (3*x1*a + 2*b)*x1;
  130. d = y1 - ((x1*a + b)*x1 + c)*x1;
  131. }
  132. // ----------------------------------------------------------------------------
  133. // Evaluation of cubic polynomial by brute force.
  134. // ----------------------------------------------------------------------------
  135. template<class PointPlotter>
  136. inline
  137. void interpolate_brute_force(double x1, double y1, double x2, double y2,
  138. double k1, double k2,
  139. PointPlotter plot, double res)
  140. {
  141. double a, b, c, d;
  142. cubic_coefficients(x1, y1, x2, y2, k1, k2, a, b, c, d);
  143. // Calculate each point.
  144. for (double x = x1; x <= x2; x += res) {
  145. double y = ((a*x + b)*x + c)*x + d;
  146. plot(x, y);
  147. }
  148. }
  149. // ----------------------------------------------------------------------------
  150. // Evaluation of cubic polynomial by forward differencing.
  151. // ----------------------------------------------------------------------------
  152. template<class PointPlotter>
  153. inline
  154. void interpolate_forward_difference(double x1, double y1, double x2, double y2,
  155. double k1, double k2,
  156. PointPlotter plot, double res)
  157. {
  158. double a, b, c, d;
  159. cubic_coefficients(x1, y1, x2, y2, k1, k2, a, b, c, d);
  160. double y = ((a*x1 + b)*x1 + c)*x1 + d;
  161. double dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res;
  162. double d2y = (6*a*(x1 + res) + 2*b)*res*res;
  163. double d3y = 6*a*res*res*res;
  164. // Calculate each point.
  165. for (double x = x1; x <= x2; x += res) {
  166. plot(x, y);
  167. y += dy; dy += d2y; d2y += d3y;
  168. }
  169. }
  170. template<class PointIter>
  171. inline
  172. double x(PointIter p)
  173. {
  174. return (*p)[0];
  175. }
  176. template<class PointIter>
  177. inline
  178. double y(PointIter p)
  179. {
  180. return (*p)[1];
  181. }
  182. // ----------------------------------------------------------------------------
  183. // Evaluation of complete interpolating function.
  184. // Note that since each curve segment is controlled by four points, the
  185. // end points will not be interpolated. If extra control points are not
  186. // desirable, the end points can simply be repeated to ensure interpolation.
  187. // Note also that points of non-differentiability and discontinuity can be
  188. // introduced by repeating points.
  189. // ----------------------------------------------------------------------------
  190. template<class PointIter, class PointPlotter>
  191. inline
  192. void interpolate(PointIter p0, PointIter pn, PointPlotter plot, double res)
  193. {
  194. double k1, k2;
  195. // Set up points for first curve segment.
  196. PointIter p1 = p0; ++p1;
  197. PointIter p2 = p1; ++p2;
  198. PointIter p3 = p2; ++p3;
  199. // Draw each curve segment.
  200. for (; p2 != pn; ++p0, ++p1, ++p2, ++p3) {
  201. // p1 and p2 equal; single point.
  202. if (x(p1) == x(p2)) {
  203. continue;
  204. }
  205. // Both end points repeated; straight line.
  206. if (x(p0) == x(p1) && x(p2) == x(p3)) {
  207. k1 = k2 = (y(p2) - y(p1))/(x(p2) - x(p1));
  208. }
  209. // p0 and p1 equal; use f''(x1) = 0.
  210. else if (x(p0) == x(p1)) {
  211. k2 = (y(p3) - y(p1))/(x(p3) - x(p1));
  212. k1 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k2)/2;
  213. }
  214. // p2 and p3 equal; use f''(x2) = 0.
  215. else if (x(p2) == x(p3)) {
  216. k1 = (y(p2) - y(p0))/(x(p2) - x(p0));
  217. k2 = (3*(y(p2) - y(p1))/(x(p2) - x(p1)) - k1)/2;
  218. }
  219. // Normal curve.
  220. else {
  221. k1 = (y(p2) - y(p0))/(x(p2) - x(p0));
  222. k2 = (y(p3) - y(p1))/(x(p3) - x(p1));
  223. }
  224. interpolate_segment(x(p1), y(p1), x(p2), y(p2), k1, k2, plot, res);
  225. }
  226. }
  227. // ----------------------------------------------------------------------------
  228. // Class for plotting integers into an array.
  229. // ----------------------------------------------------------------------------
  230. template<class F>
  231. class PointPlotter
  232. {
  233. protected:
  234. F* f;
  235. public:
  236. PointPlotter(F* arr) : f(arr)
  237. {
  238. }
  239. void operator ()(double x, double y)
  240. {
  241. // Clamp negative values to zero.
  242. if (y < 0) {
  243. y = 0;
  244. }
  245. f[F(x)] = F(y);
  246. }
  247. };
  248. #endif // not __SPLINE_H__