View Javadoc
1   /*
2    * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.geometry;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  
21  import java.io.Serializable;
22  
23  /**
24   * This class defines a circle.
25   */
26  public class Circle implements Serializable {
27  
28      /**
29       * Constant defining minimum allowed radius.
30       */
31      public static final double MIN_RADIUS = 0.0;
32  
33      /**
34       * Constant defining default threshold value used when none is provided.
35       */
36      public static final double DEFAULT_THRESHOLD = 1e-9;
37  
38      /**
39       * Constant defining minimum allowed threshold.
40       */
41      public static final double MIN_THRESHOLD = 0.0;
42  
43      /**
44       * Constant defining machine precision.
45       */
46      public static final double EPS = 1e-12;
47  
48      /**
49       * Center of circle.
50       */
51      private Point2D center;
52  
53      /**
54       * Radius of circle.
55       */
56      private double radius;
57  
58      /**
59       * Empty constructor.
60       * Creates circle located at space origin (0,0) with radius 1.0.
61       */
62      public Circle() {
63          center = Point2D.create();
64          radius = 1.0;
65      }
66  
67      /**
68       * Constructor.
69       * Sets center and radius of circle.
70       *
71       * @param center Center of circle.
72       * @param radius Radius of circle.
73       * @throws IllegalArgumentException Raised if provided radius is negative.
74       */
75      public Circle(final Point2D center, final double radius) {
76          setCenterAndRadius(center, radius);
77      }
78  
79      /**
80       * Constructor.
81       * Computes a circle by using three points that must belong to its locus.
82       *
83       * @param point1 point 1.
84       * @param point2 point 2.
85       * @param point3 point 3.
86       * @throws ColinearPointsException if provided set of points are coincident
87       *                                 or co-linear (form a single line). In such cases a singularity occurs
88       *                                 since a circle having an infinite radius would be required to contain all
89       *                                 three points in its locus.
90       */
91      public Circle(final Point2D point1, final Point2D point2, final Point2D point3) throws ColinearPointsException {
92          setParametersFromPoints(point1, point2, point3);
93      }
94  
95      /**
96       * Constructor.
97       * Computes a circle from a valid conic corresponding to a circle.
98       *
99       * @param conic a conic to create a circle from.
100      * @throws IllegalArgumentException if provided conic is not a circle.
101      */
102     public Circle(final Conic conic) {
103         setFromConic(conic);
104     }
105 
106     /**
107      * Returns center of circle.
108      *
109      * @return Center of circle.
110      */
111     public Point2D getCenter() {
112         return center;
113     }
114 
115     /**
116      * Sets center of circle.
117      *
118      * @param center Center of circle.
119      * @throws NullPointerException Raised if provided center is null.
120      */
121     public void setCenter(final Point2D center) {
122         if (center == null) {
123             throw new NullPointerException();
124         }
125         this.center = center;
126     }
127 
128     /**
129      * Returns radius of circle.
130      *
131      * @return Radius of circle.
132      */
133     public double getRadius() {
134         return radius;
135     }
136 
137     /**
138      * Sets radius of circle.
139      *
140      * @param radius Radius of circle.
141      * @throws IllegalArgumentException Raised if provided radius is negative.
142      */
143     public void setRadius(final double radius) {
144         if (radius < MIN_RADIUS) {
145             throw new IllegalArgumentException();
146         }
147 
148         this.radius = radius;
149     }
150 
151     /**
152      * Sets center and radius of this circle.
153      *
154      * @param center Center to be set.
155      * @param radius Radius to be set.
156      * @throws IllegalArgumentException Raised if provided radius is negative.
157      * @throws NullPointerException     Raised if provided center is null.
158      */
159     public final void setCenterAndRadius(final Point2D center, final double radius) {
160         setRadius(radius);
161         setCenter(center);
162     }
163 
164     /**
165      * Sets parameters of a circle by using three points that must belong to its
166      * locus.
167      *
168      * @param point1 point 1.
169      * @param point2 point 2.
170      * @param point3 point 3.
171      * @throws ColinearPointsException if provided set of points are coincident
172      *                                 or co-linear (form a single line). In such cases a singularity occurs
173      *                                 since a circle having an infinite radius would be required to contain all
174      *                                 three points in its locus.
175      */
176     public final void setParametersFromPoints(
177             final Point2D point1, final Point2D point2, final Point2D point3) throws ColinearPointsException {
178 
179         // normalize points to increase accuracy
180         point1.normalize();
181         point2.normalize();
182         point3.normalize();
183 
184         try {
185             final var m = new Matrix(3, 3);
186             final var b = new double[3];
187 
188             // 1st point
189             var x = point1.getHomX();
190             var y = point1.getHomY();
191             var w = point1.getHomW();
192             m.setElementAt(0, 0, 2.0 * x * w);
193             m.setElementAt(0, 1, 2.0 * y * w);
194             m.setElementAt(0, 2, w * w);
195             b[0] = -x * x - y * y;
196 
197             // 2nd point
198             x = point2.getHomX();
199             y = point2.getHomY();
200             w = point2.getHomW();
201             m.setElementAt(1, 0, 2.0 * x * w);
202             m.setElementAt(1, 1, 2.0 * y * w);
203             m.setElementAt(1, 2, w * w);
204             b[1] = -x * x - y * y;
205 
206             // 3rd point
207             x = point3.getHomX();
208             y = point3.getHomY();
209             w = point3.getHomW();
210             m.setElementAt(2, 0, 2.0 * x * w);
211             m.setElementAt(2, 1, 2.0 * y * w);
212             m.setElementAt(2, 2, w * w);
213             b[2] = -x * x - y * y;
214 
215             // normalize each row to increase accuracy
216             final var row = new double[3];
217             double rowNorm;
218 
219             for (int j = 0; j < 3; j++) {
220                 m.getSubmatrixAsArray(j, 0, j, 2, row);
221                 rowNorm = com.irurueta.algebra.Utils.normF(row);
222                 for (int i = 0; i < 3; i++) {
223                     m.setElementAt(j, i, m.getElementAt(j, i) / rowNorm);
224                 }
225                 b[j] /= rowNorm;
226             }
227 
228             final var params = com.irurueta.algebra.Utils.solve(m, b);
229 
230             // d = -cx
231             final var d = params[0];
232             // e = -cy
233             final var e = params[1];
234             // f = cx^2 + cy^2 - R^2
235             final var f = params[2];
236 
237             // compute center
238             final var inhomCx = -d;
239             final var inhomCy = -e;
240             final var c = new InhomogeneousPoint2D(inhomCx, inhomCy);
241 
242             // compute radius
243             final var r = Math.sqrt(inhomCx * inhomCx + inhomCy * inhomCy - f);
244 
245             setCenterAndRadius(c, r);
246         } catch (final AlgebraException e) {
247             throw new ColinearPointsException(e);
248         }
249     }
250 
251     /**
252      * Returns area of a circle having provided radius.
253      *
254      * @param radius Radius of a circle.
255      * @return Area of a circle having provided radius.
256      * @throws IllegalArgumentException Raised if provided radius is negative.
257      */
258     public static double area(final double radius) {
259         if (radius < MIN_RADIUS) {
260             throw new IllegalArgumentException();
261         }
262         return Math.PI * radius * radius;
263     }
264 
265     /**
266      * Returns area of this circle.
267      *
268      * @return Area of this circle.
269      */
270     public double getArea() {
271         return area(radius);
272     }
273 
274     /**
275      * Returns perimeter of a circle having provided radius.
276      *
277      * @param radius Radius of a circle.
278      * @return Perimeter of a circle having provided radius.
279      * @throws IllegalArgumentException Raised if provided radius is negative.
280      */
281     public static double perimeter(final double radius) {
282         if (radius < MIN_RADIUS) {
283             throw new IllegalArgumentException();
284         }
285         return 2.0 * Math.PI * radius;
286     }
287 
288     /**
289      * Returns perimeter of this circle.
290      *
291      * @return Perimeter of this circle.
292      */
293     public double getPerimeter() {
294         return perimeter(radius);
295     }
296 
297     /**
298      * Gets curvature for provided radius.
299      * Curvature of a circle is always the reciprocal of its radius.
300      *
301      * @param radius radius of a circle.
302      * @return curvature of a circle.
303      */
304     public static double curvature(final double radius) {
305         return 1.0 / radius;
306     }
307 
308     /**
309      * Gets curvature of radius at provided point.
310      * Curvature of a circle is always the reciprocal of its radius.
311      *
312      * @return curvature of circle.
313      */
314     public double getCurvature() {
315         return curvature(radius);
316     }
317 
318     /**
319      * Determines if provided point is inside this circle or not up to a certain
320      * threshold.
321      * If provided threshold is positive, the circle behaves as if it was a
322      * larger circle increased by threshold amount, if provided threshold is
323      * negative, the circle behaves as if it was a smaller circle decreased by
324      * threshold amount in radius.
325      *
326      * @param point     Point to be checked.
327      * @param threshold Threshold to determine if point is inside or not.
328      * @return True if point is considered to be inside this circle, false
329      * otherwise.
330      */
331     public boolean isInside(final Point2D point, final double threshold) {
332         return point.distanceTo(center) - threshold <= radius;
333     }
334 
335     /**
336      * Determines if provided point is inside this circle or not.
337      *
338      * @param point Point to be checked.
339      * @return True if point is considered to be inside this circle, false
340      * otherwise.
341      */
342     public boolean isInside(final Point2D point) {
343         return isInside(point, 0.0);
344     }
345 
346     /**
347      * Returns distance from provided point to the closest point located in the
348      * circle boundary.
349      * Returned distance will be negative when point is inside of circle, and
350      * positive otherwise.
351      *
352      * @param point Point to be checked.
353      * @return Distance from point to circle boundary.
354      */
355     public double getSignedDistance(final Point2D point) {
356         return signedDistance(this, point);
357     }
358 
359     /**
360      * Returns distance from provided point to the closest point located in
361      * provided circle boundary.
362      * Returned distance will be negative when point is inside of circle, and
363      * positive otherwise.
364      *
365      * @param circle A circle.
366      * @param point  Point to be checked.
367      * @return Distance from point to provided circle boundary.
368      */
369     public static double signedDistance(final Circle circle, final Point2D point) {
370         return point.distanceTo(circle.getCenter()) - circle.getRadius();
371     }
372 
373     /**
374      * Returns distance from provided point to the closest point located in the
375      * circle boundary.
376      *
377      * @param point Point to be checked.
378      * @return Distance from point to circle boundary.
379      */
380     public double getDistance(final Point2D point) {
381         return Math.abs(getSignedDistance(point));
382     }
383 
384     /**
385      * Returns distance from provided point to the closest point located in
386      * provided circle boundary.
387      *
388      * @param circle A circle.
389      * @param point  Point to be checked.
390      * @return Distance from point to provided circle boundary.
391      */
392     public static double distance(final Circle circle, final Point2D point) {
393         return Math.abs(signedDistance(circle, point));
394     }
395 
396     /**
397      * Returns closest point to provided point that is located in this circle
398      * boundary.
399      *
400      * @param point A point to be checked.
401      * @return Closest point laying in circle boundary.
402      * @throws UndefinedPointException Raised if provided point is at circle
403      *                                 center or very close to it.
404      */
405     public Point2D getClosestPoint(final Point2D point) throws UndefinedPointException {
406         final var result = Point2D.create();
407         closestPoint(point, result);
408         return result;
409     }
410 
411     /**
412      * Computes closest point to provided point that is located in this circle
413      * boundary and stores the result in provided result instance.
414      *
415      * @param point  A point to be checked.
416      * @param result Instance where result will be stored.
417      * @throws UndefinedPointException Raised if provided point is at circle
418      *                                 center or very close to ti.
419      */
420     public void closestPoint(final Point2D point, final Point2D result) throws UndefinedPointException {
421 
422         var directionX = point.getInhomX() - center.getInhomX();
423         var directionY = point.getInhomY() - center.getInhomY();
424         // normalize direction and multiply by radius to set result as locus of
425         // circle
426         final var norm = Math.sqrt(directionX * directionX + directionY * directionY);
427 
428         // check if point is at center or very close to center, in that case the
429         // closest point cannot be found (would be all points of a circle)
430         if (norm < EPS) {
431             throw new UndefinedPointException();
432         }
433 
434         directionX *= radius / norm;
435         directionY *= radius / norm;
436 
437         result.setInhomogeneousCoordinates(center.getInhomX() + directionX,
438                 center.getInhomY() + directionY);
439     }
440 
441     /**
442      * Determines whether provided point lies at circle boundary or not up to
443      * a certain threshold.
444      *
445      * @param point     Point to be checked.
446      * @param threshold A small threshold to determine whether point lies at
447      *                  circle boundary.
448      * @return True if point lies at circle boundary, false otherwise.
449      * @throws IllegalArgumentException Raised if provided threshold is
450      *                                  negative.
451      */
452     public boolean isLocus(final Point2D point, final double threshold) {
453         if (threshold < MIN_THRESHOLD) {
454             throw new IllegalArgumentException();
455         }
456 
457         return Math.abs(point.distanceTo(center) - radius) <= threshold;
458     }
459 
460     /**
461      * Determines whether provided point lies at circle boundary or not.
462      *
463      * @param point Point to be checked.
464      * @return True if point lies at circle boundary, false otherwise.
465      */
466     public boolean isLocus(final Point2D point) {
467         return isLocus(point, DEFAULT_THRESHOLD);
468     }
469 
470     /**
471      * Returns a line tangent to this circle at provided point. Provided point
472      * must be locus of this circle, otherwise a NotLocusException will be
473      * thrown.
474      *
475      * @param point a locus point of this circle.
476      * @return a 2D line tangent to this circle at provided point.
477      * @throws NotLocusException if provided point is not locus of this circle
478      *                           up to DEFAULT_THRESHOLD.
479      */
480     public Line2D getTangentLineAt(final Point2D point) throws NotLocusException {
481         return getTangentLineAt(point, DEFAULT_THRESHOLD);
482     }
483 
484     /**
485      * Returns a line tangent to this circle at provided point. Provided point
486      * must be locus of this circle, otherwise a NotLocusException will be
487      * thrown.
488      *
489      * @param point     a locus point of this circle.
490      * @param threshold threshold to determine if provided point is locus.
491      * @return a 2D line tangent to this circle at provided point.
492      * @throws NotLocusException        if provided point is not locus of this circle
493      *                                  up to provided threshold.
494      * @throws IllegalArgumentException if provided threshold is negative.
495      */
496     public Line2D getTangentLineAt(final Point2D point, final double threshold) throws NotLocusException {
497         final var line = new Line2D();
498         tangentLineAt(point, line, threshold);
499         return line;
500     }
501 
502     /**
503      * Computes a line tangent to this circle at provided point. Provided point
504      * must be locus of this circle, otherwise a NotLocusException will be
505      * thrown.
506      *
507      * @param point     a locus point of this circle.
508      * @param line      instance of a 2D line where result will be stored.
509      * @param threshold threshold to determine if provided point is locus.
510      * @throws NotLocusException        if provided point is not locus of this circle
511      *                                  up to provided threshold.
512      * @throws IllegalArgumentException if provided threshold is negative.
513      */
514     public void tangentLineAt(final Point2D point, final Line2D line, final double threshold) throws NotLocusException {
515         if (!isLocus(point, threshold)) {
516             throw new NotLocusException();
517         }
518 
519         point.normalize();
520         center.normalize();
521 
522         // C =   [1  0   d]
523         //       [0  1   e]
524         //       [d  e   f]
525 
526         // Hence line is l = C * p, where C is the circle conic and p is a
527         // point in the locus of the circle
528 
529         final var homX = point.getHomX();
530         final var homY = point.getHomY();
531         final var homW = point.getHomW();
532         final var cx = center.getInhomX();
533         final var cy = center.getInhomY();
534         final var conicD = -cx;
535         final var conicE = -cy;
536         final var conicF = cx * cx + cy * cy - radius * radius;
537 
538         final var lineA = homX + conicD * homW;
539         final var lineB = homY + conicE * homW;
540         final var lineC = conicD * homX + conicE * homY + conicF * homW;
541         line.setParameters(lineA, lineB, lineC);
542     }
543 
544     /**
545      * Converts this circle into a conic.
546      * Conics are a more general representation of circles.
547      *
548      * @return A conic representing this circle.
549      */
550     public Conic toConic() {
551         center.normalize();
552         // use inhomogeneous center coordinates
553         final var cx = center.getInhomX();
554         final var cy = center.getInhomY();
555 
556         final var a = 1.0;
557         final var b = 0.0;
558         final var c = 1.0;
559         final var d = -cx;
560         final var e = -cy;
561         final var f = cx * cx + cy * cy - radius * radius;
562 
563         return new Conic(a, b, c, d, e, f);
564     }
565 
566     /**
567      * Set parameters of this circle from a valid conic corresponding to a
568      * circle.
569      *
570      * @param conic conic to set parameters from.
571      * @throws IllegalArgumentException if provided conic is not a circle.
572      */
573     public final void setFromConic(final Conic conic) {
574         if (conic.getConicType() != ConicType.CIRCLE_CONIC_TYPE) {
575             throw new IllegalArgumentException();
576         }
577 
578         conic.normalize();
579 
580         final var a = conic.getA();
581         // normalize parameters so that a = 1.0, d = -cx, e = -cy and
582         // f = cx^ + cy^2 - r^
583         final var normD = conic.getD() / a;
584         final var normE = conic.getE() / a;
585         final var normF = conic.getF() / a;
586 
587         final var cx = -normD;
588         final var cy = -normE;
589         final var r = Math.sqrt(cx * cx + cy * cy - normF);
590 
591         center = new InhomogeneousPoint2D(cx, cy);
592         radius = r;
593     }
594 }