1 /*
   2  * Copyright (c) 2011, 2016, 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.Affine3D;
  39 import com.sun.javafx.geom.transform.BaseTransform;
  40 import com.sun.javafx.scene.transform.TransformHelper;
  41 import com.sun.javafx.scene.transform.TransformUtils;
  42 import java.lang.ref.SoftReference;
  43 import javafx.beans.InvalidationListener;
  44 import javafx.beans.property.ObjectProperty;
  45 import javafx.beans.property.ReadOnlyBooleanProperty;
  46 import javafx.beans.property.SimpleObjectProperty;
  47 import javafx.beans.value.ChangeListener;
  48 import javafx.event.Event;
  49 import javafx.event.EventHandler;
  50 import javafx.event.EventTarget;
  51 import javafx.event.EventType;
  52 import javafx.geometry.Bounds;
  53 import javafx.geometry.Point2D;
  54 import javafx.geometry.Point3D;
  55 
  56 // PENDING_DOC_REVIEW of this whole class
  57 /**
  58  * This class is a base class for different affine transformations.
  59  * It provides factory methods for the simple transformations - rotating,
  60  * scaling, shearing, and translation. It allows to get the transformation
  61  * matrix elements for any transform.
  62  *
  63  * <p>Example:</p>
  64  *
  65  * <pre><code>
  66  *  Rectangle rect = new Rectangle(50,50, Color.RED);
  67  *  rect.getTransforms().add(new Rotate(45,0,0)); //rotate by 45 degrees
  68  * </code></pre>
  69  * @since JavaFX 2.0
  70  */
  71 public abstract class Transform implements Cloneable, EventTarget {
  72 
  73     static {
  74         // This is used by classes in different packages to get access to
  75         // private and package private methods.
  76         TransformHelper.setTransformAccessor(new TransformHelper.TransformAccessor() {
  77 
  78             @Override
  79             public void add(Transform transform, Node node) {
  80                 transform.add(node);
  81             }
  82 
  83             @Override
  84             public void remove(Transform transform, Node node) {
  85                 transform.remove(node);
  86             }
  87 
  88             @Override
  89             public void apply(Transform transform, Affine3D affine3D) {
  90                 transform.apply(affine3D);
  91             }
  92 
  93             @Override
  94             public BaseTransform derive(Transform transform, BaseTransform baseTransform) {
  95                 return transform.derive(baseTransform);
  96             }
  97 
  98             @Override
  99             public Transform createImmutableTransform() {
 100                 return Transform.createImmutableTransform();
 101             }
 102 
 103             @Override
 104             public Transform createImmutableTransform(
 105                     double mxx, double mxy, double mxz, double tx,
 106                     double myx, double myy, double myz, double ty,
 107                     double mzx, double mzy, double mzz, double tz) {
 108                 return Transform.createImmutableTransform(mxx, mxy, mxz, tx,
 109                         myx, myy, myz, ty, mzx, mzy, mzz, tz);
 110             }
 111 
 112             @Override
 113             public Transform createImmutableTransform(Transform transform,
 114                     double mxx, double mxy, double mxz, double tx,
 115                     double myx, double myy, double myz, double ty,
 116                     double mzx, double mzy, double mzz, double tz) {
 117                 return Transform.createImmutableTransform(transform,
 118                         mxx, mxy, mxz, tx, myx, myy, myz, ty, mzx, mzy, mzz, tz);
 119             }
 120 
 121             @Override
 122             public Transform createImmutableTransform(Transform transform,
 123                     Transform left, Transform right) {
 124                 return Transform.createImmutableTransform(transform, left, right);
 125             }
 126         });
 127     }
 128 
 129     /* *************************************************************************
 130      *                                                                         *
 131      *                            Factories                                    *
 132      *                                                                         *
 133      **************************************************************************/
 134 
 135     /**
 136      * Returns a new {@code Affine} object from 12 number
 137      * values representing the 6 specifiable entries of the 3x4
 138      * Affine transformation matrix.
 139      *
 140      * @param mxx the X coordinate scaling element of the 3x4 matrix
 141      * @param myx the Y coordinate shearing element of the 3x4 matrix
 142      * @param mxy the X coordinate shearing element of the 3x4 matrix
 143      * @param myy the Y coordinate scaling element of the 3x4 matrix
 144      * @param tx the X coordinate translation element of the 3x4 matrix
 145      * @param ty the Y coordinate translation element of the 3x4 matrix
 146      * @return a new {@code Affine} object derived from specified parameters
 147      */
 148     public static Affine affine(
 149         double mxx, double myx, double mxy, double myy, double tx, double ty) {
 150         final Affine affine = new Affine();
 151         affine.setMxx(mxx);
 152         affine.setMxy(mxy);
 153         affine.setTx(tx);
 154         affine.setMyx(myx);
 155         affine.setMyy(myy);
 156         affine.setTy(ty);
 157         return affine;
 158     }
 159 
 160 
 161     /**
 162      * Returns a new {@code Affine} object from 12 number
 163      * values representing the 12 specifiable entries of the 3x4
 164      * Affine transformation matrix.
 165      *
 166      * @param mxx the X coordinate scaling element of the 3x4 matrix
 167      * @param mxy the XY element of the 3x4 matrix
 168      * @param mxz the XZ element of the 3x4 matrix
 169      * @param tx the X coordinate translation element of the 3x4 matrix
 170      * @param myx the YX element of the 3x4 matrix
 171      * @param myy the Y coordinate scaling element of the 3x4 matrix
 172      * @param myz the YZ element of the 3x4 matrix
 173      * @param ty the Y coordinate translation element of the 3x4 matrix
 174      * @param mzx the ZX element of the 3x4 matrix
 175      * @param mzy the ZY element of the 3x4 matrix
 176      * @param mzz the Z coordinate scaling element of the 3x4 matrix
 177      * @param tz the Z coordinate translation element of the 3x4 matrix
 178      * @return a new {@code Affine} object derived from specified parameters
 179      */
 180     public static Affine affine(
 181         double mxx, double mxy, double mxz, double tx,
 182         double myx, double myy, double myz, double ty,
 183         double mzx, double mzy, double mzz, double tz) {
 184         final Affine affine = new Affine();
 185         affine.setMxx(mxx);
 186         affine.setMxy(mxy);
 187         affine.setMxz(mxz);
 188         affine.setTx(tx);
 189         affine.setMyx(myx);
 190         affine.setMyy(myy);
 191         affine.setMyz(myz);
 192         affine.setTy(ty);
 193         affine.setMzx(mzx);
 194         affine.setMzy(mzy);
 195         affine.setMzz(mzz);
 196         affine.setTz(tz);
 197         return affine;
 198     }
 199 
 200 
 201     /**
 202      * Returns a {@code Translate} object representing a translation transformation.
 203      * <p>
 204      * This is equivalent to:
 205      * <pre>
 206      *    new Translate(x, y);
 207      * </pre>
 208      */
 209     public static Translate translate(double x, double y) {
 210         final Translate translate = new Translate();
 211         translate.setX(x);
 212         translate.setY(y);
 213         return translate;
 214     }
 215 
 216 
 217     /**
 218      * Returns a {@code Rotate} object that rotates coordinates around a pivot
 219      * point.
 220      * <p>
 221      * This is equivalent to:
 222      * <pre>
 223      *    new Rotate(angle, pivotX, pivotY);
 224      * </pre>
 225      */
 226     public static Rotate rotate(double angle, double pivotX, double pivotY) {
 227         final Rotate rotate = new Rotate();
 228         rotate.setAngle(angle);
 229         rotate.setPivotX(pivotX);
 230         rotate.setPivotY(pivotY);
 231         return rotate;
 232     }
 233 
 234 
 235     /**
 236      * Returns a {@code Scale} object representing a scaling transformation.
 237      * <p>
 238      * This is equivalent to:
 239      * <pre>
 240      *    new Scale(x, y);
 241      * </pre>
 242      */
 243     public static Scale scale(double x, double y) {
 244         final Scale scale = new Scale();
 245         scale.setX(x);
 246         scale.setY(y);
 247         return scale;
 248     }
 249 
 250 
 251     /**
 252      * Returns a {@code Scale} object representing a scaling transformation.
 253      * The returned scale operation will be about the given pivot point.
 254      * <p>
 255      * This is equivalent to:
 256      * <pre>
 257      *    new Scale(x, y, pivotX, pivotY);
 258      * </pre>
 259      */
 260     public static Scale scale(double x, double y, double pivotX, double pivotY) {
 261         final Scale scale = new Scale();
 262         scale.setX(x);
 263         scale.setY(y);
 264         scale.setPivotX(pivotX);
 265         scale.setPivotY(pivotY);
 266         return scale;
 267     }
 268 
 269 
 270     /**
 271      * Returns a {@code Shear} object representing a shearing transformation.
 272      * <p>
 273      * This is equivalent to:
 274      * <pre>
 275      *    new Shear(x, y);
 276      * </pre>
 277      */
 278     public static Shear shear(double x, double y) {
 279         final Shear shear = new Shear();
 280         shear.setX(x);
 281         shear.setY(y);
 282         return shear;
 283     }
 284 
 285     /**
 286      * Returns a {@code Shear} object representing a shearing transformation.
 287      * <p>
 288      * This is equivalent to:
 289      * <pre>
 290      *    new Shear(x, y, pivotX, pivotY);
 291      * </pre>
 292      */
 293     public static Shear shear(double x, double y, double pivotX, double pivotY) {
 294         final Shear shear = new Shear();
 295         shear.setX(x);
 296         shear.setY(y);
 297         shear.setPivotX(pivotX);
 298         shear.setPivotY(pivotY);
 299         return shear;
 300     }
 301 
 302     /**
 303      * For transforms with expensive inversion we cache the inverted matrix
 304      * once it is needed and computed for some operation.
 305      */
 306     private SoftReference<Transform> inverseCache = null;
 307 
 308     private WeakReferenceQueue nodes = new WeakReferenceQueue();
 309 
 310     /* *************************************************************************
 311      *                                                                         *
 312      *                         Element getters                                 *
 313      *                                                                         *
 314      **************************************************************************/
 315 
 316     /**
 317      * Gets the X coordinate scaling element of the 3x4 matrix.
 318      *
 319      * @since JavaFX 2.2
 320      */
 321     public  double getMxx() {
 322         return 1.0;
 323     }
 324 
 325     /**
 326      * Gets the XY coordinate element of the 3x4 matrix.
 327      *
 328      * @since JavaFX 2.2
 329      */
 330     public  double getMxy() {
 331         return 0.0;
 332     }
 333 
 334     /**
 335      * Gets the XZ coordinate element of the 3x4 matrix.
 336      *
 337      * @since JavaFX 2.2
 338      */
 339     public  double getMxz() {
 340         return 0.0;
 341     }
 342 
 343     /**
 344      * Gets the X coordinate translation element of the 3x4 matrix.
 345      *
 346      * @since JavaFX 2.2
 347      */
 348     public  double getTx() {
 349         return 0.0;
 350     }
 351 
 352     /**
 353      * Gets the YX coordinate element of the 3x4 matrix.
 354      *
 355      * @since JavaFX 2.2
 356      */
 357     public  double getMyx() {
 358         return 0.0;
 359     }
 360 
 361     /**
 362      * Gets the Y coordinate scaling element of the 3x4 matrix.
 363      *
 364      * @since JavaFX 2.2
 365      */
 366     public  double getMyy() {
 367         return 1.0;
 368     }
 369 
 370     /**
 371      * Gets the YZ coordinate element of the 3x4 matrix.
 372      *
 373      * @since JavaFX 2.2
 374      */
 375     public  double getMyz() {
 376         return 0.0;
 377     }
 378 
 379     /**
 380      * Gets the Y coordinate translation element of the 3x4 matrix.
 381      *
 382      * @since JavaFX 2.2
 383      */
 384     public  double getTy() {
 385         return 0.0;
 386     }
 387 
 388     /**
 389      * Gets the ZX coordinate element of the 3x4 matrix.
 390      *
 391      * @since JavaFX 2.2
 392      */
 393     public  double getMzx() {
 394         return 0.0;
 395     }
 396 
 397     /**
 398      * Gets the ZY coordinate element of the 3x4 matrix.
 399      *
 400      * @since JavaFX 2.2
 401      */
 402     public  double getMzy() {
 403         return 0.0;
 404     }
 405 
 406     /**
 407      * Gets the Z coordinate scaling element of the 3x4 matrix.
 408      *
 409      * @since JavaFX 2.2
 410      */
 411     public  double getMzz() {
 412         return 1.0;
 413     }
 414 
 415     /**
 416      * Gets the Z coordinate translation element of the 3x4 matrix.
 417      *
 418      * @since JavaFX 2.2
 419      */
 420     public  double getTz() {
 421         return 0.0;
 422     }
 423 
 424     /**
 425      * Gets the specified element of the transformation matrix.
 426      * @param type type of matrix to get the value from
 427      * @param row zero-based row number
 428      * @param column zero-based column number
 429      * @return value of the specified transformation matrix element
 430      * @throws IllegalArgumentException if a 2D matrix type is requested for
 431      *         a 3D transform
 432      * @throws IndexOutOfBoundsException if the indices are not within
 433      *         the specified matrix type
 434      * @throws NullPointerException if the specified {@code type} is null
 435      * @since JavaFX 8.0
 436      */
 437     public double getElement(MatrixType type, int row, int column) {
 438         if (row < 0 || row >= type.rows() || column < 0 || column >= type.columns()) {
 439             throw new IndexOutOfBoundsException("Index outside of affine "
 440                     + "matrix " + type + ": [" + row + ", " + column + "]");
 441         }
 442         switch(type) {
 443             case MT_2D_2x3:
 444                 // fall-through
 445             case MT_2D_3x3:
 446                 if (!isType2D()) {
 447                     throw new IllegalArgumentException("Cannot access 2D matrix "
 448                             + "of a 3D transform");
 449                 }
 450                 switch(row) {
 451                     case 0:
 452                         switch(column) {
 453                             case 0: return getMxx();
 454                             case 1: return getMxy();
 455                             case 2: return getTx();
 456                         }
 457                     case 1:
 458                         switch(column) {
 459                             case 0: return getMyx();
 460                             case 1: return getMyy();
 461                             case 2: return getTy();
 462                         }
 463                     case 2:
 464                         switch(column) {
 465                             case 0: return 0.0;
 466                             case 1: return 0.0;
 467                             case 2: return 1.0;
 468                         }
 469                 }
 470                 break;
 471             case MT_3D_3x4:
 472                 // fall-through
 473             case MT_3D_4x4:
 474                 switch(row) {
 475                     case 0:
 476                         switch(column) {
 477                             case 0: return getMxx();
 478                             case 1: return getMxy();
 479                             case 2: return getMxz();
 480                             case 3: return getTx();
 481                         }
 482                     case 1:
 483                         switch(column) {
 484                             case 0: return getMyx();
 485                             case 1: return getMyy();
 486                             case 2: return getMyz();
 487                             case 3: return getTy();
 488                         }
 489                     case 2:
 490                         switch(column) {
 491                             case 0: return getMzx();
 492                             case 1: return getMzy();
 493                             case 2: return getMzz();
 494                             case 3: return getTz();
 495                         }
 496                     case 3:
 497                         switch(column) {
 498                             case 0: return 0.0;
 499                             case 1: return 0.0;
 500                             case 2: return 0.0;
 501                             case 3: return 1.0;
 502                         }
 503                 }
 504                 break;
 505         }
 506         // cannot reach here
 507         throw new InternalError("Unsupported matrix type " + type);
 508     }
 509 
 510     /* *************************************************************************
 511      *                                                                         *
 512      *                           State getters                                 *
 513      *                                                                         *
 514      **************************************************************************/
 515 
 516     /**
 517      * Computes if this transform is currently a 2D transform (has no effect
 518      * in the direction of Z axis).
 519      * Used by the subclasses to effectively provide value of the type2D
 520      * property.
 521      * @return true if this transform is currently 2D-only
 522      */
 523     boolean computeIs2D() {
 524         return getMxz() == 0.0 && getMzx() == 0.0 && getMzy() == 0.0 &&
 525                     getMzz() == 1.0 && getTz() == 0.0;
 526     }
 527 
 528     /**
 529      * Computes if this transform is currently an identity (has
 530      * no effect in any direction).
 531      * Used by the subclasses to effectively provide value of the identity
 532      * property.
 533      * @return true if this transform is currently an identity transform
 534      */
 535     boolean computeIsIdentity() {
 536         return
 537             getMxx() == 1.0 && getMxy() == 0.0 && getMxz() == 0.0 && getTx() == 0.0 &&
 538             getMyx() == 0.0 && getMyy() == 1.0 && getMyz() == 0.0 && getTy() == 0.0 &&
 539             getMzx() == 0.0 && getMzy() == 0.0 && getMzz() == 1.0 && getTz() == 0.0;
 540     }
 541 
 542     /**
 543      * Computes determinant of the transformation matrix.
 544      * Among other things, determinant can be used for testing this transform's
 545      * invertibility - it is invertible if determinant is not equal to zero.
 546      * @return Determinant of the transformation matrix
 547      * @since JavaFX 8.0
 548      */
 549     public double determinant() {
 550         final double myx = getMyx();
 551         final double myy = getMyy();
 552         final double myz = getMyz();
 553         final double mzx = getMzx();
 554         final double mzy = getMzy();
 555         final double mzz = getMzz();
 556 
 557         return (getMxx() * (myy * mzz - mzy * myz) +
 558                 getMxy() * (myz * mzx - mzz * myx) +
 559                 getMxz() * (myx * mzy - mzx * myy));
 560     }
 561 
 562     /**
 563      * Determines if this is currently a 2D transform.
 564      * Transform is 2D if it has no effect along the Z axis.
 565      * @since JavaFX 8.0
 566      */
 567     private LazyBooleanProperty type2D;
 568 
 569     public final boolean isType2D() {
 570         return type2D == null ? computeIs2D() : type2D.get();
 571     }
 572 
 573     public final ReadOnlyBooleanProperty type2DProperty() {
 574         if (type2D == null) {
 575             type2D = new LazyBooleanProperty() {
 576 
 577                 @Override
 578                 protected boolean computeValue() {
 579                     return computeIs2D();
 580                 }
 581 
 582                 @Override
 583                 public Object getBean() {
 584                     return Transform.this;
 585                 }
 586 
 587                 @Override
 588                 public String getName() {
 589                     return "type2D";
 590                 }
 591             };
 592         }
 593         return type2D;
 594     }
 595 
 596     /**
 597      * Determines if this is currently an identity transform.
 598      * Identity transform has no effect on the transformed nodes.
 599      * @since JavaFX 8.0
 600      */
 601     private LazyBooleanProperty identity;
 602 
 603     public final boolean isIdentity() {
 604         return identity == null ? computeIsIdentity() : identity.get();
 605     }
 606 
 607     public final ReadOnlyBooleanProperty identityProperty() {
 608         if (identity == null) {
 609             identity = new LazyBooleanProperty() {
 610 
 611                 @Override
 612                 protected boolean computeValue() {
 613                     return computeIsIdentity();
 614                 }
 615 
 616                 @Override
 617                 public Object getBean() {
 618                     return Transform.this;
 619                 }
 620 
 621                 @Override
 622                 public String getName() {
 623                     return "identity";
 624                 }
 625             };
 626         }
 627         return identity;
 628     }
 629 
 630     /**
 631      * Lazily computed read-only boolean property implementation.
 632      * Used for type2D and identity properties.
 633      */
 634     private static abstract class LazyBooleanProperty
 635             extends ReadOnlyBooleanProperty {
 636 
 637         private ExpressionHelper<Boolean> helper;
 638         private boolean valid;
 639         private boolean value;
 640 
 641         @Override
 642         public void addListener(InvalidationListener listener) {
 643             helper = ExpressionHelper.addListener(helper, this, listener);
 644         }
 645 
 646         @Override
 647         public void removeListener(InvalidationListener listener) {
 648             helper = ExpressionHelper.removeListener(helper, listener);
 649         }
 650 
 651         @Override
 652         public void addListener(ChangeListener<? super Boolean> listener) {
 653             helper = ExpressionHelper.addListener(helper, this, listener);
 654         }
 655 
 656         @Override
 657         public void removeListener(ChangeListener<? super Boolean> listener) {
 658             helper = ExpressionHelper.removeListener(helper, listener);
 659         }
 660 
 661         @Override
 662         public boolean get() {
 663             if (!valid) {
 664                 value = computeValue();
 665                 valid = true;
 666             }
 667 
 668             return value;
 669         }
 670 
 671         public void invalidate() {
 672             if (valid) {
 673                 valid = false;
 674                 ExpressionHelper.fireValueChangedEvent(helper);
 675             }
 676         }
 677 
 678         protected abstract boolean computeValue();
 679     }
 680 
 681     /**
 682      * Transforms the specified point by this transform and by the specified
 683      * transform and returns distance of the result points. Used for similarTo
 684      * method. Has to be used only for 2D transforms (otherwise throws an
 685      * exception).
 686      * @param t the other transform
 687      * @param x point's X coordinate
 688      * @param y point's Y coordinate
 689      * @return distance of the transformed points
 690      */
 691     private double transformDiff(Transform t, double x, double y) {
 692         final Point2D byThis = transform(x, y);
 693         final Point2D byOther = t.transform(x, y);
 694         return byThis.distance(byOther);
 695     }
 696 
 697     /**
 698      * Transforms the specified point by this transform and by the specified
 699      * transform and returns distance of the result points. Used for similarTo
 700      * method.
 701      * @param t the other transform
 702      * @param x point's X coordinate
 703      * @param y point's Y coordinate
 704      * @param z point's Z coordinate
 705      * @return distance of the transformed points
 706      */
 707     private double transformDiff(Transform t, double x, double y, double z) {
 708         final Point3D byThis = transform(x, y, z);
 709         final Point3D byOther = t.transform(x, y, z);
 710         return byThis.distance(byOther);
 711     }
 712 
 713     /**
 714      * Checks if this transform is similar to the specified transform.
 715      * The two transforms are considered similar if any point from
 716      * {@code range} is transformed by them to points that are no farther
 717      * than {@code maxDelta} from each other.
 718      * @param transform transform to be compared to this transform
 719      * @param range region of interest on which the two transforms are compared
 720      * @param maxDelta maximum allowed distance for the results of transforming
 721      *                 any single point from {@code range} by the two transforms
 722      * @return true if the transforms are similar according to the specified
 723      *              criteria
 724      * @throws NullPointerException if the specified {@code transform}
 725      *         or {@code range} is null
 726      * @since JavaFX 8.0
 727      */
 728     public boolean similarTo(Transform transform, Bounds range, double maxDelta) {
 729 
 730         double cornerX, cornerY, cornerZ;
 731 
 732         if (isType2D() && transform.isType2D()) {
 733             cornerX = range.getMinX();
 734             cornerY = range.getMinY();
 735 
 736             if (transformDiff(transform, cornerX, cornerY) > maxDelta) {
 737                 return false;
 738             }
 739 
 740             cornerY = range.getMaxY();
 741             if (transformDiff(transform, cornerX, cornerY) > maxDelta) {
 742                 return false;
 743             }
 744 
 745             cornerX = range.getMaxX();
 746             cornerY = range.getMinY();
 747             if (transformDiff(transform, cornerX, cornerY) > maxDelta) {
 748                 return false;
 749             }
 750 
 751             cornerY = range.getMaxY();
 752             if (transformDiff(transform, cornerX, cornerY) > maxDelta) {
 753                 return false;
 754             }
 755 
 756             return true;
 757         }
 758 
 759         cornerX = range.getMinX();
 760         cornerY = range.getMinY();
 761         cornerZ = range.getMinZ();
 762         if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 763             return false;
 764         }
 765 
 766         cornerY = range.getMaxY();
 767         if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 768             return false;
 769         }
 770 
 771         cornerX = range.getMaxX();
 772         cornerY = range.getMinY();
 773         if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 774             return false;
 775         }
 776 
 777         cornerY = range.getMaxY();
 778         if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 779             return false;
 780         }
 781 
 782         if (range.getDepth() != 0.0) {
 783             cornerX = range.getMinX();
 784             cornerY = range.getMinY();
 785             cornerZ = range.getMaxZ();
 786             if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 787                 return false;
 788             }
 789 
 790             cornerY = range.getMaxY();
 791             if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 792                 return false;
 793             }
 794 
 795             cornerX = range.getMaxX();
 796             cornerY = range.getMinY();
 797             if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 798                 return false;
 799             }
 800 
 801             cornerY = range.getMaxY();
 802             if (transformDiff(transform, cornerX, cornerY, cornerZ) > maxDelta) {
 803                 return false;
 804             }
 805         }
 806 
 807         return true;
 808     }
 809 
 810     /* *************************************************************************
 811      *                                                                         *
 812      *                           Array getters                                 *
 813      *                                                                         *
 814      **************************************************************************/
 815 
 816     /**
 817      * Core of the toArray implementation for the 2D case.
 818      * All of the checks has been made by the enclosing method as well as
 819      * the constant elements filled, this method only fills the varying
 820      * elements to the array. Used by subclasses to fill
 821      * the elements efficiently.
 822      * @param array array to be filled with the 6 2D elements
 823      */
 824     void fill2DArray(double[] array) {
 825         array[0] = getMxx();
 826         array[1] = getMxy();
 827         array[2] = getTx();
 828         array[3] = getMyx();
 829         array[4] = getMyy();
 830         array[5] = getTy();
 831     }
 832 
 833     /**
 834      * Core of the toArray implementation for the 3D case.
 835      * All of the checks has been made by the enclosing method as well as
 836      * the constant elements filled, this method only fills the varying
 837      * elements to the array. Used by subclasses to fill
 838      * the elements efficiently.
 839      * @param array array to be filled with the 12 3D elements
 840      */
 841     void fill3DArray(double[] array) {
 842         array[0] = getMxx();
 843         array[1] = getMxy();
 844         array[2] = getMxz();
 845         array[3] = getTx();
 846         array[4] = getMyx();
 847         array[5] = getMyy();
 848         array[6] = getMyz();
 849         array[7] = getTy();
 850         array[8] = getMzx();
 851         array[9] = getMzy();
 852         array[10] = getMzz();
 853         array[11] = getTz();
 854     }
 855 
 856     /**
 857      * Returns an array containing the flattened transformation matrix.
 858      * If the requested matrix type fits in the specified array, it is returned
 859      * therein. Otherwise, a new array is created.
 860      * @param type matrix type to be filled in the array
 861      * @param array array into which the elements of the matrix are to be
 862      *              stored, if it is non-null and big enough; otherwise,
 863      *              a new array is created for this purpose.
 864      * @return an array containing the elements of the requested matrix type
 865      *         representing this transform
 866      * @throws IllegalArgumentException if a 2D matrix type is requested for
 867      *         a 3D transform
 868      * @throws NullPointerException if the specified {@code type} is null
 869      * @since JavaFX 8.0
 870      */
 871     public double[] toArray(MatrixType type, double[] array) {
 872         checkRequestedMAT(type);
 873 
 874         if (array == null || array.length < type.elements()) {
 875             array = new double[type.elements()];
 876         }
 877 
 878         switch (type) {
 879             case MT_2D_3x3:
 880                 array[6] = 0.0;
 881                 array[7] = 0.0;
 882                 array[8] = 1.0;
 883                 // fall-through
 884             case MT_2D_2x3:
 885                 fill2DArray(array);
 886                 break;
 887             case MT_3D_4x4:
 888                 array[12] = 0.0;
 889                 array[13] = 0.0;
 890                 array[14] = 0.0;
 891                 array[15] = 1.0;
 892                 // fall-through
 893             case MT_3D_3x4:
 894                 fill3DArray(array);
 895                 break;
 896             default:
 897                 throw new InternalError("Unsupported matrix type " + type);
 898         }
 899 
 900         return array;
 901     }
 902 
 903     /**
 904      * Returns an array containing the flattened transformation matrix.
 905      * @param type matrix type to be filled in the array
 906      * @return an array containing the elements of the requested matrix type
 907      *         representing this transform
 908      * @throws IllegalArgumentException if a 2D matrix type is requested for
 909      *         a 3D transform
 910      * @throws NullPointerException if the specified {@code type} is null
 911      * @since JavaFX 8.0
 912      */
 913     public double[] toArray(MatrixType type) {
 914         return toArray(type, null);
 915     }
 916 
 917     /**
 918      * Returns an array containing a row of the transformation matrix.
 919      * If the row of the requested matrix type fits in the specified array,
 920      * it is returned therein. Otherwise, a new array is created.
 921      * @param type matrix type whose row is to be filled in the array
 922      * @param row zero-based index of the row
 923      * @param array array into which the elements of the row are to be
 924      *              stored, if it is non-null and big enough; otherwise,
 925      *              a new array is created for this purpose.
 926      * @return an array containing the requested row of the requested matrix
 927      *         type representing this transform
 928      * @throws IllegalArgumentException if a 2D matrix type is requested for
 929      *         a 3D transform
 930      * @throws IndexOutOfBoundsException if the {@code row} index is not within
 931      *         the number of rows of the specified matrix type
 932      * @throws NullPointerException if the specified {@code type} is null
 933      * @since JavaFX 8.0
 934      */
 935     public double[] row(MatrixType type, int row, double[] array) {
 936 
 937         checkRequestedMAT(type);
 938 
 939         if (row < 0 || row >= type.rows()) {
 940             throw new IndexOutOfBoundsException(
 941                     "Cannot get row " + row + " from " + type);
 942         }
 943 
 944         if (array == null || array.length < type.columns()) {
 945             array = new double[type.columns()];
 946         }
 947 
 948         switch(type) {
 949             case MT_2D_2x3:
 950             case MT_2D_3x3:
 951                 switch (row) {
 952                     case 0:
 953                         array[0] = getMxx();
 954                         array[1] = getMxy();
 955                         array[2] = getTx();
 956                         break;
 957                     case 1:
 958                         array[0] = getMyx();
 959                         array[1] = getMyy();
 960                         array[2] = getTy();
 961                         break;
 962                     case 2:
 963                         array[0] = 0.0;
 964                         array[1] = 0.0;
 965                         array[2] = 1.0;
 966                         break;
 967                 }
 968                 break;
 969             case MT_3D_3x4:
 970             case MT_3D_4x4:
 971                 switch (row) {
 972                     case 0:
 973                         array[0] = getMxx();
 974                         array[1] = getMxy();
 975                         array[2] = getMxz();
 976                         array[3] = getTx();
 977                         break;
 978                     case 1:
 979                         array[0] = getMyx();
 980                         array[1] = getMyy();
 981                         array[2] = getMyz();
 982                         array[3] = getTy();
 983                         break;
 984                     case 2:
 985                         array[0] = getMzx();
 986                         array[1] = getMzy();
 987                         array[2] = getMzz();
 988                         array[3] = getTz();
 989                         break;
 990                     case 3:
 991                         array[0] = 0.0;
 992                         array[1] = 0.0;
 993                         array[2] = 0.0;
 994                         array[3] = 1.0;
 995                         break;
 996                 }
 997                 break;
 998             default:
 999                 throw new InternalError("Unsupported row " + row + " of " + type);
1000         }
1001         return array;
1002     }
1003 
1004     /**
1005      * Returns an array containing a row of the transformation matrix.
1006      * @param type matrix type whose row is to be filled in the array
1007      * @param row zero-based index of the row
1008      * @return an array containing the requested row of the requested matrix
1009      *         type representing this transform
1010      * @throws IllegalArgumentException if a 2D matrix type is requested for
1011      *         a 3D transform
1012      * @throws IndexOutOfBoundsException if the {@code row} index is not within
1013      *         the number of rows of the specified matrix type
1014      * @throws NullPointerException if the specified {@code type} is null
1015      * @since JavaFX 8.0
1016      */
1017     public double[] row(MatrixType type, int row) {
1018         return row(type, row, null);
1019     }
1020 
1021     /**
1022      * Returns an array containing a column of the transformation matrix.
1023      * If the column of the requested matrix type fits in the specified array,
1024      * it is returned therein. Otherwise, a new array is created.
1025      * @param type matrix type whose column is to be filled in the array
1026      * @param column zero-based index of the column
1027      * @param array array into which the elements of the column are to be
1028      *              stored, if it is non-null and big enough; otherwise,
1029      *              a new array is created for this purpose.
1030      * @return an array containing the requested column of the requested matrix
1031      *         type representing this transform
1032      * @throws IllegalArgumentException if a 2D matrix type is requested for
1033      *         a 3D transform
1034      * @throws IndexOutOfBoundsException if the {@code column} index
1035      *         is not within the number of columns of the specified matrix type
1036      * @throws NullPointerException if the specified {@code type} is null
1037      * @since JavaFX 8.0
1038      */
1039     public double[] column(MatrixType type, int column, double[] array) {
1040 
1041         checkRequestedMAT(type);
1042 
1043         if (column < 0 || column >= type.columns()) {
1044             throw new IndexOutOfBoundsException(
1045                     "Cannot get row " + column + " from " + type);
1046         }
1047 
1048         if (array == null || array.length < type.rows()) {
1049             array = new double[type.rows()];
1050         }
1051 
1052         switch(type) {
1053             case MT_2D_2x3:
1054                 switch (column) {
1055                     case 0:
1056                         array[0] = getMxx();
1057                         array[1] = getMyx();
1058                         break;
1059                     case 1:
1060                         array[0] = getMxy();
1061                         array[1] = getMyy();
1062                         break;
1063                     case 2:
1064                         array[0] = getTx();
1065                         array[1] = getTy();
1066                         break;
1067                 }
1068                 break;
1069             case MT_2D_3x3:
1070                 switch (column) {
1071                     case 0:
1072                         array[0] = getMxx();
1073                         array[1] = getMyx();
1074                         array[2] = 0.0;
1075                         break;
1076                     case 1:
1077                         array[0] = getMxy();
1078                         array[1] = getMyy();
1079                         array[2] = 0.0;
1080                         break;
1081                     case 2:
1082                         array[0] = getTx();
1083                         array[1] = getTy();
1084                         array[2] = 1.0;
1085                         break;
1086                 }
1087                 break;
1088             case MT_3D_3x4:
1089                 switch (column) {
1090                     case 0:
1091                         array[0] = getMxx();
1092                         array[1] = getMyx();
1093                         array[2] = getMzx();
1094                         break;
1095                     case 1:
1096                         array[0] = getMxy();
1097                         array[1] = getMyy();
1098                         array[2] = getMzy();
1099                         break;
1100                     case 2:
1101                         array[0] = getMxz();
1102                         array[1] = getMyz();
1103                         array[2] = getMzz();
1104                         break;
1105                     case 3:
1106                         array[0] = getTx();
1107                         array[1] = getTy();
1108                         array[2] = getTz();
1109                         break;
1110                 }
1111                 break;
1112             case MT_3D_4x4:
1113                 switch (column) {
1114                     case 0:
1115                         array[0] = getMxx();
1116                         array[1] = getMyx();
1117                         array[2] = getMzx();
1118                         array[3] = 0.0;
1119                         break;
1120                     case 1:
1121                         array[0] = getMxy();
1122                         array[1] = getMyy();
1123                         array[2] = getMzy();
1124                         array[3] = 0.0;
1125                         break;
1126                     case 2:
1127                         array[0] = getMxz();
1128                         array[1] = getMyz();
1129                         array[2] = getMzz();
1130                         array[3] = 0.0;
1131                         break;
1132                     case 3:
1133                         array[0] = getTx();
1134                         array[1] = getTy();
1135                         array[2] = getTz();
1136                         array[3] = 1.0;
1137                         break;
1138                 }
1139                 break;
1140             default:
1141                 throw new InternalError("Unsupported column " + column + " of "
1142                         + type);
1143         }
1144         return array;
1145     }
1146 
1147     /**
1148      * Returns an array containing a column of the transformation matrix.
1149      * @param type matrix type whose column is to be filled in the array
1150      * @param column zero-based index of the column
1151      * @return an array containing the requested column of the requested matrix
1152      *         type representing this transform
1153      * @throws IllegalArgumentException if a 2D matrix type is requested for
1154      *         a 3D transform
1155      * @throws IndexOutOfBoundsException if the {@code column} index
1156      *         is not within the number of columns of the specified matrix type
1157      * @throws NullPointerException if the specified {@code type} is null
1158      * @since JavaFX 8.0
1159      */
1160     public double[] column(MatrixType type, int column) {
1161         return column(type, column, null);
1162     }
1163 
1164     /* *************************************************************************
1165      *                                                                         *
1166      *                         Transform creators                              *
1167      *                                                                         *
1168      **************************************************************************/
1169 
1170     /**
1171      * Returns the concatenation of this transform and the specified transform.
1172      * Applying the resulting transform to a node has the same effect as
1173      * adding the two transforms to its {@code getTransforms()} list,
1174      * {@code this} transform first and the specified {@code transform} second.
1175      * @param transform transform to be concatenated with this transform
1176      * @return The concatenated transform
1177      * @throws NullPointerException if the specified {@code transform} is null
1178      * @since JavaFX 8.0
1179      */
1180     public Transform createConcatenation(Transform transform) {
1181         final double txx = transform.getMxx();
1182         final double txy = transform.getMxy();
1183         final double txz = transform.getMxz();
1184         final double ttx = transform.getTx();
1185         final double tyx = transform.getMyx();
1186         final double tyy = transform.getMyy();
1187         final double tyz = transform.getMyz();
1188         final double tty = transform.getTy();
1189         final double tzx = transform.getMzx();
1190         final double tzy = transform.getMzy();
1191         final double tzz = transform.getMzz();
1192         final double ttz = transform.getTz();
1193         return new Affine(
1194             (getMxx() * txx + getMxy() * tyx + getMxz() * tzx),
1195             (getMxx() * txy + getMxy() * tyy + getMxz() * tzy),
1196             (getMxx() * txz + getMxy() * tyz + getMxz() * tzz),
1197             (getMxx() * ttx + getMxy() * tty + getMxz() * ttz + getTx()),
1198             (getMyx() * txx + getMyy() * tyx + getMyz() * tzx),
1199             (getMyx() * txy + getMyy() * tyy + getMyz() * tzy),
1200             (getMyx() * txz + getMyy() * tyz + getMyz() * tzz),
1201             (getMyx() * ttx + getMyy() * tty + getMyz() * ttz + getTy()),
1202             (getMzx() * txx + getMzy() * tyx + getMzz() * tzx),
1203             (getMzx() * txy + getMzy() * tyy + getMzz() * tzy),
1204             (getMzx() * txz + getMzy() * tyz + getMzz() * tzz),
1205             (getMzx() * ttx + getMzy() * tty + getMzz() * ttz + getTz()));
1206     }
1207 
1208     /**
1209      * Returns the inverse transform of this transform.
1210      * @return the inverse transform
1211      * @throws NonInvertibleTransformException if this transform
1212      *         cannot be inverted
1213      * @since JavaFX 8.0
1214      */
1215     public Transform createInverse() throws NonInvertibleTransformException {
1216         return getInverseCache().clone();
1217     }
1218 
1219     /**
1220      * Returns a deep copy of this transform.
1221      * @return a copy of this transform
1222      * @since JavaFX 8.0
1223      */
1224     @Override
1225     public Transform clone() {
1226         return TransformUtils.immutableTransform(this);
1227     }
1228 
1229     /* *************************************************************************
1230      *                                                                         *
1231      *                     Transform, Inverse Transform                        *
1232      *                                                                         *
1233      **************************************************************************/
1234 
1235     /**
1236      * Transforms the specified point by this transform.
1237      * This method can be used only for 2D transforms.
1238      * @param x the X coordinate of the point
1239      * @param y the Y coordinate of the point
1240      * @return the transformed point
1241      * @throws IllegalStateException if this is a 3D transform
1242      * @since JavaFX 8.0
1243      */
1244     public Point2D transform(double x, double y) {
1245         ensureCanTransform2DPoint();
1246 
1247         return new Point2D(
1248             getMxx() * x + getMxy() * y + getTx(),
1249             getMyx() * x + getMyy() * y + getTy());
1250     }
1251 
1252     /**
1253      * Transforms the specified point by this transform.
1254      * This method can be used only for 2D transforms.
1255      * @param point the point to be transformed
1256      * @return the transformed point
1257      * @throws IllegalStateException if this is a 3D transform
1258      * @throws NullPointerException if the specified {@code point} is null
1259      * @since JavaFX 8.0
1260      */
1261     public Point2D transform(Point2D point) {
1262         return transform(point.getX(), point.getY());
1263     }
1264 
1265     /**
1266      * Transforms the specified point by this transform.
1267      * @param x the X coordinate of the point
1268      * @param y the Y coordinate of the point
1269      * @param z the Z coordinate of the point
1270      * @return the transformed point
1271      * @since JavaFX 8.0
1272      */
1273     public Point3D transform(double x, double y, double z) {
1274         return new Point3D(
1275             getMxx() * x + getMxy() * y + getMxz() * z + getTx(),
1276             getMyx() * x + getMyy() * y + getMyz() * z + getTy(),
1277             getMzx() * x + getMzy() * y + getMzz() * z + getTz());
1278     }
1279 
1280     /**
1281      * Transforms the specified point by this transform.
1282      * @param point the point to be transformed
1283      * @return the transformed point
1284      * @throws NullPointerException if the specified {@code point} is null
1285      * @since JavaFX 8.0
1286      */
1287     public Point3D transform(Point3D point) {
1288         return transform(point.getX(), point.getY(), point.getZ());
1289     }
1290 
1291     /**
1292      * Transforms the specified bounds by this transform.
1293      * @param bounds the bounds to be transformed
1294      * @return the transformed bounds
1295      * @since JavaFX 8.0
1296      */
1297     public Bounds transform(Bounds bounds) {
1298         if (isType2D() && (bounds.getMinZ() == 0) && (bounds.getMaxZ() == 0)) {
1299             Point2D p1 = transform(bounds.getMinX(), bounds.getMinY());
1300             Point2D p2 = transform(bounds.getMaxX(), bounds.getMinY());
1301             Point2D p3 = transform(bounds.getMaxX(), bounds.getMaxY());
1302             Point2D p4 = transform(bounds.getMinX(), bounds.getMaxY());
1303 
1304             return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
1305         }
1306         Point3D p1 = transform(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ());
1307         Point3D p2 = transform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ());
1308         Point3D p3 = transform(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ());
1309         Point3D p4 = transform(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ());
1310         Point3D p5 = transform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ());
1311         Point3D p6 = transform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ());
1312         Point3D p7 = transform(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ());
1313         Point3D p8 = transform(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ());
1314 
1315         return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
1316     }
1317 
1318     /**
1319      * Core of the transform2DPoints method.
1320      * All the checks has been performed and the care of the overlaps has been
1321      * taken by the enclosing method, this method only transforms the points
1322      * and fills them to the array. Used by the subclasses to perform
1323      * the transform efficiently.
1324      */
1325     void transform2DPointsImpl(double[] srcPts, int srcOff,
1326             double[] dstPts, int dstOff, int numPts) {
1327         final double xx = getMxx();
1328         final double xy = getMxy();
1329         final double tx = getTx();
1330         final double yx = getMyx();
1331         final double yy = getMyy();
1332         final double ty = getTy();
1333 
1334         while (--numPts >= 0) {
1335             final double x = srcPts[srcOff++];
1336             final double y = srcPts[srcOff++];
1337 
1338             dstPts[dstOff++] = xx * x + xy * y + tx;
1339             dstPts[dstOff++] = yx * x + yy * y + ty;
1340         }
1341     }
1342 
1343     /**
1344      * Core of the transform3DPoints method.
1345      * All the checks has been performed and the care of the overlaps has been
1346      * taken by the enclosing method, this method only transforms the points
1347      * and fills them to the array. Used by the subclasses to perform
1348      * the transform efficiently.
1349      */
1350     void transform3DPointsImpl(double[] srcPts, int srcOff,
1351             double[] dstPts, int dstOff, int numPts) {
1352 
1353         final double xx = getMxx();
1354         final double xy = getMxy();
1355         final double xz = getMxz();
1356         final double tx = getTx();
1357         final double yx = getMyx();
1358         final double yy = getMyy();
1359         final double yz = getMyz();
1360         final double ty = getTy();
1361         final double zx = getMzx();
1362         final double zy = getMzy();
1363         final double zz = getMzz();
1364         final double tz = getTz();
1365 
1366         while (--numPts >= 0) {
1367             final double x = srcPts[srcOff++];
1368             final double y = srcPts[srcOff++];
1369             final double z = srcPts[srcOff++];
1370 
1371             dstPts[dstOff++] = xx * x + xy * y + xz * z + tx;
1372             dstPts[dstOff++] = yx * x + yy * y + yz * z + ty;
1373             dstPts[dstOff++] = zx * x + zy * y + zz * z + tz;
1374         }
1375     }
1376 
1377     /**
1378      * Transforms an array of coordinates by this transform.
1379      * The two coordinate array sections can be exactly the same or
1380      * can be overlapping sections of the same array without affecting the
1381      * validity of the results.
1382      * This method ensures that no source coordinates are overwritten by a
1383      * previous operation before they can be transformed.
1384      * The coordinates are stored in the arrays starting at the specified
1385      * offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>.
1386      * This method can be used only for 2D transforms.
1387      * @param srcPts the array containing the source point coordinates.
1388      * Each point is stored as a pair of x,&nbsp;y coordinates.
1389      * @param srcOff the offset to the first point to be transformed
1390      * in the source array
1391      * @param dstPts the array into which the transformed point coordinates
1392      * are returned.  Each point is stored as a pair of x,&nbsp;y
1393      * coordinates.
1394      * @param dstOff the offset to the location of the first
1395      * transformed point that is stored in the destination array
1396      * @param numPts the number of points to be transformed
1397      * @throws IllegalStateException if this is a 3D transform
1398      * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
1399      * @since JavaFX 8.0
1400      */
1401     public void transform2DPoints(double[] srcPts, int srcOff,
1402                           double[] dstPts, int dstOff,
1403                           int numPts) {
1404 
1405         if (srcPts == null || dstPts == null) {
1406             throw new NullPointerException();
1407         }
1408 
1409         if (!isType2D()) {
1410             throw new IllegalStateException("Cannot transform 2D points "
1411                     + "with a 3D transform");
1412         }
1413 
1414         // deal with overlapping arrays
1415         srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 2);
1416 
1417         // do the transformations
1418         transform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
1419     }
1420 
1421     /**
1422      * Transforms an array of floating point coordinates by this transform.
1423      * The three coordinate array sections can be exactly the same or
1424      * can be overlapping sections of the same array without affecting the
1425      * validity of the results.
1426      * This method ensures that no source coordinates are overwritten by a
1427      * previous operation before they can be transformed.
1428      * The coordinates are stored in the arrays starting at the specified
1429      * offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>.
1430      * @param srcPts the array containing the source point coordinates.
1431      * Each point is stored as a tiplet of x,&nbsp;y,&nbsp;z coordinates.
1432      * @param srcOff the offset to the first point to be transformed
1433      * in the source array
1434      * @param dstPts the array into which the transformed point coordinates
1435      * are returned.  Each point is stored as a triplet of x,&nbsp;y,&nbsp;z
1436      * coordinates.
1437      * @param dstOff the offset to the location of the first
1438      * transformed point that is stored in the destination array
1439      * @param numPts the number of points to be transformed
1440      * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
1441      * @since JavaFX 8.0
1442      */
1443     public void transform3DPoints(double[] srcPts, int srcOff,
1444                           double[] dstPts, int dstOff,
1445                           int numPts) {
1446 
1447         if (srcPts == null || dstPts == null) {
1448             throw new NullPointerException();
1449         }
1450 
1451         // deal with overlapping arrays
1452         srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 3);
1453 
1454         // do the transformations
1455         transform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
1456     }
1457 
1458     /**
1459      * Transforms the relative magnitude vector by this transform.
1460      * The vector is transformed without applying the translation components
1461      * of the affine transformation matrix.
1462      * This method can be used only for a 2D transform.
1463      * @param x vector magnitude in the direction of the X axis
1464      * @param y vector magnitude in the direction of the Y axis
1465      * @return the transformed relative magnitude vector represented
1466      *         by a {@code Point2D} instance
1467      * @throws IllegalStateException if this is a 3D transform
1468      * @since JavaFX 8.0
1469      */
1470     public Point2D deltaTransform(double x, double y) {
1471         ensureCanTransform2DPoint();
1472 
1473         return new Point2D(
1474             getMxx() * x + getMxy() * y,
1475             getMyx() * x + getMyy() * y);
1476     }
1477 
1478     /**
1479      * Transforms the relative magnitude vector represented by the specified
1480      * {@code Point2D} instance by this transform.
1481      * The vector is transformed without applying the translation components
1482      * of the affine transformation matrix.
1483      * This method can be used only for a 2D transform.
1484      * @param point the relative magnitude vector
1485      * @return the transformed relative magnitude vector represented
1486      *         by a {@code Point2D} instance
1487      * @throws IllegalStateException if this is a 3D transform
1488      * @throws NullPointerException if the specified {@code point} is null
1489      * @since JavaFX 8.0
1490      */
1491     public Point2D deltaTransform(Point2D point) {
1492         return deltaTransform(point.getX(), point.getY());
1493     }
1494 
1495     /**
1496      * Transforms the relative magnitude vector by this transform.
1497      * The vector is transformed without applying the translation components
1498      * of the affine transformation matrix.
1499      * @param x vector magnitude in the direction of the X axis
1500      * @param y vector magnitude in the direction of the Y axis
1501      * @return the transformed relative magnitude vector represented
1502      *         by a {@code Point3D} instance
1503      * @since JavaFX 8.0
1504      */
1505     public Point3D deltaTransform(double x, double y, double z) {
1506         return new Point3D(
1507             getMxx() * x + getMxy() * y + getMxz() * z,
1508             getMyx() * x + getMyy() * y + getMyz() * z,
1509             getMzx() * x + getMzy() * y + getMzz() * z);
1510     }
1511 
1512     /**
1513      * Transforms the relative magnitude vector represented by the specified
1514      * {@code Point3D} instance by this transform.
1515      * The vector is transformed without applying the translation components
1516      * of the affine transformation matrix.
1517      * @param point the relative magnitude vector
1518      * @return the transformed relative magnitude vector represented
1519      *         by a {@code Point3D} instance
1520      * @throws NullPointerException if the specified {@code point} is null
1521      * @since JavaFX 8.0
1522      */
1523     public Point3D deltaTransform(Point3D point) {
1524         return deltaTransform(point.getX(), point.getY(), point.getZ());
1525     }
1526 
1527     /**
1528      * Transforms the specified point by the inverse of this transform.
1529      * This method can be used only for 2D transforms.
1530      * @param x the X coordinate of the point
1531      * @param y the Y coordinate of the point
1532      * @return the inversely transformed point
1533      * @throws IllegalStateException if this is a 3D transform
1534      * @throws NonInvertibleTransformException if this transform
1535      *         cannot be inverted
1536      * @since JavaFX 8.0
1537      */
1538     public Point2D inverseTransform(double x, double y)
1539             throws NonInvertibleTransformException {
1540 
1541         ensureCanTransform2DPoint();
1542 
1543         return getInverseCache().transform(x, y);
1544     }
1545 
1546     /**
1547      * Transforms the specified point by the inverse of this transform.
1548      * This method can be used only for 2D transforms.
1549      * @param point the point to be transformed
1550      * @return the inversely transformed point
1551      * @throws IllegalStateException if this is a 3D transform
1552      * @throws NonInvertibleTransformException if this transform
1553      *         cannot be inverted
1554      * @throws NullPointerException if the specified {@code point} is null
1555      * @since JavaFX 8.0
1556      */
1557     public Point2D inverseTransform(Point2D point)
1558             throws NonInvertibleTransformException {
1559         return inverseTransform(point.getX(), point.getY());
1560     }
1561 
1562     /**
1563      * Transforms the specified point by the inverse of this transform.
1564      * @param x the X coordinate of the point
1565      * @param y the Y coordinate of the point
1566      * @param z the Z coordinate of the point
1567      * @return the inversely transformed point
1568      * @throws NonInvertibleTransformException if this transform
1569      *         cannot be inverted
1570      * @since JavaFX 8.0
1571      */
1572     public Point3D inverseTransform(double x, double y, double z)
1573             throws NonInvertibleTransformException {
1574 
1575         return getInverseCache().transform(x, y, z);
1576     }
1577 
1578     /**
1579      * Transforms the specified point by the inverse of this transform.
1580      * @param point the point to be transformed
1581      * @return the inversely transformed point
1582      * @throws NonInvertibleTransformException if this transform
1583      *         cannot be inverted
1584      * @throws NullPointerException if the specified {@code point} is null
1585      * @since JavaFX 8.0
1586      */
1587     public Point3D inverseTransform(Point3D point)
1588             throws NonInvertibleTransformException {
1589         return inverseTransform(point.getX(), point.getY(), point.getZ());
1590     }
1591 
1592     /**
1593      * Transforms the specified bounds by the inverse of this transform.
1594      * @param bounds the bounds to be transformed
1595      * @return the inversely transformed bounds
1596      * @throws NonInvertibleTransformException if this transform
1597      *         cannot be inverted
1598      * @throws NullPointerException if the specified {@code bounds} is null
1599      * @since JavaFX 8.0
1600      */
1601     public Bounds inverseTransform(Bounds bounds)
1602             throws NonInvertibleTransformException {
1603         if (isType2D() && (bounds.getMinZ() == 0) && (bounds.getMaxZ() == 0)) {
1604             Point2D p1 = inverseTransform(bounds.getMinX(), bounds.getMinY());
1605             Point2D p2 = inverseTransform(bounds.getMaxX(), bounds.getMinY());
1606             Point2D p3 = inverseTransform(bounds.getMaxX(), bounds.getMaxY());
1607             Point2D p4 = inverseTransform(bounds.getMinX(), bounds.getMaxY());
1608 
1609             return BoundsUtils.createBoundingBox(p1, p2, p3, p4);
1610         }
1611         Point3D p1 = inverseTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ());
1612         Point3D p2 = inverseTransform(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ());
1613         Point3D p3 = inverseTransform(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ());
1614         Point3D p4 = inverseTransform(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ());
1615         Point3D p5 = inverseTransform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ());
1616         Point3D p6 = inverseTransform(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ());
1617         Point3D p7 = inverseTransform(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ());
1618         Point3D p8 = inverseTransform(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ());
1619 
1620         return BoundsUtils.createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
1621 
1622     }
1623 
1624     /**
1625      * Core of the inverseTransform2DPoints method.
1626      * All the checks has been performed and the care of the overlaps has been
1627      * taken by the enclosing method, this method only transforms the points
1628      * and fills them to the array. Used by the subclasses to perform
1629      * the transform efficiently.
1630      */
1631     void inverseTransform2DPointsImpl(double[] srcPts, int srcOff,
1632             double[] dstPts, int dstOff, int numPts)
1633             throws NonInvertibleTransformException {
1634 
1635         getInverseCache().transform2DPointsImpl(srcPts, srcOff,
1636                 dstPts, dstOff, numPts);
1637     }
1638 
1639     /**
1640      * Core of the inverseTransform3DPoints method.
1641      * All the checks has been performed and the care of the overlaps has been
1642      * taken by the enclosing method, this method only transforms the points
1643      * and fills them to the array. Used by the subclasses to perform
1644      * the transform efficiently.
1645      */
1646     void inverseTransform3DPointsImpl(double[] srcPts, int srcOff,
1647             double[] dstPts, int dstOff, int numPts)
1648             throws NonInvertibleTransformException {
1649 
1650         getInverseCache().transform3DPointsImpl(srcPts, srcOff,
1651                 dstPts, dstOff, numPts);
1652     }
1653 
1654     /**
1655      * Transforms an array of coordinates by the inverse of this transform.
1656      * The two coordinate array sections can be exactly the same or
1657      * can be overlapping sections of the same array without affecting the
1658      * validity of the results.
1659      * This method ensures that no source coordinates are overwritten by a
1660      * previous operation before they can be transformed.
1661      * The coordinates are stored in the arrays starting at the specified
1662      * offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>.
1663      * This method can be used only for 2D transforms.
1664      * @param srcPts the array containing the source point coordinates.
1665      * Each point is stored as a pair of x,&nbsp;y coordinates.
1666      * @param srcOff the offset to the first point to be transformed
1667      * in the source array
1668      * @param dstPts the array into which the transformed point coordinates
1669      * are returned.  Each point is stored as a pair of x,&nbsp;y
1670      * coordinates.
1671      * @param dstOff the offset to the location of the first
1672      * transformed point that is stored in the destination array
1673      * @param numPts the number of points to be transformed
1674      * @throws IllegalStateException if this is a 3D transform
1675      * @throws NonInvertibleTransformException if this transform
1676      *         cannot be inverted
1677      * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
1678      * @since JavaFX 8.0
1679      */
1680     public void inverseTransform2DPoints(double[] srcPts, int srcOff,
1681                           double[] dstPts, int dstOff,
1682                           int numPts) throws NonInvertibleTransformException{
1683 
1684         if (srcPts == null || dstPts == null) {
1685             throw new NullPointerException();
1686         }
1687 
1688         if (!isType2D()) {
1689             throw new IllegalStateException("Cannot transform 2D points "
1690                     + "with a 3D transform");
1691         }
1692 
1693         // deal with overlapping arrays
1694         srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 2);
1695 
1696         // do the transformations
1697         inverseTransform2DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
1698     }
1699 
1700     /**
1701      * Transforms an array of floating point coordinates by the inverse
1702      * of this transform.
1703      * The three coordinate array sections can be exactly the same or
1704      * can be overlapping sections of the same array without affecting the
1705      * validity of the results.
1706      * This method ensures that no source coordinates are overwritten by a
1707      * previous operation before they can be transformed.
1708      * The coordinates are stored in the arrays starting at the specified
1709      * offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>.
1710      * @param srcPts the array containing the source point coordinates.
1711      * Each point is stored as a triplet of x,&nbsp;y,&nbsp;z coordinates.
1712      * @param srcOff the offset to the first point to be transformed
1713      * in the source array
1714      * @param dstPts the array into which the transformed point coordinates
1715      * are returned.  Each point is stored as a triplet of x,&nbsp;y,&nbsp;z
1716      * coordinates.
1717      * @param dstOff the offset to the location of the first
1718      * transformed point that is stored in the destination array
1719      * @param numPts the number of points to be transformed
1720      * @throws NonInvertibleTransformException if this transform
1721      *         cannot be inverted
1722      * @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
1723      * @since JavaFX 8.0
1724      */
1725     public void inverseTransform3DPoints(double[] srcPts, int srcOff,
1726                           double[] dstPts, int dstOff,
1727                           int numPts) throws NonInvertibleTransformException {
1728 
1729         if (srcPts == null || dstPts == null) {
1730             throw new NullPointerException();
1731         }
1732 
1733         // deal with overlapping arrays
1734         srcOff = getFixedSrcOffset(srcPts, srcOff, dstPts, dstOff, numPts, 3);
1735 
1736         // do the transformations
1737         inverseTransform3DPointsImpl(srcPts, srcOff, dstPts, dstOff, numPts);
1738     }
1739 
1740     /**
1741      * Transforms the relative magnitude vector by the inverse of this transform.
1742      * The vector is transformed without applying the translation components
1743      * of the affine transformation matrix.
1744      * This method can be used only for a 2D transform.
1745      * @param x vector magnitude in the direction of the X axis
1746      * @param y vector magnitude in the direction of the Y axis
1747      * @return the inversely transformed relative magnitude vector represented
1748      *         by a {@code Point2D} instance
1749      * @throws IllegalStateException if this is a 3D transform
1750      * @throws NonInvertibleTransformException if this transform
1751      *         cannot be inverted
1752      * @since JavaFX 8.0
1753      */
1754     public Point2D inverseDeltaTransform(double x, double y)
1755             throws NonInvertibleTransformException {
1756 
1757         ensureCanTransform2DPoint();
1758 
1759         return getInverseCache().deltaTransform(x, y);
1760     }
1761 
1762     /**
1763      * Transforms the relative magnitude vector represented by the specified
1764      * {@code Point2D} instance by the inverse of this transform.
1765      * The vector is transformed without applying the translation components
1766      * of the affine transformation matrix.
1767      * This method can be used only for a 2D transform.
1768      * @param point the relative magnitude vector
1769      * @return the inversely transformed relative magnitude vector represented
1770      *         by a {@code Point2D} instance
1771      * @throws IllegalStateException if this is a 3D transform
1772      * @throws NonInvertibleTransformException if this transform
1773      *         cannot be inverted
1774      * @throws NullPointerException if the specified {@code point} is null
1775      * @since JavaFX 8.0
1776      */
1777     public Point2D inverseDeltaTransform(Point2D point)
1778             throws NonInvertibleTransformException {
1779         return inverseDeltaTransform(point.getX(), point.getY());
1780     }
1781 
1782     /**
1783      * Transforms the relative magnitude vector by the inverse of this transform.
1784      * The vector is transformed without applying the translation components
1785      * of the affine transformation matrix.
1786      * @param x vector magnitude in the direction of the X axis
1787      * @param y vector magnitude in the direction of the Y axis
1788      * @return the inversely transformed relative magnitude vector represented
1789      *         by a {@code Point3D} instance
1790      * @throws NonInvertibleTransformException if this transform
1791      *         cannot be inverted
1792      * @since JavaFX 8.0
1793      */
1794     public Point3D inverseDeltaTransform(double x, double y, double z)
1795             throws NonInvertibleTransformException {
1796 
1797         return getInverseCache().deltaTransform(x, y, z);
1798     }
1799 
1800     /**
1801      * Transforms the relative magnitude vector represented by the specified
1802      * {@code Point3D} instance by the inverse of this transform.
1803      * The vector is transformed without applying the translation components
1804      * of the affine transformation matrix.
1805      * @param point the relative magnitude vector
1806      * @return the inversely transformed relative magnitude vector represented
1807      *         by a {@code Point3D} instance
1808      * @throws NonInvertibleTransformException if this transform
1809      *         cannot be inverted
1810      * @throws NullPointerException if the specified {@code point} is null
1811      * @since JavaFX 8.0
1812      */
1813     public Point3D inverseDeltaTransform(Point3D point)
1814             throws NonInvertibleTransformException {
1815         return inverseDeltaTransform(point.getX(), point.getY(), point.getZ());
1816     }
1817 
1818     /**
1819      * Helper method for transforming arrays of points that deals with
1820      * overlapping arrays.
1821      * @return the (if necessary fixed) srcOff
1822      */
1823     private int getFixedSrcOffset(double[] srcPts, int srcOff,
1824             double[] dstPts, int dstOff,
1825             int numPts, int dimensions) {
1826 
1827         if (dstPts == srcPts &&
1828             dstOff > srcOff && dstOff < srcOff + numPts * dimensions)
1829         {
1830             // If the arrays overlap partially with the destination higher
1831             // than the source and we transform the coordinates normally
1832             // we would overwrite some of the later source coordinates
1833             // with results of previous transformations.
1834             // To get around this we use arraycopy to copy the points
1835             // to their final destination with correct overwrite
1836             // handling and then transform them in place in the new
1837             // safer location.
1838             System.arraycopy(srcPts, srcOff, dstPts, dstOff, numPts * dimensions);
1839             return dstOff;
1840         }
1841 
1842         return srcOff;
1843     }
1844 
1845     /* *************************************************************************
1846      *                                                                         *
1847      *                         Event Dispatch                                  *
1848      *                                                                         *
1849      **************************************************************************/
1850 
1851     private EventHandlerManager internalEventDispatcher;
1852     private EventHandlerManager getInternalEventDispatcher() {
1853         if (internalEventDispatcher == null) {
1854             internalEventDispatcher = new EventHandlerManager(this);
1855         }
1856         return internalEventDispatcher;
1857     }
1858     private ObjectProperty<EventHandler<? super TransformChangedEvent>>
1859             onTransformChanged;
1860 
1861     @Override
1862     public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
1863         return internalEventDispatcher == null
1864                 ? tail : tail.append(getInternalEventDispatcher());
1865     }
1866 
1867     /**
1868      * <p>
1869      * Registers an event handler to this transform. Any event filters are first
1870      * processed, then the specified onFoo event handlers, and finally any
1871      * event handlers registered by this method.
1872      * </p><p>
1873      * Currently the only event delivered to a {@code Transform} is the
1874      * {@code TransformChangedEvent} with it's single type
1875      * {@code TRANSFORM_CHANGED}.
1876      * </p>
1877      *
1878      * @param <T> the specific event class of the handler
1879      * @param eventType the type of the events to receive by the handler
1880      * @param eventHandler the handler to register
1881      * @throws NullPointerException if the event type or handler is null
1882      * @since JavaFX 8.0
1883      */
1884     public final <T extends Event> void addEventHandler(
1885             final EventType<T> eventType,
1886             final EventHandler<? super T> eventHandler) {
1887         getInternalEventDispatcher()
1888                 .addEventHandler(eventType, eventHandler);
1889         // need to validate all properties to get the change events
1890         validate();
1891     }
1892 
1893     /**
1894      * Unregisters a previously registered event handler from this transform.
1895      * One handler might have been registered for different event types, so the
1896      * caller needs to specify the particular event type from which to
1897      * unregister the handler.
1898      *
1899      * @param <T> the specific event class of the handler
1900      * @param eventType the event type from which to unregister
1901      * @param eventHandler the handler to unregister
1902      * @throws NullPointerException if the event type or handler is null
1903      * @since JavaFX 8.0
1904      */
1905     public final <T extends Event> void removeEventHandler(
1906             final EventType<T> eventType,
1907             final EventHandler<? super T> eventHandler) {
1908         getInternalEventDispatcher()
1909                 .removeEventHandler(eventType, eventHandler);
1910     }
1911 
1912     /**
1913      * <p>
1914      * Registers an event filter to this transform. Registered event filters get
1915      * an event before any associated event handlers.
1916      * </p><p>
1917      * Currently the only event delivered to a {@code Transform} is the
1918      * {@code TransformChangedEvent} with it's single type
1919      * {@code TRANSFORM_CHANGED}.
1920      * <p>
1921      *
1922      * @param <T> the specific event class of the filter
1923      * @param eventType the type of the events to receive by the filter
1924      * @param eventFilter the filter to register
1925      * @throws NullPointerException if the event type or filter is null
1926      * @since JavaFX 8.0
1927      */
1928     public final <T extends Event> void addEventFilter(
1929             final EventType<T> eventType,
1930             final EventHandler<? super T> eventFilter) {
1931         getInternalEventDispatcher()
1932                 .addEventFilter(eventType, eventFilter);
1933         // need to validate all properties to get the change events
1934         validate();
1935     }
1936 
1937     /**
1938      * Unregisters a previously registered event filter from this transform. One
1939      * filter might have been registered for different event types, so the
1940      * caller needs to specify the particular event type from which to
1941      * unregister the filter.
1942      *
1943      * @param <T> the specific event class of the filter
1944      * @param eventType the event type from which to unregister
1945      * @param eventFilter the filter to unregister
1946      * @throws NullPointerException if the event type or filter is null
1947      * @since JavaFX 8.0
1948      */
1949     public final <T extends Event> void removeEventFilter(
1950             final EventType<T> eventType,
1951             final EventHandler<? super T> eventFilter) {
1952         getInternalEventDispatcher()
1953                 .removeEventFilter(eventType, eventFilter);
1954     }
1955 
1956     /**
1957      * Sets the onTransformChanged event handler which is called whenever
1958      * the transform changes any of its parameters.
1959      *
1960      * @param value the event handler, can be null to clear it
1961      * @since JavaFX 8.0
1962      */
1963     public final void setOnTransformChanged(
1964             EventHandler<? super TransformChangedEvent> value) {
1965         onTransformChangedProperty().set(value);
1966         // need to validate all properties to get the change events
1967         validate();
1968     }
1969 
1970     /**
1971      * Gets the onTransformChanged event handler.
1972      * @return the event handler previously set by {@code setOnTransformChanged}
1973      * method, null if the handler is not set.
1974      * @since JavaFX 8.0
1975      */
1976     public final EventHandler<? super TransformChangedEvent> getOnTransformChanged() {
1977         return (onTransformChanged == null) ? null : onTransformChanged.get();
1978     }
1979 
1980     /**
1981      * The onTransformChanged event handler is called whenever the transform
1982      * changes any of its parameters.
1983      * @since JavaFX 8.0
1984      */
1985     public final ObjectProperty<EventHandler<? super TransformChangedEvent>>
1986             onTransformChangedProperty() {
1987         if (onTransformChanged == null) {
1988 
1989             onTransformChanged = new SimpleObjectProperty<EventHandler
1990                     <? super TransformChangedEvent>>(this, "onTransformChanged") {
1991 
1992                 @Override protected void invalidated() {
1993                     getInternalEventDispatcher().setEventHandler(
1994                             TransformChangedEvent.TRANSFORM_CHANGED, get());
1995                 }
1996             };
1997         }
1998 
1999         return onTransformChanged;
2000     }
2001 
2002     /* *************************************************************************
2003      *                                                                         *
2004      *                    Internal implementation stuff                        *
2005      *                                                                         *
2006      **************************************************************************/
2007 
2008     /**
2009      * Makes sure the specified matrix type can be requested from this transform.
2010      * Is used for convenience in various methods that accept
2011      * the MatrixType argument.
2012      * @param type matrix type to check
2013      * @throws IllegalArgumentException if this is a 3D transform and
2014      *                                  a 2D type is requested
2015      */
2016     void checkRequestedMAT(MatrixType type) throws IllegalArgumentException{
2017         if (type.is2D() && !isType2D()) {
2018             throw new IllegalArgumentException("Cannot access 2D matrix "
2019                     + "for a 3D transform");
2020         }
2021     }
2022 
2023     /**
2024      * Makes sure this is a 2D transform.
2025      * Is used for convenience in various 2D point transformation methods.
2026      * @throws IllegalStateException if this is a 2D transform
2027      */
2028     void ensureCanTransform2DPoint() throws IllegalStateException {
2029         if (!isType2D()) {
2030             throw new IllegalStateException("Cannot transform 2D point "
2031                     + "with a 3D transform");
2032         }
2033     }
2034 
2035     /**
2036      * Needed for the proper delivery of the TransformChangedEvent.
2037      * If the members are invalid, the transformChanged() notification
2038      * is not called and the event is not delivered. To avoid that
2039      * we need to manually validate all properties. Subclasses validate
2040      * their specific properties.
2041      */
2042     void validate() {
2043         getMxx(); getMxy(); getMxz(); getTx();
2044         getMyx(); getMyy(); getMyz(); getTy();
2045         getMzx(); getMzy(); getMzz(); getTz();
2046     }
2047 
2048     abstract void apply(Affine3D t);
2049 
2050     abstract BaseTransform derive(BaseTransform t);
2051 
2052     private void add(final Node node) {
2053         nodes.add(node);
2054     }
2055 
2056     private void remove(final Node node) {
2057         nodes.remove(node);
2058     }
2059 
2060     /**
2061      * This method must be called by all transforms whenever any of their
2062      * parameters changes. It is typically called when any of the transform's
2063      * properties is invalidated (it is OK to skip the call if an invalid
2064      * property is set).
2065      * @since JavaFX 8.0
2066      */
2067     protected void transformChanged() {
2068         inverseCache = null;
2069         final Iterator iterator = nodes.iterator();
2070         while (iterator.hasNext()) {
2071             ((Node) iterator.next()).impl_transformsChanged();
2072         }
2073 
2074         if (type2D != null) {
2075             type2D.invalidate();
2076         }
2077 
2078         if (identity != null) {
2079             identity.invalidate();
2080         }
2081 
2082         if (internalEventDispatcher != null) {
2083             // need to validate all properties for the event to be fired next time
2084             validate();
2085             Event.fireEvent(this, new TransformChangedEvent(this, this));
2086         }
2087     }
2088 
2089     /**
2090      * Visitor from {@code Affine} class which provides an efficient
2091      * {@code append} operation for the subclasses.
2092      * @param a {@code Affine} instance to append to
2093      */
2094     void appendTo(Affine a) {
2095         a.append(getMxx(), getMxy(), getMxz(), getTx(),
2096                  getMyx(), getMyy(), getMyz(), getTy(),
2097                  getMzx(), getMzy(), getMzz(), getTz());
2098     }
2099 
2100     /**
2101      * Visitor from {@code Affine} class which provides an efficient
2102      * {@code prepend} operation for the subclasses.
2103      * @param a {@code Affine} instance to prepend to
2104      */
2105     void prependTo(Affine a) {
2106         a.prepend(getMxx(), getMxy(), getMxz(), getTx(),
2107                   getMyx(), getMyy(), getMyz(), getTy(),
2108                   getMzx(), getMzy(), getMzz(), getTz());
2109     }
2110 
2111     /**
2112      * <p>
2113      * Gets the inverse transform cache.
2114      * </p><p>
2115      * Computing the inverse transform is generally an expensive operation,
2116      * so once it is needed we cache the result (throwing it away when the
2117      * transform changes). The subclasses may avoid using the cache if their
2118      * inverse can be computed quickly on the fly.
2119      * </p><p>
2120      * This method computes the inverse if the cache is not valid.
2121      * </p>
2122      * @return the cached inverse transformation
2123      * @throws NonInvertibleTransformException if this transform
2124      *         cannot be inverted
2125      */
2126     private Transform getInverseCache() throws NonInvertibleTransformException {
2127         if (inverseCache == null || inverseCache.get() == null) {
2128             Affine inv = new Affine(
2129                     getMxx(), getMxy(), getMxz(), getTx(),
2130                     getMyx(), getMyy(), getMyz(), getTy(),
2131                     getMzx(), getMzy(), getMzz(), getTz());
2132             inv.invert();
2133             inverseCache = new SoftReference<Transform>(inv);
2134             return inv;
2135         }
2136 
2137         return inverseCache.get();
2138     }
2139 
2140     /**
2141      * Used only by tests to emulate garbage collecting the soft references
2142      */
2143     void clearInverseCache() {
2144         if (inverseCache != null) {
2145             inverseCache.clear();
2146         }
2147     }
2148 
2149     /**************************************************************************
2150      *  ImmutableTransform Class and supporting methods
2151      **************************************************************************/
2152 
2153     static Transform createImmutableTransform() {
2154         return new ImmutableTransform();
2155     }
2156 
2157     static Transform createImmutableTransform(
2158             double mxx, double mxy, double mxz, double tx,
2159             double myx, double myy, double myz, double ty,
2160             double mzx, double mzy, double mzz, double tz) {
2161         return new ImmutableTransform(
2162                 mxx, mxy, mxz, tx,
2163                 myx, myy, myz, ty,
2164                 mzx, mzy, mzz, tz);
2165     }
2166 
2167     static Transform createImmutableTransform(Transform transform,
2168             double mxx, double mxy, double mxz, double tx,
2169             double myx, double myy, double myz, double ty,
2170             double mzx, double mzy, double mzz, double tz) {
2171         if (transform == null) {
2172             return createImmutableTransform(
2173                     mxx, mxy, mxz, tx,
2174                     myx, myy, myz, ty,
2175                     mzx, mzy, mzz, tz);
2176         }
2177         ((Transform.ImmutableTransform) transform).setToTransform(
2178                 mxx, mxy, mxz, tx,
2179                 myx, myy, myz, ty,
2180                 mzx, mzy, mzz, tz);
2181         return transform;
2182     }
2183 
2184     static Transform createImmutableTransform(Transform transform,
2185             Transform left, Transform right) {
2186         if (transform == null) {
2187             transform = createImmutableTransform();
2188         }
2189         ((Transform.ImmutableTransform) transform).setToConcatenation(
2190                 (ImmutableTransform) left, (ImmutableTransform) right);
2191         return transform;
2192     }
2193 
2194     /**
2195      * Immutable transformation with performance optimizations based on Affine.
2196      *
2197      * From user's perspective, this transform is immutable. However, we can
2198      * modify it internally. This allows for reusing instances that were
2199      * not handed to users. The caller is responsible for not modifying
2200      * user-visible instances.
2201      *
2202      * Note: can't override Transform's package private methods so they cannot
2203      * be optimized. Currently not a big deal.
2204      */
2205     static class ImmutableTransform extends Transform {
2206 
2207         private static final int APPLY_IDENTITY = 0;
2208         private static final int APPLY_TRANSLATE = 1;
2209         private static final int APPLY_SCALE = 2;
2210         private static final int APPLY_SHEAR = 4;
2211         private static final int APPLY_NON_3D = 0;
2212         private static final int APPLY_3D_COMPLEX = 4;
2213         private transient int state2d;
2214         private transient int state3d;
2215 
2216         private double xx;
2217         private double xy;
2218         private double xz;
2219         private double yx;
2220         private double yy;
2221         private double yz;
2222         private double zx;
2223         private double zy;
2224         private double zz;
2225         private double xt;
2226         private double yt;
2227         private double zt;
2228 
2229         ImmutableTransform() {
2230             xx = yy = zz = 1.0;
2231         }
2232 
2233         ImmutableTransform(Transform transform) {
2234             this(transform.getMxx(), transform.getMxy(), transform.getMxz(),
2235                                                                  transform.getTx(),
2236                  transform.getMyx(), transform.getMyy(), transform.getMyz(),
2237                                                                  transform.getTy(),
2238                  transform.getMzx(), transform.getMzy(), transform.getMzz(),
2239                                                                  transform.getTz());
2240         }
2241 
2242         ImmutableTransform(double mxx, double mxy, double mxz, double tx,
2243                       double myx, double myy, double myz, double ty,
2244                       double mzx, double mzy, double mzz, double tz) {
2245             xx = mxx;
2246             xy = mxy;
2247             xz = mxz;
2248             xt = tx;
2249 
2250             yx = myx;
2251             yy = myy;
2252             yz = myz;
2253             yt = ty;
2254 
2255             zx = mzx;
2256             zy = mzy;
2257             zz = mzz;
2258             zt = tz;
2259 
2260             updateState();
2261         }
2262 
2263         // Beware: this is modifying immutable transform!
2264         // It is private and it is there just for the purpose of reusing
2265         // instances not given to users
2266         private void setToTransform(double mxx, double mxy, double mxz, double tx,
2267                                     double myx, double myy, double myz, double ty,
2268                                     double mzx, double mzy, double mzz, double tz)
2269         {
2270             xx = mxx;
2271             xy = mxy;
2272             xz = mxz;
2273             xt = tx;
2274             yx = myx;
2275             yy = myy;
2276             yz = myz;
2277             yt = ty;
2278             zx = mzx;
2279             zy = mzy;
2280             zz = mzz;
2281             zt = tz;
2282             updateState();
2283         }
2284 
2285         // Beware: this is modifying immutable transform!
2286         // It is private and it is there just for the purpose of reusing
2287         // instances not given to users
2288         private void setToConcatenation(ImmutableTransform left, ImmutableTransform right) {
2289             if (left.state3d == APPLY_NON_3D && right.state3d == APPLY_NON_3D) {
2290                 xx = left.xx * right.xx + left.xy * right.yx;
2291                 xy = left.xx * right.xy + left.xy * right.yy;
2292                 xt = left.xx * right.xt + left.xy * right.yt + left.xt;
2293                 yx = left.yx * right.xx + left.yy * right.yx;
2294                 yy = left.yx * right.xy + left.yy * right.yy;
2295                 yt = left.yx * right.xt + left.yy * right.yt + left.yt;
2296                 if (state3d != APPLY_NON_3D) {
2297                     xz = yz = zx = zy = zt = 0.0;
2298                     zz = 1.0;
2299                     state3d = APPLY_NON_3D;
2300                 }
2301                 updateState2D();
2302             } else {
2303                 xx = left.xx * right.xx + left.xy * right.yx + left.xz * right.zx;
2304                 xy = left.xx * right.xy + left.xy * right.yy + left.xz * right.zy;
2305                 xz = left.xx * right.xz + left.xy * right.yz + left.xz * right.zz;
2306                 xt = left.xx * right.xt + left.xy * right.yt + left.xz * right.zt + left.xt;
2307                 yx = left.yx * right.xx + left.yy * right.yx + left.yz * right.zx;
2308                 yy = left.yx * right.xy + left.yy * right.yy + left.yz * right.zy;
2309                 yz = left.yx * right.xz + left.yy * right.yz + left.yz * right.zz;
2310                 yt = left.yx * right.xt + left.yy * right.yt + left.yz * right.zt + left.yt;
2311                 zx = left.zx * right.xx + left.zy * right.yx + left.zz * right.zx;
2312                 zy = left.zx * right.xy + left.zy * right.yy + left.zz * right.zy;
2313                 zz = left.zx * right.xz + left.zy * right.yz + left.zz * right.zz;
2314                 zt = left.zx * right.xt + left.zy * right.yt + left.zz * right.zt + left.zt;
2315                 updateState();
2316             }
2317             // could be further optimized using the states, but that would
2318             // require a lot of code (see Affine and all its append* methods)
2319         }
2320 
2321         @Override
2322         public double getMxx() {
2323             return xx;
2324         }
2325 
2326         @Override
2327         public double getMxy() {
2328             return xy;
2329         }
2330 
2331         @Override
2332         public double getMxz() {
2333             return xz;
2334         }
2335 
2336         @Override
2337         public double getTx() {
2338             return xt;
2339         }
2340 
2341         @Override
2342         public double getMyx() {
2343             return yx;
2344         }
2345 
2346         @Override
2347         public double getMyy() {
2348             return yy;
2349         }
2350 
2351         @Override
2352         public double getMyz() {
2353             return yz;
2354         }
2355 
2356         @Override
2357         public double getTy() {
2358             return yt;
2359         }
2360 
2361         @Override
2362         public double getMzx() {
2363             return zx;
2364         }
2365 
2366         @Override
2367         public double getMzy() {
2368             return zy;
2369         }
2370 
2371         @Override
2372         public double getMzz() {
2373             return zz;
2374         }
2375 
2376         @Override
2377         public double getTz() {
2378             return zt;
2379         }
2380 
2381     /* *************************************************************************
2382      *                                                                         *
2383      *                           State getters                                 *
2384      *                                                                         *
2385      **************************************************************************/
2386 
2387         @Override
2388         public double determinant() {
2389             switch(state3d) {
2390                 default:
2391                     stateError();
2392                     // cannot reach
2393                 case APPLY_NON_3D:
2394                     switch (state2d) {
2395                         default:
2396                             stateError();
2397                             // cannot reach
2398                         case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
2399                         case APPLY_SHEAR | APPLY_SCALE:
2400                             return xx * yy - xy * yx;
2401                         case APPLY_SHEAR | APPLY_TRANSLATE:
2402                         case APPLY_SHEAR:
2403                             return -(xy* yx);
2404                         case APPLY_SCALE | APPLY_TRANSLATE:
2405                         case APPLY_SCALE:
2406                             return xx * yy;
2407                         case APPLY_TRANSLATE:
2408                         case APPLY_IDENTITY:
2409                             return 1.0;
2410                     }
2411                 case APPLY_TRANSLATE:
2412                     return 1.0;
2413                 case APPLY_SCALE:
2414                 case APPLY_SCALE | APPLY_TRANSLATE:
2415                     return xx * yy * zz;
2416                 case APPLY_3D_COMPLEX:
2417                     return (xx* (yy * zz - zy * yz) +
2418                             xy* (yz * zx - zz * yx) +
2419                             xz* (yx * zy - zx * yy));
2420             }
2421         }
2422 
2423         @Override
2424         public Transform createConcatenation(Transform transform) {
2425             javafx.scene.transform.Affine a = new Affine(this);
2426             a.append(transform);
2427             return a;
2428         }
2429 
2430         @Override
2431         public javafx.scene.transform.Affine createInverse() throws NonInvertibleTransformException {
2432             javafx.scene.transform.Affine t = new Affine(this);
2433             t.invert();
2434             return t;
2435         }
2436 
2437         @Override
2438         public Transform clone() {
2439             return new ImmutableTransform(this);
2440         }
2441 
2442         /* *************************************************************************
2443          *                                                                         *
2444          *                     Transform, Inverse Transform                        *
2445          *                                                                         *
2446          **************************************************************************/
2447 
2448         @Override
2449         public Point2D transform(double x, double y) {
2450             ensureCanTransform2DPoint();
2451 
2452             switch (state2d) {
2453                 default:
2454                     stateError();
2455                     // cannot reach
2456                 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
2457                     return new Point2D(
2458                         xx * x + xy * y + xt,
2459                         yx * x + yy * y + yt);
2460                 case APPLY_SHEAR | APPLY_SCALE:
2461                     return new Point2D(
2462                         xx * x + xy * y,
2463                         yx * x + yy * y);
2464                 case APPLY_SHEAR | APPLY_TRANSLATE:
2465                     return new Point2D(
2466                             xy * y + xt,
2467                             yx * x + yt);
2468                 case APPLY_SHEAR:
2469                     return new Point2D(xy * y, yx * x);
2470                 case APPLY_SCALE | APPLY_TRANSLATE:
2471                     return new Point2D(
2472                             xx * x + xt,
2473                             yy * y + yt);
2474                 case APPLY_SCALE:
2475                     return new Point2D(xx * x, yy * y);
2476                 case APPLY_TRANSLATE:
2477                     return new Point2D(x + xt, y + yt);
2478                 case APPLY_IDENTITY:
2479                     return new Point2D(x, y);
2480             }
2481         }
2482 
2483         @Override
2484         public Point3D transform(double x, double y, double z) {
2485             switch (state3d) {
2486                 default:
2487                     stateError();
2488                     // cannot reach
2489                 case APPLY_NON_3D:
2490                     switch (state2d) {
2491                         default:
2492                             stateError();
2493                             // cannot reach
2494                         case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
2495                             return new Point3D(
2496                                 xx * x + xy * y + xt,
2497                                 yx * x + yy * y + yt, z);
2498                         case APPLY_SHEAR | APPLY_SCALE:
2499                             return new Point3D(
2500                                 xx * x + xy * y,
2501                                 yx * x + yy * y, z);
2502                         case APPLY_SHEAR | APPLY_TRANSLATE:
2503                             return new Point3D(
2504                                     xy * y + xt, yx * x + yt,
2505                                     z);
2506                         case APPLY_SHEAR:
2507                             return new Point3D(xy * y, yx * x, z);
2508                         case APPLY_SCALE | APPLY_TRANSLATE:
2509                             return new Point3D(
2510                                     xx * x + xt, yy * y + yt,
2511                                     z);
2512                         case APPLY_SCALE:
2513                             return new Point3D(xx * x, yy * y, z);
2514                         case APPLY_TRANSLATE:
2515                             return new Point3D(x + xt, y + yt, z);
2516                         case APPLY_IDENTITY:
2517                             return new Point3D(x, y, z);
2518                     }
2519                 case APPLY_TRANSLATE:
2520                     return new Point3D(x + xt, y + yt, z + zt);
2521                 case APPLY_SCALE:
2522                     return new Point3D(xx * x, yy * y, zz * z);
2523                 case APPLY_SCALE | APPLY_TRANSLATE:
2524                     return new Point3D(
2525                             xx * x + xt,
2526                             yy * y + yt,
2527                             zz * z + zt);
2528                 case APPLY_3D_COMPLEX:
2529                     return new Point3D(
2530                         xx * x + xy * y + xz * z + xt,
2531                         yx * x + yy * y + yz * z + yt,
2532                         zx * x + zy * y + zz * z + zt);
2533             }
2534         }
2535 
2536         @Override
2537         public Point2D deltaTransform(double x, double y) {
2538             ensureCanTransform2DPoint();
2539 
2540             switch (state2d) {
2541                 default:
2542                     stateError();
2543                     // cannot reach
2544                 case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
2545                 case APPLY_SHEAR | APPLY_SCALE:
2546                     return new Point2D(
2547                         xx * x + xy * y,
2548                         yx * x + yy * y);
2549                 case APPLY_SHEAR | APPLY_TRANSLATE:
2550                 case APPLY_SHEAR:
2551                     return new Point2D(xy * y, yx * x);
2552                 case APPLY_SCALE | APPLY_TRANSLATE:
2553                 case APPLY_SCALE:
2554                     return new Point2D(xx * x, yy * y);
2555                 case APPLY_TRANSLATE:
2556                 case APPLY_IDENTITY:
2557                     return new Point2D(x, y);
2558             }
2559         }
2560 
2561         @Override
2562         public Point3D deltaTransform(double x, double y, double z) {
2563             switch (state3d) {
2564                 default:
2565                     stateError();
2566                     // cannot reach
2567                 case APPLY_NON_3D:
2568                     switch (state2d) {
2569                         default:
2570                             stateError();
2571                             // cannot reach
2572                         case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
2573                         case APPLY_SHEAR | APPLY_SCALE:
2574                             return new Point3D(
2575                                 xx * x + xy * y,
2576                                 yx * x + yy * y, z);
2577                         case APPLY_SHEAR | APPLY_TRANSLATE:
2578                         case APPLY_SHEAR:
2579                             return new Point3D(xy * y, yx * x, z);
2580                         case APPLY_SCALE | APPLY_TRANSLATE:
2581                         case APPLY_SCALE:
2582                             return new Point3D(xx * x, yy * y, z);
2583                         case APPLY_TRANSLATE:
2584                         case APPLY_IDENTITY:
2585                             return new Point3D(x, y, z);
2586                     }
2587                 case APPLY_TRANSLATE:
2588                     return new Point3D(x, y, z);
2589                 case APPLY_SCALE:
2590                 case APPLY_SCALE | APPLY_TRANSLATE:
2591                     return new Point3D(xx * x, yy * y, zz * z);
2592                 case APPLY_3D_COMPLEX:
2593                     return new Point3D(
2594                         xx * x + xy * y + xz * z,
2595                         yx * x + yy * y + yz * z,
2596                         zx * x + zy * y + zz * z);
2597             }
2598         }
2599 
2600         @Override
2601         public Point2D inverseTransform(double x, double y)
2602                 throws NonInvertibleTransformException {
2603             ensureCanTransform2DPoint();
2604 
2605             switch (state2d) {
2606                 default:
2607                     return super.inverseTransform(x, y);
2608                 case APPLY_SHEAR | APPLY_TRANSLATE:
2609                     if (xy == 0.0 || yx == 0.0) {
2610                         throw new NonInvertibleTransformException("Determinant is 0");
2611                     }
2612                     return new Point2D(
2613                             (1.0 / yx) * y - yt / yx,
2614                             (1.0 / xy) * x - xt / xy);
2615                 case APPLY_SHEAR:
2616                     if (xy == 0.0 || yx == 0.0) {
2617                         throw new NonInvertibleTransformException("Determinant is 0");
2618                     }
2619                     return new Point2D((1.0 / yx) * y, (1.0 / xy) * x);
2620                 case APPLY_SCALE | APPLY_TRANSLATE:
2621                     if (xx == 0.0 || yy == 0.0) {
2622                         throw new NonInvertibleTransformException("Determinant is 0");
2623                     }
2624                     return new Point2D(
2625                             (1.0 / xx) * x - xt / xx,
2626                             (1.0 / yy) * y - yt / yy);
2627                 case APPLY_SCALE:
2628                     if (xx == 0.0 || yy == 0.0) {
2629                         throw new NonInvertibleTransformException("Determinant is 0");
2630                     }
2631                     return new Point2D((1.0 / xx) * x, (1.0 / yy) * y);
2632                 case APPLY_TRANSLATE:
2633                     return new Point2D(x - xt, y - yt);
2634                 case APPLY_IDENTITY:
2635                     return new Point2D(x, y);
2636             }
2637         }
2638 
2639         @Override
2640         public Point3D inverseTransform(double x, double y, double z)
2641                 throws NonInvertibleTransformException {
2642             switch(state3d) {
2643                 default:
2644                     stateError();
2645                     // cannot reach
2646                 case APPLY_NON_3D:
2647                     switch (state2d) {
2648                         default:
2649                             return super.inverseTransform(x, y, z);
2650                         case APPLY_SHEAR | APPLY_TRANSLATE:
2651                             if (xy == 0.0 || yx == 0.0) {
2652                                 throw new NonInvertibleTransformException(
2653                                         "Determinant is 0");
2654                             }
2655                             return new Point3D(
2656                                     (1.0 / yx) * y - yt / yx,
2657                                     (1.0 / xy) * x - xt / xy, z);
2658                         case APPLY_SHEAR:
2659                             if (xy == 0.0 || yx == 0.0) {
2660                                 throw new NonInvertibleTransformException(
2661                                         "Determinant is 0");
2662                             }
2663                             return new Point3D(
2664                                     (1.0 / yx) * y,
2665                                     (1.0 / xy) * x, z);
2666                         case APPLY_SCALE | APPLY_TRANSLATE:
2667                             if (xx == 0.0 || yy == 0.0) {
2668                                 throw new NonInvertibleTransformException(
2669                                         "Determinant is 0");
2670                             }
2671                             return new Point3D(
2672                                     (1.0 / xx) * x - xt / xx,
2673                                     (1.0 / yy) * y - yt / yy, z);
2674                         case APPLY_SCALE:
2675                             if (xx == 0.0 || yy == 0.0) {
2676                                 throw new NonInvertibleTransformException(
2677                                         "Determinant is 0");
2678                             }
2679                             return new Point3D((1.0 / xx) * x, (1.0 / yy) * y, z);
2680                         case APPLY_TRANSLATE:
2681                             return new Point3D(x - xt, y - yt, z);
2682                         case APPLY_IDENTITY:
2683                             return new Point3D(x, y, z);
2684                     }
2685                 case APPLY_TRANSLATE:
2686                     return new Point3D(x - xt, y - yt, z - zt);
2687                 case APPLY_SCALE:
2688                     if (xx == 0.0 || yy == 0.0 || zz == 0.0) {
2689                         throw new NonInvertibleTransformException("Determinant is 0");
2690                     }
2691                     return new Point3D(
2692                             (1.0 / xx) * x,
2693                             (1.0 / yy) * y,
2694                             (1.0 / zz) * z);
2695                 case APPLY_SCALE | APPLY_TRANSLATE:
2696                     if (xx == 0.0 || yy == 0.0 || zz == 0.0) {
2697                         throw new NonInvertibleTransformException("Determinant is 0");
2698                     }
2699                     return new Point3D(
2700                             (1.0 / xx) * x - xt / xx,
2701                             (1.0 / yy) * y - yt / yy,
2702                             (1.0 / zz) * z - zt / zz);
2703                 case APPLY_3D_COMPLEX:
2704                     return super.inverseTransform(x, y, z);
2705             }
2706         }
2707 
2708         @Override
2709         public Point2D inverseDeltaTransform(double x, double y)
2710                 throws NonInvertibleTransformException {
2711             ensureCanTransform2DPoint();
2712 
2713             switch (state2d) {
2714                 default:
2715                     return super.inverseDeltaTransform(x, y);
2716                 case APPLY_SHEAR | APPLY_TRANSLATE:
2717                 case APPLY_SHEAR:
2718                     if (xy == 0.0 || yx == 0.0) {
2719                         throw new NonInvertibleTransformException("Determinant is 0");
2720                     }
2721                     return new Point2D((1.0 / yx) * y, (1.0 / xy) * x);
2722                 case APPLY_SCALE | APPLY_TRANSLATE:
2723                 case APPLY_SCALE:
2724                     if (xx == 0.0 || yy == 0.0) {
2725                         throw new NonInvertibleTransformException("Determinant is 0");
2726                     }
2727                     return new Point2D((1.0 / xx) * x, (1.0 / yy) * y);
2728                 case APPLY_TRANSLATE:
2729                 case APPLY_IDENTITY:
2730                     return new Point2D(x, y);
2731             }
2732         }
2733 
2734         @Override
2735         public Point3D inverseDeltaTransform(double x, double y, double z)
2736                 throws NonInvertibleTransformException {
2737             switch(state3d) {
2738                 default:
2739                     stateError();
2740                     // cannot reach
2741                 case APPLY_NON_3D:
2742                     switch (state2d) {
2743                         default:
2744                             return super.inverseDeltaTransform(x, y, z);
2745                         case APPLY_SHEAR | APPLY_TRANSLATE:
2746                         case APPLY_SHEAR:
2747                             if (xy == 0.0 || yx == 0.0) {
2748                                 throw new NonInvertibleTransformException(
2749                                         "Determinant is 0");
2750                             }
2751                             return new Point3D(
2752                                     (1.0 / yx) * y,
2753                                     (1.0 / xy) * x, z);
2754                         case APPLY_SCALE | APPLY_TRANSLATE:
2755                         case APPLY_SCALE:
2756                             if (xx == 0.0 || yy == 0.0) {
2757                                 throw new NonInvertibleTransformException(
2758                                         "Determinant is 0");
2759                             }
2760                             return new Point3D(
2761                                     (1.0 / xx) * x,
2762                                     (1.0 / yy) * y, z);
2763                         case APPLY_TRANSLATE:
2764                         case APPLY_IDENTITY:
2765                             return new Point3D(x, y, z);
2766                     }
2767 
2768                 case APPLY_TRANSLATE:
2769                     return new Point3D(x, y, z);
2770                 case APPLY_SCALE | APPLY_TRANSLATE:
2771                 case APPLY_SCALE:
2772                     if (xx == 0.0 || yy == 0.0 || zz == 0.0) {
2773                         throw new NonInvertibleTransformException("Determinant is 0");
2774                     }
2775                     return new Point3D(
2776                             (1.0 / xx) * x,
2777                             (1.0 / yy) * y,
2778                             (1.0 / zz) * z);
2779                 case APPLY_3D_COMPLEX:
2780                     return super.inverseDeltaTransform(x, y, z);
2781             }
2782         }
2783 
2784         /* *************************************************************************
2785          *                                                                         *
2786          *                               Other API                                 *
2787          *                                                                         *
2788          **************************************************************************/
2789 
2790         @Override
2791         public String toString() {
2792            final StringBuilder sb = new StringBuilder("Transform [\n");
2793 
2794             sb.append("\t").append(xx);
2795             sb.append(", ").append(xy);
2796             sb.append(", ").append(xz);
2797             sb.append(", ").append(xt);
2798             sb.append('\n');
2799             sb.append("\t").append(yx);
2800             sb.append(", ").append(yy);
2801             sb.append(", ").append(yz);
2802             sb.append(", ").append(yt);
2803             sb.append('\n');
2804             sb.append("\t").append(zx);
2805             sb.append(", ").append(zy);
2806             sb.append(", ").append(zz);
2807             sb.append(", ").append(zt);
2808 
2809             return sb.append("\n]").toString();
2810         }
2811 
2812         /* *************************************************************************
2813          *                                                                         *
2814          *                    Internal implementation stuff                        *
2815          *                                                                         *
2816          **************************************************************************/
2817 
2818         private void updateState() {
2819             updateState2D();
2820 
2821             state3d = APPLY_NON_3D;
2822 
2823             if (xz != 0.0 ||
2824                 yz != 0.0 ||
2825                 zx != 0.0 ||
2826                 zy != 0.0)
2827             {
2828                 state3d = APPLY_3D_COMPLEX;
2829             } else {
2830                 if ((state2d & APPLY_SHEAR) == 0) {
2831                     if (zt != 0.0) {
2832                         state3d |= APPLY_TRANSLATE;
2833                     }
2834                     if (zz != 1.0) {
2835                         state3d |= APPLY_SCALE;
2836                     }
2837                     if (state3d != APPLY_NON_3D) {
2838                         state3d |= (state2d & (APPLY_SCALE | APPLY_TRANSLATE));
2839                     }
2840                 } else {
2841                     if (zz != 1.0 || zt != 0.0) {
2842                         state3d = APPLY_3D_COMPLEX;
2843                     }
2844                 }
2845             }
2846         }
2847 
2848         private void updateState2D() {
2849             if (xy == 0.0 && yx == 0.0) {
2850                 if (xx == 1.0 && yy == 1.0) {
2851                     if (xt == 0.0 && yt == 0.0) {
2852                         state2d = APPLY_IDENTITY;
2853                     } else {
2854                         state2d = APPLY_TRANSLATE;
2855                     }
2856                 } else {
2857                     if (xt == 0.0 && yt == 0.0) {
2858                         state2d = APPLY_SCALE;
2859                     } else {
2860                         state2d = (APPLY_SCALE | APPLY_TRANSLATE);
2861                     }
2862                 }
2863             } else {
2864                 if (xx == 0.0 && yy == 0.0) {
2865                     if (xt == 0.0 && yt == 0.0) {
2866                         state2d = APPLY_SHEAR;
2867                     } else {
2868                         state2d = (APPLY_SHEAR | APPLY_TRANSLATE);
2869                     }
2870                 } else {
2871                     if (xt == 0.0 && yt == 0.0) {
2872                         state2d = (APPLY_SHEAR | APPLY_SCALE);
2873                     } else {
2874                         state2d = (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE);
2875                     }
2876                 }
2877             }
2878         }
2879 
2880         void ensureCanTransform2DPoint() throws IllegalStateException {
2881             if (state3d != APPLY_NON_3D) {
2882                 throw new IllegalStateException("Cannot transform 2D point "
2883                         + "with a 3D transform");
2884             }
2885         }
2886 
2887         private static void stateError() {
2888             throw new InternalError("missing case in a switch");
2889         }
2890 
2891 
2892         @Override
2893         void apply(final Affine3D trans) {
2894             trans.concatenate(xx, xy, xz, xt,
2895                               yx, yy, yz, yt,
2896                               zx, zy, zz, zt);
2897         }
2898 
2899         @Override
2900         BaseTransform derive(final BaseTransform trans) {
2901             return trans.deriveWithConcatenation(xx, xy, xz, xt,
2902                                                  yx, yy, yz, yt,
2903                                                  zx, zy, zz, zt);
2904         }
2905 
2906         /**
2907          * Used only by tests to check the 2d matrix state
2908          */
2909         int getState2d() {
2910             return state2d;
2911         }
2912 
2913         /**
2914          * Used only by tests to check the 3d matrix state
2915          */
2916         int getState3d() {
2917             return state3d;
2918         }
2919 
2920     }
2921 
2922 }