View Javadoc

1   /* file : Circle2D.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 30 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.Iterator;
33  import java.util.Locale;
34  
35  import math.geom2d.*;
36  import math.geom2d.circulinear.CircleLine2D;
37  import math.geom2d.circulinear.CirculinearBoundary2D;
38  import math.geom2d.circulinear.CirculinearCurve2DUtils;
39  import math.geom2d.circulinear.CirculinearDomain2D;
40  import math.geom2d.circulinear.CirculinearElement2D;
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.line.AbstractLine2D;
47  import math.geom2d.line.LinearShape2D;
48  import math.geom2d.line.StraightLine2D;
49  import math.geom2d.transform.CircleInversion2D;
50  
51  /**
52   * A circle in the plane, defined as the set of points located at an equal
53   * distance from the circle center. A circle is a particular ellipse, with first
54   * and second axis length equal.
55   * 
56   * @author dlegland
57   */
58  public class Circle2D extends Ellipse2D
59  implements Cloneable, CirculinearElement2D, CirculinearBoundary2D,
60  CircularShape2D, CircleLine2D {
61  
62      /** the radius of the circle. */
63      protected double r = 0;
64  
65  
66      // ===================================================================
67      // Constructors
68  
69      /** Empty constructor: center 0,0 and radius 0. */
70      public Circle2D() {
71          this(0, 0, 0, true);
72      }
73  
74      /** Create a new circle with specified point center and radius */
75      public Circle2D(Point2D center, double radius) {
76          this(center.getX(), center.getY(), radius, true);
77      }
78  
79      /** Create a new circle with specified center, radius and orientation */
80      public Circle2D(Point2D center, double radius, boolean direct) {
81          this(center.getX(), center.getY(), radius, direct);
82      }
83  
84      /** Create a new circle with specified center and radius */
85      public Circle2D(double xcenter, double ycenter, double radius) {
86          this(xcenter, ycenter, radius, true);
87      }
88  
89      /** Create a new circle with specified center, radius and orientation. */
90      public Circle2D(double xcenter, double ycenter, double radius,
91              boolean direct) {
92          super(xcenter, ycenter, radius, radius, 0, direct);
93          this.r = radius;
94      }
95  
96      
97      // ===================================================================
98      // Static methods
99  
100     /**
101      * Creates a circle from a center and a radius.
102      */
103     public static Circle2D create(Point2D center, double radius) {
104     	return new Circle2D(center, radius);    	
105     }
106     
107     /**
108      * Creates a circle containing 3 points.
109      */
110     public static Circle2D create(Point2D p1, Point2D p2, Point2D p3) {
111     	if(Point2D.isColinear(p1, p2, p3))
112     		throw new ColinearPointsException(p1, p2, p3);
113     	
114     	// create two median lines
115         StraightLine2D line12 = StraightLine2D.createMedian(p1, p2);
116         StraightLine2D line23 = StraightLine2D.createMedian(p2, p3);
117 
118         // check medians are not parallel
119         assert !AbstractLine2D.isParallel(line12, line23) : 
120         	"If points are not colinear, medians should not be parallel";
121 
122         // Compute intersection of the medians, and circle radius
123         Point2D center = AbstractLine2D.getIntersection(line12, line23);
124         double radius = Point2D.getDistance(center, p2);
125 
126         // return the created circle
127         return new Circle2D(center, radius);
128     }
129 
130     public static Collection<Point2D> getIntersections(Circle2D circle1,
131             Circle2D circle2) {
132         ArrayList<Point2D> intersections = new ArrayList<Point2D>(2);
133 
134         // extract center and radius of each circle
135         Point2D center1 = circle1.getCenter();
136         Point2D center2 = circle2.getCenter();
137         double r1 = circle1.getRadius();
138         double r2 = circle2.getRadius();
139 
140         double d = Point2D.getDistance(center1, center2);
141 
142         // case of no intersection
143         if (d<Math.abs(r1-r2)|d>(r1+r2))
144             return intersections;
145 
146         // Angle of line from center1 to center2
147         double angle = Angle2D.getHorizontalAngle(center1, center2);
148 
149         // position of intermediate point
150         double d1 = d/2+(r1*r1-r2*r2)/(2*d);
151         Point2D tmp = Point2D.createPolar(center1, d1, angle);
152 
153         // Add the 2 intersection points
154         double h = Math.sqrt(r1*r1-d1*d1);
155         intersections.add(Point2D.createPolar(tmp, h, angle+Math.PI/2));
156         intersections.add(Point2D.createPolar(tmp, h, angle-Math.PI/2));
157 
158         return intersections;
159     }
160 
161     /**
162      * Compute intersections of a circle with a line. Return an array of
163      * Point2D, of size 0, 1 or 2 depending on the distance between circle and
164      * line. If there are 2 intersections points, the first one in the array is
165      * the first one on the line.
166      */
167     public static Collection<Point2D> getIntersections(
168     		CircularShape2D circle,
169     		LinearShape2D line) {
170     	// initialize array of points (maximum 2 intersections)
171     	ArrayList<Point2D> intersections = new ArrayList<Point2D>(2);
172 
173     	// extract parameters of the circle
174     	Circle2D parent = circle.getSupportingCircle();
175     	Point2D center 	= parent.getCenter();
176     	double radius 	= parent.getRadius();
177     	
178     	// Compute line perpendicular to the test line, and going through the
179     	// circle center
180     	StraightLine2D perp = StraightLine2D.createPerpendicular(line, center);
181 
182     	// Compute distance between line and circle center
183     	Point2D inter 	= perp.getIntersection(new StraightLine2D(line));
184     	assert(inter!=null);
185     	double dist 	= inter.getDistance(center);
186 
187     	// if the distance is the radius of the circle, return the
188     	// intersection point
189     	if (Math.abs(dist-radius)<Shape2D.ACCURACY) {
190     		if (line.contains(inter) && circle.contains(inter))
191     			intersections.add(inter);
192     		return intersections;
193     	}
194 
195     	// compute angle of the line, and distance between 'inter' point and
196     	// each intersection point
197     	double angle 	= line.getHorizontalAngle();
198     	double d2 		= Math.sqrt(radius*radius-dist*dist);
199 
200     	// Compute position and angle of intersection points
201     	Point2D p1 = Point2D.createPolar(inter, d2, angle+Math.PI);
202     	Point2D p2 = Point2D.createPolar(inter, d2, angle);
203 
204     	// add points to the array only if they belong to the line
205     	if (line.contains(p1) && circle.contains(p1))
206     		intersections.add(p1);
207     	if (line.contains(p2) && circle.contains(p2))
208     		intersections.add(p2);
209 
210     	// return the result
211     	return intersections;
212     }
213     
214 
215     // ===================================================================
216     // methods specific to class Circle2D
217 
218     public double getRadius() {
219         return r;
220     }
221 
222     /**
223      * @deprecated conics will become immutable in a future release
224      */
225     @Deprecated
226     public void setRadius(double radius) {
227         this.r = radius;
228         this.r1 = this.r2 = radius;
229     }
230 
231     /**
232      * @deprecated conics will become immutable in a future release
233      */
234     @Deprecated
235     public void setCircle(double xc, double yc, double r) {
236         this.xc = xc;
237         this.yc = yc;
238         this.r = r;
239         this.r1 = r;
240         this.r2 = r;
241     }
242 
243     /**
244      * @deprecated conics will become immutable in a future release
245      */
246     @Deprecated
247     public void setCircle(Point2D center, double r) {
248         this.xc = center.getX();
249         this.yc = center.getY();
250         this.r = r;
251         this.r1 = r;
252         this.r2 = r;
253     }
254 
255     // ===================================================================
256     // methods implementing CircularShape2D interface
257 
258     /**
259      * Returns the circle itself.
260      */
261     public Circle2D getSupportingCircle() {
262         return this;
263     }
264 
265     
266     // ===================================================================
267     // methods implementing the Conic2D interface
268 
269     @Override
270     public Type getConicType() {
271         return Conic2D.Type.CIRCLE;
272     }
273 
274     @Override
275     public boolean isCircle() {
276         return true;
277     }
278 
279     /**
280      * Returns Cartesian equation of the circle:
281      * <p>
282      * <code>(x-xc)^2 + (y-yc)^2 = r^2</code>, giving:
283      * <p>
284      * <code>x^2 + 0*x*y + y^2 -2*xc*x -2*yc*y + xc*xc+yc*yc-r*r = 0</code>.
285      */
286     @Override
287     public double[] getConicCoefficients() {
288         return new double[] { 1, 0, 1, -2*xc, -2*yc, xc*xc+yc*yc-r*r };
289     }
290 
291     /**
292      * Return 0, which is the eccentricity of a circle by definition.
293      */
294     @Override
295     public double getEccentricity() {
296         return 0;
297     }
298 
299     /**
300      * Return the first focus, which for a circle is the same point as the
301      * center.
302      */
303     @Override
304     public Point2D getFocus1() {
305         return new Point2D(xc, yc);
306     }
307 
308     /**
309      * Return the second focus, which for a circle is the same point as the
310      * center.
311      */
312     @Override
313     public Point2D getFocus2() {
314         return new Point2D(xc, yc);
315     }
316 
317     // ===================================================================
318     // Methods implementing the CirculinearCurve2D interface
319 
320 	/* (non-Javadoc)
321 	 * @see math.geom2d.circulinear.CirculinearShape2D#getBuffer(double)
322 	 */
323 	public CirculinearDomain2D getBuffer(double dist) {
324 		return CirculinearCurve2DUtils.computeBuffer(this, dist);
325 	}
326 
327 	/**
328      * Returns the parallel circle located at a distance d from this circle.
329      * For direct circle, distance is positive outside of the circle,
330      * and negative inside. This is the contrary for indirect circles.
331      */
332     @Override
333     public Circle2D getParallel(double d) {
334     	double rp = Math.max(direct ? r+d : r-d, 0);
335         return new Circle2D(xc, yc, rp, direct);
336     }
337 
338     /** Returns perimeter of the circle (equal to 2*PI*radius). */
339     public double getLength() {
340         return r*Math.PI*2;
341     }
342 
343 	/* (non-Javadoc)
344 	 * @see math.geom2d.circulinear.CirculinearCurve2D#getLength(double)
345 	 */
346 	public double getLength(double pos) {
347 		return pos*this.r;
348 	}
349 
350 	/* (non-Javadoc)
351 	 * @see math.geom2d.circulinear.CirculinearCurve2D#getPosition(double)
352 	 */
353 	public double getPosition(double length) {
354 		return length/this.r;
355 	}
356 
357 	/* (non-Javadoc)
358 	 * @see math.geom2d.circulinear.CirculinearCurve2D#transform(math.geom2d.transform.CircleInversion2D)
359 	 */
360 	public CircleLine2D transform(CircleInversion2D inv) {
361 		// Extract inversion parameters
362         Point2D center = inv.getCenter();        
363         Point2D c1 = this.getCenter();
364         
365         // If circles are concentric, creates directly the new circle
366         if(center.getDistance(c1)<Shape2D.ACCURACY) {
367         	double r0 = inv.getRadius();
368         	double r2 = r0*r0 / this.r;
369         	return new Circle2D(center, r2, this.direct);
370         }
371         
372         // line joining centers of the two circles
373         StraightLine2D line = new StraightLine2D(center, c1);
374 
375         // transform the two extreme points of the circle
376         Collection<Point2D> points = this.getIntersections(line);
377         Iterator<Point2D> iter = points.iterator();
378         Point2D p1 = iter.next().transform(inv);
379         Point2D p2 = iter.next().transform(inv);
380 
381         // If the circle contains the inversion center, it transforms into a
382         // straight line
383         if(this.getDistance(center)<Shape2D.ACCURACY) {
384         	Point2D p0 = center.getDistance(p1)<Shape2D.ACCURACY ? p2 : p1;
385         	p0 = p0.transform(inv);
386         	return StraightLine2D.createPerpendicular(line, p0);
387         } 
388         
389         // For regular cases, the circle transforms into an other circle
390         
391         // compute center and diameter of transformed circle
392         double d = p1.getDistance(p2);
393         c1 = Point2D.midPoint(p1, p2);
394 
395         // create the transformed circle
396         return new Circle2D(c1, d/2, !this.isDirect());
397 	}
398 
399 	
400 	// ===================================================================
401     // methods of SmoothCurve2D interface
402 
403     @Override
404     public Vector2D getTangent(double t) {
405         if (!direct)
406             t = -t;
407         double cot  = Math.cos(theta);
408         double sit  = Math.sin(theta);
409         double cost = Math.cos(t);
410         double sint = Math.sin(t);
411 
412         if (direct)
413             return new Vector2D(
414                     -r*sint*cot-r*cost*sit, 
415                     -r*sint*sit+r*cost*cot);
416         else
417             return new Vector2D(
418                     r*sint*cot+r*cost*sit,
419                     r*sint*sit-r*cost*cot);
420     }
421 
422     // ===================================================================
423     // methods of ContinuousCurve2D interface
424 
425     /**
426      * Returns a set of smooth curves, which contains only the circle.
427      */
428 	@Override
429     public Collection<? extends Circle2D> getSmoothPieces() {
430         ArrayList<Circle2D> list = new ArrayList<Circle2D>(1);
431         list.add(this);
432         return list;
433     }
434 
435     // ===================================================================
436     // methods of OrientedCurve2D interface
437 
438     /**
439      * Test whether the point is inside the circle. The test is performed by
440      * translating the point, and re-scaling it such that its coordinates are
441      * expressed in unit circle basis.
442      */
443     @Override
444     public boolean isInside(java.awt.geom.Point2D point) {
445         double xp = (point.getX()-this.xc)/this.r;
446         double yp = (point.getY()-this.yc)/this.r;
447         return (xp*xp+yp*yp<1)^!direct;
448     }
449 
450     @Override
451     public double getSignedDistance(java.awt.geom.Point2D point) {
452         return getSignedDistance(point.getX(), point.getY());
453     }
454 
455     @Override
456     public double getSignedDistance(double x, double y) {
457         if (direct)
458             return Point2D.getDistance(xc, yc, x, y)-r;
459         else
460             return r-Point2D.getDistance(xc, yc, x, y);
461     }
462 
463     // ===================================================================
464     // methods of Curve2D interface
465 
466     /**
467      * Get the position of the curve from internal parametric representation,
468      * depending on the parameter t. This parameter is between the two limits 0
469      * and 2*Math.PI.
470      */
471     @Override
472     public Point2D getPoint(double t) {
473         double angle = theta+t;
474         if (!direct)
475             angle = theta-t;
476         return new Point2D(xc+r*Math.cos(angle), yc+r*Math.sin(angle));
477     }
478 
479     /**
480      * Get the first point of the circle, which is the same as the last point.
481      * 
482      * @return the first point of the curve
483      */
484     @Override
485     public Point2D getFirstPoint() {
486         return new Point2D(xc+r*Math.cos(theta), yc+r*Math.sin(theta));
487     }
488 
489     /**
490      * Get the last point of the circle, which is the same as the first point.
491      * 
492      * @return the last point of the curve.
493      */
494     @Override
495     public Point2D getLastPoint() {
496         return new Point2D(xc+r*Math.cos(theta), yc+r*Math.sin(theta));
497     }
498 
499     @Override
500     public double getPosition(java.awt.geom.Point2D point) {
501         double angle = 
502             Angle2D.getHorizontalAngle(xc, yc, point.getX(), point.getY());
503         if (direct)
504             return Angle2D.formatAngle(angle-theta);
505         else
506             return Angle2D.formatAngle(theta-angle);
507     }
508 
509     /**
510      * Returns the circle with same center and same radius, but with the other
511      * orientation.
512      */
513     @Override
514     public Circle2D getReverseCurve() {
515         return new Circle2D(this.getCenter().getX(), this.getCenter().getY(),
516                 this.getRadius(), !this.direct);
517     }
518 
519     /**
520      * Returns a new CircleArc2D. t0 and t1 are position on circle.
521      */
522     @Override
523     public CircleArc2D getSubCurve(double t0, double t1) {
524         double startAngle, extent;
525         if (this.direct) {
526             startAngle = t0;
527             extent = Angle2D.formatAngle(t1-t0);
528         } else {
529             extent = -Angle2D.formatAngle(t1-t0);
530             startAngle = Angle2D.formatAngle(-t0);
531         }
532         return new CircleArc2D(this, startAngle, extent);
533     }
534 
535     @Override
536     public Collection<? extends Circle2D> getContinuousCurves() {
537     	return wrapCurve(this);
538     }
539 
540     // ===================================================================
541     // methods of Shape2D interface
542 
543     @Override
544     public double getDistance(java.awt.geom.Point2D point) {
545         return Math.abs(Point2D.getDistance(xc, yc, point.getX(),
546                 point.getY())
547                 -r);
548     }
549 
550     @Override
551     public double getDistance(double x, double y) {
552         return Math.abs(Point2D.getDistance(xc, yc, x, y)-r);
553     }
554 
555     /**
556      * Compute intersections of the circle with a line. Return an array of
557      * Point2D, of size 0, 1 or 2 depending on the distance between circle and
558      * line. If there are 2 intersections points, the first one in the array is
559      * the first one on the line.
560      */
561     @Override
562     public Collection<Point2D> getIntersections(LinearShape2D line) {
563     	return Circle2D.getIntersections(this, line);
564     }
565 
566     /**
567      * Clip the circle by a box. The result is an instance of CurveSet2D<SmoothOrientedCurve2D>,
568      * which contains only instances of CircleArc2D or Circle2D. If the circle
569      * is not clipped, the result is an instance of CurveSet2D<SmoothOrientedCurve2D>
570      * which contains 0 curves.
571      */
572     @Override
573     public CurveSet2D<? extends CircularShape2D> clip(Box2D box) {
574         // Clip the curve
575         CurveSet2D<SmoothCurve2D> set = 
576         	Curve2DUtils.clipSmoothCurve(this, box);
577 
578         // Stores the result in appropriate structure
579         CurveArray2D<CircularShape2D> result = 
580         	new CurveArray2D<CircularShape2D>(set.getCurveNumber());
581 
582         // convert the result
583         for (Curve2D curve : set.getCurves()) {
584             if (curve instanceof CircleArc2D)
585                 result.addCurve((CircleArc2D) curve);
586             if (curve instanceof Circle2D)
587                 result.addCurve((Circle2D) curve);
588         }
589         return result;
590     }
591 
592     // ===================================================================
593     // methods of Shape interface
594 
595     /**
596      * Return true if the point (x, y) lies exactly on the circle.
597      */
598     @Override
599     public boolean contains(double x, double y) {
600         return Math.abs(getDistance(x, y))<=Shape2D.ACCURACY;
601     }
602 
603     @Override
604     public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
605         double cot = Math.cos(theta);
606         double sit = Math.sin(theta);
607         double cost, sint;
608 
609         if (direct)
610             for (double t = .1; t<Math.PI*2; t += .1) {
611                 cost = Math.cos(t);
612                 sint = Math.sin(t);
613                 path.lineTo(
614                         (float) (xc+r*cost*cot-r*sint*sit),
615                         (float) (yc+r*cost*sit+r*sint*cot));
616             }
617         else
618             for (double t = .1; t<Math.PI*2; t += .1) {
619                 cost = Math.cos(t);
620                 sint = Math.sin(t);
621                 path.lineTo(
622                         (float) (xc+r*cost*cot+r*sint*sit),
623                         (float) (yc+r*cost*sit-r*sint*cot));
624             }
625 
626         // line to first point
627         path.lineTo((float) (xc+r*cot), (float) (yc+r*sit));
628 
629         return path;
630     }
631 
632     @Override
633     public void draw(Graphics2D g2) {
634         java.awt.geom.Ellipse2D.Double ellipse = 
635             new java.awt.geom.Ellipse2D.Double(xc-r, yc-r, 2*r, 2*r);
636         g2.draw(ellipse);
637     }
638 
639     // ===================================================================
640     // methods of Object interface
641 
642     @Override
643     public String toString() {
644         return String.format(Locale.US, 
645                 "Circle2D(%7.2f,%7.2f,%7.2f,%s)",
646                 xc, yc, r, direct?"true":"false");
647     }
648 
649     @Override
650     public boolean equals(Object obj) {
651         if (!(obj instanceof Ellipse2D))
652             return false;
653 
654         if (obj instanceof Circle2D) {
655             Circle2D circle = (Circle2D) obj;
656 
657             if (Math.abs(circle.xc-xc)>Shape2D.ACCURACY)
658                 return false;
659             if (Math.abs(circle.yc-yc)>Shape2D.ACCURACY)
660                 return false;
661             if (Math.abs(circle.r-r)>Shape2D.ACCURACY)
662                 return false;
663             if (circle.direct!=direct)
664                 return false;
665             return true;
666         }
667         return super.equals(obj);
668     }
669 
670     @Override
671     public Circle2D clone() {
672         return new Circle2D(xc, yc, r, direct);
673     }
674     
675 }