/* * $Id$ * * Copyright (C) 2015 Stephane GALLAND, Hamza JAFFALI. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * This program is free software; you can redistribute it and/or modify */ package org.arakhne.afc.math.geometry.d3.continuous; 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.geometry.PathElementType; import org.arakhne.afc.math.geometry.PathWindingRule; import org.arakhne.afc.math.geometry.d3.FunctionalPoint3D; import org.arakhne.afc.math.geometry.d3.Path3D; import org.arakhne.afc.math.geometry.d3.Point3D; import org.eclipse.xtext.xbase.lib.Pure; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.Property; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; /** A generic 3D-path. * * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public class Path3d extends AbstractShape3F<Path3d> implements Path3D<Shape3F,AlignedBox3d,AbstractPathElement3D,PathIterator3d> { private static final long serialVersionUID = -8167977956565440101L; /** Multiple of cubic & quad curve size. */ //36 = (3+3+3)*4 static final int GROW_SIZE = 36; /** Indicates if the two property arrays are strictly equals * * @param array * @param array2 * @return <code>true</code> if every Property in the first array is equals to the Property * in same index in the second array, <code>false</code> otherwise */ @Pure public static boolean propertyArraysEquals (Property<?>[] array, Property<?> [] array2) { if(array.length==array2.length) { for(int i=0; i<array.length; i++) { if(array[i]==null) { if(array2[i]!=null) return false; } else if(array2[i]==null) { return false; } else if(!array[i].getValue().equals(array2[i].getValue())) { return false; } } return true; } return false; } /** 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 PathIterator3d#isPolyline()} of <var>pi</var> is replying * <code>true</code>. * {@link #getClosestPointTo(Point3D)} avoids this restriction. * * @param pi is the iterator on the elements of the path. * @param x * @param y * @param z * @return the closest point on the shape; or the point itself * if it is inside the shape. */ public static Point3d getClosestPointTo(PathIterator3d pathIterator, double x, double y, double z) { Point3d closest = null; double bestDist = Double.POSITIVE_INFINITY; Point3d candidate; AbstractPathElement3D pe = pathIterator.next(); Path3d subPath; if (pe.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } candidate = new Point3d(pe.getToX(), pe.getToY(), pe.getToZ()); while (pathIterator.hasNext()) { pe = pathIterator.next(); candidate = null; switch(pe.type) { case MOVE_TO: candidate = new Point3d(pe.getToX(), pe.getToY(), pe.getToZ()); break; case LINE_TO: candidate = new Point3d((new Segment3d(pe.getFromX(), pe.getFromY(), pe.getFromZ(), pe.getToX(), pe.getToY(), pe.getToZ())).getClosestPointTo(new Point3d(x,y,z))); break; case CLOSE: if (!pe.isEmpty()) { candidate = new Point3d((new Segment3d(pe.getFromX(), pe.getFromY(), pe.getFromZ(), pe.getToX(), pe.getToY(), pe.getToZ())).getClosestPointTo(new Point3d(x,y,z))); } break; case QUAD_TO: subPath = new Path3d(); subPath.moveTo(pe.getFromX(), pe.getFromY(), pe.getFromZ()); subPath.quadTo( pe.getCtrlX1(), pe.getCtrlY1(), pe.getCtrlZ1(), pe.getToX(), pe.getToY(), pe.getToZ()); candidate = subPath.getClosestPointTo(new Point3d(x,y,z)); break; case CURVE_TO: subPath = new Path3d(); subPath.moveTo(pe.getFromX(), pe.getFromY(), pe.getFromZ()); subPath.curveTo( pe.getCtrlX1(), pe.getCtrlY1(), pe.getCtrlZ1(), pe.getCtrlX2(), pe.getCtrlY2(), pe.getCtrlZ2(), pe.getToX(), pe.getToY(), pe.getToZ()); candidate = subPath.getClosestPointTo(new Point3d(x,y,z)); break; default: throw new IllegalStateException( pe.type==null ? null : pe.type.toString()); } if (candidate!=null) { double d = candidate.getDistanceSquared(new Point3d(x,y,z)); if (d<bestDist) { bestDist = d; closest = candidate; } } } return closest; } /** Replies the point on the path that is farthest to the given point. * <p> * <strong>CAUTION:</strong> This function works only on path iterators * that are replying polyline primitives, ie. if the * {@link PathIterator3d#isPolyline()} of <var>pi</var> is replying * <code>true</code>. * {@link #getFarthestPointTo(Point3D)} avoids this restriction. * * @param pi is the iterator on the elements of the path. * @param x * @param y * @param z * @return the farthest point on the shape. */ public static Point3d getFarthestPointTo(PathIterator3d pathIterator, double x, double y, double z) { Point3d farthest = null; double bestDist = Double.NEGATIVE_INFINITY; Point3d candidate; AbstractPathElement3D pe = pathIterator.next(); Path3d subPath; if (pe.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } candidate = new Point3d(pe.getToX(), pe.getToY(), pe.getToZ()); while (pathIterator.hasNext()) { pe = pathIterator.next(); candidate = null; switch(pe.type) { case MOVE_TO: candidate = new Point3d(pe.getToX(), pe.getToY(), pe.getToZ()); break; case LINE_TO: candidate = new Point3d((new Segment3d(pe.getFromX(), pe.getFromY(), pe.getFromZ(), pe.getToX(), pe.getToY(), pe.getToZ())).getFarthestPointTo(new Point3d(x,y,z))); break; case CLOSE: if (!pe.isEmpty()) { candidate = new Point3d((new Segment3d(pe.getFromX(), pe.getFromY(), pe.getFromZ(), pe.getToX(), pe.getToY(), pe.getToZ())).getFarthestPointTo(new Point3d(x,y,z))); } break; case QUAD_TO: subPath = new Path3d(); subPath.moveTo(pe.getFromX(), pe.getFromY(), pe.getFromZ()); subPath.quadTo( pe.getCtrlX1(), pe.getCtrlY1(), pe.getCtrlZ1(), pe.getToX(), pe.getToY(), pe.getToZ()); candidate = subPath.getFarthestPointTo(new Point3d(x,y,z)); break; case CURVE_TO: subPath = new Path3d(); subPath.moveTo(pe.getFromX(), pe.getFromY(), pe.getFromZ()); subPath.curveTo( pe.getCtrlX1(), pe.getCtrlY1(), pe.getCtrlZ1(), pe.getCtrlX2(), pe.getCtrlY2(), pe.getCtrlZ2(), pe.getToX(), pe.getToY(), pe.getToZ()); candidate = subPath.getFarthestPointTo(new Point3d(x,y,z)); break; default: throw new IllegalStateException( pe.type==null ? null : pe.type.toString()); } if (candidate!=null) { double d = candidate.getDistanceSquared(new Point3d(x,y,z)); if (d>bestDist) { bestDist = d; farthest = candidate; } } } return farthest; } private static boolean buildGraphicalBoundingBox(PathIterator3d iterator, AlignedBox3d box) { 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; AbstractPathElement3D element; Path3d subPath; while (iterator.hasNext()) { element = iterator.next(); switch(element.type) { 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 = new Path3d(); 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 (buildGraphicalBoundingBox( subPath.getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO), box)) { if (box.getMinX()<xmin) xmin = box.getMinX(); if (box.getMinY()<ymin) ymin = box.getMinY(); if (box.getMinZ()<zmin) zmin = box.getMinZ(); if (box.getMaxX()>xmax) xmax = box.getMaxX(); if (box.getMinY()>ymax) ymax = box.getMinY(); if (box.getMinZ()>zmax) zmax = box.getMinZ(); foundOneLine = true; } break; case QUAD_TO: subPath = new Path3d(); subPath.moveTo(element.getFromX(), element.getFromY(), element.getFromZ()); subPath.quadTo( element.getCtrlX1(), element.getCtrlY1(), element.getCtrlZ1(), element.getToX(), element.getToY(), element.getToZ()); if (buildGraphicalBoundingBox( subPath.getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO), box)) { if (box.getMinX()<xmin) xmin = box.getMinX(); if (box.getMinY()<ymin) ymin = box.getMinY(); if (box.getMinZ()<zmin) zmin = box.getMinZ(); if (box.getMaxX()>xmax) xmax = box.getMaxX(); if (box.getMinY()>ymax) ymax = box.getMinY(); if (box.getMinZ()>zmax) zmax = box.getMinZ(); foundOneLine = true; } break; case MOVE_TO: case CLOSE: default: } } if (foundOneLine) { box.setFromCorners(xmin, ymin, zmin, xmax, ymax, zmax); } else { box.clear(); } return foundOneLine; } private boolean buildLogicalBoundingBox(AlignedBox3d box) { if (this.numCoordsProperty.get()>0) { 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; for(int i=0; i<this.numCoordsProperty.get(); i+= 3) { if (this.coordsProperty[i].get()<xmin) xmin = this.coordsProperty[i].get(); if (this.coordsProperty[i+1].get()<ymin) ymin = this.coordsProperty[i+1].get(); if (this.coordsProperty[i+2].get()<zmin) zmin = this.coordsProperty[i+2].get(); if (this.coordsProperty[i].get()>xmax) xmax = this.coordsProperty[i].get(); if (this.coordsProperty[i+1].get()>ymax) ymax = this.coordsProperty[i+1].get(); if (this.coordsProperty[i+2].get()>zmax) zmax = this.coordsProperty[i+2].get(); } box.setFromCorners(xmin, ymin, zmin, xmax, ymax, zmax); return true; } return false; } //--------------------------------------------------------------- /** Array of types. */ PathElementType[] types; /** Array of coords. */ DoubleProperty[] coordsProperty; /** Number of types in the array. */ IntegerProperty numTypesProperty = new SimpleIntegerProperty(0); /** Number of coords in the array. */ IntegerProperty numCoordsProperty = new SimpleIntegerProperty(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 BooleanProperty isEmptyProperty = new SimpleBooleanProperty(true); /** Indicates if the path contains base primitives * (no curve). */ private BooleanProperty isPolylineProperty = new SimpleBooleanProperty(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<AlignedBox3d> graphicalBounds = null; /** Buffer for the bounds of the path that corresponds * to all the points added in the path. */ private SoftReference<AlignedBox3d> logicalBounds = null; /** */ public Path3d() { this(PathWindingRule.NON_ZERO); } /** * @param iterator */ public Path3d(Iterator<AbstractPathElement3D> iterator) { this(PathWindingRule.NON_ZERO, iterator); } /** * @param windingRule1 */ public Path3d(PathWindingRule windingRule1) { assert(windingRule1!=null); this.types = new PathElementType[GROW_SIZE]; this.coordsProperty = new SimpleDoubleProperty[GROW_SIZE]; for(int i=0; i<this.coordsProperty.length; i++) { this.coordsProperty[i] = new SimpleDoubleProperty(); } this.windingRule = windingRule1; } /** * @param windingRule1 * @param iterator */ public Path3d(PathWindingRule windingRule1, Iterator<AbstractPathElement3D> iterator) { assert(windingRule1!=null); this.types = new PathElementType[GROW_SIZE]; this.coordsProperty = new SimpleDoubleProperty[GROW_SIZE]; for(int i=0; i<this.coordsProperty.length; i++) { this.coordsProperty[i] = new SimpleDoubleProperty(); } this.windingRule = windingRule1; add(iterator); } /** Construct a path from an existing Path3d. * * If copyProperties is true, we only copy the properties values of Path3d into * this properties. * * If copyProperties is false, the properties of this path will have same references with the Path3d * properties in parameter. So if the Path3d in parameter changes, this path will be affected. * * * @param p * @param copyProperties indicates if the properties must be copied or binded */ public Path3d(Path3d p , boolean copyProperties) { this(); this.coordsProperty = new DoubleProperty[p.coordsProperty.length]; if(copyProperties) { for(int i=0;i<p.coordsProperty.length;i++) { this.coordsProperty[i] = new SimpleDoubleProperty(p.coordsProperty[i].get()); } if(p.isEmptyProperty==null) { this.isEmptyProperty=null; } else { this.isEmptyProperty = new SimpleBooleanProperty(p.isEmptyProperty.get()); } if(p.isPolylineProperty==null) { this.isPolylineProperty=null; } else { this.isPolylineProperty = new SimpleBooleanProperty(p.isPolylineProperty.get()); } this.numCoordsProperty.set(p.numCoordsProperty.get()); this.numTypesProperty.set(p.numTypesProperty.get()); this.types = p.types.clone(); this.windingRule = p.windingRule; AlignedBox3d box; box = p.graphicalBounds==null ? null : new AlignedBox3d(p.graphicalBounds.get()); if (box!=null) { this.graphicalBounds = new SoftReference<>(box.clone()); } box = p.logicalBounds==null ? null : new AlignedBox3d(p.logicalBounds.get()); if (box!=null) { this.logicalBounds = new SoftReference<>(box.clone()); } } else { for(int i=0;i<p.coordsProperty.length;i++) { this.coordsProperty[i] = p.coordsProperty[i]; } if(p.isEmptyProperty==null) { this.isEmptyProperty=null; } else { this.isEmptyProperty = p.isEmptyProperty; } if(p.isPolylineProperty==null) { this.isPolylineProperty=null; } else { this.isPolylineProperty = p.isPolylineProperty; } this.numCoordsProperty = p.numCoordsProperty; this.numTypesProperty = p.numTypesProperty; this.types = p.types.clone(); this.windingRule = p.windingRule; AlignedBox3d box; box = p.graphicalBounds==null ? null : new AlignedBox3d(p.graphicalBounds.get()); if (box!=null) { this.graphicalBounds = new SoftReference<>(box.clone()); } box = p.logicalBounds==null ? null : new AlignedBox3d(p.logicalBounds.get()); if (box!=null) { this.logicalBounds = new SoftReference<>(box.clone()); } } } /** Remove the point with the given coordinates. * * @param x * @param y * @param z * @return <code>true</code> if the point was removed; <code>false</code> otherwise. */ boolean remove(double x, double y, double z) { for(int i=0, j=0; i<this.numCoordsProperty.get() && j<this.numTypesProperty.get();) { switch(this.types[j]) { case MOVE_TO: case LINE_TO: if (x==this.coordsProperty[i].get() && y==this.coordsProperty[i+1].get() && z==this.coordsProperty[i+2].get()) { this.numCoordsProperty.set(this.numCoordsProperty.get()-3); this.numTypesProperty.set(this.numTypesProperty.get()-1); System.arraycopy(this.coordsProperty, i+3, this.coordsProperty, i, this.numCoordsProperty.get()); System.arraycopy(this.types, j+1, this.types, j, this.numTypesProperty.get()); this.isEmptyProperty = null; return true; } i += 3; ++j; break; case CURVE_TO: if ((x==this.coordsProperty[i].get() && y==this.coordsProperty[i+1].get() && z==this.coordsProperty[i+2].get()) ||(x==this.coordsProperty[i+3].get() && y==this.coordsProperty[i+4].get() && z==this.coordsProperty[i+5].get()) ||(x==this.coordsProperty[i+6].get() && y==this.coordsProperty[i+7].get() && z==this.coordsProperty[i+8].get())) { this.numCoordsProperty.set(this.numCoordsProperty.get()-9); this.numTypesProperty.set(this.numTypesProperty.get()-1); System.arraycopy(this.coordsProperty, i+9, this.coordsProperty, i, this.numCoordsProperty.get()); System.arraycopy(this.types, j+1, this.types, j, this.numTypesProperty.get()); this.isEmptyProperty = null; this.isPolylineProperty = null; return true; } i += 9; ++j; break; case QUAD_TO: if ((x==this.coordsProperty[i].get() && y==this.coordsProperty[i+1].get() && z==this.coordsProperty[i+2].get()) ||(x==this.coordsProperty[i+3].get() && y==this.coordsProperty[i+4].get() && z==this.coordsProperty[i+5].get())) { this.numCoordsProperty.set(this.numCoordsProperty.get()-6); this.numTypesProperty.set(this.numTypesProperty.get()-1); System.arraycopy(this.coordsProperty, i+6, this.coordsProperty, i, this.numCoordsProperty.get()); System.arraycopy(this.types, j+1, this.types, j, this.numTypesProperty.get()); this.isEmptyProperty = null; this.isPolylineProperty = null; return true; } i += 6; ++j; break; case CLOSE: ++j; break; default: break; } } return false; } /** 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. */ @Pure boolean containsControlPoint(Point3D p) { double x, y, z; for(int i=0; i<this.numCoordsProperty.get();) { x = this.coordsProperty[i++].get(); y = this.coordsProperty[i++].get(); z = this.coordsProperty[i++].get(); if (x==p.getX() && y==p.getY() && z==p.getZ()) { return true; } } return false; } @Pure @Override public String toString() { StringBuilder b = new StringBuilder(); b.append("["); if (this.numCoordsProperty.get()>0) { b.append(this.coordsProperty[0].get()); for(int i=1; i<this.numCoordsProperty.get(); ++i) { b.append(", "); b.append(this.coordsProperty[i].get()); } } b.append("]"); return b.toString(); } @Override public boolean isEmpty() { if (this.isEmptyProperty==null) { this.isEmptyProperty = new SimpleBooleanProperty(true); PathIterator3d pi = getPathIteratorProperty(); AbstractPathElement3D pe; while (this.isEmptyProperty.get()==true && pi.hasNext()) { pe = pi.next(); if (pe.isDrawable()) { this.isEmptyProperty.set(false); } } } return this.isEmptyProperty.get(); } @Override public void clear() { this.types = new PathElementType[GROW_SIZE]; this.coordsProperty = new DoubleProperty[GROW_SIZE]; this.windingRule = PathWindingRule.NON_ZERO; this.numCoordsProperty.set(0); this.numTypesProperty.set(0); this.isEmptyProperty.set(true); this.isPolylineProperty.set(true); this.graphicalBounds = null; this.logicalBounds = null; } @Pure @Override public Path3d clone() { Path3d clone = super.clone(); clone.coordsProperty = this.coordsProperty.clone(); clone.types = this.types.clone(); clone.windingRule = this.windingRule; return clone; } @Pure @Override public Point3d getClosestPointTo(Point3D p) { return getClosestPointTo( getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO), p.getX(), p.getY(),p.getZ()); } @Pure @Override public Point3d getFarthestPointTo(Point3D p) { return getFarthestPointTo( getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO), p.getX(), p.getY(), p.getZ()); } @Override public void set(Shape3F s) { clear(); add(s.getPathIteratorProperty()); } @Override public AlignedBox3d toBoundingBox() { AlignedBox3d bb = this.graphicalBounds==null ? null : this.graphicalBounds.get(); if (bb==null) { bb = new AlignedBox3d(); buildGraphicalBoundingBox( getPathIteratorProperty(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 AlignedBox3d toBoundingBoxWithCtrlPoints() { AlignedBox3d bb = this.logicalBounds==null ? null : this.logicalBounds.get(); if (bb==null) { bb = new AlignedBox3d(); buildLogicalBoundingBox(bb); this.logicalBounds = new SoftReference<>(bb); } return bb; } @Override public void toBoundingBox(AbstractBoxedShape3F<?> box) { AlignedBox3d bb = this.graphicalBounds==null ? null : this.graphicalBounds.get(); if (bb==null) { bb = new AlignedBox3d(); buildGraphicalBoundingBox( getPathIteratorProperty(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(AlignedBox3f box) { AlignedBox3d bb = this.logicalBounds==null ? null : this.logicalBounds.get(); if (bb==null) { bb = new AlignedBox3d(); buildLogicalBoundingBox(bb); this.logicalBounds = new SoftReference<>(bb); } box.set(bb); } @Pure @Override public double distanceSquared(Point3D p) { Point3D c = getClosestPointTo(p); return c.getDistanceSquared(p); } @Pure @Override public double distanceL1(Point3D p) { Point3D c = getClosestPointTo(p); return c.getDistanceL1(p); } @Pure @Override public double distanceLinf(Point3D p) { Point3D c = getClosestPointTo(p); return c.getDistanceLinf(p); } /** Transform the current path. * This function changes the current path. * * @param transform is the spatial transformation to apply. * @see #createTransformedShape(Transform3D) */ @Override public void transform(Transform3D transform) { if (transform!=null) { Point3D p = new Point3d(); for(int i=0; i<this.numCoordsProperty.get();) { p.set(this.coordsProperty[i].get(), this.coordsProperty[i+1].get(), this.coordsProperty[i+2].get()); transform.transform(p); this.coordsProperty[i++].set(p.getX()); this.coordsProperty[i++].set(p.getY()); this.coordsProperty[i++].set(p.getZ()); } this.graphicalBounds = null; this.logicalBounds = null; } } @Pure @Override public Shape3F createTransformedShape(Transform3D transform) { Path3d newP = new Path3d(this,true); newP.transform(transform); return newP; } @Override public void translate(double dx, double dy, double dz) { for(int i=0; i<this.numCoordsProperty.get();) { this.coordsProperty[i].set(this.coordsProperty[i].get()+dx); i++; this.coordsProperty[i].set(this.coordsProperty[i].get()+dy); i++; this.coordsProperty[i].set(this.coordsProperty[i].get()+dz); i++; } AlignedBox3d bb; bb = this.logicalBounds==null ? null : this.logicalBounds.get(); if (bb!=null) bb.translate(dx, dy, dz); bb = this.graphicalBounds==null ? null : this.graphicalBounds.get(); if (bb!=null) bb.translate(dx, dy, dz); } @Pure @Override public boolean intersects(AbstractBoxedShape3F<?> s) { if (s.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = AbstractSegment3F.intersectsSegmentAlignedBox( curx, cury, curz, endx, endy, endz, s.getMinX(), s.getMinY(), s.getMinZ(), s.getMaxX(), s.getMaxY(), s.getMaxZ()); curx = endx; cury = endy; curz = endz; break; case QUAD_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = AbstractSegment3F.intersectsSegmentAlignedBox( curx, cury, curz, movx, movy, movz, s.getMinX(), s.getMinY(), s.getMinZ(), s.getMaxX(), s.getMaxY(), s.getMaxZ()); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Pure @Override public boolean intersects(Path3d p) { if (p.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = p.intersects(new Segment3f( 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 = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(p.clone()); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(p.clone()); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = p.intersects(new Segment3f( curx, cury, curz, movx, movy, movz)); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Pure @Override public boolean intersects(Path3f p) { if (p.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3f pi = getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3F pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3f subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = p.intersects(new Segment3f( 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 = new Path3f(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(p.clone()); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3f(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(p.clone()); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = p.intersects(new Segment3f( curx, cury, curz, movx, movy, movz)); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Pure @Override public boolean intersects(AbstractSphere3F s) { if (s.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = s.intersects(new Segment3f( 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 = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = s.intersects(new Segment3f( curx, cury, curz, movx, movy, movz)); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Pure @Override public boolean intersects(AbstractSegment3F s) { if (s.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = s.intersects(new Segment3f( 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 = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = s.intersects(new Segment3f( curx, cury, curz, movx, movy, movz)); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Pure @Override public boolean intersects(AbstractTriangle3F triangle) { if (triangle.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = triangle.intersects(new Segment3f( 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 = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(triangle); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(triangle); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = triangle.intersects(new Segment3f( curx, cury, curz, movx, movy, movz)); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Pure @Override public boolean intersects(AbstractCapsule3F s) { if (s.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = s.intersects(new Segment3f( 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 = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = s.intersects(new Segment3f( curx, cury, curz, movx, movy, movz)); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Pure @Override public boolean intersects(AbstractOrientedBox3F s) { if (s.isEmpty()) return false; int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = s.intersects(new Segment3f( 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 = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(s); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = s.intersects(new Segment3f( curx, cury, curz, movx, movy, movz)); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Override @Pure public boolean intersects(Plane3D<?> p) { int mask = (this.windingRule == PathWindingRule.NON_ZERO ? -1 : 2); boolean intersects = false; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext() && intersects==false) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = cury = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); intersects = p.intersects(new Segment3f( 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 = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); intersects = subPath.intersects(p); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury,curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); intersects = subPath.intersects(p); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { intersects = p.intersects(new Segment3f( curx, cury, curz, movx, movy, movz)); } curx = movx; cury = movy; curz = movz; break; default: } } return intersects && (mask!=0); } @Override public boolean isPolyline() { if (this.isPolylineProperty==null) { this.isPolylineProperty.set(true); PathIterator3d pi = getPathIteratorProperty(); AbstractPathElement3D pe; PathElementType t; while (this.isPolylineProperty.get()==true && pi.hasNext()) { pe = pi.next(); t = pe.getType(); if (t==PathElementType.CURVE_TO || t==PathElementType.QUAD_TO) { this.isPolylineProperty.set(false); } } } return this.isPolylineProperty.get(); } /** * {@inheritDoc} */ @Pure @Override public boolean contains(double x, double y, double z) { AlignedBox3d ab = this.toBoundingBox(); return ab.contains(new Point3f(x,y,z)); } @Pure public boolean equals(Path3d path) { return (this.numCoordsProperty.get()==path.numCoordsProperty.get() && this.numTypesProperty.get()==path.numTypesProperty.get() && propertyArraysEquals(this.coordsProperty, path.coordsProperty) && Arrays.equals(this.types, path.types) && this.windingRule==path.windingRule); } @Pure @Override public boolean equals(Object obj) { if (obj instanceof Path3d) { Path3d path = (Path3d)obj; return (this.numCoordsProperty.get()==path.numCoordsProperty.get() && this.numTypesProperty.get()==path.numTypesProperty.get() && propertyArraysEquals(this.coordsProperty, path.coordsProperty) && Arrays.equals(this.types, path.types) && this.windingRule==path.windingRule); } else if (obj instanceof Path3f) { Path3f path = (Path3f)obj; return (this.numCoordsProperty.get()==path.numCoords && this.numTypesProperty.get()==path.numTypes && this.toString().equals(path.toString()) && Arrays.equals(this.types, path.types) && this.windingRule==path.windingRule); } return false; } @Pure @Override public int hashCode() { long bits = 1L; bits = 31L * bits + this.numCoordsProperty.get(); bits = 31L * bits + this.numTypesProperty.get(); bits = 31L * bits + Arrays.hashCode(this.coordsProperty); bits = 31L * bits + Arrays.hashCode(this.types); bits = 31L * bits + this.windingRule.ordinal(); return (int) (bits ^ (bits >> 32)); } @Pure @Override public PathIterator3f getPathIterator(double flatness) { return new Path3f.FlatteningPathIterator3f(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 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 public PathIterator3f getPathIterator(Transform3D transform, double flatness) { return new Path3f.FlatteningPathIterator3f(getWindingRule(), getPathIterator(transform), flatness, 10); } /** {@inheritDoc} */ @Pure @Override public PathIterator3f getPathIterator(Transform3D transform) { if (transform == null) { return new CopyPathIterator3f(); } return new TransformPathIterator3f(transform); } @Pure @Override public PathIterator3d getPathIteratorProperty(double flatness) { return new FlatteningPathIterator3d(getWindingRule(), getPathIteratorProperty(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 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 public PathIterator3d getPathIteratorProperty(Transform3D transform, double flatness) { return new FlatteningPathIterator3d(getWindingRule(), getPathIteratorProperty(transform), flatness, 10); } /** {@inheritDoc} */ @Pure @Override public PathIterator3d getPathIteratorProperty(Transform3D transform) { if (transform == null) { return new CopyPathIterator3d(); } return new TransformPathIterator3d(transform); } @Pure @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<AbstractPathElement3D> iterator) { AbstractPathElement3D element; while (iterator.hasNext()) { element = iterator.next(); switch(element.type) { 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; default: } } } /**Add the element in parameter into this path. * * If the element changes, the path will not be affected. * * @param pathElement */ public void add(AbstractPathElement3D element) { switch(element.type) { 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; default: } } /** Remove the last action. */ public void removeLast() { if (this.numTypesProperty.get()>0) { switch(this.types[this.numTypesProperty.get()-1]) { case CLOSE: // no coord to remove break; case MOVE_TO: case LINE_TO: this.numCoordsProperty.set(this.numCoordsProperty.get()-3); break; case CURVE_TO: this.numCoordsProperty.set(this.numCoordsProperty.get()-9); this.isPolylineProperty = null; break; case QUAD_TO: this.numCoordsProperty.set(this.numCoordsProperty.get()-6); this.isPolylineProperty = null; break; default: throw new IllegalStateException(); } this.numTypesProperty.set(this.numTypesProperty.get()-1); this.isEmptyProperty = null; this.graphicalBounds = null; this.logicalBounds = null; } } /** Change the coordinates of the last inserted point. * * @param x * @param y * @param z */ public void setLastPoint(double x, double y, double z) { if (this.numCoordsProperty.get()>=3) { this.coordsProperty[this.numCoordsProperty.get()-3].set(x); this.coordsProperty[this.numCoordsProperty.get()-2].set(y); this.coordsProperty[this.numCoordsProperty.get()-1].set(z); this.graphicalBounds = null; this.logicalBounds = null; } } /** Change the coordinates of the last inserted point. * * If the point in parameter is modified, the path will be changed also. * * @param point */ public void setLastPoint(Point3d point) { if (this.numCoordsProperty.get()>=3) { this.coordsProperty[this.numCoordsProperty.get()-3] = point.xProperty; this.coordsProperty[this.numCoordsProperty.get()-2] = point.yProperty; this.coordsProperty[this.numCoordsProperty.get()-1] = point.zProperty; this.graphicalBounds = null; this.logicalBounds = null; } } private void ensureSlots(boolean needMove, int n) { if (needMove && this.numTypesProperty.get()==0) { throw new IllegalStateException("missing initial moveto in path definition"); } if (this.types.length==this.numTypesProperty.get()) { this.types = Arrays.copyOf(this.types, this.types.length+GROW_SIZE); } while ((this.numCoordsProperty.get() + n)>=this.coordsProperty.length) { this.coordsProperty = Arrays.copyOf(this.coordsProperty, this.coordsProperty.length+GROW_SIZE); } for( int i=0; i<this.coordsProperty.length; i++) { if(this.coordsProperty[i]==null) this.coordsProperty[i] = new SimpleDoubleProperty(); } } /** * 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 Z coordinate */ public void moveTo(double x, double y, double z) { if (this.numTypesProperty.get()>0 && this.types[this.numTypesProperty.get()-1]==PathElementType.MOVE_TO) { this.coordsProperty[this.numCoordsProperty.get()-3].set(x); this.coordsProperty[this.numCoordsProperty.get()-2].set(y); this.coordsProperty[this.numCoordsProperty.get()-1].set(z); } else { ensureSlots(false, 3); this.types[this.numTypesProperty.get()] = PathElementType.MOVE_TO; this.numTypesProperty.set(this.numTypesProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(x); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(y); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(z); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); } this.graphicalBounds = null; this.logicalBounds = null; } /** * Adds a point to the path by moving to the specified * coordinates specified in point in paramater. * * We store the property here, and not the values. So when the point changes, * the path will be automatically updated. * * @param point the specified point */ public void moveTo(Point3d point) { if (this.numTypesProperty.get()>0 && this.types[this.numTypesProperty.get()-1]==PathElementType.MOVE_TO) { this.coordsProperty[this.numCoordsProperty.get()-3] = point.xProperty; this.coordsProperty[this.numCoordsProperty.get()-2] = point.yProperty; this.coordsProperty[this.numCoordsProperty.get()-1] = point.zProperty; } else { ensureSlots(false, 3); this.types[this.numTypesProperty.get()] = PathElementType.MOVE_TO; this.numTypesProperty.set(this.numTypesProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = point.xProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = point.yProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = point.zProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); } 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 the point in paramater. * * We store the property here, and not the value. So when the point changes, * the path will be automatically updated. * * @param point the specified point */ public void lineTo(Point3d point) { ensureSlots(true, 3); this.types[this.numTypesProperty.get()] = PathElementType.LINE_TO; this.numTypesProperty.set(this.numTypesProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = point.xProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = point.yProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = point.zProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.isEmptyProperty = null; 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 double precision. * * @param x the specified X coordinate * @param y the specified Y coordinate * @param z the specified Z coordinate */ public void lineTo(double x, double y, double z) { ensureSlots(true, 3); this.types[this.numTypesProperty.get()] = PathElementType.LINE_TO; this.numTypesProperty.set(this.numTypesProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(x); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(y); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(z); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.isEmptyProperty = 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,z2)}, * using the specified point {@code (x1,y1,z1)} 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 Z coordinate of the final end point */ public void quadTo(double x1, double y1, double z1, double x2, double y2, double z2) { ensureSlots(true, 6); this.types[this.numTypesProperty.get()] = PathElementType.QUAD_TO; this.numTypesProperty.set(this.numTypesProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(x1); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(y1); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(z1); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(x2); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(y2); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(z2); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.isEmptyProperty = null; this.isPolylineProperty.set(false); 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 endPoint, * using the specified controlPoint as a quadratic * parametric control point. * All coordinates are specified in Point3d. * * We store the property here, and not the value. So when the points changes, * the path will be automatically updated. * * @param controlPoint the quadratic control point * @param endPoint the final end point */ public void quadTo(Point3d controlPoint, Point3d endPoint) { ensureSlots(true, 6); this.types[this.numTypesProperty.get()] = PathElementType.QUAD_TO; this.numTypesProperty.set(this.numTypesProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint.xProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint.yProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint.zProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = endPoint.xProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = endPoint.yProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = endPoint.zProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.isEmptyProperty = null; this.isPolylineProperty.set(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,z3)}, * using the specified points {@code (x1,y1,z1)} and {@code (x2,y2,z2)} 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 */ public void curveTo(double x1, double y1, double z1, double x2, double y2, double z2, double x3, double y3, double z3) { ensureSlots(true, 9); this.types[this.numTypesProperty.get()] = PathElementType.CURVE_TO; this.numTypesProperty.set(this.numTypesProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(x1); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(y1); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(z1); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(x2); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(y2); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(z2); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(x3); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(y3); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()].set(z3); this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.isEmptyProperty = null; this.isPolylineProperty.set(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 endPoint, * using the specified points controlPoint1 and controlPoint2 as * Bézier control points. * All coordinates are specified in Point3d. * * We store the property here, and not the value. So when the points changes, * the path will be automatically updated. * * @param controlPoint1 the first Bézier control point * @param controlPoint2 the second Bézier control point * @param endPoint the final end point */ public void curveTo(Point3d controlPoint1, Point3d controlPoint2, Point3d endPoint) { ensureSlots(true, 9); this.types[this.numTypesProperty.get()] = PathElementType.CURVE_TO; this.numTypesProperty.set(this.numTypesProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint1.xProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint1.yProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint1.zProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint2.xProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint2.yProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = controlPoint2.zProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = endPoint.xProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = endPoint.yProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.coordsProperty[this.numCoordsProperty.get()] = endPoint.zProperty; this.numCoordsProperty.set(this.numCoordsProperty.get()+1); this.isEmptyProperty = null; this.isPolylineProperty.set(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.numTypesProperty.get()<=0 || (this.types[this.numTypesProperty.get()-1]!=PathElementType.CLOSE &&this.types[this.numTypesProperty.get()-1]!=PathElementType.MOVE_TO)) { ensureSlots(true, 0); this.types[this.numTypesProperty.get()] = PathElementType.CLOSE; this.numTypesProperty.set(this.numTypesProperty.get()+1); } } /** Replies the number of points in the path. * * @return the number of points in the path. */ @Pure public int size() { return this.numCoordsProperty.get()/3; } /** Replies the total lentgh of the path. * * @return the length of the path. */ //FIXME TO BE IMPLEMENTED IN POLYLINE public double length() { if (this.isEmpty()) return 0; double length = 0; PathIterator3d pi = getPathIteratorProperty(MathConstants.SPLINE_APPROXIMATION_RATIO); AbstractPathElement3D pathElement = pi.next(); if (pathElement.type != PathElementType.MOVE_TO) { throw new IllegalArgumentException("missing initial moveto in path definition"); } Path3d subPath; double curx, cury, curz, movx, movy, movz, endx, endy, endz; curx = movx = pathElement.getToX(); cury = movy = pathElement.getToY(); curz = movz = pathElement.getToZ(); while (pi.hasNext()) { pathElement = pi.next(); switch (pathElement.type) { case MOVE_TO: movx = curx = pathElement.getToX(); movy = cury = pathElement.getToY(); movz = curz = pathElement.getToZ(); break; case LINE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); length += FunctionalPoint3D.distancePointPoint( 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 = new Path3d(); subPath.moveTo(curx, cury, curz); subPath.quadTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), endx, endy, endz); length += subPath.length(); curx = endx; cury = endy; curz = endz; break; case CURVE_TO: endx = pathElement.getToX(); endy = pathElement.getToY(); endz = pathElement.getToZ(); subPath = new Path3d(); subPath.moveTo(curx, cury, curz); subPath.curveTo( pathElement.getCtrlX1(), pathElement.getCtrlY1(), pathElement.getCtrlZ1(), pathElement.getCtrlX2(), pathElement.getCtrlY2(), pathElement.getCtrlZ2(), endx, endy, endz); length += subPath.length(); curx = endx; cury = endy; curz = endz; break; case CLOSE: if (curx != movx || cury != movy || curz != movz) { length += FunctionalPoint3D.distancePointPoint( curx, cury, curz, movx, movy, movz); } curx = movx; cury = movy; cury = movz; break; default: } } return length; } /** Replies the coordinates of this path in an array of * double precision floating-point numbers. * * @return the coordinates. */ @Pure 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. */ @Pure public double[] toDoubleArray(Transform3D transform) { double[] clone = new double[this.numCoordsProperty.get()]; if (transform==null) { for(int i=0; i<this.numCoordsProperty.get(); ++i) { clone[i] = this.coordsProperty[i].get(); } } else { Point3f p = new Point3f(); for(int i=0; i<clone.length;) { p.x = this.coordsProperty[i].get(); p.y = this.coordsProperty[i+1].get(); p.y = this.coordsProperty[i+2].get(); transform.transform(p); clone[i++] = p.x; clone[i++] = p.y; clone[i++] = p.z; } } return clone; } /** Replies the points of this path in an array. * * @return the points. */ @Pure public final Point3D[] 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. */ @Pure public Point3D[] toPointArray(Transform3D transform) { Point3D[] clone = new Point3D[this.numCoordsProperty.get()/3]; if (transform==null) { for(int i=0, j=0; j<this.numCoordsProperty.get(); ++i) { clone[i] = new Point3f( this.coordsProperty[j++].get(), this.coordsProperty[j++].get(), this.coordsProperty[j++].get()); } } else { for(int i=0, j=0; j<clone.length; ++i) { clone[i] = new Point3f( this.coordsProperty[j++].get(), this.coordsProperty[j++].get(), this.coordsProperty[j++].get()); transform.transform(clone[i]); } } return clone; } /** Replies the point at the given index. * The index is in [0;{@link #size()}). * * If the returned point is modified, the path will be changed also. * * @param index * @return the point at the given index. */ @Pure public Point3d getPointAt(int index) { Point3d point = new Point3d(); point.xProperty = this.coordsProperty[index*3]; point.yProperty = this.coordsProperty[index*3+1]; point.zProperty = this.coordsProperty[index*3+2]; return point; } /** Replies the last point in the path. * * If the returned point is modified, the path will be changed also. * * @return the last point. */ @Pure public Point3d getCurrentPoint() { Point3d point = new Point3d(); point.xProperty = this.coordsProperty[this.numCoordsProperty.get()-3]; point.yProperty = this.coordsProperty[this.numCoordsProperty.get()-2]; point.zProperty = this.coordsProperty[this.numCoordsProperty.get()-1]; return point; } //----------------------------------------------------------------------------- /** A path iterator that does not transform the coordinates. * * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class CopyPathIterator3d implements PathIterator3d { private final Point3D p1 = new Point3d(); private final Point3D p2 = new Point3d(); private IntegerProperty iTypeProperty = new SimpleIntegerProperty(0); private IntegerProperty iCoordProperty = new SimpleIntegerProperty(0); private DoubleProperty movexProperty, moveyProperty, movezProperty; /** */ public CopyPathIterator3d() { this.movexProperty = new SimpleDoubleProperty(); this.moveyProperty = new SimpleDoubleProperty(); this.movezProperty = new SimpleDoubleProperty(); } @Pure @Override public boolean hasNext() { return this.iTypeProperty.get()<Path3d.this.numTypesProperty.get(); } @Override public AbstractPathElement3D next() { int type = this.iTypeProperty.get(); if (this.iTypeProperty.get()>=Path3d.this.numTypesProperty.get()) { throw new NoSuchElementException(); } AbstractPathElement3D element = null; switch(Path3d.this.types[type]) { case MOVE_TO: if (this.iCoordProperty.get()+3>Path3d.this.numCoordsProperty.get()) { throw new NoSuchElementException(); } this.movexProperty.set(Path3d.this.coordsProperty[this.iCoordProperty.get()].get()); this.iCoordProperty.set(this.iCoordProperty.get()+1); this.moveyProperty.set(Path3d.this.coordsProperty[this.iCoordProperty.get()].get()); this.iCoordProperty.set(this.iCoordProperty.get()+1); this.movezProperty.set(Path3d.this.coordsProperty[this.iCoordProperty.get()].get()); this.iCoordProperty.set(this.iCoordProperty.get()+1); this.p2.set(this.movexProperty.get(), this.moveyProperty.get(), this.movezProperty.get()); element = new AbstractPathElement3D.MovePathElement3d( this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; case LINE_TO: if (this.iCoordProperty.get()+3>Path3d.this.numCoordsProperty.get()) { throw new NoSuchElementException(); } this.p1.set(this.p2); this.p2.set( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); element = new AbstractPathElement3D.LinePathElement3d( this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; case QUAD_TO: { if (this.iCoordProperty.get()+6>Path3d.this.numCoordsProperty.get()) { throw new NoSuchElementException(); } this.p1.set(this.p2); double ctrlx = Path3d.this.coordsProperty[this.iCoordProperty.get()].get(); double ctrly = Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(); double ctrlz = Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get(); this.iCoordProperty.set(this.iCoordProperty.get()+3); this.p2.set( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); element = new AbstractPathElement3D.QuadPathElement3d( this.p1.getX(), this.p1.getY(), this.p1.getZ(), ctrlx, ctrly, ctrlz, this.p2.getX(), this.p2.getY(), this.p2.getZ()); } break; case CURVE_TO: { if (this.iCoordProperty.get()+9>Path3d.this.numCoordsProperty.get()) { throw new NoSuchElementException(); } this.p1.set(this.p2); double ctrlx1 = Path3d.this.coordsProperty[this.iCoordProperty.get()].get(); double ctrly1 = Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(); double ctrlz1 = Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get(); double ctrlx2 = Path3d.this.coordsProperty[this.iCoordProperty.get()+3].get(); double ctrly2 = Path3d.this.coordsProperty[this.iCoordProperty.get()+4].get(); double ctrlz2 = Path3d.this.coordsProperty[this.iCoordProperty.get()+5].get(); this.iCoordProperty.set(this.iCoordProperty.get()+6); this.p2.set( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); element = new AbstractPathElement3D.CurvePathElement3d( 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.movexProperty.get(), this.moveyProperty.get(), this.movezProperty.get()); element = new AbstractPathElement3D.ClosePathElement3d( this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; default: } if (element==null) throw new NoSuchElementException(); this.iTypeProperty.set(this.iTypeProperty.get()+1); return element; } @Override public void remove() { throw new UnsupportedOperationException(); } @Pure @Override public PathWindingRule getWindingRule() { return Path3d.this.getWindingRule(); } @Override public boolean isPolyline() { return Path3d.this.isPolyline(); } } // class CopyPathIterator3d /** A path iterator that does not transform the coordinates. * * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class CopyPathIterator3f implements PathIterator3f { private final Point3D p1 = new Point3f(); private final Point3D p2 = new Point3f(); private int iType = 0; private int iCoord = 0; private double movex, movey, movez; /** */ public CopyPathIterator3f() { // } @Pure @Override public boolean hasNext() { return this.iType<Path3d.this.numTypesProperty.get(); } @Override public AbstractPathElement3F next() { int type = this.iType; if (this.iType>=Path3d.this.numTypesProperty.get()) { throw new NoSuchElementException(); } AbstractPathElement3F element = null; switch(Path3d.this.types[type]) { case MOVE_TO: if (this.iCoord+3>Path3d.this.numCoordsProperty.get()) { throw new NoSuchElementException(); } this.movex = Path3d.this.coordsProperty[this.iCoord++].get(); this.movey = Path3d.this.coordsProperty[this.iCoord++].get(); this.movez = Path3d.this.coordsProperty[this.iCoord++ ].get(); this.p2.set(this.movex, this.movey, this.movez); element = new AbstractPathElement3F.MovePathElement3f( this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; case LINE_TO: if (this.iCoord+3>Path3d.this.numCoordsProperty.get()) { throw new NoSuchElementException(); } this.p1.set(this.p2); this.p2.set( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); element = new AbstractPathElement3F.LinePathElement3f( this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; case QUAD_TO: { if (this.iCoord+6>Path3d.this.numCoordsProperty.get()) { throw new NoSuchElementException(); } this.p1.set(this.p2); double ctrlx = Path3d.this.coordsProperty[this.iCoord++].get(); double ctrly = Path3d.this.coordsProperty[this.iCoord++].get(); double ctrlz = Path3d.this.coordsProperty[this.iCoord++].get(); this.p2.set( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); element = new AbstractPathElement3F.QuadPathElement3f( this.p1.getX(), this.p1.getY(), this.p1.getZ(), ctrlx, ctrly, ctrlz, this.p2.getX(), this.p2.getY(), this.p2.getZ()); } break; case CURVE_TO: { if (this.iCoord+9>Path3d.this.numCoordsProperty.get()) { throw new NoSuchElementException(); } this.p1.set(this.p2); double ctrlx1 = Path3d.this.coordsProperty[this.iCoord++].get(); double ctrly1 = Path3d.this.coordsProperty[this.iCoord++].get(); double ctrlz1 = Path3d.this.coordsProperty[this.iCoord++].get(); double ctrlx2 = Path3d.this.coordsProperty[this.iCoord++].get(); double ctrly2 = Path3d.this.coordsProperty[this.iCoord++].get(); double ctrlz2 = Path3d.this.coordsProperty[this.iCoord++].get(); this.p2.set( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); element = new AbstractPathElement3F.CurvePathElement3f( 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 = new AbstractPathElement3F.ClosePathElement3f( this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; default: } if (element==null) throw new NoSuchElementException(); ++this.iType; return element; } @Override public void remove() { throw new UnsupportedOperationException(); } @Pure @Override public PathWindingRule getWindingRule() { return Path3d.this.getWindingRule(); } @Override public boolean isPolyline() { return Path3d.this.isPolyline(); } } // class CopyPathIterator3f /** A path iterator that transforms the coordinates. * * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class TransformPathIterator3d implements PathIterator3d { private final Transform3D transform; private final Point3D p1 = new Point3d(); private final Point3D p2 = new Point3d(); private final Point3D ptmp1 = new Point3d(); private final Point3D ptmp2 = new Point3d(); private IntegerProperty iTypeProperty = new SimpleIntegerProperty(0); private IntegerProperty iCoordProperty = new SimpleIntegerProperty(0); private DoubleProperty movexProperty, moveyProperty, movezProperty; /** * @param transform1 */ public TransformPathIterator3d(Transform3D transform1) { assert(transform1!=null); this.transform = transform1; this.movexProperty = new SimpleDoubleProperty(); this.moveyProperty = new SimpleDoubleProperty(); this.movezProperty = new SimpleDoubleProperty(); } @Pure @Override public boolean hasNext() { return this.iTypeProperty.get()<Path3d.this.numTypesProperty.get(); } @Override public AbstractPathElement3D next() { if (this.iTypeProperty.get()>=Path3d.this.numTypesProperty.get()) { throw new NoSuchElementException(); } AbstractPathElement3D element = null; switch(Path3d.this.types[this.iTypeProperty.get()]) { case MOVE_TO: this.movexProperty.set(Path3d.this.coordsProperty[this.iCoordProperty.get()].get()); this.iCoordProperty.set(this.iCoordProperty.get()+1); this.moveyProperty.set(Path3d.this.coordsProperty[this.iCoordProperty.get()].get()); this.iCoordProperty.set(this.iCoordProperty.get()+1); this.movezProperty.set(Path3d.this.coordsProperty[this.iCoordProperty.get()].get()); this.iCoordProperty.set(this.iCoordProperty.get()+1); this.p2.set(this.movexProperty.get(), this.moveyProperty.get(), this.movezProperty.get()); this.transform.transform(this.p2); element = new AbstractPathElement3D.MovePathElement3d( this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; case LINE_TO: this.p1.set(this.p2); this.p2.set( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); this.transform.transform(this.p2); element = new AbstractPathElement3D.LinePathElement3d( 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( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); this.transform.transform(this.ptmp1); this.p2.set( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); this.transform.transform(this.p2); element = new AbstractPathElement3D.QuadPathElement3d( 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( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); this.transform.transform(this.ptmp1); this.ptmp2.set( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); this.transform.transform(this.ptmp2); this.p2.set( Path3d.this.coordsProperty[this.iCoordProperty.get()].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+1].get(), Path3d.this.coordsProperty[this.iCoordProperty.get()+2].get()); this.iCoordProperty.set(this.iCoordProperty.get()+3); this.transform.transform(this.p2); element = new AbstractPathElement3D.CurvePathElement3d( 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.movexProperty.get(), this.moveyProperty.get(), this.movezProperty.get()); this.transform.transform(this.p2); element = new AbstractPathElement3D.ClosePathElement3d( this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; default: } if (element==null) throw new NoSuchElementException(); this.iTypeProperty.set(this.iTypeProperty.get()+1); return element; } @Override public void remove() { throw new UnsupportedOperationException(); } @Pure @Override public PathWindingRule getWindingRule() { return Path3d.this.getWindingRule(); } @Override public boolean isPolyline() { return Path3d.this.isPolyline(); } } // class TransformPathIterator3d /** A path iterator that transforms the coordinates. * * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class TransformPathIterator3f implements PathIterator3f { private final Transform3D transform; private final Point3D p1 = new Point3f(); private final Point3D p2 = new Point3f(); private final Point3D ptmp1 = new Point3f(); private final Point3D ptmp2 = new Point3f(); private int iType = 0; private int iCoord = 0; private double movex, movey, movez; /** * @param transform1 */ public TransformPathIterator3f(Transform3D transform1) { assert(transform1!=null); this.transform = transform1; } @Pure @Override public boolean hasNext() { return this.iType<Path3d.this.numTypesProperty.get(); } @Override public AbstractPathElement3F next() { if (this.iType>=Path3d.this.numTypesProperty.get()) { throw new NoSuchElementException(); } AbstractPathElement3F element = null; switch(Path3d.this.types[this.iType]) { case MOVE_TO: this.movex = Path3d.this.coordsProperty[this.iCoord++].get(); this.movey = Path3d.this.coordsProperty[this.iCoord++].get(); this.movez = Path3d.this.coordsProperty[this.iCoord++].get(); this.p2.set(this.movex, this.movey, this.movez); this.transform.transform(this.p2); element = new AbstractPathElement3F.MovePathElement3f( this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; case LINE_TO: this.p1.set(this.p2); this.p2.set( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); this.transform.transform(this.p2); element = new AbstractPathElement3F.LinePathElement3f( 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( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); this.transform.transform(this.ptmp1); this.p2.set( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); this.transform.transform(this.p2); element = new AbstractPathElement3F.QuadPathElement3f( 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( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); this.transform.transform(this.ptmp1); this.ptmp2.set( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); this.transform.transform(this.ptmp2); this.p2.set( Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get(), Path3d.this.coordsProperty[this.iCoord++].get()); this.transform.transform(this.p2); element = new AbstractPathElement3F.CurvePathElement3f( 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 = new AbstractPathElement3F.ClosePathElement3f( this.p1.getX(), this.p1.getY(), this.p1.getZ(), this.p2.getX(), this.p2.getY(), this.p2.getZ()); break; default: } if (element==null) throw new NoSuchElementException(); ++this.iType; return element; } @Override public void remove() { throw new UnsupportedOperationException(); } @Pure @Override public PathWindingRule getWindingRule() { return Path3d.this.getWindingRule(); } @Override public boolean isPolyline() { return Path3d.this.isPolyline(); } } // class TransformPathIterator3f /** A path iterator that is flattening the path. * This iterator was copied from AWT FlatteningPathIterator. * * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ protected static class FlatteningPathIterator3d implements PathIterator3d { /** Winding rule of the path. */ private final PathWindingRule windingRule; /** The source iterator. */ private final PathIterator3d 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. * */ private double hold[] = new double[36]; /** The index of the last curve segment being held for interpolation. */ private IntegerProperty holdEndProperty = new SimpleIntegerProperty(); /** * 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 IntegerProperty holdIndexProperty = new SimpleIntegerProperty(); /** The ending x of the last segment. */ private DoubleProperty currentXProperty = new SimpleDoubleProperty(); /** The ending y of the last segment. */ private DoubleProperty currentYProperty = new SimpleDoubleProperty(); /** The ending z of the last segment. */ private DoubleProperty currentZProperty = new SimpleDoubleProperty(); /** The x of the last move segment. */ private DoubleProperty moveXProperty = new SimpleDoubleProperty(); /** The y of the last move segment. */ private DoubleProperty moveYProperty = new SimpleDoubleProperty(); /** The z of the last move segment. */ private DoubleProperty moveZProperty = new SimpleDoubleProperty(); /** The index of the entry in the * levels array of the curve segment * at the holdIndex */ private IntegerProperty levelIndexProperty = new SimpleIntegerProperty(); /** True when iteration is done. */ private BooleanProperty doneProperty = new SimpleBooleanProperty(); /** The type of the path element. */ private PathElementType holdType; /** The x of the last move segment replied by next. */ private DoubleProperty lastNextXProperty = new SimpleDoubleProperty(); /** The y of the last move segment replied by next. */ private DoubleProperty lastNextYProperty = new SimpleDoubleProperty(); /** The y of the last move segment replied by next. */ private DoubleProperty lastNextZProperty = new SimpleDoubleProperty(); /** * @param windingRule1 is the winding rule of the path. * @param pathIterator1 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 limit1 the maximum number of recursive subdivisions * allowed for any curved segment */ public FlatteningPathIterator3d(PathWindingRule windingRule1, PathIterator3d pathIterator1, double flatness, int limit1) { assert(windingRule1!=null); assert(flatness>=0f); assert(limit1>=0); this.windingRule = windingRule1; this.pathIterator = pathIterator1; this.squaredFlatness = flatness * flatness; this.limit = limit1; this.levels = new int[limit1 + 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.holdIndexProperty.get() - want < 0) { int have = this.hold.length - this.holdIndexProperty.get(); int newsize = this.hold.length + GROW_SIZE; double newhold[] = new double[newsize]; System.arraycopy(this.hold, this.holdIndexProperty.get(), newhold, this.holdIndexProperty.get() + GROW_SIZE, have); this.hold = newhold; this.holdIndexProperty.set(this.holdIndexProperty.get()+GROW_SIZE); this.holdEndProperty.set(this.holdEndProperty.get()+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. */ @Pure private static double getQuadSquaredFlatness(double coords[], int offset) { return AbstractSegment3F.distanceSquaredLinePoint( coords[offset + 0], coords[offset + 1], coords[offset + 2], coords[offset + 6], coords[offset + 7], coords[offset + 8], coords[offset + 3], 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> + 8 * 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> + 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 9 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 9 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 9 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 z1 = src[srcoff + 2]; double ctrlx = src[srcoff + 3]; double ctrly = src[srcoff + 4]; double ctrlz = src[srcoff + 5]; double x2 = src[srcoff + 6]; double y2 = src[srcoff + 7]; double z2 = src[srcoff + 8]; if (left != null) { left[leftoff + 0] = x1; left[leftoff + 1] = y1; left[leftoff + 2] = z1; } if (right != null) { right[rightoff + 6] = x2; right[rightoff + 7] = y2; right[rightoff + 8] = z2; } x1 = (x1 + ctrlx) / 2f; y1 = (y1 + ctrly) / 2f; z1 = (z1 + ctrlz) / 2f; x2 = (x2 + ctrlx) / 2f; y2 = (y2 + ctrly) / 2f; z2 = (z2 + ctrlz) / 2f; ctrlx = (x1 + x2) / 2f; ctrly = (y1 + y2) / 2f; ctrlz = (z1 + z2) / 2f; if (left != null) { left[leftoff + 3] = x1; left[leftoff + 4] = y1; left[leftoff + 5] = z1; left[leftoff + 6] = ctrlx; left[leftoff + 7] = ctrly; left[leftoff + 8] = ctrlz; } if (right != null) { right[rightoff + 0] = ctrlx; right[rightoff + 1] = ctrly; right[rightoff + 2] = ctrlz; right[rightoff + 3] = x2; right[rightoff + 4] = y2; right[rightoff + 5] = z2; } } /** * 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>CubicCurve3D</code> * specified by the coordinates in <code>coords</code> at * the specified offset. */ @Pure private static double getCurveSquaredFlatness(double coords[], int offset) { return Math.max( AbstractSegment3F.distanceSquaredSegmentPoint( coords[offset + 9], coords[offset + 10], coords[offset + 11], coords[offset + 3], coords[offset + 4], coords[offset + 5], coords[offset + 0], coords[offset + 1], coords[offset + 2]), AbstractSegment3F.distanceSquaredSegmentPoint( coords[offset + 9], coords[offset + 10], coords[offset + 11], coords[offset + 6], coords[offset + 7], coords[offset + 8], coords[offset + 0], coords[offset + 1], coords[offset + 2])); } /** * Subdivides the cubic curve specified by the coordinates * stored in the <code>src</code> array at indices <code>srcoff</code> * through (<code>srcoff</code> + 11) 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> + 9), 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 9 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 9 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 9 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 z1 = src[srcoff + 2]; double ctrlx1 = src[srcoff + 3]; double ctrly1 = src[srcoff + 4]; double ctrlz1 = src[srcoff + 5]; double ctrlx2 = src[srcoff + 6]; double ctrly2 = src[srcoff + 7]; double ctrlz2 = src[srcoff + 8]; double x2 = src[srcoff + 9]; double y2 = src[srcoff + 10]; double z2 = src[srcoff + 11]; if (left != null) { left[leftoff + 0] = x1; left[leftoff + 1] = y1; left[leftoff + 2] = z1; } if (right != null) { right[rightoff + 9] = x2; right[rightoff + 10] = y2; right[rightoff + 11] = z2; } x1 = (x1 + ctrlx1) / 2f; y1 = (y1 + ctrly1) / 2f; y1 = (z1 + ctrlz1) / 2f; x2 = (x2 + ctrlx2) / 2f; y2 = (y2 + ctrly2) / 2f; z2 = (z2 + ctrlz2) / 2f; double centerx = (ctrlx1 + ctrlx2) / 2f; double centery = (ctrly1 + ctrly2) / 2f; double centerz = (ctrlz1 + ctrlz2) / 2f; ctrlx1 = (x1 + centerx) / 2f; ctrly1 = (y1 + centery) / 2f; ctrlz1 = (z1 + centerz) / 2f; ctrlx2 = (x2 + centerx) / 2f; ctrly2 = (y2 + centery) / 2f; ctrlz2 = (z2 + centerz) / 2f; centerx = (ctrlx1 + ctrlx2) / 2f; centery = (ctrly1 + ctrly2) / 2f; centerz = (ctrlz1 + ctrlz2) / 2f; if (left != null) { left[leftoff + 3] = x1; left[leftoff + 4] = y1; left[leftoff + 5] = z1; left[leftoff + 6] = ctrlx1; left[leftoff + 7] = ctrly1; left[leftoff + 8] = ctrlz1; left[leftoff + 9] = centerx; left[leftoff + 10] = centery; left[leftoff + 11] = centerz; } if (right != null) { right[rightoff + 0] = centerx; right[rightoff + 1] = centery; right[rightoff + 2] = centerz; right[rightoff + 3] = ctrlx2; right[rightoff + 4] = ctrly2; right[rightoff + 5] = ctrly2; right[rightoff + 6] = x2; right[rightoff + 7] = y2; right[rightoff + 8] = z2; } } private void searchNext() { int level; if (this.holdIndexProperty.get() >= this.holdEndProperty.get()) { if (!this.pathIterator.hasNext()) { this.doneProperty.set(true); return; } AbstractPathElement3D pathElement = this.pathIterator.next(); this.holdType = pathElement.type; pathElement.toArray(this.hold); this.levelIndexProperty.set(0); this.levels[0] = 0; } switch (this.holdType) { case MOVE_TO: case LINE_TO: this.currentXProperty.set(this.hold[0]); this.currentYProperty.set(this.hold[1]); this.currentZProperty.set(this.hold[2]); if (this.holdType == PathElementType.MOVE_TO) { this.moveXProperty.set(this.currentXProperty.get()); this.moveYProperty.set(this.currentYProperty.get()); this.moveZProperty.set(this.currentZProperty.get()); } this.holdIndexProperty.set(0); this.holdEndProperty.set(0); break; case CLOSE: this.currentXProperty.set(this.moveXProperty.get()); this.currentYProperty.set(this.moveYProperty.get()); this.currentZProperty.set(this.moveZProperty.get()); this.holdIndexProperty.set(0); this.holdEndProperty.set(0); break; case QUAD_TO: if (this.holdIndexProperty.get() >= this.holdEndProperty.get()) { // Move the coordinates to the end of the array. this.holdIndexProperty.set(this.hold.length - 9); this.holdEndProperty.set(this.hold.length - 3); this.hold[this.holdIndexProperty.get() + 0] = this.currentXProperty.get(); this.hold[this.holdIndexProperty.get() + 1] = this.currentYProperty.get(); this.hold[this.holdIndexProperty.get() + 2] = this.currentZProperty.get(); this.hold[this.holdIndexProperty.get() + 3] = this.hold[0]; this.hold[this.holdIndexProperty.get() + 4] = this.hold[1]; this.hold[this.holdIndexProperty.get() + 5] = this.hold[2]; this.hold[this.holdIndexProperty.get() + 6] = this.hold[3]; this.currentXProperty.set(this.hold[3]); this.hold[this.holdIndexProperty.get() + 7] = this.hold[4]; this.currentYProperty.set(this.hold[4]); this.hold[this.holdIndexProperty.get() + 8] = this.hold[5]; this.currentZProperty.set(this.hold[5]); } level = this.levels[this.levelIndexProperty.get()]; while (level < this.limit) { if (getQuadSquaredFlatness(this.hold, this.holdIndexProperty.get()) < this.squaredFlatness) { break; } ensureHoldCapacity(6); subdivideQuad( this.hold, this.holdIndexProperty.get(), this.hold, this.holdIndexProperty.get() - 6, this.hold, this.holdIndexProperty.get()); this.holdIndexProperty.set(this.holdIndexProperty.get()-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.levelIndexProperty.get()] = level; this.levelIndexProperty.set(this.levelIndexProperty.get()+1); this.levels[this.levelIndexProperty.get()] = 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.holdIndexProperty.set(this.holdIndexProperty.get()+6); this.levelIndexProperty.set(this.levelIndexProperty.get()-1); break; case CURVE_TO: if (this.holdIndexProperty.get() >= this.holdEndProperty.get()) { // Move the coordinates to the end of the array. this.holdIndexProperty.set(this.hold.length - 12); this.holdEndProperty.set(this.hold.length - 3); this.hold[this.holdIndexProperty.get() + 0] = this.currentXProperty.get(); this.hold[this.holdIndexProperty.get() + 1] = this.currentYProperty.get(); this.hold[this.holdIndexProperty.get() + 2] = this.currentYProperty.get(); this.hold[this.holdIndexProperty.get() + 3] = this.hold[0]; this.hold[this.holdIndexProperty.get() + 4] = this.hold[1]; this.hold[this.holdIndexProperty.get() + 5] = this.hold[2]; this.hold[this.holdIndexProperty.get() + 6] = this.hold[3]; this.hold[this.holdIndexProperty.get() + 7] = this.hold[4]; this.hold[this.holdIndexProperty.get() + 8] = this.hold[5]; this.hold[this.holdIndexProperty.get() + 9] = this.hold[6]; this.currentXProperty.set(this.hold[6]); this.hold[this.holdIndexProperty.get() + 10] = this.hold[7]; this.currentYProperty.set(this.hold[7]); this.hold[this.holdIndexProperty.get() + 11] = this.hold[8]; this.currentYProperty.set(this.hold[8]); } level = this.levels[this.levelIndexProperty.get()]; while (level < this.limit) { if (getCurveSquaredFlatness(this.hold,this. holdIndexProperty.get()) < this.squaredFlatness) { break; } ensureHoldCapacity(9); subdivideCurve( this.hold, this.holdIndexProperty.get(), this.hold, this.holdIndexProperty.get() - 9, this.hold, this.holdIndexProperty.get()); this.holdIndexProperty.set(this.holdIndexProperty.get()-9); // 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.levelIndexProperty.get()] = level; this.levelIndexProperty.set(this.levelIndexProperty.get()+1); this.levels[this.levelIndexProperty.get()] = 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+9 and holdIndex+10 now // contain the endpoint of the curve which can be the // endpoint of an approximating line segment. this.holdIndexProperty.set(this.holdIndexProperty.get()+9); this.levelIndexProperty.set(this.levelIndexProperty.get()-1); break; default: } } @Pure @Override public boolean hasNext() { return !this.doneProperty.get(); } @Override public AbstractPathElement3D next() { if (this.doneProperty.get()) { throw new NoSuchElementException("flattening iterator out of bounds"); } AbstractPathElement3D element; PathElementType type = this.holdType; if (type!=PathElementType.CLOSE) { double x = this.hold[this.holdIndexProperty.get() + 0]; double y = this.hold[this.holdIndexProperty.get() + 1]; double z = this.hold[this.holdIndexProperty.get() + 2]; if (type == PathElementType.MOVE_TO) { element = new AbstractPathElement3D.MovePathElement3d(x, y, z); } else { element = new AbstractPathElement3D.LinePathElement3d( this.lastNextXProperty.get(), this.lastNextYProperty.get(), this.lastNextZProperty.get(), x, y, z); } this.lastNextXProperty.set(x); this.lastNextYProperty.set(y); this.lastNextZProperty.set(z); } else { element = new AbstractPathElement3D.ClosePathElement3d( this.lastNextXProperty.get(), this.lastNextYProperty.get(), this.lastNextZProperty.get(), this.moveXProperty.get(), this.moveYProperty.get(), this.moveZProperty.get()); this.lastNextXProperty.set(this.moveXProperty.get()); this.lastNextYProperty.set(this.moveYProperty.get()); this.lastNextZProperty.set(this.moveZProperty.get()); } searchNext(); return element; } @Override public void remove() { throw new UnsupportedOperationException(); } @Pure @Override public PathWindingRule getWindingRule() { return this.windingRule; } @Pure @Override public boolean isPolyline() { return false; // Because the iterator flats the path, this is no curve inside. } } // class FlatteningPathIterator3d /** An collection of the points of the path. * * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ @SuppressWarnings("unused") private class PointCollection3d implements Collection<Point3d> { /** */ public PointCollection3d() { // } @Pure @Override public int size() { return Path3d.this.size(); } @Pure @Override public boolean isEmpty() { return Path3d.this.size()<=0; } @Pure @Override public boolean contains(Object o) { if (o instanceof Point3d) { return Path3d.this.containsControlPoint((Point3d)o); } return false; } @Pure @Override public Iterator<Point3d> iterator() { return new PointIterator3d(); } @Pure @Override public Object[] toArray() { return Path3d.this.toPointArray(); } @Pure @SuppressWarnings("unchecked") @Override public <T> T[] toArray(T[] a) { Iterator<Point3d> iterator = new PointIterator3d(); for(int i=0; i<a.length && iterator.hasNext(); ++i) { a[i] = (T)iterator.next(); } return a; } @Override public boolean add(Point3d e) { if (e!=null) { if (Path3d.this.size()==0) { Path3d.this.moveTo(e); } else { Path3d.this.lineTo(e); } return true; } return false; } @Override public boolean remove(Object o) { if (o instanceof Point3d) { Point3d p = (Point3d)o; return Path3d.this.remove(p.getX(), p.getY(), p.getZ()); } return false; } @Pure @Override public boolean containsAll(Collection<?> c) { for(Object obj : c) { if ((!(obj instanceof Point3d)) ||(!Path3d.this.containsControlPoint((Point3d)obj))) { return false; } } return true; } @Override public boolean addAll(Collection<? extends Point3d> c) { boolean changed = false; for(Point3d 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 Point3d) { Point3d pts = (Point3d)obj; if (Path3d.this.remove(pts.getX(), pts.getY(), pts.getZ())) { changed = true; } } } return changed; } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { Path3d.this.clear(); } } // class PointCollection /** Iterator on the points of the path. * * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class PointIterator3d implements Iterator<Point3d> { private int index = 0; private Point3d lastReplied = null; /** */ public PointIterator3d() { // } @Pure @Override public boolean hasNext() { return this.index<Path3d.this.size(); } @Override public Point3d next() { try { this.lastReplied = Path3d.this.getPointAt(this.index++); return this.lastReplied; } catch( Throwable e) { throw new NoSuchElementException(); } } @Override public void remove() { Point3d p = this.lastReplied; this.lastReplied = null; if (p==null) throw new NoSuchElementException(); Path3d.this.remove(p.getX(), p.getY(), p.getZ()); } } }