View Javadoc
1   /******************************************************************************
2    * _Spline2D.java - Spline Math in Java
3    * $Id$
4    * 
5    * BuckoFIBS - Backgammon by BuckoSoft
6    * Copyright© 2011 - Dick Balaska - BuckoSoft, Corp.
7    * 
8    * Jave Spline algorithm from <a href="http://www.java2s.com/Code/Java/2D-Graphics-GUI/Spline2D.htm">Spline2D.htm</a>
9    * 
10   * $Log$
11   */
12  
13  ///////////////////////////////////////////
14  /*
15   * @(#)Spline2D.java
16   * 
17   * Copyright (c) 2003 Martin Krueger
18   * Copyright (c) 2005 David Benson
19   *  
20   */
21  
22  package com.buckosoft.fibs.BuckoFIBS.gui.boardTab.boardPane.animateType;
23  
24  import java.util.Arrays;
25  
26  /**
27   * Interpolates points given in the 2D plane. The resulting spline
28   * is a function s: R -> R^2 with parameter t in [0,1].
29   * 
30   * @author krueger
31   * @see <a href="http://www.java2s.com/Code/Java/2D-Graphics-GUI/Spline2D.htm">Spline2D.htm</a>
32   * @see <a href="http://cvs.buckosoft.com/Projects/BuckoFIBS/BuckoFIBS/src/main/java/com/buckosoft/fibs/BuckoFIBS/gui/boardTab/boardPane/animateType/_Spline2D.java">cvs _Spline2D.java</a>
33   */
34  /*protected*/
35  class _Spline2D {
36  	/** 
37  	 *  Array representing the relative proportion of the total distance
38  	 *  of each point in the line ( i.e. first point is 0.0, end point is
39  	 *  1.0, a point halfway on line is 0.5 ).
40  	 */
41  	private double[] t;
42  	private Spline splineX;
43  	private Spline splineY;
44  	/**
45  	 * Total length tracing the points on the spline
46  	 */
47  	private double length;
48  
49  	/**
50  	 * Creates a new Spline2D.
51  	 * @param points
52  	 */
53  /*
54  	public _Spline2D(Point2D[] points) {
55  		double[] x = new double[points.length];
56  		double[] y = new double[points.length];
57  
58  		for(int i = 0; i< points.length; i++) {
59  			x[i] = points[i].getX();
60  			y[i] = points[i].getY(); 
61  		}
62  
63  		init(x, y);
64  	}
65  */
66  	/**
67  	 * Creates a new Spline2D.
68  	 * @param x
69  	 * @param y
70  	 */
71  	public _Spline2D(double[] x, double[] y) {
72  		init(x, y);
73  	}
74  
75  	/** Get the x/y coordinates at this offset into the spline.
76  	 * @param t 0 <= t <= 1
77  	 * @return The x/y pair
78  	 */
79  	public int[] getPoint(double t) {
80  		int[] result = new int[2];
81  		result[0] = (int)splineX.getValue(t);
82  		result[1] = (int)splineY.getValue(t);
83  
84  		return result;
85  	}
86  
87  	private void init(double[] x, double[] y) {
88  		if (x.length != y.length) {
89  			throw new IllegalArgumentException("Arrays must have the same length.");
90  		}
91  
92  		if (x.length < 2) {
93  			throw new IllegalArgumentException("Spline edges must have at least two points.");
94  		}
95  
96  		t = new double[x.length];
97  		t[0] = 0.0; // start point is always 0.0
98  
99  		// Calculate the partial proportions of each section between each set
100 		// of points and the total length of sum of all sections
101 		for (int i = 1; i < t.length; i++) {
102 			double lx = x[i] - x[i-1];
103 			double ly = y[i] - y[i-1];
104 			// If either diff is zero there is no point performing the square root
105 			if ( 0.0 == lx ) {
106 				t[i] = Math.abs(ly);
107 			} else if ( 0.0 == ly ) {
108 				t[i] = Math.abs(lx);
109 			} else {
110 				t[i] = Math.sqrt(lx*lx+ly*ly);
111 			}
112 
113 			length += t[i];
114 			t[i] += t[i-1];
115 		}
116 
117 		for(int i = 1; i< (t.length)-1; i++) {
118 			t[i] = t[i] / length;
119 		}
120 
121 		t[(t.length)-1] = 1.0; // end point is always 1.0
122 
123 		splineX = new Spline(t, x);
124 		splineY = new Spline(t, y);
125 	}
126 
127 	/**
128 	 * Used to check the correctness of this spline
129 	 */
130 	/*
131 	public boolean checkValues() {
132 		return (splineX.checkValues() && splineY.checkValues());
133 	}
134 
135 	public double getDx(double t) {
136 		return splineX.getDx(t);
137 	}
138 
139 	public double getDy(double t) {
140 		return splineY.getDx(t);
141 	}
142 
143 	public Spline getSplineX() {
144 		return splineX;
145 	}
146 
147 	public Spline getSplineY() {
148 		return splineY;
149 	}
150 
151 	public double getLength() {
152 		return length;
153 	}
154 	*/
155 }
156 
157 /* This code is PUBLIC DOMAIN */
158 
159 
160 /**
161  * Interpolates given values by B-Splines.
162  * 
163  * @author krueger
164  */
165 class Spline {
166 
167 	private double[] xx;
168 	private double[] yy;
169 
170 	private double[] a;
171 	private double[] b;
172 	private double[] c;
173 	private double[] d;
174 
175 	/** tracks the last index found since that is mostly commonly the next one used */
176 	private int storageIndex = 0;
177 
178 	/**
179 	 * Creates a new Spline.
180 	 * @param xx
181 	 * @param yy
182 	 */
183 	public Spline(double[] xx, double[] yy) {
184 		setValues(xx, yy);
185 	}
186 
187 	/**
188 	 * Set values for this Spline.
189 	 * @param xx
190 	 * @param yy
191 	 */
192 	public void setValues(double[] xx, double[] yy) {
193 		this.xx = xx;
194 		this.yy = yy;
195 		if (xx.length > 1) {
196 			calculateCoefficients();
197 		}
198 	}
199 
200 	/**
201 	 * Returns an interpolated value.
202 	 * @param x
203 	 * @return the interpolated value
204 	 */
205 	public double getValue(double x) {
206 		if (xx.length == 0) {
207 			return Double.NaN;
208 		}
209 
210 		if (xx.length == 1) {
211 			if (xx[0] == x) {
212 				return yy[0];
213 			} else {
214 				return Double.NaN;
215 			}
216 		}
217 
218 		int index = Arrays.binarySearch(xx, x);
219 		if (index > 0) {
220 			return yy[index];
221 		}
222 
223 		index = - (index + 1) - 1;
224 		// TO DO linear interpolation or extrapolation
225 		if (index < 0) {
226 			return yy[0];
227 		}
228 
229 		return a[index]
230 		         + b[index] * (x - xx[index])
231 		         + c[index] * Math.pow(x - xx[index], 2)
232 		         + d[index] * Math.pow(x - xx[index], 3);
233 	}
234 
235 	/**
236 	 * Returns an interpolated value. To be used when a long sequence of values
237 	 * are required in order, but ensure checkValues() is called beforehand to
238 	 * ensure the boundary checks from getValue() are made
239 	 * @param x
240 	 * @return the interpolated value
241 	 */
242 	public double getFastValue(double x) {
243 		// Fast check to see if previous index is still valid
244 		if (storageIndex > -1 && storageIndex < xx.length-1 && x > xx[storageIndex] && x < xx[storageIndex + 1]) {
245 
246 		} else {
247 			int index = Arrays.binarySearch(xx, x);
248 			if (index > 0) {
249 				return yy[index];
250 			}
251 			index = - (index + 1) - 1;
252 			storageIndex = index;
253 		}
254 
255 		// TO DO linear interpolation or extrapolation
256 		if (storageIndex < 0) {
257 			return yy[0];
258 		}
259 		double value = x - xx[storageIndex];
260 		return a[storageIndex]
261 		         + b[storageIndex] * value
262 		         + c[storageIndex] * (value * value)
263 		         + d[storageIndex] * (value * value * value);
264 	}
265 
266 	/**
267 	 * Used to check the correctness of this spline
268 	 */
269 	public boolean checkValues() {
270 		if (xx.length < 2) {
271 			return false;
272 		} else {
273 			return true;
274 		}
275 	}
276 
277 	/**
278 	 * Returns the first derivation at x.
279 	 * @param x
280 	 * @return the first derivation at x
281 	 */
282 	public double getDx(double x) {
283 		if (xx.length == 0 || xx.length == 1) {
284 			return 0;
285 		}
286 
287 		int index = Arrays.binarySearch(xx, x);
288 		if (index < 0) {
289 			index = - (index + 1) - 1;
290 		}
291 
292 		return b[index]
293 		         + 2 * c[index] * (x - xx[index])
294 		         + 3 * d[index] * Math.pow(x - xx[index], 2);
295 	}
296 
297 	/**
298 	 * Calculates the Spline coefficients.
299 	 */
300 	private void calculateCoefficients() {
301 		int N = yy.length;
302 		a = new double[N];
303 		b = new double[N];
304 		c = new double[N];
305 		d = new double[N];
306 
307 		if (N == 2) {
308 			a[0] = yy[0];
309 			b[0] = yy[1] - yy[0];
310 			return;
311 		}
312 
313 		double[] h = new double[N - 1];
314 		for (int i = 0; i < N - 1; i++) {
315 			a[i] = yy[i];
316 			h[i] = xx[i + 1] - xx[i];
317 			// h[i] is used for division later, avoid a NaN
318 			if (h[i] == 0.0) {
319 				h[i] = 0.01;
320 			}
321 		}
322 		a[N - 1] = yy[N - 1];
323 
324 		double[][] A = new double[N - 2][N - 2];
325 		double[] y = new double[N - 2];
326 		for (int i = 0; i < N - 2; i++) {
327 			y[i] =
328 				3
329 				* ((yy[i + 2] - yy[i + 1]) / h[i
330 				                               + 1]
331 				                               - (yy[i + 1] - yy[i]) / h[i]);
332 
333 			A[i][i] = 2 * (h[i] + h[i + 1]);
334 
335 			if (i > 0) {
336 				A[i][i - 1] = h[i];
337 			}
338 
339 			if (i < N - 3) {
340 				A[i][i + 1] = h[i + 1];
341 			}
342 		}
343 		solve(A, y);
344 
345 		for (int i = 0; i < N - 2; i++) {
346 			c[i + 1] = y[i];
347 			b[i] = (a[i + 1] - a[i]) / h[i] - (2 * c[i] + c[i + 1]) / 3 * h[i];
348 			d[i] = (c[i + 1] - c[i]) / (3 * h[i]);
349 		}
350 		b[N - 2] =
351 			(a[N - 1] - a[N - 2]) / h[N
352 			                          - 2]
353 			                          - (2 * c[N - 2] + c[N - 1]) / 3 * h[N
354 			                                                              - 2];
355 		d[N - 2] = (c[N - 1] - c[N - 2]) / (3 * h[N - 2]);
356 	}
357 
358 	/**
359 	 * Solves Ax=b and stores the solution in b.
360 	 */
361 	public void solve(double[][] A, double[] b) {
362 		int n = b.length;
363 		for (int i = 1; i < n; i++) {
364 			A[i][i - 1] = A[i][i - 1] / A[i - 1][i - 1];
365 			A[i][i] = A[i][i] - A[i - 1][i] * A[i][i - 1];
366 			b[i] = b[i] - A[i][i - 1] * b[i - 1];
367 		}
368 
369 		b[n - 1] = b[n - 1] / A[n - 1][n - 1];
370 		for (int i = b.length - 2; i >= 0; i--) {
371 			b[i] = (b[i] - A[i][i + 1] * b[i + 1]) / A[i][i];
372 		}
373 	}
374 }