1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.euclidean.EuclideanTestUtils;
24 import org.apache.commons.geometry.euclidean.EuclideanTransform;
25 import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
26 import org.apache.commons.geometry.euclidean.twod.Line;
27 import org.apache.commons.geometry.euclidean.twod.Lines;
28 import org.apache.commons.geometry.euclidean.twod.Vector2D;
29 import org.apache.commons.numbers.angle.Angle;
30 import org.apache.commons.numbers.core.Precision;
31 import org.junit.jupiter.api.Assertions;
32 import org.junit.jupiter.api.Test;
33
34 class Rotation2DTest {
35
36 private static final double TEST_EPS = 1e-10;
37
38 private static final Precision.DoubleEquivalence TEST_PRECISION =
39 Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
40
41 private static final double THREE_PI_OVER_TWO = 3 * Math.PI / 2;
42
43 @Test
44 void testIdentity() {
45
46 final Rotation2D r = Rotation2D.identity();
47
48
49 Assertions.assertEquals(0.0, r.getAngle(), 0.0);
50 Assertions.assertTrue(r.preservesOrientation());
51 }
52
53 @Test
54 void testProperties() {
55
56 final Rotation2D r = Rotation2D.of(100.0);
57
58
59 Assertions.assertEquals(100.0, r.getAngle(), 0.0);
60 Assertions.assertTrue(r.preservesOrientation());
61 }
62
63 @Test
64 void testApply() {
65
66 checkApply(1.0, Vector2D.ZERO, Vector2D.ZERO);
67
68 checkApply(0.0, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X);
69 checkApply(Angle.PI_OVER_TWO, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_Y);
70 checkApply(Math.PI, Vector2D.Unit.PLUS_X, Vector2D.Unit.MINUS_X);
71 checkApply(THREE_PI_OVER_TWO, Vector2D.Unit.PLUS_X, Vector2D.Unit.MINUS_Y);
72 checkApply(Angle.TWO_PI, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X);
73
74 checkRotate(Rotation2D::of, Rotation2D::apply);
75 }
76
77 @Test
78 void testApplyVector() {
79
80 checkApplyVector(1.0, Vector2D.ZERO, Vector2D.ZERO);
81
82 checkApplyVector(0.0, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X);
83 checkApplyVector(Angle.PI_OVER_TWO, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_Y);
84 checkApplyVector(Math.PI, Vector2D.Unit.PLUS_X, Vector2D.Unit.MINUS_X);
85 checkApplyVector(THREE_PI_OVER_TWO, Vector2D.Unit.PLUS_X, Vector2D.Unit.MINUS_Y);
86 checkApplyVector(Angle.TWO_PI, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X);
87
88 checkRotate(Rotation2D::of, Rotation2D::applyVector);
89 }
90
91 @Test
92 void testInverse_properties() {
93
94 final Rotation2D orig = Rotation2D.of(100.0);
95
96
97 final Rotation2D r = orig.inverse();
98
99
100 Assertions.assertEquals(-100.0, r.getAngle(), 0.0);
101 Assertions.assertTrue(r.preservesOrientation());
102 }
103
104 @Test
105 void testInverse_apply() {
106
107 final Rotation2D orig = Rotation2D.of(100.0);
108 final Rotation2D inv = orig.inverse();
109
110 final Vector2D v1 = Vector2D.of(1, 2);
111 final Vector2D v2 = Vector2D.of(-3, 4);
112 final Vector2D v3 = Vector2D.of(-5, -6);
113 final Vector2D v4 = Vector2D.of(7, -8);
114
115
116 EuclideanTestUtils.assertCoordinatesEqual(v1, orig.apply(inv.apply(v1)), TEST_EPS);
117 EuclideanTestUtils.assertCoordinatesEqual(v2, inv.apply(orig.apply(v2)), TEST_EPS);
118 EuclideanTestUtils.assertCoordinatesEqual(v3, orig.apply(inv.apply(v3)), TEST_EPS);
119 EuclideanTestUtils.assertCoordinatesEqual(v4, inv.apply(orig.apply(v4)), TEST_EPS);
120 }
121
122 @Test
123 void testToMatrix() {
124
125 final double angle = 0.1 * Math.PI;
126
127
128 final AffineTransformMatrix2D m = Rotation2D.of(angle).toMatrix();
129
130
131 final double sin = Math.sin(angle);
132 final double cos = Math.cos(angle);
133
134 final double[] expected = {
135 cos, -sin, 0,
136 sin, cos, 0
137 };
138 Assertions.assertArrayEquals(expected, m.toArray(), 0.0);
139 }
140
141 @Test
142 void testToMatrix_apply() {
143
144 checkRotate(angle -> Rotation2D.of(angle).toMatrix(), AffineTransformMatrix2D::apply);
145 }
146
147 @Test
148 void testCreateRotationVector() {
149
150 final double min = -8;
151 final double max = 8;
152 final double step = 1;
153
154 EuclideanTestUtils.permuteSkipZero(min, max, step, (ux, uy) -> {
155 EuclideanTestUtils.permuteSkipZero(min, max, step, (vx, vy) -> {
156
157 final Vector2D u = Vector2D.of(ux, uy);
158 final Vector2D v = Vector2D.of(vx, vy);
159
160
161 final Rotation2D r = Rotation2D.createVectorRotation(u, v);
162
163
164 EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), r.apply(u).normalize(), TEST_EPS);
165 Assertions.assertEquals(0.0, v.dot(r.apply(u.orthogonal())), TEST_EPS);
166 });
167 });
168 }
169
170 @Test
171 void testCreateRotationVector_invalidVectors() {
172
173 final Vector2D vec = Vector2D.of(1, 1);
174
175 final Vector2D zero = Vector2D.ZERO;
176 final Vector2D nan = Vector2D.NaN;
177 final Vector2D posInf = Vector2D.POSITIVE_INFINITY;
178 final Vector2D negInf = Vector2D.POSITIVE_INFINITY;
179
180
181 Assertions.assertThrows(IllegalArgumentException.class, () -> Rotation2D.createVectorRotation(zero, vec));
182 Assertions.assertThrows(IllegalArgumentException.class, () -> Rotation2D.createVectorRotation(vec, zero));
183 Assertions.assertThrows(IllegalArgumentException.class, () -> Rotation2D.createVectorRotation(nan, vec));
184 Assertions.assertThrows(IllegalArgumentException.class, () -> Rotation2D.createVectorRotation(vec, nan));
185 Assertions.assertThrows(IllegalArgumentException.class, () -> Rotation2D.createVectorRotation(posInf, vec));
186 Assertions.assertThrows(IllegalArgumentException.class, () -> Rotation2D.createVectorRotation(vec, negInf));
187 Assertions.assertThrows(IllegalArgumentException.class, () -> Rotation2D.createVectorRotation(zero, nan));
188 Assertions.assertThrows(IllegalArgumentException.class, () -> Rotation2D.createVectorRotation(negInf, posInf));
189 }
190
191 @Test
192 void testHashCode() {
193
194 final Rotation2D a = Rotation2D.of(1.0);
195 final Rotation2D b = Rotation2D.of(0.0);
196 final Rotation2D c = Rotation2D.of(-1.0);
197 final Rotation2D d = Rotation2D.of(1.0);
198
199 final int hash = a.hashCode();
200
201
202 Assertions.assertEquals(hash, a.hashCode());
203
204 Assertions.assertNotEquals(hash, b.hashCode());
205 Assertions.assertNotEquals(hash, c.hashCode());
206
207 Assertions.assertEquals(hash, d.hashCode());
208 }
209
210 @Test
211 void testEquals() {
212
213 final Rotation2D a = Rotation2D.of(1.0);
214 final Rotation2D b = Rotation2D.of(0.0);
215 final Rotation2D c = Rotation2D.of(-1.0);
216 final Rotation2D d = Rotation2D.of(1.0);
217
218
219 GeometryTestUtils.assertSimpleEqualsCases(a);
220
221 Assertions.assertNotEquals(a, b);
222 Assertions.assertNotEquals(a, c);
223
224 Assertions.assertEquals(a, d);
225 Assertions.assertEquals(d, a);
226 }
227
228 @Test
229 void testToString() {
230
231 final Rotation2D r = Rotation2D.of(1.0);
232
233
234 final String str = r.toString();
235
236
237 Assertions.assertEquals("Rotation2D[angle=1.0]", str);
238 }
239
240 private static void checkApply(final double angle, final Vector2D pt, final Vector2D expectedPt) {
241 final Rotation2D r = Rotation2D.of(angle);
242 EuclideanTestUtils.assertCoordinatesEqual(expectedPt, r.apply(pt), TEST_EPS);
243 }
244
245 private static void checkApplyVector(final double angle, final Vector2D pt, final Vector2D expectedPt) {
246 final Rotation2D r = Rotation2D.of(angle);
247 EuclideanTestUtils.assertCoordinatesEqual(expectedPt, r.applyVector(pt), TEST_EPS);
248 }
249
250
251
252
253
254
255 private static <T extends EuclideanTransform<Vector2D>> void checkRotate(
256 final DoubleFunction<T> factory, final BiFunction<T, Vector2D, Vector2D> transformFn) {
257
258
259 final T transform = factory.apply(0);
260 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, transformFn.apply(transform, Vector2D.ZERO), TEST_EPS);
261
262
263 EuclideanTestUtils.permuteSkipZero(-2, -2, 1, (x, y) -> {
264 checkRotatePoint(Vector2D.of(x, y), factory, transformFn);
265 });
266 }
267
268
269
270
271
272
273
274
275 private static <T extends EuclideanTransform<Vector2D>> void checkRotatePoint(
276 final Vector2D pt, final DoubleFunction<T> factory, final BiFunction<T, ? super Vector2D, ? extends Vector2D> transformFn) {
277
278
279 final double limit = 4 * Math.PI;
280 final double inc = 0.25;
281
282 final Line line = Lines.fromPointAndDirection(Vector2D.ZERO, pt, TEST_PRECISION);
283
284 T transform;
285 Vector2D resultPt;
286 Line resultLine;
287 for (double angle = -limit; angle < limit; angle += inc) {
288 transform = factory.apply(angle);
289
290
291 resultPt = transformFn.apply(transform, pt);
292
293
294
295 Assertions.assertEquals(pt.norm(), resultPt.norm(), TEST_EPS);
296
297 resultLine = Lines.fromPointAndDirection(Vector2D.ZERO, resultPt, TEST_PRECISION);
298 final double lineAngle = line.angle(resultLine);
299
300
301 Assertions.assertEquals(Angle.Rad.WITHIN_MINUS_PI_AND_PI.applyAsDouble(angle), lineAngle, TEST_EPS);
302 }
303 }
304 }