| /* |
| * 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 |
| 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 |
| double[] expected = { 1, 0 }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateTranslation_value() { |
| // act |
| AffineTransformMatrix1D transform = AffineTransformMatrix1D.createTranslation(2); |
| |
| // assert |
| 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 |
| 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 |
| 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 |
| 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 |
| double[] expected = { 4, 0 }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateScale_value() { |
| // act |
| AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(7); |
| |
| // assert |
| 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 |
| 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 |
| 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.ONE), |
| IllegalNormException.class); |
| GeometryTestUtils.assertThrows(() -> AffineTransformMatrix1D.createScale(2).applyDirection(Vector1D.ZERO), |
| IllegalNormException.class); |
| } |
| |
| @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.ONE; |
| 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); |
| } |
| } |
| } |
| } |