/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.math.geometry.d2; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.math.MathUtil; import org.arakhne.afc.math.matrix.Matrix3d; import org.arakhne.afc.math.matrix.SingularMatrixException; import org.arakhne.afc.vmutil.asserts.AssertMessages; import org.arakhne.afc.vmutil.locale.Locale; /** A 2D transformation. * Is represented internally as a 3x3 floating point matrix. The * mathematical representation is row major, as in traditional * matrix mathematics. * * <p>The transformation matrix is: * <pre><code> * | cos(theta) | -+sin(theta) | Tx | * | -+sin(theta) | cos(theta) | Ty | * | 0 | 0 | 1 | * </code></pre> * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public class Transform2D extends Matrix3d { /** This is the identifity transformation. */ public static final Transform2D IDENTITY = new Transform2D(); private static final long serialVersionUID = -2858647743636794878L; /** * Constructs a new Transform2D object and sets it to the identity transformation. */ public Transform2D() { super(1., 0., 0., 0., 1., 0., 0., 0., 1.); } /** * Constructs a new Transform2D object and initializes it from the * specified transform. * * @param tranform the transformation to copy. */ public Transform2D(Transform2D tranform) { super(tranform); } /** Constructor by copy. * * @param matrix the matrix to copy. */ public Transform2D(Matrix3d matrix) { super(matrix); } /** * Constructs and initializes a Matrix3f from the specified nine values. * * @param m00 * the [0][0] element * @param m01 * the [0][1] element * @param m02 * the [0][2] element * @param m10 * the [1][0] element * @param m11 * the [1][1] element * @param m12 * the [1][2] element */ public Transform2D(double m00, double m01, double m02, double m10, double m11, double m12) { super(m00, m01, m02, m10, m11, m12, 0., 0., 1.); } @Pure @Override public Transform2D clone() { return (Transform2D) super.clone(); } /** Set the position. * * <p>This function changes only the elements of * the matrix related to the translation (m02, * m12). The scaling and the shearing are not changed. * * <p>After a call to this function, the matrix will * contains (? means any value): * <pre> * [ ? ? x ] * [ ? ? y ] * [ ? ? ? ] * </pre> * * @param x x translation. * @param y y translation. * @see #makeTranslationMatrix(double, double) */ public void setTranslation(double x, double y) { this.m02 = x; this.m12 = y; } /** Set the position. * * <p>This function changes only the elements of * the matrix related to the translation (m02, * m12). The scaling and the shearing are not changed. * * <p>After a call to this function, the matrix will * contains (? means any value): * <pre> * [ ? ? t.x ] * [ ? ? t.y ] * [ ? ? ? ] * </pre> * * @param translation the translation. * @see #makeTranslationMatrix(double, double) */ public void setTranslation(Tuple2D<?> translation) { assert translation != null : AssertMessages.notNullParameter(); this.m02 = translation.getX(); this.m12 = translation.getY(); } /** Translate the position. * * <p>This function is equivalent to: * <pre> * this = this * [ 1 0 dx ] * [ 0 1 dy ] * [ 0 0 1 ] * </pre> * * @param dx the x translation * @param dy the y translation */ public void translate(double dx, double dy) { this.m02 = this.m00 * dx + this.m01 * dy + this.m02; this.m12 = this.m10 * dx + this.m11 * dy + this.m12; } /** Translate the position. * * <p>This function is equivalent to: * <pre> * this = this * [ 1 0 t.x ] * [ 0 1 t.y ] * [ 0 0 1 ] * </pre> * * @param translation the translation. */ public void translate(Vector2D<?, ?> translation) { assert translation != null : AssertMessages.notNullParameter(); translate(translation.getX(), translation.getY()); } /** Replies the X translation. * * @return the amount */ @Pure public double getTranslationX() { return this.m02; } /** Replies the Y translation. * * @return the amount */ @Pure public double getTranslationY() { return this.m12; } /** Replies the translation. * * @param translation the vector to set with the translation component. */ @Pure public void getTranslationVector(Tuple2D<?> translation) { assert translation != null : AssertMessages.notNullParameter(); translation.set(this.m02, this.m12); } /** * Rotate the object (theta). * * <p>This function is equivalent to: * <pre> * this = this * [ cos(theta) -sin(theta) 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 0 1 ] * </pre> * * @param theta the rotation angle, in radians. */ public void rotate(double theta) { // Copied from AWT API final double sin = Math.sin(theta); if (sin == 1.) { rotate90(); } else if (sin == -1.) { rotate270(); } else { final double cos = Math.cos(theta); if (cos == -1.) { rotate180(); } else if (cos != 1.) { double m0 = this.m00; double m1 = this.m01; this.m00 = cos * m0 + sin * m1; this.m01 = -sin * m0 + cos * m1; m0 = this.m10; m1 = this.m11; this.m10 = cos * m0 + sin * m1; this.m11 = -sin * m0 + cos * m1; } } } private void rotate90() { // Copied from AWT API double m0 = this.m00; this.m00 = this.m01; this.m01 = -m0; m0 = this.m10; this.m10 = this.m11; this.m11 = -m0; } private void rotate180() { // Copied from AWT API this.m00 = -this.m00; this.m11 = -this.m11; // If there was a shear, then this rotation has no // effect on the state. this.m01 = -this.m01; this.m10 = -this.m10; } private void rotate270() { // Copied from AWT API double m0 = this.m00; this.m00 = -this.m01; this.m01 = m0; m0 = this.m10; this.m10 = -this.m11; this.m11 = m0; } /** * Perform SVD on the 2x2 matrix containing the rotation and scaling factors. * * @param scales the scaling factors. * @param rots the rotation factors. */ @SuppressWarnings("checkstyle:magicnumber") protected void getScaleRotate2x2(double[] scales, double[] rots) { final double[] tmp = new double[9]; tmp[0] = this.m00; tmp[1] = this.m01; tmp[2] = 0; tmp[3] = this.m10; tmp[4] = this.m11; tmp[5] = 0; tmp[6] = 0; tmp[7] = 0; tmp[8] = 0; computeSVD(tmp, scales, rots); } /** * Performs an SVD normalization of this matrix to calculate and return the * rotation angle. * * @return the rotation angle of this matrix. The value is in [-PI; PI] */ @Pure @SuppressWarnings("checkstyle:magicnumber") public double getRotation() { final double[] tmpScale = new double[3]; final double[] tmpRot = new double[9]; getScaleRotate2x2(tmpScale, tmpRot); if (Math.signum(tmpRot[0]) != Math.signum(tmpRot[4])) { // Sinuses are on the top-left to bottom-right diagonal // -s c 0 // c s 0 // 0 0 1 return Math.atan2(tmpRot[4], tmpRot[3]); } // Sinuses are on the top-right to bottom-left diagonal // c -s 0 // s c 0 // 0 0 1 return Math.atan2(tmpRot[3], tmpRot[0]); } /** Change the rotation of this matrix. * Performs an SVD normalization of this matrix for determining and preserving the scaling. * * @param angle the rotation angle. */ @SuppressWarnings("checkstyle:magicnumber") public void setRotation(double angle) { final double[] tmpScale = new double[3]; final double[] tmpRot = new double[9]; getScaleRotate2x2(tmpScale, tmpRot); final double cos = Math.cos(angle); final double sin = Math.sin(angle); // R * S this.m00 = tmpScale[0] * cos; this.m01 = tmpScale[1] * -sin; this.m10 = tmpScale[0] * sin; this.m11 = tmpScale[1] * cos; // S * R // this.m00 = tmp_scale[0] * cos; // this.m01 = tmp_scale[0] * -sin; // this.m10 = tmp_scale[1] * sin; // this.m11 = tmp_scale[1] * cos; } /** Concatenates this transform with a scaling transformation. * * <p>This function is equivalent to: * <pre> * this = this * [ sx 0 0 ] * [ 0 sy 0 ] * [ 0 0 1 ] * </pre> * * @param scaleX scaling along x axis. * @param scaleY scaling along y axis. */ public void scale(double scaleX, double scaleY) { this.m00 *= scaleX; this.m11 *= scaleY; this.m01 *= scaleY; this.m10 *= scaleX; } /** Concatenates this transform with a scaling transformation. * * <p>This function is equivalent to: * <pre> * this = this * [ t.x 0 0 ] * [ 0 t.y 0 ] * [ 0 0 1 ] * </pre> * * @param tuple the scaling factors. */ public void scale(Tuple2D<?> tuple) { assert tuple != null : AssertMessages.notNullParameter(); scale(tuple.getX(), tuple.getY()); } /** Concatenates this transform with a scaling transformation. * * <p>This function is equivalent to: * <pre> * this = this * [ s 0 0 ] * [ 0 s 0 ] * [ 0 0 1 ] * </pre> * * @param scale the scaling factor. */ public void scale(double scale) { this.m00 *= scale; this.m11 *= scale; this.m01 *= scale; this.m10 *= scale; } /** * Performs an SVD normalization of this matrix to calculate and return the * uniform scale factor. If the matrix has non-uniform scale factors, the * largest of the x, y scale factors will be returned. * * @return the scale factor of this matrix. */ @Pure @SuppressWarnings("checkstyle:magicnumber") public double getScale() { final double[] tmpScale = new double[3]; final double[] tmpRot = new double[9]; getScaleRotate2x2(tmpScale, tmpRot); return MathUtil.max(tmpScale); } /** Performs an SVD normalization of this matrix to calculate and return the * scale factor for X axis. * * @return the x scale factor. */ @Pure @SuppressWarnings("checkstyle:magicnumber") public double getScaleX() { final double[] tmpScale = new double[3]; final double[] tmpRot = new double[9]; getScaleRotate2x2(tmpScale, tmpRot); return tmpScale[0]; } /** Performs an SVD normalization of this matrix to calculate and return the * scale factor for Y axis. * * @return the y scale factor. */ @Pure @SuppressWarnings("checkstyle:magicnumber") public double getScaleY() { final double[] tmpScale = new double[3]; final double[] tmpRot = new double[9]; getScaleRotate2x2(tmpScale, tmpRot); return tmpScale[1]; } /** Performs an SVD normalization of this matrix to calculate and return the * scale factors for X and Y axess. * * @param scale the tuple to set. */ @Pure @SuppressWarnings("checkstyle:magicnumber") public void getScaleVector(Tuple2D<?> scale) { assert scale != null : AssertMessages.notNullParameter(); final double[] tmpScale = new double[3]; final double[] tmpRot = new double[9]; getScaleRotate2x2(tmpScale, tmpRot); scale.set(tmpScale[0], tmpScale[1]); } /** Change the scale of this matrix. * Performs an SVD normalization of this matrix for determining and preserving the rotation. * * @param scaleX the scaling factor along x axis. * @param scaleY the scaling factor along y axis. * @see #makeScaleMatrix(double, double) */ @SuppressWarnings("checkstyle:magicnumber") public void setScale(double scaleX, double scaleY) { final double[] tmpScale = new double[3]; final double[] tmpRot = new double[9]; getScaleRotate2x2(tmpScale, tmpRot); this.m00 = tmpRot[0] * scaleX; this.m01 = tmpRot[1] * scaleY; this.m10 = tmpRot[3] * scaleX; this.m11 = tmpRot[4] * scaleY; } /** Set the scale. * * <p>This function changes only the elements of * the matrix related to the scaling (m00, * m11). The shearing and the translation are not changed. * The rotation is lost. * * <p>After a call to this function, the matrix will * contains (? means any value): * <pre> * [ t.x ? ? ] * [ ? t.y ? ] * [ ? ? ? ] * </pre> * * @param tuple the scaling factors. * @see #makeScaleMatrix(double, double) */ public void setScale(Tuple2D<?> tuple) { assert tuple != null : AssertMessages.notNullParameter(); setScale(tuple.getX(), tuple.getY()); } /** Concatenates this transform with a shearing transformation. * * <p>This function is equivalent to: * <pre> * this = this * [ 1 shx 0 ] * [ shy 1 0 ] * [ 0 0 1 ] * </pre> * * @param shearX the shearing factory along x axis. * @param shearY the shearing factory along y axis. */ public void shear(double shearX, double shearY) { double m0 = this.m00; double m1 = this.m01; this.m00 = m0 + m1 * shearY; this.m01 = m0 * shearX + m1; m0 = this.m10; m1 = this.m11; this.m10 = m0 + m1 * shearY; this.m11 = m0 * shearX + m1; } /** Concatenates this transform with a shearing transformation. * * <p>This function is equivalent to: * <pre> * this = this * [ 1 shx 0 ] * [ shy 1 0 ] * [ 0 0 1 ] * </pre> * * @param shear the shear factors. */ public void shear(Tuple2D<?> shear) { assert shear != null : AssertMessages.notNullParameter(); shear(shear.getX(), shear.getY()); } /** * Sets the value of this matrix to a counter clockwise rotation about the x * axis, and no translation * * <p>This function changes all the elements of * the matrix, icluding the translation. * * <p>After a call to this function, the matrix will * contains (? means any value): * <pre> * [ cos(theta) -sin(theta) 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 0 1 ] * </pre> * * @param angle * the angle to rotate about the X axis in radians * @see #setRotation(double) */ public void makeRotationMatrix(double angle) { final double sinAngle = Math.sin(angle); final double cosAngle = Math.cos(angle); this.m00 = cosAngle; this.m01 = -sinAngle; this.m02 = 0.; this.m11 = cosAngle; this.m10 = sinAngle; this.m12 = 0.; this.m20 = 0.; this.m21 = 0.; this.m22 = 1.; } /** * Sets the value of this matrix to the given translation, without rotation. * * <p>This function changes all the elements of * the matrix including the scaling and the shearing. * * <p>After a call to this function, the matrix will * contains (? means any value): * <pre> * [ 1 0 x ] * [ 0 1 y ] * [ 0 0 1 ] * </pre> * * @param dx is the translation along X. * @param dy is the translation along Y. * @see #setTranslation(double, double) * @see #setTranslation(Tuple2D) */ public void makeTranslationMatrix(double dx, double dy) { this.m00 = 1.; this.m01 = 0.; this.m02 = dx; this.m10 = 0.; this.m11 = 1.; this.m12 = dy; this.m20 = 0.; this.m21 = 0.; this.m22 = 1.; } /** * Sets the value of this matrix to the given scaling, without rotation. * * <p>This function changes all the elements of * the matrix, including the shearing and the * translation. * * <p>After a call to this function, the matrix will * contains (? means any value): * <pre> * [ sx 0 0 ] * [ 0 sy 0 ] * [ 0 0 1 ] * </pre> * * @param scaleX is the scaling along X. * @param scaleY is the scaling along Y. * @see #setScale(double, double) * @see #setScale(Tuple2D) */ public void makeScaleMatrix(double scaleX, double scaleY) { this.m00 = scaleX; this.m01 = 0.; this.m02 = 0.; this.m10 = 0.; this.m11 = scaleY; this.m12 = 0.; this.m20 = 0.; this.m21 = 0.; this.m22 = 1.; } /** * Multiply this matrix by the tuple t and place the result back into the * tuple (t = this*t). * * @param tuple * the tuple to be multiplied by this matrix and then replaced */ public void transform(Tuple2D<?> tuple) { assert tuple != null : AssertMessages.notNullParameter(); final double x = this.m00 * tuple.getX() + this.m01 * tuple.getY() + this.m02; final double y = this.m10 * tuple.getX() + this.m11 * tuple.getY() + this.m12; tuple.set(x, y); } /** * Multiply this matrix by the tuple t and and place the result into the * tuple "result". * * <p>This function is equivalent to: * <pre> * result = this * [ t.x ] * [ t.y ] * [ 1 ] * </pre> * * @param tuple * the tuple to be multiplied by this matrix * @param result * the tuple into which the product is placed */ public void transform(Tuple2D<?> tuple, Tuple2D<?> result) { assert tuple != null : AssertMessages.notNullParameter(0); assert result != null : AssertMessages.notNullParameter(1); result.set( this.m00 * tuple.getX() + this.m01 * tuple.getY() + this.m02, this.m10 * tuple.getX() + this.m11 * tuple.getY() + this.m12); } /** * Returns an <code>Transform2D</code> 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)). * * <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 <code>determinant</code> method can be used to determine if this * transform has no inverse, in which case an exception will be * thrown if the <code>createInverse</code> method is called. * @return a new <code>Transform2D</code> object representing the * inverse transformation. * @see #determinant() * @throws SingularMatrixException if the matrix cannot be inverted. */ @Pure public Transform2D createInverse() { final double det = this.m00 * this.m11 - this.m01 * this.m10; if (MathUtil.isEpsilonZero(det)) { throw new SingularMatrixException(Locale.getString("E1", det)); //$NON-NLS-1$ } return new Transform2D( this.m11 / det, -this.m01 / det, (this.m01 * this.m12 - this.m11 * this.m02) / det, -this.m10 / det, this.m00 / det, (this.m10 * this.m02 - this.m00 * this.m12) / det); } /** * Set the components of the transformation. * * @param m00 * the [0][0] element * @param m01 * the [0][1] element * @param m02 * the [0][2] element * @param m10 * the [1][0] element * @param m11 * the [1][1] element * @param m12 * the [1][2] element */ public void set(double m00, double m01, double m02, double m10, double m11, double m12) { set(m00, m01, m02, m10, m11, m12, 0., 0., 1.); } /** * Invert this 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)). * * <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 <code>determinant</code> method can be used to determine if this * transform has no inverse, in which case an exception will be * thrown if the <code>createInverse</code> method is called. * @see #determinant() * @throws SingularMatrixException if the matrix cannot be inverted. */ @Override public void invert() { final double det = this.m00 * this.m11 - this.m01 * this.m10; if (MathUtil.isEpsilonZero(det)) { throw new SingularMatrixException(Locale.getString("E1", det)); //$NON-NLS-1$ } set( this.m11 / det, -this.m01 / det, (this.m01 * this.m12 - this.m11 * this.m02) / det, -this.m10 / det, this.m00 / det, (this.m10 * this.m02 - this.m00 * this.m12) / det); } /** * Invert this 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)). * * <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 <code>determinant</code> method can be used to determine if this * transform has no inverse, in which case an exception will be * thrown if the <code>createInverse</code> method is called. * @param matrix is the matrix to invert * @see #determinant() * @throws SingularMatrixException if the matrix cannot be inverted. */ @Override public void invert(Matrix3d matrix) { assert matrix != null : AssertMessages.notNullParameter(); final double det = matrix.getM00() * matrix.getM11() - matrix.getM01() * matrix.getM10(); if (MathUtil.isEpsilonZero(det)) { throw new SingularMatrixException(Locale.getString("E1", det)); //$NON-NLS-1$ } set( matrix.getM11() / det, -matrix.getM01() / det, (matrix.getM01() * matrix.getM12() - matrix.getM11() * matrix.getM02()) / det, -matrix.getM10() / det, matrix.getM00() / det, (matrix.getM10() * matrix.getM02() - matrix.getM00() * matrix.getM12()) / det); } }