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