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