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