1 /*
   2  * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.transform;
  27 
  28 import java.util.Iterator;
  29 
  30 import com.sun.javafx.geometry.BoundsUtils;
  31 import javafx.event.EventDispatchChain;
  32 
  33 import javafx.scene.Node;
  34 
  35 import com.sun.javafx.util.WeakReferenceQueue;
  36 import com.sun.javafx.binding.ExpressionHelper;
  37 import com.sun.javafx.event.EventHandlerManager;
  38 import com.sun.javafx.geom.transform.Affine2D;
  39 import com.sun.javafx.geom.transform.Affine3D;
  40 import com.sun.javafx.geom.transform.AffineBase;
  41 import com.sun.javafx.geom.transform.BaseTransform;
  42 import com.sun.javafx.scene.transform.TransformUtils;
  43 import java.lang.ref.SoftReference;
  44 import javafx.beans.InvalidationListener;
  45 import javafx.beans.property.ObjectProperty;
  46 import javafx.beans.property.ReadOnlyBooleanProperty;
  47 import javafx.beans.property.SimpleObjectProperty;
  48 import javafx.beans.value.ChangeListener;
  49 import javafx.event.Event;
  50 import javafx.event.EventHandler;
  51 import javafx.event.EventTarget;
  52 import javafx.event.EventType;
  53 import javafx.geometry.BoundingBox;
  54 import javafx.geometry.Bounds;
  55 import javafx.geometry.Point2D;
  56 import javafx.geometry.Point3D;
  57 
  58 // PENDING_DOC_REVIEW of this whole class
  59 /**
  60  * This class is a base class for different affine transformations.
  61  * It provides factory methods for the simple transformations - rotating,
  62  * scaling, shearing, and translation. It allows to get the transformation
  63  * matrix elements for any transform.
  64  *
  65  * <p>Example:</p>
  66  *
  67  * <pre><code>
  68  *  Rectangle rect = new Rectangle(50,50, Color.RED);
  69  *  rect.getTransforms().add(new Rotate(45,0,0)); //rotate by 45 degrees
  70  * </code></pre>
  71  * @since JavaFX 2.0
  72  */
  73 public abstract class Transform implements Cloneable, EventTarget {
  74 
  75     /* *************************************************************************
  76      *                                                                         *
  77      *                            Factories                                    *
  78      *                                                                         *
  79      **************************************************************************/
  80 
  81     /**
  82      * Returns a new {@code Affine} object from 12 number
  83      * values representing the 6 specifiable entries of the 3x4
  84      * Affine transformation matrix.
  85      *
  86      * @param mxx the X coordinate scaling element of the 3x4 matrix
  87      * @param myx the Y coordinate shearing element of the 3x4 matrix
  88      * @param mxy the X coordinate shearing element of the 3x4 matrix
  89      * @param myy the Y coordinate scaling element of the 3x4 matrix
  90      * @param tx the X coordinate translation element of the 3x4 matrix
  91      * @param ty the Y coordinate translation element of the 3x4 matrix
  92      * @return a new {@code Affine} object derived from specified parameters
  93      */
  94     public static Affine affine(
  95         double mxx, double myx, double mxy, double myy, double tx, double ty) {
  96         final Affine affine = new Affine();
  97         affine.setMxx(mxx);
  98         affine.setMxy(mxy);
  99         affine.setTx(tx);
 100         affine.setMyx(myx);
 101         affine.setMyy(myy);
 102         affine.setTy(ty);
 103         return affine;
 104     }
 105 
 106 
 107     /**
 108      * Returns a new {@code Affine} object from 12 number
 109      * values representing the 12 specifiable entries of the 3x4
 110      * Affine transformation matrix.
 111      *
 112      * @param mxx the X coordinate scaling element of the 3x4 matrix
 113      * @param mxy the XY element of the 3x4 matrix
 114      * @param mxz the XZ element of the 3x4 matrix
 115      * @param tx the X coordinate translation element of the 3x4 matrix
 116      * @param myx the YX element of the 3x4 matrix
 117      * @param myy the Y coordinate scaling element of the 3x4 matrix
 118      * @param myz the YZ element of the 3x4 matrix
 119      * @param ty the Y coordinate translation element of the 3x4 matrix
 120      * @param mzx the ZX element of the 3x4 matrix
 121      * @param mzy the ZY element of the 3x4 matrix
 122      * @param mzz the Z coordinate scaling element of the 3x4 matrix
 123      * @param tz the Z coordinate translation element of the 3x4 matrix
 124      * @return a new {@code Affine} object derived from specified parameters
 125      */
 126     public static Affine affine(
 127         double mxx, double mxy, double mxz, double tx,
 128         double myx, double myy, double myz, double ty,
 129         double mzx, double mzy, double mzz, double tz) {
 130         final Affine affine = new Affine();
 131         affine.setMxx(mxx);
 132         affine.setMxy(mxy);
 133         affine.setMxz(mxz);
 134         affine.setTx(tx);
 135         affine.setMyx(myx);
 136         affine.setMyy(myy);
 137         affine.setMyz(myz);
 138         affine.setTy(ty);
 139         affine.setMzx(mzx);
 140         affine.setMzy(mzy);
 141         affine.setMzz(mzz);
 142         affine.setTz(tz);
 143         return affine;
 144     }
 145 
 146 
 147     /**
 148      * Returns a {@code Translate} object representing a translation transformation.
 149      * <p>
 150      * This is equivalent to:
 151      * <pre>
 152      *    new Translate(x, y);
 153      * </pre>
 154      */
 155     public static Translate translate(double x, double y) {
 156         final Translate translate = new Translate();
 157         translate.setX(x);
 158         translate.setY(y);
 159         return translate;
 160     }
 161 
 162 
 163     /**
 164      * Returns a {@code Rotate} object that rotates coordinates around a pivot
 165      * point.
 166      * <p>
 167      * This is equivalent to:
 168      * <pre>
 169      *    new Rotate(angle, pivotX, pivotY);
 170      * </pre>
 171      */
 172     public static Rotate rotate(double angle, double pivotX, double pivotY) {
 173         final Rotate rotate = new Rotate();
 174         rotate.setAngle(angle);
 175         rotate.setPivotX(pivotX);
 176         rotate.setPivotY(pivotY);
 177         return rotate;
 178     }
 179 
 180 
 181     /**
 182      * Returns a {@code Scale} object representing a scaling transformation.
 183      * <p>
 184      * This is equivalent to:
 185      * <pre>
 186      *    new Scale(x, y);
 187      * </pre>
 188      */
 189     public static Scale scale(double x, double y) {
 190         final Scale scale = new Scale();
 191         scale.setX(x);
 192         scale.setY(y);
 193         return scale;
 194     }
 195 
 196 
 197     /**
 198      * Returns a {@code Scale} object representing a scaling transformation.
 199      * The returned scale operation will be about the given pivot point.
 200      * <p>
 201      * This is equivalent to:
 202      * <pre>
 203      *    new Scale(x, y, pivotX, pivotY);
 204      * </pre>
 205      */
 206     public static Scale scale(double x, double y, double pivotX, double pivotY) {
 207         final Scale scale = new Scale();
 208         scale.setX(x);
 209         scale.setY(y);
 210         scale.setPivotX(pivotX);
 211         scale.setPivotY(pivotY);
 212         return scale;
 213     }
 214 
 215 
 216     /**
 217      * Returns a {@code Shear} object representing a shearing transformation.
 218      * <p>
 219      * This is equivalent to:
 220      * <pre>
 221      *    new Shear(x, y);
 222      * </pre>
 223      */
 224     public static Shear shear(double x, double y) {
 225         final Shear shear = new Shear();
 226         shear.setX(x);
 227         shear.setY(y);
 228         return shear;
 229     }
 230 
 231     /**
 232      * Returns a {@code Shear} object representing a shearing transformation.
 233      * <p>
 234      * This is equivalent to:
 235      * <pre>
 236      *    new Shear(x, y, pivotX, pivotY);
 237      * </pre>
 238      */
 239     public static Shear shear(double x, double y, double pivotX, double pivotY) {
 240         final Shear shear = new Shear();
 241         shear.setX(x);
 242         shear.setY(y);
 243         shear.setPivotX(pivotX);
 244         shear.setPivotY(pivotY);
 245         return shear;
 246     }
 247 
 248     /**
 249      * For transforms with expensive inversion we cache the inverted matrix
 250      * once it is needed and computed for some operation.
 251      */
 252     private SoftReference<Transform> inverseCache = null;
 253     
 254     private WeakReferenceQueue impl_nodes = new WeakReferenceQueue();
 255 
 256     /* *************************************************************************
 257      *                                                                         *
 258      *                         Element getters                                 *
 259      *                                                                         *
 260      **************************************************************************/
 261 
 262     /**
 263      * Gets the X coordinate scaling element of the 3x4 matrix.
 264      *
 265      * @since JavaFX 2.2
 266      */
 267     public  double getMxx() {
 268         return 1.0;
 269     }
 270 
 271     /**
 272      * Gets the XY coordinate element of the 3x4 matrix.
 273      *
 274      * @since JavaFX 2.2
 275      */
 276     public  double getMxy() {
 277         return 0.0;
 278     }
 279 
 280     /**
 281      * Gets the XZ coordinate element of the 3x4 matrix.
 282      *
 283      * @since JavaFX 2.2
 284      */
 285     public  double getMxz() {
 286         return 0.0;
 287     }
 288 
 289     /**
 290      * Gets the X coordinate translation element of the 3x4 matrix.
 291      *
 292      * @since JavaFX 2.2
 293      */
 294     public  double getTx() {
 295         return 0.0;
 296     }
 297 
 298     /**
 299      * Gets the YX coordinate element of the 3x4 matrix.
 300      *
 301      * @since JavaFX 2.2
 302      */
 303     public  double getMyx() {
 304         return 0.0;
 305     }
 306 
 307     /**
 308      * Gets the Y coordinate scaling element of the 3x4 matrix.
 309      *
 310      * @since JavaFX 2.2
 311      */
 312     public  double getMyy() {
 313         return 1.0;
 314     }
 315 
 316     /**
 317      * Gets the YZ coordinate element of the 3x4 matrix.
 318      *
 319      * @since JavaFX 2.2
 320      */
 321     public  double getMyz() {
 322         return 0.0;
 323     }
 324 
 325     /**
 326      * Gets the Y coordinate translation element of the 3x4 matrix.
 327      *
 328      * @since JavaFX 2.2
 329      */
 330     public  double getTy() {
 331         return 0.0;
 332     }
 333 
 334     /**
 335      * Gets the ZX coordinate element of the 3x4 matrix.
 336      *
 337      * @since JavaFX 2.2
 338      */
 339     public  double getMzx() {
 340         return 0.0;
 341     }
 342 
 343     /**
 344      * Gets the ZY coordinate element of the 3x4 matrix.
 345      *
 346      * @since JavaFX 2.2
 347      */
 348     public  double getMzy() {
 349         return 0.0;
 350     }
 351 
 352     /**
 353      * Gets the Z coordinate scaling element of the 3x4 matrix.
 354      *
 355      * @since JavaFX 2.2
 356      */
 357     public  double getMzz() {
 358         return 1.0;
 359     }
 360 
 361     /**
 362      * Gets the Z coordinate translation element of the 3x4 matrix.
 363      *
 364      * @since JavaFX 2.2
 365      */
 366     public  double getTz() {
 367         return 0.0;
 368     }
 369 
 370     /**
 371      * Gets the specified element of the transformation matrix.
 372      * @param type type of matrix to get the value from
 373      * @param row zero-based row number
 374      * @param column zero-based column number
 375      * @return value of the specified transformation matrix element
 376      * @throws IllegalArgumentException if a 2D matrix type is requested for
 377      *         a 3D transform
 378      * @throws IndexOutOfBoundsException if the indices are not within
 379      *         the specified matrix type
 380      * @throws NullPointerException if the specified {@code type} is null
 381      * @since JavaFX 8.0
 382      */
 383     public double getElement(MatrixType type, int row, int column) {
 384         if (row < 0 || row >= type.rows() || column < 0 || column >= type.columns()) {
 385             throw new IndexOutOfBoundsException("Index outside of affine "
 386                     + "matrix " + type + ": [" + row + ", " + column + "]");
 387         }
 388         switch(type) {
 389             case MT_2D_2x3:
 390                 // fall-through
 391             case MT_2D_3x3:
 392                 if (!isType2D()) {
 393                     throw new IllegalArgumentException("Cannot access 2D matrix "
 394                             + "of a 3D transform");
 395                 }
 396                 switch(row) {
 397                     case 0:
 398                         switch(column) {
 399                             case 0: return getMxx();
 400                             case 1: return getMxy();
 401                             case 2: return getTx();
 402                         }
 403                     case 1:
 404                         switch(column) {
 405                             case 0: return getMyx();
 406                             case 1: return getMyy();
 407                             case 2: return getTy();
 408                         }
 409                     case 2:
 410                         switch(column) {
 411                             case 0: return 0.0;
 412                             case 1: return 0.0;
 413                             case 2: return 1.0;
 414                         }
 415                 }
 416                 break;
 417             case MT_3D_3x4:
 418                 // fall-through
 419             case MT_3D_4x4:
 420                 switch(row) {
 421                     case 0:
 422                         switch(column) {
 423                             case 0: return getMxx();
 424                             case 1: return getMxy();
 425                             case 2: return getMxz();
 426                             case 3: return getTx();
 427                         }
 428                     case 1:
 429                         switch(column) {
 430                             case 0: return getMyx();
 431                             case 1: return getMyy();
 432                             case 2: return getMyz();
 433                             case 3: return getTy();
 434                         }
 435                     case 2:
 436                         switch(column) {
 437                             case 0: return getMzx();
 438                             case 1: return getMzy();
 439                             case 2: return getMzz();
 440                             case 3: return getTz();
 441                         }
 442                     case 3:
 443                         switch(column) {
 444                             case 0: return 0.0;
 445                             case 1: return 0.0;
 446                             case 2: return 0.0;
 447                             case 3: return 1.0;
 448                         }
 449                 }
 450                 break;
 451         }
 452         // cannot reach here
 453         throw new InternalError("Unsupported matrix type " + type);
 454     }
 455 
 456     /* *************************************************************************
 457      *                                                                         *
 458      *                           State getters                                 *
 459      *                                                                         *
 460      **************************************************************************/
 461 
 462     /**
 463      * Computes if this transform is currently a 2D transform (has no effect
 464      * in the direction of Z axis).
 465      * Used by the subclasses to effectively provide value of the type2D
 466      * property.
 467      * @return true if this transform is currently 2D-only
 468      */
 469     boolean computeIs2D() {
 470         return getMxz() == 0.0 && getMzx() == 0.0 && getMzy() == 0.0 &&
 471                     getMzz() == 1.0 && getTz() == 0.0;
 472     }
 473 
 474     /**
 475      * Computes if this transform is currently an identity (has
 476      * no effect in any direction).
 477      * Used by the subclasses to effectively provide value of the identity
 478      * property.
 479      * @return true if this transform is currently an identity transform
 480      */
 481     boolean computeIsIdentity() {
 482         return
 483             getMxx() == 1.0 && getMxy() == 0.0 && getMxz() == 0.0 && getTx() == 0.0 &&
 484             getMyx() == 0.0 && getMyy() == 1.0 && getMyz() == 0.0 && getTy() == 0.0 &&
 485             getMzx() == 0.0 && getMzy() == 0.0 && getMzz() == 1.0 && getTz() == 0.0;
 486     }
 487 
 488     /**
 489      * Computes determinant of the transformation matrix.
 490      * Among other things, determinant can be used for testing this transform's
 491      * invertibility - it is invertible if determinant is not equal to zero.
 492      * @return Determinant of the transformation matrix
 493      * @since JavaFX 8.0
 494      */
 495     public double determinant() {
 496         final double myx = getMyx();
 497         final double myy = getMyy();
 498         final double myz = getMyz();
 499         final double mzx = getMzx();
 500         final double mzy = getMzy();
 501         final double mzz = getMzz();
 502 
 503         return (getMxx() * (myy * mzz - mzy * myz) +
 504                 getMxy() * (myz * mzx - mzz * myx) +
 505                 getMxz() * (myx * mzy - mzx * myy));
 506     }
 507 
 508     /**
 509      * Determines if this is currently a 2D transform.
 510      * Transform is 2D if it has no effect along the Z axis.
 511      * @since JavaFX 8.0
 512      */
 513     private LazyBooleanProperty type2D;
 514 
 515     public final boolean isType2D() {
 516         return type2D == null ? computeIs2D() : type2D.get();
 517     }
 518 
 519     public final ReadOnlyBooleanProperty type2DProperty() {
 520         if (type2D == null) {
 521             type2D = new LazyBooleanProperty() {
 522 
 523                 @Override
 524                 protected boolean computeValue() {
 525                     return computeIs2D();
 526                 }
 527 
 528                 @Override
 529                 public Object getBean() {
 530                     return Transform.this;
 531                 }
 532 
 533                 @Override
 534                 public String getName() {
 535                     return "type2D";
 536                 }
 537             };
 538         }
 539         return type2D;
 540     }
 541 
 542     /**
 543      * Determines if this is currently an identity transform.
 544      * Identity transform has no effect on the transformed nodes.
 545      * @since JavaFX 8.0
 546      */
 547     private LazyBooleanProperty identity;
 548 
 549     public final boolean isIdentity() {
 550         return identity == null ? computeIsIdentity() : identity.get();
 551     }
 552 
 553     public final ReadOnlyBooleanProperty identityProperty() {
 554         if (identity == null) {
 555             identity = new LazyBooleanProperty() {
 556 
 557                 @Override
 558                 protected boolean computeValue() {
 559                     return computeIsIdentity();
 560                 }
 561 
 562                 @Override
 563                 public Object getBean() {
 564                     return Transform.this;
 565                 }
 566 
 567                 @Override
 568                 public String getName() {
 569                     return "identity";
 570                 }
 571             };
 572         }
 573         return identity;
 574     }
 575 
 576     /**
 577      * Lazily computed read-only boolean property implementation.
 578      * Used for type2D and identity properties.
 579      */
 580     private static abstract class LazyBooleanProperty
 581             extends ReadOnlyBooleanProperty {
 582 
 583         private ExpressionHelper<Boolean> helper;
 584         private boolean valid;
 585         private boolean value;
 586 
 587         @Override
 588         public void addListener(InvalidationListener listener) {
 589             helper = ExpressionHelper.addListener(helper, this, listener);
 590         }
 591 
 592         @Override
 593         public void removeListener(InvalidationListener listener) {
 594             helper = ExpressionHelper.removeListener(helper, listener);
 595         }
 596 
 597         @Override
 598         public void addListener(ChangeListener<? super Boolean> listener) {
 599             helper = ExpressionHelper.addListener(helper, this, listener);
 600         }
 601 
 602         @Override
 603         public void removeListener(ChangeListener<? super Boolean> listener) {
 604             helper = ExpressionHelper.removeListener(helper, listener);
 605         }
 606 
 607         @Override
 608         public boolean get() {
 609             if (!valid) {
 610                 value = computeValue();
 611                 valid = true;
 612             }
 613 
 614             return value;
 615         }
 616 
 617         public void invalidate() {
 618             if (valid) {
 619                 valid = false;
 620                 ExpressionHelper.fireValueChangedEvent(helper);
 621             }
 622         }
 623 
 624         protected abstract boolean computeValue();
 625     }
 626 
 627     /**
 628      * Transforms the specified point by this transform and by the specified
 629      * transform and returns distance of the result points. Used for similarTo
 630      * method. Has to be used only for 2D transforms (otherwise throws an
 631      * exception).
 632      * @param t the other transform
 633      * @param x point's X coordinate
 634      * @param y point's Y coordinate
 635      * @return distance of the transformed points
 636      */
 637     private double transformDiff(Transform t, double x, double y) {
 638         final Point2D byThis = transform(x, y);
 639         final Point2D byOther = t.transform(x, y);
 640         return byThis.distance(byOther);
 641     }
 642 
 643     /**
 644      * Transforms the specified point by this transform and by the specified
 645      * transform and returns distance of the result points. Used for similarTo
 646      * method.
 647      * @param t the other transform
 648      * @param x point's X coordinate
 649      * @param y point's Y coordinate
 650      * @param z point's Z coordinate
 651      * @return distance of the transformed points
 652      */
 653     private double transformDiff(Transform t, double x, double y, double z) {
 654         final Point3D byThis = transform(x, y, z);
 655         final Point3D byOther = t.transform(x, y, z);
 656         return byThis.distance(byOther);
 657     }
 658 
 659     /**
 660      * Checks if this transform is similar to the specified transform.
 661      * The two transforms are considered similar if any point from
 662      * {@code range} is transformed by them to points that are no farther
 663      * than {@code maxDelta} from each other.
 664      * @param transform transform to be compared to this transform
 665      * @param range region of interest on which the two transforms are compared
 666      * @param maxDelta maximum allowed distance for the results of transforming
 667      *                 any single point from {@code range} by the two transforms
 668      * @return true if the transforms are similar according to the specified
 669      *              criteria
 670      * @throws NullPointerException if the specified {@code transform}
 671      *         or {@code range} is null
 672      * @since JavaFX 8.0
 673      */
 674     public boolean similarTo(Transform transform, Bounds range, double maxDelta) {
 675 
 676         double cornerX, cornerY, cornerZ;
 677 
 678         if (isType2D() && transform.isType2D()) {
 679             cornerX = range.getMinX();
 680             cornerY = range.getMinY();
 681 
 682             if (transformDiff(transform, cornerX, cornerY) > maxDelta) {
 683                 return false;
 684             }
 685 
 686             cornerY = range.getMaxY();
 687             if (transformDiff(transform, cornerX, cornerY) > maxDelta) {
 688                 return false;
 689             }
 690 
 691             cornerX = range.getMaxX();
 692             cornerY = range.getMinY();
 693             if (transformDiff(transform, cornerX, cornerY) > maxDelta) {
 694                 return false;
 695             }
 696 
 697             cornerY = range.getMaxY();
 698             if (transformDiff(transform, cornerX, cornerY) > maxDelta) {
 699                 return false;
 700             }
 701 
 702             return true;
 703         }
 704 
 705         cornerX = range.getMinX();
 706         cornerY = range.getMinY();
 707         cornerZ = range.getMinZ();
 708         if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 709             return false;
 710         }
 711 
 712         cornerY = range.getMaxY();
 713         if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 714             return false;
 715         }
 716 
 717         cornerX = range.getMaxX();
 718         cornerY = range.getMinY();
 719         if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 720             return false;
 721         }
 722 
 723         cornerY = range.getMaxY();
 724         if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 725             return false;
 726         }
 727 
 728         if (range.getDepth() != 0.0) {
 729             cornerX = range.getMinX();
 730             cornerY = range.getMinY();
 731             cornerZ = range.getMaxZ();
 732             if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 733                 return false;
 734             }
 735 
 736             cornerY = range.getMaxY();
 737             if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 738                 return false;
 739             }
 740 
 741             cornerX = range.getMaxX();
 742             cornerY = range.getMinY();
 743             if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 744                 return false;
 745             }
 746 
 747             cornerY = range.getMaxY();
 748             if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 749                 return false;
 750             }
 751         }
 752 
 753         return true;
 754     }
 755 
 756     /* *************************************************************************
 757      *                                                                         *
 758      *                           Array getters                                 *
 759      *                                                                         *
 760      **************************************************************************/
 761 
 762     /**
 763      * Core of the toArray implementation for the 2D case.
 764      * All of the checks has been made by the enclosing method as well as
 765      * the constant elements filled, this method only fills the varying
 766      * elements to the array. Used by subclasses to fill
 767      * the elements efficiently.
 768      * @param array array to be filled with the 6 2D elements
 769      */
 770     void fill2DArray(double[] array) {
 771         array[0] = getMxx();
 772         array[1] = getMxy();
 773         array[2] = getTx();
 774         array[3] = getMyx();
 775         array[4] = getMyy();
 776         array[5] = getTy();
 777     }
 778 
 779     /**
 780      * Core of the toArray implementation for the 3D case.
 781      * All of the checks has been made by the enclosing method as well as
 782      * the constant elements filled, this method only fills the varying
 783      * elements to the array. Used by subclasses to fill
 784      * the elements efficiently.
 785      * @param array array to be filled with the 12 3D elements
 786      */
 787     void fill3DArray(double[] array) {
 788         array[0] = getMxx();
 789         array[1] = getMxy();
 790         array[2] = getMxz();
 791         array[3] = getTx();
 792         array[4] = getMyx();
 793         array[5] = getMyy();
 794         array[6] = getMyz();
 795         array[7] = getTy();
 796         array[8] = getMzx();
 797         array[9] = getMzy();
 798         array[10] = getMzz();
 799         array[11] = getTz();
 800     }
 801 
 802     /**
 803      * Returns an array containing the flattened transformation matrix.
 804      * If the requested matrix type fits in the specified array, it is returned
 805      * therein. Otherwise, a new array is created.
 806      * @param type matrix type to be filled in the array
 807      * @param array array into which the elements of the matrix are to be
 808      *              stored, if it is non-null and big enough; otherwise,
 809      *              a new array is created for this purpose.
 810      * @return an array containing the elements of the requested matrix type
 811      *         representing this transform
 812      * @throws IllegalArgumentException if a 2D matrix type is requested for
 813      *         a 3D transform
 814      * @throws NullPointerException if the specified {@code type} is null
 815      * @since JavaFX 8.0
 816      */
 817     public double[] toArray(MatrixType type, double[] array) {
 818         checkRequestedMAT(type);
 819 
 820         if (array == null || array.length < type.elements()) {
 821             array = new double[type.elements()];
 822         }
 823 
 824         switch (type) {
 825             case MT_2D_3x3:
 826                 array[6] = 0.0;
 827                 array[7] = 0.0;
 828                 array[8] = 1.0;
 829                 // fall-through
 830             case MT_2D_2x3:
 831                 fill2DArray(array);
 832                 break;
 833             case MT_3D_4x4:
 834                 array[12] = 0.0;
 835                 array[13] = 0.0;
 836                 array[14] = 0.0;
 837                 array[15] = 1.0;
 838                 // fall-through
 839             case MT_3D_3x4:
 840                 fill3DArray(array);
 841                 break;
 842             default:
 843                 throw new InternalError("Unsupported matrix type " + type);
 844         }
 845 
 846         return array;
 847     }
 848 
 849     /**
 850      * Returns an array containing the flattened transformation matrix.
 851      * @param type matrix type to be filled in the array
 852      * @return an array containing the elements of the requested matrix type
 853      *         representing this transform
 854      * @throws IllegalArgumentException if a 2D matrix type is requested for
 855      *         a 3D transform
 856      * @throws NullPointerException if the specified {@code type} is null
 857      * @since JavaFX 8.0
 858      */
 859     public double[] toArray(MatrixType type) {
 860         return toArray(type, null);
 861     }
 862 
 863     /**
 864      * Returns an array containing a row of the transformation matrix.
 865      * If the row of the requested matrix type fits in the specified array,
 866      * it is returned therein. Otherwise, a new array is created.
 867      * @param type matrix type whose row is to be filled in the array
 868      * @param row zero-based index of the row
 869      * @param array array into which the elements of the row are to be
 870      *              stored, if it is non-null and big enough; otherwise,
 871      *              a new array is created for this purpose.
 872      * @return an array containing the requested row of the requested matrix
 873      *         type representing this transform
 874      * @throws IllegalArgumentException if a 2D matrix type is requested for
 875      *         a 3D transform
 876      * @throws IndexOutOfBoundsException if the {@code row} index is not within
 877      *         the number of rows of the specified matrix type
 878      * @throws NullPointerException if the specified {@code type} is null
 879      * @since JavaFX 8.0
 880      */
 881     public double[] row(MatrixType type, int row, double[] array) {
 882 
 883         checkRequestedMAT(type);
 884 
 885         if (row < 0 || row >= type.rows()) {
 886             throw new IndexOutOfBoundsException(
 887                     "Cannot get row " + row + " from " + type);
 888         }
 889 
 890         if (array == null || array.length < type.columns()) {
 891             array = new double[type.columns()];
 892         }
 893 
 894         switch(type) {
 895             case MT_2D_2x3:
 896             case MT_2D_3x3:
 897                 switch (row) {
 898                     case 0:
 899                         array[0] = getMxx();
 900                         array[1] = getMxy();
 901                         array[2] = getTx();
 902                         break;
 903                     case 1:
 904                         array[0] = getMyx();
 905                         array[1] = getMyy();
 906                         array[2] = getTy();
 907                         break;
 908                     case 2:
 909                         array[0] = 0.0;
 910                         array[1] = 0.0;
 911                         array[2] = 1.0;
 912                         break;
 913                 }
 914                 break;
 915             case MT_3D_3x4:
 916             case MT_3D_4x4:
 917                 switch (row) {
 918                     case 0:
 919                         array[0] = getMxx();
 920                         array[1] = getMxy();
 921                         array[2] = getMxz();
 922                         array[3] = getTx();
 923                         break;
 924                     case 1:
 925                         array[0] = getMyx();
 926                         array[1] = getMyy();
 927                         array[2] = getMyz();
 928                         array[3] = getTy();
 929                         break;
 930                     case 2:
 931                         array[0] = getMzx();
 932                         array[1] = getMzy();
 933                         array[2] = getMzz();
 934                         array[3] = getTz();
 935                         break;
 936                     case 3:
 937                         array[0] = 0.0;
 938                         array[1] = 0.0;
 939                         array[2] = 0.0;
 940                         array[3] = 1.0;
 941                         break;
 942                 }
 943                 break;
 944             default:
 945                 throw new InternalError("Unsupported row " + row + " of " + type);
 946         }
 947         return array;
 948     }
 949 
 950     /**
 951      * Returns an array containing a row of the transformation matrix.
 952      * @param type matrix type whose row is to be filled in the array
 953      * @param row zero-based index of the row
 954      * @return an array containing the requested row of the requested matrix
 955      *         type representing this transform
 956      * @throws IllegalArgumentException if a 2D matrix type is requested for
 957      *         a 3D transform
 958      * @throws IndexOutOfBoundsException if the {@code row} index is not within
 959      *         the number of rows of the specified matrix type
 960      * @throws NullPointerException if the specified {@code type} is null
 961      * @since JavaFX 8.0
 962      */
 963     public double[] row(MatrixType type, int row) {
 964         return row(type, row, null);
 965     }
 966 
 967     /**
 968      * Returns an array containing a column of the transformation matrix.
 969      * If the column of the requested matrix type fits in the specified array,
 970      * it is returned therein. Otherwise, a new array is created.
 971      * @param type matrix type whose column is to be filled in the array
 972      * @param column zero-based index of the column
 973      * @param array array into which the elements of the column are to be
 974      *              stored, if it is non-null and big enough; otherwise,
 975      *              a new array is created for this purpose.
 976      * @return an array containing the requested column of the requested matrix
 977      *         type representing this transform
 978      * @throws IllegalArgumentException if a 2D matrix type is requested for
 979      *         a 3D transform
 980      * @throws IndexOutOfBoundsException if the {@code column} index
 981      *         is not within the number of columns of the specified matrix type
 982      * @throws NullPointerException if the specified {@code type} is null
 983      * @since JavaFX 8.0
 984      */
 985     public double[] column(MatrixType type, int column, double[] array) {
 986 
 987         checkRequestedMAT(type);
 988 
 989         if (column < 0 || column >= type.columns()) {
 990             throw new IndexOutOfBoundsException(
 991                     "Cannot get row " + column + " from " + type);
 992         }
 993 
 994         if (array == null || array.length < type.rows()) {
 995             array = new double[type.rows()];
 996         }
 997 
 998         switch(type) {
 999             case MT_2D_2x3:
1000                 switch (column) {
1001                     case 0:
1002                         array[0] = getMxx();
1003                         array[1] = getMyx();
1004                         break;
1005                     case 1:
1006                         array[0] = getMxy();
1007                         array[1] = getMyy();
1008                         break;
1009                     case 2:
1010                         array[0] = getTx();
1011                         array[1] = getTy();
1012                         break;
1013                 }
1014                 break;
1015             case MT_2D_3x3:
1016                 switch (column) {
1017                     case 0:
1018                         array[0] = getMxx();
1019                         array[1] = getMyx();
1020                         array[2] = 0.0;
1021                         break;
1022                     case 1:
1023                         array[0] = getMxy();
1024                         array[1] = getMyy();
1025                         array[2] = 0.0;
1026                         break;
1027                     case 2:
1028                         array[0] = getTx();
1029                         array[1] = getTy();
1030                         array[2] = 1.0;
1031                         break;
1032                 }
1033                 break;
1034             case MT_3D_3x4:
1035                 switch (column) {
1036                     case 0:
1037                         array[0] = getMxx();
1038                         array[1] = getMyx();
1039                         array[2] = getMzx();
1040                         break;
1041                     case 1:
1042                         array[0] = getMxy();
1043                         array[1] = getMyy();
1044                         array[2] = getMzy();
1045                         break;
1046                     case 2:
1047                         array[0] = getMxz();
1048                         array[1] = getMyz();
1049                         array[2] = getMzz();
1050                         break;
1051                     case 3:
1052                         array[0] = getTx();
1053                         array[1] = getTy();
1054                         array[2] = getTz();
1055                         break;
1056                 }
1057                 break;
1058             case MT_3D_4x4:
1059                 switch (column) {
1060                     case 0:
1061                         array[0] = getMxx();
1062                         array[1] = getMyx();
1063                         array[2] = getMzx();
1064                         array[3] = 0.0;
1065                         break;
1066                     case 1:
1067                         array[0] = getMxy();
1068                         array[1] = getMyy();
1069                         array[2] = getMzy();
1070                         array[3] = 0.0;
1071                         break;
1072                     case 2:
1073                         array[0] = getMxz();
1074                         array[1] = getMyz();
1075                         array[2] = getMzz();
1076                         array[3] = 0.0;
1077                         break;
1078                     case 3:
1079                         array[0] = getTx();
1080                         array[1] = getTy();
1081                         array[2] = getTz();
1082                         array[3] = 1.0;
1083                         break;
1084                 }
1085                 break;
1086             default:
1087                 throw new InternalError("Unsupported column " + column + " of "
1088                         + type);
1089         }
1090         return array;
1091     }
1092 
1093     /**
1094      * Returns an array containing a column of the transformation matrix.
1095      * @param type matrix type whose column is to be filled in the array
1096      * @param column zero-based index of the column
1097      * @return an array containing the requested column of the requested matrix
1098      *         type representing this transform
1099      * @throws IllegalArgumentException if a 2D matrix type is requested for
1100      *         a 3D transform
1101      * @throws IndexOutOfBoundsException if the {@code column} index
1102      *         is not within the number of columns of the specified matrix type
1103      * @throws NullPointerException if the specified {@code type} is null
1104      * @since JavaFX 8.0
1105      */
1106     public double[] column(MatrixType type, int column) {
1107         return column(type, column, null);
1108     }
1109 
1110     /* *************************************************************************
1111      *                                                                         *
1112      *                         Transform creators                              *
1113      *                                                                         *
1114      **************************************************************************/
1115 
1116     /**
1117      * Returns the concatenation of this transform and the specified transform.
1118      * Applying the resulting transform to a node has the same effect as
1119      * adding the two transforms to its {@code getTransforms()} list,
1120      * {@code this} transform first and the specified {@code transform} second.
1121      * @param transform transform to be concatenated with this transform
1122      * @return The concatenated transform
1123      * @throws NullPointerException if the specified {@code transform} is null
1124      * @since JavaFX 8.0
1125      */
1126     public Transform createConcatenation(Transform transform) {
1127         final double txx = transform.getMxx();
1128         final double txy = transform.getMxy();
1129         final double txz = transform.getMxz();
1130         final double ttx = transform.getTx();
1131         final double tyx = transform.getMyx();
1132         final double tyy = transform.getMyy();
1133         final double tyz = transform.getMyz();
1134         final double tty = transform.getTy();
1135         final double tzx = transform.getMzx();
1136         final double tzy = transform.getMzy();
1137         final double tzz = transform.getMzz();
1138         final double ttz = transform.getTz();
1139         return new Affine(
1140             (getMxx() * txx + getMxy() * tyx + getMxz() * tzx),
1141             (getMxx() * txy + getMxy() * tyy + getMxz() * tzy),
1142             (getMxx() * txz + getMxy() * tyz + getMxz() * tzz),
1143             (getMxx() * ttx + getMxy() * tty + getMxz() * ttz + getTx()),
1144             (getMyx() * txx + getMyy() * tyx + getMyz() * tzx),
1145             (getMyx() * txy + getMyy() * tyy + getMyz() * tzy),
1146             (getMyx() * txz + getMyy() * tyz + getMyz() * tzz),
1147             (getMyx() * ttx + getMyy() * tty + getMyz() * ttz + getTy()),
1148             (getMzx() * txx + getMzy() * tyx + getMzz() * tzx),
1149             (getMzx() * txy + getMzy() * tyy + getMzz() * tzy),
1150             (getMzx() * txz + getMzy() * tyz + getMzz() * tzz),
1151             (getMzx() * ttx + getMzy() * tty + getMzz() * ttz + getTz()));
1152     }
1153 
1154     /**
1155      * Returns the inverse transform of this transform.
1156      * @return the inverse transform
1157      * @throws NonInvertibleTransformException if this transform
1158      *         cannot be inverted
1159      * @since JavaFX 8.0
1160      */
1161     public Transform createInverse() throws NonInvertibleTransformException {
1162         return getInverseCache().clone();
1163     }
1164 
1165     /**
1166      * Returns a deep copy of this transform.
1167      * @return a copy of this transform
1168      * @since JavaFX 8.0
1169      */
1170     @Override
1171     public Transform clone() {
1172         return TransformUtils.immutableTransform(this);
1173     }
1174 
1175     /* *************************************************************************
1176      *                                                                         *
1177      *                     Transform, Inverse Transform                        *
1178      *                                                                         *
1179      **************************************************************************/
1180 
1181     /**
1182      * Transforms the specified point by this transform.
1183      * This method can be used only for 2D transforms.
1184      * @param x the X coordinate of the point
1185      * @param y the Y coordinate of the point
1186      * @return the transformed point
1187      * @throws IllegalStateException if this is a 3D transform
1188      * @since JavaFX 8.0
1189      */
1190     public Point2D transform(double x, double y) {
1191         ensureCanTransform2DPoint();
1192 
1193         return new Point2D(
1194             getMxx() * x + getMxy() * y + getTx(),
1195             getMyx() * x + getMyy() * y + getTy());
1196     }
1197 
1198     /**
1199      * Transforms the specified point by this transform.
1200      * This method can be used only for 2D transforms.
1201      * @param point the point to be transformed
1202      * @return the transformed point
1203      * @throws IllegalStateException if this is a 3D transform
1204      * @throws NullPointerException if the specified {@code point} is null
1205      * @since JavaFX 8.0
1206      */
1207     public Point2D transform(Point2D point) {
1208         return transform(point.getX(), point.getY());
1209     }
1210 
1211     /**
1212      * Transforms the specified point by this transform.
1213      * @param x the X coordinate of the point
1214      * @param y the Y coordinate of the point
1215      * @param z the Z coordinate of the point
1216      * @return the transformed point
1217      * @since JavaFX 8.0
1218      */
1219     public Point3D transform(double x, double y, double z) {
1220         return new Point3D(
1221             getMxx() * x + getMxy() * y + getMxz() * z + getTx(),
1222             getMyx() * x + getMyy() * y + getMyz() * z + getTy(),
1223             getMzx() * x + getMzy() * y + getMzz() * z + getTz());
1224     }
1225 
1226     /**
1227      * Transforms the specified point by this transform.
1228      * @param point the point to be transformed
1229      * @return the transformed point
1230      * @throws NullPointerException if the specified {@code point} is null
1231      * @since JavaFX 8.0
1232      */
1233     public Point3D transform(Point3D point) {
1234         return transform(point.getX(), point.getY(), point.getZ());
1235     }
1236 
1237     /**
1238      * Transforms the specified bounds by this transform.
1239      * @param bounds the bounds to be transformed
1240      * @return the transformed bounds
1241      * @since JavaFX 8.0
1242      */
1243     public Bounds transform(Bounds bounds) {
1244         if (isType2D() && (bounds.getMinZ() == 0) && (bounds.getMaxZ() == 0)) {
1245             Point2D p1 = transform(bounds.getMinX(), bounds.getMinY());
1246             Point2D p2 = transform(bounds.getMaxX(), bounds.getMinY());
1247             Point2D p3 = transform(bounds.getMaxX(), bounds.getMaxY());
1248             Point2D p4 = transform(bounds.getMinX(), bounds.getMaxY());
1249 
1250             return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
1251         }
1252         Point3D p1 = transform(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ());
1253         Point3D p2 = transform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ());
1254         Point3D p3 = transform(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ());
1255         Point3D p4 = transform(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ());
1256         Point3D p5 = transform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ());
1257         Point3D p6 = transform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ());
1258         Point3D p7 = transform(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ());
1259         Point3D p8 = transform(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ());
1260         
1261         return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
1262     }
1263 
1264     /**
1265      * Core of the transform2DPoints method.
1266      * All the checks has been performed and the care of the overlaps has been
1267      * taken by the enclosing method, this method only transforms the points
1268      * and fills them to the array. Used by the subclasses to perform
1269      * the transform efficiently.
1270      */
1271     void transform2DPointsImpl(double[] srcPts, int srcOff,
1272             double[] dstPts, int dstOff, int numPts) {
1273         final double xx = getMxx();
1274         final double xy = getMxy();
1275         final double tx = getTx();
1276         final double yx = getMyx();
1277         final double yy = getMyy();
1278         final double ty = getTy();
1279 
1280         while (--numPts >= 0) {
1281             final double x = srcPts[srcOff++];
1282             final double y = srcPts[srcOff++];
1283 
1284             dstPts[dstOff++] = xx * x + xy * y + tx;
1285             dstPts[dstOff++] = yx * x + yy * y + ty;
1286         }
1287     }
1288 
1289     /**
1290      * Core of the transform3DPoints method.
1291      * All the checks has been performed and the care of the overlaps has been
1292      * taken by the enclosing method, this method only transforms the points
1293      * and fills them to the array. Used by the subclasses to perform
1294      * the transform efficiently.
1295      */
1296     void transform3DPointsImpl(double[] srcPts, int srcOff,
1297             double[] dstPts, int dstOff, int numPts) {
1298 
1299         final double xx = getMxx();
1300         final double xy = getMxy();
1301         final double xz = getMxz();
1302         final double tx = getTx();
1303         final double yx = getMyx();
1304         final double yy = getMyy();
1305         final double yz = getMyz();
1306         final double ty = getTy();
1307         final double zx = getMzx();
1308         final double zy = getMzy();
1309         final double zz = getMzz();
1310         final double tz = getTz();
1311 
1312         while (--numPts >= 0) {
1313             final double x = srcPts[srcOff++];
1314             final double y = srcPts[srcOff++];
1315             final double z = srcPts[srcOff++];
1316 
1317             dstPts[dstOff++] = xx * x + xy * y + xz * z + tx;
1318             dstPts[dstOff++] = yx * x + yy * y + yz * z + ty;
1319             dstPts[dstOff++] = zx * x + zy * y + zz * z + tz;
1320         }
1321     }
1322 
1323     /**
1324      * Transforms an array of coordinates by this transform.
1325      * The two coordinate array sections can be exactly the same or
1326      * can be overlapping sections of the same array without affecting the
1327      * validity of the results.
1328      * This method ensures that no source coordinates are overwritten by a
1329      * previous operation before they can be transformed.
1330      * The coordinates are stored in the arrays starting at the specified
1331      * offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>.
1332      * This method can be used only for 2D transforms.
1333      * @param srcPts the array containing the source point coordinates.
1334      * Each point is stored as a pair of x,&nbsp;y coordinates.
1335      * @param srcOff the offset to the first point to be transformed
1336      * in the source array
1337      * @param dstPts the array into which the transformed point coordinates
1338      * are returned.  Each point is stored as a pair of x,&nbsp;y
1339      * coordinates.
1340      * @param dstOff the offset to the location of the first
1341      * transformed point that is stored in the destination array
1342      * @param numPts the number of points to be transformed
1343      * @throws IllegalStateException if this is a 3D transform
1344      * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
1345      * @since JavaFX 8.0
1346      */
1347     public void transform2DPoints(double[] srcPts, int srcOff,
1348                           double[] dstPts, int dstOff,
1349                           int numPts) {
1350 
1351         if (srcPts == null || dstPts == null) {
1352             throw new NullPointerException();
1353         }
1354 
1355         if (!isType2D()) {
1356             throw new IllegalStateException("Cannot transform 2D points "
1357                     + "with a 3D transform");
1358         }
1359 
1360         // deal with overlapping arrays
1361         srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 2);
1362 
1363         // do the transformations
1364         transform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
1365     }
1366 
1367     /**
1368      * Transforms an array of floating point coordinates by this transform.
1369      * The three coordinate array sections can be exactly the same or
1370      * can be overlapping sections of the same array without affecting the
1371      * validity of the results.
1372      * This method ensures that no source coordinates are overwritten by a
1373      * previous operation before they can be transformed.
1374      * The coordinates are stored in the arrays starting at the specified
1375      * offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>.
1376      * @param srcPts the array containing the source point coordinates.
1377      * Each point is stored as a tiplet of x,&nbsp;y,&nbsp;z coordinates.
1378      * @param srcOff the offset to the first point to be transformed
1379      * in the source array
1380      * @param dstPts the array into which the transformed point coordinates
1381      * are returned.  Each point is stored as a triplet of x,&nbsp;y,&nbsp;z
1382      * coordinates.
1383      * @param dstOff the offset to the location of the first
1384      * transformed point that is stored in the destination array
1385      * @param numPts the number of points to be transformed
1386      * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
1387      * @since JavaFX 8.0
1388      */
1389     public void transform3DPoints(double[] srcPts, int srcOff,
1390                           double[] dstPts, int dstOff,
1391                           int numPts) {
1392 
1393         if (srcPts == null || dstPts == null) {
1394             throw new NullPointerException();
1395         }
1396 
1397         // deal with overlapping arrays
1398         srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 3);
1399 
1400         // do the transformations
1401         transform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
1402     }
1403 
1404     /**
1405      * Transforms the relative magnitude vector by this transform.
1406      * The vector is transformed without applying the translation components
1407      * of the affine transformation matrix.
1408      * This method can be used only for a 2D transform.
1409      * @param x vector magnitude in the direction of the X axis
1410      * @param y vector magnitude in the direction of the Y axis
1411      * @return the transformed relative magnitude vector represented
1412      *         by a {@code Point2D} instance
1413      * @throws IllegalStateException if this is a 3D transform
1414      * @since JavaFX 8.0
1415      */
1416     public Point2D deltaTransform(double x, double y) {
1417         ensureCanTransform2DPoint();
1418         
1419         return new Point2D(
1420             getMxx() * x + getMxy() * y,
1421             getMyx() * x + getMyy() * y);
1422     }
1423 
1424     /**
1425      * Transforms the relative magnitude vector represented by the specified
1426      * {@code Point2D} instance by this transform.
1427      * The vector is transformed without applying the translation components
1428      * of the affine transformation matrix.
1429      * This method can be used only for a 2D transform.
1430      * @param point the relative magnitude vector
1431      * @return the transformed relative magnitude vector represented
1432      *         by a {@code Point2D} instance
1433      * @throws IllegalStateException if this is a 3D transform
1434      * @throws NullPointerException if the specified {@code point} is null
1435      * @since JavaFX 8.0
1436      */
1437     public Point2D deltaTransform(Point2D point) {
1438         return deltaTransform(point.getX(), point.getY());
1439     }
1440 
1441     /**
1442      * Transforms the relative magnitude vector by this transform.
1443      * The vector is transformed without applying the translation components
1444      * of the affine transformation matrix.
1445      * @param x vector magnitude in the direction of the X axis
1446      * @param y vector magnitude in the direction of the Y axis
1447      * @return the transformed relative magnitude vector represented
1448      *         by a {@code Point3D} instance
1449      * @since JavaFX 8.0
1450      */
1451     public Point3D deltaTransform(double x, double y, double z) {
1452         return new Point3D(
1453             getMxx() * x + getMxy() * y + getMxz() * z,
1454             getMyx() * x + getMyy() * y + getMyz() * z,
1455             getMzx() * x + getMzy() * y + getMzz() * z);
1456     }
1457 
1458     /**
1459      * Transforms the relative magnitude vector represented by the specified
1460      * {@code Point3D} instance by this transform.
1461      * The vector is transformed without applying the translation components
1462      * of the affine transformation matrix.
1463      * @param point the relative magnitude vector
1464      * @return the transformed relative magnitude vector represented
1465      *         by a {@code Point3D} instance
1466      * @throws NullPointerException if the specified {@code point} is null
1467      * @since JavaFX 8.0
1468      */
1469     public Point3D deltaTransform(Point3D point) {
1470         return deltaTransform(point.getX(), point.getY(), point.getZ());
1471     }
1472 
1473     /**
1474      * Transforms the specified point by the inverse of this transform.
1475      * This method can be used only for 2D transforms.
1476      * @param x the X coordinate of the point
1477      * @param y the Y coordinate of the point
1478      * @return the inversely transformed point
1479      * @throws IllegalStateException if this is a 3D transform
1480      * @throws NonInvertibleTransformException if this transform
1481      *         cannot be inverted
1482      * @since JavaFX 8.0
1483      */
1484     public Point2D inverseTransform(double x, double y)
1485             throws NonInvertibleTransformException {
1486 
1487         ensureCanTransform2DPoint();
1488 
1489         return getInverseCache().transform(x, y);
1490     }
1491 
1492     /**
1493      * Transforms the specified point by the inverse of this transform.
1494      * This method can be used only for 2D transforms.
1495      * @param point the point to be transformed
1496      * @return the inversely transformed point
1497      * @throws IllegalStateException if this is a 3D transform
1498      * @throws NonInvertibleTransformException if this transform
1499      *         cannot be inverted
1500      * @throws NullPointerException if the specified {@code point} is null
1501      * @since JavaFX 8.0
1502      */
1503     public Point2D inverseTransform(Point2D point)
1504             throws NonInvertibleTransformException {
1505         return inverseTransform(point.getX(), point.getY());
1506     }
1507 
1508     /**
1509      * Transforms the specified point by the inverse of this transform.
1510      * @param x the X coordinate of the point
1511      * @param y the Y coordinate of the point
1512      * @param z the Z coordinate of the point
1513      * @return the inversely transformed point
1514      * @throws NonInvertibleTransformException if this transform
1515      *         cannot be inverted
1516      * @since JavaFX 8.0
1517      */
1518     public Point3D inverseTransform(double x, double y, double z)
1519             throws NonInvertibleTransformException {
1520 
1521         return getInverseCache().transform(x, y, z);
1522     }
1523 
1524     /**
1525      * Transforms the specified point by the inverse of this transform.
1526      * @param point the point to be transformed
1527      * @return the inversely transformed point
1528      * @throws NonInvertibleTransformException if this transform
1529      *         cannot be inverted
1530      * @throws NullPointerException if the specified {@code point} is null
1531      * @since JavaFX 8.0
1532      */
1533     public Point3D inverseTransform(Point3D point)
1534             throws NonInvertibleTransformException {
1535         return inverseTransform(point.getX(), point.getY(), point.getZ());
1536     }
1537 
1538     /**
1539      * Transforms the specified bounds by the inverse of this transform.
1540      * @param bounds the bounds to be transformed
1541      * @return the inversely transformed bounds
1542      * @throws NonInvertibleTransformException if this transform
1543      *         cannot be inverted
1544      * @throws NullPointerException if the specified {@code bounds} is null
1545      * @since JavaFX 8.0
1546      */
1547     public Bounds inverseTransform(Bounds bounds)
1548             throws NonInvertibleTransformException {
1549         if (isType2D() && (bounds.getMinZ() == 0) && (bounds.getMaxZ() == 0)) {
1550             Point2D p1 = inverseTransform(bounds.getMinX(), bounds.getMinY());
1551             Point2D p2 = inverseTransform(bounds.getMaxX(), bounds.getMinY());
1552             Point2D p3 = inverseTransform(bounds.getMaxX(), bounds.getMaxY());
1553             Point2D p4 = inverseTransform(bounds.getMinX(), bounds.getMaxY());
1554 
1555             return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
1556         }
1557         Point3D p1 = inverseTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ());
1558         Point3D p2 = inverseTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ());
1559         Point3D p3 = inverseTransform(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ());
1560         Point3D p4 = inverseTransform(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ());
1561         Point3D p5 = inverseTransform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ());
1562         Point3D p6 = inverseTransform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ());
1563         Point3D p7 = inverseTransform(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ());
1564         Point3D p8 = inverseTransform(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ());
1565 
1566         return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
1567 
1568     }
1569 
1570     /**
1571      * Core of the inverseTransform2DPoints method.
1572      * All the checks has been performed and the care of the overlaps has been
1573      * taken by the enclosing method, this method only transforms the points
1574      * and fills them to the array. Used by the subclasses to perform
1575      * the transform efficiently.
1576      */
1577     void inverseTransform2DPointsImpl(double[] srcPts, int srcOff,
1578             double[] dstPts, int dstOff, int numPts)
1579             throws NonInvertibleTransformException {
1580 
1581         getInverseCache().transform2DPointsImpl(srcPts, srcOff,
1582                 dstPts, dstOff, numPts);
1583     }
1584 
1585     /**
1586      * Core of the inverseTransform3DPoints method.
1587      * All the checks has been performed and the care of the overlaps has been
1588      * taken by the enclosing method, this method only transforms the points
1589      * and fills them to the array. Used by the subclasses to perform
1590      * the transform efficiently.
1591      */
1592     void inverseTransform3DPointsImpl(double[] srcPts, int srcOff,
1593             double[] dstPts, int dstOff, int numPts)
1594             throws NonInvertibleTransformException {
1595 
1596         getInverseCache().transform3DPointsImpl(srcPts, srcOff,
1597                 dstPts, dstOff, numPts);
1598     }
1599 
1600     /**
1601      * Transforms an array of coordinates by the inverse of this transform.
1602      * The two coordinate array sections can be exactly the same or
1603      * can be overlapping sections of the same array without affecting the
1604      * validity of the results.
1605      * This method ensures that no source coordinates are overwritten by a
1606      * previous operation before they can be transformed.
1607      * The coordinates are stored in the arrays starting at the specified
1608      * offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>.
1609      * This method can be used only for 2D transforms.
1610      * @param srcPts the array containing the source point coordinates.
1611      * Each point is stored as a pair of x,&nbsp;y coordinates.
1612      * @param srcOff the offset to the first point to be transformed
1613      * in the source array
1614      * @param dstPts the array into which the transformed point coordinates
1615      * are returned.  Each point is stored as a pair of x,&nbsp;y
1616      * coordinates.
1617      * @param dstOff the offset to the location of the first
1618      * transformed point that is stored in the destination array
1619      * @param numPts the number of points to be transformed
1620      * @throws IllegalStateException if this is a 3D transform
1621      * @throws NonInvertibleTransformException if this transform
1622      *         cannot be inverted
1623      * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
1624      * @since JavaFX 8.0
1625      */
1626     public void inverseTransform2DPoints(double[] srcPts, int srcOff,
1627                           double[] dstPts, int dstOff,
1628                           int numPts) throws NonInvertibleTransformException{
1629 
1630         if (srcPts == null || dstPts == null) {
1631             throw new NullPointerException();
1632         }
1633 
1634         if (!isType2D()) {
1635             throw new IllegalStateException("Cannot transform 2D points "
1636                     + "with a 3D transform");
1637         }
1638 
1639         // deal with overlapping arrays
1640         srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 2);
1641 
1642         // do the transformations
1643         inverseTransform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
1644     }
1645 
1646     /**
1647      * Transforms an array of floating point coordinates by the inverse
1648      * of this transform.
1649      * The three coordinate array sections can be exactly the same or
1650      * can be overlapping sections of the same array without affecting the
1651      * validity of the results.
1652      * This method ensures that no source coordinates are overwritten by a
1653      * previous operation before they can be transformed.
1654      * The coordinates are stored in the arrays starting at the specified
1655      * offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>.
1656      * @param srcPts the array containing the source point coordinates.
1657      * Each point is stored as a triplet of x,&nbsp;y,&nbsp;z coordinates.
1658      * @param srcOff the offset to the first point to be transformed
1659      * in the source array
1660      * @param dstPts the array into which the transformed point coordinates
1661      * are returned.  Each point is stored as a triplet of x,&nbsp;y,&nbsp;z
1662      * coordinates.
1663      * @param dstOff the offset to the location of the first
1664      * transformed point that is stored in the destination array
1665      * @param numPts the number of points to be transformed
1666      * @throws NonInvertibleTransformException if this transform
1667      *         cannot be inverted
1668      * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
1669      * @since JavaFX 8.0
1670      */
1671     public void inverseTransform3DPoints(double[] srcPts, int srcOff,
1672                           double[] dstPts, int dstOff,
1673                           int numPts) throws NonInvertibleTransformException {
1674 
1675         if (srcPts == null || dstPts == null) {
1676             throw new NullPointerException();
1677         }
1678 
1679         // deal with overlapping arrays
1680         srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 3);
1681 
1682         // do the transformations
1683         inverseTransform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
1684     }
1685 
1686     /**
1687      * Transforms the relative magnitude vector by the inverse of this transform.
1688      * The vector is transformed without applying the translation components
1689      * of the affine transformation matrix.
1690      * This method can be used only for a 2D transform.
1691      * @param x vector magnitude in the direction of the X axis
1692      * @param y vector magnitude in the direction of the Y axis
1693      * @return the inversely transformed relative magnitude vector represented
1694      *         by a {@code Point2D} instance
1695      * @throws IllegalStateException if this is a 3D transform
1696      * @throws NonInvertibleTransformException if this transform
1697      *         cannot be inverted
1698      * @since JavaFX 8.0
1699      */
1700     public Point2D inverseDeltaTransform(double x, double y)
1701             throws NonInvertibleTransformException {
1702 
1703         ensureCanTransform2DPoint();
1704 
1705         return getInverseCache().deltaTransform(x, y);
1706     }
1707 
1708     /**
1709      * Transforms the relative magnitude vector represented by the specified
1710      * {@code Point2D} instance by the inverse of this transform.
1711      * The vector is transformed without applying the translation components
1712      * of the affine transformation matrix.
1713      * This method can be used only for a 2D transform.
1714      * @param point the relative magnitude vector
1715      * @return the inversely transformed relative magnitude vector represented
1716      *         by a {@code Point2D} instance
1717      * @throws IllegalStateException if this is a 3D transform
1718      * @throws NonInvertibleTransformException if this transform
1719      *         cannot be inverted
1720      * @throws NullPointerException if the specified {@code point} is null
1721      * @since JavaFX 8.0
1722      */
1723     public Point2D inverseDeltaTransform(Point2D point)
1724             throws NonInvertibleTransformException {
1725         return inverseDeltaTransform(point.getX(), point.getY());
1726     }
1727 
1728     /**
1729      * Transforms the relative magnitude vector by the inverse of this transform.
1730      * The vector is transformed without applying the translation components
1731      * of the affine transformation matrix.
1732      * @param x vector magnitude in the direction of the X axis
1733      * @param y vector magnitude in the direction of the Y axis
1734      * @return the inversely transformed relative magnitude vector represented
1735      *         by a {@code Point3D} instance
1736      * @throws NonInvertibleTransformException if this transform
1737      *         cannot be inverted
1738      * @since JavaFX 8.0
1739      */
1740     public Point3D inverseDeltaTransform(double x, double y, double z)
1741             throws NonInvertibleTransformException {
1742 
1743         return getInverseCache().deltaTransform(x, y, z);
1744     }
1745 
1746     /**
1747      * Transforms the relative magnitude vector represented by the specified
1748      * {@code Point3D} instance by the inverse of this transform.
1749      * The vector is transformed without applying the translation components
1750      * of the affine transformation matrix.
1751      * @param point the relative magnitude vector
1752      * @return the inversely transformed relative magnitude vector represented
1753      *         by a {@code Point3D} instance
1754      * @throws NonInvertibleTransformException if this transform
1755      *         cannot be inverted
1756      * @throws NullPointerException if the specified {@code point} is null
1757      * @since JavaFX 8.0
1758      */
1759     public Point3D inverseDeltaTransform(Point3D point)
1760             throws NonInvertibleTransformException {
1761         return inverseDeltaTransform(point.getX(), point.getY(), point.getZ());
1762     }
1763 
1764     /**
1765      * Helper method for transforming arrays of points that deals with
1766      * overlapping arrays.
1767      * @return the (if necessary fixed) srcOff
1768      */
1769     private int getFixedSrcOffset(double[] srcPts, int srcOff,
1770             double[] dstPts, int dstOff,
1771             int numPts, int dimensions) {
1772 
1773         if (dstPts == srcPts &&
1774             dstOff > srcOff && dstOff < srcOff + numPts * dimensions)
1775         {
1776             // If the arrays overlap partially with the destination higher
1777             // than the source and we transform the coordinates normally
1778             // we would overwrite some of the later source coordinates
1779             // with results of previous transformations.
1780             // To get around this we use arraycopy to copy the points
1781             // to their final destination with correct overwrite
1782             // handling and then transform them in place in the new
1783             // safer location.
1784             System.arraycopy(srcPts, srcOff, dstPts, dstOff, numPts * dimensions);
1785             return dstOff;
1786         }
1787 
1788         return srcOff;
1789     }
1790 
1791     /* *************************************************************************
1792      *                                                                         *
1793      *                         Event Dispatch                                  *
1794      *                                                                         *
1795      **************************************************************************/
1796 
1797     private EventHandlerManager internalEventDispatcher;
1798     private EventHandlerManager getInternalEventDispatcher() {
1799         if (internalEventDispatcher == null) {
1800             internalEventDispatcher = new EventHandlerManager(this);
1801         }
1802         return internalEventDispatcher;
1803     }
1804     private ObjectProperty<EventHandler<? super TransformChangedEvent>>
1805             onTransformChanged;
1806 
1807     @Override
1808     public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
1809         return internalEventDispatcher == null
1810                 ? tail : tail.append(getInternalEventDispatcher());
1811     }
1812 
1813     /**
1814      * <p>
1815      * Registers an event handler to this transform. Any event filters are first
1816      * processed, then the specified onFoo event handlers, and finally any
1817      * event handlers registered by this method.
1818      * </p><p>
1819      * Currently the only event delivered to a {@code Transform} is the
1820      * {@code TransformChangedEvent} with it's single type
1821      * {@code TRANSFORM_CHANGED}.
1822      * </p>
1823      *
1824      * @param <T> the specific event class of the handler
1825      * @param eventType the type of the events to receive by the handler
1826      * @param eventHandler the handler to register
1827      * @throws NullPointerException if the event type or handler is null
1828      * @since JavaFX 8.0
1829      */
1830     public final <T extends Event> void addEventHandler(
1831             final EventType<T> eventType,
1832             final EventHandler<? super T> eventHandler) {
1833         getInternalEventDispatcher()
1834                 .addEventHandler(eventType, eventHandler);
1835         // need to validate all properties to get the change events
1836         validate();
1837     }
1838 
1839     /**
1840      * Unregisters a previously registered event handler from this transform.
1841      * One handler might have been registered for different event types, so the
1842      * caller needs to specify the particular event type from which to
1843      * unregister the handler.
1844      *
1845      * @param <T> the specific event class of the handler
1846      * @param eventType the event type from which to unregister
1847      * @param eventHandler the handler to unregister
1848      * @throws NullPointerException if the event type or handler is null
1849      * @since JavaFX 8.0
1850      */
1851     public final <T extends Event> void removeEventHandler(
1852             final EventType<T> eventType,
1853             final EventHandler<? super T> eventHandler) {
1854         getInternalEventDispatcher()
1855                 .removeEventHandler(eventType, eventHandler);
1856     }
1857 
1858     /**
1859      * <p>
1860      * Registers an event filter to this transform. Registered event filters get
1861      * an event before any associated event handlers.
1862      * </p><p>
1863      * Currently the only event delivered to a {@code Transform} is the
1864      * {@code TransformChangedEvent} with it's single type
1865      * {@code TRANSFORM_CHANGED}.
1866      * <p>
1867      * 
1868      * @param <T> the specific event class of the filter
1869      * @param eventType the type of the events to receive by the filter
1870      * @param eventFilter the filter to register
1871      * @throws NullPointerException if the event type or filter is null
1872      * @since JavaFX 8.0
1873      */
1874     public final <T extends Event> void addEventFilter(
1875             final EventType<T> eventType,
1876             final EventHandler<? super T> eventFilter) {
1877         getInternalEventDispatcher()
1878                 .addEventFilter(eventType, eventFilter);
1879         // need to validate all properties to get the change events
1880         validate();
1881     }
1882 
1883     /**
1884      * Unregisters a previously registered event filter from this transform. One
1885      * filter might have been registered for different event types, so the
1886      * caller needs to specify the particular event type from which to
1887      * unregister the filter.
1888      *
1889      * @param <T> the specific event class of the filter
1890      * @param eventType the event type from which to unregister
1891      * @param eventFilter the filter to unregister
1892      * @throws NullPointerException if the event type or filter is null
1893      * @since JavaFX 8.0
1894      */
1895     public final <T extends Event> void removeEventFilter(
1896             final EventType<T> eventType,
1897             final EventHandler<? super T> eventFilter) {
1898         getInternalEventDispatcher()
1899                 .removeEventFilter(eventType, eventFilter);
1900     }
1901 
1902     /**
1903      * Sets the onTransformChanged event handler which is called whenever
1904      * the transform changes any of its parameters.
1905      *
1906      * @param value the event handler, can be null to clear it
1907      * @since JavaFX 8.0
1908      */
1909     public final void setOnTransformChanged(
1910             EventHandler<? super TransformChangedEvent> value) {
1911         onTransformChangedProperty().set(value);
1912         // need to validate all properties to get the change events
1913         validate();
1914     }
1915 
1916     /**
1917      * Gets the onTransformChanged event handler.
1918      * @return the event handler previously set by {@code setOnTransformChanged}
1919      * method, null if the handler is not set.
1920      * @since JavaFX 8.0
1921      */
1922     public final EventHandler<? super TransformChangedEvent> getOnTransformChanged() {
1923         return (onTransformChanged == null) ? null : onTransformChanged.get();
1924     }
1925 
1926     /**
1927      * The onTransformChanged event handler is called whenever the transform
1928      * changes any of its parameters.
1929      * @since JavaFX 8.0
1930      */
1931     public final ObjectProperty<EventHandler<? super TransformChangedEvent>>
1932             onTransformChangedProperty() {
1933         if (onTransformChanged == null) {
1934 
1935             onTransformChanged = new SimpleObjectProperty<EventHandler
1936                     <? super TransformChangedEvent>>(this, "onTransformChanged") {
1937 
1938                 @Override protected void invalidated() {
1939                     getInternalEventDispatcher().setEventHandler(
1940                             TransformChangedEvent.TRANSFORM_CHANGED, get());
1941                 }
1942             };
1943         }
1944 
1945         return onTransformChanged;
1946     }
1947 
1948     /* *************************************************************************
1949      *                                                                         *
1950      *                    Internal implementation stuff                        *
1951      *                                                                         *
1952      **************************************************************************/
1953 
1954     /**
1955      * Makes sure the specified matrix type can be requested from this transform.
1956      * Is used for convenience in various methods that accept
1957      * the MatrixType argument.
1958      * @param type matrix type to check
1959      * @throws IllegalArgumentException if this is a 3D transform and
1960      *                                  a 2D type is requested
1961      */
1962     void checkRequestedMAT(MatrixType type) throws IllegalArgumentException{
1963         if (type.is2D() && !isType2D()) {
1964             throw new IllegalArgumentException("Cannot access 2D matrix "
1965                     + "for a 3D transform");
1966         }
1967     }
1968 
1969     /**
1970      * Makes sure this is a 2D transform.
1971      * Is used for convenience in various 2D point transformation methods.
1972      * @throws IllegalStateException if this is a 2D transform
1973      */
1974     void ensureCanTransform2DPoint() throws IllegalStateException {
1975         if (!isType2D()) {
1976             throw new IllegalStateException("Cannot transform 2D point "
1977                     + "with a 3D transform");
1978         }
1979     }
1980 
1981     /**
1982      * Needed for the proper delivery of the TransformChangedEvent.
1983      * If the members are invalid, the transformChanged() notification
1984      * is not called and the event is not delivered. To avoid that
1985      * we need to manually validate all properties. Subclasses validate
1986      * their specific properties.
1987      */
1988     void validate() {
1989         getMxx(); getMxy(); getMxz(); getTx();
1990         getMyx(); getMyy(); getMyz(); getTy();
1991         getMzx(); getMzy(); getMzz(); getTz();
1992     }
1993 
1994     /**
1995      * @treatAsPrivate implementation detail
1996      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1997      */
1998     @Deprecated
1999     public abstract void impl_apply(Affine3D t);
2000 
2001     /**
2002      * @treatAsPrivate implementation detail
2003      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2004      */
2005     @Deprecated
2006     public abstract BaseTransform impl_derive(BaseTransform t);
2007 
2008     /**
2009      * @treatAsPrivate implementation detail
2010      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2011      */
2012     @Deprecated
2013     public void impl_add(final Node node) {
2014         impl_nodes.add(node);
2015     }
2016 
2017     /**
2018      * @treatAsPrivate implementation detail
2019      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2020      */
2021     @Deprecated
2022     public void impl_remove(final Node node) {
2023         impl_nodes.remove(node);
2024     }
2025 
2026     /**
2027      * This method must be called by all transforms whenever any of their
2028      * parameters changes. It is typically called when any of the transform's
2029      * properties is invalidated (it is OK to skip the call if an invalid
2030      * property is set).
2031      * @since JavaFX 8.0
2032      */
2033     protected void transformChanged() {
2034         inverseCache = null;
2035         final Iterator iterator = impl_nodes.iterator();
2036         while (iterator.hasNext()) {
2037             ((Node) iterator.next()).impl_transformsChanged();
2038         }
2039 
2040         if (type2D != null) {
2041             type2D.invalidate();
2042         }
2043 
2044         if (identity != null) {
2045             identity.invalidate();
2046         }
2047 
2048         if (internalEventDispatcher != null) {
2049             // need to validate all properties for the event to be fired next time
2050             validate();
2051             Event.fireEvent(this, new TransformChangedEvent(this, this));
2052         }
2053     }
2054 
2055     /**
2056      * Visitor from {@code Affine} class which provides an efficient
2057      * {@code append} operation for the subclasses.
2058      * @param a {@code Affine} instance to append to
2059      */
2060     void appendTo(Affine a) {
2061         a.append(getMxx(), getMxy(), getMxz(), getTx(),
2062                  getMyx(), getMyy(), getMyz(), getTy(),
2063                  getMzx(), getMzy(), getMzz(), getTz());
2064     }
2065 
2066     /**
2067      * Visitor from {@code Affine} class which provides an efficient
2068      * {@code prepend} operation for the subclasses.
2069      * @param a {@code Affine} instance to prepend to
2070      */
2071     void prependTo(Affine a) {
2072         a.prepend(getMxx(), getMxy(), getMxz(), getTx(),
2073                   getMyx(), getMyy(), getMyz(), getTy(),
2074                   getMzx(), getMzy(), getMzz(), getTz());
2075     }
2076 
2077     /**
2078      * <p>
2079      * Gets the inverse transform cache.
2080      * </p><p>
2081      * Computing the inverse transform is generally an expensive operation,
2082      * so once it is needed we cache the result (throwing it away when the
2083      * transform changes). The subclasses may avoid using the cache if their
2084      * inverse can be computed quickly on the fly.
2085      * </p><p>
2086      * This method computes the inverse if the cache is not valid.
2087      * </p>
2088      * @return the cached inverse transformation
2089      * @throws NonInvertibleTransformException if this transform
2090      *         cannot be inverted
2091      */
2092     private Transform getInverseCache() throws NonInvertibleTransformException {
2093         if (inverseCache == null || inverseCache.get() == null) {
2094             Affine inv = new Affine(
2095                     getMxx(), getMxy(), getMxz(), getTx(),
2096                     getMyx(), getMyy(), getMyz(), getTy(),
2097                     getMzx(), getMzy(), getMzz(), getTz());
2098             inv.invert();
2099             inverseCache = new SoftReference<Transform>(inv);
2100             return inv;
2101         }
2102 
2103         return inverseCache.get();
2104     }
2105 
2106     /**
2107      * Used only by tests to emulate garbage collecting the soft references
2108      */
2109     void clearInverseCache() {
2110         if (inverseCache != null) {
2111             inverseCache.clear();
2112         }
2113     }
2114 }