/*
* $Id$
* This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc
*
* Copyright (c) 2000-2012 Stephane GALLAND.
* Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports,
* Universite de Technologie de Belfort-Montbeliard.
* Copyright (c) 2013-2016 The original authors, and other authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.arakhne.afc.math.geometry.d2.ai;
import java.util.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.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.math.geometry.d2.afp.Segment2afp;
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 Path2ai<
ST extends Shape2ai<?, ?, IE, P, V, B>,
IT extends Path2ai<?, ?, IE, P, V, B>,
IE extends PathElement2ai,
P extends Point2D<? super P, ? super V>,
V extends Vector2D<? super V, ? super P>,
B extends Rectangle2ai<?, ?, IE, P, V, B>>
extends Shape2ai<ST, IT, IE, P, V, B>, Path2D<ST, IT, PathIterator2ai<IE>, P, V, B> {
/** Multiple of cubic & quad curve size.
*/
int GROW_SIZE = 24;
/** Default depth for the flattening of the path.
*/
int DEFAULT_FLATTENING_LIMIT = 10;
/** The default winding rule: {@link PathWindingRule#NON_ZERO}.
*/
PathWindingRule DEFAULT_WINDING_RULE = PathWindingRule.NON_ZERO;
/** 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.
*
* @param iterator the iterator on the path elements.
* @param box the box to set.
* @return <code>true</code> if a drawable element was found.
* @deprecated since 13.0, see {@link #calculatesDrawableElementBoundingBox(PathIterator2ai, Rectangle2ai)}
*/
@Deprecated
static boolean computeDrawableElementBoundingBox(PathIterator2ai<?> iterator,
Rectangle2ai<?, ?, ?, ?, ?, ?> 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.
*
* @param iterator the iterator on the path elements.
* @param box the box to set.
* @return <code>true</code> if a drawable element was found.
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static boolean calculatesDrawableElementBoundingBox(PathIterator2ai<?> iterator,
Rectangle2ai<?, ?, ?, ?, ?, ?> box) {
assert iterator != null : AssertMessages.notNullParameter(0);
assert box != null : AssertMessages.notNullParameter(1);
final GeomFactory2ai<?, ?, ?, ?> factory = iterator.getGeomFactory();
boolean foundOneLine = false;
int xmin = Integer.MAX_VALUE;
int ymin = Integer.MAX_VALUE;
int xmax = Integer.MIN_VALUE;
int ymax = Integer.MIN_VALUE;
PathElement2ai element;
Path2ai<?, ?, ?, ?, ?, ?> subPath;
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());
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),
box)) {
if (box.getMinX() < xmin) {
xmin = box.getMinX();
}
if (box.getMinY() < ymin) {
ymin = box.getMinY();
}
if (box.getMaxX() > xmax) {
xmax = box.getMaxX();
}
if (box.getMaxY() > ymax) {
ymax = box.getMaxY();
}
foundOneLine = true;
}
break;
case QUAD_TO:
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(element.getFromX(), element.getFromY());
subPath.quadTo(
element.getCtrlX1(), element.getCtrlY1(),
element.getToX(), element.getToY());
if (calculatesDrawableElementBoundingBox(
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
box)) {
if (box.getMinX() < xmin) {
xmin = box.getMinX();
}
if (box.getMinY() < ymin) {
ymin = box.getMinY();
}
if (box.getMaxX() > xmax) {
xmax = box.getMaxX();
}
if (box.getMaxY() > ymax) {
ymax = box.getMaxY();
}
foundOneLine = true;
}
break;
case ARC_TO:
subPath = factory.newPath(iterator.getWindingRule());
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),
box)) {
if (box.getMinX() < xmin) {
xmin = box.getMinX();
}
if (box.getMinY() < ymin) {
ymin = box.getMinY();
}
if (box.getMaxX() > xmax) {
xmax = box.getMaxX();
}
if (box.getMaxY() > ymax) {
ymax = box.getMaxY();
}
foundOneLine = true;
}
break;
case MOVE_TO:
case CLOSE:
default:
}
}
if (foundOneLine) {
box.setFromCorners(xmin, ymin, xmax, ymax);
} else {
box.clear();
}
return foundOneLine;
}
/** 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(PathIterator2ai, Rectangle2ai)}.
*
* @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(PathIterator2ai, Rectangle2ai)
* @deprecated since 13.0, see {@link #calculatesControlPointBoundingBox(PathIterator2ai, Rectangle2ai)}
*/
@Deprecated
static boolean computeControlPointBoundingBox(PathIterator2ai<?> iterator,
Rectangle2ai<?, ?, ?, ?, ?, ?> 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(PathIterator2ai, Rectangle2ai)}.
*
* @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(PathIterator2ai, Rectangle2ai)
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static boolean calculatesControlPointBoundingBox(PathIterator2ai<?> iterator,
Rectangle2ai<?, ?, ?, ?, ?, ?> box) {
assert iterator != null : AssertMessages.notNullParameter(0);
assert box != null : AssertMessages.notNullParameter(1);
boolean foundOneControlPoint = false;
int xmin = Integer.MAX_VALUE;
int ymin = Integer.MAX_VALUE;
int xmax = Integer.MIN_VALUE;
int ymax = Integer.MIN_VALUE;
PathElement2ai 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;
}
/**
* 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 pi 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
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorSegmentShadow(int,
* PathIterator2ai, int, int, int, int, CrossingComputationType)}
*/
@Deprecated
static int computeCrossingsFromSegment(int crossings, PathIterator2ai<?> pi, int x1, int y1, int x2, int y2,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorSegmentShadow(crossings, pi, x1, y1, x2, y2, 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 pi 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, PathIterator2ai<?> pi, int x1, int y1, int x2, int y2,
CrossingComputationType type) {
assert pi != null : AssertMessages.notNullParameter(1);
// Copied from the AWT API
if (!pi.hasNext()) {
return 0;
}
PathElement2ai element;
element = pi.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
int movx = element.getToX();
int movy = element.getToY();
int curx = movx;
int cury = movy;
int endx;
int endy;
int numCrosses = crossings;
while (pi.hasNext()) {
element = pi.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 = Segment2ai.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();
Path2ai<?, ?, ?, ?, ?, ?> localPath = pi.getGeomFactory().newPath(pi.getWindingRule());
localPath.moveTo(element.getFromX(), element.getFromY());
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();
localPath = pi.getGeomFactory().newPath(pi.getWindingRule());
localPath.moveTo(element.getFromX(), element.getFromY());
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();
localPath = pi.getGeomFactory().newPath(pi.getWindingRule());
localPath.moveTo(element.getFromX(), element.getFromY());
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 = Segment2ai.calculatesCrossingsSegmentShadowSegment(
numCrosses,
x1, y1, x2, y2,
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 = Segment2ai.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;
}
/**
* 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 pi 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
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorCircleShadow(int,
* PathIterator2ai, int, int, int, CrossingComputationType)}
*/
@Deprecated
static int computeCrossingsFromCircle(int crossings, PathIterator2ai<?> pi, int cx, int cy, int radius,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorCircleShadow(crossings, pi, 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 pi 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:cyclomaticcomplexity", "checkstyle:npathcomplexity", "checkstyle:magicnumber"})
static int calculatesCrossingsPathIteratorCircleShadow(int crossings, PathIterator2ai<?> pi, int cx, int cy, int radius,
CrossingComputationType type) {
assert pi != null : AssertMessages.notNullParameter(1);
assert radius >= 0 : AssertMessages.positiveOrZeroParameter(4);
// Copied from the AWT API
if (!pi.hasNext()) {
return 0;
}
PathElement2ai element;
element = pi.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
int movx = element.getToX();
int movy = element.getToY();
int curx = movx;
int cury = movy;
int endx;
int endy;
int numCrosses = crossings;
while (pi.hasNext()) {
element = pi.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 = Segment2ai.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();
Path2ai<?, ?, ?, ?, ?, ?> localPath = pi.getGeomFactory().newPath(pi.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();
localPath = pi.getGeomFactory().newPath(pi.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();
localPath = pi.getGeomFactory().newPath(pi.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 = Segment2ai.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 = Segment2ai.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;
}
/**
* 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 the initial crossing.
* @param pi 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, or {@link MathConstants#SHAPE_INTERSECTS}
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorPointShadow(int,
* PathIterator2ai, int, int, CrossingComputationType)}
*/
@Deprecated
static int computeCrossingsFromPoint(int crossings, PathIterator2ai<?> pi, int px, int py, CrossingComputationType type) {
return calculatesCrossingsPathIteratorPointShadow(crossings, pi, 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 the initial crossing.
* @param pi 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, or {@link MathConstants#SHAPE_INTERSECTS}
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity",
"checkstyle:returncount"})
static int calculatesCrossingsPathIteratorPointShadow(int crossings, PathIterator2ai<?> pi, int px, int py,
CrossingComputationType type) {
assert pi != null : AssertMessages.notNullParameter(1);
// Copied and adapted from the AWT API
if (!pi.hasNext()) {
return 0;
}
PathElement2ai element;
element = pi.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
int movx = element.getToX();
int movy = element.getToY();
int curx = movx;
int cury = movy;
int endx;
int endy;
int numCrossings = crossings;
while (pi.hasNext()) {
element = pi.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();
numCrossings = Segment2ai.calculatesCrossingsPointShadowSegment(
numCrossings,
px, py,
curx, cury,
endx, endy);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
Path2ai<?, ?, ?, ?, ?, ?> curve = pi.getGeomFactory().newPath(pi.getWindingRule());
curve.moveTo(element.getFromX(), element.getFromY());
curve.quadTo(element.getCtrlX1(), element.getCtrlY1(), endx, endy);
numCrossings = calculatesCrossingsPathIteratorPointShadow(
numCrossings,
curve.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();
curve = pi.getGeomFactory().newPath(pi.getWindingRule());
curve.moveTo(element.getFromX(), element.getFromY());
curve.curveTo(
element.getCtrlX1(), element.getCtrlY1(),
element.getCtrlX2(), element.getCtrlY2(),
endx, endy);
numCrossings = calculatesCrossingsPathIteratorPointShadow(
numCrossings,
curve.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();
curve = pi.getGeomFactory().newPath(pi.getWindingRule());
curve.moveTo(element.getFromX(), element.getFromY());
curve.arcTo(
endx, endy,
element.getRadiusX(), element.getRadiusY(),
element.getRotationX(), element.getLargeArcFlag(),
element.getSweepFlag());
numCrossings = calculatesCrossingsPathIteratorPointShadow(
numCrossings,
curve.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) {
numCrossings = Segment2ai.calculatesCrossingsPointShadowSegment(
numCrossings,
px, py,
curx, cury,
movx, movy);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
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
if (movx == px && movy == py) {
return MathConstants.SHAPE_INTERSECTS;
}
numCrossings = Segment2ai.calculatesCrossingsPointShadowSegment(
numCrossings,
px, py,
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;
}
/**
* 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 the initial 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"
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorPathShadow(int,
* PathIterator2ai, BasicPathShadow2ai, CrossingComputationType)}
*/
@Deprecated
static int computeCrossingsFromPath(
int crossings,
PathIterator2ai<?> iterator,
BasicPathShadow2ai 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 the initial 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,
PathIterator2ai<?> iterator,
BasicPathShadow2ai shadow,
CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert shadow != null : AssertMessages.notNullParameter(2);
if (!iterator.hasNext()) {
return 0;
}
PathElement2ai pathElement1 = iterator.next();
if (pathElement1.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory2ai<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path2ai<?, ?, ?, ?, ?, ?> subPath;
int curx = pathElement1.getToX();
int movx = curx;
int cury = pathElement1.getToY();
int movy = cury;
int numCrossings = crossings;
int endx;
int endy;
while (numCrossings != MathConstants.SHAPE_INTERSECTS
&& iterator.hasNext()) {
pathElement1 = iterator.next();
switch (pathElement1.getType()) {
case MOVE_TO:
// Count should always be a multiple of 2 here.
// assert (crossings & 1 != 0);
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();
// only for local use.
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();
// only for local use
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();
// only for local use
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:
// Not closed
numCrossings = shadow.computeCrossings(numCrossings,
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;
}
/**
* Tests if the specified coordinates are inside the closed
* boundary of the specified {@link PathIterator2ai}.
*
* <p>This method provides a basic facility for implementors of
* the {@link Shape2ai} interface to implement support for the
* {@link Shape2ai#contains(int, int)} 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(PathIterator2ai<?> pi, int x, int 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 PathIterator2ai}.
*
* <p>The points on the path are assumed to be outside the path area.
* It means that is the rectangle is intersecting the path, this
* function replies <code>false</code>.
*
* @param pi the specified {@code PathIterator2ai}
* @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(PathIterator2ai<?> pi, int rx, int ry, int rwidth, int rheight) {
assert pi != null : AssertMessages.notNullParameter(0);
assert rwidth >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert rheight >= 0 : AssertMessages.positiveOrZeroParameter(4);
// Copied from AWT API
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 specified coordinates are inside the closed
* boundary of the specified {@link PathIterator2ai}.
*
* <p>This method provides a basic facility for implementors of
* the {@link Shape2ai} interface to implement support for the
* {@link Shape2ai#contains(int, int)} 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
* @deprecated since 13.0, see {@link #containsPoint(PathIterator2ai, int, int)}
*/
@Deprecated
static boolean contains(PathIterator2ai<?> pi, int x, int y) {
return containsPoint(pi, x, y);
}
/**
* Tests if the specified rectangle is inside the closed
* boundary of the specified {@link PathIterator2ai}.
*
* <p>The points on the path are assumed to be outside the path area.
* It means that is the rectangle is intersecting the path, this
* function replies <code>false</code>.
*
* @param pi the specified {@code PathIterator2ai}
* @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.
* @deprecated since 13.0, see {@link #containsRectangle(PathIterator2ai, int, int, int, int)}
*/
@Deprecated
static boolean contains(PathIterator2ai<?> pi, int rx, int ry, int rwidth, int rheight) {
return containsRectangle(pi, rx, ry, rwidth, rheight);
}
@Pure
@Override
default boolean contains(Rectangle2ai<?, ?, ?, ?, ?, ?> box) {
assert box != null : AssertMessages.notNullParameter();
return containsRectangle(getPathIterator(),
box.getMinX(), box.getMinY(), box.getWidth(), box.getHeight());
}
@Override
default boolean contains(int x, int y) {
return containsPoint(getPathIterator(), x, y);
}
/**
* 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 the initial crossing.
* @param pi 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.
* @deprecated since 13.0, see {@link #calculatesCrossingsPathIteratorRectangleShadow(int,
* PathIterator2ai, int, int, int, int, CrossingComputationType)}
*/
@Deprecated
static int computeCrossingsFromRect(
int crossings,
PathIterator2ai<?> pi,
int rxmin, int rymin,
int rxmax, int rymax,
CrossingComputationType type) {
return calculatesCrossingsPathIteratorRectangleShadow(crossings, pi, 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 the initial crossing.
* @param pi 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"})
static int calculatesCrossingsPathIteratorRectangleShadow(
int crossings,
PathIterator2ai<?> pi,
int rxmin, int rymin,
int rxmax, int rymax,
CrossingComputationType type) {
assert pi != null : AssertMessages.notNullParameter(1);
// Copied from AWT API
if (!pi.hasNext()) {
return 0;
}
PathElement2ai pathElement = pi.next();
if (pathElement.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
int curx = pathElement.getToX();
int movx = curx;
int cury = pathElement.getToY();
int movy = cury;
int numCrossings = crossings;
int endx;
int endy;
while (pi.hasNext()) {
pathElement = pi.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 = Segment2ai.calculatesCrossingsRectangleShadowSegment(
numCrossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
endx, endy);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return MathConstants.SHAPE_INTERSECTS;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
Path2ai<?, ?, ?, ?, ?, ?> curve = pi.getGeomFactory().newPath(pi.getWindingRule());
curve.moveTo(pathElement.getFromX(), pathElement.getFromY());
curve.quadTo(pathElement.getCtrlX1(), pathElement.getCtrlY1(), endx, endy);
numCrossings = calculatesCrossingsPathIteratorRectangleShadow(
numCrossings,
curve.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rxmin, rymin, rxmax, rymax,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return MathConstants.SHAPE_INTERSECTS;
}
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
curve = pi.getGeomFactory().newPath(pi.getWindingRule());
curve.moveTo(pathElement.getFromX(), pathElement.getFromY());
curve.curveTo(pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlX2(),
pathElement.getCtrlY2(), endx, endy);
numCrossings = calculatesCrossingsPathIteratorRectangleShadow(
numCrossings,
curve.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rxmin, rymin, rxmax, rymax,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return MathConstants.SHAPE_INTERSECTS;
}
curx = endx;
cury = endy;
break;
case ARC_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
curve = pi.getGeomFactory().newPath(pi.getWindingRule());
curve.moveTo(pathElement.getFromX(), pathElement.getFromY());
curve.arcTo(endx, endy, pathElement.getRadiusX(), pathElement.getRadiusY(),
pathElement.getRotationX(), pathElement.getLargeArcFlag(), pathElement.getSweepFlag());
numCrossings = calculatesCrossingsPathIteratorRectangleShadow(
numCrossings,
curve.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rxmin, rymin, rxmax, rymax,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return MathConstants.SHAPE_INTERSECTS;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
numCrossings = Segment2ai.calculatesCrossingsRectangleShadowSegment(
numCrossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
// Stop as soon as possible
if (numCrossings != 0) {
return numCrossings;
}
curx = movx;
cury = movy;
// Count should always be a multiple of 2 here.
// assert (crossings & 1 != 0);
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 = Segment2ai.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;
}
/**
* Tests if the interior of the specified {@link PathIterator2ai}
* intersects the interior of a specified set of rectangular
* coordinates.
*
* <p>This method provides a basic facility for implementors of
* the {@link Shape2ai} interface to implement support for the
* {@code intersects()} method.
*
* <p>This method object may conservatively return true in
* cases where the specified rectangular area intersects a
* segment of the path, but that segment does not represent a
* boundary between the interior and exterior of the path.
* Such a case may occur if some set of segments of the
* path are retraced in the reverse direction such that the
* two sets of segments cancel each other out without any
* interior area between them.
* To determine whether segments represent true boundaries of
* the interior of the path would require extensive calculations
* involving all of the segments of the path and the winding
* rule and are thus beyond the scope of this implementation.
*
* @param pi the specified {@code PathIterator}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @param width the width of the specified rectangular coordinates
* @param height the height of the specified rectangular coordinates
* @return {@code true} if the specified {@code PathIterator} and
* the interior of the specified set of rectangular
* coordinates intersect each other; {@code false} otherwise.
*/
@SuppressWarnings("checkstyle:magicnumber")
static boolean intersectsRectangle(PathIterator2ai<?> pi, int x, int y, int width, int height) {
assert pi != null : AssertMessages.notNullParameter(0);
assert width >= 0 : AssertMessages.positiveOrZeroParameter(3);
assert height >= 0 : AssertMessages.positiveOrZeroParameter(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;
}
/**
* Tests if the interior of the specified {@link PathIterator2ai}
* intersects the interior of a specified set of rectangular
* coordinates.
*
* <p>This method provides a basic facility for implementors of
* the {@link Shape2ai} interface to implement support for the
* {@code intersects()} method.
*
* <p>This method object may conservatively return true in
* cases where the specified rectangular area intersects a
* segment of the path, but that segment does not represent a
* boundary between the interior and exterior of the path.
* Such a case may occur if some set of segments of the
* path are retraced in the reverse direction such that the
* two sets of segments cancel each other out without any
* interior area between them.
* To determine whether segments represent true boundaries of
* the interior of the path would require extensive calculations
* involving all of the segments of the path and the winding
* rule and are thus beyond the scope of this implementation.
*
* @param pi the specified {@code PathIterator}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @param width the width of the specified rectangular coordinates
* @param height the height of the specified rectangular coordinates
* @return {@code true} if the specified {@code PathIterator} and
* the interior of the specified set of rectangular
* coordinates intersect each other; {@code false} otherwise.
* @deprecated since 13.0, see {@link #intersectsRectangle(PathIterator2ai, int, int, int, int)}
*/
@Deprecated
static boolean intersects(PathIterator2ai<?> pi, int x, int y, int width, int height) {
return intersectsRectangle(pi, x, y, width, height);
}
@Pure
@Override
default boolean intersects(Circle2ai<?, ?, ?, ?, ?, ?> 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(Rectangle2ai<?, ?, ?, ?, ?, ?> rectangle) {
assert rectangle != null : AssertMessages.notNullParameter();
return intersectsRectangle(getPathIterator(), rectangle.getMinX(), rectangle.getMinY(),
rectangle.getWidth(), rectangle.getHeight());
}
@Pure
@Override
default boolean intersects(Segment2ai<?, ?, ?, ?, ?, ?> 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;
}
@Override
default boolean intersects(PathIterator2ai<?> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = calculatesCrossingsPathIteratorPathShadow(
0, iterator,
new BasicPathShadow2ai(this),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS
|| (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(MultiShape2ai<?, ?, ?, ?, ?, ?, ?> multishape) {
assert multishape != null : AssertMessages.notNullParameter();
return multishape.intersects(this);
}
/** 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 polyline primitives, ie. if the
* {@link PathIterator2D#isPolyline()} of {@code pi} is replying
* <code>true</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.
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static void findsClosestPointPathIteratorPoint(PathIterator2ai<? extends PathElement2ai> pi, int x, int y,
Point2D<?, ?> result) {
assert pi != null : AssertMessages.notNullParameter(0);
int bestManhantanDist = Integer.MAX_VALUE;
int bestLinfinvDist = Integer.MAX_VALUE;
Point2D<?, ?> candidate;
PathElement2ai pe;
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1;
int crossings = 0;
boolean isClosed = false;
int moveX = 0;
int moveY = 0;
int currentX = 0;
int currentY = 0;
while (pi.hasNext()) {
pe = pi.next();
candidate = null;
currentX = pe.getToX();
currentY = pe.getToY();
switch (pe.getType()) {
case MOVE_TO:
moveX = pe.getToX();
moveY = pe.getToY();
isClosed = false;
break;
case LINE_TO:
isClosed = false;
candidate = new InnerComputationPoint2ai();
Segment2ai.findsClosestPointSegmentPoint(pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY(), x, y, candidate);
if (crossings != MathConstants.SHAPE_INTERSECTS) {
crossings = Segment2ai.calculatesCrossingsPointShadowSegment(
crossings,
x, y,
pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY());
}
break;
case CLOSE:
isClosed = true;
if (!pe.isEmpty()) {
candidate = new InnerComputationPoint2ai();
Segment2ai.findsClosestPointSegmentPoint(pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY(),
x, y, candidate);
if (crossings != MathConstants.SHAPE_INTERSECTS) {
crossings = Segment2ai.calculatesCrossingsPointShadowSegment(
crossings,
x, y,
pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY());
}
}
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
default:
throw new IllegalStateException();
}
if (candidate != null) {
final int dx = Math.abs(x - candidate.ix());
final int dy = Math.abs(y - candidate.iy());
final int manhatanDist = dx + dy;
if (manhatanDist <= 0) {
result.set(candidate);
return;
}
final int linfinvDist = Math.min(dx, dy);
if (manhatanDist < bestManhantanDist || (manhatanDist == bestManhantanDist && linfinvDist < bestLinfinvDist)) {
bestManhantanDist = manhatanDist;
bestLinfinvDist = linfinvDist;
result.set(candidate);
}
}
}
if (!isClosed && crossings != MathConstants.SHAPE_INTERSECTS) {
crossings = Segment2ai.calculatesCrossingsPointShadowSegment(
crossings,
x, y,
currentX, currentY,
moveX, moveY);
}
if (crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0) {
result.set(x, y);
}
}
/** 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>.
*
* @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(PathIterator2ai<? extends PathElement2ai> pi,
PathIterator2ai<? extends PathElement2ai> 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;
}
PathElement2ai 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 Rectangle2ai<?, ?, ?, ?, ?, ?> box = pi.getGeomFactory().newBox();
calculatesDrawableElementBoundingBox(shape.restartIterations(), box);
final ClosestPointPathShadow2ai shadow = new ClosestPointPathShadow2ai(shape.restartIterations(), box);
int crossings = 0;
int curx = pathElement1.getToX();
int movx = curx;
int cury = pathElement1.getToY();
int movy = cury;
int endx;
int 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;
}
/** 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 polyline primitives, ie. if the
* {@link PathIterator2D#isPolyline()} of {@code pi} is replying
* <code>true</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.
* @deprecated since 13.0, see {@link #findsClosestPointPathIteratorPoint(PathIterator2ai, int, int, Point2D)}
*/
@Deprecated
static void getClosestPointTo(PathIterator2ai<? extends PathElement2ai> pi, int x, int y, Point2D<?, ?> result) {
findsClosestPointPathIteratorPoint(pi, x, y, result);
}
/** 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>.
*
* @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>.
* @deprecated since 13.0, see {@link #findsClosestPointPathIteratorPoint(PathIterator2ai, int, int, Point2D)}
*/
@Deprecated
static boolean getClosestPointTo(PathIterator2ai<? extends PathElement2ai> pi,
PathIterator2ai<? extends PathElement2ai> shape, Point2D<?, ?> result) {
return findsClosestPointPathIteratorPathIterator(pi, shape, result);
}
@Override
default P getClosestPointTo(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
findsClosestPointPathIteratorPoint(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), pt.ix(), pt.iy(), point);
return point;
}
@Override
default P getClosestPointTo(Rectangle2ai<?, ?, ?, ?, ?, ?> rectangle) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2ai.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rectangle.getPathIterator(), result);
} else {
Path2ai.findsClosestPointPathIteratorPathIterator(getPathIterator(), rectangle.getPathIterator(), result);
}
return result;
}
@Override
default P getClosestPointTo(Circle2ai<?, ?, ?, ?, ?, ?> circle) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2ai.findsClosestPointPathIteratorPoint(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
circle.getX(), circle.getY(), result);
} else {
Path2ai.findsClosestPointPathIteratorPoint(getPathIterator(), circle.getX(), circle.getY(), result);
}
return result;
}
@Override
default P getClosestPointTo(Segment2ai<?, ?, ?, ?, ?, ?> segment) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2ai.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
segment.getPathIterator(), result);
} else {
Path2ai.findsClosestPointPathIteratorPathIterator(getPathIterator(), segment.getPathIterator(), result);
}
return result;
}
@Override
default P getClosestPointTo(Path2ai<?, ?, ?, ?, ?, ?> path) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path2ai.findsClosestPointPathIteratorPathIterator(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
path.getPathIterator(), result);
} else {
Path2ai.findsClosestPointPathIteratorPathIterator(getPathIterator(), path.getPathIterator(), result);
}
return result;
}
/** 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 polyline primitives, ie. if the
* {@link PathIterator2D#isPolyline()} of {@code pi} is replying
* <code>true</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(PathIterator2ai<? extends PathElement2ai> pi, int x, int y,
Point2D<?, ?> result) {
assert pi != null : AssertMessages.notNullParameter(0);
int bestX = x;
int bestY = y;
int bestManhatanDist = Integer.MIN_VALUE;
int bestLinfinvDist = Integer.MIN_VALUE;
PathElement2ai pe;
final Point2D<?, ?> point = new InnerComputationPoint2ai();
while (pi.hasNext()) {
pe = pi.next();
final boolean foundCandidate;
final int candidateX;
final int candidateY;
switch (pe.getType()) {
case MOVE_TO:
foundCandidate = true;
candidateX = pe.getToX();
candidateY = pe.getToY();
break;
case LINE_TO:
case CLOSE:
Segment2ai.findsFarthestPointSegmentPoint(
pe.getFromX(), pe.getFromY(), pe.getToX(), pe.getToY(),
x, y, point);
foundCandidate = true;
candidateX = point.ix();
candidateY = point.iy();
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
default:
throw new IllegalStateException(
pe.getType() == null ? null : pe.getType().toString());
}
if (foundCandidate) {
final int dx = Math.abs(x - candidateX);
final int dy = Math.abs(y - candidateY);
final int manhatanDist = dx + dy;
final int linfinvDist = Math.min(dx, dy);
if ((manhatanDist > bestManhatanDist) || (manhatanDist == bestManhatanDist && linfinvDist < bestLinfinvDist)) {
bestManhatanDist = manhatanDist;
bestLinfinvDist = linfinvDist;
bestX = candidateX;
bestY = candidateY;
}
}
}
result.set(bestX, bestY);
}
/** 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 polyline primitives, ie. if the
* {@link PathIterator2D#isPolyline()} of {@code pi} is replying
* <code>true</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.
* @deprecated since 13.0, see {@link #findsFarthestPointPathIteratorPoint(PathIterator2ai, int, int, Point2D)}
*/
@Deprecated
static void getFarthestPointTo(PathIterator2ai<? extends PathElement2ai> pi, int x, int y, Point2D<?, ?> result) {
findsFarthestPointPathIteratorPoint(pi, x, y, result);
}
@Override
default P getFarthestPointTo(Point2D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
findsFarthestPointPathIteratorPoint(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), pt.ix(), pt.iy(), point);
return point;
}
@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 on the elements to add.
*/
default void add(Iterator<? extends PathElement2ai> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
PathElement2ai 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(Path2ai<?, ?, ?, ?, ?, ?> 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(int x, int y);
@Override
default void moveTo(Point2D<?, ?> position) {
assert position != null : AssertMessages.notNullParameter();
moveTo(position.ix(), position.iy());
}
/**
* 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(int x, int y);
@Override
default void lineTo(Point2D<?, ?> to) {
assert to != null : AssertMessages.notNullParameter();
lineTo(to.ix(), to.iy());
}
/**
* 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(int x1, int y1, int x2, int y2);
@Override
default void quadTo(Point2D<?, ?> ctrl, Point2D<?, ?> to) {
assert ctrl != null : AssertMessages.notNullParameter(0);
assert to != null : AssertMessages.notNullParameter(1);
quadTo(ctrl.ix(), ctrl.iy(), to.ix(), to.iy());
}
/**
* 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(int x1, int y1,
int x2, int y2,
int x3, int 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.ix(), ctrl1.iy(), ctrl2.ix(), ctrl2.iy(), to.ix(), to.iy());
}
/**
* 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(int, int)} or {@link #lineTo(int, int)} 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(int ctrlx, int ctrly, int tox, int 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);
int currentx = getCurrentX();
int currenty = getCurrentY();
final int ocurrentx = currentx;
final int ocurrenty = currenty;
int targetx = tox;
int targety = toy;
double realtfrom = tfrom;
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;
}
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 && (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((int) Math.round(cx0), (int) Math.round(cy0), (int) Math.round(cx1), (int) Math.round(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.ix(), ctrl.iy(), to.ix(), to.iy(), tfrom, tto, type);
}
/**
* Adds a section of an shallow ellipse to the current path.
*
* <p>This function is equivalent to:<pre><code>
* this.arcTo(ctrlx, ctrly, tox, toy, 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(int ctrlx, int ctrly, int tox, int 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.ix(), to.iy(), radii.ix(), radii.iy(), 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",
"checkstyle:parameternumber"})
default void arcTo(int tox, int toy, int radiusx, int 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 int ocurrentx = getCurrentX();
final int ocurrenty = getCurrentY();
int x1 = ocurrentx;
int y1 = ocurrenty;
final int x2 = tox;
final int 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 int xq = (int) Math.round(mx + relxq);
final int yq = (int) Math.round(my + relyq);
double xc = x1 + relxq;
double yc = y1 + relyq;
if (x1 != ocurrentx || y1 != ocurrenty) {
lineTo(x1, y1);
}
arcTo((int) Math.round(xc), (int) Math.round(yc), xq, yq, 0, 1, ArcType.ARC_ONLY);
xc = x2 + relxq;
yc = y2 + relyq;
arcTo((int) Math.round(xc), (int) Math.round(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 int xq = (int) Math.round(mx + relxq);
final int yq = (int) Math.round(my + relyq);
final double xc = x1 + relxq;
final double yc = y1 + relyq;
arcTo((int) Math.round(xc), (int) Math.round(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);
}
@Override
default double getLengthSquared() {
if (isEmpty()) {
return 0;
}
double length = 0;
final PathIterator2ai<?> pi = getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO);
PathElement2ai pathElement = pi.next();
if (pathElement.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
while (pi.hasNext()) {
pathElement = pi.next();
switch (pathElement.getType()) {
case LINE_TO:
length += Point2D.getDistanceSquaredPointPoint(
pathElement.getFromX(), pathElement.getFromY(),
pathElement.getToX(), pathElement.getToY());
break;
case CLOSE:
length += Point2D.getDistanceSquaredPointPoint(
pathElement.getFromX(), pathElement.getFromY(),
pathElement.getToX(), pathElement.getToY());
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
throw new IllegalStateException();
case MOVE_TO:
default:
}
}
return length;
}
@Pure
@Override
default P getCurrentPoint() {
return getGeomFactory().newPoint(getCurrentX(), getCurrentY());
}
/** Replies the x coordinate of the last point in the path.
*
* @return the x coordinate of the last point in the path.
*/
@Pure
int getCurrentX();
/** Replies the x coordinate of the last point in the path.
*
* @return the x coordinate of the last point in the path.
*/
@Pure
int getCurrentY();
/** 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
int 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(int x, int y);
@Override
default void setLastPoint(Point2D<?, ?> point) {
assert point != null : AssertMessages.notNullParameter();
setLastPoint(point.ix(), point.iy());
}
/** Transform the current path.
* This function changes the current path.
*
* @param transform is the affine transformation to apply.
* @see #createTransformedShape
*/
void transform(Transform2D transform);
/** 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 point to remove.
* @param y the y coordinate of the point to remove.
* @return <code>true</code> if the point was removed; <code>false</code> otherwise.
*/
boolean remove(int x, int y);
@Pure
@Override
default PathIterator2ai<IE> getPathIterator(double flatness) {
return new FlatteningPathIterator<>(getPathIterator(null), flatness, DEFAULT_FLATTENING_LIMIT);
}
@Pure
@Override
default PathIterator2ai<IE> getPathIterator(Transform2D transform) {
if (transform == null || transform.isIdentity()) {
return new PathPathIterator<>(this);
}
return new TransformedPathIterator<>(this, transform);
}
/** Replies a path iterator on this shape that is replacing the
* curves by line approximations.
*
* @return the iterator on the approximation.
* @see #getPathIterator()
* @see MathConstants#SPLINE_APPROXIMATION_RATIO
*/
@Pure
default PathIterator2ai<IE> getFlatteningPathIterator() {
return new FlatteningPathIterator<>(
getPathIterator(null),
MathConstants.SPLINE_APPROXIMATION_RATIO,
Path2ai.DEFAULT_FLATTENING_LIMIT);
}
@Pure
@Override
default Iterator<P> getPointIterator() {
final PathIterator2ai<IE> pathIterator = getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO);
return new PixelIterator<>(pathIterator, getGeomFactory());
}
@Pure
@Override
default Collection<P> toCollection() {
return new PointCollection<>(this);
}
/** Private API for Path2ai.
*
* @author $Author: sgalland$
* @version $Name$ $Revision$ $Date$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
final class PrivateAPI {
private PrivateAPI() {
//
}
/** Not documented.
*
* @param crossings the previous value of the crossing.
* @param rxmin x coordinate of the minimum corner.
* @param rymin y coordinate of the minimum corner.
* @param rxmax x coordinate of the maximum corner.
* @param rymax y coordinate of the maximum corner.
* @param curx x coordinate of the current point.
* @param cury y coordinate of the current point.
* @param movx x coordinate of the last move.
* @param movy y coordinate of the last mobe.
* @param intersectingBehavior <code>true</code> if the expected behavior is intersection,
* <code>false</code> for simple crossing computation.
* @return thr crossing.
*/
@Pure
@SuppressWarnings("checkstyle:parameternumber")
private static int crossingHelper(
int crossings,
int rxmin, int rymin,
int rxmax, int rymax,
int curx, int cury,
int movx, int movy,
boolean intersectingBehavior) {
int crosses = Segment2ai.calculatesCrossingsRectangleShadowSegment(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
if (!intersectingBehavior && crosses == MathConstants.SHAPE_INTERSECTS) {
final int x1 = rxmin + 1;
final int x2 = rxmax - 1;
final int y1 = rymin + 1;
final int y2 = rymax - 1;
crosses = Segment2ai.calculatesCrossingsRectangleShadowSegment(crossings,
x1, y1,
x2, y2,
curx, cury,
movx, movy);
}
return crosses;
}
}
/** An abstract path iterator.
*
* @param <E> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
abstract class AbstractPathIterator<E extends PathElement2ai> implements PathIterator2ai<E> {
/** Path.
*/
protected final Path2ai<?, ?, E, ?, ?, ?> path;
/**
* @param path the path.
*/
public AbstractPathIterator(Path2ai<?, ?, E, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
this.path = path;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@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 isMultiParts() {
return this.path.isMultiParts();
}
@Override
public boolean isPolygon() {
return this.path.isPolygon();
}
@Override
public GeomFactory2ai<E, ?, ?, ?> getGeomFactory() {
return this.path.getGeomFactory();
}
}
/** A path iterator that does not transform the coordinates.
*
* @param <E> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
@SuppressWarnings("checkstyle:magicnumber")
class PathPathIterator<E extends PathElement2ai> extends AbstractPathIterator<E> {
private final Point2D<?, ?> p1;
private final Point2D<?, ?> p2;
private int typeIndex;
private int coordIndex;
private int movex;
private int movey;
/**
* @param path the path.
*/
public PathPathIterator(Path2ai<?, ?, E, ?, ?, ?> path) {
super(path);
this.p1 = new InnerComputationPoint2ai();
this.p2 = new InnerComputationPoint2ai();
}
@Override
public PathIterator2ai<E> restartIterations() {
return new PathPathIterator<>(this.path);
}
@Override
public boolean hasNext() {
return this.typeIndex < this.path.getPathElementCount();
}
@Override
public E next() {
final int type = this.typeIndex;
if (this.typeIndex >= this.path.getPathElementCount()) {
throw new NoSuchElementException();
}
final E element;
switch (this.path.getPathElementTypeAt(type)) {
case MOVE_TO:
if ((this.coordIndex + 2) > (this.path.size() * 2)) {
throw new NoSuchElementException();
}
this.movex = this.path.getCoordAt(this.coordIndex++);
this.movey = this.path.getCoordAt(this.coordIndex++);
this.p2.set(this.movex, this.movey);
element = getGeomFactory().newMovePathElement(
this.p2.ix(), this.p2.iy());
break;
case LINE_TO:
if ((this.coordIndex + 2) > (this.path.size() * 2)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
this.p2.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
element = getGeomFactory().newLinePathElement(
this.p1.ix(), this.p1.iy(),
this.p2.ix(), this.p2.iy());
break;
case QUAD_TO:
if ((this.coordIndex + 4) > (this.path.size() * 2)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
final int ctrlx = this.path.getCoordAt(this.coordIndex++);
final int ctrly = this.path.getCoordAt(this.coordIndex++);
this.p2.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
element = getGeomFactory().newCurvePathElement(
this.p1.ix(), this.p1.iy(),
ctrlx, ctrly,
this.p2.ix(), this.p2.iy());
break;
case CURVE_TO:
if (this.coordIndex + 6 > (this.path.size() * 2)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
final int ctrlx1 = this.path.getCoordAt(this.coordIndex++);
final int ctrly1 = this.path.getCoordAt(this.coordIndex++);
final int ctrlx2 = this.path.getCoordAt(this.coordIndex++);
final int ctrly2 = this.path.getCoordAt(this.coordIndex++);
this.p2.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
element = getGeomFactory().newCurvePathElement(
this.p1.ix(), this.p1.iy(),
ctrlx1, ctrly1,
ctrlx2, ctrly2,
this.p2.ix(), this.p2.iy());
break;
case CLOSE:
this.p1.set(this.p2);
this.p2.set(this.movex, this.movey);
element = getGeomFactory().newClosePathElement(
this.p1.ix(), this.p1.iy(),
this.p2.ix(), this.p2.iy());
break;
case ARC_TO:
default:
throw new IllegalStateException();
}
if (element == null) {
throw new NoSuchElementException();
}
++this.typeIndex;
return element;
}
}
/** A path iterator that transforms the coordinates.
*
* @param <E> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
class TransformedPathIterator<E extends PathElement2ai> extends AbstractPathIterator<E> {
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 int movex;
private int movey;
/**
* @param path the path.
* @param transform the transformation.
*/
public TransformedPathIterator(Path2ai<?, ?, E, ?, ?, ?> path, Transform2D transform) {
super(path);
assert transform != null : AssertMessages.notNullParameter(1);
this.transform = transform;
this.p1 = new InnerComputationPoint2ai();
this.p2 = new InnerComputationPoint2ai();
this.ptmp1 = new InnerComputationPoint2ai();
this.ptmp2 = new InnerComputationPoint2ai();
}
@Override
public PathIterator2ai<E> restartIterations() {
return new TransformedPathIterator<>(this.path, this.transform);
}
@Override
public boolean hasNext() {
return this.typeIndex < this.path.getPathElementCount();
}
@Override
public E next() {
if (this.typeIndex >= this.path.getPathElementCount()) {
throw new NoSuchElementException();
}
final E element;
switch (this.path.getPathElementTypeAt(this.typeIndex++)) {
case MOVE_TO:
this.movex = this.path.getCoordAt(this.coordIndex++);
this.movey = this.path.getCoordAt(this.coordIndex++);
this.p2.set(this.movex, this.movey);
this.transform.transform(this.p2);
element = getGeomFactory().newMovePathElement(
this.p2.ix(), this.p2.iy());
break;
case LINE_TO:
this.p1.set(this.p2);
this.p2.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newLinePathElement(
this.p1.ix(), this.p1.iy(),
this.p2.ix(), this.p2.iy());
break;
case QUAD_TO:
this.p1.set(this.p2);
this.ptmp1.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp1);
this.p2.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newCurvePathElement(
this.p1.ix(), this.p1.iy(),
this.ptmp1.ix(), this.ptmp1.iy(),
this.p2.ix(), this.p2.iy());
break;
case CURVE_TO:
this.p1.set(this.p2);
this.ptmp1.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp1);
this.ptmp2.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp2);
this.p2.set(
this.path.getCoordAt(this.coordIndex++),
this.path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newCurvePathElement(
this.p1.ix(), this.p1.iy(),
this.ptmp1.ix(), this.ptmp1.iy(),
this.ptmp2.ix(), this.ptmp2.iy(),
this.p2.ix(), this.p2.iy());
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.ix(), this.p1.iy(),
this.p2.ix(), this.p2.iy());
break;
case ARC_TO:
default:
throw new IllegalStateException();
}
if (element == null) {
throw new NoSuchElementException();
}
return element;
}
}
/** Iterator on the pixels of the path.
*
* @param <P> the type of the points.
* @param <V> the type of the vectors.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
class PixelIterator<P extends Point2D<? super P, ? super V>,
V extends Vector2D<? super V, ? super P>> implements Iterator<P> {
private final PathIterator2ai<?> pathIterator;
private final GeomFactory2ai<?, P, V, ?> factory;
private Iterator<P> lineIterator;
private P next;
/**
* @param pi the iterator.
* @param factory the element factory.
*/
public PixelIterator(PathIterator2ai<?> pi, GeomFactory2ai<?, P, V, ?> factory) {
assert pi != null : AssertMessages.notNullParameter(0);
assert factory != null : AssertMessages.notNullParameter(1);
this.pathIterator = pi;
this.factory = factory;
searchNext();
}
private void searchNext() {
final P old = this.next;
this.next = null;
while (this.pathIterator.hasNext() && (this.lineIterator == null || !this.lineIterator.hasNext())) {
this.lineIterator = null;
final PathElement2ai elt = this.pathIterator.next();
if (elt.isDrawable()) {
switch (elt.getType()) {
case LINE_TO:
case CLOSE:
final Segment2ai<?, ?, ?, P, V, ?> segment = this.factory.newSegment(
elt.getFromX(), elt.getFromY(),
elt.getToX(), elt.getToY());
this.lineIterator = segment.getPointIterator();
break;
case MOVE_TO:
case CURVE_TO:
case QUAD_TO:
case ARC_TO:
default:
throw new IllegalStateException();
}
}
}
if (this.lineIterator != null && this.lineIterator.hasNext()) {
this.next = this.lineIterator.next();
while (this.next.equals(old)) {
this.next = this.lineIterator.next();
}
}
}
@Override
public boolean hasNext() {
return this.next != null;
}
@Override
public P next() {
final P n = this.next;
if (n == null) {
throw new NoSuchElementException();
}
searchNext();
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/** A path iterator that is flattening the path.
* This iterator was copied from AWT FlatteningPathIterator.
*
* @param <E> the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
@SuppressWarnings("checkstyle:magicnumber")
class FlatteningPathIterator<E extends PathElement2ai> implements PathIterator2ai<E> {
/** The source iterator.
*/
private final PathIterator2ai<E> 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 int lastNextX;
/** The y of the last move segment replied by next.
*/
private int 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(PathIterator2ai<E> pathIterator,
double flatness, int limit) {
assert pathIterator != null : AssertMessages.notNullParameter(0);
assert flatness > 0f : 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(true);
}
@Override
public PathIterator2ai<E> 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
* 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 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];
double ctrly1 = src[srcoff + 3];
x1 = (x1 + ctrlx1) / 2f;
y1 = (y1 + ctrly1) / 2f;
double ctrlx2 = src[srcoff + 4];
double ctrly2 = src[srcoff + 5];
x2 = (x2 + ctrlx2) / 2f;
y2 = (y2 + ctrly2) / 2f;
double centerx = (ctrlx1 + ctrlx2) / 2f;
double centery = (ctrly1 + ctrly2) / 2f;
ctrlx1 = (x1 + centerx) / 2f;
ctrly1 = (y1 + centery) / 2f;
ctrlx2 = (x2 + centerx) / 2f;
ctrly2 = (y2 + centery) / 2f;
centerx = (ctrlx1 + ctrlx2) / 2f;
centery = (ctrly1 + ctrly2) / 2f;
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;
}
}
private void searchNext(boolean isFirst) {
do {
flattening();
}
while (!this.done && !isFirst && isSame());
}
private boolean isSame() {
final PathElementType type = this.holdType;
final int x;
final int y;
if (type == PathElementType.CLOSE) {
x = (int) Math.round(this.moveX);
y = (int) Math.round(this.moveY);
} else {
x = (int) Math.round(this.hold[this.holdIndex + 0]);
y = (int) Math.round(this.hold[this.holdIndex + 1]);
}
return x == this.lastNextX && y == this.lastNextY;
}
@SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity"})
private void flattening() {
int level;
if (this.holdIndex >= this.holdEnd) {
if (!this.pathIterator.hasNext()) {
this.done = true;
return;
}
final PathElement2ai 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.hold[this.holdIndex + 4] = this.hold[2];
this.currentX = this.hold[2];
this.hold[this.holdIndex + 5] = this.hold[3];
this.currentY = this.hold[3];
}
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.hold[this.holdIndex + 6] = this.hold[4];
this.currentX = this.hold[4];
this.hold[this.holdIndex + 7] = this.hold[5];
this.currentY = this.hold[5];
}
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:
default:
throw new IllegalStateException();
}
}
@Override
public boolean hasNext() {
return !this.done;
}
@Override
public E next() {
if (this.done) {
throw new NoSuchElementException();
}
final E element;
final PathElementType type = this.holdType;
if (type != PathElementType.CLOSE) {
final int x = (int) Math.round(this.hold[this.holdIndex + 0]);
final int y = (int) Math.round(this.hold[this.holdIndex + 1]);
if (type == PathElementType.MOVE_TO) {
element = this.pathIterator.getGeomFactory().newMovePathElement(x, y);
} else {
element = this.pathIterator.getGeomFactory().newLinePathElement(
this.lastNextX, this.lastNextY,
x, y);
}
this.lastNextX = x;
this.lastNextY = y;
} else {
final int x = (int) Math.round(this.moveX);
final int y = (int) Math.round(this.moveY);
element = this.pathIterator.getGeomFactory().newClosePathElement(
this.lastNextX, this.lastNextY,
x, y);
this.lastNextX = x;
this.lastNextY = y;
}
searchNext(false);
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public PathWindingRule getWindingRule() {
return this.pathIterator.getWindingRule();
}
@Override
public boolean isPolyline() {
return !isMultiParts() && !isPolygon();
}
@Override
public boolean isCurved() {
// Because the iterator flats the path, this is no curve inside.
return false;
}
@Override
public boolean isMultiParts() {
return this.pathIterator.isMultiParts();
}
@Override
public boolean isPolygon() {
return this.pathIterator.isPolygon();
}
@Override
public GeomFactory2ai<E, ?, ?, ?> getGeomFactory() {
return this.pathIterator.getGeomFactory();
}
}
}