Numerous bits:

- nixed all transforms except identity and affine; the intermediate transforms
were more trouble than they were worth
- fixed bugs in AffineTransform.translate/scaleX/scaleY/rotate; aiya!
- replaced Transform.clone with Transform.copy; deprecated clone
- rewrote transform test in Scala and using Java AffineTransform as a
reference.
This commit is contained in:
Michael Bayne
2012-07-12 12:25:06 -07:00
parent 6d95f45603
commit 485abe95e6
16 changed files with 255 additions and 1871 deletions
@@ -1,403 +0,0 @@
//
// 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 t = t1.clone().rotate(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(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 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 t = t1.clone().uniformScale(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(t, point, expect);
test(tpost, point, expect);
test(tpre, point, expect);
}
for (Vector vector : VECTORS) {
Vector expect = vector.scale(scale);
test(t, vector, expect);
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.scale(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 t = t1.clone().uniformScale(scale);
Transform tpost = t2.concatenate(t1);
Transform tpre = t1.preConcatenate(t2);
for (Point point : POINTS) {
Point expect = point.rotate(angle).multLocal(scale);
test(t, point, expect);
test(tpost, point, expect);
test(tpre, point, expect);
}
for (Vector vector : VECTORS) {
Vector expect = vector.rotate(angle).scaleLocal(scale);
test(t, vector, expect);
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.scale(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.scale(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.scale(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.scale(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) {
if (Math.abs(p1.x - p2.x) > MathUtil.EPSILON ||
Math.abs(p1.y - p2.y) > MathUtil.EPSILON) {
fail(desc + " want " + p1 + " got " + p2);
}
}
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) {
if (Math.abs(v1.x - v2.x) > MathUtil.EPSILON ||
Math.abs(v1.y - v2.y) > MathUtil.EPSILON) {
fail(desc + " want " + v1 + " got " + v2);
}
}
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);
}
}
}
}
@@ -0,0 +1,211 @@
//
// Pythagoras - a collection of geometry classes
// http://github.com/samskivert/pythagoras
package pythagoras.f
import java.awt.geom.{AffineTransform => JAffineTransform}
import scala.collection.mutable.ListBuffer
import org.junit._
import org.junit.Assert._
/**
* Tests the various transform implementations.
*/
class TransformTest
{
trait Action {
def set (t :Transform)
def add (t :Transform)
def add (xf :JAffineTransform)
def info :String
}
val ANGLES = Seq(
0, FloatMath.PI/2, FloatMath.PI, FloatMath.PI*3/2,
-FloatMath.PI/2, -FloatMath.PI, -FloatMath.PI*3/2
).map(angle => new Action() {
def set (t :Transform) = t.setRotation(angle)
def add (t :Transform) = t.rotate(angle)
def add (xf :JAffineTransform) = xf.rotate(angle)
def info = "r:" + (180*angle/math.Pi).toInt + "'"
})
val SCALES = Seq(0.5f, 1, 1.5f, -0.5f, -1, -1.5f).map(scale => new Action {
def set (t :Transform) = t.setUniformScale(scale)
def add (t :Transform) = t.uniformScale(scale)
def add (xf :JAffineTransform) = xf.scale(scale, scale)
def info = "s:" + scale
})
val DXS = Seq(-25, 0, 25)
val DYS = Seq(-25, 0, 25)
val TRANS = (for (dx <- DXS; dy <- DYS) yield new Point(dx, dy)).map(trans => new Action {
def set (t :Transform) = t.setTranslation(trans.x, trans.y)
def add (t :Transform) = t.translate(trans.x, trans.y)
def add (xf :JAffineTransform) = xf.translate(trans.x, trans.y)
def info = "t:" + trans
})
val XFORMS = Seq(new AffineTransform)
val POINTS = Seq(
new Point(0, 0), new Point(MathUtil.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)
)
val VECTORS = Seq(
new Vector(0, 0), new Vector(MathUtil.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)
)
@Test def testTranslate {
test(List(TRANS))
}
@Test def testRotate () {
test(List(ANGLES))
}
@Test def testScale () {
test(List(SCALES))
}
@Test def testTranslateRotate () {
test(List(TRANS, ANGLES))
}
@Test def testRotateTranslate () {
test(List(ANGLES, TRANS))
}
@Test def testTranslateScale () {
test(List(TRANS, SCALES))
}
@Test def testScaleTranslate () {
test(List(SCALES, TRANS))
}
@Test def testRotateScale () {
test(List(ANGLES, SCALES))
}
@Test def testScaleRotate () {
test(List(SCALES, ANGLES))
}
@Test def testScaleRotateTranslate () {
test(List(SCALES, ANGLES, TRANS))
}
def test (actionLists :List[Seq[Action]]) {
def test (lists :List[Seq[Action]], toApply :List[Action]) {
lists match {
case Nil => runTest(toApply);
case h :: t => h foreach { a => test(t, toApply :+ a) }
}
}
test(actionLists, Nil)
}
def runTest (actions :List[Action]) {
for (proto <- XFORMS) {
var descs = ListBuffer[String]()
var trans = ListBuffer[Transform]()
val t = proto.copy
val xf = new JAffineTransform
for (a <- actions) { // TODO: check generality
descs += a.info
a.add(t)
a.add(xf)
val st = proto.copy
a.set(st)
trans += st
}
val action = descs.mkString(" ")
val tpost = trans.reduceLeft(_ concatenate _)
val tpre = trans.reverse.reduceLeft(_ preConcatenate _)
for (p <- POINTS) test(action, t, tpost, tpre, xf, p)
for (v <- VECTORS) test(action, t, tpost, tpre, xf, v)
}
}
def transform (xf :JAffineTransform, point :IPoint) = {
val dest = Array(0f, 0f)
xf.transform(Array(point.x, point.y), 0, dest, 0, 1)
new Point(dest(0), dest(1))
}
def transform (xf :JAffineTransform, vec :IVector) = {
val dest = Array(0.0, 0.0);
xf.deltaTransform(Array(vec.x.toDouble, vec.y.toDouble), 0, dest, 0, 1);
new Vector(dest(0).toFloat, dest(1).toFloat)
}
def test (action :String, tseq :Transform, tpost :Transform, tpre :Transform,
xf :JAffineTransform, point :Point) {
val expect = transform(xf, point)
test(action + " seq", tseq, point, expect)
test(action + " post", tpost, point, expect)
test(action + " pre", tpre, point, expect)
}
def test (form :String, t :Transform, p :Point, expect :Point) {
val orig = new Point(p);
val desc = form + "\n " + t + " @ " + p;
// test single point transform and inverse transform
val tp = t.transform(p, new Point);
val itp = t.inverseTransform(tp, new Point);
assertEquals(desc, orig, p);
assertPointsEqual(desc, expect, tp);
assertPointsEqual(desc, p, itp);
// test multipoint transform
val ps = Array[IPoint](null, p, null);
val tps = Array(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));
}
def assertPointsEqual (desc :String, p1 :Point, p2 :Point) {
if (math.abs(p1.x - p2.x) > MathUtil.EPSILON || math.abs(p1.y - p2.y) > MathUtil.EPSILON) {
fail(desc + "\n wantPoint " + p1 + " got " + p2);
}
}
def test (action :String, tseq :Transform, tpost :Transform, tpre :Transform,
xf :JAffineTransform, vector :Vector) {
val expect = transform(xf, vector)
test(action + " seq", tseq, vector, expect)
test(action + " post", tpost, vector, expect)
test(action + " pre", tpre, vector, expect)
}
def test (form :String, t :Transform, v :Vector, expect :Vector) {
val orig = new Vector(v);
val desc = form + "\n " + t + " @ " + v;
// test vector transform and inverse transform
val tv = t.transform(v, new Vector);
val itv = t.inverseTransform(tv, new Vector);
assertEquals(desc, orig, v);
assertVectorsEqual(desc, expect, tv);
assertVectorsEqual(desc, v, itv);
}
def assertVectorsEqual (desc :String, v1 :Vector, v2 :Vector) {
if (math.abs(v1.x - v2.x) > MathUtil.EPSILON || math.abs(v1.y - v2.y) > MathUtil.EPSILON) {
fail(desc + "\n wantVec " + v1 + " got " + v2);
}
}
}