/* * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ 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; import javafx.scene.transform.Transform; import javafx.scene.transform.Affine; /** * Internal utilities for transformations */ public class TransformUtils { /** * Creates an immutable arbitrary transformation. * This method is not intended for public use, users should use the Affine * class. */ public static Transform immutableTransform( double mxx, double mxy, double mxz, double tx, double myx, double myy, double myz, double ty, double mzx, double mzy, double mzz, double tz) { return new ImmutableTransform( mxx, mxy, mxz, tx, myx, myy, myz, ty, mzx, mzy, mzz, tz); } /** * Creates an immutable transformation filled with current values * from the given transformation. * This method is not intended for public use, users should use the Affine * class. */ public static Transform immutableTransform(Transform t) { return new ImmutableTransform( t.getMxx(), t.getMxy(), t.getMxz(), t.getTx(), t.getMyx(), t.getMyy(), t.getMyz(), t.getTy(), t.getMzx(), t.getMzy(), t.getMzz(), t.getTz()); } /** * Creates an immutable arbitrary transformation. * If the given instance is not null, it is reused. * This method is not intended for public use, users should use the Affine * class. * @throws ClassCastException if the given transform to be reused * is not instance of ImmutableTransform */ public static Transform immutableTransform(Transform reuse, double mxx, double mxy, double mxz, double tx, double myx, double myy, double myz, double ty, double mzx, double mzy, double mzz, double tz) { if (reuse == null) { return new ImmutableTransform( mxx, mxy, mxz, tx, myx, myy, myz, ty, mzx, mzy, mzz, tz); } ((ImmutableTransform) reuse).setToTransform( mxx, mxy, mxz, tx, myx, myy, myz, ty, mzx, mzy, mzz, tz); return reuse; } /** * Creates an immutable transformation filled with current values * from the given transformation. * If the given instance is not null, it is reused. * This method is not intended for public use, users should use the Affine * class. * @throws ClassCastException if the given transform to be reused * is not instance of ImmutableTransform */ public static Transform immutableTransform(Transform reuse, Transform t) { return immutableTransform((ImmutableTransform) reuse, t.getMxx(), t.getMxy(), t.getMxz(), t.getTx(), t.getMyx(), t.getMyy(), t.getMyz(), t.getTy(), t.getMzx(), t.getMzy(), t.getMzz(), t.getTz()); } /** * Creates an immutable transformation filled with concatenation * of the given transformations. * If the given instance is not null, it is reused. * This method is not intended for public use, users should use the Affine * class. * @throws ClassCastException if one of the given transforms * is not instance of ImmutableTransform */ public static Transform immutableTransform(Transform reuse, Transform left, Transform right) { if (reuse == null) { reuse = new ImmutableTransform(); } ((ImmutableTransform) reuse).setToConcatenation( (ImmutableTransform) left, ((ImmutableTransform) right)); return reuse; } /** * Immutable transformation with performance optimizations based on Affine. * * From user's perspective, this transform is immutable. However, we can * modify it internally. This allows for reusing instances that were * not handed to users. The caller is responsible for not modifying * user-visible instances. * * Note: can't override Transform's package private methods so they cannot * be optimized. Currently not a big deal. */ static class ImmutableTransform extends Transform { private static final int APPLY_IDENTITY = 0; private static final int APPLY_TRANSLATE = 1; private static final int APPLY_SCALE = 2; private static final int APPLY_SHEAR = 4; private static final int APPLY_NON_3D = 0; private static final int APPLY_3D_COMPLEX = 4; private transient int state2d; private transient int state3d; private double xx; private double xy; private double xz; private double yx; private double yy; private double yz; private double zx; private double zy; private double zz; private double xt; private double yt; private double zt; public ImmutableTransform() { xx = yy = zz = 1.0; } public ImmutableTransform(Transform transform) { this(transform.getMxx(), transform.getMxy(), transform.getMxz(), transform.getTx(), transform.getMyx(), transform.getMyy(), transform.getMyz(), transform.getTy(), transform.getMzx(), transform.getMzy(), transform.getMzz(), transform.getTz()); } public ImmutableTransform(double mxx, double mxy, double mxz, double tx, double myx, double myy, double myz, double ty, double mzx, double mzy, double mzz, double tz) { xx = mxx; xy = mxy; xz = mxz; xt = tx; yx = myx; yy = myy; yz = myz; yt = ty; zx = mzx; zy = mzy; zz = mzz; zt = tz; updateState(); } // Beware: this is modifying immutable transform! // It is private and it is there just for the purpose of reusing // instances not given to users private void setToTransform(double mxx, double mxy, double mxz, double tx, double myx, double myy, double myz, double ty, double mzx, double mzy, double mzz, double tz) { xx = mxx; xy = mxy; xz = mxz; xt = tx; yx = myx; yy = myy; yz = myz; yt = ty; zx = mzx; zy = mzy; zz = mzz; zt = tz; updateState(); } // Beware: this is modifying immutable transform! // It is private and it is there just for the purpose of reusing // instances not given to users private void setToConcatenation(ImmutableTransform left, ImmutableTransform right) { if (left.state3d == APPLY_NON_3D && right.state3d == APPLY_NON_3D) { xx = left.xx * right.xx + left.xy * right.yx; xy = left.xx * right.xy + left.xy * right.yy; xt = left.xx * right.xt + left.xy * right.yt + left.xt; yx = left.yx * right.xx + left.yy * right.yx; yy = left.yx * right.xy + left.yy * right.yy; yt = left.yx * right.xt + left.yy * right.yt + left.yt; if (state3d != APPLY_NON_3D) { xz = yz = zx = zy = zt = 0.0; zz = 1.0; state3d = APPLY_NON_3D; } updateState2D(); } else { xx = left.xx * right.xx + left.xy * right.yx + left.xz * right.zx; xy = left.xx * right.xy + left.xy * right.yy + left.xz * right.zy; xz = left.xx * right.xz + left.xy * right.yz + left.xz * right.zz; xt = left.xx * right.xt + left.xy * right.yt + left.xz * right.zt + left.xt; yx = left.yx * right.xx + left.yy * right.yx + left.yz * right.zx; yy = left.yx * right.xy + left.yy * right.yy + left.yz * right.zy; yz = left.yx * right.xz + left.yy * right.yz + left.yz * right.zz; yt = left.yx * right.xt + left.yy * right.yt + left.yz * right.zt + left.yt; zx = left.zx * right.xx + left.zy * right.yx + left.zz * right.zx; zy = left.zx * right.xy + left.zy * right.yy + left.zz * right.zy; zz = left.zx * right.xz + left.zy * right.yz + left.zz * right.zz; zt = left.zx * right.xt + left.zy * right.yt + left.zz * right.zt + left.zt; updateState(); } // could be further optimized using the states, but that would // require a lot of code (see Affine and all its append* methods) } @Override public double getMxx() { return xx; } @Override public double getMxy() { return xy; } @Override public double getMxz() { return xz; } @Override public double getTx() { return xt; } @Override public double getMyx() { return yx; } @Override public double getMyy() { return yy; } @Override public double getMyz() { return yz; } @Override public double getTy() { return yt; } @Override public double getMzx() { return zx; } @Override public double getMzy() { return zy; } @Override public double getMzz() { return zz; } @Override public double getTz() { return zt; } /* ************************************************************************* * * * State getters * * * **************************************************************************/ @Override public double determinant() { switch(state3d) { default: stateError(); // cannot reach case APPLY_NON_3D: switch (state2d) { default: stateError(); // cannot reach case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SHEAR | APPLY_SCALE: return xx * yy - xy * yx; case APPLY_SHEAR | APPLY_TRANSLATE: case APPLY_SHEAR: return -(xy* yx); case APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SCALE: return xx * yy; case APPLY_TRANSLATE: case APPLY_IDENTITY: return 1.0; } case APPLY_TRANSLATE: return 1.0; case APPLY_SCALE: case APPLY_SCALE | APPLY_TRANSLATE: return xx * yy * zz; case APPLY_3D_COMPLEX: return (xx* (yy * zz - zy * yz) + xy* (yz * zx - zz * yx) + xz* (yx * zy - zx * yy)); } } @Override public Transform createConcatenation(Transform transform) { javafx.scene.transform.Affine a = new Affine(this); a.append(transform); return a; } @Override public javafx.scene.transform.Affine createInverse() throws NonInvertibleTransformException { javafx.scene.transform.Affine t = new Affine(this); t.invert(); return t; } @Override public Transform clone() { return new ImmutableTransform(this); } /* ************************************************************************* * * * Transform, Inverse Transform * * * **************************************************************************/ @Override public Point2D transform(double x, double y) { ensureCanTransform2DPoint(); switch (state2d) { default: stateError(); // cannot reach case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: return new Point2D( xx * x + xy * y + xt, yx * x + yy * y + yt); case APPLY_SHEAR | APPLY_SCALE: return new Point2D( xx * x + xy * y, yx * x + yy * y); case APPLY_SHEAR | APPLY_TRANSLATE: return new Point2D( xy * y + xt, yx * x + yt); case APPLY_SHEAR: return new Point2D(xy * y, yx * x); case APPLY_SCALE | APPLY_TRANSLATE: return new Point2D( xx * x + xt, yy * y + yt); case APPLY_SCALE: return new Point2D(xx * x, yy * y); case APPLY_TRANSLATE: return new Point2D(x + xt, y + yt); case APPLY_IDENTITY: return new Point2D(x, y); } } @Override public Point3D transform(double x, double y, double z) { switch (state3d) { default: stateError(); // cannot reach case APPLY_NON_3D: switch (state2d) { default: stateError(); // cannot reach case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: return new Point3D( xx * x + xy * y + xt, yx * x + yy * y + yt, z); case APPLY_SHEAR | APPLY_SCALE: return new Point3D( xx * x + xy * y, yx * x + yy * y, z); case APPLY_SHEAR | APPLY_TRANSLATE: return new Point3D( xy * y + xt, yx * x + yt, z); case APPLY_SHEAR: return new Point3D(xy * y, yx * x, z); case APPLY_SCALE | APPLY_TRANSLATE: return new Point3D( xx * x + xt, yy * y + yt, z); case APPLY_SCALE: return new Point3D(xx * x, yy * y, z); case APPLY_TRANSLATE: return new Point3D(x + xt, y + yt, z); case APPLY_IDENTITY: return new Point3D(x, y, z); } case APPLY_TRANSLATE: return new Point3D(x + xt, y + yt, z + zt); case APPLY_SCALE: return new Point3D(xx * x, yy * y, zz * z); case APPLY_SCALE | APPLY_TRANSLATE: return new Point3D( xx * x + xt, yy * y + yt, zz * z + zt); case APPLY_3D_COMPLEX: return new Point3D( xx * x + xy * y + xz * z + xt, yx * x + yy * y + yz * z + yt, zx * x + zy * y + zz * z + zt); } } @Override public Point2D deltaTransform(double x, double y) { ensureCanTransform2DPoint(); switch (state2d) { default: stateError(); // cannot reach case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SHEAR | APPLY_SCALE: return new Point2D( xx * x + xy * y, yx * x + yy * y); case APPLY_SHEAR | APPLY_TRANSLATE: case APPLY_SHEAR: return new Point2D(xy * y, yx * x); case APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SCALE: return new Point2D(xx * x, yy * y); case APPLY_TRANSLATE: case APPLY_IDENTITY: return new Point2D(x, y); } } @Override public Point3D deltaTransform(double x, double y, double z) { switch (state3d) { default: stateError(); // cannot reach case APPLY_NON_3D: switch (state2d) { default: stateError(); // cannot reach case APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SHEAR | APPLY_SCALE: return new Point3D( xx * x + xy * y, yx * x + yy * y, z); case APPLY_SHEAR | APPLY_TRANSLATE: case APPLY_SHEAR: return new Point3D(xy * y, yx * x, z); case APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SCALE: return new Point3D(xx * x, yy * y, z); case APPLY_TRANSLATE: case APPLY_IDENTITY: return new Point3D(x, y, z); } case APPLY_TRANSLATE: return new Point3D(x, y, z); case APPLY_SCALE: case APPLY_SCALE | APPLY_TRANSLATE: return new Point3D(xx * x, yy * y, zz * z); case APPLY_3D_COMPLEX: return new Point3D( xx * x + xy * y + xz * z, yx * x + yy * y + yz * z, zx * x + zy * y + zz * z); } } @Override public Point2D inverseTransform(double x, double y) throws NonInvertibleTransformException { ensureCanTransform2DPoint(); switch (state2d) { default: return super.inverseTransform(x, y); case APPLY_SHEAR | APPLY_TRANSLATE: if (xy == 0.0 || yx == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point2D( (1.0 / yx) * y - yt / yx, (1.0 / xy) * x - xt / xy); case APPLY_SHEAR: if (xy == 0.0 || yx == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point2D((1.0 / yx) * y, (1.0 / xy) * x); case APPLY_SCALE | APPLY_TRANSLATE: if (xx == 0.0 || yy == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point2D( (1.0 / xx) * x - xt / xx, (1.0 / yy) * y - yt / yy); case APPLY_SCALE: if (xx == 0.0 || yy == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point2D((1.0 / xx) * x, (1.0 / yy) * y); case APPLY_TRANSLATE: return new Point2D(x - xt, y - yt); case APPLY_IDENTITY: return new Point2D(x, y); } } @Override public Point3D inverseTransform(double x, double y, double z) throws NonInvertibleTransformException { switch(state3d) { default: stateError(); // cannot reach case APPLY_NON_3D: switch (state2d) { default: return super.inverseTransform(x, y, z); case APPLY_SHEAR | APPLY_TRANSLATE: if (xy == 0.0 || yx == 0.0) { throw new NonInvertibleTransformException( "Determinant is 0"); } return new Point3D( (1.0 / yx) * y - yt / yx, (1.0 / xy) * x - xt / xy, z); case APPLY_SHEAR: if (xy == 0.0 || yx == 0.0) { throw new NonInvertibleTransformException( "Determinant is 0"); } return new Point3D( (1.0 / yx) * y, (1.0 / xy) * x, z); case APPLY_SCALE | APPLY_TRANSLATE: if (xx == 0.0 || yy == 0.0) { throw new NonInvertibleTransformException( "Determinant is 0"); } return new Point3D( (1.0 / xx) * x - xt / xx, (1.0 / yy) * y - yt / yy, z); case APPLY_SCALE: if (xx == 0.0 || yy == 0.0) { throw new NonInvertibleTransformException( "Determinant is 0"); } return new Point3D((1.0 / xx) * x, (1.0 / yy) * y, z); case APPLY_TRANSLATE: return new Point3D(x - xt, y - yt, z); case APPLY_IDENTITY: return new Point3D(x, y, z); } case APPLY_TRANSLATE: return new Point3D(x - xt, y - yt, z - zt); case APPLY_SCALE: if (xx == 0.0 || yy == 0.0 || zz == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point3D( (1.0 / xx) * x, (1.0 / yy) * y, (1.0 / zz) * z); case APPLY_SCALE | APPLY_TRANSLATE: if (xx == 0.0 || yy == 0.0 || zz == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point3D( (1.0 / xx) * x - xt / xx, (1.0 / yy) * y - yt / yy, (1.0 / zz) * z - zt / zz); case APPLY_3D_COMPLEX: return super.inverseTransform(x, y, z); } } @Override public Point2D inverseDeltaTransform(double x, double y) throws NonInvertibleTransformException { ensureCanTransform2DPoint(); switch (state2d) { default: return super.inverseDeltaTransform(x, y); case APPLY_SHEAR | APPLY_TRANSLATE: case APPLY_SHEAR: if (xy == 0.0 || yx == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point2D((1.0 / yx) * y, (1.0 / xy) * x); case APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SCALE: if (xx == 0.0 || yy == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point2D((1.0 / xx) * x, (1.0 / yy) * y); case APPLY_TRANSLATE: case APPLY_IDENTITY: return new Point2D(x, y); } } @Override public Point3D inverseDeltaTransform(double x, double y, double z) throws NonInvertibleTransformException { switch(state3d) { default: stateError(); // cannot reach case APPLY_NON_3D: switch (state2d) { default: return super.inverseDeltaTransform(x, y, z); case APPLY_SHEAR | APPLY_TRANSLATE: case APPLY_SHEAR: if (xy == 0.0 || yx == 0.0) { throw new NonInvertibleTransformException( "Determinant is 0"); } return new Point3D( (1.0 / yx) * y, (1.0 / xy) * x, z); case APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SCALE: if (xx == 0.0 || yy == 0.0) { throw new NonInvertibleTransformException( "Determinant is 0"); } return new Point3D( (1.0 / xx) * x, (1.0 / yy) * y, z); case APPLY_TRANSLATE: case APPLY_IDENTITY: return new Point3D(x, y, z); } case APPLY_TRANSLATE: return new Point3D(x, y, z); case APPLY_SCALE | APPLY_TRANSLATE: case APPLY_SCALE: if (xx == 0.0 || yy == 0.0 || zz == 0.0) { throw new NonInvertibleTransformException("Determinant is 0"); } return new Point3D( (1.0 / xx) * x, (1.0 / yy) * y, (1.0 / zz) * z); case APPLY_3D_COMPLEX: return super.inverseDeltaTransform(x, y, z); } } /* ************************************************************************* * * * Other API * * * **************************************************************************/ @Override public String toString() { final StringBuilder sb = new StringBuilder("Transform [\n"); sb.append("\t").append(xx); sb.append(", ").append(xy); sb.append(", ").append(xz); sb.append(", ").append(xt); sb.append('\n'); sb.append("\t").append(yx); sb.append(", ").append(yy); sb.append(", ").append(yz); sb.append(", ").append(yt); sb.append('\n'); sb.append("\t").append(zx); sb.append(", ").append(zy); sb.append(", ").append(zz); sb.append(", ").append(zt); return sb.append("\n]").toString(); } /* ************************************************************************* * * * Internal implementation stuff * * * **************************************************************************/ private void updateState() { updateState2D(); state3d = APPLY_NON_3D; if (xz != 0.0 || yz != 0.0 || zx != 0.0 || zy != 0.0) { state3d = APPLY_3D_COMPLEX; } else { if ((state2d & APPLY_SHEAR) == 0) { if (zt != 0.0) { state3d |= APPLY_TRANSLATE; } if (zz != 1.0) { state3d |= APPLY_SCALE; } if (state3d != APPLY_NON_3D) { state3d |= (state2d & (APPLY_SCALE | APPLY_TRANSLATE)); } } else { if (zz != 1.0 || zt != 0.0) { state3d = APPLY_3D_COMPLEX; } } } } private void updateState2D() { if (xy == 0.0 && yx == 0.0) { if (xx == 1.0 && yy == 1.0) { if (xt == 0.0 && yt == 0.0) { state2d = APPLY_IDENTITY; } else { state2d = APPLY_TRANSLATE; } } else { if (xt == 0.0 && yt == 0.0) { state2d = APPLY_SCALE; } else { state2d = (APPLY_SCALE | APPLY_TRANSLATE); } } } else { if (xx == 0.0 && yy == 0.0) { if (xt == 0.0 && yt == 0.0) { state2d = APPLY_SHEAR; } else { state2d = (APPLY_SHEAR | APPLY_TRANSLATE); } } else { if (xt == 0.0 && yt == 0.0) { state2d = (APPLY_SHEAR | APPLY_SCALE); } else { state2d = (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE); } } } } void ensureCanTransform2DPoint() throws IllegalStateException { if (state3d != APPLY_NON_3D) { throw new IllegalStateException("Cannot transform 2D point " + "with a 3D transform"); } } private static void stateError() { throw new InternalError("missing case in a switch"); } /** * @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 void impl_apply(final Affine3D trans) { trans.concatenate(xx, xy, xz, xt, yx, yy, yz, yt, zx, zy, zz, zt); } /** * @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() { return state2d; } /** * Used only by tests to check the 3d matrix state */ int getState3d() { return state3d; } } }