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 }