All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.javafx.geom.transform.Affine2D Maven / Gradle / Ivy

There is a newer version: 24-ea+15
Show newest version
/*
 * Copyright (c) 1996, 2022, 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 com.sun.javafx.geom.transform;

import com.sun.javafx.geom.Point2D;

/**
 * The Affine2D class represents a 2D affine transform
 * that performs a linear mapping from 2D coordinates to other 2D
 * coordinates that preserves the "straightness" and
 * "parallelness" of lines.  Affine transformations can be constructed
 * using sequences of translations, scales, flips, rotations, and shears.
 * 

* Such a coordinate transformation can be represented by a 3 row by * 3 column matrix with an implied last row of [ 0 0 1 ]. This matrix * transforms source coordinates {@code (x,y)} into * destination coordinates {@code (x',y')} by considering * them to be a column vector and multiplying the coordinate vector * by the matrix according to the following process: *

 *  [ x']   [  m00  m01  m02  ] [ x ]   [ m00x + m01y + m02 ]
 *  [ y'] = [  m10  m11  m12  ] [ y ] = [ m10x + m11y + m12 ]
 *  [ 1 ]   [   0    0    1   ] [ 1 ]   [         1         ]
 * 
*

*

Handling 90-Degree Rotations

*

* In some variations of the rotate methods in the * Affine2D class, a double-precision argument * specifies the angle of rotation in radians. * These methods have special handling for rotations of approximately * 90 degrees (including multiples such as 180, 270, and 360 degrees), * so that the common case of quadrant rotation is handled more * efficiently. * This special handling can cause angles very close to multiples of * 90 degrees to be treated as if they were exact multiples of * 90 degrees. * For small multiples of 90 degrees the range of angles treated * as a quadrant rotation is approximately 0.00000121 degrees wide. * This section explains why such special care is needed and how * it is implemented. *

* Since 90 degrees is represented as PI/2 in radians, * and since PI is a transcendental (and therefore irrational) number, * it is not possible to exactly represent a multiple of 90 degrees as * an exact double precision value measured in radians. * As a result it is theoretically impossible to describe quadrant * rotations (90, 180, 270 or 360 degrees) using these values. * Double precision floating point values can get very close to * non-zero multiples of PI/2 but never close enough * for the sine or cosine to be exactly 0.0, 1.0 or -1.0. * The implementations of Math.sin() and * Math.cos() correspondingly never return 0.0 * for any case other than Math.sin(0.0). * These same implementations do, however, return exactly 1.0 and * -1.0 for some range of numbers around each multiple of 90 * degrees since the correct answer is so close to 1.0 or -1.0 that * the double precision significand cannot represent the difference * as accurately as it can for numbers that are near 0.0. *

* The net result of these issues is that if the * Math.sin() and Math.cos() methods * are used to directly generate the values for the matrix modifications * during these radian-based rotation operations then the resulting * transform is never strictly classifiable as a quadrant rotation * even for a simple case like rotate(Math.PI/2.0), * due to minor variations in the matrix caused by the non-0.0 values * obtained for the sine and cosine. * If these transforms are not classified as quadrant rotations then * subsequent code which attempts to optimize further operations based * upon the type of the transform will be relegated to its most general * implementation. *

* Because quadrant rotations are fairly common, * this class should handle these cases reasonably quickly, both in * applying the rotations to the transform and in applying the resulting * transform to the coordinates. * To facilitate this optimal handling, the methods which take an angle * of rotation measured in radians attempt to detect angles that are * intended to be quadrant rotations and treat them as such. * These methods therefore treat an angle theta as a quadrant * rotation if either Math.sin(theta) or * Math.cos(theta) returns exactly 1.0 or -1.0. * As a rule of thumb, this property holds true for a range of * approximately 0.0000000211 radians (or 0.00000121 degrees) around * small multiples of Math.PI/2.0. * * @version 1.83, 05/05/07 */ public class Affine2D extends AffineBase { private Affine2D(double mxx, double myx, double mxy, double myy, double mxt, double myt, int state) { this.mxx = mxx; this.myx = myx; this.mxy = mxy; this.myy = myy; this.mxt = mxt; this.myt = myt; this.state = state; this.type = TYPE_UNKNOWN; } /** * Constructs a new Affine2D representing the * Identity transformation. */ public Affine2D() { mxx = myy = 1.0; // m01 = m10 = m02 = m12 = 0.0; /* Not needed. */ // state = APPLY_IDENTITY; /* Not needed. */ // type = TYPE_IDENTITY; /* Not needed. */ } /** * Constructs a new Affine2D that uses the same transform * as the specified BaseTransform object. * @param Tx the BaseTransform object to copy */ public Affine2D(BaseTransform Tx) { setTransform(Tx); } /** * Constructs a new Affine2D from 6 floating point * values representing the 6 specifiable entries of the 3x3 * transformation matrix. * * @param mxx the X coordinate scaling element of the 3x3 matrix * @param myx the Y coordinate shearing element of the 3x3 matrix * @param mxy the X coordinate shearing element of the 3x3 matrix * @param myy the Y coordinate scaling element of the 3x3 matrix * @param mxt the X coordinate translation element of the 3x3 matrix * @param myt the Y coordinate translation element of the 3x3 matrix */ public Affine2D(float mxx, float myx, float mxy, float myy, float mxt, float myt) { this.mxx = mxx; this.myx = myx; this.mxy = mxy; this.myy = myy; this.mxt = mxt; this.myt = myt; updateState2D(); } /** * Constructs a new Affine2D from 6 double * precision values representing the 6 specifiable entries of the 3x3 * transformation matrix. * * @param mxx the X coordinate scaling element of the 3x3 matrix * @param myx the Y coordinate shearing element of the 3x3 matrix * @param mxy the X coordinate shearing element of the 3x3 matrix * @param myy the Y coordinate scaling element of the 3x3 matrix * @param mxt the X coordinate translation element of the 3x3 matrix * @param myt the Y coordinate translation element of the 3x3 matrix */ public Affine2D(double mxx, double myx, double mxy, double myy, double mxt, double myt) { this.mxx = mxx; this.myx = myx; this.mxy = mxy; this.myy = myy; this.mxt = mxt; this.myt = myt; updateState2D(); } @Override public Degree getDegree() { return Degree.AFFINE_2D; } @Override protected void reset3Delements() { /* NOP for Affine2D */ } /** * Concatenates this transform with a transform 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). *

* This operation is equivalent to the following sequence of calls: *

     *     translate(anchorx, anchory);      // S3: final translation
     *     rotate(theta);                    // S2: rotate around anchor
     *     translate(-anchorx, -anchory);    // S1: translate anchor to origin
     * 
* Rotating by a positive angle theta rotates points on the positive * X axis toward the positive Y axis. * Note also the discussion of * Handling 90-Degree Rotations * above. * * @param theta the angle of rotation measured in radians * @param anchorx the X coordinate of the rotation anchor point * @param anchory the Y coordinate of the rotation anchor point */ public void rotate(double theta, double anchorx, double anchory) { // REMIND: Simple for now - optimize later translate(anchorx, anchory); rotate(theta); translate(-anchorx, -anchory); } /** * Concatenates this transform with a transform that rotates * coordinates according to a rotation vector. * All coordinates rotate about the origin by the same amount. * The amount of rotation is such that coordinates along the former * positive X axis will subsequently align with the vector pointing * from the origin to the specified vector coordinates. * If both vecx and vecy are 0.0, * no additional rotation is added to this transform. * This operation is equivalent to calling: *
     *          rotate(Math.atan2(vecy, vecx));
     * 
* * @param vecx the X coordinate of the rotation vector * @param vecy the Y coordinate of the rotation vector */ public void rotate(double vecx, double vecy) { if (vecy == 0.0) { if (vecx < 0.0) { rotate180(); } // If vecx > 0.0 - no rotation // If vecx == 0.0 - undefined rotation - treat as no rotation } else if (vecx == 0.0) { if (vecy > 0.0) { rotate90(); } else { // vecy must be < 0.0 rotate270(); } } else { double len = Math.sqrt(vecx * vecx + vecy * vecy); double sin = vecy / len; double cos = vecx / len; double M0, M1; M0 = mxx; M1 = mxy; mxx = cos * M0 + sin * M1; mxy = -sin * M0 + cos * M1; M0 = myx; M1 = myy; myx = cos * M0 + sin * M1; myy = -sin * M0 + cos * M1; updateState2D(); } } /** * Concatenates this transform with a transform that rotates * coordinates around an anchor point according to a rotation * vector. * All coordinates rotate about the specified anchor coordinates * by the same amount. * The amount of rotation is such that coordinates along the former * positive X axis will subsequently align with the vector pointing * from the origin to the specified vector coordinates. * If both vecx and vecy are 0.0, * the transform is not modified in any way. * This method is equivalent to calling: *
     *     rotate(Math.atan2(vecy, vecx), anchorx, anchory);
     * 
* * @param vecx the X coordinate of the rotation vector * @param vecy the Y coordinate of the rotation vector * @param anchorx the X coordinate of the rotation anchor point * @param anchory the Y coordinate of the rotation anchor point */ public void rotate(double vecx, double vecy, double anchorx, double anchory) { // REMIND: Simple for now - optimize later translate(anchorx, anchory); rotate(vecx, vecy); translate(-anchorx, -anchory); } /** * Concatenates this transform with a transform that rotates * coordinates by the specified number of quadrants. * This is equivalent to calling: *
     *     rotate(numquadrants * Math.PI / 2.0);
     * 
* Rotating by a positive number of quadrants rotates points on * the positive X axis toward the positive Y axis. * @param numquadrants the number of 90 degree arcs to rotate by */ public void quadrantRotate(int numquadrants) { switch (numquadrants & 3) { case 0: break; case 1: rotate90(); break; case 2: rotate180(); break; case 3: rotate270(); break; } } /** * Concatenates this transform with a transform that rotates * coordinates by the specified number of quadrants around * the specified anchor point. * This method is equivalent to calling: *
     *     rotate(numquadrants * Math.PI / 2.0, anchorx, anchory);
     * 
* Rotating by a positive number of quadrants rotates points on * the positive X axis toward the positive Y axis. * * @param numquadrants the number of 90 degree arcs to rotate by * @param anchorx the X coordinate of the rotation anchor point * @param anchory the Y coordinate of the rotation anchor point */ public void quadrantRotate(int numquadrants, double anchorx, double anchory) { switch (numquadrants & 3) { case 0: return; case 1: mxt += anchorx * (mxx - mxy) + anchory * (mxy + mxx); myt += anchorx * (myx - myy) + anchory * (myy + myx); rotate90(); break; case 2: mxt += anchorx * (mxx + mxx) + anchory * (mxy + mxy); myt += anchorx * (myx + myx) + anchory * (myy + myy); rotate180(); break; case 3: mxt += anchorx * (mxx + mxy) + anchory * (mxy - mxx); myt += anchorx * (myx + myy) + anchory * (myy - myx); rotate270(); break; } if (mxt == 0.0 && myt == 0.0) { state &= ~APPLY_TRANSLATE; if (type != TYPE_UNKNOWN) { type &= ~TYPE_TRANSLATION; } } else { state |= APPLY_TRANSLATE; type |= TYPE_TRANSLATION; } } /** * Sets this transform to a translation transformation. * The matrix representing this transform becomes: *
     *      [   1    0    tx  ]
     *      [   0    1    ty  ]
     *      [   0    0    1   ]
     * 
* @param tx the distance by which coordinates are translated in the * X axis direction * @param ty the distance by which coordinates are translated in the * Y axis direction */ public void setToTranslation(double tx, double ty) { mxx = 1.0; myx = 0.0; mxy = 0.0; myy = 1.0; mxt = tx; myt = ty; if (tx != 0.0 || ty != 0.0) { state = APPLY_TRANSLATE; type = TYPE_TRANSLATION; } else { state = APPLY_IDENTITY; type = TYPE_IDENTITY; } } /** * Sets this transform to a rotation transformation. * The matrix representing this transform becomes: *
     *      [   cos(theta)    -sin(theta)    0   ]
     *      [   sin(theta)     cos(theta)    0   ]
     *      [       0              0         1   ]
     * 
* Rotating by a positive angle theta rotates points on the positive * X axis toward the positive Y axis. * Note also the discussion of * Handling 90-Degree Rotations * above. * @param theta the angle of rotation measured in radians */ public void setToRotation(double theta) { double sin = Math.sin(theta); double cos; if (sin == 1.0 || sin == -1.0) { cos = 0.0; state = APPLY_SHEAR; type = TYPE_QUADRANT_ROTATION; } else { cos = Math.cos(theta); if (cos == -1.0) { sin = 0.0; state = APPLY_SCALE; type = TYPE_QUADRANT_ROTATION; } else if (cos == 1.0) { sin = 0.0; state = APPLY_IDENTITY; type = TYPE_IDENTITY; } else { state = APPLY_SHEAR | APPLY_SCALE; type = TYPE_GENERAL_ROTATION; } } mxx = cos; myx = sin; mxy = -sin; myy = cos; mxt = 0.0; myt = 0.0; } /** * Sets this transform to a translated rotation transformation. * 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). *

* This operation is equivalent to the following sequence of calls: *

     *     setToTranslation(anchorx, anchory); // S3: final translation
     *     rotate(theta);                      // S2: rotate around anchor
     *     translate(-anchorx, -anchory);      // S1: translate anchor to origin
     * 
* The matrix representing this transform becomes: *
     *      [   cos(theta)    -sin(theta)    x-x*cos+y*sin  ]
     *      [   sin(theta)     cos(theta)    y-x*sin-y*cos  ]
     *      [       0              0               1        ]
     * 
* Rotating by a positive angle theta rotates points on the positive * X axis toward the positive Y axis. * Note also the discussion of * Handling 90-Degree Rotations * above. * * @param theta the angle of rotation measured in radians * @param anchorx the X coordinate of the rotation anchor point * @param anchory the Y coordinate of the rotation anchor point */ public void setToRotation(double theta, double anchorx, double anchory) { setToRotation(theta); double sin = myx; double oneMinusCos = 1.0 - mxx; mxt = anchorx * oneMinusCos + anchory * sin; myt = anchory * oneMinusCos - anchorx * sin; if (mxt != 0.0 || myt != 0.0) { state |= APPLY_TRANSLATE; type |= TYPE_TRANSLATION; } } /** * Sets this transform to a rotation transformation that rotates * coordinates according to a rotation vector. * All coordinates rotate about the origin by the same amount. * The amount of rotation is such that coordinates along the former * positive X axis will subsequently align with the vector pointing * from the origin to the specified vector coordinates. * If both vecx and vecy are 0.0, * the transform is set to an identity transform. * This operation is equivalent to calling: *
     *     setToRotation(Math.atan2(vecy, vecx));
     * 
* * @param vecx the X coordinate of the rotation vector * @param vecy the Y coordinate of the rotation vector */ public void setToRotation(double vecx, double vecy) { double sin, cos; if (vecy == 0) { sin = 0.0; if (vecx < 0.0) { cos = -1.0; state = APPLY_SCALE; type = TYPE_QUADRANT_ROTATION; } else { cos = 1.0; state = APPLY_IDENTITY; type = TYPE_IDENTITY; } } else if (vecx == 0) { cos = 0.0; sin = (vecy > 0.0) ? 1.0 : -1.0; state = APPLY_SHEAR; type = TYPE_QUADRANT_ROTATION; } else { double len = Math.sqrt(vecx * vecx + vecy * vecy); cos = vecx / len; sin = vecy / len; state = APPLY_SHEAR | APPLY_SCALE; type = TYPE_GENERAL_ROTATION; } mxx = cos; myx = sin; mxy = -sin; myy = cos; mxt = 0.0; myt = 0.0; } /** * Sets this transform to a rotation transformation that rotates * coordinates around an anchor point according to a rotation * vector. * All coordinates rotate about the specified anchor coordinates * by the same amount. * The amount of rotation is such that coordinates along the former * positive X axis will subsequently align with the vector pointing * from the origin to the specified vector coordinates. * If both vecx and vecy are 0.0, * the transform is set to an identity transform. * This operation is equivalent to calling: *
     *     setToTranslation(Math.atan2(vecy, vecx), anchorx, anchory);
     * 
* * @param vecx the X coordinate of the rotation vector * @param vecy the Y coordinate of the rotation vector * @param anchorx the X coordinate of the rotation anchor point * @param anchory the Y coordinate of the rotation anchor point */ public void setToRotation(double vecx, double vecy, double anchorx, double anchory) { setToRotation(vecx, vecy); double sin = myx; double oneMinusCos = 1.0 - mxx; mxt = anchorx * oneMinusCos + anchory * sin; myt = anchory * oneMinusCos - anchorx * sin; if (mxt != 0.0 || myt != 0.0) { state |= APPLY_TRANSLATE; type |= TYPE_TRANSLATION; } } /** * Sets this transform to a rotation transformation that rotates * coordinates by the specified number of quadrants. * This operation is equivalent to calling: *
     *     setToRotation(numquadrants * Math.PI / 2.0);
     * 
* Rotating by a positive number of quadrants rotates points on * the positive X axis toward the positive Y axis. * @param numquadrants the number of 90 degree arcs to rotate by */ public void setToQuadrantRotation(int numquadrants) { switch (numquadrants & 3) { case 0: mxx = 1.0; myx = 0.0; mxy = 0.0; myy = 1.0; mxt = 0.0; myt = 0.0; state = APPLY_IDENTITY; type = TYPE_IDENTITY; break; case 1: mxx = 0.0; myx = 1.0; mxy = -1.0; myy = 0.0; mxt = 0.0; myt = 0.0; state = APPLY_SHEAR; type = TYPE_QUADRANT_ROTATION; break; case 2: mxx = -1.0; myx = 0.0; mxy = 0.0; myy = -1.0; mxt = 0.0; myt = 0.0; state = APPLY_SCALE; type = TYPE_QUADRANT_ROTATION; break; case 3: mxx = 0.0; myx = -1.0; mxy = 1.0; myy = 0.0; mxt = 0.0; myt = 0.0; state = APPLY_SHEAR; type = TYPE_QUADRANT_ROTATION; break; } } /** * Sets this transform to a translated rotation transformation * that rotates coordinates by the specified number of quadrants * around the specified anchor point. * This operation is equivalent to calling: *
     *     setToRotation(numquadrants * Math.PI / 2.0, anchorx, anchory);
     * 
* Rotating by a positive number of quadrants rotates points on * the positive X axis toward the positive Y axis. * * @param numquadrants the number of 90 degree arcs to rotate by * @param anchorx the X coordinate of the rotation anchor point * @param anchory the Y coordinate of the rotation anchor point */ public void setToQuadrantRotation(int numquadrants, double anchorx, double anchory) { switch (numquadrants & 3) { case 0: mxx = 1.0; myx = 0.0; mxy = 0.0; myy = 1.0; mxt = 0.0; myt = 0.0; state = APPLY_IDENTITY; type = TYPE_IDENTITY; break; case 1: mxx = 0.0; myx = 1.0; mxy = -1.0; myy = 0.0; mxt = anchorx + anchory; myt = anchory - anchorx; if (mxt == 0.0 && myt == 0.0) { state = APPLY_SHEAR; type = TYPE_QUADRANT_ROTATION; } else { state = APPLY_SHEAR | APPLY_TRANSLATE; type = TYPE_QUADRANT_ROTATION | TYPE_TRANSLATION; } break; case 2: mxx = -1.0; myx = 0.0; mxy = 0.0; myy = -1.0; mxt = anchorx + anchorx; myt = anchory + anchory; if (mxt == 0.0 && myt == 0.0) { state = APPLY_SCALE; type = TYPE_QUADRANT_ROTATION; } else { state = APPLY_SCALE | APPLY_TRANSLATE; type = TYPE_QUADRANT_ROTATION | TYPE_TRANSLATION; } break; case 3: mxx = 0.0; myx = -1.0; mxy = 1.0; myy = 0.0; mxt = anchorx - anchory; myt = anchory + anchorx; if (mxt == 0.0 && myt == 0.0) { state = APPLY_SHEAR; type = TYPE_QUADRANT_ROTATION; } else { state = APPLY_SHEAR | APPLY_TRANSLATE; type = TYPE_QUADRANT_ROTATION | TYPE_TRANSLATION; } break; } } /** * Sets this transform to a scaling transformation. * The matrix representing this transform becomes: *
     *      [   sx   0    0   ]
     *      [   0    sy   0   ]
     *      [   0    0    1   ]
     * 
* @param sx the factor by which coordinates are scaled along the * X axis direction * @param sy the factor by which coordinates are scaled along the * Y axis direction */ public void setToScale(double sx, double sy) { mxx = sx; myx = 0.0; mxy = 0.0; myy = sy; mxt = 0.0; myt = 0.0; if (sx != 1.0 || sy != 1.0) { state = APPLY_SCALE; type = TYPE_UNKNOWN; } else { state = APPLY_IDENTITY; type = TYPE_IDENTITY; } } /** * Sets this transform to a copy of the transform in the specified * BaseTransform object. * @param Tx the BaseTransform object from which to * copy the transform */ @Override public void setTransform(BaseTransform Tx) { switch (Tx.getDegree()) { case IDENTITY: setToIdentity(); break; case TRANSLATE_2D: setToTranslation(Tx.getMxt(), Tx.getMyt()); break; default: if (Tx.getType() > TYPE_AFFINE2D_MASK) { System.out.println(Tx+" is "+Tx.getType()); System.out.print(" "+Tx.getMxx()); System.out.print(", "+Tx.getMxy()); System.out.print(", "+Tx.getMxz()); System.out.print(", "+Tx.getMxt()); System.out.println(); System.out.print(" "+Tx.getMyx()); System.out.print(", "+Tx.getMyy()); System.out.print(", "+Tx.getMyz()); System.out.print(", "+Tx.getMyt()); System.out.println(); System.out.print(" "+Tx.getMzx()); System.out.print(", "+Tx.getMzy()); System.out.print(", "+Tx.getMzz()); System.out.print(", "+Tx.getMzt()); System.out.println(); // TODO: Should this be thrown before we modify anything? // (RT-26801) degreeError(Degree.AFFINE_2D); } /* No Break */ case AFFINE_2D: this.mxx = Tx.getMxx(); this.myx = Tx.getMyx(); this.mxy = Tx.getMxy(); this.myy = Tx.getMyy(); this.mxt = Tx.getMxt(); this.myt = Tx.getMyt(); if (Tx instanceof AffineBase) { this.state = ((AffineBase) Tx).state; this.type = ((AffineBase) Tx).type; } else { updateState2D(); } break; } } /** * Concatenates a BaseTransform Tx to * this Affine2D Cx * in a less commonly used way such that Tx modifies the * coordinate transformation relative to the absolute pixel * space rather than relative to the existing user space. * Cx is updated to perform the combined transformation. * Transforming a point p by the updated transform Cx' is * equivalent to first transforming p by the original transform * Cx and then transforming the result by * Tx like this: * Cx'(p) = Tx(Cx(p)) * In matrix notation, if this transform Cx * is represented by the matrix [this] and Tx is * represented by the matrix [Tx] then this method does the * following: *
     *      [this] = [Tx] x [this]
     * 
* @param Tx the BaseTransform object to be * concatenated with this Affine2D object. * @see #concatenate */ public void preConcatenate(BaseTransform Tx) { switch (Tx.getDegree()) { case IDENTITY: return; case TRANSLATE_2D: translate(Tx.getMxt(), Tx.getMyt()); return; case AFFINE_2D: break; default: degreeError(Degree.AFFINE_2D); } double M0, M1; double Txx, Txy, Tyx, Tyy; double Txt, Tyt; int mystate = state; Affine2D at = (Affine2D) Tx; int txstate = at.state; switch ((txstate << HI_SHIFT) | mystate) { case (HI_IDENTITY | APPLY_IDENTITY): case (HI_IDENTITY | APPLY_TRANSLATE): case (HI_IDENTITY | APPLY_SCALE): case (HI_IDENTITY | APPLY_SCALE | APPLY_TRANSLATE): case (HI_IDENTITY | APPLY_SHEAR): case (HI_IDENTITY | APPLY_SHEAR | APPLY_TRANSLATE): case (HI_IDENTITY | APPLY_SHEAR | APPLY_SCALE): case (HI_IDENTITY | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): // Tx is IDENTITY... return; case (HI_TRANSLATE | APPLY_IDENTITY): case (HI_TRANSLATE | APPLY_SCALE): case (HI_TRANSLATE | APPLY_SHEAR): case (HI_TRANSLATE | APPLY_SHEAR | APPLY_SCALE): // Tx is TRANSLATE, this has no TRANSLATE mxt = at.mxt; myt = at.myt; state = mystate | APPLY_TRANSLATE; type |= TYPE_TRANSLATION; return; case (HI_TRANSLATE | APPLY_TRANSLATE): case (HI_TRANSLATE | APPLY_SCALE | APPLY_TRANSLATE): case (HI_TRANSLATE | APPLY_SHEAR | APPLY_TRANSLATE): case (HI_TRANSLATE | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): // Tx is TRANSLATE, this has one too mxt = mxt + at.mxt; myt = myt + at.myt; return; case (HI_SCALE | APPLY_TRANSLATE): case (HI_SCALE | APPLY_IDENTITY): // Only these two existing states need a new state state = mystate | APPLY_SCALE; /* NOBREAK */ case (HI_SCALE | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): case (HI_SCALE | APPLY_SHEAR | APPLY_SCALE): case (HI_SCALE | APPLY_SHEAR | APPLY_TRANSLATE): case (HI_SCALE | APPLY_SHEAR): case (HI_SCALE | APPLY_SCALE | APPLY_TRANSLATE): case (HI_SCALE | APPLY_SCALE): // Tx is SCALE, this is anything Txx = at.mxx; Tyy = at.myy; if ((mystate & APPLY_SHEAR) != 0) { mxy = mxy * Txx; myx = myx * Tyy; if ((mystate & APPLY_SCALE) != 0) { mxx = mxx * Txx; myy = myy * Tyy; } } else { mxx = mxx * Txx; myy = myy * Tyy; } if ((mystate & APPLY_TRANSLATE) != 0) { mxt = mxt * Txx; myt = myt * Tyy; } type = TYPE_UNKNOWN; return; case (HI_SHEAR | APPLY_SHEAR | APPLY_TRANSLATE): case (HI_SHEAR | APPLY_SHEAR): mystate = mystate | APPLY_SCALE; /* NOBREAK */ case (HI_SHEAR | APPLY_TRANSLATE): case (HI_SHEAR | APPLY_IDENTITY): case (HI_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): case (HI_SHEAR | APPLY_SCALE): state = mystate ^ APPLY_SHEAR; /* NOBREAK */ case (HI_SHEAR | APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): case (HI_SHEAR | APPLY_SHEAR | APPLY_SCALE): // Tx is SHEAR, this is anything Txy = at.mxy; Tyx = at.myx; M0 = mxx; mxx = myx * Txy; myx = M0 * Tyx; M0 = mxy; mxy = myy * Txy; myy = M0 * Tyx; M0 = mxt; mxt = myt * Txy; myt = M0 * Tyx; type = TYPE_UNKNOWN; return; } // If Tx has more than one attribute, it is not worth optimizing // all of those cases... Txx = at.mxx; Txy = at.mxy; Txt = at.mxt; Tyx = at.myx; Tyy = at.myy; Tyt = at.myt; switch (mystate) { default: stateError(); /* NOTREACHED */ case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): M0 = mxt; M1 = myt; Txt += M0 * Txx + M1 * Txy; Tyt += M0 * Tyx + M1 * Tyy; /* NOBREAK */ case (APPLY_SHEAR | APPLY_SCALE): mxt = Txt; myt = Tyt; M0 = mxx; M1 = myx; mxx = M0 * Txx + M1 * Txy; myx = M0 * Tyx + M1 * Tyy; M0 = mxy; M1 = myy; mxy = M0 * Txx + M1 * Txy; myy = M0 * Tyx + M1 * Tyy; break; case (APPLY_SHEAR | APPLY_TRANSLATE): M0 = mxt; M1 = myt; Txt += M0 * Txx + M1 * Txy; Tyt += M0 * Tyx + M1 * Tyy; /* NOBREAK */ case (APPLY_SHEAR): mxt = Txt; myt = Tyt; M0 = myx; mxx = M0 * Txy; myx = M0 * Tyy; M0 = mxy; mxy = M0 * Txx; myy = M0 * Tyx; break; case (APPLY_SCALE | APPLY_TRANSLATE): M0 = mxt; M1 = myt; Txt += M0 * Txx + M1 * Txy; Tyt += M0 * Tyx + M1 * Tyy; /* NOBREAK */ case (APPLY_SCALE): mxt = Txt; myt = Tyt; M0 = mxx; mxx = M0 * Txx; myx = M0 * Tyx; M0 = myy; mxy = M0 * Txy; myy = M0 * Tyy; break; case (APPLY_TRANSLATE): M0 = mxt; M1 = myt; Txt += M0 * Txx + M1 * Txy; Tyt += M0 * Tyx + M1 * Tyy; /* NOBREAK */ case (APPLY_IDENTITY): mxt = Txt; myt = Tyt; mxx = Txx; myx = Tyx; mxy = Txy; myy = Tyy; state = mystate | txstate; type = TYPE_UNKNOWN; return; } updateState2D(); } /** * Returns an Affine2D object representing the * inverse transformation. * The inverse transform Tx' of this transform Tx * maps coordinates transformed by Tx back * to their original coordinates. * In other words, Tx'(Tx(p)) = p = Tx(Tx'(p)). *

* If this transform maps all coordinates onto a point or a line * then it will not have an inverse, since coordinates that do * not lie on the destination point or line will not have an inverse * mapping. * The getDeterminant method can be used to determine if this * transform has no inverse, in which case an exception will be * thrown if the createInverse method is called. * @return a new Affine2D object representing the * inverse transformation. * @see #getDeterminant * @exception NoninvertibleTransformException * if the matrix cannot be inverted. */ @Override public Affine2D createInverse() throws NoninvertibleTransformException { double det; switch (state) { default: stateError(); /* NOTREACHED */ case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): det = mxx * myy - mxy * myx; if (det == 0 || Math.abs(det) <= Double.MIN_VALUE) { throw new NoninvertibleTransformException("Determinant is "+ det); } return new Affine2D( myy / det, -myx / det, -mxy / det, mxx / det, (mxy * myt - myy * mxt) / det, (myx * mxt - mxx * myt) / det, (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE)); case (APPLY_SHEAR | APPLY_SCALE): det = mxx * myy - mxy * myx; if (det == 0 || Math.abs(det) <= Double.MIN_VALUE) { throw new NoninvertibleTransformException("Determinant is "+ det); } return new Affine2D( myy / det, -myx / det, -mxy / det, mxx / det, 0.0, 0.0, (APPLY_SHEAR | APPLY_SCALE)); case (APPLY_SHEAR | APPLY_TRANSLATE): if (mxy == 0.0 || myx == 0.0) { throw new NoninvertibleTransformException("Determinant is 0"); } return new Affine2D( 0.0, 1.0 / mxy, 1.0 / myx, 0.0, -myt / myx, -mxt / mxy, (APPLY_SHEAR | APPLY_TRANSLATE)); case (APPLY_SHEAR): if (mxy == 0.0 || myx == 0.0) { throw new NoninvertibleTransformException("Determinant is 0"); } return new Affine2D(0.0, 1.0 / mxy, 1.0 / myx, 0.0, 0.0, 0.0, (APPLY_SHEAR)); case (APPLY_SCALE | APPLY_TRANSLATE): if (mxx == 0.0 || myy == 0.0) { throw new NoninvertibleTransformException("Determinant is 0"); } return new Affine2D( 1.0 / mxx, 0.0, 0.0, 1.0 / myy, -mxt / mxx, -myt / myy, (APPLY_SCALE | APPLY_TRANSLATE)); case (APPLY_SCALE): if (mxx == 0.0 || myy == 0.0) { throw new NoninvertibleTransformException("Determinant is 0"); } return new Affine2D(1.0 / mxx, 0.0, 0.0, 1.0 / myy, 0.0, 0.0, (APPLY_SCALE)); case (APPLY_TRANSLATE): return new Affine2D( 1.0, 0.0, 0.0, 1.0, -mxt, -myt, (APPLY_TRANSLATE)); case (APPLY_IDENTITY): return new Affine2D(); } /* NOTREACHED */ } /** * Transforms an array of point objects by this transform. * If any element of the ptDst array is * null, a new Point2D object is allocated * and stored into that element before storing the results of the * transformation. *

* Note that this method does not take any precautions to * avoid problems caused by storing results into Point2D * objects that will be used as the source for calculations * further down the source array. * This method does guarantee that if a specified Point2D * object is both the source and destination for the same single point * transform operation then the results will not be stored until * the calculations are complete to avoid storing the results on * top of the operands. * If, however, the destination Point2D object for one * operation is the same object as the source Point2D * object for another operation further down the source array then * the original coordinates in that point are overwritten before * they can be converted. * @param ptSrc the array containing the source point objects * @param ptDst the array into which the transform point objects are * returned * @param srcOff the offset to the first point object to be * transformed in the source array * @param dstOff the offset to the location of the first * transformed point object that is stored in the destination array * @param numPts the number of point objects to be transformed */ public void transform(Point2D[] ptSrc, int srcOff, Point2D[] ptDst, int dstOff, int numPts) { int mystate = this.state; while (--numPts >= 0) { // Copy source coords into local variables in case src == dst Point2D src = ptSrc[srcOff++]; double x = src.x; double y = src.y; Point2D dst = ptDst[dstOff++]; if (dst == null) { dst = new Point2D(); ptDst[dstOff - 1] = dst; } switch (mystate) { default: stateError(); /* NOTREACHED */ case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): dst.setLocation((float)(x * mxx + y * mxy + mxt), (float)(x * myx + y * myy + myt)); break; case (APPLY_SHEAR | APPLY_SCALE): dst.setLocation((float)(x * mxx + y * mxy), (float)(x * myx + y * myy)); break; case (APPLY_SHEAR | APPLY_TRANSLATE): dst.setLocation((float)(y * mxy + mxt), (float)(x * myx + myt)); break; case (APPLY_SHEAR): dst.setLocation((float)(y * mxy), (float)(x * myx)); break; case (APPLY_SCALE | APPLY_TRANSLATE): dst.setLocation((float)(x * mxx + mxt), (float)(y * myy + myt)); break; case (APPLY_SCALE): dst.setLocation((float)(x * mxx), (float)(y * myy)); break; case (APPLY_TRANSLATE): dst.setLocation((float)(x + mxt), (float)(y + myt)); break; case (APPLY_IDENTITY): dst.setLocation((float) x, (float) y); break; } } /* NOTREACHED */ } /** * Transforms the relative distance vector specified by * ptSrc and stores the result in ptDst. * A relative distance vector is transformed without applying the * translation components of the affine transformation matrix * using the following equations: *

     *  [  x' ]   [  m00  m01 (m02) ] [  x  ]   [ m00x + m01y ]
     *  [  y' ] = [  m10  m11 (m12) ] [  y  ] = [ m10x + m11y ]
     *  [ (1) ]   [  (0)  (0) ( 1 ) ] [ (1) ]   [     (1)     ]
     * 
* If ptDst is null, a new * Point2D object is allocated and then the result of the * transform is stored in this object. * In either case, ptDst, which contains the * transformed point, is returned for convenience. * If ptSrc and ptDst are the same object, * the input point is correctly overwritten with the transformed * point. * @param ptSrc the distance vector to be delta transformed * @param ptDst the resulting transformed distance vector * @return ptDst, which contains the result of the * transformation. */ public Point2D deltaTransform(Point2D ptSrc, Point2D ptDst) { if (ptDst == null) { ptDst = new Point2D(); } // Copy source coords into local variables in case src == dst double x = ptSrc.x; double y = ptSrc.y; switch (state) { default: stateError(); /* NOTREACHED */ case (APPLY_SHEAR | APPLY_SCALE | APPLY_TRANSLATE): case (APPLY_SHEAR | APPLY_SCALE): ptDst.setLocation((float)(x * mxx + y * mxy), (float)(x * myx + y * myy)); return ptDst; case (APPLY_SHEAR | APPLY_TRANSLATE): case (APPLY_SHEAR): ptDst.setLocation((float)(y * mxy), (float)(x * myx)); return ptDst; case (APPLY_SCALE | APPLY_TRANSLATE): case (APPLY_SCALE): ptDst.setLocation((float)(x * mxx), (float)(y * myy)); return ptDst; case (APPLY_TRANSLATE): case (APPLY_IDENTITY): ptDst.setLocation((float) x, (float) y); return ptDst; } /* NOTREACHED */ } // Round values to sane precision for printing // Note that Math.sin(Math.PI) has an error of about 10^-16 private static double _matround(double matval) { return Math.rint(matval * 1E15) / 1E15; } /** * Returns a String that represents the value of this * {@link Object}. * @return a String representing the value of this * Object. */ @Override public String toString() { return ("Affine2D[[" + _matround(mxx) + ", " + _matround(mxy) + ", " + _matround(mxt) + "], [" + _matround(myx) + ", " + _matround(myy) + ", " + _matround(myt) + "]]"); } @Override public boolean is2D() { return true; } @Override public void restoreTransform(double mxx, double myx, double mxy, double myy, double mxt, double myt) { setTransform(mxx, myx, mxy, myy, mxt, myt); } @Override public void restoreTransform(double mxx, double mxy, double mxz, double mxt, double myx, double myy, double myz, double myt, double mzx, double mzy, double mzz, double mzt) { if ( mxz != 0 || myz != 0 || mzx != 0 || mzy != 0 || mzz != 1 || mzt != 0.0) { degreeError(Degree.AFFINE_2D); } setTransform(mxx, myx, mxy, myy, mxt, myt); } @Override public BaseTransform deriveWithTranslation(double mxt, double myt) { translate(mxt, myt); return this; } @Override public BaseTransform deriveWithTranslation(double mxt, double myt, double mzt) { if (mzt == 0.0) { translate(mxt, myt); return this; } Affine3D a = new Affine3D(this); a.translate(mxt, myt, mzt); return a; } @Override public BaseTransform deriveWithScale(double mxx, double myy, double mzz) { if (mzz == 1.0) { scale(mxx, myy); return this; } Affine3D a = new Affine3D(this); a.scale(mxx, myy, mzz); return a; } @Override public BaseTransform deriveWithRotation(double theta, double axisX, double axisY, double axisZ) { if (theta == 0.0) { return this; } if (almostZero(axisX) && almostZero(axisY)) { if (axisZ > 0) { rotate(theta); } else if (axisZ < 0) { rotate(-theta); } // else rotating about zero vector - NOP return this; } Affine3D a = new Affine3D(this); a.rotate(theta, axisX, axisY, axisZ); return a; } @Override public BaseTransform deriveWithPreTranslation(double mxt, double myt) { this.mxt += mxt; this.myt += myt; if (this.mxt != 0.0 || this.myt != 0.0) { state |= APPLY_TRANSLATE; // if (type != TYPE_UNKNOWN) { type |= TYPE_TRANSLATION; // } } else { state &= ~APPLY_TRANSLATE; if (type != TYPE_UNKNOWN) { type &= ~TYPE_TRANSLATION; } } return this; } @Override public BaseTransform deriveWithConcatenation(double mxx, double myx, double mxy, double myy, double mxt, double myt) { // TODO: Simplify this (RT-26801) BaseTransform tmpTx = getInstance(mxx, myx, mxy, myy, mxt, myt); concatenate(tmpTx); return this; } @Override public BaseTransform deriveWithConcatenation( double mxx, double mxy, double mxz, double mxt, double myx, double myy, double myz, double myt, double mzx, double mzy, double mzz, double mzt) { if ( mxz == 0.0 && myz == 0.0 && mzx == 0.0 && mzy == 0.0 && mzz == 1.0 && mzt == 0.0) { concatenate(mxx, mxy, mxt, myx, myy, myt); return this; } Affine3D t3d = new Affine3D(this); t3d.concatenate(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt); return t3d; } @Override public BaseTransform deriveWithConcatenation(BaseTransform tx) { if (tx.is2D()) { concatenate(tx); return this; } Affine3D t3d = new Affine3D(this); t3d.concatenate(tx); return t3d; } @Override public BaseTransform deriveWithPreConcatenation(BaseTransform tx) { if (tx.is2D()) { preConcatenate(tx); return this; } Affine3D t3d = new Affine3D(this); t3d.preConcatenate(tx); return t3d; } @Override public BaseTransform deriveWithNewTransform(BaseTransform tx) { if (tx.is2D()) { setTransform(tx); return this; } return getInstance(tx); } @Override public BaseTransform copy() { return new Affine2D(this); } private static final long BASE_HASH; static { long bits = 0; bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMzz()); bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMzy()); bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMzx()); bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMyz()); bits = bits * 31 + Double.doubleToLongBits(IDENTITY_TRANSFORM.getMxz()); BASE_HASH = bits; } /** * Returns the hashcode for this transform. The base algorithm for * computing the hashcode is defined by the implementation in * the {@code BaseTransform} class. This implementation is just a * faster way of computing the same value knowing which elements of * the transform matrix are populated. * @return a hash code for this transform. */ @Override public int hashCode() { if (isIdentity()) return 0; long bits = BASE_HASH; bits = bits * 31 + Double.doubleToLongBits(getMyy()); bits = bits * 31 + Double.doubleToLongBits(getMyx()); bits = bits * 31 + Double.doubleToLongBits(getMxy()); bits = bits * 31 + Double.doubleToLongBits(getMxx()); bits = bits * 31 + Double.doubleToLongBits(0.0); // mzt bits = bits * 31 + Double.doubleToLongBits(getMyt()); bits = bits * 31 + Double.doubleToLongBits(getMxt()); return (((int) bits) ^ ((int) (bits >> 32))); } /** * Returns true if this Affine2D * represents the same coordinate transform as the specified * argument. * @param obj the Object to test for equality with this * Affine2D * @return true if obj equals this * Affine2D object; false otherwise. */ @Override public boolean equals(Object obj) { if (obj instanceof BaseTransform) { BaseTransform a = (BaseTransform) obj; return (a.getType() <= TYPE_AFFINE2D_MASK && a.getMxx() == this.mxx && a.getMxy() == this.mxy && a.getMxt() == this.mxt && a.getMyx() == this.myx && a.getMyy() == this.myy && a.getMyt() == this.myt); } return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy