changeset 4979:3e8b6b2d3c1e

RT-32384: localToParentTx kept as simple as possible.
author Pavel Safrata <pavel.safrata@oracle.com>
date Thu, 05 Sep 2013 08:26:28 +0100
parents a18f63e9aba6
children acf4b07fa2f6
files modules/graphics/src/main/java/com/sun/javafx/geom/transform/Affine2D.java modules/graphics/src/main/java/com/sun/javafx/geom/transform/Affine3D.java modules/graphics/src/main/java/com/sun/javafx/geom/transform/AffineBase.java modules/graphics/src/main/java/com/sun/javafx/geom/transform/BaseTransform.java modules/graphics/src/main/java/com/sun/javafx/geom/transform/Identity.java modules/graphics/src/main/java/com/sun/javafx/geom/transform/Translate2D.java modules/graphics/src/main/java/com/sun/javafx/scene/transform/TransformUtils.java modules/graphics/src/main/java/javafx/scene/Node.java modules/graphics/src/main/java/javafx/scene/transform/Affine.java modules/graphics/src/main/java/javafx/scene/transform/Rotate.java modules/graphics/src/main/java/javafx/scene/transform/Scale.java modules/graphics/src/main/java/javafx/scene/transform/Shear.java modules/graphics/src/main/java/javafx/scene/transform/Transform.java modules/graphics/src/main/java/javafx/scene/transform/Translate.java modules/graphics/src/test/java/com/sun/javafx/test/TransformHelper.java modules/graphics/src/test/java/javafx/scene/NodeTest.java modules/graphics/src/test/java/javafx/scene/transform/TransformDeriveTest.java modules/graphics/src/test/java/javafx/scene/transform/TransformTest.java
diffstat 18 files changed, 961 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/modules/graphics/src/main/java/com/sun/javafx/geom/transform/Affine2D.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/geom/transform/Affine2D.java	Thu Sep 05 08:26:28 2013 +0100
@@ -1323,6 +1323,48 @@
     }
 
     @Override
+    public BaseTransform deriveWithTranslation(double mxt, double myt, double mzt) {
+        if (mzt == 0.0) {
+            translate(mxt, myt);
+            return this;
+        }
+        Affine3D a = new Affine3D(this);
+        a.translate(mxt, myt, mzt);
+        return a;
+    }
+
+    @Override
+    public BaseTransform deriveWithScale(double mxx, double myy, double mzz) {
+        if (mzz == 1.0) {
+            scale(mxx, myy);
+            return this;
+        }
+        Affine3D a = new Affine3D(this);
+        a.scale(mxx, myy, mzz);
+        return a;
+
+    }
+
+    @Override
+    public BaseTransform deriveWithRotation(double theta,
+            double axisX, double axisY, double axisZ) {
+        if (theta == 0.0) {
+            return this;
+        }
+        if (almostZero(axisX) && almostZero(axisY)) {
+            if (axisZ > 0) {
+                rotate(theta);
+            } else if (axisZ < 0) {
+                rotate(-theta);
+            } // else rotating about zero vector - NOP
+            return this;
+        }
+        Affine3D a = new Affine3D(this);
+        a.rotate(theta, axisX, axisY, axisZ);
+        return a;
+    }
+
+    @Override
     public BaseTransform deriveWithPreTranslation(double mxt, double myt) {
         this.mxt += mxt;
         this.myt += myt;
@@ -1354,6 +1396,27 @@
     }
 
     @Override
+    public BaseTransform deriveWithConcatenation(
+            double mxx,   double mxy,   double mxz,   double mxt,
+            double myx,   double myy,   double myz,   double myt,
+            double mzx,   double mzy,   double mzz,   double mzt) {
+        if (                                   mxz == 0.0
+                                            && myz == 0.0
+                && mzx == 0.0 && mzy == 0.0 && mzz == 1.0 && mzt == 0.0) {
+            concatenate(mxx, mxy,
+                        mxt, myx,
+                        myy, myt);
+            return this;
+        }
+
+        Affine3D t3d = new Affine3D(this);
+        t3d.concatenate(mxx, mxy, mxz, mxt,
+                        myx, myy, myz, myt,
+                        mzx, mzy, mzz, mzt);
+        return t3d;
+    }
+
+    @Override
     public BaseTransform deriveWithConcatenation(BaseTransform tx) {
         if (tx.is2D()) {
             concatenate(tx);
--- a/modules/graphics/src/main/java/com/sun/javafx/geom/transform/Affine3D.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/geom/transform/Affine3D.java	Thu Sep 05 08:26:28 2013 +0100
@@ -647,6 +647,25 @@
         return this;
     }
 
+    @Override
+    public BaseTransform deriveWithTranslation(double mxt, double myt, double mzt) {
+        translate(mxt, myt, mzt);
+        return this;
+    }
+
+    @Override
+    public BaseTransform deriveWithScale(double mxx, double myy, double mzz) {
+        scale(mxx, myy, mzz);
+        return this;
+    }
+
+    @Override
+    public BaseTransform deriveWithRotation(double theta,
+            double axisX, double axisY, double axisZ) {
+        rotate(theta, axisX, axisY, axisZ);
+        return this;
+    }
+
     public void preTranslate(double mxt, double myt, double mzt) {
         this.mxt += mxt;
         this.myt += myt;
@@ -940,6 +959,17 @@
         return this;
     }
 
+    @Override
+    public BaseTransform deriveWithConcatenation(
+            double mxx, double mxy, double mxz, double mxt,
+            double myx, double myy, double myz, double myt,
+            double mzx, double mzy, double mzz, double mzt) {
+        concatenate(mxx, mxy, mxz, mxt,
+                    myx, myy, myz, myt,
+                    mzx, mzy, mzz, mzt);
+        return this;
+    }
+
     public void preConcatenate(BaseTransform transform) {
         switch (transform.getDegree()) {
             case IDENTITY:
@@ -1091,12 +1121,6 @@
         return this;
     }
 
-    private static final double EPSILON_ABSOLUTE = 1.0e-5;
-
-    public static boolean almostZero(double a) {
-        return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE));
-    }
-
     static boolean almostOne(double a) {
         return ((a < 1+EPSILON_ABSOLUTE) && (a > 1-EPSILON_ABSOLUTE));
     }
