1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.twod;
18
19 import java.util.function.UnaryOperator;
20
21 import org.apache.commons.geometry.core.GeometryTestUtils;
22 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
23 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
24 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
25 import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;
26 import org.apache.commons.numbers.angle.PlaneAngleRadians;
27 import org.junit.Assert;
28 import org.junit.Test;
29
30 public class AffineTransformMatrix2DTest {
31
32 private static final double EPS = 1e-12;
33
34 private static final DoublePrecisionContext TEST_PRECISION =
35 new EpsilonDoublePrecisionContext(EPS);
36
37 @Test
38 public void testOf() {
39
40 final double[] arr = {
41 1, 2, 3,
42 4, 5, 6
43 };
44
45
46 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.of(arr);
47
48
49 final double[] result = transform.toArray();
50 Assert.assertNotSame(arr, result);
51 Assert.assertArrayEquals(arr, result, 0.0);
52 }
53
54 @Test
55 public void testOf_invalidDimensions() {
56
57 GeometryTestUtils.assertThrows(() -> AffineTransformMatrix2D.of(1, 2),
58 IllegalArgumentException.class, "Dimension mismatch: 2 != 6");
59 }
60
61 @Test
62 public void testFromColumnVectors_twoVector() {
63
64 final Vector2D u = Vector2D.of(1, 2);
65 final Vector2D v = Vector2D.of(3, 4);
66
67
68 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.fromColumnVectors(u, v);
69
70
71 Assert.assertArrayEquals(new double[] {
72 1, 3, 0,
73 2, 4, 0
74 }, transform.toArray(), 0.0);
75 }
76
77 @Test
78 public void testFromColumnVectors_threeVectors() {
79
80 final Vector2D u = Vector2D.of(1, 2);
81 final Vector2D v = Vector2D.of(3, 4);
82 final Vector2D t = Vector2D.of(5, 6);
83
84
85 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.fromColumnVectors(u, v, t);
86
87
88 Assert.assertArrayEquals(new double[] {
89 1, 3, 5,
90 2, 4, 6
91 }, transform.toArray(), 0.0);
92 }
93
94 @Test
95 public void testIdentity() {
96
97 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity();
98
99
100 final double[] expected = {
101 1, 0, 0,
102 0, 1, 0
103 };
104 Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
105 }
106
107 @Test
108 public void testFrom() {
109
110 Assert.assertArrayEquals(new double[] {
111 1, 0, 0,
112 0, 1, 0
113 }, AffineTransformMatrix2D.from(UnaryOperator.identity()).toArray(), EPS);
114 Assert.assertArrayEquals(new double[] {
115 1, 0, 2,
116 0, 1, 3
117 }, AffineTransformMatrix2D.from(v -> v.add(Vector2D.of(2, 3))).toArray(), EPS);
118 Assert.assertArrayEquals(new double[] {
119 3, 0, 0,
120 0, 3, 0
121 }, AffineTransformMatrix2D.from(v -> v.multiply(3)).toArray(), EPS);
122 Assert.assertArrayEquals(new double[] {
123 3, 0, 6,
124 0, 3, 9
125 }, AffineTransformMatrix2D.from(v -> v.add(Vector2D.of(2, 3)).multiply(3)).toArray(), EPS);
126 }
127
128 @Test
129 public void testFrom_invalidFunction() {
130
131 GeometryTestUtils.assertThrows(() -> {
132 AffineTransformMatrix2D.from(v -> v.multiply(0));
133 }, IllegalArgumentException.class);
134 }
135
136 @Test
137 public void testCreateTranslation_xy() {
138
139 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(2, 3);
140
141
142 final double[] expected = {
143 1, 0, 2,
144 0, 1, 3
145 };
146 Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
147 }
148
149 @Test
150 public void testCreateTranslation_vector() {
151
152 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(Vector2D.of(5, 6));
153
154
155 final double[] expected = {
156 1, 0, 5,
157 0, 1, 6
158 };
159 Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
160 }
161
162 @Test
163 public void testCreateScale_xy() {
164
165 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(2, 3);
166
167
168 final double[] expected = {
169 2, 0, 0,
170 0, 3, 0
171 };
172 Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
173 }
174
175 @Test
176 public void testTranslate_xy() {
177
178 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
179 2, 0, 10,
180 0, 3, 11
181 );
182
183
184 final AffineTransformMatrix2D result = a.translate(4, 5);
185
186
187 final double[] expected = {
188 2, 0, 14,
189 0, 3, 16
190 };
191 Assert.assertArrayEquals(expected, result.toArray(), 0.0);
192 }
193
194 @Test
195 public void testTranslate_vector() {
196
197 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
198 2, 0, 10,
199 0, 3, 11
200 );
201
202
203 final AffineTransformMatrix2D result = a.translate(Vector2D.of(7, 8));
204
205
206 final double[] expected = {
207 2, 0, 17,
208 0, 3, 19
209 };
210 Assert.assertArrayEquals(expected, result.toArray(), 0.0);
211 }
212
213 @Test
214 public void testCreateScale_vector() {
215
216 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(Vector2D.of(4, 5));
217
218
219 final double[] expected = {
220 4, 0, 0,
221 0, 5, 0
222 };
223 Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
224 }
225
226 @Test
227 public void testCreateScale_singleValue() {
228
229 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(7);
230
231
232 final double[] expected = {
233 7, 0, 0,
234 0, 7, 0
235 };
236 Assert.assertArrayEquals(expected, transform.toArray(), 0.0);
237 }
238
239 @Test
240 public void testScale_xy() {
241
242 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
243 2, 0, 10,
244 0, 3, 11
245 );
246
247
248 final AffineTransformMatrix2D result = a.scale(4, 5);
249
250
251 final double[] expected = {
252 8, 0, 40,
253 0, 15, 55
254 };
255 Assert.assertArrayEquals(expected, result.toArray(), 0.0);
256 }
257
258 @Test
259 public void testScale_vector() {
260
261 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
262 2, 0, 10,
263 0, 3, 11
264 );
265
266
267 final AffineTransformMatrix2D result = a.scale(Vector2D.of(7, 8));
268
269
270 final double[] expected = {
271 14, 0, 70,
272 0, 24, 88
273 };
274 Assert.assertArrayEquals(expected, result.toArray(), 0.0);
275 }
276
277 @Test
278 public void testScale_singleValue() {
279
280 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
281 2, 0, 10,
282 0, 3, 11
283 );
284
285
286 final AffineTransformMatrix2D result = a.scale(10);
287
288
289 final double[] expected = {
290 20, 0, 100,
291 0, 30, 110
292 };
293 Assert.assertArrayEquals(expected, result.toArray(), 0.0);
294 }
295
296 @Test
297 public void testCreateRotation() {
298
299 final double angle = PlaneAngleRadians.PI * 2.0 / 3.0;
300 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(angle);
301
302
303 final double sin = Math.sin(angle);
304 final double cos = Math.cos(angle);
305
306 final double[] expected = {
307 cos, -sin, 0,
308 sin, cos, 0
309 };
310 Assert.assertArrayEquals(expected, transform.toArray(), EPS);
311 }
312
313 @Test
314 public void testCreateRotation_aroundCenter_rawAngle() {
315
316 final Vector2D center = Vector2D.of(1, 2);
317 final double angle = PlaneAngleRadians.PI * 2.0 / 3.0;
318 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(center, angle);
319
320
321 final double sin = Math.sin(angle);
322 final double cos = Math.cos(angle);
323
324 final double[] expected = {
325 cos, -sin, -cos + (2 * sin) + 1,
326 sin, cos, -sin - (2 * cos) + 2
327 };
328 Assert.assertArrayEquals(expected, transform.toArray(), EPS);
329 }
330
331 @Test
332 public void testCreateRotation_aroundCenter_rotationInstance() {
333
334 final Vector2D center = Vector2D.of(1, 2);
335 final double angle = PlaneAngleRadians.PI * 4.0 / 3.0;
336 final Rotation2D rotation = Rotation2D.of(angle);
337 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(center, rotation);
338
339
340 final double sin = Math.sin(angle);
341 final double cos = Math.cos(angle);
342
343 final double[] expected = {
344 cos, -sin, -cos + (2 * sin) + 1,
345 sin, cos, -sin - (2 * cos) + 2
346 };
347 Assert.assertArrayEquals(expected, transform.toArray(), EPS);
348 }
349
350 @Test
351 public void testRotate_rawAngle() {
352
353 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
354 1, 2, 3,
355 4, 5, 6
356 );
357
358
359 final AffineTransformMatrix2D result = a.rotate(PlaneAngleRadians.PI_OVER_TWO);
360
361
362 final double[] expected = {
363 -4, -5, -6,
364 1, 2, 3
365 };
366 Assert.assertArrayEquals(expected, result.toArray(), EPS);
367 }
368
369 @Test
370 public void testRotate_rotationInstance() {
371
372 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
373 1, 2, 3,
374 4, 5, 6
375 );
376
377
378 final AffineTransformMatrix2D result = a.rotate(Rotation2D.of(PlaneAngleRadians.PI_OVER_TWO));
379
380
381 final double[] expected = {
382 -4, -5, -6,
383 1, 2, 3
384 };
385 Assert.assertArrayEquals(expected, result.toArray(), EPS);
386 }
387
388 @Test
389 public void testRotate_aroundCenter_rawAngle() {
390
391 final Vector2D center = Vector2D.of(1, 2);
392
393 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
394 1, 2, 3,
395 4, 5, 6
396 );
397
398
399 final AffineTransformMatrix2D result = a.rotate(center, PlaneAngleRadians.PI_OVER_TWO);
400
401
402 final double[] expected = {
403 -4, -5, -3,
404 1, 2, 4
405 };
406 Assert.assertArrayEquals(expected, result.toArray(), EPS);
407 }
408
409 @Test
410 public void testRotate_aroundCenter_rotationInstance() {
411
412 final Vector2D center = Vector2D.of(1, 2);
413
414 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
415 1, 2, 3,
416 4, 5, 6
417 );
418
419
420 final AffineTransformMatrix2D result = a.rotate(center, Rotation2D.of(PlaneAngleRadians.PI_OVER_TWO));
421
422
423 final double[] expected = {
424 -4, -5, -3,
425 1, 2, 4
426 };
427 Assert.assertArrayEquals(expected, result.toArray(), EPS);
428 }
429
430 @Test
431 public void testApply_identity() {
432
433 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity();
434
435
436 runWithCoordinates((x, y) -> {
437 final Vector2D v = Vector2D.of(x, y);
438
439 EuclideanTestUtils.assertCoordinatesEqual(v, transform.apply(v), EPS);
440 });
441 }
442
443 @Test
444 public void testApply_translate() {
445
446 final Vector2D translation = Vector2D.of(1.1, -PlaneAngleRadians.PI);
447
448 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
449 .translate(translation);
450
451
452 runWithCoordinates((x, y) -> {
453 final Vector2D vec = Vector2D.of(x, y);
454
455 final Vector2D expectedVec = vec.add(translation);
456
457 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
458 });
459 }
460
461 @Test
462 public void testApply_scale() {
463
464 final Vector2D factors = Vector2D.of(2.0, -3.0);
465
466 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
467 .scale(factors);
468
469
470 runWithCoordinates((x, y) -> {
471 final Vector2D vec = Vector2D.of(x, y);
472
473 final Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y);
474
475 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
476 });
477 }
478
479 @Test
480 public void testApply_rotate() {
481
482 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
483 .rotate(-PlaneAngleRadians.PI_OVER_TWO);
484
485
486 runWithCoordinates((x, y) -> {
487 final Vector2D vec = Vector2D.of(x, y);
488
489 final Vector2D expectedVec = Vector2D.of(y, -x);
490
491 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
492 });
493 }
494
495 @Test
496 public void testApply_rotate_aroundCenter_minusHalfPi() {
497
498 final Vector2D center = Vector2D.of(1, 2);
499 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
500 .rotate(center, -PlaneAngleRadians.PI_OVER_TWO);
501
502
503 runWithCoordinates((x, y) -> {
504 final Vector2D vec = Vector2D.of(x, y);
505
506 final Vector2D centered = vec.subtract(center);
507 final Vector2D expectedVec = Vector2D.of(centered.getY(), -centered.getX()).add(center);
508
509 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
510 });
511 }
512
513 @Test
514 public void testApply_rotate_aroundCenter_pi() {
515
516 final Vector2D center = Vector2D.of(1, 2);
517 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
518 .rotate(center, PlaneAngleRadians.PI);
519
520
521 runWithCoordinates((x, y) -> {
522 final Vector2D vec = Vector2D.of(x, y);
523
524 final Vector2D centered = vec.subtract(center);
525 final Vector2D expectedVec = Vector2D.of(-centered.getX(), -centered.getY()).add(center);
526
527 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
528 });
529 }
530
531 @Test
532 public void testApply_translateScaleRotate() {
533
534 final Vector2D translation = Vector2D.of(-2.0, -3.0);
535 final Vector2D scale = Vector2D.of(5.0, 6.0);
536
537 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
538 .translate(translation)
539 .scale(scale)
540 .rotate(PlaneAngleRadians.PI_OVER_TWO);
541
542
543 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(12, -5), transform.apply(Vector2D.of(1, 1)), EPS);
544
545 runWithCoordinates((x, y) -> {
546 final Vector2D vec = Vector2D.of(x, y);
547
548 final Vector2D temp = Vector2D.of(
549 (x + translation.getX()) * scale.getX(),
550 (y + translation.getY()) * scale.getY()
551 );
552 final Vector2D expectedVec = Vector2D.of(-temp.getY(), temp.getX());
553
554 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
555 });
556 }
557
558 @Test
559 public void testApply_scaleTranslateRotate() {
560
561 final Vector2D scale = Vector2D.of(5.0, 6.0);
562 final Vector2D translation = Vector2D.of(-2.0, -3.0);
563
564 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
565 .scale(scale)
566 .translate(translation)
567 .rotate(-PlaneAngleRadians.PI_OVER_TWO);
568
569
570 runWithCoordinates((x, y) -> {
571 final Vector2D vec = Vector2D.of(x, y);
572
573 final Vector2D temp = Vector2D.of(
574 (x * scale.getX()) + translation.getX(),
575 (y * scale.getY()) + translation.getY()
576 );
577 final Vector2D expectedVec = Vector2D.of(temp.getY(), -temp.getX());
578
579 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
580 });
581 }
582
583 @Test
584 public void testApplyVector_identity() {
585
586 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity();
587
588
589 runWithCoordinates((x, y) -> {
590 final Vector2D v = Vector2D.of(x, y);
591
592 EuclideanTestUtils.assertCoordinatesEqual(v, transform.applyVector(v), EPS);
593 });
594 }
595
596 @Test
597 public void testApplyVector_translate() {
598
599 final Vector2D translation = Vector2D.of(1.1, -PlaneAngleRadians.PI);
600
601 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
602 .translate(translation);
603
604
605 runWithCoordinates((x, y) -> {
606 final Vector2D vec = Vector2D.of(x, y);
607
608 EuclideanTestUtils.assertCoordinatesEqual(vec, transform.applyVector(vec), EPS);
609 });
610 }
611
612 @Test
613 public void testApplyVector_scale() {
614
615 final Vector2D factors = Vector2D.of(2.0, -3.0);
616
617 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
618 .scale(factors);
619
620
621 runWithCoordinates((x, y) -> {
622 final Vector2D vec = Vector2D.of(x, y);
623
624 final Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y);
625
626 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyVector(vec), EPS);
627 });
628 }
629
630 @Test
631 public void testApplyVector_representsDisplacement() {
632
633 final Vector2D p1 = Vector2D.of(2, 3);
634
635 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
636 .scale(1.5)
637 .translate(4, 6)
638 .rotate(PlaneAngleRadians.PI_OVER_TWO);
639
640
641 runWithCoordinates((x, y) -> {
642 final Vector2D p2 = Vector2D.of(x, y);
643 final Vector2D input = p1.subtract(p2);
644
645 final Vector2D expected = transform.apply(p1).subtract(transform.apply(p2));
646
647 EuclideanTestUtils.assertCoordinatesEqual(expected, transform.applyVector(input), EPS);
648 });
649 }
650
651 @Test
652 public void testApplyDirection_identity() {
653
654 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity();
655
656
657 EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> {
658 final Vector2D v = Vector2D.of(x, y);
659
660 EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), transform.applyDirection(v), EPS);
661 });
662 }
663
664 @Test
665 public void testApplyDirection_translate() {
666
667 final Vector2D translation = Vector2D.of(1.1, -PlaneAngleRadians.PI);
668
669 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
670 .translate(translation);
671
672
673 EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> {
674 final Vector2D vec = Vector2D.of(x, y);
675
676 EuclideanTestUtils.assertCoordinatesEqual(vec.normalize(), transform.applyDirection(vec), EPS);
677 });
678 }
679
680 @Test
681 public void testApplyDirection_scale() {
682
683 final Vector2D factors = Vector2D.of(2.0, -3.0);
684
685 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
686 .scale(factors);
687
688
689 EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> {
690 final Vector2D vec = Vector2D.of(x, y);
691
692 final Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y).normalize();
693
694 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyDirection(vec), EPS);
695 });
696 }
697
698 @Test
699 public void testApplyDirection_representsNormalizedDisplacement() {
700
701 final Vector2D p1 = Vector2D.of(2.1, 3.2);
702
703 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
704 .scale(1.5)
705 .translate(4, 6)
706 .rotate(PlaneAngleRadians.PI_OVER_TWO);
707
708
709 EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
710 final Vector2D p2 = Vector2D.of(x, y);
711 final Vector2D input = p1.subtract(p2);
712
713 final Vector2D expected = transform.apply(p1).subtract(transform.apply(p2)).normalize();
714
715 EuclideanTestUtils.assertCoordinatesEqual(expected, transform.applyDirection(input), EPS);
716 });
717 }
718
719 @Test
720 public void testApplyDirection_illegalNorm() {
721
722 GeometryTestUtils.assertThrows(() -> AffineTransformMatrix2D.createScale(1, 0).applyDirection(Vector2D.Unit.PLUS_Y),
723 IllegalArgumentException.class);
724 GeometryTestUtils.assertThrows(() -> AffineTransformMatrix2D.createScale(2).applyDirection(Vector2D.ZERO),
725 IllegalArgumentException.class);
726 }
727
728 @Test
729 public void testDeterminant() {
730
731 Assert.assertEquals(1.0, AffineTransformMatrix2D.identity().determinant(), EPS);
732 Assert.assertEquals(6.0, AffineTransformMatrix2D.of(
733 2, 0, 4,
734 0, 3, 5
735 ).determinant(), EPS);
736 Assert.assertEquals(-6.0, AffineTransformMatrix2D.of(
737 2, 0, 4,
738 0, -3, 5
739 ).determinant(), EPS);
740 Assert.assertEquals(-5.0, AffineTransformMatrix2D.of(
741 1, 3, 0,
742 2, 1, 0
743 ).determinant(), EPS);
744 Assert.assertEquals(-0.0, AffineTransformMatrix2D.of(
745 0, 0, 1,
746 0, 0, 2
747 ).determinant(), EPS);
748 }
749
750 @Test
751 public void testPreservesOrientation() {
752
753 Assert.assertTrue(AffineTransformMatrix2D.identity().preservesOrientation());
754 Assert.assertTrue(AffineTransformMatrix2D.of(
755 2, 0, 4,
756 0, 3, 5
757 ).preservesOrientation());
758
759 Assert.assertFalse(AffineTransformMatrix2D.of(
760 2, 0, 4,
761 0, -3, 5
762 ).preservesOrientation());
763 Assert.assertFalse(AffineTransformMatrix2D.of(
764 1, 3, 0,
765 2, 1, 0
766 ).preservesOrientation());
767 Assert.assertFalse(AffineTransformMatrix2D.of(
768 0, 0, 1,
769 0, 0, 2
770 ).preservesOrientation());
771 }
772
773 @Test
774 public void testMultiply() {
775
776 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
777 1, 2, 3,
778 5, 6, 7
779 );
780 final AffineTransformMatrix2D b = AffineTransformMatrix2D.of(
781 13, 14, 15,
782 17, 18, 19
783 );
784
785
786 final AffineTransformMatrix2D result = a.multiply(b);
787
788
789 final double[] arr = result.toArray();
790 Assert.assertArrayEquals(new double[] {
791 47, 50, 56,
792 167, 178, 196
793 }, arr, EPS);
794 }
795
796 @Test
797 public void testMultiply_combinesTransformOperations() {
798
799 final Vector2D translation1 = Vector2D.of(1, 2);
800 final double scale = 2.0;
801 final Vector2D translation2 = Vector2D.of(4, 5);
802
803 final AffineTransformMatrix2D a = AffineTransformMatrix2D.createTranslation(translation1);
804 final AffineTransformMatrix2D b = AffineTransformMatrix2D.createScale(scale);
805 final AffineTransformMatrix2D c = AffineTransformMatrix2D.identity();
806 final AffineTransformMatrix2D d = AffineTransformMatrix2D.createTranslation(translation2);
807
808
809 final AffineTransformMatrix2D transform = d.multiply(c).multiply(b).multiply(a);
810
811
812 runWithCoordinates((x, y) -> {
813 final Vector2D vec = Vector2D.of(x, y);
814
815 final Vector2D expectedVec = vec
816 .add(translation1)
817 .multiply(scale)
818 .add(translation2);
819
820 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
821 });
822 }
823
824 @Test
825 public void testPremultiply() {
826
827 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
828 1, 2, 3,
829 5, 6, 7
830 );
831 final AffineTransformMatrix2D b = AffineTransformMatrix2D.of(
832 13, 14, 15,
833 17, 18, 19
834 );
835
836
837 final AffineTransformMatrix2D result = b.premultiply(a);
838
839
840 final double[] arr = result.toArray();
841 Assert.assertArrayEquals(new double[] {
842 47, 50, 56,
843 167, 178, 196
844 }, arr, EPS);
845 }
846
847 @Test
848 public void testPremultiply_combinesTransformOperations() {
849
850 final Vector2D translation1 = Vector2D.of(1, 2);
851 final double scale = 2.0;
852 final Vector2D translation2 = Vector2D.of(4, 5);
853
854 final AffineTransformMatrix2D a = AffineTransformMatrix2D.createTranslation(translation1);
855 final AffineTransformMatrix2D b = AffineTransformMatrix2D.createScale(scale);
856 final AffineTransformMatrix2D c = AffineTransformMatrix2D.identity();
857 final AffineTransformMatrix2D d = AffineTransformMatrix2D.createTranslation(translation2);
858
859
860 final AffineTransformMatrix2D transform = a.premultiply(b).premultiply(c).premultiply(d);
861
862
863 runWithCoordinates((x, y) -> {
864 final Vector2D vec = Vector2D.of(x, y);
865
866 final Vector2D expectedVec = vec
867 .add(translation1)
868 .multiply(scale)
869 .add(translation2);
870
871 EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
872 });
873 }
874
875 @Test
876 public void testInverse_identity() {
877
878 final AffineTransformMatrix2D inverse = AffineTransformMatrix2D.identity().inverse();
879
880
881 final double[] expected = {
882 1, 0, 0,
883 0, 1, 0
884 };
885 Assert.assertArrayEquals(expected, inverse.toArray(), 0.0);
886 }
887
888 @Test
889 public void testInverse_multiplyByInverse_producesIdentity() {
890
891 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
892 1, 3, 7,
893 2, 4, 9
894 );
895
896 final AffineTransformMatrix2D inv = a.inverse();
897
898
899 final AffineTransformMatrix2D result = inv.multiply(a);
900
901
902 final double[] expected = {
903 1, 0, 0,
904 0, 1, 0
905 };
906 Assert.assertArrayEquals(expected, result.toArray(), EPS);
907 }
908
909 @Test
910 public void testInverse_translate() {
911
912 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(1, -2);
913
914
915 final AffineTransformMatrix2D inverse = transform.inverse();
916
917
918 final double[] expected = {
919 1, 0, -1,
920 0, 1, 2
921 };
922 Assert.assertArrayEquals(expected, inverse.toArray(), 0.0);
923 }
924
925 @Test
926 public void testInverse_scale() {
927
928 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(10, -2);
929
930
931 final AffineTransformMatrix2D inverse = transform.inverse();
932
933
934 final double[] expected = {
935 0.1, 0, 0,
936 0, -0.5, 0
937 };
938 Assert.assertArrayEquals(expected, inverse.toArray(), 0.0);
939 }
940
941 @Test
942 public void testInverse_rotate() {
943
944 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
945
946
947 final AffineTransformMatrix2D inverse = transform.inverse();
948
949
950 final double[] expected = {
951 0, 1, 0,
952 -1, 0, 0
953 };
954 Assert.assertArrayEquals(expected, inverse.toArray(), EPS);
955 }
956
957 @Test
958 public void testInverse_rotate_aroundCenter() {
959
960 final Vector2D center = Vector2D.of(1, 2);
961 final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(center, PlaneAngleRadians.PI_OVER_TWO);
962
963
964 final AffineTransformMatrix2D inverse = transform.inverse();
965
966
967 final double[] expected = {
968 0, 1, -1,
969 -1, 0, 3
970 };
971 Assert.assertArrayEquals(expected, inverse.toArray(), EPS);
972 }
973
974 @Test
975 public void testInverse_undoesOriginalTransform() {
976
977 final Vector2D v1 = Vector2D.ZERO;
978 final Vector2D v2 = Vector2D.Unit.PLUS_X;
979 final Vector2D v3 = Vector2D.of(1, 1);
980 final Vector2D v4 = Vector2D.of(-2, 3);
981
982 final Vector2D center = Vector2D.of(-0.5, 2);
983
984
985 runWithCoordinates((x, y) -> {
986 final AffineTransformMatrix2D transform = AffineTransformMatrix2D
987 .createTranslation(x, y)
988 .scale(2, 3)
989 .translate(x / 3, y / 3)
990 .rotate(x / 4)
991 .rotate(center, y / 2);
992
993 final AffineTransformMatrix2D inverse = transform.inverse();
994
995 EuclideanTestUtils.assertCoordinatesEqual(v1, inverse.apply(transform.apply(v1)), EPS);
996 EuclideanTestUtils.assertCoordinatesEqual(v2, inverse.apply(transform.apply(v2)), EPS);
997 EuclideanTestUtils.assertCoordinatesEqual(v3, inverse.apply(transform.apply(v3)), EPS);
998 EuclideanTestUtils.assertCoordinatesEqual(v4, inverse.apply(transform.apply(v4)), EPS);
999 });
1000 }
1001
1002 @Test
1003 public void testInverse_nonInvertible() {
1004
1005 GeometryTestUtils.assertThrows(() -> {
1006 AffineTransformMatrix2D.of(
1007 0, 0, 0,
1008 0, 0, 0).inverse();
1009 }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is 0.0");
1010
1011 GeometryTestUtils.assertThrows(() -> {
1012 AffineTransformMatrix2D.of(
1013 1, 0, 0,
1014 0, Double.NaN, 0).inverse();
1015 }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
1016
1017 GeometryTestUtils.assertThrows(() -> {
1018 AffineTransformMatrix2D.of(
1019 1, 0, 0,
1020 0, Double.NEGATIVE_INFINITY, 0).inverse();
1021 }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is -Infinity");
1022
1023 GeometryTestUtils.assertThrows(() -> {
1024 AffineTransformMatrix2D.of(
1025 Double.POSITIVE_INFINITY, 0, 0,
1026 0, 1, 0).inverse();
1027 }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is Infinity");
1028
1029 GeometryTestUtils.assertThrows(() -> {
1030 AffineTransformMatrix2D.of(
1031 1, 0, Double.NaN,
1032 0, 1, 0).inverse();
1033 }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: NaN");
1034
1035 GeometryTestUtils.assertThrows(() -> {
1036 AffineTransformMatrix2D.of(
1037 1, 0, Double.POSITIVE_INFINITY,
1038 0, 1, 0).inverse();
1039 }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: Infinity");
1040
1041 GeometryTestUtils.assertThrows(() -> {
1042 AffineTransformMatrix2D.of(
1043 1, 0, Double.NEGATIVE_INFINITY,
1044 0, 1, 0).inverse();
1045 }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: -Infinity");
1046 }
1047
1048 @Test
1049 public void testLinear() {
1050
1051 final AffineTransformMatrix2D mat = AffineTransformMatrix2D.of(
1052 2, 3, 4,
1053 5, 6, 7);
1054
1055
1056 final AffineTransformMatrix2D result = mat.linear();
1057
1058
1059 final double[] expected = {
1060 2, 3, 0,
1061 5, 6, 0
1062 };
1063 Assert.assertArrayEquals(expected, result.toArray(), 0.0);
1064 }
1065
1066 @Test
1067 public void testLinearTranspose() {
1068
1069 final AffineTransformMatrix2D mat = AffineTransformMatrix2D.of(
1070 2, 3, 4,
1071 5, 6, 7);
1072
1073
1074 final AffineTransformMatrix2D result = mat.linearTranspose();
1075
1076
1077 final double[] expected = {
1078 2, 5, 0,
1079 3, 6, 0
1080 };
1081 Assert.assertArrayEquals(expected, result.toArray(), 0.0);
1082 }
1083
1084 @Test
1085 public void testNormalTransform() {
1086
1087 checkNormalTransform(AffineTransformMatrix2D.identity());
1088
1089 checkNormalTransform(AffineTransformMatrix2D.createTranslation(2, 3));
1090 checkNormalTransform(AffineTransformMatrix2D.createTranslation(-3, -4));
1091
1092 checkNormalTransform(AffineTransformMatrix2D.createScale(2, 5));
1093 checkNormalTransform(AffineTransformMatrix2D.createScale(-3, 4));
1094 checkNormalTransform(AffineTransformMatrix2D.createScale(-2, -5));
1095
1096 checkNormalTransform(AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO));
1097 checkNormalTransform(AffineTransformMatrix2D.createRotation(PlaneAngleRadians.THREE_PI_OVER_TWO));
1098
1099 checkNormalTransform(AffineTransformMatrix2D.createRotation(Vector2D.of(3, 4), PlaneAngleRadians.THREE_PI_OVER_TWO)
1100 .translate(8, 2)
1101 .scale(-3, -2));
1102 checkNormalTransform(AffineTransformMatrix2D.createScale(2, -1)
1103 .translate(-3, -4)
1104 .rotate(Vector2D.of(-0.5, 0.5), 0.75 * Math.PI));
1105 }
1106
1107 private void checkNormalTransform(final AffineTransformMatrix2D transform) {
1108 final AffineTransformMatrix2D normalTransform = transform.normalTransform();
1109
1110 final Vector2D p1 = Vector2D.of(-0.25, 0.75);
1111 final Vector2D t1 = transform.apply(p1);
1112
1113 EuclideanTestUtils.permute(-10, 10, 1, (x, y) -> {
1114 final Vector2D p2 = Vector2D.of(x, y);
1115 final Vector2D n = Lines.fromPoints(p1, p2, TEST_PRECISION).getOffsetDirection();
1116
1117 final Vector2D t2 = transform.apply(p2);
1118
1119 final Line tLine = transform.preservesOrientation() ?
1120 Lines.fromPoints(t1, t2, TEST_PRECISION) :
1121 Lines.fromPoints(t2, t1, TEST_PRECISION);
1122 final Vector2D expected = tLine.getOffsetDirection();
1123
1124 final Vector2D actual = normalTransform.apply(n).normalize();
1125
1126 EuclideanTestUtils.assertCoordinatesEqual(expected, actual, EPS);
1127 });
1128 }
1129
1130 @Test
1131 public void testNormalTransform_nonInvertible() {
1132
1133 GeometryTestUtils.assertThrows(() -> {
1134 AffineTransformMatrix2D.createScale(0).normalTransform();
1135 }, IllegalStateException.class);
1136 }
1137
1138 @Test
1139 public void testHashCode() {
1140
1141 final double[] values = {
1142 1, 2, 3,
1143 5, 6, 7
1144 };
1145
1146
1147 final int orig = AffineTransformMatrix2D.of(values).hashCode();
1148 final int same = AffineTransformMatrix2D.of(values).hashCode();
1149
1150 Assert.assertEquals(orig, same);
1151
1152 double[] temp;
1153 for (int i = 0; i < values.length; ++i) {
1154 temp = values.clone();
1155 temp[i] = 0;
1156
1157 final int modified = AffineTransformMatrix2D.of(temp).hashCode();
1158
1159 Assert.assertNotEquals(orig, modified);
1160 }
1161 }
1162
1163 @Test
1164 public void testEquals() {
1165
1166 final double[] values = {
1167 1, 2, 3,
1168 5, 6, 7
1169 };
1170
1171 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(values);
1172
1173
1174 Assert.assertEquals(a, a);
1175
1176 Assert.assertFalse(a.equals(null));
1177 Assert.assertFalse(a.equals(new Object()));
1178
1179 double[] temp;
1180 for (int i = 0; i < values.length; ++i) {
1181 temp = values.clone();
1182 temp[i] = 0;
1183
1184 final AffineTransformMatrix2D modified = AffineTransformMatrix2D.of(temp);
1185
1186 Assert.assertNotEquals(a, modified);
1187 }
1188 }
1189
1190 @Test
1191 public void testEqualsAndHashCode_signedZeroConsistency() {
1192
1193 final double[] arrWithPosZero = {
1194 1.0, 0.0, 0.0,
1195 0.0, 1.0, 0.0
1196 };
1197 final double[] arrWithNegZero = {
1198 1.0, 0.0, 0.0,
1199 0.0, 1.0, -0.0
1200 };
1201 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(arrWithPosZero);
1202 final AffineTransformMatrix2D b = AffineTransformMatrix2D.of(arrWithNegZero);
1203 final AffineTransformMatrix2D c = AffineTransformMatrix2D.of(arrWithPosZero);
1204 final AffineTransformMatrix2D d = AffineTransformMatrix2D.of(arrWithNegZero);
1205
1206
1207 Assert.assertFalse(a.equals(b));
1208 Assert.assertNotEquals(a.hashCode(), b.hashCode());
1209
1210 Assert.assertTrue(a.equals(c));
1211 Assert.assertEquals(a.hashCode(), c.hashCode());
1212
1213 Assert.assertTrue(b.equals(d));
1214 Assert.assertEquals(b.hashCode(), d.hashCode());
1215 }
1216
1217 @Test
1218 public void testToString() {
1219
1220 final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
1221 1, 2, 3,
1222 5, 6, 7
1223 );
1224
1225
1226 final String result = a.toString();
1227
1228
1229 Assert.assertEquals(
1230 "[ 1.0, 2.0, 3.0; " +
1231 "5.0, 6.0, 7.0 ]", result);
1232 }
1233
1234 @FunctionalInterface
1235 private interface Coordinate2DTest {
1236
1237 void run(double x, double y);
1238 }
1239
1240 private static void runWithCoordinates(final Coordinate2DTest test) {
1241 runWithCoordinates(test, -1e-2, 1e-2, 5e-3);
1242 runWithCoordinates(test, -1e2, 1e2, 5);
1243 }
1244
1245 private static void runWithCoordinates(final Coordinate2DTest test, final double min, final double max, final double step) {
1246 for (double x = min; x <= max; x += step) {
1247 for (double y = min; y <= max; y += step) {
1248 test.run(x, y);
1249 }
1250 }
1251 }
1252 }