/* * $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.ai; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.math.MathConstants; import org.arakhne.afc.math.geometry.CrossingComputationType; import org.arakhne.afc.math.geometry.PathWindingRule; import org.arakhne.afc.math.geometry.d2.GeomFactory2D; import org.arakhne.afc.math.geometry.d2.Point2D; import org.arakhne.afc.math.geometry.d2.Transform2D; import org.arakhne.afc.math.geometry.d2.Tuple2iComparator; import org.arakhne.afc.math.geometry.d2.Vector2D; import org.arakhne.afc.vmutil.asserts.AssertMessages; /** Fonctional interface that represented a 2D circle 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$ * @since 13.0 */ public interface Circle2ai< ST extends Shape2ai<?, ?, IE, P, V, B>, IT extends Circle2ai<?, ?, IE, P, V, B>, IE extends PathElement2ai, P extends Point2D<? super P, ? super V>, V extends Vector2D<? super V, ? super P>, B extends Rectangle2ai<?, ?, IE, P, V, B>> extends Shape2ai<ST, IT, IE, P, V, B> { /** Replies if the given point is inside the circle. * * @param cx is the x-coordinate of the circle center * @param cy is the y-coordinate of the circle center * @param cr is the radius of the circle center * @param x is the x-coordinate of the point * @param y is the y-coordinate of the point * @return <code>true</code> if the point is inside the circle. */ @Pure @SuppressWarnings("checkstyle:magicnumber") static boolean containsCirclePoint(int cx, int cy, int cr, int x, int y) { assert cr >= 0 : AssertMessages.positiveOrZeroParameter(2); final int vx = x - cx; final int vy = y - cy; if (vx >= -cr && vx <= cr && vy >= -cr && vy <= cr) { final int octant; final boolean xpos = vx >= 0; final boolean ypos = vy >= 0; if (xpos) { if (ypos) { octant = 0; } else { octant = 2; } } else { if (ypos) { octant = 6; } else { octant = 4; } } boolean allNull = true; final CirclePerimeterIterator<InnerComputationPoint2ai, InnerComputationVector2ai> iterator = new CirclePerimeterIterator<>( InnerComputationGeomFactory.SINGLETON, cx, cy, cr, octant, octant + 2, false); while (iterator.hasNext()) { final Point2D<?, ?> p = iterator.next(); // Trivial case if (p.ix() == x && p.iy() == y) { return true; } final int px = cy - p.iy(); final int py = p.ix() - cx; final int cpx = x - p.ix(); final int cpy = y - p.iy(); final int ccw = cpx * py - cpy * px; if (ccw > 0) { return false; } if (ccw < 0) { allNull = false; } } return !allNull; } return false; } /** Replies if the given point is inside the quadrant of the given circle. * * <table border="1" width="100%" summary="definition of the quadrant values"> * <thead> * <tr><td>quadrant</td><td>x</td><td>y</td></tr> * </thead> * <tbody> * <tr><td>0</td><td>≥cx</td><td>≥cy</td></tr> * <tr><td>1</td><td>≥cx</td><td><cy</td></tr> * <tr><td>2</td><td><cx</td><td>≥cy</td></tr> * <tr><td>3</td><td><cx</td><td><cy</td></tr> * </tbody> * </table> * * @param cx is the x-coordinate of the circle center * @param cy is the y-coordinate of the circle center * @param cr is the radius of the circle center * @param quadrant is the quadrant, see table in the method description. * @param x is the x-coordinate of the point * @param y is the y-coordinate of the point * @return <code>true</code> if the point is inside the circle. */ @Pure @SuppressWarnings("checkstyle:magicnumber") static boolean containsCircleQuadrantPoint(int cx, int cy, int cr, int quadrant, int x, int y) { assert cr >= 0 : AssertMessages.positiveOrZeroParameter(2); assert quadrant >= 0 && quadrant <= 3 : AssertMessages.outsideRangeInclusiveParameter(3, quadrant, 0, 3); final int vx = x - cx; final int vy = y - cy; if (vx >= -cr && vx <= cr && vy >= -cr && vy <= cr) { final int octant; final boolean xpos = vx >= 0; final boolean ypos = vy >= 0; if (xpos) { if (ypos) { octant = 0; } else { octant = 2; } } else { if (ypos) { octant = 6; } else { octant = 4; } } if (quadrant * 2 != octant) { return false; } final CirclePerimeterIterator<InnerComputationPoint2ai, InnerComputationVector2ai> iterator = new CirclePerimeterIterator<>( InnerComputationGeomFactory.SINGLETON, cx, cy, cr, octant, octant + 2, false); while (iterator.hasNext()) { final Point2D<?, ?> p = iterator.next(); final int px = cy - p.iy(); final int py = p.ix() - cx; final int cpx = x - p.ix(); final int cpy = y - p.iy(); final int ccw = cpx * py - cpy * px; if (ccw > 0) { return false; } } return true; } return false; } /** Replies if the given point is inside the circle. * * @param cx is the x-coordinate of the circle center * @param cy is the y-coordinate of the circle center * @param cr is the radius of the circle center * @param x is the x-coordinate of the point * @param y is the y-coordinate of the point * @return <code>true</code> if the point is inside the circle. * @deprecated since 13.0, see {@link #containsCirclePoint(int, int, int, int, int)} */ @Pure @Deprecated static boolean contains(int cx, int cy, int cr, int x, int y) { return containsCirclePoint(cx, cy, cr, x, y); } /** Replies if the given point is inside the quadrant of the given circle. * * <table border="1" width="100%" summary="definition of the quadrant values"> * <thead> * <tr><td>quadrant</td><td>x</td><td>y</td></tr> * </thead> * <tbody> * <tr><td>0</td><td>≥cx</td><td>≥cy</td></tr> * <tr><td>1</td><td>≥cx</td><td><cy</td></tr> * <tr><td>2</td><td><cx</td><td>≥cy</td></tr> * <tr><td>3</td><td><cx</td><td><cy</td></tr> * </tbody> * </table> * * @param cx is the x-coordinate of the circle center * @param cy is the y-coordinate of the circle center * @param cr is the radius of the circle center * @param quadrant is the quadrant, see table in the method description. * @param x is the x-coordinate of the point * @param y is the y-coordinate of the point * @return <code>true</code> if the point is inside the circle. * @deprecated since 13.0, see {@link #containsCircleQuadrantPoint(int, int, int, int, int, int)} */ @Pure @Deprecated static boolean contains(int cx, int cy, int cr, int quadrant, int x, int y) { return containsCircleQuadrantPoint(cx, cy, cr, quadrant, x, y); } @Pure @Override default boolean contains(int x, int y) { return containsCirclePoint(getX(), getY(), getRadius(), x, y); } @Pure @Override @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:magicnumber", "checkstyle:booleanexpressioncomplexity"}) default boolean contains(Rectangle2ai<?, ?, ?, ?, ?, ?> box) { assert box != null : AssertMessages.notNullParameter(); final int cx = getX(); final int cy = getY(); final int radius = getRadius(); final int vx1 = box.getMinX() - cx; final int vy1 = box.getMinY() - cy; final int vx2 = box.getMaxX() - cx; final int vy2 = box.getMaxY() - cy; if (vx1 >= -radius && vx1 <= radius && vy1 >= -radius && vy1 <= radius && vx2 >= -radius && vx2 <= radius && vy2 >= -radius && vy2 <= radius) { final int[] quadrants = new int[4]; final int[] x = new int[] {vx1, vx2, vx2, vx1}; final int[] y = new int[] {vy1, vy1, vy2, vy2}; for (int i = 0; i < 4; ++i) { final int xcoord = x[i]; final int ycoord = y[i]; final int flag = 1 << i; if (xcoord > 0) { if (ycoord > 0) { quadrants[0] |= flag; } else { quadrants[1] |= flag; } } else { if (ycoord > 0) { quadrants[3] |= flag; } else { quadrants[2] |= flag; } } } for (int i = 0; i < quadrants.length; ++i) { if (quadrants[i] != 0) { final int didx = i * 2; final CirclePerimeterIterator<P, V> iterator = new CirclePerimeterIterator<>( getGeomFactory(), cx, cy, radius, didx, didx + 2, false); while (iterator.hasNext()) { final Point2D<?, ?> p = iterator.next(); final int px = cy - p.iy(); final int py = p.ix() - cx; for (int j = 0; j < 4; ++j) { if ((quadrants[i] & (1 << j)) != 0) { final int cpx = x[j] - p.ix(); final int cpy = y[j] - p.iy(); final int ccw = cpx * py - cpy * px; if (ccw > 0) { return false; } } } } } } return true; } return false; } /** Replies the closest point in a circle to a point. * * <p>The closest point is the point on the perimeter or inside the circle's disk that * has the lowest Manhatan distance to the given origin point. * * @param cx is the center of the circle * @param cy is the center of the circle * @param cr is the radius of the circle * @param x is the point * @param y is the point * @param result the closest point in the circle to the point. * @deprecated since 13.0, see {@link #findsClosestPointCirclePoint(int, int, int, int, int, Point2D)} */ @Deprecated static void computeClosestPointTo(int cx, int cy, int cr, int x, int y, Point2D<?, ?> result) { findsClosestPointCirclePoint(cx, cy, cr, x, y, result); } /** Replies the closest point in a circle to a point. * * <p>The closest point is the point on the perimeter or inside the circle's disk that * has the lowest Manhatan distance to the given origin point. * * @param cx is the center of the circle * @param cy is the center of the circle * @param cr is the radius of the circle * @param x is the point * @param y is the point * @param result the closest point in the circle to the point. */ @SuppressWarnings("checkstyle:magicnumber") static void findsClosestPointCirclePoint(int cx, int cy, int cr, int x, int y, Point2D<?, ?> result) { assert cr >= 0 : AssertMessages.positiveOrZeroParameter(2); assert result != null : AssertMessages.notNullParameter(5); final int vx = x - cx; final int vy = y - cy; final int octant; final boolean xpos = vx >= 0; final boolean ypos = vy >= 0; if (xpos) { if (ypos) { octant = 0; } else { octant = 2; } } else { if (ypos) { octant = 6; } else { octant = 4; } } final CirclePerimeterIterator<InnerComputationPoint2ai, InnerComputationVector2ai> iterator = new CirclePerimeterIterator<>( InnerComputationGeomFactory.SINGLETON, cx, cy, cr, octant, octant + 2, false); boolean isInside = true; int minDist = Integer.MAX_VALUE; while (iterator.hasNext()) { final Point2D<?, ?> p = iterator.next(); final int px = cy - p.iy(); final int py = p.ix() - cx; final int cpx = x - p.ix(); final int cpy = y - p.iy(); final int ccw = cpx * py - cpy * px; if (ccw >= 0) { isInside = false; // Mahantan distance final int d = Math.abs(cpx) + Math.abs(cpy); if (d < minDist) { minDist = d; result.set(p); } } } // inside the circle if (isInside) { result.set(x, y); } } /** Replies the farthest point in a circle to a point. * * <p>The farthest point is the point on the perimeter of the circle that has the highest Manhatan distance * to the given origin point. * * @param cx is the center of the circle * @param cy is the center of the circle * @param cr is the radius of the circle * @param x is the point * @param y is the point * @param result the farthest point in the circle to the point. * @deprecated since 13.0, see {@link #findsFarthestPointCirclePoint(int, int, int, int, int, Point2D)} */ @Deprecated static void computeFarthestPointTo(int cx, int cy, int cr, int x, int y, Point2D<?, ?> result) { findsFarthestPointCirclePoint(cx, cy, cr, x, y, result); } /** Replies the farthest point in a circle to a point. * * <p>The farthest point is the point on the perimeter of the circle that has the highest Manhatan distance * to the given origin point. * * @param cx is the center of the circle * @param cy is the center of the circle * @param cr is the radius of the circle * @param x is the point * @param y is the point * @param result the farthest point in the circle to the point. */ @SuppressWarnings("checkstyle:magicnumber") static void findsFarthestPointCirclePoint(int cx, int cy, int cr, int x, int y, Point2D<?, ?> result) { assert cr >= 0 : AssertMessages.positiveOrZeroParameter(2); final int vx = x - cx; final int vy = y - cy; final int octant; final boolean xpos = vx >= 0; final boolean ypos = vy >= 0; if (xpos) { if (ypos) { octant = 4; } else { octant = 6; } } else { if (ypos) { octant = 2; } else { octant = 0; } } final CirclePerimeterIterator<InnerComputationPoint2ai, InnerComputationVector2ai> iterator = new CirclePerimeterIterator<>( InnerComputationGeomFactory.SINGLETON, cx, cy, cr, octant, octant + 2, false); int maxL1Dist = Integer.MIN_VALUE; int maxLinfDist = Integer.MIN_VALUE; result.set(x, y); while (iterator.hasNext()) { final Point2D<?, ?> p = iterator.next(); final int cpx = Math.abs(p.ix() - x); final int cpy = Math.abs(p.iy() - y); // Mahantan distance final int l1 = cpx + cpy; final int linfinv = Math.min(cpx, cpy); if (l1 > maxL1Dist || (l1 == maxL1Dist && linfinv < maxLinfDist)) { maxL1Dist = l1; maxLinfDist = linfinv; result.set(p); } } } /** Replies if two circles are intersecting. * * @param x1 is the center of the first circle * @param y1 is the center of the first circle * @param radius1 is the radius of the first circle * @param x2 is the center of the second circle * @param y2 is the center of the second circle * @param radius2 is the radius of the second circle * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ @Pure @SuppressWarnings("checkstyle:magicnumber") static boolean intersectsCircleCircle(int x1, int y1, int radius1, int x2, int y2, int radius2) { assert radius1 >= 0 : AssertMessages.positiveOrZeroParameter(2); assert radius2 >= 0 : AssertMessages.positiveOrZeroParameter(5); final Point2D<?, ?> point = new InnerComputationPoint2ai(); findsClosestPointCirclePoint(x1, y1, radius1, x2, y2, point); return containsCirclePoint(x2, y2, radius2, point.ix(), point.iy()); } /** Replies if a circle and a rectangle are intersecting. * * @param x1 is the center of the circle * @param y1 is the center of the circle * @param radius is the radius of the circle * @param x2 is the first corner of the rectangle. * @param y2 is the first corner of the rectangle. * @param x3 is the second corner of the rectangle. * @param y3 is the second corner of the rectangle. * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ @Pure static boolean intersectsCircleRectangle(int x1, int y1, int radius, int x2, int y2, int x3, int y3) { assert radius >= 0 : AssertMessages.positiveOrZeroParameter(2); final Point2D<?, ?> point = new InnerComputationPoint2ai(); Rectangle2ai.findsClosestPointRectanglePoint(x2, y2, x3, y3, x1, y1, point); return containsCirclePoint(x1, y1, radius, point.ix(), point.iy()); } /** Replies if a circle and a segment are intersecting. * * @param x1 is the center of the circle * @param y1 is the center of the circle * @param radius is the radius of the circle * @param x2 is the first point of the segment. * @param y2 is the first point of the segment. * @param x3 is the second point of the segment. * @param y3 is the second point of the segment. * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ @Pure static boolean intersectsCircleSegment(int x1, int y1, int radius, int x2, int y2, int x3, int y3) { assert radius >= 0 : AssertMessages.positiveOrZeroParameter(2); final Point2D<?, ?> point = new InnerComputationPoint2ai(); Segment2ai.findsClosestPointSegmentPoint(x2, y2, x3, y3, x1, y1, point); return containsCirclePoint(x1, y1, radius, point.ix(), point.iy()); } /** Replies the points of the circle perimeters starting by the first octant. * * @param <P> the type of the points. * @param <V> the type of the vectors. * @param cx is the center of the radius. * @param cy is the center of the radius. * @param radius is the radius of the radius. * @param firstOctantIndex is the index of the first octant to treat (value in [0;7]. * @param nbOctants is the number of octants to traverse (value in [0; 7 - firstOctantIndex]. * @param factory the factory to use for creating the points. * @return the points on the perimeters. */ @Pure @SuppressWarnings("checkstyle:magicnumber") static <P extends Point2D<? super P, ? super V>, V extends Vector2D<? super V, ? super P>> Iterator<P> newPointIterator(int cx, int cy, int radius, int firstOctantIndex, int nbOctants, GeomFactory2ai<?, P, V, ?> factory) { assert radius >= 0 : AssertMessages.positiveOrZeroParameter(2); assert firstOctantIndex >= 0 && firstOctantIndex < 8 : AssertMessages.outsideRangeInclusiveParameter(3, firstOctantIndex, 0, 7); int maxOctant; maxOctant = Math.min(8, firstOctantIndex + nbOctants); if (maxOctant > 8) { maxOctant = 8; } return new CirclePerimeterIterator<>( factory, cx, cy, radius, firstOctantIndex, maxOctant, true); } /** Replies the points of the circle perimeters starting by the first octant. * * @param <P> the type of the points. * @param <V> the type of the vectors. * @param cx is the center of the radius. * @param cy is the center of the radius. * @param radius is the radius of the radius. * @param firstOctantIndex is the index of the first octant to treat (value in [0;7]. * @param nbOctants is the number of octants to traverse (value in [0; 7 - firstOctantIndex]. * @param factory the factory to use for creating the points. * @return the points on the perimeters. * @deprecated since 13.0, see {@link #newPointIterator(int, int, int, int, int, GeomFactory2ai)} */ @Pure @Deprecated static <P extends Point2D<? super P, ? super V>, V extends Vector2D<? super V, ? super P>> Iterator<P> getPointIterator(int cx, int cy, int radius, int firstOctantIndex, int nbOctants, GeomFactory2ai<?, P, V, ?> factory) { return newPointIterator(cx, cy, radius, firstOctantIndex, nbOctants, factory); } /** Replies the points of the circle perimeters starting by the first octant. * * @param firstOctantIndex is the index of the first octant (see figure) to treat. * @param nbOctants is the number of octants to traverse (greater than zero). * @return the points on the perimeters. */ @Pure default Iterator<P> getPointIterator(int firstOctantIndex, int nbOctants) { return newPointIterator(getX(), getY(), getRadius(), firstOctantIndex, nbOctants, getGeomFactory()); } /** Replies the points of the circle perimeters starting by the first octant. * * @return the points on the perimeters. */ @Pure @Override @SuppressWarnings("checkstyle:magicnumber") default Iterator<P> getPointIterator() { return new CirclePerimeterIterator<>(getGeomFactory(), getX(), getY(), getRadius(), 0, 8, true); } @Pure @Override default boolean equalsToShape(IT shape) { if (shape == null) { return false; } if (shape == this) { return true; } return getX() == shape.getX() && getY() == shape.getY() && getRadius() == shape.getRadius(); } @Override default void clear() { set(0, 0, 0); } @Pure @Override default boolean isEmpty() { return getRadius() <= 0; } /** Change the circle. * * @param x the x coordinate of the center. * @param y the y coordinate of the center. * @param radius the radiusof the center. */ void set(int x, int y, int radius); @Override default void set(IT shape) { set(shape.getX(), shape.getY(), shape.getRadius()); } /** Change the circle. * * @param center the center of the circle. * @param radius the radius of the circle. */ default void set(Point2D<?, ?> center, int radius) { set(center.ix(), center.iy(), Math.abs(radius)); } /** Change the circle's center. * * @param center the center of the circle. */ default void setCenter(Point2D<?, ?> center) { set(center.ix(), center.iy(), getRadius()); } /** Change the circle's center. * * @param x x coordinate of the center of the circle. * @param y y coordinate of the center of the circle. */ default void setCenter(int x, int y) { set(x, y, getRadius()); } /** Replies the center X. * * @return the center x. */ @Pure int getX(); /** Change the center X. * * @param x the center x. */ @Pure void setX(int x); /** Replies the center y. * * @return the center y. */ @Pure int getY(); /** Change the center Y. * * @param y the center y. */ @Pure void setY(int y); /** Replies the center. * * @return a copy of the center. */ @Pure default P getCenter() { return getGeomFactory().newPoint(getX(), getY()); } /** Replies the radius. * * @return the radius. */ @Pure int getRadius(); /** Change the radius. * * @param radius the radius. */ @Pure void setRadius(int radius); @Pure @Override default void toBoundingBox(B box) { assert box != null : AssertMessages.notNullParameter(); final int centerX = getX(); final int centerY = getY(); final int radius = getRadius(); box.setFromCorners( centerX - radius, centerY - radius, centerX + radius, centerY + radius); } @Pure @Override default double getDistanceSquared(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final P c = getClosestPointTo(pt); return c.getDistanceSquared(pt); } @Pure @Override default double getDistanceL1(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final P c = getClosestPointTo(pt); return c.getDistanceL1(pt); } @Pure @Override default double getDistanceLinf(Point2D<?, ?> pt) { final P c = getClosestPointTo(pt); return c.getDistanceLinf(pt); } @Pure @Override default P getClosestPointTo(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final P point = getGeomFactory().newPoint(); findsClosestPointCirclePoint(getX(), getY(), getRadius(), pt.ix(), pt.iy(), point); return point; } @Override default P getClosestPointTo(Rectangle2ai<?, ?, ?, ?, ?, ?> rectangle) { assert rectangle != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = rectangle.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Override default P getClosestPointTo(Circle2ai<?, ?, ?, ?, ?, ?> circle) { assert circle != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = circle.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Override default P getClosestPointTo(Segment2ai<?, ?, ?, ?, ?, ?> segment) { assert segment != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = segment.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Override default P getClosestPointTo(Path2ai<?, ?, ?, ?, ?, ?> path) { assert path != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = path.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getFarthestPointTo(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final P point = getGeomFactory().newPoint(); findsFarthestPointCirclePoint(getX(), getY(), getRadius(), pt.ix(), pt.iy(), point); return point; } @Pure @Override default boolean intersects(Rectangle2ai<?, ?, ?, ?, ?, ?> rectangle) { assert rectangle != null : AssertMessages.notNullParameter(); return intersectsCircleRectangle( getX(), getY(), getRadius(), rectangle.getMinX(), rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMaxY()); } @Pure @Override default boolean intersects(Circle2ai<?, ?, ?, ?, ?, ?> circle) { assert circle != null : AssertMessages.notNullParameter(); return intersectsCircleCircle( getX(), getY(), getRadius(), circle.getX(), circle.getY(), circle.getRadius()); } @Pure @Override default boolean intersects(Segment2ai<?, ?, ?, ?, ?, ?> segment) { assert segment != null : AssertMessages.notNullParameter(); return intersectsCircleSegment( getX(), getY(), getRadius(), segment.getX1(), segment.getY1(), segment.getX2(), segment.getY2()); } @Pure @Override default boolean intersects(PathIterator2ai<?> iterator) { assert iterator != null : AssertMessages.notNullParameter(); final int mask = iterator.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2; final int crossings = Path2ai.calculatesCrossingsPathIteratorCircleShadow( 0, iterator, getX(), getY(), getRadius(), CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON); return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0; } @Pure @Override default boolean intersects(MultiShape2ai<?, ?, ?, ?, ?, ?, ?> multishape) { assert multishape != null : AssertMessages.notNullParameter(); return multishape.intersects(this); } @Override default void translate(int dx, int dy) { setCenter(getX() + dx, getY() + dy); } @Pure @Override default PathIterator2ai<IE> getPathIterator(Transform2D transform) { if (transform == null || transform.isIdentity()) { return new CirclePathIterator<>(this); } return new TransformedCirclePathIterator<>(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 PathIterator2ai<IE> getFlatteningPathIterator() { return new Path2ai.FlatteningPathIterator<>( getPathIterator(null), MathConstants.SPLINE_APPROXIMATION_RATIO, Path2ai.DEFAULT_FLATTENING_LIMIT); } /** Abstract iterator on the path elements of the circle. * * @param <IE> is the type of the path elements. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ @SuppressWarnings("checkstyle:magicnumber") abstract class AbstractCirclePathIterator<IE extends PathElement2ai> implements PathIterator2ai<IE> { /** * ArcIterator.btan(Math.PI/2). */ protected static final double CTRL_VAL = 0.5522847498307933; /** * 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. */ protected static final double PCV = 0.5 + CTRL_VAL * 0.5; /** * 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. */ protected static final double NCV = 0.5 - CTRL_VAL * 0.5; /** * 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. */ protected static final double[][] CTRL_PTS = { {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}, }; /** * The element factory. */ protected final Circle2ai<?, ?, IE, ?, ?, ?> circle; /** * @param circle the circle. */ public AbstractCirclePathIterator(Circle2ai<?, ?, IE, ?, ?, ?> circle) { assert circle != null : AssertMessages.notNullParameter(); this.circle = circle; } @Override public void remove() { throw new UnsupportedOperationException(); } @Pure @Override public PathWindingRule getWindingRule() { return PathWindingRule.NON_ZERO; } @Pure @Override public boolean isPolyline() { return false; } @Override public GeomFactory2ai<IE, ?, ?, ?> getGeomFactory() { return this.circle.getGeomFactory(); } @Override public boolean isCurved() { return true; } @Override public boolean isMultiParts() { return false; } @Override public boolean isPolygon() { return true; } } /** Iterator on the path elements of the circle. * * @param <IE> is the type of the path elements. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ @SuppressWarnings("checkstyle:magicnumber") class CirclePathIterator<IE extends PathElement2ai> extends AbstractCirclePathIterator<IE> { private int x; private int y; private int radius; private int index; private int movex; private int movey; private int lastx; private int lasty; /** * @param circle the circle to iterate on. */ public CirclePathIterator(Circle2ai<?, ?, IE, ?, ?, ?> circle) { super(circle); this.radius = circle.getRadius(); this.x = circle.getX() - this.radius; this.y = circle.getY() - this.radius; } @Override public PathIterator2ai<IE> restartIterations() { return new CirclePathIterator<>(this.circle); } @Pure @Override public boolean hasNext() { return this.index <= 5; } @Override public IE next() { if (this.index > 5) { throw new NoSuchElementException(); } final int idx = this.index; ++this.index; if (idx == 0) { final int dr = 2 * this.radius; final double[] ctrls = CTRL_PTS[3]; this.movex = (int) (this.x + ctrls[4] * dr); this.movey = (int) (this.y + ctrls[5] * dr); this.lastx = this.movex; this.lasty = this.movey; return getGeomFactory().newMovePathElement( this.lastx, this.lasty); } else if (idx < 5) { final int dr = 2 * this.radius; final double[] ctrls = CTRL_PTS[idx - 1]; final int ppx = this.lastx; final int ppy = this.lasty; this.lastx = (int) (this.x + ctrls[4] * dr); this.lasty = (int) (this.y + ctrls[5] * dr); return getGeomFactory().newCurvePathElement( ppx, ppy, (int) (this.x + ctrls[0] * dr), (int) (this.y + ctrls[1] * dr), (int) (this.x + ctrls[2] * dr), (int) (this.y + ctrls[3] * dr), this.lastx, this.lasty); } final int ppx = this.lastx; final int ppy = this.lasty; this.lastx = this.movex; this.lasty = this.movey; return getGeomFactory().newClosePathElement( ppx, ppy, this.lastx, this.lasty); } } /** Iterator on the path elements of the circle. * * @param <IE> is the type of the path elements. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ @SuppressWarnings("checkstyle:magicnumber") class TransformedCirclePathIterator<IE extends PathElement2ai> extends AbstractCirclePathIterator<IE> { private final Transform2D transform; private int x; private int y; private int radius; private int index; private int movex; private int movey; private Point2D<?, ?> p1; private Point2D<?, ?> p2; private Point2D<?, ?> ptmp1; private Point2D<?, ?> ptmp2; /** * @param circle the circle to iterate on. * @param transform the transformation to apply. */ public TransformedCirclePathIterator(Circle2ai<?, ?, IE, ?, ?, ?> circle, Transform2D transform) { super(circle); assert transform != null : AssertMessages.notNullParameter(1); this.transform = transform; this.p1 = new InnerComputationPoint2ai(); this.p2 = new InnerComputationPoint2ai(); this.ptmp1 = new InnerComputationPoint2ai(); this.ptmp2 = new InnerComputationPoint2ai(); this.radius = circle.getRadius(); this.x = circle.getX() - this.radius; this.y = circle.getY() - this.radius; } @Override public PathIterator2ai<IE> restartIterations() { return new TransformedCirclePathIterator<>(this.circle, this.transform); } @Pure @Override public boolean hasNext() { return this.index <= 5; } @Override public IE next() { if (this.index > 5) { throw new NoSuchElementException(); } final int idx = this.index; ++this.index; if (idx == 0) { final int dr = 2 * this.radius; final double[] ctrls = CTRL_PTS[3]; this.movex = (int) (this.x + ctrls[4] * dr); this.movey = (int) (this.y + ctrls[5] * dr); this.p2.set(this.movex, this.movey); this.transform.transform(this.p2); return getGeomFactory().newMovePathElement( this.p2.ix(), this.p2.iy()); } else if (idx < 5) { final int dr = 2 * this.radius; final double[] ctrls = CTRL_PTS[idx - 1]; this.p1.set(this.p2); this.p2.set( this.x + ctrls[4] * dr, this.y + ctrls[5] * dr); this.transform.transform(this.p2); this.ptmp1.set( this.x + ctrls[0] * dr, this.y + ctrls[1] * dr); this.transform.transform(this.ptmp1); this.ptmp2.set( this.x + ctrls[2] * dr, this.y + ctrls[3] * dr); this.transform.transform(this.ptmp2); return getGeomFactory().newCurvePathElement( this.p1.ix(), this.p1.iy(), this.ptmp1.ix(), this.ptmp1.iy(), this.ptmp2.ix(), this.ptmp2.iy(), this.p2.ix(), this.p2.iy()); } this.p1.set(this.p2); this.p2.set(this.movex, this.movey); this.transform.transform(this.p2); return getGeomFactory().newClosePathElement( this.p1.ix(), this.p1.iy(), this.p2.ix(), this.p2.iy()); } } /** Iterates on points on the perimeter of a circle. * * <p>The rastrerization is based on a Bresenham algorithm. * * @param <P> the type of the points. * @param <V> the type of the vectors. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ @SuppressWarnings("checkstyle:magicnumber") class CirclePerimeterIterator<P extends Point2D<? super P, ? super V>, V extends Vector2D<? super V, ? super P>> implements Iterator<P> { private final GeomFactory2D<V, P> factory; private final int cx; private final int cy; private final int cr; private final boolean skip; private final int maxOctant; private int currentOctant; private int x; private int y; private int dval; private P next; private final Set<P> junctionPoint = new TreeSet<>(new Tuple2iComparator()); /** Construct the iterator from the initialOctant (inclusive) to the lastOctant (exclusive). * * @param factory the point factory. * @param centerX the x coordinate of the center of the circle. * @param centerY the y coordinate of the center of the circle. * @param radius the radius of the circle. * @param initialOctant the octant from which the iteration must start. * @param lastOctant the first octant that must not be iterated on. * @param skip indicates if the first point on an octant must be skip, because it is already replied when treating the * previous octant. */ @SuppressWarnings("checkstyle:magicnumber") public CirclePerimeterIterator(GeomFactory2D<V, P> factory, int centerX, int centerY, int radius, int initialOctant, int lastOctant, boolean skip) { assert factory != null : AssertMessages.notNullParameter(0); assert radius >= 0 : AssertMessages.positiveOrZeroParameter(3); assert initialOctant >= 0 && initialOctant < 8 : AssertMessages.outsideRangeInclusiveParameter(4, initialOctant, 0, 7); assert lastOctant > initialOctant && lastOctant <= 8 : AssertMessages.outsideRangeInclusiveParameter(5, lastOctant, initialOctant + 1, 8); this.factory = factory; this.cx = centerX; this.cy = centerY; this.cr = radius; this.skip = skip; this.maxOctant = lastOctant; this.currentOctant = initialOctant; reset(); searchNext(); } private void reset() { this.x = 0; this.y = this.cr; this.dval = 3 - 2 * this.cr; if (this.skip && (this.currentOctant == 3 || this.currentOctant == 4 || this.currentOctant == 6 || this.currentOctant == 7)) { // skip the first point because already replied in previous octant if (this.dval <= 0) { this.dval += 4 * this.x + 6; } else { this.dval += 4 * (this.x - this.y) + 10; --this.y; } ++this.x; } } @Pure @Override public boolean hasNext() { return this.next != null; } @SuppressWarnings("checkstyle:cyclomaticcomplexity") private void searchNext() { if (this.currentOctant >= this.maxOctant) { this.next = null; } else { this.next = this.factory.newPoint(); while (true) { switch (this.currentOctant) { case 0: this.next.set(this.cx + this.x, this.cy + this.y); break; case 1: this.next.set(this.cx + this.y, this.cy + this.x); break; case 2: this.next.set(this.cx + this.x, this.cy - this.y); break; case 3: this.next.set(this.cx + this.y, this.cy - this.x); break; case 4: this.next.set(this.cx - this.x, this.cy - this.y); break; case 5: this.next.set(this.cx - this.y, this.cy - this.x); break; case 6: this.next.set(this.cx - this.x, this.cy + this.y); break; case 7: this.next.set(this.cx - this.y, this.cy + this.x); break; default: throw new NoSuchElementException(); } if (this.dval <= 0) { this.dval += 4 * this.x + 6; } else { this.dval += 4 * (this.x - this.y) + 10; --this.y; } ++this.x; if (this.x > this.y) { // The octant is finished. // Save the junction. boolean cont = this.junctionPoint.contains(this.next); if (!cont) { final P point = this.factory.newPoint(); point.set(this.next.ix(), this.next.iy()); this.junctionPoint.add(point); } // Goto next. ++this.currentOctant; reset(); if (this.currentOctant >= this.maxOctant) { if (cont) { this.next = null; } cont = false; } if (!cont) { return; } } else { return; } } } } @Override public P next() { final P pixel = this.next; if (pixel == null) { throw new NoSuchElementException(); } searchNext(); return pixel; } } }