diff --git a/src/main/java/pythagoras/f/AbstractArc.java b/src/main/java/pythagoras/f/AbstractArc.java
index bed858e..f6efee0 100644
--- a/src/main/java/pythagoras/f/AbstractArc.java
+++ b/src/main/java/pythagoras/f/AbstractArc.java
@@ -20,9 +20,8 @@ public abstract class AbstractArc extends RectangularShape implements IArc
@Override // from interface IArc
public Point getStartPoint (Point target) {
float a = FloatMath.toRadians(getAngleStart());
- target.setLocation(getX() + (1f + FloatMath.cos(a)) * getWidth() / 2f,
- getY() + (1f - FloatMath.sin(a)) * getHeight() / 2f);
- return target;
+ return target.set(getX() + (1f + FloatMath.cos(a)) * getWidth() / 2f,
+ getY() + (1f - FloatMath.sin(a)) * getHeight() / 2f);
}
@Override // from interface IArc
@@ -33,9 +32,8 @@ public abstract class AbstractArc extends RectangularShape implements IArc
@Override // from interface IArc
public Point getEndPoint (Point target) {
float a = FloatMath.toRadians(getAngleStart() + getAngleExtent());
- target.setLocation(getX() + (1f + FloatMath.cos(a)) * getWidth() / 2f,
- getY() + (1f - FloatMath.sin(a)) * getHeight() / 2f);
- return target;
+ return target.set(getX() + (1f + FloatMath.cos(a)) * getWidth() / 2f,
+ getY() + (1f - FloatMath.sin(a)) * getHeight() / 2f);
}
@Override // from interface IArc
@@ -189,7 +187,7 @@ public abstract class AbstractArc extends RectangularShape implements IArc
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform at) {
+ public PathIterator getPathIterator (Transform at) {
return new Iterator(this, at);
}
@@ -223,7 +221,7 @@ public abstract class AbstractArc extends RectangularShape implements IArc
private int type;
/** The path iterator transformation */
- private AffineTransform t;
+ private Transform t;
/** The current segment index */
private int index;
@@ -259,7 +257,7 @@ public abstract class AbstractArc extends RectangularShape implements IArc
/** The y coordinate of the first path point (MOVE_TO) */
private float my;
- Iterator (IArc a, AffineTransform t) {
+ Iterator (IArc a, Transform t) {
this.width = a.getWidth() / 2f;
this.height = a.getHeight() / 2f;
this.x = a.getX() + width;
diff --git a/src/main/java/pythagoras/f/AbstractCubicCurve.java b/src/main/java/pythagoras/f/AbstractCubicCurve.java
index a0b3526..c9c870f 100644
--- a/src/main/java/pythagoras/f/AbstractCubicCurve.java
+++ b/src/main/java/pythagoras/f/AbstractCubicCurve.java
@@ -111,12 +111,12 @@ public abstract class AbstractCubicCurve implements ICubicCurve
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t) {
+ public PathIterator getPathIterator (Transform t) {
return new Iterator(this, t);
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform at, float flatness) {
+ public PathIterator getPathIterator (Transform at, float flatness) {
return new FlatteningPathIterator(getPathIterator(at), flatness);
}
@@ -124,10 +124,10 @@ public abstract class AbstractCubicCurve implements ICubicCurve
protected static class Iterator implements PathIterator
{
private ICubicCurve c;
- private AffineTransform t;
+ private Transform t;
private int index;
- Iterator (ICubicCurve c, AffineTransform t) {
+ Iterator (ICubicCurve c, Transform t) {
this.c = c;
this.t = t;
}
diff --git a/src/main/java/pythagoras/f/AbstractEllipse.java b/src/main/java/pythagoras/f/AbstractEllipse.java
index a7870fa..070a0e7 100644
--- a/src/main/java/pythagoras/f/AbstractEllipse.java
+++ b/src/main/java/pythagoras/f/AbstractEllipse.java
@@ -44,7 +44,7 @@ public abstract class AbstractEllipse extends RectangularShape implements IEllip
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform at) {
+ public PathIterator getPathIterator (Transform at) {
return new Iterator(this, at);
}
@@ -52,10 +52,10 @@ public abstract class AbstractEllipse extends RectangularShape implements IEllip
protected static class Iterator implements PathIterator
{
private final float x, y, width, height;
- private final AffineTransform t;
+ private final Transform t;
private int index;
- Iterator (IEllipse e, AffineTransform t) {
+ Iterator (IEllipse e, Transform t) {
this.x = e.getX();
this.y = e.getY();
this.width = e.getWidth();
diff --git a/src/main/java/pythagoras/f/AbstractLine.java b/src/main/java/pythagoras/f/AbstractLine.java
index d0553dd..6a9fbbb 100644
--- a/src/main/java/pythagoras/f/AbstractLine.java
+++ b/src/main/java/pythagoras/f/AbstractLine.java
@@ -19,8 +19,7 @@ public abstract class AbstractLine implements ILine
@Override // from interface ILine
public Point getP1 (Point target) {
- target.setLocation(getX1(), getY1());
- return target;
+ return target.set(getX1(), getY1());
}
@Override // from interface ILine
@@ -30,8 +29,7 @@ public abstract class AbstractLine implements ILine
@Override // from interface ILine
public Point getP2 (Point target) {
- target.setLocation(getX2(), getY2());
- return target;
+ return target.set(getX2(), getY2());
}
@Override // from interface ILine
@@ -152,12 +150,12 @@ public abstract class AbstractLine implements ILine
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform at) {
+ public PathIterator getPathIterator (Transform at) {
return new Iterator(this, at);
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform at, float flatness) {
+ public PathIterator getPathIterator (Transform at, float flatness) {
return new Iterator(this, at);
}
@@ -165,10 +163,10 @@ public abstract class AbstractLine implements ILine
protected static class Iterator implements PathIterator
{
private float x1, y1, x2, y2;
- private AffineTransform t;
+ private Transform t;
private int index;
- Iterator (ILine l, AffineTransform at) {
+ Iterator (ILine l, Transform at) {
this.x1 = l.getX1();
this.y1 = l.getY1();
this.x2 = l.getX2();
diff --git a/src/main/java/pythagoras/f/AbstractPoint.java b/src/main/java/pythagoras/f/AbstractPoint.java
index 5066483..f18a4fa 100644
--- a/src/main/java/pythagoras/f/AbstractPoint.java
+++ b/src/main/java/pythagoras/f/AbstractPoint.java
@@ -12,27 +12,59 @@ import pythagoras.util.Platform;
*/
public abstract class AbstractPoint implements IPoint
{
- @Override // from interface IPoint
+ @Override // from IPoint
public float distanceSq (float px, float py) {
return Points.distanceSq(getX(), getY(), px, py);
}
- @Override // from interface IPoint
+ @Override // from IPoint
public float distanceSq (IPoint p) {
return Points.distanceSq(getX(), getY(), p.getX(), p.getY());
}
- @Override // from interface IPoint
+ @Override // from IPoint
public float distance (float px, float py) {
return Points.distance(getX(), getY(), px, py);
}
- @Override // from interface IPoint
+ @Override // from IPoint
public float distance (IPoint p) {
return Points.distance(getX(), getY(), p.getX(), p.getY());
}
- @Override // from interface IPoint
+ @Override // from IPoint
+ public Point mult (float s) {
+ return mult(s, new Point());
+ }
+
+ @Override // from IPoint
+ public Point mult (float s, Point result) {
+ return result.set(getX() * s, getY() * s);
+ }
+
+ @Override // from IPoint
+ public Point add (float x, float y) {
+ return new Point(getX() + x, getY() + y);
+ }
+
+ @Override // from IPoint
+ public Point add (float x, float y, Point result) {
+ return result.set(getX() + x, getY() + y);
+ }
+
+ @Override // from IPoint
+ public Point rotate (float angle) {
+ return rotate(angle, new Point());
+ }
+
+ @Override // from IPoint
+ public Point rotate (float angle, Point result) {
+ float x = getX(), y = getY();
+ float sina = FloatMath.sin(angle), cosa = FloatMath.cos(angle);
+ return result.set(x*cosa - y*sina, x*sina + y*cosa);
+ }
+
+ @Override // from IPoint
public Point clone () {
return new Point(this);
}
diff --git a/src/main/java/pythagoras/f/AbstractQuadCurve.java b/src/main/java/pythagoras/f/AbstractQuadCurve.java
index 1649eb5..865da78 100644
--- a/src/main/java/pythagoras/f/AbstractQuadCurve.java
+++ b/src/main/java/pythagoras/f/AbstractQuadCurve.java
@@ -102,12 +102,12 @@ public abstract class AbstractQuadCurve implements IQuadCurve
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t) {
+ public PathIterator getPathIterator (Transform t) {
return new Iterator(this, t);
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t, float flatness) {
+ public PathIterator getPathIterator (Transform t, float flatness) {
return new FlatteningPathIterator(getPathIterator(t), flatness);
}
@@ -115,10 +115,10 @@ public abstract class AbstractQuadCurve implements IQuadCurve
protected static class Iterator implements PathIterator
{
private IQuadCurve c;
- private AffineTransform t;
+ private Transform t;
private int index;
- Iterator (IQuadCurve q, AffineTransform t) {
+ Iterator (IQuadCurve q, Transform t) {
this.c = q;
this.t = t;
}
diff --git a/src/main/java/pythagoras/f/AbstractRectangle.java b/src/main/java/pythagoras/f/AbstractRectangle.java
index 6234c9b..3cf4c40 100644
--- a/src/main/java/pythagoras/f/AbstractRectangle.java
+++ b/src/main/java/pythagoras/f/AbstractRectangle.java
@@ -21,8 +21,7 @@ public abstract class AbstractRectangle extends RectangularShape implements IRec
@Override // from interface IRectangle
public Point getLocation (Point target) {
- target.setLocation(getX(), getY());
- return target;
+ return target.set(getX(), getY());
}
@Override // from interface IRectangle
@@ -129,12 +128,12 @@ public abstract class AbstractRectangle extends RectangularShape implements IRec
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t) {
+ public PathIterator getPathIterator (Transform t) {
return new Iterator(this, t);
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t, float flatness) {
+ public PathIterator getPathIterator (Transform t, float flatness) {
return new Iterator(this, t);
}
@@ -167,12 +166,12 @@ public abstract class AbstractRectangle extends RectangularShape implements IRec
protected static class Iterator implements PathIterator
{
private float x, y, width, height;
- private AffineTransform t;
+ private Transform t;
/** The current segment index. */
private int index;
- Iterator (IRectangle r, AffineTransform at) {
+ Iterator (IRectangle r, Transform at) {
this.x = r.getX();
this.y = r.getY();
this.width = r.getWidth();
diff --git a/src/main/java/pythagoras/f/AbstractRoundRectangle.java b/src/main/java/pythagoras/f/AbstractRoundRectangle.java
index 2562e49..0e534c5 100644
--- a/src/main/java/pythagoras/f/AbstractRoundRectangle.java
+++ b/src/main/java/pythagoras/f/AbstractRoundRectangle.java
@@ -75,7 +75,7 @@ public abstract class AbstractRoundRectangle extends RectangularShape implements
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform at) {
+ public PathIterator getPathIterator (Transform at) {
return new Iterator(this, at);
}
@@ -83,10 +83,10 @@ public abstract class AbstractRoundRectangle extends RectangularShape implements
protected static class Iterator implements PathIterator
{
private final float x, y, width, height, aw, ah;
- private final AffineTransform t;
+ private final Transform t;
private int index;
- Iterator (IRoundRectangle rr, AffineTransform at) {
+ Iterator (IRoundRectangle rr, Transform at) {
this.x = rr.getX();
this.y = rr.getY();
this.width = rr.getWidth();
diff --git a/src/main/java/pythagoras/f/AbstractTransform.java b/src/main/java/pythagoras/f/AbstractTransform.java
new file mode 100644
index 0000000..19cf17f
--- /dev/null
+++ b/src/main/java/pythagoras/f/AbstractTransform.java
@@ -0,0 +1,73 @@
+//
+// Pythagoras - a collection of geometry classes
+// http://github.com/samskivert/pythagoras
+
+package pythagoras.f;
+
+/**
+ * Implements some code shared by the various {@link Transform} implementations.
+ */
+public abstract class AbstractTransform implements Transform
+{
+ @Override // from Transform
+ public Vector getScale () {
+ return new Vector(getScaleX(), getScaleY());
+ }
+
+ @Override // from Transform
+ public Vector getTranslation () {
+ return new Vector(getTx(), getTy());
+ }
+
+ @Override // from Transform
+ public Transform setUniformScale (float scale) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override // from Transform
+ public Transform setScale (float scaleX, float scaleY) {
+ setScaleX(scaleX);
+ setScaleY(scaleY);
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setScaleX (float scaleX) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override // from Transform
+ public Transform setScaleY (float scaleY) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override // from Transform
+ public Transform setRotation (float angle) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override // from Transform
+ public Transform setTranslation (float tx, float ty) {
+ setTx(tx);
+ setTy(ty);
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setTx (float tx) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override // from Transform
+ public Transform setTy (float ty) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override // from Transform
+ public Transform setTransform (float m00, float m01, float m10, float m11, float tx, float ty) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override // from Transform
+ public abstract Transform clone ();
+}
diff --git a/src/main/java/pythagoras/f/AffineTransform.java b/src/main/java/pythagoras/f/AffineTransform.java
index d46609c..287c8d8 100644
--- a/src/main/java/pythagoras/f/AffineTransform.java
+++ b/src/main/java/pythagoras/f/AffineTransform.java
@@ -4,317 +4,144 @@
package pythagoras.f;
-import java.io.Serializable;
-
import pythagoras.util.NoninvertibleTransformException;
-import pythagoras.util.Platform;
/**
- * Represents a 2D affine transform, which performs a linear mapping that preserves the
- * straightness and parallelness of lines.
- *
- * See http://download.oracle.com/javase/6/docs/api/java/awt/geom/AffineTransform.html
+ * Implements an affine (3x2 matrix) transform. The transformation matrix has the form:
+ * {@code
+ * [ m00, m10, 0 ]
+ * [ m01, m11, 0 ]
+ * [ tx, ty, 1 ]
+ * }
*/
-public class AffineTransform implements Cloneable, Serializable
+public class AffineTransform extends AbstractTransform
{
- public static final int TYPE_IDENTITY = 0;
- public static final int TYPE_TRANSLATION = 1;
- public static final int TYPE_UNIFORM_SCALE = 2;
- public static final int TYPE_GENERAL_SCALE = 4;
- public static final int TYPE_QUADRANT_ROTATION = 8;
- public static final int TYPE_GENERAL_ROTATION = 16;
- public static final int TYPE_GENERAL_TRANSFORM = 32;
- public static final int TYPE_FLIP = 64;
- public static final int TYPE_MASK_SCALE = TYPE_UNIFORM_SCALE | TYPE_GENERAL_SCALE;
- public static final int TYPE_MASK_ROTATION = TYPE_QUADRANT_ROTATION | TYPE_GENERAL_ROTATION;
+ /** Identifies the affine transform in {@link #generality}. */
+ public static final int GENERALITY = 4;
- /**
- * Returns a transform that performs the specified translation.
- */
- public static AffineTransform getTranslateInstance (float tx, float ty) {
- AffineTransform t = new AffineTransform();
- t.setToTranslation(tx, ty);
- return t;
- }
+ /** The scale, rotation and shear components of this transform. */
+ public float m00, m01, m10, m11;
- /**
- * Returns a transform that performs the specified scale.
- */
- public static AffineTransform getScaleInstance (float scx, float scY) {
- AffineTransform t = new AffineTransform();
- t.setToScale(scx, scY);
- return t;
- }
+ /** The translation components of this transform. */
+ public float tx, ty;
- /**
- * Returns a transform that performs the specified shear.
- */
- public static AffineTransform getShearInstance (float shx, float shy) {
- AffineTransform m = new AffineTransform();
- m.setToShear(shx, shy);
- return m;
- }
-
- /**
- * Returns a transform that performs the specified rotation.
- */
- public static AffineTransform getRotateInstance (float angle) {
- AffineTransform t = new AffineTransform();
- t.setToRotation(angle);
- return t;
- }
-
- /**
- * Returns a transform that performs the specified rotation.
- */
- public static AffineTransform getRotateInstance (float angle, float x, float y) {
- AffineTransform t = new AffineTransform();
- t.setToRotation(angle, x, y);
- return t;
- }
-
- /**
- * Constructs an identity transform.
- */
+ /** Creates an affine transform configured with the identity transform. */
public AffineTransform () {
- setToIdentity();
+ this(1, 0, 0, 1, 0, 0);
}
- /**
- * Constructs a transform that is a copy of the supplied transform.
- */
- public AffineTransform (AffineTransform t) {
- setTransform(t);
+ /** Creates an affine transform from the supplied scale, rotation and translation. */
+ public AffineTransform (float scale, float angle, float tx, float ty) {
+ this(scale, scale, angle, tx, ty);
}
- /**
- * Constructs a transform with the specified transformation matrix.
- */
- public AffineTransform (float m00, float m10, float m01, float m11, float m02, float m12) {
- setTransform(m00, m10, m01, m11, m02, m12);
+ /** Creates an affine transform from the supplied scale, rotation and translation. */
+ public AffineTransform (float scaleX, float scaleY, float angle, float tx, float ty) {
+ float sina = FloatMath.sin(angle), cosa = FloatMath.cos(angle);
+ this.m00 = cosa * scaleX; this.m01 = -sina * scaleX;
+ this.m10 = sina * scaleY; this.m11 = cosa * scaleY;
+ this.tx = tx; this.ty = ty;
}
- /**
- * Constructs a transform with the specified transformation matrix.
- *
- * @param matrix either {@code [m00, m10, m01, m11]} or {@code [m00, m10, m01, m11, m02, m12]}.
- */
- public AffineTransform (float[] matrix) {
- this.type = TYPE_UNKNOWN;
- m00 = matrix[0];
- m10 = matrix[1];
- m01 = matrix[2];
- m11 = matrix[3];
- if (matrix.length > 4) {
- m02 = matrix[4];
- m12 = matrix[5];
- }
+ /** Creates an affine transform with the specified transform matrix. */
+ public AffineTransform (float m00, float m01, float m10, float m11, float tx, float ty) {
+ this.m00 = m00; this.m01 = m01;
+ this.m10 = m10; this.m11 = m11;
+ this.tx = tx; this.ty = ty;
}
- /**
- * Returns the type of this affine transform, which is a bitwise-or of the type flags
- * ({@link #TYPE_TRANSLATION}, etc.).
- */
- public int getType () {
- if (type != TYPE_UNKNOWN) {
- return type;
- }
-
- int type = 0;
-
- if (m00 * m01 + m10 * m11 != 0) {
- type |= TYPE_GENERAL_TRANSFORM;
- return type;
- }
-
- if (m02 != 0 || m12 != 0) {
- type |= TYPE_TRANSLATION;
- } else if (m00 == 1f && m11 == 1f && m01 == 0 && m10 == 0) {
- type = TYPE_IDENTITY;
- return type;
- }
-
- if (m00 * m11 - m01 * m10 < 0) {
- type |= TYPE_FLIP;
- }
-
- float dx = m00 * m00 + m10 * m10;
- float dy = m01 * m01 + m11 * m11;
- if (dx != dy) {
- type |= TYPE_GENERAL_SCALE;
- } else if (dx != 1f) {
- type |= TYPE_UNIFORM_SCALE;
- }
-
- if ((m00 == 0 && m11 == 0) || (m10 == 0 && m01 == 0 && (m00 < 0 || m11 < 0))) {
- type |= TYPE_QUADRANT_ROTATION;
- } else if (m01 != 0 || m10 != 0) {
- type |= TYPE_GENERAL_ROTATION;
- }
-
- return type;
+ @Override // from Transform
+ public float getUniformScale () {
+ // the square root of the signed area of the parallelogram spanned by the axis vectors
+ float cp = m00*m11 - m01*m10;
+ return (cp < 0f) ? -FloatMath.sqrt(-cp) : FloatMath.sqrt(cp);
}
- /**
- * Returns the x-component of the scale vector.
- */
+ @Override // from Transform
public float getScaleX () {
- return m00;
+ return FloatMath.sqrt(m00*m00 + m01*m01);
}
- /**
- * Returns the y-component of the scale vector.
- */
+ @Override // from Transform
public float getScaleY () {
- return m11;
+ return FloatMath.sqrt(m10*m10 + m11*m11);
}
- /**
- * Returns the x-component of the shear vector.
- */
- public float getShearX () {
- return m01;
- }
+ @Override // from Transform
+ public float getRotation () {
+ // use the iterative polar decomposition algorithm described by Ken Shoemake:
+ // http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf
- /**
- * Returns the y-component of the shear vector.
- */
- public float getShearY () {
- return m10;
- }
+ // start with the contents of the upper 2x2 portion of the matrix
+ float n00 = m00, n10 = m10;
+ float n01 = m01, n11 = m11;
+ for (int ii = 0; ii < 10; ii++) {
+ // store the results of the previous iteration
+ float o00 = n00, o10 = n10;
+ float o01 = n01, o11 = n11;
- /**
- * Returns the x-component of the translation vector.
- */
- public float getTranslateX () {
- return m02;
- }
+ // compute average of the matrix with its inverse transpose
+ float det = o00*o11 - o10*o01;
+ if (Math.abs(det) == 0f) {
+ // determinant is zero; matrix is not invertible
+ throw new NoninvertibleTransformException(this.toString());
+ }
+ float hrdet = 0.5f / det;
+ n00 = +o11 * hrdet + o00*0.5f;
+ n10 = -o01 * hrdet + o10*0.5f;
- /**
- * Returns the y-component of the translation vector.
- */
- public float getTranslateY () {
- return m12;
- }
+ n01 = -o10 * hrdet + o01*0.5f;
+ n11 = +o00 * hrdet + o11*0.5f;
- /**
- * Returns true if this transform is the identity.
- */
- public boolean isIdentity () {
- return getType() == TYPE_IDENTITY;
- }
-
- /**
- * Fills in the supplied matrix with this transform's values.
- *
- * @param matrix either a length-4 or length-6 array.
- */
- public void getMatrix (float[] matrix) {
- matrix[0] = m00;
- matrix[1] = m10;
- matrix[2] = m01;
- matrix[3] = m11;
- if (matrix.length > 4) {
- matrix[4] = m02;
- matrix[5] = m12;
+ // compute the difference; if it's small enough, we're done
+ float d00 = n00 - o00, d10 = n10 - o10;
+ float d01 = n01 - o01, d11 = n11 - o11;
+ if (d00*d00 + d10*d10 + d01*d01 + d11*d11 < FloatMath.EPSILON) {
+ break;
+ }
}
+ // now that we have a nice orthogonal matrix, we can extract the rotation
+ return FloatMath.atan2(n01, n00);
}
- /**
- * Returns the determinant of this
- * matrix.
- */
- public float getDeterminant () {
- return m00 * m11 - m01 * m10;
+ @Override // from Transform
+ public float getTx () {
+ return this.tx;
}
- /**
- * Sets this transform's values.
- */
- public void setTransform (float m00, float m10, float m01, float m11, float m02, float m12) {
- this.type = TYPE_UNKNOWN;
- this.m00 = m00;
- this.m10 = m10;
- this.m01 = m01;
- this.m11 = m11;
- this.m02 = m02;
- this.m12 = m12;
+ @Override // from Transform
+ public float getTy () {
+ return this.tx;
}
- /**
- * Sets this transform's values to be equal to those of the supplied transform.
- */
- public void setTransform (AffineTransform t) {
- setTransform(t.m00, t.m10, t.m01, t.m11, t.m02, t.m12);
- type = t.type;
+ @Override // from Transform
+ public Transform setUniformScale (float scale) {
+ return setScale(scale, scale);
}
- /**
- * Sets this transform to the identity transform. Any existing transform values are
- * overwritten.
- */
- public void setToIdentity () {
- type = TYPE_IDENTITY;
- m00 = m11 = 1f;
- m10 = m01 = m02 = m12 = 0;
+ @Override // from Transform
+ public Transform setScaleX (float scaleX) {
+ // normalize the scale to 1, then re-apply
+ float osx = getScaleX();
+ m00 /= osx; m01 /= osx;
+ m00 *= scaleX; m01 *= scaleX;
+ return this;
}
- /**
- * Sets this transform to a simple translation using the supplied values. Any existing
- * transform values are overwritten.
- */
- public void setToTranslation (float tx, float ty) {
- m00 = m11 = 1f;
- m01 = m10 = 0;
- m02 = tx;
- m12 = ty;
- if (tx == 0 && ty == 0) {
- type = TYPE_IDENTITY;
- } else {
- type = TYPE_TRANSLATION;
- }
+ @Override // from Transform
+ public Transform setScaleY (float scaleY) {
+ // normalize the scale to 1, then re-apply
+ float osy = getScaleY();
+ m10 /= osy; m11 /= osy;
+ m10 *= scaleY; m11 *= scaleY;
+ return this;
}
- /**
- * Sets this transform to a simple scale using the supplied values. Any existing transform
- * values are overwritten.
- */
- public void setToScale (float scx, float scy) {
- m00 = scx;
- m11 = scy;
- m10 = m01 = m02 = m12 = 0;
- if (scx != 1f || scy != 1f) {
- type = TYPE_UNKNOWN;
- } else {
- type = TYPE_IDENTITY;
- }
- }
-
- /**
- * Sets this transform to a simple shear using the supplied values. Any existing transform
- * values are overwritten.
- */
- public void setToShear (float shx, float shy) {
- m00 = m11 = 1f;
- m02 = m12 = 0;
- m01 = shx;
- m10 = shy;
- if (shx != 0 || shy != 0) {
- type = TYPE_UNKNOWN;
- } else {
- type = TYPE_IDENTITY;
- }
- }
-
- /**
- * Sets this transform to a simple rotation using the supplied values. Any existing transform
- * values are overwritten.
- *
- * @param angle the angle of rotation (in radians).
- */
- public void setToRotation (float angle) {
- float sin = FloatMath.sin(angle);
- float cos = FloatMath.cos(angle);
+ @Override // from Transform
+ public Transform setRotation (float angle) {
+ // extract the scale, then reapply rotation and scale together
+ float sx = getScaleX(), sy = getScaleY();
+ float sin = FloatMath.sin(angle), cos = FloatMath.cos(angle);
if (Math.abs(cos) < ZERO) {
cos = 0;
sin = sin > 0 ? 1f : -1f;
@@ -322,329 +149,160 @@ public class AffineTransform implements Cloneable, Serializable
sin = 0;
cos = cos > 0 ? 1f : -1f;
}
- m00 = m11 = cos;
- m01 = -sin;
- m10 = sin;
- m02 = m12 = 0;
- type = TYPE_UNKNOWN;
+ m00 = cos * sx; m01 = -sin * sx;
+ m10 = sin * sy; m11 = cos * sy;
+ return this;
}
- /**
- * Sets this transform to a simple rotation using the supplied values. Any existing transform
- * values are overwritten.
- *
- * @param angle the angle of rotation (in radians).
- * @param px the x-coordinate of the point around which to rotate.
- * @param py the y-coordinate of the point around which to rotate.
- */
- public void setToRotation (float angle, float px, float py) {
- setToRotation(angle);
- m02 = px * (1f - m00) + py * m10;
- m12 = py * (1f - m00) - px * m10;
- type = TYPE_UNKNOWN;
+ @Override // from Transform
+ public Transform setTranslation (float tx, float ty) {
+ this.tx = tx;
+ this.ty = ty;
+ return this;
}
- /**
- * Concatenates the specified translation to this transform.
- */
- public void translate (float tx, float ty) {
- concatenate(getTranslateInstance(tx, ty));
+ @Override // from Transform
+ public Transform setTx (float tx) {
+ this.tx = tx;
+ return this;
}
- /**
- * Concatenates the specified scale to this transform.
- */
- public void scale (float scx, float scy) {
- concatenate(getScaleInstance(scx, scy));
+ @Override // from Transform
+ public Transform setTy (float ty) {
+ this.ty = ty;
+ return this;
}
- /**
- * Concatenates the specified shear to this transform.
- */
- public void shear (float shx, float shy) {
- concatenate(getShearInstance(shx, shy));
+ @Override // from Transform
+ public Transform setTransform (float m00, float m01, float m10, float m11, float tx, float ty) {
+ this.m00 = m00;
+ this.m01 = m01;
+ this.m10 = m10;
+ this.m11 = m11;
+ this.tx = tx;
+ this.ty = ty;
+ return this;
}
- /**
- * Concatenates the specified rotation to this transform.
- */
- public void rotate (float angle) {
- concatenate(getRotateInstance(angle));
- }
-
- /**
- * Concatenates the specified rotation to this transform.
- */
- public void rotate (float angle, float px, float py) {
- concatenate(getRotateInstance(angle, px, py));
- }
-
- /**
- * Concatenates the specified transform to this transform.
- */
- public void concatenate (AffineTransform t) {
- multiply(t, this, this);
- }
-
- /**
- * Pre-concatenates the specified transform to this transform.
- */
- public void preConcatenate (AffineTransform t) {
- multiply(this, t, this);
- }
-
- /**
- * Computes the inverse of this transform and stores it in the supplied target.
- *
- * @return the supplied target.
- * @throws NoninvertibleTransformException if this transform cannot be inverted.
- */
- public AffineTransform createInverse (AffineTransform target)
- throws NoninvertibleTransformException {
- float det = getDeterminant();
- if (Math.abs(det) < ZERO) {
- throw new NoninvertibleTransformException("Determinant is zero");
+ @Override // from Transform
+ public Transform invert () {
+ // compute the determinant, storing the subdeterminants for later use
+ float det = m00*m11 - m10*m01;
+ if (Math.abs(det) == 0f) {
+ // determinant is zero; matrix is not invertible
+ throw new NoninvertibleTransformException(this.toString());
}
- target.setTransform(m11 / det, // m00
- -m10 / det, // m10
- -m01 / det, // m01
- m00 / det, // m11
- (m01 * m12 - m11 * m02) / det, // m02
- (m10 * m02 - m00 * m12) / det); // m12
- return target;
+ float rdet = 1f / det;
+ return new AffineTransform(
+ +m11 * rdet, -m01 * rdet,
+ -m10 * rdet, +m00 * rdet,
+ (m01*ty - m11*tx) * rdet, (m10*tx - m00*ty) * rdet);
}
- /**
- * Computes and returns the inverse of this transform.
- *
- * @return the supplied target.
- * @throws NoninvertibleTransformException if this transform cannot be inverted.
- */
- public AffineTransform createInverse () throws NoninvertibleTransformException {
- return createInverse(new AffineTransform());
+ @Override // from Transform
+ public Transform concatenate (Transform other) {
+ if (generality() < other.generality()) {
+ return other.preConcatenate(this);
+ }
+ return multiply((other instanceof AffineTransform) ?
+ (AffineTransform)other : new AffineTransform(other));
}
- /**
- * Transforms the supplied point using this transform's matrix.
- *
- * @param src the point to be transformed.
- * @param dst the point in which to store the transformed values, if null a new instance will
- * be created. May be {@code src}.
- * @return the supplied (or created) destination point.
- */
- public Point transform (IPoint src, Point dst) {
- if (dst == null) {
- dst = new Point();
+ @Override // from Transform
+ public Transform preConcatenate (Transform other) {
+ if (generality() < other.generality()) {
+ return other.concatenate(this);
+ }
+ return ((other instanceof AffineTransform) ?
+ (AffineTransform)other : new AffineTransform(other)).multiply(this);
+ }
+
+ @Override // from Transform
+ public Transform lerp (Transform other, float t) {
+ if (generality() < other.generality()) {
+ return other.lerp(this, -t); // TODO: is this correct?
}
- float x = src.getX(), y = src.getY();
- dst.setLocation(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12);
- return dst;
+ AffineTransform ot = (other instanceof AffineTransform) ?
+ (AffineTransform)other : new AffineTransform(other);
+ return new AffineTransform(
+ m00 + t*(ot.m00 - m00), m01 + t*(ot.m01 - m01),
+ m10 + t*(ot.m10 - m10), m11 + t*(ot.m11 - m11),
+ tx + t*(ot.tx - tx ), ty + t*(ot.ty - ty ));
}
- /**
- * Transforms the supplied points using this transform's matrix.
- *
- * @param src the points to be transformed.
- * @param srcOff the offset into the {@code src} array at which to start.
- * @param dst the points into which to store the transformed points. May be {@code src}.
- * @param dstOff the offset into the {@code dst} array at which to start.
- * @param length the number of points to transform.
- */
- public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int length) {
- while (--length >= 0) {
- IPoint srcPoint = src[srcOff++];
- float x = srcPoint.getX();
- float y = srcPoint.getY();
- Point dstPoint = dst[dstOff];
- if (dstPoint == null) {
- dstPoint = new Point();
- }
- dstPoint.setLocation(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12);
- dst[dstOff++] = dstPoint;
+ @Override // from Transform
+ public Point transform (IPoint p, Point into) {
+ float x = p.getX(), y = p.getY();
+ return into.set(m00*x + m01*y + tx, m10*x + m11*y + ty);
+ }
+
+ @Override // from Transform
+ public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int count) {
+ for (int ii = 0; ii < count; ii++) {
+ transform(src[srcOff++], dst[dstOff++]);
}
}
- /**
- * Transforms the supplied points using this transform's matrix.
- *
- * @param src the points to be transformed (as {@code [x, y, x, y, ...]}).
- * @param srcOff the offset into the {@code src} array at which to start.
- * @param dst the points into which to store the transformed points. May be {@code src}.
- * @param dstOff the offset into the {@code dst} array at which to start.
- * @param length the number of points to transform.
- */
- public void transform (float[] src, int srcOff, float[] dst, int dstOff, int length) {
- int step = 2;
- if (src == dst && srcOff < dstOff && dstOff < srcOff + length * 2) {
- srcOff = srcOff + length * 2 - 2;
- dstOff = dstOff + length * 2 - 2;
- step = -2;
- }
- while (--length >= 0) {
- float x = src[srcOff + 0];
- float y = src[srcOff + 1];
- dst[dstOff + 0] = (x * m00 + y * m01 + m02);
- dst[dstOff + 1] = (x * m10 + y * m11 + m12);
- srcOff += step;
- dstOff += step;
- }
- }
-
- /**
- * Transforms the supplied relative distance vector (ignores the translation component).
- *
- * @param src the point to be transformed.
- * @param dst the point in which to store the transformed values, if null a new instance will
- * be created. May be {@code src}.
- * @return the supplied (or created) destination point.
- */
- public Point deltaTransform (IPoint src, Point dst) {
- if (dst == null) {
- dst = new Point();
- }
- float x = src.getX(), y = src.getY();
- dst.setLocation(x * m00 + y * m01, x * m10 + y * m11);
- return dst;
- }
-
- /**
- * Transforms the supplied relative distance vectors using this transform's matrix (ignores the
- * translation component).
- *
- * @param src the points to be transformed (as {@code [x, y, x, y, ...]}).
- * @param srcOff the offset into the {@code src} array at which to start.
- * @param dst the points into which to store the transformed points. May be {@code src}.
- * @param dstOff the offset into the {@code dst} array at which to start.
- * @param length the number of points to transform.
- */
- public void deltaTransform (float[] src, int srcOff, float[] dst, int dstOff, int length) {
- while (--length >= 0) {
+ @Override // from Transform
+ public void transform (float[] src, int srcOff, float[] dst, int dstOff, int count) {
+ for (int ii = 0; ii < count; ii++) {
float x = src[srcOff++], y = src[srcOff++];
- dst[dstOff++] = x * m00 + y * m01;
- dst[dstOff++] = x * m10 + y * m11;
+ dst[dstOff++] = m00*x + m01*y + tx;
+ dst[dstOff++] = m10*x + m11*y + ty;
}
}
- /**
- * Transforms the supplied point using the inverse of this transform's matrix.
- *
- * @param src the point to be transformed.
- * @param dst the point in which to store the transformed values, if null a new instance will
- * be created. May be {@code src}.
- * @return the supplied (or created) destination point.
- */
- public Point inverseTransform (IPoint src, Point dst) throws NoninvertibleTransformException {
- float det = getDeterminant();
- if (Math.abs(det) < ZERO) {
- throw new NoninvertibleTransformException("Determinant is zero");
- }
- if (dst == null) {
- dst = new Point();
- }
- float x = src.getX() - m02, y = src.getY() - m12;
- dst.setLocation((x * m11 - y * m01) / det, (y * m00 - x * m10) / det);
- return dst;
+ @Override // from Transform
+ public Point inverseTransform (IPoint p, Point into) {
+ return invert().transform(p, into); // TODO
}
- /**
- * Transforms the supplied points using the inverse of this transform's matrix.
- *
- * @param src the points to be transformed (as {@code [x, y, x, y, ...]}).
- * @param srcOff the offset into the {@code src} array at which to start.
- * @param dst the points into which to store the transformed points. May be {@code src}.
- * @param dstOff the offset into the {@code dst} array at which to start.
- * @param length the number of points to transform.
- */
- public void inverseTransform (float[] src, int srcOff, float[] dst, int dstOff, int length)
- throws NoninvertibleTransformException {
- float det = getDeterminant();
- if (Math.abs(det) < ZERO) {
- throw new NoninvertibleTransformException("Determinant is zero");
- }
- while (--length >= 0) {
- float x = src[srcOff++] - m02, y = src[srcOff++] - m12;
- dst[dstOff++] = (x * m11 - y * m01) / det;
- dst[dstOff++] = (y * m00 - x * m10) / det;
- }
+ @Override // from Transform
+ public Vector transform (IVector v, Vector into) {
+ float x = v.getX(), y = v.getY();
+ return into.set(m00*x + m01*y, m10*x + m11*y);
}
- /**
- * Creates and returns a new shape that is the supplied shape transformed by this transform's
- * matrix.
- */
- public IShape createTransformedShape (IShape src) {
- if (src == null) {
- return null;
- }
- if (src instanceof Path) {
- return ((Path)src).createTransformedShape(this);
- }
- PathIterator path = src.getPathIterator(this);
- Path dst = new Path(path.getWindingRule());
- dst.append(path, false);
- return dst;
+ @Override // from Transform
+ public Vector inverseTransform (IVector v, Vector into) {
+ return invert().transform(v, into); // TODO
+ }
+
+ @Override // from Transform
+ public Transform clone () {
+ return new AffineTransform(m00, m01, m10, m11, tx, ty);
+ }
+
+ @Override // from Transform
+ public int generality () {
+ return GENERALITY;
}
@Override
public String toString () {
- return getClass().getName() +
- "[[" + m00 + ", " + m01 + ", " + m02 + "], [" + m10 + ", " + m11 + ", " + m12 + "]]";
+ return "affine [" + m00 + " " + m01 + " " + m10 + " " + m11 + " " + getTranslation() + "]";
}
- // @Override // can't declare @Override due to GWT
- public AffineTransform clone () {
- return new AffineTransform(this);
+ // we don't publicize this because it might encourage someone to do something stupid like
+ // create a new AffineTransform from another AffineTransform using this instead of clone()
+ protected AffineTransform (Transform other) {
+ this(other.getScaleX(), other.getScaleY(), other.getRotation(),
+ other.getTx(), other.getTy());
}
- @Override
- public int hashCode () {
- return Platform.hashCode(m00) ^ Platform.hashCode(m01) ^ Platform.hashCode(m02) ^
- Platform.hashCode(m10) ^ Platform.hashCode(m11) ^ Platform.hashCode(m12);
+ protected AffineTransform multiply (AffineTransform other) {
+ return multiply(other.m00, other.m01, other.m10, other.m11, other.tx, other.ty);
}
- @Override
- public boolean equals (Object obj) {
- if (obj == this) {
- return true;
- }
- if (obj instanceof AffineTransform) {
- AffineTransform t = (AffineTransform)obj;
- return m00 == t.m00 && m01 == t.m01 && m02 == t.m02 &&
- m10 == t.m10 && m11 == t.m11 && m12 == t.m12;
- }
- return false;
+ protected AffineTransform multiply (float m00, float m01, float m10, float m11,
+ float tx, float ty) {
+ return new AffineTransform(
+ this.m00 * m00 + this.m01 * m10, this.m00 * m01 + this.m01 * m11,
+ this.m10 * m00 + this.m11 * m10, this.m10 * m01 + this.m11 * m11,
+ this.m00 * tx + this.m01 * ty + this.tx, this.m10 * tx + this.m11 * ty + this.ty);
}
- /**
- * Multiplies two transforms, storing the result in the target transform.
- * @return the supplied target transform.
- */
- protected static AffineTransform multiply (AffineTransform t1, AffineTransform t2,
- AffineTransform into) {
- into.setTransform(t1.m00 * t2.m00 + t1.m10 * t2.m01, // m00
- t1.m00 * t2.m10 + t1.m10 * t2.m11, // m01
- t1.m01 * t2.m00 + t1.m11 * t2.m01, // m10
- t1.m01 * t2.m10 + t1.m11 * t2.m11, // m11
- t1.m02 * t2.m00 + t1.m12 * t2.m01 + t2.m02, // m02
- t1.m02 * t2.m10 + t1.m12 * t2.m11 + t2.m12); // m12
- return into;
- }
-
- // the values of transformation matrix
- private float m00;
- private float m10;
- private float m01;
- private float m11;
- private float m02;
- private float m12;
-
- /** The transformation {@code type}. */
- private transient int type;
-
- /** An initial type value. */
- private static final int TYPE_UNKNOWN = -1;
-
/** The min value equivalent to zero. An absolute value < ZERO is considered to be zero. */
- private static final float ZERO = 1E-10f;
+ private static final float ZERO = 1E-7f;
}
diff --git a/src/main/java/pythagoras/f/Area.java b/src/main/java/pythagoras/f/Area.java
index b3ec4cd..6b751a9 100644
--- a/src/main/java/pythagoras/f/Area.java
+++ b/src/main/java/pythagoras/f/Area.java
@@ -112,15 +112,15 @@ public class Area implements IShape, Cloneable
/**
* Transforms this area with the supplied transform.
*/
- public void transform (AffineTransform t) {
- copy(new Area(t.createTransformedShape(this)), this);
+ public void transform (Transform t) {
+ copy(new Area(Transforms.createTransformedShape(t, this)), this);
}
/**
* Creates a new area equal to this area transformed by the supplied transform.
*/
- public Area createTransformedArea (AffineTransform t) {
- return new Area(t.createTransformedShape(this));
+ public Area createTransformedArea (Transform t) {
+ return new Area(Transforms.createTransformedShape(t, this));
}
/**
@@ -258,12 +258,12 @@ public class Area implements IShape, Cloneable
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t) {
+ public PathIterator getPathIterator (Transform t) {
return new AreaPathIterator(t);
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t, float flatness) {
+ public PathIterator getPathIterator (Transform t, float flatness) {
return new FlatteningPathIterator(getPathIterator(t), flatness);
}
@@ -1159,11 +1159,11 @@ public class Area implements IShape, Cloneable
// the internal class implements PathIterator
private class AreaPathIterator implements PathIterator
{
- private final AffineTransform transform;
+ private final Transform transform;
private int curRuleIndex = 0;
private int curCoordIndex = 0;
- AreaPathIterator (AffineTransform t) {
+ AreaPathIterator (Transform t) {
this.transform = t;
}
diff --git a/src/main/java/pythagoras/f/FloatMath.java b/src/main/java/pythagoras/f/FloatMath.java
index 428d72a..2c47ae3 100644
--- a/src/main/java/pythagoras/f/FloatMath.java
+++ b/src/main/java/pythagoras/f/FloatMath.java
@@ -12,17 +12,20 @@ public class FloatMath
/** The ratio of a circle's circumference to its diameter. */
public static final float PI = (float)Math.PI;
- /** The circle constant, tau (τ) http://tauday.com/ */
- public static final float TWO_PI = (float)(Math.PI * 2);
+ /** The circle constant, Tau (τ) http://tauday.com/ */
+ public static final float TAU = (float)(Math.PI * 2);
+
+ /** Twice Pi. */
+ public static final float TWO_PI = TAU;
/** Pi times one half. */
- public static final float HALF_PI = PI * 0.5f;
+ public static final float HALF_PI = (float)(Math.PI * 0.5);
/** The base value of the natural logarithm. */
public static final float E = (float)Math.E;
/** A small number. */
- public static final float EPSILON = 0.000001f;
+ public static final float EPSILON = 0.00001f;
/**
* Computes and returns the sine of the given angle.
diff --git a/src/main/java/pythagoras/f/IPoint.java b/src/main/java/pythagoras/f/IPoint.java
index c34bd86..7d3a369 100644
--- a/src/main/java/pythagoras/f/IPoint.java
+++ b/src/main/java/pythagoras/f/IPoint.java
@@ -27,6 +27,31 @@ public interface IPoint extends Cloneable
/** Returns the Euclidian distance between this point and the supplied point. */
float distance (IPoint p);
+ /** Multiplies this point by a scale factor.
+ * @return a new point containing the result. */
+ Point mult (float s);
+
+ /** Multiplies this point by a scale factor and places the result in the supplied object.
+ * @return a reference to the result, for chaining. */
+ Point mult (float s, Point result);
+
+ /** Translates this point by the specified offset.
+ * @return a new point containing the result. */
+ Point add (float x, float y);
+
+ /** Translates this point by the specified offset and stores the result in the object provided.
+ * @return a reference to the result, for chaining. */
+ Point add (float x, float y, Point result);
+
+ /** Rotates this point around the origin by the specified angle.
+ * @return a new point containing the result. */
+ Point rotate (float angle);
+
+ /** Rotates this point around the origin by the specified angle, storing the result in the
+ * point provided.
+ * @return a reference to the result point, for chaining. */
+ Point rotate (float angle, Point result);
+
/** Returns a mutable copy of this point. */
Point clone ();
}
diff --git a/src/main/java/pythagoras/f/IShape.java b/src/main/java/pythagoras/f/IShape.java
index 108cabe..9a3f5e9 100644
--- a/src/main/java/pythagoras/f/IShape.java
+++ b/src/main/java/pythagoras/f/IShape.java
@@ -42,7 +42,7 @@ public interface IShape
*
* @param at if supplied, the points in the path are transformed using this.
*/
- PathIterator getPathIterator (AffineTransform at);
+ PathIterator getPathIterator (Transform at);
/**
* Returns an iterator over the path described by this shape.
@@ -52,5 +52,5 @@ public interface IShape
* distance the lines are allowed to deviate from the approximated curve, thus a higher
* flatness value generally allows for a path with fewer segments.
*/
- PathIterator getPathIterator (AffineTransform at, float flatness);
+ PathIterator getPathIterator (Transform at, float flatness);
}
diff --git a/src/main/java/pythagoras/f/IdentityTransform.java b/src/main/java/pythagoras/f/IdentityTransform.java
new file mode 100644
index 0000000..d7e1f22
--- /dev/null
+++ b/src/main/java/pythagoras/f/IdentityTransform.java
@@ -0,0 +1,113 @@
+//
+// Pythagoras - a collection of geometry classes
+// http://github.com/samskivert/pythagoras
+
+package pythagoras.f;
+
+/**
+ * Implements the identity transform.
+ */
+public class IdentityTransform extends AbstractTransform
+{
+ /** Identifies the identity transform in {@link #generality}. */
+ public static final int GENERALITY = 0;
+
+ @Override // from Transform
+ public float getUniformScale () {
+ return 1;
+ }
+
+ @Override // from Transform
+ public float getScaleX () {
+ return 1;
+ }
+
+ @Override // from Transform
+ public float getScaleY () {
+ return 1;
+ }
+
+ @Override // from Transform
+ public float getRotation () {
+ return 0;
+ }
+
+ @Override // from Transform
+ public float getTx () {
+ return 0;
+ }
+
+ @Override // from Transform
+ public float getTy () {
+ return 0;
+ }
+
+ @Override // from Transform
+ public Transform invert () {
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform concatenate (Transform other) {
+ return other;
+ }
+
+ @Override // from Transform
+ public Transform preConcatenate (Transform other) {
+ return other;
+ }
+
+ @Override // from Transform
+ public Transform lerp (Transform other, float t) {
+ throw new UnsupportedOperationException(); // TODO
+ }
+
+ @Override // from Transform
+ public Point transform (IPoint p, Point into) {
+ return into.set(p);
+ }
+
+ @Override // from Transform
+ public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int count) {
+ for (int ii = 0; ii < count; ii++) {
+ transform(src[srcOff++], dst[dstOff++]);
+ }
+ }
+
+ @Override // from Transform
+ public void transform (float[] src, int srcOff, float[] dst, int dstOff, int count) {
+ for (int ii = 0; ii < count; ii++) {
+ dst[dstOff++] = src[srcOff++];
+ }
+ }
+
+ @Override // from Transform
+ public Point inverseTransform (IPoint p, Point into) {
+ return into.set(p);
+ }
+
+ @Override // from Transform
+ public Vector transform (IVector v, Vector into) {
+ return into.set(v);
+ }
+
+ @Override // from Transform
+ public Vector inverseTransform (IVector v, Vector into) {
+ return into.set(v);
+ }
+
+ @Override // from Transform
+ public Transform clone () {
+ return this;
+ }
+
+ @Override // from Transform
+ public int generality () {
+ return GENERALITY;
+ }
+
+ @Override
+ public String toString () {
+ return "ident";
+ }
+}
diff --git a/src/main/java/pythagoras/f/NonUniformTransform.java b/src/main/java/pythagoras/f/NonUniformTransform.java
new file mode 100644
index 0000000..4cef0ba
--- /dev/null
+++ b/src/main/java/pythagoras/f/NonUniformTransform.java
@@ -0,0 +1,213 @@
+//
+// Pythagoras - a collection of geometry classes
+// http://github.com/samskivert/pythagoras
+
+package pythagoras.f;
+
+/**
+ * Implements a uniform (translation, rotation, scaleX, scaleY) transform.
+ */
+public class NonUniformTransform extends AbstractTransform
+{
+ /** Identifies the uniform transform in {@link #generality}. */
+ public static final int GENERALITY = 3;
+
+ /** The scale components of this transform. */
+ public float scaleX, scaleY;
+
+ /** The rotation component of this transform (in radians). */
+ public float rotation;
+
+ /** The translation components of this transform. */
+ public float tx, ty;
+
+ /** Creates a uniform transform with zero translation and rotation, and unit scale. */
+ public NonUniformTransform () {
+ this.scaleX = this.scaleY = 1;
+ }
+
+ /** Creates a uniform transform with the specified translation, rotation and scale. */
+ public NonUniformTransform (float scaleX, float scaleY, float rotation, float tx, float ty) {
+ setScale(scaleX, scaleY);
+ setRotation(rotation);
+ setTranslation(tx, ty);
+ }
+
+ @Override // from Transform
+ public float getUniformScale () {
+ return (scaleX + scaleY) / 2; // TODO: is this sane
+ }
+
+ @Override // from Transform
+ public float getScaleX () {
+ return scaleX;
+ }
+
+ @Override // from Transform
+ public float getScaleY () {
+ return scaleY;
+ }
+
+ @Override // from Transform
+ public float getRotation () {
+ return rotation;
+ }
+
+ @Override // from Transform
+ public float getTx () {
+ return tx;
+ }
+
+ @Override // from Transform
+ public float getTy () {
+ return ty;
+ }
+
+ @Override // from Transform
+ public Transform setUniformScale (float scale) {
+ setScaleX(scale);
+ setScaleY(scale);
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setScaleX (float scaleX) {
+ if (scaleX == 0) throw new IllegalArgumentException("Scale (x) must not be zero.");
+ this.scaleX = scaleX;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setScaleY (float scaleY) {
+ if (scaleY == 0) throw new IllegalArgumentException("Scale (y) must not be zero.");
+ this.scaleY = scaleY;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setRotation (float angle) {
+ this.rotation = angle;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setTx (float tx) {
+ this.tx = tx;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setTy (float ty) {
+ this.ty = ty;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform invert () {
+ Vector iscale = new Vector(1f / scaleX, 1f / scaleY);
+ Vector t = new Vector(tx, ty).negateLocal().rotateLocal(-rotation).multLocal(iscale);
+ return new NonUniformTransform(iscale.x, iscale.y, -rotation, t.x, t.y);
+ }
+
+ @Override // from Transform
+ public Transform concatenate (Transform other) {
+ if (generality() < other.generality()) {
+ return other.preConcatenate(this);
+ }
+
+ float otx = other.getTx(), oty = other.getTy();
+ float sina = FloatMath.sin(rotation), cosa = FloatMath.cos(rotation);
+ float ntx = (otx*cosa - oty*sina) * scaleX + getTx();
+ float nty = (otx*sina + oty*cosa) * scaleY + getTy();
+
+ float nrotation = FloatMath.normalizeAngle(rotation + other.getRotation());
+ float nscaleX = scaleX * other.getScaleX();
+ float nscaleY = scaleY * other.getScaleY();
+ return new NonUniformTransform(nscaleX, nscaleY, nrotation, ntx, nty);
+ }
+
+ @Override // from Transform
+ public Transform preConcatenate (Transform other) {
+ if (generality() < other.generality()) {
+ return other.concatenate(this);
+ }
+
+ float tx = getTx(), ty = getTy();
+ float sina = FloatMath.sin(other.getRotation()), cosa = FloatMath.cos(other.getRotation());
+ float ntx = (tx*cosa - ty*sina) * other.getScaleX() + other.getTx();
+ float nty = (tx*sina + ty*cosa) * other.getScaleY() + other.getTy();
+ float nrotation = FloatMath.normalizeAngle(other.getRotation() + rotation);
+ float nscaleX = other.getScaleX() * scaleX;
+ float nscaleY = other.getScaleY() * scaleY;
+ return new NonUniformTransform(nscaleX, nscaleY, nrotation, ntx, nty);
+ }
+
+ @Override // from Transform
+ public Transform lerp (Transform other, float t) {
+ if (generality() < other.generality()) {
+ return other.lerp(this, -t); // TODO: is this correct?
+ }
+
+ Vector nt = getTranslation().lerpLocal(other.getTranslation(), t);
+ float nrotation = FloatMath.lerpa(rotation, other.getRotation(), t);
+ float nscaleX = FloatMath.lerp(scaleX, other.getScaleX(), t);
+ float nscaleY = FloatMath.lerp(scaleY, other.getScaleY(), t);
+ return new NonUniformTransform(nscaleX, nscaleY, nrotation, nt.x, nt.y);
+ }
+
+ @Override // from Transform
+ public Point transform (IPoint p, Point into) {
+ return Points.transform(p.getX(), p.getY(), scaleX, scaleY, rotation, tx, ty, into);
+ }
+
+ @Override // from Transform
+ public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int count) {
+ float sina = FloatMath.sin(rotation), cosa = FloatMath.cos(rotation);
+ for (int ii = 0; ii < count; ii++) {
+ IPoint s = src[srcOff++];
+ Points.transform(s.getX(), s.getY(), scaleX, scaleY, sina, cosa, tx, ty, dst[dstOff++]);
+ }
+ }
+
+ @Override // from Transform
+ public void transform (float[] src, int srcOff, float[] dst, int dstOff, int count) {
+ Point p = new Point();
+ float sina = FloatMath.sin(rotation), cosa = FloatMath.cos(rotation);
+ for (int ii = 0; ii < count; ii++) {
+ Points.transform(src[srcOff++], src[srcOff++], scaleX, scaleY, sina, cosa, tx, ty, p);
+ dst[dstOff++] = p.x;
+ dst[dstOff++] = p.y;
+ }
+ }
+
+ @Override // from Transform
+ public Point inverseTransform (IPoint p, Point into) {
+ return Points.inverseTransform(p.getX(), p.getY(), scaleX, scaleY, rotation, tx, ty, into);
+ }
+
+ @Override // from Transform
+ public Vector transform (IVector v, Vector into) {
+ return Vectors.transform(v.getX(), v.getY(), scaleX, scaleY, rotation, into);
+ }
+
+ @Override // from Transform
+ public Vector inverseTransform (IVector v, Vector into) {
+ return Vectors.inverseTransform(v.getX(), v.getY(), scaleX, scaleY, rotation, into);
+ }
+
+ @Override // from Transform
+ public Transform clone () {
+ return new NonUniformTransform(scaleX, scaleY, rotation, tx, ty);
+ }
+
+ @Override // from Transform
+ public int generality () {
+ return GENERALITY;
+ }
+
+ @Override
+ public String toString () {
+ return "nonunif [scale=" + getScale() + ", rot=" + rotation +
+ ", trans=" + getTranslation() + "]";
+ }
+}
diff --git a/src/main/java/pythagoras/f/Path.java b/src/main/java/pythagoras/f/Path.java
index 56f86b2..68f212c 100644
--- a/src/main/java/pythagoras/f/Path.java
+++ b/src/main/java/pythagoras/f/Path.java
@@ -157,11 +157,11 @@ public final class Path implements IShape, Cloneable
pointSize = 0;
}
- public void transform (AffineTransform t) {
+ public void transform (Transform t) {
t.transform(points, 0, points, 0, pointSize / 2);
}
- public IShape createTransformedShape (AffineTransform t) {
+ public IShape createTransformedShape (Transform t) {
Path p = clone();
if (t != null) {
p.transform(t);
@@ -241,12 +241,12 @@ public final class Path implements IShape, Cloneable
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t) {
+ public PathIterator getPathIterator (Transform t) {
return new Iterator(this, t);
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t, float flatness) {
+ public PathIterator getPathIterator (Transform t, float flatness) {
return new FlatteningPathIterator(getPathIterator(t), flatness);
}
@@ -309,13 +309,13 @@ public final class Path implements IShape, Cloneable
private Path p;
/** The path iterator transformation. */
- private AffineTransform t;
+ private Transform t;
Iterator (Path path) {
this(path, null);
}
- Iterator (Path path, AffineTransform at) {
+ Iterator (Path path, Transform at) {
this.p = path;
this.t = at;
}
diff --git a/src/main/java/pythagoras/f/Point.java b/src/main/java/pythagoras/f/Point.java
index 25a9092..9cc3998 100644
--- a/src/main/java/pythagoras/f/Point.java
+++ b/src/main/java/pythagoras/f/Point.java
@@ -27,44 +27,46 @@ public class Point extends AbstractPoint implements Serializable
* Constructs a point at the specified coordinates.
*/
public Point (float x, float y) {
- setLocation(x, y);
+ set(x, y);
}
/**
* Constructs a point with coordinates equal to the supplied point.
*/
public Point (IPoint p) {
- setLocation(p.getX(), p.getY());
+ set(p.getX(), p.getY());
}
- /**
- * Sets the coordinates of this point to be equal to those of the supplied point.
- */
- public void setLocation (IPoint p) {
- setLocation(p.getX(), p.getY());
+ /** Sets the coordinates of this point to be equal to those of the supplied point.
+ * @return a reference to this this, for chaining. */
+ public Point set (IPoint p) {
+ return set(p.getX(), p.getY());
}
- /**
- * Sets the coordinates of this point to the supplied values.
- */
- public void setLocation (float x, float y) {
+ /** Sets the coordinates of this point to the supplied values.
+ * @return a reference to this this, for chaining. */
+ public Point set (float x, float y) {
this.x = x;
this.y = y;
+ return this;
}
- /**
- * A synonym for {@link #setLocation}.
- */
- public void move (float x, float y) {
- setLocation(x, y);
+ /** Multiplies this point by a scale factor.
+ * @return a a reference to this point, for chaining. */
+ public Point multLocal (float s) {
+ return mult(s, this);
}
- /**
- * Translates this point by the specified offset.
- */
- public void translate (float dx, float dy) {
- x += dx;
- y += dy;
+ /** Translates this point by the specified offset.
+ * @return a reference to this point, for chaining. */
+ public Point addLocal (float dx, float dy) {
+ return add(dx, dy, this);
+ }
+
+ /** Rotates this point in-place by the specified angle.
+ * @return a reference to this point, for chaining. */
+ public Point rotateLocal (float angle) {
+ return rotate(angle, this);
}
@Override // from interface IPoint
diff --git a/src/main/java/pythagoras/f/Points.java b/src/main/java/pythagoras/f/Points.java
index e393ae4..ab95ab6 100644
--- a/src/main/java/pythagoras/f/Points.java
+++ b/src/main/java/pythagoras/f/Points.java
@@ -25,6 +25,32 @@ public class Points
return (float)Math.sqrt(distanceSq(x1, y1, x2, y2));
}
+ /** Transforms a point as specified, storing the result in the point provided.
+ * @return a reference to the result point, for chaining. */
+ public static Point transform (float x, float y, float sx, float sy, float rotation,
+ float tx, float ty, Point result) {
+ return transform(x, y, sx, sy, FloatMath.sin(rotation), FloatMath.cos(rotation), tx, ty,
+ result);
+ }
+
+ /** Transforms a point as specified, storing the result in the point provided.
+ * @return a reference to the result point, for chaining. */
+ public static Point transform (float x, float y, float sx, float sy, float sina, float cosa,
+ float tx, float ty, Point result) {
+ return result.set((x*cosa - y*sina) * sx + tx, (x*sina + y*cosa) * sy + ty);
+ }
+
+ /** Inverse transforms a point as specified, storing the result in the point provided.
+ * @return a reference to the result point, for chaining. */
+ public static Point inverseTransform (float x, float y, float sx, float sy, float rotation,
+ float tx, float ty, Point result) {
+ x -= tx; y -= ty; // untranslate
+ float sinnega = FloatMath.sin(-rotation), cosnega = FloatMath.cos(-rotation);
+ float nx = (x * cosnega - y * sinnega); // unrotate
+ float ny = (x * sinnega + y * cosnega);
+ return result.set(nx / sx, ny / sy); // unscale
+ }
+
/**
* Returns a string describing the supplied point, of the form +x+y,
* +x-y, -x-y, etc.
diff --git a/src/main/java/pythagoras/f/RectangularShape.java b/src/main/java/pythagoras/f/RectangularShape.java
index 2ea57e2..c47c4aa 100644
--- a/src/main/java/pythagoras/f/RectangularShape.java
+++ b/src/main/java/pythagoras/f/RectangularShape.java
@@ -170,7 +170,7 @@ public abstract class RectangularShape implements IRectangularShape
}
@Override // from interface IShape
- public PathIterator getPathIterator (AffineTransform t, float flatness) {
+ public PathIterator getPathIterator (Transform t, float flatness) {
return new FlatteningPathIterator(getPathIterator(t), flatness);
}
}
diff --git a/src/main/java/pythagoras/f/RigidTransform.java b/src/main/java/pythagoras/f/RigidTransform.java
new file mode 100644
index 0000000..4da8d5f
--- /dev/null
+++ b/src/main/java/pythagoras/f/RigidTransform.java
@@ -0,0 +1,172 @@
+//
+// Pythagoras - a collection of geometry classes
+// http://github.com/samskivert/pythagoras
+
+package pythagoras.f;
+
+/**
+ * Implements a rigid body (translation, rotation) transform.
+ */
+public class RigidTransform extends AbstractTransform
+{
+ /** Identifies the rigid body transform in {@link #generality}. */
+ public static final int GENERALITY = 1;
+
+ /** The rotation component of this transform (in radians). */
+ public float rotation;
+
+ /** The translation components of this transform. */
+ public float tx, ty;
+
+ /** Creates a rigid body transform with zero translation and rotation. */
+ public RigidTransform () {
+ }
+
+ /** Creates a rigid body transform with the specified translation and rotation. */
+ public RigidTransform (float rotation, float tx, float ty) {
+ setRotation(rotation);
+ setTranslation(tx, ty);
+ }
+
+ @Override // from Transform
+ public float getUniformScale () {
+ return 1;
+ }
+
+ @Override // from Transform
+ public float getScaleX () {
+ return 1;
+ }
+
+ @Override // from Transform
+ public float getScaleY () {
+ return 1;
+ }
+
+ @Override // from Transform
+ public float getRotation () {
+ return rotation;
+ }
+
+ @Override // from Transform
+ public float getTx () {
+ return tx;
+ }
+
+ @Override // from Transform
+ public float getTy () {
+ return ty;
+ }
+
+ @Override // from Transform
+ public Transform setRotation (float angle) {
+ this.rotation = angle;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setTx (float tx) {
+ this.tx = tx;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setTy (float ty) {
+ this.ty = ty;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform invert () {
+ Vector t = getTranslation().negateLocal().rotateLocal(-rotation);
+ return new RigidTransform(-rotation, t.x, t.y);
+ }
+
+ @Override // from Transform
+ public Transform concatenate (Transform other) {
+ if (generality() < other.generality()) {
+ return other.preConcatenate(this);
+ }
+
+ Vector nt = other.getTranslation();
+ nt.rotateAndAdd(rotation, getTranslation(), nt);
+ float nrotation = FloatMath.normalizeAngle(rotation + other.getRotation());
+ return new RigidTransform(nrotation, nt.x, nt.y);
+ }
+
+ @Override // from Transform
+ public Transform preConcatenate (Transform other) {
+ if (generality() < other.generality()) {
+ return other.concatenate(this);
+ }
+
+ Vector nt = getTranslation();
+ nt.rotateAndAdd(other.getRotation(), other.getTranslation(), nt);
+ float nrotation = FloatMath.normalizeAngle(other.getRotation() + rotation);
+ return new RigidTransform(nrotation, nt.x, nt.y);
+ }
+
+ @Override // from Transform
+ public Transform lerp (Transform other, float t) {
+ if (generality() < other.generality()) {
+ return other.lerp(this, -t); // TODO: is this correct?
+ }
+ Vector nt = getTranslation().lerpLocal(other.getTranslation(), t);
+ return new RigidTransform(FloatMath.lerpa(rotation, other.getRotation(), t), nt.x, nt.y);
+ }
+
+ @Override // from Transform
+ public Point transform (IPoint p, Point into) {
+ return Points.transform(p.getX(), p.getY(), 1, 1, rotation, tx, ty, into);
+ }
+
+ @Override // from Transform
+ public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int count) {
+ float sina = FloatMath.sin(rotation), cosa = FloatMath.cos(rotation);
+ for (int ii = 0; ii < count; ii++) {
+ IPoint s = src[srcOff++];
+ Points.transform(s.getX(), s.getY(), 1, 1, sina, cosa, tx, ty, dst[dstOff++]);
+ }
+ }
+
+ @Override // from Transform
+ public void transform (float[] src, int srcOff, float[] dst, int dstOff, int count) {
+ Point p = new Point();
+ float sina = FloatMath.sin(rotation), cosa = FloatMath.cos(rotation);
+ for (int ii = 0; ii < count; ii++) {
+ Points.transform(src[srcOff++], src[srcOff++], 1, 1, sina, cosa, tx, ty, p);
+ dst[dstOff++] = p.x;
+ dst[dstOff++] = p.y;
+ }
+ }
+
+ @Override // from Transform
+ public Point inverseTransform (IPoint p, Point into) {
+ return Points.inverseTransform(p.getX(), p.getY(), 1, 1, rotation, tx, ty, into);
+ }
+
+ @Override // from Transform
+ public Vector transform (IVector v, Vector into) {
+ return v.rotate(rotation, into);
+ }
+
+ @Override // from Transform
+ public Vector inverseTransform (IVector v, Vector into) {
+ return v.rotate(-rotation, into);
+ }
+
+ @Override // from Transform
+ public Transform clone () {
+ return new RigidTransform(rotation, tx, ty);
+ }
+
+ @Override // from Transform
+ public int generality () {
+ return GENERALITY;
+ }
+
+ @Override
+ public String toString () {
+ return "rigid [rot=" + rotation + ", trans=" + getTranslation() + "]";
+ }
+}
diff --git a/src/main/java/pythagoras/f/Transform.java b/src/main/java/pythagoras/f/Transform.java
index b2bd6f1..88d27b9 100644
--- a/src/main/java/pythagoras/f/Transform.java
+++ b/src/main/java/pythagoras/f/Transform.java
@@ -6,52 +6,32 @@ package pythagoras.f;
/**
* Represents a geometric transform. Specialized implementations exist for identity, rigid body,
- * uniform, affine and general transforms.
+ * uniform, non-uniform, and affine transforms.
*/
public interface Transform
{
- /** Sets the translation component of this transform.
- * @throws UnsupportedOperationException if the transform is not rigid body or greater. */
- void setTranslation (float tx, float ty);
+ /** Returns the uniform scale applied by this transform. The uniform scale will be approximated
+ * for non-uniform transforms. */
+ float getUniformScale ();
- /** Sets the x-component of this transform's translation.
- * @throws UnsupportedOperationException if the transform is not rigid body or greater. */
- void setTx (float tx);
+ /** Returns the scale vector for this transform. */
+ Vector getScale ();
- /** Sets the y-component of this transform's translation.
- * @throws UnsupportedOperationException if the transform is not rigid body or greater. */
- void setTy (float ty);
+ /** Returns the x-component of the scale applied by this transform. Note that this will be
+ * extracted and therefore approximate for affine transforms. */
+ float getScaleX ();
- /** Sets the rotation component of this transform.
- * @throws UnsupportedOperationException if the transform is not rigid body or greater. */
- void setRotation (float angle);
+ /** Returns the y-component of the scale applied by this transform. Note that this will be
+ * extracted and therefore approximate for affine transforms. */
+ float getScaleY ();
- /** Sets the uniform scale of this transform.
- * @throws UnsupportedOperationException if the transform is not uniform or greater. */
- void setScale (float scale);
+ /** Returns the rotation applied by this transform. Note that the rotation is extracted and
+ * therefore approximate for affine transforms.
+ * @throws NoninvertibleTransformException if the transform is not invertible. */
+ float getRotation ();
- /** Sets the x and y scale of this transform.
- * @throws UnsupportedOperationException if the transform is not affine or greater. */
- void setScale (float scaleX, float scaleY);
-
- /** Sets the x scale of this transform.
- * @throws UnsupportedOperationException if the transform is not affine or greater. */
- void setScaleX (float scaleX);
-
- /** Sets the y scale of this transform.
- * @throws UnsupportedOperationException if the transform is not affine or greater. */
- void setScaleY (float scaleY);
-
- /** Sets the affine transform matrix.
- * @throws UnsupportedOperationException if the transform is not affine or greater. */
- void setTransform (float m00, float m01, float m10, float m11,
- float tx, float ty);
-
- /** Sets the general transform matrix.
- * @throws UnsupportedOperationException if the transform is not general. */
- void setTransform (float m00, float m01, float m02,
- float m10, float m11, float m12,
- float m20, float m21, float m22);
+ /** Returns the translation vector for this transform. */
+ Vector getTranslation ();
/** Returns the x-coordinate of the translation component. */
float getTx ();
@@ -59,45 +39,114 @@ public interface Transform
/** Returns the y-coordinate of the translation component. */
float getTy ();
- /** Returns the rotation applied by this transform. Note that the rotation is extracted and
- * therefore approximate for affine and general transforms. */
- float getRotation (); // will be extracted from affine+
+ /** Sets the uniform scale of this transform.
+ * @return this instance, for chaining.
+ * @throws IllegalArgumentException if the supplied scale is zero.
+ * @throws UnsupportedOperationException if the transform is not uniform or greater. */
+ Transform setUniformScale (float scale);
- /** Returns the uniform scale applied by this transform. Note that the uniform scale will be
- * approximated for non-uniform transforms (affine and general). */
- float getScale (); // will be extracted/approximated for affine+
+ /** Sets the x and y scale of this transform.
+ * @return this instance, for chaining.
+ * @throws IllegalArgumentException if either supplied scale is zero.
+ * @throws UnsupportedOperationException if the transform is not affine or greater. */
+ Transform setScale (float scaleX, float scaleY);
- /** Returns the x-component of the scale applied by this transform. */
- float getScaleX (); // will be extracted from affine+
+ /** Sets the x scale of this transform.
+ * @return this instance, for chaining.
+ * @throws IllegalArgumentException if the supplied scale is zero.
+ * @throws UnsupportedOperationException if the transform is not affine or greater. */
+ Transform setScaleX (float scaleX);
- /** Returns the y-component of the scale applied by this transform. */
- float getScaleY (); // will be extracted from affine+
+ /** Sets the y scale of this transform.
+ * @return this instance, for chaining.
+ * @throws IllegalArgumentException if the supplied scale is zero.
+ * @throws UnsupportedOperationException if the transform is not affine or greater. */
+ Transform setScaleY (float scaleY);
- /** Returns the inverse of this transform.
+ /** Sets the rotation component of this transform.
+ * @return this instance, for chaining.
+ * @throws UnsupportedOperationException if the transform is not rigid body or greater. */
+ Transform setRotation (float angle);
+
+ /** Sets the translation component of this transform.
+ * @return this instance, for chaining.
+ * @throws UnsupportedOperationException if the transform is not rigid body or greater. */
+ Transform setTranslation (float tx, float ty);
+
+ /** Sets the x-component of this transform's translation.
+ * @return this instance, for chaining.
+ * @throws UnsupportedOperationException if the transform is not rigid body or greater. */
+ Transform setTx (float tx);
+
+ /** Sets the y-component of this transform's translation.
+ * @return this instance, for chaining.
+ * @throws UnsupportedOperationException if the transform is not rigid body or greater. */
+ Transform setTy (float ty);
+
+ /** Sets the affine transform matrix.
+ * @return this instance, for chaining.
+ * @throws UnsupportedOperationException if the transform is not affine or greater. */
+ Transform setTransform (float m00, float m01, float m10, float m11,
+ float tx, float ty);
+
+ /** Returns a new transform that represents the inverse of this transform.
* @throws NoninvertibleTransformException if the transform is not invertible. */
Transform invert ();
- /** Composes this transform with the supplied transform (i.e. {@code this x other}). */
- Transform compose (Transform other);
+ /** Returns a new transform comprised of the concatenation of {@code other} to this transform
+ * (i.e. {@code this x other}). */
+ Transform concatenate (Transform other);
- /** Returns the linear interpolation between this transform and the specified other. */
+ /** Returns a new transform comprised of the concatenation of this transform to {@code other}
+ * (i.e. {@code other x this}). */
+ Transform preConcatenate (Transform other);
+
+ /** Returns a new transform comprised of the linear interpolation between this transform and
+ * the specified other. */
Transform lerp (Transform other, float t);
- /** Transforms the supplied point, writing the result into {@code into}, which may reference
- * the same object as {@code p}. */
- void transform (IPoint p, Point into);
+ /** Transforms the supplied point, writing the result into {@code into}.
+ * @param into a point into which to store the result, may be the same object as {@code p}.
+ * @return {@code into} for chaining. */
+ Point transform (IPoint p, Point into);
- /** Inverse transforms the supplied point, writing the result into {@code into}, which may
- * reference the same object as {@code p}.
+ /** Transforms the supplied points.
+ * @param src the points to be transformed.
+ * @param srcOff the offset into the {@code src} array at which to start.
+ * @param dst the points into which to store the transformed points. May be {@code src}.
+ * @param dstOff the offset into the {@code dst} array at which to start.
+ * @param count the number of points to transform. */
+ void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int count);
+
+ /** Transforms the supplied points.
+ * @param src the points to be transformed (as {@code [x, y, x, y, ...]}).
+ * @param srcOff the offset into the {@code src} array at which to start.
+ * @param dst the points into which to store the transformed points. May be {@code src}.
+ * @param dstOff the offset into the {@code dst} array at which to start.
+ * @param count the number of points to transform. */
+ void transform (float[] src, int srcOff, float[] dst, int dstOff, int count);
+
+ /** Inverse transforms the supplied point, writing the result into {@code into}.
+ * @param into a point into which to store the result, may be the same object as {@code p}.
+ * @return {@code into}, for chaining.
* @throws NoninvertibleTransformException if the transform is not invertible. */
- void inverseTransform (IPoint p, Point into);
+ Point inverseTransform (IPoint p, Point into);
- /** Transforms the supplied vector, writing the result into {@code into}, which may reference
- * the same object as {@code v}. */
- void transform (IVector v, Vector into);
+ /** Transforms the supplied vector, writing the result into {@code into}.
+ * @param into a vector into which to store the result, may be the same object as {@code v}.
+ * @return {@code into}, for chaining. */
+ Vector transform (IVector v, Vector into);
- /** Inverse transforms the supplied vector, writing the result into {@code into}, which may
- * reference the same object as {@code v}.
+ /** Inverse transforms the supplied vector, writing the result into {@code into}.
+ * @param into a vector into which to store the result, may be the same object as {@code v}.
+ * @return {@code into}, for chaining.
* @throws NoninvertibleTransformException if the transform is not invertible. */
- void inverseTransform (IVector v, Vector into);
+ Vector inverseTransform (IVector v, Vector into);
+
+ /** Returns a clone of this transform. */
+ Transform clone ();
+
+ /** Returns an integer that increases monotonically with the generality of the transform
+ * implementation. Used internally when combining transforms. */
+ int generality ();
}
diff --git a/src/main/java/pythagoras/f/Transforms.java b/src/main/java/pythagoras/f/Transforms.java
new file mode 100644
index 0000000..cc74298
--- /dev/null
+++ b/src/main/java/pythagoras/f/Transforms.java
@@ -0,0 +1,28 @@
+//
+// Pythagoras - a collection of geometry classes
+// http://github.com/samskivert/pythagoras
+
+package pythagoras.f;
+
+/**
+ * {@link Transform} related utility methods.
+ */
+public class Transforms
+{
+ /**
+ * Creates and returns a new shape that is the supplied shape transformed by this transform's
+ * matrix.
+ */
+ public static IShape createTransformedShape (Transform t, IShape src) {
+ if (src == null) {
+ return null;
+ }
+ if (src instanceof Path) {
+ return ((Path)src).createTransformedShape(t);
+ }
+ PathIterator path = src.getPathIterator(t);
+ Path dst = new Path(path.getWindingRule());
+ dst.append(path, false);
+ return dst;
+ }
+}
diff --git a/src/main/java/pythagoras/f/UniformTransform.java b/src/main/java/pythagoras/f/UniformTransform.java
new file mode 100644
index 0000000..5e6cd81
--- /dev/null
+++ b/src/main/java/pythagoras/f/UniformTransform.java
@@ -0,0 +1,192 @@
+//
+// Pythagoras - a collection of geometry classes
+// http://github.com/samskivert/pythagoras
+
+package pythagoras.f;
+
+/**
+ * Implements a uniform (translation, rotation, scale) transform.
+ */
+public class UniformTransform extends AbstractTransform
+{
+ /** Identifies the uniform transform in {@link #generality}. */
+ public static final int GENERALITY = 2;
+
+ /** The uniform scale component of this transform. */
+ public float scale;
+
+ /** The rotation component of this transform (in radians). */
+ public float rotation;
+
+ /** The translation components of this transform. */
+ public float tx, ty;
+
+ /** Creates a uniform transform with zero translation and rotation, and unit scale. */
+ public UniformTransform () {
+ setUniformScale(1);
+ }
+
+ /** Creates a uniform transform with the specified translation, rotation and scale. */
+ public UniformTransform (float scale, float rotation, float tx, float ty) {
+ setUniformScale(scale);
+ setRotation(rotation);
+ setTranslation(tx, ty);
+ }
+
+ @Override // from Transform
+ public float getUniformScale () {
+ return scale;
+ }
+
+ @Override // from Transform
+ public float getScaleX () {
+ return scale;
+ }
+
+ @Override // from Transform
+ public float getScaleY () {
+ return scale;
+ }
+
+ @Override // from Transform
+ public float getRotation () {
+ return rotation;
+ }
+
+ @Override // from Transform
+ public float getTx () {
+ return tx;
+ }
+
+ @Override // from Transform
+ public float getTy () {
+ return ty;
+ }
+
+ @Override // from Transform
+ public Transform setUniformScale (float scale) {
+ if (scale == 0) throw new IllegalArgumentException("Scale must be non-zero.");
+ this.scale = scale;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setRotation (float angle) {
+ this.rotation = angle;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setTx (float tx) {
+ this.tx = tx;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform setTy (float ty) {
+ this.ty = ty;
+ return this;
+ }
+
+ @Override // from Transform
+ public Transform invert () {
+ float nscale = 1f / scale, nrotation = -rotation;
+ Vector t = getTranslation().negateLocal().rotateLocal(nrotation).multLocal(nscale);
+ return new UniformTransform(nscale, nrotation, t.x, t.y);
+ }
+
+ @Override // from Transform
+ public Transform concatenate (Transform other) {
+ if (generality() < other.generality()) {
+ return other.preConcatenate(this);
+ }
+
+ Vector nt = other.getTranslation();
+ nt.rotateScaleAndAdd(rotation, scale, getTranslation(), nt);
+ float nrotation = FloatMath.normalizeAngle(rotation + other.getRotation());
+ float nscale = scale * other.getUniformScale();
+ return new UniformTransform(nscale, nrotation, nt.x, nt.y);
+ }
+
+ @Override // from Transform
+ public Transform preConcatenate (Transform other) {
+ if (generality() < other.generality()) {
+ return other.concatenate(this);
+ }
+
+ Vector nt = getTranslation();
+ nt.rotateScaleAndAdd(other.getRotation(), other.getUniformScale(),
+ other.getTranslation(), nt);
+ float nrotation = FloatMath.normalizeAngle(other.getRotation() + rotation);
+ float nscale = other.getUniformScale() * scale;
+ return new UniformTransform(nscale, nrotation, nt.x, nt.y);
+ }
+
+ @Override // from Transform
+ public Transform lerp (Transform other, float t) {
+ if (generality() < other.generality()) {
+ return other.lerp(this, -t); // TODO: is this correct?
+ }
+
+ Vector nt = getTranslation().lerpLocal(other.getTranslation(), t);
+ float nrotation = FloatMath.lerpa(rotation, other.getRotation(), t);
+ float nscale = FloatMath.lerp(scale, other.getUniformScale(), t);
+ return new UniformTransform(nscale, nrotation, nt.x, nt.y);
+ }
+
+ @Override // from Transform
+ public Point transform (IPoint p, Point into) {
+ return Points.transform(p.getX(), p.getY(), scale, scale, rotation, tx, ty, into);
+ }
+
+ @Override // from Transform
+ public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int count) {
+ float sina = FloatMath.sin(rotation), cosa = FloatMath.cos(rotation);
+ for (int ii = 0; ii < count; ii++) {
+ IPoint p = src[srcOff++];
+ Points.transform(p.getX(), p.getY(), scale, scale, sina, cosa, tx, ty, dst[dstOff++]);
+ }
+ }
+
+ @Override // from Transform
+ public void transform (float[] src, int srcOff, float[] dst, int dstOff, int count) {
+ Point p = new Point();
+ float sina = FloatMath.sin(rotation), cosa = FloatMath.cos(rotation);
+ for (int ii = 0; ii < count; ii++) {
+ Points.transform(src[srcOff++], src[srcOff++], scale, scale, sina, cosa, tx, ty, p);
+ dst[dstOff++] = p.x;
+ dst[dstOff++] = p.y;
+ }
+ }
+
+ @Override // from Transform
+ public Point inverseTransform (IPoint p, Point into) {
+ return Points.inverseTransform(p.getX(), p.getY(), scale, scale, rotation, tx, ty, into);
+ }
+
+ @Override // from Transform
+ public Vector transform (IVector v, Vector into) {
+ return Vectors.transform(v.getX(), v.getY(), scale, scale, rotation, into);
+ }
+
+ @Override // from Transform
+ public Vector inverseTransform (IVector v, Vector into) {
+ return Vectors.inverseTransform(v.getX(), v.getY(), scale, scale, rotation, into);
+ }
+
+ @Override // from Transform
+ public Transform clone () {
+ return new UniformTransform(scale, rotation, tx, ty);
+ }
+
+ @Override // from Transform
+ public int generality () {
+ return GENERALITY;
+ }
+
+ @Override
+ public String toString () {
+ return "uniform [scale=" + scale + ", rot=" + rotation +
+ ", trans=" + getTranslation() + "]";
+ }
+}
diff --git a/src/main/java/pythagoras/f/Vector.java b/src/main/java/pythagoras/f/Vector.java
index 43751f7..ff6ac9c 100644
--- a/src/main/java/pythagoras/f/Vector.java
+++ b/src/main/java/pythagoras/f/Vector.java
@@ -29,16 +29,6 @@ public class Vector extends AbstractVector
public Vector () {
}
- @Override // from AbstractVector
- public float getX () {
- return x;
- }
-
- @Override // from AbstractVector
- public float getY () {
- return y;
- }
-
/** Negates this vector in-place.
* @return a reference to this vector, for chaining. */
public Vector negateLocal () {
@@ -118,4 +108,14 @@ public class Vector extends AbstractVector
this.y = y;
return this;
}
+
+ @Override // from AbstractVector
+ public float getX () {
+ return x;
+ }
+
+ @Override // from AbstractVector
+ public float getY () {
+ return y;
+ }
}
diff --git a/src/main/java/pythagoras/f/Vectors.java b/src/main/java/pythagoras/f/Vectors.java
index 7f1dfc8..403d8b1 100644
--- a/src/main/java/pythagoras/f/Vectors.java
+++ b/src/main/java/pythagoras/f/Vectors.java
@@ -25,6 +25,50 @@ public class Vectors
/** A vector containing the maximum floating point value for all components. */
public static final IVector MAX_VALUE = new Vector(Float.MAX_VALUE, Float.MAX_VALUE);
+ /**
+ * Returns the magnitude of the specified vector.
+ */
+ public static final float length (float x, float y) {
+ return FloatMath.sqrt(lengthSq(x, y));
+ }
+
+ /**
+ * Returns the square of the magnitude of the specified vector.
+ */
+ public static final float lengthSq (float x, float y) {
+ return (x*x + y*y);
+ }
+
+ /**
+ * Transforms a point as specified, storing the result in the point provided.
+ * @return a reference to the result vector, for chaining.
+ */
+ public static Vector transform (float x, float y, float sx, float sy, float rotation,
+ Vector result) {
+ return transform(x, y, sx, sy, FloatMath.sin(rotation), FloatMath.cos(rotation), result);
+ }
+
+ /**
+ * Transforms a vector as specified, storing the result in the vector provided.
+ * @return a reference to the result vector, for chaining.
+ */
+ public static Vector transform (float x, float y, float sx, float sy, float sina, float cosa,
+ Vector result) {
+ return result.set((x*cosa - y*sina) * sx, (x*sina + y*cosa) * sy);
+ }
+
+ /**
+ * Inverse transforms a point as specified, storing the result in the point provided.
+ * @return a reference to the result vector, for chaining.
+ */
+ public static Vector inverseTransform (float x, float y, float sx, float sy, float rotation,
+ Vector result) {
+ float sinnega = FloatMath.sin(-rotation), cosnega = FloatMath.cos(-rotation);
+ float nx = (x * cosnega - y * sinnega); // unrotate
+ float ny = (x * sinnega + y * cosnega);
+ return result.set(nx / sx, ny / sy); // unscale
+ }
+
/**
* Returns a string describing the supplied vector, of the form +x+y,
* +x-y, -x-y, etc.
diff --git a/src/main/java/pythagoras/util/NoninvertibleTransformException.java b/src/main/java/pythagoras/util/NoninvertibleTransformException.java
index 1835ef1..d7e6a19 100644
--- a/src/main/java/pythagoras/util/NoninvertibleTransformException.java
+++ b/src/main/java/pythagoras/util/NoninvertibleTransformException.java
@@ -5,7 +5,7 @@
package pythagoras.util;
/**
- * An exception thrown by {@code AffineTransform} when a request for an inverse transform cannot be
+ * An exception thrown by {@code Transform} when a request for an inverse transform cannot be
* satisfied.
*/
public class NoninvertibleTransformException extends java.lang.RuntimeException
diff --git a/src/test/java/pythagoras/f/TransformTest.java b/src/test/java/pythagoras/f/TransformTest.java
new file mode 100644
index 0000000..343ab85
--- /dev/null
+++ b/src/test/java/pythagoras/f/TransformTest.java
@@ -0,0 +1,390 @@
+//
+// Pythagoras - a collection of geometry classes
+// http://github.com/samskivert/pythagoras
+
+package pythagoras.f;
+
+import org.junit.*;
+import static org.junit.Assert.*;
+
+/**
+ * Tests the various transform implementations.
+ */
+public class TransformTest
+{
+ @Test public void testTranslate () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < RigidTransform.GENERALITY) continue;
+ for (Point trans : TRANS) {
+ Transform t = proto.clone();
+ t.setTranslation(trans.x, trans.y);
+ for (Point point : POINTS) {
+ test(t, point, point.add(trans.x, trans.y));
+ }
+ for (Vector vec : VECTORS) {
+ test(t, vec, vec);
+ }
+ }
+ }
+ }
+
+ @Test public void testRotate () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < RigidTransform.GENERALITY) continue;
+ for (float angle : ANGLES) {
+ Transform t = proto.clone();
+ t.setRotation(angle);
+ for (Point point : POINTS) {
+ test(t, point, point.rotate(angle));
+ }
+ for (Vector vector : VECTORS) {
+ test(t, vector, vector.rotate(angle));
+ }
+ }
+ }
+ }
+
+ @Test public void testScale () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < UniformTransform.GENERALITY) continue;
+ for (Point point : POINTS) {
+ for (float scale : SCALES) {
+ Transform t = proto.clone();
+ t.setUniformScale(scale);
+ test(t, point, point.mult(scale));
+ }
+ }
+ }
+ }
+
+ @Test public void testTranslateRotate () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < RigidTransform.GENERALITY) continue;
+ for (Point trans : TRANS) {
+ Transform t1 = proto.clone();
+ t1.setTranslation(trans.x, trans.y);
+ for (float angle : ANGLES) {
+ Transform t2 = proto.clone();
+ t2.setRotation(angle);
+
+ Transform tpost = t2.concatenate(t1);
+ Transform tpre = t1.preConcatenate(t2);
+ for (Point point : POINTS) {
+ Point expect = point.add(trans.x, trans.y).rotateLocal(angle);
+ test(tpost, point, expect);
+ test(tpre, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.rotate(angle);
+ test(tpost, vector, expect);
+ test(tpre, vector, expect);
+ }
+ }
+ }
+ }
+ }
+
+ @Test public void testRotateTranslate () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < RigidTransform.GENERALITY) continue;
+ for (float angle : ANGLES) {
+ Transform t1 = proto.clone();
+ t1.setRotation(angle);
+ for (Point trans : TRANS) {
+ Transform t2 = proto.clone();
+ t2.setTranslation(trans.x, trans.y);
+
+ // test that a single transform rotates then translates
+ Transform t = proto.clone();
+ t.setRotation(angle);
+ t.setTranslation(trans.x, trans.y);
+
+ // test explicitly via concatenation
+ Transform tpost = t2.concatenate(t1);
+ Transform tpre = t1.preConcatenate(t2);
+ for (Point point : POINTS) {
+ Point expect = point.rotate(angle).addLocal(trans.x, trans.y);
+ test(t, point, expect);
+ test(tpost, point, expect);
+ test(tpre, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.rotate(angle);
+ test(t, vector, expect);
+ test(tpost, vector, expect);
+ test(tpre, vector, expect);
+ }
+ }
+ }
+ }
+ }
+
+ @Test public void testTranslateScale () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < UniformTransform.GENERALITY) continue;
+ for (Point trans : TRANS) {
+ Transform t1 = proto.clone();
+ t1.setTranslation(trans.x, trans.y);
+ for (float scale : SCALES) {
+ Transform t2 = proto.clone();
+ t2.setUniformScale(scale);
+
+ Transform tpost = t2.concatenate(t1);
+ Transform tpre = t1.preConcatenate(t2);
+ for (Point point : POINTS) {
+ Point expect = point.add(trans.x, trans.y).multLocal(scale);
+ test(tpost, point, expect);
+ test(tpre, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.mult(scale);
+ test(tpost, vector, expect);
+ test(tpre, vector, expect);
+ }
+ }
+ }
+ }
+ }
+
+ @Test public void testScaleTranslate () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < UniformTransform.GENERALITY) continue;
+ for (float scale : SCALES) {
+ Transform t1 = proto.clone();
+ t1.setUniformScale(scale);
+ for (Point trans : TRANS) {
+ Transform t2 = proto.clone();
+ t2.setTranslation(trans.x, trans.y);
+
+ // test that a single transform scales then translates
+ Transform t = proto.clone();
+ t.setUniformScale(scale);
+ t.setTranslation(trans.x, trans.y);
+
+ // test explicitly via concatenation
+ Transform tpost = t2.concatenate(t1);
+ Transform tpre = t1.preConcatenate(t2);
+ for (Point point : POINTS) {
+ Point expect = point.mult(scale).addLocal(trans.x, trans.y);
+ test(t, point, expect);
+ test(tpost, point, expect);
+ test(tpre, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.mult(scale);
+ test(t, vector, expect);
+ test(tpost, vector, expect);
+ test(tpre, vector, expect);
+ }
+ }
+ }
+ }
+ }
+
+ @Test public void testRotateScale () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < UniformTransform.GENERALITY) continue;
+ for (float angle : ANGLES) {
+ Transform t1 = proto.clone();
+ t1.setRotation(angle);
+ for (float scale : SCALES) {
+ Transform t2 = proto.clone();
+ t2.setUniformScale(scale);
+
+ Transform tpost = t2.concatenate(t1);
+ Transform tpre = t1.preConcatenate(t2);
+ for (Point point : POINTS) {
+ Point expect = point.rotate(angle).multLocal(scale);
+ test(tpost, point, expect);
+ test(tpre, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.rotate(angle).multLocal(scale);
+ test(tpost, vector, expect);
+ test(tpre, vector, expect);
+ }
+ }
+ }
+ }
+ }
+
+ @Test public void testScaleRotate () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < UniformTransform.GENERALITY) continue;
+ for (float scale : SCALES) {
+ Transform t1 = proto.clone();
+ t1.setUniformScale(scale);
+ for (float angle : ANGLES) {
+ Transform t2 = proto.clone();
+ t2.setRotation(angle);
+
+ // test explicitly via concatenation
+ Transform tpost = t2.concatenate(t1);
+ Transform tpre = t1.preConcatenate(t2);
+ for (Point point : POINTS) {
+ Point expect = point.mult(scale).rotateLocal(angle);
+ test(tpost, point, expect);
+ test(tpre, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.mult(scale).rotateLocal(angle);
+ test(tpost, vector, expect);
+ test(tpre, vector, expect);
+ }
+
+ // if we have an affine transform, we cannot set the scale and then set the
+ // rotation, because setting the rotation will first extract the scale and then
+ // reapply it, losing the sign of the scale in the process
+ if (proto.generality() >= AffineTransform.GENERALITY) continue;
+
+ // test that a single transform scales then rotates
+ Transform t = proto.clone();
+ t.setUniformScale(scale);
+ t.setRotation(angle);
+ for (Point point : POINTS) {
+ Point expect = point.mult(scale).rotateLocal(angle);
+ test(t, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.mult(scale).rotateLocal(angle);
+ test(t, vector, expect);
+ }
+ }
+ }
+ }
+ }
+
+ @Test public void testScaleRotateTranslate () {
+ for (Transform proto : createTransforms()) {
+ if (proto.generality() < UniformTransform.GENERALITY) continue;
+ for (float scale : SCALES) {
+ Transform t1 = proto.clone();
+ t1.setUniformScale(scale);
+ for (float angle : ANGLES) {
+ Transform t2 = proto.clone();
+ t2.setRotation(angle);
+ for (Point trans : TRANS) {
+ Transform t3 = proto.clone();
+ t3.setTranslation(trans.x, trans.y);
+
+ // test explicitly via concatenation
+ Transform tpost = t3.concatenate(t2).concatenate(t1);
+ Transform tpre = t1.preConcatenate(t2.preConcatenate(t3));
+ for (Point point : POINTS) {
+ Point expect = point.mult(scale).rotateLocal(angle).
+ addLocal(trans.x, trans.y);
+ test(tpost, point, expect);
+ test(tpre, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.mult(scale).rotateLocal(angle);
+ test(tpost, vector, expect);
+ test(tpre, vector, expect);
+ }
+
+ // if we have an affine transform, we cannot set the scale and then set the
+ // rotation, because setting the rotation will first extract the scale and
+ // then reapply it, losing the sign of the scale in the process
+ if (proto.generality() >= AffineTransform.GENERALITY) continue;
+
+ // test that a single transform scales, rotates, then translates
+ Transform t = proto.clone();
+ t.setUniformScale(scale);
+ t.setRotation(angle);
+ t.setTranslation(trans.x, trans.y);
+ for (Point point : POINTS) {
+ Point expect = point.mult(scale).rotateLocal(angle).
+ addLocal(trans.x, trans.y);
+ test(t, point, expect);
+ }
+ for (Vector vector : VECTORS) {
+ Vector expect = vector.mult(scale).rotateLocal(angle);
+ test(t, vector, expect);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected void test (Transform t, Point p, Point expect) {
+ Point orig = new Point(p);
+ String desc = t + " @ " + p;
+
+ // test single point transform and inverse transform
+ Point tp = t.transform(p, new Point());
+ Point itp = t.inverseTransform(tp, new Point());
+ assertEquals(desc, orig, p);
+ assertPointsEqual(desc, expect, tp);
+ assertPointsEqual(desc, p, itp);
+
+ // test multipoint transform
+ Point[] ps = new Point[] { null, p, null };
+ Point[] tps = new Point[] { null, new Point(), null };
+ t.transform(ps, 1, tps, 1, 1);
+ assertEquals(desc, orig, p);
+ assertEquals(desc, null, tps[0]);
+ assertPointsEqual(desc, expect, tps[1]);
+ assertEquals(desc, null, tps[2]);
+ }
+
+ protected void assertPointsEqual (String desc, Point p1, Point p2) {
+ assertEquals(desc + " = " + p1, p1.x, p2.x, FloatMath.EPSILON);
+ assertEquals(desc + " = " + p1, p1.y, p2.y, FloatMath.EPSILON);
+ }
+
+ protected void test (Transform t, Vector v, Vector expect) {
+ Vector orig = new Vector(v);
+ String desc = t + " @ " + v;
+
+ // test vector transform and inverse transform
+ Vector tv = t.transform(v, new Vector());
+ Vector itv = t.inverseTransform(tv, new Vector());
+ assertEquals(desc, orig, v);
+ assertVectorsEqual(desc, expect, tv);
+ assertVectorsEqual(desc, v, itv);
+ }
+
+ protected void assertVectorsEqual (String desc, Vector v1, Vector v2) {
+ assertEquals(desc + " = " + v1, v1.x, v2.x, FloatMath.EPSILON);
+ assertEquals(desc + " = " + v1, v1.y, v2.y, FloatMath.EPSILON);
+ }
+
+ protected Transform[] createTransforms () {
+ return new Transform[] {
+ new IdentityTransform(),
+ new RigidTransform(),
+ new UniformTransform(),
+ new NonUniformTransform(),
+ new AffineTransform(),
+ };
+ }
+
+ protected static final Point[] POINTS = {
+ new Point(0, 0), new Point(FloatMath.TAU, FloatMath.E),
+ new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1),
+ new Point(1, 1), new Point(-1, 1), new Point(-1, -1), new Point(1, -1)
+ };
+ protected static final Vector[] VECTORS = {
+ new Vector(0, 0), new Vector(FloatMath.TAU, FloatMath.E),
+ new Vector(1, 0), new Vector(0, 1), new Vector(-1, 0), new Vector(0, -1),
+ new Vector(1, 1), new Vector(-1, 1), new Vector(-1, -1), new Vector(1, -1)
+ };
+ protected static final float[] ANGLES = {
+ 0, FloatMath.PI/2, FloatMath.PI, FloatMath.PI*3/2,
+ -FloatMath.PI/2, -FloatMath.PI, -FloatMath.PI*3/2
+ };
+ protected static final float[] SCALES = { 0.5f, 1, 1.5f, -0.5f, -1, -1.5f };
+
+ protected static final float[] DXS = { -25, 0, 25 };
+ protected static final float[] DYS = { -25, 0, 25 };
+ protected static final Point[] TRANS = new Point[DXS.length * DYS.length];
+ static {
+ int ii = 0;
+ for (float dx : DXS) {
+ for (float dy : DYS) {
+ TRANS[ii++] = new Point(dx, dy);
+ }
+ }
+ }
+}