/*
* $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.continous.object2d;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.arakhne.afc.math.MathConstants;
import org.arakhne.afc.math.MathUtil;
import org.arakhne.afc.math.generic.Path2D;
import org.arakhne.afc.math.generic.PathElementType;
import org.arakhne.afc.math.generic.PathWindingRule;
import org.arakhne.afc.math.generic.Point2D;
import org.arakhne.afc.math.geometry.d2.afp.Segment2afp;
import org.arakhne.afc.math.geometry.d2.d.Path2d;
import org.arakhne.afc.math.matrix.Transform2D;
import org.arakhne.afc.vmutil.ReflectionUtil;
/** A generic path.
*
* @author $Author: sgalland$
* @author $Author: fozgul$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @deprecated see {@link Path2d}
*/
@Deprecated
@SuppressWarnings("all")
public class Path2f extends AbstractShape2f<Path2f> implements Path2D<Shape2f,Rectangle2f,PathElement2f,PathIterator2f> {
private static final long serialVersionUID = -873231223923726975L;
/** Multiple of cubic & quad curve size.
*/
static final int GROW_SIZE = 24;
/** Replies the point on the path that is closest to the given point.
* <p>
* <strong>CAUTION:</strong> This function works only on path iterators
* that are replying polyline primitives, ie. if the
* {@link PathIterator2f#isPolyline()} of <var>pi</var> is replying
* <code>true</code>.
* {@link #getClosestPointTo(Point2D)} avoids this restriction.
*
* @param pi is the iterator on the elements of the path.
* @param x
* @param y
* @return the closest point on the shape; or the point itself
* if it is inside the shape.
*/
public static Point2D getClosestPointTo(PathIterator2f pi, float x, float y) {
Point2D closest = null;
float bestDist = Float.POSITIVE_INFINITY;
Point2D candidate;
PathElement2f pe;
int mask = (pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1);
int crossings = 0;
while (pi.hasNext()) {
pe = pi.next();
candidate = null;
switch(pe.type) {
case MOVE_TO:
candidate = new Point2f(pe.toX, pe.toY);
break;
case LINE_TO:
{
float factor = (float) Segment2afp.findsProjectedPointPointLine(
x, y,
pe.fromX, pe.fromY, pe.toX, pe.toY);
factor = MathUtil.clamp(factor, 0f, 1f);
Vector2f v = new Vector2f(pe.toX, pe.toY);
v.sub(pe.fromX, pe.fromY);
v.scale(factor);
candidate = new Point2f(
pe.fromX + v.getX(),
pe.fromY + v.getY());
crossings += Segment2f.computeCrossingsFromPoint(
x, y,
pe.fromX, pe.fromY, pe.toX, pe.toY);
break;
}
case CLOSE:
crossings += Segment2f.computeCrossingsFromPoint(
x, y,
pe.fromX, pe.fromY, pe.toX, pe.toY);
if ((crossings & mask) != 0) return new Point2f(x, y);
if (!pe.isEmpty()) {
float factor = (float) Segment2afp.findsProjectedPointPointLine(
x, y,
pe.fromX, pe.fromY, pe.toX, pe.toY);
factor = MathUtil.clamp(factor, 0f, 1f);
Vector2f v = new Vector2f(pe.toX, pe.toY);
v.sub(pe.fromX, pe.fromY);
v.scale(factor);
candidate = new Point2f(
pe.fromX + v.getX(),
pe.fromY + v.getY());
}
crossings = 0;
break;
case QUAD_TO:
case CURVE_TO:
default:
throw new IllegalStateException(
pe.type==null ? null : pe.type.toString());
}
if (candidate!=null) {
float d = candidate.distanceSquared(new Point2f(x,y));
if (d<bestDist) {
bestDist = d;
closest = candidate;
}
}
}
return closest;
}
/**
* Tests if the specified coordinates are inside the closed
* boundary of the specified {@link PathIterator2f}.
* <p>
* This method provides a basic facility for implementors of
* the {@link Shape2f} interface to implement support for the
* {@link Shape2f#contains(float, float)} method.
*
* @param pi the specified {@code PathIterator2f}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @return {@code true} if the specified coordinates are inside the
* specified {@code PathIterator2f}; {@code false} otherwise
*/
public static boolean contains(PathIterator2f pi, float x, float y) {
// Copied from the AWT API
int mask = (pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 1);
int cross = computeCrossingsFromPoint(pi, x, y, false, true);
return ((cross & mask) != 0);
}
/**
* Tests if the specified rectangle is inside the closed
* boundary of the specified {@link PathIterator2f}.
* <p>
* This method provides a basic facility for implementors of
* the {@link Shape2f} interface to implement support for the
* {@link Shape2f#contains(Rectangle2f)} method.
*
* @param pi the specified {@code PathIterator2f}
* @param rx the lowest corner of the rectangle.
* @param ry the lowest corner of the rectangle.
* @param rwidth is the width of the rectangle.
* @param rheight is the width of the rectangle.
* @return {@code true} if the specified rectangle is inside the
* specified {@code PathIterator2f}; {@code false} otherwise.
*/
public static boolean contains(PathIterator2f pi, float rx, float ry, float rwidth, float rheight) {
// Copied from AWT API
if (rwidth <= 0 || rheight <= 0) {
return false;
}
int mask = (pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = computeCrossingsFromRect(
pi,
rx, ry, rx+rwidth, ry+rheight,
false,
true);
return (crossings != MathConstants.SHAPE_INTERSECTS &&
(crossings & mask) != 0);
}
/**
* Tests if the interior of the specified {@link PathIterator2f}
* intersects the interior of a specified set of rectangular
* coordinates.
* <p>
* This method provides a basic facility for implementors of
* the {@link Shape2f} interface to implement support for the
* {@code intersects()} method.
* <p>
* This method object may conservatively return true in
* cases where the specified rectangular area intersects a
* segment of the path, but that segment does not represent a
* boundary between the interior and exterior of the path.
* Such a case may occur if some set of segments of the
* path are retraced in the reverse direction such that the
* two sets of segments cancel each other out without any
* interior area between them.
* To determine whether segments represent true boundaries of
* the interior of the path would require extensive calculations
* involving all of the segments of the path and the winding
* rule and are thus beyond the scope of this implementation.
*
* @param pi the specified {@code PathIterator}
* @param x the specified X coordinate
* @param y the specified Y coordinate
* @param w the width of the specified rectangular coordinates
* @param h the height of the specified rectangular coordinates
* @return {@code true} if the specified {@code PathIterator} and
* the interior of the specified set of rectangular
* coordinates intersect each other; {@code false} otherwise.
*/
public static boolean intersects(PathIterator2f pi, float x, float y, float w, float h) {
if (w <= 0f || h <= 0f) {
return false;
}
int mask = (pi.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = computeCrossingsFromRect(pi, x, y, x+w, y+h, false, true);
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 pi is the description of the path.
* @param px is the reference point to test.
* @param py is the reference point to test.
* @return the crossing
*/
public static int computeCrossingsFromPoint(PathIterator2f pi, float px, float py) {
return computeCrossingsFromPoint(pi, px, py, true, true);
}
/**
* 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 pi is the description of the path.
* @param px is the reference point to test.
* @param py is the reference point to test.
* @param closeable indicates if the shape is automatically closed or not.
* @param onlyIntersectWhenOpen indicates if the crossings is set to 0 when
* the path is open and there is not SHAPE_INTERSECT.
* @return the crossing
*/
public static int computeCrossingsFromPoint(
PathIterator2f pi,
float px, float py,
boolean closeable,
boolean onlyIntersectWhenOpen) {
// Copied from the AWT API
if (!pi.hasNext()) return 0;
PathElement2f element;
element = pi.next();
if (element.type != PathElementType.MOVE_TO) {
throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$
}
Path2f subPath;
float movx = element.toX;
float movy = element.toY;
float curx = movx;
float cury = movy;
float endx, endy;
int r, crossings = 0;
while (pi.hasNext()) {
element = pi.next();
switch (element.type) {
case MOVE_TO:
movx = curx = element.toX;
movy = cury = element.toY;
break;
case LINE_TO:
endx = element.toX;
endy = element.toY;
if (endx==px && endy==py)
return MathConstants.SHAPE_INTERSECTS;
crossings += Segment2f.computeCrossingsFromPoint(
px, py,
curx, cury,
endx, endy);
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = element.toX;
endy = element.toY;
if (endx==px && endy==py)
return MathConstants.SHAPE_INTERSECTS;
subPath = new Path2f();
subPath.moveTo(curx, cury);
subPath.quadTo(
element.ctrlX1, element.ctrlY1,
endx, endy);
r = computeCrossingsFromPoint(
subPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
px, py,
false,
false);
if (r==MathConstants.SHAPE_INTERSECTS)
return r;
crossings += r;
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = element.toX;
endy = element.toY;
if (endx==px || endy==py)
return MathConstants.SHAPE_INTERSECTS;
subPath = new Path2f();
subPath.moveTo(curx, cury);
subPath.curveTo(
element.ctrlX1, element.ctrlY1,
element.ctrlX2, element.ctrlY2,
endx, endy);
r = computeCrossingsFromPoint(
subPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
px, py,
false,
false);
if (r==MathConstants.SHAPE_INTERSECTS) {
return r;
}
crossings += r;
curx = endx;
cury = endy;
break;
case CLOSE:
if (cury != movy || curx != movx) {
if (movx==px && movy==py)
return MathConstants.SHAPE_INTERSECTS;
crossings += Segment2f.computeCrossingsFromPoint(
px, py,
curx, cury,
movx, movy);
}
curx = movx;
cury = movy;
break;
default:
}
}
assert(crossings!=MathConstants.SHAPE_INTERSECTS);
boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen) {
if (closeable) {
// Not closed
if (movx==px && movy==py)
return MathConstants.SHAPE_INTERSECTS;
crossings += Segment2f.computeCrossingsFromPoint(
px, py,
curx, cury,
movx, movy);
}
else if (onlyIntersectWhenOpen) {
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
crossings = 0;
}
}
return crossings;
}
/**
* Calculates the number of times the given path
* crosses the given ellipse extending to the right.
*
* @param pi is the description of the path.
* @param ex is the first point of the ellipse.
* @param ey is the first point of the ellipse.
* @param ew is the width of the ellipse.
* @param eh is the height of the ellipse.
* @return the crossing or {@link MathConstants#SHAPE_INTERSECTS}
*/
public static int computeCrossingsFromEllipse(PathIterator2f pi, float ex, float ey, float ew, float eh) {
return computeCrossingsFromEllipse(0, pi, ex, ey, ew, eh, true, true);
}
/**
* Calculates the number of times the given path
* crosses the given ellipse extending to the right.
*
* @param crossings is the initial value for crossing.
* @param pi is the description of the path.
* @param ex is the first point of the ellipse.
* @param ey is the first point of the ellipse.
* @param ew is the width of the ellipse.
* @param eh is the height of the ellipse.
* @param closeable indicates if the shape is automatically closed or not.
* @param onlyIntersectWhenOpen indicates if the crossings is set to 0 when
* the path is open and there is not SHAPE_INTERSECT.
* @return the crossing or {@link MathConstants#SHAPE_INTERSECTS}
*/
public static int computeCrossingsFromEllipse(
int crossings,
PathIterator2f pi,
float ex, float ey, float ew, float eh,
boolean closeable,
boolean onlyIntersectWhenOpen) {
// Copied from the AWT API
if (!pi.hasNext()) return 0;
PathElement2f element;
element = pi.next();
if (element.type != PathElementType.MOVE_TO) {
throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$
}
float movx = element.toX;
float movy = element.toY;
float curx = movx;
float cury = movy;
float endx, endy;
int numCrosses = crossings;
while (numCrosses!=MathConstants.SHAPE_INTERSECTS && pi.hasNext()) {
element = pi.next();
switch (element.type) {
case MOVE_TO:
movx = curx = element.toX;
movy = cury = element.toY;
break;
case LINE_TO:
endx = element.toX;
endy = element.toY;
numCrosses = Segment2f.computeCrossingsFromEllipse(
numCrosses,
ex, ey, ew, eh,
curx, cury,
endx, endy);
if (numCrosses==MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
{
endx = element.toX;
endy = element.toY;
Path2f localPath = new Path2f();
localPath.moveTo(curx, cury);
localPath.quadTo(
element.ctrlX1, element.ctrlY1,
endx, endy);
numCrosses = computeCrossingsFromEllipse(
numCrosses,
localPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
ex, ey, ew, eh,
false,
false);
if (numCrosses==MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
}
case CURVE_TO:
endx = element.toX;
endy = element.toY;
Path2f localPath = new Path2f();
localPath.moveTo(curx, cury);
localPath.curveTo(
element.ctrlX1, element.ctrlY1,
element.ctrlX2, element.ctrlY2,
endx, endy);
numCrosses = computeCrossingsFromEllipse(
numCrosses,
localPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
ex, ey, ew, eh,
false,
false);
if (numCrosses==MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (cury != movy || curx != movx) {
numCrosses = Segment2f.computeCrossingsFromEllipse(
numCrosses,
ex, ey, ew, eh,
curx, cury,
movx, movy);
if (numCrosses==MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
}
curx = movx;
cury = movy;
break;
default:
}
}
assert(numCrosses!=MathConstants.SHAPE_INTERSECTS);
boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen) {
if (closeable) {
// Not closed
numCrosses = Segment2f.computeCrossingsFromEllipse(
numCrosses,
ex, ey, ew, eh,
curx, cury,
movx, movy);
}
else if (onlyIntersectWhenOpen) {
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
numCrosses = 0;
}
}
return numCrosses;
}
/**
* Calculates the number of times the given path
* crosses the given ellipse extending to the right.
*
* @param pi is the description of the path.
* @param cx is the center of the circle.
* @param cy is the center of the circle.
* @param radius is the radius of the circle.
* @return the crossing or {@link MathConstants#SHAPE_INTERSECTS}.
*/
public static int computeCrossingsFromCircle(PathIterator2f pi, float cx, float cy, float radius) {
return computeCrossingsFromCircle(0, pi, cx, cy, radius, true, true);
}
/**
* Calculates the number of times the given path
* crosses the given circle extending to the right.
*
* @param crossings is the initial value for crossing.
* @param pi is the description of the path.
* @param cx is the center of the circle.
* @param cy is the center of the circle.
* @param radius is the radius of the circle.
* @param closeable indicates if the shape is automatically closed or not.
* @param onlyIntersectWhenOpen indicates if the crossings is set to 0 when
* the path is open and there is not SHAPE_INTERSECT.
* @return the crossing
*/
public static int computeCrossingsFromCircle(
int crossings,
PathIterator2f pi,
float cx, float cy, float radius,
boolean closeable,
boolean onlyIntersectWhenOpen) {
// Copied from the AWT API
if (!pi.hasNext()) return 0;
PathElement2f element;
element = pi.next();
if (element.type != PathElementType.MOVE_TO) {
throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$
}
float movx = element.toX;
float movy = element.toY;
float curx = movx;
float cury = movy;
float endx, endy;
int numCrosses = crossings;
while (numCrosses!=MathConstants.SHAPE_INTERSECTS && pi.hasNext()) {
element = pi.next();
switch (element.type) {
case MOVE_TO:
movx = curx = element.toX;
movy = cury = element.toY;
break;
case LINE_TO:
endx = element.toX;
endy = element.toY;
numCrosses = Segment2f.computeCrossingsFromCircle(
numCrosses,
cx, cy, radius,
curx, cury,
endx, endy);
if (numCrosses==MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case QUAD_TO:
{
endx = element.toX;
endy = element.toY;
Path2f localPath = new Path2f();
localPath.moveTo(element.fromX, element.fromY);
localPath.quadTo(
element.ctrlX1, element.ctrlY1,
endx, endy);
numCrosses = computeCrossingsFromCircle(
numCrosses,
localPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
cx, cy, radius,
false,
false);
if (numCrosses==MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
}
case CURVE_TO:
endx = element.toX;
endy = element.toY;
Path2f localPath = new Path2f();
localPath.moveTo(element.fromX, element.fromY);
localPath.curveTo(
element.ctrlX1, element.ctrlY1,
element.ctrlX2, element.ctrlY2,
endx, endy);
numCrosses = computeCrossingsFromCircle(
numCrosses,
localPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
cx, cy, radius,
false,
false);
if (numCrosses==MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
curx = endx;
cury = endy;
break;
case CLOSE:
if (cury != movy || curx != movx) {
numCrosses = Segment2f.computeCrossingsFromCircle(
numCrosses,
cx, cy, radius,
curx, cury,
movx, movy);
if (numCrosses==MathConstants.SHAPE_INTERSECTS) {
return numCrosses;
}
}
curx = movx;
cury = movy;
break;
default:
}
}
assert(numCrosses!=MathConstants.SHAPE_INTERSECTS);
boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen) {
if (closeable) {
// Not closed
numCrosses = Segment2f.computeCrossingsFromCircle(
numCrosses,
cx, cy, radius,
curx, cury,
movx, movy);
}
else if (onlyIntersectWhenOpen) {
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
numCrosses = 0;
}
}
return numCrosses;
}
/**
* Calculates the number of times the given path
* crosses the given segment extending to the right.
*
* @param pi is the description of the path.
* @param x1 is the first point of the segment.
* @param y1 is the first point of the segment.
* @param x2 is the first point of the segment.
* @param y2 is the first point of the segment.
* @return the crossing or {@link MathConstants#SHAPE_INTERSECTS}.
*/
public static int computeCrossingsFromSegment(PathIterator2f pi, float x1, float y1, float x2, float y2) {
return computeCrossingsFromSegment(0, pi, x1, y1, x2, y2, true);
}
/**
* 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 pi is the description of the path.
* @param x1 is the first point of the segment.
* @param y1 is the first point of the segment.
* @param x2 is the first point of the segment.
* @param y2 is the first point of the segment.
* @param closeable indicates if the shape is automatically closed or not.
* @return the crossing
*/
public static int computeCrossingsFromSegment(int crossings, PathIterator2f pi, float x1, float y1, float x2, float y2, boolean closeable) {
// Copied from the AWT API
if (!pi.hasNext() || crossings==MathConstants.SHAPE_INTERSECTS) return crossings;
PathElement2f element;
element = pi.next();
if (element.type != PathElementType.MOVE_TO) {
throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$
}
float movx = element.toX;
float movy = element.toY;
float curx = movx;
float cury = movy;
float endx, endy;
int numCrosses = crossings;
while (numCrosses!=MathConstants.SHAPE_INTERSECTS && pi.hasNext()) {
element = pi.next();
switch (element.type) {
case MOVE_TO:
movx = curx = element.toX;
movy = cury = element.toY;
break;
case LINE_TO:
endx = element.toX;
endy = element.toY;
numCrosses = Segment2f.computeCrossingsFromSegment(
numCrosses,
x1, y1, x2, y2,
curx, cury,
endx, endy);
if (numCrosses==MathConstants.SHAPE_INTERSECTS)
return numCrosses;
curx = endx;
cury = endy;
break;
case QUAD_TO:
{
endx = element.toX;
endy = element.toY;
Path2f localPath = new Path2f();
localPath.moveTo(curx, cury);
localPath.quadTo(
element.ctrlX1, element.ctrlY1,
endx, endy);
numCrosses = computeCrossingsFromSegment(
numCrosses,
localPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2,
false);
if (numCrosses==MathConstants.SHAPE_INTERSECTS)
return numCrosses;
curx = endx;
cury = endy;
break;
}
case CURVE_TO:
endx = element.toX;
endy = element.toY;
Path2f localPath = new Path2f();
localPath.moveTo(curx, cury);
localPath.curveTo(
element.ctrlX1, element.ctrlY1,
element.ctrlX2, element.ctrlY2,
endx, endy);
numCrosses = computeCrossingsFromSegment(
numCrosses,
localPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
x1, y1, x2, y2,
false);
if (numCrosses==MathConstants.SHAPE_INTERSECTS)
return numCrosses;
curx = endx;
cury = endy;
break;
case CLOSE:
if (cury != movy || curx != movx) {
numCrosses = Segment2f.computeCrossingsFromSegment(
numCrosses,
x1, y1, x2, y2,
curx, cury,
movx, movy);
}
if (numCrosses!=0) return numCrosses;
curx = movx;
cury = movy;
break;
default:
}
}
assert(numCrosses!=MathConstants.SHAPE_INTERSECTS);
boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen) {
if (closeable) {
numCrosses = Segment2f.computeCrossingsFromSegment(
numCrosses,
x1, y1, x2, y2,
curx, cury,
movx, movy);
}
else {
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
numCrosses = 0;
}
}
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 pi is the iterator on the path elements.
* @param rxmin is the first corner of the rectangle.
* @param rymin is the first corner of the rectangle.
* @param rxmax is the second corner of the rectangle.
* @param rymax is the second corner of the rectangle.
* @return the crossings.
*/
public static int computeCrossingsFromRect(PathIterator2f pi,
float rxmin, float rymin,
float rxmax, float rymax) {
return computeCrossingsFromRect(pi, rxmin, rymin, rxmax, rymax, true, true);
}
/**
* 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 pi is the iterator on the path elements.
* @param rxmin is the first corner of the rectangle.
* @param rymin is the first corner of the rectangle.
* @param rxmax is the second corner of the rectangle.
* @param rymax is the second corner of the rectangle.
* @param closeable indicates if the shape is automatically closed or not.
* @param onlyIntersectWhenOpen indicates if the crossings is set to 0 when
* the path is open and there is not SHAPE_INTERSECT.
* @return the crossings.
*/
public static int computeCrossingsFromRect(PathIterator2f pi,
float rxmin, float rymin,
float rxmax, float rymax,
boolean closeable,
boolean onlyIntersectWhenOpen) {
// Copied from AWT API
if (rxmax <= rxmin || rymax <= rymin) return 0;
if (!pi.hasNext()) return 0;
PathElement2f pathElement = pi.next();
if (pathElement.type != PathElementType.MOVE_TO) {
throw new IllegalArgumentException("missing initial moveto in path definition"); //$NON-NLS-1$
}
Path2f subPath;
float curx, cury, movx, movy, endx, endy;
curx = movx = pathElement.toX;
cury = movy = pathElement.toY;
int crossings = 0;
int n;
while (crossings != MathConstants.SHAPE_INTERSECTS
&& pi.hasNext()) {
pathElement = pi.next();
switch (pathElement.type) {
case MOVE_TO:
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
movx = curx = pathElement.toX;
movy = cury = pathElement.toY;
break;
case LINE_TO:
endx = pathElement.toX;
endy = pathElement.toY;
crossings = Segment2f.computeCrossingsFromRect(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
endx, endy);
if (crossings==MathConstants.SHAPE_INTERSECTS)
return crossings;
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = pathElement.toX;
endy = pathElement.toY;
subPath = new Path2f();
subPath.moveTo(curx, cury);
subPath.quadTo(
pathElement.ctrlX1, pathElement.ctrlY1,
endx, endy);
n = computeCrossingsFromRect(
subPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
rxmin, rymin,
rxmax, rymax,
false,
false);
if (n==MathConstants.SHAPE_INTERSECTS)
return n;
crossings += n;
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = pathElement.toX;
endy = pathElement.toY;
subPath = new Path2f();
subPath.moveTo(curx, cury);
subPath.curveTo(
pathElement.ctrlX1, pathElement.ctrlY1,
pathElement.ctrlX2, pathElement.ctrlY2,
endx, endy);
n = computeCrossingsFromRect(
subPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
rxmin, rymin,
rxmax, rymax,
false,
false);
if (n==MathConstants.SHAPE_INTERSECTS)
return n;
crossings += n;
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
crossings = Segment2f.computeCrossingsFromRect(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
// Stop as soon as possible
if (crossings!=0) return crossings;
curx = movx;
cury = movy;
break;
default:
}
}
assert(crossings != MathConstants.SHAPE_INTERSECTS);
boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen) {
if (closeable) {
// Not closed
crossings = Segment2f.computeCrossingsFromRect(crossings,
rxmin, rymin,
rxmax, rymax,
curx, cury,
movx, movy);
}
else if (onlyIntersectWhenOpen) {
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
crossings = 0;
}
}
return crossings;
}
/** Array of types.
*/
PathElementType[] types;
/** Array of coords.
*/
float[] coords;
/** Number of types in the array.
*/
int numTypes = 0;
/** Number of coords in the array.
*/
int numCoords = 0;
/** Winding rule for the path.
*/
PathWindingRule windingRule;
/** Indicates if the path is empty.
* The path is empty when there is no point inside, or
* all the points are at the same coordinate, or
* when the path does not represents a drawable path
* (a path with a line or a curve).
*/
private Boolean isEmpty = Boolean.TRUE;
/** Indicates if the path contains base primitives
* (no curve).
*/
private Boolean isPolyline = Boolean.TRUE;
/** Buffer for the bounds of the path that corresponds
* to the points really on the path (eg, the pixels
* drawn). The control points of the curves are
* not considered in this bounds.
*/
private SoftReference<Rectangle2f> graphicalBounds = null;
/** Buffer for the bounds of the path that corresponds
* to all the points added in the path.
*/
private SoftReference<Rectangle2f> logicalBounds = null;
/**
*/
public Path2f() {
this(PathWindingRule.NON_ZERO);
}
/**
* @param iterator
*/
public Path2f(Iterator<PathElement2f> iterator) {
this(PathWindingRule.NON_ZERO, iterator);
}
/**
* @param windingRule
*/
public Path2f(PathWindingRule windingRule) {
assert(windingRule!=null);
this.types = new PathElementType[GROW_SIZE];
this.coords = new float[GROW_SIZE];
this.windingRule = windingRule;
}
/**
* @param windingRule
* @param iterator
*/
public Path2f(PathWindingRule windingRule, Iterator<PathElement2f> iterator) {
assert(windingRule!=null);
this.types = new PathElementType[GROW_SIZE];
this.coords = new float[GROW_SIZE];
this.windingRule = windingRule;
add(iterator);
}
/**
* @param p
*/
public Path2f(Path2f p) {
this.coords = p.coords.clone();
this.isEmpty = p.isEmpty;
this.isPolyline = p.isPolyline;
this.numCoords = p.numCoords;
this.types = p.types.clone();
this.windingRule = p.windingRule;
Rectangle2f box;
box = p.graphicalBounds==null ? null : p.graphicalBounds.get();
if (box!=null) {
this.graphicalBounds = new SoftReference<>(box.clone());
}
box = p.logicalBounds==null ? null : p.logicalBounds.get();
if (box!=null) {
this.logicalBounds = new SoftReference<>(box.clone());
}
}
@Override
public void clear() {
this.types = new PathElementType[GROW_SIZE];
this.coords = new float[GROW_SIZE];
this.windingRule = PathWindingRule.NON_ZERO;
this.numCoords = 0;
this.numTypes = 0;
this.isEmpty = Boolean.TRUE;
this.isPolyline = Boolean.TRUE;
this.graphicalBounds = null;
this.logicalBounds = null;
}
@Override
public String toString() {
return ReflectionUtil.toString(this);
}
@Override
public Path2f clone() {
Path2f clone = super.clone();
clone.coords = this.coords.clone();
clone.types = this.types.clone();
return clone;
}
@Override
public PathWindingRule getWindingRule() {
return this.windingRule;
}
/** Set the winding rule for the path.
*
* @param r is the winding rule for the path.
*/
public void setWindingRule(PathWindingRule r) {
assert(r!=null);
this.windingRule = r;
}
/** Add the elements replied by the iterator into this path.
*
* @param iterator
*/
public void add(Iterator<PathElement2f> iterator) {
PathElement2f element;
while (iterator.hasNext()) {
element = iterator.next();
switch(element.type) {
case MOVE_TO:
moveTo(element.toX, element.toY);
break;
case LINE_TO:
lineTo(element.toX, element.toY);
break;
case QUAD_TO:
quadTo(element.ctrlX1, element.ctrlY1, element.toX, element.toY);
break;
case CURVE_TO:
curveTo(element.ctrlX1, element.ctrlY1, element.ctrlX2, element.ctrlY2, element.toX, element.toY);
break;
case CLOSE:
closePath();
break;
default:
}
}
}
private void ensureSlots(boolean needMove, int n) {
if (needMove && this.numTypes==0) {
throw new IllegalStateException("missing initial moveto in path definition"); //$NON-NLS-1$
}
if (this.types.length==this.numTypes) {
this.types = Arrays.copyOf(this.types, this.types.length+GROW_SIZE);
}
while ((this.numCoords+n)>=this.coords.length) {
this.coords = Arrays.copyOf(this.coords, this.coords.length+GROW_SIZE);
}
}
/**
* Adds a point to the path by moving to the specified
* coordinates specified in float precision.
*
* @param x the specified X coordinate
* @param y the specified Y coordinate
*/
public void moveTo(float x, float y) {
if (this.numTypes>0 && this.types[this.numTypes-1]==PathElementType.MOVE_TO) {
this.coords[this.numCoords-2] = x;
this.coords[this.numCoords-1] = y;
}
else {
ensureSlots(false, 2);
this.types[this.numTypes++] = PathElementType.MOVE_TO;
this.coords[this.numCoords++] = x;
this.coords[this.numCoords++] = y;
}
this.graphicalBounds = null;
this.logicalBounds = null;
}
/**
* Adds a point to the path by drawing a straight line from the
* current coordinates to the new specified coordinates
* specified in float precision.
*
* @param x the specified X coordinate
* @param y the specified Y coordinate
*/
public void lineTo(float x, float y) {
ensureSlots(true, 2);
this.types[this.numTypes++] = PathElementType.LINE_TO;
this.coords[this.numCoords++] = x;
this.coords[this.numCoords++] = y;
this.isEmpty = null;
this.graphicalBounds = null;
this.logicalBounds = null;
}
/**
* 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 float precision.
*
* @param x1 the X coordinate of the quadratic control point
* @param y1 the Y coordinate of the quadratic control point
* @param x2 the X coordinate of the final end point
* @param y2 the Y coordinate of the final end point
*/
public void quadTo(float x1, float y1, float x2, float y2) {
ensureSlots(true, 4);
this.types[this.numTypes++] = PathElementType.QUAD_TO;
this.coords[this.numCoords++] = x1;
this.coords[this.numCoords++] = y1;
this.coords[this.numCoords++] = x2;
this.coords[this.numCoords++] = y2;
this.isEmpty = null;
this.isPolyline = Boolean.FALSE;
this.graphicalBounds = null;
this.logicalBounds = null;
}
/**
* 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 float precision.
*
* @param x1 the X coordinate of the first Bézier control point
* @param y1 the Y coordinate of the first Bézier control point
* @param x2 the X coordinate of the second Bézier control point
* @param y2 the Y coordinate of the second Bézier control point
* @param x3 the X coordinate of the final end point
* @param y3 the Y coordinate of the final end point
*/
public void curveTo(float x1, float y1,
float x2, float y2,
float x3, float y3) {
ensureSlots(true, 6);
this.types[this.numTypes++] = PathElementType.CURVE_TO;
this.coords[this.numCoords++] = x1;
this.coords[this.numCoords++] = y1;
this.coords[this.numCoords++] = x2;
this.coords[this.numCoords++] = y2;
this.coords[this.numCoords++] = x3;
this.coords[this.numCoords++] = y3;
this.isEmpty = null;
this.isPolyline = Boolean.FALSE;
this.graphicalBounds = null;
this.logicalBounds = null;
}
/**
* Closes the current subpath by drawing a straight line back to
* the coordinates of the last {@code moveTo}. If the path is already
* closed or if the previous coordinates are for a {@code moveTo}
* then this method has no effect.
*/
public void closePath() {
if (this.numTypes<=0 ||
(this.types[this.numTypes-1]!=PathElementType.CLOSE
&&this.types[this.numTypes-1]!=PathElementType.MOVE_TO)) {
ensureSlots(true, 0);
this.types[this.numTypes++] = PathElementType.CLOSE;
}
}
@Override
public PathIterator2f getPathIterator(float flatness) {
return new FlatteningPathIterator(getWindingRule(), getPathIterator(null), flatness, 10);
}
/** Replies an iterator on the path elements.
* <p>
* Only {@link PathElementType#MOVE_TO},
* {@link PathElementType#LINE_TO}, and
* {@link PathElementType#CLOSE} types are returned by the iterator.
* <p>
* The amount of subdivision of the curved segments is controlled by the
* flatness parameter, which specifies the maximum distance that any point
* on the unflattened transformed curve can deviate from the returned
* flattened path segments. Note that a limit on the accuracy of the
* flattened path might be silently imposed, causing very small flattening
* parameters to be treated as larger values. This limit, if there is one,
* is defined by the particular implementation that is used.
* <p>
* The iterator for this class is not multi-threaded safe.
*
* @param transform is an optional affine Transform2D to be applied to the
* coordinates as they are returned in the iteration, or <code>null</code> if
* untransformed coordinates are desired.
* @param flatness is the maximum distance that the line segments used to approximate
* the curved segments are allowed to deviate from any point on the original curve.
* @return an iterator on the path elements.
*/
public PathIterator2f getPathIterator(Transform2D transform, float flatness) {
return new FlatteningPathIterator(getWindingRule(), getPathIterator(transform), flatness, 10);
}
/** {@inheritDoc}
*/
@Override
public PathIterator2f getPathIterator(Transform2D transform) {
if (transform == null) {
return new CopyPathIterator();
}
return new TransformPathIterator(transform);
}
/** Transform the current path.
* This function changes the current path.
*
* @param transform is the affine transformation to apply.
* @see #createTransformedShape(Transform2D)
*/
public void transform(Transform2D transform) {
if (transform!=null) {
Point2D p = new Point2f();
for(int i=0; i<this.numCoords;i+=2) {
p.set(this.coords[i], this.coords[i+1]);
transform.transform(p);
this.coords[i] = p.getX();
this.coords[i+1] = p.getY();
}
this.graphicalBounds = null;
this.logicalBounds = null;
}
}
/** {@inheritDoc}
*/
@Override
public void translate(float dx, float dy) {
for(int i=0; i<this.numCoords;i+=2) {
this.coords[i] += dx;
this.coords[i+1] += dy;
}
Rectangle2f bb;
bb = this.logicalBounds==null ? null : this.logicalBounds.get();
if (bb!=null) bb.translate(dx, dy);
bb = this.graphicalBounds==null ? null : this.graphicalBounds.get();
if (bb!=null) bb.translate(dx, dy);
}
/** {@inheritDoc}
*/
@Override
public Shape2f createTransformedShape(Transform2D transform) {
Path2f newPath = new Path2f(getWindingRule());
PathIterator2f pi = getPathIterator();
Point2f p = new Point2f();
Point2f t1 = new Point2f();
Point2f t2 = new Point2f();
PathElement2f e;
while (pi.hasNext()) {
e = pi.next();
switch(e.type) {
case MOVE_TO:
p.set(e.toX, e.toY);
transform.transform(p);
newPath.moveTo(p.getX(), p.getY());
break;
case LINE_TO:
p.set(e.toX, e.toY);
transform.transform(p);
newPath.lineTo(p.getX(), p.getY());
break;
case QUAD_TO:
t1.set(e.ctrlX1, e.ctrlY1);
transform.transform(t1);
p.set(e.toX, e.toY);
transform.transform(p);
newPath.quadTo(t1.getX(), t1.getY(), p.getX(), p.getY());
break;
case CURVE_TO:
t1.set(e.ctrlX1, e.ctrlY1);
transform.transform(t1);
t2.set(e.ctrlX2, e.ctrlY2);
transform.transform(t2);
p.set(e.toX, e.toY);
transform.transform(p);
newPath.curveTo(t1.getX(), t1.getY(), t2.getX(), t2.getY(), p.getX(), p.getY());
break;
case CLOSE:
newPath.closePath();
break;
default:
}
}
return newPath;
}
@Override
public float distanceSquared(Point2D p) {
Point2D c = getClosestPointTo(p);
return c.distanceSquared(p);
}
@Override
public float distanceL1(Point2D p) {
Point2D c = getClosestPointTo(p);
return c.distanceL1(p);
}
@Override
public float distanceLinf(Point2D p) {
Point2D c = getClosestPointTo(p);
return c.distanceLinf(p);
}
@Override
public boolean contains(float x, float y) {
return contains(getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO), x, y);
}
@Override
public boolean contains(Rectangle2f r) {
return contains(getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight());
}
@Override
public boolean intersects(Rectangle2f s) {
// Copied from AWT API
if (s.isEmpty()) return false;
int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = computeCrossingsFromRect(
getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
s.getMinX(), s.getMinY(), s.getMaxX(), s.getMaxY(),
false, true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(Ellipse2f s) {
int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = computeCrossingsFromEllipse(
0,
getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
s.getMinX(), s.getMinY(), s.getWidth(), s.getHeight(),
false, true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(Circle2f s) {
int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = computeCrossingsFromCircle(
0,
getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
s.getX(), s.getY(), s.getRadius(),
false,
true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(Segment2f s) {
int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = computeCrossingsFromSegment(
0,
getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
s.getX1(), s.getY1(), s.getX2(), s.getY2(),
false);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(Path2f s) {
int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = computeCrossingsFromPath(
s.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
new PathShadow2f(this),
false,
true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
@Override
public boolean intersects(PathIterator2f s) {
int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2);
int crossings = computeCrossingsFromPath(
s,
new PathShadow2f(this),
false,
true);
return (crossings == MathConstants.SHAPE_INTERSECTS ||
(crossings & mask) != 0);
}
/**
* 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 iterator1 is the iterator on the path elements.
* @param shadow is the description of the shape to project to the right.
* @param closeable indicates if the shape is automatically closed or not.
* @param onlyIntersectWhenOpen indicates if the crossings is set to 0 when
* the path is open and there is not SHAPE_INTERSECT.
* @return the crossings.
* @see "Weiler–Atherton clipping algorithm"
*/
public static int computeCrossingsFromPath(
PathIterator2f iterator1,
PathShadow2f shadow,
boolean closeable,
boolean onlyIntersectWhenOpen) {
if (!iterator1.hasNext()) return 0;
PathElement2f pathElement1 = iterator1.next();
if (pathElement1.type != PathElementType.MOVE_TO) {
throw new IllegalArgumentException("missing initial moveto in the first path definition"); //$NON-NLS-1$
}
Path2f subPath;
float curx, cury, movx, movy, endx, endy;
curx = movx = pathElement1.toX;
cury = movy = pathElement1.toY;
int crossings = 0;
int n;
while (crossings != MathConstants.SHAPE_INTERSECTS
&& iterator1.hasNext()) {
pathElement1 = iterator1.next();
switch (pathElement1.type) {
case MOVE_TO:
// Count should always be a multiple of 2 here.
// assert((crossings & 1) != 0);
movx = curx = pathElement1.toX;
movy = cury = pathElement1.toY;
break;
case LINE_TO:
endx = pathElement1.toX;
endy = pathElement1.toY;
crossings = shadow.computeCrossings(crossings,
curx, cury,
endx, endy);
if (crossings==MathConstants.SHAPE_INTERSECTS)
return crossings;
curx = endx;
cury = endy;
break;
case QUAD_TO:
endx = pathElement1.toX;
endy = pathElement1.toY;
subPath = new Path2f();
subPath.moveTo(curx, cury);
subPath.quadTo(
pathElement1.ctrlX1, pathElement1.ctrlY1,
endx, endy);
n = computeCrossingsFromPath(
subPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
shadow,
false,
false);
if (n==MathConstants.SHAPE_INTERSECTS)
return n;
crossings += n;
curx = endx;
cury = endy;
break;
case CURVE_TO:
endx = pathElement1.toX;
endy = pathElement1.toY;
subPath = new Path2f();
subPath.moveTo(curx, cury);
subPath.curveTo(
pathElement1.ctrlX1, pathElement1.ctrlY1,
pathElement1.ctrlX2, pathElement1.ctrlY2,
endx, endy);
n = computeCrossingsFromPath(
subPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
shadow,
false,
false);
if (n==MathConstants.SHAPE_INTERSECTS)
return n;
crossings += n;
curx = endx;
cury = endy;
break;
case CLOSE:
if (curx != movx || cury != movy) {
crossings = shadow.computeCrossings(crossings,
curx, cury,
movx, movy);
}
// Stop as soon as possible
if (crossings!=0) return crossings;
curx = movx;
cury = movy;
break;
default:
}
}
assert(crossings != MathConstants.SHAPE_INTERSECTS);
boolean isOpen = (curx != movx) || (cury != movy);
if (isOpen) {
if (closeable) {
// Not closed
crossings = shadow.computeCrossings(crossings,
curx, cury,
movx, movy);
}
else if (onlyIntersectWhenOpen) {
// Assume that when is the path is open, only
// SHAPE_INTERSECTS may be return
crossings = 0;
}
}
return crossings;
}
private static boolean buildGraphicalBoundingBox(PathIterator2f iterator, Rectangle2f box) {
boolean foundOneLine = false;
float xmin = Float.POSITIVE_INFINITY;
float ymin = Float.POSITIVE_INFINITY;
float xmax = Float.NEGATIVE_INFINITY;
float ymax = Float.NEGATIVE_INFINITY;
PathElement2f element;
Path2f subPath;
while (iterator.hasNext()) {
element = iterator.next();
switch(element.type) {
case LINE_TO:
if (element.fromX<xmin) xmin = element.fromX;
if (element.fromY<ymin) ymin = element.fromY;
if (element.fromX>xmax) xmax = element.fromX;
if (element.fromY>ymax) ymax = element.fromY;
if (element.toX<xmin) xmin = element.toX;
if (element.toY<ymin) ymin = element.toY;
if (element.toX>xmax) xmax = element.toX;
if (element.toY>ymax) ymax = element.toY;
foundOneLine = true;
break;
case CURVE_TO:
subPath = new Path2f();
subPath.moveTo(element.fromX, element.fromY);
subPath.curveTo(
element.ctrlX1, element.ctrlY1,
element.ctrlX2, element.ctrlY2,
element.toX, element.toY);
if (buildGraphicalBoundingBox(
subPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
box)) {
if (box.getMinX()<xmin) xmin = box.getMinX();
if (box.getMinY()<ymin) ymin = box.getMinY();
if (box.getMaxX()>xmax) xmax = box.getMaxX();
if (box.getMinX()>ymax) ymax = box.getMinX();
foundOneLine = true;
}
break;
case QUAD_TO:
subPath = new Path2f();
subPath.moveTo(element.fromX, element.fromY);
subPath.quadTo(
element.ctrlX1, element.ctrlY1,
element.toX, element.toY);
if (buildGraphicalBoundingBox(
subPath.getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
box)) {
if (box.getMinX()<xmin) xmin = box.getMinX();
if (box.getMinY()<ymin) ymin = box.getMinY();
if (box.getMaxX()>xmax) xmax = box.getMaxX();
if (box.getMinX()>ymax) ymax = box.getMinX();
foundOneLine = true;
}
break;
case MOVE_TO:
case CLOSE:
default:
}
}
if (foundOneLine) {
box.setFromCorners(xmin, ymin, xmax, ymax);
}
else {
box.clear();
}
return foundOneLine;
}
private boolean buildLogicalBoundingBox(Rectangle2f box) {
if (this.numCoords>0) {
float xmin = Float.POSITIVE_INFINITY;
float ymin = Float.POSITIVE_INFINITY;
float xmax = Float.NEGATIVE_INFINITY;
float ymax = Float.NEGATIVE_INFINITY;
for(int i=0; i<this.numCoords; i+= 2) {
if (this.coords[i]<xmin) xmin = this.coords[i];
if (this.coords[i+1]<ymin) ymin = this.coords[i+1];
if (this.coords[i]>xmax) xmax = this.coords[i];
if (this.coords[i+1]>ymax) ymax = this.coords[i+1];
}
box.setFromCorners(xmin, ymin, xmax, ymax);
return true;
}
return false;
}
/**
* {@inheritDoc}
* <p>
* The replied bounding box does not consider the control points
* of the path. Only the "visible" points are considered.
*
* @see #toBoundingBoxWithCtrlPoints()
*/
@Override
public Rectangle2f toBoundingBox() {
Rectangle2f bb = this.graphicalBounds==null ? null : this.graphicalBounds.get();
if (bb==null) {
bb = new Rectangle2f();
buildGraphicalBoundingBox(
getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
bb);
this.graphicalBounds = new SoftReference<>(bb);
}
return bb;
}
/** Replies the bounding box of all the points added in this path.
* <p>
* The replied bounding box includes the (invisible) control points.
*
* @return the bounding box with the control points.
* @see #toBoundingBox()
*/
public Rectangle2f toBoundingBoxWithCtrlPoints() {
Rectangle2f bb = this.logicalBounds==null ? null : this.logicalBounds.get();
if (bb==null) {
bb = new Rectangle2f();
buildLogicalBoundingBox(bb);
this.logicalBounds = new SoftReference<>(bb);
}
return bb;
}
/**
* {@inheritDoc}
* <p>
* The replied bounding box does not consider the control points
* of the path. Only the "visible" points are considered.
*
* @see #toBoundingBoxWithCtrlPoints(Rectangle2f)
*/
@Override
public void toBoundingBox(Rectangle2f box) {
Rectangle2f bb = this.graphicalBounds==null ? null : this.graphicalBounds.get();
if (bb==null) {
bb = new Rectangle2f();
buildGraphicalBoundingBox(
getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
bb);
this.graphicalBounds = new SoftReference<>(bb);
}
box.set(bb);
}
/** Compute the bounding box of all the points added in this path.
* <p>
* The replied bounding box includes the (invisible) control points.
*
* @param box is the rectangle to set with the bounds.
* @see #toBoundingBox()
*/
public void toBoundingBoxWithCtrlPoints(Rectangle2f box) {
Rectangle2f bb = this.logicalBounds==null ? null : this.logicalBounds.get();
if (bb==null) {
bb = new Rectangle2f();
buildLogicalBoundingBox(bb);
this.logicalBounds = new SoftReference<>(bb);
}
box.set(bb);
}
@Override
public Point2D getClosestPointTo(Point2D p) {
return getClosestPointTo(
getPathIterator((float) MathConstants.SPLINE_APPROXIMATION_RATIO),
p.getX(), p.getY());
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Path2f) {
Path2f p = (Path2f)obj;
return (this.numCoords==p.numCoords
&&this.numTypes==p.numTypes
&&Arrays.equals(this.coords, p.coords)
&&Arrays.equals(this.types, p.types)
&&this.windingRule==p.windingRule);
}
return false;
}
@Override
public int hashCode() {
long bits = 1L;
bits = 31L * bits + this.numCoords;
bits = 31L * bits + this.numTypes;
bits = 31L * bits + Arrays.hashCode(this.coords);
bits = 31L * bits + Arrays.hashCode(this.types);
bits = 31L * bits + this.windingRule.ordinal();
return (int) (bits ^ (bits >> 32));
}
/** Replies the coordinates of this path in an array of
* single precision floating-point numbers.
*
* @return the coordinates.
*/
public final float[] toFloatArray() {
return toFloatArray(null);
}
/** Replies the coordinates of this path in an array of
* single precision floating-point numbers.
*
* @param transform is the transformation to apply to all the coordinates.
* @return the coordinates.
*/
public float[] toFloatArray(Transform2D transform) {
if (transform==null) {
return Arrays.copyOf(this.coords, this.numCoords);
}
Point2f p = new Point2f();
float[] clone = new float[this.numCoords];
for(int i=0; i<clone.length;i+=2) {
p.x = this.coords[i];
p.y = this.coords[i+1];
transform.transform(p);
clone[i] = p.x;
clone[i+1] = p.y;
}
return clone;
}
/** Replies the coordinates of this path in an array of
* double precision floating-point numbers.
*
* @return the coordinates.
*/
public final double[] toDoubleArray() {
return toDoubleArray(null);
}
/** Replies the coordinates of this path in an array of
* double precision floating-point numbers.
*
* @param transform is the transformation to apply to all the coordinates.
* @return the coordinates.
*/
public double[] toDoubleArray(Transform2D transform) {
double[] clone = new double[this.numCoords];
if (transform==null) {
for(int i=0; i<this.numCoords; ++i) {
clone[i] = this.coords[i];
}
}
else {
Point2f p = new Point2f();
for(int i=0; i<clone.length;i+=2) {
p.x = this.coords[i];
p.y = this.coords[i+1];
transform.transform(p);
clone[i] = p.x;
clone[i+1] = p.y;
}
}
return clone;
}
/** Replies the points of this path in an array.
*
* @return the points.
*/
public final Point2D[] toPointArray() {
return toPointArray(null);
}
/** Replies the points of this path in an array.
*
* @param transform is the transformation to apply to all the points.
* @return the points.
*/
public Point2D[] toPointArray(Transform2D transform) {
Point2D[] clone = new Point2D[this.numCoords/2];
if (transform==null) {
for(int i=0, j=0; j<this.numCoords; ++i) {
clone[i] = new Point2f(
this.coords[j++],
this.coords[j++]);
}
}
else {
for(int i=0, j=0; j<clone.length; ++i) {
clone[i] = new Point2f(
this.coords[j++],
this.coords[j++]);
transform.transform(clone[i]);
}
}
return clone;
}
/** Replies the collection that is contains all the points of the path.
*
* @return the point collection.
*/
public final Collection<Point2D> toCollection() {
return new PointCollection();
}
/** Replies the coordinate at the given index.
* The index is in [0;{@link #size()}*2).
*
* @param index
* @return the coordinate at the given index.
*/
public float getCoordAt(int index) {
return this.coords[index];
}
/** Replies the point at the given index.
* The index is in [0;{@link #size()}).
*
* @param index
* @return the point at the given index.
*/
public Point2f getPointAt(int index) {
return new Point2f(
this.coords[index*2],
this.coords[index*2+1]);
}
/** Replies the last point in the path.
*
* @return the last point.
*/
public Point2f getCurrentPoint() {
return new Point2f(
this.coords[this.coords.length-1],
this.coords[this.coords.length-2]);
}
/** Replies the number of points in the path.
*
* @return the number of points in the path.
*/
public int size() {
return this.numCoords/2;
}
/** Replies if this path is empty.
* The path is empty when there is no point inside, or
* all the points are at the same coordinate, or
* when the path does not represents a drawable path
* (a path with a line or a curve).
*
* @return <code>true</code> if the path does not contain
* a coordinate; otherwise <code>false</code>.
*/
@Override
public boolean isEmpty() {
if (this.isEmpty==null) {
this.isEmpty = Boolean.TRUE;
PathIterator2f pi = getPathIterator();
PathElement2f pe;
while (this.isEmpty==Boolean.TRUE && pi.hasNext()) {
pe = pi.next();
if (pe.isDrawable()) {
this.isEmpty = Boolean.FALSE;
}
}
}
return this.isEmpty.booleanValue();
}
@Override
public boolean isPolyline() {
if (this.isPolyline==null) {
this.isPolyline = Boolean.TRUE;
PathIterator2f pi = getPathIterator();
PathElement2f pe;
PathElementType t;
while (this.isPolyline==Boolean.TRUE && pi.hasNext()) {
pe = pi.next();
t = pe.getType();
if (t==PathElementType.CURVE_TO || t==PathElementType.QUAD_TO) {
this.isPolyline = Boolean.FALSE;
}
}
}
return this.isPolyline.booleanValue();
}
/** Replies if the given points exists in the coordinates of this path.
*
* @param p
* @return <code>true</code> if the point is a control point of the path.
*/
boolean containsPoint(Point2D p) {
float x, y;
for(int i=0; i<this.numCoords;i+=2) {
x = this.coords[i];
y = this.coords[i+1];
if (x==p.getX() && y==p.getY()) {
return true;
}
}
return false;
}
/** Remove the point with the given coordinates.
*
* @param x
* @param y
* @return <code>true</code> if the point was removed; <code>false</code> otherwise.
*/
boolean remove(float x, float y) {
for(int i=0, j=0; i<this.numCoords && j<this.numTypes;) {
switch(this.types[j]) {
case MOVE_TO:
case LINE_TO:
if (x==this.coords[i] && y==this.coords[i+1]) {
this.numCoords -= 2;
--this.numTypes;
System.arraycopy(this.coords, i+2, this.coords, i, this.numCoords);
System.arraycopy(this.types, j+1, this.types, j, this.numTypes);
this.isEmpty = null;
return true;
}
i += 2;
++j;
break;
case CURVE_TO:
if ((x==this.coords[i] && y==this.coords[i+1])
||(x==this.coords[i+2] && y==this.coords[i+3])
||(x==this.coords[i+4] && y==this.coords[i+5])) {
this.numCoords -= 6;
--this.numTypes;
System.arraycopy(this.coords, i+6, this.coords, i, this.numCoords);
System.arraycopy(this.types, j+1, this.types, j, this.numTypes);
this.isEmpty = null;
this.isPolyline = null;
return true;
}
i += 6;
++j;
break;
case QUAD_TO:
if ((x==this.coords[i] && y==this.coords[i+1])
||(x==this.coords[i+2] && y==this.coords[i+3])) {
this.numCoords -= 4;
--this.numTypes;
System.arraycopy(this.coords, i+4, this.coords, i, this.numCoords);
System.arraycopy(this.types, j+1, this.types, j, this.numTypes);
this.isEmpty = null;
this.isPolyline = null;
return true;
}
i += 4;
++j;
break;
case CLOSE:
++j;
break;
default:
break;
}
}
return false;
}
/** Remove the last action.
*/
public void removeLast() {
if (this.numTypes>0) {
switch(this.types[this.numTypes-1]) {
case CLOSE:
// no coord to remove
break;
case MOVE_TO:
case LINE_TO:
this.numCoords -= 2;
break;
case CURVE_TO:
this.numCoords -= 6;
this.isPolyline = null;
break;
case QUAD_TO:
this.numCoords -= 4;
this.isPolyline = null;
break;
default:
throw new IllegalStateException();
}
--this.numTypes;
this.isEmpty = null;
this.graphicalBounds = null;
this.logicalBounds = null;
}
}
/** Change the coordinates of the last inserted point.
*
* @param x
* @param y
*/
public void setLastPoint(float x, float y) {
if (this.numCoords>=2) {
this.coords[this.numCoords-2] = x;
this.coords[this.numCoords-1] = y;
this.graphicalBounds = null;
this.logicalBounds = null;
}
}
@Override
public void set(Shape2f s) {
clear();
add(s.getPathIterator());
}
/** A path iterator that does not transform the coordinates.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class CopyPathIterator implements PathIterator2f {
private final Point2D p1 = new Point2f();
private final Point2D p2 = new Point2f();
private int iType = 0;
private int iCoord = 0;
private float movex, movey;
/**
*/
public CopyPathIterator() {
//
}
@Override
public boolean hasNext() {
return this.iType<Path2f.this.numTypes;
}
@Override
public PathElement2f next() {
int type = this.iType;
if (this.iType>=Path2f.this.numTypes) {
throw new NoSuchElementException();
}
PathElement2f element = null;
switch(Path2f.this.types[type]) {
case MOVE_TO:
if (this.iCoord+2>Path2f.this.numCoords) {
throw new NoSuchElementException();
}
this.movex = Path2f.this.coords[this.iCoord++];
this.movey = Path2f.this.coords[this.iCoord++];
this.p2.set(this.movex, this.movey);
element = new PathElement2f.MovePathElement2f(
this.p2.getX(), this.p2.getY());
break;
case LINE_TO:
if (this.iCoord+2>Path2f.this.numCoords) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
this.p2.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
element = new PathElement2f.LinePathElement2f(
this.p1.getX(), this.p1.getY(),
this.p2.getX(), this.p2.getY());
break;
case QUAD_TO:
{
if (this.iCoord+4>Path2f.this.numCoords) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
float ctrlx = Path2f.this.coords[this.iCoord++];
float ctrly = Path2f.this.coords[this.iCoord++];
this.p2.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
element = new PathElement2f.QuadPathElement2f(
this.p1.getX(), this.p1.getY(),
ctrlx, ctrly,
this.p2.getX(), this.p2.getY());
}
break;
case CURVE_TO:
{
if (this.iCoord+6>Path2f.this.numCoords) {
throw new NoSuchElementException();
}
this.p1.set(this.p2);
float ctrlx1 = Path2f.this.coords[this.iCoord++];
float ctrly1 = Path2f.this.coords[this.iCoord++];
float ctrlx2 = Path2f.this.coords[this.iCoord++];
float ctrly2 = Path2f.this.coords[this.iCoord++];
this.p2.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
element = new PathElement2f.CurvePathElement2f(
this.p1.getX(), this.p1.getY(),
ctrlx1, ctrly1,
ctrlx2, ctrly2,
this.p2.getX(), this.p2.getY());
}
break;
case CLOSE:
this.p1.set(this.p2);
this.p2.set(this.movex, this.movey);
element = new PathElement2f.ClosePathElement2f(
this.p1.getX(), this.p1.getY(),
this.p2.getX(), this.p2.getY());
break;
default:
}
if (element==null)
throw new NoSuchElementException();
++this.iType;
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public PathWindingRule getWindingRule() {
return Path2f.this.getWindingRule();
}
@Override
public boolean isPolyline() {
return Path2f.this.isPolyline();
}
} // class CopyPathIterator
/** A path iterator that transforms the coordinates.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class TransformPathIterator implements PathIterator2f {
private final Transform2D transform;
private final Point2D p1 = new Point2f();
private final Point2D p2 = new Point2f();
private final Point2D ptmp1 = new Point2f();
private final Point2D ptmp2 = new Point2f();
private int iType = 0;
private int iCoord = 0;
private float movex, movey;
/**
* @param transform
*/
public TransformPathIterator(Transform2D transform) {
assert(transform!=null);
this.transform = transform;
}
@Override
public boolean hasNext() {
return this.iType<Path2f.this.numTypes;
}
@Override
public PathElement2f next() {
if (this.iType>=Path2f.this.numTypes) {
throw new NoSuchElementException();
}
PathElement2f element = null;
switch(Path2f.this.types[this.iType++]) {
case MOVE_TO:
this.movex = Path2f.this.coords[this.iCoord++];
this.movey = Path2f.this.coords[this.iCoord++];
this.p2.set(this.movex, this.movey);
this.transform.transform(this.p2);
element = new PathElement2f.MovePathElement2f(
this.p2.getX(), this.p2.getY());
break;
case LINE_TO:
this.p1.set(this.p2);
this.p2.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
this.transform.transform(this.p2);
element = new PathElement2f.LinePathElement2f(
this.p1.getX(), this.p1.getY(),
this.p2.getX(), this.p2.getY());
break;
case QUAD_TO:
{
this.p1.set(this.p2);
this.ptmp1.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
this.transform.transform(this.ptmp1);
this.p2.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
this.transform.transform(this.p2);
element = new PathElement2f.QuadPathElement2f(
this.p1.getX(), this.p1.getY(),
this.ptmp1.getX(), this.ptmp1.getY(),
this.p2.getX(), this.p2.getY());
}
break;
case CURVE_TO:
{
this.p1.set(this.p2);
this.ptmp1.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
this.transform.transform(this.ptmp1);
this.ptmp2.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
this.transform.transform(this.ptmp2);
this.p2.set(
Path2f.this.coords[this.iCoord++],
Path2f.this.coords[this.iCoord++]);
this.transform.transform(this.p2);
element = new PathElement2f.CurvePathElement2f(
this.p1.getX(), this.p1.getY(),
this.ptmp1.getX(), this.ptmp1.getY(),
this.ptmp2.getX(), this.ptmp2.getY(),
this.p2.getX(), this.p2.getY());
}
break;
case CLOSE:
this.p1.set(this.p2);
this.p2.set(this.movex, this.movey);
this.transform.transform(this.p2);
element = new PathElement2f.ClosePathElement2f(
this.p1.getX(), this.p1.getY(),
this.p2.getX(), this.p2.getY());
break;
default:
}
if (element==null)
throw new NoSuchElementException();
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public PathWindingRule getWindingRule() {
return Path2f.this.getWindingRule();
}
@Override
public boolean isPolyline() {
return Path2f.this.isPolyline();
}
} // class TransformPathIterator
/** A path iterator that is flattening the path.
* This iterator was copied from AWT FlatteningPathIterator.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private static class FlatteningPathIterator implements PathIterator2f {
/** Winding rule of the path.
*/
private final PathWindingRule windingRule;
/** The source iterator.
*/
private final PathIterator2f pathIterator;
/**
* Square of the flatness parameter for testing against squared lengths.
*/
private final float 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 float hold[] = new float[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 float currentX;
/** The ending y of the last segment.
*/
private float currentY;
/** The x of the last move segment.
*/
private float moveX;
/** The y of the last move segment.
*/
private float moveY;
/** The index of the entry in the
* levels array of the curve segment
* at the holdIndex
*/
private int levelIndex;
/** True when iteration is done.
*/
private boolean done;
/** The type of the path element.
*/
private PathElementType holdType;
/** The x of the last move segment replied by next.
*/
private float lastNextX;
/** The y of the last move segment replied by next.
*/
private float lastNextY;
/**
* @param windingRule is the winding rule of the path.
* @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(PathWindingRule windingRule, PathIterator2f pathIterator, float flatness, int limit) {
assert(windingRule!=null);
assert(flatness>=0f);
assert(limit>=0);
this.windingRule = windingRule;
this.pathIterator = pathIterator;
this.squaredFlatness = flatness * flatness;
this.limit = limit;
this.levels = new int[limit + 1];
searchNext();
}
/**
* 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) {
int have = this.hold.length - this.holdIndex;
int newsize = this.hold.length + GROW_SIZE;
float newhold[] = new float[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 float getQuadSquaredFlatness(float coords[], int offset) {
return (float) Segment2afp.calculatesDistanceSquaredLinePoint(
coords[offset + 2], coords[offset + 3],
coords[offset + 0], coords[offset + 1],
coords[offset + 4], coords[offset + 5]);
}
/**
* 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(float src[], int srcoff,
float left[], int leftoff,
float right[], int rightoff) {
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx = src[srcoff + 2];
float ctrly = src[srcoff + 3];
float x2 = src[srcoff + 4];
float 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;
}
x1 = (x1 + ctrlx) / 2f;
y1 = (y1 + ctrly) / 2f;
x2 = (x2 + ctrlx) / 2f;
y2 = (y2 + ctrly) / 2f;
ctrlx = (x1 + x2) / 2f;
ctrly = (y1 + y2) / 2f;
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 float getCurveSquaredFlatness(float coords[], int offset) {
return (float) Math.max(
Segment2afp.calculatesDistanceSquaredSegmentPoint(
coords[offset + 0],
coords[offset + 1],
coords[offset + 6],
coords[offset + 7],
coords[offset + 2],
coords[offset + 3]),
Segment2afp.calculatesDistanceSquaredSegmentPoint(
coords[offset + 0],
coords[offset + 1],
coords[offset + 6],
coords[offset + 7],
coords[offset + 4], coords[offset + 5]));
}
/**
* 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(
float src[], int srcoff,
float left[], int leftoff,
float right[], int rightoff) {
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx1 = src[srcoff + 2];
float ctrly1 = src[srcoff + 3];
float ctrlx2 = src[srcoff + 4];
float ctrly2 = src[srcoff + 5];
float x2 = src[srcoff + 6];
float 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;
}
x1 = (x1 + ctrlx1) / 2f;
y1 = (y1 + ctrly1) / 2f;
x2 = (x2 + ctrlx2) / 2f;
y2 = (y2 + ctrly2) / 2f;
float centerx = (ctrlx1 + ctrlx2) / 2f;
float centery = (ctrly1 + ctrly2) / 2f;
ctrlx1 = (x1 + centerx) / 2f;
ctrly1 = (y1 + centery) / 2f;
ctrlx2 = (x2 + centerx) / 2f;
ctrly2 = (y2 + centery) / 2f;
centerx = (ctrlx1 + ctrlx2) / 2f;
centery = (ctrly1 + ctrly2) / 2f;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
}
private void searchNext() {
int level;
if (this.holdIndex >= this.holdEnd) {
if (!this.pathIterator.hasNext()) {
this.done = true;
return;
}
PathElement2f pathElement = this.pathIterator.next();
this.holdType = pathElement.type;
pathElement.toArray(this.hold);
this.levelIndex = 0;
this.levels[0] = 0;
}
switch (this.holdType) {
case MOVE_TO:
case LINE_TO:
this.currentX = this.hold[0];
this.currentY = this.hold[1];
if (this.holdType == PathElementType.MOVE_TO) {
this.moveX = this.currentX;
this.moveY = this.currentY;
}
this.holdIndex = 0;
this.holdEnd = 0;
break;
case CLOSE:
this.currentX = this.moveX;
this.currentY = this.moveY;
this.holdIndex = 0;
this.holdEnd = 0;
break;
case QUAD_TO:
if (this.holdIndex >= this.holdEnd) {
// Move the coordinates to the end of the array.
this.holdIndex = this.hold.length - 6;
this.holdEnd = this.hold.length - 2;
this.hold[this.holdIndex + 0] = this.currentX;
this.hold[this.holdIndex + 1] = this.currentY;
this.hold[this.holdIndex + 2] = this.hold[0];
this.hold[this.holdIndex + 3] = this.hold[1];
this.hold[this.holdIndex + 4] = this.currentX = this.hold[2];
this.hold[this.holdIndex + 5] = this.currentY = this.hold[3];
}
level = this.levels[this.levelIndex];
while (level < this.limit) {
if (getQuadSquaredFlatness(this.hold, this.holdIndex) < this.squaredFlatness) {
break;
}
ensureHoldCapacity(4);
subdivideQuad(
this.hold, this.holdIndex,
this.hold, this.holdIndex - 4,
this.hold, this.holdIndex);
this.holdIndex -= 4;
// Now that we have subdivided, we have constructed
// two curves of one depth lower than the original
// curve. One of those curves is in the place of
// the former curve and one of them is in the next
// set of held coordinate slots. We now set both
// curves level values to the next higher level.
level++;
this.levels[this.levelIndex] = level;
this.levelIndex++;
this.levels[this.levelIndex] = level;
}
// This curve segment is flat enough, or it is too deep
// in recursion levels to try to flatten any more. The
// two coordinates at holdIndex+4 and holdIndex+5 now
// contain the endpoint of the curve which can be the
// endpoint of an approximating line segment.
this.holdIndex += 4;
this.levelIndex--;
break;
case CURVE_TO:
if (this.holdIndex >= this.holdEnd) {
// Move the coordinates to the end of the array.
this.holdIndex = this.hold.length - 8;
this.holdEnd = this.hold.length - 2;
this.hold[this.holdIndex + 0] = this.currentX;
this.hold[this.holdIndex + 1] = this.currentY;
this.hold[this.holdIndex + 2] = this.hold[0];
this.hold[this.holdIndex + 3] = this.hold[1];
this.hold[this.holdIndex + 4] = this.hold[2];
this.hold[this.holdIndex + 5] = this.hold[3];
this.hold[this.holdIndex + 6] = this.currentX = this.hold[4];
this.hold[this.holdIndex + 7] = this.currentY = this.hold[5];
}
level = this.levels[this.levelIndex];
while (level < this.limit) {
if (getCurveSquaredFlatness(this.hold,this. holdIndex) < this.squaredFlatness) {
break;
}
ensureHoldCapacity(6);
subdivideCurve(
this.hold, this.holdIndex,
this.hold, this.holdIndex - 6,
this.hold, this.holdIndex);
this.holdIndex -= 6;
// Now that we have subdivided, we have constructed
// two curves of one depth lower than the original
// curve. One of those curves is in the place of
// the former curve and one of them is in the next
// set of held coordinate slots. We now set both
// curves level values to the next higher level.
level++;
this.levels[this.levelIndex] = level;
this.levelIndex++;
this.levels[this.levelIndex] = level;
}
// This curve segment is flat enough, or it is too deep
// in recursion levels to try to flatten any more. The
// two coordinates at holdIndex+6 and holdIndex+7 now
// contain the endpoint of the curve which can be the
// endpoint of an approximating line segment.
this.holdIndex += 6;
this.levelIndex--;
break;
default:
}
}
@Override
public boolean hasNext() {
return !this.done;
}
@Override
public PathElement2f next() {
if (this.done) {
throw new NoSuchElementException("flattening iterator out of bounds"); //$NON-NLS-1$
}
PathElement2f element;
PathElementType type = this.holdType;
if (type!=PathElementType.CLOSE) {
float x = this.hold[this.holdIndex + 0];
float y = this.hold[this.holdIndex + 1];
if (type == PathElementType.MOVE_TO) {
element = new PathElement2f.MovePathElement2f(x, y);
}
else {
element = new PathElement2f.LinePathElement2f(
this.lastNextX, this.lastNextY,
x, y);
}
this.lastNextX = x;
this.lastNextY = y;
}
else {
element = new PathElement2f.ClosePathElement2f(
this.lastNextX, this.lastNextY,
this.moveX, this.moveY);
this.lastNextX = this.moveX;
this.lastNextY = this.moveY;
}
searchNext();
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public PathWindingRule getWindingRule() {
return this.windingRule;
}
@Override
public boolean isPolyline() {
return false; // Because the iterator flats the path, this is no curve inside.
}
} // class FlatteningPathIterator
/** An collection of the points of the path.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class PointCollection implements Collection<Point2D> {
/**
*/
public PointCollection() {
//
}
@Override
public int size() {
return Path2f.this.size();
}
@Override
public boolean isEmpty() {
return Path2f.this.size()<=0;
}
@Override
public boolean contains(Object o) {
if (o instanceof Point2D) {
return Path2f.this.containsPoint((Point2D)o);
}
return false;
}
@Override
public Iterator<Point2D> iterator() {
return new PointIterator();
}
@Override
public Object[] toArray() {
return Path2f.this.toPointArray();
}
@Override
public <T> T[] toArray(T[] a) {
Iterator<Point2D> iterator = new PointIterator();
for(int i=0; i<a.length && iterator.hasNext(); ++i) {
a[i] = (T)iterator.next();
}
return a;
}
@Override
public boolean add(Point2D e) {
if (e!=null) {
if (Path2f.this.size()==0) {
Path2f.this.moveTo(e.getX(), e.getY());
}
else {
Path2f.this.lineTo(e.getX(), e.getY());
}
return true;
}
return false;
}
@Override
public boolean remove(Object o) {
if (o instanceof Point2D) {
Point2D p = (Point2D)o;
return Path2f.this.remove(p.getX(), p.getY());
}
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
for(Object obj : c) {
if ((!(obj instanceof Point2D))
||(!Path2f.this.containsPoint((Point2D)obj))) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection<? extends Point2D> c) {
boolean changed = false;
for(Point2D pts : c) {
if (add(pts)) {
changed = true;
}
}
return changed;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean changed = false;
for(Object obj : c) {
if (obj instanceof Point2D) {
Point2D pts = (Point2D)obj;
if (Path2f.this.remove(pts.getX(), pts.getY())) {
changed = true;
}
}
}
return changed;
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
Path2f.this.clear();
}
} // class PointCollection
/** Iterator on the points of the path.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class PointIterator implements Iterator<Point2D> {
private int index = 0;
private Point2D lastReplied = null;
/**
*/
public PointIterator() {
//
}
@Override
public boolean hasNext() {
return this.index<Path2f.this.size();
}
@Override
public Point2D next() {
try {
this.lastReplied = Path2f.this.getPointAt(this.index++);
return this.lastReplied;
}
catch(Throwable esxception) {
throw new NoSuchElementException();
}
}
@Override
public void remove() {
Point2D p = this.lastReplied;
this.lastReplied = null;
if (p==null)
throw new NoSuchElementException();
Path2f.this.remove(p.getX(), p.getY());
}
}
}