View Javadoc

1   /**********************************************
2    * Copyright (C) 2009 Lukas Laag
3    * This file is part of Vectomatic.
4    * 
5    * Vectomatic 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 3 of the License, or
8    * (at your option) any later version.
9    * 
10   * Vectomatic 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 Vectomatic.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.common.model.geometry;
19  
20  import com.google.gwt.user.client.rpc.IsSerializable;
21  
22  /**
23   * Class to represent a Bezier spline segment in Path
24   */
25  public class BezierSegment extends Segment implements IsSerializable {
26  	public BezierSegment() {
27  		// For GWT serialization
28  	}
29  
30  	public BezierSegment(Point[] pts) {
31  		super(pts);
32  		assert(pts.length == 4);
33  	}
34  	
35  	public BezierSegment(BezierSegment segment) {
36  		super(segment);
37  	}
38  	
39  	@Override
40  	public boolean equals(Object obj) {
41  		if (obj instanceof BezierSegment) {
42  			BezierSegment segment = (BezierSegment)obj;
43  			for (int i = 0; i < _pts.length; i++) {
44  				if (!_pts[i].equals(segment._pts[i])) {
45  					return false;
46  				}
47  			}
48  			return true;
49  		}
50  		return false;
51  	}
52  	
53  	@Override
54  	public int hashCode() {
55  		int code = 0;
56  		for (int i = 0; i < _pts.length; i++) {
57  			code += _pts[i].hashCode();
58  		}
59  		return code;
60  	}
61  
62  	public Point getStartControlPoint() {
63  		return _pts[1];
64  	}
65  	
66  	public Point getEndControlPoint() {
67  		return _pts[2];
68  	}
69  	
70  	@Override
71  	public String toString() {
72  		StringBuffer buffer = new StringBuffer();
73  		buffer.append("BezierSegment{");
74  		buffer.append(_pts[0]);
75  		buffer.append(", ");
76  		buffer.append(_pts[1]);
77  		buffer.append(", ");
78  		buffer.append(_pts[2]);
79  		buffer.append(", ");
80  		buffer.append(_pts[3]);
81  		buffer.append("}");
82  		return buffer.toString();
83  	}
84  	
85  	@Override
86  	public void nearestPointOnSegment(Point p, Point dest) {
87  		BezierSolver.nearestPointOnCurve(p, _pts).copyTo(dest);
88  	}
89  
90  	@Override
91  	public Segment clone() {
92  		return new BezierSegment(this);
93  	}
94  
95  
96  	/**
97  	 * Class to determine the point on a Bezier curve
98  	 * nearest an arbitrary point in the plane.
99  	 * This code is a java adaptation of the Graphics Gem II
100 	 * article and code:
101 	 * A Bezier Curve-Based Root-Finder
102 	 * by Philip J. Schneider
103 	 */
104 	private static class BezierSolver {
105 		private static final int MAXDEPTH = 64;	// Maximum depth for recursion
106 		private static final float EPSILON = (float)Math.pow(2, -MAXDEPTH -1);
107 		private static final int DEGREE	= 3;	// Cubic Bezier curve
108 		private static final int W_DEGREE = 5;	// Degree of eqn to find roots of
109 		private static float z[][] = {			// Precomputed "z" for cubics
110 			{1.0f, 0.6f, 0.3f, 0.1f},
111 			{0.4f, 0.6f, 0.6f, 0.4f},
112 			{0.1f, 0.3f, 0.6f, 1.0f},
113 		    };
114 
115 		/**
116 		 * NearestPointOnCurve :
117 		 * Compute the parameter value of the point on a Bezier
118 		 * curve segment closest to some arbtitrary, user-input point.
119 		 * Return the point on the curve at that parameter value.
120 		 * @param P The user-supplied point
121 		 * @param V Control points of cubic Bezier
122 		 */
123 		public static Point nearestPointOnCurve(Point P, Point[] V) {
124 		    // Convert problem to 5th-degree Bezier form
125 		    Point[] w = convertToBezierForm(P, V);
126 
127 		    // Find all possible roots of 5th-degree equation/
128 		    float[] t_candidate = new float[W_DEGREE];	// Possible roots     
129 		    int n_solutions = findRoots(w, W_DEGREE, t_candidate, 0);
130 
131 		    // Compare distances of P to all candidates, and to t=0, and t=1
132 			float dist, new_dist;
133 			Point v = new Point();
134 
135 			// Check distance to beginning of curve, where t = 0
136 			dist = P.subtract(V[0], v).squaredLength();
137 			float t = 0.0f; // Parameter value of closest pt
138 
139 			// Find distances for candidate points
140 			for (int i = 0; i < n_solutions; i++) {
141 				Point p = bezier(V, DEGREE, t_candidate[i], null, null);
142 				new_dist = P.subtract(p, v).squaredLength();
143 				if (new_dist < dist) {
144 					dist = new_dist;
145 					t = t_candidate[i];
146 				}
147 			}
148 
149 			// Finally, look at distance to end point, where t = 1.0
150 			new_dist = P.subtract(V[DEGREE], v).squaredLength();
151 			if (new_dist < dist) {
152 				dist = new_dist;
153 				t = 1.0f;
154 			}
155 		    
156 		    // Return the point on the curve at parameter value t
157 		    return bezier(V, DEGREE, t, null, null);
158 		}
159 
160 		/**
161 		 * ConvertToBezierForm :
162 		 * Given a point and a Bezier curve, generate a 5th-degree
163 		 * Bezier-format equation whose solution finds the point on the
164 		 * curve nearest the user-defined point.
165 		 * @param P The point to find t for
166 		 * @param V The control points
167 		 */
168 		private static Point[] convertToBezierForm(Point P, Point[] V) {
169 			Point[] c = new1DPointArray(DEGREE+1);		// V(i)'s - P
170 			Point[] d = new1DPointArray(DEGREE);		// V(i+1) - V(i)
171 		    Point[] w = new1DPointArray(W_DEGREE+1);	// Ctl pts of 5th-degree curve
172 
173 		    float 	cdTable[][] = new float[3][];		// Dot product of c, d
174 		    for (int i = 0; i < cdTable.length; i++) {
175 		    	cdTable[i] = new float[4];
176 		    }
177 
178 		    // Determine the c's -- these are vectors created by subtracting
179 		    // point P from each of the control points
180 		    for (int i = 0; i <= DEGREE; i++) {
181 		    	V[i].subtract(P, c[i]);
182 		    }
183 		    // Determine the d's -- these are vectors created by subtracting
184 		    // each control point from the next
185 		    for (int i = 0; i <= DEGREE - 1; i++) { 
186 		    	V[i+1].subtract(V[i], d[i]).multiply(3f);
187 		    }
188 
189 		    // Create the c,d table -- this is a table of dot products of the
190 		    // c's and d's
191 		    for (int row = 0; row <= DEGREE - 1; row++) {
192 				for (int column = 0; column <= DEGREE; column++) {
193 			    	cdTable[row][column] = d[row].dotProduct(c[column]);
194 				}
195 		    }
196 
197 		    // Now, apply the z's to the dot products, on the skew diagonal
198 		    // Also, set up the x-values, making these "points"
199 		    for (int i = 0; i <= W_DEGREE; i++) {
200 				w[i].y = 0.0f;
201 				w[i].x = (float)(i) / W_DEGREE;
202 		    }
203 
204 		    int n = DEGREE;
205 		    int m = DEGREE-1;
206 		    for (int k = 0; k <= n + m; k++) {
207 				int lb = Math.max(0, k - m);
208 				int ub = Math.min(k, n);
209 				for (int i = lb; i <= ub; i++) {
210 			    	int j = k - i;
211 			    	w[i+j].y += cdTable[j][i] * z[j][i];
212 				}
213 		    }
214 		    return w;
215 		}
216 
217 
218 		/**
219 		 * FindRoots :
220 		 * Given a 5th-degree equation in Bernstein-Bezier form, find
221 		 * all of the roots in the interval [0, 1].  Return the number
222 		 * of roots found.
223 		 * @param w The control points
224 		 * @param degree The degree of the polynomial
225 		 * @param t RETURN candidate t-values
226 		 * @param depth The depth of the recursion
227 		 */
228 		private static int findRoots(Point[] w, int degree, float[] t, int depth) {  
229 		    Point[] left = new1DPointArray(W_DEGREE+1);	 // New left and right
230 		    Point[] right = new1DPointArray(W_DEGREE+1); // control polygons
231 		    int left_count;		// Solution count from
232 			int right_count;	// children
233 		    float left_t[] = new float[W_DEGREE+1];	 //Solutions from kids
234 		    float right_t[] = new float[W_DEGREE+1];
235 
236 		    switch (crossingCount(w, degree)) {
237 				case 0: { // No solutions here
238 					return 0;
239 				}
240 				case 1: { // Unique solution
241 					// Stop recursion when the tree is deep enough
242 					// if deep enough, return 1 solution at midpoint
243 					if (depth >= MAXDEPTH) {
244 						t[0] = (w[0].x + w[W_DEGREE].x) / 2.0f;
245 						return 1;
246 					}
247 					if (controlPolygonFlatEnough(w, degree)) {
248 						t[0] = computeXIntercept(w, degree);
249 						return 1;
250 					}
251 					break;
252 				}
253 			}
254 
255 		    // Otherwise, solve recursively after
256 		    // subdividing control polygon
257 		    bezier(w, degree, 0.5f, left, right);
258 		    left_count  = findRoots(left,  degree, left_t, depth+1);
259 		    right_count = findRoots(right, degree, right_t, depth+1);
260 
261 
262 		    // Gather solutions together
263 		    for (int i = 0; i < left_count; i++) {
264 		        t[i] = left_t[i];
265 		    }
266 		    for (int i = 0; i < right_count; i++) {
267 		 		t[i+left_count] = right_t[i];
268 		    }
269 
270 		    // Send back total number of solutions
271 		    return (left_count+right_count);
272 		}
273 
274 
275 		/*
276 		 * CrossingCount :
277 		 * Count the number of times a Bezier control polygon 
278 		 * crosses the 0-axis. This number is >= the number of roots.
279 		 * @param V Control pts of Bezier curve
280 		 * @param degree Degreee of Bezier curve
281 		 */
282 		private static int crossingCount(Point[] V, int degree) {
283 		    int 	n_crossings = 0;	/*  Number of zero-crossings	*/
284 		    float	sign, old_sign;		/*  Sign of coefficients	*/
285 
286 		    sign = old_sign = Math.signum(V[0].y);
287 		    for (int i = 1; i <= degree; i++) {
288 				sign =Math.signum(V[i].y);
289 				if (sign != old_sign) {
290 					n_crossings++;
291 				}
292 				old_sign = sign;
293 		    }
294 		    return n_crossings;
295 		}
296 
297 
298 
299 		/**
300 		 * ControlPolygonFlatEnough :
301 		 * Check if the control polygon of a Bezier curve is flat enough
302 		 * for recursive subdivision to bottom out.
303 		 * @param V Control points
304 		 * @param degree Degree of polynomial
305 		 */
306 		private static boolean controlPolygonFlatEnough(Point[] V, int degree) {
307 		    float 	distance[] = new float[degree + 1];		/* Distances from pts to line	*/
308 
309 			// Derive the implicit equation for line connecting first' / and last
310 			// control points
311 			float a = V[0].y - V[degree].y;
312 			float b = V[degree].x - V[0].x;
313 			float c = V[0].x * V[degree].y - V[degree].x * V[0].y;
314 
315 		    // Find the  perpendicular distance
316 		    // from each interior control point to
317 		    // line connecting V[0] and V[degree]
318 			float abSquared = (a * a) + (b * b);
319 
320 			for (int i = 1; i < degree; i++) {
321 				// Compute distance from each of the points to that line
322 				distance[i] = a * V[i].x + b * V[i].y + c;
323 				if (distance[i] > 0.0) {
324 					distance[i] = (distance[i] * distance[i]) / abSquared;
325 				}
326 				if (distance[i] < 0.0) {
327 					distance[i] = -((distance[i] * distance[i]) / abSquared);
328 				}
329 			}
330 
331 		    // Find the largest distance
332 			float max_distance_above = 0.0f;
333 			float max_distance_below = 0.0f;
334 		    for (int i = 1; i < degree; i++) {
335 				if (distance[i] < 0.0) {
336 			    	max_distance_below = Math.min(max_distance_below, distance[i]);
337 				}
338 				if (distance[i] > 0.0) {
339 			    	max_distance_above = Math.max(max_distance_above, distance[i]);
340 				}
341 		    }
342 
343 			// Implicit equation for zero line
344 			float a1 = 0.0f;
345 			float b1 = 1.0f;
346 			float c1 = 0.0f;
347 
348 			// Implicit equation for "above" line
349 			float a2 = a;
350 			float b2 = b;
351 			float c2 = c + max_distance_above;
352 
353 			float det = a1 * b2 - a2 * b1;
354 			float dInv = 1.0f/det;
355 			
356 			float intercept_1 = (b1 * c2 - b2 * c1) * dInv;
357 
358 			// Implicit equation for "below" line
359 			a2 = a;
360 			b2 = b;
361 			c2 = c + max_distance_below;
362 			
363 			det = a1 * b2 - a2 * b1;
364 			dInv = 1.0f/det;
365 			
366 			float intercept_2 = (b1 * c2 - b2 * c1) * dInv;
367 
368 		    // Compute intercepts of bounding box
369 		    float left_intercept = Math.min(intercept_1, intercept_2);
370 		    float right_intercept = Math.max(intercept_1, intercept_2);
371 
372 		    // Compute precision of root
373 		    float error = 0.5f * (right_intercept-left_intercept);    
374 		    return error < EPSILON;
375 		}
376 
377 
378 
379 		/**
380 		 *  ComputeXIntercept :
381 		 *	Compute intersection of chord from first control point to last
382 		 *  	with 0-axis.
383 		 *  @param V Control points
384 		 *  @param degree Degree of curve
385 		 * 
386 		 */
387 		/* NOTE: "T" and "Y" do not have to be computed, and there are many useless
388 		 * operations in the following (e.g. "0.0 - 0.0").
389 		 */
390 		private static float computeXIntercept(Point[] V, int degree) {
391 		    float XLK = 1.0f - 0.0f;
392 		    float YLK = 0.0f - 0.0f;
393 		    float XNM = V[degree].x - V[0].x;
394 		    float YNM = V[degree].y - V[0].y;
395 		    float XMK = V[0].x - 0.0f;
396 		    float YMK = V[0].y - 0.0f;
397 		    float det = XNM*YLK - YNM*XLK;
398 		    float detInv = 1.0f/det;
399 		    float S = (XNM*YMK - YNM*XMK) * detInv;
400 		    float X = 0.0f + XLK * S;
401 		    return X;
402 		}
403 
404 
405 		/**
406 		 * Bezier : 
407 		 * Evaluate a Bezier curve at a particular parameter value
408 		 * Fill in control points for resulting sub-curves if "Left" and
409 		 * "Right" are non-null.
410 		 * @param V Control pts
411 		 * @param degree Degree of bezier curve
412 		 * @param t Parameter value
413 		 * @param Left RETURN left half ctl pts
414 		 * @param Right RETURN right half ctl pts
415 		 */
416 		private static Point bezier(Point[] V, int degree, float t, Point[] Left, Point[] Right) {
417 		    Point[][] Vtemp = new2DPointArray(W_DEGREE+1, W_DEGREE+1);
418 
419 		    // Copy control points
420 		    for (int j = 0; j <= degree; j++) {
421 				 V[j].copyTo(Vtemp[0][j]);
422 		    }
423 
424 		    // Triangle computation
425 		    for (int i = 1; i <= degree; i++) {	
426 				for (int j =0 ; j <= degree - i; j++) {
427 			    	Vtemp[i][j].x =
428 			      		(1.0f - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x;
429 			    	Vtemp[i][j].y =
430 			      		(1.0f - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y;
431 				}
432 		    }
433 		    
434 		    if (Left != null) {
435 				for (int j = 0; j <= degree; j++) {
436 			    	Vtemp[j][0].copyTo(Left[j]);
437 				}
438 		    }
439 		    if (Right != null) {
440 				for (int j = 0; j <= degree; j++) {
441 			    	Vtemp[degree-j][j].copyTo(Right[j]);
442 				}
443 		    }
444 
445 		    return (Vtemp[degree][0]);
446 		}
447 		
448 		private static Point[] new1DPointArray(int d1) {
449 			Point[] array = new Point[d1];
450 			for (int i = 0; i < d1; i++) {
451 				array[i] = new Point();
452 			}
453 			return array;
454 		}
455 		private static Point[][] new2DPointArray(int d1, int d2) {
456 			Point[][] array = new Point[d1][];
457 			for (int i = 0; i < d1; i++) {
458 				array[i] = new Point[d2];
459 				for (int j = 0; j < d2; j++) {
460 					array[i][j] = new Point();
461 				}
462 			}
463 			return array;
464 		}
465 	}
466 
467 }