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 }