1 // ---------------------------------------------------------------------------
2 // This file is part of reSID, a MOS6581 SID emulator engine.
3 // Copyright (C) 2004 Dag Lem <resid@nimrod.no>
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.
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.
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 // ---------------------------------------------------------------------------
23 // Our objective is to construct a smooth interpolating single-valued function
26 // Catmull-Rom splines are widely used for interpolation, however these are
27 // parametric curves [x(t) y(t) ...] and can not be used to directly calculate
29 // For a discussion of Catmull-Rom splines see Catmull, E., and R. Rom,
30 // "A Class of Local Interpolating Splines", Computer Aided Geometric Design.
32 // Natural cubic splines are single-valued functions, and have been used in
33 // several applications e.g. to specify gamma curves for image display.
34 // These splines do not afford local control, and a set of linear equations
35 // including all interpolation points must be solved before any point on the
36 // curve can be calculated. The lack of local control makes the splines
37 // more difficult to handle than e.g. Catmull-Rom splines, and real-time
38 // interpolation of a stream of data points is not possible.
39 // For a discussion of natural cubic splines, see e.g. Kreyszig, E., "Advanced
40 // Engineering Mathematics".
42 // Our approach is to approximate the properties of Catmull-Rom splines for
43 // piecewice cubic polynomials f(x) = ax^3 + bx^2 + cx + d as follows:
44 // Each curve segment is specified by four interpolation points,
46 // The curve between p1 and p2 must interpolate both p1 and p2, and in addition
47 // f'(p1.x) = k1 = (p2.y - p0.y)/(p2.x - p0.x) and
48 // f'(p2.x) = k2 = (p3.y - p1.y)/(p3.x - p1.x).
50 // The constraints are expressed by the following system of linear equations
52 // [ 1 xi xi^2 xi^3 ] [ d ] [ yi ]
53 // [ 1 2*xi 3*xi^2 ] * [ c ] = [ ki ]
54 // [ 1 xj xj^2 xj^3 ] [ b ] [ yj ]
55 // [ 1 2*xj 3*xj^2 ] [ a ] [ kj ]
57 // Solving using Gaussian elimination and back substitution, setting
58 // dy = yj - yi, dx = xj - xi, we get
60 // a = ((ki + kj) - 2*dy/dx)/(dx*dx);
61 // b = ((kj - ki)/dx - 3*(xi + xj)*a)/2;
62 // c = ki - (3*xi*a + 2*b)*xi;
63 // d = yi - ((xi*a + b)*xi + c)*xi;
65 // Having calculated the coefficients of the cubic polynomial we have the
66 // choice of evaluation by brute force
68 // for (x = x1; x <= x2; x += res) {
69 // y = ((a*x + b)*x + c)*x + d;
73 // or by forward differencing
75 // y = ((a*x1 + b)*x1 + c)*x1 + d;
76 // dy = (3*a*(x1 + res) + 2*b)*x1*res + ((a*res + b)*res + c)*res;
77 // d2y = (6*a*(x1 + res) + 2*b)*res*res;
78 // d3y = 6*a*res*res*res;
80 // for (x = x1; x <= x2; x += res) {
82 // y += dy; dy += d2y; d2y += d3y;
85 // See Foley, Van Dam, Feiner, Hughes, "Computer Graphics, Principles and
86 // Practice" for a discussion of forward differencing.
88 // If we have a set of interpolation points p0, ..., pn, we may specify
89 // curve segments between p0 and p1, and between pn-1 and pn by using the
90 // following constraints:
94 // Substituting the results for a and b in
100 // ki = (3*dy/dx - kj)/2;
102 // or by substituting the results for a and b in
108 // kj = (3*dy/dx - ki)/2;
110 // Finally, if we have only two interpolation points, the cubic polynomial
111 // will degenerate to a straight line if we set
117 #if SPLINE_BRUTE_FORCE
118 #define interpolate_segment interpolate_brute_force
120 #define interpolate_segment interpolate_forward_difference
124 // ----------------------------------------------------------------------------
125 // Calculation of coefficients.
126 // ----------------------------------------------------------------------------
128 void cubic_coefficients(double x1
, double y1
, double x2
, double y2
,
129 double k1
, double k2
,
130 double& a
, double& b
, double& c
, double& d
)
132 double dx
= x2
- x1
, dy
= y2
- y1
;
134 a
= ((k1
+ k2
) - 2*dy
/dx
)/(dx
*dx
);
135 b
= ((k2
- k1
)/dx
- 3*(x1
+ x2
)*a
)/2;
136 c
= k1
- (3*x1
*a
+ 2*b
)*x1
;
137 d
= y1
- ((x1
*a
+ b
)*x1
+ c
)*x1
;
140 // ----------------------------------------------------------------------------
141 // Evaluation of cubic polynomial by brute force.
142 // ----------------------------------------------------------------------------
143 template<class PointPlotter
>
145 void interpolate_brute_force(double x1
, double y1
, double x2
, double y2
,
146 double k1
, double k2
,
147 PointPlotter plot
, double res
)
150 cubic_coefficients(x1
, y1
, x2
, y2
, k1
, k2
, a
, b
, c
, d
);
152 // Calculate each point.
153 for (double x
= x1
; x
<= x2
; x
+= res
) {
154 double y
= ((a
*x
+ b
)*x
+ c
)*x
+ d
;
159 // ----------------------------------------------------------------------------
160 // Evaluation of cubic polynomial by forward differencing.
161 // ----------------------------------------------------------------------------
162 template<class PointPlotter
>
164 void interpolate_forward_difference(double x1
, double y1
, double x2
, double y2
,
165 double k1
, double k2
,
166 PointPlotter plot
, double res
)
169 cubic_coefficients(x1
, y1
, x2
, y2
, k1
, k2
, a
, b
, c
, d
);
171 double y
= ((a
*x1
+ b
)*x1
+ c
)*x1
+ d
;
172 double dy
= (3*a
*(x1
+ res
) + 2*b
)*x1
*res
+ ((a
*res
+ b
)*res
+ c
)*res
;
173 double d2y
= (6*a
*(x1
+ res
) + 2*b
)*res
*res
;
174 double d3y
= 6*a
*res
*res
*res
;
176 // Calculate each point.
177 for (double x
= x1
; x
<= x2
; x
+= res
) {
179 y
+= dy
; dy
+= d2y
; d2y
+= d3y
;
183 template<class PointIter
>
185 double x(PointIter p
)
190 template<class PointIter
>
192 double y(PointIter p
)
197 // ----------------------------------------------------------------------------
198 // Evaluation of complete interpolating function.
199 // Note that since each curve segment is controlled by four points, the
200 // end points will not be interpolated. If extra control points are not
201 // desirable, the end points can simply be repeated to ensure interpolation.
202 // Note also that points of non-differentiability and discontinuity can be
203 // introduced by repeating points.
204 // ----------------------------------------------------------------------------
205 template<class PointIter
, class PointPlotter
>
207 void interpolate(PointIter p0
, PointIter pn
, PointPlotter plot
, double res
)
211 // Set up points for first curve segment.
212 PointIter p1
= p0
; ++p1
;
213 PointIter p2
= p1
; ++p2
;
214 PointIter p3
= p2
; ++p3
;
216 // Draw each curve segment.
217 for (; p2
!= pn
; ++p0
, ++p1
, ++p2
, ++p3
) {
218 // p1 and p2 equal; single point.
219 if (x(p1
) == x(p2
)) {
222 // Both end points repeated; straight line.
223 if (x(p0
) == x(p1
) && x(p2
) == x(p3
)) {
224 k1
= k2
= (y(p2
) - y(p1
))/(x(p2
) - x(p1
));
226 // p0 and p1 equal; use f''(x1) = 0.
227 else if (x(p0
) == x(p1
)) {
228 k2
= (y(p3
) - y(p1
))/(x(p3
) - x(p1
));
229 k1
= (3*(y(p2
) - y(p1
))/(x(p2
) - x(p1
)) - k2
)/2;
231 // p2 and p3 equal; use f''(x2) = 0.
232 else if (x(p2
) == x(p3
)) {
233 k1
= (y(p2
) - y(p0
))/(x(p2
) - x(p0
));
234 k2
= (3*(y(p2
) - y(p1
))/(x(p2
) - x(p1
)) - k1
)/2;
238 k1
= (y(p2
) - y(p0
))/(x(p2
) - x(p0
));
239 k2
= (y(p3
) - y(p1
))/(x(p3
) - x(p1
));
242 interpolate_segment(x(p1
), y(p1
), x(p2
), y(p2
), k1
, k2
, plot
, res
);
246 // ----------------------------------------------------------------------------
247 // Class for plotting integers into an array.
248 // ----------------------------------------------------------------------------
256 PointPlotter(F
* arr
) : f(arr
)
260 void operator ()(double x
, double y
)
262 // Clamp negative values to zero.
272 #endif // not __SPLINE_H__