Circle.java
/*
* Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.irurueta.geometry;
import com.irurueta.algebra.AlgebraException;
import com.irurueta.algebra.Matrix;
import java.io.Serializable;
/**
* This class defines a circle.
*/
public class Circle implements Serializable {
/**
* Constant defining minimum allowed radius.
*/
public static final double MIN_RADIUS = 0.0;
/**
* Constant defining default threshold value used when none is provided.
*/
public static final double DEFAULT_THRESHOLD = 1e-9;
/**
* Constant defining minimum allowed threshold.
*/
public static final double MIN_THRESHOLD = 0.0;
/**
* Constant defining machine precision.
*/
public static final double EPS = 1e-12;
/**
* Center of circle.
*/
private Point2D center;
/**
* Radius of circle.
*/
private double radius;
/**
* Empty constructor.
* Creates circle located at space origin (0,0) with radius 1.0.
*/
public Circle() {
center = Point2D.create();
radius = 1.0;
}
/**
* Constructor.
* Sets center and radius of circle.
*
* @param center Center of circle.
* @param radius Radius of circle.
* @throws IllegalArgumentException Raised if provided radius is negative.
*/
public Circle(final Point2D center, final double radius) {
setCenterAndRadius(center, radius);
}
/**
* Constructor.
* Computes a circle by using three points that must belong to its locus.
*
* @param point1 point 1.
* @param point2 point 2.
* @param point3 point 3.
* @throws ColinearPointsException if provided set of points are coincident
* or co-linear (form a single line). In such cases a singularity occurs
* since a circle having an infinite radius would be required to contain all
* three points in its locus.
*/
public Circle(final Point2D point1, final Point2D point2, final Point2D point3) throws ColinearPointsException {
setParametersFromPoints(point1, point2, point3);
}
/**
* Constructor.
* Computes a circle from a valid conic corresponding to a circle.
*
* @param conic a conic to create a circle from.
* @throws IllegalArgumentException if provided conic is not a circle.
*/
public Circle(final Conic conic) {
setFromConic(conic);
}
/**
* Returns center of circle.
*
* @return Center of circle.
*/
public Point2D getCenter() {
return center;
}
/**
* Sets center of circle.
*
* @param center Center of circle.
* @throws NullPointerException Raised if provided center is null.
*/
public void setCenter(final Point2D center) {
if (center == null) {
throw new NullPointerException();
}
this.center = center;
}
/**
* Returns radius of circle.
*
* @return Radius of circle.
*/
public double getRadius() {
return radius;
}
/**
* Sets radius of circle.
*
* @param radius Radius of circle.
* @throws IllegalArgumentException Raised if provided radius is negative.
*/
public void setRadius(final double radius) {
if (radius < MIN_RADIUS) {
throw new IllegalArgumentException();
}
this.radius = radius;
}
/**
* Sets center and radius of this circle.
*
* @param center Center to be set.
* @param radius Radius to be set.
* @throws IllegalArgumentException Raised if provided radius is negative.
* @throws NullPointerException Raised if provided center is null.
*/
public final void setCenterAndRadius(final Point2D center, final double radius) {
setRadius(radius);
setCenter(center);
}
/**
* Sets parameters of a circle by using three points that must belong to its
* locus.
*
* @param point1 point 1.
* @param point2 point 2.
* @param point3 point 3.
* @throws ColinearPointsException if provided set of points are coincident
* or co-linear (form a single line). In such cases a singularity occurs
* since a circle having an infinite radius would be required to contain all
* three points in its locus.
*/
public final void setParametersFromPoints(
final Point2D point1, final Point2D point2, final Point2D point3) throws ColinearPointsException {
// normalize points to increase accuracy
point1.normalize();
point2.normalize();
point3.normalize();
try {
final var m = new Matrix(3, 3);
final var b = new double[3];
// 1st point
var x = point1.getHomX();
var y = point1.getHomY();
var w = point1.getHomW();
m.setElementAt(0, 0, 2.0 * x * w);
m.setElementAt(0, 1, 2.0 * y * w);
m.setElementAt(0, 2, w * w);
b[0] = -x * x - y * y;
// 2nd point
x = point2.getHomX();
y = point2.getHomY();
w = point2.getHomW();
m.setElementAt(1, 0, 2.0 * x * w);
m.setElementAt(1, 1, 2.0 * y * w);
m.setElementAt(1, 2, w * w);
b[1] = -x * x - y * y;
// 3rd point
x = point3.getHomX();
y = point3.getHomY();
w = point3.getHomW();
m.setElementAt(2, 0, 2.0 * x * w);
m.setElementAt(2, 1, 2.0 * y * w);
m.setElementAt(2, 2, w * w);
b[2] = -x * x - y * y;
// normalize each row to increase accuracy
final var row = new double[3];
double rowNorm;
for (int j = 0; j < 3; j++) {
m.getSubmatrixAsArray(j, 0, j, 2, row);
rowNorm = com.irurueta.algebra.Utils.normF(row);
for (int i = 0; i < 3; i++) {
m.setElementAt(j, i, m.getElementAt(j, i) / rowNorm);
}
b[j] /= rowNorm;
}
final var params = com.irurueta.algebra.Utils.solve(m, b);
// d = -cx
final var d = params[0];
// e = -cy
final var e = params[1];
// f = cx^2 + cy^2 - R^2
final var f = params[2];
// compute center
final var inhomCx = -d;
final var inhomCy = -e;
final var c = new InhomogeneousPoint2D(inhomCx, inhomCy);
// compute radius
final var r = Math.sqrt(inhomCx * inhomCx + inhomCy * inhomCy - f);
setCenterAndRadius(c, r);
} catch (final AlgebraException e) {
throw new ColinearPointsException(e);
}
}
/**
* Returns area of a circle having provided radius.
*
* @param radius Radius of a circle.
* @return Area of a circle having provided radius.
* @throws IllegalArgumentException Raised if provided radius is negative.
*/
public static double area(final double radius) {
if (radius < MIN_RADIUS) {
throw new IllegalArgumentException();
}
return Math.PI * radius * radius;
}
/**
* Returns area of this circle.
*
* @return Area of this circle.
*/
public double getArea() {
return area(radius);
}
/**
* Returns perimeter of a circle having provided radius.
*
* @param radius Radius of a circle.
* @return Perimeter of a circle having provided radius.
* @throws IllegalArgumentException Raised if provided radius is negative.
*/
public static double perimeter(final double radius) {
if (radius < MIN_RADIUS) {
throw new IllegalArgumentException();
}
return 2.0 * Math.PI * radius;
}
/**
* Returns perimeter of this circle.
*
* @return Perimeter of this circle.
*/
public double getPerimeter() {
return perimeter(radius);
}
/**
* Gets curvature for provided radius.
* Curvature of a circle is always the reciprocal of its radius.
*
* @param radius radius of a circle.
* @return curvature of a circle.
*/
public static double curvature(final double radius) {
return 1.0 / radius;
}
/**
* Gets curvature of radius at provided point.
* Curvature of a circle is always the reciprocal of its radius.
*
* @return curvature of circle.
*/
public double getCurvature() {
return curvature(radius);
}
/**
* Determines if provided point is inside this circle or not up to a certain
* threshold.
* If provided threshold is positive, the circle behaves as if it was a
* larger circle increased by threshold amount, if provided threshold is
* negative, the circle behaves as if it was a smaller circle decreased by
* threshold amount in radius.
*
* @param point Point to be checked.
* @param threshold Threshold to determine if point is inside or not.
* @return True if point is considered to be inside this circle, false
* otherwise.
*/
public boolean isInside(final Point2D point, final double threshold) {
return point.distanceTo(center) - threshold <= radius;
}
/**
* Determines if provided point is inside this circle or not.
*
* @param point Point to be checked.
* @return True if point is considered to be inside this circle, false
* otherwise.
*/
public boolean isInside(final Point2D point) {
return isInside(point, 0.0);
}
/**
* Returns distance from provided point to the closest point located in the
* circle boundary.
* Returned distance will be negative when point is inside of circle, and
* positive otherwise.
*
* @param point Point to be checked.
* @return Distance from point to circle boundary.
*/
public double getSignedDistance(final Point2D point) {
return signedDistance(this, point);
}
/**
* Returns distance from provided point to the closest point located in
* provided circle boundary.
* Returned distance will be negative when point is inside of circle, and
* positive otherwise.
*
* @param circle A circle.
* @param point Point to be checked.
* @return Distance from point to provided circle boundary.
*/
public static double signedDistance(final Circle circle, final Point2D point) {
return point.distanceTo(circle.getCenter()) - circle.getRadius();
}
/**
* Returns distance from provided point to the closest point located in the
* circle boundary.
*
* @param point Point to be checked.
* @return Distance from point to circle boundary.
*/
public double getDistance(final Point2D point) {
return Math.abs(getSignedDistance(point));
}
/**
* Returns distance from provided point to the closest point located in
* provided circle boundary.
*
* @param circle A circle.
* @param point Point to be checked.
* @return Distance from point to provided circle boundary.
*/
public static double distance(final Circle circle, final Point2D point) {
return Math.abs(signedDistance(circle, point));
}
/**
* Returns closest point to provided point that is located in this circle
* boundary.
*
* @param point A point to be checked.
* @return Closest point laying in circle boundary.
* @throws UndefinedPointException Raised if provided point is at circle
* center or very close to it.
*/
public Point2D getClosestPoint(final Point2D point) throws UndefinedPointException {
final var result = Point2D.create();
closestPoint(point, result);
return result;
}
/**
* Computes closest point to provided point that is located in this circle
* boundary and stores the result in provided result instance.
*
* @param point A point to be checked.
* @param result Instance where result will be stored.
* @throws UndefinedPointException Raised if provided point is at circle
* center or very close to ti.
*/
public void closestPoint(final Point2D point, final Point2D result) throws UndefinedPointException {
var directionX = point.getInhomX() - center.getInhomX();
var directionY = point.getInhomY() - center.getInhomY();
// normalize direction and multiply by radius to set result as locus of
// circle
final var norm = Math.sqrt(directionX * directionX + directionY * directionY);
// check if point is at center or very close to center, in that case the
// closest point cannot be found (would be all points of a circle)
if (norm < EPS) {
throw new UndefinedPointException();
}
directionX *= radius / norm;
directionY *= radius / norm;
result.setInhomogeneousCoordinates(center.getInhomX() + directionX,
center.getInhomY() + directionY);
}
/**
* Determines whether provided point lies at circle boundary or not up to
* a certain threshold.
*
* @param point Point to be checked.
* @param threshold A small threshold to determine whether point lies at
* circle boundary.
* @return True if point lies at circle boundary, false otherwise.
* @throws IllegalArgumentException Raised if provided threshold is
* negative.
*/
public boolean isLocus(final Point2D point, final double threshold) {
if (threshold < MIN_THRESHOLD) {
throw new IllegalArgumentException();
}
return Math.abs(point.distanceTo(center) - radius) <= threshold;
}
/**
* Determines whether provided point lies at circle boundary or not.
*
* @param point Point to be checked.
* @return True if point lies at circle boundary, false otherwise.
*/
public boolean isLocus(final Point2D point) {
return isLocus(point, DEFAULT_THRESHOLD);
}
/**
* Returns a line tangent to this circle at provided point. Provided point
* must be locus of this circle, otherwise a NotLocusException will be
* thrown.
*
* @param point a locus point of this circle.
* @return a 2D line tangent to this circle at provided point.
* @throws NotLocusException if provided point is not locus of this circle
* up to DEFAULT_THRESHOLD.
*/
public Line2D getTangentLineAt(final Point2D point) throws NotLocusException {
return getTangentLineAt(point, DEFAULT_THRESHOLD);
}
/**
* Returns a line tangent to this circle at provided point. Provided point
* must be locus of this circle, otherwise a NotLocusException will be
* thrown.
*
* @param point a locus point of this circle.
* @param threshold threshold to determine if provided point is locus.
* @return a 2D line tangent to this circle at provided point.
* @throws NotLocusException if provided point is not locus of this circle
* up to provided threshold.
* @throws IllegalArgumentException if provided threshold is negative.
*/
public Line2D getTangentLineAt(final Point2D point, final double threshold) throws NotLocusException {
final var line = new Line2D();
tangentLineAt(point, line, threshold);
return line;
}
/**
* Computes a line tangent to this circle at provided point. Provided point
* must be locus of this circle, otherwise a NotLocusException will be
* thrown.
*
* @param point a locus point of this circle.
* @param line instance of a 2D line where result will be stored.
* @param threshold threshold to determine if provided point is locus.
* @throws NotLocusException if provided point is not locus of this circle
* up to provided threshold.
* @throws IllegalArgumentException if provided threshold is negative.
*/
public void tangentLineAt(final Point2D point, final Line2D line, final double threshold) throws NotLocusException {
if (!isLocus(point, threshold)) {
throw new NotLocusException();
}
point.normalize();
center.normalize();
// C = [1 0 d]
// [0 1 e]
// [d e f]
// Hence line is l = C * p, where C is the circle conic and p is a
// point in the locus of the circle
final var homX = point.getHomX();
final var homY = point.getHomY();
final var homW = point.getHomW();
final var cx = center.getInhomX();
final var cy = center.getInhomY();
final var conicD = -cx;
final var conicE = -cy;
final var conicF = cx * cx + cy * cy - radius * radius;
final var lineA = homX + conicD * homW;
final var lineB = homY + conicE * homW;
final var lineC = conicD * homX + conicE * homY + conicF * homW;
line.setParameters(lineA, lineB, lineC);
}
/**
* Converts this circle into a conic.
* Conics are a more general representation of circles.
*
* @return A conic representing this circle.
*/
public Conic toConic() {
center.normalize();
// use inhomogeneous center coordinates
final var cx = center.getInhomX();
final var cy = center.getInhomY();
final var a = 1.0;
final var b = 0.0;
final var c = 1.0;
final var d = -cx;
final var e = -cy;
final var f = cx * cx + cy * cy - radius * radius;
return new Conic(a, b, c, d, e, f);
}
/**
* Set parameters of this circle from a valid conic corresponding to a
* circle.
*
* @param conic conic to set parameters from.
* @throws IllegalArgumentException if provided conic is not a circle.
*/
public final void setFromConic(final Conic conic) {
if (conic.getConicType() != ConicType.CIRCLE_CONIC_TYPE) {
throw new IllegalArgumentException();
}
conic.normalize();
final var a = conic.getA();
// normalize parameters so that a = 1.0, d = -cx, e = -cy and
// f = cx^ + cy^2 - r^
final var normD = conic.getD() / a;
final var normE = conic.getE() / a;
final var normF = conic.getF() / a;
final var cx = -normD;
final var cy = -normE;
final var r = Math.sqrt(cx * cx + cy * cy - normF);
center = new InhomogeneousPoint2D(cx, cy);
radius = r;
}
}