/*
* $Id$
* This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc
*
* Copyright (c) 2000-2012 Stephane GALLAND.
* Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports,
* Universite de Technologie de Belfort-Montbeliard.
* Copyright (c) 2013-2016 The original authors, and other authors.
*
* 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 org.arakhne.afc.math.geometry.d2.afp;
import java.util.NoSuchElementException;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.Unefficient;
import org.arakhne.afc.math.geometry.CrossingComputationType;
import org.arakhne.afc.math.geometry.PathWindingRule;
import org.arakhne.afc.math.geometry.d2.Point2D;
import org.arakhne.afc.math.geometry.d2.Transform2D;
import org.arakhne.afc.math.geometry.d2.Vector2D;
import org.arakhne.afc.math.geometry.d2.afp.Circle2afp.AbstractCirclePathIterator;
import org.arakhne.afc.vmutil.asserts.AssertMessages;
/** Fonctional interface that represented a 2D ellipse on a plane.
*
* @param <ST> is the type of the general implementation.
* @param <IT> is the type of the implementation of this shape.
* @param <IE> is the type of the path elements.
* @param <P> is the type of the points.
* @param <V> is the type of the vectors.
* @param <B> is the type of the bounding boxes.
* @author $Author: sgalland$
* @author $Author: hjaffali$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public interface Ellipse2afp<
ST extends Shape2afp<?, ?, IE, P, V, B>,
IT extends Ellipse2afp<?, ?, IE, P, V, B>,
IE extends PathElement2afp,
P extends Point2D<? super P, ? super V>,
V extends Vector2D<? super V, ? super P>,
B extends Rectangle2afp<?, ?, IE, P, V, B>>
extends RectangularShape2afp<ST, IT, IE, P, V, B> {
/**
* Replies if the given point is inside the given ellipse.
*
* @param ellx is the min corner of the ellipse.
* @param elly is the min corner of the ellipse.
* @param ellw is the width of the ellipse.
* @param ellh is the height of the ellipse.
* @param px is the point to test.
* @param py is the point to test.
* @return <code>true</code> if the point is inside the ellipse;
* <code>false</code> if not.
*/
@Pure
@SuppressWarnings("checkstyle:magicnumber")
static boolean containsEllipsePoint(double ellx, double elly, double ellw, double ellh, double px, double py) {
assert ellw >= 0. : AssertMessages.positiveOrZeroParameter(2);
assert ellh >= 0. : AssertMessages.positiveOrZeroParameter(3);
// Copied from AWT Ellipse2D
// Normalize the coordinates compared to the ellipse
// having a center at 0, 0 and a radius of 0.5.
if (ellw <= 0. || ellh <= 0.) {
return false;
}
final double normx = (px - ellx) / ellw - 0.5;
final double normy = (py - elly) / ellh - 0.5;
return (normx * normx + normy * normy) <= 0.25;
}
/** Replies the closest point from the given point in the solid ellipse.
* A solid ellipse is an ellipse with a border and an interior area.
*
* @param px is the coordinate of the point.
* @param py is the coordinate of the point.
* @param ex is the coordinate of the min corner of the ellipse
* @param ey is the coordinate of the min corner of the ellipse
* @param ew is the width of the ellipse
* @param eh is the height of the ellipse
* @param result the closest point in the ellipse.
* @see #findsClosestPointShallowEllipsePoint(double, double, double, double, double, double, Point2D)
* @deprecated since 13.0, see {@link #findsClosestPointSolidEllipsePoint(double,
* double, double, double, double, double, Point2D)}
*/
@Deprecated
@Unefficient
static void computeClosestPointToSolidEllipse(
double px, double py, double ex, double ey, double ew, double eh,
Point2D<?, ?> result) {
findsClosestPointSolidEllipsePoint(px, py, ex, ey, ew, eh, result);
}
/** Replies the closest point from the given point in the solid ellipse.
* A solid ellipse is an ellipse with a border and an interior area.
*
* @param px is the coordinate of the point.
* @param py is the coordinate of the point.
* @param ex is the coordinate of the min corner of the ellipse
* @param ey is the coordinate of the min corner of the ellipse
* @param ew is the width of the ellipse
* @param eh is the height of the ellipse
* @param result the closest point in the ellipse.
* @see #findsClosestPointShallowEllipsePoint(double, double, double, double, double, double, Point2D)
*/
@Unefficient
@SuppressWarnings("checkstyle:magicnumber")
static void findsClosestPointSolidEllipsePoint(
double px, double py, double ex, double ey, double ew, double eh,
Point2D<?, ?> result) {
assert ew >= 0. : AssertMessages.positiveOrZeroParameter(4);
assert eh >= 0. : AssertMessages.positiveOrZeroParameter(5);
assert result != null : AssertMessages.notNullParameter(6);
// Translate the point in the local ellipse's coordinate system.
final double e0;
final double e1;
final double translateX;
final double translateY;
double pointX;
double pointY;
final double[] data;
// The basic algorithm assumes ew >= eh => swap coordinates.
if (ew >= eh) {
e0 = ew / 2.;
e1 = eh / 2.;
translateX = ex + e0;
translateY = ey + e1;
pointX = px - translateX;
pointY = py - translateY;
} else {
e0 = eh / 2;
e1 = ew / 2;
translateX = ex + e1;
translateY = ey + e0;
pointX = py - translateY;
pointY = px - translateX;
}
// The basic algorithm work only for the positive quadrant => switch the coordinates if necessary.
if (pointX < 0.) {
if (pointY < 0.) {
data = PrivateAPI.computeClosestPointOnSolidEllipseInPositiveQuadrant(
-pointX, -pointY,
e0, e1, false);
pointY = -data[1];
} else {
data = PrivateAPI.computeClosestPointOnSolidEllipseInPositiveQuadrant(
-pointX, pointY,
e0, e1, false);
pointY = data[1];
}
pointX = -data[0];
} else {
if (pointY < 0.) {
data = PrivateAPI.computeClosestPointOnSolidEllipseInPositiveQuadrant(
pointX, -pointY,
e0, e1, false);
pointY = -data[1];
} else {
data = PrivateAPI.computeClosestPointOnSolidEllipseInPositiveQuadrant(
pointX, pointY,
e0, e1, false);
pointY = data[1];
}
pointX = data[0];
}
// Revert translation and swaping of coordinates
if (ew >= eh) {
result.set(pointX + translateX, pointY + translateY);
} else {
result.set(pointY + translateX, pointX + translateY);
}
}
/** Replies the closest point from the given point in the shallow ellipse.
* A shallow ellipse is an ellipse with a border and not an interior area.
*
* @param px is the coordinate of the point.
* @param py is the coordinate of the point.
* @param ex is the coordinate of the min corner of the ellipse
* @param ey is the coordinate of the min corner of the ellipse
* @param ew is the width of the ellipse
* @param eh is the height of the ellipse
* @param result the closest point in the ellipse.
* @see #findsClosestPointSolidEllipsePoint(double, double, double, double, double, double, Point2D)
* @deprecated since 13.0, see {@link #findsClosestPointShallowEllipsePoint(double,
* double, double, double, double, double, Point2D)}
*/
@Deprecated
static void computeClosestPointToShallowEllipse(double px, double py, double ex, double ey, double ew, double eh,
Point2D<?, ?> result) {
findsClosestPointShallowEllipsePoint(px, py, ex, ey, ew, eh, result);
}
/** Replies the closest point from the given point in the shallow ellipse.
* A shallow ellipse is an ellipse with a border and not an interior area.
*
* @param px is the coordinate of the point.
* @param py is the coordinate of the point.
* @param ex is the coordinate of the min corner of the ellipse
* @param ey is the coordinate of the min corner of the ellipse
* @param ew is the width of the ellipse
* @param eh is the height of the ellipse
* @param result the closest point in the ellipse.
* @see #findsClosestPointSolidEllipsePoint(double, double, double, double, double, double, Point2D)
*/
@Unefficient
@SuppressWarnings("checkstyle:magicnumber")
static void findsClosestPointShallowEllipsePoint(double px, double py, double ex, double ey, double ew, double eh,
Point2D<?, ?> result) {
assert ew >= 0. : AssertMessages.positiveOrZeroParameter(4);
assert eh >= 0. : AssertMessages.positiveOrZeroParameter(5);
assert result != null : AssertMessages.notNullParameter(6);
// Translate the point in the local ellipse's coordinate system.
final double e0;
final double e1;
final double translateX;
final double translateY;
double pointX;
double pointY;
final double[] data;
// The basic algorithm assumes ew >= eh => swap coordinates.
if (ew >= eh) {
e0 = ew / 2.;
e1 = eh / 2.;
translateX = ex + e0;
translateY = ey + e1;
pointX = px - translateX;
pointY = py - translateY;
} else {
e0 = eh / 2.;
e1 = ew / 2.;
translateX = ex + e1;
translateY = ey + e0;
pointX = py - translateY;
pointY = px - translateX;
}
// The basic algorithm work only for the positive quadrant => switch the coordinates if necessary.
if (pointX < 0.) {
if (pointY < 0.) {
data = PrivateAPI.computeClosestPointOnShallowEllipseInPositiveQuadrant(
-pointX, -pointY,
e0, e1, false);
pointY = -data[1];
} else {
data = PrivateAPI.computeClosestPointOnShallowEllipseInPositiveQuadrant(
-pointX, pointY,
e0, e1, false);
pointY = data[1];
}
pointX = -data[0];
} else {
if (pointY < 0.) {
data = PrivateAPI.computeClosestPointOnShallowEllipseInPositiveQuadrant(
pointX, -pointY,
e0, e1, false);
pointY = -data[1];
} else {
data = PrivateAPI.computeClosestPointOnShallowEllipseInPositiveQuadrant(
pointX, pointY,
e0, e1, false);
pointY = data[1];
}
pointX = data[0];
}
// Revert translation and swaping of coordinates
if (ew >= eh) {
result.set(pointX + translateX, pointY + translateY);
} else {
result.set(pointY + translateX, pointX + translateY);
}
}
/** Replies the farthest point from the given point in the shallow ellipse.
* A shallow ellipse is an ellipse with a border and not an interior area.
*
* @param px is the coordinate of the point.
* @param py is the coordinate of the point.
* @param ex is the coordinate of the min corner of the ellipse
* @param ey is the coordinate of the min corner of the ellipse
* @param ew is the width of the ellipse
* @param eh is the height of the ellipse
* @param result the farthest point in the ellipse.
* @deprecated since 13.0, see {@link #findsFarthestPointShallowEllipsePoint(double,
* double, double, double, double, double, Point2D)}
*/
@Deprecated
static void computeFarthestPointToShallowEllipse(double px, double py, double ex, double ey,
double ew, double eh, Point2D<?, ?> result) {
findsFarthestPointShallowEllipsePoint(px, py, ex, ey, ew, eh, result);
}
/** Replies the farthest point from the given point in the shallow ellipse.
* A shallow ellipse is an ellipse with a border and not an interior area.
*
* @param px is the coordinate of the point.
* @param py is the coordinate of the point.
* @param ex is the coordinate of the min corner of the ellipse
* @param ey is the coordinate of the min corner of the ellipse
* @param ew is the width of the ellipse
* @param eh is the height of the ellipse
* @param result the farthest point in the ellipse.
*/
@Unefficient
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:magicnumber"})
static void findsFarthestPointShallowEllipsePoint(double px, double py, double ex, double ey,
double ew, double eh, Point2D<?, ?> result) {
assert ew >= 0. : AssertMessages.positiveOrZeroParameter(4);
assert eh >= 0. : AssertMessages.positiveOrZeroParameter(5);
assert result != null : AssertMessages.notNullParameter(6);
// Translate the point in the local ellipse's coordinate system.
final double e0;
final double e1;
final double translateX;
final double translateY;
double pointX;
double pointY;
final double[] data;
// The basic algorithm assumes ew >= eh => swap coordinates.
if (ew >= eh) {
e0 = ew / 2.;
e1 = eh / 2.;
translateX = ex + e0;
translateY = ey + e1;
pointX = px - translateX;
pointY = py - translateY;
} else {
e0 = eh / 2.;
e1 = ew / 2.;
translateX = ex + e1;
translateY = ey + e0;
pointX = py - translateY;
pointY = px - translateX;
}
// The basic algorithm work only for the positive quadrant for the ellipse, and the
// negative quadrant for the point => switch the coordinates if necessary.
if (pointX < 0.) {
if (pointY < 0.) {
data = PrivateAPI.computeFarthestPointOnShallowEllipseInPositiveQuadrant(
pointX, pointY,
e0, e1, false);
pointY = data[1];
} else {
data = PrivateAPI.computeFarthestPointOnShallowEllipseInPositiveQuadrant(
pointX, -pointY,
e0, e1, false);
pointY = -data[1];
}
pointX = data[0];
} else {
if (pointY < 0.) {
data = PrivateAPI.computeFarthestPointOnShallowEllipseInPositiveQuadrant(
-pointX, pointY,
e0, e1, false);
pointY = data[1];
} else {
data = PrivateAPI.computeFarthestPointOnShallowEllipseInPositiveQuadrant(
-pointX, -pointY,
e0, e1, false);
pointY = -data[1];
}
pointX = -data[0];
}
// Revert translation and swaping of coordinates
if (ew >= eh) {
result.set(pointX + translateX, pointY + translateY);
} else {
result.set(pointY + translateX, pointX + translateY);
}
}
/** Replies if a rectangle is inside in the ellipse.
*
* @param ex is the lowest corner of the ellipse.
* @param ey is the lowest corner of the ellipse.
* @param ewidth is the width of the ellipse.
* @param eheight is the height of the ellipse.
* @param rxmin is the lowest corner of the rectangle.
* @param rymin is the lowest corner of the rectangle.
* @param rxmax is the uppest corner of the rectangle.
* @param rymax is the uppest corner of the rectangle.
* @return <code>true</code> if the given rectangle is inside the ellipse;
* otherwise <code>false</code>.
*/
@Pure
@SuppressWarnings("checkstyle:magicnumber")
static boolean containsEllipseRectangle(double ex, double ey, double ewidth, double eheight,
double rxmin, double rymin, double rxmax, double rymax) {
assert ewidth >= 0. : AssertMessages.positiveOrZeroParameter(2);
assert eheight >= 0. : AssertMessages.positiveOrZeroParameter(3);
assert rxmin <= rxmax : AssertMessages.lowerEqualParameters(4, rxmin, 6, rxmax);
assert rymin <= rymax : AssertMessages.lowerEqualParameters(5, rymin, 7, rymax);
final double ecx = ex + ewidth / 2.;
final double ecy = ey + eheight / 2.;
final double rcx = (rxmin + rxmax) / 2.;
final double rcy = (rymin + rymax) / 2.;
final double farX;
if (ecx <= rcx) {
farX = rxmax;
} else {
farX = rxmin;
}
final double farY;
if (ecy <= rcy) {
farY = rymax;
} else {
farY = rymin;
}
return containsEllipsePoint(ex, ey, ewidth, eheight, farX, farY);
}
/** Replies if two ellipses are intersecting.
*
* @param x1 is the lowest corner of the first ellipse.
* @param y1 is the lowest corner of the first ellipse.
* @param width1 is the width of the first ellipse.
* @param height1 is the height of the first ellipse.
* @param x2 is the lowest corner of the second ellipse.
* @param y2 is the lowest corner of the second ellipse.
* @param width2 is the width of the second ellipse.
* @param height2 is the height of the second ellipse.
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
*/
@Pure
@Unefficient
@SuppressWarnings("checkstyle:magicnumber")
static boolean intersectsEllipseEllipse(double x1, double y1, double width1, double height1,
double x2, double y2, double width2, double height2) {
assert width1 >= 0. : AssertMessages.positiveOrZeroParameter(2);
assert height1 >= 0. : AssertMessages.positiveOrZeroParameter(3);
assert width2 >= 0. : AssertMessages.positiveOrZeroParameter(6);
assert height2 >= 0. : AssertMessages.positiveOrZeroParameter(7);
if (width2 <= 0 || height2 <= 0 || width1 <= 0 || height1 <= 0) {
return false;
}
// Normalize coordinates for tranqforming the first ellipse to an origin-centric circle with radius 1.
final double radius1 = width1 / 2;
final double radius2 = height1 / 2;
final double transformedX = (x2 - x1) / radius1 - 1;
final double transformedY = (y2 - y1) / radius2 - 1;
final double transformedWidth = width2 / radius1;
final double transformedHeight = height2 / radius2;
// Use the standard ellipse-circle intersection test
return intersectsEllipseCircle(transformedX, transformedY, transformedWidth, transformedHeight, 0, 0, 1);
}
/** Replies if an ellipse and a circle are intersecting.
*
* @param ex is the lowest corner of the ellipse.
* @param ey is the lowest corner of the ellipse.
* @param ewidth is the width of the ellipse.
* @param eheight is the height of the ellipse.
* @param cx is the center of the circle.
* @param cy is the center of the circle.
* @param cradius the radius of the circle.
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
*/
@Pure
@Unefficient
@SuppressWarnings("checkstyle:magicnumber")
static boolean intersectsEllipseCircle(double ex, double ey, double ewidth, double eheight,
double cx, double cy, double cradius) {
assert ewidth >= 0. : AssertMessages.positiveOrZeroParameter(2);
assert eheight >= 0. : AssertMessages.positiveOrZeroParameter(3);
assert cradius >= 0. : AssertMessages.positiveOrZeroParameter(6);
final Point2D<?, ?> p = new InnerComputationPoint2afp();
findsClosestPointSolidEllipsePoint(cx, cy, ex, ey, ewidth, eheight, p);
final double dx = p.getX() - cx;
final double dy = p.getY() - cy;
return (dx * dx + dy * dy) < (cradius * cradius);
}
/** Replies if an ellipse and a line are intersecting.
*
* @param ex is the lowest corner of the ellipse.
* @param ey is the lowest corner of the ellipse.
* @param ew is the width of the ellipse.
* @param eh is the height of the ellipse.
* @param x1 is the first point of the line.
* @param y1 is the first point of the line.
* @param x2 is the second point of the line.
* @param y2 is the second point of the line.
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
* @see "http://blog.csharphelper.com/2012/09/24/calculate-where-a-line-segment-and-an-ellipse-intersect-in-c.aspx"
*/
@Pure
static boolean intersectsEllipseLine(double ex, double ey, double ew, double eh,
double x1, double y1, double x2, double y2) {
assert ew >= 0. : AssertMessages.positiveOrZeroParameter(2);
assert eh >= 0. : AssertMessages.positiveOrZeroParameter(3);
// If the ellipse or line segment are empty, return no intersections.
if (eh <= 0 || ew <= 0) {
return false;
}
// Get the semimajor and semiminor axes.
final double a = ew / 2.;
final double b = eh / 2.;
// Translate so the ellipse is centered at the origin.
final double ecx = ex + a;
final double ecy = ey + b;
final double px1 = x1 - ecx;
final double py1 = y1 - ecy;
final double px2 = x2 - ecx;
final double py2 = y2 - ecy;
final double sqA = a * a;
final double sqB = b * b;
final double vx = px2 - px1;
final double vy = py2 - py1;
assert sqA != 0 && sqB != 0;
// Calculate the quadratic parameters.
final double aParam = vx * vx / sqA + vy * vy / sqB;
final double bParam = 2 * px1 * vx / sqA + 2 * py1 * vy / sqB;
final double cParam = px1 * px1 / sqA + py1 * py1 / sqB - 1.;
// Calculate the discriminant.
final double discriminant = bParam * bParam - 4. * aParam * cParam;
return discriminant >= 0.;
}
/** Replies if an ellipse and a segment are intersecting.
*
* @param ex is the lowest corner of the ellipse.
* @param ey is the lowest corner of the ellipse.
* @param ew is the width of the ellipse.
* @param eh is the height of the ellipse.
* @param x1 is the first point of the segment.
* @param y1 is the first point of the segment.
* @param x2 is the second point of the segment.
* @param y2 is the second point of the segment.
* @param intersectsWhenTouching indicates if there is an intersection if the segment is touching
* the ellipse at one point.
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
* @see "http://blog.csharphelper.com/2012/09/24/calculate-where-a-line-segment-and-an-ellipse-intersect-in-c.aspx"
*/
@Pure
@SuppressWarnings("checkstyle:parameternumber")
static boolean intersectsEllipseSegment(double ex, double ey, double ew, double eh,
double x1, double y1, double x2, double y2, boolean intersectsWhenTouching) {
assert ew >= 0. : AssertMessages.positiveOrZeroParameter(2);
assert eh >= 0. : AssertMessages.positiveOrZeroParameter(3);
// If the ellipse or line segment are empty, return no intersections.
if (eh <= 0. || ew <= 0.) {
return false;
}
// Get the semimajor and semiminor axes.
final double a = ew / 2.;
final double b = eh / 2.;
// Translate so the ellipse is centered at the origin.
final double ecx = ex + a;
final double ecy = ey + b;
final double px1 = x1 - ecx;
final double py1 = y1 - ecy;
final double px2 = x2 - ecx;
final double py2 = y2 - ecy;
final double sqA = a * a;
final double sqB = b * b;
final double vx = px2 - px1;
final double vy = py2 - py1;
assert sqA != 0 && sqB != 0;
// Calculate the quadratic parameters.
final double aParam = vx * vx / sqA + vy * vy / sqB;
final double bParam = 2 * px1 * vx / sqA + 2 * py1 * vy / sqB;
final double cParam = px1 * px1 / sqA + py1 * py1 / sqB - 1;
// Calculate the discriminant.
final double discriminant = bParam * bParam - 4 * aParam * cParam;
if (discriminant < 0.) {
// No solution
return false;
}
if (discriminant == 0.) {
// One real solution.
if (intersectsWhenTouching) {
final double t = -bParam / 2 / aParam;
return (t >= 0.) && (t <= 1.);
}
return false;
}
assert discriminant > 0;
// Two real solutions.
final double t1 = (-bParam + Math.sqrt(discriminant)) / 2 / aParam;
final double t2 = (-bParam - Math.sqrt(discriminant)) / 2 / aParam;
return (t1 >= 0 || t2 >= 0) && (t1 <= 1 || t2 <= 1);
}
/** Replies if two ellipses are intersecting.
*
* @param ex is the first corner of the first ellipse.
* @param ey is the first corner of the first ellipse.
* @param ewidth is the width of the first ellipse.
* @param eheight is the height of the first ellipse.
* @param x3 is the first corner of the second rectangle.
* @param y3 is the first corner of the second rectangle.
* @param x4 is the second corner of the second rectangle.
* @param y4 is the second corner of the second rectangle.
* @return <code>true</code> if the two shapes are intersecting; otherwise
* <code>false</code>
*/
@Pure
@SuppressWarnings("checkstyle:magicnumber")
static boolean intersectsEllipseRectangle(double ex, double ey, double ewidth, double eheight,
double x3, double y3, double x4, double y4) {
assert ewidth >= 0. : AssertMessages.positiveOrZeroParameter(2);
assert eheight >= 0. : AssertMessages.positiveOrZeroParameter(3);
// From AWT Ellipse2D
final double rectw = Math.abs(x4 - x3);
final double recth = Math.abs(y4 - y3);
if (rectw <= 0 || recth <= 0) {
return false;
}
if (ewidth <= 0 || eheight <= 0) {
return false;
}
// Normalize the rectangular coordinates compared to the ellipse
// having a center at 0, 0 and a radius of 0.5.
final double normx0 = (x3 - ex) / ewidth - 0.5;
final double normx1 = normx0 + rectw / ewidth;
final double normy0 = (y3 - ey) / eheight - 0.5;
final double normy1 = normy0 + recth / eheight;
// find nearest x (left edge, right edge, 0.0)
// find nearest y (top edge, bottom edge, 0.0)
// if nearest x, y is inside circle of radius 0.5, then intersects
final double nearx;
final double neary;
if (normx0 > 0) {
// center to left of X extents
nearx = normx0;
} else if (normx1 < 0) {
// center to right of X extents
nearx = normx1;
} else {
nearx = 0;
}
if (normy0 > 0) {
// center above Y extents
neary = normy0;
} else if (normy1 < 0) {
// center below Y extents
neary = normy1;
} else {
neary = 0;
}
return (nearx * nearx + neary * neary) < 0.25;
}
@Pure
@Override
default boolean equalsToShape(IT shape) {
if (shape == null) {
return false;
}
if (shape == this) {
return true;
}
return getMinX() == shape.getMinX()
&& getMinY() == shape.getMinY()
&& getMaxX() == shape.getMaxX()
&& getMaxY() == shape.getMaxY();
}
@Pure
@Override
default double getDistanceSquared(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final Point2D<?, ?> r = getClosestPointTo(pt);
return r.getDistanceSquared(pt);
}
@Pure
@Override
default double getDistanceL1(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final Point2D<?, ?> r = getClosestPointTo(pt);
return r.getDistanceL1(pt);
}
@Pure
@Override
default double getDistanceLinf(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final Point2D<?, ?> r = getClosestPointTo(pt);
return r.getDistanceLinf(pt);
}
@Pure
@Override
default boolean contains(double x, double y) {
return containsEllipsePoint(
getMinX(), getMinY(), getWidth(), getHeight(),
x, y);
}
@Override
default boolean contains(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) {
assert rectangle != null : AssertMessages.notNullParameter();
return containsEllipseRectangle(
getMinX(), getMinY(), getWidth(), getHeight(),
rectangle.getMinX(), rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMaxY());
}
@Override
default boolean intersects(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) {
assert rectangle != null : AssertMessages.notNullParameter();
return intersectsEllipseRectangle(
getMinX(), getMinY(),
getWidth(), getHeight(),
rectangle.getMinX(), rectangle.getMinY(),
rectangle.getMaxX(), rectangle.getMaxY());
}
@Override
default boolean intersects(Ellipse2afp<?, ?, ?, ?, ?, ?> ellipse) {
assert ellipse != null : AssertMessages.notNullParameter();
return intersectsEllipseEllipse(
getMinX(), getMinY(),
getWidth(), getHeight(),
ellipse.getMinX(), ellipse.getMinY(),
ellipse.getWidth(), ellipse.getHeight());
}
@Pure
@Override
default boolean intersects(Circle2afp<?, ?, ?, ?, ?, ?> circle) {
assert circle != null : AssertMessages.notNullParameter();
return intersectsEllipseCircle(
getMinX(), getMinY(),
getWidth(), getHeight(),
circle.getX(), circle.getY(), circle.getRadius());
}
@Pure
@Override
default boolean intersects(Segment2afp<?, ?, ?, ?, ?, ?> segment) {
assert segment != null : AssertMessages.notNullParameter();
return intersectsEllipseSegment(
getMinX(), getMinY(),
getWidth(), getHeight(),
segment.getX1(), segment.getY1(),
segment.getX2(), segment.getY2(),
false);
}
@Pure
@Override
default boolean intersects(OrientedRectangle2afp<?, ?, ?, ?, ?, ?> orientedRectangle) {
assert orientedRectangle != null : AssertMessages.notNullParameter();
return OrientedRectangle2afp.intersectsOrientedRectangleEllipse(
orientedRectangle.getCenterX(), orientedRectangle.getCenterY(),
orientedRectangle.getFirstAxisX(), orientedRectangle.getFirstAxisY(), orientedRectangle.getFirstAxisExtent(),
orientedRectangle.getSecondAxisExtent(),
getMinX(), getMinY(), getWidth(), getHeight());
}
@Pure
@Override
default boolean intersects(Triangle2afp<?, ?, ?, ?, ?, ?> triangle) {
assert triangle != null : AssertMessages.notNullParameter();
return Triangle2afp.intersectsTriangleEllipse(
triangle.getX1(), triangle.getY1(),
triangle.getX2(), triangle.getY2(),
triangle.getX3(), triangle.getY3(),
getMinX(), getMinY(),
getWidth(), getHeight());
}
@Pure
@Override
default boolean intersects(Parallelogram2afp<?, ?, ?, ?, ?, ?> parallelogram) {
assert parallelogram != null : AssertMessages.notNullParameter();
return Parallelogram2afp.intersectsParallelogramEllipse(
parallelogram.getCenterX(), parallelogram.getCenterY(),
parallelogram.getFirstAxisX(), parallelogram.getFirstAxisY(), parallelogram.getFirstAxisExtent(),
parallelogram.getSecondAxisX(), parallelogram.getSecondAxisY(), parallelogram.getSecondAxisExtent(),
getMinX(), getMinY(), getWidth(), getHeight());
}
@Pure
@Override
default boolean intersects(RoundRectangle2afp<?, ?, ?, ?, ?, ?> roundRectangle) {
assert roundRectangle != null : AssertMessages.notNullParameter();
return RoundRectangle2afp.intersectsRoundRectangleEllipse(
roundRectangle.getMinX(), roundRectangle.getMinY(),
roundRectangle.getMaxX(), roundRectangle.getMaxY(),
roundRectangle.getArcWidth(), roundRectangle.getArcHeight(),
getMinX(), getMinY(), getWidth(), getHeight());
}
@Override
default boolean intersects(PathIterator2afp<?> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
final int mask = iterator.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = Path2afp.calculatesCrossingsPathIteratorEllipseShadow(
0,
iterator,
getMinX(), getMinY(), getWidth(), getHeight(),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS
|| (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(MultiShape2afp<?, ?, ?, ?, ?, ?, ?> multishape) {
assert multishape != null : AssertMessages.notNullParameter();
return multishape.intersects(this);
}
@Override
default PathIterator2afp<IE> getPathIterator(Transform2D transform) {
if (transform == null || transform.isIdentity()) {
return new EllipsePathIterator<>(this);
}
return new TransformedEllipsePathIterator<>(this, transform);
}
/** Replies a path iterator on this shape that is replacing the
* curves by line approximations.
*
* @return the iterator on the approximation.
* @see #getPathIterator()
* @see MathConstants#SPLINE_APPROXIMATION_RATIO
*/
@Pure
default PathIterator2afp<IE> getFlatteningPathIterator() {
return new Path2afp.FlatteningPathIterator<>(
getPathIterator(null),
MathConstants.SPLINE_APPROXIMATION_RATIO,
Path2afp.DEFAULT_FLATTENING_LIMIT);
}
@Override
default P getClosestPointTo(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
Ellipse2afp.findsClosestPointSolidEllipsePoint(
pt.getX(), pt.getY(),
getMinX(), getMinY(),
getWidth(), getHeight(),
point);
return point;
}
@Override
default P getClosestPointTo(Circle2afp<?, ?, ?, ?, ?, ?> circle) {
assert circle != null : AssertMessages.notNullParameter();
return getClosestPointTo(circle.getCenter());
}
@Override
@Unefficient
default P getClosestPointTo(Ellipse2afp<?, ?, ?, ?, ?, ?> ellipse) {
assert ellipse != null : AssertMessages.notNullParameter();
final P pointOnSecondEllipse = getGeomFactory().newPoint();
Path2afp.findsClosestPointPathIteratorPathIterator(
ellipse.getFlatteningPathIterator(),
getPathIterator(), pointOnSecondEllipse);
return getClosestPointTo(pointOnSecondEllipse);
}
@Override
@Unefficient
default P getClosestPointTo(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) {
assert rectangle != null : AssertMessages.notNullParameter();
final Point2D<?, ?> pointOnRectangle = rectangle.getClosestPointTo(this);
return getClosestPointTo(pointOnRectangle);
}
@Override
@Unefficient
default P getClosestPointTo(Segment2afp<?, ?, ?, ?, ?, ?> segment) {
assert segment != null : AssertMessages.notNullParameter();
final Point2D<?, ?> pointOnSegment = segment.getClosestPointTo(this);
return getClosestPointTo(pointOnSegment);
}
@Override
@Unefficient
default P getClosestPointTo(Triangle2afp<?, ?, ?, ?, ?, ?> triangle) {
assert triangle != null : AssertMessages.notNullParameter();
final Point2D<?, ?> pointOnTriangle = triangle.getClosestPointTo(this);
return getClosestPointTo(pointOnTriangle);
}
@Override
@Unefficient
default P getClosestPointTo(OrientedRectangle2afp<?, ?, ?, ?, ?, ?> orientedRectangle) {
assert orientedRectangle != null : AssertMessages.notNullParameter();
final Point2D<?, ?> pointOnRectangle = orientedRectangle.getClosestPointTo(this);
return getClosestPointTo(pointOnRectangle);
}
@Override
@Unefficient
default P getClosestPointTo(Parallelogram2afp<?, ?, ?, ?, ?, ?> parallelogram) {
assert parallelogram != null : AssertMessages.notNullParameter();
final Point2D<?, ?> pointOnParallelogram = parallelogram.getClosestPointTo(this);
return getClosestPointTo(pointOnParallelogram);
}
@Override
@Unefficient
default P getClosestPointTo(RoundRectangle2afp<?, ?, ?, ?, ?, ?> roundRectangle) {
assert roundRectangle != null : AssertMessages.notNullParameter();
final Point2D<?, ?> pointOnRectangle = roundRectangle.getClosestPointTo(this);
return getClosestPointTo(pointOnRectangle);
}
@Override
@Unefficient
default P getClosestPointTo(Path2afp<?, ?, ?, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
Path2afp.findsClosestPointPathIteratorPathIterator(
getFlatteningPathIterator(),
path.getPathIterator(), point);
return point;
}
@Override
default P getFarthestPointTo(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
Ellipse2afp.findsFarthestPointShallowEllipsePoint(
pt.getX(), pt.getY(),
getMinX(), getMinY(),
getWidth(), getHeight(),
point);
return point;
}
/** Replies the horizontal radius of the ellipse.
*
* @return the horizontal radius.
*/
default double getHorizontalRadius() {
return getWidth() / 2;
}
/** Replies the vertical radius of the ellipse.
*
* @return the vertical radius.
*/
default double getVerticalRadius() {
return getHeight() / 2;
}
/** Replies the focus point with the lower coordinates.
*
* <p>The foci always lie on the major (longest) axis, spaced equally
* each side of the center.
* If the major axis and minor axis are the same length, the figure is a circle and both foci are at the center.
*
* @return the focus point.
*/
default P getMinFocusPoint() {
final double radius1 = getHorizontalRadius();
final double radius2 = getVerticalRadius();
final double squaredRadius1 = radius1 * radius1;
final double squaredRadius2 = radius2 * radius2;
final double centerX = getCenterX();
final double centerY = getCenterY();
final GeomFactory2afp<?, P, V, ?> factory = getGeomFactory();
if (radius1 >= radius2) {
final double focusDistance = Math.sqrt(squaredRadius1 - squaredRadius2);
return factory.newPoint(centerX - focusDistance, centerY);
}
final double focusDistance = Math.sqrt(squaredRadius2 - squaredRadius1);
return factory.newPoint(centerX, centerY - focusDistance);
}
/** Replies the focus point with the higher coordinates.
*
* <p>The foci always lie on the major (longest) axis, spaced equally
* each side of the center.
* If the major axis and minor axis are the same length, the figure is a circle and both foci are at the center.
*
* @return the focus point.
*/
default P getMaxFocusPoint() {
final double radius1 = getHorizontalRadius();
final double radius2 = getVerticalRadius();
final double squaredRadius1 = radius1 * radius1;
final double squaredRadius2 = radius2 * radius2;
final double centerX = getCenterX();
final double centerY = getCenterY();
final GeomFactory2afp<?, P, V, ?> factory = getGeomFactory();
if (radius1 >= radius2) {
final double focusDistance = Math.sqrt(squaredRadius1 - squaredRadius2);
return factory.newPoint(centerX + focusDistance, centerY);
}
final double focusDistance = Math.sqrt(squaredRadius2 - squaredRadius1);
return factory.newPoint(centerX, centerY + focusDistance);
}
/** Abstract iterator on the path elements of the ellipse.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
abstract class AbstractEllipsePathIterator<T extends PathElement2afp> implements PathIterator2afp<T> {
/** Positive curve tangent size.
*/
protected static final double PCV = 0.5 + AbstractCirclePathIterator.CTRL_POINT_DISTANCE * 0.5;
/** Negative curve tangent size.
*/
protected static final double NCV = 0.5 - AbstractCirclePathIterator.CTRL_POINT_DISTANCE * 0.5;
/**
* The control points for a set of 4 cubic
* bezier curves that approximate a circle of radius 0.5
* centered at 0.5, 0.5.
*/
protected static final double[][] BEZIER_CONTROL_POINTS = {
{1.0, PCV, PCV, 1.0, 0.5, 1.0},
{NCV, 1.0, 0.0, PCV, 0.0, 0.5},
{0.0, NCV, NCV, 0.0, 0.5, 0.0},
{PCV, 0.0, 1.0, NCV, 1.0, 0.5},
};
/** 4 segments + close.
*/
protected static final int NUMBER_ELEMENTS = 5;
/** The iterated shape.
*/
protected final Ellipse2afp<?, ?, T, ?, ?, ?> ellipse;
/**
* @param ellipse the ellipse to iterate on.
*/
public AbstractEllipsePathIterator(Ellipse2afp<?, ?, T, ?, ?, ?> ellipse) {
assert ellipse != null : AssertMessages.notNullParameter();
this.ellipse = ellipse;
}
@Override
public GeomFactory2afp<T, ?, ?, ?> getGeomFactory() {
return this.ellipse.getGeomFactory();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Pure
@Override
public PathWindingRule getWindingRule() {
return PathWindingRule.NON_ZERO;
}
@Pure
@Override
public boolean isPolyline() {
return false;
}
@Pure
@Override
public boolean isCurved() {
return true;
}
@Pure
@Override
public boolean isPolygon() {
return true;
}
@Pure
@Override
public boolean isMultiParts() {
return false;
}
}
/** Iterator on the ellipse path elements.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
class EllipsePathIterator<T extends PathElement2afp> extends AbstractEllipsePathIterator<T> {
private double x;
private double y;
private double width;
private double height;
private int index;
private double lastX;
private double lastY;
/**
* @param ellipse the ellipse to iterate on.
*/
public EllipsePathIterator(Ellipse2afp<?, ?, T, ?, ?, ?> ellipse) {
super(ellipse);
if (ellipse.isEmpty()) {
this.index = NUMBER_ELEMENTS;
} else {
this.x = ellipse.getMinX();
this.y = ellipse.getMinY();
this.width = ellipse.getWidth();
this.height = ellipse.getHeight();
this.index = -1;
}
}
@Override
public PathIterator2afp<T> restartIterations() {
return new EllipsePathIterator<>(this.ellipse);
}
@Pure
@Override
public boolean hasNext() {
return this.index < NUMBER_ELEMENTS;
}
@Override
@SuppressWarnings("checkstyle:magicnumber")
public T next() {
if (this.index >= NUMBER_ELEMENTS) {
throw new NoSuchElementException();
}
final int idx = this.index;
++this.index;
if (idx < 0) {
final double[] ctrls = BEZIER_CONTROL_POINTS[3];
this.lastX = this.x + ctrls[4] * this.width;
this.lastY = this.y + ctrls[5] * this.height;
return getGeomFactory().newMovePathElement(
this.lastX, this.lastY);
}
if (idx < (NUMBER_ELEMENTS - 1)) {
final double[] ctrls = BEZIER_CONTROL_POINTS[idx];
final double ix = this.lastX;
final double iy = this.lastY;
this.lastX = this.x + ctrls[4] * this.width;
this.lastY = this.y + ctrls[5] * this.height;
return getGeomFactory().newCurvePathElement(
ix, iy,
this.x + ctrls[0] * this.width,
this.y + ctrls[1] * this.height,
this.x + ctrls[2] * this.width,
this.y + ctrls[3] * this.height,
this.lastX,
this.lastY);
}
return getGeomFactory().newClosePathElement(
this.lastX, this.lastY,
this.lastX, this.lastY);
}
}
/** Iterator on the path elements of a transformed ellipse.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
@SuppressWarnings("checkstyle:magicnumber")
class TransformedEllipsePathIterator<T extends PathElement2afp> extends AbstractEllipsePathIterator<T> {
private final Transform2D transform;
private Point2D<?, ?> lastPoint;
private Point2D<?, ?> ptmp1;
private Point2D<?, ?> ptmp2;
private double x1;
private double y1;
private double width;
private double height;
private int index;
/**
* @param ellipse the ellipse to iterate on.
* @param transform the transformation to apply to the ellipse.
*/
public TransformedEllipsePathIterator(Ellipse2afp<?, ?, T, ?, ?, ?> ellipse, Transform2D transform) {
super(ellipse);
assert transform != null : AssertMessages.notNullParameter(1);
this.transform = transform;
if (ellipse.isEmpty()) {
this.index = 6;
} else {
this.lastPoint = new InnerComputationPoint2afp();
this.ptmp1 = new InnerComputationPoint2afp();
this.ptmp2 = new InnerComputationPoint2afp();
this.x1 = ellipse.getMinX();
this.y1 = ellipse.getMinY();
this.width = ellipse.getWidth();
this.height = ellipse.getHeight();
}
}
@Override
public PathIterator2afp<T> restartIterations() {
return new TransformedEllipsePathIterator<>(this.ellipse, this.transform);
}
@Pure
@Override
public boolean hasNext() {
return this.index <= 5;
}
@Override
public T next() {
if (this.index > 5) {
throw new NoSuchElementException();
}
final int idx = this.index;
++this.index;
if (idx == 0) {
final double[] ctrls = BEZIER_CONTROL_POINTS[3];
this.lastPoint.set(
this.x1 + ctrls[4] * this.width,
this.y1 + ctrls[5] * this.height);
this.transform.transform(this.lastPoint);
return getGeomFactory().newMovePathElement(
this.lastPoint.getX(), this.lastPoint.getY());
} else if (idx < 5) {
final double[] ctrls = BEZIER_CONTROL_POINTS[idx - 1];
final double ix = this.lastPoint.getX();
final double iy = this.lastPoint.getY();
this.lastPoint.set(
this.x1 + ctrls[4] * this.width,
this.y1 + ctrls[5] * this.height);
this.transform.transform(this.lastPoint);
this.ptmp1.set(
this.x1 + ctrls[0] * this.width,
this.y1 + ctrls[1] * this.height);
this.transform.transform(this.ptmp1);
this.ptmp2.set(
this.x1 + ctrls[2] * this.width,
this.y1 + ctrls[3] * this.height);
this.transform.transform(this.ptmp2);
return getGeomFactory().newCurvePathElement(
ix, iy,
this.ptmp1.getX(), this.ptmp1.getY(),
this.ptmp2.getX(), this.ptmp2.getY(),
this.lastPoint.getX(), this.lastPoint.getY());
}
final double ix = this.lastPoint.getX();
final double iy = this.lastPoint.getY();
return getGeomFactory().newClosePathElement(
ix, iy,
ix, iy);
}
}
/** Private API functions for the ellipses.
*
* @author $Author: sgalland$
* @version $Name$ $Revision$ $Date$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
final class PrivateAPI {
private static final int MAX_ITERATIONS = 1074;
private PrivateAPI() {
//
}
@Unefficient
private static double getClosestNormalPointRoot(double r0, double zx, double zy, double gval) {
final double n0 = r0 * zx;
double s0 = zy - 1;
double localG = gval;
double s1 = (localG < 0) ? 0 : Math.hypot(n0, zy) - 1.;
double result = Double.NaN;
for (int i = 0; i < MAX_ITERATIONS; ++i) {
result = (s0 + s1) / 2.;
if (result == s0 || result == s1) {
return result;
}
final double ratio0 = n0 / (result + r0);
final double ratio1 = zy / (result + 1.);
localG = ratio0 * ratio0 + ratio1 * ratio1 - 1.;
if (localG > 0) {
s0 = result;
} else if (localG < 0) {
s1 = result;
} else {
return result;
}
}
return result;
}
/** Compute the closest point to a shallow ellipse centered on (0, 0) and in the positive quadrant.
* The coordinates of the point must be positive.
*
* <p>The mathematrical definition of the algorithm is explained in:
* <a href="./doc-files/DistancePointEllipseEllipsoid.pdf">DistancePointEllipseEllipsoid.pdf</a>
* (source: <a href="http://www.geometrictools.com/">geometrictools.com</a>).
*
* @param px the x coordinate of the point. It must be positive or nul.
* @param py the y coordinate of the point. It must be positive or nul.
* @param horizontalRadius the horizontal radius.
* @param verticalRadius the vertical radius.
* @param computeDistance indicates if the distance musst be computed and replied.
* @return the triplet (closest point x, closest point y, distance to closest point) if
* <code>computeDistance</code> if <code>true</code>. Otherwise, the triplet (closest point x, closest point y).
*/
@Unefficient
public static double[] computeClosestPointOnShallowEllipseInPositiveQuadrant(
double px, double py,
double horizontalRadius, double verticalRadius,
boolean computeDistance) {
assert px >= 0 : AssertMessages.positiveOrZeroParameter(0);
assert py >= 0 : AssertMessages.positiveOrZeroParameter(1);
assert horizontalRadius >= 0 : AssertMessages.positiveOrZeroParameter(2);
assert verticalRadius >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert verticalRadius <= horizontalRadius
: AssertMessages.lowerEqualParameters(3, verticalRadius, 2, horizontalRadius);
final double closeX;
final double closeY;
double distance = 0;
if (py > 0) {
if (px > 0) {
final double zx = px / horizontalRadius;
final double zy = py / verticalRadius;
final double g = zx * zx + zy * zy - 1.;
// g > 0, then point is outside ellipse
// g = 0, then point is on ellipse
// g < 0, then point is inside ellipse
if (g != 0) {
double r0 = horizontalRadius / verticalRadius;
r0 = r0 * r0;
final double sbar = getClosestNormalPointRoot(r0, zx, zy, g);
closeX = r0 * px / (sbar + r0);
closeY = py / (sbar + 1.);
if (computeDistance) {
distance = Math.hypot(closeX - px, closeY - py);
}
} else {
closeX = px;
closeY = py;
}
} else {
// px == 0
closeX = 0;
closeY = verticalRadius;
if (computeDistance) {
distance = Math.abs(py - verticalRadius);
}
}
} else {
// py == 0
final double numer0 = horizontalRadius * px;
final double denom0 = horizontalRadius * horizontalRadius - verticalRadius * verticalRadius;
if (numer0 < denom0) {
final double xde0 = numer0 / denom0;
closeX = horizontalRadius * xde0;
closeY = verticalRadius * Math.sqrt(1. - xde0 * xde0);
if (computeDistance) {
distance = Math.hypot(closeX - px, closeY);
}
} else {
closeX = horizontalRadius;
closeY = 0;
if (computeDistance) {
distance = Math.abs(px - horizontalRadius);
}
}
}
if (computeDistance) {
return new double[] {closeX, closeY, distance};
}
return new double[] {closeX, closeY};
}
/** Compute the closest point to a solid ellipse centered on (0, 0) and in the positive quadrant.
* The coordinates of the point must be positive.
*
* <p>The mathematrical definition of the algorithm is explained in:
* <a href="./doc-files/DistancePointEllipseEllipsoid.pdf">DistancePointEllipseEllipsoid.pdf</a>
* (source: <a href="http://www.geometrictools.com/">geometrictools.com</a>).
*
* @param px the x coordinate of the point. It must be positive or nul.
* @param py the y coordinate of the point. It must be positive or nul.
* @param horizontalRadius the horizontal radius.
* @param verticalRadius the vertical radius.
* @param computeDistance indicates if the distance musst be computed and replied.
* @return the triplet (closest point x, closest point y, distance to closest point) if
* <code>computeDistance</code> if <code>true</code>. Otherwise, the triplet (closest point x, closest point y).
*/
@Unefficient
@SuppressWarnings("checkstyle:nestedifdepth")
public static double[] computeClosestPointOnSolidEllipseInPositiveQuadrant(
double px, double py,
double horizontalRadius, double verticalRadius,
boolean computeDistance) {
assert px >= 0 : AssertMessages.positiveOrZeroParameter(0);
assert py >= 0 : AssertMessages.positiveOrZeroParameter(1);
assert horizontalRadius >= 0 : AssertMessages.positiveOrZeroParameter(2);
assert verticalRadius >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert verticalRadius <= horizontalRadius
: AssertMessages.lowerEqualParameters(3, verticalRadius, 2, horizontalRadius);
final double closeX;
final double closeY;
double distance = 0;
if (py > 0) {
if (px > 0) {
final double zx = px / horizontalRadius;
final double zy = py / verticalRadius;
final double g = zx * zx + zy * zy - 1;
// g > 0, then point is outside ellipse
// g = 0, then point is on ellipse
// g < 0, then point is inside ellipse
if (g > 0) {
double r0 = horizontalRadius / verticalRadius;
r0 = r0 * r0;
final double sbar = getClosestNormalPointRoot(r0, zx, zy, g);
closeX = r0 * px / (sbar + r0);
closeY = py / (sbar + 1);
if (computeDistance) {
distance = Math.hypot(closeX - px, closeY - py);
}
} else {
closeX = px;
closeY = py;
}
} else {
// px == 0
closeX = 0;
if (py <= verticalRadius) {
closeY = py;
} else {
closeY = verticalRadius;
if (computeDistance) {
distance = Math.abs(py - verticalRadius);
}
}
}
} else {
// py == 0
if (px <= horizontalRadius) {
closeX = px;
closeY = py;
} else {
final double numer0 = horizontalRadius * px;
final double denom0 = horizontalRadius * horizontalRadius - verticalRadius * verticalRadius;
if (numer0 < denom0) {
final double xde0 = numer0 / denom0;
closeX = horizontalRadius * xde0;
closeY = verticalRadius * Math.sqrt(1 - xde0 * xde0);
if (computeDistance) {
distance = Math.hypot(closeX - px, closeY);
}
} else {
closeX = horizontalRadius;
closeY = 0;
if (computeDistance) {
distance = Math.abs(px - horizontalRadius);
}
}
}
}
if (computeDistance) {
return new double[] {closeX, closeY, distance};
}
return new double[] {closeX, closeY};
}
@Unefficient
private static double getFarthestNormalPointRoot(double r0, double e0, double e1, double zx, double zy) {
double localG;
double s0 = -Math.hypot(zx, r0 * zy) - 1.;
double s1 = zx - 1;
double result = Double.NaN;
final double v0 = e1 * e1;
final double v1 = v0 * zy;
final double v2 = e0 * e0;
for (int i = 0; i < MAX_ITERATIONS; ++i) {
result = (s0 + s1) / 2.;
if (result == s0 || result == s1) {
return result;
}
final double ratio0 = zx / (result + 1.);
final double ratio1 = v1 / (result * v2 + v1);
localG = ratio0 * ratio0 + ratio1 * ratio1 - 1.;
if (localG > 0) {
s1 = result;
} else if (localG < 0) {
s0 = result;
} else {
return result;
}
}
return result;
}
/** Compute the farthest point to a shallow ellipse centered on (0, 0) and in the positive quadrant.
* The coordinates of the point must be negative.
*
* <p>This function is an adaptation of the mathematrical definition that is explained in:
* <a href="./doc-files/DistancePointEllipseEllipsoid.pdf">DistancePointEllipseEllipsoid.pdf</a>
* (source: <a href="http://www.geometrictools.com/">geometrictools.com</a>).
*
* @param px the x coordinate of the point. It must be positive or nul.
* @param py the y coordinate of the point. It must be positive or nul.
* @param horizontalRadius the horizontal radius.
* @param verticalRadius the vertical radius.
* @param computeDistance indicates if the distance musst be computed and replied.
* @return the triplet (closest point x, closest point y, distance to closest point) if
* <code>computeDistance</code> if <code>true</code>. Otherwise, the triplet (closest point x, closest point y).
*/
@Unefficient
public static double[] computeFarthestPointOnShallowEllipseInPositiveQuadrant(
double px, double py,
double horizontalRadius, double verticalRadius,
boolean computeDistance) {
assert px <= 0 : AssertMessages.negativeOrZeroParameter(0);
assert py <= 0 : AssertMessages.negativeOrZeroParameter(1);
assert horizontalRadius >= 0 : AssertMessages.positiveOrZeroParameter(2);
assert verticalRadius >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert verticalRadius <= horizontalRadius
: AssertMessages.lowerEqualParameters(3, verticalRadius, 2, horizontalRadius);
final double farX;
final double farY;
double distance = 0;
if (py < 0) {
if (px < 0) {
final double zx = px / horizontalRadius;
final double zy = py / verticalRadius;
final double g = zx * zx + zy * zy - 1.;
// g > 0, then point is outside ellipse
// g = 0, then point is on ellipse
// g < 0, then point is inside ellipse
if (g != 0) {
double r0 = verticalRadius / horizontalRadius;
r0 = r0 * r0;
final double sbar = getFarthestNormalPointRoot(r0, horizontalRadius, verticalRadius, zx, zy);
farX = px / (sbar + 1.);
farY = r0 * py / (sbar + r0);
if (computeDistance) {
distance = Math.hypot(farX - px, farY - py);
}
} else {
farX = px;
farY = py;
}
} else {
// px == 0
final double psquare = py * py;
final double d1 = psquare + horizontalRadius * horizontalRadius;
final double d2 = psquare + 2 * Math.abs(py) * verticalRadius * verticalRadius;
if (d1 > d2) {
farX = horizontalRadius;
farY = 0;
if (computeDistance) {
distance = Math.sqrt(d1);
}
} else {
farX = 0;
farY = verticalRadius;
if (computeDistance) {
distance = Math.abs(px) + horizontalRadius;
}
}
}
} else {
// py == 0
final double psquare = px * px;
final double d1 = psquare + verticalRadius * verticalRadius;
final double d2 = psquare + 2 * Math.abs(px) * horizontalRadius * horizontalRadius;
if (d1 > d2) {
farX = 0;
farY = verticalRadius;
if (computeDistance) {
distance = Math.sqrt(d1);
}
} else {
farX = horizontalRadius;
farY = 0;
if (computeDistance) {
distance = Math.abs(px) + horizontalRadius;
}
}
}
if (computeDistance) {
return new double[] {farX, farY, distance};
}
return new double[] {farX, farY};
}
}
}