1 /* file : EllipseArc2D.java
2 *
3 * Project : geometry
4 *
5 * ===========================================
6 *
7 * This library is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 2.1 of the License, or (at
10 * your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE.
15 *
16 * See the GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this library. if not, write to :
20 * The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
21 * Boston, MA 02111-1307, USA.
22 *
23 * Created on 24 avr. 2006
24 *
25 */
26
27 package math.geom2d.conic;
28
29 import java.awt.Graphics2D;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Locale;
33
34 import math.geom2d.AffineTransform2D;
35 import math.geom2d.Angle2D;
36 import math.geom2d.Box2D;
37 import math.geom2d.Point2D;
38 import math.geom2d.Shape2D;
39 import math.geom2d.Vector2D;
40 import math.geom2d.curve.AbstractSmoothCurve2D;
41 import math.geom2d.curve.Curve2D;
42 import math.geom2d.curve.Curve2DUtils;
43 import math.geom2d.curve.CurveArray2D;
44 import math.geom2d.curve.CurveSet2D;
45 import math.geom2d.curve.SmoothCurve2D;
46 import math.geom2d.domain.SmoothOrientedCurve2D;
47 import math.geom2d.line.LinearShape2D;
48 import math.geom2d.line.Ray2D;
49 import math.geom2d.line.StraightLine2D;
50
51 /**
52 * An arc of ellipse. It is defined by a supporting ellipse, a starting angle,
53 * and a signed angle extent, both in radians. The ellipse arc is oriented
54 * counter-clockwise if angle extent is positive, and clockwise otherwise.
55 *
56 * @author dlegland
57 */
58 public class EllipseArc2D extends AbstractSmoothCurve2D
59 implements SmoothOrientedCurve2D, Cloneable {
60
61 /** The supporting ellipse */
62 protected Ellipse2D ellipse;
63
64 /** The starting position on ellipse, in radians between 0 and +2PI */
65 protected double startAngle = 0;
66
67 /** The signed angle extent, in radians between -2PI and +2PI. */
68 protected double angleExtent = Math.PI;
69
70 // ====================================================================
71 // Constructors
72
73 /**
74 * Construct a default Ellipse arc, centered on (0,0), with radii equal to 1
75 * and 1, orientation equal to 0, start angle equal to 0, and angle extent
76 * equal to PI/2.
77 */
78 public EllipseArc2D() {
79 this(0, 0, 1, 1, 0, 0, Math.PI/2);
80 }
81
82 /**
83 * Specify supporting ellipse, start angle and angle extent.
84 *
85 * @param ell the supporting ellipse
86 * @param start the starting angle (angle between 0 and 2*PI)
87 * @param extent the angle extent (signed angle)
88 */
89 public EllipseArc2D(Ellipse2D ell, double start, double extent) {
90 this(ell.xc, ell.yc, ell.r1, ell.r2, ell.theta, start, extent);
91 }
92
93 /**
94 * Specify supporting ellipse, start angle and end angle, and a flag
95 * indicating whether the arc is directed or not.
96 *
97 * @param ell the supporting ellipse
98 * @param start the starting angle
99 * @param end the ending angle
100 * @param direct flag indicating if the arc is direct
101 */
102 public EllipseArc2D(Ellipse2D ell, double start, double end, boolean direct) {
103 this(ell.xc, ell.yc, ell.r1, ell.r2, ell.theta, start, end, direct);
104 }
105
106 /**
107 * Specify parameters of supporting ellipse, start angle, and angle extent.
108 */
109 public EllipseArc2D(double xc, double yc, double a, double b, double theta,
110 double start, double extent) {
111 this.ellipse = new Ellipse2D(xc, yc, a, b, theta);
112 this.startAngle = start;
113 this.angleExtent = extent;
114 }
115
116 /**
117 * Specify parameters of supporting ellipse, bounding angles and flag for
118 * direct ellipse.
119 */
120 public EllipseArc2D(double xc, double yc, double a, double b, double theta,
121 double start, double end, boolean direct) {
122 this.ellipse = new Ellipse2D(xc, yc, a, b, theta);
123 this.startAngle = start;
124 this.angleExtent = Angle2D.formatAngle(end-start);
125 if (!direct)
126 this.angleExtent = this.angleExtent-Math.PI*2;
127 }
128
129 // ====================================================================
130 // methods specific to EllipseArc2D
131
132 /**
133 * Specify supporting ellipse, start angle and end angle, and a flag
134 * indicating whether the arc is directed or not.
135 *
136 * @param ell the supporting ellipse
137 * @param start the starting angle
138 * @param end the ending angle
139 * @param direct flag indicating if the arc is direct
140 */
141 public static EllipseArc2D create(Ellipse2D ell, double start,
142 double extent) {
143 return new EllipseArc2D(ell.xc, ell.yc, ell.r1, ell.r2, ell.theta,
144 start, extent);
145 }
146
147 /**
148 * Specify supporting ellipse, start angle and end angle, and a flag
149 * indicating whether the arc is directed or not.
150 *
151 * @param ell the supporting ellipse
152 * @param start the starting angle
153 * @param end the ending angle
154 * @param direct flag indicating if the arc is direct
155 */
156 public static EllipseArc2D create(Ellipse2D ell, double start, double end,
157 boolean direct) {
158 return new EllipseArc2D(ell.xc, ell.yc, ell.r1, ell.r2, ell.theta,
159 start, end, direct);
160 }
161
162 // ====================================================================
163 // methods specific to EllipseArc2D
164
165 public Ellipse2D getSupportingEllipse() {
166 return ellipse;
167 }
168
169 public double getStartAngle() {
170 return startAngle;
171 }
172
173 public double getAngleExtent() {
174 return angleExtent;
175 }
176
177 /**
178 * Returns true if the ellipse arc is direct, i.e. if the angle extent is
179 * positive.
180 */
181 public boolean isDirect() {
182 return angleExtent>0;
183 }
184
185 public boolean containsAngle(double angle) {
186 return Angle2D.containsAngle(
187 startAngle, startAngle+angleExtent, angle, angleExtent>0);
188 }
189
190 /** Returns the angle associated with the given position */
191 public double getAngle(double position) {
192 if (position<0)
193 position = 0;
194 if (position>Math.abs(angleExtent))
195 position = Math.abs(angleExtent);
196 if (angleExtent<0)
197 position = -position;
198 return Angle2D.formatAngle(startAngle+position);
199 }
200
201 // ====================================================================
202 // methods from interface OrientedCurve2D
203
204 /*
205 * (non-Javadoc)
206 *
207 * @see math.geom2d.OrientedCurve2D#getViewAngle(math.geom2d.Point2D)
208 */
209 public double getWindingAngle(java.awt.geom.Point2D point) {
210 Point2D p1 = getPoint(0);
211 Point2D p2 = getPoint(Math.abs(angleExtent));
212
213 // compute angle of point with extreme points
214 double angle1 = Angle2D.getHorizontalAngle(point, p1);
215 double angle2 = Angle2D.getHorizontalAngle(point, p2);
216
217 // test on which 'side' of the arc the point lie
218 boolean b1 = (new StraightLine2D(p1, p2)).isInside(point);
219 boolean b2 = ellipse.isInside(point);
220
221 if (angleExtent>0) {
222 if (b1||b2) { // inside of ellipse arc
223 if (angle2>angle1)
224 return angle2-angle1;
225 else
226 return 2*Math.PI-angle1+angle2;
227 } else { // outside of ellipse arc
228 if (angle2>angle1)
229 return angle2-angle1-2*Math.PI;
230 else
231 return angle2-angle1;
232 }
233 } else {
234 if (!b1||b2) {
235 if (angle1>angle2)
236 return angle2-angle1;
237 else
238 return angle2-angle1-2*Math.PI;
239 } else {
240 if (angle1>angle2)
241 return angle2-angle1+2*Math.PI;
242 else
243 return angle2-angle1;
244 }
245 // if(b1 || b2){
246 // if(angle1>angle2) return angle1 - angle2;
247 // else return 2*Math.PI - angle2 + angle1;
248 // }else{
249 // if(angle1>angle2) return angle1 - angle2 - 2*Math.PI;
250 // else return angle1 - angle2;
251 // }
252 }
253 }
254
255 public boolean isInside(java.awt.geom.Point2D p) {
256 return getSignedDistance(p.getX(), p.getY())<0;
257 }
258
259 public double getSignedDistance(java.awt.geom.Point2D p) {
260 return getSignedDistance(p.getX(), p.getY());
261 }
262
263 /*
264 * (non-Javadoc)
265 *
266 * @see math.geom2d.Shape2D#getSignedDistance(math.geom2d.Point2D)
267 */
268 public double getSignedDistance(double x, double y) {
269 boolean direct = angleExtent>0;
270
271 double dist = getDistance(x, y);
272 Point2D point = new Point2D(x, y);
273
274 boolean inside = ellipse.isInside(point);
275 if (inside)
276 return angleExtent>0 ? -dist : dist;
277
278 Point2D p1 = getPoint(startAngle);
279 Point2D p2 = getPoint(startAngle+angleExtent);
280 boolean onLeft = (new StraightLine2D(p1, p2)).isInside(point);
281
282 if (direct&&!onLeft)
283 return dist;
284 if (!direct&&onLeft)
285 return -dist;
286
287 boolean left1 = (new Ray2D(p1, -Math.sin(startAngle), Math
288 .cos(startAngle))).isInside(point);
289 if (direct&&!left1)
290 return dist;
291 if (!direct&&left1)
292 return -dist;
293
294 boolean left2 = (new Ray2D(p2, -Math.sin(startAngle+angleExtent), Math
295 .cos(startAngle+angleExtent))).isInside(point);
296 if (direct&&!left2)
297 return dist;
298 if (!direct&&left2)
299 return -dist;
300
301 if (direct)
302 return -dist;
303 else
304 return dist;
305 }
306
307 // ====================================================================
308 // methods from interface SmoothCurve2D
309
310 public Vector2D getTangent(double t) {
311 // format between min and max admissible values
312 t = Math.min(Math.max(0, t), Math.abs(angleExtent));
313
314 // compute tangent vector depending on position
315 if (angleExtent<0) {
316 // need to invert vector for indirect arcs
317 return ellipse.getTangent(startAngle-t).times(-1);
318 } else {
319 return ellipse.getTangent(startAngle+t);
320 }
321 }
322
323 /**
324 * returns the curvature of the ellipse arc.
325 */
326 public double getCurvature(double t) {
327 // convert position to angle
328 if (angleExtent<0)
329 t = startAngle-t;
330 else
331 t = startAngle+t;
332 double kappa = ellipse.getCurvature(t);
333 return this.isDirect() ? kappa : -kappa;
334 }
335
336 // ====================================================================
337 // methods from interface ContinuousCurve2D
338
339 /** Returns false, as an ellipse arc is never closed. */
340 public boolean isClosed() {
341 return false;
342 }
343
344 // ====================================================================
345 // methods from interface Curve2D
346
347 /** Always returns 0 */
348 public double getT0() {
349 return 0;
350 }
351
352 /** Always returns the absolute value of the angle extent */
353 public double getT1() {
354 return Math.abs(angleExtent);
355 }
356
357 /*
358 * (non-Javadoc)
359 *
360 * @see math.geom2d.Curve2D#getPoint(double, math.geom2d.Point2D)
361 */
362 public Point2D getPoint(double t) {
363 // check bounds
364 t = Math.max(t, 0);
365 t = Math.min(t, Math.abs(angleExtent));
366
367 // convert position to angle
368 if (angleExtent<0)
369 t = startAngle-t;
370 else
371 t = startAngle+t;
372
373 // return corresponding point
374 return ellipse.getPoint(t);
375 }
376
377 /*
378 * (non-Javadoc)
379 *
380 * @see math.geom2d.Curve2D#getPosition(math.geom2d.Point2D)
381 */
382 public double getPosition(java.awt.geom.Point2D point) {
383 double angle = Angle2D.getHorizontalAngle(ellipse.getCenter(), point);
384 if (this.containsAngle(angle))
385 if (angleExtent>0)
386 return Angle2D.formatAngle(angle-startAngle);
387 else
388 return Angle2D.formatAngle(startAngle-angle);
389
390 // If the point is not contained in the arc, return NaN.
391 return Double.NaN;
392 }
393
394 public double project(java.awt.geom.Point2D point) {
395 double angle = ellipse.project(point);
396
397 // Case of an angle contained in the ellipse arc
398 if (this.containsAngle(angle)) {
399 if (angleExtent>0)
400 return Angle2D.formatAngle(angle-startAngle);
401 else
402 return Angle2D.formatAngle(startAngle-angle);
403 }
404
405 // return either 0 or T1, depending on which extremity is closer.
406 double d1 = this.getFirstPoint().distance(point);
407 double d2 = this.getLastPoint().distance(point);
408 return d1<d2 ? 0 : Math.abs(angleExtent);
409 }
410
411 /*
412 * (non-Javadoc)
413 *
414 * @see math.geom2d.Curve2D#getIntersections(math.geom2d.LinearShape2D)
415 */
416 public Collection<Point2D> getIntersections(LinearShape2D line) {
417
418 // check point contained in it
419 ArrayList<Point2D> array = new ArrayList<Point2D>();
420 for (Point2D point : ellipse.getIntersections(line))
421 if (contains(point))
422 array.add(point);
423
424 return array;
425 }
426
427 /**
428 * Returns the ellipse arc which refers to the reversed parent ellipse, with
429 * same start angle, and with opposite angle extent.
430 */
431 public EllipseArc2D getReverseCurve() {
432 return new EllipseArc2D(ellipse, Angle2D.formatAngle(startAngle
433 +angleExtent), -angleExtent);
434 }
435
436 @Override
437 public Collection<? extends EllipseArc2D> getContinuousCurves() {
438 return wrapCurve(this);
439 }
440
441 /**
442 * Returns a new EllipseArc2D.
443 */
444 public EllipseArc2D getSubCurve(double t0, double t1) {
445 // convert position to angle
446 t0 = Angle2D.formatAngle(startAngle+t0);
447 t1 = Angle2D.formatAngle(startAngle+t1);
448
449 // check bounds of angles
450 if (!Angle2D.containsAngle(startAngle, startAngle+angleExtent, t0,
451 angleExtent>0))
452 t0 = startAngle;
453 if (!Angle2D.containsAngle(startAngle, startAngle+angleExtent, t1,
454 angleExtent>0))
455 t1 = angleExtent;
456
457 // create new arc
458 return new EllipseArc2D(ellipse, t0, t1, angleExtent>0);
459 }
460
461 // ====================================================================
462 // methods from interface Shape2D
463
464 /*
465 * (non-Javadoc)
466 *
467 * @see math.geom2d.Shape2D#getDistance(math.geom2d.Point2D)
468 */
469 public double getDistance(java.awt.geom.Point2D point) {
470 return getDistance(point.getX(), point.getY());
471 }
472
473 /*
474 * (non-Javadoc)
475 *
476 * @see math.geom2d.Shape2D#getDistance(double, double)
477 */
478 public double getDistance(double x, double y) {
479 Point2D p = getPoint(project(new Point2D(x, y)));
480 return p.getDistance(x, y);
481 }
482
483 /** Always return true: an ellipse arc is bounded by definition */
484 public boolean isBounded() {
485 return true;
486 }
487
488 public boolean isEmpty() {
489 return false;
490 }
491
492 /**
493 * Clip the ellipse arc by a box. The result is an instance of CurveSet2D<EllipseArc2D>,
494 * which contains only instances of EllipseArc2D. If the ellipse arc is not
495 * clipped, the result is an instance of CurveSet2D<EllipseArc2D> which
496 * contains 0 curves.
497 */
498 public CurveSet2D<? extends EllipseArc2D> clip(Box2D box) {
499 // Clip the curve
500 CurveSet2D<SmoothCurve2D> set = Curve2DUtils.clipSmoothCurve(this, box);
501
502 // Stores the result in appropriate structure
503 CurveArray2D<EllipseArc2D> result =
504 new CurveArray2D<EllipseArc2D>(set.getCurveNumber());
505
506 // convert the result
507 for (Curve2D curve : set.getCurves()) {
508 if (curve instanceof EllipseArc2D)
509 result.addCurve((EllipseArc2D) curve);
510 }
511 return result;
512 }
513
514 public Box2D getBoundingBox() {
515
516 // first get ending points
517 Point2D p0 = getFirstPoint();
518 Point2D p1 = getLastPoint();
519
520 // get coordinate of ending points
521 double x0 = p0.getX();
522 double y0 = p0.getY();
523 double x1 = p1.getX();
524 double y1 = p1.getY();
525
526 // intialize min and max coords
527 double xmin = Math.min(x0, x1);
528 double xmax = Math.max(x0, x1);
529 double ymin = Math.min(y0, y1);
530 double ymax = Math.max(y0, y1);
531
532 // check cases arc contains one maximum
533 Point2D center = ellipse.getCenter();
534 double xc = center.getX();
535 double yc = center.getY();
536 if (Angle2D.containsAngle(startAngle, startAngle+angleExtent, Math.PI/2
537 +ellipse.theta, angleExtent>=0))
538 ymax = Math.max(ymax, yc+ellipse.r1);
539 if (Angle2D.containsAngle(startAngle, startAngle+angleExtent, 3*Math.PI
540 /2+ellipse.theta, angleExtent>=0))
541 ymin = Math.min(ymin, yc-ellipse.r1);
542 if (Angle2D.containsAngle(startAngle, startAngle+angleExtent,
543 ellipse.theta, angleExtent>=0))
544 xmax = Math.max(xmax, xc+ellipse.r2);
545 if (Angle2D.containsAngle(startAngle, startAngle+angleExtent, Math.PI
546 +ellipse.theta, angleExtent>=0))
547 xmin = Math.min(xmin, xc-ellipse.r2);
548
549 // return a bounding with computed limits
550 return new Box2D(xmin, xmax, ymin, ymax);
551 }
552
553 /*
554 * (non-Javadoc)
555 *
556 * @see math.geom2d.Shape2D#transform(math.geom2d.AffineTransform2D)
557 */
558 public EllipseArc2D transform(AffineTransform2D trans) {
559 // transform supporting ellipse
560 Ellipse2D ell = ellipse.transform(trans);
561
562 // ensure ellipse is direct
563 if (!ell.isDirect())
564 ell = ell.getReverseCurve();
565
566 // Compute position of end points on the transformed ellipse
567 double startPos = ell.project(this.getFirstPoint().transform(trans));
568 double endPos = ell.project(this.getLastPoint().transform(trans));
569
570 // Compute the new arc
571 boolean direct = !(angleExtent>0^trans.isDirect());
572 return new EllipseArc2D(ell, startPos, endPos, direct);
573 }
574
575 /*
576 * (non-Javadoc)
577 *
578 * @see java.awt.Shape#contains(double, double)
579 */
580 public boolean contains(double x, double y) {
581 return getDistance(x, y)>Shape2D.ACCURACY;
582 }
583
584 /*
585 * (non-Javadoc)
586 *
587 * @see java.awt.Shape#contains(java.awt.geom.Point2D)
588 */
589 public boolean contains(java.awt.geom.Point2D point) {
590 return contains(point.getX(), point.getY());
591 }
592
593 public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
594 // number of curves to approximate the arc
595 int nSeg = (int) Math.ceil(Math.abs(angleExtent)/(Math.PI/2));
596 nSeg = Math.min(nSeg, 4);
597
598 // angular extent of each curve
599 double ext = angleExtent/nSeg;
600
601 // compute coefficient
602 double k = btan(Math.abs(ext));
603
604 for(int i=0; i<nSeg; i++) {
605 // position of the two extremities
606 double ti0 = Math.abs(i*ext);
607 double ti1 = Math.abs((i+1)*ext);
608
609 // extremity points
610 Point2D p1 = this.getPoint(ti0);
611 Point2D p2 = this.getPoint(ti1);
612
613 // tangent vectors, multiplied by appropriate coefficient
614 Vector2D v1 = this.getTangent(ti0).times(k);
615 Vector2D v2 = this.getTangent(ti1).times(k);
616
617 // append a cubic curve to the path
618 path.curveTo(
619 p1.getX()+v1.getX(), p1.getY()+v1.getY(),
620 p2.getX()-v2.getX(), p2.getY()-v2.getY(),
621 p2.getX(), p2.getY());
622 }
623 return path;
624 }
625
626 public java.awt.geom.GeneralPath getGeneralPath() {
627 // create new path
628 java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();
629
630 // move to the first point
631 Point2D point = this.getFirstPoint();
632 path.moveTo((float) point.getX(), (float) point.getY());
633
634 // append the curve
635 path = this.appendPath(path);
636
637 // return the final path
638 return path;
639 }
640
641 @Override
642 public void draw(Graphics2D g2) {
643 g2.draw(this.getGeneralPath());
644 }
645
646 /**
647 *
648 * btan computes the length (k) of the control segments at
649 * the beginning and end of a cubic Bezier that approximates
650 * a segment of an arc with extent less than or equal to
651 * 90 degrees. This length (k) will be used to generate the
652 * 2 Bezier control points for such a segment.
653 *
654 * Assumptions:
655 * a) arc is centered on 0,0 with radius of 1.0
656 * b) arc extent is less than 90 degrees
657 * c) control points should preserve tangent
658 * d) control segments should have equal length
659 *
660 * Initial data:
661 * start angle: ang1
662 * end angle: ang2 = ang1 + extent
663 * start point: P1 = (x1, y1) = (cos(ang1), sin(ang1))
664 * end point: P4 = (x4, y4) = (cos(ang2), sin(ang2))
665 *
666 * Control points:
667 * P2 = (x2, y2)
668 * | x2 = x1 - k * sin(ang1) = cos(ang1) - k * sin(ang1)
669 * | y2 = y1 + k * cos(ang1) = sin(ang1) + k * cos(ang1)
670 *
671 * P3 = (x3, y3)
672 * | x3 = x4 + k * sin(ang2) = cos(ang2) + k * sin(ang2)
673 * | y3 = y4 - k * cos(ang2) = sin(ang2) - k * cos(ang2)
674 *
675 * The formula for this length (k) can be found using the
676 * following derivations:
677 *
678 * Midpoints:
679 * a) Bezier (t = 1/2)
680 * bPm = P1 * (1-t)^3 +
681 * 3 * P2 * t * (1-t)^2 +
682 * 3 * P3 * t^2 * (1-t) +
683 * P4 * t^3 =
684 * = (P1 + 3P2 + 3P3 + P4)/8
685 *
686 * b) arc
687 * aPm = (cos((ang1 + ang2)/2), sin((ang1 + ang2)/2))
688 *
689 * Let angb = (ang2 - ang1)/2; angb is half of the angle
690 * between ang1 and ang2.
691 *
692 * Solve the equation bPm == aPm
693 *
694 * a) For xm coord:
695 * x1 + 3*x2 + 3*x3 + x4 = 8*cos((ang1 + ang2)/2)
696 *
697 * cos(ang1) + 3*cos(ang1) - 3*k*sin(ang1) +
698 * 3*cos(ang2) + 3*k*sin(ang2) + cos(ang2) =
699 * = 8*cos((ang1 + ang2)/2)
700 *
701 * 4*cos(ang1) + 4*cos(ang2) + 3*k*(sin(ang2) - sin(ang1)) =
702 * = 8*cos((ang1 + ang2)/2)
703 *
704 * 8*cos((ang1 + ang2)/2)*cos((ang2 - ang1)/2) +
705 * 6*k*sin((ang2 - ang1)/2)*cos((ang1 + ang2)/2) =
706 * = 8*cos((ang1 + ang2)/2)
707 *
708 * 4*cos(angb) + 3*k*sin(angb) = 4
709 *
710 * k = 4 / 3 * (1 - cos(angb)) / sin(angb)
711 *
712 * b) For ym coord we derive the same formula.
713 *
714 * Since this formula can generate "NaN" values for small
715 * angles, we will derive a safer form that does not involve
716 * dividing by very small values:
717 * (1 - cos(angb)) / sin(angb) =
718 * = (1 - cos(angb))*(1 + cos(angb)) / sin(angb)*(1 + cos(angb)) =
719 * = (1 - cos(angb)^2) / sin(angb)*(1 + cos(angb)) =
720 * = sin(angb)^2 / sin(angb)*(1 + cos(angb)) =
721 * = sin(angb) / (1 + cos(angb))
722 *
723 * Function taken from java.awt.geom.ArcIterator.
724 */
725 private static double btan(double increment) {
726 increment /= 2.0;
727 return 4.0 / 3.0 * Math.sin(increment) / (1.0 + Math.cos(increment));
728 }
729
730 // ====================================================================
731 // methods from interface Object
732
733 @Override
734 public String toString() {
735 Point2D center = ellipse.getCenter();
736 return String.format(Locale.US,
737 "EllipseArc2D(%7.2f,%7.2f,%7.2f,%7.2f,%7.5f,%7.5f,%7.5f)",
738 center.getX(), center.getY(),
739 ellipse.r1, ellipse.r2, ellipse.theta,
740 startAngle, angleExtent);
741 }
742
743 @Override
744 public boolean equals(Object obj) {
745 if (!(obj instanceof EllipseArc2D))
746 return false;
747 EllipseArc2D arc = (EllipseArc2D) obj;
748
749 // test whether supporting ellipses have same support
750 if (Math.abs(ellipse.xc-arc.ellipse.xc)>Shape2D.ACCURACY)
751 return false;
752 if (Math.abs(ellipse.yc-arc.ellipse.yc)>Shape2D.ACCURACY)
753 return false;
754 if (Math.abs(ellipse.r1-arc.ellipse.r1)>Shape2D.ACCURACY)
755 return false;
756 if (Math.abs(ellipse.r2-arc.ellipse.r2)>Shape2D.ACCURACY)
757 return false;
758 if (Math.abs(ellipse.theta-arc.ellipse.theta)>Shape2D.ACCURACY)
759 return false;
760
761 // test if angles are the same
762 if (!Angle2D.equals(startAngle, arc.startAngle))
763 return false;
764 if (!Angle2D.equals(angleExtent, arc.angleExtent))
765 return false;
766
767 return true;
768 }
769
770 @Override
771 public EllipseArc2D clone() {
772 return new EllipseArc2D(ellipse, startAngle, angleExtent);
773 }
774 }