/*
* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene.transform;
import java.util.
Iterator;
import com.sun.javafx.geometry.
BoundsUtils;
import javafx.event.
EventDispatchChain;
import javafx.scene.
Node;
import com.sun.javafx.util.
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;
import javafx.beans.property.
ObjectProperty;
import javafx.beans.property.
ReadOnlyBooleanProperty;
import javafx.beans.property.
SimpleObjectProperty;
import javafx.beans.value.
ChangeListener;
import javafx.event.
Event;
import javafx.event.
EventHandler;
import javafx.event.
EventTarget;
import javafx.event.
EventType;
import javafx.geometry.
BoundingBox;
import javafx.geometry.
Bounds;
import javafx.geometry.
Point2D;
import javafx.geometry.
Point3D;
// PENDING_DOC_REVIEW of this whole class
/**
* This class is a base class for different affine transformations.
* It provides factory methods for the simple transformations - rotating,
* scaling, shearing, and translation. It allows to get the transformation
* matrix elements for any transform.
*
* <p>Example:</p>
*
* <pre><code>
* Rectangle rect = new Rectangle(50,50, Color.RED);
* rect.getTransforms().add(new Rotate(45,0,0)); //rotate by 45 degrees
* </code></pre>
* @since JavaFX 2.0
*/
public abstract class
Transform implements
Cloneable,
EventTarget {
/* *************************************************************************
* *
* Factories *
* *
**************************************************************************/
/**
* Returns a new {@code Affine} object from 12 number
* values representing the 6 specifiable entries of the 3x4
* Affine transformation matrix.
*
* @param mxx the X coordinate scaling element of the 3x4 matrix
* @param myx the Y coordinate shearing element of the 3x4 matrix
* @param mxy the X coordinate shearing element of the 3x4 matrix
* @param myy the Y coordinate scaling element of the 3x4 matrix
* @param tx the X coordinate translation element of the 3x4 matrix
* @param ty the Y coordinate translation element of the 3x4 matrix
* @return a new {@code Affine} object derived from specified parameters
*/
public static
Affine affine(
double
mxx, double
myx, double
mxy, double
myy, double
tx, double
ty) {
final
Affine affine = new
Affine();
affine.
setMxx(
mxx);
affine.
setMxy(
mxy);
affine.
setTx(
tx);
affine.
setMyx(
myx);
affine.
setMyy(
myy);
affine.
setTy(
ty);
return
affine;
}
/**
* Returns a new {@code Affine} object from 12 number
* values representing the 12 specifiable entries of the 3x4
* Affine transformation matrix.
*
* @param mxx the X coordinate scaling element of the 3x4 matrix
* @param mxy the XY element of the 3x4 matrix
* @param mxz the XZ element of the 3x4 matrix
* @param tx the X coordinate translation element of the 3x4 matrix
* @param myx the YX element of the 3x4 matrix
* @param myy the Y coordinate scaling element of the 3x4 matrix
* @param myz the YZ element of the 3x4 matrix
* @param ty the Y coordinate translation element of the 3x4 matrix
* @param mzx the ZX element of the 3x4 matrix
* @param mzy the ZY element of the 3x4 matrix
* @param mzz the Z coordinate scaling element of the 3x4 matrix
* @param tz the Z coordinate translation element of the 3x4 matrix
* @return a new {@code Affine} object derived from specified parameters
*/
public static
Affine affine(
double
mxx, double
mxy, double
mxz, double
tx,
double
myx, double
myy, double
myz, double
ty,
double
mzx, double
mzy, double
mzz, double
tz) {
final
Affine affine = new
Affine();
affine.
setMxx(
mxx);
affine.
setMxy(
mxy);
affine.
setMxz(
mxz);
affine.
setTx(
tx);
affine.
setMyx(
myx);
affine.
setMyy(
myy);
affine.
setMyz(
myz);
affine.
setTy(
ty);
affine.
setMzx(
mzx);
affine.
setMzy(
mzy);
affine.
setMzz(
mzz);
affine.
setTz(
tz);
return
affine;
}
/**
* Returns a {@code Translate} object representing a translation transformation.
* <p>
* This is equivalent to:
* <pre>
* new Translate(x, y);
* </pre>
*/
public static
Translate translate(double
x, double
y) {
final
Translate translate = new
Translate();
translate.
setX(
x);
translate.
setY(
y);
return
translate;
}
/**
* Returns a {@code Rotate} object that rotates coordinates around a pivot
* point.
* <p>
* This is equivalent to:
* <pre>
* new Rotate(angle, pivotX, pivotY);
* </pre>
*/
public static
Rotate rotate(double
angle, double
pivotX, double
pivotY) {
final
Rotate rotate = new
Rotate();
rotate.
setAngle(
angle);
rotate.
setPivotX(
pivotX);
rotate.
setPivotY(
pivotY);
return
rotate;
}
/**
* Returns a {@code Scale} object representing a scaling transformation.
* <p>
* This is equivalent to:
* <pre>
* new Scale(x, y);
* </pre>
*/
public static
Scale scale(double
x, double
y) {
final
Scale scale = new
Scale();
scale.
setX(
x);
scale.
setY(
y);
return
scale;
}
/**
* Returns a {@code Scale} object representing a scaling transformation.
* The returned scale operation will be about the given pivot point.
* <p>
* This is equivalent to:
* <pre>
* new Scale(x, y, pivotX, pivotY);
* </pre>
*/
public static
Scale scale(double
x, double
y, double
pivotX, double
pivotY) {
final
Scale scale = new
Scale();
scale.
setX(
x);
scale.
setY(
y);
scale.
setPivotX(
pivotX);
scale.
setPivotY(
pivotY);
return
scale;
}
/**
* Returns a {@code Shear} object representing a shearing transformation.
* <p>
* This is equivalent to:
* <pre>
* new Shear(x, y);
* </pre>
*/
public static
Shear shear(double
x, double
y) {
final
Shear shear = new
Shear();
shear.
setX(
x);
shear.
setY(
y);
return
shear;
}
/**
* Returns a {@code Shear} object representing a shearing transformation.
* <p>
* This is equivalent to:
* <pre>
* new Shear(x, y, pivotX, pivotY);
* </pre>
*/
public static
Shear shear(double
x, double
y, double
pivotX, double
pivotY) {
final
Shear shear = new
Shear();
shear.
setX(
x);
shear.
setY(
y);
shear.
setPivotX(
pivotX);
shear.
setPivotY(
pivotY);
return
shear;
}
/**
* For transforms with expensive inversion we cache the inverted matrix
* once it is needed and computed for some operation.
*/
private
SoftReference<
Transform>
inverseCache = null;
private
WeakReferenceQueue impl_nodes = new
WeakReferenceQueue();
/* *************************************************************************
* *
* Element getters *
* *
**************************************************************************/
/**
* Gets the X coordinate scaling element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMxx() {
return 1.0;
}
/**
* Gets the XY coordinate element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMxy() {
return 0.0;
}
/**
* Gets the XZ coordinate element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMxz() {
return 0.0;
}
/**
* Gets the X coordinate translation element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getTx() {
return 0.0;
}
/**
* Gets the YX coordinate element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMyx() {
return 0.0;
}
/**
* Gets the Y coordinate scaling element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMyy() {
return 1.0;
}
/**
* Gets the YZ coordinate element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMyz() {
return 0.0;
}
/**
* Gets the Y coordinate translation element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getTy() {
return 0.0;
}
/**
* Gets the ZX coordinate element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMzx() {
return 0.0;
}
/**
* Gets the ZY coordinate element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMzy() {
return 0.0;
}
/**
* Gets the Z coordinate scaling element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getMzz() {
return 1.0;
}
/**
* Gets the Z coordinate translation element of the 3x4 matrix.
*
* @since JavaFX 2.2
*/
public double
getTz() {
return 0.0;
}
/**
* Gets the specified element of the transformation matrix.
* @param type type of matrix to get the value from
* @param row zero-based row number
* @param column zero-based column number
* @return value of the specified transformation matrix element
* @throws IllegalArgumentException if a 2D matrix type is requested for
* a 3D transform
* @throws IndexOutOfBoundsException if the indices are not within
* the specified matrix type
* @throws NullPointerException if the specified {@code type} is null
* @since JavaFX 8.0
*/
public double
getElement(
MatrixType type, int
row, int
column) {
if (
row < 0 ||
row >=
type.
rows() ||
column < 0 ||
column >=
type.
columns()) {
throw new
IndexOutOfBoundsException("Index outside of affine "
+ "matrix " +
type + ": [" +
row + ", " +
column + "]");
}
switch(
type) {
case
MT_2D_2x3:
// fall-through
case
MT_2D_3x3:
if (!
isType2D()) {
throw new
IllegalArgumentException("Cannot access 2D matrix "
+ "of a 3D transform");
}
switch(
row) {
case 0:
switch(
column) {
case 0: return
getMxx();
case 1: return
getMxy();
case 2: return
getTx();
}
case 1:
switch(
column) {
case 0: return
getMyx();
case 1: return
getMyy();
case 2: return
getTy();
}
case 2:
switch(
column) {
case 0: return 0.0;
case 1: return 0.0;
case 2: return 1.0;
}
}
break;
case
MT_3D_3x4:
// fall-through
case
MT_3D_4x4:
switch(
row) {
case 0:
switch(
column) {
case 0: return
getMxx();
case 1: return
getMxy();
case 2: return
getMxz();
case 3: return
getTx();
}
case 1:
switch(
column) {
case 0: return
getMyx();
case 1: return
getMyy();
case 2: return
getMyz();
case 3: return
getTy();
}
case 2:
switch(
column) {
case 0: return
getMzx();
case 1: return
getMzy();
case 2: return
getMzz();
case 3: return
getTz();
}
case 3:
switch(
column) {
case 0: return 0.0;
case 1: return 0.0;
case 2: return 0.0;
case 3: return 1.0;
}
}
break;
}
// cannot reach here
throw new
InternalError("Unsupported matrix type " +
type);
}
/* *************************************************************************
* *
* State getters *
* *
**************************************************************************/
/**
* Computes if this transform is currently a 2D transform (has no effect
* in the direction of Z axis).
* Used by the subclasses to effectively provide value of the type2D
* property.
* @return true if this transform is currently 2D-only
*/
boolean
computeIs2D() {
return
getMxz() == 0.0 &&
getMzx() == 0.0 &&
getMzy() == 0.0 &&
getMzz() == 1.0 &&
getTz() == 0.0;
}
/**
* Computes if this transform is currently an identity (has
* no effect in any direction).
* Used by the subclasses to effectively provide value of the identity
* property.
* @return true if this transform is currently an identity transform
*/
boolean
computeIsIdentity() {
return
getMxx() == 1.0 &&
getMxy() == 0.0 &&
getMxz() == 0.0 &&
getTx() == 0.0 &&
getMyx() == 0.0 &&
getMyy() == 1.0 &&
getMyz() == 0.0 &&
getTy() == 0.0 &&
getMzx() == 0.0 &&
getMzy() == 0.0 &&
getMzz() == 1.0 &&
getTz() == 0.0;
}
/**
* Computes determinant of the transformation matrix.
* Among other things, determinant can be used for testing this transform's
* invertibility - it is invertible if determinant is not equal to zero.
* @return Determinant of the transformation matrix
* @since JavaFX 8.0
*/
public double
determinant() {
final double
myx =
getMyx();
final double
myy =
getMyy();
final double
myz =
getMyz();
final double
mzx =
getMzx();
final double
mzy =
getMzy();
final double
mzz =
getMzz();
return (
getMxx() * (
myy *
mzz -
mzy *
myz) +
getMxy() * (
myz *
mzx -
mzz *
myx) +
getMxz() * (
myx *
mzy -
mzx *
myy));
}
/**
* Determines if this is currently a 2D transform.
* Transform is 2D if it has no effect along the Z axis.
* @since JavaFX 8.0
*/
private
LazyBooleanProperty type2D;
public final boolean
isType2D() {
return
type2D == null ?
computeIs2D() :
type2D.
get();
}
public final
ReadOnlyBooleanProperty type2DProperty() {
if (
type2D == null) {
type2D = new
LazyBooleanProperty() {
@
Override
protected boolean
computeValue() {
return
computeIs2D();
}
@
Override
public
Object getBean() {
return
Transform.this;
}
@
Override
public
String getName() {
return "type2D";
}
};
}
return
type2D;
}
/**
* Determines if this is currently an identity transform.
* Identity transform has no effect on the transformed nodes.
* @since JavaFX 8.0
*/
private
LazyBooleanProperty identity;
public final boolean
isIdentity() {
return
identity == null ?
computeIsIdentity() :
identity.
get();
}
public final
ReadOnlyBooleanProperty identityProperty() {
if (
identity == null) {
identity = new
LazyBooleanProperty() {
@
Override
protected boolean
computeValue() {
return
computeIsIdentity();
}
@
Override
public
Object getBean() {
return
Transform.this;
}
@
Override
public
String getName() {
return "identity";
}
};
}
return
identity;
}
/**
* Lazily computed read-only boolean property implementation.
* Used for type2D and identity properties.
*/
private static abstract class
LazyBooleanProperty
extends
ReadOnlyBooleanProperty {
private
ExpressionHelper<
Boolean>
helper;
private boolean
valid;
private boolean
value;
@
Override
public void
addListener(
InvalidationListener listener) {
helper =
ExpressionHelper.
addListener(
helper, this,
listener);
}
@
Override
public void
removeListener(
InvalidationListener listener) {
helper =
ExpressionHelper.
removeListener(
helper,
listener);
}
@
Override
public void
addListener(
ChangeListener<? super
Boolean>
listener) {
helper =
ExpressionHelper.
addListener(
helper, this,
listener);
}
@
Override
public void
removeListener(
ChangeListener<? super
Boolean>
listener) {
helper =
ExpressionHelper.
removeListener(
helper,
listener);
}
@
Override
public boolean
get() {
if (!
valid) {
value =
computeValue();
valid = true;
}
return
value;
}
public void
invalidate() {
if (
valid) {
valid = false;
ExpressionHelper.
fireValueChangedEvent(
helper);
}
}
protected abstract boolean
computeValue();
}
/**
* Transforms the specified point by this transform and by the specified
* transform and returns distance of the result points. Used for similarTo
* method. Has to be used only for 2D transforms (otherwise throws an
* exception).
* @param t the other transform
* @param x point's X coordinate
* @param y point's Y coordinate
* @return distance of the transformed points
*/
private double
transformDiff(
Transform t, double
x, double
y) {
final
Point2D byThis =
transform(
x,
y);
final
Point2D byOther =
t.
transform(
x,
y);
return
byThis.
distance(
byOther);
}
/**
* Transforms the specified point by this transform and by the specified
* transform and returns distance of the result points. Used for similarTo
* method.
* @param t the other transform
* @param x point's X coordinate
* @param y point's Y coordinate
* @param z point's Z coordinate
* @return distance of the transformed points
*/
private double
transformDiff(
Transform t, double
x, double
y, double
z) {
final
Point3D byThis =
transform(
x,
y,
z);
final
Point3D byOther =
t.
transform(
x,
y,
z);
return
byThis.
distance(
byOther);
}
/**
* Checks if this transform is similar to the specified transform.
* The two transforms are considered similar if any point from
* {@code range} is transformed by them to points that are no farther
* than {@code maxDelta} from each other.
* @param transform transform to be compared to this transform
* @param range region of interest on which the two transforms are compared
* @param maxDelta maximum allowed distance for the results of transforming
* any single point from {@code range} by the two transforms
* @return true if the transforms are similar according to the specified
* criteria
* @throws NullPointerException if the specified {@code transform}
* or {@code range} is null
* @since JavaFX 8.0
*/
public boolean
similarTo(
Transform transform,
Bounds range, double
maxDelta) {
double
cornerX,
cornerY,
cornerZ;
if (
isType2D() &&
transform.
isType2D()) {
cornerX =
range.
getMinX();
cornerY =
range.
getMinY();
if (
transformDiff(
transform,
cornerX,
cornerY) >
maxDelta) {
return false;
}
cornerY =
range.
getMaxY();
if (
transformDiff(
transform,
cornerX,
cornerY) >
maxDelta) {
return false;
}
cornerX =
range.
getMaxX();
cornerY =
range.
getMinY();
if (
transformDiff(
transform,
cornerX,
cornerY) >
maxDelta) {
return false;
}
cornerY =
range.
getMaxY();
if (
transformDiff(
transform,
cornerX,
cornerY) >
maxDelta) {
return false;
}
return true;
}
cornerX =
range.
getMinX();
cornerY =
range.
getMinY();
cornerZ =
range.
getMinZ();
if (
transformDiff(
transform,
cornerX,
cornerY,
cornerZ) >
maxDelta) {
return false;
}
cornerY =
range.
getMaxY();
if (
transformDiff(
transform,
cornerX,
cornerY,
cornerZ) >
maxDelta) {
return false;
}
cornerX =
range.
getMaxX();
cornerY =
range.
getMinY();
if (
transformDiff(
transform,
cornerX,
cornerY,
cornerZ) >
maxDelta) {
return false;
}
cornerY =
range.
getMaxY();
if (
transformDiff(
transform,
cornerX,
cornerY,
cornerZ) >
maxDelta) {
return false;
}
if (
range.
getDepth() != 0.0) {
cornerX =
range.
getMinX();
cornerY =
range.
getMinY();
cornerZ =
range.
getMaxZ();
if (
transformDiff(
transform,
cornerX,
cornerY,
cornerZ) >
maxDelta) {
return false;
}
cornerY =
range.
getMaxY();
if (
transformDiff(
transform,
cornerX,
cornerY,
cornerZ) >
maxDelta) {
return false;
}
cornerX =
range.
getMaxX();
cornerY =
range.
getMinY();
if (
transformDiff(
transform,
cornerX,
cornerY,
cornerZ) >
maxDelta) {
return false;
}
cornerY =
range.
getMaxY();
if (
transformDiff(
transform,
cornerX,
cornerY,
cornerZ) >
maxDelta) {
return false;
}
}
return true;
}
/* *************************************************************************
* *
* Array getters *
* *
**************************************************************************/
/**
* Core of the toArray implementation for the 2D case.
* All of the checks has been made by the enclosing method as well as
* the constant elements filled, this method only fills the varying
* elements to the array. Used by subclasses to fill
* the elements efficiently.
* @param array array to be filled with the 6 2D elements
*/
void
fill2DArray(double[]
array) {
array[0] =
getMxx();
array[1] =
getMxy();
array[2] =
getTx();
array[3] =
getMyx();
array[4] =
getMyy();
array[5] =
getTy();
}
/**
* Core of the toArray implementation for the 3D case.
* All of the checks has been made by the enclosing method as well as
* the constant elements filled, this method only fills the varying
* elements to the array. Used by subclasses to fill
* the elements efficiently.
* @param array array to be filled with the 12 3D elements
*/
void
fill3DArray(double[]
array) {
array[0] =
getMxx();
array[1] =
getMxy();
array[2] =
getMxz();
array[3] =
getTx();
array[4] =
getMyx();
array[5] =
getMyy();
array[6] =
getMyz();
array[7] =
getTy();
array[8] =
getMzx();
array[9] =
getMzy();
array[10] =
getMzz();
array[11] =
getTz();
}
/**
* Returns an array containing the flattened transformation matrix.
* If the requested matrix type fits in the specified array, it is returned
* therein. Otherwise, a new array is created.
* @param type matrix type to be filled in the array
* @param array array into which the elements of the matrix are to be
* stored, if it is non-null and big enough; otherwise,
* a new array is created for this purpose.
* @return an array containing the elements of the requested matrix type
* representing this transform
* @throws IllegalArgumentException if a 2D matrix type is requested for
* a 3D transform
* @throws NullPointerException if the specified {@code type} is null
* @since JavaFX 8.0
*/
public double[]
toArray(
MatrixType type, double[]
array) {
checkRequestedMAT(
type);
if (
array == null ||
array.length <
type.
elements()) {
array = new double[
type.
elements()];
}
switch (
type) {
case
MT_2D_3x3:
array[6] = 0.0;
array[7] = 0.0;
array[8] = 1.0;
// fall-through
case
MT_2D_2x3:
fill2DArray(
array);
break;
case
MT_3D_4x4:
array[12] = 0.0;
array[13] = 0.0;
array[14] = 0.0;
array[15] = 1.0;
// fall-through
case
MT_3D_3x4:
fill3DArray(
array);
break;
default:
throw new
InternalError("Unsupported matrix type " +
type);
}
return
array;
}
/**
* Returns an array containing the flattened transformation matrix.
* @param type matrix type to be filled in the array
* @return an array containing the elements of the requested matrix type
* representing this transform
* @throws IllegalArgumentException if a 2D matrix type is requested for
* a 3D transform
* @throws NullPointerException if the specified {@code type} is null
* @since JavaFX 8.0
*/
public double[]
toArray(
MatrixType type) {
return
toArray(
type, null);
}
/**
* Returns an array containing a row of the transformation matrix.
* If the row of the requested matrix type fits in the specified array,
* it is returned therein. Otherwise, a new array is created.
* @param type matrix type whose row is to be filled in the array
* @param row zero-based index of the row
* @param array array into which the elements of the row are to be
* stored, if it is non-null and big enough; otherwise,
* a new array is created for this purpose.
* @return an array containing the requested row of the requested matrix
* type representing this transform
* @throws IllegalArgumentException if a 2D matrix type is requested for
* a 3D transform
* @throws IndexOutOfBoundsException if the {@code row} index is not within
* the number of rows of the specified matrix type
* @throws NullPointerException if the specified {@code type} is null
* @since JavaFX 8.0
*/
public double[]
row(
MatrixType type, int
row, double[]
array) {
checkRequestedMAT(
type);
if (
row < 0 ||
row >=
type.
rows()) {
throw new
IndexOutOfBoundsException(
"Cannot get row " +
row + " from " +
type);
}
if (
array == null ||
array.length <
type.
columns()) {
array = new double[
type.
columns()];
}
switch(
type) {
case
MT_2D_2x3:
case
MT_2D_3x3:
switch (
row) {
case 0:
array[0] =
getMxx();
array[1] =
getMxy();
array[2] =
getTx();
break;
case 1:
array[0] =
getMyx();
array[1] =
getMyy();
array[2] =
getTy();
break;
case 2:
array[0] = 0.0;
array[1] = 0.0;
array[2] = 1.0;
break;
}
break;
case
MT_3D_3x4:
case
MT_3D_4x4:
switch (
row) {
case 0:
array[0] =
getMxx();
array[1] =
getMxy();
array[2] =
getMxz();
array[3] =
getTx();
break;
case 1:
array[0] =
getMyx();
array[1] =
getMyy();
array[2] =
getMyz();
array[3] =
getTy();
break;
case 2:
array[0] =
getMzx();
array[1] =
getMzy();
array[2] =
getMzz();
array[3] =
getTz();
break;
case 3:
array[0] = 0.0;
array[1] = 0.0;
array[2] = 0.0;
array[3] = 1.0;
break;
}
break;
default:
throw new
InternalError("Unsupported row " +
row + " of " +
type);
}
return
array;
}
/**
* Returns an array containing a row of the transformation matrix.
* @param type matrix type whose row is to be filled in the array
* @param row zero-based index of the row
* @return an array containing the requested row of the requested matrix
* type representing this transform
* @throws IllegalArgumentException if a 2D matrix type is requested for
* a 3D transform
* @throws IndexOutOfBoundsException if the {@code row} index is not within
* the number of rows of the specified matrix type
* @throws NullPointerException if the specified {@code type} is null
* @since JavaFX 8.0
*/
public double[]
row(
MatrixType type, int
row) {
return
row(
type,
row, null);
}
/**
* Returns an array containing a column of the transformation matrix.
* If the column of the requested matrix type fits in the specified array,
* it is returned therein. Otherwise, a new array is created.
* @param type matrix type whose column is to be filled in the array
* @param column zero-based index of the column
* @param array array into which the elements of the column are to be
* stored, if it is non-null and big enough; otherwise,
* a new array is created for this purpose.
* @return an array containing the requested column of the requested matrix
* type representing this transform
* @throws IllegalArgumentException if a 2D matrix type is requested for
* a 3D transform
* @throws IndexOutOfBoundsException if the {@code column} index
* is not within the number of columns of the specified matrix type
* @throws NullPointerException if the specified {@code type} is null
* @since JavaFX 8.0
*/
public double[]
column(
MatrixType type, int
column, double[]
array) {
checkRequestedMAT(
type);
if (
column < 0 ||
column >=
type.
columns()) {
throw new
IndexOutOfBoundsException(
"Cannot get row " +
column + " from " +
type);
}
if (
array == null ||
array.length <
type.
rows()) {
array = new double[
type.
rows()];
}
switch(
type) {
case
MT_2D_2x3:
switch (
column) {
case 0:
array[0] =
getMxx();
array[1] =
getMyx();
break;
case 1:
array[0] =
getMxy();
array[1] =
getMyy();
break;
case 2:
array[0] =
getTx();
array[1] =
getTy();
break;
}
break;
case
MT_2D_3x3:
switch (
column) {
case 0:
array[0] =
getMxx();
array[1] =
getMyx();
array[2] = 0.0;
break;
case 1:
array[0] =
getMxy();
array[1] =
getMyy();
array[2] = 0.0;
break;
case 2:
array[0] =
getTx();
array[1] =
getTy();
array[2] = 1.0;
break;
}
break;
case
MT_3D_3x4:
switch (
column) {
case 0:
array[0] =
getMxx();
array[1] =
getMyx();
array[2] =
getMzx();
break;
case 1:
array[0] =
getMxy();
array[1] =
getMyy();
array[2] =
getMzy();
break;
case 2:
array[0] =
getMxz();
array[1] =
getMyz();
array[2] =
getMzz();
break;
case 3:
array[0] =
getTx();
array[1] =
getTy();
array[2] =
getTz();
break;
}
break;
case
MT_3D_4x4:
switch (
column) {
case 0:
array[0] =
getMxx();
array[1] =
getMyx();
array[2] =
getMzx();
array[3] = 0.0;
break;
case 1:
array[0] =
getMxy();
array[1] =
getMyy();
array[2] =
getMzy();
array[3] = 0.0;
break;
case 2:
array[0] =
getMxz();
array[1] =
getMyz();
array[2] =
getMzz();
array[3] = 0.0;
break;
case 3:
array[0] =
getTx();
array[1] =
getTy();
array[2] =
getTz();
array[3] = 1.0;
break;
}
break;
default:
throw new
InternalError("Unsupported column " +
column + " of "
+
type);
}
return
array;
}
/**
* Returns an array containing a column of the transformation matrix.
* @param type matrix type whose column is to be filled in the array
* @param column zero-based index of the column
* @return an array containing the requested column of the requested matrix
* type representing this transform
* @throws IllegalArgumentException if a 2D matrix type is requested for
* a 3D transform
* @throws IndexOutOfBoundsException if the {@code column} index
* is not within the number of columns of the specified matrix type
* @throws NullPointerException if the specified {@code type} is null
* @since JavaFX 8.0
*/
public double[]
column(
MatrixType type, int
column) {
return
column(
type,
column, null);
}
/* *************************************************************************
* *
* Transform creators *
* *
**************************************************************************/
/**
* Returns the concatenation of this transform and the specified transform.
* Applying the resulting transform to a node has the same effect as
* adding the two transforms to its {@code getTransforms()} list,
* {@code this} transform first and the specified {@code transform} second.
* @param transform transform to be concatenated with this transform
* @return The concatenated transform
* @throws NullPointerException if the specified {@code transform} is null
* @since JavaFX 8.0
*/
public
Transform createConcatenation(
Transform transform) {
final double
txx =
transform.
getMxx();
final double
txy =
transform.
getMxy();
final double
txz =
transform.
getMxz();
final double
ttx =
transform.
getTx();
final double
tyx =
transform.
getMyx();
final double
tyy =
transform.
getMyy();
final double
tyz =
transform.
getMyz();
final double
tty =
transform.
getTy();
final double
tzx =
transform.
getMzx();
final double
tzy =
transform.
getMzy();
final double
tzz =
transform.
getMzz();
final double
ttz =
transform.
getTz();
return new
Affine(
(
getMxx() *
txx +
getMxy() *
tyx +
getMxz() *
tzx),
(
getMxx() *
txy +
getMxy() *
tyy +
getMxz() *
tzy),
(
getMxx() *
txz +
getMxy() *
tyz +
getMxz() *
tzz),
(
getMxx() *
ttx +
getMxy() *
tty +
getMxz() *
ttz +
getTx()),
(
getMyx() *
txx +
getMyy() *
tyx +
getMyz() *
tzx),
(
getMyx() *
txy +
getMyy() *
tyy +
getMyz() *
tzy),
(
getMyx() *
txz +
getMyy() *
tyz +
getMyz() *
tzz),
(
getMyx() *
ttx +
getMyy() *
tty +
getMyz() *
ttz +
getTy()),
(
getMzx() *
txx +
getMzy() *
tyx +
getMzz() *
tzx),
(
getMzx() *
txy +
getMzy() *
tyy +
getMzz() *
tzy),
(
getMzx() *
txz +
getMzy() *
tyz +
getMzz() *
tzz),
(
getMzx() *
ttx +
getMzy() *
tty +
getMzz() *
ttz +
getTz()));
}
/**
* Returns the inverse transform of this transform.
* @return the inverse transform
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @since JavaFX 8.0
*/
public
Transform createInverse() throws
NonInvertibleTransformException {
return
getInverseCache().
clone();
}
/**
* Returns a deep copy of this transform.
* @return a copy of this transform
* @since JavaFX 8.0
*/
@
Override
public
Transform clone() {
return
TransformUtils.
immutableTransform(this);
}
/* *************************************************************************
* *
* Transform, Inverse Transform *
* *
**************************************************************************/
/**
* Transforms the specified point by this transform.
* This method can be used only for 2D transforms.
* @param x the X coordinate of the point
* @param y the Y coordinate of the point
* @return the transformed point
* @throws IllegalStateException if this is a 3D transform
* @since JavaFX 8.0
*/
public
Point2D transform(double
x, double
y) {
ensureCanTransform2DPoint();
return new
Point2D(
getMxx() *
x +
getMxy() *
y +
getTx(),
getMyx() *
x +
getMyy() *
y +
getTy());
}
/**
* Transforms the specified point by this transform.
* This method can be used only for 2D transforms.
* @param point the point to be transformed
* @return the transformed point
* @throws IllegalStateException if this is a 3D transform
* @throws NullPointerException if the specified {@code point} is null
* @since JavaFX 8.0
*/
public
Point2D transform(
Point2D point) {
return
transform(
point.
getX(),
point.
getY());
}
/**
* Transforms the specified point by this transform.
* @param x the X coordinate of the point
* @param y the Y coordinate of the point
* @param z the Z coordinate of the point
* @return the transformed point
* @since JavaFX 8.0
*/
public
Point3D transform(double
x, double
y, double
z) {
return new
Point3D(
getMxx() *
x +
getMxy() *
y +
getMxz() *
z +
getTx(),
getMyx() *
x +
getMyy() *
y +
getMyz() *
z +
getTy(),
getMzx() *
x +
getMzy() *
y +
getMzz() *
z +
getTz());
}
/**
* Transforms the specified point by this transform.
* @param point the point to be transformed
* @return the transformed point
* @throws NullPointerException if the specified {@code point} is null
* @since JavaFX 8.0
*/
public
Point3D transform(
Point3D point) {
return
transform(
point.
getX(),
point.
getY(),
point.
getZ());
}
/**
* Transforms the specified bounds by this transform.
* @param bounds the bounds to be transformed
* @return the transformed bounds
* @since JavaFX 8.0
*/
public
Bounds transform(
Bounds bounds) {
if (
isType2D() && (
bounds.
getMinZ() == 0) && (
bounds.
getMaxZ() == 0)) {
Point2D p1 =
transform(
bounds.
getMinX(),
bounds.
getMinY());
Point2D p2 =
transform(
bounds.
getMaxX(),
bounds.
getMinY());
Point2D p3 =
transform(
bounds.
getMaxX(),
bounds.
getMaxY());
Point2D p4 =
transform(
bounds.
getMinX(),
bounds.
getMaxY());
return
BoundsUtils.
createBoundingBox(
p1,
p2,
p3,
p4);
}
Point3D p1 =
transform(
bounds.
getMinX(),
bounds.
getMinY(),
bounds.
getMinZ());
Point3D p2 =
transform(
bounds.
getMinX(),
bounds.
getMinY(),
bounds.
getMaxZ());
Point3D p3 =
transform(
bounds.
getMinX(),
bounds.
getMaxY(),
bounds.
getMinZ());
Point3D p4 =
transform(
bounds.
getMinX(),
bounds.
getMaxY(),
bounds.
getMaxZ());
Point3D p5 =
transform(
bounds.
getMaxX(),
bounds.
getMaxY(),
bounds.
getMinZ());
Point3D p6 =
transform(
bounds.
getMaxX(),
bounds.
getMaxY(),
bounds.
getMaxZ());
Point3D p7 =
transform(
bounds.
getMaxX(),
bounds.
getMinY(),
bounds.
getMinZ());
Point3D p8 =
transform(
bounds.
getMaxX(),
bounds.
getMinY(),
bounds.
getMaxZ());
return
BoundsUtils.
createBoundingBox(
p1,
p2,
p3,
p4,
p5,
p6,
p7,
p8);
}
/**
* Core of the transform2DPoints method.
* All the checks has been performed and the care of the overlaps has been
* taken by the enclosing method, this method only transforms the points
* and fills them to the array. Used by the subclasses to perform
* the transform efficiently.
*/
void
transform2DPointsImpl(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff, int
numPts) {
final double
xx =
getMxx();
final double
xy =
getMxy();
final double
tx =
getTx();
final double
yx =
getMyx();
final double
yy =
getMyy();
final double
ty =
getTy();
while (--
numPts >= 0) {
final double
x =
srcPts[
srcOff++];
final double
y =
srcPts[
srcOff++];
dstPts[
dstOff++] =
xx *
x +
xy *
y +
tx;
dstPts[
dstOff++] =
yx *
x +
yy *
y +
ty;
}
}
/**
* Core of the transform3DPoints method.
* All the checks has been performed and the care of the overlaps has been
* taken by the enclosing method, this method only transforms the points
* and fills them to the array. Used by the subclasses to perform
* the transform efficiently.
*/
void
transform3DPointsImpl(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff, int
numPts) {
final double
xx =
getMxx();
final double
xy =
getMxy();
final double
xz =
getMxz();
final double
tx =
getTx();
final double
yx =
getMyx();
final double
yy =
getMyy();
final double
yz =
getMyz();
final double
ty =
getTy();
final double
zx =
getMzx();
final double
zy =
getMzy();
final double
zz =
getMzz();
final double
tz =
getTz();
while (--
numPts >= 0) {
final double
x =
srcPts[
srcOff++];
final double
y =
srcPts[
srcOff++];
final double
z =
srcPts[
srcOff++];
dstPts[
dstOff++] =
xx *
x +
xy *
y +
xz *
z +
tx;
dstPts[
dstOff++] =
yx *
x +
yy *
y +
yz *
z +
ty;
dstPts[
dstOff++] =
zx *
x +
zy *
y +
zz *
z +
tz;
}
}
/**
* Transforms an array of coordinates by this transform.
* The two coordinate array sections can be exactly the same or
* can be overlapping sections of the same array without affecting the
* validity of the results.
* This method ensures that no source coordinates are overwritten by a
* previous operation before they can be transformed.
* The coordinates are stored in the arrays starting at the specified
* offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>.
* This method can be used only for 2D transforms.
* @param srcPts the array containing the source point coordinates.
* Each point is stored as a pair of x, y coordinates.
* @param srcOff the offset to the first point to be transformed
* in the source array
* @param dstPts the array into which the transformed point coordinates
* are returned. Each point is stored as a pair of x, y
* coordinates.
* @param dstOff the offset to the location of the first
* transformed point that is stored in the destination array
* @param numPts the number of points to be transformed
* @throws IllegalStateException if this is a 3D transform
* @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
* @since JavaFX 8.0
*/
public void
transform2DPoints(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff,
int
numPts) {
if (
srcPts == null ||
dstPts == null) {
throw new
NullPointerException();
}
if (!
isType2D()) {
throw new
IllegalStateException("Cannot transform 2D points "
+ "with a 3D transform");
}
// deal with overlapping arrays
srcOff =
getFixedSrcOffset(
srcPts,
srcOff,
dstPts,
dstOff,
numPts, 2);
// do the transformations
transform2DPointsImpl(
srcPts,
srcOff,
dstPts,
dstOff,
numPts);
}
/**
* Transforms an array of floating point coordinates by this transform.
* The three coordinate array sections can be exactly the same or
* can be overlapping sections of the same array without affecting the
* validity of the results.
* This method ensures that no source coordinates are overwritten by a
* previous operation before they can be transformed.
* The coordinates are stored in the arrays starting at the specified
* offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>.
* @param srcPts the array containing the source point coordinates.
* Each point is stored as a tiplet of x, y, z coordinates.
* @param srcOff the offset to the first point to be transformed
* in the source array
* @param dstPts the array into which the transformed point coordinates
* are returned. Each point is stored as a triplet of x, y, z
* coordinates.
* @param dstOff the offset to the location of the first
* transformed point that is stored in the destination array
* @param numPts the number of points to be transformed
* @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
* @since JavaFX 8.0
*/
public void
transform3DPoints(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff,
int
numPts) {
if (
srcPts == null ||
dstPts == null) {
throw new
NullPointerException();
}
// deal with overlapping arrays
srcOff =
getFixedSrcOffset(
srcPts,
srcOff,
dstPts,
dstOff,
numPts, 3);
// do the transformations
transform3DPointsImpl(
srcPts,
srcOff,
dstPts,
dstOff,
numPts);
}
/**
* Transforms the relative magnitude vector by this transform.
* The vector is transformed without applying the translation components
* of the affine transformation matrix.
* This method can be used only for a 2D transform.
* @param x vector magnitude in the direction of the X axis
* @param y vector magnitude in the direction of the Y axis
* @return the transformed relative magnitude vector represented
* by a {@code Point2D} instance
* @throws IllegalStateException if this is a 3D transform
* @since JavaFX 8.0
*/
public
Point2D deltaTransform(double
x, double
y) {
ensureCanTransform2DPoint();
return new
Point2D(
getMxx() *
x +
getMxy() *
y,
getMyx() *
x +
getMyy() *
y);
}
/**
* Transforms the relative magnitude vector represented by the specified
* {@code Point2D} instance by this transform.
* The vector is transformed without applying the translation components
* of the affine transformation matrix.
* This method can be used only for a 2D transform.
* @param point the relative magnitude vector
* @return the transformed relative magnitude vector represented
* by a {@code Point2D} instance
* @throws IllegalStateException if this is a 3D transform
* @throws NullPointerException if the specified {@code point} is null
* @since JavaFX 8.0
*/
public
Point2D deltaTransform(
Point2D point) {
return
deltaTransform(
point.
getX(),
point.
getY());
}
/**
* Transforms the relative magnitude vector by this transform.
* The vector is transformed without applying the translation components
* of the affine transformation matrix.
* @param x vector magnitude in the direction of the X axis
* @param y vector magnitude in the direction of the Y axis
* @return the transformed relative magnitude vector represented
* by a {@code Point3D} instance
* @since JavaFX 8.0
*/
public
Point3D deltaTransform(double
x, double
y, double
z) {
return new
Point3D(
getMxx() *
x +
getMxy() *
y +
getMxz() *
z,
getMyx() *
x +
getMyy() *
y +
getMyz() *
z,
getMzx() *
x +
getMzy() *
y +
getMzz() *
z);
}
/**
* Transforms the relative magnitude vector represented by the specified
* {@code Point3D} instance by this transform.
* The vector is transformed without applying the translation components
* of the affine transformation matrix.
* @param point the relative magnitude vector
* @return the transformed relative magnitude vector represented
* by a {@code Point3D} instance
* @throws NullPointerException if the specified {@code point} is null
* @since JavaFX 8.0
*/
public
Point3D deltaTransform(
Point3D point) {
return
deltaTransform(
point.
getX(),
point.
getY(),
point.
getZ());
}
/**
* Transforms the specified point by the inverse of this transform.
* This method can be used only for 2D transforms.
* @param x the X coordinate of the point
* @param y the Y coordinate of the point
* @return the inversely transformed point
* @throws IllegalStateException if this is a 3D transform
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @since JavaFX 8.0
*/
public
Point2D inverseTransform(double
x, double
y)
throws
NonInvertibleTransformException {
ensureCanTransform2DPoint();
return
getInverseCache().
transform(
x,
y);
}
/**
* Transforms the specified point by the inverse of this transform.
* This method can be used only for 2D transforms.
* @param point the point to be transformed
* @return the inversely transformed point
* @throws IllegalStateException if this is a 3D transform
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @throws NullPointerException if the specified {@code point} is null
* @since JavaFX 8.0
*/
public
Point2D inverseTransform(
Point2D point)
throws
NonInvertibleTransformException {
return
inverseTransform(
point.
getX(),
point.
getY());
}
/**
* Transforms the specified point by the inverse of this transform.
* @param x the X coordinate of the point
* @param y the Y coordinate of the point
* @param z the Z coordinate of the point
* @return the inversely transformed point
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @since JavaFX 8.0
*/
public
Point3D inverseTransform(double
x, double
y, double
z)
throws
NonInvertibleTransformException {
return
getInverseCache().
transform(
x,
y,
z);
}
/**
* Transforms the specified point by the inverse of this transform.
* @param point the point to be transformed
* @return the inversely transformed point
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @throws NullPointerException if the specified {@code point} is null
* @since JavaFX 8.0
*/
public
Point3D inverseTransform(
Point3D point)
throws
NonInvertibleTransformException {
return
inverseTransform(
point.
getX(),
point.
getY(),
point.
getZ());
}
/**
* Transforms the specified bounds by the inverse of this transform.
* @param bounds the bounds to be transformed
* @return the inversely transformed bounds
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @throws NullPointerException if the specified {@code bounds} is null
* @since JavaFX 8.0
*/
public
Bounds inverseTransform(
Bounds bounds)
throws
NonInvertibleTransformException {
if (
isType2D() && (
bounds.
getMinZ() == 0) && (
bounds.
getMaxZ() == 0)) {
Point2D p1 =
inverseTransform(
bounds.
getMinX(),
bounds.
getMinY());
Point2D p2 =
inverseTransform(
bounds.
getMaxX(),
bounds.
getMinY());
Point2D p3 =
inverseTransform(
bounds.
getMaxX(),
bounds.
getMaxY());
Point2D p4 =
inverseTransform(
bounds.
getMinX(),
bounds.
getMaxY());
return
BoundsUtils.
createBoundingBox(
p1,
p2,
p3,
p4);
}
Point3D p1 =
inverseTransform(
bounds.
getMinX(),
bounds.
getMinY(),
bounds.
getMinZ());
Point3D p2 =
inverseTransform(
bounds.
getMinX(),
bounds.
getMinY(),
bounds.
getMaxZ());
Point3D p3 =
inverseTransform(
bounds.
getMinX(),
bounds.
getMaxY(),
bounds.
getMinZ());
Point3D p4 =
inverseTransform(
bounds.
getMinX(),
bounds.
getMaxY(),
bounds.
getMaxZ());
Point3D p5 =
inverseTransform(
bounds.
getMaxX(),
bounds.
getMaxY(),
bounds.
getMinZ());
Point3D p6 =
inverseTransform(
bounds.
getMaxX(),
bounds.
getMaxY(),
bounds.
getMaxZ());
Point3D p7 =
inverseTransform(
bounds.
getMaxX(),
bounds.
getMinY(),
bounds.
getMinZ());
Point3D p8 =
inverseTransform(
bounds.
getMaxX(),
bounds.
getMinY(),
bounds.
getMaxZ());
return
BoundsUtils.
createBoundingBox(
p1,
p2,
p3,
p4,
p5,
p6,
p7,
p8);
}
/**
* Core of the inverseTransform2DPoints method.
* All the checks has been performed and the care of the overlaps has been
* taken by the enclosing method, this method only transforms the points
* and fills them to the array. Used by the subclasses to perform
* the transform efficiently.
*/
void
inverseTransform2DPointsImpl(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff, int
numPts)
throws
NonInvertibleTransformException {
getInverseCache().
transform2DPointsImpl(
srcPts,
srcOff,
dstPts,
dstOff,
numPts);
}
/**
* Core of the inverseTransform3DPoints method.
* All the checks has been performed and the care of the overlaps has been
* taken by the enclosing method, this method only transforms the points
* and fills them to the array. Used by the subclasses to perform
* the transform efficiently.
*/
void
inverseTransform3DPointsImpl(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff, int
numPts)
throws
NonInvertibleTransformException {
getInverseCache().
transform3DPointsImpl(
srcPts,
srcOff,
dstPts,
dstOff,
numPts);
}
/**
* Transforms an array of coordinates by the inverse of this transform.
* The two coordinate array sections can be exactly the same or
* can be overlapping sections of the same array without affecting the
* validity of the results.
* This method ensures that no source coordinates are overwritten by a
* previous operation before they can be transformed.
* The coordinates are stored in the arrays starting at the specified
* offset in the order <code>[x0, y0, x1, y1, ..., xn, yn]</code>.
* This method can be used only for 2D transforms.
* @param srcPts the array containing the source point coordinates.
* Each point is stored as a pair of x, y coordinates.
* @param srcOff the offset to the first point to be transformed
* in the source array
* @param dstPts the array into which the transformed point coordinates
* are returned. Each point is stored as a pair of x, y
* coordinates.
* @param dstOff the offset to the location of the first
* transformed point that is stored in the destination array
* @param numPts the number of points to be transformed
* @throws IllegalStateException if this is a 3D transform
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
* @since JavaFX 8.0
*/
public void
inverseTransform2DPoints(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff,
int
numPts) throws
NonInvertibleTransformException{
if (
srcPts == null ||
dstPts == null) {
throw new
NullPointerException();
}
if (!
isType2D()) {
throw new
IllegalStateException("Cannot transform 2D points "
+ "with a 3D transform");
}
// deal with overlapping arrays
srcOff =
getFixedSrcOffset(
srcPts,
srcOff,
dstPts,
dstOff,
numPts, 2);
// do the transformations
inverseTransform2DPointsImpl(
srcPts,
srcOff,
dstPts,
dstOff,
numPts);
}
/**
* Transforms an array of floating point coordinates by the inverse
* of this transform.
* The three coordinate array sections can be exactly the same or
* can be overlapping sections of the same array without affecting the
* validity of the results.
* This method ensures that no source coordinates are overwritten by a
* previous operation before they can be transformed.
* The coordinates are stored in the arrays starting at the specified
* offset in the order <code>[x0, y0, z0, x1, y1, z1, ..., xn, yn, zn]</code>.
* @param srcPts the array containing the source point coordinates.
* Each point is stored as a triplet of x, y, z coordinates.
* @param srcOff the offset to the first point to be transformed
* in the source array
* @param dstPts the array into which the transformed point coordinates
* are returned. Each point is stored as a triplet of x, y, z
* coordinates.
* @param dstOff the offset to the location of the first
* transformed point that is stored in the destination array
* @param numPts the number of points to be transformed
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @throws NullPointerException if {@code srcPts} or (@code dstPts} is null
* @since JavaFX 8.0
*/
public void
inverseTransform3DPoints(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff,
int
numPts) throws
NonInvertibleTransformException {
if (
srcPts == null ||
dstPts == null) {
throw new
NullPointerException();
}
// deal with overlapping arrays
srcOff =
getFixedSrcOffset(
srcPts,
srcOff,
dstPts,
dstOff,
numPts, 3);
// do the transformations
inverseTransform3DPointsImpl(
srcPts,
srcOff,
dstPts,
dstOff,
numPts);
}
/**
* Transforms the relative magnitude vector by the inverse of this transform.
* The vector is transformed without applying the translation components
* of the affine transformation matrix.
* This method can be used only for a 2D transform.
* @param x vector magnitude in the direction of the X axis
* @param y vector magnitude in the direction of the Y axis
* @return the inversely transformed relative magnitude vector represented
* by a {@code Point2D} instance
* @throws IllegalStateException if this is a 3D transform
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @since JavaFX 8.0
*/
public
Point2D inverseDeltaTransform(double
x, double
y)
throws
NonInvertibleTransformException {
ensureCanTransform2DPoint();
return
getInverseCache().
deltaTransform(
x,
y);
}
/**
* Transforms the relative magnitude vector represented by the specified
* {@code Point2D} instance by the inverse of this transform.
* The vector is transformed without applying the translation components
* of the affine transformation matrix.
* This method can be used only for a 2D transform.
* @param point the relative magnitude vector
* @return the inversely transformed relative magnitude vector represented
* by a {@code Point2D} instance
* @throws IllegalStateException if this is a 3D transform
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @throws NullPointerException if the specified {@code point} is null
* @since JavaFX 8.0
*/
public
Point2D inverseDeltaTransform(
Point2D point)
throws
NonInvertibleTransformException {
return
inverseDeltaTransform(
point.
getX(),
point.
getY());
}
/**
* Transforms the relative magnitude vector by the inverse of this transform.
* The vector is transformed without applying the translation components
* of the affine transformation matrix.
* @param x vector magnitude in the direction of the X axis
* @param y vector magnitude in the direction of the Y axis
* @return the inversely transformed relative magnitude vector represented
* by a {@code Point3D} instance
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @since JavaFX 8.0
*/
public
Point3D inverseDeltaTransform(double
x, double
y, double
z)
throws
NonInvertibleTransformException {
return
getInverseCache().
deltaTransform(
x,
y,
z);
}
/**
* Transforms the relative magnitude vector represented by the specified
* {@code Point3D} instance by the inverse of this transform.
* The vector is transformed without applying the translation components
* of the affine transformation matrix.
* @param point the relative magnitude vector
* @return the inversely transformed relative magnitude vector represented
* by a {@code Point3D} instance
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
* @throws NullPointerException if the specified {@code point} is null
* @since JavaFX 8.0
*/
public
Point3D inverseDeltaTransform(
Point3D point)
throws
NonInvertibleTransformException {
return
inverseDeltaTransform(
point.
getX(),
point.
getY(),
point.
getZ());
}
/**
* Helper method for transforming arrays of points that deals with
* overlapping arrays.
* @return the (if necessary fixed) srcOff
*/
private int
getFixedSrcOffset(double[]
srcPts, int
srcOff,
double[]
dstPts, int
dstOff,
int
numPts, int
dimensions) {
if (
dstPts ==
srcPts &&
dstOff >
srcOff &&
dstOff <
srcOff +
numPts *
dimensions)
{
// If the arrays overlap partially with the destination higher
// than the source and we transform the coordinates normally
// we would overwrite some of the later source coordinates
// with results of previous transformations.
// To get around this we use arraycopy to copy the points
// to their final destination with correct overwrite
// handling and then transform them in place in the new
// safer location.
System.
arraycopy(
srcPts,
srcOff,
dstPts,
dstOff,
numPts *
dimensions);
return
dstOff;
}
return
srcOff;
}
/* *************************************************************************
* *
* Event Dispatch *
* *
**************************************************************************/
private
EventHandlerManager internalEventDispatcher;
private
EventHandlerManager getInternalEventDispatcher() {
if (
internalEventDispatcher == null) {
internalEventDispatcher = new
EventHandlerManager(this);
}
return
internalEventDispatcher;
}
private
ObjectProperty<
EventHandler<? super
TransformChangedEvent>>
onTransformChanged;
@
Override
public
EventDispatchChain buildEventDispatchChain(
EventDispatchChain tail) {
return
internalEventDispatcher == null
?
tail :
tail.
append(
getInternalEventDispatcher());
}
/**
* <p>
* Registers an event handler to this transform. Any event filters are first
* processed, then the specified onFoo event handlers, and finally any
* event handlers registered by this method.
* </p><p>
* Currently the only event delivered to a {@code Transform} is the
* {@code TransformChangedEvent} with it's single type
* {@code TRANSFORM_CHANGED}.
* </p>
*
* @param <T> the specific event class of the handler
* @param eventType the type of the events to receive by the handler
* @param eventHandler the handler to register
* @throws NullPointerException if the event type or handler is null
* @since JavaFX 8.0
*/
public final <T extends
Event> void
addEventHandler(
final
EventType<T>
eventType,
final
EventHandler<? super T>
eventHandler) {
getInternalEventDispatcher()
.
addEventHandler(
eventType,
eventHandler);
// need to validate all properties to get the change events
validate();
}
/**
* Unregisters a previously registered event handler from this transform.
* One handler might have been registered for different event types, so the
* caller needs to specify the particular event type from which to
* unregister the handler.
*
* @param <T> the specific event class of the handler
* @param eventType the event type from which to unregister
* @param eventHandler the handler to unregister
* @throws NullPointerException if the event type or handler is null
* @since JavaFX 8.0
*/
public final <T extends
Event> void
removeEventHandler(
final
EventType<T>
eventType,
final
EventHandler<? super T>
eventHandler) {
getInternalEventDispatcher()
.
removeEventHandler(
eventType,
eventHandler);
}
/**
* <p>
* Registers an event filter to this transform. Registered event filters get
* an event before any associated event handlers.
* </p><p>
* Currently the only event delivered to a {@code Transform} is the
* {@code TransformChangedEvent} with it's single type
* {@code TRANSFORM_CHANGED}.
* <p>
*
* @param <T> the specific event class of the filter
* @param eventType the type of the events to receive by the filter
* @param eventFilter the filter to register
* @throws NullPointerException if the event type or filter is null
* @since JavaFX 8.0
*/
public final <T extends
Event> void
addEventFilter(
final
EventType<T>
eventType,
final
EventHandler<? super T>
eventFilter) {
getInternalEventDispatcher()
.
addEventFilter(
eventType,
eventFilter);
// need to validate all properties to get the change events
validate();
}
/**
* Unregisters a previously registered event filter from this transform. One
* filter might have been registered for different event types, so the
* caller needs to specify the particular event type from which to
* unregister the filter.
*
* @param <T> the specific event class of the filter
* @param eventType the event type from which to unregister
* @param eventFilter the filter to unregister
* @throws NullPointerException if the event type or filter is null
* @since JavaFX 8.0
*/
public final <T extends
Event> void
removeEventFilter(
final
EventType<T>
eventType,
final
EventHandler<? super T>
eventFilter) {
getInternalEventDispatcher()
.
removeEventFilter(
eventType,
eventFilter);
}
/**
* Sets the onTransformChanged event handler which is called whenever
* the transform changes any of its parameters.
*
* @param value the event handler, can be null to clear it
* @since JavaFX 8.0
*/
public final void
setOnTransformChanged(
EventHandler<? super
TransformChangedEvent>
value) {
onTransformChangedProperty().
set(
value);
// need to validate all properties to get the change events
validate();
}
/**
* Gets the onTransformChanged event handler.
* @return the event handler previously set by {@code setOnTransformChanged}
* method, null if the handler is not set.
* @since JavaFX 8.0
*/
public final
EventHandler<? super
TransformChangedEvent>
getOnTransformChanged() {
return (
onTransformChanged == null) ? null :
onTransformChanged.
get();
}
/**
* The onTransformChanged event handler is called whenever the transform
* changes any of its parameters.
* @since JavaFX 8.0
*/
public final
ObjectProperty<
EventHandler<? super
TransformChangedEvent>>
onTransformChangedProperty() {
if (
onTransformChanged == null) {
onTransformChanged = new
SimpleObjectProperty<
EventHandler
<? super
TransformChangedEvent>>(this, "onTransformChanged") {
@
Override protected void
invalidated() {
getInternalEventDispatcher().
setEventHandler(
TransformChangedEvent.
TRANSFORM_CHANGED,
get());
}
};
}
return
onTransformChanged;
}
/* *************************************************************************
* *
* Internal implementation stuff *
* *
**************************************************************************/
/**
* Makes sure the specified matrix type can be requested from this transform.
* Is used for convenience in various methods that accept
* the MatrixType argument.
* @param type matrix type to check
* @throws IllegalArgumentException if this is a 3D transform and
* a 2D type is requested
*/
void
checkRequestedMAT(
MatrixType type) throws
IllegalArgumentException{
if (
type.
is2D() && !
isType2D()) {
throw new
IllegalArgumentException("Cannot access 2D matrix "
+ "for a 3D transform");
}
}
/**
* Makes sure this is a 2D transform.
* Is used for convenience in various 2D point transformation methods.
* @throws IllegalStateException if this is a 2D transform
*/
void
ensureCanTransform2DPoint() throws
IllegalStateException {
if (!
isType2D()) {
throw new
IllegalStateException("Cannot transform 2D point "
+ "with a 3D transform");
}
}
/**
* Needed for the proper delivery of the TransformChangedEvent.
* If the members are invalid, the transformChanged() notification
* is not called and the event is not delivered. To avoid that
* we need to manually validate all properties. Subclasses validate
* their specific properties.
*/
void
validate() {
getMxx();
getMxy();
getMxz();
getTx();
getMyx();
getMyy();
getMyz();
getTy();
getMzx();
getMzy();
getMzz();
getTz();
}
/**
* @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 abstract void
impl_apply(
Affine3D 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 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);
}
/**
* @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_remove(final
Node node) {
impl_nodes.
remove(
node);
}
/**
* This method must be called by all transforms whenever any of their
* parameters changes. It is typically called when any of the transform's
* properties is invalidated (it is OK to skip the call if an invalid
* property is set).
* @since JavaFX 8.0
*/
protected void
transformChanged() {
inverseCache = null;
final
Iterator iterator =
impl_nodes.
iterator();
while (
iterator.
hasNext()) {
((
Node)
iterator.
next()).
impl_transformsChanged();
}
if (
type2D != null) {
type2D.
invalidate();
}
if (
identity != null) {
identity.
invalidate();
}
if (
internalEventDispatcher != null) {
// need to validate all properties for the event to be fired next time
validate();
Event.
fireEvent(this, new
TransformChangedEvent(this, this));
}
}
/**
* Visitor from {@code Affine} class which provides an efficient
* {@code append} operation for the subclasses.
* @param a {@code Affine} instance to append to
*/
void
appendTo(
Affine a) {
a.
append(
getMxx(),
getMxy(),
getMxz(),
getTx(),
getMyx(),
getMyy(),
getMyz(),
getTy(),
getMzx(),
getMzy(),
getMzz(),
getTz());
}
/**
* Visitor from {@code Affine} class which provides an efficient
* {@code prepend} operation for the subclasses.
* @param a {@code Affine} instance to prepend to
*/
void
prependTo(
Affine a) {
a.
prepend(
getMxx(),
getMxy(),
getMxz(),
getTx(),
getMyx(),
getMyy(),
getMyz(),
getTy(),
getMzx(),
getMzy(),
getMzz(),
getTz());
}
/**
* <p>
* Gets the inverse transform cache.
* </p><p>
* Computing the inverse transform is generally an expensive operation,
* so once it is needed we cache the result (throwing it away when the
* transform changes). The subclasses may avoid using the cache if their
* inverse can be computed quickly on the fly.
* </p><p>
* This method computes the inverse if the cache is not valid.
* </p>
* @return the cached inverse transformation
* @throws NonInvertibleTransformException if this transform
* cannot be inverted
*/
private
Transform getInverseCache() throws
NonInvertibleTransformException {
if (
inverseCache == null ||
inverseCache.
get() == null) {
Affine inv = new
Affine(
getMxx(),
getMxy(),
getMxz(),
getTx(),
getMyx(),
getMyy(),
getMyz(),
getTy(),
getMzx(),
getMzy(),
getMzz(),
getTz());
inv.
invert();
inverseCache = new
SoftReference<
Transform>(
inv);
return
inv;
}
return
inverseCache.
get();
}
/**
* Used only by tests to emulate garbage collecting the soft references
*/
void
clearInverseCache() {
if (
inverseCache != null) {
inverseCache.
clear();
}
}
}