--- a/modules/graphics/src/main/java/com/sun/javafx/geom/transform/AffineBase.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/geom/transform/AffineBase.java	Thu Sep 05 08:26:28 2013 +0100
@@ -2841,6 +2841,28 @@
     }
 
     /**
+     * Similar to {@link #concatenate(com.sun.javafx.geom.transform.BaseTransform)},
+     * passing the individual elements of the transformation.
+     */
+    public void concatenate(double Txx, double Txy, double Txt,
+                            double Tyx, double Tyy, double Tyt)
+    {
+        double rxx = (mxx * Txx + mxy * Tyx /* + mxt * 0.0 */);
+        double rxy = (mxx * Txy + mxy * Tyy /* + mxt * 0.0 */);
+        double rxt = (mxx * Txt + mxy * Tyt + mxt /* * 1.0 */);
+        double ryx = (myx * Txx + myy * Tyx /* + myt * 0.0 */);
+        double ryy = (myx * Txy + myy * Tyy /* + myt * 0.0 */);
+        double ryt = (myx * Txt + myy * Tyt + myt /* * 1.0 */);
+        this.mxx = rxx;
+        this.mxy = rxy;
+        this.mxt = rxt;
+        this.myx = ryx;
+        this.myy = ryy;
+        this.myt = ryt;
+        updateState();
+    }
+
+    /**
      * Sets this transform to the inverse of itself.
      * The inverse transform Tx' of this transform Tx
      * maps coordinates transformed by Tx back
--- a/modules/graphics/src/main/java/com/sun/javafx/geom/transform/BaseTransform.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/geom/transform/BaseTransform.java	Thu Sep 05 08:26:28 2013 +0100
@@ -437,10 +437,17 @@
                                           double mzx, double mzy, double mzz, double mzt);
 
     public abstract BaseTransform deriveWithTranslation(double mxt, double myt);
+    public abstract BaseTransform deriveWithTranslation(double mxt, double myt, double mzt);
+    public abstract BaseTransform deriveWithScale(double mxx, double myy, double mzz);
+    public abstract BaseTransform deriveWithRotation(double theta, double axisX, double axisY, double axisZ);
     public abstract BaseTransform deriveWithPreTranslation(double mxt, double myt);
     public abstract BaseTransform deriveWithConcatenation(double mxx, double myx,
                                                           double mxy, double myy,
                                                           double mxt, double myt);
+    public abstract BaseTransform deriveWithConcatenation(
+            double mxx, double mxy, double mxz, double mxt,
+            double myx, double myy, double myz, double myt,
+            double mzx, double mzy, double mzz, double mzt);
     public abstract BaseTransform deriveWithPreConcatenation(BaseTransform transform);
     public abstract BaseTransform deriveWithConcatenation(BaseTransform tx);
     public abstract BaseTransform deriveWithNewTransform(BaseTransform tx);
@@ -517,6 +524,12 @@
         return dst;
     }
 
+    static final double EPSILON_ABSOLUTE = 1.0e-5;
+
+    public static boolean almostZero(double a) {
+        return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE));
+    }
+
     /**
      * Returns the matrix elements and degree of this transform as a string.
      * @return  the matrix elements and degree of this transform
--- a/modules/graphics/src/main/java/com/sun/javafx/geom/transform/Identity.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/geom/transform/Identity.java	Thu Sep 05 08:26:28 2013 +0100
@@ -281,6 +281,58 @@
     }
 
     @Override
+    public BaseTransform deriveWithTranslation(double mxt, double myt, double mzt) {
+        if (mzt == 0.0) {
+            if (mxt == 0.0 && myt == 0.0) {
+                return this;
+            }
+            return new Translate2D(mxt, myt);
+        }
+        Affine3D a = new Affine3D();
+        a.translate(mxt, myt, mzt);
+        return a;
+    }
+
+    @Override
+    public BaseTransform deriveWithScale(double mxx, double myy, double mzz) {
+        if (mzz == 1.0) {
+            if (mxx == 1.0 && myy == 1.0) {
+                return this;
+            }
+            Affine2D a = new Affine2D();
+            a.scale(mxx, myy);
+            return a;
+        }
+        Affine3D a = new Affine3D();
+        a.scale(mxx, myy, mzz);
+        return a;
+
+    }
+
+    @Override
+    public BaseTransform deriveWithRotation(double theta,
+            double axisX, double axisY, double axisZ) {
+        if (theta == 0.0) {
+            return this;
+        }
+        if (almostZero(axisX) && almostZero(axisY)) {
+            if (axisZ == 0.0) {
+                return this;
+            }
+            Affine2D a = new Affine2D();
+            if (axisZ > 0) {
+                a.rotate(theta);
+            } else if (axisZ < 0) {
+                a.rotate(-theta);
+            }
+            return a;
+        }
+        Affine3D a = new Affine3D();
+        a.rotate(theta, axisX, axisY, axisZ);
+        return a;
+    }
+
+    @Override
     public BaseTransform deriveWithConcatenation(double mxx, double myx,
                                                  double mxy, double myy,
                                                  double mxt, double myt)
@@ -291,6 +343,16 @@
     }
 
     @Override
+    public BaseTransform deriveWithConcatenation(
+            double mxx, double mxy, double mxz, double mxt,
+            double myx, double myy, double myz, double myt,
+            double mzx, double mzy, double mzz, double mzt) {
+        return getInstance(mxx, mxy, mxz, mxt,
+                           myx, myy, myz, myt,
+                           mzx, mzy, mzz, mzt);
+    }
+
+    @Override
     public BaseTransform deriveWithConcatenation(BaseTransform tx) {
         return getInstance(tx);
     }
--- a/modules/graphics/src/main/java/com/sun/javafx/geom/transform/Translate2D.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/geom/transform/Translate2D.java	Thu Sep 05 08:26:28 2013 +0100
@@ -451,6 +451,61 @@
     }
 
     @Override
+    public BaseTransform deriveWithTranslation(double mxt, double myt, double mzt) {
+        if (mzt == 0.0) {
+            this.mxt += mxt;
+            this.myt += myt;
+            return this;
+        }
+        Affine3D a = new Affine3D();
+        a.translate(this.mxt + mxt, this.myt + myt, mzt);
+        return a;
+    }
+
+    @Override
+    public BaseTransform deriveWithScale(double mxx, double myy, double mzz) {
+        if (mzz == 1.0) {
+            if (mxx == 1.0 && myy == 1.0) {
+                return this;
+            }
+            Affine2D a = new Affine2D();
+            a.translate(this.mxt, this.myt);
+            a.scale(mxx, myy);
+            return a;
+        }
+        Affine3D a = new Affine3D();
+        a.translate(this.mxt, this.myt);
+        a.scale(mxx, myy, mzz);
+        return a;
+
+    }
+
+    @Override
+    public BaseTransform deriveWithRotation(double theta,
+            double axisX, double axisY, double axisZ) {
+        if (theta == 0.0) {
+            return this;
+        }
+        if (almostZero(axisX) && almostZero(axisY)) {
+            if (axisZ == 0.0) {
+                return this;
+            }
+            Affine2D a = new Affine2D();
+            a.translate(this.mxt, this.myt);
+            if (axisZ > 0) {
+                a.rotate(theta);
+            } else if (axisZ < 0) {
+                a.rotate(-theta);
+            }
+            return a;
+        }
+        Affine3D a = new Affine3D();
+        a.translate(this.mxt, this.myt);
+        a.rotate(theta, axisX, axisY, axisZ);
+        return a;
+    }
+
+    @Override
     public BaseTransform deriveWithPreTranslation(double mxt, double myt) {
         this.mxt += mxt;
         this.myt += myt;
@@ -474,6 +529,24 @@
     }
 
     @Override
+    public BaseTransform deriveWithConcatenation(
+            double mxx,   double mxy,   double mxz,   double mxt,
+            double myx,   double myy,   double myz,   double myt,
+            double mzx,   double mzy,   double mzz,   double mzt) {
+        if (                                   mxz == 0.0
+                                            && myz == 0.0
+                && mzx == 0.0 && mzy == 0.0 && mzz == 1.0 && mzt == 0.0) {
+            return deriveWithConcatenation(mxx, myx,
+                                           mxy, myy,
+                                           mxt, myt);
+        }
+
+        return new Affine3D(mxx, mxy, mxz, mxt + this.mxt,
+                            myx, myy, myz, myt + this.myt,
+                            mzx, mzy, mzz, mzt);
+    }
+
+    @Override
     public BaseTransform deriveWithConcatenation(BaseTransform tx) {
         if (tx.isTranslateOrIdentity()) {
             this.mxt += tx.getMxt();
--- a/modules/graphics/src/main/java/com/sun/javafx/scene/transform/TransformUtils.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/com/sun/javafx/scene/transform/TransformUtils.java	Thu Sep 05 08:26:28 2013 +0100
@@ -25,7 +25,9 @@
 
 package com.sun.javafx.scene.transform;
 
+import com.sun.javafx.geom.transform.Affine2D;
 import com.sun.javafx.geom.transform.Affine3D;
+import com.sun.javafx.geom.transform.BaseTransform;
 import javafx.geometry.Point2D;
 import javafx.geometry.Point3D;
 import javafx.scene.transform.NonInvertibleTransformException;
@@ -841,6 +843,18 @@
         }
 
         /**
+         * @treatAsPrivate implementation detail
+         * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+         */
+        @Deprecated
+        @Override
+        public BaseTransform impl_derive(final BaseTransform trans) {
+            return trans.deriveWithConcatenation(xx, xy, xz, xt,
+                                                 yx, yy, yz, yt,
+                                                 zx, zy, zz, zt);
+        }
+
+        /**
          * Used only by tests to check the 2d matrix state
          */
         int getState2d() {
--- a/modules/graphics/src/main/java/javafx/scene/Node.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/javafx/scene/Node.java	Thu Sep 05 08:26:28 2013 +0100
@@ -149,7 +149,12 @@
 import com.sun.javafx.scene.traversal.Direction;
 import com.sun.javafx.sg.prism.NGNode;
 import com.sun.javafx.tk.Toolkit;
+import com.sun.javafx.accessible.providers.AccessibleProvider;
+import com.sun.javafx.geom.transform.Affine2D;
+import com.sun.javafx.geom.transform.AffineBase;
+import com.sun.javafx.geom.transform.Translate2D;
 import com.sun.prism.impl.PrismSettings;
+import javafx.scene.transform.Translate;
 import sun.util.logging.PlatformLogger;
 import sun.util.logging.PlatformLogger.Level;
 
@@ -3237,7 +3242,7 @@
      * This is the concatenation of all transforms in this node, including all
      * of the convenience transforms.
      */
-    private final Affine3D localToParentTx = new Affine3D();
+    private BaseTransform localToParentTx = BaseTransform.IDENTITY_TRANSFORM;
 
     /**
      * This flag is used to indicate that localToParentTx is dirty and needs
@@ -4443,9 +4448,12 @@
                     // (must be the last transformation)
                     mirroringCenter = sceneValue.getWidth() / 2;
 
-                    localToParentTx.translate(mirroringCenter, 0, 0);
-                    localToParentTx.scale(-1, 1);
-                    localToParentTx.translate(-mirroringCenter, 0, 0);
+                    localToParentTx = localToParentTx.deriveWithTranslation(
+                            mirroringCenter, 0.0);
+                    localToParentTx = localToParentTx.deriveWithScale(
+                            -1.0, 1.0, 1.0);
+                    localToParentTx = localToParentTx.deriveWithTranslation(
+                            -mirroringCenter, 0.0);
                 } else {
                     // mirror later
                     mirror = true;
@@ -4458,25 +4466,39 @@
                 double pivotX = impl_getPivotX();
                 double pivotY = impl_getPivotY();
                 double pivotZ = impl_getPivotZ();
-                localToParentTx.translate(getTranslateX() + getLayoutX() + pivotX, getTranslateY() + getLayoutY() + pivotY, getTranslateZ() + pivotZ);
-                localToParentTx.rotate(Math.toRadians(getRotate()), getRotationAxis().getX(), getRotationAxis().getY(), getRotationAxis().getZ());
-                localToParentTx.scale(getScaleX(), getScaleY(), getScaleZ());
-                localToParentTx.translate(-pivotX, -pivotY, -pivotZ);
+
+                localToParentTx = localToParentTx.deriveWithTranslation(
+                        getTranslateX() + getLayoutX() + pivotX,
+                        getTranslateY() + getLayoutY() + pivotY,
+                        getTranslateZ() + pivotZ);
+                localToParentTx = localToParentTx.deriveWithRotation(
+                        Math.toRadians(getRotate()), getRotationAxis().getX(),
+                        getRotationAxis().getY(), getRotationAxis().getZ());
+                localToParentTx = localToParentTx.deriveWithScale(
+                        getScaleX(), getScaleY(), getScaleZ());
+                localToParentTx = localToParentTx.deriveWithTranslation(
+                        -pivotX, -pivotY, -pivotZ);
             } else {
-                localToParentTx.translate(getTranslateX() + getLayoutX(), getTranslateY() + getLayoutY(), getTranslateZ());
+                localToParentTx = localToParentTx.deriveWithTranslation(
+                        getTranslateX() + getLayoutX(),
+                        getTranslateY() + getLayoutY(),
+                        getTranslateZ());
             }
 
             if (impl_hasTransforms()) {
                 for (Transform t : getTransforms()) {
-                    t.impl_apply(localToParentTx);
+                    localToParentTx = t.impl_derive(localToParentTx);
                 }
             }
 
             // Check to see whether the node requires mirroring
             if (mirror) {
-                localToParentTx.translate(mirroringCenter, 0, 0);
-                localToParentTx.scale(-1, 1);
-                localToParentTx.translate(-mirroringCenter, 0, 0);
+                localToParentTx = localToParentTx.deriveWithTranslation(
+                        mirroringCenter, 0);
+                localToParentTx = localToParentTx.deriveWithScale(
+                        -1.0, 1.0, 1.0);
+                localToParentTx = localToParentTx.deriveWithTranslation(
+                        -mirroringCenter, 0);
             }
 
             transformDirty = false;
--- a/modules/graphics/src/main/java/javafx/scene/transform/Affine.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/javafx/scene/transform/Affine.java	Thu Sep 05 08:26:28 2013 +0100
@@ -27,6 +27,7 @@
 
 
 import com.sun.javafx.geom.transform.Affine3D;
+import com.sun.javafx.geom.transform.BaseTransform;
 import javafx.beans.property.DoubleProperty;
 import javafx.beans.property.SimpleDoubleProperty;
 import javafx.geometry.Point2D;
@@ -5714,6 +5715,47 @@
     }
 
     /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    @Override
+    public BaseTransform impl_derive(final BaseTransform trans) {
+        switch(state3d) {
+            default:
+                stateError();
+                // cannot reach
+            case APPLY_NON_3D:
+                switch(state2d) {
+                    case APPLY_IDENTITY:
+                        return trans;
+                    case APPLY_TRANSLATE:
+                        return trans.deriveWithTranslation(getTx(), getTy());
+                    case APPLY_SCALE:
+                        return trans.deriveWithScale(getMxx(), getMyy(), 1.0);
+                    case APPLY_SCALE | APPLY_TRANSLATE:
+                        // fall through
+                    default:
+                        return trans.deriveWithConcatenation(
+                                getMxx(), getMyx(),
+                                getMxy(), getMyy(),
+                                getTx(), getTy());
+                }
+            case APPLY_TRANSLATE:
+                return trans.deriveWithTranslation(getTx(), getTy(), getTz());
+            case APPLY_SCALE:
+                return trans.deriveWithScale(getMxx(), getMyy(), getMzz());
+            case APPLY_SCALE | APPLY_TRANSLATE:
+                // fall through
+            case APPLY_3D_COMPLEX:
+                return trans.deriveWithConcatenation(
+                        getMxx(), getMxy(), getMxz(), getTx(),
+                        getMyx(), getMyy(), getMyz(), getTy(),
+                        getMzx(), getMzy(), getMzz(), getTz());
+        }
+    }
+
+    /**
      * Keeps track of the atomic changes of more elements.
      * Don't forget to end or cancel a running atomic operation
      * when an exception is to be thrown during one.
--- a/modules/graphics/src/main/java/javafx/scene/transform/Rotate.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/javafx/scene/transform/Rotate.java	Thu Sep 05 08:26:28 2013 +0100
@@ -32,6 +32,7 @@
 import javafx.geometry.Point3D;
 
 import com.sun.javafx.geom.transform.Affine3D;
+import com.sun.javafx.geom.transform.BaseTransform;
 import javafx.geometry.Point2D;
 
 
@@ -734,6 +735,33 @@
         }
     }
 
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    @Override
+    public BaseTransform impl_derive(BaseTransform trans) {
+        if (isIdentity()) {
+            return trans;
+        }
+
+        double localPivotX = getPivotX();
+        double localPivotY = getPivotY();
+        double localPivotZ = getPivotZ();
+        double localAngle = getAngle();
+
+        if (localPivotX != 0 || localPivotY != 0 || localPivotZ != 0) {
+            trans = trans.deriveWithTranslation(localPivotX, localPivotY, localPivotZ);
+            trans = trans.deriveWithRotation(Math.toRadians(localAngle),
+                         getAxis().getX(),getAxis().getY(), getAxis().getZ());
+            return trans.deriveWithTranslation(-localPivotX, -localPivotY, -localPivotZ);
+        } else {
+            return trans.deriveWithRotation(Math.toRadians(localAngle),
+                         getAxis().getX(), getAxis().getY(), getAxis().getZ());
+        }
+    }
+
     @Override
     void validate() {
         getAxis();
--- a/modules/graphics/src/main/java/javafx/scene/transform/Scale.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/javafx/scene/transform/Scale.java	Thu Sep 05 08:26:28 2013 +0100
@@ -29,6 +29,7 @@
 import javafx.beans.property.DoublePropertyBase;
 
 import com.sun.javafx.geom.transform.Affine3D;
+import com.sun.javafx.geom.transform.BaseTransform;
 import javafx.geometry.Point2D;
 import javafx.geometry.Point3D;
 
@@ -763,6 +764,25 @@
         }
     }
 
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    @Override
+    public BaseTransform impl_derive(BaseTransform trans) {
+        if (isIdentity()) {
+            return trans;
+        }
+        if (getPivotX() != 0 || getPivotY() != 0 || getPivotZ() != 0) {
+            trans = trans.deriveWithTranslation(getPivotX(), getPivotY(), getPivotZ());
+            trans = trans.deriveWithScale(getX(), getY(), getZ());
+            return trans.deriveWithTranslation(-getPivotX(), -getPivotY(), -getPivotZ());
+        } else {
+            return trans.deriveWithScale(getX(), getY(), getZ());
+        }
+    }
+
     @Override
     void validate() {
         getX(); getPivotX();
--- a/modules/graphics/src/main/java/javafx/scene/transform/Shear.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/javafx/scene/transform/Shear.java	Thu Sep 05 08:26:28 2013 +0100
@@ -29,6 +29,7 @@
 import javafx.beans.property.DoublePropertyBase;
 
 import com.sun.javafx.geom.transform.Affine3D;
+import com.sun.javafx.geom.transform.BaseTransform;
 import javafx.geometry.Point2D;
 import javafx.geometry.Point3D;
 
@@ -698,6 +699,19 @@
         }
     }
 
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    @Override
+    public BaseTransform impl_derive(final BaseTransform trans) {
+        return trans.deriveWithConcatenation(
+                1.0, getY(),
+                getX(), 1.0,
+                getTx(), getTy());
+    }
+
     @Override
     void validate() {
         getX(); getPivotX();
--- a/modules/graphics/src/main/java/javafx/scene/transform/Transform.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/javafx/scene/transform/Transform.java	Thu Sep 05 08:26:28 2013 +0100
@@ -33,7 +33,10 @@
 import com.sun.javafx.WeakReferenceQueue;
 import com.sun.javafx.binding.ExpressionHelper;
 import com.sun.javafx.event.EventHandlerManager;
+import com.sun.javafx.geom.transform.Affine2D;
 import com.sun.javafx.geom.transform.Affine3D;
+import com.sun.javafx.geom.transform.AffineBase;
+import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.scene.transform.TransformUtils;
 import java.lang.ref.SoftReference;
 import javafx.beans.InvalidationListener;
@@ -1982,6 +1985,13 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
+    public abstract BaseTransform impl_derive(BaseTransform t);
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
     public void impl_add(final Node node) {
         impl_nodes.add(node);
     }
--- a/modules/graphics/src/main/java/javafx/scene/transform/Translate.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/main/java/javafx/scene/transform/Translate.java	Thu Sep 05 08:26:28 2013 +0100
@@ -29,6 +29,7 @@
 import javafx.beans.property.DoublePropertyBase;
 
 import com.sun.javafx.geom.transform.Affine3D;
+import com.sun.javafx.geom.transform.BaseTransform;
 import javafx.geometry.Point2D;
 import javafx.geometry.Point3D;
 
@@ -526,6 +527,16 @@
         trans.translate(getX(), getY(), getZ());
     }
 
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    @Override
+    public BaseTransform impl_derive(final BaseTransform trans) {
+        return trans.deriveWithTranslation(getX(), getY(), getZ());
+    }
+
     @Override
     void validate() {
         getX();
--- a/modules/graphics/src/test/java/com/sun/javafx/test/TransformHelper.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/test/java/com/sun/javafx/test/TransformHelper.java	Thu Sep 05 08:26:28 2013 +0100
@@ -69,7 +69,7 @@
     /**
      * Asserts the {@code matrix} equals to the specified expected values
      */
-    public static void assertMatrix(Affine3D matrix,
+    public static void assertMatrix(BaseTransform matrix,
             double mxx, double mxy, double mxz, double tx,
             double myx, double myy, double myz, double ty,
             double mzx, double mzy, double mzz, double tz) {
@@ -247,6 +247,41 @@
                 rzx, rzy, rzz, rzt);
     }
 
+    /**
+     * Concatenates the two transforms.
+     */
+    public static Transform concatenate(BaseTransform t1, Transform t2) {
+
+        final double txx = t2.getMxx();
+        final double txy = t2.getMxy();
+        final double txz = t2.getMxz();
+        final double ttx = t2.getTx();
+        final double tyx = t2.getMyx();
+        final double tyy = t2.getMyy();
+        final double tyz = t2.getMyz();
+        final double tty = t2.getTy();
+        final double tzx = t2.getMzx();
+        final double tzy = t2.getMzy();
+        final double tzz = t2.getMzz();
+        final double ttz = t2.getTz();
+        final double rxx = (t1.getMxx() * txx + t1.getMxy() * tyx + t1.getMxz() * tzx /* + getMxt * 0.0 */);
+        final double rxy = (t1.getMxx() * txy + t1.getMxy() * tyy + t1.getMxz() * tzy /* + getMxt * 0.0 */);
+        final double rxz = (t1.getMxx() * txz + t1.getMxy() * tyz + t1.getMxz() * tzz /* + getMxt * 0.0 */);
+        final double rxt = (t1.getMxx() * ttx + t1.getMxy() * tty + t1.getMxz() * ttz + t1.getMxt() /* * 1.0 */);
+        final double ryx = (t1.getMyx() * txx + t1.getMyy() * tyx + t1.getMyz() * tzx /* + getMyt * 0.0 */);
+        final double ryy = (t1.getMyx() * txy + t1.getMyy() * tyy + t1.getMyz() * tzy /* + getMyt * 0.0 */);
+        final double ryz = (t1.getMyx() * txz + t1.getMyy() * tyz + t1.getMyz() * tzz /* + getMyt * 0.0 */);
+        final double ryt = (t1.getMyx() * ttx + t1.getMyy() * tty + t1.getMyz() * ttz + t1.getMyt() /* * 1.0 */);
+        final double rzx = (t1.getMzx() * txx + t1.getMzy() * tyx + t1.getMzz() * tzx /* + getMzt * 0.0 */);
+        final double rzy = (t1.getMzx() * txy + t1.getMzy() * tyy + t1.getMzz() * tzy /* + getMzt * 0.0 */);
+        final double rzz = (t1.getMzx() * txz + t1.getMzy() * tyz + t1.getMzz() * tzz /* + getMzt * 0.0 */);
+        final double rzt = (t1.getMzx() * ttx + t1.getMzy() * tty + t1.getMzz() * ttz + t1.getMzt() /* * 1.0 */);
+
+        return TransformUtils.immutableTransform(
+                rxx, rxy, rxz, rxt,
+                ryx, ryy, ryz, ryt,
+                rzx, rzy, rzz, rzt);
+    }
 
     /**
      * Computes determinant of the specified transform.
@@ -719,5 +754,13 @@
                     getMyx(), getMyy(), getMyz(), getTy(),
                     getMzx(), getMzy(), getMzz(), getTz());
         }
+
+        @Override
+        public BaseTransform impl_derive(BaseTransform t) {
+            return t.deriveWithConcatenation(
+                    getMxx(), getMxy(), getMxz(), getTx(),
+                    getMyx(), getMyy(), getMyz(), getTy(),
+                    getMzx(), getMzy(), getMzz(), getTz());
+        }
     }
 }
--- a/modules/graphics/src/test/java/javafx/scene/NodeTest.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/test/java/javafx/scene/NodeTest.java	Thu Sep 05 08:26:28 2013 +0100
@@ -27,7 +27,10 @@
 
 import com.sun.javafx.geom.BoxBounds;
 import com.sun.javafx.geom.PickRay;
+import com.sun.javafx.geom.transform.Affine2D;
+import com.sun.javafx.geom.transform.Affine3D;
 import com.sun.javafx.geom.transform.BaseTransform;
+import com.sun.javafx.geom.transform.Translate2D;
 import com.sun.javafx.pgstub.StubToolkit;
 import com.sun.javafx.scene.DirtyBits;
 import com.sun.javafx.scene.input.PickResultChooser;
@@ -55,9 +58,14 @@
 import java.lang.reflect.Method;
 import java.util.Comparator;
 import javafx.scene.layout.AnchorPane;
+import javafx.scene.transform.Affine;
+import javafx.scene.transform.Scale;
+import javafx.scene.transform.Shear;
+import javafx.scene.transform.Translate;
 import javafx.stage.Stage;
 
 import static org.junit.Assert.*;
+import org.junit.Ignore;
 /**
  * Tests various aspects of Node.
  *
@@ -1360,7 +1368,7 @@
 
         final Rectangle clip = new Rectangle(100, 100) {
             @Override protected NGNode impl_createPeer() {
-                return new MockClip();
+                return new MockNGRect();
             }
         };
         circle.setClip(clip);
@@ -1378,15 +1386,261 @@
 
         ((StubToolkit) Toolkit.getToolkit()).firePulse();
 
-        assertEquals(300, ((MockClip) clip.impl_getPeer()).w, 1e-10);
+        assertEquals(300, ((MockNGRect) clip.impl_getPeer()).w, 1e-10);
     }
 
-    private class MockClip extends NGRectangle {
+    @Test
+    public void untransformedNodeShouldSyncIdentityTransform() {
+        final Node node = createTestRect();
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(BaseTransform.IDENTITY_TRANSFORM,
+                ((MockNGRect) node.impl_getPeer()).t);
+    }
+
+    @Test
+    public void nodeTransfomedByIdentitiesShouldSyncIdentityTransform() {
+        final Node node = createTestRect();
+        node.setRotationAxis(Rotate.X_AXIS);
+        node.getTransforms().add(new Translate());
+        node.getTransforms().add(new Scale());
+        node.getTransforms().add(new Affine());
+        node.getTransforms().add(new Rotate(0, Rotate.Y_AXIS));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(BaseTransform.IDENTITY_TRANSFORM,
+                ((MockNGRect) node.impl_getPeer()).t);
+    }
+
+    @Test
+    public void translatedNodeShouldSyncTranslateTransform1() {
+        final Node node = createTestRect();
+        node.setTranslateX(30);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Translate2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void translatedNodeShouldSyncTranslateTransform2() {
+        final Node node = createTestRect();
+        node.getTransforms().add(new Translate(20, 10));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Translate2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void multitranslatedNodeShouldSyncTranslateTransform() {
+        final Node node = createTestRect();
+        node.setTranslateX(30);
+        node.getTransforms().add(new Translate(20, 10));
+        node.getTransforms().add(new Translate(10, 20));
+        node.getTransforms().add(new Translate(5, 5, 0));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Translate2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void mirroringShouldSyncAffine2DTransform() {
+        final Node node = createTestRect();
+        node.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void rotatedNodeShouldSyncAffine2DTransform1() {
+        final Node node = createTestRect();
+        node.setRotate(20);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void rotatedNodeShouldSyncAffine2DTransform2() {
+        final Node node = createTestRect();
+        node.getTransforms().add(new Rotate(20));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void multiRotatedNodeShouldSyncAffine2DTransform() {
+        final Node node = createTestRect();
+        node.setRotate(20);
+        node.getTransforms().add(new Rotate(20));
+        node.getTransforms().add(new Rotate(0, Rotate.X_AXIS));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void scaledNodeShouldSyncAffine2DTransform1() {
+        final Node node = createTestRect();
+        node.setScaleX(2);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void scaledNodeShouldSyncAffine2DTransform2() {
+        final Node node = createTestRect();
+        node.getTransforms().add(new Scale(2, 1));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void multiScaledNodeShouldSyncAffine2DTransform() {
+        final Node node = createTestRect();
+        node.setScaleX(20);
+        node.getTransforms().add(new Scale(2, 1));
+        node.getTransforms().add(new Scale(0.5, 2, 1));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void shearedNodeShouldSyncAffine2DTransform() {
+        final Node node = createTestRect();
+        node.getTransforms().add(new Shear(2, 1));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine2D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void ztranslatedNodeShouldSyncAffine3DTransform1() {
+        final Node node = createTestRect();
+        node.setTranslateZ(30);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine3D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void ztranslatedNodeShouldSyncAffine3DTransform2() {
+        final Node node = createTestRect();
+        node.getTransforms().add(new Translate(0, 0, 10));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine3D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void zscaledNodeShouldSyncAffine3DTransform1() {
+        final Node node = createTestRect();
+        node.setScaleZ(0.5);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine3D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void zscaledNodeShouldSyncAffine3DTransform2() {
+        final Node node = createTestRect();
+        node.getTransforms().add(new Scale(1, 1, 2));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine3D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void nonZRotatedNodeShouldSyncAffine3DTransform1() {
+        final Node node = createTestRect();
+        node.setRotationAxis(Rotate.Y_AXIS);
+        node.setRotate(10);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine3D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void nonZRotatedNodeShouldSyncAffine3DTransform2() {
+        final Node node = createTestRect();
+        node.getTransforms().add(new Rotate(10, Rotate.X_AXIS));
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+        assertSame(Affine3D.class,
+                ((MockNGRect) node.impl_getPeer()).t.getClass());
+    }
+
+    @Test
+    public void translateTransformShouldBeReusedWhenPossible() {
+        final Node node = createTestRect();
+        node.setTranslateX(10);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+
+        BaseTransform t = ((MockNGRect) node.impl_getPeer()).t;
+
+        ((MockNGRect) node.impl_getPeer()).t = null;
+        node.setTranslateX(20);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+
+        assertSame(t, ((MockNGRect) node.impl_getPeer()).t);
+    }
+
+    @Test
+    public void affine2DTransformShouldBeReusedWhenPossible() {
+        final Node node = createTestRect();
+        node.setScaleX(10);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+
+        BaseTransform t = ((MockNGRect) node.impl_getPeer()).t;
+
+        ((MockNGRect) node.impl_getPeer()).t = null;
+        node.setRotate(20);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+
+        assertSame(t, ((MockNGRect) node.impl_getPeer()).t);
+    }
+
+    @Test
+    public void affine3DTransformShouldBeReusedWhenPossible() {
+        final Node node = createTestRect();
+        node.setScaleZ(10);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+
+        BaseTransform t = ((MockNGRect) node.impl_getPeer()).t;
+
+        ((MockNGRect) node.impl_getPeer()).t = null;
+        node.setRotate(20);
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
+
+        assertSame(t, ((MockNGRect) node.impl_getPeer()).t);
+    }
+
+    private Node createTestRect() {
+        final Rectangle rect = new Rectangle() {
+            @Override protected NGNode impl_createPeer() {
+                return new MockNGRect();
+            }
+        };
+        Scene scene = new Scene(new Group(rect));
+        Stage stage = new Stage();
+        stage.setScene(scene);
+        stage.show();
+        return rect;
+    }
+
+    private class MockNGRect extends NGRectangle {
         double w = 0;
+        BaseTransform t = null;
 
         @Override public void updateRectangle(float x, float y, float width,
                 float height, float arcWidth, float arcHeight) {
             w = width;
         }
+
+        @Override
+        public void setTransformMatrix(BaseTransform tx) {
+            t = tx;
+        }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/test/java/javafx/scene/transform/TransformDeriveTest.java	Thu Sep 05 08:26:28 2013 +0100
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.scene.transform;
+
+import com.sun.javafx.geom.transform.Affine2D;
+import com.sun.javafx.geom.transform.Affine3D;
+import com.sun.javafx.geom.transform.BaseTransform;
+import com.sun.javafx.geom.transform.Identity;
+import com.sun.javafx.geom.transform.Translate2D;
+import java.util.Arrays;
+import java.util.Collection;
+import com.sun.javafx.test.TransformHelper;
+import javafx.geometry.Point3D;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static org.junit.Assert.*;
+
+@RunWith(Parameterized.class)
+public class TransformDeriveTest {
+
+    private static final Identity identity = new Identity();
+    private static final Translate2D translate2d = new Translate2D(10, 20);
+    private static final Affine2D affine2d = new Affine2D();
+    static { affine2d.scale(2, 4); }
+    private static final Affine3D affine3d = new Affine3D();
+    static { affine3d.scale(2, 4, 8); }
+
+    private static final Translate translate_identity = new Translate();
+    private static final Translate translate_translate2d = new Translate(10, 20);
+    private static final Translate translate_translate3d = new Translate(10, 20, 30);
+
+    private static final Scale scale_identity = new Scale();
+    private static final Scale scale_pivotedidentity = new Scale(1, 1, 20, 30);
+    private static final Scale scale_scale2d = new Scale(2, 4);
+    private static final Scale scale_pivotedscale2d = new Scale(2, 4, 20, 40);
+    private static final Scale scale_scale3d = new Scale(2, 4, 8);
+    private static final Scale scale_pivotedscale3d = new Scale(2, 4, 8, 20, 40, 60);
+
+    private static final Rotate rotate_identity = new Rotate();
+    private static final Rotate rotate_pivotedidentity = new Rotate(0.0, 10, 20, 30, Rotate.X_AXIS);
+    private static final Rotate rotate_rotate2d = new Rotate(90);
+    private static final Rotate rotate_negative2d = new Rotate(90, 0, 0, 0, new Point3D(0, 0, -1));
+    private static final Rotate rotate_pivotedrotate2d = new Rotate(90, 20, 30);
+    private static final Rotate rotate_rotate3d = new Rotate(90, 0, 0, 0, Rotate.X_AXIS);
+    private static final Rotate rotate_pivotedrotate3d = new Rotate(90, 20, 30, 0, Rotate.X_AXIS);
+
+    private static final Shear shear_identity = new Shear();
+    private static final Shear shear_shear = new Shear(2, 3);
+
+    private static final Affine affine_identity = new Affine();
+    private static final Affine affine_translate2d = new Affine();
+    static { affine_translate2d.appendTranslation(10, 20); }
+    private static final Affine affine_translate3d = new Affine();
+    static { affine_translate3d.appendTranslation(10, 20, 30); }
+    private static final Affine affine_scale2d = new Affine();
+    static { affine_scale2d.appendScale(2, 4); }
+    private static final Affine affine_scale3d = new Affine();
+    static { affine_scale3d.appendScale(2, 4, 8); }
+    private static final Affine affine_affine2d = new Affine();
+    static { affine_affine2d.appendRotation(45); }
+    private static final Affine affine_affine3d = new Affine();
+    static { affine_affine3d.appendRotation(45, 10, 20, 30, 1, 1, 1); }
+
+    @Parameters
+    public static Collection getParams() {
+        return Arrays.asList(new Object[][] {
+            { identity, translate_identity, Identity.class },           //  0
+            { identity, translate_translate2d, Translate2D.class },
+            { identity, translate_translate3d, Affine3D.class },
+            { identity, scale_identity, Identity.class },
+            { identity, scale_pivotedidentity, Identity.class },
+            { identity, scale_scale2d, Affine2D.class },
+            { identity, scale_pivotedscale2d, Affine2D.class },
+            { identity, scale_scale3d, Affine3D.class },
+            { identity, scale_pivotedscale3d, Affine3D.class },
+            { identity, rotate_identity, Identity.class },
+            { identity, rotate_pivotedidentity, Identity.class },       // 10
+            { identity, rotate_rotate2d, Affine2D.class },
+            { identity, rotate_negative2d, Affine2D.class },
+            { identity, rotate_pivotedrotate2d, Affine2D.class },
+            { identity, rotate_rotate3d, Affine3D.class },
+            { identity, rotate_pivotedrotate3d, Affine3D.class },
+            { identity, shear_identity, Identity.class },
+            { identity, shear_shear, Affine2D.class },
+            { identity, affine_identity, Identity.class },
+            { identity, affine_translate2d, Translate2D.class },
+            { identity, affine_translate3d, Affine3D.class },           // 20
+            { identity, affine_scale2d, Affine2D.class },
+            { identity, affine_scale3d, Affine3D.class },
+            { identity, affine_affine2d, Affine2D.class },
+            { identity, affine_affine3d, Affine3D.class },
+
+            { translate2d, translate_identity, Translate2D.class },
+            { translate2d, translate_translate2d, Translate2D.class },
+            { translate2d, translate_translate3d, Affine3D.class },
+            { translate2d, scale_identity, Translate2D.class },
+            { translate2d, scale_pivotedidentity, Translate2D.class },
+            { translate2d, scale_scale2d, Affine2D.class },             // 30
+            { translate2d, scale_pivotedscale2d, Affine2D.class },
+            { translate2d, scale_scale3d, Affine3D.class },
+            { translate2d, scale_pivotedscale3d, Affine3D.class },
+            { translate2d, rotate_identity, Translate2D.class },
+            { translate2d, rotate_pivotedidentity, Translate2D.class },
+            { translate2d, rotate_rotate2d, Affine2D.class },
+            { translate2d, rotate_negative2d, Affine2D.class },
+            { translate2d, rotate_pivotedrotate2d, Affine2D.class },
+            { translate2d, rotate_rotate3d, Affine3D.class },
+            { translate2d, rotate_pivotedrotate3d, Affine3D.class },    // 40
+            { translate2d, shear_identity, Translate2D.class },
+            { translate2d, shear_shear, Affine2D.class },
+            { translate2d, affine_identity, Translate2D.class },
+            { translate2d, affine_translate2d, Translate2D.class },
+            { translate2d, affine_translate3d, Affine3D.class },
+            { translate2d, affine_scale2d, Affine2D.class },
+            { translate2d, affine_scale3d, Affine3D.class },
+            { translate2d, affine_affine2d, Affine2D.class },
+            { translate2d, affine_affine3d, Affine3D.class },
+
+            { affine2d, translate_identity, Affine2D.class },           // 50
+            { affine2d, translate_translate2d, Affine2D.class },
+            { affine2d, translate_translate3d, Affine3D.class },
+            { affine2d, scale_identity, Affine2D.class },
+            { affine2d, scale_pivotedidentity, Affine2D.class },
+            { affine2d, scale_scale2d, Affine2D.class },
+            { affine2d, scale_pivotedscale2d, Affine2D.class },
+            { affine2d, scale_scale3d, Affine3D.class },
+            { affine2d, scale_pivotedscale3d, Affine3D.class },
+            { affine2d, rotate_identity, Affine2D.class },
+            { affine2d, rotate_pivotedidentity, Affine2D.class },       // 60
+            { affine2d, rotate_rotate2d, Affine2D.class },
+            { affine2d, rotate_negative2d, Affine2D.class },
+            { affine2d, rotate_pivotedrotate2d, Affine2D.class },
+            { affine2d, rotate_rotate3d, Affine3D.class },
+            { affine2d, rotate_pivotedrotate3d, Affine3D.class },
+            { affine2d, shear_identity, Affine2D.class },
+            { affine2d, shear_shear, Affine2D.class },
+            { affine2d, affine_identity, Affine2D.class },
+            { affine2d, affine_translate2d, Affine2D.class },
+            { affine2d, affine_translate3d, Affine3D.class },           // 70
+            { affine2d, affine_scale2d, Affine2D.class },
+            { affine2d, affine_scale3d, Affine3D.class },
+            { affine2d, affine_affine2d, Affine2D.class },
+            { affine2d, affine_affine3d, Affine3D.class },
+
+            { affine3d, translate_identity, Affine3D.class },
+            { affine3d, translate_translate2d, Affine3D.class },
+            { affine3d, translate_translate3d, Affine3D.class },
+            { affine3d, scale_identity, Affine3D.class },
+            { affine3d, scale_pivotedidentity, Affine3D.class },
+            { affine3d, scale_scale2d, Affine3D.class },                // 80
+            { affine3d, scale_pivotedscale2d, Affine3D.class },
+            { affine3d, scale_scale3d, Affine3D.class },
+            { affine3d, scale_pivotedscale3d, Affine3D.class },
+            { affine3d, rotate_identity, Affine3D.class },
+            { affine3d, rotate_pivotedidentity, Affine3D.class },
+            { affine3d, rotate_rotate2d, Affine3D.class },
+            { affine3d, rotate_negative2d, Affine3D.class },
+            { affine3d, rotate_pivotedrotate2d, Affine3D.class },
+            { affine3d, rotate_rotate3d, Affine3D.class },
+            { affine3d, rotate_pivotedrotate3d, Affine3D.class },       // 90
+            { affine3d, shear_identity, Affine3D.class },
+            { affine3d, shear_shear, Affine3D.class },
+            { affine3d, affine_identity, Affine3D.class },
+            { affine3d, affine_translate2d, Affine3D.class },
+            { affine3d, affine_translate3d, Affine3D.class },
+            { affine3d, affine_scale2d, Affine3D.class },
+            { affine3d, affine_scale3d, Affine3D.class },
+            { affine3d, affine_affine2d, Affine3D.class },
+            { affine3d, affine_affine3d, Affine3D.class },              // 99
+        });
+    }
+
+    private BaseTransform from;
+    private Transform deriver;
+    private Class deriveType;
+
+    public TransformDeriveTest(BaseTransform from, Transform deriver, Class deriveType) {
+        this.from = from.copy();
+        this.deriver = deriver;
+        this.deriveType = deriveType;
+    }
+
+    @Test public void testDerive() {
+        Transform conc = TransformHelper.concatenate(from, deriver);
+
+        BaseTransform res = deriver.impl_derive(from);
+
+        assertSame(deriveType, res.getClass());
+        TransformHelper.assertMatrix(res,
+                conc.getMxx(), conc.getMxy(), conc.getMxz(), conc.getTx(),
+                conc.getMyx(), conc.getMyy(), conc.getMyz(), conc.getTy(),
+                conc.getMzx(), conc.getMzy(), conc.getMzz(), conc.getTz());
+   }
+}
--- a/modules/graphics/src/test/java/javafx/scene/transform/TransformTest.java	Thu Sep 05 10:53:58 2013 +0400
+++ b/modules/graphics/src/test/java/javafx/scene/transform/TransformTest.java	Thu Sep 05 08:26:28 2013 +0100
@@ -154,6 +154,9 @@
         final Transform t = new Transform() {
             @Override
             public void impl_apply(com.sun.javafx.geom.transform.Affine3D ad) {}
+
+            @Override
+            public BaseTransform impl_derive(BaseTransform ad) { return null; }
         };
 
         TransformHelper.assertMatrix(t,