/* * $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.MathUtil; 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.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 Circle2afp< ST extends Shape2afp<?, ?, IE, P, V, B>, IT extends Circle2afp<?, ?, 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 Ellipse2afp<ST, IT, IE, P, V, B> { /** * Replies if the given point is inside the given ellipse. * * @param px is the point to test. * @param py is the point to test. * @param cx is the center of the circle. * @param cy is the center of the circle. * @param radius is the radius of the circle. * @return <code>true</code> if the point is inside the circle; * <code>false</code> if not. */ @Pure static boolean containsCirclePoint(double cx, double cy, double radius, double px, double py) { assert radius >= 0 : AssertMessages.positiveOrZeroParameter(2); return Point2D.getDistanceSquaredPointPoint( px, py, cx, cy) <= (radius * radius); } /** Replies if a rectangle is inside in the circle. * * @param cx is the center of the circle. * @param cy is the center of the circle. * @param radius is the radius of the circle. * @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 circle; * otherwise <code>false</code>. */ @Pure @SuppressWarnings("checkstyle:magicnumber") static boolean containsCircleRectangle(double cx, double cy, double radius, double rxmin, double rymin, double rxmax, double rymax) { assert radius >= 0 : AssertMessages.positiveOrZeroParameter(2); assert rxmin <= rxmax : AssertMessages.lowerEqualParameters(3, rxmin, 5, rxmax); assert rymin <= rymax : AssertMessages.lowerEqualParameters(4, rymin, 6, rymax); final double rcx = (rxmin + rxmax) / 2; final double rcy = (rymin + rymax) / 2; final double farX; if (cx <= rcx) { farX = rxmax; } else { farX = rxmin; } final double farY; if (cy <= rcy) { farY = rymax; } else { farY = rymin; } return containsCirclePoint(cx, cy, radius, farX, farY); } /** 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(double x1, double y1, double radius1, double x2, double y2, double radius2) { assert radius1 >= 0 : AssertMessages.positiveOrZeroParameter(2); assert radius1 >= 0 : AssertMessages.positiveOrZeroParameter(5); final double r = radius1 + radius2; return Point2D.getDistanceSquaredPointPoint(x1, y1, x2, y2) < (r * r); } /** 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 @SuppressWarnings("checkstyle:magicnumber") static boolean intersectsCircleRectangle(double x1, double y1, double radius, double x2, double y2, double x3, double y3) { assert radius >= 0 : AssertMessages.positiveOrZeroParameter(2); assert x2 <= x3 : AssertMessages.lowerEqualParameters(3, x2, 5, x3); assert y2 <= y3 : AssertMessages.lowerEqualParameters(4, y2, 6, y3); final double dx; if (x1 < x2) { dx = x2 - x1; } else if (x1 > x3) { dx = x1 - x3; } else { dx = 0; } final double dy; if (y1 < y2) { dy = y2 - y1; } else if (y1 > y3) { dy = y1 - y3; } else { dy = 0; } return (dx * dx + dy * dy) < (radius * radius); } /** Replies if a circle and a line 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 line. * @param y2 is the first point of the line. * @param x3 is the second point of the line. * @param y3 is the second point of the line. * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ @Pure static boolean intersectsCircleLine(double x1, double y1, double radius, double x2, double y2, double x3, double y3) { assert radius >= 0 : AssertMessages.positiveOrZeroParameter(2); final double d = Segment2afp.calculatesDistanceSquaredLinePoint(x2, y2, x3, y3, x1, y1); return d < (radius * radius); } /** 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(double x1, double y1, double radius, double x2, double y2, double x3, double y3) { assert radius >= 0 : AssertMessages.positiveOrZeroParameter(2); final double d = Segment2afp.calculatesDistanceSquaredSegmentPoint(x2, y2, x3, y3, x1, y1); return d < (radius * radius); } @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(); } /** Replies the center X. * * @return the center x. */ @Pure double getX(); /** Replies the center y. * * @return the center y. */ @Pure double getY(); @Override @Pure default double getCenterX() { return getX(); } @Override @Pure default double getCenterY() { return getY(); } @Pure @Override default P getCenter() { return getGeomFactory().newPoint(getX(), getY()); } /** Change the center. * * @param center the center point. */ default void setCenter(Point2D<?, ?> center) { assert center != null : AssertMessages.notNullParameter(); set(center.getX(), center.getY(), getRadius()); } /** Change the center. * * @param x x coordinate of the center point. * @param y y coordinate of the center point. */ default void setCenter(double x, double y) { setX(x); setY(y); } /** Change the x coordinate of the center. * * @param x x coordinate of the center point. */ void setX(double x); /** Change the y coordinate of the center. * * @param y y coordinate of the center point. */ void setY(double y); /** Replies the radius. * * @return the radius. */ @Pure double getRadius(); /** Set the radius. * * @param radius is the radius. */ void setRadius(double radius); /** Change the frame of the circle. * * @param x x coordinate of the center point. * @param y y coordinate of the center point. * @param radius the radius. */ // Not a default implementation for ensuring atomic change. void set(double x, double y, double radius); /** Change the frame of the circle. * * @param center the center point. * @param radius the radius. */ default void set(Point2D<?, ?> center, double radius) { assert center != null : AssertMessages.notNullParameter(); set(center.getX(), center.getY(), radius); } @Override default void set(IT shape) { assert shape != null : AssertMessages.notNullParameter(); set(shape.getX(), shape.getY(), shape.getRadius()); } @Override default void clear() { set(0, 0, 0); } @Override default void toBoundingBox(B box) { assert box != null : AssertMessages.notNullParameter(); final double x = getX(); final double y = getY(); final double radius = getRadius(); box.setFromCorners( x - radius, y - radius, x + radius, y + radius); } @Override default boolean isEmpty() { return MathUtil.isEpsilonZero(getRadius()); } @Pure @Override default double getDistance(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); double distance = Point2D.getDistancePointPoint(getX(), getY(), pt.getX(), pt.getY()); distance = distance - getRadius(); return Math.max(0., distance); } @Pure @Override default double getDistanceSquared(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final double x = getX(); final double y = getY(); final double radius = getRadius(); final double vx = pt.getX() - x; final double vy = pt.getY() - y; final double sqLength = vx * vx + vy * vy; final double sqRadius = radius * radius; if (sqLength <= sqRadius) { return 0; } return Math.max(0., sqLength - 2 * Math.sqrt(sqLength) * radius + sqRadius); } @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 containsCirclePoint(getX(), getY(), getRadius(), x, y); } @Override default boolean contains(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) { assert rectangle != null : AssertMessages.notNullParameter(); return containsCircleRectangle(getX(), getY(), getRadius(), rectangle.getMinX(), rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMaxY()); } @Override default void translate(double dx, double dy) { setCenter(getX() + dx, getY() + dy); } @Pure @Override default boolean intersects(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) { assert rectangle != null : AssertMessages.notNullParameter(); return intersectsCircleRectangle( getX(), getY(), getRadius(), rectangle.getMinX(), rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMaxY()); } @Pure @Override default boolean intersects(Ellipse2afp<?, ?, ?, ?, ?, ?> ellipse) { assert ellipse != null : AssertMessages.notNullParameter(); return Ellipse2afp.intersectsEllipseCircle( ellipse.getMinX(), ellipse.getMinY(), ellipse.getWidth(), ellipse.getHeight(), getX(), getY(), getRadius()); } @Pure @Override default boolean intersects(Circle2afp<?, ?, ?, ?, ?, ?> circle) { assert circle != null : AssertMessages.notNullParameter(); return intersectsCircleCircle( getX(), getY(), getRadius(), circle.getX(), circle.getY(), circle.getRadius()); } @Pure @Override default boolean intersects(Triangle2afp<?, ?, ?, ?, ?, ?> triangle) { assert triangle != null : AssertMessages.notNullParameter(); return Triangle2afp.intersectsTriangleCircle( triangle.getX1(), triangle.getY1(), triangle.getX2(), triangle.getY2(), triangle.getX3(), triangle.getY3(), getX(), getY(), getRadius()); } @Pure @Override default boolean intersects(Segment2afp<?, ?, ?, ?, ?, ?> segment) { assert segment != null : AssertMessages.notNullParameter(); return intersectsCircleSegment( getX(), getY(), getRadius(), segment.getX1(), segment.getY1(), segment.getX2(), segment.getY2()); } @Pure @Override default boolean intersects(OrientedRectangle2afp<?, ?, ?, ?, ?, ?> orientedRectangle) { assert orientedRectangle != null : AssertMessages.notNullParameter(); return OrientedRectangle2afp.intersectsOrientedRectangleCircle( orientedRectangle.getCenterX(), orientedRectangle.getCenterY(), orientedRectangle.getFirstAxisX(), orientedRectangle.getFirstAxisY(), orientedRectangle.getFirstAxisExtent(), orientedRectangle.getSecondAxisExtent(), getX(), getY(), getRadius()); } @Pure @Override default boolean intersects(Parallelogram2afp<?, ?, ?, ?, ?, ?> parallelogram) { assert parallelogram != null : AssertMessages.notNullParameter(); return Parallelogram2afp.intersectsParallelogramCircle( parallelogram.getCenterX(), parallelogram.getCenterY(), parallelogram.getFirstAxisX(), parallelogram.getFirstAxisY(), parallelogram.getFirstAxisExtent(), parallelogram.getSecondAxisX(), parallelogram.getSecondAxisY(), parallelogram.getSecondAxisExtent(), getX(), getY(), getRadius()); } @Pure @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.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(RoundRectangle2afp<?, ?, ?, ?, ?, ?> roundRectangle) { assert roundRectangle != null : AssertMessages.notNullParameter(); return RoundRectangle2afp.intersectsRoundRectangleCircle( roundRectangle.getMinX(), roundRectangle.getMinY(), roundRectangle.getMaxX(), roundRectangle.getMaxY(), roundRectangle.getArcWidth(), roundRectangle.getArcHeight(), getX(), getY(), getRadius()); } @Pure @Override default boolean intersects(MultiShape2afp<?, ?, ?, ?, ?, ?, ?> multishape) { assert multishape != null : AssertMessages.notNullParameter(); return multishape.intersects(this); } @Pure @Override default P getClosestPointTo(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final double x = getX(); final double y = getY(); final double radius = getRadius(); final double vx = pt.getX() - x; final double vy = pt.getY() - y; final double sqLength = vx * vx + vy * vy; if (sqLength <= (radius * radius)) { return getGeomFactory().convertToPoint(pt); } final double s = radius / Math.sqrt(sqLength); return getGeomFactory().newPoint(x + vx * s, y + vy * s); } @Pure @Override default P getClosestPointTo(Ellipse2afp<?, ?, ?, ?, ?, ?> ellipse) { assert ellipse != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = ellipse.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(Circle2afp<?, ?, ?, ?, ?, ?> circle) { assert circle != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = circle.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) { assert rectangle != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = rectangle.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(Segment2afp<?, ?, ?, ?, ?, ?> segment) { assert segment != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = segment.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(Triangle2afp<?, ?, ?, ?, ?, ?> triangle) { assert triangle != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = triangle.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(Path2afp<?, ?, ?, ?, ?, ?> path) { assert path != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = path.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(OrientedRectangle2afp<?, ?, ?, ?, ?, ?> orientedRectangle) { assert orientedRectangle != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = orientedRectangle.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(Parallelogram2afp<?, ?, ?, ?, ?, ?> parallelogram) { assert parallelogram != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = parallelogram.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(RoundRectangle2afp<?, ?, ?, ?, ?, ?> roundRectangle) { assert roundRectangle != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = roundRectangle.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getClosestPointTo(MultiShape2afp<?, ?, ?, ?, ?, ?, ?> multishape) { assert multishape != null : AssertMessages.notNullParameter(); final Point2D<?, ?> point = multishape.getClosestPointTo(getCenter()); return getClosestPointTo(point); } @Pure @Override default P getFarthestPointTo(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final double x = getX(); final double y = getY(); final double vx = x - pt.getX(); final double vy = y - pt.getY(); final double radius = getRadius(); final double sqLength = vx * vx + vy * vy; if (sqLength <= 0.) { return getGeomFactory().newPoint(radius, 0); } final double s = radius / Math.sqrt(sqLength); return getGeomFactory().newPoint(x + vx * s, y + vy * s); } @Pure @Override default PathIterator2afp<IE> getPathIterator(Transform2D transform) { if (transform == null || transform.isIdentity()) { return new CirclePathIterator<>(this); } return new TransformedCirclePathIterator<>(this, transform); } @Override @Pure default double getHorizontalRadius() { return getRadius(); } @Override @Pure default double getVerticalRadius() { return getRadius(); } /** {@inheritDoc} * * <p>The circle is set in order to be enclosed inside the given box. * It means that the center of the circle is the center of the box, and the * radius of the circle is the minimum of the demi-width and demi-height. */ @Override default void setFromCenter(double centerX, double centerY, double cornerX, double cornerY) { final double demiWidth = Math.abs(cornerX - centerX); final double demiHeight = Math.abs(cornerY - centerY); if (demiWidth <= demiHeight) { set(centerX, centerY, demiWidth); } else { set(centerX, centerY, demiHeight); } } /** {@inheritDoc} * * <p>The circle is set in order to be enclosed inside the given box. * It means that the center of the circle is the center of the box, and the * radius of the circle is the minimum of the demi-width and demi-height. */ @Override default void setFromCorners(double x1, double y1, double x2, double y2) { setFromCenter((x1 + x2) / 2., (y1 + y2) / 2., x2, y2); } @Override default double getMinX() { return getX() - getRadius(); } /** {@inheritDoc} * * <p>Assuming that the maximum X coordinate should not change, the center of * the circle is the point between the new minimum and the current maximum coordinates, * and the radius of the circle is set to the difference between the new minimum and * center coordinates. * * <p>If the new minimum is greater than the current maximum, the coordinates are automatically * swapped. */ @Override default void setMinX(double x) { final double cx = (x + getX() + getRadius()) / 2.; final double radius = Math.abs(cx - x); set(cx, getY(), radius); } @Override default double getMaxX() { return getX() + getRadius(); } /** {@inheritDoc} * * <p>Assuming that the minimum X coordinate should not change, the center of * the circle is the point between the new maximum and the current minimum coordinates, * and the radius of the circle is set to the difference between the new maximum and * center coordinates. * * <p>If the new maximum is lower than the current minimum, the coordinates are automatically * swapped. */ @Override default void setMaxX(double x) { final double cx = (x + getX() - getRadius()) / 2.; final double radius = Math.abs(cx - x); set(cx, getY(), radius); } @Override default double getMinY() { return getY() - getRadius(); } /** {@inheritDoc} * * <p>Assuming that the maximum Y coordinate should not change, the center of * the circle is the point between the new minimum and the current maximum coordinates, * and the radius of the circle is set to the difference between the new minimum and * center coordinates. * * <p>If the new minimum is greater than the current maximum, the coordinates are automatically * swapped. */ @Override default void setMinY(double y) { final double cy = (y + getY() + getRadius()) / 2.; final double radius = Math.abs(cy - y); set(getX(), cy, radius); } @Override default double getMaxY() { return getY() + getRadius(); } /** {@inheritDoc} * * <p>Assuming that the minimum Y coordinate should not change, the center of * the circle is the point between the new maximum and the current minimum coordinates, * and the radius of the circle is set to the difference between the new maximum and * center coordinates. * * <p>If the new maximum is lower than the current minimum, the coordinates are automatically * swapped. */ @Override default void setMaxY(double y) { final double cy = (y + getY() - getRadius()) / 2.; final double radius = Math.abs(cy - y); set(getX(), cy, radius); } /** Abstract iterator on the path elements of the circle. * * <h3>Discretization of the circle with Bezier</h3> * * <p>For n segments on the circle, the optimal distance to the control points, in the sense that the * middle of the curve lies on the circle itself, is (4/3)*tan(pi/(2n)). * * <p><img alt="" src="./doc-files/circlebezier.png" width="100%" > * * <p>In the case of a discretization with 4 bezier curves, the distance is is * (4/3)*tan(pi/8) = 4*(sqrt(2)-1)/3 = 0.552284749831. * * <p><img alt="" src="./doc-files/circlepathiterator.png" width="100%" > * * @param <T> the type of the path elements. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ abstract class AbstractCirclePathIterator<T extends PathElement2afp> implements PathIterator2afp<T> { /** * Distance from a Bezier curve control point on the circle to the other control point. * * <p>4/3 tan (PI/(2*n)), where n is the number on points on the circle. */ public static final double CTRL_POINT_DISTANCE = 0.5522847498307933; /** * Contains the control points for a set of 4 cubic * bezier curves that approximate a circle of radius 1 * centered at (0, 0). */ public static final double[][] BEZIER_CONTROL_POINTS = { // First quarter: max x, max y. {1, CTRL_POINT_DISTANCE, CTRL_POINT_DISTANCE, 1, 0, 1}, // Second quarter: min x, max y. {-CTRL_POINT_DISTANCE, 1, -1, CTRL_POINT_DISTANCE, -1, 0}, // Third quarter: min x, min y. {-1, -CTRL_POINT_DISTANCE, -CTRL_POINT_DISTANCE, -1, 0, -1}, // Fourth quarter: max x, min y. {CTRL_POINT_DISTANCE, -1, 1, -CTRL_POINT_DISTANCE, 1, 0}, }; /** 4 segments + close. */ protected static final int NUMBER_ELEMENTS = 5; /** The iterated shape. */ protected final Circle2afp<?, ?, T, ?, ?, ?> circle; /** * @param circle the circle. */ public AbstractCirclePathIterator(Circle2afp<?, ?, T, ?, ?, ?> circle) { assert circle != null : AssertMessages.notNullParameter(); this.circle = circle; } @Override public GeomFactory2afp<T, ?, ?, ?> getGeomFactory() { return this.circle.getGeomFactory(); } @Pure @Override public PathWindingRule getWindingRule() { return PathWindingRule.NON_ZERO; } @Pure @Override public boolean isPolyline() { return false; } @Override public boolean isCurved() { return true; } @Override public boolean isPolygon() { return true; } @Pure @Override public boolean isMultiParts() { return false; } } /** Iterator on the path elements of the circle. * * @param <T> the type of the path elements. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ class CirclePathIterator<T extends PathElement2afp> extends AbstractCirclePathIterator<T> { private double x; private double y; private double radius; private int index; private double movex; private double movey; private double lastx; private double lasty; /** * @param circle the circle to iterate on. */ public CirclePathIterator(Circle2afp<?, ?, T, ?, ?, ?> circle) { super(circle); if (circle.isEmpty()) { this.index = NUMBER_ELEMENTS; } else { this.radius = circle.getRadius(); this.x = circle.getX(); this.y = circle.getY(); this.index = -1; } } @Override public PathIterator2afp<T> restartIterations() { return new CirclePathIterator<>(this.circle); } @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.movex = this.x + ctrls[4] * this.radius; this.movey = this.y + ctrls[5] * this.radius; this.lastx = this.movex; this.lasty = this.movey; return getGeomFactory().newMovePathElement( this.lastx, this.lasty); } if (idx < (NUMBER_ELEMENTS - 1)) { final double[] ctrls = BEZIER_CONTROL_POINTS[idx]; final double ppx = this.lastx; final double ppy = this.lasty; this.lastx = this.x + ctrls[4] * this.radius; this.lasty = this.y + ctrls[5] * this.radius; return getGeomFactory().newCurvePathElement( ppx, ppy, this.x + ctrls[0] * this.radius, this.y + ctrls[1] * this.radius, this.x + ctrls[2] * this.radius, this.y + ctrls[3] * this.radius, this.lastx, this.lasty); } return getGeomFactory().newClosePathElement( this.lastx, this.lasty, this.movex, this.movey); } } /** Iterator on the path elements of the circle. * * @param <T> the type of the path elements. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ class TransformedCirclePathIterator<T extends PathElement2afp> extends AbstractCirclePathIterator<T> { private final Transform2D transform; private final Point2D<?, ?> tmpPoint; private double x; private double y; private double radius; private double movex; private double movey; private double lastx; private double lasty; private int index; /** * @param circle the iterated circle. * @param transform the transformation to apply. */ public TransformedCirclePathIterator(Circle2afp<?, ?, T, ?, ?, ?> circle, Transform2D transform) { super(circle); assert transform != null : AssertMessages.notNullParameter(1); this.transform = transform; if (circle.isEmpty()) { this.index = NUMBER_ELEMENTS; this.tmpPoint = null; } else { this.tmpPoint = new InnerComputationPoint2afp(); this.radius = circle.getRadius(); this.x = circle.getX(); this.y = circle.getY(); this.index = -1; } } @Override public PathIterator2afp<T> restartIterations() { return new TransformedCirclePathIterator<>(this.circle, this.transform); } @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.tmpPoint.set(this.x + ctrls[4] * this.radius, this.y + ctrls[5] * this.radius); this.transform.transform(this.tmpPoint); this.movex = this.tmpPoint.getX(); this.lastx = this.movex; this.movey = this.tmpPoint.getY(); this.lasty = this.movey; return getGeomFactory().newMovePathElement( this.lastx, this.lasty); } if (idx < (NUMBER_ELEMENTS - 1)) { final double[] ctrls = BEZIER_CONTROL_POINTS[idx]; final double ppx = this.lastx; final double ppy = this.lasty; this.tmpPoint.set(this.x + ctrls[0] * this.radius, this.y + ctrls[1] * this.radius); this.transform.transform(this.tmpPoint); final double ctrlX1 = this.tmpPoint.getX(); final double ctrlY1 = this.tmpPoint.getY(); this.tmpPoint.set(this.x + ctrls[2] * this.radius, this.y + ctrls[3] * this.radius); this.transform.transform(this.tmpPoint); final double ctrlX2 = this.tmpPoint.getX(); final double ctrlY2 = this.tmpPoint.getY(); this.tmpPoint.set(this.x + ctrls[4] * this.radius, this.y + ctrls[5] * this.radius); this.transform.transform(this.tmpPoint); this.lastx = this.tmpPoint.getX(); this.lasty = this.tmpPoint.getY(); return getGeomFactory().newCurvePathElement( ppx, ppy, ctrlX1, ctrlY1, ctrlX2, ctrlY2, this.lastx, this.lasty); } return getGeomFactory().newClosePathElement( this.lastx, this.lasty, this.movex, this.movey); } } }