| /* |
| * 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.twod; |
| |
| import org.apache.commons.numbers.angle.PlaneAngleRadians; |
| 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 AffineTransformMatrix2DTest { |
| |
| private static final double EPS = 1e-12; |
| |
| @Test |
| public void testOf() { |
| // arrange |
| double[] arr = { |
| 1, 2, 3, |
| 4, 5, 6 |
| }; |
| |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.of(arr); |
| |
| // assert |
| double[] result = transform.toArray(); |
| Assert.assertNotSame(arr, result); |
| Assert.assertArrayEquals(arr, result, 0.0); |
| } |
| |
| @Test |
| public void testOf_invalidDimensions() { |
| // act/assert |
| GeometryTestUtils.assertThrows(() -> AffineTransformMatrix2D.of(1, 2), |
| IllegalArgumentException.class, "Dimension mismatch: 2 != 6"); |
| } |
| |
| @Test |
| public void testFromColumnVectors_twoVector() { |
| // arrange |
| Vector2D u = Vector2D.of(1, 2); |
| Vector2D v = Vector2D.of(3, 4); |
| |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.fromColumnVectors(u, v); |
| |
| // assert |
| Assert.assertArrayEquals(new double[] { |
| 1, 3, 0, |
| 2, 4, 0 |
| }, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testFromColumnVectors_threeVectors() { |
| // arrange |
| Vector2D u = Vector2D.of(1, 2); |
| Vector2D v = Vector2D.of(3, 4); |
| Vector2D t = Vector2D.of(5, 6); |
| |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.fromColumnVectors(u, v, t); |
| |
| // assert |
| Assert.assertArrayEquals(new double[] { |
| 1, 3, 5, |
| 2, 4, 6 |
| }, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testIdentity() { |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity(); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, |
| 0, 1, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateTranslation_xy() { |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(2, 3); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 2, |
| 0, 1, 3 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateTranslation_vector() { |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(Vector2D.of(5, 6)); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 5, |
| 0, 1, 6 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateScale_xy() { |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(2, 3); |
| |
| // assert |
| double[] expected = { |
| 2, 0, 0, |
| 0, 3, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testTranslate_xy() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 2, 0, 10, |
| 0, 3, 11 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = a.translate(4, 5); |
| |
| // assert |
| double[] expected = { |
| 2, 0, 14, |
| 0, 3, 16 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testTranslate_vector() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 2, 0, 10, |
| 0, 3, 11 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = a.translate(Vector2D.of(7, 8)); |
| |
| // assert |
| double[] expected = { |
| 2, 0, 17, |
| 0, 3, 19 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateScale_vector() { |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(Vector2D.of(4, 5)); |
| |
| // assert |
| double[] expected = { |
| 4, 0, 0, |
| 0, 5, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateScale_singleValue() { |
| // act |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(7); |
| |
| // assert |
| double[] expected = { |
| 7, 0, 0, |
| 0, 7, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testScale_xy() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 2, 0, 10, |
| 0, 3, 11 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = a.scale(4, 5); |
| |
| // assert |
| double[] expected = { |
| 8, 0, 40, |
| 0, 15, 55 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testScale_vector() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 2, 0, 10, |
| 0, 3, 11 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = a.scale(Vector2D.of(7, 8)); |
| |
| // assert |
| double[] expected = { |
| 14, 0, 70, |
| 0, 24, 88 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testScale_singleValue() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 2, 0, 10, |
| 0, 3, 11 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = a.scale(10); |
| |
| // assert |
| double[] expected = { |
| 20, 0, 100, |
| 0, 30, 110 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateRotation() { |
| // act |
| double angle = PlaneAngleRadians.PI * 2.0 / 3.0; |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(angle); |
| |
| // assert |
| double sin = Math.sin(angle); |
| double cos = Math.cos(angle); |
| |
| double[] expected = { |
| cos, -sin, 0, |
| sin, cos, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), EPS); |
| } |
| |
| @Test |
| public void testCreateRotation_aroundCenter() { |
| // act |
| Vector2D center = Vector2D.of(1, 2); |
| double angle = PlaneAngleRadians.PI * 2.0 / 3.0; |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(center, angle); |
| |
| // assert |
| double sin = Math.sin(angle); |
| double cos = Math.cos(angle); |
| |
| double[] expected = { |
| cos, -sin, -cos + 2*sin + 1, |
| sin, cos, -sin - 2*cos + 2 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), EPS); |
| } |
| |
| @Test |
| public void testRotate() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 1, 2, 3, |
| 4, 5, 6 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = a.rotate(PlaneAngleRadians.PI_OVER_TWO); |
| |
| // assert |
| double[] expected = { |
| -4, -5, -6, |
| 1, 2, 3 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), EPS); |
| } |
| |
| @Test |
| public void testRotate_aroundCenter() { |
| // arrange |
| Vector2D center = Vector2D.of(1, 2); |
| |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 1, 2, 3, |
| 4, 5, 6 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = a.rotate(center, PlaneAngleRadians.PI_OVER_TWO); |
| |
| // assert |
| double[] expected = { |
| -4, -5, -3, |
| 1, 2, 4 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), EPS); |
| } |
| |
| @Test |
| public void testApply_identity() { |
| // arrange |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity(); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D v = Vector2D.of(x, y); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(v, transform.apply(v), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_translate() { |
| // arrange |
| Vector2D translation = Vector2D.of(1.1, -PlaneAngleRadians.PI); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .translate(translation); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D expectedVec = vec.add(translation); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_scale() { |
| // arrange |
| Vector2D factors = Vector2D.of(2.0, -3.0); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .scale(factors); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_rotate() { |
| // arrange |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .rotate(PlaneAngleRadians.MINUS_PI_OVER_TWO); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D expectedVec = Vector2D.of(y, -x); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_rotate_aroundCenter_minusHalfPi() { |
| // arrange |
| Vector2D center = Vector2D.of(1, 2); |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .rotate(center, PlaneAngleRadians.MINUS_PI_OVER_TWO); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D centered = vec.subtract(center); |
| Vector2D expectedVec = Vector2D.of(centered.getY(), -centered.getX()).add(center); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_rotate_aroundCenter_pi() { |
| // arrange |
| Vector2D center = Vector2D.of(1, 2); |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .rotate(center, PlaneAngleRadians.PI); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D centered = vec.subtract(center); |
| Vector2D expectedVec = Vector2D.of(-centered.getX(), -centered.getY()).add(center); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_translateScaleRotate() { |
| // arrange |
| Vector2D translation = Vector2D.of(-2.0, -3.0); |
| Vector2D scale = Vector2D.of(5.0, 6.0); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .translate(translation) |
| .scale(scale) |
| .rotate(PlaneAngleRadians.PI_OVER_TWO); |
| |
| // act/assert |
| EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(12, -5), transform.apply(Vector2D.of(1, 1)), EPS); |
| |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D temp = Vector2D.of( |
| (x + translation.getX()) * scale.getX(), |
| (y + translation.getY()) * scale.getY() |
| ); |
| Vector2D expectedVec = Vector2D.of(-temp.getY(), temp.getX()); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_scaleTranslateRotate() { |
| // arrange |
| Vector2D scale = Vector2D.of(5.0, 6.0); |
| Vector2D translation = Vector2D.of(-2.0, -3.0); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .scale(scale) |
| .translate(translation) |
| .rotate(PlaneAngleRadians.MINUS_PI_OVER_TWO); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D temp = Vector2D.of( |
| (x * scale.getX()) + translation.getX(), |
| (y * scale.getY()) + translation.getY() |
| ); |
| Vector2D expectedVec = Vector2D.of(temp.getY(), -temp.getX()); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyVector_identity() { |
| // arrange |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity(); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D v = Vector2D.of(x, y); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(v, transform.applyVector(v), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyVector_translate() { |
| // arrange |
| Vector2D translation = Vector2D.of(1.1, -PlaneAngleRadians.PI); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .translate(translation); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(vec, transform.applyVector(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyVector_scale() { |
| // arrange |
| Vector2D factors = Vector2D.of(2.0, -3.0); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .scale(factors); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyVector(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyVector_representsDisplacement() { |
| // arrange |
| Vector2D p1 = Vector2D.of(2, 3); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .scale(1.5) |
| .translate(4, 6) |
| .rotate(PlaneAngleRadians.PI_OVER_TWO); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| Vector2D p2 = Vector2D.of(x, y); |
| Vector2D input = p1.subtract(p2); |
| |
| Vector2D expected = transform.apply(p1).subtract(transform.apply(p2)); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expected, transform.applyVector(input), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_identity() { |
| // arrange |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity(); |
| |
| // act/assert |
| EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> { |
| Vector2D v = Vector2D.of(x, y); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), transform.applyDirection(v), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_translate() { |
| // arrange |
| Vector2D translation = Vector2D.of(1.1, -PlaneAngleRadians.PI); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .translate(translation); |
| |
| // act/assert |
| EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(vec.normalize(), transform.applyDirection(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_scale() { |
| // arrange |
| Vector2D factors = Vector2D.of(2.0, -3.0); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .scale(factors); |
| |
| // act/assert |
| EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y).normalize(); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyDirection(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_representsNormalizedDisplacement() { |
| // arrange |
| Vector2D p1 = Vector2D.of(2.1, 3.2); |
| |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity() |
| .scale(1.5) |
| .translate(4, 6) |
| .rotate(PlaneAngleRadians.PI_OVER_TWO); |
| |
| // act/assert |
| EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> { |
| Vector2D p2 = Vector2D.of(x, y); |
| Vector2D input = p1.subtract(p2); |
| |
| Vector2D expected = transform.apply(p1).subtract(transform.apply(p2)).normalize(); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expected, transform.applyDirection(input), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_illegalNorm() { |
| // act/assert |
| GeometryTestUtils.assertThrows(() -> AffineTransformMatrix2D.createScale(1, 0).applyDirection(Vector2D.Unit.PLUS_Y), |
| IllegalNormException.class); |
| GeometryTestUtils.assertThrows(() -> AffineTransformMatrix2D.createScale(2).applyDirection(Vector2D.ZERO), |
| IllegalNormException.class); |
| } |
| |
| @Test |
| public void testDeterminant() { |
| // act/assert |
| Assert.assertEquals(1.0, AffineTransformMatrix2D.identity().determinant(), EPS); |
| Assert.assertEquals(6.0, AffineTransformMatrix2D.of( |
| 2, 0, 4, |
| 0, 3, 5 |
| ).determinant(), EPS); |
| Assert.assertEquals(-6.0, AffineTransformMatrix2D.of( |
| 2, 0, 4, |
| 0, -3, 5 |
| ).determinant(), EPS); |
| Assert.assertEquals(-5.0, AffineTransformMatrix2D.of( |
| 1, 3, 0, |
| 2, 1, 0 |
| ).determinant(), EPS); |
| Assert.assertEquals(-0.0, AffineTransformMatrix2D.of( |
| 0, 0, 1, |
| 0, 0, 2 |
| ).determinant(), EPS); |
| } |
| |
| @Test |
| public void testPreservesOrientation() { |
| // act/assert |
| Assert.assertTrue(AffineTransformMatrix2D.identity().preservesOrientation()); |
| Assert.assertTrue(AffineTransformMatrix2D.of( |
| 2, 0, 4, |
| 0, 3, 5 |
| ).preservesOrientation()); |
| |
| Assert.assertFalse(AffineTransformMatrix2D.of( |
| 2, 0, 4, |
| 0, -3, 5 |
| ).preservesOrientation()); |
| Assert.assertFalse(AffineTransformMatrix2D.of( |
| 1, 3, 0, |
| 2, 1, 0 |
| ).preservesOrientation()); |
| Assert.assertFalse(AffineTransformMatrix2D.of( |
| 0, 0, 1, |
| 0, 0, 2 |
| ).preservesOrientation()); |
| } |
| |
| @Test |
| public void testToMatrix() { |
| // arrange |
| AffineTransformMatrix2D t = AffineTransformMatrix2D.createScale(2.0); |
| |
| // act/assert |
| Assert.assertSame(t, t.toMatrix()); |
| } |
| |
| @Test |
| public void testMultiply() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 1, 2, 3, |
| 5, 6, 7 |
| ); |
| AffineTransformMatrix2D b = AffineTransformMatrix2D.of( |
| 13, 14, 15, |
| 17, 18, 19 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = a.multiply(b); |
| |
| // assert |
| double[] arr = result.toArray(); |
| Assert.assertArrayEquals(new double[] { |
| 47, 50, 56, |
| 167, 178, 196 |
| }, arr, EPS); |
| } |
| |
| @Test |
| public void testMultiply_combinesTransformOperations() { |
| // arrange |
| Vector2D translation1 = Vector2D.of(1, 2); |
| double scale = 2.0; |
| Vector2D translation2 = Vector2D.of(4, 5); |
| |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.createTranslation(translation1); |
| AffineTransformMatrix2D b = AffineTransformMatrix2D.createScale(scale); |
| AffineTransformMatrix2D c = AffineTransformMatrix2D.identity(); |
| AffineTransformMatrix2D d = AffineTransformMatrix2D.createTranslation(translation2); |
| |
| // act |
| AffineTransformMatrix2D transform = d.multiply(c).multiply(b).multiply(a); |
| |
| // assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D expectedVec = vec |
| .add(translation1) |
| .multiply(scale) |
| .add(translation2); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testPremultiply() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 1, 2, 3, |
| 5, 6, 7 |
| ); |
| AffineTransformMatrix2D b = AffineTransformMatrix2D.of( |
| 13, 14, 15, |
| 17, 18, 19 |
| ); |
| |
| // act |
| AffineTransformMatrix2D result = b.premultiply(a); |
| |
| // assert |
| double[] arr = result.toArray(); |
| Assert.assertArrayEquals(new double[] { |
| 47, 50, 56, |
| 167, 178, 196 |
| }, arr, EPS); |
| } |
| |
| @Test |
| public void testPremultiply_combinesTransformOperations() { |
| // arrange |
| Vector2D translation1 = Vector2D.of(1, 2); |
| double scale = 2.0; |
| Vector2D translation2 = Vector2D.of(4, 5); |
| |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.createTranslation(translation1); |
| AffineTransformMatrix2D b = AffineTransformMatrix2D.createScale(scale); |
| AffineTransformMatrix2D c = AffineTransformMatrix2D.identity(); |
| AffineTransformMatrix2D d = AffineTransformMatrix2D.createTranslation(translation2); |
| |
| // act |
| AffineTransformMatrix2D transform = a.premultiply(b).premultiply(c).premultiply(d); |
| |
| // assert |
| runWithCoordinates((x, y) -> { |
| Vector2D vec = Vector2D.of(x, y); |
| |
| Vector2D expectedVec = vec |
| .add(translation1) |
| .multiply(scale) |
| .add(translation2); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testInverse_identity() { |
| // act |
| AffineTransformMatrix2D inverse = AffineTransformMatrix2D.identity().inverse(); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, |
| 0, 1, 0 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testInverse_multiplyByInverse_producesIdentity() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 1, 3, 7, |
| 2, 4, 9 |
| ); |
| |
| AffineTransformMatrix2D inv = a.inverse(); |
| |
| // act |
| AffineTransformMatrix2D result = inv.multiply(a); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, |
| 0, 1, 0 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), EPS); |
| } |
| |
| @Test |
| public void testInverse_translate() { |
| // arrange |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(1, -2); |
| |
| // act |
| AffineTransformMatrix2D inverse = transform.inverse(); |
| |
| // assert |
| double[] expected = { |
| 1, 0, -1, |
| 0, 1, 2 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testInverse_scale() { |
| // arrange |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(10, -2); |
| |
| // act |
| AffineTransformMatrix2D inverse = transform.inverse(); |
| |
| // assert |
| double[] expected = { |
| 0.1, 0, 0, |
| 0, -0.5, 0 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testInverse_rotate() { |
| // arrange |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO); |
| |
| // act |
| AffineTransformMatrix2D inverse = transform.inverse(); |
| |
| // assert |
| double[] expected = { |
| 0, 1, 0, |
| -1, 0, 0 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), EPS); |
| } |
| |
| @Test |
| public void testInverse_rotate_aroundCenter() { |
| // arrange |
| Vector2D center = Vector2D.of(1, 2); |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(center, PlaneAngleRadians.PI_OVER_TWO); |
| |
| // act |
| AffineTransformMatrix2D inverse = transform.inverse(); |
| |
| // assert |
| double[] expected = { |
| 0, 1, -1, |
| -1, 0, 3 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), EPS); |
| } |
| |
| @Test |
| public void testInverse_undoesOriginalTransform() { |
| // arrange |
| Vector2D v1 = Vector2D.ZERO; |
| Vector2D v2 = Vector2D.Unit.PLUS_X; |
| Vector2D v3 = Vector2D.of(1, 1); |
| Vector2D v4 = Vector2D.of(-2, 3); |
| |
| Vector2D center = Vector2D.of(-0.5, 2); |
| |
| // act/assert |
| runWithCoordinates((x, y) -> { |
| AffineTransformMatrix2D transform = AffineTransformMatrix2D |
| .createTranslation(x, y) |
| .scale(2, 3) |
| .translate(x / 3, y / 3) |
| .rotate(x / 4) |
| .rotate(center, y / 2); |
| |
| AffineTransformMatrix2D 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(() -> { |
| AffineTransformMatrix2D.of( |
| 0, 0, 0, |
| 0, 0, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is 0.0"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix2D.of( |
| 1, 0, 0, |
| 0, Double.NaN, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is NaN"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix2D.of( |
| 1, 0, 0, |
| 0, Double.NEGATIVE_INFINITY, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is -Infinity"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix2D.of( |
| Double.POSITIVE_INFINITY, 0, 0, |
| 0, 1, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is Infinity"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix2D.of( |
| 1, 0, Double.NaN, |
| 0, 1, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: NaN"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix2D.of( |
| 1, 0, Double.POSITIVE_INFINITY, |
| 0, 1, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: Infinity"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix2D.of( |
| 1, 0, Double.NEGATIVE_INFINITY, |
| 0, 1, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: -Infinity"); |
| } |
| |
| @Test |
| public void testHashCode() { |
| // arrange |
| double[] values = new double[] { |
| 1, 2, 3, |
| 5, 6, 7 |
| }; |
| |
| // act/assert |
| int orig = AffineTransformMatrix2D.of(values).hashCode(); |
| int same = AffineTransformMatrix2D.of(values).hashCode(); |
| |
| Assert.assertEquals(orig, same); |
| |
| double[] temp; |
| for (int i=0; i<values.length; ++i) { |
| temp = values.clone(); |
| temp[i] = 0; |
| |
| int modified = AffineTransformMatrix2D.of(temp).hashCode(); |
| |
| Assert.assertNotEquals(orig, modified); |
| } |
| } |
| |
| @Test |
| public void testEquals() { |
| // arrange |
| double[] values = new double[] { |
| 1, 2, 3, |
| 5, 6, 7 |
| }; |
| |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of(values); |
| |
| // act/assert |
| Assert.assertTrue(a.equals(a)); |
| |
| Assert.assertFalse(a.equals(null)); |
| Assert.assertFalse(a.equals(new Object())); |
| |
| double[] temp; |
| for (int i=0; i<values.length; ++i) { |
| temp = values.clone(); |
| temp[i] = 0; |
| |
| AffineTransformMatrix2D modified = AffineTransformMatrix2D.of(temp); |
| |
| Assert.assertFalse(a.equals(modified)); |
| } |
| } |
| |
| @Test |
| public void testToString() { |
| // arrange |
| AffineTransformMatrix2D a = AffineTransformMatrix2D.of( |
| 1, 2, 3, |
| 5, 6, 7 |
| ); |
| |
| // act |
| String result = a.toString(); |
| |
| // assert |
| Assert.assertEquals("[ 1.0, 2.0, 3.0; " |
| + "5.0, 6.0, 7.0 ]", result); |
| } |
| |
| @FunctionalInterface |
| private static interface Coordinate2DTest { |
| |
| void run(double x, double y); |
| } |
| |
| private static void runWithCoordinates(Coordinate2DTest test) { |
| runWithCoordinates(test, -1e-2, 1e-2, 5e-3); |
| runWithCoordinates(test, -1e2, 1e2, 5); |
| } |
| |
| private static void runWithCoordinates(Coordinate2DTest test, double min, double max, double step) |
| { |
| for (double x = min; x <= max; x += step) { |
| for (double y = min; y <= max; y += step) { |
| test.run(x, y); |
| } |
| } |
| } |
| } |