/* * $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.continous.object2d; import java.util.NoSuchElementException; import org.arakhne.afc.math.MathConstants; import org.arakhne.afc.math.generic.PathWindingRule; import org.arakhne.afc.math.generic.Point2D; import org.arakhne.afc.math.geometry.d2.afp.Ellipse2afp; import org.arakhne.afc.math.geometry.d2.d.Ellipse2d; import org.arakhne.afc.math.geometry.d2.d.Point2d; import org.arakhne.afc.math.matrix.Transform2D; import org.arakhne.afc.vmutil.ReflectionUtil; /** 2D ellipse with floating-point points. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @deprecated see {@link Ellipse2d} */ @Deprecated @SuppressWarnings("all") public class Ellipse2f extends AbstractRectangularShape2f<Ellipse2f> { private static final long serialVersionUID = -2745313055404516167L; // ArcIterator.btan(Math.PI/2) private static final float CTRL_VAL = 0.5522847498307933f; /** * ctrlpts contains 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 */ static final float PCV = 0.5f + CTRL_VAL * 0.5f; /** * ctrlpts contains 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 */ static final float NCV = 0.5f - CTRL_VAL * 0.5f; /** * ctrlpts contains 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 */ static final float CTRL_PTS[][] = { { 1.0f, PCV, PCV, 1.0f, 0.5f, 1.0f }, { NCV, 1.0f, 0.0f, PCV, 0.0f, 0.5f }, { 0.0f, NCV, NCV, 0.0f, 0.5f, 0.0f }, { PCV, 0.0f, 1.0f, NCV, 1.0f, 0.5f } }; /** 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 rx is the lowest corner of the rectangle. * @param ry is the lowest corner of the rectangle. * @param rwidth is the width of the rectangle. * @param rheight is the height of the rectangle. * @return <code>true</code> if the given rectangle is inside the ellipse; * otherwise <code>false</code>. */ public static boolean containsEllipseRectangle(float ex, float ey, float ewidth, float eheight, float rx, float ry, float rwidth, float rheight) { float ecx = (ex + ewidth/2f); float ecy = (ey + eheight/2f); float rcx = (rx + rwidth/2f); float rcy = (ry + rheight/2f); float farX; if (ecx<=rcx) farX = rx + rwidth; else farX = rx; float farY; if (ecy<=rcy) farY = ry + rheight; else farY = ry; return Ellipse2afp.containsEllipsePoint(ex, ey, ewidth, eheight, farX, farY); } /** Replies if two ellipses are intersecting. * * @param x1 is the first corner of the first ellipse. * @param y1 is the first corner of the first ellipse. * @param x2 is the second corner of the first ellipse. * @param y2 is the second corner of the first ellipse. * @param x3 is the first corner of the second ellipse. * @param y3 is the first corner of the second ellipse. * @param x4 is the second corner of the second ellipse. * @param y4 is the second corner of the second ellipse. * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ public static boolean intersectsEllipseEllipse(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { float ell2w = Math.abs(x4 - x3); float ell2h = Math.abs(y4 - y3); float ellw = Math.abs(x2 - x1); float ellh = Math.abs(y2 - y1); if (ell2w <= 0f || ell2h <= 0f) return false; if (ellw <= 0f || ellh <= 0f) return false; // Normalize the second ellipse coordinates compared to the ellipse // having a center at 0,0 and a radius of 0.5. float normx0 = (x3 - x1) / ellw - 0.5f; float normx1 = normx0 + ell2w / ellw; float normy0 = (y3 - y1) / ellh - 0.5f; float normy1 = normy0 + ell2h / ellh; // 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 float nearx, neary; if (normx0 > 0f) { // center to left of X extents nearx = normx0; } else if (normx1 < 0f) { // center to right of X extents nearx = normx1; } else { nearx = 0f; } if (normy0 > 0f) { // center above Y extents neary = normy0; } else if (normy1 < 0f) { // center below Y extents neary = normy1; } else { neary = 0f; } return (nearx * nearx + neary * neary) < 0.25f; } /** 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" */ public static boolean intersectsEllipseLine(float ex, float ey, float ew, float eh, float x1, float y1, float x2, float y2) { // If the ellipse or line segment are empty, return no intersections. if (eh<=0f || ew<=0f) { return false; } // Get the semimajor and semiminor axes. float a = ew / 2f; float b = eh / 2f; // Translate so the ellipse is centered at the origin. float ecx = ex + a; float ecy = ey + b; float px1 = x1 - ecx; float py1 = y1 - ecy; float px2 = x2 - ecx; float py2 = y2 - ecy; float sq_a = a*a; float sq_b = b*b; float vx = px2 - px1; float vy = py2 - py1; assert(sq_a!=0f && sq_b!=0f); // Calculate the quadratic parameters. float A = vx * vx / sq_a + vy * vy / sq_b; float B = 2f * px1 * vx / sq_a + 2f * py1 * vy / sq_b; float C = px1 * px1 / sq_a + py1 * py1 / sq_b - 1; // Calculate the discriminant. float discriminant = B * B - 4f * A * C; return (discriminant>=0f); } /** 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. * @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" */ public static boolean intersectsEllipseSegment(float ex, float ey, float ew, float eh, float x1, float y1, float x2, float y2) { // If the ellipse or line segment are empty, return no intersections. if (eh<=0f || ew<=0f) { return false; } // Get the semimajor and semiminor axes. float a = ew / 2f; float b = eh / 2f; // Translate so the ellipse is centered at the origin. float ecx = ex + a; float ecy = ey + b; float px1 = x1 - ecx; float py1 = y1 - ecy; float px2 = x2 - ecx; float py2 = y2 - ecy; float sq_a = a*a; float sq_b = b*b; float vx = px2 - px1; float vy = py2 - py1; assert(sq_a!=0f && sq_b!=0f); // Calculate the quadratic parameters. float A = vx * vx / sq_a + vy * vy / sq_b; float B = 2f * px1 * vx / sq_a + 2f * py1 * vy / sq_b; float C = px1 * px1 / sq_a + py1 * py1 / sq_b - 1; // Calculate the discriminant. float discriminant = B * B - 4f * A * C; if (discriminant<0f) { // No solution return false; } if (discriminant==0f) { // One real solution. float t = -B / 2f / A; return ((t >= 0f) && (t <= 1f)); } assert(discriminant>0f); // Two real solutions. float t1 = (-B + (float)Math.sqrt(discriminant)) / 2f / A; float t2 = (-B - (float)Math.sqrt(discriminant)) / 2f / A; return (t1>=0 || t2>=0) && (t1<=1 || t2<=1); } /** Replies if two ellipses are intersecting. * * @param x1 is the first corner of the first ellipse. * @param y1 is the first corner of the first ellipse. * @param x2 is the second corner of the first ellipse. * @param y2 is the second corner 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> */ public static boolean intersectsEllipseRectangle(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { // From AWT Ellipse2D float rectw = Math.abs(x4 - x3); float recth = Math.abs(y4 - y3); float ellw = Math.abs(x2 - x1); float ellh = Math.abs(y2 - y1); if (rectw <= 0f || recth <= 0f) return false; if (ellw <= 0f || ellh <= 0f) return false; // Normalize the rectangular coordinates compared to the ellipse // having a center at 0,0 and a radius of 0.5. float normx0 = (x3 - x1) / ellw - 0.5f; float normx1 = normx0 + rectw / ellw; float normy0 = (y3 - y1) / ellh - 0.5f; float normy1 = normy0 + recth / ellh; // 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 float nearx, neary; if (normx0 > 0f) { // center to left of X extents nearx = normx0; } else if (normx1 < 0f) { // center to right of X extents nearx = normx1; } else { nearx = 0f; } if (normy0 > 0f) { // center above Y extents neary = normy0; } else if (normy1 < 0f) { // center below Y extents neary = normy1; } else { neary = 0f; } return (nearx * nearx + neary * neary) < 0.25f; } /** */ public Ellipse2f() { // } /** * @param min is the min corner of the ellipse. * @param max is the max corner of the ellipse. */ public Ellipse2f(Point2f min, Point2f max) { super(min, max); } /** * @param x * @param y * @param width * @param height */ public Ellipse2f(float x, float y, float width, float height) { super(x, y, width, height); } /** * @param e */ public Ellipse2f(Ellipse2f e) { super(e); } /** {@inheritDoc} */ @Override public float distanceSquared(Point2D p) { Point2D r = getClosestPointTo(p); return r.distanceSquared(p); } /** {@inheritDoc} */ @Override public float distanceL1(Point2D p) { Point2D r = getClosestPointTo(p); return r.distanceL1(p); } /** {@inheritDoc} */ @Override public float distanceLinf(Point2D p) { Point2D r = getClosestPointTo(p); return r.distanceLinf(p); } /** * {@inheritDoc} */ @Override public boolean contains(float x, float y) { return Ellipse2afp.containsEllipsePoint( getMinX(), getMinY(), getWidth(), getHeight(), x, y); } @Override public boolean contains(Rectangle2f r) { return containsEllipseRectangle( getMinX(), getMinY(), getWidth(), getHeight(), r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); } /** {@inheritDoc} */ @Override public Point2D getClosestPointTo(Point2D p) { Point2d pts = new Point2d(); Ellipse2afp.findsClosestPointSolidEllipsePoint( p.getX(), p.getY(), getMinX(), getMinY(), getWidth(), getHeight(), pts); return new Point2f((float) pts.getX(), (float) pts.getY()); } @Override public PathIterator2f getPathIterator(Transform2D transform) { if (transform==null) { return new CopyPathIterator( getMinX(), getMinY(), getMaxX(), getMaxY()); } return new TransformPathIterator( getMinX(), getMinY(), getMaxX(), getMaxY(), transform); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Ellipse2f) { Ellipse2f rr2d = (Ellipse2f) obj; return ((getMinX() == rr2d.getMinX()) && (getMinY() == rr2d.getMinY()) && (getWidth() == rr2d.getWidth()) && (getHeight() == rr2d.getHeight())); } return false; } @Override public int hashCode() { long bits = 1L; bits = 31L * bits + floatToIntBits(getMinX()); bits = 31L * bits + floatToIntBits(getMinY()); bits = 31L * bits + floatToIntBits(getMaxX()); bits = 31L * bits + floatToIntBits(getMaxY()); return (int) (bits ^ (bits >> 32)); } @Override public boolean intersects(Rectangle2f s) { return intersectsEllipseRectangle( getMinX(), getMinY(), getMaxX(), getMaxY(), s.getMinX(), s.getMinY(), s.getMaxX(), s.getMaxY()); } @Override public boolean intersects(Ellipse2f s) { return intersectsEllipseRectangle( getMinX(), getMinY(), getMaxX(), getMaxY(), s.getMinX(), s.getMinY(), s.getMaxX(), s.getMaxY()); } @Override public boolean intersects(Circle2f s) { return intersectsEllipseEllipse( getMinX(), getMinY(), getMaxX(), getMaxY(), s.getX()-s.getRadius(), s.getY()-s.getRadius(), s.getX()+s.getRadius(), s.getY()+s.getRadius()); } @Override public boolean intersects(Segment2f s) { return intersectsEllipseSegment( getMinX(), getMinY(), getWidth(), getHeight(), s.getX1(), s.getY1(), s.getX2(), s.getY2()); } @Override public boolean intersects(Path2f s) { return intersects(s.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO)); } @Override public boolean intersects(PathIterator2f s) { int mask = (s.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2); int crossings = Path2f.computeCrossingsFromEllipse( 0, s, getMinX(), getMinY(), getWidth(), getHeight(), false, true); return (crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0); } @Override public String toString() { return ReflectionUtil.toString(this); } /** * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public static class CopyPathIterator implements PathIterator2f { private final float x1; private final float y1; private final float w; private final float h; private int index; private float lastX, lastY; /** * @param x1 * @param y1 * @param x2 * @param y2 */ public CopyPathIterator(float x1, float y1, float x2, float y2) { this.x1 = x1; this.y1 = y1; this.w = x2 - x1; this.h = y2 - y1; if (this.w==0f && this.h==0f) { this.index = 6; } } @Override public boolean hasNext() { return this.index<=5; } @Override public PathElement2f next() { if (this.index>5) throw new NoSuchElementException(); int idx = this.index; ++this.index; if (idx==0) { float ctrls[] = CTRL_PTS[3]; this.lastX = this.x1 + ctrls[4] * this.w; this.lastY = this.y1 + ctrls[5] * this.h; return new PathElement2f.MovePathElement2f( this.lastX, this.lastY); } else if (idx<5) { float ctrls[] = CTRL_PTS[idx - 1]; float ix = this.lastX; float iy = this.lastY; this.lastX = (this.x1 + ctrls[4] * this.w); this.lastY = (this.y1 + ctrls[5] * this.h); return new PathElement2f.CurvePathElement2f( ix, iy, (this.x1 + ctrls[0] * this.w), (this.y1 + ctrls[1] * this.h), (this.x1 + ctrls[2] * this.w), (this.y1 + ctrls[3] * this.h), this.lastX, this.lastY); } return new PathElement2f.ClosePathElement2f( this.lastX, this.lastY, this.lastX, this.lastY); } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public PathWindingRule getWindingRule() { return PathWindingRule.NON_ZERO; } @Override public boolean isPolyline() { return false; } } /** * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public static class TransformPathIterator implements PathIterator2f { private final Point2D lastPoint = new Point2f(); private final Point2D ptmp1 = new Point2f(); private final Point2D ptmp2 = new Point2f(); private final Transform2D transform; private final float x1; private final float y1; private final float w; private final float h; private int index; /** * @param x1 * @param y1 * @param x2 * @param y2 * @param transform */ public TransformPathIterator(float x1, float y1, float x2, float y2, Transform2D transform) { this.transform = transform; this.x1 = x1; this.y1 = y1; this.w = x2 - x1; this.h = y2 - y1; if (this.w==0f && this.h==0f) { this.index = 6; } } @Override public boolean hasNext() { return this.index<=5; } @Override public PathElement2f next() { if (this.index>5) throw new NoSuchElementException(); int idx = this.index; ++this.index; if (idx==0) { float ctrls[] = CTRL_PTS[3]; this.lastPoint.set( this.x1 + ctrls[4] * this.w, this.y1 + ctrls[5] * this.h); this.transform.transform(this.lastPoint); return new PathElement2f.MovePathElement2f( this.lastPoint.getX(), this.lastPoint.getY()); } else if (idx<5) { float ctrls[] = CTRL_PTS[idx - 1]; float ix = this.lastPoint.getX(); float iy = this.lastPoint.getY(); this.lastPoint.set( (this.x1 + ctrls[4] * this.w), (this.y1 + ctrls[5] * this.h)); this.transform.transform(this.lastPoint); this.ptmp1.set( (this.x1 + ctrls[0] * this.w), (this.y1 + ctrls[1] * this.h)); this.transform.transform(this.ptmp1); this.ptmp2.set( (this.x1 + ctrls[2] * this.w), (this.y1 + ctrls[3] * this.h)); this.transform.transform(this.ptmp2); return new PathElement2f.CurvePathElement2f( ix, iy, this.ptmp1.getX(), this.ptmp1.getY(), this.ptmp2.getX(), this.ptmp2.getY(), this.lastPoint.getX(), this.lastPoint.getY()); } float ix = this.lastPoint.getX(); float iy = this.lastPoint.getY(); return new PathElement2f.ClosePathElement2f( ix, iy, ix, iy); } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public PathWindingRule getWindingRule() { return PathWindingRule.NON_ZERO; } @Override public boolean isPolyline() { return false; } } }