1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package math.geom2d.conic;
28
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Locale;
32
33 import math.geom2d.AffineTransform2D;
34 import math.geom2d.Angle2D;
35 import math.geom2d.Box2D;
36 import math.geom2d.Point2D;
37 import math.geom2d.Shape2D;
38 import math.geom2d.Vector2D;
39 import math.geom2d.circulinear.CirculinearCurve2DUtils;
40 import math.geom2d.circulinear.CirculinearDomain2D;
41 import math.geom2d.circulinear.CirculinearElement2D;
42 import math.geom2d.curve.Curve2D;
43 import math.geom2d.curve.Curve2DUtils;
44 import math.geom2d.curve.CurveArray2D;
45 import math.geom2d.curve.CurveSet2D;
46 import math.geom2d.curve.SmoothCurve2D;
47 import math.geom2d.line.LineSegment2D;
48 import math.geom2d.line.LinearShape2D;
49 import math.geom2d.line.Ray2D;
50 import math.geom2d.line.StraightLine2D;
51 import math.geom2d.transform.CircleInversion2D;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 public class CircleArc2D extends EllipseArc2D
67 implements Cloneable, CircularShape2D, CirculinearElement2D {
68
69 protected Circle2D circle;
70
71
72
73
74
75
76
77
78 public CircleArc2D() {
79 this(0, 0, 1, 0, Math.PI/2);
80 }
81
82
83
84
85
86
87 public CircleArc2D(Circle2D circle, double startAngle, double angleExtent) {
88 this(circle.xc, circle.yc, circle.r, startAngle, angleExtent);
89 }
90
91
92
93
94
95 public CircleArc2D(Circle2D circle, double startAngle, double endAngle,
96 boolean direct) {
97 this(circle.xc, circle.yc, circle.r, startAngle, endAngle, direct);
98 }
99
100
101
102
103 public CircleArc2D(Point2D center, double radius, double startAngle,
104 double angleExtent) {
105 this(center.getX(), center.getY(), radius, startAngle, angleExtent);
106 }
107
108
109
110
111
112 public CircleArc2D(Point2D center, double radius, double start, double end,
113 boolean direct) {
114 this(center.getX(), center.getY(), radius, start, end, direct);
115 }
116
117
118
119
120
121
122
123 public CircleArc2D(double xc, double yc, double r, double start,
124 double end, boolean direct) {
125 super(xc, yc, r, r, 0, start, end, direct);
126 this.circle = new Circle2D(xc, yc, r);
127 this.ellipse = this.circle;
128 }
129
130
131 public CircleArc2D(double xc, double yc, double r, double start,
132 double extent) {
133 super(xc, yc, r, r, 0, start, extent);
134 this.circle = new Circle2D(xc, yc, r);
135 this.ellipse = this.circle;
136 }
137
138
139
140
141 public static CircleArc2D create(Circle2D support, double startAngle,
142 double angleExtent) {
143 return new CircleArc2D(support, startAngle, angleExtent);
144 }
145
146 public static CircleArc2D create(Circle2D support, double startAngle,
147 double endAngle, boolean direct) {
148 return new CircleArc2D(support, startAngle, endAngle, direct);
149 }
150
151 public static CircleArc2D create(Point2D center, double radius,
152 double startAngle, double angleExtent) {
153 return new CircleArc2D(center, radius, startAngle, angleExtent);
154 }
155
156 public static CircleArc2D create(Point2D center, double radius,
157 double startAngle, double endAngle, boolean direct) {
158 return new CircleArc2D(center, radius, startAngle, endAngle, direct);
159 }
160
161
162
163
164
165
166
167 private double positionToAngle(double t) {
168 if (t>Math.abs(angleExtent))
169 t = Math.abs(angleExtent);
170 if (t<0)
171 t = 0;
172 if (angleExtent<0)
173 t = -t;
174 t = t+startAngle;
175 return t;
176 }
177
178
179
180
181
182 @Deprecated
183 public Circle2D getSupportCircle() {
184 return circle;
185 }
186
187
188
189
190
191
192
193
194 @Deprecated
195 public void setCenter(Point2D point) {
196 circle.xc = point.getX();
197 circle.yc = point.getY();
198 }
199
200
201
202
203
204
205
206 @Deprecated
207 public void setRadius(double r) {
208 circle.r = r;
209 }
210
211
212
213
214 @Deprecated
215 public void setArc(Point2D center, double radius, double start,
216 double extent) {
217 circle.xc = center.getX();
218 circle.yc = center.getY();
219 circle.r = radius;
220 startAngle = start;
221 angleExtent = extent;
222 }
223
224
225
226
227
228
229
230 public Circle2D getSupportingCircle() {
231 return circle;
232 }
233
234
235
236
237
238
239
240 public CirculinearDomain2D getBuffer(double dist) {
241 return CirculinearCurve2DUtils.computeBuffer(this, dist);
242 }
243
244 public CircleArc2D getParallel (double dist) {
245 double r = circle.getRadius();
246 double r2 = Math.max(angleExtent>0 ? r+dist : r-dist, 0);
247 return new CircleArc2D(
248 circle.getCenter(), r2,
249 startAngle, angleExtent);
250 }
251
252 public double getLength() {
253 return circle.getRadius()*Math.abs(angleExtent);
254 }
255
256
257
258
259 public double getLength(double pos) {
260 return pos*circle.getRadius();
261 }
262
263
264
265
266 public double getPosition(double length) {
267 return length/circle.getRadius();
268 }
269
270
271
272
273 public CirculinearElement2D transform(CircleInversion2D inv) {
274
275 Point2D center = inv.getCenter();
276
277
278 Point2D p1 = this.getFirstPoint().transform(inv);
279 Point2D p2 = this.getLastPoint().transform(inv);
280
281 CirculinearElement2D element = circle.transform(inv);
282
283 if(element instanceof Circle2D) {
284 return new CircleArc2D(
285 (Circle2D)element,
286 Angle2D.getHorizontalAngle(center, p1),
287 Angle2D.getHorizontalAngle(center, p2),
288 !this.isDirect());
289 } else if (element instanceof StraightLine2D) {
290
291 return new LineSegment2D(p1, p2);
292 }
293
294 System.err.println(
295 "CircleArc2D.transform(): error in transforming circle by inversion");
296 return null;
297
298 }
299
300
301
302
303 @Override
304 public double getWindingAngle(java.awt.geom.Point2D point) {
305 Point2D p1 = getFirstPoint();
306 Point2D p2 = getLastPoint();
307
308
309 double angle1 = Angle2D.getHorizontalAngle(point, p1);
310 double angle2 = Angle2D.getHorizontalAngle(point, p2);
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340 boolean b1 = (new StraightLine2D(p1, p2)).isInside(point);
341 boolean b2 = ellipse.isInside(point);
342
343 if (angleExtent>0) {
344 if (b1||b2) {
345 if (angle2>angle1)
346 return angle2-angle1;
347 else
348 return 2*Math.PI-angle1+angle2;
349 } else {
350 if (angle2>angle1)
351 return angle2-angle1-2*Math.PI;
352 else
353 return angle2-angle1;
354 }
355 } else {
356 if (!b1||b2) {
357 if (angle1>angle2)
358 return angle2-angle1;
359 else
360 return angle2-angle1-2*Math.PI;
361 } else {
362 if (angle1>angle2)
363 return angle2-angle1+2*Math.PI;
364 else
365 return angle2-angle1;
366 }
367
368
369
370
371
372
373
374
375 }
376 }
377
378 @Override
379 public boolean isInside(java.awt.geom.Point2D point) {
380 return getSignedDistance(point.getX(), point.getY())<0;
381 }
382
383 @Override
384 public double getSignedDistance(java.awt.geom.Point2D p) {
385 return getSignedDistance(p.getX(), p.getY());
386 }
387
388 @Override
389 public double getSignedDistance(double x, double y) {
390 double dist = getDistance(x, y);
391 Point2D point = new Point2D(x, y);
392
393 boolean direct = angleExtent>0;
394
395 boolean inCircle = circle.isInside(point);
396 if (inCircle)
397 return angleExtent>0 ? -dist : dist;
398
399 Point2D p1 = circle.getPoint(startAngle);
400 Point2D p2 = circle.getPoint(startAngle+angleExtent);
401 boolean onLeft = (new StraightLine2D(p1, p2)).isInside(point);
402
403 if (direct&&!onLeft)
404 return dist;
405 if (!direct&&onLeft)
406 return -dist;
407
408 boolean left1 = (new Ray2D(p1, circle.getTangent(startAngle)))
409 .isInside(point);
410 if (direct&&!left1)
411 return dist;
412 if (!direct&&left1)
413 return -dist;
414
415 boolean left2 = (new Ray2D(p2, circle
416 .getTangent(startAngle+angleExtent))).isInside(point);
417 if (direct&&!left2)
418 return dist;
419 if (!direct&&left2)
420 return -dist;
421
422 if (direct)
423 return -dist;
424 else
425 return dist;
426 }
427
428
429
430
431 @Override
432 public Vector2D getTangent(double t) {
433 t = this.positionToAngle(t);
434
435 double r = circle.getRadius();
436 if (angleExtent>0)
437 return new Vector2D(-r*Math.sin(t), r*Math.cos(t));
438 else
439 return new Vector2D(r*Math.sin(t), -r*Math.cos(t));
440 }
441
442
443
444
445 @Override
446 public Collection<? extends CircleArc2D> getSmoothPieces() {
447 ArrayList<CircleArc2D> list = new ArrayList<CircleArc2D>(1);
448 list.add(this);
449 return list;
450 }
451
452
453
454
455 @Override
456 public boolean isClosed() {
457 return false;
458 }
459
460
461
462
463
464 @Override
465 public double getT0() {
466 return 0;
467 }
468
469
470
471
472
473 @Override
474 public double getT1() {
475 return Math.abs(this.angleExtent);
476 }
477
478
479
480
481 @Override
482 public Point2D getPoint(double t) {
483 t = this.positionToAngle(t);
484 return circle.getPoint(t);
485 }
486
487
488
489
490 @Override
491 public double getPosition(java.awt.geom.Point2D point) {
492 double angle = Angle2D.getHorizontalAngle(circle.getCenter(), point);
493 if (containsAngle(angle))
494 if (angleExtent>0)
495 return Angle2D.formatAngle(angle-startAngle);
496 else
497 return Angle2D.formatAngle(startAngle-angle);
498
499
500 return getFirstPoint().distance(point)<getLastPoint().distance(point) ? 0
501 : Math.abs(angleExtent);
502 }
503
504
505
506
507
508
509
510 @Override
511 public Collection<Point2D> getIntersections(LinearShape2D line) {
512 return Circle2D.getIntersections(this, line);
513 }
514
515 @Override
516 public double project(java.awt.geom.Point2D point) {
517 double angle = circle.project(point);
518
519
520 if (Angle2D.containsAngle(startAngle, startAngle+angleExtent, angle,
521 angleExtent>0)) {
522 if (angleExtent>0)
523 return Angle2D.formatAngle(angle-startAngle);
524 else
525 return Angle2D.formatAngle(startAngle-angle);
526 }
527
528 Point2D p1 = this.getFirstPoint();
529 Point2D p2 = this.getLastPoint();
530 if (p1.getDistance(point)<p2.getDistance(point))
531 return 0;
532 else
533 return Math.abs(angleExtent);
534
535
536
537
538
539
540
541
542
543
544
545
546 }
547
548
549
550
551 @Override
552 public double getDistance(java.awt.geom.Point2D p) {
553 return getDistance(p.getX(), p.getY());
554 }
555
556 @Override
557 public double getDistance(double x, double y) {
558 double angle = Angle2D.getHorizontalAngle(circle.xc, circle.yc, x, y);
559
560 if (containsAngle(angle))
561 return Math.abs(Point2D.getDistance(circle.xc, circle.yc, x, y)
562 -circle.r);
563 else
564 return Math.min(getFirstPoint().getDistance(x, y), getLastPoint()
565 .getDistance(x, y));
566 }
567
568
569 @Override
570 public boolean isBounded() {
571 return true;
572 }
573
574
575
576
577
578 @Override
579 public CircleArc2D getSubCurve(double t0, double t1) {
580
581 if (angleExtent>0) {
582 t0 = Angle2D.formatAngle(startAngle+t0);
583 t1 = Angle2D.formatAngle(startAngle+t1);
584 } else {
585 t0 = Angle2D.formatAngle(startAngle-t0);
586 t1 = Angle2D.formatAngle(startAngle-t1);
587 }
588
589
590 if (!Angle2D.containsAngle(startAngle, startAngle+angleExtent, t0,
591 angleExtent>0))
592 t0 = startAngle;
593 if (!Angle2D.containsAngle(startAngle, startAngle+angleExtent, t1,
594 angleExtent>0))
595 t1 = Angle2D.formatAngle(startAngle+angleExtent);
596
597
598 return new CircleArc2D(circle, t0, t1, angleExtent>0);
599 }
600
601
602
603
604
605 @Override
606 public CircleArc2D getReverseCurve() {
607 return new CircleArc2D(this.circle, Angle2D.formatAngle(startAngle
608 +angleExtent), -angleExtent);
609 }
610
611 @Override
612 public Collection<? extends CircleArc2D> getContinuousCurves() {
613 return wrapCurve(this);
614 }
615
616
617
618
619
620
621 @Override
622 public CurveSet2D<CircleArc2D> clip(Box2D box) {
623
624 CurveSet2D<SmoothCurve2D> set = Curve2DUtils.clipSmoothCurve(this, box);
625
626
627 CurveArray2D<CircleArc2D> result =
628 new CurveArray2D<CircleArc2D>(set.getCurveNumber());
629
630
631 for (Curve2D curve : set.getCurves()) {
632 if (curve instanceof CircleArc2D)
633 result.addCurve((CircleArc2D) curve);
634 }
635 return result;
636 }
637
638
639
640
641
642 @Override
643 public EllipseArc2D transform(AffineTransform2D trans) {
644 if (!AffineTransform2D.isSimilarity(trans))
645 return super.transform(trans);
646
647
648
649
650 Point2D center = circle.getCenter();
651 Point2D point1 = this.getFirstPoint();
652 Point2D point2 = this.getLastPoint();
653
654
655 center = center.transform(trans);
656 point1 = point1.transform(trans);
657 point2 = point2.transform(trans);
658
659
660 double angle1 = Angle2D.getHorizontalAngle(center, point1);
661 double angle2 = Angle2D.getHorizontalAngle(center, point2);
662
663
664 double[] coefs = trans.getCoefficients();
665 double factor = Math.sqrt(coefs[0]*coefs[0]+coefs[3]*coefs[3]);
666
667
668 double xc = center.getX(), yc = center.getY();
669 double r2 = circle.getRadius()*factor;
670 double startAngle = angle1;
671 double angleExtent = Angle2D.formatAngle(angle2-angle1);
672
673 boolean b1 = AffineTransform2D.isDirect(trans);
674 boolean b2 = this.isDirect();
675 if (b1&!b2|!b1&b2)
676 angleExtent = angleExtent-2*Math.PI;
677
678
679 return new CircleArc2D(xc, yc, r2, startAngle, angleExtent);
680 }
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737 @Override
738 public boolean contains(java.awt.geom.Point2D p) {
739 return contains(p.getX(), p.getY());
740 }
741
742 @Override
743 public boolean contains(double x, double y) {
744
745 double r = circle.getRadius();
746 if (Math.abs(Point2D.getDistance(circle.xc, circle.yc, x, y)-r)>Shape2D.ACCURACY)
747 return false;
748
749
750 double angle = Angle2D.getHorizontalAngle(circle.xc, circle.yc, x, y);
751
752
753 return this.containsAngle(angle);
754 }
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811 @Override
812 public String toString() {
813 Point2D center = circle.getCenter();
814 return String.format(Locale.US,
815 "CircleArc2D(%7.2f,%7.2f,%7.2f,%7.5f,%7.5f)",
816 center.getX(), center.getY(), circle.getRadius(),
817 getStartAngle(), getAngleExtent());
818 }
819
820
821
822
823
824 @Override
825 public boolean equals(Object obj) {
826 if (!(obj instanceof EllipseArc2D))
827 return false;
828
829 if (!(obj instanceof CircleArc2D))
830 return super.equals(obj);
831
832 CircleArc2D arc = (CircleArc2D) obj;
833
834 if (Math.abs(circle.xc-arc.circle.xc)>Shape2D.ACCURACY)
835 return false;
836 if (Math.abs(circle.yc-arc.circle.yc)>Shape2D.ACCURACY)
837 return false;
838 if (Math.abs(circle.r-arc.circle.r)>Shape2D.ACCURACY)
839 return false;
840 if (Math.abs(circle.r1-arc.circle.r1)>Shape2D.ACCURACY)
841 return false;
842 if (Math.abs(circle.r2-arc.circle.r2)>Shape2D.ACCURACY)
843 return false;
844 if (Math.abs(circle.theta-arc.circle.theta)>Shape2D.ACCURACY)
845 return false;
846
847
848 if (Math.abs(Angle2D.formatAngle(startAngle)
849 -Angle2D.formatAngle(arc.startAngle))>Shape2D.ACCURACY)
850 return false;
851 if (Math.abs(Angle2D.formatAngle(angleExtent)
852 -Angle2D.formatAngle(arc.angleExtent))>Shape2D.ACCURACY)
853 return false;
854
855
856 return true;
857 }
858
859 @Override
860 public CircleArc2D clone() {
861 return new CircleArc2D(circle.clone(), startAngle, angleExtent);
862 }
863 }