Added
Link Here
|
1 |
/******************************************************************************* |
2 |
* Copyright (c) 2011 itemis AG and others. |
3 |
* All rights reserved. This program and the accompanying materials |
4 |
* are made available under the terms of the Eclipse Public License v1.0 |
5 |
* which accompanies this distribution, and is available at |
6 |
* http://www.eclipse.org/legal/epl-v10.html |
7 |
* |
8 |
* Contributors: |
9 |
* Matthias Wienand (itemis AG) - initial API and implementation |
10 |
* |
11 |
*******************************************************************************/ |
12 |
package org.eclipse.gef4.geometry.utils; |
13 |
|
14 |
import java.util.HashSet; |
15 |
import java.util.Set; |
16 |
|
17 |
import org.eclipse.gef4.geometry.Point; |
18 |
import org.eclipse.gef4.geometry.euclidean.Straight; |
19 |
import org.eclipse.gef4.geometry.shapes.CubicCurve; |
20 |
import org.eclipse.gef4.geometry.shapes.Line; |
21 |
import org.eclipse.gef4.geometry.shapes.QuadraticCurve; |
22 |
import org.eclipse.gef4.geometry.shapes.Rectangle; |
23 |
|
24 |
/** |
25 |
* The {@link CurveUtils} class provides functionality that can be used for |
26 |
* linear curves ({@link Line}, {@link Straight}), as well as quadratic and |
27 |
* cubic Bezier curves. |
28 |
* |
29 |
* @author wienand |
30 |
* |
31 |
*/ |
32 |
public class CurveUtils { |
33 |
|
34 |
/** |
35 |
* The Vector3D class implements a three dimensional vector (components x, |
36 |
* y, z) with its standard operations: addition and multiplication (scalar, |
37 |
* dot-product, cross-product). |
38 |
* |
39 |
* It is used to represent planar lines and planar points which are |
40 |
* represented by three dimensional planes and three dimensional lines |
41 |
* through the origin, respectively. |
42 |
* |
43 |
* @author wienand |
44 |
*/ |
45 |
private static final class Vector3D { |
46 |
public double x, y, z; |
47 |
|
48 |
/** |
49 |
* Constructs a new {@link Vector3D} from the given {@link Point}, |
50 |
* setting z to 1. |
51 |
* |
52 |
* @param p |
53 |
*/ |
54 |
public Vector3D(Point p) { |
55 |
this(p.x, p.y, 1); |
56 |
} |
57 |
|
58 |
/** |
59 |
* Constructs a new {@link Vector3D} object with the given component |
60 |
* values. |
61 |
* |
62 |
* @param px |
63 |
* @param py |
64 |
* @param pz |
65 |
*/ |
66 |
public Vector3D(double px, double py, double pz) { |
67 |
x = px; |
68 |
y = py; |
69 |
z = pz; |
70 |
} |
71 |
|
72 |
/** |
73 |
* Returns a copy of this {@link Vector3D}. |
74 |
* |
75 |
* @return a copy of this {@link Vector3D} |
76 |
*/ |
77 |
public Vector3D getCopy() { |
78 |
return new Vector3D(x, y, z); |
79 |
} |
80 |
|
81 |
@Override |
82 |
public boolean equals(Object other) { |
83 |
if (other instanceof Vector3D) { |
84 |
Vector3D o = (Vector3D) other; |
85 |
Point tmp = this.toPoint(); |
86 |
if (tmp == null) { |
87 |
return o.toPoint() == null; |
88 |
} |
89 |
return tmp.equals(o.toPoint()); |
90 |
} |
91 |
return false; |
92 |
} |
93 |
|
94 |
/** |
95 |
* Returns a new {@link Vector3D} object with its components set to the |
96 |
* sum of the individual x, y and z components of this {@link Vector3D} |
97 |
* and the given other {@link Vector3D}. |
98 |
* |
99 |
* @param other |
100 |
* @return a new {@link Vector3D} object representing the sum of this |
101 |
* {@link Vector3D} and the given other {@link Vector3D} |
102 |
*/ |
103 |
public Vector3D getAdded(Vector3D other) { |
104 |
return new Vector3D(this.x + other.x, this.y + other.y, this.z |
105 |
+ other.z); |
106 |
} |
107 |
|
108 |
/** |
109 |
* Returns a new {@link Vector3D} object with its components set to the |
110 |
* difference of the individual x, y and z components of this |
111 |
* {@link Vector3D} and the given other {@link Vector3D}. |
112 |
* |
113 |
* @param other |
114 |
* @return a new {@link Vector3D} object representing the difference of |
115 |
* this {@link Vector3D} and the given other {@link Vector3D} |
116 |
*/ |
117 |
public Vector3D getSubtracted(Vector3D other) { |
118 |
return new Vector3D(this.x - other.x, this.y - other.y, this.z |
119 |
- other.z); |
120 |
} |
121 |
|
122 |
/** |
123 |
* Returns a new {@link Vector3D} object with its components set to the |
124 |
* x, y and z components of this {@link Vector3D} scaled by the given |
125 |
* factor. |
126 |
* |
127 |
* @param f |
128 |
* The scaling factor. |
129 |
* @return a new {@link Vector3D} object with its components set to the |
130 |
* x, y and z components of this {@link Vector3D} scaled by the |
131 |
* given factor |
132 |
*/ |
133 |
public Vector3D getScaled(double f) { |
134 |
return new Vector3D(x * f, y * f, z * f); |
135 |
} |
136 |
|
137 |
/** |
138 |
* Returns a new {@link Vector3D} object with its components set to the |
139 |
* given ratio between this {@link Vector3D} and the given other |
140 |
* {@link Vector3D}. |
141 |
* |
142 |
* @param other |
143 |
* The other {@link Vector3D}. |
144 |
* @param t |
145 |
* The ratio. |
146 |
* @return a new {@link Vector3D} object with its components set to the |
147 |
* given ratio between this {@link Vector3D} and the given other |
148 |
* {@link Vector3D} |
149 |
*/ |
150 |
public Vector3D getRatio(Vector3D other, double t) { |
151 |
return getAdded(other.getSubtracted(this).getScaled(t)); |
152 |
} |
153 |
|
154 |
/** |
155 |
* Returns a new {@link Vector3D} object that has the same direction as |
156 |
* this {@link Vector3D} but the x- and y-components are normalized so |
157 |
* that <code>x*x + y*y = 1</code>. |
158 |
* |
159 |
* @return a new {@link Vector3D} object with x- and y-components |
160 |
* normalized to fulfill x*x + y*y = 1. |
161 |
*/ |
162 |
public Vector3D getLineNormalized() { |
163 |
double f = Math.sqrt(x * x + y * y); |
164 |
if (f == 0) { |
165 |
return null; |
166 |
} |
167 |
return new Vector3D(x / f, y / f, z / f); |
168 |
} |
169 |
|
170 |
/** |
171 |
* Returns a new {@link Vector3D} object that is the cross product of |
172 |
* this and the given other {@link Vector3D}. |
173 |
* |
174 |
* @param other |
175 |
* @return a new {@link Vector3D} object that is the cross product of |
176 |
* this and the given other {@link Vector3D} |
177 |
*/ |
178 |
public Vector3D getCrossed(Vector3D other) { |
179 |
return new Vector3D(this.y * other.z - this.z * other.y, this.z |
180 |
* other.x - this.x * other.z, this.x * other.y - this.y |
181 |
* other.x); |
182 |
} |
183 |
|
184 |
/** |
185 |
* Returns the dot-product of this and the given other {@link Vector3D}. |
186 |
* |
187 |
* @param other |
188 |
* @return the dot-product of this and the given other {@link Vector3D} |
189 |
*/ |
190 |
public double getDot(Vector3D other) { |
191 |
return this.x * other.x + this.y * other.y + this.z * other.z; |
192 |
} |
193 |
|
194 |
/** |
195 |
* Returns a new {@link Point} object that is represented by this |
196 |
* {@link Vector3D}. |
197 |
* |
198 |
* @return a new {@link Point} object that is represented by this |
199 |
* {@link Vector3D} |
200 |
*/ |
201 |
public Point toPoint() { |
202 |
if (this.z == 0) { |
203 |
return null; |
204 |
} |
205 |
return new Point(this.x / this.z, this.y / this.z); |
206 |
} |
207 |
|
208 |
public String toString() { |
209 |
return "Vector3D (" + x + ", " + y + ", " + z + ")"; |
210 |
} |
211 |
|
212 |
@Override |
213 |
public int hashCode() { |
214 |
// cannot generate a good hash-code because of the imprecise |
215 |
// comparisons |
216 |
return 0; |
217 |
} |
218 |
} |
219 |
|
220 |
/** |
221 |
* The {@link BezierCurve} provides a common representation for arbitrary |
222 |
* Bezier curves. |
223 |
* |
224 |
* It can evaluate points on the curve, check points for containment and |
225 |
* compute intersection points for one curve with another. |
226 |
* |
227 |
* It uses homogeneous coordinates (represented by {@link Vector3D} objects) |
228 |
* to represent planar lines and points and to compute line/line |
229 |
* intersections and point/line distances. |
230 |
* |
231 |
* @author wienand |
232 |
*/ |
233 |
private static final class BezierCurve { |
234 |
|
235 |
private static final double UNRECOGNIZABLE_PRECISION_FRACTION = PrecisionUtils |
236 |
.calculateFraction(0) / 10; |
237 |
|
238 |
private static final double END_POINTS_CHECK_AREA = 1; |
239 |
|
240 |
private Vector3D[] points; |
241 |
|
242 |
/** |
243 |
* Constructs a new {@link BezierCurve} object from the given control |
244 |
* points. |
245 |
* |
246 |
* @param controlPoints |
247 |
*/ |
248 |
public BezierCurve(Point... controlPoints) { |
249 |
points = new Vector3D[controlPoints.length]; |
250 |
for (int i = 0; i < points.length; i++) { |
251 |
points[i] = new Vector3D(controlPoints[i].x, |
252 |
controlPoints[i].y, 1); |
253 |
} |
254 |
} |
255 |
|
256 |
/** |
257 |
* Constructs a new {@link BezierCurve} object from the given control |
258 |
* points. |
259 |
* |
260 |
* Note that a Point(2, 3) is represented by a Vector3D(2, 3, 1). So for |
261 |
* a Point(x, y) the corresponding vector is Vector(x, y, 1). |
262 |
* |
263 |
* @param controlPoints |
264 |
*/ |
265 |
public BezierCurve(Vector3D... controlPoints) { |
266 |
points = new Vector3D[controlPoints.length]; |
267 |
for (int i = 0; i < points.length; i++) { |
268 |
points[i] = controlPoints[i].getCopy(); |
269 |
} |
270 |
} |
271 |
|
272 |
/** |
273 |
* Constructs a new {@link BezierCurve} from the given |
274 |
* {@link QuadraticCurve}. |
275 |
* |
276 |
* @param c |
277 |
*/ |
278 |
public BezierCurve(QuadraticCurve c) { |
279 |
this(c.getP1(), c.getCtrl(), c.getP2()); |
280 |
} |
281 |
|
282 |
/** |
283 |
* Constructs a new {@link BezierCurve} from the given |
284 |
* {@link CubicCurve}. |
285 |
* |
286 |
* @param c |
287 |
*/ |
288 |
public BezierCurve(CubicCurve c) { |
289 |
this(c.getP1(), c.getCtrl1(), c.getCtrl2(), c.getP2()); |
290 |
} |
291 |
|
292 |
/** |
293 |
* Returns a copy of this {@link BezierCurve}'s points. |
294 |
* |
295 |
* @return a copy of this {@link BezierCurve}'s points |
296 |
*/ |
297 |
private Vector3D[] getPointsCopy() { |
298 |
Vector3D[] copy = new Vector3D[points.length]; |
299 |
for (int i = 0; i < points.length; i++) { |
300 |
copy[i] = points[i].getCopy(); |
301 |
} |
302 |
return copy; |
303 |
} |
304 |
|
305 |
/** |
306 |
* Constructs the explicit Bézier curve for this curve's x-components. |
307 |
* |
308 |
* @return the explicit Bézier curve for this curve's x-components |
309 |
*/ |
310 |
public BezierCurve getExplicitX() { |
311 |
Vector3D[] explicit = new Vector3D[points.length]; |
312 |
|
313 |
for (int i = 0; i < points.length; i++) { |
314 |
explicit[i] = new Vector3D((double) i |
315 |
/ ((double) points.length - 1d), points[i].toPoint().x, |
316 |
1); |
317 |
} |
318 |
|
319 |
return new BezierCurve(explicit); |
320 |
} |
321 |
|
322 |
/** |
323 |
* Constructs the explicit Bézier curve for this curve's y-components. |
324 |
* |
325 |
* @return the explicit Bézier curve for this curve's y-components |
326 |
*/ |
327 |
public BezierCurve getExplicitY() { |
328 |
Vector3D[] explicit = new Vector3D[points.length]; |
329 |
|
330 |
for (int i = 0; i < points.length; i++) { |
331 |
explicit[i] = new Vector3D((double) i |
332 |
/ ((double) points.length - 1d), points[i].toPoint().y, |
333 |
1); |
334 |
} |
335 |
|
336 |
return new BezierCurve(explicit); |
337 |
} |
338 |
|
339 |
/** |
340 |
* Checks if all y-components of this {@link BezierCurve}'s points have |
341 |
* the same sign. |
342 |
* |
343 |
* Returns true if either all y-components are positive or all |
344 |
* y-components are negative. |
345 |
* |
346 |
* Returns false, otherwise. |
347 |
* |
348 |
* @param c |
349 |
* @return true if all y-components are either positive or negative, |
350 |
* otherwise false |
351 |
*/ |
352 |
private static boolean sameSign(BezierCurve c) { |
353 |
double sign = c.points[0].toPoint().y; |
354 |
if (sign == 0) { |
355 |
return false; |
356 |
} |
357 |
for (int i = 1; i < c.points.length; i++) { |
358 |
if (sign < 0) { |
359 |
if (c.points[i].toPoint().y >= 0) { |
360 |
return false; |
361 |
} |
362 |
} else if (sign > 0) { |
363 |
if (c.points[i].toPoint().y <= 0) { |
364 |
return false; |
365 |
} |
366 |
} |
367 |
} |
368 |
return true; |
369 |
} |
370 |
|
371 |
/** |
372 |
* Used to store parameter values in a HashSet with an imprecise |
373 |
* equals() operation. |
374 |
* |
375 |
* @author wienand |
376 |
*/ |
377 |
private static final class ImpreciseDouble { |
378 |
private double value; |
379 |
private int shift; |
380 |
|
381 |
public ImpreciseDouble(double a) { |
382 |
value = a; |
383 |
shift = 1; |
384 |
} |
385 |
|
386 |
/** |
387 |
* Returns the double value represented by this |
388 |
* {@link ImpreciseDouble}. |
389 |
* |
390 |
* @return the double value represented by this |
391 |
* {@link ImpreciseDouble} |
392 |
*/ |
393 |
public double getValue() { |
394 |
return value; |
395 |
} |
396 |
|
397 |
@Override |
398 |
public boolean equals(Object obj) { |
399 |
if (obj instanceof ImpreciseDouble) { |
400 |
ImpreciseDouble o = (ImpreciseDouble) obj; |
401 |
return PrecisionUtils.equal(value, o.value, shift); |
402 |
} |
403 |
return false; |
404 |
} |
405 |
} |
406 |
|
407 |
/** |
408 |
* Calculates the roots of the given explicit {@link BezierCurve} on the |
409 |
* interval [a;b]. |
410 |
* |
411 |
* You can get an explicit {@link BezierCurve} from an arbitrary |
412 |
* {@link BezierCurve} for either its x- or y-components using the |
413 |
* {@link BezierCurve#getExplicitX()} or |
414 |
* {@link BezierCurve#getExplicitY()} methods, respectively. |
415 |
* |
416 |
* @param c |
417 |
* @param a |
418 |
* start of the parameter interval |
419 |
* @param b |
420 |
* end of the parameter interval |
421 |
* @return the roots of the given explicit {@link BezierCurve} on the |
422 |
* interval [a;b] |
423 |
*/ |
424 |
private static HashSet<ImpreciseDouble> getRoots(BezierCurve c, |
425 |
double a, double b) { |
426 |
BezierCurve clipped = c.getClipped(a, b); |
427 |
|
428 |
if (sameSign(clipped)) { |
429 |
return new HashSet<ImpreciseDouble>(); |
430 |
} |
431 |
|
432 |
if (PrecisionUtils.equal(a, b, +2)) { |
433 |
HashSet<ImpreciseDouble> root = new HashSet<ImpreciseDouble>(); |
434 |
root.add(new ImpreciseDouble((a + b) / 2)); |
435 |
return root; |
436 |
} |
437 |
|
438 |
HashSet<ImpreciseDouble> left = getRoots(c, a, (a + b) / 2); |
439 |
HashSet<ImpreciseDouble> right = getRoots(c, (a + b) / 2, b); |
440 |
|
441 |
left.addAll(right); |
442 |
|
443 |
return left; |
444 |
} |
445 |
|
446 |
/** |
447 |
* Calculates the roots of the given {@link BezierCurve} which is |
448 |
* expected to be explicit. |
449 |
* |
450 |
* You can get an explicit {@link BezierCurve} from an arbitrary |
451 |
* {@link BezierCurve} for either its x- or y-components using the |
452 |
* {@link BezierCurve#getExplicitX()} or |
453 |
* {@link BezierCurve#getExplicitY()} methods, respectively. |
454 |
* |
455 |
* @param c |
456 |
* @return the roots of the given explicit {@link BezierCurve} |
457 |
*/ |
458 |
private static double[] getRoots(BezierCurve c) { |
459 |
// TODO: check that the given BezierCurve is explicit |
460 |
HashSet<ImpreciseDouble> roots = getRoots(c, 0, 1); |
461 |
ImpreciseDouble[] rootsFuzzyDouble = roots |
462 |
.toArray(new ImpreciseDouble[] {}); |
463 |
double[] rootsDouble = new double[rootsFuzzyDouble.length]; |
464 |
for (int i = 0; i < rootsDouble.length; i++) { |
465 |
rootsDouble[i] = rootsFuzzyDouble[i].getValue(); |
466 |
} |
467 |
return rootsDouble; |
468 |
} |
469 |
|
470 |
/** |
471 |
* Computes all real roots of this {@link BezierCurve}'s x(t) function. |
472 |
* |
473 |
* @return all real roots of this {@link BezierCurve}'s x(t) function |
474 |
*/ |
475 |
public double[] getRootsX() { |
476 |
return getRoots(getExplicitX()); |
477 |
} |
478 |
|
479 |
/** |
480 |
* Computes all real roots of this {@link BezierCurve}'s y(t) function. |
481 |
* |
482 |
* @return all real roots of this {@link BezierCurve}'s y(t) function |
483 |
*/ |
484 |
public double[] getRootsY() { |
485 |
return getRoots(getExplicitY()); |
486 |
} |
487 |
|
488 |
/** |
489 |
* Computes the real planar {@link Point}s for this {@link BezierCurve}. |
490 |
* |
491 |
* @return the real planar {@link Point}s for this {@link BezierCurve} |
492 |
*/ |
493 |
public Point[] getRealPoints() { |
494 |
Point[] realPoints = new Point[points.length]; |
495 |
for (int i = 0; i < points.length; i++) { |
496 |
realPoints[i] = points[i].toPoint(); |
497 |
} |
498 |
return realPoints; |
499 |
} |
500 |
|
501 |
/** |
502 |
* Returns the {@link Point} at the given parameter value t. |
503 |
* |
504 |
* @param t |
505 |
* Parameter value |
506 |
* @return {@link Point} at parameter value t |
507 |
*/ |
508 |
public Vector3D get(double t) { |
509 |
if (t < 0 || t > 1) { |
510 |
throw new IllegalArgumentException("t out of range"); |
511 |
} |
512 |
|
513 |
// using horner's scheme: |
514 |
int n = points.length; |
515 |
if (n < 1) { |
516 |
return null; |
517 |
} |
518 |
|
519 |
double bn = 1, tn = 1, d = 1d - t; |
520 |
Vector3D pn = points[0].getScaled(bn * tn); |
521 |
for (int i = 1; i < n; i++) { |
522 |
bn = bn * (n - i) / i; |
523 |
tn = tn * t; |
524 |
pn = pn.getScaled(d).getAdded(points[i].getScaled(bn * tn)); |
525 |
} |
526 |
|
527 |
return pn; |
528 |
} |
529 |
|
530 |
/** |
531 |
* Creates a new {@link BezierCurve} with all points translated by the |
532 |
* given {@link Point}. |
533 |
* |
534 |
* @param p |
535 |
* @return a new {@link BezierCurve} with all points translated by the |
536 |
* given {@link Point} |
537 |
*/ |
538 |
public BezierCurve getTranslated(Point p) { |
539 |
Point[] translated = new Point[points.length]; |
540 |
|
541 |
for (int i = 0; i < translated.length; i++) { |
542 |
translated[i] = points[i].toPoint().getTranslated(p); |
543 |
} |
544 |
|
545 |
return new BezierCurve(translated); |
546 |
} |
547 |
|
548 |
/** |
549 |
* Returns true if the given {@link Point} lies on this |
550 |
* {@link BezierCurve}. Returns false, otherwise. |
551 |
* |
552 |
* @param p |
553 |
* the {@link Point} to test for containment |
554 |
* @return true if the {@link Point} is contained, false otherwise |
555 |
*/ |
556 |
public boolean contains(Point p) { |
557 |
if (p == null) { |
558 |
return false; |
559 |
} |
560 |
|
561 |
BezierCurve test = this.getTranslated(p.getNegated()); |
562 |
double[] xts = test.getRootsX(); |
563 |
double[] yts = test.getRootsY(); |
564 |
|
565 |
for (double xt : xts) { |
566 |
for (double yt : yts) { |
567 |
if (PrecisionUtils.equal(xt, yt)) { |
568 |
return true; |
569 |
} else { |
570 |
Point qx = get(xt).toPoint(); |
571 |
Point qy = get(yt).toPoint(); |
572 |
// qx != null && qy != null && |
573 |
if (qx.equals(qy)) { |
574 |
return true; |
575 |
} |
576 |
} |
577 |
} |
578 |
} |
579 |
return false; |
580 |
} |
581 |
|
582 |
/** |
583 |
* Subdivides this {@link BezierCurve} at the given parameter value t |
584 |
* into two new {@link BezierCurve}. The left-of t and the right-of t |
585 |
* {@link BezierCurve} objects. |
586 |
* |
587 |
* @param t |
588 |
* Parameter value |
589 |
* @return The left-of t and right-of t {@link BezierCurve} objects |
590 |
*/ |
591 |
public BezierCurve[] split(double t) { |
592 |
Vector3D[] leftPoints = new Vector3D[points.length]; |
593 |
Vector3D[] rightPoints = new Vector3D[points.length]; |
594 |
|
595 |
Vector3D[] ratioPoints = getPointsCopy(); |
596 |
|
597 |
for (int i = 0; i < points.length; i++) { |
598 |
leftPoints[i] = ratioPoints[0]; |
599 |
rightPoints[points.length - 1 - i] = ratioPoints[points.length |
600 |
- 1 - i]; |
601 |
|
602 |
for (int j = 0; j < points.length - i - 1; j++) { |
603 |
ratioPoints[j] = ratioPoints[j].getRatio( |
604 |
ratioPoints[j + 1], t); |
605 |
} |
606 |
} |
607 |
|
608 |
return new BezierCurve[] { new BezierCurve(leftPoints), |
609 |
new BezierCurve(rightPoints) }; |
610 |
} |
611 |
|
612 |
/** |
613 |
* Returns a new {@link BezierCurve} object representing this bezier |
614 |
* curve on the interval [s;e]. |
615 |
* |
616 |
* @param s |
617 |
* @param e |
618 |
* @return a new {@link BezierCurve} object representing this bezier |
619 |
* curve on the interval [s;e] |
620 |
*/ |
621 |
public BezierCurve getClipped(double s, double e) { |
622 |
BezierCurve right = split(s)[1]; |
623 |
double rightT2 = (e - s) / (1 - s); |
624 |
return right.split(rightT2)[0]; |
625 |
} |
626 |
|
627 |
/** |
628 |
* Checks if the parameters are considered equal on both curves and adds |
629 |
* the point of intersection of the mid lines of the curves. |
630 |
* |
631 |
* @param p |
632 |
* @param q |
633 |
* @param intersections |
634 |
* @return true if the parameters are considered equal and false |
635 |
* otherwise |
636 |
*/ |
637 |
private static Vector3D parameterConvergence(BezierCurve p, double ps, |
638 |
double pe, BezierCurve q, double qs, double qe) { |
639 |
// localEndPointsCheck(); |
640 |
if (PrecisionUtils.equal(ps, pe, +2)) { |
641 |
Vector3D poi = p.get((ps + pe) / 2); |
642 |
return poi; |
643 |
} |
644 |
if (PrecisionUtils.equal(qs, qe, +2)) { |
645 |
Vector3D poi = q.get((qs + qe) / 2); |
646 |
return poi; |
647 |
} |
648 |
return null; |
649 |
} |
650 |
|
651 |
/** |
652 |
* Returns the bounds of the control polygon of this {@link BezierCurve} |
653 |
* . |
654 |
* |
655 |
* @return a {@link Rectangle} representing the bounds of the control |
656 |
* polygon of this {@link BezierCurve} |
657 |
*/ |
658 |
public Rectangle getControlBounds() { |
659 |
Point[] realPoints = getRealPoints(); |
660 |
|
661 |
double xmin = realPoints[0].x, xmax = realPoints[0].x, ymin = realPoints[0].y, ymax = realPoints[0].y; |
662 |
|
663 |
for (int i = 1; i < realPoints.length; i++) { |
664 |
if (realPoints[i].x < xmin) { |
665 |
xmin = realPoints[i].x; |
666 |
} else if (realPoints[i].x > xmax) { |
667 |
xmax = realPoints[i].x; |
668 |
} |
669 |
|
670 |
if (realPoints[i].y < ymin) { |
671 |
ymin = realPoints[i].y; |
672 |
} else if (realPoints[i].y > ymax) { |
673 |
ymax = realPoints[i].y; |
674 |
} |
675 |
} |
676 |
|
677 |
return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); |
678 |
} |
679 |
|
680 |
/** |
681 |
* Generates the difference points of this {@link BezierCurve} to the |
682 |
* given line. |
683 |
* |
684 |
* The difference points are the control points of a Bezier curve that |
685 |
* yields the signed difference of the point on this curve at a |
686 |
* determinate parameter value to the given line. |
687 |
* |
688 |
* @param line |
689 |
* @return the difference curve's control points |
690 |
*/ |
691 |
private Vector3D[] genDifferencePoints(Vector3D line) { |
692 |
Vector3D[] D = new Vector3D[points.length]; |
693 |
for (int i = 0; i < points.length; i++) { |
694 |
double y = line.getDot(points[i]); |
695 |
D[i] = new Vector3D( |
696 |
(double) (i) / (double) (points.length - 1), y, 1); |
697 |
} |
698 |
return D; |
699 |
} |
700 |
|
701 |
public Set<Vector3D> getIntersections(BezierCurve other) { |
702 |
// end point intersections |
703 |
Set<Vector3D> endPointIntersections = getEndPointIntersections( |
704 |
this, other); |
705 |
|
706 |
// TODO: tangential intersections |
707 |
|
708 |
// simple intersections |
709 |
// TODO: recursion => iteration |
710 |
Set<Vector3D> intersections = getIntersections( |
711 |
endPointIntersections, this, 0, 1, other, 0, 1); |
712 |
|
713 |
intersections.addAll(endPointIntersections); |
714 |
return intersections; |
715 |
} |
716 |
|
717 |
private Set<Vector3D> getEndPointIntersections(BezierCurve p, |
718 |
BezierCurve q) { |
719 |
Set<Vector3D> intersections = new HashSet<Vector3D>(); |
720 |
for (int i : new int[] { 0, p.points.length - 1 }) { |
721 |
if (q.contains(p.points[i].toPoint())) { |
722 |
intersections.add(p.points[i]); |
723 |
} |
724 |
} |
725 |
for (int i : new int[] { 0, q.points.length - 1 }) { |
726 |
if (p.contains(q.points[i].toPoint())) { |
727 |
intersections.add(q.points[i]); |
728 |
} |
729 |
} |
730 |
return intersections; |
731 |
} |
732 |
|
733 |
private static class FatLine { |
734 |
public Vector3D line; |
735 |
public double dmin, dmax; |
736 |
|
737 |
private FatLine() { |
738 |
line = new Vector3D(0, 0, 0); |
739 |
dmin = dmax = 0; |
740 |
} |
741 |
|
742 |
public static FatLine from(BezierCurve c, boolean ortho) { |
743 |
FatLine L = new FatLine(); |
744 |
L.dmin = L.dmax = 0; |
745 |
|
746 |
L.line = c.points[0].getCrossed(c.points[c.points.length - 1]); |
747 |
|
748 |
if (ortho) { |
749 |
L.line = c.points[0].getCrossed(c.points[0] |
750 |
.getAdded(new Vector3D(L.line.x, L.line.y, 0))); |
751 |
} |
752 |
|
753 |
L.line = L.line.getLineNormalized(); |
754 |
|
755 |
if (L.line == null) { |
756 |
return null; |
757 |
} |
758 |
|
759 |
for (int i = 0; i < c.points.length; i++) { |
760 |
double d = L.line.getDot(c.points[i]); |
761 |
if (d < L.dmin) |
762 |
L.dmin = d; |
763 |
else if (d > L.dmax) |
764 |
L.dmax = d; |
765 |
} |
766 |
|
767 |
return L; |
768 |
} |
769 |
} |
770 |
|
771 |
private static double intersectXAxisParallel(Point p, Point q, double y) { |
772 |
// p.y != q.y because this routine is only called when either the |
773 |
// lower or the higher fat line bound is crossed. |
774 |
return new Vector3D(p).getCrossed(new Vector3D(q)) |
775 |
.getCrossed(new Vector3D(0, 1, -y)).toPoint().x; |
776 |
// double dy = q.y - p.y; |
777 |
// double s = (y - p.y) / dy; |
778 |
// return (q.x - p.x) * s + p.x; |
779 |
} |
780 |
|
781 |
private static Point[] getConvexHull(Vector3D[] points) { |
782 |
Point[] chPoints = new Point[points.length]; |
783 |
for (int i = 0; i < points.length; i++) { |
784 |
chPoints[i] = points[i].toPoint(); |
785 |
} |
786 |
return PointListUtils.getConvexHull(chPoints); |
787 |
} |
788 |
|
789 |
/** |
790 |
* @param L |
791 |
* @return |
792 |
*/ |
793 |
private double[] clipTo(FatLine L) { |
794 |
double[] interval = new double[] { 1, 0 }; |
795 |
|
796 |
Point[] D = getConvexHull(genDifferencePoints(L.line)); |
797 |
|
798 |
// we do not know which point is returned first by the |
799 |
// getConvexHull() method. That's why we have to check the "first" |
800 |
// point, too. |
801 |
boolean isBelow = D[0].y < L.dmin; |
802 |
boolean isAbove = D[0].y > L.dmax; |
803 |
insideFatLineCheck(interval, D, 0, isBelow, isAbove); |
804 |
|
805 |
boolean wasBelow = isBelow, wasAbove = isAbove; |
806 |
|
807 |
for (int i = 1; i < D.length; i++) { |
808 |
isBelow = D[i].y < L.dmin; |
809 |
isAbove = D[i].y > L.dmax; |
810 |
|
811 |
insideFatLineCheck(interval, D, i, isBelow, isAbove); |
812 |
wasBelow = belowFatLineCheck(interval, L, D, i - 1, i, isBelow, |
813 |
wasBelow); |
814 |
wasAbove = aboveFatLineCheck(interval, L, D, i - 1, i, isAbove, |
815 |
wasAbove); |
816 |
} |
817 |
|
818 |
// closing segment |
819 |
isBelow = D[0].y < L.dmin; |
820 |
isAbove = D[0].y > L.dmax; |
821 |
belowFatLineCheck(interval, L, D, D.length - 1, 0, isBelow, |
822 |
wasBelow); |
823 |
aboveFatLineCheck(interval, L, D, D.length - 1, 0, isAbove, |
824 |
wasAbove); |
825 |
|
826 |
return interval; |
827 |
} |
828 |
|
829 |
private boolean aboveFatLineCheck(double[] interval, FatLine L, |
830 |
Point[] D, int i, int j, boolean isAbove, boolean wasAbove) { |
831 |
if (isAbove != wasAbove) { |
832 |
// crosses higher |
833 |
double x = intersectXAxisParallel(D[i], D[j], L.dmax); |
834 |
moveInterval(interval, x); |
835 |
wasAbove = isAbove; |
836 |
} |
837 |
return wasAbove; |
838 |
} |
839 |
|
840 |
private boolean belowFatLineCheck(double[] interval, FatLine L, |
841 |
Point[] D, int i, int j, boolean isBelow, boolean wasBelow) { |
842 |
if (isBelow != wasBelow) { |
843 |
// crosses lower |
844 |
double x = intersectXAxisParallel(D[i], D[j], L.dmin); |
845 |
moveInterval(interval, x); |
846 |
wasBelow = isBelow; |
847 |
} |
848 |
return wasBelow; |
849 |
} |
850 |
|
851 |
private void insideFatLineCheck(double[] interval, Point[] D, int i, |
852 |
boolean isBelow, boolean isAbove) { |
853 |
if (!(isBelow || isAbove)) { |
854 |
// inside |
855 |
moveInterval(interval, D[i].x); |
856 |
} |
857 |
} |
858 |
|
859 |
private void moveInterval(double[] interval, double x) { |
860 |
if (interval[0] > x) |
861 |
interval[0] = x; |
862 |
if (interval[1] < x) |
863 |
interval[1] = x; |
864 |
} |
865 |
|
866 |
/** |
867 |
* Computes and returns the points of intersection between this |
868 |
* {@link BezierCurve} and the given other {@link BezierCurve}. |
869 |
* |
870 |
* @param endPointIntersections |
871 |
* all points of intersections that are end-points of one of |
872 |
* the curves |
873 |
* @param p |
874 |
* first {@link BezierCurve} |
875 |
* @param ps |
876 |
* start value of the first {@link BezierCurve}'s parameter |
877 |
* interval |
878 |
* @param pe |
879 |
* end value of the first {@link BezierCurve}'s parameter |
880 |
* interval |
881 |
* @param q |
882 |
* second {@link BezierCurve} |
883 |
* @param qs |
884 |
* start value of the second {@link BezierCurve}'s parameter |
885 |
* interval |
886 |
* @param qe |
887 |
* end value of the second {@link BezierCurve}'s parameter |
888 |
* interval |
889 |
* @return the intersections between this {@link BezierCurve} and the |
890 |
* given other {@link BezierCurve} |
891 |
*/ |
892 |
@SuppressWarnings("unchecked") |
893 |
public static Set<Vector3D> getIntersections( |
894 |
Set<Vector3D> endPointIntersections, BezierCurve p, double ps, |
895 |
double pe, BezierCurve q, double qs, double qe) { |
896 |
BezierCurve pClipped = p.getClipped(ps, pe); |
897 |
BezierCurve qClipped = q.getClipped(qs, qe); |
898 |
|
899 |
// end point intersection check |
900 |
if (endPointIntersectionConvergence(endPointIntersections, |
901 |
pClipped, ps, pe, qClipped, qs, qe)) { |
902 |
Set<Vector3D> no_intersections = new HashSet<Vector3D>(0); |
903 |
return no_intersections; |
904 |
} |
905 |
|
906 |
// TODO: tangential intersection check |
907 |
|
908 |
// parameter convergence check |
909 |
Vector3D poi = parameterConvergence(p, ps, pe, q, qs, qe); |
910 |
if (poi != null) { |
911 |
// "exactly" one intersection |
912 |
if (p.contains(poi.toPoint()) && q.contains(poi.toPoint())) { |
913 |
Set<Vector3D> intersection = new HashSet<Vector3D>(1); |
914 |
intersection.add(poi); |
915 |
return intersection; |
916 |
} |
917 |
Set<Vector3D> no_intersections = new HashSet<Vector3D>(0); |
918 |
return no_intersections; |
919 |
} |
920 |
|
921 |
Set<Vector3D> intersections = new HashSet<Vector3D>(); |
922 |
|
923 |
// construct "parallel" and "orthogonal" fat lines |
924 |
FatLine L1 = FatLine.from(qClipped, false); |
925 |
FatLine L2 = FatLine.from(qClipped, true); |
926 |
|
927 |
// curve implosion check |
928 |
if (L1 == null || L2 == null) { |
929 |
// qClipped is too small to construct a fat line from it |
930 |
// therefore, return its mid-point if it is contained by the |
931 |
// other curve |
932 |
Vector3D mid = q.get((qs + qe) / 2); |
933 |
if (p.contains(mid.toPoint())) { |
934 |
Set<Vector3D> intersection = new HashSet<Vector3D>(1); |
935 |
intersection.add(mid); |
936 |
return intersection; |
937 |
} |
938 |
Set<Vector3D> no_intersections = new HashSet<Vector3D>(0); |
939 |
return no_intersections; |
940 |
} |
941 |
|
942 |
// clip to the fat lines |
943 |
double[] interval = pClipped.clipTo(L1); |
944 |
double[] interval_ortho = pClipped.clipTo(L2); |
945 |
|
946 |
// pick smaller interval range |
947 |
if ((interval[1] - interval[0]) > (interval_ortho[1] - interval_ortho[0])) { |
948 |
interval[0] = interval_ortho[0]; |
949 |
interval[1] = interval_ortho[1]; |
950 |
} |
951 |
|
952 |
// re-calculate s and e from the clipped interval |
953 |
double news = ps + interval[0] * (pe - ps); |
954 |
double newe = ps + interval[1] * (pe - ps); |
955 |
double ratio = (newe - news) / (pe - ps); |
956 |
ps = news; |
957 |
pe = newe; |
958 |
|
959 |
if (ratio < 0) { |
960 |
// no more intersections |
961 |
return intersections; |
962 |
} else if (ratio > 0.8) { |
963 |
// split longer curve and find intersections for both halves |
964 |
if ((pe - ps) > (qe - qs)) { |
965 |
double pm = (ps + pe) / 2; |
966 |
intersections.addAll(getIntersections( |
967 |
endPointIntersections, p, ps, pm, q, qs, qe)); |
968 |
intersections.addAll(getIntersections( |
969 |
endPointIntersections, p, pm |
970 |
+ UNRECOGNIZABLE_PRECISION_FRACTION, pe, q, |
971 |
qs, qe)); |
972 |
} else { |
973 |
double qm = (qs + qe) / 2; |
974 |
intersections.addAll(getIntersections( |
975 |
endPointIntersections, q, qs, qm, p, ps, pe)); |
976 |
intersections.addAll(getIntersections( |
977 |
endPointIntersections, q, qm |
978 |
+ UNRECOGNIZABLE_PRECISION_FRACTION, qe, p, |
979 |
ps, pe)); |
980 |
} |
981 |
|
982 |
return intersections; |
983 |
} else { |
984 |
// clipped more than 20% |
985 |
return getIntersections(endPointIntersections, q, qs, qe, p, |
986 |
ps, pe); |
987 |
} |
988 |
} |
989 |
|
990 |
private static boolean endPointIntersectionConvergence( |
991 |
Set<Vector3D> endPointIntersections, BezierCurve pClipped, |
992 |
double ps, double pe, BezierCurve qClipped, double qs, double qe) { |
993 |
if (PrecisionUtils.equal(ps, pe, -3)) { |
994 |
if (endPointIntersections.contains(pClipped.points[0]) |
995 |
|| endPointIntersections |
996 |
.contains(pClipped.points[pClipped.points.length - 1])) { |
997 |
return true; |
998 |
} |
999 |
} |
1000 |
if (PrecisionUtils.equal(qs, qe, -3)) { |
1001 |
if (endPointIntersections.contains(qClipped.points[0]) |
1002 |
|| endPointIntersections |
1003 |
.contains(qClipped.points[qClipped.points.length - 1])) { |
1004 |
return true; |
1005 |
} |
1006 |
} |
1007 |
return false; |
1008 |
} |
1009 |
} |
1010 |
|
1011 |
/** |
1012 |
* Computes and returns the points of intersection between two |
1013 |
* {@link CubicCurve}s. |
1014 |
* |
1015 |
* @param p |
1016 |
* the first {@link CubicCurve} to intersect |
1017 |
* @param q |
1018 |
* the second {@link CubicCurve} to intersect |
1019 |
* @return the intersections between two {@link CubicCurve}s |
1020 |
*/ |
1021 |
public static Point[] getIntersections(CubicCurve p, CubicCurve q) { |
1022 |
Set<CurveUtils.Vector3D> intersections = new BezierCurve(p) |
1023 |
.getIntersections(new BezierCurve(q)); |
1024 |
|
1025 |
Set<Point> pois = new HashSet<Point>(); |
1026 |
for (CurveUtils.Vector3D poi : intersections) { |
1027 |
if (poi.z != 0) { |
1028 |
pois.add(poi.toPoint()); |
1029 |
} |
1030 |
} |
1031 |
|
1032 |
return pois.toArray(new Point[] {}); |
1033 |
} |
1034 |
|
1035 |
/** |
1036 |
* Computes the {@link Point} of intersection of two {@link Straight}s. |
1037 |
* |
1038 |
* @param s1 |
1039 |
* the first {@link Straight} to test for intersection |
1040 |
* @param s2 |
1041 |
* the second {@link Straight} to test for intersection |
1042 |
* @return the {@link Point} of intersection if it exists, <code>null</code> |
1043 |
* otherwise |
1044 |
*/ |
1045 |
public static Point getIntersection(Straight s1, Straight s2) { |
1046 |
Vector3D l1 = new Vector3D(s1.position.toPoint()) |
1047 |
.getCrossed(new Vector3D(s1.position.getAdded(s1.direction) |
1048 |
.toPoint())); |
1049 |
Vector3D l2 = new Vector3D(s2.position.toPoint()) |
1050 |
.getCrossed(new Vector3D(s2.position.getAdded(s2.direction) |
1051 |
.toPoint())); |
1052 |
|
1053 |
return l1.getCrossed(l2).toPoint(); |
1054 |
} |
1055 |
|
1056 |
/** |
1057 |
* Computes the signed distance of the third {@link Point} to the line |
1058 |
* through the first two {@link Point}s. |
1059 |
* |
1060 |
* The signed distance is positive if the three {@link Point}s are in |
1061 |
* counter-clockwise order and negative if the {@link Point}s are in |
1062 |
* clockwise order. It is zero if the third {@link Point} lies on the line. |
1063 |
* |
1064 |
* If the first two {@link Point}s do not form a line (i.e. they are equal) |
1065 |
* this function returns the distance of the first and the last |
1066 |
* {@link Point}. |
1067 |
* |
1068 |
* @param p |
1069 |
* the start-{@link Point} of the line |
1070 |
* @param q |
1071 |
* the end-{@link Point} of the line |
1072 |
* @param r |
1073 |
* the relative {@link Point} to test for |
1074 |
* @return the signed distance of {@link Point} r to the line through |
1075 |
* {@link Point}s p and q |
1076 |
*/ |
1077 |
public static double getSignedDistance(Point p, Point q, Point r) { |
1078 |
Vector3D normalizedLine = new Vector3D(p).getCrossed(new Vector3D(q)) |
1079 |
.getLineNormalized(); |
1080 |
|
1081 |
if (normalizedLine == null) { |
1082 |
return p.getDistance(r); |
1083 |
} |
1084 |
|
1085 |
double dot = normalizedLine.getDot(new Vector3D(r)); |
1086 |
return -dot; |
1087 |
} |
1088 |
|
1089 |
/** |
1090 |
* Returns the signed distance of the {@link Point} to the {@link Straight}. |
1091 |
* |
1092 |
* {@link Point}s that are to the left of the {@link Straight} in the |
1093 |
* direction of the {@link Straight}'s direction vector have a positive |
1094 |
* distance whereas {@link Point}s to the right of the {@link Straight} in |
1095 |
* the direction of the {@link Straight}'s direction vector have a negative |
1096 |
* distance. |
1097 |
* |
1098 |
* The absolute value of the signed distance is the actual distance of the |
1099 |
* {@link Point} to the {@link Straight}. |
1100 |
* |
1101 |
* @param s |
1102 |
* @param p |
1103 |
* @return the signed distance of the {@link Point} to the {@link Straight} |
1104 |
* |
1105 |
* @see CurveUtils#getSignedDistance(Point, Point, Point) |
1106 |
*/ |
1107 |
public static double getSignedDistance(Straight s, Point p) { |
1108 |
return getSignedDistance(s.position.toPoint(), |
1109 |
s.position.getAdded(s.direction).toPoint(), p); |
1110 |
} |
1111 |
|
1112 |
/** |
1113 |
* Tests if the given {@link Point} lies on the given {@link CubicCurve}. |
1114 |
* |
1115 |
* @param c |
1116 |
* the {@link CubicCurve} to test |
1117 |
* @param p |
1118 |
* the {@link Point} to test for containment |
1119 |
* @return true if the {@link Point} lies on the {@link CubicCurve}, false |
1120 |
* otherwise |
1121 |
*/ |
1122 |
public static boolean contains(CubicCurve c, Point p) { |
1123 |
return new BezierCurve(c).contains(p); |
1124 |
} |
1125 |
|
1126 |
/** |
1127 |
* Tests if the given {@link Point} lies on the given {@link QuadraticCurve} |
1128 |
* . |
1129 |
* |
1130 |
* @param c |
1131 |
* the {@link QuadraticCurve} to test |
1132 |
* @param p |
1133 |
* the {@link Point} to test for containment |
1134 |
* @return true if the {@link Point} lies on the {@link QuadraticCurve}, |
1135 |
* false otherwise |
1136 |
*/ |
1137 |
public static boolean contains(QuadraticCurve c, Point p) { |
1138 |
return new BezierCurve(c).contains(p); |
1139 |
} |
1140 |
|
1141 |
/** |
1142 |
* Subdivides the given {@link CubicCurve} at parameter value t in the left |
1143 |
* and right sub-curves. |
1144 |
* |
1145 |
* @param c |
1146 |
* the {@link CubicCurve} to subdivide |
1147 |
* @param t |
1148 |
* the parameter value to subdivide at |
1149 |
* @return the left and right sub-curves as an array of {@link CubicCurve} |
1150 |
*/ |
1151 |
public static CubicCurve[] split(CubicCurve c, double t) { |
1152 |
BezierCurve[] split = new BezierCurve(c).split(t); |
1153 |
return new CubicCurve[] { new CubicCurve(split[0].getRealPoints()), |
1154 |
new CubicCurve(split[1].getRealPoints()) }; |
1155 |
} |
1156 |
|
1157 |
/** |
1158 |
* Subdivides the given {@link QuadraticCurve} at parameter value t in the |
1159 |
* left and right sub-curves. |
1160 |
* |
1161 |
* @param c |
1162 |
* the {@link QuadraticCurve} to subdivide |
1163 |
* @param t |
1164 |
* the parameter value to subdivide at |
1165 |
* @return the left and right sub-curves as an array of |
1166 |
* {@link QuadraticCurve} |
1167 |
*/ |
1168 |
public static QuadraticCurve[] split(QuadraticCurve c, double t) { |
1169 |
BezierCurve[] split = new BezierCurve(c).split(t); |
1170 |
return new QuadraticCurve[] { |
1171 |
new QuadraticCurve(split[0].getRealPoints()), |
1172 |
new QuadraticCurve(split[1].getRealPoints()) }; |
1173 |
} |
1174 |
|
1175 |
/** |
1176 |
* Returns a new {@link QuadraticCurve} that represents the given |
1177 |
* {@link QuadraticCurve} on the parameter interval [t1;t2]. |
1178 |
* |
1179 |
* @param c |
1180 |
* the {@link QuadraticCurve} to clip |
1181 |
* @param t1 |
1182 |
* lower parameter bound |
1183 |
* @param t2 |
1184 |
* upper parameter bound |
1185 |
* @return a new {@link QuadraticCurve} that represents the given |
1186 |
* {@link QuadraticCurve} on the interval [t1;t2] |
1187 |
*/ |
1188 |
public static QuadraticCurve clip(QuadraticCurve c, double t1, double t2) { |
1189 |
BezierCurve bc = new BezierCurve(c); |
1190 |
return new QuadraticCurve(bc.getClipped(t1, t2).getRealPoints()); |
1191 |
} |
1192 |
|
1193 |
/** |
1194 |
* Returns a new {@link CubicCurve} that represents the given |
1195 |
* {@link CubicCurve} on the parameter interval [t1;t2]. |
1196 |
* |
1197 |
* @param c |
1198 |
* the {@link CubicCurve} to clip |
1199 |
* @param t1 |
1200 |
* lower parameter bound |
1201 |
* @param t2 |
1202 |
* upper parameter bound |
1203 |
* @return a new {@link CubicCurve} that represents the given |
1204 |
* {@link CubicCurve} on the interval [t1;t2] |
1205 |
*/ |
1206 |
public static CubicCurve clip(CubicCurve c, double t1, double t2) { |
1207 |
BezierCurve bc = new BezierCurve(c); |
1208 |
return new CubicCurve(bc.getClipped(t1, t2).getRealPoints()); |
1209 |
} |
1210 |
|
1211 |
/** |
1212 |
* Evaluates the {@link Point} on the given {@link CubicCurve} at parameter |
1213 |
* value t. |
1214 |
* |
1215 |
* @param c |
1216 |
* the {@link CubicCurve} |
1217 |
* @param t |
1218 |
* the parameter value |
1219 |
* @return the {@link Point} on the given {@link CubicCurve} at parameter |
1220 |
* value t |
1221 |
*/ |
1222 |
public static Point get(CubicCurve c, double t) { |
1223 |
return new BezierCurve(c).get(t).toPoint(); |
1224 |
} |
1225 |
|
1226 |
/** |
1227 |
* Evaluates the {@link Point} on the given {@link QuadraticCurve} at |
1228 |
* parameter value t. |
1229 |
* |
1230 |
* @param c |
1231 |
* the {@link QuadraticCurve} |
1232 |
* @param t |
1233 |
* the parameter value |
1234 |
* @return the {@link Point} on the given {@link QuadraticCurve} at |
1235 |
* parameter value t |
1236 |
*/ |
1237 |
public static Point get(QuadraticCurve c, double t) { |
1238 |
return new BezierCurve(c).get(t).toPoint(); |
1239 |
} |
1240 |
|
1241 |
/** |
1242 |
* Computes and returns the bounds of the control polygon of the given |
1243 |
* {@link CubicCurve}. The control polygon is the convex hull of the start-, |
1244 |
* end-, and control points of the {@link CubicCurve}. |
1245 |
* |
1246 |
* @param c |
1247 |
* the {@link CubicCurve} to compute the control bounds for |
1248 |
* @return the bounds of the control polygon of the given {@link CubicCurve} |
1249 |
*/ |
1250 |
public static Rectangle getControlBounds(CubicCurve c) { |
1251 |
return new BezierCurve(c).getControlBounds(); |
1252 |
} |
1253 |
} |