VirtualC64 v5.0 beta
Commodore 64 Emulator
Loading...
Searching...
No Matches
spline.h
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
23namespace 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// ----------------------------------------------------------------------------
130inline
131void 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// ----------------------------------------------------------------------------
146template<class PointPlotter>
147inline
148void 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// ----------------------------------------------------------------------------
165template<class PointPlotter>
166inline
167void 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
186template<class PointIter>
187inline
188double x(PointIter p)
189{
190 return (*p)[0];
191}
192
193template<class PointIter>
194inline
195double 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// ----------------------------------------------------------------------------
208template<class PointIter, class PointPlotter>
209inline
210void 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// ----------------------------------------------------------------------------
252template<class F>
253class PointPlotter
254{
255 protected:
256 F* f;
257
258 public:
259 PointPlotter(F* arr) : f(arr)
260 {
261 }
262
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