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 
  29 import com.sun.javafx.geom.transform.Affine3D;
  30 import com.sun.javafx.geom.transform.BaseTransform;
  31 import javafx.beans.property.DoubleProperty;
  32 import javafx.beans.property.SimpleDoubleProperty;
  33 import javafx.geometry.Point2D;
  34 import javafx.geometry.Point3D;
  35 
  36 // PENDING_DOC_REVIEW of this whole class
  37 /**
  38  * <p>
  39  * The {@code Affine} class represents a general affine transform. An affine
  40  * transform performs a linear mapping from 2D/3D coordinates to other 2D/3D
  41  * coordinates while preserving the "straightness" and "parallelness"
  42  * of lines.
  43  * Affine transformations can be constructed using sequence rotations,
  44  * translations, scales, and shears.</p>
  45  *
  46  * <p>
  47  * For simple transformations application developers should use the
  48  * specific {@code Translate}, {@code Scale}, {@code Rotate}, or {@code Shear}
  49  * transforms, which are more lightweight and thus more optimal for this simple
  50  * purpose. The {@code Affine} class, on the other hand, has the advantage
  51  * of being able to represent a general affine transform and perform matrix
  52  * operations on it in place, so it fits better for more complex transformation
  53  * usages.</p>
  54 
  55  * <p>
  56  * Such a coordinate transformation can be represented by a 3 row by
  57  * 4 column matrix. This matrix transforms source coordinates {@code (x,y,z)}
  58  * into destination coordinates {@code (x',y',z')} by considering
  59  * them to be a column vector and multiplying the coordinate vector
  60  * by the matrix according to the following process:</p>
  61  *
  62  * <pre>
  63  *      [ x']   [  mxx  mxy  mxz  tx  ] [ x ]   [ mxx * x + mxy * y + mxz * z + tx ]
  64  *      [ y'] = [  myx  myy  myz  ty  ] [ y ] = [ myx * x + myy * y + myz * z + ty ]
  65  *      [ z']   [  mzx  mzy  mzz  tz  ] [ z ]   [ mzx * x + mzy * y + mzz * z + tz ]
  66  *                                      [ 1 ]
  67  * </pre>
  68  * @since JavaFX 2.0
  69  */
  70 public class Affine extends Transform {
  71 
  72     /**
  73      * Tracks atomic changes of more elements.
  74      */
  75     AffineAtomicChange atomicChange = new AffineAtomicChange();
  76 
  77     /**
  78      * This constant is used for the internal state2d variable to indicate
  79      * that no calculations need to be performed and that the source
  80      * coordinates only need to be copied to their destinations to
  81      * complete the transformation equation of this transform.
  82      * @see #state2d
  83      */
  84     private static final int APPLY_IDENTITY = 0;
  85 
  86     /**
  87      * This constant is used for the internal state2d and state3d variables
  88      * that the translation components of the matrix need to be added
  89      * to complete the transformation equation of this transform.
  90      * @see #state2d
  91      * @see #state3d
  92      */
  93     private static final int APPLY_TRANSLATE = 1;
  94 
  95     /**
  96      * This constant is used for the internal state2d and state3d variables
  97      * to indicate that the scaling components of the matrix need
  98      * to be factored in to complete the transformation equation of
  99      * this transform. If the APPLY_SHEAR bit is also set then it
 100      * indicates that the scaling components are 0.0.  If the
 101      * APPLY_SHEAR bit is not also set then it indicates that the
 102      * scaling components are not 1.0. If neither the APPLY_SHEAR
 103      * nor the APPLY_SCALE bits are set then the scaling components
 104      * are 1.0, which means that the x and y components contribute
 105      * to the transformed coordinate, but they are not multiplied by
 106      * any scaling factor.
 107      * @see #state2d
 108      * @see #state3d
 109      */
 110     private static final int APPLY_SCALE = 2;
 111 
 112     /**
 113      * This constant is used for the internal state2d variable to indicate
 114      * that the shearing components of the matrix (mxy and myx) need
 115      * to be factored in to complete the transformation equation of this
 116      * transform.  The presence of this bit in the state variable changes
 117      * the interpretation of the APPLY_SCALE bit as indicated in its
 118      * documentation.
 119      * @see #state2d
 120      */
 121     private static final int APPLY_SHEAR = 4;
 122 
 123     /**
 124      * This constant is used for the internal state3d variable to indicate
 125      * that the matrix represents a 2D-only transform.
 126      */
 127     private static final int APPLY_NON_3D = 0;
 128 
 129     /**
 130      * This constant is used for the internal state3d variable to indicate
 131      * that the matrix is not in any of the recognized simple states
 132      * and therefore needs a full usage of all elements to complete
 133      * the transformation equation of this transform.
 134      */
 135     private static final int APPLY_3D_COMPLEX = 4;
 136 
 137     /**
 138      * If this is a 2D transform, this field keeps track of which components
 139      * of the matrix need to be applied when performing a transformation.
 140      * If this is a 3D transform, its state is store in the state3d variable
 141      * and value of state2d is undefined.
 142      * @see #APPLY_IDENTITY
 143      * @see #APPLY_TRANSLATE
 144      * @see #APPLY_SCALE
 145      * @see #APPLY_SHEAR
 146      * @see #state3d
 147      * @see #updateState()
 148      */
 149     private transient int state2d;
 150 
 151     /**
 152      * This field keeps track of whether or not this transform is 3D and if so
 153      * it tracks several simple states that can be treated faster. If the state
 154      * is equal to APPLY_NON_3D, this is a 2D transform with its state stored
 155      * in the state2d variable. If the state is equal to APPLY_3D_COMPLEX,
 156      * the matrix is not in any of the simple states and needs to be fully
 157      * processed. Otherwise we recognize scale (mxx, myy and mzz
 158      * are not all equal to 1.0), translation (tx, ty and tz are not all
 159      * equal to 0.0) and their combination. In one of the simple states
 160      * all of the other elements of the matrix are equal to 0.0 (not even
 161      * shear is allowed).
 162      * @see #APPLY_NON_3D
 163      * @see #APPLY_TRANSLATE
 164      * @see #APPLY_SCALE
 165      * @see #APPLY_3D_COMPLEX
 166      * @see #state2d
 167      * @see #updateState()
 168      */
 169     private transient int state3d;
 170 
 171     // Variables used for the elements until user requests creation
 172     // of the heavy-weight properties
 173     private double xx;
 174     private double xy;
 175     private double xz;
 176     private double yx;
 177     private double yy;
 178     private double yz;
 179     private double zx;
 180     private double zy;
 181     private double zz;
 182     private double xt;
 183     private double yt;
 184     private double zt;
 185 
 186     /**
 187      * Creates a new instance of {@code Affine} containing an identity transform.
 188      */
 189     public Affine() {
 190         xx = yy = zz = 1.0;
 191     }
 192 
 193     /**
 194      * Creates a new instance of {@code Affine} filled with the values from
 195      * the specified transform.
 196      * @param transform transform whose matrix is to be filled to the new
 197      *        instance
 198      * @throws NullPointerException if the specified {@code transform} is null
 199      * @since JavaFX 8.0
 200      */
 201     public Affine(Transform transform) {
 202         this(transform.getMxx(), transform.getMxy(), transform.getMxz(),
 203                                                              transform.getTx(),
 204              transform.getMyx(), transform.getMyy(), transform.getMyz(),
 205                                                              transform.getTy(),
 206              transform.getMzx(), transform.getMzy(), transform.getMzz(),
 207                                                              transform.getTz());
 208     }
 209 
 210     /**
 211      * Creates a new instance of {@code Affine} with a 2D transform specified
 212      * by the element values.
 213      * @param mxx the X coordinate scaling element
 214      * @param mxy the XY coordinate element
 215      * @param tx the X coordinate translation element
 216      * @param myx the YX coordinate element
 217      * @param myy the Y coordinate scaling element
 218      * @param ty the Y coordinate translation element
 219      * @since JavaFX 8.0
 220      */
 221     public Affine(double mxx, double mxy, double tx,
 222                   double myx, double myy, double ty) {
 223         xx = mxx;
 224         xy = mxy;
 225         xt = tx;
 226 
 227         yx = myx;
 228         yy = myy;
 229         yt = ty;
 230 
 231         zz = 1.0;
 232 
 233         updateState2D();
 234     }
 235 
 236     /**
 237      * Creates a new instance of {@code Affine} with a transform specified
 238      * by the element values.
 239      * @param mxx the X coordinate scaling element
 240      * @param mxy the XY coordinate element
 241      * @param mxz the XZ coordinate element
 242      * @param tx the X coordinate translation element
 243      * @param myx the YX coordinate element
 244      * @param myy the Y coordinate scaling element
 245      * @param myz the YZ coordinate element
 246      * @param ty the Y coordinate translation element
 247      * @param mzx the ZX coordinate element
 248      * @param mzy the ZY coordinate element
 249      * @param mzz the Z coordinate scaling element
 250      * @param tz the Z coordinate translation element
 251      * @since JavaFX 8.0
 252      */
 253     public Affine(double mxx, double mxy, double mxz, double tx,
 254                   double myx, double myy, double myz, double ty,
 255                   double mzx, double mzy, double mzz, double tz) {
 256         xx = mxx;
 257         xy = mxy;
 258         xz = mxz;
 259         xt = tx;
 260 
 261         yx = myx;
 262         yy = myy;
 263         yz = myz;
 264         yt = ty;
 265 
 266         zx = mzx;
 267         zy = mzy;
 268         zz = mzz;
 269         zt = tz;
 270 
 271         updateState();
 272     }
 273 
 274     /**
 275      * Creates a new instance of {@code Affine} with a transformation matrix
 276      * specified by an array.
 277      * @param matrix array containing the flattened transformation matrix
 278      * @param type type of matrix contained in the array
 279      * @param offset offset of the first element in the array
 280      * @throws IndexOutOfBoundsException if the array is too short for
 281      *         the specified {@code type} and {@code offset}
 282      * @throws IllegalArgumentException if the specified matrix is not affine
 283      *         (the last line of a 2D 3x3 matrix is not {@code [0, 0, 1]} or
 284      *          the last line of a 3D 4x4 matrix is not {@code [0, 0, 0, 1]}.
 285      * @throws NullPointerException if the specified {@code matrix}
 286      *         or {@code type} is null
 287      * @since JavaFX 8.0
 288      */
 289     public Affine(double[] matrix, MatrixType type, int offset) {
 290         if (matrix.length < offset + type.elements()) {
 291             throw new IndexOutOfBoundsException("The array is too short.");
 292         }
 293 
 294         switch(type) {
 295             default:
 296                 stateError();
 297                 // cannot reach
 298             case MT_2D_3x3:
 299                 if (matrix[offset + 6] != 0.0 ||
 300                         matrix[offset + 7] != 0.0 ||
 301                         matrix[offset + 8] != 1.0) {
 302                     throw new IllegalArgumentException("The matrix is "
 303                             + "not affine");
 304                 }
 305                 // fall-through
 306             case MT_2D_2x3:
 307                 xx = matrix[offset++];
 308                 xy = matrix[offset++];
 309                 xt = matrix[offset++];
 310                 yx = matrix[offset++];
 311                 yy = matrix[offset++];
 312                 yt = matrix[offset];
 313                 zz = 1.0;
 314                 updateState2D();
 315                 return;
 316             case MT_3D_4x4:
 317                 if (matrix[offset + 12] != 0.0 ||
 318                         matrix[offset + 13] != 0.0 ||
 319                         matrix[offset + 14] != 0.0 ||
 320                         matrix[offset + 15] != 1.0) {
 321                     throw new IllegalArgumentException("The matrix is "
 322                             + "not affine");
 323                 }
 324                 // fall-through
 325             case MT_3D_3x4:
 326                 xx = matrix[offset++];
 327                 xy = matrix[offset++];
 328                 xz = matrix[offset++];
 329                 xt = matrix[offset++];
 330                 yx = matrix[offset++];
 331                 yy = matrix[offset++];
 332                 yz = matrix[offset++];
 333                 yt = matrix[offset++];
 334                 zx = matrix[offset++];
 335                 zy = matrix[offset++];
 336                 zz = matrix[offset++];
 337                 zt = matrix[offset];
 338                 updateState();
 339                 return;
 340         }
 341     }
 342 
 343     /**
 344      * Defines the X coordinate scaling element of the 3x4 matrix.
 345      */
 346     private AffineElementProperty mxx;
 347 
 348 
 349     public final void setMxx(double value) {
 350         if (mxx == null) {
 351             if (xx != value) {
 352                 xx = value;
 353                 postProcessChange();
 354             }
 355         } else {
 356             mxxProperty().set(value);
 357         }
 358     }
 359 
 360     @Override
 361     public final double getMxx() {
 362         return mxx == null ? xx : mxx.get();
 363     }
 364 
 365     public final DoubleProperty mxxProperty() {
 366         if (mxx == null) {
 367             mxx = new AffineElementProperty(xx) {
 368                 @Override
 369                 public Object getBean() {
 370                     return Affine.this;
 371                 }
 372 
 373                 @Override
 374                 public String getName() {
 375                     return "mxx";
 376                 }
 377             };
 378         }
 379         return mxx;
 380     }
 381 
 382     /**
 383      * Defines the XY coordinate element of the 3x4 matrix.
 384      */
 385     private AffineElementProperty mxy;
 386 
 387 
 388     public final void setMxy(double value) {
 389         if (mxy == null) {
 390             if (xy != value) {
 391                 xy = value;
 392                 postProcessChange();
 393             }
 394         } else {
 395             mxyProperty().set(value);
 396         }
 397     }
 398 
 399     @Override
 400     public final double getMxy() {
 401         return mxy == null ? xy : mxy.get();
 402     }
 403 
 404     public final DoubleProperty mxyProperty() {
 405         if (mxy == null) {
 406             mxy = new AffineElementProperty(xy) {
 407                 @Override
 408                 public Object getBean() {
 409                     return Affine.this;
 410                 }
 411 
 412                 @Override
 413                 public String getName() {
 414                     return "mxy";
 415                 }
 416             };
 417         }
 418         return mxy;
 419     }
 420 
 421     /**
 422      * Defines the XZ coordinate element of the 3x4 matrix.
 423      */
 424     private AffineElementProperty mxz;
 425 
 426 
 427     public final void setMxz(double value) {
 428         if (mxz == null) {
 429             if (xz != value) {
 430                 xz = value;
 431                 postProcessChange();
 432             }
 433         } else {
 434             mxzProperty().set(value);
 435         }
 436     }
 437 
 438     @Override
 439     public final double getMxz() {
 440         return mxz == null ? xz : mxz.get();
 441     }
 442 
 443     public final DoubleProperty mxzProperty() {
 444         if (mxz == null) {
 445             mxz = new AffineElementProperty(xz) {
 446                 @Override
 447                 public Object getBean() {
 448                     return Affine.this;
 449                 }
 450 
 451                 @Override
 452                 public String getName() {
 453                     return "mxz";
 454                 }
 455             };
 456         }
 457         return mxz;
 458     }
 459 
 460     /**
 461      * Defines the X coordinate translation element of the 3x4 matrix.
 462      */
 463     private AffineElementProperty tx;
 464 
 465 
 466     public final void setTx(double value) {
 467         if (tx == null) {
 468             if (xt != value) {
 469                 xt = value;
 470                 postProcessChange();
 471             }
 472         } else {
 473             txProperty().set(value);
 474         }
 475     }
 476 
 477     @Override
 478     public final double getTx() {
 479         return tx == null ? xt : tx.get();
 480     }
 481 
 482     public final DoubleProperty txProperty() {
 483         if (tx == null) {
 484             tx = new AffineElementProperty(xt) {
 485                 @Override
 486                 public Object getBean() {
 487                     return Affine.this;
 488                 }
 489 
 490                 @Override
 491                 public String getName() {
 492                     return "tx";
 493                 }
 494             };
 495         }
 496         return tx;
 497     }
 498 
 499     /**
 500      * Defines the YX coordinate element of the 3x4 matrix.
 501      */
 502     private AffineElementProperty myx;
 503 
 504 
 505     public final void setMyx(double value) {
 506         if (myx == null) {
 507             if (yx != value) {
 508                 yx = value;
 509                 postProcessChange();
 510             }
 511         } else {
 512             myxProperty().set(value);
 513         }
 514     }
 515 
 516     @Override
 517     public final double getMyx() {
 518         return myx == null ? yx : myx.get();
 519     }
 520 
 521     public final DoubleProperty myxProperty() {
 522         if (myx == null) {
 523             myx = new AffineElementProperty(yx) {
 524                 @Override
 525                 public Object getBean() {
 526                     return Affine.this;
 527                 }
 528 
 529                 @Override
 530                 public String getName() {
 531                     return "myx";
 532                 }
 533             };
 534         }
 535         return myx;
 536     }
 537 
 538     /**
 539      * Defines the Y coordinate scaling element of the 3x4 matrix.
 540      */
 541     private AffineElementProperty myy;
 542 
 543 
 544     public final void setMyy(double value) {
 545         if (myy == null) {
 546             if (yy != value) {
 547                 yy = value;
 548                 postProcessChange();
 549             }
 550         } else{
 551             myyProperty().set(value);
 552         }
 553     }
 554 
 555     @Override
 556     public final double getMyy() {
 557         return myy == null ? yy : myy.get();
 558     }
 559 
 560     public final DoubleProperty myyProperty() {
 561         if (myy == null) {
 562             myy = new AffineElementProperty(yy) {
 563                 @Override
 564                 public Object getBean() {
 565                     return Affine.this;
 566                 }
 567 
 568                 @Override
 569                 public String getName() {
 570                     return "myy";
 571                 }
 572             };
 573         }
 574         return myy;
 575     }
 576 
 577     /**
 578      * Defines the YZ coordinate element of the 3x4 matrix.
 579      */
 580     private AffineElementProperty myz;
 581 
 582 
 583     public final void setMyz(double value) {
 584         if (myz == null) {
 585             if (yz != value) {
 586                 yz = value;
 587                 postProcessChange();
 588             }
 589         } else {
 590             myzProperty().set(value);
 591         }
 592     }
 593 
 594     @Override
 595     public final double getMyz() {
 596         return myz == null ? yz : myz.get();
 597     }
 598 
 599     public final DoubleProperty myzProperty() {
 600         if (myz == null) {
 601             myz = new AffineElementProperty(yz) {
 602                 @Override
 603                 public Object getBean() {
 604                     return Affine.this;
 605                 }
 606 
 607                 @Override
 608                 public String getName() {
 609                     return "myz";
 610                 }
 611             };
 612         }
 613         return myz;
 614     }
 615 
 616     /**
 617      * Defines the Y coordinate translation element of the 3x4 matrix.
 618      */
 619     private AffineElementProperty ty;
 620 
 621 
 622     public final void setTy(double value) {
 623         if (ty == null) {
 624             if (yt != value) {
 625                 yt = value;
 626                 postProcessChange();
 627             }
 628         } else {
 629             tyProperty().set(value);
 630         }
 631     }
 632 
 633     @Override
 634     public final double getTy() {
 635         return ty == null ? yt : ty.get();
 636     }
 637 
 638     public final DoubleProperty tyProperty() {
 639         if (ty == null) {
 640             ty = new AffineElementProperty(yt) {
 641                 @Override
 642                 public Object getBean() {
 643                     return Affine.this;
 644                 }
 645 
 646                 @Override
 647                 public String getName() {
 648                     return "ty";
 649                 }
 650             };
 651         }
 652         return ty;
 653     }
 654 
 655     /**
 656      * Defines the ZX coordinate element of the 3x4 matrix.
 657      */
 658     private AffineElementProperty mzx;
 659 
 660 
 661     public final void setMzx(double value) {
 662         if (mzx == null) {
 663             if (zx != value) {
 664                 zx = value;
 665                 postProcessChange();
 666             }
 667         } else {
 668             mzxProperty().set(value);
 669         }
 670     }
 671 
 672     @Override
 673     public final double getMzx() {
 674         return mzx == null ? zx : mzx.get();
 675     }
 676 
 677     public final DoubleProperty mzxProperty() {
 678         if (mzx == null) {
 679             mzx = new AffineElementProperty(zx) {
 680                 @Override
 681                 public Object getBean() {
 682                     return Affine.this;
 683                 }
 684 
 685                 @Override
 686                 public String getName() {
 687                     return "mzx";
 688                 }
 689             };
 690         }
 691         return mzx;
 692     }
 693 
 694     /**
 695      * Defines the ZY coordinate element of the 3x4 matrix.
 696      */
 697     private AffineElementProperty mzy;
 698 
 699 
 700     public final void setMzy(double value) {
 701         if (mzy == null) {
 702             if (zy != value) {
 703                 zy = value;
 704                 postProcessChange();
 705             }
 706         } else {
 707             mzyProperty().set(value);
 708         }
 709     }
 710 
 711     @Override
 712     public final double getMzy() {
 713         return mzy == null ? zy : mzy.get();
 714     }
 715 
 716     public final DoubleProperty mzyProperty() {
 717         if (mzy == null) {
 718             mzy = new AffineElementProperty(zy) {
 719                 @Override
 720                 public Object getBean() {
 721                     return Affine.this;
 722                 }
 723 
 724                 @Override
 725                 public String getName() {
 726                     return "mzy";
 727                 }
 728             };
 729         }
 730         return mzy;
 731     }
 732 
 733     /**
 734      * Defines the Z coordinate scaling element of the 3x4 matrix.
 735      */
 736     private AffineElementProperty mzz;
 737 
 738 
 739     public final void setMzz(double value) {
 740         if (mzz == null) {
 741             if (zz != value) {
 742                 zz = value;
 743                 postProcessChange();
 744             }
 745         } else {
 746             mzzProperty().set(value);
 747         }
 748     }
 749 
 750     @Override
 751     public final double getMzz() {
 752         return mzz == null ? zz : mzz.get();
 753     }
 754 
 755     public final DoubleProperty mzzProperty() {
 756         if (mzz == null) {
 757             mzz = new AffineElementProperty(zz) {
 758                 @Override
 759                 public Object getBean() {
 760                     return Affine.this;
 761                 }
 762 
 763                 @Override
 764                 public String getName() {
 765                     return "mzz";
 766                 }
 767             };
 768         }
 769         return mzz;
 770     }
 771 
 772     /**
 773      * Defines the Z coordinate translation element of the 3x4 matrix.
 774      */
 775     private AffineElementProperty tz;
 776 
 777 
 778     public final void setTz(double value) {
 779         if (tz == null) {
 780             if (zt != value) {
 781                 zt = value;
 782                 postProcessChange();
 783             }
 784         } else {
 785             tzProperty().set(value);
 786         }
 787     }
 788 
 789     @Override
 790     public final double getTz() {
 791         return tz == null ? zt : tz.get();
 792     }
 793 
 794     public final DoubleProperty tzProperty() {
 795         if (tz == null) {
 796             tz = new AffineElementProperty(zt) {
 797                 @Override
 798                 public Object getBean() {
 799                     return Affine.this;
 800                 }
 801 
 802                 @Override
 803                 public String getName() {
 804                     return "tz";
 805                 }
 806             };
 807         }
 808         return tz;
 809     }
 810 
 811     /**
 812      * Sets the specified element of the transformation matrix.
 813      * @param type type of matrix to work with
 814      * @param row zero-based row number
 815      * @param column zero-based column number
 816      * @param value new value of the specified transformation matrix element
 817      * @throws IndexOutOfBoundsException if the indices are not within
 818      *         the specified matrix type
 819      * @throws IllegalArgumentException if setting the value would break
 820      *         transform's affinity (for convenience the method allows to set
 821      *         the elements of the last line of a 2D 3x3 matrix to
 822      *         {@code [0, 0, 1]} and the elements of the last line
 823      *         of a 3D 4x4 matrix to {@code [0, 0, 0, 1]}).
 824      * @throws NullPointerException if the specified {@code type} is null
 825      * @since JavaFX 8.0
 826      */
 827     public void setElement(MatrixType type, int row, int column, double value) {
 828         if (row < 0 || row >= type.rows() ||
 829                 column < 0 || column >= type.columns()) {
 830             throw new IndexOutOfBoundsException("Index outside of affine "
 831                     + "matrix " + type + ": [" + row + ", " + column + "]");
 832         }
 833         switch(type) {
 834             default:
 835                 stateError();
 836                 // cannot reach
 837             case MT_2D_2x3:
 838                 // fall-through
 839             case MT_2D_3x3:
 840                 if (!isType2D()) {
 841                     throw new IllegalArgumentException("Cannot access 2D matrix "
 842                             + "of a 3D transform");
 843                 }
 844                 switch(row) {
 845                     case 0:
 846                         switch(column) {
 847                             case 0: setMxx(value); return;
 848                             case 1: setMxy(value); return;
 849                             case 2: setTx(value); return;
 850                         }
 851                     case 1:
 852                         switch(column) {
 853                             case 0: setMyx(value); return;
 854                             case 1: setMyy(value); return;
 855                             case 2: setTy(value); return;
 856                         }
 857                     case 2:
 858                         switch(column) {
 859                             case 0: if (value == 0.0) return; else break;
 860                             case 1: if (value == 0.0) return; else break;
 861                             case 2: if (value == 1.0) return; else break;
 862                         }
 863                 }
 864                 break;
 865             case MT_3D_3x4:
 866                 // fall-through
 867             case MT_3D_4x4:
 868                 switch(row) {
 869                     case 0:
 870                         switch(column) {
 871                             case 0: setMxx(value); return;
 872                             case 1: setMxy(value); return;
 873                             case 2: setMxz(value); return;
 874                             case 3: setTx(value); return;
 875                         }
 876                     case 1:
 877                         switch(column) {
 878                             case 0: setMyx(value); return;
 879                             case 1: setMyy(value); return;
 880                             case 2: setMyz(value); return;
 881                             case 3: setTy(value); return;
 882                         }
 883                     case 2:
 884                         switch(column) {
 885                             case 0: setMzx(value); return;
 886                             case 1: setMzy(value); return;
 887                             case 2: setMzz(value); return;
 888                             case 3: setTz(value); return;
 889                         }
 890                     case 3:
 891                         switch(column) {
 892                             case 0: if (value == 0.0) return; else break;
 893                             case 1: if (value == 0.0) return; else break;
 894                             case 2: if (value == 0.0) return; else break;
 895                             case 3: if (value == 1.0) return; else break;
 896                         }
 897                 }
 898                 break;
 899         }
 900         // reaches here when last line is set to something else than 0 .. 0 1
 901         throw new IllegalArgumentException("Cannot set affine matrix " + type +
 902                 " element " + "[" + row + ", " + column + "] to " + value);
 903     }
 904 
 905     /**
 906      * Affine element property which handles the atomic changes of more
 907      * properties.
 908      */
 909     private class AffineElementProperty extends SimpleDoubleProperty {
 910 
 911         private boolean needsValueChangedEvent = false;
 912         private double oldValue;
 913 
 914         public AffineElementProperty(double initialValue) {
 915             super(initialValue);
 916         }
 917 
 918         @Override
 919         public void invalidated() {
 920             // if an atomic change runs, postpone the change notifications
 921             if (!atomicChange.runs()) {
 922                 updateState();
 923                 transformChanged();
 924             }
 925         }
 926 
 927         @Override
 928         protected void fireValueChangedEvent() {
 929             // if an atomic change runs, postpone the change notifications
 930             if (!atomicChange.runs()) {
 931                 super.fireValueChangedEvent();
 932             } else {
 933                 needsValueChangedEvent = true;
 934             }
 935         }
 936 
 937         /**
 938          * Called before an atomic change
 939          */
 940         private void preProcessAtomicChange() {
 941             // remember the value before an atomic change
 942             oldValue = get();
 943         }
 944 
 945         /**
 946          * Called after an atomic change
 947          */
 948         private void postProcessAtomicChange() {
 949             // if there was a change notification  during the atomic change,
 950             // fire it now
 951             if (needsValueChangedEvent) {
 952                 needsValueChangedEvent = false;
 953                 // the value might have change back an forth
 954                 // (happens quite commonly for transforms with pivot)
 955                 if (oldValue != get()) {
 956                     super.fireValueChangedEvent();
 957                 }
 958             }
 959         }
 960     }
 961 
 962     /**
 963      * Called by each element property after value change to
 964      * update the state variables and call transform change notifications.
 965      * If an atomic change runs, this is a NOP and the work is done
 966      * in the end of the entire atomic change.
 967      */
 968     private void postProcessChange() {
 969         if (!atomicChange.runs()) {
 970             updateState();
 971             transformChanged();
 972         }
 973     }
 974 
 975     /* *************************************************************************
 976      *                                                                         *
 977      *                           State getters                                 *
 978      *                                                                         *
 979      **************************************************************************/
 980 
 981     @Override
 982     boolean computeIs2D() {
 983         return (state3d == APPLY_NON_3D);
 984     }
 985 
 986     @Override
 987     boolean computeIsIdentity() {
 988         return state3d == APPLY_NON_3D && state2d == APPLY_IDENTITY;
 989     }
 990 
 991     @Override
 992     public double determinant() {
 993         if (state3d == APPLY_NON_3D) {
 994             return getDeterminant2D();
 995         } else {
 996             return getDeterminant3D();
 997         }
 998     }
 999 
1000     /**
1001      * 2D implementation of {@code determinant()}.
1002      * The behavior is undefined if this is a 3D transform.
1003      * @return determinant
1004      */
1005     private double getDeterminant2D() {
1006         // assert(state3d == APPLY_NON_3D)
1007         switch (state2d) {
1008             default:
1009                 stateError();
1010                 // cannot reach
1011             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
1012             case APPLY_SHEAR | APPLY_SCALE:
1013                 return getMxx() * getMyy() - getMxy() * getMyx();
1014             case APPLY_SHEAR | APPLY_TRANSLATE:
1015             case APPLY_SHEAR:
1016                 return -(getMxy() * getMyx());
1017             case APPLY_SCALE | APPLY_TRANSLATE:
1018             case APPLY_SCALE:
1019                 return getMxx() * getMyy();
1020             case APPLY_TRANSLATE:
1021             case APPLY_IDENTITY:
1022                 return 1.0;
1023         }
1024     }
1025 
1026     /**
1027      * 3D implementation of {@code determinant()}.
1028      * The behavior is undefined if this is a 2D transform.
1029      * @return determinant
1030      */
1031     private double getDeterminant3D() {
1032         // assert(state3d != APPLY_NON_3D)
1033         switch(state3d) {
1034             default:
1035                 stateError();
1036                 // cannot reach
1037             case APPLY_TRANSLATE:
1038                 return 1.0;
1039             case APPLY_SCALE:
1040             case APPLY_SCALE | APPLY_TRANSLATE:
1041                 return getMxx() * getMyy() * getMzz();
1042             case APPLY_3D_COMPLEX:
1043                 final double myx = getMyx();
1044                 final double myy = getMyy();
1045                 final double myz = getMyz();
1046                 final double mzx = getMzx();
1047                 final double mzy = getMzy();
1048                 final double mzz = getMzz();
1049 
1050                 return (getMxx() * (myy * mzz - mzy * myz) +
1051                         getMxy() * (myz * mzx - mzz * myx) +
1052                         getMxz() * (myx * mzy - mzx * myy));
1053         }
1054     }
1055 
1056     /* *************************************************************************
1057      *                                                                         *
1058      *                         Transform creators                              *
1059      *                                                                         *
1060      **************************************************************************/
1061 
1062     @Override
1063     public Transform createConcatenation(Transform transform) {
1064         Affine a = clone();
1065         a.append(transform);
1066         return a;
1067     }
1068 
1069     @Override
1070     public Affine createInverse() throws NonInvertibleTransformException {
1071         Affine t = clone();
1072         t.invert();
1073         return t;
1074     }
1075 
1076     @Override
1077     public Affine clone() {
1078         return new Affine(this);
1079     }
1080 
1081     /* *************************************************************************
1082      *                                                                         *
1083      *                           Matrix setters                                *
1084      *                                                                         *
1085      **************************************************************************/
1086 
1087     /**
1088      * Sets the values of this instance to the values provided by the specified
1089      * transform.
1090      * @param transform transform whose matrix is to be filled to this instance
1091      * @throws NullPointerException if the specified {@code transform} is null
1092      * @since JavaFX 8.0
1093      */
1094     public void setToTransform(Transform transform) {
1095         setToTransform(
1096                 transform.getMxx(), transform.getMxy(),
1097                                          transform.getMxz(), transform.getTx(),
1098                 transform.getMyx(), transform.getMyy(),
1099                                          transform.getMyz(), transform.getTy(),
1100                 transform.getMzx(), transform.getMzy(),
1101                                          transform.getMzz(), transform.getTz());
1102     }
1103 
1104     /**
1105      * Sets the values of this instance to the 2D transform specified
1106      * by the element values.
1107      * @param mxx the X coordinate scaling element
1108      * @param mxy the XY coordinate element
1109      * @param tx the X coordinate translation element
1110      * @param myx the YX coordinate element
1111      * @param myy the Y coordinate scaling element
1112      * @param ty the Y coordinate translation element
1113      * @since JavaFX 8.0
1114      */
1115     public void setToTransform(double mxx, double mxy, double tx,
1116                                double myx, double myy, double ty) {
1117         setToTransform(mxx, mxy, 0.0, tx,
1118                 myx, myy, 0.0, ty,
1119                 0.0, 0.0, 1.0, 0.0);
1120     }
1121 
1122     /**
1123      * Sets the values of this instance to the transform specified
1124      * by the element values.
1125      * @param mxx the X coordinate scaling element
1126      * @param mxy the XY coordinate element
1127      * @param mxz the XZ coordinate element
1128      * @param tx the X coordinate translation element
1129      * @param myx the YX coordinate element
1130      * @param myy the Y coordinate scaling element
1131      * @param myz the YZ coordinate element
1132      * @param ty the Y coordinate translation element
1133      * @param mzx the ZX coordinate element
1134      * @param mzy the ZY coordinate element
1135      * @param mzz the Z coordinate scaling element
1136      * @param tz the Z coordinate translation element
1137      * @since JavaFX 8.0
1138      */
1139     public void setToTransform(double mxx, double mxy, double mxz, double tx,
1140                                double myx, double myy, double myz, double ty,
1141                                double mzx, double mzy, double mzz, double tz)
1142     {
1143         atomicChange.start();
1144 
1145         setMxx(mxx);
1146         setMxy(mxy);
1147         setMxz(mxz);
1148         setTx(tx);
1149 
1150         setMyx(myx);
1151         setMyy(myy);
1152         setMyz(myz);
1153         setTy(ty);
1154 
1155         setMzx(mzx);
1156         setMzy(mzy);
1157         setMzz(mzz);
1158         setTz(tz);
1159 
1160         updateState();
1161         atomicChange.end();
1162     }
1163 
1164     /**
1165      * Sets the values of this instance to the transformation matrix
1166      * specified by an array.
1167      * @param matrix array containing the flattened transformation matrix
1168      * @param type type of matrix contained in the array
1169      * @param offset offset of the first element in the array
1170      * @throws IndexOutOfBoundsException if the array is too short for
1171      *         the specified {@code type} and {@code offset}
1172      * @throws IllegalArgumentException if the specified matrix is not affine
1173      *         (the last line of a 2D 3x3 matrix is not {@code [0, 0, 1]} or
1174      *          the last line of a 3D 4x4 matrix is not {@code [0, 0, 0, 1]}.
1175      * @throws NullPointerException if the specified {@code matrix}
1176      *         or {@code type} is null
1177      * @since JavaFX 8.0
1178      */
1179     public void setToTransform(double[] matrix, MatrixType type, int offset) {
1180         if (matrix.length < offset + type.elements()) {
1181             throw new IndexOutOfBoundsException("The array is too short.");
1182         }
1183 
1184         switch(type) {
1185             default:
1186                 stateError();
1187                 // cannot reach
1188             case MT_2D_3x3:
1189                 if (matrix[offset + 6] != 0.0 ||
1190                         matrix[offset + 7] != 0.0 ||
1191                         matrix[offset + 8] != 1.0) {
1192                     throw new IllegalArgumentException("The matrix is "
1193                             + "not affine");
1194                 }
1195                 // fall-through
1196             case MT_2D_2x3:
1197                 setToTransform(matrix[offset++], matrix[offset++],
1198                         matrix[offset++], matrix[offset++],
1199                         matrix[offset++], matrix[offset++]);
1200                 return;
1201             case MT_3D_4x4:
1202                 if (matrix[offset + 12] != 0.0 ||
1203                         matrix[offset + 13] != 0.0 ||
1204                         matrix[offset + 14] != 0.0 ||
1205                         matrix[offset + 15] != 1.0) {
1206                     throw new IllegalArgumentException("The matrix is "
1207                             + "not affine");
1208                 }
1209                 // fall-through
1210             case MT_3D_3x4:
1211                 setToTransform(matrix[offset++], matrix[offset++],
1212                         matrix[offset++], matrix[offset++], matrix[offset++],
1213                         matrix[offset++], matrix[offset++], matrix[offset++],
1214                         matrix[offset++], matrix[offset++], matrix[offset++],
1215                         matrix[offset++]);
1216                 return;
1217         }
1218     }
1219 
1220     /**
1221      * Resets this transform to the identity transform.
1222      * @since JavaFX 8.0
1223      */
1224     public void setToIdentity() {
1225         atomicChange.start();
1226 
1227         if (state3d != APPLY_NON_3D) {
1228             setMxx(1.0); setMxy(0.0); setMxz(0.0); setTx(0.0);
1229             setMyx(0.0); setMyy(1.0); setMyz(0.0); setTy(0.0);
1230             setMzx(0.0); setMzy(0.0); setMzz(1.0); setTz(0.0);
1231             state3d = APPLY_NON_3D;
1232             state2d = APPLY_IDENTITY;
1233         } else if (state2d != APPLY_IDENTITY) {
1234             setMxx(1.0); setMxy(0.0); setTx(0.0);
1235             setMyx(0.0); setMyy(1.0); setTy(0.0);
1236             state2d = APPLY_IDENTITY;
1237         }
1238 
1239         atomicChange.end();
1240     }
1241 
1242     /* *************************************************************************
1243      *                                                                         *
1244      *                           Matrix operations                             *
1245      *                                                                         *
1246      **************************************************************************/
1247 
1248 
1249                /* *************************************************
1250                 *                   Inversion                     *
1251                 **************************************************/
1252 
1253     /**
1254      * Inverts this transform in place.
1255      * @throws NonInvertibleTransformException if this transform
1256      *         cannot be inverted
1257      * @since JavaFX 8.0
1258      */
1259     public void invert() throws NonInvertibleTransformException {
1260         atomicChange.start();
1261 
1262         if (state3d == APPLY_NON_3D) {
1263             invert2D();
1264             updateState2D();
1265         } else {
1266             invert3D();
1267             updateState();
1268         }
1269 
1270         atomicChange.end();
1271     }
1272 
1273     /**
1274      * 2D implementation of {@code invert()}.
1275      * The behavior is undefined for a 3D transform.
1276      */
1277     private void invert2D() throws NonInvertibleTransformException {
1278         double Mxx, Mxy, Mxt;
1279         double Myx, Myy, Myt;
1280         double det;
1281         // assert(state3d == APPLY_NON_3D)
1282 
1283         switch (state2d) {
1284             default:
1285                 stateError();
1286                 // cannot reach
1287             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
1288                 Mxx = getMxx(); Mxy = getMxy(); Mxt = getTx();
1289                 Myx = getMyx(); Myy = getMyy(); Myt = getTy();
1290                 det = getDeterminant2D();
1291                 if (det == 0.0) {
1292                     atomicChange.cancel();
1293                     throw new NonInvertibleTransformException("Determinant is 0");
1294                 }
1295                 setMxx(Myy / det);
1296                 setMyx(-Myx / det);
1297                 setMxy(-Mxy / det);
1298                 setMyy(Mxx / det);
1299                 setTx((Mxy * Myt - Myy * Mxt) / det);
1300                 setTy((Myx * Mxt - Mxx * Myt) / det);
1301                 return;
1302             case APPLY_SHEAR | APPLY_SCALE:
1303                 Mxx = getMxx(); Mxy = getMxy();
1304                 Myx = getMyx(); Myy = getMyy();
1305                 det = getDeterminant2D();
1306                 if (det == 0.0) {
1307                     atomicChange.cancel();
1308                     throw new NonInvertibleTransformException("Determinant is 0");
1309                 }
1310                 setMxx(Myy / det);
1311                 setMyx(-Myx / det);
1312                 setMxy(-Mxy / det);
1313                 setMyy(Mxx / det);
1314                 return;
1315             case APPLY_SHEAR | APPLY_TRANSLATE:
1316                 Mxy = getMxy(); Mxt = getTx();
1317                 Myx = getMyx(); Myt = getTy();
1318                 if (Mxy == 0.0 || Myx == 0.0) {
1319                     atomicChange.cancel();
1320                     throw new NonInvertibleTransformException("Determinant is 0");
1321                 }
1322                 setMyx(1.0 / Mxy);
1323                 setMxy(1.0 / Myx);
1324                 setTx(-Myt / Myx);
1325                 setTy(-Mxt / Mxy);
1326                 return;
1327             case APPLY_SHEAR:
1328                 Mxy = getMxy();
1329                 Myx = getMyx();
1330                 if (Mxy == 0.0 || Myx == 0.0) {
1331                     atomicChange.cancel();
1332                     throw new NonInvertibleTransformException("Determinant is 0");
1333                 }
1334                 setMyx(1.0 / Mxy);
1335                 setMxy(1.0 / Myx);
1336                 return;
1337             case APPLY_SCALE | APPLY_TRANSLATE:
1338                 Mxx = getMxx(); Mxt = getTx();
1339                 Myy = getMyy(); Myt = getTy();
1340                 if (Mxx == 0.0 || Myy == 0.0) {
1341                     atomicChange.cancel();
1342                     throw new NonInvertibleTransformException("Determinant is 0");
1343                 }
1344                 setMxx(1.0 / Mxx);
1345                 setMyy(1.0 / Myy);
1346                 setTx(-Mxt / Mxx);
1347                 setTy(-Myt / Myy);
1348                 return;
1349             case APPLY_SCALE:
1350                 Mxx = getMxx();
1351                 Myy = getMyy();
1352                 if (Mxx == 0.0 || Myy == 0.0) {
1353                     atomicChange.cancel();
1354                     throw new NonInvertibleTransformException("Determinant is 0");
1355                 }
1356                 setMxx(1.0 / Mxx);
1357                 setMyy(1.0 / Myy);
1358                 return;
1359             case APPLY_TRANSLATE:
1360                 setTx(-getTx());
1361                 setTy(-getTy());
1362                 return;
1363             case APPLY_IDENTITY:
1364                 return;
1365         }
1366     }
1367 
1368     /**
1369      * 3D implementation of {@code invert()}.
1370      * The behavior is undefined if this is a 2D transform.
1371      */
1372     private void invert3D() throws NonInvertibleTransformException {
1373 
1374         switch(state3d) {
1375             default:
1376                 stateError();
1377                 // cannot reach
1378             case APPLY_TRANSLATE:
1379                 setTx(-getTx());
1380                 setTy(-getTy());
1381                 setTz(-getTz());
1382                 return;
1383             case APPLY_SCALE:
1384                 final double mxx_s = getMxx();
1385                 final double myy_s = getMyy();
1386                 final double mzz_s = getMzz();
1387                 if (mxx_s == 0.0 || myy_s == 0.0 || mzz_s == 0.0) {
1388                     atomicChange.cancel();
1389                     throw new NonInvertibleTransformException("Determinant is 0");
1390                 }
1391                 setMxx(1.0 / mxx_s);
1392                 setMyy(1.0 / myy_s);
1393                 setMzz(1.0 / mzz_s);
1394                 return;
1395             case APPLY_SCALE | APPLY_TRANSLATE:
1396                 final double mxx_st = getMxx();
1397                 final double tx_st = getTx();
1398                 final double myy_st = getMyy();
1399                 final double ty_st = getTy();
1400                 final double mzz_st = getMzz();
1401                 final double tz_st = getTz();
1402                 if (mxx_st == 0.0 || myy_st == 0.0 || mzz_st == 0.0) {
1403                     atomicChange.cancel();
1404                     throw new NonInvertibleTransformException("Determinant is 0");
1405                 }
1406                 setMxx(1.0 / mxx_st);
1407                 setMyy(1.0 / myy_st);
1408                 setMzz(1.0 / mzz_st);
1409                 setTx(-tx_st / mxx_st);
1410                 setTy(-ty_st / myy_st);
1411                 setTz(-tz_st / mzz_st);
1412                 return;
1413             case APPLY_3D_COMPLEX:
1414 
1415                 // InvM = Transpose(Cofactor(M)) / det(M)
1416                 // Cofactor(M) = matrix of cofactors(0..3,0..3)
1417                 // cofactor(r,c) = (-1 if r+c is odd) * minor(r,c)
1418                 // minor(r,c) = det(M with row r and col c removed)
1419                 // For an Affine3D matrix, minor(r, 3) is {0, 0, 0, det}
1420                 // which generates {0, 0, 0, 1} and so can be ignored.
1421 
1422                 final double mxx = getMxx();
1423                 final double mxy = getMxy();
1424                 final double mxz = getMxz();
1425                 final double tx = getTx();
1426                 final double myx = getMyx();
1427                 final double myy = getMyy();
1428                 final double myz = getMyz();
1429                 final double ty = getTy();
1430                 final double mzy = getMzy();
1431                 final double mzx = getMzx();
1432                 final double mzz = getMzz();
1433                 final double tz = getTz();
1434 
1435                 final double det =
1436                         mxx * (myy * mzz - mzy * myz) +
1437                         mxy * (myz * mzx - mzz * myx) +
1438                         mxz * (myx * mzy - mzx * myy);
1439 
1440                 if (det == 0.0) {
1441                     atomicChange.cancel();
1442                     throw new NonInvertibleTransformException("Determinant is 0");
1443                 }
1444 
1445                 final double cxx =   myy * mzz - myz * mzy;
1446                 final double cyx = - myx * mzz + myz * mzx;
1447                 final double czx =   myx * mzy - myy * mzx;
1448                 final double cxt = - mxy * (myz * tz - mzz  * ty)
1449                                    - mxz * (ty  * mzy - tz  * myy)
1450                                    - tx  * (myy * mzz - mzy * myz);
1451                 final double cxy = - mxy * mzz + mxz * mzy;
1452                 final double cyy =   mxx * mzz - mxz * mzx;
1453                 final double czy = - mxx * mzy + mxy * mzx;
1454                 final double cyt =   mxx * (myz * tz  - mzz * ty)
1455                                    + mxz * (ty  * mzx - tz  * myx)
1456                                    + tx  * (myx * mzz - mzx * myz);
1457                 final double cxz =   mxy * myz - mxz * myy;
1458                 final double cyz = - mxx * myz + mxz * myx;
1459                 final double czz =   mxx * myy - mxy * myx;
1460                 final double czt = - mxx * (myy * tz - mzy  * ty)
1461                                    - mxy * (ty  * mzx - tz  * myx)
1462                                    - tx  * (myx * mzy - mzx * myy);
1463 
1464                 setMxx(cxx / det);
1465                 setMxy(cxy / det);
1466                 setMxz(cxz / det);
1467                 setTx(cxt / det);
1468                 setMyx(cyx / det);
1469                 setMyy(cyy / det);
1470                 setMyz(cyz / det);
1471                 setTy(cyt / det);
1472                 setMzx(czx / det);
1473                 setMzy(czy / det);
1474                 setMzz(czz / det);
1475                 setTz(czt / det);
1476                 return;
1477         }
1478     }
1479 
1480                /* *************************************************
1481                 *             General concatenations              *
1482                 **************************************************/
1483 
1484     /**
1485      * <p>
1486      * Appends the specified transform to this instance.
1487      * The operation modifies this transform in a way that applying it to a node
1488      * has the same effect as adding the two transforms to its
1489      * {@code getTransforms()} list, {@code this} transform first and the specified
1490      * {@code transform} second.
1491      * </p><p>
1492      * From the matrix point of view, the transformation matrix of this
1493      * transform is multiplied on the right by the transformation matrix of
1494      * the specified transform.
1495      * </p>
1496      *
1497      * @param transform transform to be appended to this instance
1498      * @throws NullPointerException if the specified {@code transform} is null
1499      * @since JavaFX 8.0
1500      */
1501     public void append(Transform transform) {
1502         transform.appendTo(this);
1503     }
1504 
1505     /**
1506      * <p>
1507      * Appends the 2D transform specified by the element values to this instance.
1508      * The operation modifies this transform in a way that applying it to a node
1509      * has the same effect as adding the two transforms to its
1510      * {@code getTransforms()} list, {@code this} transform first and the specified
1511      * {@code transform} second.
1512      * </p><p>
1513      * From the matrix point of view, the transformation matrix of this
1514      * transform is multiplied on the right by the transformation matrix of
1515      * the specified transform.
1516      * </p>
1517      * @param mxx the X coordinate scaling element of the transform to be
1518      *            appended
1519      * @param mxy the XY coordinate element of the transform to be appended
1520      * @param tx the X coordinate translation element of the transform to be
1521      *            appended
1522      * @param myx the YX coordinate element of the transform to be appended
1523      * @param myy the Y coordinate scaling element of the transform to be
1524      *            appended
1525      * @param ty the Y coordinate translation element of the transform to be
1526      *            appended
1527      * @since JavaFX 8.0
1528      */
1529     public void append(double mxx, double mxy, double tx,
1530                         double myx, double myy, double ty) {
1531 
1532         if (state3d == APPLY_NON_3D) {
1533 
1534             atomicChange.start();
1535 
1536             final double m_xx = getMxx();
1537             final double m_xy = getMxy();
1538             final double m_yx = getMyx();
1539             final double m_yy = getMyy();
1540 
1541             setMxx(m_xx * mxx + m_xy * myx);
1542             setMxy(m_xx * mxy + m_xy * myy);
1543             setTx(m_xx * tx + m_xy * ty + getTx());
1544             setMyx(m_yx * mxx + m_yy * myx);
1545             setMyy(m_yx * mxy + m_yy * myy);
1546             setTy(m_yx * tx + m_yy * ty + getTy());
1547 
1548             updateState();
1549             atomicChange.end();
1550         } else {
1551             append(mxx, mxy, 0.0, tx,
1552                     myx, myy, 0.0, ty,
1553                     0.0, 0.0, 1.0, 0.0);
1554         }
1555     }
1556 
1557     /**
1558      * <p>
1559      * Appends the transform specified by the element values to this instance.
1560      * The operation modifies this transform in a way that applying it to a node
1561      * has the same effect as adding the two transforms to its
1562      * {@code getTransforms()} list, {@code this} transform first and the specified
1563      * {@code transform} second.
1564      * </p><p>
1565      * From the matrix point of view, the transformation matrix of this
1566      * transform is multiplied on the right by the transformation matrix of
1567      * the specified transform.
1568      * </p>
1569      *
1570      * @param mxx the X coordinate scaling element of the transform to be
1571      *            appended
1572      * @param mxy the XY coordinate element of the transform to be appended
1573      * @param mxz the XZ coordinate element of the transform to be appended
1574      * @param tx the X coordinate translation element of the transform to be
1575      *            appended
1576      * @param myx the YX coordinate element of the transform to be appended
1577      * @param myy the Y coordinate scaling element of the transform to be
1578      *            appended
1579      * @param myz the YZ coordinate element of the transform to be appended
1580      * @param ty the Y coordinate translation element of the transform to be
1581      *            appended
1582      * @param mzx the ZX coordinate element of the transform to be appended
1583      * @param mzy the ZY coordinate element of the transform to be appended
1584      * @param mzz the Z coordinate scaling element of the transform to be
1585      *            appended
1586      * @param tz the Z coordinate translation element of the transform to be
1587      *            appended
1588      * @since JavaFX 8.0
1589      */
1590     public void append(double mxx, double mxy, double mxz, double tx,
1591                        double myx, double myy, double myz, double ty,
1592                        double mzx, double mzy, double mzz, double tz)
1593     {
1594         atomicChange.start();
1595 
1596         final double m_xx = getMxx();
1597         final double m_xy = getMxy();
1598         final double m_xz = getMxz();
1599         final double t_x = getTx();
1600         final double m_yx = getMyx();
1601         final double m_yy = getMyy();
1602         final double m_yz = getMyz();
1603         final double t_y = getTy();
1604         final double m_zx = getMzx();
1605         final double m_zy = getMzy();
1606         final double m_zz = getMzz();
1607         final double t_z = getTz();
1608 
1609         setMxx(m_xx * mxx + m_xy * myx + m_xz * mzx);
1610         setMxy(m_xx * mxy + m_xy * myy + m_xz * mzy);
1611         setMxz(m_xx * mxz + m_xy * myz + m_xz * mzz);
1612         setTx( m_xx * tx + m_xy * ty + m_xz * tz + t_x);
1613         setMyx(m_yx * mxx + m_yy * myx + m_yz * mzx);
1614         setMyy(m_yx * mxy + m_yy * myy + m_yz * mzy);
1615         setMyz(m_yx * mxz + m_yy * myz + m_yz * mzz);
1616         setTy( m_yx * tx + m_yy * ty + m_yz * tz + t_y);
1617         setMzx(m_zx * mxx + m_zy * myx + m_zz * mzx);
1618         setMzy(m_zx * mxy + m_zy * myy + m_zz * mzy);
1619         setMzz(m_zx * mxz + m_zy * myz + m_zz * mzz);
1620         setTz( m_zx * tx + m_zy * ty + m_zz * tz + t_z);
1621 
1622         updateState();
1623         atomicChange.end();
1624     }
1625 
1626     /**
1627      * <p>
1628      * Appends the transform specified by the array to this instance.
1629      * The operation modifies this transform in a way that applying it to a node
1630      * has the same effect as adding the two transforms to its
1631      * {@code getTransforms()} list, {@code this} transform first and the specified
1632      * {@code transform} second.
1633      * </p><p>
1634      * From the matrix point of view, the transformation matrix of this
1635      * transform is multiplied on the right by the transformation matrix of
1636      * the specified transform.
1637      * </p>
1638      *
1639      * @param matrix array containing the flattened transformation matrix
1640      *               to be appended
1641      * @param type type of matrix contained in the array
1642      * @param offset offset of the first matrix element in the array
1643      * @throws IndexOutOfBoundsException if the array is too short for
1644      *         the specified {@code type} and {@code offset}
1645      * @throws IllegalArgumentException if the specified matrix is not affine
1646      *         (the last line of a 2D 3x3 matrix is not {@code [0, 0, 1]} or
1647      *          the last line of a 3D 4x4 matrix is not {@code [0, 0, 0, 1]}.
1648      * @throws NullPointerException if the specified {@code matrix}
1649      *         or {@code type} is null
1650      * @since JavaFX 8.0
1651      */
1652     public void append(double[] matrix, MatrixType type, int offset) {
1653         if (matrix.length < offset + type.elements()) {
1654             throw new IndexOutOfBoundsException("The array is too short.");
1655         }
1656 
1657         switch(type) {
1658             default:
1659                 stateError();
1660                 // cannot reach
1661             case MT_2D_3x3:
1662                 if (matrix[offset + 6] != 0.0 ||
1663                         matrix[offset + 7] != 0.0 ||
1664                         matrix[offset + 8] != 1.0) {
1665                     throw new IllegalArgumentException("The matrix is "
1666                             + "not affine");
1667                 }
1668                 // fall-through
1669             case MT_2D_2x3:
1670                 append(matrix[offset++], matrix[offset++],
1671                         matrix[offset++], matrix[offset++],
1672                         matrix[offset++], matrix[offset++]);
1673                 return;
1674             case MT_3D_4x4:
1675                 if (matrix[offset + 12] != 0.0 ||
1676                         matrix[offset + 13] != 0.0 ||
1677                         matrix[offset + 14] != 0.0 ||
1678                         matrix[offset + 15] != 1.0) {
1679                     throw new IllegalArgumentException("The matrix is "
1680                             + "not affine");
1681                 }
1682                 // fall-through
1683             case MT_3D_3x4:
1684                 append(matrix[offset++], matrix[offset++], matrix[offset++],
1685                         matrix[offset++], matrix[offset++], matrix[offset++],
1686                         matrix[offset++], matrix[offset++], matrix[offset++],
1687                         matrix[offset++], matrix[offset++], matrix[offset++]);
1688                 return;
1689         }
1690     }
1691 
1692     @Override
1693     void appendTo(Affine a) {
1694         switch(state3d) {
1695             default:
1696                 stateError();
1697                 // cannot reach
1698             case APPLY_NON_3D:
1699                 switch(state2d) {
1700                     case APPLY_IDENTITY:
1701                         return;
1702                     case APPLY_TRANSLATE:
1703                         a.appendTranslation(getTx(), getTy());
1704                         return;
1705                     case APPLY_SCALE:
1706                         a.appendScale(getMxx(), getMyy());
1707                         return;
1708                     case APPLY_SCALE | APPLY_TRANSLATE:
1709                         a.appendTranslation(getTx(), getTy());
1710                         a.appendScale(getMxx(), getMyy());
1711                         return;
1712                     default:
1713                         a.append(getMxx(), getMxy(), getTx(),
1714                                  getMyx(), getMyy(), getTy());
1715                         return;
1716                 }
1717             case APPLY_TRANSLATE:
1718                 a.appendTranslation(getTx(), getTy(), getTz());
1719                 return;
1720             case APPLY_SCALE:
1721                 a.appendScale(getMxx(), getMyy(), getMzz());
1722                 return;
1723             case APPLY_SCALE | APPLY_TRANSLATE:
1724                 a.appendTranslation(getTx(), getTy(), getTz());
1725                 a.appendScale(getMxx(), getMyy(), getMzz());
1726                 return;
1727             case APPLY_3D_COMPLEX:
1728                 a.append(getMxx(), getMxy(), getMxz(), getTx(),
1729                          getMyx(), getMyy(), getMyz(), getTy(),
1730                          getMzx(), getMzy(), getMzz(), getTz());
1731                 return;
1732         }
1733     }
1734 
1735     /**
1736      * <p>
1737      * Prepends the specified transform to this instance.
1738      * The operation modifies this transform in a way that applying it to a node
1739      * has the same effect as adding the two transforms to its
1740      * {@code getTransforms()} list, the specified {@code transform} first
1741      * and {@code this} transform second.
1742      * </p><p>
1743      * From the matrix point of view, the transformation matrix of this
1744      * transform is multiplied on the left by the transformation matrix of
1745      * the specified transform.
1746      * </p>
1747      *
1748      * @param transform transform to be prepended to this instance
1749      * @throws NullPointerException if the specified {@code transform} is null
1750      * @since JavaFX 8.0
1751      */
1752     public void prepend(Transform transform) {
1753         transform.prependTo(this);
1754     }
1755 
1756     /**
1757      * <p>
1758      * Prepends the 2D transform specified by the element values to this instance.
1759      * The operation modifies this transform in a way that applying it to a node
1760      * has the same effect as adding the two transforms to its
1761      * {@code getTransforms()} list, the specified {@code transform} first
1762      * and {@code this} transform second.
1763      * </p><p>
1764      * From the matrix point of view, the transformation matrix of this
1765      * transform is multiplied on the left by the transformation matrix of
1766      * the specified transform.
1767      * </p>
1768      *
1769      * @param mxx the X coordinate scaling element of the transform to be
1770      *            prepended
1771      * @param mxy the XY coordinate element of the transform to be prepended
1772      * @param tx the X coordinate translation element of the transform to be
1773      *            prepended
1774      * @param myx the YX coordinate element of the transform to be prepended
1775      * @param myy the Y coordinate scaling element of the transform to be
1776      *            prepended
1777      * @param ty the Y coordinate translation element of the transform to be
1778      *            prepended
1779      * @since JavaFX 8.0
1780      */
1781     public void prepend(double mxx, double mxy, double tx,
1782                         double myx, double myy, double ty) {
1783 
1784         if (state3d == APPLY_NON_3D) {
1785             atomicChange.start();
1786 
1787             final double m_xx = getMxx();
1788             final double m_xy = getMxy();
1789             final double t_x = getTx();
1790             final double m_yx = getMyx();
1791             final double m_yy = getMyy();
1792             final double t_y = getTy();
1793 
1794             setMxx(mxx * m_xx + mxy * m_yx);
1795             setMxy(mxx * m_xy + mxy * m_yy);
1796             setTx(mxx * t_x + mxy * t_y + tx);
1797             setMyx(myx * m_xx + myy * m_yx);
1798             setMyy(myx * m_xy + myy * m_yy);
1799             setTy(myx * t_x + myy * t_y + ty);
1800 
1801             updateState2D();
1802             atomicChange.end();
1803         } else {
1804             prepend(mxx, mxy, 0.0, tx,
1805                    myx, myy, 0.0, ty,
1806                    0.0, 0.0, 1.0, 0.0);
1807         }
1808     }
1809 
1810     /**
1811      * <p>
1812      * Prepends the transform specified by the element values to this instance.
1813      * The operation modifies this transform in a way that applying it to a node
1814      * has the same effect as adding the two transforms to its
1815      * {@code getTransforms()} list, the specified {@code transform} first
1816      * and {@code this} transform second.
1817      * </p><p>
1818      * From the matrix point of view, the transformation matrix of this
1819      * transform is multiplied on the left by the transformation matrix of
1820      * the specified transform.
1821      * </p>
1822      *
1823      * @param mxx the X coordinate scaling element of the transform to be
1824      *            prepended
1825      * @param mxy the XY coordinate element of the transform to be prepended
1826      * @param mxz the XZ coordinate element of the transform to be prepended
1827      * @param tx the X coordinate translation element of the transform to be
1828      *            prepended
1829      * @param myx the YX coordinate element of the transform to be prepended
1830      * @param myy the Y coordinate scaling element of the transform to be
1831      *            prepended
1832      * @param myz the YZ coordinate element of the transform to be prepended
1833      * @param ty the Y coordinate translation element of the transform to be
1834      *            prepended
1835      * @param mzx the ZX coordinate element of the transform to be prepended
1836      * @param mzy the ZY coordinate element of the transform to be prepended
1837      * @param mzz the Z coordinate scaling element of the transform to be
1838      *            prepended
1839      * @param tz the Z coordinate translation element of the transform to be
1840      *            prepended
1841      * @since JavaFX 8.0
1842      */
1843     public void prepend(double mxx, double mxy, double mxz, double tx,
1844                         double myx, double myy, double myz, double ty,
1845                         double mzx, double mzy, double mzz, double tz) {
1846         atomicChange.start();
1847 
1848         final double m_xx = getMxx();
1849         final double m_xy = getMxy();
1850         final double m_xz = getMxz();
1851         final double t_x = getTx();
1852         final double m_yx = getMyx();
1853         final double m_yy = getMyy();
1854         final double m_yz = getMyz();
1855         final double t_y = getTy();
1856         final double m_zx = getMzx();
1857         final double m_zy = getMzy();
1858         final double m_zz = getMzz();
1859         final double t_z = getTz();
1860 
1861         setMxx(mxx * m_xx + mxy * m_yx + mxz * m_zx);
1862         setMxy(mxx * m_xy + mxy * m_yy + mxz * m_zy);
1863         setMxz(mxx * m_xz + mxy * m_yz + mxz * m_zz);
1864         setTx( mxx * t_x + mxy * t_y + mxz * t_z + tx);
1865         setMyx(myx * m_xx + myy * m_yx + myz * m_zx);
1866         setMyy(myx * m_xy + myy * m_yy + myz * m_zy);
1867         setMyz(myx * m_xz + myy * m_yz + myz * m_zz);
1868         setTy( myx * t_x + myy * t_y + myz * t_z + ty);
1869         setMzx(mzx * m_xx + mzy * m_yx + mzz * m_zx);
1870         setMzy(mzx * m_xy + mzy * m_yy + mzz * m_zy);
1871         setMzz(mzx * m_xz + mzy * m_yz + mzz * m_zz);
1872         setTz( mzx * t_x + mzy * t_y + mzz * t_z + tz);
1873 
1874         updateState();
1875         atomicChange.end();
1876     }
1877 
1878     /**
1879      * <p>
1880      * Prepends the transform specified by the array to this instance.
1881      * The operation modifies this transform in a way that applying it to a node
1882      * has the same effect as adding the two transforms to its
1883      * {@code getTransforms()} list, the specified {@code transform} first
1884      * and {@code this} transform second.
1885      * </p><p>
1886      * From the matrix point of view, the transformation matrix of this
1887      * transform is multiplied on the left by the transformation matrix of
1888      * the specified transform.
1889      * </p>
1890      *
1891      * @param matrix array containing the flattened transformation matrix
1892      *               to be prepended
1893      * @param type type of matrix contained in the array
1894      * @param offset offset of the first matrix element in the array
1895      * @throws IndexOutOfBoundsException if the array is too short for
1896      *         the specified {@code type} and {@code offset}
1897      * @throws IllegalArgumentException if the specified matrix is not affine
1898      *         (the last line of a 2D 3x3 matrix is not {@code [0, 0, 1]} or
1899      *          the last line of a 3D 4x4 matrix is not {@code [0, 0, 0, 1]}.
1900      * @throws NullPointerException if the specified {@code matrix}
1901      *         or {@code type} is null
1902      * @since JavaFX 8.0
1903      */
1904     public void prepend(double[] matrix, MatrixType type, int offset) {
1905         if (matrix.length < offset + type.elements()) {
1906             throw new IndexOutOfBoundsException("The array is too short.");
1907         }
1908 
1909         switch(type) {
1910             default:
1911                 stateError();
1912                 // cannot reach
1913             case MT_2D_3x3:
1914                 if (matrix[offset + 6] != 0.0 ||
1915                         matrix[offset + 7] != 0.0 ||
1916                         matrix[offset + 8] != 1.0) {
1917                     throw new IllegalArgumentException("The matrix is "
1918                             + "not affine");
1919                 }
1920                 // fall-through
1921             case MT_2D_2x3:
1922                 prepend(matrix[offset++], matrix[offset++],
1923                         matrix[offset++], matrix[offset++],
1924                         matrix[offset++], matrix[offset++]);
1925                 return;
1926             case MT_3D_4x4:
1927                 if (matrix[offset + 12] != 0.0 ||
1928                         matrix[offset + 13] != 0.0 ||
1929                         matrix[offset + 14] != 0.0 ||
1930                         matrix[offset + 15] != 1.0) {
1931                     throw new IllegalArgumentException("The matrix is "
1932                             + "not affine");
1933                 }
1934                 // fall-through
1935             case MT_3D_3x4:
1936                 prepend(matrix[offset++], matrix[offset++], matrix[offset++],
1937                         matrix[offset++], matrix[offset++], matrix[offset++],
1938                         matrix[offset++], matrix[offset++], matrix[offset++],
1939                         matrix[offset++], matrix[offset++], matrix[offset++]);
1940                 return;
1941         }
1942     }
1943 
1944     @Override
1945     void prependTo(Affine a) {
1946         switch(state3d) {
1947             default:
1948                 stateError();
1949                 // cannot reach
1950             case APPLY_NON_3D:
1951                 switch(state2d) {
1952                     case APPLY_IDENTITY:
1953                         return;
1954                     case APPLY_TRANSLATE:
1955                         a.prependTranslation(getTx(), getTy());
1956                         return;
1957                     case APPLY_SCALE:
1958                         a.prependScale(getMxx(), getMyy());
1959                         return;
1960                     case APPLY_SCALE | APPLY_TRANSLATE:
1961                         a.prependScale(getMxx(), getMyy());
1962                         a.prependTranslation(getTx(), getTy());
1963                         return;
1964                     default:
1965                         a.prepend(getMxx(), getMxy(), getTx(),
1966                                   getMyx(), getMyy(), getTy());
1967                         return;
1968                 }
1969             case APPLY_TRANSLATE:
1970                 a.prependTranslation(getTx(), getTy(), getTz());
1971                 return;
1972             case APPLY_SCALE:
1973                 a.prependScale(getMxx(), getMyy(), getMzz());
1974                 return;
1975             case APPLY_SCALE | APPLY_TRANSLATE:
1976                 a.prependScale(getMxx(), getMyy(), getMzz());
1977                 a.prependTranslation(getTx(), getTy(), getTz());
1978                 return;
1979             case APPLY_3D_COMPLEX:
1980                 a.prepend(getMxx(), getMxy(), getMxz(), getTx(),
1981                           getMyx(), getMyy(), getMyz(), getTy(),
1982                           getMzx(), getMzy(), getMzz(), getTz());
1983                 return;
1984         }
1985     }
1986 
1987 
1988                /* *************************************************
1989                 *                    Translate                    *
1990                 **************************************************/
1991 
1992     /**
1993      * <p>
1994      * Appends the 2D translation to this instance.
1995      * It is equivalent to {@code append(new Translate(tx, ty))}.
1996      * </p><p>
1997      * The operation modifies this transform in a way that applying it to a node
1998      * has the same effect as adding two transforms to its
1999      * {@code getTransforms()} list, {@code this} transform first and the specified
2000      * translation second.
2001      * </p><p>
2002      * From the matrix point of view, the transformation matrix of this
2003      * transform is multiplied on the right by the transformation matrix of
2004      * the specified translation.
2005      * </p>
2006      * @param tx the X coordinate translation
2007      * @param ty the Y coordinate translation
2008      * @since JavaFX 8.0
2009      */
2010     public void appendTranslation(double tx, double ty) {
2011         atomicChange.start();
2012         translate2D(tx, ty);
2013         atomicChange.end();
2014     }
2015 
2016     /**
2017      * <p>
2018      * Appends the translation to this instance.
2019      * It is equivalent to {@code append(new Translate(tx, ty, tz))}.
2020      * </p><p>
2021      * The operation modifies this transform in a way that applying it to a node
2022      * has the same effect as adding two transforms to its
2023      * {@code getTransforms()} list, {@code this} transform first and the specified
2024      * translation second.
2025      * </p><p>
2026      * From the matrix point of view, the transformation matrix of this
2027      * transform is multiplied on the right by the transformation matrix of
2028      * the specified translation.
2029      * </p>
2030      * @param tx the X coordinate translation
2031      * @param ty the Y coordinate translation
2032      * @param tz the Z coordinate translation
2033      * @since JavaFX 8.0
2034      */
2035     public void appendTranslation(double tx, double ty, double tz) {
2036         atomicChange.start();
2037         translate3D(tx, ty, tz);
2038         atomicChange.end();
2039     }
2040 
2041     /**
2042      * 2D implementation of {@code appendTranslation()}.
2043      * If this is a 3D transform, the call is redirected to {@code transalte3D()}.
2044      */
2045     private void translate2D(double tx, double ty) {
2046         if (state3d != APPLY_NON_3D) {
2047             translate3D(tx, ty, 0.0);
2048             return;
2049         }
2050 
2051         switch (state2d) {
2052             default:
2053                 stateError();
2054                 // cannot reach
2055             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
2056                 setTx(tx * getMxx() + ty * getMxy() + getTx());
2057                 setTy(tx * getMyx() + ty * getMyy() + getTy());
2058                 if (getTx() == 0.0 && getTy() == 0.0) {
2059                     state2d = APPLY_SHEAR | APPLY_SCALE;
2060                 }
2061                 return;
2062             case APPLY_SHEAR | APPLY_SCALE:
2063                 setTx(tx * getMxx() + ty * getMxy());
2064                 setTy(tx * getMyx() + ty * getMyy());
2065                 if (getTx() != 0.0 || getTy() != 0.0) {
2066                     state2d = APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE;
2067                 }
2068                 return;
2069             case APPLY_SHEAR | APPLY_TRANSLATE:
2070                 setTx(ty * getMxy() + getTx());
2071                 setTy(tx * getMyx() + getTy());
2072                 if (getTx() == 0.0 && getTy() == 0.0) {
2073                     state2d = APPLY_SHEAR;
2074                 }
2075                 return;
2076             case APPLY_SHEAR:
2077                 setTx(ty * getMxy());
2078                 setTy(tx * getMyx());
2079                 if (getTx() != 0.0 || getTy() != 0.0) {
2080                     state2d = APPLY_SHEAR | APPLY_TRANSLATE;
2081                 }
2082                 return;
2083             case APPLY_SCALE | APPLY_TRANSLATE:
2084                 setTx(tx * getMxx() + getTx());
2085                 setTy(ty * getMyy() + getTy());
2086                 if (getTx() == 0.0 && getTy() == 0.0) {
2087                     state2d = APPLY_SCALE;
2088                 }
2089                 return;
2090             case APPLY_SCALE:
2091                 setTx(tx * getMxx());
2092                 setTy(ty * getMyy());
2093                 if (getTx() != 0.0 || getTy() != 0.0) {
2094                     state2d = APPLY_SCALE | APPLY_TRANSLATE;
2095                 }
2096                 return;
2097             case APPLY_TRANSLATE:
2098                 setTx(tx + getTx());
2099                 setTy(ty + getTy());
2100                 if (getTx() == 0.0 && getTy() == 0.0) {
2101                     state2d = APPLY_IDENTITY;
2102                 }
2103                 return;
2104             case APPLY_IDENTITY:
2105                 setTx(tx);
2106                 setTy(ty);
2107                 if (tx != 0.0 || ty != 0.0) {
2108                     state2d = APPLY_TRANSLATE;
2109                 }
2110                 return;
2111         }
2112     }
2113 
2114     /**
2115      * 3D implementation of {@code appendTranslation()}.
2116      * Works fine if this is a 2D transform.
2117      */
2118     private void translate3D(double tx, double ty, double tz) {
2119         switch(state3d) {
2120             default:
2121                 stateError();
2122                 // cannot reach
2123             case APPLY_NON_3D:
2124                 translate2D(tx, ty);
2125                 if (tz != 0.0) {
2126                     setTz(tz);
2127                     if ((state2d & APPLY_SHEAR) == 0) {
2128                         state3d = (state2d & APPLY_SCALE) | APPLY_TRANSLATE;
2129                     } else {
2130                         state3d = APPLY_3D_COMPLEX;
2131                     }
2132                 }
2133                 return;
2134             case APPLY_TRANSLATE:
2135                 setTx(tx + getTx());
2136                 setTy(ty + getTy());
2137                 setTz(tz + getTz());
2138                 if (getTz() == 0.0) {
2139                     state3d = APPLY_NON_3D;
2140                     if (getTx() == 0.0 && getTy() == 0.0) {
2141                         state2d = APPLY_IDENTITY;
2142                     } else {
2143                         state2d = APPLY_TRANSLATE;
2144                     }
2145                 }
2146                 return;
2147             case APPLY_SCALE:
2148                 setTx(tx * getMxx());
2149                 setTy(ty * getMyy());
2150                 setTz(tz * getMzz());
2151                 if (getTx() != 0.0 || getTy() != 0.0 || getTz() != 0.0) {
2152                     state3d |= APPLY_TRANSLATE;
2153                 }
2154                 return;
2155             case APPLY_SCALE | APPLY_TRANSLATE:
2156                 setTx(tx * getMxx() + getTx());
2157                 setTy(ty * getMyy() + getTy());
2158                 setTz(tz * getMzz() + getTz());
2159                 if (getTz() == 0.0) {
2160                     if (getTx() == 0.0 && getTy() == 0.0) {
2161                         state3d = APPLY_SCALE;
2162                     }
2163                     if (getMzz() == 1.0) {
2164                         state2d = state3d;
2165                         state3d = APPLY_NON_3D;
2166                     }
2167                 }
2168                 return;
2169             case APPLY_3D_COMPLEX:
2170                 setTx(tx * getMxx() + ty * getMxy() + tz * getMxz() + getTx());
2171                 setTy(tx * getMyx() + ty * getMyy() + tz * getMyz() + getTy());
2172                 setTz(tx * getMzx() + ty * getMzy() + tz * getMzz() + getTz());
2173                 updateState();
2174                 return;
2175         }
2176     }
2177 
2178     /**
2179      * <p>
2180      * Prepends the translation to this instance.
2181      * It is equivalent to {@code prepend(new Translate(tx, ty, tz))}.
2182      * </p><p>
2183      * The operation modifies this transform in a way that applying it to a node
2184      * has the same effect as adding two transforms to its
2185      * {@code getTransforms()} list, the specified translation first
2186      * and {@code this} transform second.
2187      * </p><p>
2188      * From the matrix point of view, the transformation matrix of this
2189      * transform is multiplied on the left by the transformation matrix of
2190      * the specified translation.
2191      * </p>
2192      * @param tx the X coordinate translation
2193      * @param ty the Y coordinate translation
2194      * @param tz the Z coordinate translation
2195      * @since JavaFX 8.0
2196      */
2197     public void prependTranslation(double tx, double ty, double tz) {
2198         atomicChange.start();
2199         preTranslate3D(tx, ty, tz);
2200         atomicChange.end();
2201     }
2202 
2203     /**
2204      * <p>
2205      * Prepends the 2D translation to this instance.
2206      * It is equivalent to {@code prepend(new Translate(tx, ty))}.
2207      * </p><p>
2208      * The operation modifies this transform in a way that applying it to a node
2209      * has the same effect as adding two transforms to its
2210      * {@code getTransforms()} list, the specified translation first
2211      * and {@code this} transform second.
2212      * </p><p>
2213      * From the matrix point of view, the transformation matrix of this
2214      * transform is multiplied on the left by the transformation matrix of
2215      * the specified translation.
2216      * </p>
2217      * @param tx the X coordinate translation
2218      * @param ty the Y coordinate translation
2219      * @since JavaFX 8.0
2220      */
2221     public void prependTranslation(double tx, double ty) {
2222         atomicChange.start();
2223         preTranslate2D(tx, ty);
2224         atomicChange.end();
2225     }
2226 
2227     /**
2228      * 2D implementation of {@code prependTranslation()}.
2229      * If this is a 3D transform, the call is redirected to
2230      * {@code preTransalte3D}.
2231      * @since JavaFX 8.0
2232      */
2233     private void preTranslate2D(double tx, double ty) {
2234         if (state3d != APPLY_NON_3D) {
2235             preTranslate3D(tx, ty, 0.0);
2236             return;
2237         }
2238 
2239         setTx(getTx() + tx);
2240         setTy(getTy() + ty);
2241 
2242         if (getTx() == 0.0 && getTy() == 0.0) {
2243             state2d &= ~APPLY_TRANSLATE;
2244         } else {
2245             state2d |= APPLY_TRANSLATE;
2246         }
2247     }
2248 
2249     /**
2250      * 3D implementation of {@code prependTranslation()}.
2251      * Works fine if this is a 2D transform.
2252      */
2253     private void preTranslate3D(double tx, double ty, double tz) {
2254         switch(state3d) {
2255             default:
2256                 stateError();
2257                 // cannot reach
2258             case APPLY_NON_3D:
2259                 preTranslate2D(tx, ty);
2260 
2261                 if (tz != 0.0) {
2262                     setTz(tz);
2263 
2264                     if ((state2d & APPLY_SHEAR) == 0) {
2265                         state3d = (state2d & APPLY_SCALE) | APPLY_TRANSLATE;
2266                     } else {
2267                         state3d = APPLY_3D_COMPLEX;
2268                     }
2269                 }
2270                 return;
2271             case APPLY_TRANSLATE:
2272                 setTx(getTx() + tx);
2273                 setTy(getTy() + ty);
2274                 setTz(getTz() + tz);
2275                 if (getTz() == 0.0) {
2276                     state3d = APPLY_NON_3D;
2277                     if (getTx() == 0.0 && getTy() == 0.0) {
2278                         state2d = APPLY_IDENTITY;
2279                     } else {
2280                         state2d = APPLY_TRANSLATE;
2281                     }
2282                 }
2283                 return;
2284             case APPLY_SCALE:
2285                 setTx(tx);
2286                 setTy(ty);
2287                 setTz(tz);
2288                 if (tx != 0.0 || ty != 0.0 || tz != 0.0) {
2289                     state3d |= APPLY_TRANSLATE;
2290                 }
2291                 return;
2292             case APPLY_SCALE | APPLY_TRANSLATE:
2293                 setTx(getTx() + tx);
2294                 setTy(getTy() + ty);
2295                 setTz(getTz() + tz);
2296 
2297                 if (getTz() == 0.0) {
2298                     if (getTx() == 0.0 && getTy() == 0.0) {
2299                         state3d = APPLY_SCALE;
2300                     }
2301                     if (getMzz() == 1.0) {
2302                         state2d = state3d;
2303                         state3d = APPLY_NON_3D;
2304                     }
2305                 }
2306                 return;
2307             case APPLY_3D_COMPLEX:
2308                 setTx(getTx() + tx);
2309                 setTy(getTy() + ty);
2310                 setTz(getTz() + tz);
2311                 if (getTz() == 0.0 && getMxz() == 0.0 && getMyz() == 0.0 &&
2312                         getMzx() == 0.0 && getMzy() == 0.0 && getMzz() == 1.0) {
2313                     state3d = APPLY_NON_3D;
2314                     updateState2D();
2315                 } // otherwise state remains COMPLEX
2316                 return;
2317         }
2318     }
2319 
2320                /* *************************************************
2321                 *                      Scale                      *
2322                 **************************************************/
2323 
2324     /**
2325      * <p>
2326      * Appends the 2D scale to this instance.
2327      * It is equivalent to {@code append(new Scale(sx, sy))}.
2328      * </p><p>
2329      * The operation modifies this transform in a way that applying it to a node
2330      * has the same effect as adding two transforms to its
2331      * {@code getTransforms()} list, {@code this} transform first and the specified
2332      * scale second.
2333      * </p><p>
2334      * From the matrix point of view, the transformation matrix of this
2335      * transform is multiplied on the right by the transformation matrix of
2336      * the specified scale.
2337      * </p>
2338      * @param sx the X coordinate scale factor
2339      * @param sy the Y coordinate scale factor
2340      * @since JavaFX 8.0
2341      */
2342     public void appendScale(double sx, double sy) {
2343         atomicChange.start();
2344         scale2D(sx, sy);
2345         atomicChange.end();
2346     }
2347 
2348     /**
2349      * <p>
2350      * Appends the 2D scale with pivot to this instance.
2351      * It is equivalent to {@code append(new Scale(sx, sy, pivotX, pivotY))}.
2352      * </p><p>
2353      * The operation modifies this transform in a way that applying it to a node
2354      * has the same effect as adding two transforms to its
2355      * {@code getTransforms()} list, {@code this} transform first and the specified
2356      * scale second.
2357      * </p><p>
2358      * From the matrix point of view, the transformation matrix of this
2359      * transform is multiplied on the right by the transformation matrix of
2360      * the specified scale.
2361      * </p>
2362      * @param sx the X coordinate scale factor
2363      * @param sy the Y coordinate scale factor
2364      * @param pivotX the X coordinate of the point about which the scale occurs
2365      * @param pivotY the Y coordinate of the point about which the scale occurs
2366      * @since JavaFX 8.0
2367      */
2368     public void appendScale(double sx, double sy,
2369             double pivotX, double pivotY) {
2370         atomicChange.start();
2371         if (pivotX != 0.0 || pivotY != 0.0) {
2372             translate2D(pivotX, pivotY);
2373             scale2D(sx, sy);
2374             translate2D(-pivotX, -pivotY);
2375         } else {
2376             scale2D(sx, sy);
2377         }
2378         atomicChange.end();
2379     }
2380 
2381     /**
2382      * <p>
2383      * Appends the 2D scale with pivot to this instance.
2384      * It is equivalent to
2385      * {@code append(new Scale(sx, sy, pivot.getX(), pivot.getY())}.
2386      * </p><p>
2387      * The operation modifies this transform in a way that applying it to a node
2388      * has the same effect as adding two transforms to its
2389      * {@code getTransforms()} list, {@code this} transform first and the specified
2390      * scale second.
2391      * </p><p>
2392      * From the matrix point of view, the transformation matrix of this
2393      * transform is multiplied on the right by the transformation matrix of
2394      * the specified scale.
2395      * </p>
2396      * @param sx the X coordinate scale factor
2397      * @param sy the Y coordinate scale factor
2398      * @param pivot the point about which the scale occurs
2399      * @throws NullPointerException if the specified {@code pivot} is null
2400      * @since JavaFX 8.0
2401      */
2402     public void appendScale(double sx, double sy, Point2D pivot) {
2403         appendScale(sx, sy, pivot.getX(), pivot.getY());
2404     }
2405 
2406     /**
2407      * <p>
2408      * Appends the scale to this instance.
2409      * It is equivalent to {@code append(new Scale(sx, sy, sz))}.
2410      * </p><p>
2411      * The operation modifies this transform in a way that applying it to a node
2412      * has the same effect as adding two transforms to its
2413      * {@code getTransforms()} list, {@code this} transform first and the specified
2414      * scale second.
2415      * </p><p>
2416      * From the matrix point of view, the transformation matrix of this
2417      * transform is multiplied on the right by the transformation matrix of
2418      * the specified scale.
2419      * </p>
2420      * @param sx the X coordinate scale factor
2421      * @param sy the Y coordinate scale factor
2422      * @param sz the Z coordinate scale factor
2423      * @since JavaFX 8.0
2424      */
2425     public void appendScale(double sx, double sy, double sz) {
2426         atomicChange.start();
2427         scale3D(sx, sy, sz);
2428         atomicChange.end();
2429     }
2430 
2431     /**
2432      * <p>
2433      * Appends the scale with pivot to this instance.
2434      * It is equivalent to {@code append(new Scale(sx, sy, sz, pivotX,
2435      * pivotY, pivotZ))}.
2436      * </p><p>
2437      * The operation modifies this transform in a way that applying it to a node
2438      * has the same effect as adding two transforms to its
2439      * {@code getTransforms()} list, {@code this} transform first and the specified
2440      * scale second.
2441      * </p><p>
2442      * From the matrix point of view, the transformation matrix of this
2443      * transform is multiplied on the right by the transformation matrix of
2444      * the specified scale.
2445      * </p>
2446      * @param sx the X coordinate scale factor
2447      * @param sy the Y coordinate scale factor
2448      * @param sz the Z coordinate scale factor
2449      * @param pivotX the X coordinate of the point about which the scale occurs
2450      * @param pivotY the Y coordinate of the point about which the scale occurs
2451      * @param pivotZ the Z coordinate of the point about which the scale occurs
2452      * @since JavaFX 8.0
2453      */
2454     public void appendScale(double sx, double sy, double sz,
2455             double pivotX, double pivotY, double pivotZ) {
2456         atomicChange.start();
2457         if (pivotX != 0.0 || pivotY != 0.0 || pivotZ != 0.0) {
2458             translate3D(pivotX, pivotY, pivotZ);
2459             scale3D(sx, sy, sz);
2460             translate3D(-pivotX, -pivotY, -pivotZ);
2461         } else {
2462             scale3D(sx, sy, sz);
2463         }
2464         atomicChange.end();
2465     }
2466 
2467     /**
2468      * <p>
2469      * Appends the scale with pivot to this instance.
2470      * It is equivalent to {@code append(new Scale(sx, sy, sz, pivot.getX(),
2471      * pivot.getY(), pivot.getZ()))}.
2472      * </p><p>
2473      * The operation modifies this transform in a way that applying it to a node
2474      * has the same effect as adding two transforms to its
2475      * {@code getTransforms()} list, {@code this} transform first and the specified
2476      * scale second.
2477      * </p><p>
2478      * From the matrix point of view, the transformation matrix of this
2479      * transform is multiplied on the right by the transformation matrix of
2480      * the specified scale.
2481      * </p>
2482      * @param sx the X coordinate scale factor
2483      * @param sy the Y coordinate scale factor
2484      * @param sz the Z coordinate scale factor
2485      * @param pivot the point about which the scale occurs
2486      * @throws NullPointerException if the specified {@code pivot} is null
2487      * @since JavaFX 8.0
2488      */
2489     public void appendScale(double sx, double sy, double sz, Point3D pivot) {
2490         appendScale(sx, sy, sz, pivot.getX(), pivot.getY(), pivot.getZ());
2491     }
2492 
2493     /**
2494      * 2D implementation of {@code appendScale()}.
2495      * If this is a 3D transform, the call is redirected to {@code scale3D()}.
2496      */
2497     private void scale2D(double sx, double sy) {
2498         if (state3d != APPLY_NON_3D) {
2499             scale3D(sx, sy, 1.0);
2500             return;
2501         }
2502 
2503         int mystate = state2d;
2504         switch (mystate) {
2505             default:
2506                 stateError();
2507                 // cannot reach
2508             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
2509             case APPLY_SHEAR | APPLY_SCALE:
2510                 setMxx(getMxx() * sx);
2511                 setMyy(getMyy() * sy);
2512                 // fall-through
2513             case APPLY_SHEAR | APPLY_TRANSLATE:
2514             case APPLY_SHEAR:
2515                 setMxy(getMxy() * sy);
2516                 setMyx(getMyx() * sx);
2517                 if (getMxy() == 0.0 && getMyx() == 0.0) {
2518                     mystate &= APPLY_TRANSLATE;
2519                     if (getMxx() != 1.0 || getMyy() != 1.0) {
2520                         mystate |= APPLY_SCALE;
2521                     }
2522                     state2d = mystate;
2523                 } else if (getMxx() == 0.0 && getMyy() == 0.0) {
2524                     state2d &= ~APPLY_SCALE;
2525                 }
2526                 return;
2527             case APPLY_SCALE | APPLY_TRANSLATE:
2528             case APPLY_SCALE:
2529                 setMxx(getMxx() * sx);
2530                 setMyy(getMyy() * sy);
2531                 if (getMxx() == 1.0 && getMyy() == 1.0) {
2532                     state2d = (mystate &= APPLY_TRANSLATE);
2533                 }
2534                 return;
2535             case APPLY_TRANSLATE:
2536             case APPLY_IDENTITY:
2537                 setMxx(sx);
2538                 setMyy(sy);
2539                 if (sx != 1.0 || sy != 1.0) {
2540                     state2d = (mystate | APPLY_SCALE);
2541                 }
2542                 return;
2543         }
2544     }
2545 
2546     /**
2547      * 3D implementation of {@code appendScale()}.
2548      * Works fine if this is a 2D transform.
2549      */
2550     private void scale3D(double sx, double sy, double sz) {
2551         switch (state3d) {
2552             default:
2553                 stateError();
2554                 // cannot reach
2555             case APPLY_NON_3D:
2556                 scale2D(sx, sy);
2557                 if (sz != 1.0) {
2558                     setMzz(sz);
2559                     if ((state2d & APPLY_SHEAR) == 0) {
2560                         state3d = (state2d & APPLY_TRANSLATE) | APPLY_SCALE;
2561                     } else {
2562                         state3d = APPLY_3D_COMPLEX;
2563                     }
2564                 }
2565                 return;
2566             case APPLY_TRANSLATE:
2567                 setMxx(sx);
2568                 setMyy(sy);
2569                 setMzz(sz);
2570                 if (sx != 1.0 || sy != 1.0 || sz != 1.0) {
2571                     state3d |= APPLY_SCALE;
2572                 }
2573                 return;
2574             case APPLY_SCALE:
2575                 setMxx(getMxx() * sx);
2576                 setMyy(getMyy() * sy);
2577                 setMzz(getMzz() * sz);
2578                 if (getMzz() == 1.0) {
2579                     state3d = APPLY_NON_3D;
2580                     if (getMxx() == 1.0 && getMyy() == 1.0) {
2581                         state2d = APPLY_IDENTITY;
2582                     } else {
2583                         state2d = APPLY_SCALE;
2584                     }
2585                 }
2586                 return;
2587             case APPLY_SCALE | APPLY_TRANSLATE:
2588                 setMxx(getMxx() * sx);
2589                 setMyy(getMyy() * sy);
2590                 setMzz(getMzz() * sz);
2591 
2592                 if (getMxx() == 1.0 && getMyy() == 1.0 && getMzz() == 1.0) {
2593                     state3d &= ~APPLY_SCALE;
2594                 }
2595                 if (getTz() == 0.0 && getMzz() == 1.0) {
2596                     state2d = state3d;
2597                     state3d = APPLY_NON_3D;
2598                 }
2599                 return;
2600             case APPLY_3D_COMPLEX:
2601                 setMxx(getMxx() * sx);
2602                 setMxy(getMxy() * sy);
2603                 setMxz(getMxz() * sz);
2604 
2605                 setMyx(getMyx() * sx);
2606                 setMyy(getMyy() * sy);
2607                 setMyz(getMyz() * sz);
2608 
2609                 setMzx(getMzx() * sx);
2610                 setMzy(getMzy() * sy);
2611                 setMzz(getMzz() * sz);
2612 
2613                 if (sx == 0.0 || sy == 0.0 || sz == 0.0) {
2614                     updateState();
2615                 } // otherwise state remains COMPLEX
2616                 return;
2617         }
2618     }
2619 
2620     /**
2621      * <p>
2622      * Prepends the 2D scale to this instance.
2623      * It is equivalent to {@code prepend(new Scale(sx, sy))}.
2624      * </p><p>
2625      * The operation modifies this transform in a way that applying it to a node
2626      * has the same effect as adding two transforms to its
2627      * {@code getTransforms()} list, the specified scale first
2628      * and {@code this} transform second.
2629      * </p><p>
2630      * From the matrix point of view, the transformation matrix of this
2631      * transform is multiplied on the left by the transformation matrix of
2632      * the specified scale.
2633      * </p>
2634      * @param sx the X coordinate scale factor
2635      * @param sy the Y coordinate scale factor
2636      * @since JavaFX 8.0
2637      */
2638     public void prependScale(double sx, double sy) {
2639 
2640         atomicChange.start();
2641         preScale2D(sx, sy);
2642         atomicChange.end();
2643     }
2644 
2645     /**
2646      * <p>
2647      * Prepends the 2D scale with pivot to this instance.
2648      * It is equivalent to {@code prepend(new Scale(sx, sy, pivotX, pivotY))}.
2649      * </p><p>
2650      * The operation modifies this transform in a way that applying it to a node
2651      * has the same effect as adding two transforms to its
2652      * {@code getTransforms()} list, the specified scale first
2653      * and {@code this} transform second.
2654      * </p><p>
2655      * From the matrix point of view, the transformation matrix of this
2656      * transform is multiplied on the left by the transformation matrix of
2657      * the specified scale.
2658      * </p>
2659      * @param sx the X coordinate scale factor
2660      * @param sy the Y coordinate scale factor
2661      * @param pivotX the X coordinate of the point about which the scale occurs
2662      * @param pivotY the Y coordinate of the point about which the scale occurs
2663      * @since JavaFX 8.0
2664      */
2665     public void prependScale(double sx, double sy,
2666             double pivotX, double pivotY) {
2667         atomicChange.start();
2668         if (pivotX != 0.0 || pivotY != 0.0) {
2669             preTranslate2D(-pivotX, -pivotY);
2670             preScale2D(sx, sy);
2671             preTranslate2D(pivotX, pivotY);
2672         } else {
2673             preScale2D(sx, sy);
2674         }
2675         atomicChange.end();
2676     }
2677 
2678     /**
2679      * <p>
2680      * Prepends the 2D scale with pivot to this instance.
2681      * It is equivalent to {@code prepend(new Scale(sx, sy, pivot.getX(),
2682      * </p>pivot.getY()))}.
2683      * </p><p>
2684      * The operation modifies this transform in a way that applying it to a node
2685      * has the same effect as adding two transforms to its
2686      * {@code getTransforms()} list, the specified scale first
2687      * and {@code this} transform second.
2688      * </p><p>
2689      * From the matrix point of view, the transformation matrix of this
2690      * transform is multiplied on the left by the transformation matrix of
2691      * the specified scale.
2692      * </p>
2693      * @param sx the X coordinate scale factor
2694      * @param sy the Y coordinate scale factor
2695      * @param pivot the point about which the scale occurs
2696      * @throws NullPointerException if the specified {@code pivot} is null
2697      * @since JavaFX 8.0
2698      */
2699     public void prependScale(double sx, double sy, Point2D pivot) {
2700         prependScale(sx, sy, pivot.getX(), pivot.getY());
2701     }
2702 
2703     /**
2704      * <p>
2705      * Prepends the scale to this instance.
2706      * It is equivalent to {@code prepend(new Scale(sx, sy, sz))}.
2707      * </p><p>
2708      * The operation modifies this transform in a way that applying it to a node
2709      * has the same effect as adding two transforms to its
2710      * {@code getTransforms()} list, the specified scale first
2711      * and {@code this} transform second.
2712      * </p><p>
2713      * From the matrix point of view, the transformation matrix of this
2714      * transform is multiplied on the left by the transformation matrix of
2715      * the specified scale.
2716      * </p>
2717      * @param sx the X coordinate scale factor
2718      * @param sy the Y coordinate scale factor
2719      * @param sz the Z coordinate scale factor
2720      * @since JavaFX 8.0
2721      */
2722     public void prependScale(double sx, double sy, double sz) {
2723         atomicChange.start();
2724         preScale3D(sx, sy, sz);
2725         atomicChange.end();
2726     }
2727 
2728     /**
2729      * <p>
2730      * Prepends the scale with pivot to this instance.
2731      * It is equivalent to
2732      * {@code prepend(new Scale(sx, sy, sz, pivotX, pivotY, pivotZ))}.
2733      * </p><p>
2734      * The operation modifies this transform in a way that applying it to a node
2735      * has the same effect as adding two transforms to its
2736      * {@code getTransforms()} list, the specified scale first
2737      * and {@code this} transform second.
2738      * </p><p>
2739      * From the matrix point of view, the transformation matrix of this
2740      * transform is multiplied on the left by the transformation matrix of
2741      * the specified scale.
2742      * </p>
2743      * @param sx the X coordinate scale factor
2744      * @param sy the Y coordinate scale factor
2745      * @param sz the Z coordinate scale factor
2746      * @param pivotX the X coordinate of the point about which the scale occurs
2747      * @param pivotY the Y coordinate of the point about which the scale occurs
2748      * @param pivotZ the Z coordinate of the point about which the scale occurs
2749      * @since JavaFX 8.0
2750      */
2751     public void prependScale(double sx, double sy, double sz,
2752             double pivotX, double pivotY, double pivotZ) {
2753 
2754         atomicChange.start();
2755         if (pivotX != 0.0 || pivotY != 0.0 || pivotZ != 0.0) {
2756             preTranslate3D(-pivotX, -pivotY, -pivotZ);
2757             preScale3D(sx, sy, sz);
2758             preTranslate3D(pivotX, pivotY, pivotZ);
2759         } else {
2760             preScale3D(sx, sy, sz);
2761         }
2762         atomicChange.end();
2763     }
2764 
2765     /**
2766      * <p>
2767      * Prepends the scale with pivot to this instance.
2768      * It is equivalent to {@code prepend(new Scale(sx, sy, sz, pivot.getX(),
2769      * pivot.getY(), pivot.getZ()))}.
2770      * </p><p>
2771      * The operation modifies this transform in a way that applying it to a node
2772      * has the same effect as adding two transforms to its
2773      * {@code getTransforms()} list, the specified scale first
2774      * and {@code this} transform second.
2775      * </p><p>
2776      * From the matrix point of view, the transformation matrix of this
2777      * transform is multiplied on the left by the transformation matrix of
2778      * the specified scale.
2779      * </p>
2780      * @param sx the X coordinate scale factor
2781      * @param sy the Y coordinate scale factor
2782      * @param sz the Z coordinate scale factor
2783      * @param pivot the point about which the scale occurs
2784      * @throws NullPointerException if the specified {@code pivot} is null
2785      * @since JavaFX 8.0
2786      */
2787     public void prependScale(double sx, double sy, double sz, Point3D pivot) {
2788         prependScale(sx, sy, sz, pivot.getX(), pivot.getY(), pivot.getZ());
2789     }
2790 
2791     /**
2792      * 2D implementation of {@code prependScale()}.
2793      * If this is a 3D transform, the call is redirected to {@code preScale3D()}.
2794      */
2795     private void preScale2D(double sx, double sy) {
2796 
2797         if (state3d != APPLY_NON_3D) {
2798             preScale3D(sx, sy, 1.0);
2799             return;
2800         }
2801 
2802         int mystate = state2d;
2803         switch (mystate) {
2804             default:
2805                 stateError();
2806                 // cannot reach
2807             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
2808                 setTx(getTx() * sx);
2809                 setTy(getTy() * sy);
2810                 if (getTx() == 0.0 && getTy() == 0.0) {
2811                     mystate = mystate & ~APPLY_TRANSLATE;
2812                     state2d = mystate;
2813                 }
2814                 // fall-through
2815             case APPLY_SHEAR | APPLY_SCALE:
2816                 setMxx(getMxx() * sx);
2817                 setMyy(getMyy() * sy);
2818                 // fall-through
2819             case APPLY_SHEAR:
2820                 setMxy(getMxy() * sx);
2821                 setMyx(getMyx() * sy);
2822                 if (getMxy() == 0.0 && getMyx() == 0.0) {
2823                     mystate &= APPLY_TRANSLATE;
2824                     if (getMxx() != 1.0 || getMyy() != 1.0) {
2825                         mystate |= APPLY_SCALE;
2826                     }
2827                     state2d = mystate;
2828                 }
2829                 return;
2830             case APPLY_SHEAR | APPLY_TRANSLATE:
2831                 setTx(getTx() * sx);
2832                 setTy(getTy() * sy);
2833                 setMxy(getMxy() * sx);
2834                 setMyx(getMyx() * sy);
2835                 if (getMxy() == 0.0 && getMyx() == 0.0) {
2836                     if (getTx() == 0.0 && getTy() == 0.0) {
2837                         state2d = APPLY_SCALE;
2838                     } else {
2839                         state2d = APPLY_SCALE | APPLY_TRANSLATE;
2840                     }
2841                 } else if (getTx() ==0.0 && getTy() == 0.0) {
2842                     state2d = APPLY_SHEAR;
2843                 }
2844                 return;
2845             case APPLY_SCALE | APPLY_TRANSLATE:
2846                 setTx(getTx() * sx);
2847                 setTy(getTy() * sy);
2848                 if (getTx() == 0.0 && getTy() == 0.0) {
2849                     mystate = mystate & ~APPLY_TRANSLATE;
2850                     state2d = mystate;
2851                 }
2852                 // fall-through
2853             case APPLY_SCALE:
2854                 setMxx(getMxx() * sx);
2855                 setMyy(getMyy() * sy);
2856                 if (getMxx() == 1.0 && getMyy() == 1.0) {
2857                     state2d = (mystate &= APPLY_TRANSLATE);
2858                 }
2859                 return;
2860             case APPLY_TRANSLATE:
2861                 setTx(getTx() * sx);
2862                 setTy(getTy() * sy);
2863                 if (getTx() == 0.0 && getTy() == 0.0) {
2864                     mystate = mystate & ~APPLY_TRANSLATE;
2865                     state2d = mystate;
2866                 }
2867                 // fall-through
2868             case APPLY_IDENTITY:
2869                 setMxx(sx);
2870                 setMyy(sy);
2871                 if (sx != 1.0 || sy != 1.0) {
2872                     state2d = mystate | APPLY_SCALE;
2873                 }
2874                 return;
2875         }
2876     }
2877 
2878     /**
2879      * 3D implementation of {@code prependScale()}.
2880      * Works fine if this is a 2D transform.
2881      */
2882     private void preScale3D(double sx, double sy, double sz) {
2883         switch (state3d) {
2884             default:
2885                 stateError();
2886                 // cannot reach
2887             case APPLY_NON_3D:
2888                 preScale2D(sx, sy);
2889                 if (sz != 1.0) {
2890                     setMzz(sz);
2891                     if ((state2d & APPLY_SHEAR) == 0) {
2892                         state3d = (state2d & APPLY_TRANSLATE) | APPLY_SCALE;
2893                     } else {
2894                         state3d = APPLY_3D_COMPLEX;
2895                     }
2896                 }
2897                 return;
2898             case APPLY_SCALE | APPLY_TRANSLATE:
2899                 setTx(getTx() * sx);
2900                 setTy(getTy() * sy);
2901                 setTz(getTz() * sz);
2902                 setMxx(getMxx() * sx);
2903                 setMyy(getMyy() * sy);
2904                 setMzz(getMzz() * sz);
2905                 if (getTx() == 0.0 && getTy() == 0.0 && getTz() == 0.0) {
2906                     state3d &= ~APPLY_TRANSLATE;
2907                 }
2908                 if (getMxx() == 1.0 && getMyy() == 1.0 && getMzz() == 1.0) {
2909                     state3d &= ~APPLY_SCALE;
2910                 }
2911                 if (getTz() == 0.0 && getMzz() == 1.0) {
2912                     state2d = state3d;
2913                     state3d = APPLY_NON_3D;
2914                 }
2915                 return;
2916             case APPLY_SCALE:
2917                 setMxx(getMxx() * sx);
2918                 setMyy(getMyy() * sy);
2919                 setMzz(getMzz() * sz);
2920                 if (getMzz() == 1.0) {
2921                     state3d = APPLY_NON_3D;
2922                     if (getMxx() == 1.0 && getMyy() == 1.0) {
2923                         state2d = APPLY_IDENTITY;
2924                     } else {
2925                         state2d = APPLY_SCALE;
2926                     }
2927                 }
2928                 return;
2929             case APPLY_TRANSLATE:
2930                 setTx(getTx() * sx);
2931                 setTy(getTy() * sy);
2932                 setTz(getTz() * sz);
2933                 setMxx(sx);
2934                 setMyy(sy);
2935                 setMzz(sz);
2936                 if (getTx() == 0.0 && getTy() == 0.0 && getTz() == 0.0) {
2937                     state3d &= ~APPLY_TRANSLATE;
2938                 }
2939                 if (sx != 1.0 || sy != 1.0 || sz != 1.0) {
2940                     state3d |= APPLY_SCALE;
2941                 }
2942                 return;
2943             case APPLY_3D_COMPLEX:
2944                 setMxx(getMxx() * sx);
2945                 setMxy(getMxy() * sx);
2946                 setMxz(getMxz() * sx);
2947                 setTx(getTx() * sx);
2948 
2949                 setMyx(getMyx() * sy);
2950                 setMyy(getMyy() * sy);
2951                 setMyz(getMyz() * sy);
2952                 setTy(getTy() * sy);
2953 
2954                 setMzx(getMzx() * sz);
2955                 setMzy(getMzy() * sz);
2956                 setMzz(getMzz() * sz);
2957                 setTz(getTz() * sz);
2958 
2959                 if (sx == 0.0 || sy == 0.0 || sz == 0.0) {
2960                     updateState();
2961                 } // otherwise state remains COMPLEX
2962                 return;
2963         }
2964     }
2965 
2966                /* *************************************************
2967                 *                     Shear                       *
2968                 **************************************************/
2969 
2970     /**
2971      * <p>
2972      * Appends the shear to this instance.
2973      * It is equivalent to {@code append(new Shear(sx, sy))}.
2974      * </p><p>
2975      * The operation modifies this transform in a way that applying it to a node
2976      * has the same effect as adding two transforms to its
2977      * {@code getTransforms()} list, {@code this} transform first and the specified
2978      * shear second.
2979      * </p><p>
2980      * From the matrix point of view, the transformation matrix of this
2981      * transform is multiplied on the right by the transformation matrix of
2982      * the specified shear.
2983      * </p>
2984      * @param shx the XY coordinate element
2985      * @param shy the YX coordinate element
2986      * @since JavaFX 8.0
2987      */
2988     public void appendShear(double shx, double shy) {
2989         atomicChange.start();
2990         shear2D(shx, shy);
2991         atomicChange.end();
2992     }
2993 
2994     /**
2995      * <p>
2996      * Appends the shear with pivot to this instance.
2997      * It is equivalent to {@code append(new Shear(sx, sy, pivotX, pivotY))}.
2998      * </p><p>
2999      * The operation modifies this transform in a way that applying it to a node
3000      * has the same effect as adding two transforms to its
3001      * {@code getTransforms()} list, {@code this} transform first and the specified
3002      * shear second.
3003      * </p><p>
3004      * From the matrix point of view, the transformation matrix of this
3005      * transform is multiplied on the right by the transformation matrix of
3006      * the specified shear.
3007      * </p>
3008      * @param shx the XY coordinate element
3009      * @param shy the YX coordinate element
3010      * @param pivotX the X coordinate of the shear pivot point
3011      * @param pivotY the Y coordinate of the shear pivot point
3012      * @since JavaFX 8.0
3013      */
3014     public void appendShear(double shx, double shy,
3015             double pivotX, double pivotY) {
3016         atomicChange.start();
3017         if (pivotX != 0.0 || pivotY != 0.0) {
3018             translate2D(pivotX, pivotY);
3019             shear2D(shx, shy);
3020             translate2D(-pivotX, -pivotY);
3021         } else {
3022             shear2D(shx, shy);
3023         }
3024         atomicChange.end();
3025     }
3026 
3027     /**
3028      * <p>
3029      * Appends the shear with pivot to this instance.
3030      * It is equivalent to {@code append(new Shear(sx, sy,
3031      * pivot.getX(), pivot.getY()))}.
3032      * </p><p>
3033      * The operation modifies this transform in a way that applying it to a node
3034      * has the same effect as adding two transforms to its
3035      * {@code getTransforms()} list, {@code this} transform first and the specified
3036      * shear second.
3037      * </p><p>
3038      * From the matrix point of view, the transformation matrix of this
3039      * transform is multiplied on the right by the transformation matrix of
3040      * the specified shear.
3041      * </p>
3042      * @param shx the XY coordinate element
3043      * @param shy the YX coordinate element
3044      * @param pivot the shear pivot point
3045      * @throws NullPointerException if the specified {@code pivot} is null
3046      * @since JavaFX 8.0
3047      */
3048     public void appendShear(double shx, double shy, Point2D pivot) {
3049         appendShear(shx, shy, pivot.getX(), pivot.getY());
3050     }
3051 
3052     /**
3053      * 2D implementation of {@code appendShear()}.
3054      * If this is a 3D transform, the call is redirected to {@code shear3D()}.
3055      */
3056     private void shear2D(double shx, double shy) {
3057 
3058         if (state3d != APPLY_NON_3D) {
3059             shear3D(shx, shy);
3060             return;
3061         }
3062 
3063         int mystate = state2d;
3064         switch (mystate) {
3065             default:
3066                 stateError();
3067                 // cannot reach
3068             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
3069             case APPLY_SHEAR | APPLY_SCALE:
3070                 double M0, M1;
3071                 M0 = getMxx();
3072                 M1 = getMxy();
3073                 setMxx(M0 + M1 * shy);
3074                 setMxy(M0 * shx + M1);
3075 
3076                 M0 = getMyx();
3077                 M1 = getMyy();
3078                 setMyx(M0 + M1 * shy);
3079                 setMyy(M0 * shx + M1);
3080                 updateState2D();
3081                 return;
3082             case APPLY_SHEAR | APPLY_TRANSLATE:
3083             case APPLY_SHEAR:
3084                 setMxx(getMxy() * shy);
3085                 setMyy(getMyx() * shx);
3086                 if (getMxx() != 0.0 || getMyy() != 0.0) {
3087                     state2d = mystate | APPLY_SCALE;
3088                 }
3089                 return;
3090             case APPLY_SCALE | APPLY_TRANSLATE:
3091             case APPLY_SCALE:
3092                 setMxy(getMxx() * shx);
3093                 setMyx(getMyy() * shy);
3094                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3095                     state2d = mystate | APPLY_SHEAR;
3096                 }
3097                 return;
3098             case APPLY_TRANSLATE:
3099             case APPLY_IDENTITY:
3100                 setMxy(shx);
3101                 setMyx(shy);
3102                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3103                     state2d = mystate | APPLY_SCALE | APPLY_SHEAR;
3104                 }
3105                 return;
3106         }
3107     }
3108 
3109     /**
3110      * 3D implementation of {@code appendShear()}.
3111      * Works fine if this is a 2D transform.
3112      */
3113     private void shear3D(double shx, double shy) {
3114         switch (state3d) {
3115             default:
3116                 stateError();
3117                 // cannot reach
3118             case APPLY_NON_3D:
3119                 // cannot happen because currently there is no 3D appendShear
3120                 // that would call this method directly
3121                 shear2D(shx, shy);
3122                 return;
3123             case APPLY_TRANSLATE:
3124                 setMxy(shx);
3125                 setMyx(shy);
3126                 if (shx != 0.0 || shy != 0.0) {
3127                     state3d = APPLY_3D_COMPLEX;
3128                 }
3129                 return;
3130             case APPLY_SCALE:
3131             case APPLY_SCALE | APPLY_TRANSLATE:
3132                 setMxy(getMxx() * shx);
3133                 setMyx(getMyy() * shy);
3134                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3135                     state3d = APPLY_3D_COMPLEX;
3136                 }
3137                 return;
3138             case APPLY_3D_COMPLEX:
3139                 final double m_xx = getMxx();
3140                 final double m_xy = getMxy();
3141                 final double m_yx = getMyx();
3142                 final double m_yy = getMyy();
3143                 final double m_zx = getMzx();
3144                 final double m_zy = getMzy();
3145 
3146                 setMxx(m_xx + m_xy * shy);
3147                 setMxy(m_xy + m_xx * shx);
3148                 setMyx(m_yx + m_yy * shy);
3149                 setMyy(m_yy + m_yx * shx);
3150                 setMzx(m_zx + m_zy * shy);
3151                 setMzy(m_zy + m_zx * shx);
3152                 updateState();
3153                 return;
3154         }
3155     }
3156 
3157     /**
3158      * <p>
3159      * Prepends the shear to this instance.
3160      * It is equivalent to {@code prepend(new Shear(sx, sy))}.
3161      * </p><p>
3162      * The operation modifies this transform in a way that applying it to a node
3163      * has the same effect as adding two transforms to its
3164      * {@code getTransforms()} list, the specified shear first
3165      * and {@code this} transform second.
3166      * </p><p>
3167      * From the matrix point of view, the transformation matrix of this
3168      * transform is multiplied on the left by the transformation matrix of
3169      * the specified shear.
3170      * </p>
3171      * @param shx the XY coordinate element
3172      * @param shy the YX coordinate element
3173      * @since JavaFX 8.0
3174      */
3175     public void prependShear(double shx, double shy) {
3176         atomicChange.start();
3177         preShear2D(shx, shy);
3178         atomicChange.end();
3179     }
3180 
3181     /**
3182      * <p>
3183      * Prepends the shear with pivot to this instance.
3184      * It is equivalent to {@code prepend(new Shear(sx, sy, pivotX, pivotY))}.
3185      * </p><p>
3186      * The operation modifies this transform in a way that applying it to a node
3187      * has the same effect as adding two transforms to its
3188      * {@code getTransforms()} list, the specified shear first
3189      * and {@code this} transform second.
3190      * </p><p>
3191      * From the matrix point of view, the transformation matrix of this
3192      * transform is multiplied on the left by the transformation matrix of
3193      * the specified shear.
3194      * </p>
3195      * @param shx the XY coordinate element
3196      * @param shy the YX coordinate element
3197      * @param pivotX the X coordinate of the shear pivot point
3198      * @param pivotY the Y coordinate of the shear pivot point
3199      * @since JavaFX 8.0
3200      */
3201     public void prependShear(double shx, double shy,
3202             double pivotX, double pivotY) {
3203         atomicChange.start();
3204         if (pivotX != 0.0 || pivotY != 0.0) {
3205             preTranslate2D(-pivotX, -pivotY);
3206             preShear2D(shx, shy);
3207             preTranslate2D(pivotX, pivotY);
3208         } else {
3209             preShear2D(shx, shy);
3210         }
3211         atomicChange.end();
3212     }
3213 
3214     /**
3215      * <p>
3216      * Prepends the shear with pivot to this instance.
3217      * It is equivalent to {@code prepend(new Shear(sx, sy, pivot.getX(),
3218      * pivot.getY()))}.
3219      * </p><p>
3220      * The operation modifies this transform in a way that applying it to a node
3221      * has the same effect as adding two transforms to its
3222      * {@code getTransforms()} list, the specified shear first
3223      * and {@code this} transform second.
3224      * </p><p>
3225      * From the matrix point of view, the transformation matrix of this
3226      * transform is multiplied on the left by the transformation matrix of
3227      * the specified shear.
3228      * </p>
3229      * @param shx the XY coordinate element
3230      * @param shy the YX coordinate element
3231      * @param pivot the shear pivot point
3232      * @throws NullPointerException if the specified {@code pivot} is null
3233      * @since JavaFX 8.0
3234      */
3235     public void prependShear(double shx, double shy, Point2D pivot) {
3236         prependShear(shx, shy, pivot.getX(), pivot.getY());
3237     }
3238 
3239     /**
3240      * 2D implementation of {@code prependShear()}.
3241      * If this is a 3D transform, the call is redirected to {@code preShear3D()}.
3242      */
3243     private void preShear2D(double shx, double shy) {
3244 
3245         if (state3d != APPLY_NON_3D) {
3246             preShear3D(shx, shy);
3247             return;
3248         }
3249 
3250         int mystate = state2d;
3251 
3252         switch (mystate) {
3253             default:
3254                 stateError();
3255                 // cannot reach
3256             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
3257             case APPLY_SHEAR | APPLY_TRANSLATE:
3258                 final double t_x_1 = getTx();
3259                 final double t_y_1 = getTy();
3260                 setTx(t_x_1 + shx * t_y_1);
3261                 setTy(t_y_1 + shy * t_x_1);
3262                 // fall-through
3263             case APPLY_SHEAR | APPLY_SCALE:
3264             case APPLY_SHEAR:
3265                 final double m_xx = getMxx();
3266                 final double m_xy = getMxy();
3267                 final double m_yx = getMyx();
3268                 final double m_yy = getMyy();
3269 
3270                 setMxx(m_xx + shx * m_yx);
3271                 setMxy(m_xy + shx * m_yy);
3272                 setMyx(shy * m_xx + m_yx);
3273                 setMyy(shy * m_xy + m_yy);
3274                 updateState2D();
3275                 return;
3276             case APPLY_SCALE | APPLY_TRANSLATE:
3277                 final double t_x_2 = getTx();
3278                 final double t_y_2 = getTy();
3279                 setTx(t_x_2 + shx * t_y_2);
3280                 setTy(t_y_2 + shy * t_x_2);
3281                 if (getTx() == 0.0 && getTy() == 0.0) {
3282                     mystate = mystate & ~APPLY_TRANSLATE;
3283                     state2d = mystate;
3284                 }
3285                 // fall-through
3286             case APPLY_SCALE:
3287                 setMxy(shx * getMyy());
3288                 setMyx(shy * getMxx());
3289                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3290                     state2d = mystate | APPLY_SHEAR;
3291                 }
3292                 return;
3293             case APPLY_TRANSLATE:
3294                 final double t_x_3 = getTx();
3295                 final double t_y_3 = getTy();
3296                 setTx(t_x_3 + shx * t_y_3);
3297                 setTy(t_y_3 + shy * t_x_3);
3298                 if (getTx() == 0.0 && getTy() == 0.0) {
3299                     mystate = mystate & ~APPLY_TRANSLATE;
3300                     state2d = mystate;
3301                 }
3302                 // fall-through
3303             case APPLY_IDENTITY:
3304                 setMxy(shx);
3305                 setMyx(shy);
3306                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3307                     state2d = mystate | APPLY_SCALE | APPLY_SHEAR;
3308                 }
3309                 return;
3310         }
3311     }
3312 
3313     /**
3314      * 3D implementation of {@code prependShear()}.
3315      * Works fine if this is a 2D transform.
3316      */
3317     private void preShear3D(double shx, double shy) {
3318 
3319         switch (state3d) {
3320             default:
3321                 stateError();
3322                 // cannot reach
3323             case APPLY_NON_3D:
3324                 // cannot happen because currently there is no 3D prependShear
3325                 // that would call this method directly
3326                 preShear2D(shx, shy);
3327                 return;
3328             case APPLY_TRANSLATE:
3329                 final double tx_t = getTx();
3330                 setMxy(shx);
3331                 setTx(tx_t + getTy() * shx);
3332                 setMyx(shy);
3333                 setTy(tx_t * shy + getTy());
3334 
3335                 if (shx != 0.0 || shy != 0.0) {
3336                     state3d = APPLY_3D_COMPLEX;
3337                 }
3338                 return;
3339             case APPLY_SCALE:
3340                 setMxy(getMyy() * shx);
3341                 setMyx(getMxx() * shy);
3342 
3343                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3344                     state3d = APPLY_3D_COMPLEX;
3345                 }
3346                 return;
3347             case APPLY_SCALE | APPLY_TRANSLATE:
3348                 final double tx_st = getTx();
3349                 setMxy(getMyy() * shx);
3350                 setTx(tx_st + getTy() * shx);
3351                 setMyx(getMxx() * shy);
3352                 setTy(tx_st * shy + getTy());
3353 
3354                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3355                     state3d = APPLY_3D_COMPLEX;
3356                 }
3357                 return;
3358             case APPLY_3D_COMPLEX:
3359 
3360                 final double m_xx = getMxx();
3361                 final double m_xy = getMxy();
3362                 final double m_yx = getMyx();
3363                 final double t_x = getTx();
3364                 final double m_yy = getMyy();
3365                 final double m_xz = getMxz();
3366                 final double m_yz = getMyz();
3367                 final double t_y = getTy();
3368 
3369                 setMxx(m_xx + m_yx * shx);
3370                 setMxy(m_xy + m_yy * shx);
3371                 setMxz(m_xz + m_yz * shx);
3372                 setTx(t_x + t_y * shx);
3373                 setMyx(m_xx * shy + m_yx);
3374                 setMyy(m_xy * shy + m_yy);
3375                 setMyz(m_xz * shy + m_yz);
3376                 setTy(t_x * shy + t_y);
3377 
3378                 updateState();
3379                 return;
3380         }
3381     }
3382 
3383                /* *************************************************
3384                 *                     Rotate                      *
3385                 **************************************************/
3386 
3387     /**
3388      * <p>
3389      * Appends the 2D rotation to this instance.
3390      * It is equivalent to {@code append(new Rotate(angle))}.
3391      * </p><p>
3392      * The operation modifies this transform in a way that applying it to a node
3393      * has the same effect as adding two transforms to its
3394      * {@code getTransforms()} list, {@code this} transform first and the specified
3395      * rotation second.
3396      * </p><p>
3397      * From the matrix point of view, the transformation matrix of this
3398      * transform is multiplied on the right by the transformation matrix of
3399      * the specified rotation.
3400      * </p>
3401      * @param angle the angle of the rotation in degrees
3402      * @since JavaFX 8.0
3403      */
3404     public void appendRotation(double angle) {
3405         atomicChange.start();
3406         rotate2D(angle);
3407         atomicChange.end();
3408     }
3409 
3410     /**
3411      * <p>
3412      * Appends the 2D rotation with pivot to this instance.
3413      * It is equivalent to {@code append(new Rotate(angle, pivotX, pivotY))}.
3414      * </p><p>
3415      * The operation modifies this transform in a way that applying it to a node
3416      * has the same effect as adding two transforms to its
3417      * {@code getTransforms()} list, {@code this} transform first and the specified
3418      * rotation second.
3419      * </p><p>
3420      * From the matrix point of view, the transformation matrix of this
3421      * transform is multiplied on the right by the transformation matrix of
3422      * the specified rotation.
3423      * </p>
3424      * @param angle the angle of the rotation in degrees
3425      * @param pivotX the X coordinate of the rotation pivot point
3426      * @param pivotY the Y coordinate of the rotation pivot point
3427      * @since JavaFX 8.0
3428      */
3429     public void appendRotation(double angle,
3430             double pivotX, double pivotY) {
3431         atomicChange.start();
3432         if (pivotX != 0.0 || pivotY != 0.0) {
3433             translate2D(pivotX, pivotY);
3434             rotate2D(angle);
3435             translate2D(-pivotX, -pivotY);
3436         } else {
3437             rotate2D(angle);
3438         }
3439         atomicChange.end();
3440     }
3441 
3442     /**
3443      * <p>
3444      * Appends the 2D rotation with pivot to this instance.
3445      * It is equivalent to {@code append(new Rotate(angle, pivot.getX(),
3446      * pivot.getY()))}.
3447      * </p><p>
3448      * The operation modifies this transform in a way that applying it to a node
3449      * has the same effect as adding two transforms to its
3450      * {@code getTransforms()} list, {@code this} transform first and the specified
3451      * rotation second.
3452      * </p><p>
3453      * From the matrix point of view, the transformation matrix of this
3454      * transform is multiplied on the right by the transformation matrix of
3455      * the specified rotation.
3456      * </p>
3457      * @param angle the angle of the rotation in degrees
3458      * @param pivot the rotation pivot point
3459      * @throws NullPointerException if the specified {@code pivot} is null
3460      * @since JavaFX 8.0
3461      */
3462     public void appendRotation(double angle, Point2D pivot) {
3463         appendRotation(angle, pivot.getX(), pivot.getY());
3464     }
3465 
3466     /**
3467      * <p>
3468      * Appends the rotation to this instance.
3469      * It is equivalent to {@code append(new Rotate(angle, pivotX, pivotY,
3470      * pivotZ, new Point3D(axisX, axisY, axisZ)))}.
3471      * </p><p>
3472      * The operation modifies this transform in a way that applying it to a node
3473      * has the same effect as adding two transforms to its
3474      * {@code getTransforms()} list, {@code this} transform first and the specified
3475      * rotation second.
3476      * </p><p>
3477      * From the matrix point of view, the transformation matrix of this
3478      * transform is multiplied on the right by the transformation matrix of
3479      * the specified rotation.
3480      * </p>
3481      * @param angle the angle of the rotation in degrees
3482      * @param pivotX the X coordinate of the rotation pivot point
3483      * @param pivotY the Y coordinate of the rotation pivot point
3484      * @param pivotZ the Z coordinate of the rotation pivot point
3485      * @param axisX the X coordinate magnitude of the rotation axis
3486      * @param axisY the Y coordinate magnitude of the rotation axis
3487      * @param axisZ the Z coordinate magnitude of the rotation axis
3488      * @since JavaFX 8.0
3489      */
3490     public void appendRotation(double angle,
3491             double pivotX, double pivotY, double pivotZ,
3492             double axisX, double axisY, double axisZ) {
3493         atomicChange.start();
3494         if (pivotX != 0.0 || pivotY != 0.0 || pivotZ != 0.0) {
3495             translate3D(pivotX, pivotY, pivotZ);
3496             rotate3D(angle, axisX, axisY, axisZ);
3497             translate3D(-pivotX, -pivotY, -pivotZ);
3498         } else {
3499             rotate3D(angle, axisX, axisY, axisZ);
3500         }
3501         atomicChange.end();
3502     }
3503 
3504     /**
3505      * <p>
3506      * Appends the rotation to this instance.
3507      * It is equivalent to {@code append(new Rotate(angle, pivotX, pivotY,
3508      * pivotZ, axis))}.
3509      * </p><p>
3510      * The operation modifies this transform in a way that applying it to a node
3511      * has the same effect as adding two transforms to its
3512      * {@code getTransforms()} list, {@code this} transform first and the specified
3513      * rotation second.
3514      * </p><p>
3515      * From the matrix point of view, the transformation matrix of this
3516      * transform is multiplied on the right by the transformation matrix of
3517      * the specified rotation.
3518      * </p>
3519      * @param angle the angle of the rotation in degrees
3520      * @param pivotX the X coordinate of the rotation pivot point
3521      * @param pivotY the Y coordinate of the rotation pivot point
3522      * @param pivotZ the Z coordinate of the rotation pivot point
3523      * @param axis the rotation axis
3524      * @throws NullPointerException if the specified {@code axis} is null
3525      * @since JavaFX 8.0
3526      */
3527     public void appendRotation(double angle,
3528             double pivotX, double pivotY, double pivotZ,
3529             Point3D axis) {
3530         appendRotation(angle, pivotX, pivotY, pivotZ,
3531                 axis.getX(), axis.getY(), axis.getZ());
3532     }
3533 
3534     /**
3535      * <p>
3536      * Appends the rotation to this instance.
3537      * It is equivalent to {@code append(new Rotate(angle, pivot.getX(),
3538      * pivot.getY(), pivot.getZ(), axis))}.
3539      * </p><p>
3540      * The operation modifies this transform in a way that applying it to a node
3541      * has the same effect as adding two transforms to its
3542      * {@code getTransforms()} list, {@code this} transform first and the specified
3543      * rotation second.
3544      * </p><p>
3545      * From the matrix point of view, the transformation matrix of this
3546      * transform is multiplied on the right by the transformation matrix of
3547      * the specified rotation.
3548      * </p>
3549      * @param angle the angle of the rotation in degrees
3550      * @param pivot the rotation pivot point
3551      * @param axis the rotation axis
3552      * @throws NullPointerException if the specified {@code pivot}
3553      *         or {@code axis} is null
3554      * @since JavaFX 8.0
3555      */
3556     public void appendRotation(double angle, Point3D pivot, Point3D axis) {
3557         appendRotation(angle, pivot.getX(), pivot.getY(), pivot.getZ(),
3558                 axis.getX(), axis.getY(), axis.getZ());
3559     }
3560 
3561     /**
3562      * Implementation of the {@code appendRotation()} around an arbitrary axis.
3563      */
3564     private void rotate3D(double angle, double axisX, double axisY, double axisZ) {
3565         if (axisX == 0.0 && axisY == 0.0) {
3566             if (axisZ > 0.0) {
3567                 rotate3D(angle);
3568             } else if (axisZ < 0.0) {
3569                 rotate3D(-angle);
3570             } // else rotating about zero vector - NOP
3571             return;
3572         }
3573 
3574         double mag = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ);
3575 
3576         if (mag == 0.0) {
3577             return;
3578         }
3579 
3580         mag = 1.0 / mag;
3581         final double ax = axisX * mag;
3582         final double ay = axisY * mag;
3583         final double az = axisZ * mag;
3584 
3585         final double sinTheta = Math.sin(Math.toRadians(angle));
3586         final double cosTheta = Math.cos(Math.toRadians(angle));
3587         final double t = 1.0 - cosTheta;
3588 
3589         final double xz = ax * az;
3590         final double xy = ax * ay;
3591         final double yz = ay * az;
3592 
3593         final double Txx = t * ax * ax + cosTheta;
3594         final double Txy = t * xy - sinTheta * az;
3595         final double Txz = t * xz + sinTheta * ay;
3596 
3597         final double Tyx = t * xy + sinTheta * az;
3598         final double Tyy = t * ay * ay + cosTheta;
3599         final double Tyz = t * yz - sinTheta * ax;
3600 
3601         final double Tzx = t * xz - sinTheta * ay;
3602         final double Tzy = t * yz + sinTheta * ax;
3603         final double Tzz = t * az * az + cosTheta;
3604 
3605         switch (state3d) {
3606             default:
3607                 stateError();
3608                 // cannot reach
3609             case APPLY_NON_3D:
3610                 switch (state2d) {
3611                     default:
3612                         stateError();
3613                         // cannot reach
3614                     case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
3615                     case APPLY_SHEAR | APPLY_SCALE:
3616                         final double xx_sst = getMxx();
3617                         final double xy_sst = getMxy();
3618                         final double yx_sst = getMyx();
3619                         final double yy_sst = getMyy();
3620                         setMxx(xx_sst * Txx + xy_sst * Tyx);
3621                         setMxy(xx_sst * Txy + xy_sst * Tyy);
3622                         setMxz(xx_sst * Txz + xy_sst * Tyz);
3623                         setMyx(yx_sst * Txx + yy_sst * Tyx);
3624                         setMyy(yx_sst * Txy + yy_sst * Tyy);
3625                         setMyz(yx_sst * Txz + yy_sst * Tyz);
3626                         setMzx(Tzx);
3627                         setMzy(Tzy);
3628                         setMzz(Tzz);
3629                         break;
3630                     case APPLY_SHEAR | APPLY_TRANSLATE:
3631                     case APPLY_SHEAR:
3632                         final double xy_sht = getMxy();
3633                         final double yx_sht = getMyx();
3634                         setMxx(xy_sht * Tyx);
3635                         setMxy(xy_sht * Tyy);
3636                         setMxz(xy_sht * Tyz);
3637                         setMyx(yx_sht * Txx);
3638                         setMyy(yx_sht * Txy);
3639                         setMyz(yx_sht * Txz);
3640                         setMzx(Tzx);
3641                         setMzy(Tzy);
3642                         setMzz(Tzz);
3643                         break;
3644                     case APPLY_SCALE | APPLY_TRANSLATE:
3645                     case APPLY_SCALE:
3646                         final double xx_s = getMxx();
3647                         final double yy_s = getMyy();
3648                         setMxx(xx_s * Txx);
3649                         setMxy(xx_s * Txy);
3650                         setMxz(xx_s * Txz);
3651                         setMyx(yy_s * Tyx);
3652                         setMyy(yy_s * Tyy);
3653                         setMyz(yy_s * Tyz);
3654                         setMzx(Tzx);
3655                         setMzy(Tzy);
3656                         setMzz(Tzz);
3657                         break;
3658                     case APPLY_TRANSLATE:
3659                     case APPLY_IDENTITY:
3660                         setMxx(Txx);
3661                         setMxy(Txy);
3662                         setMxz(Txz);
3663                         setMyx(Tyx);
3664                         setMyy(Tyy);
3665                         setMyz(Tyz);
3666                         setMzx(Tzx);
3667                         setMzy(Tzy);
3668                         setMzz(Tzz);
3669                         break;
3670                 }
3671                 break;
3672             case APPLY_TRANSLATE:
3673                 setMxx(Txx);
3674                 setMxy(Txy);
3675                 setMxz(Txz);
3676                 setMyx(Tyx);
3677                 setMyy(Tyy);
3678                 setMyz(Tyz);
3679                 setMzx(Tzx);
3680                 setMzy(Tzy);
3681                 setMzz(Tzz);
3682                 break;
3683             case APPLY_SCALE:
3684             case APPLY_SCALE | APPLY_TRANSLATE:
3685                 final double xx_st = getMxx();
3686                 final double yy_st = getMyy();
3687                 final double zz_st = getMzz();
3688                 setMxx(xx_st * Txx);
3689                 setMxy(xx_st * Txy);
3690                 setMxz(xx_st * Txz);
3691                 setMyx(yy_st * Tyx);
3692                 setMyy(yy_st * Tyy);
3693                 setMyz(yy_st * Tyz);
3694                 setMzx(zz_st * Tzx);
3695                 setMzy(zz_st * Tzy);
3696                 setMzz(zz_st * Tzz);
3697                 break;
3698             case APPLY_3D_COMPLEX:
3699                 final double m_xx = getMxx();
3700                 final double m_xy = getMxy();
3701                 final double m_xz = getMxz();
3702                 final double m_yx = getMyx();
3703                 final double m_yy = getMyy();
3704                 final double m_yz = getMyz();
3705                 final double m_zx = getMzx();
3706                 final double m_zy = getMzy();
3707                 final double m_zz = getMzz();
3708                 setMxx(m_xx * Txx + m_xy * Tyx + m_xz * Tzx /* + mxt * 0.0 */);
3709                 setMxy(m_xx * Txy + m_xy * Tyy + m_xz * Tzy /* + mxt * 0.0 */);
3710                 setMxz(m_xx * Txz + m_xy * Tyz + m_xz * Tzz /* + mxt * 0.0 */);
3711                 setMyx(m_yx * Txx + m_yy * Tyx + m_yz * Tzx /* + myt * 0.0 */);
3712                 setMyy(m_yx * Txy + m_yy * Tyy + m_yz * Tzy /* + myt * 0.0 */);
3713                 setMyz(m_yx * Txz + m_yy * Tyz + m_yz * Tzz /* + myt * 0.0 */);
3714                 setMzx(m_zx * Txx + m_zy * Tyx + m_zz * Tzx /* + mzt * 0.0 */);
3715                 setMzy(m_zx * Txy + m_zy * Tyy + m_zz * Tzy /* + mzt * 0.0 */);
3716                 setMzz(m_zx * Txz + m_zy * Tyz + m_zz * Tzz /* + mzt * 0.0 */);
3717                 break;
3718         }
3719         updateState();
3720     }
3721 
3722     /**
3723      * Table of 2D state changes during predictable quadrant rotations where
3724      * the shear and scaleAffine values are swapped and negated.
3725      */
3726     private static final int rot90conversion[] = {
3727         /* IDENTITY => */        APPLY_SHEAR,
3728         /* TRANSLATE (TR) => */  APPLY_SHEAR | APPLY_TRANSLATE,
3729         /* SCALE (SC) => */      APPLY_SHEAR,
3730         /* SC | TR => */         APPLY_SHEAR | APPLY_TRANSLATE,
3731         /* SHEAR (SH) => */      APPLY_SCALE,
3732         /* SH | TR => */         APPLY_SCALE | APPLY_TRANSLATE,
3733         /* SH | SC => */         APPLY_SHEAR | APPLY_SCALE,
3734         /* SH | SC | TR => */    APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE,
3735     };
3736 
3737     /**
3738      * 2D implementation of {@code appendRotation}.
3739      * If this is a 3D transform, the call is redirected to {@code rotate3D()}.
3740      */
3741     private void rotate2D(double theta) {
3742         if (state3d != APPLY_NON_3D) {
3743             rotate3D(theta);
3744             return;
3745         }
3746 
3747         double sin = Math.sin(Math.toRadians(theta));
3748         if (sin == 1.0) {
3749             rotate2D_90();
3750         } else if (sin == -1.0) {
3751             rotate2D_270();
3752         } else {
3753             double cos = Math.cos(Math.toRadians(theta));
3754             if (cos == -1.0) {
3755                 rotate2D_180();
3756             } else if (cos != 1.0) {
3757                 double M0, M1;
3758                 M0 = getMxx();
3759                 M1 = getMxy();
3760                 setMxx(cos * M0 + sin * M1);
3761                 setMxy(-sin * M0 + cos * M1);
3762                 M0 = getMyx();
3763                 M1 = getMyy();
3764                 setMyx(cos * M0 + sin * M1);
3765                 setMyy(-sin * M0 + cos * M1);
3766                 updateState2D();
3767             }
3768         }
3769     }
3770 
3771     /**
3772      * 2D implementation of {@code appendRotation} for 90 degrees rotation
3773      * around Z axis.
3774      * Behaves wrong when called for a 3D transform.
3775      */
3776     private void rotate2D_90() {
3777         double M0 = getMxx();
3778         setMxx(getMxy());
3779         setMxy(-M0);
3780         M0 = getMyx();
3781         setMyx(getMyy());
3782         setMyy(-M0);
3783         int newstate = rot90conversion[state2d];
3784         if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE &&
3785                 getMxx() == 1.0 && getMyy() == 1.0) {
3786             newstate -= APPLY_SCALE;
3787         } else if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SHEAR &&
3788                 getMxy() == 0.0 && getMyx() == 0.0) {
3789             newstate = (newstate & ~APPLY_SHEAR | APPLY_SCALE);
3790         }
3791         state2d = newstate;
3792     }
3793 
3794     /**
3795      * 2D implementation of {@code appendRotation} for 180 degrees rotation
3796      * around Z axis.
3797      * Behaves wrong when called for a 3D transform.
3798      */
3799     private void rotate2D_180() {
3800         setMxx(-getMxx());
3801         setMyy(-getMyy());
3802         int oldstate = state2d;
3803         if ((oldstate & (APPLY_SHEAR)) != 0) {
3804             // If there was a shear, then this rotation has no
3805             // effect on the state.
3806             setMxy(-getMxy());
3807             setMyx(-getMyx());
3808         } else {
3809             // No shear means the SCALE state may toggle when
3810             // m00 and m11 are negated.
3811             if (getMxx() == 1.0 && getMyy() == 1.0) {
3812                 state2d = oldstate & ~APPLY_SCALE;
3813             } else {
3814                 state2d = oldstate | APPLY_SCALE;
3815             }
3816         }
3817     }
3818 
3819     /**
3820      * 2D implementation of {@code appendRotation} for 270 degrees rotation
3821      * around Z axis.
3822      * Behaves wrong when called for a 3D transform.
3823      */
3824     private void rotate2D_270() {
3825         double M0 = getMxx();
3826         setMxx(-getMxy());
3827         setMxy(M0);
3828         M0 = getMyx();
3829         setMyx(-getMyy());
3830         setMyy(M0);
3831         int newstate = rot90conversion[state2d];
3832         if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE &&
3833                 getMxx() == 1.0 && getMyy() == 1.0) {
3834             newstate -= APPLY_SCALE;
3835         } else if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SHEAR &&
3836                 getMxy() == 0.0 && getMyx() == 0.0) {
3837             newstate = (newstate & ~APPLY_SHEAR | APPLY_SCALE);
3838         }
3839         state2d = newstate;
3840     }
3841 
3842     /**
3843      * 3D implementation of {@code appendRotation} around Z axis.
3844      * If this is a 2D transform, the call is redirected to {@code rotate2D()}.
3845      */
3846     private void rotate3D(double theta) {
3847         if (state3d == APPLY_NON_3D) {
3848             rotate2D(theta);
3849             return;
3850         }
3851 
3852         double sin = Math.sin(Math.toRadians(theta));
3853         if (sin == 1.0) {
3854             rotate3D_90();
3855         } else if (sin == -1.0) {
3856             rotate3D_270();
3857         } else {
3858             double cos = Math.cos(Math.toRadians(theta));
3859             if (cos == -1.0) {
3860                 rotate3D_180();
3861             } else if (cos != 1.0) {
3862                 double M0, M1;
3863                 M0 = getMxx();
3864                 M1 = getMxy();
3865                 setMxx(cos * M0 + sin * M1);
3866                 setMxy(-sin * M0 + cos * M1);
3867                 M0 = getMyx();
3868                 M1 = getMyy();
3869                 setMyx(cos * M0 + sin * M1);
3870                 setMyy(-sin * M0 + cos * M1);
3871                 M0 = getMzx();
3872                 M1 = getMzy();
3873                 setMzx(cos * M0 + sin * M1);
3874                 setMzy(-sin * M0 + cos * M1);
3875                 updateState();
3876             }
3877         }
3878     }
3879 
3880     /**
3881      * 3D implementation of {@code appendRotation} for 90 degrees rotation
3882      * around Z axis.
3883      * Behaves wrong when called for a 2D transform.
3884      */
3885     private void rotate3D_90() {
3886         double M0 = getMxx();
3887         setMxx(getMxy());
3888         setMxy(-M0);
3889         M0 = getMyx();
3890         setMyx(getMyy());
3891         setMyy(-M0);
3892         M0 = getMzx();
3893         setMzx(getMzy());
3894         setMzy(-M0);
3895         switch(state3d) {
3896             default:
3897                 stateError();
3898                 // cannot reach
3899             case APPLY_TRANSLATE:
3900                 state3d = APPLY_3D_COMPLEX;
3901                 return;
3902             case APPLY_SCALE:
3903             case APPLY_SCALE | APPLY_TRANSLATE:
3904                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3905                     state3d = APPLY_3D_COMPLEX;
3906                 }
3907                 return;
3908             case APPLY_3D_COMPLEX:
3909                 updateState();
3910                 return;
3911         }
3912     }
3913 
3914     /**
3915      * 3D implementation of {@code appendRotation} for 180 degrees rotation
3916      * around Z axis.
3917      * Behaves wrong when called for a 2D transform.
3918      */
3919     private void rotate3D_180() {
3920         final double mxx = getMxx();
3921         final double myy = getMyy();
3922         setMxx(-mxx);
3923         setMyy(-myy);
3924         if (state3d == APPLY_3D_COMPLEX) {
3925             setMxy(-getMxy());
3926             setMyx(-getMyx());
3927             setMzx(-getMzx());
3928             setMzy(-getMzy());
3929             updateState();
3930             return;
3931         }
3932 
3933         if (mxx == -1.0 && myy == -1.0 && getMzz() == 1.0) {
3934             // must have been 3d because of translation, which remained
3935             state3d &= ~APPLY_SCALE;
3936         } else {
3937             state3d |= APPLY_SCALE;
3938         }
3939     }
3940 
3941     /**
3942      * 3D implementation of {@code appendRotation} for 270 degrees rotation
3943      * around Z axis.
3944      * Behaves wrong when called for a 2D transform.
3945      */
3946     private void rotate3D_270() {
3947         double M0 = getMxx();
3948         setMxx(-getMxy());
3949         setMxy(M0);
3950         M0 = getMyx();
3951         setMyx(-getMyy());
3952         setMyy(M0);
3953         M0 = getMzx();
3954         setMzx(-getMzy());
3955         setMzy(M0);
3956         switch(state3d) {
3957             default:
3958                 stateError();
3959                 // cannot reach
3960             case APPLY_TRANSLATE:
3961                 state3d = APPLY_3D_COMPLEX;
3962                 return;
3963             case APPLY_SCALE:
3964             case APPLY_SCALE | APPLY_TRANSLATE:
3965                 if (getMxy() != 0.0 || getMyx() != 0.0) {
3966                     state3d = APPLY_3D_COMPLEX;
3967                 }
3968                 return;
3969             case APPLY_3D_COMPLEX:
3970                 updateState();
3971                 return;
3972         }
3973     }
3974 
3975     /**
3976      * <p>
3977      * Prepends the 2D rotation to this instance.
3978      * It is equivalent to {@code prepend(new Rotate(angle))}.
3979      * </p><p>
3980      * The operation modifies this transform in a way that applying it to a node
3981      * has the same effect as adding two transforms to its
3982      * {@code getTransforms()} list, the specified rotation first
3983      * and {@code this} transform second.
3984      * </p><p>
3985      * From the matrix point of view, the transformation matrix of this
3986      * transform is multiplied on the left by the transformation matrix of
3987      * the specified rotation.
3988      * </p>
3989      * @param angle the angle of the rotation in degrees
3990      * @since JavaFX 8.0
3991      */
3992     public void prependRotation(double angle) {
3993         atomicChange.start();
3994         preRotate2D(angle);
3995         atomicChange.end();
3996     }
3997 
3998     /**
3999      * <p>
4000      * Prepends the 2D rotation with pivot to this instance.
4001      * It is equivalent to {@code prepend(new Rotate(angle, pivotX, pivotY))}.
4002      * </p><p>
4003      * The operation modifies this transform in a way that applying it to a node
4004      * has the same effect as adding two transforms to its
4005      * {@code getTransforms()} list, the specified rotation first
4006      * and {@code this} transform second.
4007      * </p><p>
4008      * From the matrix point of view, the transformation matrix of this
4009      * transform is multiplied on the left by the transformation matrix of
4010      * the specified rotation.
4011      * </p>
4012      * @param angle the angle of the rotation in degrees
4013      * @param pivotX the X coordinate of the rotation pivot point
4014      * @param pivotY the Y coordinate of the rotation pivot point
4015      * @since JavaFX 8.0
4016      */
4017     public void prependRotation(double angle, double pivotX, double pivotY) {
4018         atomicChange.start();
4019         if (pivotX != 0.0 || pivotY != 0.0) {
4020             preTranslate2D(-pivotX, -pivotY);
4021             preRotate2D(angle);
4022             preTranslate2D(pivotX, pivotY);
4023         } else {
4024             preRotate2D(angle);
4025         }
4026         atomicChange.end();
4027     }
4028 
4029     /**
4030      * <p>
4031      * Prepends the 2D rotation with pivot to this instance.
4032      * It is equivalent to {@code prepend(new Rotate(angle, pivot.getX(),
4033      * pivot.getY()))}.
4034      * </p><p>
4035      * The operation modifies this transform in a way that applying it to a node
4036      * has the same effect as adding two transforms to its
4037      * {@code getTransforms()} list, the specified rotation first
4038      * and {@code this} transform second.
4039      * </p><p>
4040      * From the matrix point of view, the transformation matrix of this
4041      * transform is multiplied on the left by the transformation matrix of
4042      * the specified rotation.
4043      * </p>
4044      * @param angle the angle of the rotation in degrees
4045      * @param pivot the rotation pivot point
4046      * @throws NullPointerException if the specified {@code pivot} is null
4047      * @since JavaFX 8.0
4048      */
4049     public void prependRotation(double angle, Point2D pivot) {
4050         prependRotation(angle, pivot.getX(), pivot.getY());
4051     }
4052 
4053     /**
4054      * <p>
4055      * Prepends the rotation to this instance.
4056      * It is equivalent to {@code prepend(new Rotate(angle, pivotX, pivotY,
4057      * pivotZ, new Point3D(axisX, axisY, axisZ)))}.
4058      * </p><p>
4059      * The operation modifies this transform in a way that applying it to a node
4060      * has the same effect as adding two transforms to its
4061      * {@code getTransforms()} list, the specified rotation first
4062      * and {@code this} transform second.
4063      * </p><p>
4064      * From the matrix point of view, the transformation matrix of this
4065      * transform is multiplied on the left by the transformation matrix of
4066      * the specified rotation.
4067      * </p>
4068      * @param angle the angle of the rotation in degrees
4069      * @param pivotX the X coordinate of the rotation pivot point
4070      * @param pivotY the Y coordinate of the rotation pivot point
4071      * @param pivotZ the Z coordinate of the rotation pivot point
4072      * @param axisX the X coordinate magnitude of the rotation axis
4073      * @param axisY the Y coordinate magnitude of the rotation axis
4074      * @param axisZ the Z coordinate magnitude of the rotation axis
4075      * @since JavaFX 8.0
4076      */
4077     public void prependRotation(double angle,
4078             double pivotX, double pivotY, double pivotZ,
4079             double axisX, double axisY, double axisZ) {
4080         atomicChange.start();
4081         if (pivotX != 0.0 || pivotY != 0.0 || pivotZ != 0.0) {
4082             preTranslate3D(-pivotX, -pivotY, -pivotZ);
4083             preRotate3D(angle, axisX, axisY, axisZ);
4084             preTranslate3D(pivotX, pivotY, pivotZ);
4085         } else {
4086             preRotate3D(angle, axisX, axisY, axisZ);
4087         }
4088         atomicChange.end();
4089     }
4090 
4091     /**
4092      * <p>
4093      * Prepends the rotation to this instance.
4094      * It is equivalent to {@code prepend(new Rotate(angle, pivotX, pivotY,
4095      * pivotZ, axis))}.
4096      * </p><p>
4097      * The operation modifies this transform in a way that applying it to a node
4098      * has the same effect as adding two transforms to its
4099      * {@code getTransforms()} list, the specified rotation first
4100      * and {@code this} transform second.
4101      * </p><p>
4102      * From the matrix point of view, the transformation matrix of this
4103      * transform is multiplied on the left by the transformation matrix of
4104      * the specified rotation.
4105      * </p>
4106      * @param angle the angle of the rotation in degrees
4107      * @param pivotX the X coordinate of the rotation pivot point
4108      * @param pivotY the Y coordinate of the rotation pivot point
4109      * @param pivotZ the Z coordinate of the rotation pivot point
4110      * @param axis the rotation axis
4111      * @throws NullPointerException if the specified {@code axis} is null
4112      * @since JavaFX 8.0
4113      */
4114     public void prependRotation(double angle,
4115             double pivotX, double pivotY, double pivotZ,
4116             Point3D axis) {
4117         prependRotation(angle, pivotX, pivotY, pivotZ,
4118                 axis.getX(), axis.getY(), axis.getZ());
4119     }
4120 
4121     /**
4122      * <p>
4123      * Prepends the rotation to this instance.
4124      * It is equivalent to {@code prepend(new Rotate(angle, pivot.getX(),
4125      * pivot.getY(), pivot.getZ(), axis))}.
4126      * </p><p>
4127      * The operation modifies this transform in a way that applying it to a node
4128      * has the same effect as adding two transforms to its
4129      * {@code getTransforms()} list, the specified rotation first
4130      * and {@code this} transform second.
4131      * </p><p>
4132      * From the matrix point of view, the transformation matrix of this
4133      * transform is multiplied on the left by the transformation matrix of
4134      * the specified rotation.
4135      * </p>
4136      * @param angle the angle of the rotation in degrees
4137      * @param pivot the rotation pivot point
4138      * @param axis the rotation axis
4139      * @throws NullPointerException if the specified {@code pivot}
4140      *         or {@code axis} is null
4141      * @since JavaFX 8.0
4142      */
4143     public void prependRotation(double angle, Point3D pivot, Point3D axis) {
4144         prependRotation(angle, pivot.getX(), pivot.getY(), pivot.getZ(),
4145                 axis.getX(), axis.getY(), axis.getZ());
4146     }
4147 
4148     /**
4149      * Implementation of the {@code prependRotation()} around an arbitrary axis.
4150      */
4151     private void preRotate3D(double angle,
4152             double axisX, double axisY, double axisZ) {
4153 
4154         if (axisX == 0.0 && axisY == 0.0) {
4155             if (axisZ > 0.0) {
4156                 preRotate3D(angle);
4157             } else if (axisZ < 0.0) {
4158                 preRotate3D(-angle);
4159             } // else rotating about zero vector - NOP
4160             return;
4161         }
4162 
4163         double mag = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ);
4164 
4165         if (mag == 0.0) {
4166             return;
4167         }
4168 
4169         mag = 1.0 / mag;
4170         final double ax = axisX * mag;
4171         final double ay = axisY * mag;
4172         final double az = axisZ * mag;
4173 
4174         final double sinTheta = Math.sin(Math.toRadians(angle));
4175         final double cosTheta = Math.cos(Math.toRadians(angle));
4176         final double t = 1.0 - cosTheta;
4177 
4178         final double xz = ax * az;
4179         final double xy = ax * ay;
4180         final double yz = ay * az;
4181 
4182         final double Txx = t * ax * ax + cosTheta;
4183         final double Txy = t * xy - sinTheta * az;
4184         final double Txz = t * xz + sinTheta * ay;
4185 
4186         final double Tyx = t * xy + sinTheta * az;
4187         final double Tyy = t * ay * ay + cosTheta;
4188         final double Tyz = t * yz - sinTheta * ax;
4189 
4190         final double Tzx = t * xz - sinTheta * ay;
4191         final double Tzy = t * yz + sinTheta * ax;
4192         final double Tzz = t * az * az + cosTheta;
4193 
4194         switch (state3d) {
4195             default:
4196                 stateError();
4197                 // cannot reach
4198             case APPLY_NON_3D:
4199                 switch (state2d) {
4200                     default:
4201                         stateError();
4202                         // cannot reach
4203                     case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
4204                         final double xx_sst = getMxx();
4205                         final double xy_sst = getMxy();
4206                         final double tx_sst = getTx();
4207                         final double yx_sst = getMyx();
4208                         final double yy_sst = getMyy();
4209                         final double ty_sst = getTy();
4210                         setMxx(Txx * xx_sst + Txy * yx_sst);
4211                         setMxy(Txx * xy_sst + Txy * yy_sst);
4212                         setMxz(Txz);
4213                         setTx( Txx * tx_sst  + Txy * ty_sst);
4214                         setMyx(Tyx * xx_sst + Tyy * yx_sst);
4215                         setMyy(Tyx * xy_sst + Tyy * yy_sst);
4216                         setMyz(Tyz);
4217                         setTy( Tyx * tx_sst  + Tyy * ty_sst);
4218                         setMzx(Tzx * xx_sst + Tzy * yx_sst);
4219                         setMzy(Tzx * xy_sst + Tzy * yy_sst);
4220                         setMzz(Tzz);
4221                         setTz( Tzx * tx_sst  + Tzy * ty_sst);
4222                         break;
4223                     case APPLY_SHEAR | APPLY_SCALE:
4224                         final double xx_ss = getMxx();
4225                         final double xy_ss = getMxy();
4226                         final double yx_ss = getMyx();
4227                         final double yy_ss = getMyy();
4228                         setMxx(Txx * xx_ss + Txy * yx_ss);
4229                         setMxy(Txx * xy_ss + Txy * yy_ss);
4230                         setMxz(Txz);
4231                         setMyx(Tyx * xx_ss + Tyy * yx_ss);
4232                         setMyy(Tyx * xy_ss + Tyy * yy_ss);
4233                         setMyz(Tyz);
4234                         setMzx(Tzx * xx_ss + Tzy * yx_ss);
4235                         setMzy(Tzx * xy_ss + Tzy * yy_ss);
4236                         setMzz(Tzz);
4237                         break;
4238                     case APPLY_SHEAR | APPLY_TRANSLATE:
4239                         final double xy_sht = getMxy();
4240                         final double tx_sht = getTx();
4241                         final double yx_sht = getMyx();
4242                         final double ty_sht = getTy();
4243                         setMxx(Txy * yx_sht);
4244                         setMxy(Txx * xy_sht);
4245                         setMxz(Txz);
4246                         setTx( Txx * tx_sht  + Txy * ty_sht);
4247                         setMyx(Tyy * yx_sht);
4248                         setMyy(Tyx * xy_sht);
4249                         setMyz(Tyz);
4250                         setTy( Tyx * tx_sht  + Tyy * ty_sht);
4251                         setMzx(Tzy * yx_sht);
4252                         setMzy(Tzx * xy_sht);
4253                         setMzz(Tzz);
4254                         setTz( Tzx * tx_sht  + Tzy * ty_sht);
4255                         break;
4256                     case APPLY_SHEAR:
4257                         final double xy_sh = getMxy();
4258                         final double yx_sh = getMyx();
4259                         setMxx(Txy * yx_sh);
4260                         setMxy(Txx * xy_sh);
4261                         setMxz(Txz);
4262                         setMyx(Tyy * yx_sh);
4263                         setMyy(Tyx * xy_sh);
4264                         setMyz(Tyz);
4265                         setMzx(Tzy * yx_sh);
4266                         setMzy(Tzx * xy_sh);
4267                         setMzz(Tzz);
4268                         break;
4269                     case APPLY_SCALE | APPLY_TRANSLATE:
4270                         final double xx_st = getMxx();
4271                         final double tx_st = getTx();
4272                         final double yy_st = getMyy();
4273                         final double ty_st = getTy();
4274                         setMxx(Txx * xx_st);
4275                         setMxy(Txy * yy_st);
4276                         setMxz(Txz);
4277                         setTx( Txx * tx_st  + Txy * ty_st);
4278                         setMyx(Tyx * xx_st);
4279                         setMyy(Tyy * yy_st);
4280                         setMyz(Tyz);
4281                         setTy( Tyx * tx_st  + Tyy * ty_st);
4282                         setMzx(Tzx * xx_st);
4283                         setMzy(Tzy * yy_st);
4284                         setMzz(Tzz);
4285                         setTz( Tzx * tx_st  + Tzy * ty_st);
4286                         break;
4287                     case APPLY_SCALE:
4288                         final double xx_s = getMxx();
4289                         final double yy_s = getMyy();
4290                         setMxx(Txx * xx_s);
4291                         setMxy(Txy * yy_s);
4292                         setMxz(Txz);
4293                         setMyx(Tyx * xx_s);
4294                         setMyy(Tyy * yy_s);
4295                         setMyz(Tyz);
4296                         setMzx(Tzx * xx_s);
4297                         setMzy(Tzy * yy_s);
4298                         setMzz(Tzz);
4299                         break;
4300                     case APPLY_TRANSLATE:
4301                         final double tx_t = getTx();
4302                         final double ty_t = getTy();
4303                         setMxx(Txx);
4304                         setMxy(Txy);
4305                         setMxz(Txz);
4306                         setTx( Txx * tx_t  + Txy * ty_t);
4307                         setMyx(Tyx);
4308                         setMyy(Tyy);
4309                         setMyz(Tyz);
4310                         setTy( Tyx * tx_t  + Tyy * ty_t);
4311                         setMzx(Tzx);
4312                         setMzy(Tzy);
4313                         setMzz(Tzz);
4314                         setTz( Tzx * tx_t  + Tzy * ty_t);
4315                         break;
4316                     case APPLY_IDENTITY:
4317                         setMxx(Txx);
4318                         setMxy(Txy);
4319                         setMxz(Txz);
4320                         setMyx(Tyx);
4321                         setMyy(Tyy);
4322                         setMyz(Tyz);
4323                         setMzx(Tzx);
4324                         setMzy(Tzy);
4325                         setMzz(Tzz);
4326                         break;
4327                 }
4328                 break;
4329             case APPLY_TRANSLATE:
4330                 final double tx_t = getTx();
4331                 final double ty_t = getTy();
4332                 final double tz_t = getTz();
4333                 setMxx(Txx);
4334                 setMxy(Txy);
4335                 setMxz(Txz);
4336                 setMyx(Tyx);
4337                 setMyy(Tyy);
4338                 setMyz(Tyz);
4339                 setMzx(Tzx);
4340                 setMzy(Tzy);
4341                 setMzz(Tzz);
4342                 setTx( Txx * tx_t  + Txy * ty_t  + Txz * tz_t);
4343                 setTy( Tyx * tx_t  + Tyy * ty_t  + Tyz * tz_t);
4344                 setTz( Tzx * tx_t  + Tzy * ty_t  + Tzz * tz_t);
4345                 break;
4346             case APPLY_SCALE:
4347                 final double xx_s = getMxx();
4348                 final double yy_s = getMyy();
4349                 final double zz_s = getMzz();
4350                 setMxx(Txx * xx_s);
4351                 setMxy(Txy * yy_s);
4352                 setMxz(Txz * zz_s);
4353                 setMyx(Tyx * xx_s);
4354                 setMyy(Tyy * yy_s);
4355                 setMyz(Tyz * zz_s);
4356                 setMzx(Tzx * xx_s);
4357                 setMzy(Tzy * yy_s);
4358                 setMzz(Tzz * zz_s);
4359                 break;
4360             case APPLY_SCALE | APPLY_TRANSLATE:
4361                 final double xx_st = getMxx();
4362                 final double tx_st = getTx();
4363                 final double yy_st = getMyy();
4364                 final double ty_st = getTy();
4365                 final double zz_st = getMzz();
4366                 final double tz_st = getTz();
4367                 setMxx(Txx * xx_st);
4368                 setMxy(Txy * yy_st);
4369                 setMxz(Txz * zz_st);
4370                 setTx( Txx * tx_st  + Txy * ty_st  + Txz * tz_st);
4371                 setMyx(Tyx * xx_st);
4372                 setMyy(Tyy * yy_st);
4373                 setMyz(Tyz * zz_st);
4374                 setTy( Tyx * tx_st  + Tyy * ty_st  + Tyz * tz_st);
4375                 setMzx(Tzx * xx_st);
4376                 setMzy(Tzy * yy_st);
4377                 setMzz(Tzz * zz_st);
4378                 setTz( Tzx * tx_st  + Tzy * ty_st  + Tzz * tz_st);
4379                 break;
4380             case APPLY_3D_COMPLEX:
4381                 final double m_xx = getMxx();
4382                 final double m_xy = getMxy();
4383                 final double m_xz = getMxz();
4384                 final double t_x = getTx();
4385                 final double m_yx = getMyx();
4386                 final double m_yy = getMyy();
4387                 final double m_yz = getMyz();
4388                 final double t_y = getTy();
4389                 final double m_zx = getMzx();
4390                 final double m_zy = getMzy();
4391                 final double m_zz = getMzz();
4392                 final double t_z = getTz();
4393                 setMxx(Txx * m_xx + Txy * m_yx + Txz * m_zx /* + Ttx * 0.0 */);
4394                 setMxy(Txx * m_xy + Txy * m_yy + Txz * m_zy /* + Ttx * 0.0 */);
4395                 setMxz(Txx * m_xz + Txy * m_yz + Txz * m_zz /* + Ttx * 0.0 */);
4396                 setTx( Txx * t_x  + Txy * t_y  + Txz * t_z  /* + Ttx * 0.0 */);
4397                 setMyx(Tyx * m_xx + Tyy * m_yx + Tyz * m_zx /* + Tty * 0.0 */);
4398                 setMyy(Tyx * m_xy + Tyy * m_yy + Tyz * m_zy /* + Tty * 0.0 */);
4399                 setMyz(Tyx * m_xz + Tyy * m_yz + Tyz * m_zz /* + Tty * 0.0 */);
4400                 setTy( Tyx * t_x  + Tyy * t_y  + Tyz * t_z  /* + Tty * 0.0 */);
4401                 setMzx(Tzx * m_xx + Tzy * m_yx + Tzz * m_zx /* + Ttz * 0.0 */);
4402                 setMzy(Tzx * m_xy + Tzy * m_yy + Tzz * m_zy /* + Ttz * 0.0 */);
4403                 setMzz(Tzx * m_xz + Tzy * m_yz + Tzz * m_zz /* + Ttz * 0.0 */);
4404                 setTz( Tzx * t_x  + Tzy * t_y  + Tzz * t_z  /* + Ttz * 0.0 */);
4405                 break;
4406         }
4407 
4408         updateState();
4409     }
4410 
4411     /**
4412      * 2D implementation of {@code prependRotation}.
4413      * If this is a 3D transform, the call is redirected to {@code preRotate3D()}.
4414      */
4415     private void preRotate2D(double theta) {
4416 
4417         if (state3d != APPLY_NON_3D) {
4418             preRotate3D(theta);
4419             return;
4420         }
4421 
4422         double sin = Math.sin(Math.toRadians(theta));
4423         if (sin == 1.0) {
4424             preRotate2D_90();
4425         } else if (sin == -1.0) {
4426             preRotate2D_270();
4427         } else {
4428             double cos = Math.cos(Math.toRadians(theta));
4429             if (cos == -1.0) {
4430                 preRotate2D_180();
4431             } else if (cos != 1.0) {
4432                 double M0, M1;
4433                 M0 = getMxx();
4434                 M1 = getMyx();
4435                 setMxx(cos * M0 - sin * M1);
4436                 setMyx(sin * M0 + cos * M1);
4437                 M0 = getMxy();
4438                 M1 = getMyy();
4439                 setMxy(cos * M0 - sin * M1);
4440                 setMyy(sin * M0 + cos * M1);
4441                 M0 = getTx();
4442                 M1 = getTy();
4443                 setTx(cos * M0 - sin * M1);
4444                 setTy(sin * M0 + cos * M1);
4445                 updateState2D();
4446             }
4447         }
4448     }
4449 
4450     /**
4451      * 2D implementation of {@code prependRotation} for 90 degrees rotation
4452      * around Z axis.
4453      * Behaves wrong when called for a 3D transform.
4454      */
4455     private void preRotate2D_90() {
4456         double M0 = getMxx();
4457         setMxx(-getMyx());
4458         setMyx(M0);
4459         M0 = getMxy();
4460         setMxy(-getMyy());
4461         setMyy(M0);
4462         M0 = getTx();
4463         setTx(-getTy());
4464         setTy(M0);
4465 
4466         int newstate = rot90conversion[state2d];
4467         if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE &&
4468                 getMxx() == 1.0 && getMyy() == 1.0) {
4469             newstate -= APPLY_SCALE;
4470         } else if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SHEAR &&
4471                 getMxy() == 0.0 && getMyx() == 0.0) {
4472             newstate = (newstate & ~APPLY_SHEAR | APPLY_SCALE);
4473         }
4474         state2d = newstate;
4475     }
4476 
4477     /**
4478      * 2D implementation of {@code prependRotation} for 180 degrees rotation
4479      * around Z axis.
4480      * Behaves wrong when called for a 3D transform.
4481      */
4482     private void preRotate2D_180() {
4483         setMxx(-getMxx());
4484         setMxy(-getMxy());
4485         setTx(-getTx());
4486         setMyx(-getMyx());
4487         setMyy(-getMyy());
4488         setTy(-getTy());
4489 
4490         if ((state2d & APPLY_SHEAR) != 0) {
4491             if (getMxx() == 0.0 && getMyy() == 0.0) {
4492                 state2d &= ~APPLY_SCALE;
4493             } else {
4494                 state2d |= APPLY_SCALE;
4495             }
4496         } else {
4497             if (getMxx() == 1.0 && getMyy() == 1.0) {
4498                 state2d &= ~APPLY_SCALE;
4499             } else {
4500                 state2d |= APPLY_SCALE;
4501             }
4502         }
4503     }
4504 
4505     /**
4506      * 2D implementation of {@code prependRotation} for 270 degrees rotation
4507      * around Z axis.
4508      * Behaves wrong when called for a 3D transform.
4509      */
4510     private void preRotate2D_270() {
4511         double M0 = getMxx();
4512         setMxx(getMyx());
4513         setMyx(-M0);
4514         M0 = getMxy();
4515         setMxy(getMyy());
4516         setMyy(-M0);
4517         M0 = getTx();
4518         setTx(getTy());
4519         setTy(-M0);
4520 
4521         int newstate = rot90conversion[state2d];
4522         if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SCALE &&
4523                 getMxx() == 1.0 && getMyy() == 1.0) {
4524             newstate -= APPLY_SCALE;
4525         } else if ((newstate & (APPLY_SHEAR | APPLY_SCALE)) == APPLY_SHEAR &&
4526                 getMxy() == 0.0 && getMyx() == 0.0) {
4527             newstate = (newstate & ~APPLY_SHEAR | APPLY_SCALE);
4528         }
4529         state2d = newstate;
4530     }
4531 
4532     /**
4533      * 3D implementation of {@code prependRotation} around Z axis.
4534      * If this is a 2D transform, the call is redirected to {@code preRotate2D()}.
4535      */
4536     private void preRotate3D(double theta) {
4537         if (state3d == APPLY_NON_3D) {
4538             preRotate2D(theta);
4539             return;
4540         }
4541 
4542         double sin = Math.sin(Math.toRadians(theta));
4543         if (sin == 1.0) {
4544             preRotate3D_90();
4545         } else if (sin == -1.0) {
4546             preRotate3D_270();
4547         } else {
4548             double cos = Math.cos(Math.toRadians(theta));
4549             if (cos == -1.0) {
4550                 preRotate3D_180();
4551             } else if (cos != 1.0) {
4552                 double M0, M1;
4553                 M0 = getMxx();
4554                 M1 = getMyx();
4555                 setMxx(cos * M0 - sin * M1);
4556                 setMyx(sin * M0 + cos * M1);
4557                 M0 = getMxy();
4558                 M1 = getMyy();
4559                 setMxy(cos * M0 - sin * M1);
4560                 setMyy(sin * M0 + cos * M1);
4561                 M0 = getMxz();
4562                 M1 = getMyz();
4563                 setMxz(cos * M0 - sin * M1);
4564                 setMyz(sin * M0 + cos * M1);
4565                 M0 = getTx();
4566                 M1 = getTy();
4567                 setTx(cos * M0 - sin * M1);
4568                 setTy(sin * M0 + cos * M1);
4569                 updateState();
4570             }
4571         }
4572     }
4573 
4574     /**
4575      * 3D implementation of {@code prependRotation} for 90 degrees rotation
4576      * around Z axis.
4577      * Behaves wrong when called for a 2D transform.
4578      */
4579     private void preRotate3D_90() {
4580         double M0 = getMxx();
4581         setMxx(-getMyx());
4582         setMyx(M0);
4583         M0 = getMxy();
4584         setMxy(-getMyy());
4585         setMyy(M0);
4586         M0 = getMxz();
4587         setMxz(-getMyz());
4588         setMyz(M0);
4589         M0 = getTx();
4590         setTx(-getTy());
4591         setTy(M0);
4592 
4593         switch(state3d) {
4594             default:
4595                 stateError();
4596                 // cannot reach
4597             case APPLY_TRANSLATE:
4598                 state3d = APPLY_3D_COMPLEX;
4599                 return;
4600             case APPLY_SCALE:
4601             case APPLY_SCALE | APPLY_TRANSLATE:
4602                 if (getMxy() != 0.0 || getMyx() != 0.0) {
4603                     state3d = APPLY_3D_COMPLEX;
4604                 }
4605                 return;
4606             case APPLY_3D_COMPLEX:
4607                 updateState();
4608                 return;
4609         }
4610     }
4611 
4612     /**
4613      * 3D implementation of {@code prependRotation} for 180 degrees rotation
4614      * around Z axis.
4615      * Behaves wrong when called for a 2D transform.
4616      */
4617     private void preRotate3D_180() {
4618         final double mxx = getMxx();
4619         final double myy = getMyy();
4620         setMxx(-mxx);
4621         setMyy(-myy);
4622         setTx(-getTx());
4623         setTy(-getTy());
4624 
4625         if (state3d == APPLY_3D_COMPLEX) {
4626             setMxy(-getMxy());
4627             setMxz(-getMxz());
4628             setMyx(-getMyx());
4629             setMyz(-getMyz());
4630             updateState();
4631             return;
4632         }
4633 
4634         if (mxx == -1.0 && myy == -1.0 && getMzz() == 1.0) {
4635             // must have been 3d because of translation, which remained
4636             state3d &= ~APPLY_SCALE;
4637         } else {
4638             state3d |= APPLY_SCALE;
4639         }
4640     }
4641 
4642     /**
4643      * 3D implementation of {@code prependRotation} for 270 degrees rotation
4644      * around Z axis.
4645      * Behaves wrong when called for a 2D transform.
4646      */
4647     private void preRotate3D_270() {
4648         double M0 = getMxx();
4649         setMxx(getMyx());
4650         setMyx(-M0);
4651         M0 = getMxy();
4652         setMxy(getMyy());
4653         setMyy(-M0);
4654         M0 = getMxz();
4655         setMxz(getMyz());
4656         setMyz(-M0);
4657         M0 = getTx();
4658         setTx(getTy());
4659         setTy(-M0);
4660 
4661         switch(state3d) {
4662             default:
4663                 stateError();
4664                 // cannot reach
4665             case APPLY_TRANSLATE:
4666                 state3d = APPLY_3D_COMPLEX;
4667                 return;
4668             case APPLY_SCALE:
4669             case APPLY_SCALE | APPLY_TRANSLATE:
4670                 if (getMxy() != 0.0 || getMyx() != 0.0) {
4671                     state3d = APPLY_3D_COMPLEX;
4672                 }
4673                 return;
4674             case APPLY_3D_COMPLEX:
4675                 updateState();
4676                 return;
4677         }
4678     }
4679 
4680     /* *************************************************************************
4681      *                                                                         *
4682      *                     Transform, Inverse Transform                        *
4683      *                                                                         *
4684      **************************************************************************/
4685 
4686     @Override
4687     public Point2D transform(double x, double y) {
4688         ensureCanTransform2DPoint();
4689 
4690         switch (state2d) {
4691             default:
4692                 stateError();
4693                 // cannot reach
4694             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
4695                 return new Point2D(
4696                     getMxx() * x + getMxy() * y + getTx(),
4697                     getMyx() * x + getMyy() * y + getTy());
4698             case APPLY_SHEAR | APPLY_SCALE:
4699                 return new Point2D(
4700                     getMxx() * x + getMxy() * y,
4701                     getMyx() * x + getMyy() * y);
4702             case APPLY_SHEAR | APPLY_TRANSLATE:
4703                 return new Point2D(
4704                         getMxy() * y + getTx(),
4705                         getMyx() * x + getTy());
4706             case APPLY_SHEAR:
4707                 return new Point2D(getMxy() * y, getMyx() * x);
4708             case APPLY_SCALE | APPLY_TRANSLATE:
4709                 return new Point2D(
4710                         getMxx() * x + getTx(),
4711                         getMyy() * y + getTy());
4712             case APPLY_SCALE:
4713                 return new Point2D(getMxx() * x, getMyy() * y);
4714             case APPLY_TRANSLATE:
4715                 return new Point2D(x + getTx(), y + getTy());
4716             case APPLY_IDENTITY:
4717                 return new Point2D(x, y);
4718         }
4719     }
4720 
4721     @Override
4722     public Point3D transform(double x, double y, double z) {
4723         switch (state3d) {
4724             default:
4725                 stateError();
4726                 // cannot reach
4727             case APPLY_NON_3D:
4728                 switch (state2d) {
4729                     default:
4730                         stateError();
4731                         // cannot reach
4732                     case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
4733                         return new Point3D(
4734                             getMxx() * x + getMxy() * y + getTx(),
4735                             getMyx() * x + getMyy() * y + getTy(), z);
4736                     case APPLY_SHEAR | APPLY_SCALE:
4737                         return new Point3D(
4738                             getMxx() * x + getMxy() * y,
4739                             getMyx() * x + getMyy() * y, z);
4740                     case APPLY_SHEAR | APPLY_TRANSLATE:
4741                         return new Point3D(
4742                                 getMxy() * y + getTx(), getMyx() * x + getTy(),
4743                                 z);
4744                     case APPLY_SHEAR:
4745                         return new Point3D(getMxy() * y, getMyx() * x, z);
4746                     case APPLY_SCALE | APPLY_TRANSLATE:
4747                         return new Point3D(
4748                                 getMxx() * x + getTx(), getMyy() * y + getTy(),
4749                                 z);
4750                     case APPLY_SCALE:
4751                         return new Point3D(getMxx() * x, getMyy() * y, z);
4752                     case APPLY_TRANSLATE:
4753                         return new Point3D(x + getTx(), y + getTy(), z);
4754                     case APPLY_IDENTITY:
4755                         return new Point3D(x, y, z);
4756                 }
4757             case APPLY_TRANSLATE:
4758                 return new Point3D(x + getTx(), y + getTy(), z + getTz());
4759             case APPLY_SCALE:
4760                 return new Point3D(getMxx() * x, getMyy() * y, getMzz() * z);
4761             case APPLY_SCALE | APPLY_TRANSLATE:
4762                 return new Point3D(
4763                         getMxx() * x + getTx(),
4764                         getMyy() * y + getTy(),
4765                         getMzz() * z + getTz());
4766             case APPLY_3D_COMPLEX:
4767                 return new Point3D(
4768                     getMxx() * x + getMxy() * y + getMxz() * z + getTx(),
4769                     getMyx() * x + getMyy() * y + getMyz() * z + getTy(),
4770                     getMzx() * x + getMzy() * y + getMzz() * z + getTz());
4771         }
4772     }
4773 
4774     @Override
4775     void transform2DPointsImpl(double[] srcPts, int srcOff,
4776             double[] dstPts, int dstOff, int numPts) {
4777 
4778         double mxx, mxy, tx, myx, myy, ty;
4779 
4780         switch (state2d) {
4781             default:
4782                 stateError();
4783                 // cannot reach
4784             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
4785                 mxx = getMxx(); mxy = getMxy(); tx = getTx();
4786                 myx = getMyx(); myy = getMyy(); ty = getTy();
4787                 while (--numPts >= 0) {
4788                     final double x = srcPts[srcOff++];
4789                     final double y = srcPts[srcOff++];
4790                     dstPts[dstOff++] = mxx * x + mxy * y + tx;
4791                     dstPts[dstOff++] = myx * x + myy * y + ty;
4792                 }
4793                 return;
4794             case APPLY_SHEAR | APPLY_SCALE:
4795                 mxx = getMxx(); mxy = getMxy();
4796                 myx = getMyx(); myy = getMyy();
4797                 while (--numPts >= 0) {
4798                     final double x = srcPts[srcOff++];
4799                     final double y = srcPts[srcOff++];
4800                     dstPts[dstOff++] = mxx * x + mxy * y;
4801                     dstPts[dstOff++] = myx * x + myy * y;
4802                 }
4803                 return;
4804             case APPLY_SHEAR | APPLY_TRANSLATE:
4805                 mxy = getMxy(); tx = getTx();
4806                 myx = getMyx(); ty = getTy();
4807                 while (--numPts >= 0) {
4808                     final double x = srcPts[srcOff++];
4809                     dstPts[dstOff++] = mxy * srcPts[srcOff++] + tx;
4810                     dstPts[dstOff++] = myx * x + ty;
4811                 }
4812                 return;
4813             case APPLY_SHEAR:
4814                 mxy = getMxy();
4815                 myx = getMyx();
4816                 while (--numPts >= 0) {
4817                     final double x = srcPts[srcOff++];
4818                     dstPts[dstOff++] = mxy * srcPts[srcOff++];
4819                     dstPts[dstOff++] = myx * x;
4820                 }
4821                 return;
4822             case APPLY_SCALE | APPLY_TRANSLATE:
4823                 mxx = getMxx(); tx = getTx();
4824                 myy = getMyy(); ty = getTy();
4825                 while (--numPts >= 0) {
4826                     dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx;
4827                     dstPts[dstOff++] = myy * srcPts[srcOff++] + ty;
4828                 }
4829                 return;
4830             case APPLY_SCALE:
4831                 mxx = getMxx();
4832                 myy = getMyy();
4833                 while (--numPts >= 0) {
4834                     dstPts[dstOff++] = mxx * srcPts[srcOff++];
4835                     dstPts[dstOff++] = myy * srcPts[srcOff++];
4836                 }
4837                 return;
4838             case APPLY_TRANSLATE:
4839                 tx = getTx();
4840                 ty = getTy();
4841                 while (--numPts >= 0) {
4842                     dstPts[dstOff++] = srcPts[srcOff++] + tx;
4843                     dstPts[dstOff++] = srcPts[srcOff++] + ty;
4844                 }
4845                 return;
4846             case APPLY_IDENTITY:
4847                 if (srcPts != dstPts || srcOff != dstOff) {
4848                     System.arraycopy(srcPts, srcOff, dstPts, dstOff,
4849                                      numPts * 2);
4850                 }
4851                 return;
4852         }
4853     }
4854 
4855     @Override
4856     void transform3DPointsImpl(double[] srcPts, int srcOff,
4857             double[] dstPts, int dstOff, int numPts) {
4858 
4859         double mxx, mxy, tx, myx, myy, ty, mzz, tz;
4860 
4861         switch(state3d) {
4862             default:
4863                 stateError();
4864                 // cannot reach
4865             case APPLY_NON_3D:
4866                 switch (state2d) {
4867                     default:
4868                         stateError();
4869                         // cannot reach
4870                     case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
4871                         mxx = getMxx(); mxy = getMxy(); tx = getTx();
4872                         myx = getMyx(); myy = getMyy(); ty = getTy();
4873                         while (--numPts >= 0) {
4874                             final double x = srcPts[srcOff++];
4875                             final double y = srcPts[srcOff++];
4876                             dstPts[dstOff++] = mxx * x + mxy * y + tx;
4877                             dstPts[dstOff++] = myx * x + myy * y + ty;
4878                             dstPts[dstOff++] = srcPts[srcOff++];
4879                         }
4880                         return;
4881                     case APPLY_SHEAR | APPLY_SCALE:
4882                         mxx = getMxx(); mxy = getMxy();
4883                         myx = getMyx(); myy = getMyy();
4884                         while (--numPts >= 0) {
4885                             final double x = srcPts[srcOff++];
4886                             final double y = srcPts[srcOff++];
4887                             dstPts[dstOff++] = mxx * x + mxy * y;
4888                             dstPts[dstOff++] = myx * x + myy * y;
4889                             dstPts[dstOff++] = srcPts[srcOff++];
4890                         }
4891                         return;
4892                     case APPLY_SHEAR | APPLY_TRANSLATE:
4893                         mxy = getMxy(); tx = getTx();
4894                         myx = getMyx(); ty = getTy();
4895                         while (--numPts >= 0) {
4896                             final double x = srcPts[srcOff++];
4897                             dstPts[dstOff++] = mxy * srcPts[srcOff++] + tx;
4898                             dstPts[dstOff++] = myx * x + ty;
4899                             dstPts[dstOff++] = srcPts[srcOff++];
4900                         }
4901                         return;
4902                     case APPLY_SHEAR:
4903                         mxy = getMxy();
4904                         myx = getMyx();
4905                         while (--numPts >= 0) {
4906                             final double x = srcPts[srcOff++];
4907                             dstPts[dstOff++] = mxy * srcPts[srcOff++];
4908                             dstPts[dstOff++] = myx * x;
4909                             dstPts[dstOff++] = srcPts[srcOff++];
4910                         }
4911                         return;
4912                     case APPLY_SCALE | APPLY_TRANSLATE:
4913                         mxx = getMxx(); tx = getTx();
4914                         myy = getMyy(); ty = getTy();
4915                         while (--numPts >= 0) {
4916                             dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx;
4917                             dstPts[dstOff++] = myy * srcPts[srcOff++] + ty;
4918                             dstPts[dstOff++] = srcPts[srcOff++];
4919                         }
4920                         return;
4921                     case APPLY_SCALE:
4922                         mxx = getMxx();
4923                         myy = getMyy();
4924                         while (--numPts >= 0) {
4925                             dstPts[dstOff++] = mxx * srcPts[srcOff++];
4926                             dstPts[dstOff++] = myy * srcPts[srcOff++];
4927                             dstPts[dstOff++] = srcPts[srcOff++];
4928                         }
4929                         return;
4930                     case APPLY_TRANSLATE:
4931                         tx = getTx();
4932                         ty = getTy();
4933                         while (--numPts >= 0) {
4934                             dstPts[dstOff++] = srcPts[srcOff++] + tx;
4935                             dstPts[dstOff++] = srcPts[srcOff++] + ty;
4936                             dstPts[dstOff++] = srcPts[srcOff++];
4937                         }
4938                         return;
4939                     case APPLY_IDENTITY:
4940                         if (srcPts != dstPts || srcOff != dstOff) {
4941                             System.arraycopy(srcPts, srcOff, dstPts, dstOff,
4942                                              numPts * 3);
4943                         }
4944                         return;
4945                 }
4946                 // cannot reach
4947             case APPLY_TRANSLATE:
4948                 tx = getTx();
4949                 ty = getTy();
4950                 tz = getTz();
4951                 while (--numPts >= 0) {
4952                     dstPts[dstOff++] = srcPts[srcOff++] + tx;
4953                     dstPts[dstOff++] = srcPts[srcOff++] + ty;
4954                     dstPts[dstOff++] = srcPts[srcOff++] + tz;
4955                 }
4956                 return;
4957             case APPLY_SCALE:
4958                 mxx = getMxx();
4959                 myy = getMyy();
4960                 mzz = getMzz();
4961                 while (--numPts >= 0) {
4962                     dstPts[dstOff++] = mxx * srcPts[srcOff++];
4963                     dstPts[dstOff++] = myy * srcPts[srcOff++];
4964                     dstPts[dstOff++] = mzz * srcPts[srcOff++];
4965                 }
4966                 return;
4967             case APPLY_SCALE | APPLY_TRANSLATE:
4968                 mxx = getMxx(); tx = getTx();
4969                 myy = getMyy(); ty = getTy();
4970                 mzz = getMzz(); tz = getTz();
4971                 while (--numPts >= 0) {
4972                     dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx;
4973                     dstPts[dstOff++] = myy * srcPts[srcOff++] + ty;
4974                     dstPts[dstOff++] = mzz * srcPts[srcOff++] + tz;
4975                 }
4976                 return;
4977             case APPLY_3D_COMPLEX:
4978                 mxx = getMxx();
4979                 mxy = getMxy();
4980                 double mxz = getMxz();
4981                 tx = getTx();
4982                 myx = getMyx();
4983                 myy = getMyy();
4984                 double myz = getMyz();
4985                 ty = getTy();
4986                 double mzx = getMzx();
4987                 double mzy = getMzy();
4988                 mzz = getMzz();
4989                 tz = getTz();
4990 
4991                 while (--numPts >= 0) {
4992                     final double x = srcPts[srcOff++];
4993                     final double y = srcPts[srcOff++];
4994                     final double z = srcPts[srcOff++];
4995 
4996                     dstPts[dstOff++] = mxx * x + mxy * y + mxz * z + tx;
4997                     dstPts[dstOff++] = myx * x + myy * y + myz * z + ty;
4998                     dstPts[dstOff++] = mzx * x + mzy * y + mzz * z + tz;
4999                 }
5000                 return;
5001         }
5002     }
5003 
5004     @Override
5005     public Point2D deltaTransform(double x, double y) {
5006         ensureCanTransform2DPoint();
5007 
5008         switch (state2d) {
5009             default:
5010                 stateError();
5011                 // cannot reach
5012             case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
5013             case APPLY_SHEAR | APPLY_SCALE:
5014                 return new Point2D(
5015                     getMxx() * x + getMxy() * y,
5016                     getMyx() * x + getMyy() * y);
5017             case APPLY_SHEAR | APPLY_TRANSLATE:
5018             case APPLY_SHEAR:
5019                 return new Point2D(getMxy() * y, getMyx() * x);
5020             case APPLY_SCALE | APPLY_TRANSLATE:
5021             case APPLY_SCALE:
5022                 return new Point2D(getMxx() * x, getMyy() * y);
5023             case APPLY_TRANSLATE:
5024             case APPLY_IDENTITY:
5025                 return new Point2D(x, y);
5026         }
5027     }
5028 
5029     @Override
5030     public Point3D deltaTransform(double x, double y, double z) {
5031         switch (state3d) {
5032             default:
5033                 stateError();
5034                 // cannot reach
5035             case APPLY_NON_3D:
5036                 switch (state2d) {
5037                     default:
5038                         stateError();
5039                         // cannot reach
5040                     case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE:
5041                     case APPLY_SHEAR | APPLY_SCALE:
5042                         return new Point3D(
5043                             getMxx() * x + getMxy() * y,
5044                             getMyx() * x + getMyy() * y, z);
5045                     case APPLY_SHEAR | APPLY_TRANSLATE:
5046                     case APPLY_SHEAR:
5047                         return new Point3D(getMxy() * y, getMyx() * x, z);
5048                     case APPLY_SCALE | APPLY_TRANSLATE:
5049                     case APPLY_SCALE:
5050                         return new Point3D(getMxx() * x, getMyy() * y, z);
5051                     case APPLY_TRANSLATE:
5052                     case APPLY_IDENTITY:
5053                         return new Point3D(x, y, z);
5054                 }
5055             case APPLY_TRANSLATE:
5056                 return new Point3D(x, y, z);
5057             case APPLY_SCALE:
5058             case APPLY_SCALE | APPLY_TRANSLATE:
5059                 return new Point3D(getMxx() * x, getMyy() * y, getMzz() * z);
5060             case APPLY_3D_COMPLEX:
5061                 return new Point3D(
5062                     getMxx() * x + getMxy() * y + getMxz() * z,
5063                     getMyx() * x + getMyy() * y + getMyz() * z,
5064                     getMzx() * x + getMzy() * y + getMzz() * z);
5065         }
5066     }
5067 
5068     @Override
5069     public Point2D inverseTransform(double x, double y)
5070             throws NonInvertibleTransformException {
5071         ensureCanTransform2DPoint();
5072 
5073         switch (state2d) {
5074             default:
5075                 return super.inverseTransform(x, y);
5076             case APPLY_SHEAR | APPLY_TRANSLATE:
5077                 final double mxy_st = getMxy();
5078                 final double myx_st = getMyx();
5079                 if (mxy_st == 0.0 || myx_st == 0.0) {
5080                     throw new NonInvertibleTransformException("Determinant is 0");
5081                 }
5082                 return new Point2D(
5083                         (1.0 / myx_st) * y - getTy() / myx_st,
5084                         (1.0 / mxy_st) * x - getTx() / mxy_st);
5085             case APPLY_SHEAR:
5086                 final double mxy_s = getMxy();
5087                 final double myx_s = getMyx();
5088                 if (mxy_s == 0.0 || myx_s == 0.0) {
5089                     throw new NonInvertibleTransformException("Determinant is 0");
5090                 }
5091                 return new Point2D((1.0 / myx_s) * y, (1.0 / mxy_s) * x);
5092             case APPLY_SCALE | APPLY_TRANSLATE:
5093                 final double mxx_st = getMxx();
5094                 final double myy_st = getMyy();
5095                 if (mxx_st == 0.0 || myy_st == 0.0) {
5096                     throw new NonInvertibleTransformException("Determinant is 0");
5097                 }
5098                 return new Point2D(
5099                         (1.0 / mxx_st) * x - getTx() / mxx_st,
5100                         (1.0 / myy_st) * y - getTy() / myy_st);
5101             case APPLY_SCALE:
5102                 final double mxx_s = getMxx();
5103                 final double myy_s = getMyy();
5104                 if (mxx_s == 0.0 || myy_s == 0.0) {
5105                     throw new NonInvertibleTransformException("Determinant is 0");
5106                 }
5107                 return new Point2D((1.0 / mxx_s) * x, (1.0 / myy_s) * y);
5108             case APPLY_TRANSLATE:
5109                 return new Point2D(x - getTx(), y - getTy());
5110             case APPLY_IDENTITY:
5111                 return new Point2D(x, y);
5112         }
5113     }
5114 
5115     @Override
5116     public Point3D inverseTransform(double x, double y, double z)
5117             throws NonInvertibleTransformException {
5118         switch(state3d) {
5119             default:
5120                 stateError();
5121                 // cannot reach
5122             case APPLY_NON_3D:
5123                 switch (state2d) {
5124                     default:
5125                         return super.inverseTransform(x, y, z);
5126                     case APPLY_SHEAR | APPLY_TRANSLATE:
5127                         final double mxy_st = getMxy();
5128                         final double myx_st = getMyx();
5129                         if (mxy_st == 0.0 || myx_st == 0.0) {
5130                             throw new NonInvertibleTransformException(
5131                                     "Determinant is 0");
5132                         }
5133                         return new Point3D(
5134                                 (1.0 / myx_st) * y - getTy() / myx_st,
5135                                 (1.0 / mxy_st) * x - getTx() / mxy_st, z);
5136                     case APPLY_SHEAR:
5137                         final double mxy_s = getMxy();
5138                         final double myx_s = getMyx();
5139                         if (mxy_s == 0.0 || myx_s == 0.0) {
5140                             throw new NonInvertibleTransformException(
5141                                     "Determinant is 0");
5142                         }
5143                         return new Point3D(
5144                                 (1.0 / myx_s) * y,
5145                                 (1.0 / mxy_s) * x, z);
5146                     case APPLY_SCALE | APPLY_TRANSLATE:
5147                         final double mxx_st = getMxx();
5148                         final double myy_st = getMyy();
5149                         if (mxx_st == 0.0 || myy_st == 0.0) {
5150                             throw new NonInvertibleTransformException(
5151                                     "Determinant is 0");
5152                         }
5153                         return new Point3D(
5154                                 (1.0 / mxx_st) * x - getTx() / mxx_st,
5155                                 (1.0 / myy_st) * y - getTy() / myy_st, z);
5156                     case APPLY_SCALE:
5157                         final double mxx_s = getMxx();
5158                         final double myy_s = getMyy();
5159                         if (mxx_s == 0.0 || myy_s == 0.0) {
5160                             throw new NonInvertibleTransformException(
5161                                     "Determinant is 0");
5162                         }
5163                         return new Point3D((1.0 / mxx_s) * x, (1.0 / myy_s) * y, z);
5164                     case APPLY_TRANSLATE:
5165                         return new Point3D(x - getTx(), y - getTy(), z);
5166                     case APPLY_IDENTITY:
5167                         return new Point3D(x, y, z);
5168                 }
5169             case APPLY_TRANSLATE:
5170                 return new Point3D(x - getTx(), y - getTy(), z - getTz());
5171             case APPLY_SCALE:
5172                 final double mxx_s = getMxx();
5173                 final double myy_s = getMyy();
5174                 final double mzz_s = getMzz();
5175                 if (mxx_s == 0.0 || myy_s == 0.0 || mzz_s == 0.0) {
5176                     throw new NonInvertibleTransformException("Determinant is 0");
5177                 }
5178                 return new Point3D(
5179                         (1.0 / mxx_s) * x,
5180                         (1.0 / myy_s) * y,
5181                         (1.0 / mzz_s) * z);
5182             case APPLY_SCALE | APPLY_TRANSLATE:
5183                 final double mxx_st = getMxx();
5184                 final double myy_st = getMyy();
5185                 final double mzz_st = getMzz();
5186                 if (mxx_st == 0.0 || myy_st == 0.0 || mzz_st == 0.0) {
5187                     throw new NonInvertibleTransformException("Determinant is 0");
5188                 }
5189                 return new Point3D(
5190                         (1.0 / mxx_st) * x - getTx() / mxx_st,
5191                         (1.0 / myy_st) * y - getTy() / myy_st,
5192                         (1.0 / mzz_st) * z - getTz() / mzz_st);
5193             case APPLY_3D_COMPLEX:
5194                 return super.inverseTransform(x, y, z);
5195         }
5196     }
5197 
5198     @Override
5199     void inverseTransform2DPointsImpl(double[] srcPts, int srcOff,
5200             double[] dstPts, int dstOff, int numPts)
5201             throws NonInvertibleTransformException {
5202 
5203         double mxx, mxy, tx, myx, myy, ty, tmp;
5204 
5205         switch (state2d) {
5206             default:
5207                 super.inverseTransform2DPointsImpl(srcPts, srcOff,
5208                         dstPts, dstOff, numPts);
5209                 return;
5210 
5211             case APPLY_SHEAR | APPLY_TRANSLATE:
5212                 mxy = getMxy(); tx = getTx();
5213                 myx = getMyx(); ty = getTy();
5214                 if (mxy == 0.0 || myx == 0.0) {
5215                     throw new NonInvertibleTransformException("Determinant is 0");
5216                 }
5217 
5218                 tmp = tx;
5219                 tx = -ty / myx;
5220                 ty = -tmp / mxy;
5221 
5222                 tmp = myx;
5223                 myx = 1.0 / mxy;
5224                 mxy = 1.0 / tmp;
5225 
5226                 while (--numPts >= 0) {
5227                     final double x = srcPts[srcOff++];
5228                     dstPts[dstOff++] = mxy * srcPts[srcOff++] + tx;
5229                     dstPts[dstOff++] = myx * x + ty;
5230                 }
5231                 return;
5232             case APPLY_SHEAR:
5233                 mxy = getMxy();
5234                 myx = getMyx();
5235                 if (mxy == 0.0 || myx == 0.0) {
5236                     throw new NonInvertibleTransformException("Determinant is 0");
5237                 }
5238 
5239                 tmp = myx;
5240                 myx = 1.0 / mxy;
5241                 mxy = 1.0 / tmp;
5242 
5243                 while (--numPts >= 0) {
5244                     final double x = srcPts[srcOff++];
5245                     dstPts[dstOff++] = mxy * srcPts[srcOff++];
5246                     dstPts[dstOff++] = myx * x;
5247                 }
5248                 return;
5249             case APPLY_SCALE | APPLY_TRANSLATE:
5250                 mxx = getMxx(); tx = getTx();
5251                 myy = getMyy(); ty = getTy();
5252                 if (mxx == 0.0 || myy == 0.0) {
5253                     throw new NonInvertibleTransformException("Determinant is 0");
5254                 }
5255 
5256                 tx = -tx / mxx;
5257                 ty = -ty / myy;
5258                 mxx = 1.0 / mxx;
5259                 myy = 1.0 / myy;
5260 
5261                 while (--numPts >= 0) {
5262                     dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx;
5263                     dstPts[dstOff++] = myy * srcPts[srcOff++] + ty;
5264                 }
5265                 return;
5266             case APPLY_SCALE:
5267                 mxx = getMxx();
5268                 myy = getMyy();
5269                 if (mxx == 0.0 || myy == 0.0) {
5270                     throw new NonInvertibleTransformException("Determinant is 0");
5271                 }
5272 
5273                 mxx = 1.0 / mxx;
5274                 myy = 1.0 / myy;
5275 
5276                 while (--numPts >= 0) {
5277                     dstPts[dstOff++] = mxx * srcPts[srcOff++];
5278                     dstPts[dstOff++] = myy * srcPts[srcOff++];
5279                 }
5280                 return;
5281             case APPLY_TRANSLATE:
5282                 tx = getTx();
5283                 ty = getTy();
5284                 while (--numPts >= 0) {
5285                     dstPts[dstOff++] = srcPts[srcOff++] - tx;
5286                     dstPts[dstOff++] = srcPts[srcOff++] - ty;
5287                 }
5288                 return;
5289             case APPLY_IDENTITY:
5290                 if (srcPts != dstPts || srcOff != dstOff) {
5291                     System.arraycopy(srcPts, srcOff, dstPts, dstOff,
5292                                      numPts * 2);
5293                 }
5294                 return;
5295         }
5296     }
5297 
5298     @Override
5299     void inverseTransform3DPointsImpl(double[] srcPts, int srcOff,
5300             double[] dstPts, int dstOff, int numPts)
5301             throws NonInvertibleTransformException {
5302 
5303         double mxx, mxy, tx, myx, myy, ty, mzz, tz, tmp;
5304 
5305         switch (state3d) {
5306             default:
5307                 stateError();
5308                 // cannot reach
5309             case APPLY_NON_3D:
5310                 switch (state2d) {
5311                     default:
5312                         super.inverseTransform3DPointsImpl(srcPts, srcOff,
5313                                 dstPts, dstOff, numPts);
5314                         return;
5315 
5316                     case APPLY_SHEAR | APPLY_TRANSLATE:
5317                         mxy = getMxy(); tx = getTx();
5318                         myx = getMyx(); ty = getTy();
5319                         if (mxy == 0.0 || myx == 0.0) {
5320                             throw new NonInvertibleTransformException(
5321                                     "Determinant is 0");
5322                         }
5323 
5324                         tmp = tx;
5325                         tx = -ty / myx;
5326                         ty = -tmp / mxy;
5327 
5328                         tmp = myx;
5329                         myx = 1.0 / mxy;
5330                         mxy = 1.0 / tmp;
5331 
5332                         while (--numPts >= 0) {
5333                             final double x = srcPts[srcOff++];
5334                             dstPts[dstOff++] = mxy * srcPts[srcOff++] + tx;
5335                             dstPts[dstOff++] = myx * x + ty;
5336                             dstPts[dstOff++] = srcPts[srcOff++];
5337                         }
5338                         return;
5339                     case APPLY_SHEAR:
5340                         mxy = getMxy();
5341                         myx = getMyx();
5342                         if (mxy == 0.0 || myx == 0.0) {
5343                             throw new NonInvertibleTransformException(
5344                                     "Determinant is 0");
5345                         }
5346 
5347                         tmp = myx;
5348                         myx = 1.0 / mxy;
5349                         mxy = 1.0 / tmp;
5350 
5351                         while (--numPts >= 0) {
5352                             final double x = srcPts[srcOff++];
5353                             dstPts[dstOff++] = mxy * srcPts[srcOff++];
5354                             dstPts[dstOff++] = myx * x;
5355                             dstPts[dstOff++] = srcPts[srcOff++];
5356                         }
5357                         return;
5358                     case APPLY_SCALE | APPLY_TRANSLATE:
5359                         mxx = getMxx(); tx = getTx();
5360                         myy = getMyy(); ty = getTy();
5361                         if (mxx == 0.0 || myy == 0.0) {
5362                             throw new NonInvertibleTransformException(
5363                                     "Determinant is 0");
5364                         }
5365 
5366                         tx = -tx / mxx;
5367                         ty = -ty / myy;
5368                         mxx = 1.0 / mxx;
5369                         myy = 1.0 / myy;
5370 
5371                         while (--numPts >= 0) {
5372                             dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx;
5373                             dstPts[dstOff++] = myy * srcPts[srcOff++] + ty;
5374                             dstPts[dstOff++] = srcPts[srcOff++];
5375                         }
5376                         return;
5377                     case APPLY_SCALE:
5378                         mxx = getMxx();
5379                         myy = getMyy();
5380                         if (mxx == 0.0 || myy == 0.0) {
5381                             throw new NonInvertibleTransformException(
5382                                     "Determinant is 0");
5383                         }
5384 
5385                         mxx = 1.0 / mxx;
5386                         myy = 1.0 / myy;
5387 
5388                         while (--numPts >= 0) {
5389                             dstPts[dstOff++] = mxx * srcPts[srcOff++];
5390                             dstPts[dstOff++] = myy * srcPts[srcOff++];
5391                             dstPts[dstOff++] = srcPts[srcOff++];
5392                         }
5393                         return;
5394                     case APPLY_TRANSLATE:
5395                         tx = getTx();
5396                         ty = getTy();
5397                         while (--numPts >= 0) {
5398                             dstPts[dstOff++] = srcPts[srcOff++] - tx;
5399                             dstPts[dstOff++] = srcPts[srcOff++] - ty;
5400                             dstPts[dstOff++] = srcPts[srcOff++];
5401                         }
5402                         return;
5403                     case APPLY_IDENTITY:
5404                         if (srcPts != dstPts || srcOff != dstOff) {
5405                             System.arraycopy(srcPts, srcOff, dstPts, dstOff,
5406                                              numPts * 3);
5407                         }
5408                         return;
5409                 }
5410                 // cannot reach
5411             case APPLY_TRANSLATE:
5412                 tx = getTx();
5413                 ty = getTy();
5414                 tz = getTz();
5415                 while (--numPts >= 0) {
5416                     dstPts[dstOff++] = srcPts[srcOff++] - tx;
5417                     dstPts[dstOff++] = srcPts[srcOff++] - ty;
5418                     dstPts[dstOff++] = srcPts[srcOff++] - tz;
5419                 }
5420                 return;
5421             case APPLY_SCALE:
5422                 mxx = getMxx();
5423                 myy = getMyy();
5424                 mzz = getMzz();
5425                 if (mxx == 0.0 || myy == 0.0 | mzz == 0.0) {
5426                     throw new NonInvertibleTransformException("Determinant is 0");
5427                 }
5428 
5429                 mxx = 1.0 / mxx;
5430                 myy = 1.0 / myy;
5431                 mzz = 1.0 / mzz;
5432 
5433                 while (--numPts >= 0) {
5434                     dstPts[dstOff++] = mxx * srcPts[srcOff++];
5435                     dstPts[dstOff++] = myy * srcPts[srcOff++];
5436                     dstPts[dstOff++] = mzz * srcPts[srcOff++];
5437                 }
5438                 return;
5439             case APPLY_SCALE | APPLY_TRANSLATE:
5440                 mxx = getMxx(); tx = getTx();
5441                 myy = getMyy(); ty = getTy();
5442                 mzz = getMzz(); tz = getTz();
5443                 if (mxx == 0.0 || myy == 0.0 || mzz == 0.0) {
5444                     throw new NonInvertibleTransformException("Determinant is 0");
5445                 }
5446 
5447                 tx = -tx / mxx;
5448                 ty = -ty / myy;
5449                 tz = -tz / mzz;
5450                 mxx = 1.0 / mxx;
5451                 myy = 1.0 / myy;
5452                 mzz = 1.0 / mzz;
5453 
5454                 while (--numPts >= 0) {
5455                     dstPts[dstOff++] = mxx * srcPts[srcOff++] + tx;
5456                     dstPts[dstOff++] = myy * srcPts[srcOff++] + ty;
5457                     dstPts[dstOff++] = mzz * srcPts[srcOff++] + tz;
5458                 }
5459                 return;
5460             case APPLY_3D_COMPLEX:
5461                 super.inverseTransform3DPointsImpl(srcPts, srcOff,
5462                         dstPts, dstOff, numPts);
5463                 return;
5464         }
5465     }
5466 
5467     @Override
5468     public Point2D inverseDeltaTransform(double x, double y)
5469             throws NonInvertibleTransformException {
5470         ensureCanTransform2DPoint();
5471 
5472         switch (state2d) {
5473             default:
5474                 return super.inverseDeltaTransform(x, y);
5475             case APPLY_SHEAR | APPLY_TRANSLATE:
5476             case APPLY_SHEAR:
5477                 final double mxy_s = getMxy();
5478                 final double myx_s = getMyx();
5479                 if (mxy_s == 0.0 || myx_s == 0.0) {
5480                     throw new NonInvertibleTransformException("Determinant is 0");
5481                 }
5482                 return new Point2D((1.0 / myx_s) * y, (1.0 / mxy_s) * x);
5483             case APPLY_SCALE | APPLY_TRANSLATE:
5484             case APPLY_SCALE:
5485                 final double mxx_s = getMxx();
5486                 final double myy_s = getMyy();
5487                 if (mxx_s == 0.0 || myy_s == 0.0) {
5488                     throw new NonInvertibleTransformException("Determinant is 0");
5489                 }
5490                 return new Point2D((1.0 / mxx_s) * x, (1.0 / myy_s) * y);
5491             case APPLY_TRANSLATE:
5492             case APPLY_IDENTITY:
5493                 return new Point2D(x, y);
5494         }
5495     }
5496 
5497     @Override
5498     public Point3D inverseDeltaTransform(double x, double y, double z)
5499             throws NonInvertibleTransformException {
5500         switch(state3d) {
5501             default:
5502                 stateError();
5503                 // cannot reach
5504             case APPLY_NON_3D:
5505                 switch (state2d) {
5506                     default:
5507                         return super.inverseDeltaTransform(x, y, z);
5508                     case APPLY_SHEAR | APPLY_TRANSLATE:
5509                     case APPLY_SHEAR:
5510                         final double mxy_s = getMxy();
5511                         final double myx_s = getMyx();
5512                         if (mxy_s == 0.0 || myx_s == 0.0) {
5513                             throw new NonInvertibleTransformException(
5514                                     "Determinant is 0");
5515                         }
5516                         return new Point3D(
5517                                 (1.0 / myx_s) * y,
5518                                 (1.0 / mxy_s) * x, z);
5519                     case APPLY_SCALE | APPLY_TRANSLATE:
5520                     case APPLY_SCALE:
5521                         final double mxx_s = getMxx();
5522                         final double myy_s = getMyy();
5523                         if (mxx_s == 0.0 || myy_s == 0.0) {
5524                             throw new NonInvertibleTransformException(
5525                                     "Determinant is 0");
5526                         }
5527                         return new Point3D(
5528                                 (1.0 / mxx_s) * x,
5529                                 (1.0 / myy_s) * y, z);
5530                     case APPLY_TRANSLATE:
5531                     case APPLY_IDENTITY:
5532                         return new Point3D(x, y, z);
5533                 }
5534 
5535             case APPLY_TRANSLATE:
5536                 return new Point3D(x, y, z);
5537             case APPLY_SCALE | APPLY_TRANSLATE:
5538             case APPLY_SCALE:
5539                 final double mxx_s = getMxx();
5540                 final double myy_s = getMyy();
5541                 final double mzz_s = getMzz();
5542                 if (mxx_s == 0.0 || myy_s == 0.0 || mzz_s == 0.0) {
5543                     throw new NonInvertibleTransformException("Determinant is 0");
5544                 }
5545                 return new Point3D(
5546                         (1.0 / mxx_s) * x,
5547                         (1.0 / myy_s) * y,
5548                         (1.0 / mzz_s) * z);
5549             case APPLY_3D_COMPLEX:
5550                 return super.inverseDeltaTransform(x, y, z);
5551         }
5552     }
5553 
5554     /* *************************************************************************
5555      *                                                                         *
5556      *                               Other API                                 *
5557      *                                                                         *
5558      **************************************************************************/
5559 
5560     /**
5561      * Returns a string representation of this {@code Affine} object.
5562      * @return a string representation of this {@code Affine} object.
5563      */
5564     @Override
5565     public String toString() {
5566        final StringBuilder sb = new StringBuilder("Affine [\n");
5567 
5568         sb.append("\t").append(getMxx());
5569         sb.append(", ").append(getMxy());
5570         sb.append(", ").append(getMxz());
5571         sb.append(", ").append(getTx());
5572         sb.append('\n');
5573         sb.append("\t").append(getMyx());
5574         sb.append(", ").append(getMyy());
5575         sb.append(", ").append(getMyz());
5576         sb.append(", ").append(getTy());
5577         sb.append('\n');
5578         sb.append("\t").append(getMzx());
5579         sb.append(", ").append(getMzy());
5580         sb.append(", ").append(getMzz());
5581         sb.append(", ").append(getTz());
5582 
5583         return sb.append("\n]").toString();
5584     }
5585 
5586     /* *************************************************************************
5587      *                                                                         *
5588      *                    Internal implementation stuff                        *
5589      *                                                                         *
5590      **************************************************************************/
5591 
5592     /**
5593      * Manually recalculates the state of the transform when the matrix
5594      * changes too much to predict the effects on the state.
5595      * The following tables specify what the various settings of the
5596      * state fields say about the values of the corresponding matrix
5597      * element fields.
5598      *
5599      * <h4>state3d:</h4>
5600      * <pre>
5601      *                     SCALE          TRANSLATE        OTHER ELEMENTS
5602      *                 mxx, myy, mzz      tx, ty, tz       all remaining
5603      *
5604      * TRANSLATE (TR)       1.0           not all 0.0           0.0
5605      * SCALE (SC)       not all 1.0           0.0               0.0
5606      * TR | SC          not all 1.0       not all 0.0           0.0
5607      * 3D_COMPLEX           any               any            not all 0.0
5608      * NON_3D: mxz, myz, mzx, mzy, tz are 0.0, mzz is 1.0, for the rest
5609      *         see state2d
5610      * </pre>
5611      *
5612      * <h4>state2d:</h4>
5613      * Contains meaningful value only if state3d == APPLY_NON_3D.
5614      * Note that the rules governing the SCALE fields are slightly
5615      * different depending on whether the SHEAR flag is also set.
5616      * <pre>
5617      *                     SCALE            SHEAR          TRANSLATE
5618      *                    mxx/myy          mxy/myx           tx/ty
5619      *
5620      * IDENTITY             1.0              0.0              0.0
5621      * TRANSLATE (TR)       1.0              0.0          not both 0.0
5622      * SCALE (SC)       not both 1.0         0.0              0.0
5623      * TR | SC          not both 1.0         0.0          not both 0.0
5624      * SHEAR (SH)           0.0          not both 0.0         0.0
5625      * TR | SH              0.0          not both 0.0     not both 0.0
5626      * SC | SH          not both 0.0     not both 0.0         0.0
5627      * TR | SC | SH     not both 0.0     not both 0.0     not both 0.0
5628      * </pre>
5629      */
5630     private void updateState() {
5631         updateState2D();
5632 
5633         state3d = APPLY_NON_3D;
5634 
5635         if (getMxz() != 0.0 ||
5636             getMyz() != 0.0 ||
5637             getMzx() != 0.0 ||
5638             getMzy() != 0.0)
5639         {
5640             state3d = APPLY_3D_COMPLEX;
5641         } else {
5642             if ((state2d & APPLY_SHEAR) == 0) {
5643                 if (getTz() != 0.0) {
5644                     state3d |= APPLY_TRANSLATE;
5645                 }
5646                 if (getMzz() != 1.0) {
5647                     state3d |= APPLY_SCALE;
5648                 }
5649                 if (state3d != APPLY_NON_3D) {
5650                     state3d |= (state2d & (APPLY_SCALE | APPLY_TRANSLATE));
5651                 }
5652             } else {
5653                 if (getMzz() != 1.0 || getTz() != 0.0) {
5654                     state3d = APPLY_3D_COMPLEX;
5655                 }
5656             }
5657         }
5658     }
5659 
5660     /**
5661      * 2D part of {@code updateState()}. It is sufficient to call this method
5662      * when we know this this a 2D transform and the operation was 2D-only
5663      * so it could not switch the transform to 3D.
5664      */
5665     private void updateState2D() {
5666         if (getMxy() == 0.0 && getMyx() == 0.0) {
5667             if (getMxx() == 1.0 && getMyy() == 1.0) {
5668                 if (getTx() == 0.0 && getTy() == 0.0) {
5669                     state2d = APPLY_IDENTITY;
5670                 } else {
5671                     state2d = APPLY_TRANSLATE;
5672                 }
5673             } else {
5674                 if (getTx() == 0.0 && getTy() == 0.0) {
5675                     state2d = APPLY_SCALE;
5676                 } else {
5677                     state2d = (APPLY_SCALE | APPLY_TRANSLATE);
5678                 }
5679             }
5680         } else {
5681             if (getMxx() == 0.0 && getMyy() == 0.0) {
5682                 if (getTx() == 0.0 && getTy() == 0.0) {
5683                     state2d = APPLY_SHEAR;
5684                 } else {
5685                     state2d = (APPLY_SHEAR | APPLY_TRANSLATE);
5686                 }
5687             } else {
5688                 if (getTx() == 0.0 && getTy() == 0.0) {
5689                     state2d = (APPLY_SHEAR | APPLY_SCALE);
5690                 } else {
5691                     state2d = (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE);
5692                 }
5693             }
5694         }
5695     }
5696 
5697     /**
5698      * Convenience method used internally to throw exceptions when
5699      * a case was forgotten in a switch statement.
5700      */
5701     private static void stateError() {
5702         throw new InternalError("missing case in a switch");
5703     }
5704 
5705     @Override
5706     void apply(final Affine3D trans) {
5707         trans.concatenate(getMxx(), getMxy(), getMxz(), getTx(),
5708                           getMyx(), getMyy(), getMyz(), getTy(),
5709                           getMzx(), getMzy(), getMzz(), getTz());
5710     }
5711 
5712     @Override
5713     BaseTransform derive(final BaseTransform trans) {
5714         switch(state3d) {
5715             default:
5716                 stateError();
5717                 // cannot reach
5718             case APPLY_NON_3D:
5719                 switch(state2d) {
5720                     case APPLY_IDENTITY:
5721                         return trans;
5722                     case APPLY_TRANSLATE:
5723                         return trans.deriveWithTranslation(getTx(), getTy());
5724                     case APPLY_SCALE:
5725                         return trans.deriveWithScale(getMxx(), getMyy(), 1.0);
5726                     case APPLY_SCALE | APPLY_TRANSLATE:
5727                         // fall through
5728                     default:
5729                         return trans.deriveWithConcatenation(
5730                                 getMxx(), getMyx(),
5731                                 getMxy(), getMyy(),
5732                                 getTx(), getTy());
5733                 }
5734             case APPLY_TRANSLATE:
5735                 return trans.deriveWithTranslation(getTx(), getTy(), getTz());
5736             case APPLY_SCALE:
5737                 return trans.deriveWithScale(getMxx(), getMyy(), getMzz());
5738             case APPLY_SCALE | APPLY_TRANSLATE:
5739                 // fall through
5740             case APPLY_3D_COMPLEX:
5741                 return trans.deriveWithConcatenation(
5742                         getMxx(), getMxy(), getMxz(), getTx(),
5743                         getMyx(), getMyy(), getMyz(), getTy(),
5744                         getMzx(), getMzy(), getMzz(), getTz());
5745         }
5746     }
5747 
5748     /**
5749      * Keeps track of the atomic changes of more elements.
5750      * Don't forget to end or cancel a running atomic operation
5751      * when an exception is to be thrown during one.
5752      */
5753     private class AffineAtomicChange {
5754         private boolean running = false;
5755 
5756         private void start() {
5757             if (running) {
5758                 throw new InternalError("Affine internal error: "
5759                         + "trying to run inner atomic operation");
5760             }
5761             if (mxx != null) mxx.preProcessAtomicChange();
5762             if (mxy != null) mxy.preProcessAtomicChange();
5763             if (mxz != null) mxz.preProcessAtomicChange();
5764             if (tx != null) tx.preProcessAtomicChange();
5765             if (myx != null) myx.preProcessAtomicChange();
5766             if (myy != null) myy.preProcessAtomicChange();
5767             if (myz != null) myz.preProcessAtomicChange();
5768             if (ty != null) ty.preProcessAtomicChange();
5769             if (mzx != null) mzx.preProcessAtomicChange();
5770             if (mzy != null) mzy.preProcessAtomicChange();
5771             if (mzz != null) mzz.preProcessAtomicChange();
5772             if (tz != null) tz.preProcessAtomicChange();
5773             running = true;
5774         }
5775 
5776         private void end() {
5777             running = false;
5778             transformChanged();
5779             if (mxx != null) mxx.postProcessAtomicChange();
5780             if (mxy != null) mxy.postProcessAtomicChange();
5781             if (mxz != null) mxz.postProcessAtomicChange();
5782             if (tx != null) tx.postProcessAtomicChange();
5783             if (myx != null) myx.postProcessAtomicChange();
5784             if (myy != null) myy.postProcessAtomicChange();
5785             if (myz != null) myz.postProcessAtomicChange();
5786             if (ty != null) ty.postProcessAtomicChange();
5787             if (mzx != null) mzx.postProcessAtomicChange();
5788             if (mzy != null) mzy.postProcessAtomicChange();
5789             if (mzz != null) mzz.postProcessAtomicChange();
5790             if (tz != null) tz.postProcessAtomicChange();
5791         }
5792 
5793         private void cancel() {
5794             running = false;
5795         }
5796 
5797         private boolean runs() {
5798             return running;
5799         }
5800     }
5801 
5802     /**
5803      * Used only by tests to check the 2d matrix state
5804      */
5805     int getState2d() {
5806         return state2d;
5807     }
5808 
5809     /**
5810      * Used only by tests to check the 3d matrix state
5811      */
5812     int getState3d() {
5813         return state3d;
5814     }
5815 
5816     /**
5817      * Used only by tests to check the atomic operation state
5818      */
5819     boolean atomicChangeRuns() {
5820         return atomicChange.runs();
5821     }
5822 }