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.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
45 final Rotation2D r = Rotation2D.identity();
46
47
48 Assert.assertEquals(0.0, r.getAngle(), 0.0);
49 Assert.assertTrue(r.preservesOrientation());
50 }
51
52 @Test
53 public void testProperties() {
54
55 final Rotation2D r = Rotation2D.of(100.0);
56
57
58 Assert.assertEquals(100.0, r.getAngle(), 0.0);
59 Assert.assertTrue(r.preservesOrientation());
60 }
61
62 @Test
63 public void testApply() {
64
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
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
93 final Rotation2D orig = Rotation2D.of(100.0);
94
95
96 final Rotation2D r = orig.inverse();
97
98
99 Assert.assertEquals(-100.0, r.getAngle(), 0.0);
100 Assert.assertTrue(r.preservesOrientation());
101 }
102
103 @Test
104 public void testInverse_apply() {
105
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
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
124 final double angle = 0.1 * Math.PI;
125
126
127 final AffineTransformMatrix2D m = Rotation2D.of(angle).toMatrix();
128
129
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
143 checkRotate(angle -> Rotation2D.of(angle).toMatrix(), AffineTransformMatrix2D::apply);
144 }
145
146 @Test
147 public void testCreateRotationVector() {
148
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
160 final Rotation2D r = Rotation2D.createVectorRotation(u, v);
161
162
163 EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), r.apply(u).normalize(), TEST_EPS);
164 Assert.assertEquals(0.0, v.dot(r.apply(u.orthogonal())), TEST_EPS);
165 });
166 });
167 }
168
169 @Test
170 public void testCreateRotationVector_invalidVectors() {
171
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
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
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
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
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
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
252 final Rotation2D r = Rotation2D.of(1.0);
253
254
255 final String str = r.toString();
256
257
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
272
273
274
275
276 private static <T extends EuclideanTransform<Vector2D>> void checkRotate(
277 final DoubleFunction<T> factory, final BiFunction<T, Vector2D, Vector2D> transformFn) {
278
279
280 final T transform = factory.apply(0);
281 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, transformFn.apply(transform, Vector2D.ZERO), TEST_EPS);
282
283
284 EuclideanTestUtils.permuteSkipZero(-2, -2, 1, (x, y) -> {
285 checkRotatePoint(Vector2D.of(x, y), factory, transformFn);
286 });
287 }
288
289
290
291
292
293
294
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
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
312 resultPt = transformFn.apply(transform, pt);
313
314
315
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
322 Assert.assertEquals(PlaneAngleRadians.normalizeBetweenMinusPiAndPi(angle), lineAngle, TEST_EPS);
323 }
324 }
325 }