View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.geometry.euclidean.twod.rotation;
    18
    19  import java.util.function.BiFunction;
    20  import java.util.function.DoubleFunction;
    21
    22  import org.apache.commons.geometry.core.GeometryTestUtils;
    23  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
    24  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
    25  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    26  import org.apache.commons.geometry.euclidean.EuclideanTransform;
    27  import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
    28  import org.apache.commons.geometry.euclidean.twod.Line;
    29  import org.apache.commons.geometry.euclidean.twod.Lines;
    30  import org.apache.commons.geometry.euclidean.twod.Vector2D;
    31  import org.apache.commons.numbers.angle.PlaneAngleRadians;
    32  import org.junit.Assert;
    33  import org.junit.Test;
    34
    35  public class Rotation2DTest {
    36
    37      private static final double TEST_EPS = 1e-10;
    38
    39      private static final DoublePrecisionContext TEST_PRECISION =
    40              new EpsilonDoublePrecisionContext(TEST_EPS);
    41
    42      @Test
    43      public void testIdentity() {
    44          // act
    45          final Rotation2D r = Rotation2D.identity();
    46
    47          // assert
    48          Assert.assertEquals(0.0, r.getAngle(), 0.0);
    49          Assert.assertTrue(r.preservesOrientation());
    50      }
    51
    52      @Test
    53      public void testProperties() {
    54          // act
    55          final Rotation2D r = Rotation2D.of(100.0);
    56
    57          // assert
    58          Assert.assertEquals(100.0, r.getAngle(), 0.0);
    59          Assert.assertTrue(r.preservesOrientation());
    60      }
    61
    62      @Test
    63      public void testApply() {
    64          // act/assert
    65          checkApply(1.0, Vector2D.ZERO, Vector2D.ZERO);
    66
    67          checkApply(0.0, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X);
    68          checkApply(PlaneAngleRadians.PI_OVER_TWO, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_Y);
    69          checkApply(PlaneAngleRadians.PI, Vector2D.Unit.PLUS_X, Vector2D.Unit.MINUS_X);
    70          checkApply(PlaneAngleRadians.THREE_PI_OVER_TWO, Vector2D.Unit.PLUS_X, Vector2D.Unit.MINUS_Y);
    71          checkApply(PlaneAngleRadians.TWO_PI, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X);
    72
    73          checkRotate(Rotation2D::of, Rotation2D::apply);
    74      }
    75
    76      @Test
    77      public void testApplyVector() {
    78          // act/assert
    79          checkApplyVector(1.0, Vector2D.ZERO, Vector2D.ZERO);
    80
    81          checkApplyVector(0.0, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X);
    82          checkApplyVector(PlaneAngleRadians.PI_OVER_TWO, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_Y);
    83          checkApplyVector(PlaneAngleRadians.PI, Vector2D.Unit.PLUS_X, Vector2D.Unit.MINUS_X);
    84          checkApplyVector(PlaneAngleRadians.THREE_PI_OVER_TWO, Vector2D.Unit.PLUS_X, Vector2D.Unit.MINUS_Y);
    85          checkApplyVector(PlaneAngleRadians.TWO_PI, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X);
    86
    87          checkRotate(Rotation2D::of, Rotation2D::applyVector);
    88      }
    89
    90      @Test
    91      public void testInverse_properties() {
    92          // arrange
    93          final Rotation2D orig = Rotation2D.of(100.0);
    94
    95          // act
    96          final Rotation2D r = orig.inverse();
    97
    98          // assert
    99          Assert.assertEquals(-100.0, r.getAngle(), 0.0);
    100         Assert.assertTrue(r.preservesOrientation());
    101     }
    102
    103     @Test
    104     public void testInverse_apply() {
    105         // arrange
    106         final Rotation2D orig = Rotation2D.of(100.0);
    107         final Rotation2D inv = orig.inverse();
    108
    109         final Vector2D v1 = Vector2D.of(1, 2);
    110         final Vector2D v2 = Vector2D.of(-3, 4);
    111         final Vector2D v3 = Vector2D.of(-5, -6);
    112         final Vector2D v4 = Vector2D.of(7, -8);
    113
    114         // act/assert
    115         EuclideanTestUtils.assertCoordinatesEqual(v1, orig.apply(inv.apply(v1)), TEST_EPS);
    116         EuclideanTestUtils.assertCoordinatesEqual(v2, inv.apply(orig.apply(v2)), TEST_EPS);
    117         EuclideanTestUtils.assertCoordinatesEqual(v3, orig.apply(inv.apply(v3)), TEST_EPS);
    118         EuclideanTestUtils.assertCoordinatesEqual(v4, inv.apply(orig.apply(v4)), TEST_EPS);
    119     }
    120
    121     @Test
    122     public void testToMatrix() {
    123         // arrange
    124         final double angle = 0.1 * Math.PI;
    125
    126         // act
    127         final AffineTransformMatrix2D m = Rotation2D.of(angle).toMatrix();
    128
    129         // assert
    130         final double sin = Math.sin(angle);
    131         final double cos = Math.cos(angle);
    132
    133         final double[] expected = {
    134             cos, -sin, 0,
    135             sin, cos, 0
    136         };
    137         Assert.assertArrayEquals(expected, m.toArray(), 0.0);
    138     }
    139
    140     @Test
    141     public void testToMatrix_apply() {
    142         // act/assert
    143         checkRotate(angle -> Rotation2D.of(angle).toMatrix(), AffineTransformMatrix2D::apply);
    144     }
    145
    146     @Test
    147     public void testCreateRotationVector() {
    148         // arrange
    149         final double min = -8;
    150         final double max = 8;
    151         final double step = 1;
    152
    153         EuclideanTestUtils.permuteSkipZero(min, max, step, (ux, uy) -> {
    154             EuclideanTestUtils.permuteSkipZero(min, max, step, (vx, vy) -> {
    155
    156                 final Vector2D u = Vector2D.of(ux, uy);
    157                 final Vector2D v = Vector2D.of(vx, vy);
    158
    159                 // act
    160                 final Rotation2D r = Rotation2D.createVectorRotation(u, v);
    161
    162                 // assert
    163                 EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), r.apply(u).normalize(), TEST_EPS); // u -> v
    164                 Assert.assertEquals(0.0, v.dot(r.apply(u.orthogonal())), TEST_EPS); // preserves orthogonality
    165             });
    166         });
    167     }
    168
    169     @Test
    170     public void testCreateRotationVector_invalidVectors() {
    171         // arrange
    172         final Vector2D vec = Vector2D.of(1, 1);
    173
    174         final Vector2D zero = Vector2D.ZERO;
    175         final Vector2D nan = Vector2D.NaN;
    176         final Vector2D posInf = Vector2D.POSITIVE_INFINITY;
    177         final Vector2D negInf = Vector2D.POSITIVE_INFINITY;
    178
    179         // act/assert
    180         GeometryTestUtils.assertThrows(() -> {
    181             Rotation2D.createVectorRotation(zero, vec);
    182         }, IllegalArgumentException.class);
    183         GeometryTestUtils.assertThrows(() -> {
    184             Rotation2D.createVectorRotation(vec, zero);
    185         }, IllegalArgumentException.class);
    186
    187         GeometryTestUtils.assertThrows(() -> {
    188             Rotation2D.createVectorRotation(nan, vec);
    189         }, IllegalArgumentException.class);
    190         GeometryTestUtils.assertThrows(() -> {
    191             Rotation2D.createVectorRotation(vec, nan);
    192         }, IllegalArgumentException.class);
    193
    194         GeometryTestUtils.assertThrows(() -> {
    195             Rotation2D.createVectorRotation(posInf, vec);
    196         }, IllegalArgumentException.class);
    197         GeometryTestUtils.assertThrows(() -> {
    198             Rotation2D.createVectorRotation(vec, negInf);
    199         }, IllegalArgumentException.class);
    200
    201         GeometryTestUtils.assertThrows(() -> {
    202             Rotation2D.createVectorRotation(zero, nan);
    203         }, IllegalArgumentException.class);
    204         GeometryTestUtils.assertThrows(() -> {
    205             Rotation2D.createVectorRotation(negInf, posInf);
    206         }, IllegalArgumentException.class);
    207     }
    208
    209     @Test
    210     public void testHashCode() {
    211         // arrange
    212         final Rotation2D a = Rotation2D.of(1.0);
    213         final Rotation2D b = Rotation2D.of(0.0);
    214         final Rotation2D c = Rotation2D.of(-1.0);
    215         final Rotation2D d = Rotation2D.of(1.0);
    216
    217         final int hash = a.hashCode();
    218
    219         // act/assert
    220         Assert.assertEquals(hash, a.hashCode());
    221
    222         Assert.assertNotEquals(hash, b.hashCode());
    223         Assert.assertNotEquals(hash, c.hashCode());
    224
    225         Assert.assertEquals(hash, d.hashCode());
    226     }
    227
    228     @Test
    229     public void testEquals() {
    230         // arrange
    231         final Rotation2D a = Rotation2D.of(1.0);
    232         final Rotation2D b = Rotation2D.of(0.0);
    233         final Rotation2D c = Rotation2D.of(-1.0);
    234         final Rotation2D d = Rotation2D.of(1.0);
    235
    236         // act/assert
    237         Assert.assertFalse(a.equals(null));
    238         Assert.assertFalse(a.equals(new Object()));
    239
    240         Assert.assertEquals(a, a);
    241
    242         Assert.assertNotEquals(a, b);
    243         Assert.assertNotEquals(a, c);
    244
    245         Assert.assertEquals(a, d);
    246         Assert.assertEquals(d, a);
    247     }
    248
    249     @Test
    250     public void testToString() {
    251         // arrange
    252         final Rotation2D r = Rotation2D.of(1.0);
    253
    254         // act
    255         final String str = r.toString();
    256
    257         // assert
    258         Assert.assertEquals("Rotation2D[angle=1.0]", str);
    259     }
    260
    261     private static void checkApply(final double angle, final Vector2D pt, final Vector2D expectedPt) {
    262         final Rotation2D r = Rotation2D.of(angle);
    263         EuclideanTestUtils.assertCoordinatesEqual(expectedPt, r.apply(pt), TEST_EPS);
    264     }
    265
    266     private static void checkApplyVector(final double angle, final Vector2D pt, final Vector2D expectedPt) {
    267         final Rotation2D r = Rotation2D.of(angle);
    268         EuclideanTestUtils.assertCoordinatesEqual(expectedPt, r.applyVector(pt), TEST_EPS);
    269     }
    270
    271     /** Check a rotation transform for consistency against a variety of points and rotation angles.
    272      * @param factory function used to create a rotation transform from an input angle
    273      * @param transformFn function that accepts the transform and a point and returns
    274      *      the transformed point
    275      */
    276     private static <T extends EuclideanTransform<Vector2D>> void checkRotate(
    277             final DoubleFunction<T> factory, final BiFunction<T, Vector2D, Vector2D> transformFn) {
    278
    279         // check zero
    280         final T transform = factory.apply(0);
    281         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, transformFn.apply(transform, Vector2D.ZERO), TEST_EPS);
    282
    283         // check a variety of non-zero points
    284         EuclideanTestUtils.permuteSkipZero(-2, -2, 1, (x, y) -> {
    285             checkRotatePoint(Vector2D.of(x, y), factory, transformFn);
    286         });
    287     }
    288
    289     /** Check a rotation transform for consistency when transforming a single point against a
    290      * variety of rotation angles.
    291      * @param pt point to transform
    292      * @param factory function used to create a rotation transform from an input angle
    293      * @param transformFn function that accepts the transform and a point and returns
    294      *      the transformed point
    295      */
    296     private static <T extends EuclideanTransform<Vector2D>> void checkRotatePoint(
    297             final Vector2D pt, final DoubleFunction<T> factory, final BiFunction<T, Vector2D, Vector2D> transformFn) {
    298
    299         // arrange
    300         final double limit = 4 * Math.PI;
    301         final double inc = 0.25;
    302
    303         final Line line = Lines.fromPointAndDirection(Vector2D.ZERO, pt, TEST_PRECISION);
    304
    305         T transform;
    306         Vector2D resultPt;
    307         Line resultLine;
    308         for (double angle = -limit; angle < limit; angle += inc) {
    309             transform = factory.apply(angle);
    310
    311             // act
    312             resultPt = transformFn.apply(transform, pt);
    313
    314             // assert
    315             // check that the norm is unchanged
    316             Assert.assertEquals(pt.norm(), resultPt.norm(), TEST_EPS);
    317
    318             resultLine = Lines.fromPointAndDirection(Vector2D.ZERO, resultPt, TEST_PRECISION);
    319             final double lineAngle = line.angle(resultLine);
    320
    321             // check that the angle is what we expect
    322             Assert.assertEquals(PlaneAngleRadians.normalizeBetweenMinusPiAndPi(angle), lineAngle, TEST_EPS);
    323         }
    324     }
    325 }