Lines 7-18
Link Here
|
7 |
* |
7 |
* |
8 |
* Contributors: |
8 |
* Contributors: |
9 |
* Alexander Nyßen (itemis AG) - initial API and implementation |
9 |
* Alexander Nyßen (itemis AG) - initial API and implementation |
|
|
10 |
* Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 |
10 |
* |
11 |
* |
11 |
*******************************************************************************/ |
12 |
*******************************************************************************/ |
12 |
package org.eclipse.gef4.geometry.planar; |
13 |
package org.eclipse.gef4.geometry.planar; |
13 |
|
14 |
|
|
|
15 |
import java.util.ArrayList; |
16 |
import java.util.Arrays; |
17 |
import java.util.HashSet; |
18 |
import java.util.List; |
19 |
import java.util.Set; |
20 |
import java.util.Stack; |
21 |
|
22 |
import org.eclipse.gef4.geometry.Angle; |
14 |
import org.eclipse.gef4.geometry.Point; |
23 |
import org.eclipse.gef4.geometry.Point; |
|
|
24 |
import org.eclipse.gef4.geometry.euclidean.Vector; |
25 |
import org.eclipse.gef4.geometry.projective.Straight3D; |
26 |
import org.eclipse.gef4.geometry.projective.Vector3D; |
15 |
import org.eclipse.gef4.geometry.utils.PointListUtils; |
27 |
import org.eclipse.gef4.geometry.utils.PointListUtils; |
|
|
28 |
import org.eclipse.gef4.geometry.utils.PrecisionUtils; |
16 |
|
29 |
|
17 |
/** |
30 |
/** |
18 |
* Abstract base class of Bezier Curves. |
31 |
* Abstract base class of Bezier Curves. |
Lines 23-280
import org.eclipse.gef4.geometry.utils.PointListUtils;
Link Here
|
23 |
* @author anyssen |
36 |
* @author anyssen |
24 |
* |
37 |
* |
25 |
*/ |
38 |
*/ |
26 |
public abstract class BezierCurve extends AbstractGeometry implements ICurve { |
39 |
public class BezierCurve extends AbstractGeometry implements ICurve { |
27 |
|
40 |
|
28 |
double x1; |
41 |
private static final long serialVersionUID = 1L; |
29 |
double y1; |
|
|
30 |
double x2; |
31 |
double y2; |
32 |
|
42 |
|
33 |
// TODO: use point array instead |
43 |
private static class FatLine { |
34 |
double[] ctrlCoordinates = null; |
44 |
public static FatLine from(BezierCurve c, boolean ortho) { |
|
|
45 |
FatLine L = new FatLine(); |
46 |
L.dmin = L.dmax = 0; |
35 |
|
47 |
|
36 |
public BezierCurve(double... coordinates) { |
48 |
L.line = Straight3D.through(c.points[0], |
37 |
if (coordinates.length < 4) { |
49 |
c.points[c.points.length - 1]); |
38 |
throw new IllegalArgumentException( |
50 |
if (L.line == null) { |
39 |
"A bezier curve needs at least a start and an end point"); |
51 |
return null; |
|
|
52 |
} |
53 |
|
54 |
if (ortho) { |
55 |
L.line = L.line.getOrtho(); |
56 |
} |
57 |
if (L.line == null) { |
58 |
return null; |
59 |
} |
60 |
|
61 |
for (int i = 0; i < c.points.length; i++) { |
62 |
double d = L.line.getSignedDistanceCW(c.points[i]); |
63 |
if (d < L.dmin) |
64 |
L.dmin = d; |
65 |
else if (d > L.dmax) |
66 |
L.dmax = d; |
67 |
} |
68 |
|
69 |
return L; |
40 |
} |
70 |
} |
41 |
this.x1 = coordinates[0]; |
71 |
|
42 |
this.y1 = coordinates[1]; |
72 |
public Straight3D line; |
43 |
this.x2 = coordinates[coordinates.length - 2]; |
73 |
|
44 |
this.y2 = coordinates[coordinates.length - 1]; |
74 |
public double dmin, dmax; |
45 |
if (coordinates.length > 4) { |
75 |
|
46 |
this.ctrlCoordinates = new double[coordinates.length - 4]; |
76 |
private FatLine() { |
47 |
System.arraycopy(coordinates, 2, ctrlCoordinates, 0, |
77 |
line = null; |
48 |
coordinates.length - 4); |
78 |
dmin = dmax = 0; |
49 |
} |
79 |
} |
50 |
} |
80 |
} |
51 |
|
81 |
|
52 |
public BezierCurve(Point... points) { |
82 |
/** |
53 |
this(PointListUtils.toCoordinatesArray(points)); |
83 |
* Representation of an interval [a;b]. It is used to represent sub-curves |
|
|
84 |
* of a {@link BezierCurve}. |
85 |
* |
86 |
* @author wienand |
87 |
*/ |
88 |
public static final class Interval { |
89 |
/** |
90 |
* Constructs a new {@link Interval} object holding an invalid parameter |
91 |
* interval. |
92 |
* |
93 |
* @return a new {@link Interval} object holding an invalid parameter |
94 |
* interval |
95 |
*/ |
96 |
public static Interval getEmpty() { |
97 |
return new Interval(1, 0); |
98 |
} |
99 |
|
100 |
/** |
101 |
* Constructs a new {@link Interval} object holding the interval [0;1] |
102 |
* which is the parameter interval representing a full |
103 |
* {@link BezierCurve}. |
104 |
* |
105 |
* @return a new {@link Interval} object holding the interval [0;1] |
106 |
*/ |
107 |
public static Interval getFull() { |
108 |
return new Interval(0, 1); |
109 |
} |
110 |
|
111 |
/** |
112 |
* Returns the smaller {@link Interval} object, i.e. the one with the |
113 |
* smallest parameter range. |
114 |
* |
115 |
* @param i |
116 |
* @param j |
117 |
* @return the {@link Interval} with the smallest parameter range |
118 |
*/ |
119 |
public static Interval min(Interval i, Interval j) { |
120 |
return (i.b - i.a) > (j.b - j.a) ? j : i; |
121 |
} |
122 |
|
123 |
/** |
124 |
* An {@link Interval} holds the parameter range [a;b]. Valid parameter |
125 |
* ranges require 0 <= a <= b <= 1. |
126 |
*/ |
127 |
public double a; |
128 |
|
129 |
/** |
130 |
* An {@link Interval} holds the parameter range [a;b]. Valid parameter |
131 |
* ranges require 0 <= a <= b <= 1. |
132 |
*/ |
133 |
public double b; |
134 |
|
135 |
/** |
136 |
* Constructs a new {@link Interval} object from the given double |
137 |
* values. Only the first two double values are of importance as the |
138 |
* rest of them are ignored. |
139 |
* |
140 |
* The new {@link Interval} holds the parameter range [a;b] if a is the |
141 |
* first double value and b is the second double value. |
142 |
* |
143 |
* @param ds |
144 |
*/ |
145 |
public Interval(double... ds) { |
146 |
if (ds.length > 1) { |
147 |
a = ds[0]; |
148 |
b = ds[1]; |
149 |
} else { |
150 |
throw new IllegalArgumentException( |
151 |
"not enough values to create interval"); |
152 |
} |
153 |
} |
154 |
|
155 |
/** |
156 |
* Tests if this {@link Interval}'s parameter range does converge with |
157 |
* default imprecision. Returns <code>true</code> for a ~= b, |
158 |
* <code>false</code> otherwise. |
159 |
* |
160 |
* @see Interval#converges(int) |
161 |
* |
162 |
* @return <code>true</code> if a ~= b (default imprecision), |
163 |
* <code>false</code> otherwise |
164 |
*/ |
165 |
public boolean converges() { |
166 |
return converges(0); |
167 |
} |
168 |
|
169 |
/** |
170 |
* Tests if this {@link Interval}'s parameter range does converge with |
171 |
* specified imprecision. Returns <code>true</code> for a ~= b, |
172 |
* <code>false</code> otherwise. |
173 |
* |
174 |
* The imprecision is specified by providing a shift value which shifts |
175 |
* the epsilon used for the number comparison. A positive shift demands |
176 |
* for a smaller epsilon (higher precision) whereas a negative shift |
177 |
* demands for a greater epsilon (lower precision). |
178 |
* |
179 |
* @param shift |
180 |
* precision shift |
181 |
* @return <code>true</code> if a ~= b (specified imprecision), |
182 |
* <code>false</code> otherwise |
183 |
*/ |
184 |
public boolean converges(int shift) { |
185 |
return PrecisionUtils.equal(a, b, shift); |
186 |
} |
187 |
|
188 |
/** |
189 |
* Returns a copy of this {@link Interval}. |
190 |
* |
191 |
* @return a copy of this {@link Interval} |
192 |
*/ |
193 |
public Interval getCopy() { |
194 |
return new Interval(a, b); |
195 |
} |
196 |
|
197 |
/** |
198 |
* Returns the middle parameter value m = (a+b)/2 of this |
199 |
* {@link Interval}. |
200 |
* |
201 |
* @return the middle parameter value of this {@link Interval} |
202 |
*/ |
203 |
public double getMid() { |
204 |
return (a + b) / 2; |
205 |
} |
206 |
|
207 |
/** |
208 |
* Scales this {@link Interval} to the given {@link Interval}. The given |
209 |
* {@link Interval} specifies the new upper and lower bounds of this |
210 |
* {@link Interval} in percent. |
211 |
* |
212 |
* Returns the ratio of this {@link Interval}'s new parameter range to |
213 |
* its old parameter range. |
214 |
* |
215 |
* @param interval |
216 |
* the new upper and lower bounds in percent |
217 |
* @return the ratio of this {@link Interval}'s new parameter range to |
218 |
* its old parameter range |
219 |
*/ |
220 |
public double scaleTo(Interval interval) { |
221 |
double na = a + interval.a * (b - a); |
222 |
double nb = a + interval.b * (b - a); |
223 |
double ratio = (nb - na) / (b - a); |
224 |
a = na; |
225 |
b = nb; |
226 |
return ratio; |
227 |
} |
54 |
} |
228 |
} |
55 |
|
229 |
|
56 |
public final boolean contains(Rectangle r) { |
230 |
/** |
57 |
// TODO: may contain the rectangle only in case the rectangle is |
231 |
* An {@link IntervalPair} combines two {@link BezierCurve}s and their |
58 |
// degenerated... |
232 |
* corresponding parameter ranges. |
59 |
return false; |
233 |
* |
|
|
234 |
* @author wienand |
235 |
*/ |
236 |
public static final class IntervalPair { |
237 |
/** |
238 |
* The first {@link BezierCurve}. |
239 |
*/ |
240 |
public BezierCurve p; |
241 |
/** |
242 |
* The second {@link BezierCurve}. |
243 |
*/ |
244 |
public BezierCurve q; |
245 |
/** |
246 |
* The parameter {@link Interval} for the first {@link BezierCurve}. |
247 |
*/ |
248 |
public Interval pi; |
249 |
/** |
250 |
* The parameter {@link Interval} for the second {@link BezierCurve}. |
251 |
*/ |
252 |
public Interval qi; |
253 |
|
254 |
/** |
255 |
* Constructs a new {@link IntervalPair} with the given |
256 |
* {@link BezierCurve}s and their corresponding parameter ranges. |
257 |
* |
258 |
* @param pp |
259 |
* the first {@link BezierCurve} |
260 |
* @param pt |
261 |
* the parameter {@link Interval} for the first |
262 |
* {@link BezierCurve} |
263 |
* @param pq |
264 |
* the second {@link BezierCurve} |
265 |
* @param pu |
266 |
* the parameter {@link Interval} for the second |
267 |
* {@link BezierCurve} |
268 |
*/ |
269 |
public IntervalPair(BezierCurve pp, Interval pt, BezierCurve pq, |
270 |
Interval pu) { |
271 |
p = pp; |
272 |
pi = pt; |
273 |
q = pq; |
274 |
qi = pu; |
275 |
} |
276 |
|
277 |
/** |
278 |
* Tests if both parameter {@link Interval}s do converge (@see |
279 |
* Interval#converges()) or both {@link BezierCurve}s are degenerated, |
280 |
* i.e. they are collapsed to a single {@link Point}. |
281 |
* |
282 |
* @return <code>true</true> if both parameter {@link Interval}s do converge, <code>false</code> |
283 |
* otherwise |
284 |
*/ |
285 |
public boolean converges() { |
286 |
return converges(0); |
287 |
} |
288 |
|
289 |
/** |
290 |
* Tests if both parameter {@link Interval}s do converge (@see |
291 |
* Interval#converges(int)) or both {@link BezierCurve}s are |
292 |
* degenerated, i.e. they are collapsed to a single {@link Point}. |
293 |
* |
294 |
* @param shift |
295 |
* the precision shift |
296 |
* @return <code>true</true> if both parameter {@link Interval}s do converge, <code>false</code> |
297 |
* otherwise |
298 |
*/ |
299 |
public boolean converges(int shift) { |
300 |
return (pi.converges(shift) || pointsEquals( |
301 |
p.getHC(pi.a).toPoint(), p.getHC(pi.b).toPoint(), shift)) |
302 |
&& (qi.converges(shift) || pointsEquals(q.getHC(qi.a) |
303 |
.toPoint(), q.getHC(qi.b).toPoint(), shift)); |
304 |
// return pi.converges(shift) && qi.converges(shift); |
305 |
} |
306 |
|
307 |
/** |
308 |
* Returns a copy of this {@link IntervalPair}. The underlying |
309 |
* {@link BezierCurve}s are only shallow copied. The corresponding |
310 |
* parameter {@link Interval}s are truly copied. |
311 |
* |
312 |
* @return a copy of this {@link IntervalPair} |
313 |
*/ |
314 |
public IntervalPair getCopy() { |
315 |
return new IntervalPair(p, pi.getCopy(), q, qi.getCopy()); |
316 |
} |
317 |
|
318 |
/** |
319 |
* Returns the first sub-curve of this {@link IntervalPair}. This curve |
320 |
* is the first {@link BezierCurve} p over its corresponding parameter |
321 |
* {@link Interval} pi. |
322 |
* |
323 |
* @return the first sub-curve of this {@link IntervalPair} |
324 |
*/ |
325 |
public BezierCurve getPClipped() { |
326 |
return p.getClipped(pi.a, pi.b); |
327 |
} |
328 |
|
329 |
/** |
330 |
* Splits the first parameter {@link Interval} pi at half and returns |
331 |
* the resulting {@link IntervalPair}s. |
332 |
* |
333 |
* @return two {@link IntervalPair}s representing a split of the first |
334 |
* paramter {@link Interval} in half |
335 |
*/ |
336 |
public IntervalPair[] getPSplit() { |
337 |
double pm = (pi.a + pi.b) / 2; |
338 |
return new IntervalPair[] { |
339 |
new IntervalPair(p, new Interval(pi.a, pm), q, qi.getCopy()), |
340 |
new IntervalPair(p, new Interval(pm + 10 |
341 |
* UNRECOGNIZABLE_PRECISION_FRACTION, pi.b), q, |
342 |
qi.getCopy()) }; |
343 |
} |
344 |
|
345 |
/** |
346 |
* Returns the second sub-curve of this {@link IntervalPair}. This curve |
347 |
* is the second {@link BezierCurve} q over its corresponding parameter |
348 |
* {@link Interval} qi. |
349 |
* |
350 |
* @return the second sub-curve of this {@link IntervalPair} |
351 |
*/ |
352 |
public BezierCurve getQClipped() { |
353 |
return q.getClipped(qi.a, qi.b); |
354 |
} |
355 |
|
356 |
/** |
357 |
* Splits the second parameter {@link Interval} qi at half and returns |
358 |
* the resulting {@link IntervalPair}s. |
359 |
* |
360 |
* @return two {@link IntervalPair}s representing a split of the second |
361 |
* paramter {@link Interval} in half |
362 |
*/ |
363 |
public IntervalPair[] getQSplit() { |
364 |
double qm = (qi.a + qi.b) / 2; |
365 |
return new IntervalPair[] { |
366 |
new IntervalPair(q, new Interval(qi.a, qm), p, pi.getCopy()), |
367 |
new IntervalPair(q, new Interval(qm + 10 |
368 |
* UNRECOGNIZABLE_PRECISION_FRACTION, qi.b), p, |
369 |
pi.getCopy()) }; |
370 |
} |
371 |
|
372 |
/** |
373 |
* Creates a new {@link IntervalPair} with swapped {@link BezierCurve}s |
374 |
* and their parameter {@link Interval}s. |
375 |
* |
376 |
* @return a new {@link IntervalPair} with swapped {@link BezierCurve}s |
377 |
* and their parameter {@link Interval}s |
378 |
*/ |
379 |
public IntervalPair getSwapped() { |
380 |
return new IntervalPair(q, qi.getCopy(), p, pi.getCopy()); |
381 |
} |
382 |
|
383 |
/** |
384 |
* Calculates which {@link BezierCurve}'s parameter {@link Interval} is |
385 |
* longer. Returns <code>true</code> if the distance from start |
386 |
* parameter to end parameter of the frist parameter {@link Interval} pi |
387 |
* is greater than the distance from start parameter to end parameter of |
388 |
* the second parameter {@link Interval} qi. Otherwise, returns |
389 |
* <code>false</code>. |
390 |
* |
391 |
* @return <code>true</code> if the distance from start to end parameter |
392 |
* value of the first parameter {@link Interval} pi is greater |
393 |
* than the distance from start to end parameter value of the |
394 |
* second parameter {@link Interval} qi. Othwise, returns |
395 |
* <code>false</code>. |
396 |
*/ |
397 |
public boolean isPLonger() { |
398 |
return (pi.b - pi.a) > (qi.b - qi.a); |
399 |
} |
400 |
} |
401 |
|
402 |
private interface IPointCmp { |
403 |
public boolean pIsBetterThanQ(Point p, Point q); |
404 |
} |
405 |
|
406 |
// TODO: use constants that limit the number of iterations for the |
407 |
// different iterative/recursive algorithms: |
408 |
// INTERSECTIONS_MAX_ITERATIONS, APPROXIMATION_MAX_ITERATIONS |
409 |
|
410 |
private static final int CHUNK_SHIFT = -3; |
411 |
|
412 |
private static final boolean ORTHOGONAL = true; |
413 |
|
414 |
private static final boolean PARALLEL = false; |
415 |
|
416 |
private static final double UNRECOGNIZABLE_PRECISION_FRACTION = PrecisionUtils |
417 |
.calculateFraction(0) / 10; |
418 |
|
419 |
private static IntervalPair[] clusterChunks(IntervalPair[] intervalPairs, |
420 |
int shift) { |
421 |
List<IntervalPair> clusters = new ArrayList<IntervalPair>(); |
422 |
|
423 |
// TODO: do something intelligent instead! |
424 |
boolean isCompletelyClustered = true; |
425 |
|
426 |
for (IntervalPair ip : intervalPairs) { |
427 |
boolean isExpansion = false; |
428 |
|
429 |
for (IntervalPair cluster : clusters) { |
430 |
if (isNextTo(cluster, ip, shift)) { |
431 |
expand(cluster, ip); |
432 |
isExpansion = true; |
433 |
break; |
434 |
} |
435 |
} |
436 |
|
437 |
if (!isExpansion) { |
438 |
clusters.add(ip); |
439 |
} else { |
440 |
isCompletelyClustered = false; |
441 |
} |
442 |
} |
443 |
|
444 |
IntervalPair[] clustersArray = clusters.toArray(new IntervalPair[] {}); |
445 |
return isCompletelyClustered ? clustersArray : clusterChunks( |
446 |
clustersArray, shift); |
447 |
} |
448 |
|
449 |
private static void copyIntervalPair(IntervalPair a, IntervalPair b) { |
450 |
a.p = b.p; |
451 |
a.q = b.q; |
452 |
a.pi = b.pi; |
453 |
a.qi = b.qi; |
454 |
} |
455 |
|
456 |
private static void expand(IntervalPair group, IntervalPair newcomer) { |
457 |
if (group.pi.a > newcomer.pi.a) { |
458 |
group.pi.a = newcomer.pi.a; |
459 |
} |
460 |
if (group.pi.b < newcomer.pi.b) { |
461 |
group.pi.b = newcomer.pi.b; |
462 |
} |
463 |
if (group.qi.a > newcomer.qi.a) { |
464 |
group.qi.a = newcomer.qi.a; |
465 |
} |
466 |
if (group.qi.b < newcomer.qi.b) { |
467 |
group.qi.b = newcomer.qi.b; |
468 |
} |
469 |
} |
470 |
|
471 |
/** |
472 |
* Returns the convex hull of the given {@link Vector3D}s. |
473 |
* |
474 |
* The {@link PointListUtils#getConvexHull(Point[])} method is used to |
475 |
* calculate the convex hull. This method does only accept {@link Point} s |
476 |
* for input. |
477 |
* |
478 |
* @param vectors |
479 |
* @return |
480 |
*/ |
481 |
private static Point[] getConvexHull(Vector3D[] vectors) { |
482 |
Point[] points = new Point[vectors.length]; |
483 |
for (int i = 0; i < vectors.length; i++) { |
484 |
points[i] = vectors[i].toPoint(); |
485 |
} |
486 |
return PointListUtils.getConvexHull(points); |
487 |
} |
488 |
|
489 |
/** |
490 |
* Computes the intersection of the line from {@link Point} p to |
491 |
* {@link Point} q with the x-axis-parallel line f(x) = y. |
492 |
* |
493 |
* There is always an intersection, because this routine is only called when |
494 |
* either the lower or the higher fat line bound is crossed. |
495 |
* |
496 |
* The following conditions are fulfilled: (p.x!=q.x) and (p.y!=q.y) and |
497 |
* (p.y<y<q.y) or (p.y>y>q.y). |
498 |
* |
499 |
* From these values, one can build a function g(x) = m*x + b where |
500 |
* m=(q.y-p.y)/(q.x-p.x) and b=p.y-m*p.x. |
501 |
* |
502 |
* The point of intersection is given by f(x) = g(x). The x-coordinate of |
503 |
* this point is x = (y - b) / m. |
504 |
* |
505 |
* @param p |
506 |
* The start point of the {@link Line} |
507 |
* @param q |
508 |
* The end point of the {@link Line} |
509 |
* @param y |
510 |
* The x-axis-parallel line f(x) = y |
511 |
* @return the x coordinate of the intersection point. |
512 |
*/ |
513 |
private static double intersectXAxisParallel(Point p, Point q, double y) { |
514 |
double m = (q.y - p.y) / (q.x - p.x); |
515 |
return (y - p.y + m * p.x) / m; |
516 |
} |
517 |
|
518 |
private static boolean isNextTo(IntervalPair a, IntervalPair b, int shift) { |
519 |
boolean isPNeighbour = PrecisionUtils.greaterEqual(a.pi.a, b.pi.a, |
520 |
shift) |
521 |
&& PrecisionUtils.smallerEqual(a.pi.a, b.pi.b, shift) |
522 |
|| PrecisionUtils.smallerEqual(a.pi.a, b.pi.a, shift) |
523 |
&& PrecisionUtils.greaterEqual(a.pi.b, b.pi.a, shift); |
524 |
boolean isQNeighbour = PrecisionUtils.greaterEqual(a.qi.a, b.qi.a, |
525 |
shift) |
526 |
&& PrecisionUtils.smallerEqual(a.qi.a, b.qi.b, shift) |
527 |
|| PrecisionUtils.smallerEqual(a.qi.a, b.qi.a, shift) |
528 |
&& PrecisionUtils.greaterEqual(a.qi.b, b.qi.a, shift); |
529 |
|
530 |
return isPNeighbour && isQNeighbour; |
531 |
} |
532 |
|
533 |
private static IntervalPair isOverlap( |
534 |
IntervalPair[] intersectionCandidates, IntervalPair[] endPoints) { |
535 |
// merge intersection candidates and end points |
536 |
IntervalPair[] fineChunks = new IntervalPair[intersectionCandidates.length |
537 |
+ endPoints.length]; |
538 |
for (int i = 0; i < intersectionCandidates.length; i++) { |
539 |
fineChunks[i] = intersectionCandidates[i]; |
540 |
} |
541 |
for (int i = 0; i < endPoints.length; i++) { |
542 |
fineChunks[intersectionCandidates.length + i] = endPoints[i]; |
543 |
} |
544 |
|
545 |
if (fineChunks.length == 0) { |
546 |
return new IntervalPair(null, null, null, null); |
547 |
} |
548 |
|
549 |
// recluster chunks |
550 |
normalizeIntervalPairs(fineChunks); |
551 |
IntervalPair[] chunks = clusterChunks(fineChunks, CHUNK_SHIFT - 1); |
552 |
|
553 |
// we should have a single chunk now |
554 |
if (chunks.length != 1) { |
555 |
return new IntervalPair(null, null, null, null); |
556 |
} |
557 |
|
558 |
IntervalPair overlap = chunks[0]; |
559 |
|
560 |
/* |
561 |
* if they do overlap in a single point, the point of intersection has |
562 |
* to be an end-point of both curves. therefore, we do not have to |
563 |
* consider this case here, because it is already checked in the main |
564 |
* intersection method. |
565 |
* |
566 |
* if they overlap, the chunk has to start/end in a start-/endpoint of |
567 |
* the curves. |
568 |
*/ |
569 |
|
570 |
if (PrecisionUtils.equal(overlap.pi.a, 0) |
571 |
&& PrecisionUtils.equal(overlap.pi.b, 1) |
572 |
|| PrecisionUtils.equal(overlap.qi.a, 0) |
573 |
&& PrecisionUtils.equal(overlap.qi.b, 1) |
574 |
|| (PrecisionUtils.equal(overlap.pi.a, 0) || PrecisionUtils |
575 |
.equal(overlap.pi.b, 1)) |
576 |
&& (PrecisionUtils.equal(overlap.qi.a, 0) || PrecisionUtils |
577 |
.equal(overlap.qi.b, 1))) { |
578 |
// it overlaps |
579 |
|
580 |
if (PrecisionUtils.equal(overlap.pi.a, 0, CHUNK_SHIFT - 1) |
581 |
&& PrecisionUtils.equal(overlap.pi.b, 0, CHUNK_SHIFT - 1) |
582 |
|| PrecisionUtils.equal(overlap.pi.a, 1, CHUNK_SHIFT - 1) |
583 |
&& PrecisionUtils.equal(overlap.pi.b, 1, CHUNK_SHIFT - 1) |
584 |
|| PrecisionUtils.equal(overlap.qi.a, 0, CHUNK_SHIFT - 1) |
585 |
&& PrecisionUtils.equal(overlap.qi.b, 0, CHUNK_SHIFT - 1) |
586 |
|| PrecisionUtils.equal(overlap.qi.a, 1, CHUNK_SHIFT - 1) |
587 |
&& PrecisionUtils.equal(overlap.qi.b, 1, CHUNK_SHIFT - 1)) { |
588 |
// end-point-intersection |
589 |
return new IntervalPair(null, null, null, null); |
590 |
} |
591 |
|
592 |
return overlap; |
593 |
} |
594 |
|
595 |
return new IntervalPair(null, null, null, null); |
596 |
} |
597 |
|
598 |
private static void normalizeIntervalPairs(IntervalPair[] intervalPairs) { |
599 |
// in every interval, p and q have to be the same curves |
600 |
if (intervalPairs.length == 0) { |
601 |
return; |
602 |
} |
603 |
|
604 |
BezierCurve pId = intervalPairs[0].p; |
605 |
BezierCurve qId = intervalPairs[0].q; |
606 |
|
607 |
for (IntervalPair ip : intervalPairs) { |
608 |
if (ip.p != pId) { |
609 |
Interval qi = ip.pi; |
610 |
Interval pi = ip.qi; |
611 |
ip.p = pId; |
612 |
ip.q = qId; |
613 |
ip.pi = pi; |
614 |
ip.qi = qi; |
615 |
} |
616 |
} |
60 |
} |
617 |
} |
61 |
|
618 |
|
62 |
public Point getCtrl(int i) { |
619 |
private static boolean pointsEquals(Point p1, Point p2, int shift) { |
63 |
return new Point(getCtrlX(i), getCtrlY(i)); |
620 |
return PrecisionUtils.equal(p1.x, p2.x, shift) |
|
|
621 |
&& PrecisionUtils.equal(p1.y, p2.y, shift); |
622 |
} |
623 |
|
624 |
private Vector3D[] points; |
625 |
|
626 |
private static final IPointCmp xminCmp = new IPointCmp() { |
627 |
public boolean pIsBetterThanQ(Point p, Point q) { |
628 |
return PrecisionUtils.smallerEqual(p.x, q.x); |
629 |
} |
630 |
}; |
631 |
|
632 |
private static final IPointCmp xmaxCmp = new IPointCmp() { |
633 |
public boolean pIsBetterThanQ(Point p, Point q) { |
634 |
return PrecisionUtils.greaterEqual(p.x, q.x); |
635 |
} |
636 |
}; |
637 |
|
638 |
private static final IPointCmp yminCmp = new IPointCmp() { |
639 |
public boolean pIsBetterThanQ(Point p, Point q) { |
640 |
return PrecisionUtils.smallerEqual(p.y, q.y); |
641 |
} |
642 |
}; |
643 |
|
644 |
private static final IPointCmp ymaxCmp = new IPointCmp() { |
645 |
public boolean pIsBetterThanQ(Point p, Point q) { |
646 |
return PrecisionUtils.greaterEqual(p.y, q.y); |
647 |
} |
648 |
}; |
649 |
|
650 |
private static boolean containmentParameter(BezierCurve c, |
651 |
double[] interval, Point p) { |
652 |
Stack<Interval> parts = new Stack<Interval>(); |
653 |
parts.push(new Interval(interval)); |
654 |
while (!parts.empty()) { |
655 |
Interval i = parts.pop(); |
656 |
|
657 |
if (i.converges(1)) { |
658 |
interval[0] = i.a; |
659 |
interval[1] = i.b; |
660 |
break; |
661 |
} |
662 |
|
663 |
double iMid = i.getMid(); |
664 |
Interval left = new Interval(i.a, iMid); |
665 |
Interval right = new Interval(iMid, i.b); |
666 |
|
667 |
BezierCurve clipped = c.getClipped(left.a, left.b); |
668 |
Rectangle bounds = clipped.getControlBounds(); |
669 |
|
670 |
if (bounds.contains(p)) { |
671 |
parts.push(left); |
672 |
} |
673 |
|
674 |
clipped = c.getClipped(right.a, right.b); |
675 |
bounds = clipped.getControlBounds(); |
676 |
|
677 |
if (bounds.contains(p)) { |
678 |
parts.push(right); |
679 |
} |
680 |
} |
681 |
return PrecisionUtils.equal(interval[0], interval[1], 1); |
64 |
} |
682 |
} |
65 |
|
683 |
|
66 |
/** |
684 |
/** |
67 |
* Returns the point-wise coordinates (i.e. x1, y1, x2, y2, etc.) of the |
685 |
* Returns the similarity of the given {@link BezierCurve} to a {@link Line} |
68 |
* inner control points of this {@link BezierCurve}, i.e. exclusive of the |
686 |
* , which is defined as the absolute distance of its control points to the |
69 |
* start and end points. |
687 |
* base line connecting its end points. |
70 |
* |
688 |
* |
71 |
* @see BezierCurve#getCtrls() |
689 |
* A similarity of 0 means that the given {@link BezierCurve}'s control |
|
|
690 |
* points are on a straight line. |
72 |
* |
691 |
* |
73 |
* @return an array containing the inner control points' coordinates |
692 |
* @param c |
|
|
693 |
* @return |
74 |
*/ |
694 |
*/ |
75 |
public double[] getCtrlCoordinates() { |
695 |
private static double distanceToBaseLine(BezierCurve c) { |
76 |
return PointListUtils.getCopy(ctrlCoordinates); |
696 |
Straight3D baseLine = Straight3D.through(c.points[0], |
|
|
697 |
c.points[c.points.length - 1]); |
698 |
|
699 |
if (baseLine == null) { |
700 |
return 0d; |
701 |
} |
77 |
|
702 |
|
|
|
703 |
double maxDistance = 0d; |
704 |
for (int i = 1; i < c.points.length - 1; i++) { |
705 |
maxDistance = Math.max(maxDistance, |
706 |
Math.abs(baseLine.getSignedDistanceCW(c.points[i]))); |
707 |
} |
708 |
|
709 |
return maxDistance; |
78 |
} |
710 |
} |
79 |
|
711 |
|
80 |
/** |
712 |
/** |
81 |
* Returns an array of points representing the inner control points of this |
713 |
* Constructs a new {@link BezierCurve} from the given {@link CubicCurve}. |
82 |
* curve, i.e. excluding the start and end points. In case of s linear |
|
|
83 |
* curve, no control points will be returned, in case of a quadratic curve, |
84 |
* one control point, and so on. |
85 |
* |
714 |
* |
86 |
* @return an array of points with the coordinates of the inner control |
715 |
* @param c |
87 |
* points of this {@link BezierCurve}, i.e. exclusive of the start |
|
|
88 |
* and end point. The number of control points will depend on the |
89 |
* degree ({@link #getDegree()}) of the curve, so in case of a line |
90 |
* (linear curve) the array will be empty, in case of a quadratic |
91 |
* curve, it will be of size <code>1</code>, in case of a cubic |
92 |
* curve of size <code>2</code>, etc.. |
93 |
*/ |
716 |
*/ |
94 |
public Point[] getCtrls() { |
717 |
public BezierCurve(CubicCurve c) { |
95 |
return PointListUtils.toPointsArray(ctrlCoordinates); |
718 |
this(c.getP1(), c.getCtrl1(), c.getCtrl2(), c.getP2()); |
96 |
} |
719 |
} |
97 |
|
720 |
|
98 |
public double getCtrlX(int i) { |
721 |
/** |
99 |
return ctrlCoordinates[2 * i]; |
722 |
* Constructs a new {@link BezierCurve} from the given control point |
|
|
723 |
* coordinates. The coordinates are expected to be in x, y order, i.e. x1, |
724 |
* y1, x2, y2, x3, y3, ... |
725 |
* |
726 |
* @param controlPoints |
727 |
*/ |
728 |
public BezierCurve(double... controlPoints) { |
729 |
this(PointListUtils.toPointsArray(controlPoints)); |
100 |
} |
730 |
} |
101 |
|
731 |
|
102 |
public double getCtrlY(int i) { |
732 |
/** |
103 |
return ctrlCoordinates[2 * i + 1]; |
733 |
* Constructs a new {@link BezierCurve} object from the given control |
|
|
734 |
* points. |
735 |
* |
736 |
* @param controlPoints |
737 |
*/ |
738 |
public BezierCurve(Point... controlPoints) { |
739 |
points = new Vector3D[controlPoints.length]; |
740 |
for (int i = 0; i < points.length; i++) { |
741 |
points[i] = new Vector3D(controlPoints[i].x, controlPoints[i].y, 1); |
742 |
} |
104 |
} |
743 |
} |
105 |
|
744 |
|
106 |
/** |
745 |
/** |
107 |
* Returns the degree of this curve which corresponds to the number of |
746 |
* Constructs a new {@link BezierCurve} from the given |
108 |
* overall control points (including start and end point) used to define the |
747 |
* {@link QuadraticCurve}. |
109 |
* curve. The degree is zero-based, so a line (linear curve) will have |
|
|
110 |
* degree <code>1</code>, a quadratic curve will have degree <code>2</code>, |
111 |
* and so on. <code>1</code> in case of a |
112 |
* |
748 |
* |
113 |
* @return The degree of this {@link ICurve}, which corresponds to the |
749 |
* @param c |
114 |
* zero-based overall number of control points (including start and |
|
|
115 |
* end point) used to define this {@link ICurve}. |
116 |
*/ |
750 |
*/ |
117 |
public int getDegree() { |
751 |
public BezierCurve(QuadraticCurve c) { |
118 |
return getCtrls().length + 1; |
752 |
this(c.getP1(), c.getCtrl(), c.getP2()); |
119 |
} |
753 |
} |
120 |
|
754 |
|
121 |
/** |
755 |
/** |
122 |
* Returns an array of points that represent this {@link BezierCurve}, i.e. |
756 |
* Constructs a new {@link BezierCurve} object from the given control |
123 |
* the start point, the inner control points, and the end points. |
757 |
* points. |
|
|
758 |
* |
759 |
* Note that a Point(2, 3) is represented by a Vector3D(2, 3, 1). So for a |
760 |
* Point(x, y) the corresponding vector is Vector(x, y, 1). |
124 |
* |
761 |
* |
125 |
* @return an array of points representing the control points (including |
762 |
* @param controlPoints |
126 |
* start and end point) of this {@link BezierCurve} |
|
|
127 |
*/ |
763 |
*/ |
128 |
public Point[] getPoints() { |
764 |
private BezierCurve(Vector3D... controlPoints) { |
129 |
Point[] points = new Point[ctrlCoordinates.length / 2 + 2]; |
765 |
points = new Vector3D[controlPoints.length]; |
130 |
points[0] = new Point(x1, y1); |
766 |
for (int i = 0; i < points.length; i++) { |
131 |
points[points.length - 1] = new Point(x2, y2); |
767 |
points[i] = controlPoints[i].getCopy(); |
132 |
for (int i = 1; i < points.length - 1; i++) { |
|
|
133 |
points[i] = new Point(ctrlCoordinates[2 * i - 2], |
134 |
ctrlCoordinates[2 * i - 1]); |
135 |
} |
768 |
} |
136 |
return points; |
|
|
137 |
} |
769 |
} |
138 |
|
770 |
|
139 |
/** |
771 |
/** |
140 |
* {@inheritDoc} |
772 |
* There are three tests, that have to be done. |
|
|
773 |
* |
774 |
* The inside-fat-line-check has to be done for every point. It adjusts the |
775 |
* interval, so that inseparable portions of the curve are detected. |
776 |
* |
777 |
* The other two checks have to be done for every segment. They find |
778 |
* intersections of the curve's convex hull with the fat-line boundaries. |
779 |
* The points of intersection identify points of breakage. |
780 |
* |
781 |
* The starting interval is chosen to be invalid. The individual checks move |
782 |
* the start and end parameter values past to one another. If everything can |
783 |
* be clipped, the resulting interval remains invalid. If the resulting |
784 |
* interval I = [a;b] is valid (a <= b), then the portions [0;a] and [b;1] |
785 |
* of the curve can be clipped away. |
141 |
* |
786 |
* |
142 |
* @see org.eclipse.gef4.geometry.planar.ICurve#getP1() |
787 |
* @param L |
|
|
788 |
* The {@link FatLine} to clip to |
789 |
* @return the new parameter interval for this {@link BezierCurve}. |
143 |
*/ |
790 |
*/ |
144 |
public Point getP1() { |
791 |
private double[] clipTo(FatLine L) { |
145 |
return new Point(x1, y1); |
792 |
double[] interval = new double[] { 1, 0 }; |
|
|
793 |
|
794 |
Point[] D = getConvexHull(genDifferencePoints(L.line)); |
795 |
|
796 |
for (Point p : D) { |
797 |
if (Double.isNaN(p.y) || L.dmin <= p.y && p.y <= L.dmax) { |
798 |
moveInterval(interval, p.x); |
799 |
} |
800 |
} |
801 |
|
802 |
for (Line seg : PointListUtils.toSegmentsArray(D, true)) { |
803 |
if (seg.getP1().y < L.dmin != seg.getP2().y < L.dmin) { |
804 |
double x = intersectXAxisParallel(seg.getP1(), seg.getP2(), |
805 |
L.dmin); |
806 |
moveInterval(interval, x); |
807 |
} |
808 |
if (seg.getP1().y < L.dmax != seg.getP2().y < L.dmax) { |
809 |
double x = intersectXAxisParallel(seg.getP1(), seg.getP2(), |
810 |
L.dmax); |
811 |
moveInterval(interval, x); |
812 |
} |
813 |
} |
814 |
|
815 |
return interval; |
146 |
} |
816 |
} |
147 |
|
817 |
|
148 |
/** |
818 |
/** |
149 |
* {@inheritDoc} |
819 |
* Returns true if the given {@link Point} lies on this {@link BezierCurve}. |
|
|
820 |
* Returns false, otherwise. |
150 |
* |
821 |
* |
151 |
* @see org.eclipse.gef4.geometry.planar.ICurve#getP2() |
822 |
* @param p |
|
|
823 |
* the {@link Point} to test for containment |
824 |
* @return true if the {@link Point} is contained, false otherwise |
152 |
*/ |
825 |
*/ |
|
|
826 |
public boolean contains(final Point p) { |
827 |
if (p == null) { |
828 |
return false; |
829 |
} |
830 |
|
831 |
return containmentParameter(this, new double[] { 0, 1 }, p); |
832 |
} |
833 |
|
834 |
private void findEndPointIntersections(IntervalPair ip, |
835 |
Set<IntervalPair> endPointIntervalPairs, Set<Point> intersections) { |
836 |
final double CHUNK_SHIFT_EPSILON = PrecisionUtils |
837 |
.calculateFraction(CHUNK_SHIFT); |
838 |
|
839 |
Point poi = ip.p.points[0].toPoint(); |
840 |
double[] interval = new double[] { 0, 1 }; |
841 |
if (containmentParameter(ip.q, interval, poi)) { |
842 |
ip.pi.a = CHUNK_SHIFT_EPSILON; |
843 |
interval[0] = (interval[0] + interval[1]) / 2; |
844 |
interval[1] = interval[0] + CHUNK_SHIFT_EPSILON / 2; |
845 |
interval[0] = interval[0] - CHUNK_SHIFT_EPSILON / 2; |
846 |
endPointIntervalPairs.add(new IntervalPair(ip.p, new Interval(0, |
847 |
ip.pi.a), ip.q, new Interval(interval))); |
848 |
intersections.add(poi); |
849 |
} |
850 |
|
851 |
poi = ip.p.points[ip.p.points.length - 1].toPoint(); |
852 |
interval[0] = 0; |
853 |
interval[1] = 1; |
854 |
if (containmentParameter(ip.q, interval, poi)) { |
855 |
ip.pi.b = 1 - CHUNK_SHIFT_EPSILON; |
856 |
interval[0] = (interval[0] + interval[1]) / 2; |
857 |
interval[1] = interval[0] + CHUNK_SHIFT_EPSILON / 2; |
858 |
interval[0] = interval[0] - CHUNK_SHIFT_EPSILON / 2; |
859 |
endPointIntervalPairs.add(new IntervalPair(ip.p, new Interval( |
860 |
ip.pi.b, 1), ip.q, new Interval(interval))); |
861 |
intersections.add(poi); |
862 |
} |
863 |
|
864 |
poi = ip.q.points[0].toPoint(); |
865 |
interval[0] = 0; |
866 |
interval[1] = 1; |
867 |
if (containmentParameter(ip.p, interval, poi)) { |
868 |
ip.qi.a = CHUNK_SHIFT_EPSILON; |
869 |
interval[0] = (interval[0] + interval[1]) / 2; |
870 |
interval[1] = interval[0] + CHUNK_SHIFT_EPSILON / 2; |
871 |
interval[0] = interval[0] - CHUNK_SHIFT_EPSILON / 2; |
872 |
endPointIntervalPairs.add(new IntervalPair(ip.p, new Interval( |
873 |
interval), ip.q, new Interval(0, ip.qi.a))); |
874 |
intersections.add(poi); |
875 |
} |
876 |
|
877 |
poi = ip.q.points[ip.q.points.length - 1].toPoint(); |
878 |
interval[0] = 0; |
879 |
interval[1] = 1; |
880 |
if (containmentParameter(ip.p, interval, poi)) { |
881 |
ip.qi.b = 1 - CHUNK_SHIFT_EPSILON; |
882 |
interval[0] = (interval[0] + interval[1]) / 2; |
883 |
interval[1] = interval[0] + CHUNK_SHIFT_EPSILON / 2; |
884 |
interval[0] = interval[0] - CHUNK_SHIFT_EPSILON / 2; |
885 |
endPointIntervalPairs.add(new IntervalPair(ip.p, new Interval( |
886 |
interval), ip.q, new Interval(ip.qi.b, 1))); |
887 |
intersections.add(poi); |
888 |
} |
889 |
} |
890 |
|
891 |
private Point findExtreme(IPointCmp cmp) { |
892 |
return findExtreme(cmp, Interval.getFull()); |
893 |
} |
894 |
|
895 |
private Point findExtreme(IPointCmp cmp, Interval iStart) { |
896 |
Stack<Interval> parts = new Stack<Interval>(); |
897 |
parts.push(iStart); |
898 |
|
899 |
Point xtreme = getHC(iStart.a).toPoint(); |
900 |
|
901 |
while (!parts.isEmpty()) { |
902 |
Interval i = parts.pop(); |
903 |
BezierCurve clipped = getClipped(i.a, i.b); |
904 |
|
905 |
Point sp = clipped.points[0].toPoint(); |
906 |
xtreme = cmp.pIsBetterThanQ(sp, xtreme) ? sp : xtreme; |
907 |
Point ep = clipped.points[clipped.points.length - 1].toPoint(); |
908 |
xtreme = cmp.pIsBetterThanQ(ep, xtreme) ? ep : xtreme; |
909 |
|
910 |
boolean everythingWorse = true; |
911 |
for (int j = 1; j < clipped.points.length - 1; j++) { |
912 |
if (!cmp.pIsBetterThanQ(xtreme, clipped.points[j].toPoint())) { |
913 |
everythingWorse = false; |
914 |
break; |
915 |
} |
916 |
} |
917 |
|
918 |
if (everythingWorse) { |
919 |
continue; |
920 |
} |
921 |
|
922 |
// split interval |
923 |
double im = i.getMid(); |
924 |
parts.push(new Interval(im, i.b)); |
925 |
parts.push(new Interval(i.a, im)); |
926 |
} |
927 |
|
928 |
return xtreme; |
929 |
} |
930 |
|
931 |
/** |
932 |
* Find intersection interval chunks. The chunks are not very precise. We |
933 |
* will refine them later. |
934 |
* |
935 |
* @param ip |
936 |
* @param intervalPairs |
937 |
* @param intersections |
938 |
*/ |
939 |
private void findIntersectionChunks(IntervalPair ip, |
940 |
Set<IntervalPair> intervalPairs, Set<Point> intersections) { |
941 |
if (ip.converges(CHUNK_SHIFT)) { |
942 |
intervalPairs.add(ip.getCopy()); |
943 |
return; |
944 |
} |
945 |
|
946 |
BezierCurve pClipped = ip.getPClipped(); |
947 |
BezierCurve qClipped = ip.getQClipped(); |
948 |
|
949 |
// construct "parallel" and "orthogonal" fat lines |
950 |
FatLine L1 = FatLine.from(qClipped, PARALLEL); |
951 |
FatLine L2 = FatLine.from(qClipped, ORTHOGONAL); |
952 |
|
953 |
// curve implosion check |
954 |
if (L1 == null || L2 == null) { |
955 |
// q is degenerated |
956 |
Point poi = ip.q.getHC(ip.qi.getMid()).toPoint(); |
957 |
if (ip.p.contains(poi)) { |
958 |
intersections.add(poi); |
959 |
} |
960 |
return; |
961 |
} |
962 |
|
963 |
// clip to the fat lines |
964 |
Interval interval = new Interval(pClipped.clipTo(L1)); |
965 |
Interval intervalOrtho = new Interval(pClipped.clipTo(L2)); |
966 |
|
967 |
// pick smaller interval range |
968 |
interval = Interval.min(interval, intervalOrtho); |
969 |
|
970 |
// re-calculate s and e from the clipped interval |
971 |
double ratio = ip.pi.scaleTo(interval); |
972 |
|
973 |
if (ratio < 0) { |
974 |
// no more intersections |
975 |
return; |
976 |
} else if (ratio > 0.8) { |
977 |
/* |
978 |
* Split longer curve and find intersections for both halves. Add an |
979 |
* unrecognizable fraction to the beginning of the second parameter |
980 |
* interval, so that only one of the getIntersection() calls can |
981 |
* converge in the middle. |
982 |
*/ |
983 |
if (ip.isPLonger()) { |
984 |
IntervalPair[] nip = ip.getPSplit(); |
985 |
findIntersectionChunks(nip[0], intervalPairs, intersections); |
986 |
findIntersectionChunks(nip[1], intervalPairs, intersections); |
987 |
} else { |
988 |
IntervalPair[] nip = ip.getQSplit(); |
989 |
findIntersectionChunks(nip[0], intervalPairs, intersections); |
990 |
findIntersectionChunks(nip[1], intervalPairs, intersections); |
991 |
} |
992 |
|
993 |
return; |
994 |
} else { |
995 |
findIntersectionChunks(ip.getSwapped(), intervalPairs, |
996 |
intersections); |
997 |
} |
998 |
} |
999 |
|
1000 |
/** |
1001 |
* This routine is only called for an interval that has been detected to |
1002 |
* contain a single tangential point of intersection. We do now try to find |
1003 |
* it. |
1004 |
* |
1005 |
* @param ipIO |
1006 |
* @param intervalPairs |
1007 |
* @param intersections |
1008 |
*/ |
1009 |
private Point findSinglePreciseIntersection(IntervalPair ipIO) { |
1010 |
Stack<IntervalPair> partStack = new Stack<IntervalPair>(); |
1011 |
partStack.push(ipIO); |
1012 |
|
1013 |
while (!partStack.isEmpty()) { |
1014 |
IntervalPair ip = partStack.pop(); |
1015 |
|
1016 |
if (ip.converges()) { |
1017 |
// TODO: do another clipping algorithm here. the one that |
1018 |
// uses control bounds. |
1019 |
for (Point pp : ip.p.toPoints(ip.pi)) { |
1020 |
for (Point qp : ip.q.toPoints(ip.qi)) { |
1021 |
if (pp.equals(qp)) { |
1022 |
copyIntervalPair(ipIO, ip); |
1023 |
return pp; |
1024 |
} |
1025 |
} |
1026 |
} |
1027 |
continue; |
1028 |
} |
1029 |
|
1030 |
BezierCurve pClipped = ip.getPClipped(); |
1031 |
BezierCurve qClipped = ip.getQClipped(); |
1032 |
|
1033 |
// construct "parallel" and "orthogonal" fat lines |
1034 |
FatLine L1 = FatLine.from(qClipped, PARALLEL); |
1035 |
FatLine L2 = FatLine.from(qClipped, ORTHOGONAL); |
1036 |
|
1037 |
// curve implosion check |
1038 |
if (L1 == null || L2 == null) { |
1039 |
// q is degenerated |
1040 |
Point poi = ip.q.getHC(ip.qi.getMid()).toPoint(); |
1041 |
if (ip.p.contains(poi)) { |
1042 |
copyIntervalPair(ipIO, ip); |
1043 |
return poi; |
1044 |
} |
1045 |
continue; |
1046 |
} |
1047 |
|
1048 |
// clip to the fat lines |
1049 |
Interval interval = new Interval(pClipped.clipTo(L1)); |
1050 |
Interval intervalOrtho = new Interval(pClipped.clipTo(L2)); |
1051 |
|
1052 |
// pick smaller interval range |
1053 |
interval = Interval.min(interval, intervalOrtho); |
1054 |
|
1055 |
// re-calculate s and e from the clipped interval |
1056 |
double ratio = ip.pi.scaleTo(interval); |
1057 |
|
1058 |
if (ratio < 0) { |
1059 |
// no more intersections |
1060 |
continue; |
1061 |
} else if (ratio > 0.8) { |
1062 |
/* |
1063 |
* Split longer curve and find intersections for both halves. |
1064 |
* Add an unrecognizable fraction to the beginning of the second |
1065 |
* parameter interval, so that only one of the getIntersection() |
1066 |
* calls can converge in the middle. |
1067 |
*/ |
1068 |
IntervalPair[] nip = ip.isPLonger() ? ip.getPSplit() : ip |
1069 |
.getQSplit(); |
1070 |
partStack.push(nip[1]); |
1071 |
partStack.push(nip[0]); |
1072 |
} else { |
1073 |
partStack.push(ip.getSwapped()); |
1074 |
} |
1075 |
} |
1076 |
|
1077 |
return null; |
1078 |
} |
1079 |
|
1080 |
/** |
1081 |
* Generates the difference points of this {@link BezierCurve} to the given |
1082 |
* line. |
1083 |
* |
1084 |
* The difference points are the control points of a Bezier curve that |
1085 |
* yields the signed difference of the point on this curve at a determinate |
1086 |
* parameter value to the given line. |
1087 |
* |
1088 |
* @param line |
1089 |
* @return the difference curve's control points |
1090 |
*/ |
1091 |
private Vector3D[] genDifferencePoints(Straight3D line) { |
1092 |
Vector3D[] D = new Vector3D[points.length]; |
1093 |
for (int i = 0; i < points.length; i++) { |
1094 |
double y = line.getSignedDistanceCW(points[i]); |
1095 |
D[i] = new Vector3D((double) (i) / (double) (points.length - 1), y, |
1096 |
1); |
1097 |
} |
1098 |
return D; |
1099 |
} |
1100 |
|
1101 |
/** |
1102 |
* Computes the {@link Point} on this {@link BezierCurve} at parameter value |
1103 |
* t, which is expected to lie in the range [0;1]. |
1104 |
* |
1105 |
* @param t |
1106 |
* parameter value |
1107 |
* @return the {@link Point} on this {@link BezierCurve} at the given |
1108 |
* parameter value |
1109 |
*/ |
1110 |
public Point get(double t) { |
1111 |
return getHC(t).toPoint(); |
1112 |
} |
1113 |
|
1114 |
/** |
1115 |
* @see IGeometry#getBounds() |
1116 |
* @return the bounds of this {@link BezierCurve}. |
1117 |
*/ |
1118 |
public Rectangle getBounds() { |
1119 |
double xmin = findExtreme(xminCmp).x; |
1120 |
double xmax = findExtreme(xmaxCmp).x; |
1121 |
double ymin = findExtreme(yminCmp).y; |
1122 |
double ymax = findExtreme(ymaxCmp).y; |
1123 |
|
1124 |
return new Rectangle(new Point(xmin, ymin), new Point(xmax, ymax)); |
1125 |
} |
1126 |
|
1127 |
/** |
1128 |
* Returns a new {@link BezierCurve} object representing this bezier curve |
1129 |
* on the interval [s;e]. |
1130 |
* |
1131 |
* @param s |
1132 |
* @param e |
1133 |
* @return a new {@link BezierCurve} object representing this bezier curve |
1134 |
* on the interval [s;e] |
1135 |
*/ |
1136 |
public BezierCurve getClipped(double s, double e) { |
1137 |
if (s == 1) { |
1138 |
return new BezierCurve(points[points.length - 1]); |
1139 |
} |
1140 |
BezierCurve right = split(s)[1]; |
1141 |
double rightT2 = (e - s) / (1 - s); |
1142 |
return right.split(rightT2)[0]; |
1143 |
} |
1144 |
|
1145 |
/** |
1146 |
* Returns the bounds of the control polygon of this {@link BezierCurve} . |
1147 |
* |
1148 |
* @return a {@link Rectangle} representing the bounds of the control |
1149 |
* polygon of this {@link BezierCurve} |
1150 |
*/ |
1151 |
public Rectangle getControlBounds() { |
1152 |
Point[] realPoints = getPoints(); |
1153 |
|
1154 |
double xmin = realPoints[0].x, xmax = realPoints[0].x, ymin = realPoints[0].y, ymax = realPoints[0].y; |
1155 |
|
1156 |
for (int i = 1; i < realPoints.length; i++) { |
1157 |
if (realPoints[i].x < xmin) { |
1158 |
xmin = realPoints[i].x; |
1159 |
} else if (realPoints[i].x > xmax) { |
1160 |
xmax = realPoints[i].x; |
1161 |
} |
1162 |
|
1163 |
if (realPoints[i].y < ymin) { |
1164 |
ymin = realPoints[i].y; |
1165 |
} else if (realPoints[i].y > ymax) { |
1166 |
ymax = realPoints[i].y; |
1167 |
} |
1168 |
} |
1169 |
|
1170 |
return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); |
1171 |
} |
1172 |
|
1173 |
public BezierCurve getCopy() { |
1174 |
return new BezierCurve(points); |
1175 |
} |
1176 |
|
1177 |
/** |
1178 |
* Computes the hodograph (first parametric derivative) of this |
1179 |
* {@link BezierCurve}. |
1180 |
* |
1181 |
* @return the hodograph (first parametric derivative) of this |
1182 |
* {@link BezierCurve} |
1183 |
*/ |
1184 |
public BezierCurve getDerivative() { |
1185 |
Vector3D[] controlPoints = new Vector3D[points.length - 1]; |
1186 |
|
1187 |
for (int i = 0; i < controlPoints.length; i++) { |
1188 |
controlPoints[i] = points[i + 1].getSubtracted(points[i]) |
1189 |
.getScaled(points.length - 1); |
1190 |
// ignore z coordinate: |
1191 |
controlPoints[i].z = 1; |
1192 |
} |
1193 |
|
1194 |
return new BezierCurve(controlPoints); |
1195 |
} |
1196 |
|
1197 |
/** |
1198 |
* Returns the {@link Point} at the given parameter value t. |
1199 |
* |
1200 |
* @param t |
1201 |
* Parameter value |
1202 |
* @return {@link Point} at parameter value t |
1203 |
*/ |
1204 |
private Vector3D getHC(double t) { |
1205 |
if (t < 0 || t > 1) { |
1206 |
throw new IllegalArgumentException("t out of range: " + t); |
1207 |
} |
1208 |
|
1209 |
// using horner's scheme: |
1210 |
int n = points.length; |
1211 |
if (n < 1) { |
1212 |
return null; |
1213 |
} |
1214 |
|
1215 |
double bn = 1, tn = 1, d = 1d - t; |
1216 |
Vector3D pn = points[0].getScaled(bn * tn); |
1217 |
for (int i = 1; i < n; i++) { |
1218 |
bn = bn * (n - i) / i; |
1219 |
tn = tn * t; |
1220 |
pn = pn.getScaled(d).getAdded(points[i].getScaled(bn * tn)); |
1221 |
} |
1222 |
|
1223 |
return pn; |
1224 |
} |
1225 |
|
1226 |
/** |
1227 |
* Computes {@link IntervalPair}s which do reflect points of intersection |
1228 |
* between this and the given other {@link BezierCurve}. Each |
1229 |
* {@link IntervalPair} reflects a single point of intersection. |
1230 |
* |
1231 |
* For every {@link IntervalPair} a point of intersection is inserted into |
1232 |
* the given {@link Set} of {@link Point}s. |
1233 |
* |
1234 |
* If there are infinite {@link Point}s of intersection, i.e. the curves do |
1235 |
* overlap, an empty set is returned. (see |
1236 |
* {@link BezierCurve#overlaps(BezierCurve)}) |
1237 |
* |
1238 |
* @param other |
1239 |
* @param intersections |
1240 |
* the {@link Point}-{@link Set} where points of intersection are |
1241 |
* inserted |
1242 |
* @return for a finite number of intersection {@link Point}s, a {@link Set} |
1243 |
* of {@link IntervalPair}s is returned where every |
1244 |
* {@link IntervalPair} represents a single {@link Point} of |
1245 |
* intersection. For an infinite number of intersection |
1246 |
* {@link Point}s, an empty {@link Set} is returned. |
1247 |
*/ |
1248 |
public Set<IntervalPair> getIntersectionIntervalPairs(BezierCurve other, |
1249 |
Set<Point> intersections) { |
1250 |
Set<IntervalPair> intervalPairs = new HashSet<IntervalPair>(); |
1251 |
Set<IntervalPair> endPointIntervalPairs = new HashSet<IntervalPair>(); |
1252 |
|
1253 |
IntervalPair ip = new IntervalPair(this, Interval.getFull(), other, |
1254 |
Interval.getFull()); |
1255 |
|
1256 |
findEndPointIntersections(ip, endPointIntervalPairs, intersections); |
1257 |
findIntersectionChunks(ip, intervalPairs, intersections); |
1258 |
normalizeIntervalPairs(intervalPairs.toArray(new IntervalPair[] {})); |
1259 |
IntervalPair[] clusters = clusterChunks( |
1260 |
intervalPairs.toArray(new IntervalPair[] {}), 0); |
1261 |
|
1262 |
if (isOverlap(clusters, |
1263 |
endPointIntervalPairs.toArray(new IntervalPair[] {})).p != null) { |
1264 |
return new HashSet<IntervalPair>(0); |
1265 |
} |
1266 |
|
1267 |
Set<IntervalPair> results = new HashSet<IntervalPair>(); |
1268 |
results.addAll(endPointIntervalPairs); |
1269 |
|
1270 |
outer: for (IntervalPair cluster : clusters) { |
1271 |
for (IntervalPair epip : endPointIntervalPairs) { |
1272 |
if (isNextTo(cluster, epip, CHUNK_SHIFT)) { |
1273 |
continue outer; |
1274 |
} |
1275 |
} |
1276 |
|
1277 |
// a.t.m. assume for every cluster just a single point of |
1278 |
// intersection: |
1279 |
Point poi = findSinglePreciseIntersection(cluster); |
1280 |
if (poi != null) { |
1281 |
if (cluster.converges()) { |
1282 |
results.add(cluster.getCopy()); |
1283 |
} else { |
1284 |
intersections.add(poi); |
1285 |
} |
1286 |
} |
1287 |
} |
1288 |
|
1289 |
return results; |
1290 |
} |
1291 |
|
1292 |
/** |
1293 |
* @see BezierCurve#findEndPointIntersections(IntervalPair, Set, Set) |
1294 |
* @see BezierCurve#findIntersectionChunks(IntervalPair, Set, Set) |
1295 |
* |
1296 |
* @param other |
1297 |
* @return the points of intersection of this {@link BezierCurve} and the |
1298 |
* given other {@link BezierCurve}. |
1299 |
*/ |
1300 |
public Point[] getIntersections(BezierCurve other) { |
1301 |
Set<Point> intersections = new HashSet<Point>(); |
1302 |
Set<IntervalPair> intervalPairs = new HashSet<IntervalPair>(); |
1303 |
Set<IntervalPair> endPointIntervalPairs = new HashSet<IntervalPair>(); |
1304 |
|
1305 |
IntervalPair ip = new IntervalPair(this, Interval.getFull(), other, |
1306 |
Interval.getFull()); |
1307 |
|
1308 |
findEndPointIntersections(ip, endPointIntervalPairs, intersections); |
1309 |
findIntersectionChunks(ip, intervalPairs, intersections); |
1310 |
normalizeIntervalPairs(intervalPairs.toArray(new IntervalPair[] {})); |
1311 |
IntervalPair[] clusters = clusterChunks( |
1312 |
intervalPairs.toArray(new IntervalPair[] {}), 0); |
1313 |
|
1314 |
if (isOverlap(clusters, |
1315 |
endPointIntervalPairs.toArray(new IntervalPair[] {})).p != null) { |
1316 |
return new Point[] {}; |
1317 |
} |
1318 |
|
1319 |
outer: for (IntervalPair cluster : clusters) { |
1320 |
for (IntervalPair epip : endPointIntervalPairs) { |
1321 |
if (isNextTo(cluster, epip, CHUNK_SHIFT)) { |
1322 |
continue outer; |
1323 |
} |
1324 |
} |
1325 |
|
1326 |
// a.t.m. assume for every cluster just a single point of |
1327 |
// intersection: |
1328 |
Point poi = findSinglePreciseIntersection(cluster); |
1329 |
if (poi != null) { |
1330 |
intersections.add(poi); |
1331 |
} |
1332 |
} |
1333 |
|
1334 |
return intersections.toArray(new Point[] {}); |
1335 |
} |
1336 |
|
1337 |
public final Point[] getIntersections(ICurve curve) { |
1338 |
Set<Point> intersections = new HashSet<Point>(); |
1339 |
|
1340 |
for (BezierCurve c : curve.toBezier()) { |
1341 |
intersections.addAll(Arrays.asList(getIntersections(c))); |
1342 |
} |
1343 |
|
1344 |
return intersections.toArray(new Point[] {}); |
1345 |
} |
1346 |
|
1347 |
/** |
1348 |
* Computes the overlap of this {@link BezierCurve} and the given other |
1349 |
* {@link BezierCurve}. If no overlap exists, <code>null</code> is returned. |
1350 |
* Otherwise, a {@link BezierCurve}, representing the overlap, is returned. |
1351 |
* |
1352 |
* An overlap is identified by an infinite number of intersection points. |
1353 |
* |
1354 |
* @param other |
1355 |
* @return a {@link BezierCurve} representing the overlap of this and the |
1356 |
* given other {@link BezierCurve} if an overlap exists, otherwise |
1357 |
* <code>null</code>. |
1358 |
*/ |
1359 |
public BezierCurve getOverlap(BezierCurve other) { |
1360 |
Set<Point> intersections = new HashSet<Point>(); |
1361 |
Set<IntervalPair> intervalPairs = new HashSet<IntervalPair>(); |
1362 |
Set<IntervalPair> endPointIntervalPairs = new HashSet<IntervalPair>(); |
1363 |
|
1364 |
IntervalPair ip = new IntervalPair(this, Interval.getFull(), other, |
1365 |
Interval.getFull()); |
1366 |
|
1367 |
findEndPointIntersections(ip, endPointIntervalPairs, intersections); |
1368 |
findIntersectionChunks(ip, intervalPairs, intersections); |
1369 |
IntervalPair[] clusters = clusterChunks( |
1370 |
intervalPairs.toArray(new IntervalPair[] {}), 0); |
1371 |
|
1372 |
IntervalPair overlap = isOverlap(clusters, |
1373 |
endPointIntervalPairs.toArray(new IntervalPair[] {})); |
1374 |
|
1375 |
if (overlap.p != null) { |
1376 |
return overlap.getPClipped(); |
1377 |
} |
1378 |
return null; |
1379 |
} |
1380 |
|
1381 |
public Point getP1() { |
1382 |
return points[0].toPoint(); |
1383 |
} |
1384 |
|
153 |
public Point getP2() { |
1385 |
public Point getP2() { |
154 |
return new Point(x2, y2); |
1386 |
return points[points.length - 1].toPoint(); |
155 |
} |
1387 |
} |
156 |
|
1388 |
|
157 |
/** |
1389 |
/** |
158 |
* {@inheritDoc} |
1390 |
* @param p |
|
|
1391 |
* @return -1 if p not on curve, otherwise the corresponding parameter |
1392 |
* value. |
1393 |
*/ |
1394 |
public double getParameterAt(Point p) { |
1395 |
if (p == null) { |
1396 |
// return -1; |
1397 |
throw new NullPointerException("Point may not be null."); |
1398 |
} |
1399 |
|
1400 |
double[] interval = new double[] { 0, 1 }; |
1401 |
if (containmentParameter(this, interval, p)) { |
1402 |
return (interval[0] + interval[1]) / 2; |
1403 |
} else { |
1404 |
// return -1; |
1405 |
throw new IllegalArgumentException( |
1406 |
"The given point does not lie on the curve."); |
1407 |
} |
1408 |
} |
1409 |
|
1410 |
/** |
1411 |
* Returns the {@link Point} at index i in the points array of this |
1412 |
* {@link BezierCurve}. The start {@link Point} is at index 0, the first |
1413 |
* handle-{@link Point} is at index 1, etc. |
159 |
* |
1414 |
* |
160 |
* @see org.eclipse.gef4.geometry.planar.ICurve#getX1() |
1415 |
* @param i |
|
|
1416 |
* the index of a {@link Point} in the points array of this |
1417 |
* {@link BezierCurve} |
1418 |
* @return the {@link Point} at the given index in the points array of this |
1419 |
* {@link BezierCurve} |
161 |
*/ |
1420 |
*/ |
162 |
public double getX1() { |
1421 |
public Point getPoint(int i) { |
163 |
return x1; |
1422 |
if (i < 0 || i >= points.length) { |
|
|
1423 |
throw new IllegalArgumentException( |
1424 |
"getPoint(" |
1425 |
+ i |
1426 |
+ "): You can only index this BezierCurve's points from 0 to " |
1427 |
+ (points.length - 1) + "."); |
1428 |
} |
1429 |
return points[i].toPoint(); |
164 |
} |
1430 |
} |
165 |
|
1431 |
|
166 |
/** |
1432 |
/** |
167 |
* {@inheritDoc} |
1433 |
* Computes the real planar {@link Point}s for this {@link BezierCurve}. |
168 |
* |
1434 |
* |
169 |
* @see org.eclipse.gef4.geometry.planar.ICurve#getX2() |
1435 |
* @return the real planar {@link Point}s for this {@link BezierCurve} |
170 |
*/ |
1436 |
*/ |
171 |
public double getX2() { |
1437 |
public Point[] getPoints() { |
172 |
return x2; |
1438 |
Point[] realPoints = new Point[points.length]; |
|
|
1439 |
for (int i = 0; i < points.length; i++) { |
1440 |
realPoints[i] = points[i].toPoint(); |
1441 |
} |
1442 |
return realPoints; |
173 |
} |
1443 |
} |
174 |
|
1444 |
|
175 |
/** |
1445 |
/** |
176 |
* {@inheritDoc} |
1446 |
* Returns a copy of this {@link BezierCurve}'s points. |
177 |
* |
1447 |
* |
178 |
* @see org.eclipse.gef4.geometry.planar.ICurve#getY1() |
1448 |
* @return a copy of this {@link BezierCurve}'s points |
179 |
*/ |
1449 |
*/ |
180 |
public double getY1() { |
1450 |
private Vector3D[] getPointsCopy() { |
181 |
return y1; |
1451 |
Vector3D[] copy = new Vector3D[points.length]; |
|
|
1452 |
for (int i = 0; i < points.length; i++) { |
1453 |
copy[i] = points[i].getCopy(); |
1454 |
} |
1455 |
return copy; |
182 |
} |
1456 |
} |
183 |
|
1457 |
|
184 |
/** |
1458 |
/** |
185 |
* {@inheritDoc} |
1459 |
* Creates a new {@link BezierCurve} with all points translated by the given |
|
|
1460 |
* {@link Point}. |
186 |
* |
1461 |
* |
187 |
* @see org.eclipse.gef4.geometry.planar.ICurve#getY2() |
1462 |
* @param p |
|
|
1463 |
* @return a new {@link BezierCurve} with all points translated by the given |
1464 |
* {@link Point} |
188 |
*/ |
1465 |
*/ |
|
|
1466 |
public BezierCurve getTranslated(Point p) { |
1467 |
Point[] translated = new Point[points.length]; |
1468 |
|
1469 |
for (int i = 0; i < translated.length; i++) { |
1470 |
translated[i] = points[i].toPoint().getTranslated(p); |
1471 |
} |
1472 |
|
1473 |
return new BezierCurve(translated); |
1474 |
} |
1475 |
|
1476 |
public double getX1() { |
1477 |
return getP1().x; |
1478 |
} |
1479 |
|
1480 |
public double getX2() { |
1481 |
return getP2().x; |
1482 |
} |
1483 |
|
1484 |
public double getY1() { |
1485 |
return getP1().y; |
1486 |
} |
1487 |
|
189 |
public double getY2() { |
1488 |
public double getY2() { |
190 |
return y2; |
1489 |
return getP2().y; |
191 |
} |
1490 |
} |
192 |
|
1491 |
|
193 |
protected void setCtrl(int i, Point p) { |
1492 |
public final boolean intersects(ICurve c) { |
194 |
setCtrlX(i, p.x); |
1493 |
return getIntersections(c).length > 0; |
195 |
setCtrlY(i, p.y); |
|
|
196 |
} |
1494 |
} |
197 |
|
1495 |
|
198 |
public void setCtrls(Point... ctrls) { |
1496 |
/** |
199 |
ctrlCoordinates = PointListUtils.toCoordinatesArray(ctrls); |
1497 |
* Moves the interval's start and end values. The start value is set to x if |
|
|
1498 |
* x is smaller than the start value. The end value is set to x if x is |
1499 |
* greater than the end value. |
1500 |
* |
1501 |
* @param interval |
1502 |
* The current interval |
1503 |
* @param x |
1504 |
*/ |
1505 |
private void moveInterval(double[] interval, double x) { |
1506 |
if (interval[0] > x) { |
1507 |
interval[0] = x; |
1508 |
} |
1509 |
if (interval[1] < x) { |
1510 |
interval[1] = x; |
1511 |
} |
1512 |
} |
1513 |
|
1514 |
/** |
1515 |
* Returns <code>true</code> if this and the given other {@link BezierCurve} |
1516 |
* do overlap. Otherwise, returns <code>false</cdoe>. |
1517 |
* |
1518 |
* @param other |
1519 |
* @return <code>true</code> if this and the given other {@link BezierCurve} |
1520 |
* overlap, otherwise <code>false</code> |
1521 |
*/ |
1522 |
public boolean overlaps(BezierCurve other) { |
1523 |
Set<Point> intersections = new HashSet<Point>(); |
1524 |
Set<IntervalPair> intervalPairs = new HashSet<IntervalPair>(); |
1525 |
Set<IntervalPair> endPointIntervalPairs = new HashSet<IntervalPair>(); |
1526 |
|
1527 |
IntervalPair ip = new IntervalPair(this, Interval.getFull(), other, |
1528 |
Interval.getFull()); |
1529 |
|
1530 |
findEndPointIntersections(ip, endPointIntervalPairs, intersections); |
1531 |
findIntersectionChunks(ip, intervalPairs, intersections); |
1532 |
IntervalPair[] clusters = clusterChunks( |
1533 |
intervalPairs.toArray(new IntervalPair[] {}), 0); |
1534 |
|
1535 |
return isOverlap(clusters, |
1536 |
endPointIntervalPairs.toArray(new IntervalPair[] {})).p != null; |
200 |
} |
1537 |
} |
201 |
|
1538 |
|
202 |
protected void setCtrlX(int i, double x) { |
1539 |
public final boolean overlaps(ICurve c) { |
203 |
// TODO: enlarge array if its too small |
1540 |
for (BezierCurve seg : c.toBezier()) { |
204 |
ctrlCoordinates[2 * i] = x; |
1541 |
if (overlaps(seg)) { |
|
|
1542 |
return true; |
1543 |
} |
1544 |
} |
1545 |
return false; |
205 |
} |
1546 |
} |
206 |
|
1547 |
|
207 |
protected void setCtrlY(int i, double y) { |
1548 |
/** |
208 |
// TODO: enlarge array if its too small |
1549 |
* @param alpha |
209 |
ctrlCoordinates[2 * i + 1] = y; |
1550 |
* @param center |
|
|
1551 |
*/ |
1552 |
public void rotateCCW(Angle alpha, Point center) { |
1553 |
for (int i = 0; i < points.length; i++) { |
1554 |
points[i] = new Vector3D(new Vector(points[i].toPoint() |
1555 |
.getTranslated(center.getNegated())).getRotatedCCW(alpha) |
1556 |
.toPoint().getTranslated(center)); |
1557 |
} |
210 |
} |
1558 |
} |
211 |
|
1559 |
|
212 |
/** |
1560 |
/** |
213 |
* Sets the start {@link Point} of this {@link BezierCurve} to the given |
1561 |
* Sets the start {@link Point} of this {@link BezierCurve} to the given |
214 |
* {@link Point} p1. |
1562 |
* {@link Point}. |
215 |
* |
1563 |
* |
216 |
* @param p1 |
1564 |
* @param p1 |
217 |
* the new start {@link Point} |
1565 |
* the new start {@link Point} of this {@link BezierCurve} |
218 |
*/ |
1566 |
*/ |
219 |
public void setP1(Point p1) { |
1567 |
public void setP1(Point p1) { |
220 |
this.x1 = p1.x; |
1568 |
setPoint(0, p1); |
221 |
this.y1 = p1.y; |
|
|
222 |
} |
1569 |
} |
223 |
|
1570 |
|
224 |
/** |
1571 |
/** |
225 |
* Sets the end {@link Point} of this {@link BezierCurve} to the given |
1572 |
* Sets the end {@link Point} of this {@link BezierCurve} to the given |
226 |
* {@link Point} p2. |
1573 |
* {@link Point}. |
227 |
* |
1574 |
* |
228 |
* @param p2 |
1575 |
* @param p2 |
229 |
* the new end {@link Point} |
1576 |
* the new end {@link Point} of this {@link BezierCurve} |
230 |
*/ |
1577 |
*/ |
231 |
public void setP2(Point p2) { |
1578 |
public void setP2(Point p2) { |
232 |
this.x2 = p2.x; |
1579 |
setPoint(points.length - 1, p2); |
233 |
this.y2 = p2.y; |
1580 |
} |
|
|
1581 |
|
1582 |
/** |
1583 |
* Sets the {@link Point} at index i in this {@link BezierCurve}'s |
1584 |
* {@link Point}s array to the given {@link Point}. The start {@link Point} |
1585 |
* 's index is 0. The index of the first handle {@link Point} is 1, etc. |
1586 |
* |
1587 |
* @param i |
1588 |
* the index in this {@link BezierCurve}'s {@link Point}s array |
1589 |
* @param p |
1590 |
* the new {@link Point} for the given index |
1591 |
*/ |
1592 |
public void setPoint(int i, Point p) { |
1593 |
if (i < 0 || i >= points.length) { |
1594 |
throw new IllegalArgumentException( |
1595 |
"setPoint(" |
1596 |
+ i |
1597 |
+ ", " |
1598 |
+ p |
1599 |
+ "): You can only index this BezierCurve's points from 0 to " |
1600 |
+ (points.length - 1) + "."); |
1601 |
} |
1602 |
points[i] = new Vector3D(p); |
234 |
} |
1603 |
} |
235 |
|
1604 |
|
236 |
/** |
1605 |
/** |
237 |
* Sets the x-coordinate of the start {@link Point} of this |
1606 |
* Subdivides this {@link BezierCurve} at the given parameter value t into |
238 |
* {@link BezierCurve} to x1. |
1607 |
* two new {@link BezierCurve}s. The first one is the curve over [0;t] and |
|
|
1608 |
* the second one is the curve over [t;1]. |
239 |
* |
1609 |
* |
240 |
* @param x1 |
1610 |
* NOTE: One could provide two methods splitLeft() and splitRight() in case |
241 |
* the new start {@link Point}'s x-coordinate |
1611 |
* just the left or right part of the curve is needed. |
|
|
1612 |
* |
1613 |
* @param t |
1614 |
* Parameter value |
1615 |
* @return Two curves; to the left and to the right of t. |
242 |
*/ |
1616 |
*/ |
243 |
public void setX1(double x1) { |
1617 |
public BezierCurve[] split(double t) { |
244 |
this.x1 = x1; |
1618 |
Vector3D[] leftPoints = new Vector3D[points.length]; |
|
|
1619 |
Vector3D[] rightPoints = new Vector3D[points.length]; |
1620 |
|
1621 |
Vector3D[] ratioPoints = getPointsCopy(); |
1622 |
|
1623 |
for (int i = 0; i < points.length; i++) { |
1624 |
leftPoints[i] = ratioPoints[0]; |
1625 |
rightPoints[points.length - 1 - i] = ratioPoints[points.length - 1 |
1626 |
- i]; |
1627 |
|
1628 |
for (int j = 0; j < points.length - i - 1; j++) { |
1629 |
ratioPoints[j] = ratioPoints[j].getRatio(ratioPoints[j + 1], t); |
1630 |
} |
1631 |
} |
1632 |
|
1633 |
return new BezierCurve[] { new BezierCurve(leftPoints), |
1634 |
new BezierCurve(rightPoints) }; |
1635 |
} |
1636 |
|
1637 |
public BezierCurve[] toBezier() { |
1638 |
return new BezierCurve[] { this }; |
245 |
} |
1639 |
} |
246 |
|
1640 |
|
247 |
/** |
1641 |
/** |
248 |
* Sets the x-coordinate of the end {@link Point} of this |
1642 |
* Returns a hard approximation of this {@link BezierCurve} as a |
249 |
* {@link BezierCurve} to x2. |
1643 |
* {@link CubicCurve}. The new {@link CubicCurve} is constructed from the |
|
|
1644 |
* first four {@link Point}s in this {@link BezierCurve}'s {@link Point}s |
1645 |
* array. If this {@link BezierCurve} is not of degree four or higher, i.e. |
1646 |
* it does not have four or more control {@link Point}s (including start and |
1647 |
* end {@link Point}), <code>null</code> is returned. |
250 |
* |
1648 |
* |
251 |
* @param x2 |
1649 |
* @return a new {@link CubicCurve} that is constructed by the first four |
252 |
* the new end {@link Point}'s x-coordinate |
1650 |
* control {@link Point}s of this {@link BezierCurve} or |
|
|
1651 |
* <code>null</code> if this {@link BezierCurve} does not have at |
1652 |
* least four control {@link Point}s |
253 |
*/ |
1653 |
*/ |
254 |
public void setX2(double x2) { |
1654 |
public CubicCurve toCubic() { |
255 |
this.x2 = x2; |
1655 |
if (points.length > 3) { |
|
|
1656 |
return new CubicCurve(points[0].toPoint(), points[1].toPoint(), |
1657 |
points[2].toPoint(), points[points.length - 1].toPoint()); |
1658 |
} |
1659 |
return null; |
256 |
} |
1660 |
} |
257 |
|
1661 |
|
258 |
/** |
1662 |
/** |
259 |
* Sets the y-coordinate of the start {@link Point} of this |
1663 |
* Returns a hard approximation of this {@link BezierCurve} as a |
260 |
* {@link BezierCurve} to y1. |
1664 |
* {@link Line}. The {@link Line} is constructed from the start and end |
|
|
1665 |
* {@link Point} of this {@link BezierCurve}. |
261 |
* |
1666 |
* |
262 |
* @param y1 |
1667 |
* Sometimes, this {@link Line} is referred to as the base-line of the |
263 |
* the new start {@link Point}'s y-coordinate |
1668 |
* {@link BezierCurve}. |
|
|
1669 |
* |
1670 |
* @return a {@link Line} from start to end {@link Point} of this |
1671 |
* {@link BezierCurve} |
264 |
*/ |
1672 |
*/ |
265 |
public void setY1(double y1) { |
1673 |
public Line toLine() { |
266 |
this.y1 = y1; |
1674 |
if (points.length > 1) { |
|
|
1675 |
return new Line(points[0].toPoint(), |
1676 |
points[points.length - 1].toPoint()); |
1677 |
} |
1678 |
return null; |
267 |
} |
1679 |
} |
268 |
|
1680 |
|
269 |
/** |
1681 |
/** |
270 |
* Sets the y-coordinate of the end {@link Point} of this |
1682 |
* Returns an approximation of this {@link BezierCurve} by a strip of |
271 |
* {@link BezierCurve} to y2. |
1683 |
* {@link Line}s. For detailed information on how the approximation is |
|
|
1684 |
* calculated, see {@link BezierCurve#toLineStrip(double, Interval)}. |
272 |
* |
1685 |
* |
273 |
* @param y2 |
1686 |
* @see BezierCurve#toLineStrip(double, Interval) |
274 |
* the new end {@link Point}'s y-coordinate |
1687 |
* @param lineSimilarity |
|
|
1688 |
* @return an approximation of this {@link BezierCurve} by a strip of |
1689 |
* {@link Line}s |
275 |
*/ |
1690 |
*/ |
276 |
public void setY2(double y2) { |
1691 |
public Line[] toLineStrip(double lineSimilarity) { |
277 |
this.y2 = y2; |
1692 |
return toLineStrip(lineSimilarity, Interval.getFull()); |
278 |
} |
1693 |
} |
279 |
|
1694 |
|
|
|
1695 |
/** |
1696 |
* Returns {@link Line} segments approximating this {@link BezierCurve}. |
1697 |
* |
1698 |
* The {@link BezierCurve} is recursively subdivided until it is "similar" |
1699 |
* to a straight line. The similarity check computes the sum of the |
1700 |
* distances of the control points to the baseline of the |
1701 |
* {@link BezierCurve}. If this sum is smaller than the given |
1702 |
* lineSimilarity, the {@link BezierCurve} is assumed to be "similar" to a |
1703 |
* straight line. |
1704 |
* |
1705 |
* @param lineSimilarity |
1706 |
* The threshold for the sum of the distances of the control |
1707 |
* points to the baseline of this {@link BezierCurve} |
1708 |
* @param startInterval |
1709 |
* The interval of the curve that has to be transformed into a |
1710 |
* strip of {@link Line}s. |
1711 |
* @return {@link Line} segments approximating this {@link BezierCurve}. |
1712 |
*/ |
1713 |
public Line[] toLineStrip(double lineSimilarity, Interval startInterval) { |
1714 |
ArrayList<Line> lines = new ArrayList<Line>(); |
1715 |
|
1716 |
Point startPoint = getHC(startInterval.a).toPoint(); |
1717 |
|
1718 |
// System.out.println("BEZIER CURVE - LINE APPROXIMATION"); |
1719 |
// System.out.println("---------------------------------"); |
1720 |
// System.out.println("moveTo(" + startPoint.x + ", " + startPoint.y |
1721 |
// + ")"); |
1722 |
|
1723 |
Stack<Interval> parts = new Stack<Interval>(); |
1724 |
parts.push(startInterval); |
1725 |
|
1726 |
while (!parts.isEmpty()) { |
1727 |
// System.out.println("pop"); |
1728 |
Interval i = parts.pop(); |
1729 |
BezierCurve part = getClipped(i.a, i.b); |
1730 |
|
1731 |
if (distanceToBaseLine(part) < lineSimilarity) { |
1732 |
Point endPoint = getHC(i.b).toPoint(); |
1733 |
lines.add(new Line(startPoint, endPoint)); |
1734 |
startPoint = endPoint; |
1735 |
// System.out.println("lineTo(" + endPoint.x + ", " |
1736 |
// + endPoint.y + ")"); |
1737 |
} else { |
1738 |
// System.out.println("push'em"); |
1739 |
double im = i.getMid(); |
1740 |
parts.push(new Interval(im, i.b)); |
1741 |
parts.push(new Interval(i.a, im)); |
1742 |
} |
1743 |
} |
1744 |
|
1745 |
return lines.toArray(new Line[] {}); |
1746 |
} |
1747 |
|
1748 |
/** |
1749 |
* Returns a {@link Path} approximating this {@link BezierCurve} using |
1750 |
* {@link Line} segments. |
1751 |
* |
1752 |
* @return a {@link Path} approximating this {@link BezierCurve} using |
1753 |
* {@link Line} segments. |
1754 |
*/ |
1755 |
public Path toPath() { |
1756 |
Path path = new Path(); |
1757 |
|
1758 |
Point startPoint = points[0].toPoint(); |
1759 |
path.moveTo(startPoint.x, startPoint.y); |
1760 |
|
1761 |
for (Line seg : toLineStrip(0.25d)) { |
1762 |
path.lineTo(seg.getX2(), seg.getY2()); |
1763 |
} |
1764 |
|
1765 |
return path; |
1766 |
} |
1767 |
|
1768 |
/** |
1769 |
* Computes {@link Point}s on this {@link BezierCurve} over the given |
1770 |
* {@link Interval}. Consecutive returned {@link Point}s are required to be |
1771 |
* {@link Point#equals(Object)} to each other. |
1772 |
* |
1773 |
* @param startInterval |
1774 |
* the {@link Interval} of this {@link BezierCurve} to calculate |
1775 |
* {@link Point}s for |
1776 |
* @return {@link Point}s on this {@link BezierCurve} over the given |
1777 |
* parameter {@link Interval} where consecutive {@link Point}s are |
1778 |
* {@link Point#equals(Object)} to each other |
1779 |
*/ |
1780 |
public Point[] toPoints(Interval startInterval) { |
1781 |
ArrayList<Point> points = new ArrayList<Point>(); |
1782 |
points.add(getHC(startInterval.a).toPoint()); |
1783 |
|
1784 |
// System.out.println("BEZIER CURVE - LINE APPROXIMATION"); |
1785 |
// System.out.println("---------------------------------"); |
1786 |
// System.out.println("moveTo(" + startPoint.x + ", " + startPoint.y |
1787 |
// + ")"); |
1788 |
|
1789 |
Stack<Interval> parts = new Stack<Interval>(); |
1790 |
parts.push(startInterval); |
1791 |
|
1792 |
while (!parts.isEmpty()) { |
1793 |
// System.out.println("pop"); |
1794 |
Interval i = parts.pop(); |
1795 |
BezierCurve part = getClipped(i.a, i.b); |
1796 |
|
1797 |
Point[] partPoints = part.getPoints(); |
1798 |
|
1799 |
boolean allTogether = true; |
1800 |
for (int j = 1; j < partPoints.length; j++) { |
1801 |
if (!partPoints[0].equals(partPoints[j])) { |
1802 |
allTogether = false; |
1803 |
break; |
1804 |
} |
1805 |
} |
1806 |
|
1807 |
if (allTogether) { |
1808 |
points.add(partPoints[partPoints.length - 1]); |
1809 |
} else { |
1810 |
double im = i.getMid(); |
1811 |
parts.push(new Interval(im, i.b)); |
1812 |
parts.push(new Interval(i.a, im)); |
1813 |
} |
1814 |
} |
1815 |
|
1816 |
return points.toArray(new Point[] {}); |
1817 |
} |
1818 |
|
1819 |
/** |
1820 |
* Returns a hard approximation of this {@link BezierCurve} as a |
1821 |
* {@link QuadraticCurve}. The new {@link QuadraticCurve} is constructed |
1822 |
* from the first three {@link Point}s in this {@link BezierCurve}'s |
1823 |
* {@link Point}s array. If this {@link BezierCurve} is not of degree three |
1824 |
* or higher, i.e. it does not have three or more control {@link Point}s |
1825 |
* (including start and end {@link Point}), <code>null</code> is returned. |
1826 |
* |
1827 |
* @return a new {@link QuadraticCurve} that is constructed by the first |
1828 |
* three control {@link Point}s of this {@link BezierCurve} or |
1829 |
* <code>null</code> if this {@link BezierCurve} does not have at |
1830 |
* least three control {@link Point}s |
1831 |
*/ |
1832 |
public QuadraticCurve toQuadratic() { |
1833 |
if (points.length > 2) { |
1834 |
return new QuadraticCurve(points[0].toPoint(), points[1].toPoint(), |
1835 |
points[points.length - 1].toPoint()); |
1836 |
} |
1837 |
return null; |
1838 |
} |
1839 |
|
1840 |
// double x1; |
1841 |
// double y1; |
1842 |
// double x2; |
1843 |
// double y2; |
1844 |
// |
1845 |
// // TODO: use point array instead |
1846 |
// double[] ctrlCoordinates = null; |
1847 |
// |
1848 |
// public BezierCurve(double... coordinates) { |
1849 |
// if (coordinates.length < 4) { |
1850 |
// throw new IllegalArgumentException( |
1851 |
// "A bezier curve needs at least a start and an end point"); |
1852 |
// } |
1853 |
// this.x1 = coordinates[0]; |
1854 |
// this.y1 = coordinates[1]; |
1855 |
// this.x2 = coordinates[coordinates.length - 2]; |
1856 |
// this.y2 = coordinates[coordinates.length - 1]; |
1857 |
// if (coordinates.length > 4) { |
1858 |
// this.ctrlCoordinates = new double[coordinates.length - 4]; |
1859 |
// System.arraycopy(coordinates, 2, ctrlCoordinates, 0, |
1860 |
// coordinates.length - 4); |
1861 |
// } |
1862 |
// } |
1863 |
// |
1864 |
// public BezierCurve(Point... points) { |
1865 |
// this(PointListUtils.toCoordinatesArray(points)); |
1866 |
// } |
1867 |
// |
1868 |
// public final boolean contains(Rectangle r) { |
1869 |
// // TODO: may contain the rectangle only in case the rectangle is |
1870 |
// // degenerated... |
1871 |
// return false; |
1872 |
// } |
1873 |
// |
1874 |
// public Point getCtrl(int i) { |
1875 |
// return new Point(getCtrlX(i), getCtrlY(i)); |
1876 |
// } |
1877 |
// |
1878 |
// /** |
1879 |
// * Returns the point-wise coordinates (i.e. x1, y1, x2, y2, etc.) of the |
1880 |
// * inner control points of this {@link BezierCurve}, i.e. exclusive of the |
1881 |
// * start and end points. |
1882 |
// * |
1883 |
// * @see BezierCurve#getCtrls() |
1884 |
// * |
1885 |
// * @return an array containing the inner control points' coordinates |
1886 |
// */ |
1887 |
// public double[] getCtrlCoordinates() { |
1888 |
// return PointListUtils.getCopy(ctrlCoordinates); |
1889 |
// |
1890 |
// } |
1891 |
// |
1892 |
// /** |
1893 |
// * Returns an array of points representing the inner control points of |
1894 |
// this |
1895 |
// * curve, i.e. excluding the start and end points. In case of s linear |
1896 |
// * curve, no control points will be returned, in case of a quadratic |
1897 |
// curve, |
1898 |
// * one control point, and so on. |
1899 |
// * |
1900 |
// * @return an array of points with the coordinates of the inner control |
1901 |
// * points of this {@link BezierCurve}, i.e. exclusive of the start |
1902 |
// * and end point. The number of control points will depend on the |
1903 |
// * degree ({@link #getDegree()}) of the curve, so in case of a line |
1904 |
// * (linear curve) the array will be empty, in case of a quadratic |
1905 |
// * curve, it will be of size <code>1</code>, in case of a cubic |
1906 |
// * curve of size <code>2</code>, etc.. |
1907 |
// */ |
1908 |
// public Point[] getCtrls() { |
1909 |
// return PointListUtils.toPointsArray(ctrlCoordinates); |
1910 |
// } |
1911 |
// |
1912 |
// public double getCtrlX(int i) { |
1913 |
// return ctrlCoordinates[2 * i]; |
1914 |
// } |
1915 |
// |
1916 |
// public double getCtrlY(int i) { |
1917 |
// return ctrlCoordinates[2 * i + 1]; |
1918 |
// } |
1919 |
// |
1920 |
// /** |
1921 |
// * Returns the degree of this curve which corresponds to the number of |
1922 |
// * overall control points (including start and end point) used to define |
1923 |
// the |
1924 |
// * curve. The degree is zero-based, so a line (linear curve) will have |
1925 |
// * degree <code>1</code>, a quadratic curve will have degree |
1926 |
// <code>2</code>, |
1927 |
// * and so on. <code>1</code> in case of a |
1928 |
// * |
1929 |
// * @return The degree of this {@link ICurve}, which corresponds to the |
1930 |
// * zero-based overall number of control points (including start and |
1931 |
// * end point) used to define this {@link ICurve}. |
1932 |
// */ |
1933 |
// public int getDegree() { |
1934 |
// return getCtrls().length + 1; |
1935 |
// } |
1936 |
// |
1937 |
// /** |
1938 |
// * Returns an array of points that represent this {@link BezierCurve}, |
1939 |
// i.e. |
1940 |
// * the start point, the inner control points, and the end points. |
1941 |
// * |
1942 |
// * @return an array of points representing the control points (including |
1943 |
// * start and end point) of this {@link BezierCurve} |
1944 |
// */ |
1945 |
// public Point[] getPoints() { |
1946 |
// Point[] points = new Point[ctrlCoordinates.length / 2 + 2]; |
1947 |
// points[0] = new Point(x1, y1); |
1948 |
// points[points.length - 1] = new Point(x2, y2); |
1949 |
// for (int i = 1; i < points.length - 1; i++) { |
1950 |
// points[i] = new Point(ctrlCoordinates[2 * i - 2], |
1951 |
// ctrlCoordinates[2 * i - 1]); |
1952 |
// } |
1953 |
// return points; |
1954 |
// } |
1955 |
// |
1956 |
// /** |
1957 |
// * {@inheritDoc} |
1958 |
// * |
1959 |
// * @see org.eclipse.gef4.geometry.planar.ICurve#getP1() |
1960 |
// */ |
1961 |
// public Point getP1() { |
1962 |
// return new Point(x1, y1); |
1963 |
// } |
1964 |
// |
1965 |
// /** |
1966 |
// * {@inheritDoc} |
1967 |
// * |
1968 |
// * @see org.eclipse.gef4.geometry.planar.ICurve#getP2() |
1969 |
// */ |
1970 |
// public Point getP2() { |
1971 |
// return new Point(x2, y2); |
1972 |
// } |
1973 |
// |
1974 |
// /** |
1975 |
// * {@inheritDoc} |
1976 |
// * |
1977 |
// * @see org.eclipse.gef4.geometry.planar.ICurve#getX1() |
1978 |
// */ |
1979 |
// public double getX1() { |
1980 |
// return x1; |
1981 |
// } |
1982 |
// |
1983 |
// /** |
1984 |
// * {@inheritDoc} |
1985 |
// * |
1986 |
// * @see org.eclipse.gef4.geometry.planar.ICurve#getX2() |
1987 |
// */ |
1988 |
// public double getX2() { |
1989 |
// return x2; |
1990 |
// } |
1991 |
// |
1992 |
// /** |
1993 |
// * {@inheritDoc} |
1994 |
// * |
1995 |
// * @see org.eclipse.gef4.geometry.planar.ICurve#getY1() |
1996 |
// */ |
1997 |
// public double getY1() { |
1998 |
// return y1; |
1999 |
// } |
2000 |
// |
2001 |
// /** |
2002 |
// * {@inheritDoc} |
2003 |
// * |
2004 |
// * @see org.eclipse.gef4.geometry.planar.ICurve#getY2() |
2005 |
// */ |
2006 |
// public double getY2() { |
2007 |
// return y2; |
2008 |
// } |
2009 |
// |
2010 |
// protected void setCtrl(int i, Point p) { |
2011 |
// setCtrlX(i, p.x); |
2012 |
// setCtrlY(i, p.y); |
2013 |
// } |
2014 |
// |
2015 |
// public void setCtrls(Point... ctrls) { |
2016 |
// ctrlCoordinates = PointListUtils.toCoordinatesArray(ctrls); |
2017 |
// } |
2018 |
// |
2019 |
// protected void setCtrlX(int i, double x) { |
2020 |
// // TODO: enlarge array if its too small |
2021 |
// ctrlCoordinates[2 * i] = x; |
2022 |
// } |
2023 |
// |
2024 |
// protected void setCtrlY(int i, double y) { |
2025 |
// // TODO: enlarge array if its too small |
2026 |
// ctrlCoordinates[2 * i + 1] = y; |
2027 |
// } |
2028 |
// |
2029 |
// /** |
2030 |
// * Sets the start {@link Point} of this {@link BezierCurve} to the given |
2031 |
// * {@link Point} p1. |
2032 |
// * |
2033 |
// * @param p1 |
2034 |
// * the new start {@link Point} |
2035 |
// */ |
2036 |
// public void setP1(Point p1) { |
2037 |
// this.x1 = p1.x; |
2038 |
// this.y1 = p1.y; |
2039 |
// } |
2040 |
// |
2041 |
// /** |
2042 |
// * Sets the end {@link Point} of this {@link BezierCurve} to the given |
2043 |
// * {@link Point} p2. |
2044 |
// * |
2045 |
// * @param p2 |
2046 |
// * the new end {@link Point} |
2047 |
// */ |
2048 |
// public void setP2(Point p2) { |
2049 |
// this.x2 = p2.x; |
2050 |
// this.y2 = p2.y; |
2051 |
// } |
2052 |
// |
2053 |
// /** |
2054 |
// * Sets the x-coordinate of the start {@link Point} of this |
2055 |
// * {@link BezierCurve} to x1. |
2056 |
// * |
2057 |
// * @param x1 |
2058 |
// * the new start {@link Point}'s x-coordinate |
2059 |
// */ |
2060 |
// public void setX1(double x1) { |
2061 |
// this.x1 = x1; |
2062 |
// } |
2063 |
// |
2064 |
// /** |
2065 |
// * Sets the x-coordinate of the end {@link Point} of this |
2066 |
// * {@link BezierCurve} to x2. |
2067 |
// * |
2068 |
// * @param x2 |
2069 |
// * the new end {@link Point}'s x-coordinate |
2070 |
// */ |
2071 |
// public void setX2(double x2) { |
2072 |
// this.x2 = x2; |
2073 |
// } |
2074 |
// |
2075 |
// /** |
2076 |
// * Sets the y-coordinate of the start {@link Point} of this |
2077 |
// * {@link BezierCurve} to y1. |
2078 |
// * |
2079 |
// * @param y1 |
2080 |
// * the new start {@link Point}'s y-coordinate |
2081 |
// */ |
2082 |
// public void setY1(double y1) { |
2083 |
// this.y1 = y1; |
2084 |
// } |
2085 |
// |
2086 |
// /** |
2087 |
// * Sets the y-coordinate of the end {@link Point} of this |
2088 |
// * {@link BezierCurve} to y2. |
2089 |
// * |
2090 |
// * @param y2 |
2091 |
// * the new end {@link Point}'s y-coordinate |
2092 |
// */ |
2093 |
// public void setY2(double y2) { |
2094 |
// this.y2 = y2; |
2095 |
// } |
2096 |
|
280 |
} |
2097 |
} |