/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.math.geometry.d2.dfx; import java.util.ArrayList; import java.util.Iterator; import java.util.Objects; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyListProperty; import javafx.beans.property.ReadOnlyListWrapper; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.math.MathConstants; import org.arakhne.afc.math.geometry.MathFXAttributeNames; import org.arakhne.afc.math.geometry.PathElementType; import org.arakhne.afc.math.geometry.PathWindingRule; import org.arakhne.afc.math.geometry.d2.Point2D; import org.arakhne.afc.math.geometry.d2.Transform2D; import org.arakhne.afc.math.geometry.d2.afp.Path2afp; import org.arakhne.afc.math.geometry.d2.afp.PathIterator2afp; import org.arakhne.afc.vmutil.asserts.AssertMessages; import org.arakhne.afc.vmutil.locale.Locale; /** Path with 2 double precision floating-point FX properties. * * @author $Author: sgalland$ * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ @SuppressWarnings("checkstyle:magicnumber") public class Path2dfx extends AbstractShape2dfx<Path2dfx> implements Path2afp<Shape2dfx<?>, Path2dfx, PathElement2dfx, Point2dfx, Vector2dfx, Rectangle2dfx> { private static final long serialVersionUID = 6051061640155091109L; /** Array of types. */ private ReadOnlyListWrapper<PathElementType> types; /** Array of coords. */ private ReadOnlyListWrapper<Point2dfx> coords; /** Winding rule for the path. */ private ObjectProperty<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 isEmpty; /** Indicates if the path is a polyline. */ private BooleanProperty isPolyline; /** Indicates if the path is curved. */ private BooleanProperty isCurved; /** Indicates if the path is a polygon. */ private BooleanProperty isPolygon; /** Indicates if the path is multipart. */ private BooleanProperty isMultiparts; /** Buffer for the bounds of the path that corresponds * to all the points added in the path. */ private ObjectProperty<Rectangle2dfx> logicalBounds; /** Buffer for the squared length of the path. */ private DoubleProperty length; /** Construct an empty path. */ public Path2dfx() { this(DEFAULT_WINDING_RULE); } /** Construct a path by copying the given elements. * @param iterator the iterator that provides the elements to copy. */ public Path2dfx(Iterator<PathElement2dfx> iterator) { this(DEFAULT_WINDING_RULE, iterator); } /** Construct a path with the given path winding rule. * @param windingRule the path winding rule. */ public Path2dfx(PathWindingRule windingRule) { assert windingRule != null : AssertMessages.notNullParameter(); if (windingRule != DEFAULT_WINDING_RULE) { windingRuleProperty().set(windingRule); } } /** Construct a path by copying the given elements, and the given path winding rule. * @param windingRule the path winding rule. * @param iterator the iterator that provides the elements to copy. */ public Path2dfx(PathWindingRule windingRule, Iterator<PathElement2dfx> iterator) { assert windingRule != null : AssertMessages.notNullParameter(0); assert iterator != null : AssertMessages.notNullParameter(1); if (windingRule != DEFAULT_WINDING_RULE) { windingRuleProperty().set(windingRule); } add(iterator); } /** Constructor by copy. * @param path the path to copy. */ public Path2dfx(Path2afp<?, ?, ?, ?, ?, ?> path) { set(path); } @Pure @Override public boolean containsControlPoint(Point2D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); if (this.coords != null && !this.coords.isEmpty()) { for (int i = 0; i < this.coords.size(); i++) { final Point2dfx point = this.coords.get(i); if (point.getX() == pt.getX() && point.getY() == pt.getY()) { return true; } } } return false; } @Override public void clear() { if (this.coords != null) { this.coords.clear(); } if (this.types != null) { this.types.clear(); } } @Pure @Override public Path2dfx clone() { final Path2dfx clone = super.clone(); clone.coords = null; if (this.coords != null && !this.coords.isEmpty()) { clone.innerCoordinatesProperty().addAll(this.coords); } clone.types = null; if (this.types != null && !this.types.isEmpty()) { clone.innerTypesProperty().addAll(this.types); } clone.windingRule = null; if (this.windingRule != null) { clone.windingRuleProperty().set(this.windingRule.get()); } clone.boundingBox = null; clone.logicalBounds = null; clone.isCurved = null; clone.isMultiparts = null; clone.isPolyline = null; clone.isPolygon = null; clone.isEmpty = null; clone.length = null; return clone; } @Pure @Override public int hashCode() { int bits = 1; bits = 31 * bits + Objects.hashCode(this.coords); bits = 31 * bits + Objects.hashCode(this.types); bits = 31 * bits + Objects.hashCode(this.windingRule); return bits ^ (bits >> 31); } @Override public void translate(double dx, double dy) { for (final Point2dfx pt : this.coords) { pt.add(dx, dy); } } @Override public void transform(Transform2D transform) { assert transform != null : AssertMessages.notNullParameter(); for (final Point2dfx pt : this.coords) { transform.transform(pt); } } /** Replies the isEmpty property. * * @return the isEmpty property. */ public BooleanProperty isEmptyProperty() { if (this.isEmpty == null) { this.isEmpty = new SimpleBooleanProperty(this, MathFXAttributeNames.IS_EMPTY); this.isEmpty.bind(Bindings.createBooleanBinding(() -> { final PathIterator2afp<PathElement2dfx> pi = getPathIterator(); while (pi.hasNext()) { final PathElement2dfx pe = pi.next(); if (pe.isDrawable()) { return false; } } return true; }, innerTypesProperty(), innerCoordinatesProperty())); } return this.isEmpty; } @Override public boolean isEmpty() { return isEmptyProperty().get(); } @Override public Rectangle2dfx toBoundingBox() { return boundingBoxProperty().get().clone(); } @Override public void toBoundingBox(Rectangle2dfx box) { assert box != null : AssertMessages.notNullParameter(); box.set(boundingBoxProperty().get()); } /** Replies the windingRule property. * * @return the windingRule property. */ public ObjectProperty<PathWindingRule> windingRuleProperty() { if (this.windingRule == null) { this.windingRule = new SimpleObjectProperty<>(this, MathFXAttributeNames.WINDING_RULE, DEFAULT_WINDING_RULE); } return this.windingRule; } @Override public PathWindingRule getWindingRule() { return (this.windingRule == null) ? DEFAULT_WINDING_RULE : windingRuleProperty().get(); } @Override public void setWindingRule(PathWindingRule rule) { assert rule != null : AssertMessages.notNullParameter(); if (this.windingRule != null || rule != DEFAULT_WINDING_RULE) { windingRuleProperty().set(rule); } } /** Replies the isPolyline property. * * @return the isPolyline property. */ public BooleanProperty isPolylineProperty() { if (this.isPolyline == null) { this.isPolyline = new ReadOnlyBooleanWrapper(this, MathFXAttributeNames.IS_POLYLINE, false); this.isPolyline.bind(Bindings.createBooleanBinding(() -> { boolean first = true; boolean hasOneLine = false; for (final PathElementType type : innerTypesProperty()) { if (first) { if (type != PathElementType.MOVE_TO) { return false; } first = false; } else if (type != PathElementType.LINE_TO) { return false; } else { hasOneLine = true; } } return hasOneLine; }, innerTypesProperty())); } return this.isPolyline; } @Override public boolean isPolyline() { return isPolylineProperty().get(); } /** Replies the isCurved property. * * @return the isCurved property. */ public BooleanProperty isCurvedProperty() { if (this.isCurved == null) { this.isCurved = new ReadOnlyBooleanWrapper(this, MathFXAttributeNames.IS_CURVED, false); this.isCurved.bind(Bindings.createBooleanBinding(() -> { for (final PathElementType type : innerTypesProperty()) { if (type == PathElementType.CURVE_TO || type == PathElementType.QUAD_TO) { return true; } } return false; }, innerTypesProperty())); } return this.isCurved; } @Override public boolean isCurved() { return isCurvedProperty().get(); } /** Replies the isMultiParts property. * * @return the isMultiParts property. */ public BooleanProperty isMultiPartsProperty() { if (this.isMultiparts == null) { this.isMultiparts = new ReadOnlyBooleanWrapper(this, MathFXAttributeNames.IS_MULTIPARTS, false); this.isMultiparts.bind(Bindings.createBooleanBinding(() -> { boolean foundOne = false; for (final PathElementType type : innerTypesProperty()) { if (type == PathElementType.MOVE_TO) { if (foundOne) { return true; } foundOne = true; } } return false; }, innerTypesProperty())); } return this.isMultiparts; } @Override public boolean isMultiParts() { return isMultiPartsProperty().get(); } /** Replies the isPolygon property. * * @return the isPolygon property. */ public BooleanProperty isPolygonProperty() { if (this.isPolygon == null) { this.isPolygon = new ReadOnlyBooleanWrapper(this, MathFXAttributeNames.IS_POLYGON, false); this.isPolygon.bind(Bindings.createBooleanBinding(() -> { boolean first = true; boolean lastIsClose = false; for (final PathElementType type : innerTypesProperty()) { lastIsClose = false; if (first) { if (type != PathElementType.MOVE_TO) { return false; } first = false; } else if (type == PathElementType.MOVE_TO) { return false; } else if (type == PathElementType.CLOSE) { lastIsClose = true; } } return lastIsClose; }, innerTypesProperty())); } return this.isPolygon; } @Override public boolean isPolygon() { return isPolygonProperty().get(); } @Override public void closePath() { if (this.types == null || this.types.isEmpty() || (this.types.get(this.types.size() - 1) != PathElementType.CLOSE && this.types.get(this.types.size() - 1) != PathElementType.MOVE_TO)) { this.types.add(PathElementType.CLOSE); } } @Override public Rectangle2dfx toBoundingBoxWithCtrlPoints() { Rectangle2dfx bb = null; if (this.logicalBounds != null) { bb = controlPointBoundingBoxProperty().get(); } if (bb == null) { return getGeomFactory().newBox(); } return bb; } @Override public void toBoundingBoxWithCtrlPoints(Rectangle2dfx box) { assert box != null : AssertMessages.notNullParameter(); if (this.logicalBounds != null) { box.set(controlPointBoundingBoxProperty().get()); } else { box.clear(); } } @Override public int[] toIntArray(Transform2D transform) { final int n = (this.coords != null) ? this.coords.size() * 2 : 0; final int[] clone = new int[n]; if (n > 0) { final Iterator<Point2dfx> iterator = this.coords.iterator(); Point2dfx point = iterator.next(); for (int i = 0; i < n; i += 2) { if (!(transform == null || transform.isIdentity())) { transform.transform(point); } clone[i] = point.ix(); clone[i + 1] = point.iy(); point = iterator.next(); } } return clone; } @Override public float[] toFloatArray(Transform2D transform) { final int n = (this.coords != null) ? this.coords.size() * 2 : 0; final float[] clone = new float[n]; if (n > 0) { final Iterator<Point2dfx> iterator = this.coords.iterator(); Point2dfx point = iterator.next(); for (int i = 0; i < n; i += 2) { if (!(transform == null || transform.isIdentity())) { transform.transform(point); } clone[i] = (float) point.getX(); clone[i + 1] = (float) point.getY(); point = iterator.next(); } } return clone; } @Override public double[] toDoubleArray(Transform2D transform) { final int n = (this.coords != null) ? this.coords.size() * 2 : 0; final double[] clone = new double[n]; if (n > 0) { final Iterator<Point2dfx> iterator = this.coords.iterator(); Point2dfx point = iterator.next(); for (int i = 0; i < n; i += 2) { if (!(transform == null || transform.isIdentity())) { transform.transform(point); } clone[i] = (float) point.getX(); clone[i + 1] = (float) point.getY(); point = iterator.next(); } } return clone; } @Override public Point2dfx[] toPointArray(Transform2D transform) { final int n = (this.coords != null) ? this.coords.size() : 0; final Point2dfx[] clone = new Point2dfx[n]; if (n > 0) { final Iterator<Point2dfx> iterator = this.coords.iterator(); int i = 0; while (iterator.hasNext()) { final Point2dfx point = iterator.next(); if (!(transform == null || transform.isIdentity())) { transform.transform(point); } clone[i++] = point; } } return clone; } @Override public Point2dfx getPointAt(int index) { if (this.coords == null) { throw new IndexOutOfBoundsException(); } return this.coords.get(index); } @Pure @Override public double getCurrentX() { if (this.coords == null) { throw new IndexOutOfBoundsException(); } final int baseIdx = this.coords.size() - 1; return this.coords.get(baseIdx).getX(); } @Pure @Override public double getCurrentY() { if (this.coords == null) { throw new IndexOutOfBoundsException(); } final int baseIdx = this.coords.size() - 1; return this.coords.get(baseIdx).getY(); } @Pure @Override public Point2dfx getCurrentPoint() { if (this.coords == null) { throw new IndexOutOfBoundsException(); } final int baseIdx = this.coords.size() - 1; return this.coords.get(baseIdx); } @Override public int size() { return (this.coords == null) ? 0 : this.coords.size(); } @Override public void removeLast() { if (this.types != null && !this.types.isEmpty() && this.coords != null && !this.coords.isEmpty()) { final int lastIndex = this.types.size() - 1; final int coordSize = this.coords.size(); final int coordIndex; switch (this.types.get(lastIndex)) { case CLOSE: // no coord to remove coordIndex = coordSize; break; case MOVE_TO: coordIndex = coordSize - 1; break; case LINE_TO: coordIndex = coordSize - 1; break; case CURVE_TO: coordIndex = coordSize - 3; break; case QUAD_TO: coordIndex = coordSize - 2; break; case ARC_TO: default: throw new IllegalStateException(); } this.coords.remove(coordIndex, coordSize); this.types.remove(lastIndex); } else { throw new IllegalStateException(); } } @Override public void moveTo(double x, double y) { if (this.types != null && !this.types.isEmpty() && this.types.get(this.types.size() - 1) == PathElementType.MOVE_TO) { assert this.coords != null && !this.coords.isEmpty(); final int idx = this.coords.size() - 1; this.coords.set(idx, getGeomFactory().newPoint(x, y)); } else { innerTypesProperty().add(PathElementType.MOVE_TO); final ReadOnlyListWrapper<Point2dfx> coords = innerCoordinatesProperty(); coords.add(getGeomFactory().newPoint(x, y)); } } @Override public void moveTo(Point2D<?, ?> position) { assert position != null : AssertMessages.notNullParameter(); if (this.types != null && !this.types.isEmpty() && this.types.get(this.types.size() - 1) == PathElementType.MOVE_TO) { assert this.coords != null && !this.coords.isEmpty(); final int idx = this.coords.size() - 1; this.coords.set(idx, getGeomFactory().convertToPoint(position)); } else { innerTypesProperty().add(PathElementType.MOVE_TO); final ReadOnlyListWrapper<Point2dfx> coords = innerCoordinatesProperty(); coords.add(getGeomFactory().convertToPoint(position)); } } private void ensureMoveTo() { if (this.types == null || this.types.isEmpty()) { throw new IllegalStateException(Locale.getString("E1")); //$NON-NLS-1$ } } @Override public void lineTo(double x, double y) { ensureMoveTo(); innerTypesProperty().add(PathElementType.LINE_TO); final ReadOnlyListWrapper<Point2dfx> coords = innerCoordinatesProperty(); coords.add(getGeomFactory().newPoint(x, y)); } @Override public void lineTo(Point2D<?, ?> to) { assert to != null : AssertMessages.notNullParameter(); ensureMoveTo(); innerTypesProperty().add(PathElementType.LINE_TO); final ReadOnlyListWrapper<Point2dfx> coords = innerCoordinatesProperty(); coords.add(getGeomFactory().convertToPoint(to)); } @Override public void quadTo(double x1, double y1, double x2, double y2) { ensureMoveTo(); innerTypesProperty().add(PathElementType.QUAD_TO); final ReadOnlyListWrapper<Point2dfx> coords = innerCoordinatesProperty(); coords.add(getGeomFactory().newPoint(x1, y1)); coords.add(getGeomFactory().newPoint(x2, y2)); } @Override public void quadTo(Point2D<?, ?> ctrl, Point2D<?, ?> to) { assert ctrl != null : AssertMessages.notNullParameter(0); assert to != null : AssertMessages.notNullParameter(1); ensureMoveTo(); innerTypesProperty().add(PathElementType.QUAD_TO); final ReadOnlyListWrapper<Point2dfx> coords = innerCoordinatesProperty(); coords.add(getGeomFactory().convertToPoint(ctrl)); coords.add(getGeomFactory().convertToPoint(to)); } @Override public void curveTo(double x1, double y1, double x2, double y2, double x3, double y3) { ensureMoveTo(); innerTypesProperty().add(PathElementType.CURVE_TO); final ReadOnlyListWrapper<Point2dfx> coords = innerCoordinatesProperty(); coords.add(getGeomFactory().newPoint(x1, y1)); coords.add(getGeomFactory().newPoint(x2, y2)); coords.add(getGeomFactory().newPoint(x3, y3)); } @Override public void curveTo(Point2D<?, ?> ctrl1, Point2D<?, ?> ctrl2, Point2D<?, ?> to) { assert ctrl1 != null : AssertMessages.notNullParameter(0); assert ctrl2 != null : AssertMessages.notNullParameter(1); assert to != null : AssertMessages.notNullParameter(2); ensureMoveTo(); innerTypesProperty().add(PathElementType.CURVE_TO); final ReadOnlyListWrapper<Point2dfx> coords = innerCoordinatesProperty(); coords.add(getGeomFactory().convertToPoint(ctrl1)); coords.add(getGeomFactory().convertToPoint(ctrl2)); coords.add(getGeomFactory().convertToPoint(to)); } /** Replies the private coordinates property. * * @return the private coordinates property. */ protected ReadOnlyListWrapper<Point2dfx> innerCoordinatesProperty() { if (this.coords == null) { this.coords = new ReadOnlyListWrapper<>(this, MathFXAttributeNames.COORDINATES, FXCollections.observableList(new ArrayList<>())); } return this.coords; } /** Replies the coordinates property. * * @return the coordinates property. */ public ReadOnlyListProperty<Point2dfx> coordinatesProperty() { return innerCoordinatesProperty().getReadOnlyProperty(); } @Override public double getCoordAt(int index) { if (this.coords == null) { throw new IndexOutOfBoundsException(); } final Point2dfx point = this.coords.get(index / 2); return index % 2 == 0 ? point.getX() : point.getY(); } @Override public void setLastPoint(double x, double y) { if (this.coords != null && this.coords.size() >= 2) { final int idx = this.coords.size() - 1; final Point2dfx point = this.coords.get(idx); point.setX(x); point.setY(y); } else { throw new IllegalStateException(); } } @Override public void setLastPoint(Point2D<?, ?> point) { if (this.coords != null && !this.coords.isEmpty()) { final int idx = this.coords.size() - 1; this.coords.get(idx).set(point.getX(), point.getY()); } else { throw new IllegalStateException(); } } @Override @SuppressWarnings({"checkstyle:magicnumber", "checkstyle:cyclomaticcomplexity"}) public boolean remove(double x, double y) { if (this.types != null && !this.types.isEmpty() && this.coords != null && !this.coords.isEmpty()) { for (int i = 0, j = 0; i < this.coords.size() && j < this.types.size(); j++) { final Point2dfx point = this.coords.get(i); switch (this.types.get(j)) { case MOVE_TO: //$FALL-THROUGH$ case LINE_TO: if (x == point.ix() && y == point.iy()) { this.coords.remove(i); this.types.remove(j); return true; } i++; break; case CURVE_TO: final Point2dfx p2 = this.coords.get(i + 1); final Point2dfx p3 = this.coords.get(i + 2); if ((x == point.ix() && y == point.iy()) || (x == p2.ix() && y == p2.iy()) || (x == p3.ix() && y == p3.iy())) { this.coords.remove(i, i + 3); this.types.remove(j); return true; } i = i + 3; break; case QUAD_TO: final Point2dfx pt = this.coords.get(i + 1); if ((x == point.ix() && y == point.iy()) || (x == pt.ix() && y == pt.iy())) { this.coords.remove(i, i + 2); this.types.remove(j); return true; } i = i + 2; break; case CLOSE: break; case ARC_TO: throw new IllegalStateException(); default: break; } } } return false; } /** Remove the point from this path. * * <p>If the given point do not match exactly a point in the path, nothing is removed. * * @param point the point to remove. * @return <code>true</code> if the point was removed; <code>false</code> otherwise. */ @SuppressWarnings({"checkstyle:magicnumber", "checkstyle:cyclomaticcomplexity"}) public boolean remove(Point2D<?, ?> point) { if (this.types != null && !this.types.isEmpty() && this.coords != null && !this.coords.isEmpty()) { for (int i = 0, j = 0; i < this.coords.size() && j < this.types.size(); j++) { final Point2dfx currentPoint = this.coords.get(i); switch (this.types.get(j)) { case MOVE_TO: //$FALL-THROUGH$ case LINE_TO: if (point.equals(currentPoint)) { this.coords.remove(i); this.types.remove(j); return true; } i++; break; case CURVE_TO: final Point2dfx p2 = this.coords.get(i + 1); final Point2dfx p3 = this.coords.get(i + 2); if ((point.equals(currentPoint)) || (point.equals(p2)) || (point.equals(p3))) { this.coords.remove(i, i + 3); this.types.remove(j); return true; } i = i + 3; break; case QUAD_TO: final Point2dfx pt = this.coords.get(i + 1); if ((point.equals(currentPoint)) || (point.equals(pt))) { this.coords.remove(i, i + 2); this.types.remove(j); return true; } i = i + 2; break; case CLOSE: break; case ARC_TO: throw new IllegalStateException(); default: break; } } } return false; } @Override public void set(Path2dfx path) { assert path != null : AssertMessages.notNullParameter(); clear(); add(path.getPathIterator()); } /** Replies the private types property. * * @return the private types property. */ protected ReadOnlyListWrapper<PathElementType> innerTypesProperty() { if (this.types == null) { this.types = new ReadOnlyListWrapper<>(this, MathFXAttributeNames.TYPES, FXCollections.observableList(new ArrayList<>())); } return this.types; } /** Replies the types property. * * @return the types property. */ public ReadOnlyListProperty<PathElementType> typesProperty() { return innerTypesProperty().getReadOnlyProperty(); } @Override public int getPathElementCount() { return this.types == null ? 0 : innerTypesProperty().size(); } @Override public PathElementType getPathElementTypeAt(int index) { if (this.types == null) { throw new IndexOutOfBoundsException(); } return this.types.get(index); } @Override public double getLength() { return lengthProperty().get(); } /** Replies the path length property. * * @return the length property. */ public DoubleProperty lengthProperty() { if (this.length == null) { this.length = new ReadOnlyDoubleWrapper(); this.length.bind(Bindings.createDoubleBinding(() -> Path2afp.calculatesPathLength(getPathIterator()), innerTypesProperty(), innerCoordinatesProperty())); } return this.length; } @Override public ObjectProperty<Rectangle2dfx> boundingBoxProperty() { if (this.boundingBox == null) { this.boundingBox = new ReadOnlyObjectWrapper<>(this, MathFXAttributeNames.BOUNDING_BOX); this.boundingBox.bind(Bindings.createObjectBinding(() -> { final Rectangle2dfx bb = getGeomFactory().newBox(); Path2afp.calculatesDrawableElementBoundingBox( getPathIterator(MathConstants.SPLINE_APPROXIMATION_RATIO), bb); return bb; }, innerCoordinatesProperty())); } return this.boundingBox; } /** Replies the property that corresponds to the bounding box of the control points. * * <p>The replied box is not the one corresponding to the drawable elements, as replied * by {@link #boundingBoxProperty()}. * * @return the bounding box of the control points. */ public ObjectProperty<Rectangle2dfx> controlPointBoundingBoxProperty() { if (this.logicalBounds == null) { this.logicalBounds = new ReadOnlyObjectWrapper<>(this, MathFXAttributeNames.CONTROL_POINT_BOUNDING_BOX); this.logicalBounds.bind(Bindings.createObjectBinding(() -> { final Rectangle2dfx bb = getGeomFactory().newBox(); Path2afp.calculatesControlPointBoundingBox( getPathIterator(), bb); return bb; }, innerCoordinatesProperty())); } return this.logicalBounds; } }