/*
* $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.Collection;
import java.util.Iterator;
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.Unefficient;
import org.arakhne.afc.math.geometry.CrossingComputationType;
import org.arakhne.afc.math.geometry.PathElementType;
import org.arakhne.afc.math.geometry.PathWindingRule;
import org.arakhne.afc.math.geometry.d2.Path2D;
import org.arakhne.afc.math.geometry.d2.PathIterator2D;
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;
import org.arakhne.afc.vmutil.locale.Locale;
/** Fonctional interface that represented a 2D path 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
*/
@SuppressWarnings("checkstyle:methodcount")
public interface Path2afp<
ST extends Shape2afp<?, ?, IE, P, V, B>, IT extends Path2afp<?, ?, 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 Shape2afp<ST, IT, IE, P, V, B>, Path2D<ST, IT, PathIterator2afp<IE>, P, V, B> {
/** Multiple of cubic & quad curve size.
*/
int GROW_SIZE = 24;
/** The default flattening depth limit.
*/
int DEFAULT_FLATTENING_LIMIT = 10;
/** The default winding rule: {@link PathWindingRule#NON_ZERO}.
*/
PathWindingRule DEFAULT_WINDING_RULE = PathWindingRule.NON_ZERO;
/**
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorPathShadow(int,
* PathIterator2afp, BasicPathShadow2afp, CrossingComputationType)}
*/
@SuppressWarnings("all")
@Deprecated
static int computeCrossingsFromPath(
int crossings,
PathIterator2afp<?> iterator,
BasicPathShadow2afp shadow,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorPathShadow(crossings, iterator, shadow, type);
}
/**
* Accumulate the number of times the path crosses the shadow extending to the right of the second path. See the comment
* for the SHAPE_INTERSECTS constant for more complete details. The return value is the sum of all crossings for both the
* top and bottom of the shadow for every segment in the path, or the special value SHAPE_INTERSECTS if the path ever enters
* the interior of the rectangle. The path must start with a SEG_MOVETO, otherwise an exception is
* thrown. The caller must check r[xy]{min, max} for NaN values.
* @param crossings is the initial value for crossing.
* @param iterator is the iterator on the path elements.
* @param shadow is the description of the shape to project to the right.
* @param type is the type of special computation to apply. If <code>null</code>, it
* is equivalent to {@link CrossingComputationType#STANDARD}.
* @return the crossings.
* @see "Weiler–Atherton clipping algorithm"
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static int calculatesCrossingsPathIteratorPathShadow(int crossings, PathIterator2afp<?> iterator,
BasicPathShadow2afp shadow, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert shadow != null : AssertMessages.notNullParameter(2);
if (!iterator.hasNext()) {
return 0;
}
PathElement2afp pathElement1 = iterator.next();
if (pathElement1.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2afp<?, ?, ?, ?, ?, ?> subPath;
double curx = pathElement1.getToX();
double movx = curx;
double cury = pathElement1.getToY();
double movy = cury;
int numCrossings = crossings;
double endx;
double endy;
while (numCrossings != MathConstants.SHAPE_INTERSECTS
&& iterator.hasNext()) {
pathElement1 = iterator.next();
switch (pathElement1.getType()) {
case MOVE_TO:
movx = pathElement1.getToX();
curx = movx;
movy = pathElement1.getToY();
cury = movy;
break;
case LINE_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
numCrossings = shadow.computeCrossings(numCrossings, curx, cury, endx, endy);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.quadTo(
pathElement1.getCtrlX1(), pathElement1.getCtrlY1(), endx, endy);
numCrossings = calculatesCrossingsPathIteratorPathShadow(
numCrossings, subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
shadow, CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.curveTo(pathElement1.getCtrlX1(), pathElement1.getCtrlY1(),
pathElement1.getCtrlX2(), pathElement1.getCtrlY2(), endx, endy);
numCrossings = calculatesCrossingsPathIteratorPathShadow(
numCrossings, subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
shadow, CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.arcTo(endx, endy, pathElement1.getRadiusX(), pathElement1.getRadiusY(),
pathElement1.getRotationX(), pathElement1.getLargeArcFlag(),
pathElement1.getSweepFlag());
numCrossings = calculatesCrossingsPathIteratorPathShadow(
numCrossings, subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
shadow, CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
numCrossings = shadow.computeCrossings(numCrossings, curx, cury, movx, movy);
}
// Stop as soon as possible
if (numCrossings != 0) {
return numCrossings;
}
curx = movx;
cury = movy;
break;
default:
}
}
assert numCrossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
numCrossings = shadow.computeCrossings(numCrossings, curx, cury, movx, movy);
break;
case SIMPLE_INTERSECTION_WHEN_NOT_POLYGON:
numCrossings = 0;
break;
case STANDARD:
default:
break;
}
}
return numCrossings;
}
/** Replies the point on the path that is closest to the given point.
*
* <p><strong>CAUTION:</strong> This function works only on path iterators
* that are replying not-curved primitives, ie. if the {@link PathIterator2D#isCurved()} of {@code pi} is replying
* <code>false</code>. {@link #getClosestPointTo(Point2D)} avoids this restriction.
* @param pi is the iterator on the elements of the path.
* @param x x coordinate of the point.
* @param y y coordinate of the point.
* @param result the closest point on the shape; or the point itself if it is inside the shape.
*/
static void findsClosestPointPathIteratorPoint(PathIterator2afp<? extends PathElement2afp> pi, double x,
double y, Point2D<?, ?> result) {
assert pi != null : AssertMessages.notNullParameter(0);
assert !pi.isCurved() : AssertMessages.invalidTrueValue(0, "isCurved"); //$NON-NLS-1$
assert result != null : AssertMessages.notNullParameter(3);
double bestDist = Double.POSITIVE_INFINITY;
PathElement2afp pe;
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1;
int crossings = 0;
while (pi.hasNext()) {
pe = pi.next();
boolean foundCandidate = false;
double candidateX = Double.NaN;
double candidateY = Double.NaN;
switch (pe.getType()) {
case MOVE_TO:
crossings = 0;
foundCandidate = true;
candidateX = pe.getToX();
candidateY = pe.getToY();
break;
case LINE_TO:
double factor = Segment2afp.findsProjectedPointPointLine(x, y,
pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY());
factor = MathUtil.clamp(factor, 0, 1);
double vx = (pe.getToX() - pe.getFromX()) * factor;
double vy = (pe.getToY() - pe.getFromY()) * factor;
foundCandidate = true;
candidateX = pe.getFromX() + vx;
candidateY = pe.getFromY() + vy;
crossings += Segment2afp.calculatesCrossingsPointShadowSegment(x, y,
pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY());
break;
case CLOSE:
crossings += Segment2afp.calculatesCrossingsPointShadowSegment(x, y,
pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY());
if ((crossings & mask) != 0) {
result.set(x, y);
return;
}
if (!pe.isEmpty()) {
factor = Segment2afp.findsProjectedPointPointLine(x, y,
pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY());
factor = MathUtil.clamp(factor, 0, 1);
vx = (pe.getToX() - pe.getFromX()) * factor;
vy = (pe.getToY() - pe.getFromY()) * factor;
foundCandidate = true;
candidateX = pe.getFromX() + vx;
candidateY = pe.getFromY() + vy;
}
crossings = 0;
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
default:
throw new IllegalStateException(pe.getType().toString());
}
if (foundCandidate) {
final double d = Point2D.getDistanceSquaredPointPoint(x, y, candidateX, candidateY);
if (d < bestDist) {
bestDist = d;
result.set(candidateX, candidateY);
}
}
}
}
/** Replies the point on the path of pi that is closest to the given shape.
*
* <p><strong>CAUTION:</strong> This function works only on path iterators
* that are replying not-curved primitives, ie. if the {@link PathIterator2D#isCurved()} of {@code pi} is replying
* <code>false</code>. {@link #getClosestPointTo(org.arakhne.afc.math.geometry.d2.Shape2D)} avoids this restriction.
*
* @param pi is the iterator of path elements, on one of which the closest point is located.
* @param shape the shape to which the closest point must be computed.
* @param result the closest point on pi.
* @return <code>true</code> if a point was found. Otherwise <code>false</code>.
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
@Unefficient
static boolean findsClosestPointPathIteratorPathIterator(PathIterator2afp<? extends PathElement2afp> pi,
PathIterator2afp<? extends PathElement2afp> shape, Point2D<?, ?> result) {
assert pi != null : AssertMessages.notNullParameter(0);
assert shape != null : AssertMessages.notNullParameter(1);
assert !pi.isCurved() : AssertMessages.invalidTrueValue(0, "isCurved"); //$NON-NLS-1$
assert result != null : AssertMessages.notNullParameter(2);
if (!pi.hasNext() || !shape.hasNext()) {
return false;
}
PathElement2afp pathElement1 = pi.next();
if (pathElement1.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
if (shape.next().getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
if (!pi.hasNext() || !shape.hasNext()) {
return false;
}
final Rectangle2afp<?, ?, ?, ?, ?, ?> box = pi.getGeomFactory().newBox();
calculatesDrawableElementBoundingBox(shape.restartIterations(), box);
final ClosestPointPathShadow2afp shadow = new ClosestPointPathShadow2afp(shape.restartIterations(), box);
int crossings = 0;
double curx = pathElement1.getToX();
double movx = curx;
double cury = pathElement1.getToY();
double movy = cury;
double endx;
double endy;
while (pi.hasNext()) {
pathElement1 = pi.next();
switch (pathElement1.getType()) {
case MOVE_TO:
movx = pathElement1.getToX();
curx = movx;
movy = pathElement1.getToY();
cury = movy;
break;
case LINE_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
crossings = shadow.computeCrossings(crossings, curx, cury, endx, endy);
if (crossings == MathConstants.SHAPE_INTERSECTS) {
result.set(shadow.getClosestPointInOtherShape());
return true;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
crossings = shadow.computeCrossings(crossings, curx, cury, movx, movy);
if (crossings == MathConstants.SHAPE_INTERSECTS) {
result.set(shadow.getClosestPointInOtherShape());
return true;
}
}
curx = movx;
cury = movy;
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
default:
throw new IllegalArgumentException();
}
}
if (curx == movx && cury == movy) {
assert crossings != MathConstants.SHAPE_INTERSECTS;
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
if ((crossings & mask) != 0) {
// Second path is inside the first shape
result.set(shadow.getClosestPointInShadowShape());
return true;
}
}
result.set(shadow.getClosestPointInOtherShape());
return true;
}
/**
* @deprecated since 13.0, see {@link #findsClosestPointPathIteratorPoint(PathIterator2afp, double, double, Point2D)}
*/
@SuppressWarnings("all")
@Deprecated
static void getClosestPointTo(PathIterator2afp<? extends PathElement2afp> pi, double x, double y, Point2D<?, ?> result) {
findsClosestPointPathIteratorPoint(pi, x, y, result);
}
/**
* @deprecated since 13.0, see {@link #findsClosestPointPathIteratorPathIterator(PathIterator2afp,
* PathIterator2afp, Point2D)}
*/
@SuppressWarnings("all")
@Deprecated
static boolean getClosestPointTo(PathIterator2afp<? extends PathElement2afp> pi,
PathIterator2afp<? extends PathElement2afp> shape, Point2D<?, ?> result) {
return findsClosestPointPathIteratorPathIterator(pi, shape, result);
}
@Pure
@Override
default P getClosestPointTo(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
Path2afp.findsClosestPointPathIteratorPoint(
getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
pt.getX(), pt.getY(),
point);
return point;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Circle2afp<?, ?, ?, ?, ?, ?> circle) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPoint(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
circle.getCenterX(), circle.getCenterY(), result);
} else {
Path2afp.findsClosestPointPathIteratorPoint(getPathIterator(), circle.getCenterX(), circle.getCenterY(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Ellipse2afp<?, ?, ?, ?, ?, ?> ellipse) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
ellipse.getPathIterator(), result);
} else {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(), ellipse.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rectangle.getPathIterator(), result);
} else {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(), rectangle.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Segment2afp<?, ?, ?, ?, ?, ?> segment) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
segment.getPathIterator(), result);
} else {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(), segment.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Triangle2afp<?, ?, ?, ?, ?, ?> triangle) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
triangle.getPathIterator(), result);
} else {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(), triangle.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Path2afp<?, ?, ?, ?, ?, ?> path) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
path.getPathIterator(), result);
} else {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(), path.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(OrientedRectangle2afp<?, ?, ?, ?, ?, ?> orientedRectangle) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
orientedRectangle.getPathIterator(), result);
} else {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(), orientedRectangle.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Parallelogram2afp<?, ?, ?, ?, ?, ?> parallelogram) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
parallelogram.getPathIterator(), result);
} else {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(), parallelogram.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(RoundRectangle2afp<?, ?, ?, ?, ?, ?> roundRectangle) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
roundRectangle.getPathIterator(), result);
} else {
Path2afp.findsClosestPointPathIteratorPathIterator(getPathIterator(), roundRectangle.getPathIterator(), result);
}
return result;
}
/**
* @deprecated since 13.0, see {@link #findsFarthestPointPathIteratorPoint(PathIterator2afp, double, double, Point2D)}
*/
@SuppressWarnings("all")
@Deprecated
static void getFarthestPointTo(PathIterator2afp<? extends PathElement2afp> pi, double x, double y, Point2D<?, ?> result) {
findsFarthestPointPathIteratorPoint(pi, x, y, result);
}
@Pure
@Override
default P getFarthestPointTo(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
Path2afp.findsFarthestPointPathIteratorPoint(
getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
pt.getX(), pt.getY(),
point);
return point;
}
/** Replies the point on the path that is farthest to the given point.
*
* <p><strong>CAUTION:</strong> This function works only on path iterators
* that are replying not-curved primitives, ie. if the {@link PathIterator2D#isCurved()} of {@code pi} is replying
* <code>false</code>. {@link #getFarthestPointTo(Point2D)} avoids this restriction.
* @param pi is the iterator on the elements of the path.
* @param x x coordinate of the point.
* @param y y coordinate of the point.
* @param result the farthest point on the shape.
*/
static void findsFarthestPointPathIteratorPoint(PathIterator2afp<? extends PathElement2afp> pi, double x,
double y, Point2D<?, ?> result) {
assert pi != null : AssertMessages.notNullParameter(0);
assert !pi.isCurved() : AssertMessages.invalidTrueValue(0, "isCurved"); //$NON-NLS-1$
assert result != null : AssertMessages.notNullParameter(3);
double bestDist = Double.NEGATIVE_INFINITY;
PathElement2afp pe;
final Point2D<?, ?> point = new InnerComputationPoint2afp();
while (pi.hasNext()) {
pe = pi.next();
switch (pe.getType()) {
case MOVE_TO:
break;
case LINE_TO:
case CLOSE:
Segment2afp.findsFarthestPointSegmentPoint(
pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY(),
x, y, point);
final double d = Point2D.getDistanceSquaredPointPoint(x, y, point.getX(), point.getY());
if (d > bestDist) {
bestDist = d;
result.set(point.getX(), point.getY());
}
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
default:
throw new IllegalStateException(pe.getType().toString());
}
}
}
/** Tests if the specified coordinates are inside the closed boundary of the specified {@link PathIterator2afp}.
*
* <p>This method provides a basic facility for implementors of the {@link Shape2afp} interface to implement support for the
* {@link Shape2afp#contains(double, double)} method.
*
* @param pi the specified {@code PathIterator2f}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @return {@code true} if the specified coordinates are inside the specified {@code PathIterator2f}; {@code false} otherwise
*/
static boolean containsPoint(PathIterator2afp<? extends PathElement2afp> pi, double x, double y) {
assert pi != null : AssertMessages.notNullParameter(0);
// Copied from the AWT API
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1;
final int cross = calculatesCrossingsPathIteratorPointShadow(0, pi, x, y,
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return (cross & mask) != 0;
}
/** Tests if the specified rectangle is inside the closed boundary of the specified {@link PathIterator2afp}.
*
* <p>This method provides a basic facility for implementors of the {@link Shape2afp} interface to implement support for the
* {@link Shape2afp#contains(Rectangle2afp)} method.
* @param pi the specified {@code PathIterator2f}
* @param rx the lowest corner of the rectangle.
* @param ry the lowest corner of the rectangle.
* @param rwidth is the width of the rectangle.
* @param rheight is the width of the rectangle.
* @return {@code true} if the specified rectangle is inside the specified {@code PathIterator2f}; {@code false} otherwise.
*/
@SuppressWarnings("checkstyle:magicnumber")
static boolean containsRectangle(PathIterator2afp<? extends PathElement2afp> pi, double rx, double ry,
double rwidth, double rheight) {
assert pi != null : AssertMessages.notNullParameter(0);
assert rwidth >= 0. : AssertMessages.positiveOrZeroParameter(3);
assert rheight >= 0. : AssertMessages.notNullParameter(4);
// Copied from AWT API
if (rwidth <= 0 || rheight <= 0) {
return false;
}
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorRectangleShadow(
0, pi, rx, ry, rx + rwidth, ry + rheight,
CrossingComputationType.AUTO_CLOSE);
return crossings != MathConstants.SHAPE_INTERSECTS
&& (crossings & mask) != 0;
}
/** Tests if the interior of the specified {@link PathIterator2afp} intersects the interior of a specified set of rectangular
* coordinates.
* @param pi the specified {@link PathIterator2afp}.
* @param x the specified X coordinate of the rectangle.
* @param y the specified Y coordinate of the rectangle.
* @param width the width of the specified rectangular coordinates.
* @param height the height of the specified rectangular coordinates.
* @return <code>true</code> if the specified {@link PathIterator2afp} and the interior of the specified set of rectangular
* coordinates intersect each other; <code>false</code> otherwise.
*/
@SuppressWarnings("checkstyle:magicnumber")
static boolean intersectsPathIteratorRectangle(PathIterator2afp<? extends PathElement2afp> pi, double x, double y,
double width, double height) {
assert pi != null : AssertMessages.notNullParameter(0);
assert width >= 0. : AssertMessages.notNullParameter(3);
assert height >= 0. : AssertMessages.notNullParameter(4);
if (width <= 0 || height <= 0) {
return false;
}
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorRectangleShadow(0, pi, x, y, x + width, y + height,
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS
|| (crossings & mask) != 0;
}
/**
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorPointShadow(int,
* PathIterator2afp, double, double, CrossingComputationType)}
*/
@Deprecated
@SuppressWarnings("all")
static int computeCrossingsFromPoint(
int crossings,
PathIterator2afp<? extends PathElement2afp> iterator,
double px, double py,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorPointShadow(crossings, iterator, px, py, type);
}
/** Calculates the number of times the given path crosses the ray extending to the right from (px, py).
* If the point lies on a part of the path, then no crossings are counted for that intersection.
* +1 is added for each crossing where the Y coordinate is increasing -1 is added for each crossing where the Y
* coordinate is decreasing. The return value is the sum of all crossings for every segment in the path.
* The path must start with a MOVE_TO, otherwise an exception is thrown.
* @param crossings is the initial value for crossing.
* @param iterator is the description of the path.
* @param px is the reference point to test.
* @param py is the reference point to test.
* @param type is the type of special computation to apply. If <code>null</code>, it
* is equivalent to {@link CrossingComputationType#STANDARD}.
* @return the crossing
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:returncount", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity"})
static int calculatesCrossingsPathIteratorPointShadow(int crossings, PathIterator2afp<? extends PathElement2afp> iterator,
double px, double py, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
// Copied from the AWT API
if (!iterator.hasNext()) {
return 0;
}
PathElement2afp element;
element = iterator.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2afp<?, ?, ?, ?, ?, ?> subPath;
double movx = element.getToX();
double movy = element.getToY();
double curx = movx;
double cury = movy;
double endx;
double endy;
int numCrossings = crossings;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
break;
case LINE_TO:
endx = element.getToX();
endy = element.getToY();
if (endx == px && endy == py) {
return MathConstants.SHAPE_INTERSECTS;
}
numCrossings += Segment2afp.calculatesCrossingsPointShadowSegment(
px, py,
curx, cury,
endx, endy);
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
if (endx == px && endy == py) {
return MathConstants.SHAPE_INTERSECTS;
}
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.quadTo(
element.getCtrlX1(), element.getCtrlY1(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorPointShadow(
numCrossings,
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
px, py,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = element.getToX();
endy = element.getToY();
if (endx == px && endy == py) {
return MathConstants.SHAPE_INTERSECTS;
}
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.curveTo(
element.getCtrlX1(), element.getCtrlY1(),
element.getCtrlX2(), element.getCtrlY2(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorPointShadow(
numCrossings,
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
px, py,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = element.getToX();
endy = element.getToY();
if (endx == px && endy == py) {
return MathConstants.SHAPE_INTERSECTS;
}
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.arcTo(
endx, endy,
element.getRadiusX(), element.getRadiusY(),
element.getRotationX(), element.getLargeArcFlag(),
element.getSweepFlag());
numCrossings = calculatesCrossingsPathIteratorPointShadow(
numCrossings,
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
px, py,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (cury != movy || curx != movx) {
if (movx == px && movy == py) {
return MathConstants.SHAPE_INTERSECTS;
}
numCrossings += Segment2afp.calculatesCrossingsPointShadowSegment(
px, py,
curx, cury,
movx, movy);
}
curx = movx;
cury = movy;
break;
default:
}
}
assert numCrossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
if (movx == px && movy == py) {
return MathConstants.SHAPE_INTERSECTS;
}
numCrossings += Segment2afp.calculatesCrossingsPointShadowSegment(
px, py,
curx, cury,
movx, movy);
break;
case SIMPLE_INTERSECTION_WHEN_NOT_POLYGON:
numCrossings = 0;
break;
case STANDARD:
default:
break;
}
}
return numCrossings;
}
/**
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorEllipseShadow(int,
* PathIterator2afp, double, double, double, double, CrossingComputationType)}
*/
@Deprecated
@SuppressWarnings("all")
static int computeCrossingsFromEllipse(
int crossings,
PathIterator2afp<? extends PathElement2afp> iterator,
double ex, double ey, double ew, double eh,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorEllipseShadow(crossings, iterator, ex, ey, ew, eh, type);
}
/** Calculates the number of times the given path crosses the given ellipse extending to the right.
* @param crossings is the initial value for crossing.
* @param iterator is the description of the path.
* @param ex is the first point of the ellipse.
* @param ey is the first point of the ellipse.
* @param ew is the width of the ellipse.
* @param eh is the height of the ellipse.
* @param type is the type of special computation to apply. If <code>null</code>, it
* is equivalent to {@link CrossingComputationType#STANDARD}.
* @return the crossing or {@link MathConstants#SHAPE_INTERSECTS}
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity", "checkstyle:magicnumber"})
static int calculatesCrossingsPathIteratorEllipseShadow(int crossings, PathIterator2afp<? extends PathElement2afp> iterator,
double ex, double ey, double ew, double eh, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert ew >= 0. : AssertMessages.notNullParameter(4);
assert eh >= 0. : AssertMessages.notNullParameter(5);
// Copied from the AWT API
if (!iterator.hasNext()) {
return 0;
}
PathElement2afp element;
element = iterator.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2afp<?, ?, ?, ?, ?, ?> localPath;
double movx = element.getToX();
double movy = element.getToY();
double curx = movx;
double cury = movy;
double endx;
double endy;
int numCrosses = crossings;
while (numCrosses != MathConstants.SHAPE_INTERSECTS && iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
break;
case LINE_TO:
endx = element.getToX();
endy = element.getToY();
numCrosses = Segment2afp.calculatesCrossingsEllipseShadowSegment(
numCrosses,
ex, ey, ew, eh,
curx, cury,
endx, endy);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.quadTo(
element.getCtrlX1(), element.getCtrlY1(),
endx, endy);
numCrosses = calculatesCrossingsPathIteratorEllipseShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
ex, ey, ew, eh,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = element.getToX();
endy = element.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.curveTo(
element.getCtrlX1(), element.getCtrlY1(),
element.getCtrlX2(), element.getCtrlY2(),
endx, endy);
numCrosses = calculatesCrossingsPathIteratorEllipseShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
ex, ey, ew, eh,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = element.getToX();
endy = element.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.arcTo(
endx, endy,
element.getRadiusX(), element.getRadiusY(),
element.getRotationX(), element.getLargeArcFlag(),
element.getSweepFlag());
numCrosses = calculatesCrossingsPathIteratorEllipseShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
ex, ey, ew, eh,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (cury != movy || curx != movx) {
numCrosses = Segment2afp.calculatesCrossingsEllipseShadowSegment(
numCrosses,
ex, ey, ew, eh,
curx, cury,
movx, movy);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
}
curx = movx;
cury = movy;
break;
default:
}
}
assert numCrosses != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
numCrosses = Segment2afp.calculatesCrossingsEllipseShadowSegment(
numCrosses,
ex, ey, ew, eh,
curx, cury,
movx, movy);
break;
case SIMPLE_INTERSECTION_WHEN_NOT_POLYGON:
// Assume that when is the path is open, only. SHAPE_INTERSECTS may be return.
numCrosses = 0;
break;
case STANDARD:
default:
break;
}
}
return numCrosses;
}
/**
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorRoundRectangleShadow(int,
* PathIterator2afp, double, double, double, double, double, double, CrossingComputationType)}
*/
@Deprecated
@SuppressWarnings("all")
static int computeCrossingsFromRoundRect(
int crossings,
PathIterator2afp<? extends PathElement2afp> iterator,
double x1, double y1, double x2, double y2,
double arcWidth, double arcHeight,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorRoundRectangleShadow(crossings, iterator, x1, y1, x2, y2,
arcWidth, arcHeight, type);
}
/** Calculates the number of times the given path crosses the given round rectangle extending to the right.
* @param crossings is the initial value for crossing.
* @param iterator is the description of the path.
* @param x1 is the first corner of the rectangle.
* @param y1 is the first corner of the rectangle.
* @param x2 is the second corner of the rectangle.
* @param y2 is the second corner of the rectangle.
* @param arcWidth is the width of the arc.
* @param arcHeight is the width of the arc.
* @param type is the type of special computation to apply. If <code>null</code>, it
* is equivalent to {@link CrossingComputationType#STANDARD}.
* @return the crossing or {@link MathConstants#SHAPE_INTERSECTS}
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity", "checkstyle:magicnumber"})
static int calculatesCrossingsPathIteratorRoundRectangleShadow(int crossings,
PathIterator2afp<? extends PathElement2afp> iterator, double x1, double y1, double x2, double y2,
double arcWidth, double arcHeight, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert x1 <= x2 : AssertMessages.lowerEqualParameters(2, x1, 4, x2);
assert y1 <= y2 : AssertMessages.lowerEqualParameters(3, y1, 5, y2);
if (!iterator.hasNext()) {
return 0;
}
PathElement2afp pathElement = iterator.next();
if (pathElement.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
double curx = pathElement.getToX();
double movx = curx;
double cury = pathElement.getToY();
double movy = cury;
int numCrossings = crossings;
Path2afp<?, ?, ?, ?, ?, ?> localPath;
double endx;
double endy;
while (numCrossings != MathConstants.SHAPE_INTERSECTS
&& iterator.hasNext()) {
pathElement = iterator.next();
switch (pathElement.getType()) {
case MOVE_TO:
// Count should always be a multiple of 2 here.
// assert (crossings & 1 != 0);
movx = pathElement.getToX();
curx = movx;
movy = pathElement.getToY();
cury = movy;
break;
case LINE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
numCrossings = Segment2afp.calculatesCrossingsRoundRectangleShadowSegment(numCrossings,
x1, y1, x2, y2, arcWidth, arcHeight,
curx, cury,
endx, endy);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.quadTo(
pathElement.getCtrlX1(), pathElement.getCtrlY1(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorRoundRectangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2, arcWidth, arcHeight,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.curveTo(
pathElement.getCtrlX1(), pathElement.getCtrlY1(),
pathElement.getCtrlX2(), pathElement.getCtrlY2(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorRoundRectangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2, arcWidth, arcHeight,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.arcTo(
endx, endy,
pathElement.getRadiusX(), pathElement.getRadiusY(),
pathElement.getRotationX(), pathElement.getLargeArcFlag(),
pathElement.getSweepFlag());
numCrossings = calculatesCrossingsPathIteratorRoundRectangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2, arcWidth, arcHeight,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
numCrossings = Segment2afp.calculatesCrossingsRoundRectangleShadowSegment(numCrossings,
x1, y1, x2, y2, arcWidth, arcHeight,
curx, cury,
movx, movy);
}
// Stop as soon as possible
if (numCrossings != 0) {
return numCrossings;
}
curx = movx;
cury = movy;
break;
default:
}
}
assert numCrossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
// Not closed
numCrossings = Segment2afp.calculatesCrossingsRoundRectangleShadowSegment(numCrossings,
x1, y1, x2, y2, arcWidth, arcHeight,
curx, cury,
movx, movy);
break;
case SIMPLE_INTERSECTION_WHEN_NOT_POLYGON:
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
numCrossings = 0;
break;
case STANDARD:
default:
break;
}
}
return numCrossings;
}
/**
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorCircleShadow(int,
* PathIterator2afp, double, double, double, CrossingComputationType)}
*/
@SuppressWarnings("all")
@Deprecated
static int computeCrossingsFromCircle(
int crossings,
PathIterator2afp<? extends PathElement2afp> iterator,
double cx, double cy, double radius,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorCircleShadow(crossings, iterator, cx, cy, radius, type);
}
/** Calculates the number of times the given path crosses the given circle extending to the right.
* @param crossings is the initial value for crossing.
* @param iterator is the description of the path.
* @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 type is the type of special computation to apply. If <code>null</code>, it
* is equivalent to {@link CrossingComputationType#STANDARD}.
* @return the crossing
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity", "checkstyle:magicnumber"})
static int calculatesCrossingsPathIteratorCircleShadow(int crossings, PathIterator2afp<? extends PathElement2afp> iterator,
double cx, double cy, double radius, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert radius >= 0. : AssertMessages.positiveOrZeroParameter(4);
// Copied from the AWT API
if (!iterator.hasNext()) {
return 0;
}
PathElement2afp element;
element = iterator.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2afp<?, ?, ?, ?, ?, ?> localPath;
double movx = element.getToX();
double movy = element.getToY();
double curx = movx;
double cury = movy;
double endx;
double endy;
int numCrosses = crossings;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
break;
case LINE_TO:
endx = element.getToX();
endy = element.getToY();
numCrosses = Segment2afp.calculatesCrossingsCircleShadowSegment(
numCrosses,
cx, cy, radius,
curx, cury,
endx, endy);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(element.getFromX(), element.getFromY());
localPath.quadTo(
element.getCtrlX1(), element.getCtrlY1(),
endx, endy);
numCrosses = calculatesCrossingsPathIteratorCircleShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
cx, cy, radius,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = element.getToX();
endy = element.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(element.getFromX(), element.getFromY());
localPath.curveTo(
element.getCtrlX1(), element.getCtrlY1(),
element.getCtrlX2(), element.getCtrlY2(),
endx, endy);
numCrosses = calculatesCrossingsPathIteratorCircleShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
cx, cy, radius,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = element.getToX();
endy = element.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(element.getFromX(), element.getFromY());
localPath.arcTo(
endx, endy,
element.getRadiusX(), element.getRadiusY(),
element.getRotationX(), element.getLargeArcFlag(),
element.getSweepFlag());
numCrosses = calculatesCrossingsPathIteratorCircleShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
cx, cy, radius,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (cury != movy || curx != movx) {
numCrosses = Segment2afp.calculatesCrossingsCircleShadowSegment(
numCrosses,
cx, cy, radius,
curx, cury,
movx, movy);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
}
curx = movx;
cury = movy;
break;
default:
}
}
assert numCrosses != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
// Auto close
numCrosses = Segment2afp.calculatesCrossingsCircleShadowSegment(
numCrosses,
cx, cy, radius,
curx, cury,
movx, movy);
break;
case SIMPLE_INTERSECTION_WHEN_NOT_POLYGON:
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
numCrosses = 0;
break;
case STANDARD:
default:
// Standard behavior
break;
}
}
return numCrosses;
}
/**
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorSegmentShadow(int,
* PathIterator2afp, double, double, double, double, CrossingComputationType)}
*/
@SuppressWarnings("all")
@Deprecated
static int computeCrossingsFromSegment(int crossings, PathIterator2afp<? extends PathElement2afp> iterator,
double x1, double y1, double x2, double y2, CrossingComputationType type) {
return calculatesCrossingsPathIteratorSegmentShadow(crossings, iterator, x1, y1, x2, y2, type);
}
/** Calculates the number of times the given path crosses the given segment extending to the right.
* @param crossings is the initial value for crossing.
* @param iterator is the description of the path.
* @param x1 is the first point of the segment.
* @param y1 is the first point of the segment.
* @param x2 is the first point of the segment.
* @param y2 is the first point of the segment.
* @param type is the type of special computation to apply. If <code>null</code>, it
* is equivalent to {@link CrossingComputationType#STANDARD}.
* @return the crossing
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static int calculatesCrossingsPathIteratorSegmentShadow(int crossings, PathIterator2afp<? extends PathElement2afp> iterator,
double x1, double y1, double x2, double y2, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
// Copied from the AWT API
if (!iterator.hasNext() || crossings == MathConstants.SHAPE_INTERSECTS) {
return crossings;
}
PathElement2afp element;
element = iterator.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2afp<?, ?, ?, ?, ?, ?> localPath;
double movx = element.getToX();
double movy = element.getToY();
double curx = movx;
double cury = movy;
double endx;
double endy;
int numCrosses = crossings;
while (numCrosses != MathConstants.SHAPE_INTERSECTS && iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
break;
case LINE_TO:
endx = element.getToX();
endy = element.getToY();
numCrosses = Segment2afp.calculatesCrossingsSegmentShadowSegment(
numCrosses,
x1, y1, x2, y2,
curx, cury,
endx, endy);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.quadTo(
element.getCtrlX1(), element.getCtrlY1(),
endx, endy);
numCrosses = calculatesCrossingsPathIteratorSegmentShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = element.getToX();
endy = element.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.curveTo(
element.getCtrlX1(), element.getCtrlY1(),
element.getCtrlX2(), element.getCtrlY2(),
endx, endy);
numCrosses = calculatesCrossingsPathIteratorSegmentShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = element.getToX();
endy = element.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.arcTo(
endx, endy,
element.getRadiusX(), element.getRadiusY(),
element.getRotationX(), element.getLargeArcFlag(),
element.getSweepFlag());
numCrosses = calculatesCrossingsPathIteratorSegmentShadow(
numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (cury != movy || curx != movx) {
numCrosses = Segment2afp.calculatesCrossingsSegmentShadowSegment(
numCrosses,
x1, y1, x2, y2,
curx, cury,
movx, movy);
}
if (numCrosses != 0) {
return numCrosses;
}
curx = movx;
cury = movy;
break;
default:
}
}
assert numCrosses != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
numCrosses = Segment2afp.calculatesCrossingsSegmentShadowSegment(
numCrosses,
x1, y1, x2, y2,
curx, cury,
movx, movy);
break;
case SIMPLE_INTERSECTION_WHEN_NOT_POLYGON:
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
numCrosses = 0;
break;
case STANDARD:
default:
}
}
return numCrosses;
}
/**
* @deprecated since 13..0, see {@link #calculatesCrossingsPathIteratorRectangleShadow(int,
* PathIterator2afp, double, double, double, double, CrossingComputationType)}
*/
@SuppressWarnings("all")
@Deprecated
static int computeCrossingsFromRect(
int crossings,
PathIterator2afp<? extends PathElement2afp> iterator,
double rxmin, double rymin,
double rxmax, double rymax,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorRectangleShadow(crossings, iterator, rxmin, rymin, rxmax, rymax, type);
}
/** Accumulate the number of times the path crosses the shadow extending to the right of the rectangle. See the comment
* for the SHAPE_INTERSECTS constant for more complete details. The return value is the sum of all crossings for both the
* top and bottom of the shadow for every segment in the path, or the special value SHAPE_INTERSECTS if the path ever enters
* the interior of the rectangle. The path must start with a SEG_MOVETO, otherwise an exception is thrown.
* The caller must check r[xy]{min, max} for NaN values.
* @param crossings is the initial value for crossing.
* @param iterator is the iterator on the path elements.
* @param rxmin is the first corner of the rectangle.
* @param rymin is the first corner of the rectangle.
* @param rxmax is the second corner of the rectangle.
* @param rymax is the second corner of the rectangle.
* @param type is the type of special computation to apply. If <code>null</code>, it
* is equivalent to {@link CrossingComputationType#STANDARD}.
* @return the crossings.
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity", "checkstyle:magicnumber"})
static int calculatesCrossingsPathIteratorRectangleShadow(int crossings, PathIterator2afp<? extends PathElement2afp> iterator,
double rxmin, double rymin, double rxmax, double rymax, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert rxmin <= rxmax : AssertMessages.lowerEqualParameters(2, rxmin, 4, rxmax);
assert rymin <= rymax : AssertMessages.lowerEqualParameters(3, rymin, 5, rymax);
// Copied from AWT API
if (!iterator.hasNext()) {
return 0;
}
PathElement2afp pathElement = iterator.next();
if (pathElement.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2afp<?, ?, ?, ?, ?, ?> localPath;
double curx = pathElement.getToX();
double movx = curx;
double cury = pathElement.getToY();
double movy = cury;
int numCrossings = crossings;
double endx;
double endy;
while (numCrossings != MathConstants.SHAPE_INTERSECTS
&& iterator.hasNext()) {
pathElement = iterator.next();
switch (pathElement.getType()) {
case MOVE_TO:
// Count should always be a multiple of 2 here.
// assert (crossings & 1 != 0);
movx = pathElement.getToX();
curx = movx;
movy = pathElement.getToY();
cury = movy;
break;
case LINE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
numCrossings = Segment2afp.calculatesCrossingsRectangleShadowSegment(
numCrossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
endx, endy);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.quadTo(
pathElement.getCtrlX1(), pathElement.getCtrlY1(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorRectangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rxmin, rymin,
rxmax, rymax,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.curveTo(
pathElement.getCtrlX1(), pathElement.getCtrlY1(),
pathElement.getCtrlX2(), pathElement.getCtrlY2(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorRectangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rxmin, rymin,
rxmax, rymax,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.arcTo(
endx, endy,
pathElement.getRadiusX(), pathElement.getRadiusY(),
pathElement.getRotationX(), pathElement.getLargeArcFlag(),
pathElement.getSweepFlag());
numCrossings = calculatesCrossingsPathIteratorRectangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rxmin, rymin,
rxmax, rymax,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
numCrossings = Segment2afp.calculatesCrossingsRectangleShadowSegment(
numCrossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
// Stop as soon as possible
if (numCrossings != 0) {
return numCrossings;
}
curx = movx;
cury = movy;
break;
default:
}
}
assert numCrossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
// Not closed
numCrossings = Segment2afp.calculatesCrossingsRectangleShadowSegment(
numCrossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
break;
case SIMPLE_INTERSECTION_WHEN_NOT_POLYGON:
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
numCrossings = 0;
break;
case STANDARD:
default:
break;
}
}
return numCrossings;
}
/**
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorTriangleShadow(int,
* PathIterator2afp, double, double, double, double, double, double, CrossingComputationType)}
*/
@Deprecated
@SuppressWarnings("all")
static int computeCrossingsFromTriangle(
int crossings,
PathIterator2afp<? extends PathElement2afp> iterator,
double x1, double y1,
double x2, double y2,
double x3, double y3,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorTriangleShadow(crossings, iterator, x1, y1, x2, y2, x3, y3, type);
}
/** Accumulate the number of times the path crosses the shadow extending to the right of the triangle. See the comment
* for the SHAPE_INTERSECTS constant for more complete details. The return value is the sum of all crossings for both the
* top and bottom of the shadow for every segment in the path, or the special value SHAPE_INTERSECTS if the path ever enters
* the interior of the triangle. The path must start with a SEG_MOVETO, otherwise an exception is thrown.
* The caller must check for NaN values.
*
* @param crossings is the initial value for crossing.
* @param iterator is the iterator on the path elements.
* @param x1 is the first point of the triangle.
* @param y1 is the first point of the triangle.
* @param x2 is the second point of the triangle.
* @param y2 is the second point of the triangle.
* @param x3 is the third point of the triangle.
* @param y3 is the third point of the triangle.
* @param type is the type of special computation to apply. If <code>null</code>, it
* is equivalent to {@link CrossingComputationType#STANDARD}.
* @return the crossings.
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static int calculatesCrossingsPathIteratorTriangleShadow(int crossings, PathIterator2afp<? extends PathElement2afp> iterator,
double x1, double y1, double x2, double y2, double x3, double y3, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
if (!iterator.hasNext()) {
return 0;
}
PathElement2afp pathElement = iterator.next();
if (pathElement.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2afp<?, ?, ?, ?, ?, ?> localPath;
double curx = pathElement.getToX();
double movx = curx;
double cury = pathElement.getToY();
double movy = cury;
int numCrossings = crossings;
double endx;
double endy;
while (numCrossings != MathConstants.SHAPE_INTERSECTS
&& iterator.hasNext()) {
pathElement = iterator.next();
switch (pathElement.getType()) {
case MOVE_TO:
// Count should always be a multiple of 2 here.
// assert (crossings & 1 != 0);
movx = pathElement.getToX();
curx = movx;
movy = pathElement.getToY();
cury = movy;
break;
case LINE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
numCrossings = Segment2afp.calculatesCrossingsTriangleShadowSegment(numCrossings,
x1, y1, x2, y2, x3, y3,
curx, cury,
endx, endy);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.quadTo(
pathElement.getCtrlX1(), pathElement.getCtrlY1(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorTriangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2, x3, y3,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.curveTo(
pathElement.getCtrlX1(), pathElement.getCtrlY1(),
pathElement.getCtrlX2(), pathElement.getCtrlY2(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorTriangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2, x3, y3,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury);
localPath.arcTo(
endx, endy,
pathElement.getRadiusX(), pathElement.getRadiusY(),
pathElement.getRotationX(), pathElement.getLargeArcFlag(),
pathElement.getSweepFlag());
numCrossings = calculatesCrossingsPathIteratorTriangleShadow(
numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2, x3, y3,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
numCrossings = Segment2afp.calculatesCrossingsTriangleShadowSegment(numCrossings,
x1, y1, x2, y2, x3, y3,
curx, cury,
movx, movy);
}
// Stop as soon as possible
if (numCrossings != 0) {
return numCrossings;
}
curx = movx;
cury = movy;
break;
default:
}
}
assert numCrossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
// Not closed
numCrossings = Segment2afp.calculatesCrossingsTriangleShadowSegment(numCrossings,
x1, y1, x2, y2, x3, y3,
curx, cury,
movx, movy);
break;
case SIMPLE_INTERSECTION_WHEN_NOT_POLYGON:
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
numCrossings = 0;
break;
case STANDARD:
default:
break;
}
}
return numCrossings;
}
/**
* @deprecated since 13.0, see {@link #calculatesDrawableElementBoundingBox(PathIterator2afp, Rectangle2afp)}
*/
@SuppressWarnings("all")
@Deprecated
static boolean computeDrawableElementBoundingBox(PathIterator2afp<?> iterator,
Rectangle2afp<?, ?, ?, ?, ?, ?> box) {
return calculatesDrawableElementBoundingBox(iterator, box);
}
/** Compute the box that corresponds to the drawable elements of the path.
* <p>An element is drawable if it is a line, a curve, or a closing path element. The box fits the drawn lines and the
* drawn curves. The control points of the curves may be outside the output box. For obtaining the bounding box
* of the path's points, use {@link #calculatesControlPointBoundingBox(PathIterator2afp, Rectangle2afp)}.
* @param iterator the iterator on the path elements.
* @param box the box to set.
* @return <code>true</code> if a drawable element was found.
* @see #calculatesControlPointBoundingBox(PathIterator2afp, Rectangle2afp)
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static boolean calculatesDrawableElementBoundingBox(PathIterator2afp<?> iterator, Rectangle2afp<?, ?, ?, ?, ?, ?> box) {
assert iterator != null : AssertMessages.notNullParameter(0);
assert box != null : AssertMessages.notNullParameter(1);
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
boolean foundOneLine = false;
double xmin = Double.POSITIVE_INFINITY;
double ymin = Double.POSITIVE_INFINITY;
double xmax = Double.NEGATIVE_INFINITY;
double ymax = Double.NEGATIVE_INFINITY;
PathElement2afp element;
Path2afp<?, ?, ?, ?, ?, ?> subPath;
Rectangle2afp<?, ?, ?, ?, ?, ?> subBox;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case LINE_TO:
if (element.getFromX() < xmin) {
xmin = element.getFromX();
}
if (element.getFromY() < ymin) {
ymin = element.getFromY();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
foundOneLine = true;
break;
case CURVE_TO:
subPath = factory.newPath(iterator.getWindingRule());
subBox = factory.newBox();
subPath.moveTo(element.getFromX(), element.getFromY());
subPath.curveTo(
element.getCtrlX1(), element.getCtrlY1(),
element.getCtrlX2(), element.getCtrlY2(),
element.getToX(), element.getToY());
if (calculatesDrawableElementBoundingBox(
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
subBox)) {
if (subBox.getMinX() < xmin) {
xmin = subBox.getMinX();
}
if (subBox.getMinY() < ymin) {
ymin = subBox.getMinY();
}
if (subBox.getMaxX() > xmax) {
xmax = subBox.getMaxX();
}
if (subBox.getMaxY() > ymax) {
ymax = subBox.getMaxY();
}
foundOneLine = true;
}
break;
case ARC_TO:
subPath = factory.newPath(iterator.getWindingRule());
subBox = factory.newBox();
subPath.moveTo(element.getFromX(), element.getFromY());
subPath.arcTo(
element.getToX(), element.getToY(),
element.getRadiusX(), element.getRadiusY(),
element.getRotationX(), element.getLargeArcFlag(),
element.getSweepFlag());
if (calculatesDrawableElementBoundingBox(
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
subBox)) {
if (subBox.getMinX() < xmin) {
xmin = subBox.getMinX();
}
if (subBox.getMinY() < ymin) {
ymin = subBox.getMinY();
}
if (subBox.getMaxX() > xmax) {
xmax = subBox.getMaxX();
}
if (subBox.getMaxY() > ymax) {
ymax = subBox.getMaxY();
}
foundOneLine = true;
}
break;
case QUAD_TO:
subPath = factory.newPath(iterator.getWindingRule());
subBox = factory.newBox();
subPath.moveTo(element.getFromX(), element.getFromY());
subPath.quadTo(
element.getCtrlX1(), element.getCtrlY1(),
element.getToX(), element.getToY());
if (calculatesDrawableElementBoundingBox(
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
subBox)) {
if (subBox.getMinX() < xmin) {
xmin = subBox.getMinX();
}
if (subBox.getMinY() < ymin) {
ymin = subBox.getMinY();
}
if (subBox.getMaxX() > xmax) {
xmax = subBox.getMaxX();
}
if (subBox.getMaxY() > ymax) {
ymax = subBox.getMaxY();
}
foundOneLine = true;
}
break;
case MOVE_TO:
case CLOSE:
default:
}
}
if (foundOneLine) {
box.setFromCorners(xmin, ymin, xmax, ymax);
} else {
box.clear();
}
return foundOneLine;
}
/**
* @deprecated since 13.0, see {@link #calculatesControlPointBoundingBox(PathIterator2afp, Rectangle2afp)}
*/
@SuppressWarnings("all")
@Deprecated
static boolean computeControlPointBoundingBox(PathIterator2afp<?> iterator,
Rectangle2afp<?, ?, ?, ?, ?, ?> box) {
return calculatesControlPointBoundingBox(iterator, box);
}
/** Compute the box that corresponds to the control points of the path.
*
* <p>An element is drawable if it is a line, a curve, or a closing path element. The box fits the drawn lines and the
* drawn curves. The control points of the curves may be outside the output box. For obtaining the bounding box
* of the drawn lines and cruves, use {@link #calculatesDrawableElementBoundingBox(PathIterator2afp, Rectangle2afp)}.
* @param iterator the iterator on the path elements.
* @param box the box to set.
* @return <code>true</code> if a control point was found.
* @see #calculatesDrawableElementBoundingBox(PathIterator2afp, Rectangle2afp)
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static boolean calculatesControlPointBoundingBox(PathIterator2afp<?> iterator, Rectangle2afp<?, ?, ?, ?, ?, ?> box) {
assert iterator != null : AssertMessages.notNullParameter(0);
assert box != null : AssertMessages.notNullParameter(1);
boolean foundOneControlPoint = false;
double xmin = Double.POSITIVE_INFINITY;
double ymin = Double.POSITIVE_INFINITY;
double xmax = Double.NEGATIVE_INFINITY;
double ymax = Double.NEGATIVE_INFINITY;
PathElement2afp element;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case LINE_TO:
if (element.getFromX() < xmin) {
xmin = element.getFromX();
}
if (element.getFromY() < ymin) {
ymin = element.getFromY();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
foundOneControlPoint = true;
break;
case CURVE_TO:
if (element.getFromX() < xmin) {
xmin = element.getFromX();
}
if (element.getFromY() < ymin) {
ymin = element.getFromY();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getCtrlX1() < xmin) {
xmin = element.getCtrlX1();
}
if (element.getCtrlY1() < ymin) {
ymin = element.getCtrlY1();
}
if (element.getCtrlX1() > xmax) {
xmax = element.getCtrlX1();
}
if (element.getCtrlY1() > ymax) {
ymax = element.getCtrlY1();
}
if (element.getCtrlX2() < xmin) {
xmin = element.getCtrlX2();
}
if (element.getCtrlY2() < ymin) {
ymin = element.getCtrlY2();
}
if (element.getCtrlX2() > xmax) {
xmax = element.getCtrlX2();
}
if (element.getCtrlY2() > ymax) {
ymax = element.getCtrlY2();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
foundOneControlPoint = true;
break;
case QUAD_TO:
if (element.getFromX() < xmin) {
xmin = element.getFromX();
}
if (element.getFromY() < ymin) {
ymin = element.getFromY();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getCtrlX1() < xmin) {
xmin = element.getCtrlX1();
}
if (element.getCtrlY1() < ymin) {
ymin = element.getCtrlY1();
}
if (element.getCtrlX1() > xmax) {
xmax = element.getCtrlX1();
}
if (element.getCtrlY1() > ymax) {
ymax = element.getCtrlY1();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
foundOneControlPoint = true;
break;
case ARC_TO:
if (element.getFromX() < xmin) {
xmin = element.getFromX();
}
if (element.getFromY() < ymin) {
ymin = element.getFromY();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
foundOneControlPoint = true;
break;
case MOVE_TO:
case CLOSE:
default:
}
}
if (foundOneControlPoint) {
box.setFromCorners(xmin, ymin, xmax, ymax);
} else {
box.clear();
}
return foundOneControlPoint;
}
/**
* @deprecated since 13.0, see {@link #calculatesPathLength(PathIterator2afp)}
*/
@SuppressWarnings("all")
@Deprecated
static double computeLength(PathIterator2afp<?> iterator) {
return calculatesPathLength(iterator);
}
/** Compute the total squared length of the path.
* @param iterator the iterator on the path elements.
* @return the squared length of the path.
*/
static double calculatesPathLength(PathIterator2afp<?> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
PathElement2afp pathElement = iterator.next();
if (pathElement.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
// only for internal use
final GeomFactory2afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2afp<?, ?, ?, ?, ?, ?> subPath;
double curx = pathElement.getToX();
double movx = curx;
double cury = pathElement.getToY();
double movy = cury;
double length = 0;
double endx;
double endy;
while (iterator.hasNext()) {
pathElement = iterator.next();
switch (pathElement.getType()) {
case MOVE_TO:
movx = pathElement.getToX();
curx = movx;
movy = pathElement.getToY();
cury = movy;
break;
case LINE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
length += Point2D.getDistancePointPoint(
curx, cury,
endx, endy);
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.quadTo(
pathElement.getCtrlX1(), pathElement.getCtrlY1(),
endx, endy);
length += calculatesPathLength(subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO));
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.curveTo(
pathElement.getCtrlX1(), pathElement.getCtrlY1(),
pathElement.getCtrlX2(), pathElement.getCtrlY2(),
endx, endy);
length += calculatesPathLength(subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO));
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury);
subPath.arcTo(
endx, endy,
pathElement.getRadiusX(), pathElement.getRadiusY(),
pathElement.getRotationX(), pathElement.getLargeArcFlag(),
pathElement.getSweepFlag());
length += calculatesPathLength(subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO));
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
length += Point2D.getDistancePointPoint(
curx, cury,
movx, movy);
}
curx = movx;
cury = movy;
break;
default:
}
}
return length;
}
@Pure
@Override
default boolean equalsToShape(IT shape) {
if (shape == null) {
return false;
}
if (shape == this) {
return true;
}
return equalsToPathIterator(shape.getPathIterator());
}
/** Add the elements replied by the iterator into this path.
* @param iterator the iterator that provides the elements to add in the path.
*/
default void add(Iterator<? extends PathElement2afp> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
PathElement2afp element;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
moveTo(element.getToX(), element.getToY());
break;
case LINE_TO:
lineTo(element.getToX(), element.getToY());
break;
case QUAD_TO:
quadTo(element.getCtrlX1(), element.getCtrlY1(), element.getToX(), element.getToY());
break;
case CURVE_TO:
curveTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlX2(),
element.getCtrlY2(), element.getToX(), element.getToY());
break;
case ARC_TO:
arcTo(element.getToX(), element.getToY(), element.getRadiusX(), element.getRadiusY(),
element.getRotationX(), element.getLargeArcFlag(),
element.getSweepFlag());
break;
case CLOSE:
closePath();
break;
default:
}
}
}
/** Set the path.
* @param path the path to copy.
*/
default void set(Path2afp<?, ?, ?, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
clear();
add(path.getPathIterator());
}
/** Adds a point to the path by moving to the specified coordinates specified in double precision.
* @param x the specified X coordinate
* @param y the specified Y coordinate
*/
void moveTo(double x, double y);
@Override
default void moveTo(Point2D<?, ?> position) {
assert position != null : AssertMessages.notNullParameter();
moveTo(position.getX(), position.getY());
}
/** Adds a point to the path by drawing a straight line from the current coordinates to the new specified coordinates
* specified in double precision.
* @param x the specified X coordinate
* @param y the specified Y coordinate
*/
void lineTo(double x, double y);
@Override
default void lineTo(Point2D<?, ?> to) {
assert to != null : AssertMessages.notNullParameter();
lineTo(to.getX(), to.getY());
}
/** Adds a curved segment, defined by two new points, to the path by drawing a Quadratic curve that intersects both the
* current coordinates and the specified coordinates {@code (x2, y2)}, using the specified point {@code (x1, y1)} as a
* quadratic parametric control point. All coordinates are specified in double precision.
* @param x1 the X coordinate of the quadratic control point
* @param y1 the Y coordinate of the quadratic control point
* @param x2 the X coordinate of the final end point
* @param y2 the Y coordinate of the final end point
*/
void quadTo(double x1, double y1, double x2, double y2);
@Override
default void quadTo(Point2D<?, ?> ctrl, Point2D<?, ?> to) {
assert ctrl != null : AssertMessages.notNullParameter(0);
assert to != null : AssertMessages.notNullParameter(1);
quadTo(ctrl.getX(), ctrl.getY(), to.getX(), to.getY());
}
/** Adds a curved segment, defined by three new points, to the path by drawing a Bézier curve that intersects both
* the current coordinates and the specified coordinates {@code (x3, y3)}, using the specified points {@code (x1, y1)}
* and {@code (x2, y2)} as Bézier control points. All coordinates are specified in double precision.
* @param x1 the X coordinate of the first Bézier control point
* @param y1 the Y coordinate of the first Bézier control point
* @param x2 the X coordinate of the second Bézier control point
* @param y2 the Y coordinate of the second Bézier control point
* @param x3 the X coordinate of the final end point
* @param y3 the Y coordinate of the final end point
*/
void curveTo(double x1, double y1,
double x2, double y2,
double x3, double y3);
@Override
default void curveTo(Point2D<?, ?> ctrl1, Point2D<?, ?> ctrl2, Point2D<?, ?> to) {
assert ctrl1 != null : AssertMessages.notNullParameter(0);
assert ctrl2 != null : AssertMessages.notNullParameter(1);
assert to != null : AssertMessages.notNullParameter(2);
curveTo(ctrl1.getX(), ctrl1.getY(), ctrl2.getX(), ctrl2.getY(), to.getX(), to.getY());
}
/** Adds a section of an shallow ellipse to the current path. The ellipse from which a quadrant is taken is the ellipse
* that would be inscribed in a parallelogram defined by 3 points. The current point which is considered to be the
* midpoint of the edge leading into the corner of the ellipse where the ellipse grazes it, {@code (ctrlx, ctrly)}
* which is considered to be the location of the corner of the parallelogram in which the ellipse is inscribed, and
* {@code (tox, toy)} which is considered to be the midpoint of the edge leading away from the corner of the oval where
* the oval grazes it.
*
* <p><img alt="" src="../doc-files/arcto0.png">
*
* <p>Only the portion of the ellipse from {@code tfrom} to {@code tto} will be included where {@code 0f} represents
* the point where the ellipse grazes the leading edge, {@code 1f} represents the point where the oval grazes the trailing
* edge, and {@code 0.5f} represents the point on the oval closest to the control point. The two values must satisfy the
* relation {@code (0 <= tfrom <= tto <= 1)}.
*
* <p>If {@code tfrom} is not {@code 0f} then the caller would most likely want to use one of the arc {@code type} values
* that inserts a segment leading to the initial point. An initial {@link #moveTo(double, double)} or
* {@link #lineTo(double, double)} can be added to direct the path to the starting point of the ellipse section if
* {@link org.arakhne.afc.math.geometry.d2.Path2D.ArcType#MOVE_THEN_ARC} or
* {@link org.arakhne.afc.math.geometry.d2.Path2D.ArcType#LINE_THEN_ARC} are specified by the type argument. The
* {@code lineTo} path segment will only be added if the current point is not already at the indicated location to avoid
* spurious empty line segments. The type can be specified as {@link org.arakhne.afc.math.geometry.d2.Path2D.ArcType#ARC_ONLY}
* if the current point on the path is known to be at the starting point of the ellipse section.
*
* @param ctrlx the x coordinate of the control point, i.e. the corner of the parallelogram in which the ellipse is inscribed.
* @param ctrly the y coordinate of the control point, i.e. the corner of the parallelogram in which the ellipse is inscribed.
* @param tox the x coordinate of the target point.
* @param toy the y coordinate of the target point.
* @param tfrom the fraction of the ellipse section where the curve should start.
* @param tto the fraction of the ellipse section where the curve should end
* @param type the specification of what additional path segments should
* be appended to lead the current path to the starting point.
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:magicnumber"})
default void arcTo(double ctrlx, double ctrly, double tox, double toy, double tfrom, double tto, ArcType type) {
// Copied from JavaFX Path2D
assert tfrom >= 0. : AssertMessages.positiveOrZeroParameter(4);
assert tto >= tfrom : AssertMessages.lowerEqualParameters(4, tfrom, 5, tto);
assert tto <= 1. : AssertMessages.lowerEqualParameter(5, tto, 1);
double currentx = getCurrentX();
double currenty = getCurrentY();
final double ocurrentx = currentx;
final double ocurrenty = currenty;
double targetx = tox;
double targety = toy;
double cx0 = currentx + (ctrlx - currentx) * AbstractCirclePathIterator.CTRL_POINT_DISTANCE;
double cy0 = currenty + (ctrly - currenty) * AbstractCirclePathIterator.CTRL_POINT_DISTANCE;
double cx1 = targetx + (ctrlx - targetx) * AbstractCirclePathIterator.CTRL_POINT_DISTANCE;
double cy1 = targety + (ctrly - targety) * AbstractCirclePathIterator.CTRL_POINT_DISTANCE;
if (tto < 1.) {
final double t = 1. - tto;
targetx += (cx1 - targetx) * t;
targety += (cy1 - targety) * t;
cx1 += (cx0 - cx1) * t;
cy1 += (cy0 - cy1) * t;
cx0 += (currentx - cx0) * t;
cy0 += (currenty - cy0) * t;
targetx += (cx1 - targetx) * t;
targety += (cy1 - targety) * t;
cx1 += (cx0 - cx1) * t;
cy1 += (cy0 - cy1) * t;
targetx += (cx1 - targetx) * t;
targety += (cy1 - targety) * t;
}
double realtfrom = tfrom;
if (realtfrom > 0.) {
if (tto < 1.) {
realtfrom = realtfrom / tto;
}
currentx += (cx0 - currentx) * realtfrom;
currenty += (cy0 - currenty) * realtfrom;
cx0 += (cx1 - cx0) * realtfrom;
cy0 += (cy1 - cy0) * realtfrom;
cx1 += (targetx - cx1) * realtfrom;
cy1 += (targety - cy1) * realtfrom;
currentx += (cx0 - currentx) * realtfrom;
currenty += (cy0 - currenty) * realtfrom;
cx0 += (cx1 - cx0) * realtfrom;
cy0 += (cy1 - cy0) * realtfrom;
currentx += (cx0 - currentx) * realtfrom;
currenty += (cy0 - currenty) * realtfrom;
}
if (type == ArcType.MOVE_THEN_ARC) {
if (currentx != ocurrentx || currenty != ocurrenty) {
moveTo(currentx, currenty);
}
} else if (type == ArcType.LINE_THEN_ARC && (currentx != ocurrentx || currenty != ocurrenty)) {
lineTo(currentx, currenty);
}
if (realtfrom == tto
|| (currentx == cx0 && cx0 == cx1 && cx1 == targetx
&& currenty == cy0 && cy0 == cy1 && cy1 == targety)) {
if (type != ArcType.LINE_THEN_ARC) {
lineTo(targetx, targety);
}
} else {
curveTo(cx0, cy0, cx1, cy1, targetx, targety);
}
}
@Override
default void arcTo(Point2D<?, ?> ctrl, Point2D<?, ?> to, double tfrom, double tto,
org.arakhne.afc.math.geometry.d2.Path2D.ArcType type) {
assert ctrl != null : AssertMessages.notNullParameter(0);
assert to != null : AssertMessages.notNullParameter(1);
arcTo(ctrl.getX(), ctrl.getY(), to.getX(), to.getY(), tfrom, tto, type);
}
/**
* Adds a section of an shallow ellipse to the current path.
*
* <p>This function is equivalent to:<pre><code>
* this.arcTo(ctrl, to, 0.0, 1.0, ArcType.ARCONLY);
* </code></pre>
*
* @param ctrlx the x coordinate of the control point, i.e. the corner of the parallelogram in which the ellipse is inscribed.
* @param ctrly the y coordinate of the control point, i.e. the corner of the parallelogram in which the ellipse is inscribed.
* @param tox the x coordinate of the target point.
* @param toy the y coordinate of the target point.
*/
default void arcTo(double ctrlx, double ctrly, double tox, double toy) {
arcTo(ctrlx, ctrly, tox, toy, 0., 1., ArcType.ARC_ONLY);
}
@Override
default void arcTo(Point2D<?, ?> to, Vector2D<?, ?> radii, double xAxisRotation, boolean largeArcFlag, boolean sweepFlag) {
assert radii != null : AssertMessages.notNullParameter(1);
assert to != null : AssertMessages.notNullParameter(0);
arcTo(to.getX(), to.getY(), radii.getX(), radii.getY(), xAxisRotation, largeArcFlag, sweepFlag);
}
/**
* Adds a section of an shallow ellipse to the current path.
* The ellipse from which the portions are extracted follows the rules:
* <ul>
* <li>The ellipse will have its X axis tilted from horizontal by the
* angle {@code xAxisRotation} specified in radians.</li>
* <li>The ellipse will have the X and Y radii (viewed from its tilted
* coordinate system) specified by {@code radiusx} and {@code radiusy}
* unless that ellipse is too small to bridge the gap from the current
* point to the specified destination point in which case a larger
* ellipse with the same ratio of dimensions will be substituted instead.</li>
* <li>The ellipse may slide perpendicular to the direction from the
* current point to the specified destination point so that it just
* touches the two points.
* The direction it slides (to the "left" or to the "right") will be
* chosen to meet the criteria specified by the two boolean flags as
* described below.
* Only one direction will allow the method to meet both criteria.</li>
* <li>If the {@code largeArcFlag} is true, then the ellipse will sweep
* the longer way around the ellipse that meets these criteria.</li>
* <li>If the {@code sweepFlag} is true, then the ellipse will sweep
* clockwise around the ellipse that meets these criteria.</li>
* </ul>
*
* <p><img alt="" src="../doc-files/arcto1.png" >
*
* <p>The method will do nothing if the destination point is the same as
* the current point.
* The method will draw a simple line segment to the destination point
* if either of the two radii are zero.
*
* @param tox the X coordinate of the target point.
* @param toy the Y coordinate of the target point.
* @param radiusx the X radius of the tilted ellipse.
* @param radiusy the Y radius of the tilted ellipse.
* @param xAxisRotation the angle of tilt of the ellipse.
* @param largeArcFlag <code>true</code> iff the path will sweep the long way around the ellipse.
* @param sweepFlag <code>true</code> iff the path will sweep clockwise around the ellipse.
* @see "http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands"
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
default void arcTo(double tox, double toy, double radiusx, double radiusy, double xAxisRotation,
boolean largeArcFlag, boolean sweepFlag) {
// Copied for JavaFX
assert radiusx >= 0. : AssertMessages.positiveOrZeroParameter(2);
assert radiusy >= 0. : AssertMessages.positiveOrZeroParameter(3);
if (radiusx == 0. || radiusy == 0.) {
lineTo(tox, toy);
return;
}
final double ocurrentx = getCurrentX();
final double ocurrenty = getCurrentY();
double x1 = ocurrentx;
double y1 = ocurrenty;
final double x2 = tox;
final double y2 = toy;
if (x1 == x2 && y1 == y2) {
return;
}
final double cosphi;
final double sinphi;
if (xAxisRotation == 0.) {
cosphi = 1.;
sinphi = 0.;
} else {
cosphi = Math.cos(xAxisRotation);
sinphi = Math.sin(xAxisRotation);
}
double mx = (x1 + x2) / 2.;
double my = (y1 + y2) / 2.;
final double relx1 = x1 - mx;
final double rely1 = y1 - my;
final double x1p = (cosphi * relx1 + sinphi * rely1) / radiusx;
final double y1p = (cosphi * rely1 - sinphi * relx1) / radiusy;
final double lenpsq = x1p * x1p + y1p * y1p;
if (lenpsq >= 1.) {
double xqpr = y1p * radiusx;
double yqpr = x1p * radiusy;
if (sweepFlag) {
xqpr = -xqpr;
} else {
yqpr = -yqpr;
}
final double relxq = cosphi * xqpr - sinphi * yqpr;
final double relyq = cosphi * yqpr + sinphi * xqpr;
final double xq = mx + relxq;
final double yq = my + relyq;
double xc = x1 + relxq;
double yc = y1 + relyq;
if (x1 != ocurrentx || y1 != ocurrenty) {
lineTo(x1, y1);
}
arcTo(xc, yc, xq, yq, 0, 1, ArcType.ARC_ONLY);
xc = x2 + relxq;
yc = y2 + relyq;
arcTo(xc, yc, x2, y2, 0, 1, ArcType.ARC_ONLY);
return;
}
final double scalef = Math.sqrt((1. - lenpsq) / lenpsq);
double cxp = scalef * y1p;
double cyp = scalef * x1p;
if (largeArcFlag == sweepFlag) {
cxp = -cxp;
} else {
cyp = -cyp;
}
mx += cosphi * cxp * radiusx - sinphi * cyp * radiusy;
my += cosphi * cyp * radiusy + sinphi * cxp * radiusx;
double ux = x1p - cxp;
double uy = y1p - cyp;
final double vx = -(x1p + cxp);
final double vy = -(y1p + cyp);
boolean done = false;
double quadlen = 1.;
boolean wasclose = false;
do {
double xqp = uy;
double yqp = ux;
if (sweepFlag) {
xqp = -xqp;
} else {
yqp = -yqp;
}
if (xqp * vx + yqp * vy > 0.) {
final double dot = ux * vx + uy * vy;
if (dot >= 0) {
quadlen = Math.acos(dot) / MathConstants.DEMI_PI;
done = true;
}
wasclose = true;
} else if (wasclose) {
break;
}
final double relxq = cosphi * xqp * radiusx - sinphi * yqp * radiusy;
final double relyq = cosphi * yqp * radiusy + sinphi * xqp * radiusx;
final double xq = mx + relxq;
final double yq = my + relyq;
final double xc = x1 + relxq;
final double yc = y1 + relyq;
arcTo(xc, yc, xq, yq, 0, quadlen, ArcType.ARC_ONLY);
x1 = xq;
y1 = yq;
ux = xqp;
uy = yqp;
} while (!done);
}
@Pure
@Override
default double getDistanceSquared(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final Point2D<?, ?> c = getClosestPointTo(pt);
return c.getDistanceSquared(pt);
}
@Pure
@Override
default double getDistanceL1(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final Point2D<?, ?> c = getClosestPointTo(pt);
return c.getDistanceL1(pt);
}
@Pure
@Override
default double getDistanceLinf(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final Point2D<?, ?> c = getClosestPointTo(pt);
return c.getDistanceLinf(pt);
}
@Pure
@Override
default boolean contains(double x, double y) {
return containsPoint(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), x, y);
}
@Override
default boolean contains(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) {
assert rectangle != null : AssertMessages.notNullParameter();
return containsRectangle(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rectangle.getMinX(), rectangle.getMinY(), rectangle.getWidth(), rectangle.getHeight());
}
@Pure
@Override
default boolean intersects(Rectangle2afp<?, ?, ?, ?, ?, ?> rectangle) {
assert rectangle != null : AssertMessages.notNullParameter();
// Copied from AWT API
if (rectangle.isEmpty()) {
return false;
}
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorRectangleShadow(
0, getPathIterator(),
rectangle.getMinX(), rectangle.getMinY(), rectangle.getMaxX(), rectangle.getMaxY(),
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 roundRectangle.intersects(this);
}
@Pure
@Override
default boolean intersects(Ellipse2afp<?, ?, ?, ?, ?, ?> ellipse) {
assert ellipse != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorEllipseShadow(
0,
getPathIterator(),
ellipse.getMinX(), ellipse.getMinY(), ellipse.getWidth(), ellipse.getHeight(),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS
|| (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(Circle2afp<?, ?, ?, ?, ?, ?> circle) {
assert circle != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorCircleShadow(
0,
getPathIterator(),
circle.getX(), circle.getY(), circle.getRadius(),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS
|| (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(Segment2afp<?, ?, ?, ?, ?, ?> segment) {
assert segment != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorSegmentShadow(
0,
getPathIterator(),
segment.getX1(), segment.getY1(), segment.getX2(), segment.getY2(),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS
|| (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(Triangle2afp<?, ?, ?, ?, ?, ?> triangle) {
assert triangle != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorTriangleShadow(
0,
getPathIterator(),
triangle.getX1(), triangle.getY1(), triangle.getX2(), triangle.getY2(), triangle.getX3(), triangle.getY3(),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS
|| (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(OrientedRectangle2afp<?, ?, ?, ?, ?, ?> orientedRectangle) {
assert orientedRectangle != null : AssertMessages.notNullParameter();
return OrientedRectangle2afp.intersectsOrientedRectanglePathIterator(
orientedRectangle.getCenterX(), orientedRectangle.getCenterY(),
orientedRectangle.getFirstAxisX(), orientedRectangle.getFirstAxisY(),
orientedRectangle.getFirstAxisExtent(), orientedRectangle.getSecondAxisExtent(),
getPathIterator());
}
@Pure
@Override
default boolean intersects(Parallelogram2afp<?, ?, ?, ?, ?, ?> sparallelogram) {
assert sparallelogram != null : AssertMessages.notNullParameter();
return Parallelogram2afp.intersectsParallelogramPathIterator(
sparallelogram.getCenterX(), sparallelogram.getCenterY(),
sparallelogram.getFirstAxisX(), sparallelogram.getFirstAxisY(), sparallelogram.getFirstAxisExtent(),
sparallelogram.getSecondAxisX(), sparallelogram.getSecondAxisY(), sparallelogram.getSecondAxisExtent(),
getPathIterator());
}
@Pure
@Override
default boolean intersects(Path2afp<?, ?, ?, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorPathShadow(
0,
path.getPathIterator(),
new BasicPathShadow2afp(this),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS
|| (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(PathIterator2afp<?> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorPathShadow(
0,
iterator,
new BasicPathShadow2afp(this),
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);
}
/** Replies the coordinate at the given index.
* The index is in [0;{@link #size()}*2).
*
* @param index the index.
* @return the coordinate at the given index.
*/
@Pure
double getCoordAt(int index);
/** Change the coordinates of the last inserted point.
*
* @param x the new x coordinate of the last point.
* @param y the new y coordinate of the last point.
*/
void setLastPoint(double x, double y);
@Override
default void setLastPoint(Point2D<?, ?> point) {
assert point != null : AssertMessages.notNullParameter();
setLastPoint(point.getX(), point.getY());
}
/** Transform the current path.
* This function changes the current path.
*
* @param transform is the affine transformation to apply.
* @see #createTransformedShape
*/
void transform(Transform2D transform);
@Override
default double getLength() {
return calculatesPathLength(getPathIterator());
}
@Override
default double getLengthSquared() {
final double length = getLength();
return length * length;
}
@Pure
@Override
default PathIterator2afp<IE> getPathIterator(Transform2D transform) {
if (transform == null) {
return new PathPathIterator<>(this);
}
return new TransformedPathPathIterator<>(this, transform);
}
@Pure
@Override
default PathIterator2afp<IE> getPathIterator(double flatness) {
return new FlatteningPathIterator<>(getPathIterator(null), flatness, DEFAULT_FLATTENING_LIMIT);
}
/** Replies an iterator on the path elements.
*
* <p>Only {@link PathElementType#MOVE_TO},
* {@link PathElementType#LINE_TO}, and
* {@link PathElementType#CLOSE} types are returned by the iterator.
*
* <p>The amount of subdivision of the curved segments is controlled by the
* flatness parameter, which specifies the maximum distance that any point
* on the unflattened transformed curve can deviate from the returned
* flattened path segments. Note that a limit on the accuracy of the
* flattened path might be silently imposed, causing very small flattening
* parameters to be treated as larger values. This limit, if there is one,
* is defined by the particular implementation that is used.
*
* <p>The iterator for this class is not multi-threaded safe.
*
* @param transform is an optional affine Transform2D to be applied to the
* coordinates as they are returned in the iteration, or <code>null</code> if
* untransformed coordinates are desired.
* @param flatness is the maximum distance that the line segments used to approximate
* the curved segments are allowed to deviate from any point on the original curve.
* @return an iterator on the path elements.
*/
@Pure
default PathIterator2afp<IE> getPathIterator(Transform2D transform, double flatness) {
return new FlatteningPathIterator<>(getPathIterator(transform), flatness, DEFAULT_FLATTENING_LIMIT);
}
/** Replies a path iterator on this path that is replacing the
* curves and corner arcs by line approximations.
*
* @return the iterator on the approximation.
* @see #getPathIterator()
* @see MathConstants#SPLINE_APPROXIMATION_RATIO
*/
@Pure
default PathIterator2afp<IE> getFlatteningPathIterator() {
return new FlatteningPathIterator<>(
getPathIterator(null),
MathConstants.SPLINE_APPROXIMATION_RATIO,
Path2afp.DEFAULT_FLATTENING_LIMIT);
}
/** Replies the x coordinate of the last point in the path.
*
* @return the x coordinate of the last point in the path.
*/
@Pure
double getCurrentX();
/** Replies the y coordinate of the last point in the path.
*
* @return the y coordinate of the last point in the path.
*/
@Pure
double getCurrentY();
@Override
@Pure
default P getCurrentPoint() {
return getGeomFactory().newPoint(getCurrentX(), getCurrentY());
}
@Override
default Collection<P> toCollection() {
return new PointCollection<>(this);
}
/** Remove the point with the given coordinates.
*
* <p>If the given coordinates do not match exactly a point in the path, nothing is removed.
*
* @param x the x coordinate of the ponit to remove.
* @param y the y coordinate of the ponit to remove.
* @return <code>true</code> if the point was removed; <code>false</code> otherwise.
*/
boolean remove(double x, double y);
@Override
default void toBoundingBox(B box) {
assert box != null : AssertMessages.notNullParameter();
Path2afp.calculatesDrawableElementBoundingBox(
getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
box);
}
/** Abstract iterator on the path elements of the path.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
abstract class AbstractPathPathIterator<T extends PathElement2afp> implements PathIterator2afp<T> {
private final Path2afp<?, ?, T, ?, ?, ?> path;
/**
* @param path the iterated path.
*/
public AbstractPathPathIterator(Path2afp<?, ?, T, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
this.path = path;
}
@Override
public GeomFactory2afp<T, ?, ?, ?> getGeomFactory() {
return this.path.getGeomFactory();
}
/** Replies the path.
*
* @return the path.
*/
public Path2afp<?, ?, T, ?, ?, ?> getPath() {
return this.path;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Pure
@Override
public PathWindingRule getWindingRule() {
return this.path.getWindingRule();
}
@Override
public boolean isPolyline() {
return this.path.isPolyline();
}
@Override
public boolean isCurved() {
return this.path.isCurved();
}
@Override
public boolean isPolygon() {
return this.path.isPolygon();
}
@Override
public boolean isMultiParts() {
return this.path.isMultiParts();
}
}
/** A path iterator that does not transform the coordinates.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
class PathPathIterator<T extends PathElement2afp> extends AbstractPathPathIterator<T> {
private Point2D<?, ?> p1;
private Point2D<?, ?> p2;
private int typeIndex;
private int coordIndex;
private double movex;
private double movey;
/**
* @param path the path to iterate on.
*/
public PathPathIterator(Path2afp<?, ?, T, ?, ?, ?> path) {
super(path);
this.p1 = new InnerComputationPoint2afp();
this.p2 = new InnerComputationPoint2afp();
}
@Override
public PathIterator2afp<T> restartIterations() {
return new PathPathIterator<>(getPath());
}
@Pure
@Override
public boolean hasNext() {
return this.typeIndex < getPath().getPathElementCount();
}
@Override
@SuppressWarnings("checkstyle:magicnumber")
public T next() {
final Path2afp<?, ?, T, ?, ?, ?> path = getPath();
final int type = this.typeIndex;
if (this.typeIndex >= path.getPathElementCount()) {
throw new NoSuchElementException();
}
T element = null;
switch (path.getPathElementTypeAt(type)) {
case MOVE_TO:
if ((this.coordIndex + 2) > (getPath().size() * 2)) {
throw new NoSuchElementException();
}
this.movex = path.getCoordAt(this.coordIndex++);
this.movey = path.getCoordAt(this.coordIndex++);
this.p2.set(this.movex, this.movey);
element = getGeomFactory().newMovePathElement(
this.p2.getX(), this.p2.getY());
break;
case LINE_TO:
if ((this.coordIndex + 2) > (path.size() * 2)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
this.p2.set(
path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
element = getGeomFactory().newLinePathElement(
this.p1.getX(), this.p1.getY(),
this.p2.getX(), this.p2.getY());
break;
case QUAD_TO:
if ((this.coordIndex + 4) > (path.size() * 2)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
final double ctrlx = path.getCoordAt(this.coordIndex++);
final double ctrly = path.getCoordAt(this.coordIndex++);
this.p2.set(
path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
element = getGeomFactory().newCurvePathElement(
this.p1.getX(), this.p1.getY(),
ctrlx, ctrly,
this.p2.getX(), this.p2.getY());
break;
case CURVE_TO:
if ((this.coordIndex + 6) > (path.size() * 2)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
final double ctrlx1 = path.getCoordAt(this.coordIndex++);
final double ctrly1 = path.getCoordAt(this.coordIndex++);
final double ctrlx2 = path.getCoordAt(this.coordIndex++);
final double ctrly2 = path.getCoordAt(this.coordIndex++);
this.p2.set(
getPath().getCoordAt(this.coordIndex++),
getPath().getCoordAt(this.coordIndex++));
element = getGeomFactory().newCurvePathElement(
this.p1.getX(), this.p1.getY(),
ctrlx1, ctrly1,
ctrlx2, ctrly2,
this.p2.getX(), this.p2.getY());
break;
case CLOSE:
this.p1.set(this.p2);
this.p2.set(this.movex, this.movey);
element = getGeomFactory().newClosePathElement(
this.p1.getX(), this.p1.getY(),
this.p2.getX(), this.p2.getY());
break;
case ARC_TO:
throw new IllegalStateException();
default:
}
if (element == null) {
throw new NoSuchElementException();
}
++this.typeIndex;
return element;
}
}
/** A path iterator that transforms the coordinates.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
class TransformedPathPathIterator<T extends PathElement2afp> extends AbstractPathPathIterator<T> {
private final Transform2D transform;
private final Point2D<?, ?> p1;
private final Point2D<?, ?> p2;
private final Point2D<?, ?> ptmp1;
private final Point2D<?, ?> ptmp2;
private int typeIndex;
private int coordIndex;
private double movex;
private double movey;
/**
* @param path the path to iterate on.
* @param transform the transformation to apply on the path.
*/
public TransformedPathPathIterator(Path2afp<?, ?, T, ?, ?, ?> path, Transform2D transform) {
super(path);
assert transform != null : AssertMessages.notNullParameter(1);
this.transform = transform;
this.p1 = new InnerComputationPoint2afp();
this.p2 = new InnerComputationPoint2afp();
this.ptmp1 = new InnerComputationPoint2afp();
this.ptmp2 = new InnerComputationPoint2afp();
}
@Override
public PathIterator2afp<T> restartIterations() {
return new TransformedPathPathIterator<>(getPath(), this.transform);
}
@Pure
@Override
public boolean hasNext() {
return this.typeIndex < getPath().getPathElementCount();
}
@Override
public T next() {
final Path2afp<?, ?, T, ?, ?, ?> path = getPath();
if (this.typeIndex >= path.getPathElementCount()) {
throw new NoSuchElementException();
}
T element = null;
switch (path.getPathElementTypeAt(this.typeIndex++)) {
case MOVE_TO:
this.movex = path.getCoordAt(this.coordIndex++);
this.movey = path.getCoordAt(this.coordIndex++);
this.p2.set(this.movex, this.movey);
this.transform.transform(this.p2);
element = getGeomFactory().newMovePathElement(
this.p2.getX(), this.p2.getY());
break;
case LINE_TO:
this.p1.set(this.p2);
this.p2.set(
path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newLinePathElement(
this.p1.getX(), this.p1.getY(),
this.p2.getX(), this.p2.getY());
break;
case QUAD_TO:
this.p1.set(this.p2);
this.ptmp1.set(
path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp1);
this.p2.set(
path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newCurvePathElement(
this.p1.getX(), this.p1.getY(),
this.ptmp1.getX(), this.ptmp1.getY(),
this.p2.getX(), this.p2.getY());
break;
case CURVE_TO:
this.p1.set(this.p2);
this.ptmp1.set(
path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp1);
this.ptmp2.set(
path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp2);
this.p2.set(
path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newCurvePathElement(
this.p1.getX(), this.p1.getY(),
this.ptmp1.getX(), this.ptmp1.getY(),
this.ptmp2.getX(), this.ptmp2.getY(),
this.p2.getX(), this.p2.getY());
break;
case CLOSE:
this.p1.set(this.p2);
this.p2.set(this.movex, this.movey);
this.transform.transform(this.p2);
element = getGeomFactory().newClosePathElement(
this.p1.getX(), this.p1.getY(),
this.p2.getX(), this.p2.getY());
break;
case ARC_TO:
throw new IllegalStateException();
default:
}
if (element == null) {
throw new NoSuchElementException();
}
return element;
}
}
/** A path iterator that is flattening the path.
* This iterator was copied from AWT FlatteningPathIterator.
*
* @param <T> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
@SuppressWarnings("checkstyle:magicnumber")
class FlatteningPathIterator<T extends PathElement2afp> implements PathIterator2afp<T> {
/** The source iterator.
*/
private final PathIterator2afp<T> pathIterator;
/**
* Square of the flatness parameter for testing against squared lengths.
*/
private final double squaredFlatness;
/**
* Maximum number of recursion levels.
*/
private final int limit;
/** The recursion level at which each curve being held in storage was generated.
*/
private int[] levels;
/** The cache of interpolated coords.
* Note that this must be long enough
* to store a full cubic segment and
* a relative cubic segment to avoid
* aliasing when copying the coords
* of a curve to the end of the array.
* This is also serendipitously equal
* to the size of a full quad segment
* and 2 relative quad segments.
*/
private double[] hold = new double[14];
/** The index of the last curve segment being held for interpolation.
*/
private int holdEnd;
/**
* The index of the curve segment that was last interpolated. This
* is the curve segment ready to be returned in the next call to
* next().
*/
private int holdIndex;
/** The ending x of the last segment.
*/
private double currentX;
/** The ending y of the last segment.
*/
private double currentY;
/** The x of the last move segment.
*/
private double moveX;
/** The y of the last move segment.
*/
private double moveY;
/** The index of the entry in the
* levels array of the curve segment
* at the holdIndex.
*/
private int levelIndex;
/** True when iteration is done.
*/
private boolean done;
/** The type of the path element.
*/
private PathElementType holdType;
/** The x of the last move segment replied by next.
*/
private double lastNextX;
/** The y of the last move segment replied by next.
*/
private double lastNextY;
/**
* @param pathIterator is the path iterator that may be used to initialize the path.
* @param flatness the maximum allowable distance between the
* control points and the flattened curve
* @param limit the maximum number of recursive subdivisions
* allowed for any curved segment
*/
public FlatteningPathIterator(PathIterator2afp<T> pathIterator, double flatness, int limit) {
assert pathIterator != null : AssertMessages.notNullParameter(0);
assert flatness >= 0. : AssertMessages.positiveOrZeroParameter(1);
assert limit >= 0 : AssertMessages.positiveOrZeroParameter(2);
this.pathIterator = pathIterator;
this.squaredFlatness = flatness * flatness;
this.limit = limit;
this.levels = new int[limit + 1];
searchNext();
}
@Override
public PathIterator2afp<T> restartIterations() {
return new FlatteningPathIterator<>(
this.pathIterator.restartIterations(),
Math.sqrt(this.squaredFlatness),
this.limit);
}
/**
* Ensures that the hold array can hold up to (want) more values.
* It is currently holding (hold.length - holdIndex) values.
*/
private void ensureHoldCapacity(int want) {
if (this.holdIndex - want < 0) {
final int have = this.hold.length - this.holdIndex;
final int newsize = this.hold.length + GROW_SIZE;
final double[] newhold = new double[newsize];
System.arraycopy(this.hold, this.holdIndex,
newhold, this.holdIndex + GROW_SIZE,
have);
this.hold = newhold;
this.holdIndex += GROW_SIZE;
this.holdEnd += GROW_SIZE;
}
}
/**
* Returns the square of the flatness, or maximum distance of a
* control point from the line connecting the end points, of the
* quadratic curve specified by the control points stored in the
* indicated array at the indicated index.
* @param coords an array containing coordinate values
* @param offset the index into <code>coords</code> from which to
* to start getting the values from the array
* @return the flatness of the quadratic curve that is defined by the
* values in the specified array at the specified index.
*/
private static double getQuadSquaredFlatness(double[] coords, int offset) {
return Segment2afp.calculatesDistanceSquaredLinePoint(
coords[offset + 0], coords[offset + 1],
coords[offset + 4], coords[offset + 5],
coords[offset + 2], coords[offset + 3]);
}
/**
* Subdivides the quadratic curve specified by the coordinates
* stored in the <code>src</code> array at indices
* <code>srcoff</code> through <code>srcoff</code> + 5
* and stores the resulting two subdivided curves into the two
* result arrays at the corresponding indices.
* Either or both of the <code>left</code> and <code>right</code>
* arrays can be <code>null</code> or a reference to the same array
* and offset as the <code>src</code> array.
* Note that the last point in the first subdivided curve is the
* same as the first point in the second subdivided curve. Thus,
* it is possible to pass the same array for <code>left</code> and
* <code>right</code> and to use offsets such that
* <code>rightoff</code> equals <code>leftoff</code> + 4 in order
* to avoid allocating extra storage for this common point.
* @param src the array holding the coordinates for the source curve
* @param srcoff the offset into the array of the beginning of the
* the 6 source coordinates
* @param left the array for storing the coordinates for the first
* half of the subdivided curve
* @param leftoff the offset into the array of the beginning of the
* the 6 left coordinates
* @param right the array for storing the coordinates for the second
* half of the subdivided curve
* @param rightoff the offset into the array of the beginning of the
* the 6 right coordinates
*/
private static void subdivideQuad(double[] src, int srcoff,
double[] left, int leftoff,
double[] right, int rightoff) {
double x1 = src[srcoff + 0];
double y1 = src[srcoff + 1];
double x2 = src[srcoff + 4];
double y2 = src[srcoff + 5];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
double ctrlx = src[srcoff + 2];
double ctrly = src[srcoff + 3];
x1 = (x1 + ctrlx) / 2;
y1 = (y1 + ctrly) / 2;
x2 = (x2 + ctrlx) / 2;
y2 = (y2 + ctrly) / 2;
ctrlx = (x1 + x2) / 2;
ctrly = (y1 + y2) / 2;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx;
left[leftoff + 5] = ctrly;
}
if (right != null) {
right[rightoff + 0] = ctrlx;
right[rightoff + 1] = ctrly;
right[rightoff + 2] = x2;
right[rightoff + 3] = y2;
}
}
/**
* Returns the square of the flatness of the cubic curve specified
* by the control points stored in the indicated array at the
* indicated index. The flatness is the maximum distance
* of a control point from the line connecting the end points.
* @param coords an array containing coordinates
* @param offset the index of <code>coords</code> from which to begin
* getting the end points and control points of the curve
* @return the square of the flatness of the <code>CubicCurve2D</code>
* specified by the coordinates in <code>coords</code> at
* the specified offset.
*/
private static double getCurveSquaredFlatness(double[] coords, int offset) {
return Math.max(
Segment2afp.calculatesDistanceSquaredSegmentPoint(
coords[offset + 6],
coords[offset + 7],
coords[offset + 2],
coords[offset + 3],
coords[offset + 0],
coords[offset + 1]),
Segment2afp.calculatesDistanceSquaredSegmentPoint(
coords[offset + 6],
coords[offset + 7],
coords[offset + 4],
coords[offset + 5],
coords[offset + 0],
coords[offset + 1]));
}
/**
* Subdivides the cubic curve specified by the coordinates
* stored in the <code>src</code> array at indices <code>srcoff</code>
* through (<code>srcoff</code> + 7) and stores the
* resulting two subdivided curves into the two result arrays at the
* corresponding indices.
* Either or both of the <code>left</code> and <code>right</code>
* arrays may be <code>null</code> or a reference to the same array
* as the <code>src</code> array.
* Note that the last point in the first subdivided curve is the
* same as the first point in the second subdivided curve. Thus,
* it is possible to pass the same array for <code>left</code>
* and <code>right</code> and to use offsets, such as <code>rightoff</code>
* equals (<code>leftoff</code> + 6), in order
* to avoid allocating extra storage for this common point.
* @param src the array holding the coordinates for the source curve
* @param srcoff the offset into the array of the beginning of the 6 source coordinates
* @param left the array for storing the coordinates for the first half of the subdivided curve
* @param leftoff the offset into the array of the beginning of the 6 left coordinates
* @param right the array for storing the coordinates for the second half of the subdivided curve
* @param rightoff the offset into the array of the beginning of the 6 right coordinates
*/
private static void subdivideCurve(
double[] src, int srcoff,
double[] left, int leftoff,
double[] right, int rightoff) {
double x1 = src[srcoff + 0];
double y1 = src[srcoff + 1];
double x2 = src[srcoff + 6];
double y2 = src[srcoff + 7];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 6] = x2;
right[rightoff + 7] = y2;
}
double ctrlx1 = src[srcoff + 2];
x1 = (x1 + ctrlx1) / 2;
double ctrly1 = src[srcoff + 3];
y1 = (y1 + ctrly1) / 2;
double ctrlx2 = src[srcoff + 4];
x2 = (x2 + ctrlx2) / 2;
double ctrly2 = src[srcoff + 5];
y2 = (y2 + ctrly2) / 2;
double centerx = (ctrlx1 + ctrlx2) / 2;
double centery = (ctrly1 + ctrly2) / 2;
ctrlx1 = (x1 + centerx) / 2;
ctrly1 = (y1 + centery) / 2;
ctrlx2 = (x2 + centerx) / 2;
ctrly2 = (y2 + centery) / 2;
centerx = (ctrlx1 + ctrlx2) / 2;
centery = (ctrly1 + ctrly2) / 2;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
}
@SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity"})
private void searchNext() {
int level;
if (this.holdIndex >= this.holdEnd) {
if (!this.pathIterator.hasNext()) {
this.done = true;
return;
}
final T pathElement = this.pathIterator.next();
this.holdType = pathElement.getType();
pathElement.toArray(this.hold);
this.levelIndex = 0;
this.levels[0] = 0;
}
switch (this.holdType) {
case MOVE_TO:
case LINE_TO:
this.currentX = this.hold[0];
this.currentY = this.hold[1];
if (this.holdType == PathElementType.MOVE_TO) {
this.moveX = this.currentX;
this.moveY = this.currentY;
}
this.holdIndex = 0;
this.holdEnd = 0;
break;
case CLOSE:
this.currentX = this.moveX;
this.currentY = this.moveY;
this.holdIndex = 0;
this.holdEnd = 0;
break;
case QUAD_TO:
if (this.holdIndex >= this.holdEnd) {
// Move the coordinates to the end of the array.
this.holdIndex = this.hold.length - 6;
this.holdEnd = this.hold.length - 2;
this.hold[this.holdIndex + 0] = this.currentX;
this.hold[this.holdIndex + 1] = this.currentY;
this.hold[this.holdIndex + 2] = this.hold[0];
this.hold[this.holdIndex + 3] = this.hold[1];
this.currentX = this.hold[2];
this.hold[this.holdIndex + 4] = this.currentX;
this.currentY = this.hold[3];
this.hold[this.holdIndex + 5] = this.currentY;
}
level = this.levels[this.levelIndex];
while (level < this.limit) {
if (getQuadSquaredFlatness(this.hold, this.holdIndex) < this.squaredFlatness) {
break;
}
ensureHoldCapacity(4);
subdivideQuad(
this.hold, this.holdIndex,
this.hold, this.holdIndex - 4,
this.hold, this.holdIndex);
this.holdIndex -= 4;
// Now that we have subdivided, we have constructed two curves of one depth lower than the original
// curve. One of those curves is in the place of the former curve and one of them is in the next
// set of held coordinate slots. We now set both curves level values to the next higher level.
level++;
this.levels[this.levelIndex] = level;
this.levelIndex++;
this.levels[this.levelIndex] = level;
}
// This curve segment is flat enough, or it is too deep in recursion levels to try to flatten any more. The
// two coordinates at holdIndex+4 and holdIndex+5 now contain the endpoint of the curve which can be the
// endpoint of an approximating line segment.
this.holdIndex += 4;
this.levelIndex--;
break;
case CURVE_TO:
if (this.holdIndex >= this.holdEnd) {
// Move the coordinates to the end of the array.
this.holdIndex = this.hold.length - 8;
this.holdEnd = this.hold.length - 2;
this.hold[this.holdIndex + 0] = this.currentX;
this.hold[this.holdIndex + 1] = this.currentY;
this.hold[this.holdIndex + 2] = this.hold[0];
this.hold[this.holdIndex + 3] = this.hold[1];
this.hold[this.holdIndex + 4] = this.hold[2];
this.hold[this.holdIndex + 5] = this.hold[3];
this.currentX = this.hold[4];
this.hold[this.holdIndex + 6] = this.currentX;
this.currentY = this.hold[5];
this.hold[this.holdIndex + 7] = this.currentY;
}
level = this.levels[this.levelIndex];
while (level < this.limit) {
if (getCurveSquaredFlatness(this.hold, this.holdIndex) < this.squaredFlatness) {
break;
}
ensureHoldCapacity(6);
subdivideCurve(
this.hold, this.holdIndex,
this.hold, this.holdIndex - 6,
this.hold, this.holdIndex);
this.holdIndex -= 6;
// Now that we have subdivided, we have constructed two curves of one depth lower than the original
// curve. One of those curves is in the place of the former curve and one of them is in the next
// set of held coordinate slots. We now set both curves level values to the next higher level.
level++;
this.levels[this.levelIndex] = level;
this.levelIndex++;
this.levels[this.levelIndex] = level;
}
// This curve segment is flat enough, or it is too deep in recursion levels to try to flatten any more. The
// two coordinates at holdIndex+6 and holdIndex+7 now contain the endpoint of the curve which can be the
// endpoint of an approximating line segment.
this.holdIndex += 6;
this.levelIndex--;
break;
case ARC_TO:
throw new IllegalStateException();
default:
}
}
@Pure
@Override
public boolean hasNext() {
return !this.done;
}
@Override
public T next() {
if (this.done) {
throw new NoSuchElementException();
}
final T element;
final PathElementType type = this.holdType;
if (type != PathElementType.CLOSE) {
final double x = this.hold[this.holdIndex + 0];
final double y = this.hold[this.holdIndex + 1];
if (type == PathElementType.MOVE_TO) {
element = getGeomFactory().newMovePathElement(x, y);
} else {
element = getGeomFactory().newLinePathElement(
this.lastNextX, this.lastNextY,
x, y);
}
this.lastNextX = x;
this.lastNextY = y;
} else {
element = getGeomFactory().newClosePathElement(
this.lastNextX, this.lastNextY,
this.moveX, this.moveY);
this.lastNextX = this.moveX;
this.lastNextY = this.moveY;
}
searchNext();
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Pure
@Override
public PathWindingRule getWindingRule() {
return this.pathIterator.getWindingRule();
}
@Pure
@Override
public boolean isPolyline() {
return this.pathIterator.isPolyline() || (!this.pathIterator.isMultiParts() && !this.pathIterator.isPolygon());
}
@Pure
@Override
public boolean isCurved() {
return false;
}
@Pure
@Override
public boolean isPolygon() {
return this.pathIterator.isPolygon();
}
@Pure
@Override
public boolean isMultiParts() {
return this.pathIterator.isMultiParts();
}
@Pure
@Override
public GeomFactory2afp<T, ?, ?, ?> getGeomFactory() {
return this.pathIterator.getGeomFactory();
}
}
}