/* File AffineTransform2D.java
*
* Project : Java Geometry Library
*
* ===========================================
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or (at
* your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. if not, write to :
* The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
// package
package math.geom2d;
// Imports
import math.geom2d.Shape2D;
import math.geom2d.Angle2D;
import math.geom2d.Point2D;
import math.geom2d.Vector2D;
import math.geom2d.line.LinearShape2D;
import math.geom2d.transform.Bijection2D;
import math.utils.EqualUtils;
import static java.lang.Math.*;
import static math.geom2d.Shape2D.ACCURACY;
/**
* Base class for generic affine transforms in the plane. They include
* rotations, translations, shears, similarities, and combinations of these.
* Such transformations can be constructed by using coefficients specification,
* or by creating specialized instances, by using static methods.
*
*/
public class AffineTransform2D implements Bijection2D, GeometricObject2D,
Cloneable {
// coefficients for x coordinate.
protected double m00, m01, m02;
// coefficients for y coordinate.
protected double m10, m11, m12;
// ===================================================================
// static methods
/**
* @since 0.8.1
*/
public static AffineTransform2D createIdentity() {
return new AffineTransform2D(1, 0, 0, 0, 1, 0);
}
/**
* Creates a new affine transform by copying coefficients.
*
* @since 0.8.1
*/
public static AffineTransform2D create(AffineTransform2D trans) {
return new AffineTransform2D(
trans.m00, trans.m01, trans.m02,
trans.m10, trans.m11, trans.m12);
}
/**
* Creates an affine transform defined by an array of coefficients.
* The input array must have either 4 or 6 coefficients.
* @since 0.8.1
*/
public static AffineTransform2D create(double[] coefs) {
if (coefs.length == 4) {
return new AffineTransform2D(
coefs[0], coefs[1], 0,
coefs[2], coefs[3], 0);
} else if (coefs.length == 6) {
return new AffineTransform2D(
coefs[0], coefs[1], coefs[2],
coefs[3], coefs[4], coefs[5]);
} else {
throw new IllegalArgumentException("Input array must have either 4 or 6 elements");
}
}
/**
* @since 0.8.1
*/
public static AffineTransform2D create(
double xx, double yx, double tx,
double xy, double yy, double ty) {
return new AffineTransform2D(xx, yx, tx, xy, yy, ty);
}
/**
* Create a glide reflection, composed of a reflection by the given line,
* and a translation in the direction of the line by a distance given by
* second parameter.
*/
public static AffineTransform2D createGlideReflection(LinearShape2D line,
double distance) {
// get origin and vector of line
Vector2D vector = line.direction().normalize();
Point2D origin = line.origin();
// extract origin and vector coordinates
double dx = vector.x();
double dy = vector.y();
double x0 = origin.x();
double y0 = origin.y();
// compute translation parameters
double tx = dx * distance;
double ty = dy * distance;
// some computation shortcuts
double delta = dx * dx + dy * dy;
double dx2 = dx * dx;
double dy2 = dy * dy;
double dxy = dx * dy;
double dxy0 = dx * y0;
double dyx0 = dy * x0;
// create the affine transform with parameters of glide reflection
return new AffineTransform2D(
(dx2 - dy2) / delta,
2 * dxy / delta,
2 * dy * (dyx0 - dxy0) / delta + tx,
2 * dxy / delta,
(dy2 - dx2) / delta,
2 * dx * (dxy0 - dyx0) / delta + ty);
}
/**
* @deprecated replaced by scaling (0.11.1)
*/
@Deprecated
public static AffineTransform2D createHomothecy(Point2D center, double k) {
return createScaling(center, k, k);
}
/**
* Creates a reflection by the given line.
* The resulting transform is indirect.
*/
public static AffineTransform2D createLineReflection(LinearShape2D line) {
// origin and direction of line
Point2D origin = line.origin();
Vector2D vector = line.direction();
// extract direction vector coordinates
double dx = vector.x();
double dy = vector.y();
double x0 = origin.x();
double y0 = origin.y();
// pre-compute some terms
double dx2 = dx * dx;
double dy2 = dy * dy;
double dxy = dx * dy;
double delta = dx2 + dy2;
// creates the new transform
return new AffineTransform2D(
(dx2 - dy2) / delta,
2 * dxy / delta,
2 * (dy2 * x0 - dxy * y0) / delta,
2 * dxy / delta,
(dy2 - dx2) / delta,
2 * (dx2 * y0 - dxy * x0) / delta);
}
/**
* Returns a center reflection around a point. The resulting transform is
* equivalent to a rotation by 180 around this point.
*
* @param center
* the center of the reflection
* @return an instance of AffineTransform2D representing a point reflection
*/
public static AffineTransform2D createPointReflection(Point2D center) {
return AffineTransform2D.createScaling(center, -1, -1);
}
/**
* Creates a rotation composed of the given number of rotations by 90
* degrees around the origin.
*/
public static AffineTransform2D createQuadrantRotation(int numQuadrant) {
int n = ((numQuadrant % 4) + 4) % 4;
switch (n) {
case 0:
return new AffineTransform2D(1, 0, 0, 0, 1, 0);
case 1:
return new AffineTransform2D(0, -1, 0, 1, 0, 0);
case 2:
return new AffineTransform2D(-1, 0, 0, 0, -1, 0);
case 3:
return new AffineTransform2D(0, 1, 0, -1, 0, 0);
default:
throw new RuntimeException("Error in integer rounding...");
}
}
/**
* Creates a rotation composed of the given number of rotations by 90
* degrees around the given point.
*/
public static AffineTransform2D createQuadrantRotation(Point2D center,
int numQuadrant) {
AffineTransform2D trans = createQuadrantRotation(numQuadrant);
trans.recenter(center.x(), center.y());
return trans;
}
/**
* Creates a rotation composed of the given number of rotations by 90
* degrees around the point given by (x0,y0).
*/
public static AffineTransform2D createQuadrantRotation(
double x0, double y0, int numQuadrant) {
AffineTransform2D trans = createQuadrantRotation(numQuadrant);
trans.recenter(x0, y0);
return trans;
}
/**
* Creates a rotation around the origin, with angle in radians.
*/
public static AffineTransform2D createRotation(double angle) {
return AffineTransform2D.createRotation(0, 0, angle);
}
/**
* Creates a rotation around the specified point, with angle in radians.
*/
public static AffineTransform2D createRotation(Point2D center, double angle) {
return AffineTransform2D.createRotation(center.x(), center.y(), angle);
}
/**
* Creates a rotation around the specified point, with angle in radians.
* If the angular distance of the angle with a multiple of PI/2 is lower
* than the threshold Shape2D.ACCURACY, the method assumes equality.
*/
public static AffineTransform2D createRotation(double cx, double cy,
double angle) {
angle = Angle2D.formatAngle(angle);
// special processing to detect angle close to multiple of PI/2.
int k = (int) round(angle * 2 / PI);
if (abs(k * PI / 2 - angle) < ACCURACY) {
return createQuadrantRotation(cx, cy, k);
}
// pre-compute trigonometric functions
double cot = cos(angle);
double sit = sin(angle);
// init coef of the new AffineTransform.
return new AffineTransform2D(
cot, -sit, (1 - cot) * cx + sit * cy,
sit, cot, (1 - cot) * cy - sit * cx);
}
/**
* Creates a scaling by the given coefficients, centered on the origin.
*/
public static AffineTransform2D createScaling(double sx, double sy) {
return AffineTransform2D.createScaling(new Point2D(0, 0), sx, sy);
}
/**
* Creates a scaling by the given coefficients, centered on the point
* given by (x0,y0).
*/
public static AffineTransform2D createScaling(Point2D center, double sx,
double sy) {
return new AffineTransform2D(
sx, 0, (1 - sx) * center.x(),
0, sy, (1 - sy) * center.y());
}
/**
* Creates a Shear transform, using the classical Java notation.
*
* @param shx
* shear in x-axis
* @param shy
* shear in y-axis
* @return a shear transform
*/
public static AffineTransform2D createShear(double shx, double shy) {
return new AffineTransform2D(1, shx, 0, shy, 1, 0);
}
/**
* Creates a new transform from a java AWT transform.
*/
public static AffineTransform2D createTransform(java.awt.geom.AffineTransform transform) {
return new AffineTransform2D(transform);
}
/**
* Return a translation by the given vector.
*/
public static AffineTransform2D createTranslation(Vector2D vect) {
return new AffineTransform2D(1, 0, vect.x(), 0, 1, vect.y());
}
/**
* Return a translation by the given vector.
*/
public static AffineTransform2D createTranslation(double dx, double dy) {
return new AffineTransform2D(1, 0, dx, 0, 1, dy);
}
// ===================================================================
// methods to identify transforms
/**
* Checks if the given transform is the identity transform.
*/
public static boolean isIdentity(AffineTransform2D trans) {
if (abs(trans.m00 - 1) > ACCURACY)
return false;
if (abs(trans.m01) > ACCURACY)
return false;
if (abs(trans.m02) > ACCURACY)
return false;
if (abs(trans.m10) > ACCURACY)
return false;
if (abs(trans.m11 - 1) > ACCURACY)
return false;
if (abs(trans.m12) > ACCURACY)
return false;
return true;
}
/**
* Checks if the transform is direct, i.e. it preserves the orientation of
* transformed shapes.
*
* @return true if transform is direct.
*/
public static boolean isDirect(AffineTransform2D trans) {
return trans.m00 * trans.m11 - trans.m01 * trans.m10 > 0;
}
/**
* Checks if the transform is an isometry, i.e. a compound of translation,
* rotation and reflection. Isometry keeps area of shapes unchanged, but can
* change orientation (direct or indirect).
*
* @return true in case of isometry.
*/
public static boolean isIsometry(AffineTransform2D trans) {
// extract matrix coefficients
double a = trans.m00;
double b = trans.m01;
double c = trans.m10;
double d = trans.m11;
// transform vectors should be normalized
if (abs(a * a + b * b - 1) > ACCURACY)
return false;
if (abs(c * c + d * d - 1) > ACCURACY)
return false;
// determinant must be -1 or +1
if (abs(a * b + c * d) > ACCURACY)
return false;
// if all tests passed, return true;
return true;
}
/**
* Checks if the transform is a motion, i.e. a compound of translations
* and rotations. Motions are special case of isometries that keep
* orientation (directed or undirected) of shapes unchanged.
*
* @return true in case of motion.
*/
public static boolean isMotion(AffineTransform2D trans) {
// Transform must be 1) an isometry and 2) be direct
return isIsometry(trans) && isDirect(trans);
}
/**
* Checks if the transform is an similarity, i.e. transformation which keeps
* unchanged the global shape, up to a scaling factor.
*
* @return true in case of similarity.
*/
public static boolean isSimilarity(AffineTransform2D trans) {
// computation shortcuts
double a = trans.m00;
double b = trans.m01;
double c = trans.m10;
double d = trans.m11;
// determinant
double k2 = abs(a * d - b * c);
// test each condition
if (abs(a * a + b * b - k2) > ACCURACY)
return false;
if (abs(c * c + d * d - k2) > ACCURACY)
return false;
if (abs(a * a + c * c - k2) > ACCURACY)
return false;
if (abs(b * b + d * d - k2) > ACCURACY)
return false;
// if each test passed, return true
return true;
}
// ===================================================================
// Constructors
/**
* Creates a new AffineTransform2D, initialized with Identity.
*/
public AffineTransform2D() {
// init to identity matrix
m00 = m11 = 1;
m01 = m10 = 0;
m02 = m12 = 0;
}
/**
* Constructor by copy of an existing transform
* @deprecated not necessary to clone immutable objects (0.11.2)
*/
@Deprecated
public AffineTransform2D(AffineTransform2D trans) {
this.m00 = trans.m00;
this.m01 = trans.m01;
this.m02 = trans.m02;
this.m10 = trans.m10;
this.m11 = trans.m11;
this.m12 = trans.m12;
}
/**
* Creates a new transform from a java AWT transform.
*/
public AffineTransform2D(java.awt.geom.AffineTransform transform) {
double[] coefs = new double[6];
transform.getMatrix(coefs);
m00 = coefs[0];
m10 = coefs[1];
m01 = coefs[2];
m11 = coefs[3];
m02 = coefs[4];
m12 = coefs[5];
}
/**
* Creates a new Affine Transform by directly specifying the coefficients,
* in the order m00, m01, m02, m10, m11, m12 (different order of
* java.awt.geom.AffineTransform).
*/
public AffineTransform2D(double[] coefs) {
if (coefs.length == 4) {
m00 = coefs[0];
m01 = coefs[1];
m10 = coefs[2];
m11 = coefs[3];
} else {
m00 = coefs[0];
m01 = coefs[1];
m02 = coefs[2];
m10 = coefs[3];
m11 = coefs[4];
m12 = coefs[5];
}
}
public AffineTransform2D(double xx, double yx, double tx, double xy,
double yy, double ty) {
m00 = xx;
m01 = yx;
m02 = tx;
m10 = xy;
m11 = yy;
m12 = ty;
}
/**
* Helper function that fixes the center of the transform.
* This function recomputes m02 and m12 from the other coefficients and
* the given parameters. If transform is a pure translation, the result is
* the identity transform.
*/
private void recenter(double x0, double y0) {
this.m02 = (1 - this.m00) * x0 - this.m01 * y0;
this.m12 = (1 - this.m11) * y0 - this.m10 * x0;
}
// ===================================================================
// methods specific to AffineTransform2D class
/**
* Returns coefficients of the transform in a linear array of 6 double.
*/
public double[] coefficients() {
double[] tab = { m00, m01, m02, m10, m11, m12 };
return tab;
}
/**
* Returns the 3x3 square matrix representing the transform.
*
* @return the 3x3 affine transform representing the matrix
*/
public double[][] affineMatrix() {
double[][] tab = new double[][] {
new double[] { m00, m01, m02 },
new double[] { m10, m11, m12 },
new double[] { 0, 0, 1 } };
return tab;
}
/**
* Returns this transform as an instance of java AWT AffineTransform.
*/
public java.awt.geom.AffineTransform asAwtTransform() {
return new java.awt.geom.AffineTransform(
this.m00, this.m10, this.m01, this.m11, this.m02, this.m12);
}
/**
* Returns the affine transform created by applying first the affine
* transform given by that, then this affine transform.
* This is the equivalent method of the 'concatenate' method in
* java.awt.geom.AffineTransform.
*
* @param that
* the transform to apply first
* @return the composition this * that
* @since 0.6.3
*/
public AffineTransform2D concatenate(AffineTransform2D that) {
double n00 = this.m00 * that.m00 + this.m01 * that.m10;
double n01 = this.m00 * that.m01 + this.m01 * that.m11;
double n02 = this.m00 * that.m02 + this.m01 * that.m12 + this.m02;
double n10 = this.m10 * that.m00 + this.m11 * that.m10;
double n11 = this.m10 * that.m01 + this.m11 * that.m11;
double n12 = this.m10 * that.m02 + this.m11 * that.m12 + this.m12;
return new AffineTransform2D(n00, n01, n02, n10, n11, n12);
}
/**
* Returns the affine transform created by applying first this affine
* transform, then the affine transform given by that. This the
* equivalent method of the 'preConcatenate' method in
* java.awt.geom.AffineTransform.
* shape = shape.transform(T1.chain(T2).chain(T3));
*
is equivalent to the sequence:
* shape = shape.transform(T1);
* shape = shape.transform(T2);
* shape = shape.transform(T3);
*
*
* @param that
* the transform to apply in a second step
* @return the composition that * this
* @since 0.6.3
*/
public AffineTransform2D chain(AffineTransform2D that) {
return new AffineTransform2D(
that.m00 * this.m00 + that.m01 * this.m10,
that.m00 * this.m01 + that.m01 * this.m11,
that.m00 * this.m02 + that.m01 * this.m12 + that.m02,
that.m10 * this.m00 + that.m11 * this.m10,
that.m10 * this.m01 + that.m11 * this.m11,
that.m10 * this.m02 + that.m11 * this.m12 + that.m12);
}
/**
* Return the affine transform created by applying first this affine
* transform, then the affine transform given by that. This the
* equivalent method of the 'preConcatenate' method in
* java.awt.geom.AffineTransform.
*
* @param that
* the transform to apply in a second step
* @return the composition that * this
* @since 0.6.3
*/
public AffineTransform2D preConcatenate(AffineTransform2D that) {
return this.chain(that);
}
// ===================================================================
// methods testing type of transform
/**
* Tests if this affine transform is a similarity.
*/
public boolean isSimilarity() {
return AffineTransform2D.isSimilarity(this);
}
/**
* Tests if this affine transform is a motion, i.e. is composed only of
* rotations and translations.
*/
public boolean isMotion() {
return AffineTransform2D.isMotion(this);
}
/**
* Tests if this affine transform is an isometry, i.e. is equivalent to a
* compound of translations, rotations and reflections.
* Isometry keeps area of shapes unchanged, but can change orientation
* (direct or indirect).
*
* @return true in case of isometry.
*/
public boolean isIsometry() {
return AffineTransform2D.isIsometry(this);
}
/**
* Tests if this affine transform is direct, i.e. the sign of the
* determinant of the associated matrix is positive.
* Direct transforms preserve the orientation of transformed shapes.
*/
public boolean isDirect() {
return AffineTransform2D.isDirect(this);
}
/**
* Tests is this affine transform is equal to the identity transform.
* @return true if this transform is the identity transform
*/
public boolean isIdentity() {
return AffineTransform2D.isIdentity(this);
}
// ===================================================================
// implementations of Bijection2D methods
/**
* Returns the inverse transform. If the transform is not invertible, throws
* a new NonInvertibleTransform2DException.
*
* @since 0.6.3
*/
public AffineTransform2D invert() {
double det = m00 * m11 - m10 * m01;
if (Math.abs(det) < Shape2D.ACCURACY)
throw new NonInvertibleTransform2DException(this);
return new AffineTransform2D(
m11 / det, -m01 / det, (m01 * m12 - m02 * m11) / det,
-m10 / det, m00 / det, (m02 * m10 - m00 * m12) / det);
}
// ===================================================================
// implementations of Transform2D methods
/**
* Computes the coordinates of the transformed point.
*/
public Point2D transform(Point2D p) {
Point2D dst = new Point2D(
p.x() * m00 + p.y() * m01 + m02,
p.x() * m10 + p.y() * m11 + m12);
return dst;
}
public Point2D[] transform(Point2D[] src, Point2D[] dst) {
if (dst == null)
dst = new Point2D[src.length];
double x, y;
for (int i = 0; i < src.length; i++) {
x = src[i].x();
y = src[i].y();
dst[i] = new Point2D(
x * m00 + y * m01 + m02,
x * m10 + y * m11 + m12);
}
return dst;
}
// ===================================================================
// implements the GeometricObject2D interface
public boolean almostEquals(GeometricObject2D obj, double eps) {
if (this == obj)
return true;
if (!(obj instanceof AffineTransform2D))
return false;
double[] tab1 = this.coefficients();
double[] tab2 = ((AffineTransform2D) obj).coefficients();
for (int i = 0; i < 6; i++)
if (Math.abs(tab1[i] - tab2[i]) > eps)
return false;
return true;
}
// ===================================================================
// Override the Object methods
/**
* Displays the coefficients of the transform, row by row.
*/
@Override
public String toString() {
return new String("AffineTransform2D(" + m00 + "," + m01 + "," + m02
+ "," + m10 + "," + m11 + "," + m12 + ",");
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof AffineTransform2D))
return false;
AffineTransform2D that = (AffineTransform2D) obj;
if (!EqualUtils.areEqual(this.m00, that.m00))
return false;
if (!EqualUtils.areEqual(this.m01, that.m01))
return false;
if (!EqualUtils.areEqual(this.m02, that.m02))
return false;
if (!EqualUtils.areEqual(this.m00, that.m00))
return false;
if (!EqualUtils.areEqual(this.m01, that.m01))
return false;
if (!EqualUtils.areEqual(this.m02, that.m02))
return false;
return true;
}
/**
* @deprecated immutable objects do not need to be cloned (0.11.2)
*/
@Deprecated
@Override
public AffineTransform2D clone() {
return new AffineTransform2D(m00, m01, m02, m10, m11, m12);
}
}
Ads help maintain this website.