1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.vectomatic.common.model.geometry;
19
20 import com.google.gwt.user.client.rpc.IsSerializable;
21
22
23
24
25 public class BezierSegment extends Segment implements IsSerializable {
26 public BezierSegment() {
27
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
98
99
100
101
102
103
104 private static class BezierSolver {
105 private static final int MAXDEPTH = 64;
106 private static final float EPSILON = (float)Math.pow(2, -MAXDEPTH -1);
107 private static final int DEGREE = 3;
108 private static final int W_DEGREE = 5;
109 private static float z[][] = {
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
117
118
119
120
121
122
123 public static Point nearestPointOnCurve(Point P, Point[] V) {
124
125 Point[] w = convertToBezierForm(P, V);
126
127
128 float[] t_candidate = new float[W_DEGREE];
129 int n_solutions = findRoots(w, W_DEGREE, t_candidate, 0);
130
131
132 float dist, new_dist;
133 Point v = new Point();
134
135
136 dist = P.subtract(V[0], v).squaredLength();
137 float t = 0.0f;
138
139
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
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
157 return bezier(V, DEGREE, t, null, null);
158 }
159
160
161
162
163
164
165
166
167
168 private static Point[] convertToBezierForm(Point P, Point[] V) {
169 Point[] c = new1DPointArray(DEGREE+1);
170 Point[] d = new1DPointArray(DEGREE);
171 Point[] w = new1DPointArray(W_DEGREE+1);
172
173 float cdTable[][] = new float[3][];
174 for (int i = 0; i < cdTable.length; i++) {
175 cdTable[i] = new float[4];
176 }
177
178
179
180 for (int i = 0; i <= DEGREE; i++) {
181 V[i].subtract(P, c[i]);
182 }
183
184
185 for (int i = 0; i <= DEGREE - 1; i++) {
186 V[i+1].subtract(V[i], d[i]).multiply(3f);
187 }
188
189
190
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
198
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
220
221
222
223
224
225
226
227
228 private static int findRoots(Point[] w, int degree, float[] t, int depth) {
229 Point[] left = new1DPointArray(W_DEGREE+1);
230 Point[] right = new1DPointArray(W_DEGREE+1);
231 int left_count;
232 int right_count;
233 float left_t[] = new float[W_DEGREE+1];
234 float right_t[] = new float[W_DEGREE+1];
235
236 switch (crossingCount(w, degree)) {
237 case 0: {
238 return 0;
239 }
240 case 1: {
241
242
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
256
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
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
271 return (left_count+right_count);
272 }
273
274
275
276
277
278
279
280
281
282 private static int crossingCount(Point[] V, int degree) {
283 int n_crossings = 0;
284 float sign, old_sign;
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
301
302
303
304
305
306 private static boolean controlPolygonFlatEnough(Point[] V, int degree) {
307 float distance[] = new float[degree + 1];
308
309
310
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
316
317
318 float abSquared = (a * a) + (b * b);
319
320 for (int i = 1; i < degree; i++) {
321
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
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
344 float a1 = 0.0f;
345 float b1 = 1.0f;
346 float c1 = 0.0f;
347
348
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
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
369 float left_intercept = Math.min(intercept_1, intercept_2);
370 float right_intercept = Math.max(intercept_1, intercept_2);
371
372
373 float error = 0.5f * (right_intercept-left_intercept);
374 return error < EPSILON;
375 }
376
377
378
379
380
381
382
383
384
385
386
387
388
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
407
408
409
410
411
412
413
414
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
420 for (int j = 0; j <= degree; j++) {
421 V[j].copyTo(Vtemp[0][j]);
422 }
423
424
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 }