/*
 * Copyright (c) 2011, 2018, 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 javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.geometry.Point3D;

import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.BaseTransform;
import javafx.geometry.Point2D;


This class represents an Affine object that rotates coordinates around an anchor point. This operation is equivalent to translating the coordinates so that the anchor point is at the origin (S1), then rotating them about the new origin (S2), and finally translating so that the intermediate origin is restored to the coordinates of the original anchor point (S3).

The matrix representing the rotation transformation around an axis (x,y,z) by an angle t is as follows:

             [   cos(t)   -sin(t)   0   x-x*cos(t)+y*sin(t)   ]
             [   sin(t)    cos(t)   0   y-x*sin(t)-y*cos(t)   ]
             [     0         0      1           z             ]

For example, to rotate a text 30 degrees around the Z-axis at anchor point of (50,30):


Text text = new Text("This is a test");
text.setX(10);
text.setY(50);
text.setFont(new Font(20));
text.getTransforms().add(new Rotate(30, 50, 30));
Since:JavaFX 2.0
/** * This class represents an {@code Affine} object that rotates coordinates * around an anchor point. This operation is equivalent to translating the * coordinates so that the anchor point is at the origin (S1), then rotating them * about the new origin (S2), and finally translating so that the * intermediate origin is restored to the coordinates of the original * anchor point (S3). * <p> * The matrix representing the rotation transformation around an axis {@code (x,y,z)} * by an angle {@code t} is as follows: * <pre> * [ cos(t) -sin(t) 0 x-x*cos(t)+y*sin(t) ] * [ sin(t) cos(t) 0 y-x*sin(t)-y*cos(t) ] * [ 0 0 1 z ] * </pre> * <p> * For example, to rotate a text 30 degrees around the Z-axis at * anchor point of (50,30): * <pre>{@code * Text text = new Text("This is a test"); * text.setX(10); * text.setY(50); * text.setFont(new Font(20)); * * text.getTransforms().add(new Rotate(30, 50, 30)); * }</pre> * * @since JavaFX 2.0 */
public class Rotate extends Transform {
Specifies the X-axis as the axis of rotation.
/** * Specifies the X-axis as the axis of rotation. */
public static final Point3D X_AXIS = new Point3D(1,0,0);
Specifies the Y-axis as the axis of rotation.
/** * Specifies the Y-axis as the axis of rotation. */
public static final Point3D Y_AXIS = new Point3D(0,1,0);
Specifies the Z-axis as the axis of rotation.
/** * Specifies the Z-axis as the axis of rotation. */
public static final Point3D Z_AXIS = new Point3D(0,0,1);
Avoids lot of repeated computation.
See Also:
  • MatrixCache
/** * Avoids lot of repeated computation. * @see #MatrixCache */
private MatrixCache cache;
Avoids lot of repeated computation.
See Also:
  • MatrixCache
/** * Avoids lot of repeated computation. * @see #MatrixCache */
private MatrixCache inverseCache;
Creates a default Rotate transform (identity).
/** * Creates a default Rotate transform (identity). */
public Rotate() { }
Creates a two-dimensional Rotate transform. The pivot point is set to (0,0)
Params:
  • angle – the angle of rotation measured in degrees
/** * Creates a two-dimensional Rotate transform. * The pivot point is set to (0,0) * @param angle the angle of rotation measured in degrees */
public Rotate(double angle) { setAngle(angle); }
Creates a three-dimensional Rotate transform. The pivot point is set to (0,0,0)
Params:
  • angle – the angle of rotation measured in degrees
  • axis – the axis of rotation
/** * Creates a three-dimensional Rotate transform. * The pivot point is set to (0,0,0) * @param angle the angle of rotation measured in degrees * @param axis the axis of rotation */
public Rotate(double angle, Point3D axis) { setAngle(angle); setAxis(axis); }
Creates a two-dimensional Rotate transform with pivot.
Params:
  • angle – the angle of rotation measured in degrees
  • pivotX – the X coordinate of the rotation pivot point
  • pivotY – the Y coordinate of the rotation pivot point
/** * Creates a two-dimensional Rotate transform with pivot. * @param angle the angle of rotation measured in degrees * @param pivotX the X coordinate of the rotation pivot point * @param pivotY the Y coordinate of the rotation pivot point */
public Rotate(double angle, double pivotX, double pivotY) { setAngle(angle); setPivotX(pivotX); setPivotY(pivotY); }
Creates a simple Rotate transform with three-dimensional pivot.
Params:
  • angle – the angle of rotation measured in degrees
  • pivotX – the X coordinate of the rotation pivot point
  • pivotY – the Y coordinate of the rotation pivot point
  • pivotZ – the Z coordinate of the rotation pivot point
/** * Creates a simple Rotate transform with three-dimensional pivot. * @param angle the angle of rotation measured in degrees * @param pivotX the X coordinate of the rotation pivot point * @param pivotY the Y coordinate of the rotation pivot point * @param pivotZ the Z coordinate of the rotation pivot point */
public Rotate(double angle, double pivotX, double pivotY, double pivotZ) { this(angle, pivotX, pivotY); setPivotZ(pivotZ); }
Creates a three-dimensional Rotate transform with pivot.
Params:
  • angle – the angle of rotation measured in degrees
  • pivotX – the X coordinate of the rotation pivot point
  • pivotY – the Y coordinate of the rotation pivot point
  • pivotZ – the Z coordinate of the rotation pivot point
  • axis – the axis of rotation
/** * Creates a three-dimensional Rotate transform with pivot. * @param angle the angle of rotation measured in degrees * @param pivotX the X coordinate of the rotation pivot point * @param pivotY the Y coordinate of the rotation pivot point * @param pivotZ the Z coordinate of the rotation pivot point * @param axis the axis of rotation */
public Rotate(double angle, double pivotX, double pivotY, double pivotZ, Point3D axis) { this(angle, pivotX, pivotY); setPivotZ(pivotZ); setAxis(axis); }
Defines the angle of rotation measured in degrees.
/** * Defines the angle of rotation measured in degrees. */
private DoubleProperty angle; public final void setAngle(double value) { angleProperty().set(value); } public final double getAngle() { return angle == null ? 0.0 : angle.get(); } public final DoubleProperty angleProperty() { if (angle == null) { angle = new DoublePropertyBase() { @Override public void invalidated() { transformChanged(); } @Override public Object getBean() { return Rotate.this; } @Override public String getName() { return "angle"; } }; } return angle; }
Defines the X coordinate of the rotation pivot point.
@defaultValue0.0
/** * Defines the X coordinate of the rotation pivot point. * * @defaultValue 0.0 */
private DoubleProperty pivotX; public final void setPivotX(double value) { pivotXProperty().set(value); } public final double getPivotX() { return pivotX == null ? 0.0 : pivotX.get(); } public final DoubleProperty pivotXProperty() { if (pivotX == null) { pivotX = new DoublePropertyBase() { @Override public void invalidated() { transformChanged(); } @Override public Object getBean() { return Rotate.this; } @Override public String getName() { return "pivotX"; } }; } return pivotX; }
Defines the Y coordinate of the rotation pivot point.
@defaultValue0.0
/** * Defines the Y coordinate of the rotation pivot point. * * @defaultValue 0.0 */
private DoubleProperty pivotY; public final void setPivotY(double value) { pivotYProperty().set(value); } public final double getPivotY() { return pivotY == null ? 0.0 : pivotY.get(); } public final DoubleProperty pivotYProperty() { if (pivotY == null) { pivotY = new DoublePropertyBase() { @Override public void invalidated() { transformChanged(); } @Override public Object getBean() { return Rotate.this; } @Override public String getName() { return "pivotY"; } }; } return pivotY; }
Defines the Z coordinate of the rotation pivot point.
@defaultValue0.0
/** * Defines the Z coordinate of the rotation pivot point. * * @defaultValue 0.0 */
private DoubleProperty pivotZ; public final void setPivotZ(double value) { pivotZProperty().set(value); } public final double getPivotZ() { return pivotZ == null ? 0.0 : pivotZ.get(); } public final DoubleProperty pivotZProperty() { if (pivotZ == null) { pivotZ = new DoublePropertyBase() { @Override public void invalidated() { transformChanged(); } @Override public Object getBean() { return Rotate.this; } @Override public String getName() { return "pivotZ"; } }; } return pivotZ; }
Defines the axis of rotation at the pivot point.
/** * Defines the axis of rotation at the pivot point. */
private ObjectProperty<Point3D> axis; public final void setAxis(Point3D value) { axisProperty().set(value); } public final Point3D getAxis() { return axis == null ? Z_AXIS : axis.get(); } public final ObjectProperty<Point3D> axisProperty() { if (axis == null) { axis = new ObjectPropertyBase<Point3D>(Z_AXIS) { @Override public void invalidated() { transformChanged(); } @Override public Object getBean() { return Rotate.this; } @Override public String getName() { return "axis"; } }; } return axis; } /* ************************************************************************* * * * Element getters * * * **************************************************************************/ @Override public double getMxx() { updateCache(); return cache.mxx; } @Override public double getMxy() { updateCache(); return cache.mxy; } @Override public double getMxz() { updateCache(); return cache.mxz; } @Override public double getTx() { updateCache(); return cache.tx; } @Override public double getMyx() { updateCache(); return cache.myx; } @Override public double getMyy() { updateCache(); return cache.myy; } @Override public double getMyz() { updateCache(); return cache.myz; } @Override public double getTy() { updateCache(); return cache.ty; } @Override public double getMzx() { updateCache(); return cache.mzx; } @Override public double getMzy() { updateCache(); return cache.mzy; } @Override public double getMzz() { updateCache(); return cache.mzz; } @Override public double getTz() { updateCache(); return cache.tz; } /* ************************************************************************* * * * State getters * * * **************************************************************************/ @Override boolean computeIs2D() { final Point3D a = getAxis(); return (a.getX() == 0.0 && a.getY() == 0.0) || getAngle() == 0; } @Override boolean computeIsIdentity() { if (getAngle() == 0.0) { return true; } final Point3D a = getAxis(); return a.getX() == 0 && a.getY() == 0 && a.getZ() == 0.0; } /* ************************************************************************* * * * Array getters * * * **************************************************************************/ @Override void fill2DArray(double[] array) { updateCache(); array[0] = cache.mxx; array[1] = cache.mxy; array[2] = cache.tx; array[3] = cache.myx; array[4] = cache.myy; array[5] = cache.ty; } @Override void fill3DArray(double[] array) { updateCache(); array[0] = cache.mxx; array[1] = cache.mxy; array[2] = cache.mxz; array[3] = cache.tx; array[4] = cache.myx; array[5] = cache.myy; array[6] = cache.myz; array[7] = cache.ty; array[8] = cache.mzx; array[9] = cache.mzy; array[10] = cache.mzz; array[11] = cache.tz; return; } /* ************************************************************************* * * * Transform creators * * * **************************************************************************/ @Override public Transform createConcatenation(Transform transform) { if (transform instanceof Rotate) { Rotate r = (Rotate) transform; final double px = getPivotX(); final double py = getPivotY(); final double pz = getPivotZ(); if ((r.getAxis() == getAxis() || r.getAxis().normalize().equals(getAxis().normalize())) && px == r.getPivotX() && py == r.getPivotY() && pz == r.getPivotZ()) { return new Rotate(getAngle() + r.getAngle(), px, py, pz, getAxis()); } } if (transform instanceof Affine) { Affine a = (Affine) transform.clone(); a.prepend(this); return a; } return super.createConcatenation(transform); } @Override public Transform createInverse() throws NonInvertibleTransformException { return new Rotate(-getAngle(), getPivotX(), getPivotY(), getPivotZ(), getAxis()); } @Override public Rotate clone() { return new Rotate(getAngle(), getPivotX(), getPivotY(), getPivotZ(), getAxis()); } /* ************************************************************************* * * * Transform, Inverse Transform * * * **************************************************************************/ @Override public Point2D transform(double x, double y) { ensureCanTransform2DPoint(); updateCache(); return new Point2D( cache.mxx * x + cache.mxy * y + cache.tx, cache.myx * x + cache.myy * y + cache.ty); } @Override public Point3D transform(double x, double y, double z) { updateCache(); return new Point3D( cache.mxx * x + cache.mxy * y + cache.mxz * z + cache.tx, cache.myx * x + cache.myy * y + cache.myz * z + cache.ty, cache.mzx * x + cache.mzy * y + cache.mzz * z + cache.tz); } @Override void transform2DPointsImpl(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) { updateCache(); while (--numPts >= 0) { final double x = srcPts[srcOff++]; final double y = srcPts[srcOff++]; dstPts[dstOff++] = cache.mxx * x + cache.mxy * y + cache.tx; dstPts[dstOff++] = cache.myx * x + cache.myy * y + cache.ty; } } @Override void transform3DPointsImpl(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) { updateCache(); while (--numPts >= 0) { final double x = srcPts[srcOff++]; final double y = srcPts[srcOff++]; final double z = srcPts[srcOff++]; dstPts[dstOff++] = cache.mxx * x + cache.mxy * y + cache.mxz * z + cache.tx; dstPts[dstOff++] = cache.myx * x + cache.myy * y + cache.myz * z + cache.ty; dstPts[dstOff++] = cache.mzx * x + cache.mzy * y + cache.mzz * z + cache.tz; } } @Override public Point2D deltaTransform(double x, double y) { ensureCanTransform2DPoint(); updateCache(); return new Point2D( cache.mxx * x + cache.mxy * y, cache.myx * x + cache.myy * y); } @Override public Point3D deltaTransform(double x, double y, double z) { updateCache(); return new Point3D( cache.mxx * x + cache.mxy * y + cache.mxz * z, cache.myx * x + cache.myy * y + cache.myz * z, cache.mzx * x + cache.mzy * y + cache.mzz * z); } @Override public Point2D inverseTransform(double x, double y) { ensureCanTransform2DPoint(); updateInverseCache(); return new Point2D( inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.tx, inverseCache.myx * x + inverseCache.myy * y + inverseCache.ty); } @Override public Point3D inverseTransform(double x, double y, double z) { updateInverseCache(); return new Point3D( inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.mxz * z + inverseCache.tx, inverseCache.myx * x + inverseCache.myy * y + inverseCache.myz * z + inverseCache.ty, inverseCache.mzx * x + inverseCache.mzy * y + inverseCache.mzz * z + inverseCache.tz); } @Override void inverseTransform2DPointsImpl(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) { updateInverseCache(); while (--numPts >= 0) { final double x = srcPts[srcOff++]; final double y = srcPts[srcOff++]; dstPts[dstOff++] = inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.tx; dstPts[dstOff++] = inverseCache.myx * x + inverseCache.myy * y + inverseCache.ty; } } @Override void inverseTransform3DPointsImpl(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) { updateInverseCache(); while (--numPts >= 0) { final double x = srcPts[srcOff++]; final double y = srcPts[srcOff++]; final double z = srcPts[srcOff++]; dstPts[dstOff++] = inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.mxz * z + inverseCache.tx; dstPts[dstOff++] = inverseCache.myx * x + inverseCache.myy * y + inverseCache.myz * z + inverseCache.ty; dstPts[dstOff++] = inverseCache.mzx * x + inverseCache.mzy * y + inverseCache.mzz * z + inverseCache.tz; } } @Override public Point2D inverseDeltaTransform(double x, double y) { ensureCanTransform2DPoint(); updateInverseCache(); return new Point2D( inverseCache.mxx * x + inverseCache.mxy * y, inverseCache.myx * x + inverseCache.myy * y); } @Override public Point3D inverseDeltaTransform(double x, double y, double z) { updateInverseCache(); return new Point3D( inverseCache.mxx * x + inverseCache.mxy * y + inverseCache.mxz * z, inverseCache.myx * x + inverseCache.myy * y + inverseCache.myz * z, inverseCache.mzx * x + inverseCache.mzy * y + inverseCache.mzz * z); } /* ************************************************************************* * * * Other API * * * **************************************************************************/
Returns a string representation of this Rotate object.
Returns:a string representation of this Rotate object.
/** * Returns a string representation of this {@code Rotate} object. * @return a string representation of this {@code Rotate} object. */
@Override public String toString() { final StringBuilder sb = new StringBuilder("Rotate ["); sb.append("angle=").append(getAngle()); sb.append(", pivotX=").append(getPivotX()); sb.append(", pivotY=").append(getPivotY()); sb.append(", pivotZ=").append(getPivotZ()); sb.append(", axis=").append(getAxis()); return sb.append("]").toString(); } /* ************************************************************************* * * * Internal implementation stuff * * * **************************************************************************/ @Override void apply(final Affine3D trans) { double localPivotX = getPivotX(); double localPivotY = getPivotY(); double localPivotZ = getPivotZ(); double localAngle = getAngle(); if (localPivotX != 0 || localPivotY != 0 || localPivotZ != 0) { trans.translate(localPivotX, localPivotY, localPivotZ); trans.rotate(Math.toRadians(localAngle), getAxis().getX(),getAxis().getY(), getAxis().getZ()); trans.translate(-localPivotX, -localPivotY, -localPivotZ); } else { trans.rotate(Math.toRadians(localAngle), getAxis().getX(), getAxis().getY(), getAxis().getZ()); } } @Override BaseTransform 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(); getAngle(); getPivotX(); getPivotY(); getPivotZ(); } @Override protected void transformChanged() { if (cache != null) { cache.invalidate(); } super.transformChanged(); } @Override void appendTo(Affine a) { a.appendRotation(getAngle(), getPivotX(), getPivotY(), getPivotZ(), getAxis()); } @Override void prependTo(Affine a) { a.prependRotation(getAngle(), getPivotX(), getPivotY(), getPivotZ(), getAxis()); }
Updates the matrix cache
/** * Updates the matrix cache */
private void updateCache() { if (cache == null) { cache = new MatrixCache(); } if (!cache.valid) { cache.update(getAngle(), getAxis(), getPivotX(), getPivotY(), getPivotZ()); } }
Updates the inverse matrix cache
/** * Updates the inverse matrix cache */
private void updateInverseCache() { if (inverseCache == null) { inverseCache = new MatrixCache(); } if (!inverseCache.valid) { inverseCache.update(-getAngle(), getAxis(), getPivotX(), getPivotY(), getPivotZ()); } }
Matrix cache. Computing single transformation matrix elements for a general rotation is quite expensive. Also each of those partial computations need some common operations to be made (compute sin and cos, normalize axis). Therefore with the direct element computations if all the getters for the elements are called to get the matrix, the result is slow. If a matrix element is asked for, we can reasonably anticipate that some other elements will be asked for as well. So when any element needs to be computed, we compute the entire matrix, cache it, and use the stored values until the transform changes.
/** * Matrix cache. Computing single transformation matrix elements for * a general rotation is quite expensive. Also each of those partial * computations need some common operations to be made (compute sin * and cos, normalize axis). Therefore with the direct element computations * if all the getters for the elements are called to get the matrix, * the result is slow. * * If a matrix element is asked for, we can reasonably anticipate that * some other elements will be asked for as well. So when any element * needs to be computed, we compute the entire matrix, cache it, * and use the stored values until the transform changes. */
private static class MatrixCache { boolean valid = false; boolean is3D = false; double mxx, mxy, mxz, tx, myx, myy, myz, ty, mzx, mzy, mzz, tz; public MatrixCache() { // to have the 3D part right when using 2D-only mzz = 1.0; } public void update(double angle, Point3D axis, double px, double py, double pz) { final double rads = Math.toRadians(angle); final double sin = Math.sin(rads); final double cos = Math.cos(rads); if (axis == Z_AXIS || (axis.getX() == 0.0 && axis.getY() == 0.0 && axis.getZ() > 0.0)) { // 2D case mxx = cos; mxy = -sin; tx = px * (1 - cos) + py * sin; myx = sin; myy = cos; ty = py * (1 - cos) - px * sin; if (is3D) { // Was 3D, needs to set the 3D values mxz = 0.0; myz = 0.0; mzx = 0.0; mzy = 0.0; mzz = 1.0; tz = 0.0; is3D = false; } valid = true; return; } // 3D case is3D = true; double axisX, axisY, axisZ; if (axis == X_AXIS || axis == Y_AXIS || axis == Z_AXIS) { axisX = axis.getX(); axisY = axis.getY(); axisZ = axis.getZ(); } else { // normalize final double mag = Math.sqrt(axis.getX() * axis.getX() + axis.getY() * axis.getY() + axis.getZ() * axis.getZ()); if (mag == 0.0) { mxx = 1; mxy = 0; mxz = 0; tx = 0; myx = 0; myy = 1; myz = 0; ty = 0; mzx = 0; mzy = 0; mzz = 1; tz = 0; valid = true; return; } else { axisX = axis.getX() / mag; axisY = axis.getY() / mag; axisZ = axis.getZ() / mag; } } mxx = cos + axisX * axisX * (1 - cos); mxy = axisX * axisY * (1 - cos) - axisZ * sin; mxz = axisX * axisZ * (1 - cos) + axisY * sin; tx = px * (1 - mxx) - py * mxy - pz * mxz; myx = axisY * axisX * (1 - cos) + axisZ * sin; myy = cos + axisY * axisY * (1 - cos); myz = axisY * axisZ * (1 - cos) - axisX * sin; ty = py * (1 - myy) - px * myx - pz * myz; mzx = axisZ * axisX * (1 - cos) - axisY * sin; mzy = axisZ * axisY * (1 - cos) + axisX * sin; mzz = cos + axisZ * axisZ * (1 - cos); tz = pz * (1 - mzz) - px * mzx - py * mzy; valid = true; } public void invalidate() { valid = false; } } }