/*
* $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.d3.afp;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.MathUtil;
import org.arakhne.afc.math.Unefficient;
import org.arakhne.afc.math.geometry.CrossingComputationType;
import org.arakhne.afc.math.geometry.PathElementType;
import org.arakhne.afc.math.geometry.PathWindingRule;
import org.arakhne.afc.math.geometry.d3.Path3D;
import org.arakhne.afc.math.geometry.d3.PathIterator3D;
import org.arakhne.afc.math.geometry.d3.Point3D;
import org.arakhne.afc.math.geometry.d3.Transform3D;
import org.arakhne.afc.math.geometry.d3.Vector3D;
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$
* @author $Author: tpiotrow$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
public interface Path3afp<
ST extends Shape3afp<?, ?, IE, P, V, B>,
IT extends Path3afp<?, ?, IE, P, V, B>,
IE extends PathElement3afp,
P extends Point3D<? super P, ? super V>,
V extends Vector3D<? super V, ? super P>,
B extends RectangularPrism3afp<?, ?, IE, P, V, B>>
extends Shape3afp<ST, IT, IE, P, V, B>, Path3D<ST, IT, PathIterator3afp<IE>, P, V, B> {
/**
* Multiple of cubic & quad curve size.
*/
int GROW_SIZE = 24;
/**
* The default flatening depth limit.
*/
int DEFAULT_FLATENING_LIMIT = 10;
/**
* The default winding rule: {@link PathWindingRule#NON_ZERO}.
*/
PathWindingRule DEFAULT_WINDING_RULE = PathWindingRule.NON_ZERO;
/**
* Accumulate the number of times the path crosses the shadow extending to the right of the second path. See the comment for
* the SHAPE_INTERSECTS constant for more complete details. The return value is the sum of all crossings for both the top and
* bottom of the shadow for every segment in the path, or the special value SHAPE_INTERSECTS if the path ever enters the
* interior of the rectangle. The path must start with a SEG_MOVETO, otherwise an exception is thrown. The caller must check
* r[xy]{min,max} for NaN values.
*
* @param crossings is the initial value for crossing.
* @param iterator is the iterator on the path elements.
* @param shadow is the description of the shape to project to the right.
* @param type is the type of special computation to apply. If <code>null</code>, it is equivalent to
* {@link CrossingComputationType#STANDARD}.
* @return the crossings.
* @see "Weiler–Atherton clipping algorithm"
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
static int computeCrossingsFromPath(int crossings, PathIterator3afp<?> iterator, BasicPathShadow3afp shadow,
CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert shadow != null : AssertMessages.notNullParameter(2);
if (!iterator.hasNext()) {
return 0;
}
PathElement3afp pathElement1 = iterator.next();
if (pathElement1.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory3afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path3afp<?, ?, ?, ?, ?, ?> subPath;
double movx = pathElement1.getToX();
double curx = movx;
double movy = pathElement1.getToY();
double cury = movy;
double movz = pathElement1.getToZ();
double curz = movz;
int numCrossings = crossings;
double endx;
double endy;
double endz;
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;
movz = pathElement1.getToZ();
curz = movz;
break;
case LINE_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
endz = pathElement1.getToZ();
numCrossings = shadow.computeCrossings(numCrossings, curx, cury, curz, endx, endy, endz);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
curz = endz;
break;
case QUAD_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
endz = pathElement1.getToZ();
// only for local use.
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury, curz);
subPath.quadTo(pathElement1.getCtrlX1(), pathElement1.getCtrlY1(), pathElement1.getCtrlZ1(), endx, endy, endz);
numCrossings = computeCrossingsFromPath(numCrossings,
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), shadow,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CURVE_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
endz = pathElement1.getToZ();
// only for local use
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury, curz);
subPath.curveTo(pathElement1.getCtrlX1(), pathElement1.getCtrlY1(), pathElement1.getCtrlZ1(),
pathElement1.getCtrlX2(), pathElement1.getCtrlY2(), pathElement1.getCtrlZ2(), endx, endy, endz);
numCrossings = computeCrossingsFromPath(numCrossings,
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), shadow,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CLOSE:
if (curx != movx || cury != movy || curz != movz) {
numCrossings = shadow.computeCrossings(numCrossings, curx, cury, curz, movx, movy, movz);
}
// Stop as soon as possible
if (numCrossings != 0) {
return numCrossings;
}
curx = movx;
cury = movy;
curz = movz;
break;
case ARC_TO:
default:
}
}
assert numCrossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy) || (curz != movz);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
// Not closed
numCrossings = shadow.computeCrossings(numCrossings, curx, cury, curz, movx, movy, movz);
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;
}
/**
* Replies the point on the path that is closest to the given point.
*
* <p><strong>CAUTION:</strong> This function works only on path iterators that are replying not-curved primitives, ie. if the
* {@link PathIterator3D#isCurved()} of {@code pi} is replying
* <code>false</code>.
* {@link #getClosestPointTo(Point3D)}
* 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 z z coordinate of the point.
* @param result the closest point on the shape; or the point itself if it is inside the shape.
*/
@SuppressWarnings("checkstyle:magicnumber")
static void getClosestPointTo(PathIterator3afp<? extends PathElement3afp> pi, double x, double y, double z,
Point3D<?, ?> result) {
assert pi != null : AssertMessages.notNullParameter(0);
assert !pi.isCurved() : AssertMessages.invalidTrueValue("isCurved"); //$NON-NLS-1$
assert result != null : AssertMessages.notNullParameter(4);
double bestDist = Double.POSITIVE_INFINITY;
PathElement3afp pe;
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1;
int crossings = 0;
while (pi.hasNext()) {
pe = pi.next();
boolean foundCandidate = false;
double candidateX = Double.NaN;
double candidateY = Double.NaN;
double candidateZ = Double.NaN;
switch (pe.getType()) {
case MOVE_TO:
foundCandidate = true;
candidateX = pe.getToX();
candidateY = pe.getToY();
candidateZ = pe.getToZ();
break;
case LINE_TO:
double factor = Segment3afp.computeProjectedPointOnLine(x, y, z, pe.getFromX(), pe.getFromY(), pe.getFromZ(),
pe.getToX(), pe.getToY(), pe.getToZ());
factor = MathUtil.clamp(factor, 0, 1);
foundCandidate = true;
double vx = (pe.getToX() - pe.getFromX()) * factor;
double vy = (pe.getToY() - pe.getFromY()) * factor;
double vz = (pe.getToZ() - pe.getFromZ()) * factor;
candidateX = pe.getFromX() + vx;
candidateY = pe.getFromY() + vy;
candidateZ = pe.getFromZ() + vz;
crossings += Segment3afp.computeCrossingsFromPoint(x, y, z, pe.getFromX(), pe.getFromY(), pe.getFromZ(),
pe.getToX(), pe.getToY(), pe.getToZ());
break;
case CLOSE:
crossings += Segment3afp.computeCrossingsFromPoint(x, y, z, pe.getFromX(), pe.getFromY(), pe.getFromZ(),
pe.getToX(), pe.getToY(), pe.getToZ());
if ((crossings & mask) != 0) {
result.set(x, y, z);
return;
}
if (!pe.isEmpty()) {
factor = Segment3afp.computeProjectedPointOnLine(x, y, z, pe.getFromX(), pe.getFromY(), pe.getFromZ(),
pe.getToX(), pe.getToY(), pe.getToZ());
factor = MathUtil.clamp(factor, 0, 1);
vx = (pe.getToX() - pe.getFromX()) * factor;
vy = (pe.getToY() - pe.getFromY()) * factor;
vz = (pe.getToZ() - pe.getFromZ()) * factor;
foundCandidate = true;
candidateX = pe.getFromX() + vx;
candidateY = pe.getFromY() + vy;
candidateZ = pe.getFromY() + vz;
}
crossings = 0;
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
default:
throw new IllegalStateException(pe.getType().toString());
}
if (foundCandidate) {
final double d = Point3D.getDistanceSquaredPointPoint(x, y, z, candidateX, candidateY, candidateZ);
if (d < bestDist) {
bestDist = d;
result.set(candidateX, candidateY, candidateZ);
}
}
}
}
/** 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 PathIterator3D#isCurved()} of {@code pi} is replying
* <code>false</code>.
* {@link #getClosestPointTo(org.arakhne.afc.math.geometry.d3.Shape3D)} avoids this restriction.
*
* @param pi is the iterator of path elements, on one of which the closest point is located.
* @param shape the shape to which the closest point must be computed.
* @param result the closest point on pi.
* @return <code>true</code> if a point was found. Otherwise <code>false</code>.
*/
@SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"})
@Unefficient
static boolean getClosestPointTo(PathIterator3afp<? extends PathElement3afp> pi,
PathIterator3afp<? extends PathElement3afp> shape, Point3D<?, ?> 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;
}
PathElement3afp 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 RectangularPrism3afp<?, ?, ?, ?, ?, ?> box = pi.getGeomFactory().newBox();
computeDrawableElementBoundingBox(shape.restartIterations(), box);
final ClosestPointPathShadow3afp shadow = new ClosestPointPathShadow3afp(shape.restartIterations(), box);
int crossings = 0;
double curx = pathElement1.getToX();
double movx = curx;
double cury = pathElement1.getToY();
double movy = cury;
double curz = pathElement1.getToZ();
double movz = curz;
double endx;
double endy;
double endz;
while (pi.hasNext()) {
pathElement1 = pi.next();
switch (pathElement1.getType()) {
case MOVE_TO:
movx = pathElement1.getToX();
curx = movx;
movy = pathElement1.getToY();
cury = movy;
movz = pathElement1.getToZ();
curz = movz;
break;
case LINE_TO:
endx = pathElement1.getToX();
endy = pathElement1.getToY();
endz = pathElement1.getToZ();
crossings = shadow.computeCrossings(crossings, curx, cury, curz, endx, endy, endz);
if (crossings == MathConstants.SHAPE_INTERSECTS) {
result.set(shadow.getClosestPointInOtherShape());
return true;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CLOSE:
if (curx != movx || cury != movy || curz != movz) {
crossings = shadow.computeCrossings(crossings, curx, cury, curz, movx, movy, movz);
if (crossings == MathConstants.SHAPE_INTERSECTS) {
result.set(shadow.getClosestPointInOtherShape());
return true;
}
}
curx = movx;
cury = movy;
curz = movz;
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
default:
throw new IllegalArgumentException();
}
}
if (curx == movx && cury == movy && curz == movz) {
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;
}
@Pure
@Override
default P getClosestPointTo(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
Path3afp.getClosestPointTo(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), pt.getX(), pt.getY(), pt.getZ(),
point);
return point;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Sphere3afp<?, ?, ?, ?, ?, ?> sphere) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path3afp.getClosestPointTo(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
sphere.getCenterX(), sphere.getCenterY(), sphere.getCenterZ(), result);
} else {
Path3afp.getClosestPointTo(getPathIterator(), sphere.getCenterX(), sphere.getCenterY(), sphere.getCenterZ(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(RectangularPrism3afp<?, ?, ?, ?, ?, ?> rectangularPrism) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path3afp.getClosestPointTo(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
rectangularPrism.getPathIterator(), result);
} else {
Path3afp.getClosestPointTo(getPathIterator(), rectangularPrism.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Segment3afp<?, ?, ?, ?, ?, ?> segment) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path3afp.getClosestPointTo(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
segment.getPathIterator(), result);
} else {
Path3afp.getClosestPointTo(getPathIterator(), segment.getPathIterator(), result);
}
return result;
}
@Pure
@Unefficient
@Override
default P getClosestPointTo(Path3afp<?, ?, ?, ?, ?, ?> path) {
final P result = getGeomFactory().newPoint();
if (isCurved()) {
Path3afp.getClosestPointTo(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
path.getPathIterator(), result);
} else {
Path3afp.getClosestPointTo(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 not-curved primitives, ie. if the
* {@link PathIterator3D#isCurved()} of {@code pi} is replying <code>false</code>. {@link #getFarthestPointTo(Point3D)}
* 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 z z coordinate of the point.
* @param result the fartheset point on the shape.
*/
@SuppressWarnings("checkstyle:magicnumber")
static void getFarthestPointTo(PathIterator3afp<? extends PathElement3afp> pi, double x, double y, double z,
Point3D<?, ?> result) {
assert pi != null : AssertMessages.notNullParameter(0);
assert !pi.isCurved() : AssertMessages.invalidTrueValue("isCurved"); //$NON-NLS-1$
assert result != null : AssertMessages.notNullParameter(4);
double bestDist = Double.NEGATIVE_INFINITY;
PathElement3afp pe;
// Only for internal use.
final Point3D<?, ?> point = new InnerComputationPoint3afp();
while (pi.hasNext()) {
pe = pi.next();
switch (pe.getType()) {
case MOVE_TO:
break;
case LINE_TO:
case CLOSE:
Segment3afp.computeFarthestPointTo(pe.getFromX(), pe.getFromY(), pe.getFromZ(), pe.getToX(), pe.getToY(),
pe.getToZ(), x, y, z, point);
final double d = Point3D.getDistanceSquaredPointPoint(x, y, z, point.getX(), point.getY(), point.getZ());
if (d > bestDist) {
bestDist = d;
result.set(point.getX(), point.getY(), point.getZ());
}
break;
case QUAD_TO:
case CURVE_TO:
case ARC_TO:
default:
throw new IllegalStateException(pe.getType().toString());
}
}
}
@Pure
@Override
default P getFarthestPointTo(Point3D<?, ?> pt) {
assert pt != null : AssertMessages.notNullParameter();
final P point = getGeomFactory().newPoint();
Path3afp.getFarthestPointTo(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), pt.getX(), pt.getY(), pt.getZ(),
point);
return point;
}
/**
* Tests if the specified coordinates are inside the closed boundary of the specified {@link PathIterator3afp}.
*
* <p>This method provides a basic facility for implementors of the {@link Shape3afp} interface to implement support for the
* {@link Shape3afp#contains(double, double, double)} method.
*
* @param pi
* the specified {@code PathIterator2f}
* @param x
* the specified X coordinate
* @param y
* the specified Y coordinate
* @param z
* the specified Z coordinate
* @return {@code true} if the specified coordinates are inside the specified {@code PathIterator2f}; {@code false} otherwise
*/
static boolean containsPoint(PathIterator3afp<? extends PathElement3afp> pi, double x, double y, double z) {
assert pi != null : AssertMessages.notNullParameter(0);
// Copied from the AWT API
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1;
final int cross = computeCrossingsFromPoint(0, pi, x, y, z, CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return (cross & mask) != 0;
}
/**
* Tests if the specified rectangle is inside the closed boundary of the specified {@link PathIterator3afp}.
*
* <p>This method provides a basic facility for implementors of the {@link Shape3afp} interface to implement support for the
* {@link Shape3afp#contains(RectangularPrism3afp)} method.
*
* @param pi
* the specified {@code PathIterator2f}
* @param rx
* the lowest corner of the rectangle.
* @param ry
* the lowest corner of the rectangle.
* @param rz
* the lowest corner of the rectangle.
* @param rwidth
* is the width of the rectangle.
* @param rheight
* is the width of the rectangle.
* @param rdepth
* is the depth 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(PathIterator3afp<? extends PathElement3afp> pi, double rx, double ry, double rz,
double rwidth, double rheight, double rdepth) {
assert pi != null : AssertMessages.notNullParameter(0);
assert rwidth >= 0. : AssertMessages.positiveOrZeroParameter(4);
assert rheight >= 0. : AssertMessages.positiveOrZeroParameter(5);
assert rdepth >= 0. : AssertMessages.positiveOrZeroParameter(6);
// Copied from AWT API
if (rwidth <= 0 || rheight <= 0 || rdepth <= 0) {
return false;
}
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = computeCrossingsFromRect(0, pi, rx, ry, rz, rx + rwidth, ry + rheight, rz + rdepth,
CrossingComputationType.AUTO_CLOSE);
return crossings != MathConstants.SHAPE_INTERSECTS && (crossings & mask) != 0;
}
/**
* Tests if the interior of the specified {@link PathIterator3afp} intersects the interior of a specified set of rectangular
* coordinates.
*
* @param pi
* the specified {@link PathIterator3afp}.
* @param x
* the specified X coordinate of the rectangle.
* @param y
* the specified Y coordinate of the rectangle.
* @param z
* the specified Y coordinate of the rectangle.
* @param width
* the width of the specified rectangular coordinates.
* @param height
* the height of the specified rectangular coordinates.
* @param depth
* the depth of the specified rectangular coordinates.
* @return <code>true</code> if the specified {@link PathIterator3afp} and the interior of the specified set of rectangular
* coordinates intersect each other; <code>false</code> otherwise.
*/
@SuppressWarnings("checkstyle:magicnumber")
static boolean intersectsPathIteratorRectangle(PathIterator3afp<? extends PathElement3afp> pi, double x, double y, double z,
double width, double height, double depth) {
assert pi != null : AssertMessages.notNullParameter(0);
assert width >= 0. : AssertMessages.positiveOrZeroParameter(4);
assert height >= 0. : AssertMessages.positiveOrZeroParameter(5);
assert depth >= 0. : AssertMessages.positiveOrZeroParameter(6);
if (width <= 0 || height <= 0 || depth <= 0) {
return false;
}
final int mask = pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = computeCrossingsFromRect(0, pi, x, y, z, x + width, y + height, z + depth,
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0;
}
/**
* Calculates the number of times the given path crosses the ray extending to the right from (px,py). If the point lies on a
* part of the path, then no crossings are counted for that intersection. +1 is added for each crossing where the Y coordinate
* is increasing -1 is added for each crossing where the Y coordinate is decreasing The return value is the sum of all
* crossings for every segment in the path. The path must start with a MOVE_TO, otherwise an exception is thrown.
*
* @param crossings
* is the initial value for crossing.
* @param iterator
* is the description of the path.
* @param px
* is the reference point to test.
* @param py
* is the reference point to test.
* @param pz
* is the reference point to test.
* @param type
* is the type of special computation to apply. If <code>null</code>, it is equivalent to
* {@link CrossingComputationType#STANDARD}.
* @return the crossing
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity", "checkstyle:returncount"})
static int computeCrossingsFromPoint(int crossings, PathIterator3afp<? extends PathElement3afp> iterator, double px,
double py, double pz, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
// Copied from the AWT API
if (!iterator.hasNext()) {
return 0;
}
PathElement3afp element;
element = iterator.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory3afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path3afp<?, ?, ?, ?, ?, ?> subPath;
double movx = element.getToX();
double movy = element.getToY();
double movz = element.getToZ();
double curx = movx;
double cury = movy;
double curz = movz;
double endx;
double endy;
double endz;
int numCrossings = crossings;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
movz = element.getToZ();
curz = movz;
break;
case LINE_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
if (endx == px && endy == py && endz == pz) {
return MathConstants.SHAPE_INTERSECTS;
}
numCrossings += Segment3afp.computeCrossingsFromPoint(px, py, pz, curx, cury, curz, endx, endy, endz);
curx = endx;
cury = endy;
curz = endz;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
if (endx == px && endy == py && endz == pz) {
return MathConstants.SHAPE_INTERSECTS;
}
// For internal use only
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury, curz);
subPath.quadTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), endx, endy, endz);
numCrossings = computeCrossingsFromPoint(numCrossings,
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), px, py, pz,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CURVE_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
if (endx == px && endy == py && endz == pz) {
return MathConstants.SHAPE_INTERSECTS;
}
// For internal use only
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury, curz);
subPath.curveTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), element.getCtrlX2(),
element.getCtrlY2(), element.getCtrlZ2(), endx, endy, endz);
numCrossings = computeCrossingsFromPoint(numCrossings,
subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), px, py, pz,
CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CLOSE:
if (cury != movy || curx != movx || curz != movz) {
if (movx == px && movy == py && movz == pz) {
return MathConstants.SHAPE_INTERSECTS;
}
numCrossings += Segment3afp.computeCrossingsFromPoint(px, py, pz, curx, cury, curz, movx, movy, movz);
}
curx = movx;
cury = movy;
curz = movz;
break;
case ARC_TO:
default:
}
}
assert numCrossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy) || (curz != movz);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
// Not closed
if (movx == px && movy == py && movz == pz) {
return MathConstants.SHAPE_INTERSECTS;
}
numCrossings += Segment3afp.computeCrossingsFromPoint(px, py, pz, curx, cury, curz, movx, movy, movz);
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;
}
/**
* Calculates the number of times the given path crosses the given circle extending to the right.
*
* @param crossings
* is the initial value for crossing.
* @param iterator
* is the description of the path.
* @param cx
* is the center of the circle.
* @param cy
* is the center of the circle.
* @param cz
* is the center of the circle.
* @param radius
* is the radius of the circle.
* @param type
* is the type of special computation to apply. If <code>null</code>, it is equivalent to
* {@link CrossingComputationType#STANDARD}.
* @return the crossing
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity", "checkstyle:magicnumber"})
static int computeCrossingsFromSphere(int crossings, PathIterator3afp<? extends PathElement3afp> iterator, double cx,
double cy, double cz, double radius, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert radius >= 0. : AssertMessages.positiveOrZeroParameter(5);
// Copied from the AWT API
if (!iterator.hasNext()) {
return 0;
}
PathElement3afp element;
element = iterator.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory3afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path3afp<?, ?, ?, ?, ?, ?> localPath;
double movx = element.getToX();
double movy = element.getToY();
double movz = element.getToZ();
double curx = movx;
double cury = movy;
double curz = movz;
double endx;
double endy;
double endz;
int numCrosses = crossings;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
movz = element.getToZ();
curz = movz;
break;
case LINE_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
numCrosses = Segment3afp.computeCrossingsFromSphere(numCrosses, cx, cy, cz, radius, curx, cury, curz, endx, endy,
endz);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
curz = endz;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(element.getFromX(), element.getFromY(), element.getFromZ());
localPath.quadTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), endx, endy, endz);
numCrosses = computeCrossingsFromSphere(numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), cx, cy, cz, radius,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CURVE_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(element.getFromX(), element.getFromY(), element.getFromZ());
localPath.curveTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), element.getCtrlX2(),
element.getCtrlY2(), element.getCtrlZ2(), endx, endy, endz);
numCrosses = computeCrossingsFromSphere(numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), cx, cy, cz, radius,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CLOSE:
if (cury != movy || curx != movx || curz != movz) {
numCrosses = Segment3afp.computeCrossingsFromSphere(numCrosses, cx, cy, cz, radius, curx, cury, curz, movx,
movy, movz);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
}
curx = movx;
cury = movy;
curz = movz;
break;
case ARC_TO:
default:
}
}
assert numCrosses != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy) || (curz != movz);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
// Auto close
numCrosses = Segment3afp.computeCrossingsFromSphere(numCrosses, cx, cy, cz, radius, curx, cury, curz, movx, movy,
movz);
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 given segment extending to the right.
*
* @param crossings
* is the initial value for crossing.
* @param iterator
* is the description of the path.
* @param x1
* is the first point of the segment.
* @param y1
* is the first point of the segment.
* @param z1
* 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 z2
* 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:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity"})
static int computeCrossingsFromSegment(int crossings, PathIterator3afp<? extends PathElement3afp> iterator, double x1,
double y1, double z1, double x2, double y2, double z2, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
// Copied from the AWT API
if (!iterator.hasNext() || crossings == MathConstants.SHAPE_INTERSECTS) {
return crossings;
}
PathElement3afp element;
element = iterator.next();
if (element.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory3afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path3afp<?, ?, ?, ?, ?, ?> localPath;
double movx = element.getToX();
double movy = element.getToY();
double movz = element.getToZ();
double curx = movx;
double cury = movy;
double curz = movz;
double endx;
double endy;
double endz;
int numCrosses = crossings;
while (numCrosses != MathConstants.SHAPE_INTERSECTS && iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
movx = element.getToX();
curx = movx;
movy = element.getToY();
cury = movy;
movz = element.getToZ();
curz = movz;
break;
case LINE_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
numCrosses = Segment3afp.computeCrossingsFromSegment(numCrosses, x1, y1, z1, x2, y2, z2, curx, cury, curz, endx,
endy, endz);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
curz = endz;
break;
case QUAD_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury, curz);
localPath.quadTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), endx, endy, endz);
numCrosses = computeCrossingsFromSegment(numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), x1, y1, z1, x2, y2, z2,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CURVE_TO:
endx = element.getToX();
endy = element.getToY();
endz = element.getToZ();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury, curz);
localPath.curveTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), element.getCtrlX2(),
element.getCtrlY2(), element.getCtrlZ2(), endx, endy, endz);
numCrosses = computeCrossingsFromSegment(numCrosses,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), x1, y1, z1, x2, y2, z2,
CrossingComputationType.STANDARD);
if (numCrosses == MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CLOSE:
if (cury != movy || curx != movx || curz != movz) {
numCrosses = Segment3afp.computeCrossingsFromSegment(numCrosses, x1, y1, z1, x2, y2, z2, curx, cury, curz,
movx, movy, movz);
}
if (numCrosses != 0) {
return numCrosses;
}
curx = movx;
cury = movy;
curz = movz;
break;
case ARC_TO:
default:
}
}
assert numCrosses != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy) || (curz != movz);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
numCrosses = Segment3afp.computeCrossingsFromSegment(numCrosses, x1, y1, z1, x2, y2, z2, curx, cury, curz, movx,
movy, movz);
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;
}
/**
* Accumulate the number of times the path crosses the shadow extending to the right of the rectangle. See the comment for the
* SHAPE_INTERSECTS constant for more complete details. The return value is the sum of all crossings for both the top and
* bottom of the shadow for every segment in the path, or the special value SHAPE_INTERSECTS if the path ever enters the
* interior of the rectangle. The path must start with a SEG_MOVETO, otherwise an exception is thrown. The caller must check
* r[xy]{min,max} for NaN values.
*
* @param crossings
* is the initial value for crossing.
* @param iterator
* is the iterator on the path elements.
* @param rxmin
* is the first corner of the rectangle.
* @param rymin
* is the first corner of the rectangle.
* @param rzmin
* 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 rzmax
* 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:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity", "checkstyle:magicnumber"})
static int computeCrossingsFromRect(int crossings, PathIterator3afp<? extends PathElement3afp> iterator, double rxmin,
double rymin, double rzmin, double rxmax, double rymax, double rzmax, CrossingComputationType type) {
assert iterator != null : AssertMessages.notNullParameter(1);
assert rxmin <= rxmax : AssertMessages.lowerEqualParameters(2, rxmin, 5, rxmax);
assert rymin <= rymax : AssertMessages.lowerEqualParameters(3, rymin, 6, rymax);
assert rzmin <= rzmax : AssertMessages.lowerEqualParameters(4, rzmin, 7, rzmax);
// Copied from AWT API
if (!iterator.hasNext()) {
return 0;
}
PathElement3afp pathElement = iterator.next();
if (pathElement.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
final GeomFactory3afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path3afp<?, ?, ?, ?, ?, ?> localPath;
double movx = pathElement.getToX();
double curx = movx;
double movy = pathElement.getToY();
double cury = movy;
double movz = pathElement.getToZ();
double curz = movz;
int numCrossings = crossings;
double endx;
double endy;
double endz;
while (numCrossings != MathConstants.SHAPE_INTERSECTS && iterator.hasNext()) {
pathElement = iterator.next();
switch (pathElement.getType()) {
case MOVE_TO:
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
movx = pathElement.getToX();
curx = movx;
movy = pathElement.getToY();
cury = movy;
movz = pathElement.getToZ();
curz = movz;
break;
case LINE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
endz = pathElement.getToZ();
numCrossings = Segment3afp.computeCrossingsFromRect(numCrossings, rxmin, rymin, rzmin, rxmax, rymax, rzmax, curx,
cury, curz, endx, endy, endz);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
curz = endz;
break;
case QUAD_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
endz = pathElement.getToZ();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury, curz);
localPath.quadTo(pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz);
numCrossings = computeCrossingsFromRect(numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), rxmin, rymin, rzmin, rxmax, rymax,
rzmax, CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CURVE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
endz = pathElement.getToZ();
// for internal use only
localPath = factory.newPath(iterator.getWindingRule());
localPath.moveTo(curx, cury, curz);
localPath.curveTo(pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(),
pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz);
numCrossings = computeCrossingsFromRect(numCrossings,
localPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), rxmin, rymin, rzmin, rxmax, rymax,
rzmax, CrossingComputationType.STANDARD);
if (numCrossings == MathConstants.SHAPE_INTERSECTS) {
return numCrossings;
}
curx = endx;
cury = endy;
curz = endz;
break;
case CLOSE:
if (curx != movx || cury != movy || curz != movz) {
numCrossings = Segment3afp.computeCrossingsFromRect(numCrossings, rxmin, rymin, rzmin, rxmax, rymax, rzmax,
curx, cury, curz, movx, movy, movz);
}
// Stop as soon as possible
if (numCrossings != 0) {
return numCrossings;
}
curx = movx;
cury = movy;
curz = movz;
break;
case ARC_TO:
default:
}
}
assert numCrossings != MathConstants.SHAPE_INTERSECTS;
final boolean isOpen = (curx != movx) || (cury != movy) || (curz != movz);
if (isOpen && type != null) {
switch (type) {
case AUTO_CLOSE:
// Not closed
numCrossings = Segment3afp.computeCrossingsFromRect(numCrossings, rxmin, rymin, rzmin, rxmax, rymax, rzmax, curx,
cury, curz, movx, movy, movz);
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;
}
/**
* Compute the box that corresponds to the drawable elements of the path.
*
* <p>An element is drawable if it is a line, a curve, or a closing path element. The box fits the drawn lines and the drawn
* curves. The control points of the curves may be outside the output box. For obtaining the bounding box of the path's
* points, use {@link #computeControlPointBoundingBox(PathIterator3afp, RectangularPrism3afp)}.
*
* @param iterator
* the iterator on the path elements.
* @param box
* the box to set.
* @return <code>true</code> if a drawable element was found.
* @see #computeControlPointBoundingBox(PathIterator3afp, RectangularPrism3afp)
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity"})
static boolean computeDrawableElementBoundingBox(PathIterator3afp<?> iterator, RectangularPrism3afp<?, ?, ?, ?, ?, ?> box) {
assert iterator != null : AssertMessages.notNullParameter(0);
assert box != null : AssertMessages.notNullParameter(1);
final GeomFactory3afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
boolean foundOneLine = false;
double xmin = Double.POSITIVE_INFINITY;
double ymin = Double.POSITIVE_INFINITY;
double zmin = Double.POSITIVE_INFINITY;
double xmax = Double.NEGATIVE_INFINITY;
double ymax = Double.NEGATIVE_INFINITY;
double zmax = Double.NEGATIVE_INFINITY;
PathElement3afp element;
Path3afp<?, ?, ?, ?, ?, ?> subPath;
RectangularPrism3afp<?, ?, ?, ?, ?, ?> subBox;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case LINE_TO:
if (element.getFromX() < xmin) {
xmin = element.getFromX();
}
if (element.getFromY() < ymin) {
ymin = element.getFromY();
}
if (element.getFromZ() < zmin) {
zmin = element.getFromZ();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getFromZ() > zmax) {
zmax = element.getFromZ();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToZ() < zmin) {
zmin = element.getToZ();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
if (element.getToZ() > zmax) {
zmax = element.getToZ();
}
foundOneLine = true;
break;
case CURVE_TO:
subPath = factory.newPath(iterator.getWindingRule());
subBox = factory.newBox();
subPath.moveTo(element.getFromX(), element.getFromY(), element.getFromZ());
subPath.curveTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), element.getCtrlX2(),
element.getCtrlY2(), element.getCtrlZ2(), element.getToX(), element.getToY(), element.getToZ());
if (computeDrawableElementBoundingBox(subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
subBox)) {
if (subBox.getMinX() < xmin) {
xmin = subBox.getMinX();
}
if (subBox.getMinY() < ymin) {
ymin = subBox.getMinY();
}
if (subBox.getMinZ() < zmin) {
zmin = subBox.getMinZ();
}
if (subBox.getMaxX() > xmax) {
xmax = subBox.getMaxX();
}
if (subBox.getMaxY() > ymax) {
ymax = subBox.getMaxY();
}
if (subBox.getMaxZ() > zmax) {
zmax = subBox.getMaxZ();
}
foundOneLine = true;
}
break;
case QUAD_TO:
subPath = factory.newPath(iterator.getWindingRule());
subBox = factory.newBox();
subPath.moveTo(element.getFromX(), element.getFromY(), element.getFromZ());
subPath.quadTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), element.getToX(), element.getToY(),
element.getToZ());
if (computeDrawableElementBoundingBox(subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO),
subBox)) {
if (subBox.getMinX() < xmin) {
xmin = subBox.getMinX();
}
if (subBox.getMinY() < ymin) {
ymin = subBox.getMinY();
}
if (subBox.getMinZ() < zmin) {
zmin = subBox.getMinZ();
}
if (subBox.getMaxX() > xmax) {
xmax = subBox.getMaxX();
}
if (subBox.getMaxY() > ymax) {
ymax = subBox.getMaxY();
}
if (subBox.getMaxZ() > zmax) {
zmax = subBox.getMaxZ();
}
foundOneLine = true;
}
break;
case MOVE_TO:
case CLOSE:
case ARC_TO:
default:
}
}
if (foundOneLine) {
box.setFromCorners(xmin, ymin, zmin, xmax, ymax, zmax);
} 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 #computeDrawableElementBoundingBox(PathIterator3afp, RectangularPrism3afp)}.
*
* @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 #computeDrawableElementBoundingBox(PathIterator3afp, RectangularPrism3afp)
*/
@SuppressWarnings({"checkstyle:parameternumber", "checkstyle:cyclomaticcomplexity",
"checkstyle:npathcomplexity"})
static boolean computeControlPointBoundingBox(PathIterator3afp<?> iterator, RectangularPrism3afp<?, ?, ?, ?, ?, ?> box) {
assert iterator != null : AssertMessages.notNullParameter(0);
assert box != null : AssertMessages.notNullParameter(1);
boolean foundOneControlPoint = false;
double xmin = Double.POSITIVE_INFINITY;
double ymin = Double.POSITIVE_INFINITY;
double zmin = Double.POSITIVE_INFINITY;
double xmax = Double.NEGATIVE_INFINITY;
double ymax = Double.NEGATIVE_INFINITY;
double zmax = Double.NEGATIVE_INFINITY;
PathElement3afp 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.getFromZ() < zmin) {
zmin = element.getFromZ();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getFromZ() > zmax) {
zmax = element.getFromZ();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToZ() < zmin) {
zmin = element.getToZ();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
if (element.getToZ() > zmax) {
zmax = element.getToZ();
}
foundOneControlPoint = true;
break;
case CURVE_TO:
if (element.getFromX() < xmin) {
xmin = element.getFromX();
}
if (element.getFromY() < ymin) {
ymin = element.getFromY();
}
if (element.getFromZ() < zmin) {
zmin = element.getFromZ();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getFromZ() > zmax) {
zmax = element.getFromZ();
}
if (element.getCtrlX1() < xmin) {
xmin = element.getCtrlX1();
}
if (element.getCtrlY1() < ymin) {
ymin = element.getCtrlY1();
}
if (element.getCtrlZ1() < zmin) {
zmin = element.getCtrlZ1();
}
if (element.getCtrlX1() > xmax) {
xmax = element.getCtrlX1();
}
if (element.getCtrlY1() > ymax) {
ymax = element.getCtrlY1();
}
if (element.getCtrlZ1() > zmax) {
zmax = element.getCtrlZ1();
}
if (element.getCtrlX2() < xmin) {
xmin = element.getCtrlX2();
}
if (element.getCtrlY2() < ymin) {
ymin = element.getCtrlY2();
}
if (element.getCtrlZ2() < zmin) {
zmin = element.getCtrlZ2();
}
if (element.getCtrlX2() > xmax) {
xmax = element.getCtrlX2();
}
if (element.getCtrlY2() > ymax) {
ymax = element.getCtrlY2();
}
if (element.getCtrlZ2() > zmax) {
zmax = element.getCtrlZ2();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToZ() < zmin) {
zmin = element.getToZ();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
if (element.getToZ() > zmax) {
zmax = element.getToZ();
}
foundOneControlPoint = true;
break;
case QUAD_TO:
if (element.getFromX() < xmin) {
xmin = element.getFromX();
}
if (element.getFromY() < ymin) {
ymin = element.getFromY();
}
if (element.getFromZ() < zmin) {
zmin = element.getFromZ();
}
if (element.getFromX() > xmax) {
xmax = element.getFromX();
}
if (element.getFromY() > ymax) {
ymax = element.getFromY();
}
if (element.getFromZ() > zmax) {
zmax = element.getFromZ();
}
if (element.getCtrlX1() < xmin) {
xmin = element.getCtrlX1();
}
if (element.getCtrlY1() < ymin) {
ymin = element.getCtrlY1();
}
if (element.getCtrlZ1() < zmin) {
zmin = element.getCtrlZ1();
}
if (element.getCtrlX1() > xmax) {
xmax = element.getCtrlX1();
}
if (element.getCtrlY1() > ymax) {
ymax = element.getCtrlY1();
}
if (element.getCtrlZ1() > zmax) {
zmax = element.getCtrlZ1();
}
if (element.getToX() < xmin) {
xmin = element.getToX();
}
if (element.getToY() < ymin) {
ymin = element.getToY();
}
if (element.getToZ() < zmin) {
zmin = element.getToZ();
}
if (element.getToX() > xmax) {
xmax = element.getToX();
}
if (element.getToY() > ymax) {
ymax = element.getToY();
}
if (element.getToZ() > zmax) {
zmax = element.getToZ();
}
foundOneControlPoint = true;
break;
case MOVE_TO:
case CLOSE:
case ARC_TO:
default:
}
}
if (foundOneControlPoint) {
box.setFromCorners(xmin, ymin, zmin, xmax, ymax, zmax);
} else {
box.clear();
}
return foundOneControlPoint;
}
/**
* Compute the total squared length of the path.
*
* @param iterator
* the iterator on the path elements.
* @return the squared length of the path.
*/
static double computeLength(PathIterator3afp<?> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
PathElement3afp pathElement = iterator.next();
if (pathElement.getType() != PathElementType.MOVE_TO) {
throw new IllegalArgumentException(Locale.getString("E1")); //$NON-NLS-1$
}
// only for internal use
final GeomFactory3afp<?, ?, ?, ?> factory = iterator.getGeomFactory();
Path3afp<?, ?, ?, ?, ?, ?> subPath;
double movx = pathElement.getToX();
double curx = movx;
double movy = pathElement.getToY();
double cury = movy;
double movz = pathElement.getToZ();
double curz = movz;
double length = 0;
double endx;
double endy;
double endz;
while (iterator.hasNext()) {
pathElement = iterator.next();
switch (pathElement.getType()) {
case MOVE_TO:
movx = pathElement.getToX();
curx = movx;
movy = pathElement.getToY();
cury = movy;
movz = pathElement.getToZ();
curz = movz;
break;
case LINE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
endz = pathElement.getToZ();
length += Point3D.getDistancePointPoint(curx, cury, curz, endx, endy, endz);
curx = endx;
cury = endy;
curz = endz;
break;
case QUAD_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
endz = pathElement.getToZ();
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury, curz);
subPath.quadTo(pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz);
length += computeLength(subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO));
curx = endx;
cury = endy;
curz = endz;
break;
case CURVE_TO:
endx = pathElement.getToX();
endy = pathElement.getToY();
endz = pathElement.getToZ();
subPath = factory.newPath(iterator.getWindingRule());
subPath.moveTo(curx, cury, curz);
subPath.curveTo(pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(),
pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz);
length += computeLength(subPath.getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO));
curx = endx;
cury = endy;
curz = endz;
break;
case CLOSE:
if (curx != movx || cury != movy || curz != movz) {
length += Point3D.getDistancePointPoint(curx, cury, curz, movx, movy, movz);
}
curx = movx;
cury = movy;
curz = movz;
break;
case ARC_TO:
default:
}
}
return length;
}
@Pure
@Override
default boolean equalsToShape(IT shape) {
if (shape == null) {
return false;
}
if (shape == this) {
return true;
}
return equalsToPathIterator(shape.getPathIterator());
}
/**
* Add the elements replied by the iterator into this path.
*
* @param iterator the iterator that provides the elements to add in the path.
*/
default void add(Iterator<? extends PathElement3afp> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
PathElement3afp element;
while (iterator.hasNext()) {
element = iterator.next();
switch (element.getType()) {
case MOVE_TO:
moveTo(element.getToX(), element.getToY(), element.getToZ());
break;
case LINE_TO:
lineTo(element.getToX(), element.getToY(), element.getToZ());
break;
case QUAD_TO:
quadTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), element.getToX(), element.getToY(),
element.getToZ());
break;
case CURVE_TO:
curveTo(element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), element.getCtrlX2(), element.getCtrlY2(),
element.getCtrlZ2(), element.getToX(), element.getToY(), element.getToZ());
break;
case CLOSE:
closePath();
break;
case ARC_TO:
default:
}
}
}
/**
* Set the path.
*
* @param path
* the path to copy.
*/
default void set(Path3afp<?, ?, ?, ?, ?, ?> 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
* @param z
* the specified Y coordinate
*/
void moveTo(double x, double y, double z);
@Override
default void moveTo(Point3D<?, ?> position) {
assert position != null : AssertMessages.notNullParameter();
moveTo(position.getX(), position.getY(), position.getZ());
}
/**
* 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
* @param z
* the specified Y coordinate
*/
void lineTo(double x, double y, double z);
@Override
default void lineTo(Point3D<?, ?> to) {
assert to != null : AssertMessages.notNullParameter();
lineTo(to.getX(), to.getY(), to.getZ());
}
/**
* 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 z1
* the Z 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
* @param z2
* the Y coordinate of the final end point
*/
void quadTo(double x1, double y1, double z1, double x2, double y2, double z2);
@Override
default void quadTo(Point3D<?, ?> ctrl, Point3D<?, ?> to) {
assert ctrl != null : AssertMessages.notNullParameter(0);
assert to != null : AssertMessages.notNullParameter(1);
quadTo(ctrl.getX(), ctrl.getY(), ctrl.getZ(), to.getX(), to.getY(), to.getZ());
}
/**
* 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 z1
* the Z 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 z2
* the Z 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
* @param z3
* the Z coordinate of the final end point
*/
@SuppressWarnings("checkstyle:parameternumber")
void curveTo(double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3);
@Override
default void curveTo(Point3D<?, ?> ctrl1, Point3D<?, ?> ctrl2, Point3D<?, ?> to) {
assert ctrl1 != null : AssertMessages.notNullParameter(0);
assert ctrl2 != null : AssertMessages.notNullParameter(1);
assert to != null : AssertMessages.notNullParameter(2);
curveTo(ctrl1.getX(), ctrl1.getY(), ctrl1.getZ(), ctrl2.getX(), ctrl2.getY(), ctrl2.getZ(), to.getX(), to.getY(),
to.getZ());
}
@Pure
@Override
default double getDistanceSquared(Point3D<?, ?> point) {
assert point != null : AssertMessages.notNullParameter();
final Point3D<?, ?> c = getClosestPointTo(point);
return c.getDistanceSquared(point);
}
@Pure
@Override
default double getDistanceL1(Point3D<?, ?> point) {
assert point != null : AssertMessages.notNullParameter();
final Point3D<?, ?> c = getClosestPointTo(point);
return c.getDistanceL1(point);
}
@Pure
@Override
default double getDistanceLinf(Point3D<?, ?> point) {
assert point != null : AssertMessages.notNullParameter();
final Point3D<?, ?> c = getClosestPointTo(point);
return c.getDistanceLinf(point);
}
@Pure
@Override
default boolean contains(double x, double y, double z) {
return containsPoint(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), x, y, z);
}
@Override
default boolean contains(RectangularPrism3afp<?, ?, ?, ?, ?, ?> prism) {
assert prism != null : AssertMessages.notNullParameter();
return containsRectangle(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), prism.getMinX(), prism.getMinY(),
prism.getMinZ(), prism.getWidth(), prism.getHeight(), prism.getDepth());
}
@Pure
@Override
default boolean intersects(RectangularPrism3afp<?, ?, ?, ?, ?, ?> prism) {
assert prism != null : AssertMessages.notNullParameter();
// Copied from AWT API
if (prism.isEmpty()) {
return false;
}
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = computeCrossingsFromRect(0, getPathIterator(), prism.getMinX(), prism.getMinY(), prism.getMinZ(),
prism.getMaxX(), prism.getMaxY(), prism.getMaxZ(), CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(Sphere3afp<?, ?, ?, ?, ?, ?> sphere) {
assert sphere != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = computeCrossingsFromSphere(0, getPathIterator(), sphere.getX(), sphere.getY(), sphere.getZ(),
sphere.getRadius(), CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(Segment3afp<?, ?, ?, ?, ?, ?> segment) {
assert segment != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = computeCrossingsFromSegment(0, getPathIterator(), segment.getX1(), segment.getY1(), segment.getZ1(),
segment.getX2(), segment.getY2(), segment.getZ2(), CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(Path3afp<?, ?, ?, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = computeCrossingsFromPath(0, path.getPathIterator(),
new BasicPathShadow3afp(this),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(PathIterator3afp<?> iterator) {
assert iterator != null : AssertMessages.notNullParameter();
final int mask = getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2;
final int crossings = computeCrossingsFromPath(0, iterator,
new BasicPathShadow3afp(this),
CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON);
return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0;
}
@Pure
@Override
default boolean intersects(MultiShape3afp<?, ?, ?, ?, ?, ?, ?> multishape) {
assert multishape != null : AssertMessages.notNullParameter();
return multishape.intersects(this);
}
/**
* Replies the coordinate at the given index. The index is in [0;{@link #size()}*2).
*
* @param index the index.
* @return the coordinate at the given index.
*/
@Pure
double getCoordAt(int index);
/**
* Change the coordinates of the last inserted point.
*
* @param x the new x coordinate of the last point.
* @param y the new y coordinate of the last point.
* @param z the new z coordinate of the last point.
*/
void setLastPoint(double x, double y, double z);
@Override
default void setLastPoint(Point3D<?, ?> point) {
assert point != null : AssertMessages.notNullParameter();
setLastPoint(point.getX(), point.getY(), point.getZ());
}
/**
* Transform the current path. This function changes the current path.
*
* @param transform
* is the affine transformation to apply.
* @see #createTransformedShape
*/
void transform(Transform3D transform);
@Override
default double getLength() {
return computeLength(getPathIterator());
}
@Override
default double getLengthSquared() {
final double length = getLength();
return length * length;
}
@Pure
@Override
default PathIterator3afp<IE> getPathIterator(Transform3D transform) {
if (transform == null) {
return new PathPathIterator<>(this);
}
return new TransformedPathPathIterator<>(this, transform);
}
@Pure
@Override
default PathIterator3afp<IE> getPathIterator(double flatness) {
return new FlatteningPathIterator<>(getPathIterator(null), flatness, DEFAULT_FLATENING_LIMIT);
}
/**
* Replies an iterator on the path elements.
*
* <p>Only {@link PathElementType#MOVE_TO},
* {@link PathElementType#LINE_TO}, and
* {@link PathElementType#CLOSE} types are returned by the iterator.
*
* <p>The amount of subdivision of the curved segments is controlled by the flatness parameter, which specifies the maximum
* distance that any point on the unflattened transformed curve can deviate from the returned flattened path segments. Note
* that a limit on the accuracy of the flattened path might be silently imposed, causing very small flattening parameters to
* be treated as larger values. This limit, if there is one, is defined by the particular implementation that is used.
*
* <p>The iterator for this class is not multi-threaded safe.
*
* @param transform
* is an optional affine Transform3D to be applied to the coordinates as they are returned in the iteration, or
* <code>null</code> if untransformed coordinates are desired.
* @param flatness
* is the maximum distance that the line segments used to approximate the curved segments are allowed to deviate
* from any point on the original curve.
* @return an iterator on the path elements.
*/
@Pure
default PathIterator3afp<IE> getPathIterator(Transform3D transform, double flatness) {
return new FlatteningPathIterator<>(getPathIterator(transform), flatness, DEFAULT_FLATENING_LIMIT);
}
/**
* Replies the x coordinate of the last point in the path.
*
* @return the x coordinate of the last point in the path.
*/
@Pure
double getCurrentX();
/**
* Replies the y coordinate of the last point in the path.
*
* @return the y coordinate of the last point in the path.
*/
@Pure
double getCurrentY();
/**
* Replies the z coordinate of the last point in the path.
*
* @return the z coordinate of the last point in the path.
*/
@Pure
double getCurrentZ();
@Override
@Pure
default P getCurrentPoint() {
return getGeomFactory().newPoint(getCurrentX(), getCurrentY(), getCurrentZ());
}
@Override
default Collection<P> toCollection() {
return new PointCollection<>(this);
}
/**
* Remove the point with the given coordinates.
*
* <p>If the given coordinates do not match exactly a point in the path, nothing is removed.
*
* @param x
* the x coordinate of the ponit to remove.
* @param y
* the y coordinate of the ponit to remove.
* @param z
* the z coordinate of the ponit to remove.
* @return <code>true</code> if the point was removed; <code>false</code> otherwise.
*/
boolean remove(double x, double y, double z);
@Override
default void toBoundingBox(B box) {
assert box != null : AssertMessages.notNullParameter();
Path3afp.computeDrawableElementBoundingBox(getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), box);
}
/**
* Abstract iterator on the path elements of the path.
*
* @param <T>
* the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
abstract class AbstractPathPathIterator<T extends PathElement3afp> implements PathIterator3afp<T> {
private final Path3afp<?, ?, T, ?, ?, ?> path;
/**
* @param path
* the iterated path.
*/
public AbstractPathPathIterator(Path3afp<?, ?, T, ?, ?, ?> path) {
assert path != null : AssertMessages.notNullParameter();
this.path = path;
}
@Override
public GeomFactory3afp<T, ?, ?, ?> getGeomFactory() {
return this.path.getGeomFactory();
}
/**
* Replies the path.
*
* @return the path.
*/
public Path3afp<?, ?, T, ?, ?, ?> getPath() {
return this.path;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Pure
@Override
public PathWindingRule getWindingRule() {
return this.path.getWindingRule();
}
@Override
public boolean isPolyline() {
return this.path.isPolyline();
}
@Override
public boolean isCurved() {
return this.path.isCurved();
}
@Override
public boolean isPolygon() {
return this.path.isPolygon();
}
@Override
public boolean isMultiParts() {
return this.path.isMultiParts();
}
}
/**
* A path iterator that does not transform the coordinates.
*
* @param <T>
* the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
@SuppressWarnings("checkstyle:magicnumber")
class PathPathIterator<T extends PathElement3afp> extends AbstractPathPathIterator<T> {
private Point3D<?, ?> p1;
private Point3D<?, ?> p2;
private int typeIndex;
private int coordIndex;
private double movex;
private double movey;
private double movez;
/**
* @param path
* the path to iterate on.
*/
public PathPathIterator(Path3afp<?, ?, T, ?, ?, ?> path) {
super(path);
this.p1 = new InnerComputationPoint3afp();
this.p2 = new InnerComputationPoint3afp();
}
@Override
public PathIterator3afp<T> restartIterations() {
return new PathPathIterator<>(getPath());
}
@Pure
@Override
public boolean hasNext() {
return this.typeIndex < getPath().getPathElementCount();
}
@Override
public T next() {
final Path3afp<?, ?, T, ?, ?, ?> path = getPath();
final int type = this.typeIndex;
if (this.typeIndex >= path.getPathElementCount()) {
throw new NoSuchElementException();
}
T element = null;
switch (path.getPathElementTypeAt(type)) {
case MOVE_TO:
if ((this.coordIndex + 2) > (getPath().size() * 2)) {
throw new NoSuchElementException();
}
this.movex = path.getCoordAt(this.coordIndex++);
this.movey = path.getCoordAt(this.coordIndex++);
this.movez = path.getCoordAt(this.coordIndex++);
this.p2.set(this.movex, this.movey, this.movez);
element = getGeomFactory().newMovePathElement(this.p2.getX(), this.p2.getY(), this.p2.getZ());
break;
case LINE_TO:
if ((this.coordIndex + 3) > (path.size() * 3)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
this.p2.set(path.getCoordAt(this.coordIndex++), path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
element = getGeomFactory().newLinePathElement(this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(),
this.p2.getY(), this.p1.getZ());
break;
case QUAD_TO:
if ((this.coordIndex + 6) > (path.size() * 3)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
final double ctrlx = path.getCoordAt(this.coordIndex++);
final double ctrly = path.getCoordAt(this.coordIndex++);
final double ctrlz = path.getCoordAt(this.coordIndex++);
this.p2.set(path.getCoordAt(this.coordIndex++), path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
element = getGeomFactory().newCurvePathElement(this.p1.getX(), this.p1.getY(), this.p1.getZ(), ctrlx, ctrly,
ctrlz, this.p2.getX(), this.p2.getY(), this.p1.getZ());
break;
case CURVE_TO:
if ((this.coordIndex + 9) > (path.size() * 3)) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
final double ctrlx1 = path.getCoordAt(this.coordIndex++);
final double ctrly1 = path.getCoordAt(this.coordIndex++);
final double ctrlz1 = path.getCoordAt(this.coordIndex++);
final double ctrlx2 = path.getCoordAt(this.coordIndex++);
final double ctrly2 = path.getCoordAt(this.coordIndex++);
final double ctrlz2 = path.getCoordAt(this.coordIndex++);
this.p2.set(getPath().getCoordAt(this.coordIndex++), getPath().getCoordAt(this.coordIndex++),
getPath().getCoordAt(this.coordIndex++));
element = getGeomFactory().newCurvePathElement(this.p1.getX(), this.p1.getY(), this.p1.getZ(), ctrlx1, ctrly1,
ctrlz1, ctrlx2, ctrly2, ctrlz2, this.p2.getX(), this.p2.getY(), this.p2.getZ());
break;
case CLOSE:
this.p1.set(this.p2);
this.p2.set(this.movex, this.movey, this.movez);
element = getGeomFactory().newClosePathElement(this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(),
this.p2.getY(), this.p2.getZ());
break;
case ARC_TO:
default:
}
if (element == null) {
throw new NoSuchElementException();
}
++this.typeIndex;
return element;
}
}
/**
* A path iterator that transforms the coordinates.
*
* @param <T>
* the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
class TransformedPathPathIterator<T extends PathElement3afp> extends AbstractPathPathIterator<T> {
private final Transform3D transform;
private final Point3D<?, ?> p1;
private final Point3D<?, ?> p2;
private final Point3D<?, ?> ptmp1;
private final Point3D<?, ?> ptmp2;
private int typeIndex;
private int coordIndex;
private double movex;
private double movey;
private double movez;
/**
* @param path
* the path to iterate on.
* @param transform
* the transformation to apply on the path.
*/
public TransformedPathPathIterator(Path3afp<?, ?, T, ?, ?, ?> path, Transform3D transform) {
super(path);
assert transform != null : AssertMessages.notNullParameter(1);
this.transform = transform;
this.p1 = new InnerComputationPoint3afp();
this.p2 = new InnerComputationPoint3afp();
this.ptmp1 = new InnerComputationPoint3afp();
this.ptmp2 = new InnerComputationPoint3afp();
}
@Override
public PathIterator3afp<T> restartIterations() {
return new TransformedPathPathIterator<>(getPath(), this.transform);
}
@Pure
@Override
public boolean hasNext() {
return this.typeIndex < getPath().getPathElementCount();
}
@Override
public T next() {
final Path3afp<?, ?, T, ?, ?, ?> path = getPath();
if (this.typeIndex >= path.getPathElementCount()) {
throw new NoSuchElementException();
}
T element = null;
switch (path.getPathElementTypeAt(this.typeIndex++)) {
case MOVE_TO:
this.movex = path.getCoordAt(this.coordIndex++);
this.movey = path.getCoordAt(this.coordIndex++);
this.movez = path.getCoordAt(this.coordIndex++);
this.p2.set(this.movex, this.movey, this.movez);
this.transform.transform(this.p2);
element = getGeomFactory().newMovePathElement(this.p2.getX(), this.p2.getY(), this.p2.getZ());
break;
case LINE_TO:
this.p1.set(this.p2);
this.p2.set(path.getCoordAt(this.coordIndex++), path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newLinePathElement(this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(),
this.p2.getY(), this.p2.getZ());
break;
case QUAD_TO:
this.p1.set(this.p2);
this.ptmp1.set(path.getCoordAt(this.coordIndex++), path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp1);
this.p2.set(path.getCoordAt(this.coordIndex++), path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newCurvePathElement(this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.ptmp1.getX(),
this.ptmp1.getY(), this.ptmp1.getZ(), this.p2.getX(), this.p2.getY(), this.p2.getZ());
break;
case CURVE_TO:
this.p1.set(this.p2);
this.ptmp1.set(path.getCoordAt(this.coordIndex++), path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp1);
this.ptmp2.set(path.getCoordAt(this.coordIndex++), path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.ptmp2);
this.p2.set(path.getCoordAt(this.coordIndex++), path.getCoordAt(this.coordIndex++),
path.getCoordAt(this.coordIndex++));
this.transform.transform(this.p2);
element = getGeomFactory().newCurvePathElement(this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.ptmp1.getX(),
this.ptmp1.getY(), this.ptmp1.getZ(), this.ptmp2.getX(), this.ptmp2.getY(), this.ptmp2.getZ(),
this.p2.getX(), this.p2.getY(), this.p2.getZ());
break;
case CLOSE:
this.p1.set(this.p2);
this.p2.set(this.movex, this.movey, this.movez);
this.transform.transform(this.p2);
element = getGeomFactory().newClosePathElement(this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(),
this.p2.getY(), this.p2.getZ());
break;
case ARC_TO:
default:
}
if (element == null) {
throw new NoSuchElementException();
}
return element;
}
}
/**
* A path iterator that is flattening the path. This iterator was copied from AWT FlatteningPathIterator.
*
* @param <T>
* the type of the path elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
// TODO integrate Z coordinate
@SuppressWarnings("checkstyle:magicnumber")
class FlatteningPathIterator<T extends PathElement3afp> implements PathIterator3afp<T> {
/**
* The source iterator.
*/
private final PathIterator3afp<T> pathIterator;
/**
* Square of the flatness parameter for testing against squared lengths.
*/
private final double squaredFlatness;
/**
* Maximum number of recursion levels.
*/
private final int limit;
/**
* The recursion level at which each curve being held in storage was generated.
*/
private int[] levels;
/**
* The cache of interpolated coords. Note that this must be long enough to store a full cubic segment and a relative cubic
* segment to avoid aliasing when copying the coords of a curve to the end of the array. This is also serendipitously
* equal to the size of a full quad segment and 2 relative quad segments.
*/
private double[] hold = new double[14];
/**
* The index of the last curve segment being held for interpolation.
*/
private int holdEnd;
/**
* The index of the curve segment that was last interpolated. This is the curve segment ready to be returned in the next
* call to next().
*/
private int holdIndex;
/**
* The ending x of the last segment.
*/
private double currentX;
/**
* The ending y of the last segment.
*/
private double currentY;
/**
* The ending z of the last segment.
*/
private double currentZ;
/**
* The x of the last move segment.
*/
private double moveX;
/**
* The y of the last move segment.
*/
private double moveY;
/**
* The z of the last move segment.
*/
private double moveZ;
/**
* The index of the entry in the levels array of the curve segment at the holdIndex.
*/
private int levelIndex;
/**
* True when iteration is done.
*/
private boolean done;
/**
* The type of the path element.
*/
private PathElementType holdType;
/**
* The x of the last move segment replied by next.
*/
private double lastNextX;
/**
* The y of the last move segment replied by next.
*/
private double lastNextY;
/**
* The z of the last move segment replied by next.
*/
private double lastNextZ;
/**
* @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(PathIterator3afp<T> pathIterator, double flatness, int limit) {
assert pathIterator != null : AssertMessages.notNullParameter(0);
assert flatness >= 0. : AssertMessages.positiveOrZeroParameter(1);
assert limit >= 0 : AssertMessages.positiveOrZeroParameter(2);
this.pathIterator = pathIterator;
this.squaredFlatness = flatness * flatness;
this.limit = limit;
this.levels = new int[limit + 1];
searchNext();
}
@Override
public PathIterator3afp<T> restartIterations() {
return new FlatteningPathIterator<>(this.pathIterator.restartIterations(), Math.sqrt(this.squaredFlatness),
this.limit);
}
/**
* Ensures that the hold array can hold up to (want) more values. It is currently holding (hold.length - holdIndex)
* values.
*/
private void ensureHoldCapacity(int want) {
if (this.holdIndex - want < 0) {
final int have = this.hold.length - this.holdIndex;
final int newsize = this.hold.length + GROW_SIZE;
final double[] newhold = new double[newsize];
System.arraycopy(this.hold, this.holdIndex, newhold, this.holdIndex + GROW_SIZE, have);
this.hold = newhold;
this.holdIndex += GROW_SIZE;
this.holdEnd += GROW_SIZE;
}
}
/**
* Returns the square of the flatness, or maximum distance of a control point from the line connecting the end points, of
* the quadratic curve specified by the control points stored in the indicated array at the indicated index.
*
* @param coords
* an array containing coordinate values
* @param offset
* the index into <code>coords</code> from which to to start getting the values from the array
* @return the flatness of the quadratic curve that is defined by the values in the specified array at the specified
* index.
*/
private static double getQuadSquaredFlatness(double[] coords, int offset) {
// return Segment3afp.computeDistanceSquaredLinePoint(
// coords[offset + 0], coords[offset + 1],
// coords[offset + 4], coords[offset + 5],
// coords[offset + 2], coords[offset + 3]);
// TODO : correct indexes
return -1;
}
/**
* 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];
x1 = (x1 + ctrlx) / 2;
double ctrly = src[srcoff + 3];
y1 = (y1 + ctrly) / 2;
// z1 = (z1 + ctrlz) / 2;
x2 = (x2 + ctrlx) / 2;
y2 = (y2 + ctrly) / 2;
// z2 = (z2 + ctrlz) / 2;
ctrlx = (x1 + x2) / 2;
ctrly = (y1 + y2) / 2;
// ctrlz = (z1 + z2) / 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(
// Segment3afp.computeDistanceSquaredSegmentPoint(
// coords[offset + 6],
// coords[offset + 7],
// coords[offset + 2],
// coords[offset + 3],
// coords[offset + 0],
// coords[offset + 1]),
// Segment3afp.computeDistanceSquaredSegmentPoint(
// coords[offset + 6],
// coords[offset + 7],
// coords[offset + 4],
// coords[offset + 5],
// coords[offset + 0],
// coords[offset + 1]));
// TODO correct indexes
return -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];
x1 = (x1 + ctrlx1) / 2;
double ctrly1 = src[srcoff + 3];
y1 = (y1 + ctrly1) / 2;
double ctrlx2 = src[srcoff + 4];
x2 = (x2 + ctrlx2) / 2;
double ctrly2 = src[srcoff + 5];
y2 = (y2 + ctrly2) / 2;
double centerx = (ctrlx1 + ctrlx2) / 2;
double centery = (ctrly1 + ctrly2) / 2;
ctrlx1 = (x1 + centerx) / 2;
ctrly1 = (y1 + centery) / 2;
ctrlx2 = (x2 + centerx) / 2;
ctrly2 = (y2 + centery) / 2;
centerx = (ctrlx1 + ctrlx2) / 2;
centery = (ctrly1 + ctrly2) / 2;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
}
@SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity"})
private void searchNext() {
int level;
if (this.holdIndex >= this.holdEnd) {
if (!this.pathIterator.hasNext()) {
this.done = true;
return;
}
final T pathElement = this.pathIterator.next();
this.holdType = pathElement.getType();
pathElement.toArray(this.hold);
this.levelIndex = 0;
this.levels[0] = 0;
}
switch (this.holdType) {
case MOVE_TO:
case LINE_TO:
this.currentX = this.hold[0];
this.currentY = this.hold[1];
this.currentY = this.hold[2];
if (this.holdType == PathElementType.MOVE_TO) {
this.moveX = this.currentX;
this.moveY = this.currentY;
this.moveZ = this.currentZ;
}
this.holdIndex = 0;
this.holdEnd = 0;
break;
case CLOSE:
this.currentX = this.moveX;
this.currentY = this.moveY;
this.currentZ = this.moveZ;
this.holdIndex = 0;
this.holdEnd = 0;
break;
case QUAD_TO:
if (this.holdIndex >= this.holdEnd) {
// Move the coordinates to the end of the array.
this.holdIndex = this.hold.length - 6;
this.holdEnd = this.hold.length - 2;
this.hold[this.holdIndex + 0] = this.currentX;
this.hold[this.holdIndex + 1] = this.currentY;
this.hold[this.holdIndex + 2] = this.hold[0];
this.hold[this.holdIndex + 3] = this.hold[1];
this.currentX = this.hold[2];
this.hold[this.holdIndex + 4] = this.currentX;
this.currentY = this.hold[3];
this.hold[this.holdIndex + 5] = this.currentY;
}
level = this.levels[this.levelIndex];
while (level < this.limit) {
if (getQuadSquaredFlatness(this.hold, this.holdIndex) < this.squaredFlatness) {
break;
}
ensureHoldCapacity(4);
subdivideQuad(this.hold, this.holdIndex, this.hold, this.holdIndex - 4, this.hold, this.holdIndex);
this.holdIndex -= 4;
// Now that we have subdivided, we have constructed
// two curves of one depth lower than the original
// curve. One of those curves is in the place of
// the former curve and one of them is in the next
// set of held coordinate slots. We now set both
// curves level values to the next higher level.
level++;
this.levels[this.levelIndex] = level;
this.levelIndex++;
this.levels[this.levelIndex] = level;
}
// This curve segment is flat enough, or it is too deep
// in recursion levels to try to flatten any more. The
// two coordinates at holdIndex+4 and holdIndex+5 now
// contain the endpoint of the curve which can be the
// endpoint of an approximating line segment.
this.holdIndex += 4;
this.levelIndex--;
break;
case CURVE_TO:
if (this.holdIndex >= this.holdEnd) {
// Move the coordinates to the end of the array.
this.holdIndex = this.hold.length - 8;
this.holdEnd = this.hold.length - 2;
this.hold[this.holdIndex + 0] = this.currentX;
this.hold[this.holdIndex + 1] = this.currentY;
this.hold[this.holdIndex + 2] = this.hold[0];
this.hold[this.holdIndex + 3] = this.hold[1];
this.hold[this.holdIndex + 4] = this.hold[2];
this.hold[this.holdIndex + 5] = this.hold[3];
this.currentX = this.hold[4];
this.hold[this.holdIndex + 6] = this.currentX;
this.currentY = this.hold[5];
this.hold[this.holdIndex + 7] = this.currentY;
}
level = this.levels[this.levelIndex];
while (level < this.limit) {
if (getCurveSquaredFlatness(this.hold, this.holdIndex) < this.squaredFlatness) {
break;
}
ensureHoldCapacity(6);
subdivideCurve(this.hold, this.holdIndex, this.hold, this.holdIndex - 6, this.hold, this.holdIndex);
this.holdIndex -= 6;
// Now that we have subdivided, we have constructed
// two curves of one depth lower than the original
// curve. One of those curves is in the place of
// the former curve and one of them is in the next
// set of held coordinate slots. We now set both
// curves level values to the next higher level.
level++;
this.levels[this.levelIndex] = level;
this.levelIndex++;
this.levels[this.levelIndex] = level;
}
// This curve segment is flat enough, or it is too deep
// in recursion levels to try to flatten any more. The
// two coordinates at holdIndex+6 and holdIndex+7 now
// contain the endpoint of the curve which can be the
// endpoint of an approximating line segment.
this.holdIndex += 6;
this.levelIndex--;
break;
case ARC_TO:
default:
}
}
@Pure
@Override
public boolean hasNext() {
return !this.done;
}
@Override
public T next() {
if (this.done) {
throw new NoSuchElementException();
}
final T element;
final PathElementType type = this.holdType;
if (type != PathElementType.CLOSE) {
final double x = this.hold[this.holdIndex + 0];
final double y = this.hold[this.holdIndex + 1];
final double z = this.hold[this.holdIndex + 2];
if (type == PathElementType.MOVE_TO) {
element = getGeomFactory().newMovePathElement(x, y, z);
} else {
element = getGeomFactory().newLinePathElement(this.lastNextX, this.lastNextY, this.lastNextZ, x, y, z);
}
this.lastNextX = x;
this.lastNextY = y;
this.lastNextZ = z;
} else {
element = getGeomFactory().newClosePathElement(this.lastNextX, this.lastNextY, this.lastNextZ, this.moveX,
this.moveY, this.moveZ);
this.lastNextX = this.moveX;
this.lastNextY = this.moveY;
this.lastNextZ = this.moveZ;
}
searchNext();
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Pure
@Override
public PathWindingRule getWindingRule() {
return this.pathIterator.getWindingRule();
}
@Pure
@Override
public boolean isPolyline() {
return this.pathIterator.isPolyline() || (!this.pathIterator.isMultiParts() && !this.pathIterator.isPolygon());
}
@Pure
@Override
public boolean isCurved() {
// Because the iterator flats the path, this is no curve inside.
return false;
}
@Pure
@Override
public boolean isPolygon() {
return this.pathIterator.isPolygon();
}
@Pure
@Override
public boolean isMultiParts() {
return this.pathIterator.isMultiParts();
}
@Pure
@Override
public GeomFactory3afp<T, ?, ?, ?> getGeomFactory() {
return this.pathIterator.getGeomFactory();
}
}
/**
* An collection of the points 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 PointCollection<P extends Point3D<? super P, ? super V>, V extends Vector3D<? super V, ? super P>>
implements Collection<P> {
private final Path3afp<?, ?, ?, P, V, ?> path;
/**
* @param path
* the path to iterate on.
*/
public PointCollection(Path3afp<?, ?, ?, P, V, ?> path) {
assert path != null : AssertMessages.notNullParameter();
this.path = path;
}
@Pure
@Override
public int size() {
return this.path.size();
}
@Pure
@Override
public boolean isEmpty() {
return this.path.size() <= 0;
}
@Pure
@Override
public boolean contains(Object obj) {
if (obj instanceof Point3D) {
return this.path.containsControlPoint((Point3D<?, ?>) obj);
}
return false;
}
@Pure
@Override
public Iterator<P> iterator() {
return new PointIterator<>(this.path);
}
@Pure
@Override
public Object[] toArray() {
return this.path.toPointArray();
}
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] array) {
assert array != null : AssertMessages.notNullParameter();
final Iterator<P> iterator = new PointIterator<>(this.path);
for (int i = 0; i < array.length && iterator.hasNext(); ++i) {
array[i] = (T) iterator.next();
}
return array;
}
@Override
public boolean add(P element) {
if (element != null) {
if (this.path.size() == 0) {
this.path.moveTo(element.getX(), element.getY(), element.getZ());
} else {
this.path.lineTo(element.getX(), element.getY(), element.getZ());
}
return true;
}
return false;
}
@Override
public boolean remove(Object object) {
if (object instanceof Point3D) {
final Point3D<?, ?> p = (Point3D<?, ?>) object;
return this.path.remove(p.getX(), p.getY(), p.getZ());
}
return false;
}
@Pure
@Override
public boolean containsAll(Collection<?> collection) {
assert collection != null : AssertMessages.notNullParameter();
for (final Object obj : collection) {
if ((!(obj instanceof Point3D)) || (!this.path.containsControlPoint((Point3D<?, ?>) obj))) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection<? extends P> collection) {
assert collection != null : AssertMessages.notNullParameter();
boolean changed = false;
for (final P pts : collection) {
if (add(pts)) {
changed = true;
}
}
return changed;
}
@Override
public boolean removeAll(Collection<?> collection) {
assert collection != null : AssertMessages.notNullParameter();
boolean changed = false;
for (final Object obj : collection) {
if (obj instanceof Point3D) {
final Point3D<?, ?> pts = (Point3D<?, ?>) obj;
if (this.path.remove(pts.getX(), pts.getY(), pts.getZ())) {
changed = true;
}
}
}
return changed;
}
@Override
public boolean retainAll(Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
this.path.clear();
}
}
/**
* Iterator on the points 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 PointIterator<P extends Point3D<? super P, ? super V>, V extends Vector3D<? super V, ? super P>>
implements Iterator<P> {
private final Path3afp<?, ?, ?, P, V, ?> path;
private int index;
private P lastReplied;
/**
* @param path
* the path to iterate on.
*/
public PointIterator(Path3afp<?, ?, ?, P, V, ?> path) {
assert path != null : AssertMessages.notNullParameter();
this.path = path;
}
@Pure
@Override
public boolean hasNext() {
return this.index < this.path.size();
}
@Override
public P next() {
try {
this.lastReplied = this.path.getPointAt(this.index++);
return this.lastReplied;
} catch (Throwable e) {
throw new NoSuchElementException();
}
}
@Override
public void remove() {
final Point3D<?, ?> p = this.lastReplied;
this.lastReplied = null;
if (p == null) {
throw new NoSuchElementException();
}
this.path.remove(p.getX(), p.getY(), p.getZ());
}
}
}