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