Great big transform revamp.

We now have a Transform interface and a bevy of implementations of varying
generality: identity, rigid body, uniform scaling, non-uniform scaling, and
affine.

Along the way, Vector was added, APIs were tidied up, bits were twiddled, but
no platonic shapes were harmed.
This commit is contained in:
Michael Bayne
2011-07-06 13:08:12 -07:00
parent b6f87af8a0
commit aa0fe0f87c
30 changed files with 1736 additions and 716 deletions
+4 -5
View File
@@ -14,8 +14,9 @@
<target name="-prepare" depends="-init-ooo">
<mkdir dir="${target.dir}"/>
<mavendep pom="pom.xml"/>
<mavendep pom="pom.xml" id="test" scope="test"/>
<path id="test.classpath">
<path refid="pom.xml.path"/>
<path refid="test.path"/>
<pathelement location="${classes.dir}"/>
<pathelement location="${tclasses.dir}"/>
</path>
@@ -30,7 +31,6 @@
<copy todir="${classes.dir}"><fileset dir="src/main/resources" includes="**"/></copy>
<ooojavac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="pom.xml.path"/>
<!--
<mkdir dir="${tclasses.dir}"/>
<javac srcdir="${test.dir}" destdir="${tclasses.dir}" includeAntRuntime="false"
debug="on" optimize="${build.optimize}" source="1.6" target="1.6" encoding="utf-8">
@@ -38,7 +38,6 @@
<compilerarg value="-Xlint"/>
<compilerarg value="-Xlint:-serial"/>
</javac>
-->
</target>
<target name="javadoc" depends="-prepare" description="Generates javadoc documentation.">
@@ -51,10 +50,10 @@
</javadoc>
</target>
<target name="tests" depends="compile" description="Runs the unit tests.">
<target name="test" depends="compile" description="Runs the unit tests.">
<!-- allows passing -Dtest=NamePrefix to restrict to subset of tests -->
<property name="test" value=""/>
<taskdef name="unit" classpathref="classpath"
<taskdef name="unit" classpathref="test.path"
classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask"/>
<mkdir dir="${target.dir}/test-reports"/>
<unit printsummary="off" haltonfailure="yes" fork="${junit.fork}">
+6
View File
@@ -43,6 +43,12 @@
</scm>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
+7 -9
View File
@@ -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;
@@ -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;
}
@@ -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();
+6 -8
View File
@@ -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();
+37 -5
View File
@@ -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);
}
@@ -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;
}
@@ -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();
@@ -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();
@@ -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 ();
}
+209 -551
View File
@@ -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:
* <pre>{@code
* [ m00, m10, 0 ]
* [ m01, m11, 0 ]
* [ tx, ty, 1 ]
* }</pre>
*/
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 <a href="http://en.wikipedia.org/wiki/Determinant">determinant</a> 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;
}
+8 -8
View File
@@ -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;
}
+7 -4
View File
@@ -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 (&#964;) http://tauday.com/ */
public static final float TWO_PI = (float)(Math.PI * 2);
/** The circle constant, Tau (&#964;) 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.
+25
View File
@@ -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 ();
}
+2 -2
View File
@@ -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);
}
@@ -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";
}
}
@@ -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() + "]";
}
}
+6 -6
View File
@@ -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;
}
+24 -22
View File
@@ -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
+26
View File
@@ -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 <code>+x+y</code>,
* <code>+x-y</code>, <code>-x-y</code>, etc.
@@ -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);
}
}
@@ -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() + "]";
}
}
+113 -64
View File
@@ -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 ();
}
@@ -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;
}
}
@@ -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() + "]";
}
}
+10 -10
View File
@@ -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;
}
}
+44
View File
@@ -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 <code>+x+y</code>,
* <code>+x-y</code>, <code>-x-y</code>, etc.
@@ -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
@@ -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);
}
}
}
}