blob: 7b27f916901960fd0d70665053d98c5a131bf909 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.euclidean.oned;
import org.apache.commons.geometry.core.Geometry;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.exception.IllegalNormException;
import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
import org.apache.commons.geometry.euclidean.exception.NonInvertibleTransformException;
import org.junit.Assert;
import org.junit.Test;
public class AffineTransformMatrix1DTest {
private static final double EPS = 1e-12;
@Test
public void testOf() {
// act
AffineTransformMatrix1D transform = AffineTransformMatrix1D.of(1, 2);
// assert
Assert.assertTrue(transform.preservesOrientation());
double[] result = transform.toArray();
Assert.assertArrayEquals(new double[] { 1, 2 }, result, 0.0);
}
@Test
public void testOf_invalidDimensions() {
// act/assert
GeometryTestUtils.assertThrows(() -> AffineTransformMatrix1D.of(1),
IllegalArgumentException.class, "Dimension mismatch: 1 != 2");
}
@Test
public void testIdentity() {
// act
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
// assert
Assert.assertTrue(transform.preservesOrientation());
double[] expected = { 1, 0 };
Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
}
@Test
public void testCreateTranslation_value() {
// act
AffineTransformMatrix1D transform = AffineTransformMatrix1D.createTranslation(2);
// assert
Assert.assertTrue(transform.preservesOrientation());
double[] expected = { 1, 2 };
Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
}
@Test
public void testCreateTranslation_vector() {
// act
AffineTransformMatrix1D transform = AffineTransformMatrix1D.createTranslation(Vector1D.of(5));
// assert
Assert.assertTrue(transform.preservesOrientation());
double[] expected = { 1, 5 };
Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
}
@Test
public void testTranslate_value() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 10);
// act
AffineTransformMatrix1D result = a.translate(4);
// assert
Assert.assertTrue(result.preservesOrientation());
double[] expected = { 2, 14 };
Assert.assertArrayEquals(expected, result.toArray(), 0.0);
}
@Test
public void testTranslate_vector() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 10);
// act
AffineTransformMatrix1D result = a.translate(Vector1D.of(7));
// assert
Assert.assertTrue(result.preservesOrientation());
double[] expected = { 2, 17 };
Assert.assertArrayEquals(expected, result.toArray(), 0.0);
}
@Test
public void testCreateScale_vector() {
// act
AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(Vector1D.of(4));
// assert
Assert.assertTrue(transform.preservesOrientation());
double[] expected = { 4, 0 };
Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
}
@Test
public void testCreateScale_value() {
// act
AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(7);
// assert
Assert.assertTrue(transform.preservesOrientation());
double[] expected = { 7, 0 };
Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
}
@Test
public void testScale_value() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 10);
// act
AffineTransformMatrix1D result = a.scale(4);
// assert
Assert.assertTrue(result.preservesOrientation());
double[] expected = { 8, 40 };
Assert.assertArrayEquals(expected, result.toArray(), 0.0);
}
@Test
public void testScale_vector() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 10);
// act
AffineTransformMatrix1D result = a.scale(Vector1D.of(7));
// assert
Assert.assertTrue(result.preservesOrientation());
double[] expected = { 14, 70 };
Assert.assertArrayEquals(expected, result.toArray(), 0.0);
}
@Test
public void testApply_identity() {
// arrange
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
// act/assert
runWithCoordinates((x) -> {
Vector1D v = Vector1D.of(x);
EuclideanTestUtils.assertCoordinatesEqual(v, transform.apply(v), EPS);
});
}
@Test
public void testApply_translate() {
// arrange
Vector1D translation = Vector1D.of(-Geometry.PI);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.translate(translation);
// act/assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
Vector1D expectedVec = vec.add(translation);
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
});
}
@Test
public void testApply_scale() {
// arrange
Vector1D factor = Vector1D.of(2.0);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.scale(factor);
// act/assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
Vector1D expectedVec = Vector1D.of(factor.getX() * x);
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
});
}
@Test
public void testApply_translateThenScale() {
// arrange
Vector1D translation = Vector1D.of(-2.0);
Vector1D scale = Vector1D.of(5.0);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.translate(translation)
.scale(scale);
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(-5), transform.apply(Vector1D.of(1)), EPS);
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
Vector1D expectedVec = Vector1D.of(
(x + translation.getX()) * scale.getX()
);
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
});
}
@Test
public void testApply_scaleThenTranslate() {
// arrange
Vector1D scale = Vector1D.of(5.0);
Vector1D translation = Vector1D.of(-2.0);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.scale(scale)
.translate(translation);
// act/assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
Vector1D expectedVec = Vector1D.of(
(x * scale.getX()) + translation.getX()
);
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
});
}
@Test
public void testApplyVector_identity() {
// arrange
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
// act/assert
runWithCoordinates((x) -> {
Vector1D v = Vector1D.of(x);
EuclideanTestUtils.assertCoordinatesEqual(v, transform.applyVector(v), EPS);
});
}
@Test
public void testApplyVector_translate() {
// arrange
Vector1D translation = Vector1D.of(-Geometry.PI);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.translate(translation);
// act/assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
EuclideanTestUtils.assertCoordinatesEqual(vec, transform.applyVector(vec), EPS);
});
}
@Test
public void testApplyVector_scale() {
// arrange
Vector1D factor = Vector1D.of(2.0);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.scale(factor);
// act/assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
Vector1D expectedVec = Vector1D.of(factor.getX() * x);
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyVector(vec), EPS);
});
}
@Test
public void testApplyVector_representsDisplacement() {
// arrange
Vector1D p1 = Vector1D.of(Geometry.PI);
Vector1D translation = Vector1D.of(-2.0);
Vector1D scale = Vector1D.of(5.0);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.translate(translation)
.scale(scale);
// act/assert
runWithCoordinates((x) -> {
Vector1D p2 = Vector1D.of(x);
Vector1D input = p1.subtract(p2);
Vector1D expectedVec = transform.apply(p1).subtract(transform.apply(p2));
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyVector(input), EPS);
});
}
@Test
public void testApplyDirection_identity() {
// arrange
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
// act/assert
runWithCoordinates((x) -> {
Vector1D v = Vector1D.of(x);
EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), transform.applyDirection(v), EPS);
}, true);
}
@Test
public void testApplyDirection_translate() {
// arrange
Vector1D translation = Vector1D.of(-Geometry.PI);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.translate(translation);
// act/assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
EuclideanTestUtils.assertCoordinatesEqual(vec.normalize(), transform.applyDirection(vec), EPS);
}, true);
}
@Test
public void testApplyDirection_scale() {
// arrange
Vector1D factor = Vector1D.of(2.0);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.scale(factor);
// act/assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
Vector1D expectedVec = Vector1D.of(factor.getX() * x).normalize();
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyDirection(vec), EPS);
}, true);
}
@Test
public void testApplyDirection_representsNormalizedDisplacement() {
// arrange
Vector1D p1 = Vector1D.of(Geometry.PI);
Vector1D translation = Vector1D.of(-2.0);
Vector1D scale = Vector1D.of(5.0);
AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
.translate(translation)
.scale(scale);
// act/assert
runWithCoordinates((x) -> {
Vector1D p2 = Vector1D.of(x);
Vector1D input = p1.subtract(p2);
Vector1D expectedVec = transform.apply(p1).subtract(transform.apply(p2)).normalize();
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyDirection(input), EPS);
});
}
@Test
public void testApplyDirection_illegalNorm() {
// act/assert
GeometryTestUtils.assertThrows(() -> AffineTransformMatrix1D.createScale(0).applyDirection(Vector1D.Unit.PLUS),
IllegalNormException.class);
GeometryTestUtils.assertThrows(() -> AffineTransformMatrix1D.createScale(2).applyDirection(Vector1D.ZERO),
IllegalNormException.class);
}
@Test
public void testDeterminant() {
// act/assert
Assert.assertEquals(0.0, AffineTransformMatrix1D.of(0, 1).determinant(), EPS);
Assert.assertEquals(1.0, AffineTransformMatrix1D.of(1, 0).determinant(), EPS);
Assert.assertEquals(-1.0, AffineTransformMatrix1D.of(-1, 2).determinant(), EPS);
}
@Test
public void testPreservesOrientation() {
// act/assert
Assert.assertFalse(AffineTransformMatrix1D.of(0, 1).preservesOrientation());
Assert.assertTrue(AffineTransformMatrix1D.of(1, 0).preservesOrientation());
Assert.assertFalse(AffineTransformMatrix1D.of(-1, 2).preservesOrientation());
}
@Test
public void testToMatrix() {
// arrange
AffineTransformMatrix1D t = AffineTransformMatrix1D.of(1, 1);
// act/assert
Assert.assertSame(t, t.toMatrix());
}
@Test
public void testMultiply() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 3);
AffineTransformMatrix1D b = AffineTransformMatrix1D.of(13, 14);
// act
AffineTransformMatrix1D result = a.multiply(b);
// assert
double[] arr = result.toArray();
Assert.assertArrayEquals(new double[] { 26, 31 }, arr, EPS);
}
@Test
public void testMultiply_combinesTransformOperations() {
// arrange
Vector1D translation1 = Vector1D.of(1);
double scale = 2.0;
Vector1D translation2 = Vector1D.of(4);
AffineTransformMatrix1D a = AffineTransformMatrix1D.createTranslation(translation1);
AffineTransformMatrix1D b = AffineTransformMatrix1D.createScale(scale);
AffineTransformMatrix1D c = AffineTransformMatrix1D.identity();
AffineTransformMatrix1D d = AffineTransformMatrix1D.createTranslation(translation2);
// act
AffineTransformMatrix1D transform = d.multiply(c).multiply(b).multiply(a);
// assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
Vector1D expectedVec = vec
.add(translation1)
.multiply(scale)
.add(translation2);
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
});
}
@Test
public void testPremultiply() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 3);
AffineTransformMatrix1D b = AffineTransformMatrix1D.of(13, 14);
// act
AffineTransformMatrix1D result = b.premultiply(a);
// assert
double[] arr = result.toArray();
Assert.assertArrayEquals(new double[] { 26, 31 }, arr, EPS);
}
@Test
public void testPremultiply_combinesTransformOperations() {
// arrange
Vector1D translation1 = Vector1D.of(1);
double scale = 2.0;
Vector1D translation2 = Vector1D.of(4);
AffineTransformMatrix1D a = AffineTransformMatrix1D.createTranslation(translation1);
AffineTransformMatrix1D b = AffineTransformMatrix1D.createScale(scale);
AffineTransformMatrix1D c = AffineTransformMatrix1D.identity();
AffineTransformMatrix1D d = AffineTransformMatrix1D.createTranslation(translation2);
// act
AffineTransformMatrix1D transform = a.premultiply(b).premultiply(c).premultiply(d);
// assert
runWithCoordinates((x) -> {
Vector1D vec = Vector1D.of(x);
Vector1D expectedVec = vec
.add(translation1)
.multiply(scale)
.add(translation2);
EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
});
}
@Test
public void testInverse_identity() {
// act
AffineTransformMatrix1D inverse = AffineTransformMatrix1D.identity().inverse();
// assert
double[] expected = { 1, 0 };
Assert.assertArrayEquals(expected, inverse.toArray(), 0.0);
}
@Test
public void testInverse_multiplyByInverse_producesIdentity() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(1, 3);
AffineTransformMatrix1D inv = a.inverse();
// act
AffineTransformMatrix1D result = inv.multiply(a);
// assert
double[] expected = { 1, 0 };
Assert.assertArrayEquals(expected, result.toArray(), EPS);
}
@Test
public void testInverse_translate() {
// arrange
AffineTransformMatrix1D transform = AffineTransformMatrix1D.createTranslation(3);
// act
AffineTransformMatrix1D inverse = transform.inverse();
// assert
double[] expected = { 1, -3 };
Assert.assertArrayEquals(expected, inverse.toArray(), 0.0);
}
@Test
public void testInverse_scale() {
// arrange
AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(10);
// act
AffineTransformMatrix1D inverse = transform.inverse();
// assert
double[] expected = { 0.1, 0 };
Assert.assertArrayEquals(expected, inverse.toArray(), 0.0);
}
@Test
public void testInverse_undoesOriginalTransform_translationAndScale() {
// arrange
Vector1D v1 = Vector1D.ZERO;
Vector1D v2 = Vector1D.Unit.PLUS;
Vector1D v3 = Vector1D.of(1.5);
Vector1D v4 = Vector1D.of(-2);
// act/assert
runWithCoordinates((x) -> {
AffineTransformMatrix1D transform = AffineTransformMatrix1D
.createTranslation(x)
.scale(2)
.translate(x / 3);
AffineTransformMatrix1D inverse = transform.inverse();
EuclideanTestUtils.assertCoordinatesEqual(v1, inverse.apply(transform.apply(v1)), EPS);
EuclideanTestUtils.assertCoordinatesEqual(v2, inverse.apply(transform.apply(v2)), EPS);
EuclideanTestUtils.assertCoordinatesEqual(v3, inverse.apply(transform.apply(v3)), EPS);
EuclideanTestUtils.assertCoordinatesEqual(v4, inverse.apply(transform.apply(v4)), EPS);
});
}
@Test
public void testInverse_nonInvertible() {
// act/assert
GeometryTestUtils.assertThrows(() -> {
AffineTransformMatrix1D.of(0, 0).inverse();
}, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is 0.0");
GeometryTestUtils.assertThrows(() -> {
AffineTransformMatrix1D.of(Double.NaN, 0).inverse();
}, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is NaN");
GeometryTestUtils.assertThrows(() -> {
AffineTransformMatrix1D.of(Double.NEGATIVE_INFINITY, 0.0).inverse();
}, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is -Infinity");
GeometryTestUtils.assertThrows(() -> {
AffineTransformMatrix1D.of(Double.POSITIVE_INFINITY, 0).inverse();
}, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is Infinity");
GeometryTestUtils.assertThrows(() -> {
AffineTransformMatrix1D.of(1, Double.NaN).inverse();
}, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: NaN");
GeometryTestUtils.assertThrows(() -> {
AffineTransformMatrix1D.of(1, Double.NEGATIVE_INFINITY).inverse();
}, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: -Infinity");
GeometryTestUtils.assertThrows(() -> {
AffineTransformMatrix1D.of(1, Double.POSITIVE_INFINITY).inverse();
}, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: Infinity");
}
@Test
public void testHashCode() {
// act
int orig = AffineTransformMatrix1D.of(1, 2).hashCode();
int same = AffineTransformMatrix1D.of(1, 2).hashCode();
// assert
Assert.assertEquals(orig, same);
Assert.assertNotEquals(orig, AffineTransformMatrix1D.of(0, 2).hashCode());
Assert.assertNotEquals(orig, AffineTransformMatrix1D.of(1, 0).hashCode());
}
@Test
public void testEquals() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(1, 2);
// act/assert
Assert.assertTrue(a.equals(a));
Assert.assertFalse(a.equals(null));
Assert.assertFalse(a.equals(new Object()));
Assert.assertFalse(a.equals(AffineTransformMatrix1D.of(0, 2)));
Assert.assertFalse(a.equals(AffineTransformMatrix1D.of(1, 0)));
}
@Test
public void testToString() {
// arrange
AffineTransformMatrix1D a = AffineTransformMatrix1D.of(1, 2);
// act
String result = a.toString();
// assert
Assert.assertEquals("[ 1.0, 2.0 ]", result);
}
@FunctionalInterface
private static interface Coordinate1DTest {
void run(double x);
}
private static void runWithCoordinates(Coordinate1DTest test) {
runWithCoordinates(test, false);
}
private static void runWithCoordinates(Coordinate1DTest test, boolean skipZero) {
runWithCoordinates(test, -1e-2, 1e-2, 5e-3, skipZero);
runWithCoordinates(test, -1e2, 1e2, 5, skipZero);
}
private static void runWithCoordinates(Coordinate1DTest test, double min, double max, double step, boolean skipZero)
{
for (double x = min; x <= max; x += step) {
if (!skipZero || x != 0.0) {
test.run(x);
}
}
}
}