| /* |
| * 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.threed; |
| |
| 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.EuclideanTestUtils.PermuteCallback3D; |
| import org.apache.commons.geometry.euclidean.exception.NonInvertibleTransformException; |
| import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation; |
| import org.apache.commons.geometry.euclidean.threed.rotation.StandardRotations; |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| public class AffineTransformMatrix3DTest { |
| |
| private static final double EPS = 1e-12; |
| |
| @Test |
| public void testOf() { |
| // arrange |
| double[] arr = { |
| 1, 2, 3, 4, |
| 5, 6, 7, 8, |
| 9, 10, 11, 12 |
| }; |
| |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.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(() -> AffineTransformMatrix3D.of(1, 2), |
| IllegalArgumentException.class, "Dimension mismatch: 2 != 12"); |
| } |
| |
| @Test |
| public void testFromColumnVectors_threeVectors() { |
| // arrange |
| Vector3D u = Vector3D.of(1, 2, 3); |
| Vector3D v = Vector3D.of(4, 5, 6); |
| Vector3D w = Vector3D.of(7, 8, 9); |
| |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.fromColumnVectors(u, v, w); |
| |
| // assert |
| Assert.assertArrayEquals(new double[] { |
| 1, 4, 7, 0, |
| 2, 5, 8, 0, |
| 3, 6, 9, 0 |
| }, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testFromColumnVectors_fourVectors() { |
| // arrange |
| Vector3D u = Vector3D.of(1, 2, 3); |
| Vector3D v = Vector3D.of(4, 5, 6); |
| Vector3D w = Vector3D.of(7, 8, 9); |
| Vector3D t = Vector3D.of(10, 11, 12); |
| |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.fromColumnVectors(u, v, w, t); |
| |
| // assert |
| Assert.assertArrayEquals(new double[] { |
| 1, 4, 7, 10, |
| 2, 5, 8, 11, |
| 3, 6, 9, 12 |
| }, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testIdentity() { |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity(); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateTranslation_xyz() { |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.createTranslation(2, 3, 4); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, 2, |
| 0, 1, 0, 3, |
| 0, 0, 1, 4 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateTranslation_vector() { |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.createTranslation(Vector3D.of(5, 6, 7)); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, 5, |
| 0, 1, 0, 6, |
| 0, 0, 1, 7 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateScale_xyz() { |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.createScale(2, 3, 4); |
| |
| // assert |
| double[] expected = { |
| 2, 0, 0, 0, |
| 0, 3, 0, 0, |
| 0, 0, 4, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testTranslate_xyz() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 2, 0, 0, 10, |
| 0, 3, 0, 11, |
| 0, 0, 4, 12 |
| ); |
| |
| // act |
| AffineTransformMatrix3D result = a.translate(4, 5, 6); |
| |
| // assert |
| double[] expected = { |
| 2, 0, 0, 14, |
| 0, 3, 0, 16, |
| 0, 0, 4, 18 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testTranslate_vector() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 2, 0, 0, 10, |
| 0, 3, 0, 11, |
| 0, 0, 4, 12 |
| ); |
| |
| // act |
| AffineTransformMatrix3D result = a.translate(Vector3D.of(7, 8, 9)); |
| |
| // assert |
| double[] expected = { |
| 2, 0, 0, 17, |
| 0, 3, 0, 19, |
| 0, 0, 4, 21 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateScale_vector() { |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.createScale(Vector3D.of(4, 5, 6)); |
| |
| // assert |
| double[] expected = { |
| 4, 0, 0, 0, |
| 0, 5, 0, 0, |
| 0, 0, 6, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateScale_singleValue() { |
| // act |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.createScale(7); |
| |
| // assert |
| double[] expected = { |
| 7, 0, 0, 0, |
| 0, 7, 0, 0, |
| 0, 0, 7, 0 |
| }; |
| Assert.assertArrayEquals(expected, transform.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testScale_xyz() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 2, 0, 0, 10, |
| 0, 3, 0, 11, |
| 0, 0, 4, 12 |
| ); |
| |
| // act |
| AffineTransformMatrix3D result = a.scale(4, 5, 6); |
| |
| // assert |
| double[] expected = { |
| 8, 0, 0, 40, |
| 0, 15, 0, 55, |
| 0, 0, 24, 72 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testScale_vector() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 2, 0, 0, 10, |
| 0, 3, 0, 11, |
| 0, 0, 4, 12 |
| ); |
| |
| // act |
| AffineTransformMatrix3D result = a.scale(Vector3D.of(7, 8, 9)); |
| |
| // assert |
| double[] expected = { |
| 14, 0, 0, 70, |
| 0, 24, 0, 88, |
| 0, 0, 36, 108 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testScale_singleValue() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 2, 0, 0, 10, |
| 0, 3, 0, 11, |
| 0, 0, 4, 12 |
| ); |
| |
| // act |
| AffineTransformMatrix3D result = a.scale(10); |
| |
| // assert |
| double[] expected = { |
| 20, 0, 0, 100, |
| 0, 30, 0, 110, |
| 0, 0, 40, 120 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testCreateRotation() { |
| // arrange |
| Vector3D center = Vector3D.of(1, 2, 3); |
| QuaternionRotation rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, PlaneAngleRadians.PI_OVER_TWO); |
| |
| // act |
| AffineTransformMatrix3D result = AffineTransformMatrix3D.createRotation(center, rotation); |
| |
| // assert |
| double[] expected = { |
| 0, -1, 0, 3, |
| 1, 0, 0, 1, |
| 0, 0, 1, 0 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), EPS); |
| } |
| |
| @Test |
| public void testRotate() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 1, 2, 3, 4, |
| 5, 6, 7, 8, |
| 9, 10, 11, 12 |
| ); |
| |
| QuaternionRotation rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, PlaneAngleRadians.PI_OVER_TWO); |
| |
| // act |
| AffineTransformMatrix3D result = a.rotate(rotation); |
| |
| // assert |
| double[] expected = { |
| -5, -6, -7, -8, |
| 1, 2, 3, 4, |
| 9, 10, 11, 12 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), EPS); |
| } |
| |
| @Test |
| public void testRotate_aroundCenter() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 1, 2, 3, 4, |
| 5, 6, 7, 8, |
| 9, 10, 11, 12 |
| ); |
| |
| Vector3D center = Vector3D.of(1, 2, 3); |
| QuaternionRotation rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, PlaneAngleRadians.PI_OVER_TWO); |
| |
| // act |
| AffineTransformMatrix3D result = a.rotate(center, rotation); |
| |
| // assert |
| double[] expected = { |
| -5, -6, -7, -5, |
| 1, 2, 3, 5, |
| 9, 10, 11, 12 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), EPS); |
| } |
| |
| @Test |
| public void testApply_identity() { |
| // arrange |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity(); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D v = Vector3D.of(x, y, z); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(v, transform.apply(v), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_translate() { |
| // arrange |
| Vector3D translation = Vector3D.of(1.1, -PlaneAngleRadians.PI, 5.5); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .translate(translation); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = vec.add(translation); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_scale() { |
| // arrange |
| Vector3D factors = Vector3D.of(2.0, -3.0, 4.0); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .scale(factors); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = Vector3D.of(factors.getX() * x, factors.getY() * y, factors.getZ() * z); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_translateThenScale() { |
| // arrange |
| Vector3D translation = Vector3D.of(-2.0, -3.0, -4.0); |
| Vector3D scale = Vector3D.of(5.0, 6.0, 7.0); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .translate(translation) |
| .scale(scale); |
| |
| // act/assert |
| EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-5, -12, -21), transform.apply(Vector3D.of(1, 1, 1)), EPS); |
| |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = Vector3D.of( |
| (x + translation.getX()) * scale.getX(), |
| (y + translation.getY()) * scale.getY(), |
| (z + translation.getZ()) * scale.getZ() |
| ); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_scaleThenTranslate() { |
| // arrange |
| Vector3D scale = Vector3D.of(5.0, 6.0, 7.0); |
| Vector3D translation = Vector3D.of(-2.0, -3.0, -4.0); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .scale(scale) |
| .translate(translation); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = Vector3D.of( |
| (x * scale.getX()) + translation.getX(), |
| (y * scale.getY()) + translation.getY(), |
| (z * scale.getZ()) + translation.getZ() |
| ); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_rotate() { |
| // arrange |
| QuaternionRotation rotation = QuaternionRotation.fromAxisAngle(Vector3D.of(1, 1, 1), 2.0 * PlaneAngleRadians.PI / 3.0); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity().rotate(rotation); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = StandardRotations.PLUS_DIAGONAL_TWO_THIRDS_PI.apply(vec); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApply_rotate_aroundCenter() { |
| // arrange |
| double scaleFactor = 2; |
| Vector3D center = Vector3D.of(3, -4, 5); |
| QuaternionRotation rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, PlaneAngleRadians.PI_OVER_TWO); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .scale(scaleFactor) |
| .rotate(center, rotation); |
| |
| // act/assert |
| EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, -3, 2), transform.apply(Vector3D.of(2, -2, 1)), EPS); |
| |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = StandardRotations.PLUS_Z_HALF_PI.apply(vec.multiply(scaleFactor).subtract(center)).add(center); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyVector_identity() { |
| // arrange |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity(); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D v = Vector3D.of(x, y, z); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(v, transform.applyVector(v), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyVector_translate() { |
| // arrange |
| Vector3D translation = Vector3D.of(1.1, -PlaneAngleRadians.PI, 5.5); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .translate(translation); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(vec, transform.applyVector(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyVector_scale() { |
| // arrange |
| Vector3D factors = Vector3D.of(2.0, -3.0, 4.0); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .scale(factors); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = Vector3D.of(factors.getX() * x, factors.getY() * y, factors.getZ() * z); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyVector(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyVector_representsDisplacement() { |
| // arrange |
| Vector3D p1 = Vector3D.of(1, 2, 3); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .scale(1.5) |
| .translate(4, 6, 5) |
| .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, PlaneAngleRadians.PI_OVER_TWO)); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D p2 = Vector3D.of(x, y, z); |
| Vector3D input = p1.subtract(p2); |
| |
| Vector3D expected = transform.apply(p1).subtract(transform.apply(p2)); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expected, transform.applyVector(input), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_identity() { |
| // arrange |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity(); |
| |
| // act/assert |
| EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y, z) -> { |
| Vector3D v = Vector3D.of(x, y, z); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), transform.applyDirection(v), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_translate() { |
| // arrange |
| Vector3D translation = Vector3D.of(1.1, -PlaneAngleRadians.PI, 5.5); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .translate(translation); |
| |
| // act/assert |
| EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(vec.normalize(), transform.applyDirection(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_scale() { |
| // arrange |
| Vector3D factors = Vector3D.of(2.0, -3.0, 4.0); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .scale(factors); |
| |
| // act/assert |
| EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = Vector3D.of(factors.getX() * x, factors.getY() * y, factors.getZ() * z).normalize(); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyDirection(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testApplyDirection_representsNormalizedDisplacement() { |
| // arrange |
| Vector3D p1 = Vector3D.of(1, 2, 3); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity() |
| .scale(1.5) |
| .translate(4, 6, 5) |
| .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, PlaneAngleRadians.PI_OVER_TWO)); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D p2 = Vector3D.of(x, y, z); |
| Vector3D input = p1.subtract(p2); |
| |
| Vector3D 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(() -> AffineTransformMatrix3D.createScale(1, 0, 1).applyDirection(Vector3D.Unit.PLUS_Y), |
| IllegalNormException.class); |
| GeometryTestUtils.assertThrows(() -> AffineTransformMatrix3D.createScale(2).applyDirection(Vector3D.ZERO), |
| IllegalNormException.class); |
| } |
| |
| @Test |
| public void testMultiply() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 1, 2, 3, 4, |
| 5, 6, 7, 8, |
| 9, 10, 11, 12 |
| ); |
| AffineTransformMatrix3D b = AffineTransformMatrix3D.of( |
| 13, 14, 15, 16, |
| 17, 18, 19, 20, |
| 21, 22, 23, 24 |
| ); |
| |
| // act |
| AffineTransformMatrix3D result = a.multiply(b); |
| |
| // assert |
| double[] arr = result.toArray(); |
| Assert.assertArrayEquals(new double[] { |
| 110, 116, 122, 132, |
| 314, 332, 350, 376, |
| 518, 548, 578, 620 |
| }, arr, EPS); |
| } |
| |
| @Test |
| public void testDeterminant() { |
| // act/assert |
| Assert.assertEquals(1.0, AffineTransformMatrix3D.identity().determinant(), EPS); |
| Assert.assertEquals(1.0, AffineTransformMatrix3D.of( |
| 1, 0, 0, 10, |
| 0, 1, 0, 11, |
| 0, 0, 1, 12 |
| ).determinant(), EPS); |
| Assert.assertEquals(-1.0, AffineTransformMatrix3D.of( |
| -1, 0, 0, 10, |
| 0, 1, 0, 11, |
| 0, 0, 1, 12 |
| ).determinant(), EPS); |
| Assert.assertEquals(1.0, AffineTransformMatrix3D.of( |
| -1, 0, 0, 10, |
| 0, -1, 0, 11, |
| 0, 0, 1, 12 |
| ).determinant(), EPS); |
| Assert.assertEquals(-1.0, AffineTransformMatrix3D.of( |
| -1, 0, 0, 10, |
| 0, -1, 0, 11, |
| 0, 0, -1, 12 |
| ).determinant(), EPS); |
| Assert.assertEquals(49.0, AffineTransformMatrix3D.of( |
| 2, -3, 1, 10, |
| 2, 0, -1, 11, |
| 1, 4, 5, -12 |
| ).determinant(), EPS); |
| Assert.assertEquals(0.0, AffineTransformMatrix3D.of( |
| 1, 2, 3, 0, |
| 4, 5, 6, 0, |
| 7, 8, 9, 0 |
| ).determinant(), EPS); |
| } |
| |
| @Test |
| public void testPreservesOrientation() { |
| // act/assert |
| Assert.assertTrue(AffineTransformMatrix3D.identity().preservesOrientation()); |
| Assert.assertTrue(AffineTransformMatrix3D.of( |
| 1, 0, 0, 10, |
| 0, 1, 0, 11, |
| 0, 0, 1, 12 |
| ).preservesOrientation()); |
| Assert.assertTrue(AffineTransformMatrix3D.of( |
| 2, -3, 1, 10, |
| 2, 0, -1, 11, |
| 1, 4, 5, -12 |
| ).preservesOrientation()); |
| |
| Assert.assertFalse(AffineTransformMatrix3D.of( |
| -1, 0, 0, 10, |
| 0, 1, 0, 11, |
| 0, 0, 1, 12 |
| ).preservesOrientation()); |
| |
| Assert.assertTrue(AffineTransformMatrix3D.of( |
| -1, 0, 0, 10, |
| 0, -1, 0, 11, |
| 0, 0, 1, 12 |
| ).preservesOrientation()); |
| |
| Assert.assertFalse(AffineTransformMatrix3D.of( |
| -1, 0, 0, 10, |
| 0, -1, 0, 11, |
| 0, 0, -1, 12 |
| ).preservesOrientation()); |
| Assert.assertFalse(AffineTransformMatrix3D.of( |
| 1, 2, 3, 0, |
| 4, 5, 6, 0, |
| 7, 8, 9, 0 |
| ).preservesOrientation()); |
| } |
| |
| @Test |
| public void testToMatrix() { |
| // arrange |
| AffineTransformMatrix3D m = AffineTransformMatrix3D.createScale(3); |
| |
| // act/assert |
| Assert.assertSame(m, m.toMatrix()); |
| } |
| |
| @Test |
| public void testMultiply_combinesTransformOperations() { |
| // arrange |
| Vector3D translation1 = Vector3D.of(1, 2, 3); |
| double scale = 2.0; |
| Vector3D translation2 = Vector3D.of(4, 5, 6); |
| |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.createTranslation(translation1); |
| AffineTransformMatrix3D b = AffineTransformMatrix3D.createScale(scale); |
| AffineTransformMatrix3D c = AffineTransformMatrix3D.identity(); |
| AffineTransformMatrix3D d = AffineTransformMatrix3D.createTranslation(translation2); |
| |
| // act |
| AffineTransformMatrix3D transform = d.multiply(c).multiply(b).multiply(a); |
| |
| // assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = vec |
| .add(translation1) |
| .multiply(scale) |
| .add(translation2); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testPremultiply() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 1, 2, 3, 4, |
| 5, 6, 7, 8, |
| 9, 10, 11, 12 |
| ); |
| AffineTransformMatrix3D b = AffineTransformMatrix3D.of( |
| 13, 14, 15, 16, |
| 17, 18, 19, 20, |
| 21, 22, 23, 24 |
| ); |
| |
| // act |
| AffineTransformMatrix3D result = b.premultiply(a); |
| |
| // assert |
| double[] arr = result.toArray(); |
| Assert.assertArrayEquals(new double[] { |
| 110, 116, 122, 132, |
| 314, 332, 350, 376, |
| 518, 548, 578, 620 |
| }, arr, EPS); |
| } |
| |
| @Test |
| public void testPremultiply_combinesTransformOperations() { |
| // arrange |
| Vector3D translation1 = Vector3D.of(1, 2, 3); |
| double scale = 2.0; |
| Vector3D translation2 = Vector3D.of(4, 5, 6); |
| |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.createTranslation(translation1); |
| AffineTransformMatrix3D b = AffineTransformMatrix3D.createScale(scale); |
| AffineTransformMatrix3D c = AffineTransformMatrix3D.identity(); |
| AffineTransformMatrix3D d = AffineTransformMatrix3D.createTranslation(translation2); |
| |
| // act |
| AffineTransformMatrix3D transform = a.premultiply(b).premultiply(c).premultiply(d); |
| |
| // assert |
| runWithCoordinates((x, y, z) -> { |
| Vector3D vec = Vector3D.of(x, y, z); |
| |
| Vector3D expectedVec = vec |
| .add(translation1) |
| .multiply(scale) |
| .add(translation2); |
| |
| EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS); |
| }); |
| } |
| |
| @Test |
| public void testInverse_identity() { |
| // act |
| AffineTransformMatrix3D inverse = AffineTransformMatrix3D.identity().inverse(); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testInverse_multiplyByInverse_producesIdentity() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 1, 3, 7, 8, |
| 2, 4, 9, 12, |
| 5, 6, 10, 11 |
| ); |
| |
| AffineTransformMatrix3D inv = a.inverse(); |
| |
| // act |
| AffineTransformMatrix3D result = inv.multiply(a); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0 |
| }; |
| Assert.assertArrayEquals(expected, result.toArray(), EPS); |
| } |
| |
| @Test |
| public void testInverse_translate() { |
| // arrange |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.createTranslation(1, -2, 4); |
| |
| // act |
| AffineTransformMatrix3D inverse = transform.inverse(); |
| |
| // assert |
| double[] expected = { |
| 1, 0, 0, -1, |
| 0, 1, 0, 2, |
| 0, 0, 1, -4 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testInverse_scale() { |
| // arrange |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.createScale(10, -2, 4); |
| |
| // act |
| AffineTransformMatrix3D inverse = transform.inverse(); |
| |
| // assert |
| double[] expected = { |
| 0.1, 0, 0, 0, |
| 0, -0.5, 0, 0, |
| 0, 0, 0.25, 0 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), 0.0); |
| } |
| |
| @Test |
| public void testInverse_rotate() { |
| // arrange |
| Vector3D center = Vector3D.of(1, 2, 3); |
| QuaternionRotation rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, PlaneAngleRadians.PI_OVER_TWO); |
| |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D.createRotation(center, rotation); |
| |
| // act |
| AffineTransformMatrix3D inverse = transform.inverse(); |
| |
| // assert |
| double[] expected = { |
| 0, 1, 0, -1, |
| -1, 0, 0, 3, |
| 0, 0, 1, 0 |
| }; |
| Assert.assertArrayEquals(expected, inverse.toArray(), EPS); |
| } |
| |
| @Test |
| public void testInverse_undoesOriginalTransform() { |
| // arrange |
| Vector3D v1 = Vector3D.ZERO; |
| Vector3D v2 = Vector3D.Unit.PLUS_X; |
| Vector3D v3 = Vector3D.of(1, 1, 1); |
| Vector3D v4 = Vector3D.of(-2, 3, 4); |
| |
| Vector3D center = Vector3D.of(1, 2, 3); |
| QuaternionRotation rotation = QuaternionRotation.fromAxisAngle(Vector3D.of(1, 2, 3), 0.25); |
| |
| // act/assert |
| runWithCoordinates((x, y, z) -> { |
| AffineTransformMatrix3D transform = AffineTransformMatrix3D |
| .createTranslation(x, y, z) |
| .scale(2, 3, 4) |
| .rotate(center, rotation) |
| .translate(x / 3, y / 3, z / 3); |
| |
| AffineTransformMatrix3D 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(() -> { |
| AffineTransformMatrix3D.of( |
| 0, 0, 0, 0, |
| 0, 0, 0, 0, |
| 0, 0, 0, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is 0.0"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix3D.of( |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, Double.NaN, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is NaN"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix3D.of( |
| 1, 0, 0, 0, |
| 0, Double.NEGATIVE_INFINITY, 0, 0, |
| 0, 0, 1, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is NaN"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix3D.of( |
| Double.POSITIVE_INFINITY, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; matrix determinant is NaN"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix3D.of( |
| 1, 0, 0, Double.NaN, |
| 0, 1, 0, 0, |
| 0, 0, 1, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: NaN"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix3D.of( |
| 1, 0, 0, 0, |
| 0, 1, 0, Double.POSITIVE_INFINITY, |
| 0, 0, 1, 0).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: Infinity"); |
| |
| GeometryTestUtils.assertThrows(() -> { |
| AffineTransformMatrix3D.of( |
| 1, 0, 0, 0, |
| 0, 1, 0, 0, |
| 0, 0, 1, Double.NEGATIVE_INFINITY).inverse(); |
| }, NonInvertibleTransformException.class, "Transform is not invertible; invalid matrix element: -Infinity"); |
| } |
| |
| @Test |
| public void testHashCode() { |
| // arrange |
| double[] values = new double[] { |
| 1, 2, 3, 4, |
| 5, 6, 7, 8, |
| 9, 10, 11, 12 |
| }; |
| |
| // act/assert |
| int orig = AffineTransformMatrix3D.of(values).hashCode(); |
| int same = AffineTransformMatrix3D.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 = AffineTransformMatrix3D.of(temp).hashCode(); |
| |
| Assert.assertNotEquals(orig, modified); |
| } |
| } |
| |
| @Test |
| public void testEquals() { |
| // arrange |
| double[] values = new double[] { |
| 1, 2, 3, 4, |
| 5, 6, 7, 8, |
| 9, 10, 11, 12 |
| }; |
| |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.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; |
| |
| AffineTransformMatrix3D modified = AffineTransformMatrix3D.of(temp); |
| |
| Assert.assertFalse(a.equals(modified)); |
| } |
| } |
| |
| @Test |
| public void testToString() { |
| // arrange |
| AffineTransformMatrix3D a = AffineTransformMatrix3D.of( |
| 1, 2, 3, 4, |
| 5, 6, 7, 8, |
| 9, 10, 11, 12 |
| ); |
| |
| // act |
| String result = a.toString(); |
| |
| // assert |
| Assert.assertEquals("[ 1.0, 2.0, 3.0, 4.0; " |
| + "5.0, 6.0, 7.0, 8.0; " |
| + "9.0, 10.0, 11.0, 12.0 ]", result); |
| } |
| |
| /** |
| * Run the given test callback with a wide range of (x, y, z) inputs. |
| * @param test |
| */ |
| private static void runWithCoordinates(PermuteCallback3D test) { |
| EuclideanTestUtils.permute(-1e-2, 1e-2, 5e-3, test); |
| EuclideanTestUtils.permute(-1e2, 1e2, 5, test); |
| } |
| } |