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;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.regex.Pattern;
24  
25  import org.apache.commons.geometry.core.GeometryTestUtils;
26  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
27  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
28  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
29  import org.apache.commons.numbers.angle.PlaneAngleRadians;
30  import org.apache.commons.numbers.core.Precision;
31  import org.junit.Assert;
32  import org.junit.Test;
33  
34  public class Vector2DTest {
35  
36      private static final double EPS = Math.ulp(1d);
37  
38      @Test
39      public void testConstants() {
40          // act/assert
41          checkVector(Vector2D.ZERO, 0, 0);
42          checkVector(Vector2D.Unit.PLUS_X, 1, 0);
43          checkVector(Vector2D.Unit.MINUS_X, -1, 0);
44          checkVector(Vector2D.Unit.PLUS_Y, 0, 1);
45          checkVector(Vector2D.Unit.MINUS_Y, 0, -1);
46          checkVector(Vector2D.NaN, Double.NaN, Double.NaN);
47          checkVector(Vector2D.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
48          checkVector(Vector2D.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
49      }
50  
51      @Test
52      public void testConstants_normalize() {
53          // act/assert
54          GeometryTestUtils.assertThrows(Vector2D.ZERO::normalize, IllegalArgumentException.class);
55          GeometryTestUtils.assertThrows(Vector2D.NaN::normalize, IllegalArgumentException.class);
56          GeometryTestUtils.assertThrows(Vector2D.POSITIVE_INFINITY::normalize, IllegalArgumentException.class);
57          GeometryTestUtils.assertThrows(Vector2D.NEGATIVE_INFINITY::normalize, IllegalArgumentException.class);
58  
59          Assert.assertSame(Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X.normalize());
60          Assert.assertSame(Vector2D.Unit.MINUS_X, Vector2D.Unit.MINUS_X.normalize());
61  
62          Assert.assertSame(Vector2D.Unit.PLUS_Y, Vector2D.Unit.PLUS_Y.normalize());
63          Assert.assertSame(Vector2D.Unit.MINUS_Y, Vector2D.Unit.MINUS_Y.normalize());
64      }
65  
66      @Test
67      public void testCoordinateAscendingOrder() {
68          // arrange
69          final Comparator<Vector2D> cmp = Vector2D.COORDINATE_ASCENDING_ORDER;
70  
71          // act/assert
72          Assert.assertEquals(0, cmp.compare(Vector2D.of(1, 2), Vector2D.of(1, 2)));
73  
74          Assert.assertEquals(-1, cmp.compare(Vector2D.of(0, 2), Vector2D.of(1, 2)));
75          Assert.assertEquals(-1, cmp.compare(Vector2D.of(1, 1), Vector2D.of(1, 2)));
76  
77          Assert.assertEquals(1, cmp.compare(Vector2D.of(2, 2), Vector2D.of(1, 2)));
78          Assert.assertEquals(1, cmp.compare(Vector2D.of(1, 3), Vector2D.of(1, 2)));
79  
80          Assert.assertEquals(-1, cmp.compare(Vector2D.of(1, 3), null));
81          Assert.assertEquals(1, cmp.compare(null, Vector2D.of(1, 2)));
82          Assert.assertEquals(0, cmp.compare(null, null));
83      }
84  
85      @Test
86      public void testCoordinates() {
87          // arrange
88          final Vector2D v = Vector2D.of(1, 2);
89  
90          // act/assert
91          Assert.assertEquals(1.0, v.getX(), EPS);
92          Assert.assertEquals(2.0, v.getY(), EPS);
93      }
94  
95      @Test
96      public void testToArray() {
97          // arrange
98          final Vector2D oneTwo = Vector2D.of(1, 2);
99  
100         // act
101         final double[] array = oneTwo.toArray();
102 
103         // assert
104         Assert.assertEquals(2, array.length);
105         Assert.assertEquals(1.0, array[0], EPS);
106         Assert.assertEquals(2.0, array[1], EPS);
107     }
108 
109     @Test
110     public void testDimension() {
111         // arrange
112         final Vector2D v = Vector2D.of(1, 2);
113 
114         // act/assert
115         Assert.assertEquals(2, v.getDimension());
116     }
117 
118     @Test
119     public void testNaN() {
120         // act/assert
121         Assert.assertTrue(Vector2D.of(0, Double.NaN).isNaN());
122         Assert.assertTrue(Vector2D.of(Double.NaN, 0).isNaN());
123 
124         Assert.assertFalse(Vector2D.of(1, 1).isNaN());
125         Assert.assertFalse(Vector2D.of(1, Double.NEGATIVE_INFINITY).isNaN());
126         Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, 1).isNaN());
127     }
128 
129     @Test
130     public void testInfinite() {
131         // act/assert
132         Assert.assertTrue(Vector2D.of(0, Double.NEGATIVE_INFINITY).isInfinite());
133         Assert.assertTrue(Vector2D.of(Double.NEGATIVE_INFINITY, 0).isInfinite());
134         Assert.assertTrue(Vector2D.of(0, Double.POSITIVE_INFINITY).isInfinite());
135         Assert.assertTrue(Vector2D.of(Double.POSITIVE_INFINITY, 0).isInfinite());
136 
137         Assert.assertFalse(Vector2D.of(1, 1).isInfinite());
138         Assert.assertFalse(Vector2D.of(0, Double.NaN).isInfinite());
139         Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, Double.NaN).isInfinite());
140         Assert.assertFalse(Vector2D.of(Double.NaN, Double.NEGATIVE_INFINITY).isInfinite());
141         Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, Double.NaN).isInfinite());
142         Assert.assertFalse(Vector2D.of(Double.NaN, Double.POSITIVE_INFINITY).isInfinite());
143     }
144 
145     @Test
146     public void testFinite() {
147         // act/assert
148         Assert.assertTrue(Vector2D.ZERO.isFinite());
149         Assert.assertTrue(Vector2D.of(1, 1).isFinite());
150 
151         Assert.assertFalse(Vector2D.of(0, Double.NEGATIVE_INFINITY).isFinite());
152         Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, 0).isFinite());
153         Assert.assertFalse(Vector2D.of(0, Double.POSITIVE_INFINITY).isFinite());
154         Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, 0).isFinite());
155 
156         Assert.assertFalse(Vector2D.of(0, Double.NaN).isFinite());
157         Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, Double.NaN).isFinite());
158         Assert.assertFalse(Vector2D.of(Double.NaN, Double.NEGATIVE_INFINITY).isFinite());
159         Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, Double.NaN).isFinite());
160         Assert.assertFalse(Vector2D.of(Double.NaN, Double.POSITIVE_INFINITY).isFinite());
161     }
162 
163     @Test
164     public void testGetZero() {
165         // act/assert
166         checkVector(Vector2D.of(1.0, 1.0).getZero(), 0, 0);
167     }
168 
169     @Test
170     public void testNorm() {
171         // act/assert
172         Assert.assertEquals(0.0, Vector2D.of(0, 0).norm(), EPS);
173 
174         Assert.assertEquals(5.0, Vector2D.of(3, 4).norm(), EPS);
175         Assert.assertEquals(5.0, Vector2D.of(3, -4).norm(), EPS);
176         Assert.assertEquals(5.0, Vector2D.of(-3, 4).norm(), EPS);
177         Assert.assertEquals(5.0, Vector2D.of(-3, -4).norm(), EPS);
178 
179         Assert.assertEquals(Math.sqrt(5.0), Vector2D.of(-1, -2).norm(), EPS);
180     }
181 
182     @Test
183     public void testNorm_unitVectors() {
184         // arrange
185         final Vector2D v = Vector2D.of(2.0, 3.0).normalize();
186 
187         // act/assert
188         Assert.assertEquals(1.0, v.norm(), 0.0);
189     }
190 
191     @Test
192     public void testNormSq() {
193         // act/assert
194         Assert.assertEquals(0.0, Vector2D.of(0, 0).normSq(), EPS);
195 
196         Assert.assertEquals(25.0, Vector2D.of(3, 4).normSq(), EPS);
197         Assert.assertEquals(25.0, Vector2D.of(3, -4).normSq(), EPS);
198         Assert.assertEquals(25.0, Vector2D.of(-3, 4).normSq(), EPS);
199         Assert.assertEquals(25.0, Vector2D.of(-3, -4).normSq(), EPS);
200 
201         Assert.assertEquals(5.0, Vector2D.of(-1, -2).normSq(), EPS);
202     }
203 
204     @Test
205     public void testNormSq_unitVectors() {
206         // arrange
207         final Vector2D v = Vector2D.of(2.0, 3.0).normalize();
208 
209         // act/assert
210         Assert.assertEquals(1.0, v.normSq(), 0.0);
211     }
212 
213     @Test
214     public void testWithNorm() {
215         // act/assert
216         checkVector(Vector2D.of(3, 4).withNorm(1.0), 0.6, 0.8);
217         checkVector(Vector2D.of(4, 3).withNorm(1.0), 0.8, 0.6);
218 
219         checkVector(Vector2D.of(-3, 4).withNorm(0.5), -0.3, 0.4);
220         checkVector(Vector2D.of(3, -4).withNorm(2.0), 1.2, -1.6);
221         checkVector(Vector2D.of(-3, -4).withNorm(3.0), -1.8, 3.0 * Math.sin(Math.atan2(-4, -3)));
222 
223         checkVector(Vector2D.of(0.5, 0.5).withNorm(2), Math.sqrt(2), Math.sqrt(2));
224     }
225 
226     @Test
227     public void testWithNorm_illegalNorm() {
228         // act/assert
229         GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.withNorm(2.0),
230                 IllegalArgumentException.class);
231         GeometryTestUtils.assertThrows(() -> Vector2D.NaN.withNorm(2.0),
232                 IllegalArgumentException.class);
233         GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.withNorm(2.0),
234                 IllegalArgumentException.class);
235         GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.withNorm(2.0),
236                 IllegalArgumentException.class);
237     }
238 
239     @Test
240     public void testWithNorm_unitVectors() {
241         // arrange
242         final double eps = 1e-14;
243         final Vector2D v = Vector2D.of(2.0, -3.0).normalize();
244 
245         // act/assert
246         checkVector(Vector2D.Unit.PLUS_X.withNorm(2.5), 2.5, 0.0);
247         checkVector(Vector2D.Unit.MINUS_Y.withNorm(3.14), 0.0, -3.14);
248 
249         for (int i = -10; i <= 10; i++) {
250             Assert.assertEquals(Math.abs((double) i), v.withNorm(i).norm(), eps);
251         }
252     }
253 
254     @Test
255     public void testAdd() {
256         // arrange
257         final Vector2D v1 = Vector2D.of(-1, 2);
258         final Vector2D v2 = Vector2D.of(3, -4);
259         final Vector2D v3 = Vector2D.of(5, 6);
260 
261         // act/assert
262         checkVector(v1.add(v1), -2, 4);
263 
264         checkVector(v1.add(v2), 2, -2);
265         checkVector(v2.add(v1), 2, -2);
266 
267         checkVector(v1.add(v3), 4, 8);
268         checkVector(v3.add(v1), 4, 8);
269     }
270 
271     @Test
272     public void testAdd_scaled() {
273         // arrange
274         final Vector2D v1 = Vector2D.of(-1, 2);
275         final Vector2D v2 = Vector2D.of(3, -4);
276         final Vector2D v3 = Vector2D.of(5, 6);
277 
278         // act/assert
279         checkVector(v1.add(2, v1), -3, 6);
280 
281         checkVector(v1.add(0, v2), -1, 2);
282         checkVector(v2.add(1, v1), 2, -2);
283 
284         checkVector(v1.add(-1, v3), -6, -4);
285         checkVector(v3.add(-2, v1), 7, 2);
286     }
287 
288     @Test
289     public void testSubtract() {
290         // arrange
291         final Vector2D v1 = Vector2D.of(-1, 2);
292         final Vector2D v2 = Vector2D.of(3, -4);
293         final Vector2D v3 = Vector2D.of(5, 6);
294 
295         // act/assert
296         checkVector(v1.subtract(v1), 0, 0);
297 
298         checkVector(v1.subtract(v2), -4, 6);
299         checkVector(v2.subtract(v1), 4, -6);
300 
301         checkVector(v1.subtract(v3), -6, -4);
302         checkVector(v3.subtract(v1), 6, 4);
303     }
304 
305     @Test
306     public void testSubtract_scaled() {
307         // arrange
308         final Vector2D v1 = Vector2D.of(-1, 2);
309         final Vector2D v2 = Vector2D.of(3, -4);
310         final Vector2D v3 = Vector2D.of(5, 6);
311 
312         // act/assert
313         checkVector(v1.subtract(2, v1), 1, -2);
314 
315         checkVector(v1.subtract(0, v2), -1, 2);
316         checkVector(v2.subtract(1, v1), 4, -6);
317 
318         checkVector(v1.subtract(-1, v3), 4, 8);
319         checkVector(v3.subtract(-2, v1), 3, 10);
320     }
321 
322     @Test
323     public void testNormalize() {
324         // act/assert
325         checkVector(Vector2D.of(100, 0).normalize(), 1, 0);
326         checkVector(Vector2D.of(-100, 0).normalize(), -1, 0);
327         checkVector(Vector2D.of(0, 100).normalize(), 0, 1);
328         checkVector(Vector2D.of(0, -100).normalize(), 0, -1);
329         checkVector(Vector2D.of(-1, 2).normalize(), -1.0 / Math.sqrt(5), 2.0 / Math.sqrt(5));
330     }
331 
332     @Test
333     public void testNormalize_illegalNorm() {
334         // act/assert
335         GeometryTestUtils.assertThrows(Vector2D.ZERO::normalize, IllegalArgumentException.class);
336         GeometryTestUtils.assertThrows(Vector2D.NaN::normalize, IllegalArgumentException.class);
337         GeometryTestUtils.assertThrows(Vector2D.POSITIVE_INFINITY::normalize, IllegalArgumentException.class);
338         GeometryTestUtils.assertThrows(Vector2D.NEGATIVE_INFINITY::normalize, IllegalArgumentException.class);
339     }
340 
341     @Test
342     public void testNormalize_isIdempotent() {
343         // arrange
344         final double invSqrt2 = 1.0 / Math.sqrt(2);
345         final Vector2D v = Vector2D.of(2, 2).normalize();
346 
347         // act/assert
348         Assert.assertSame(v, v.normalize());
349         checkVector(v.normalize(), invSqrt2, invSqrt2);
350     }
351 
352     @Test
353     public void testNegate() {
354         // act/assert
355         checkVector(Vector2D.of(1, 2).negate(), -1, -2);
356         checkVector(Vector2D.of(-3, -4).negate(), 3, 4);
357         checkVector(Vector2D.of(5, -6).negate().negate(), 5, -6);
358     }
359 
360     @Test
361     public void testNegate_unitVectors() {
362         // arrange
363         final Vector2D v1 = Vector2D.of(1.0, 1.0).normalize();
364         final Vector2D v2 = Vector2D.of(-1.0, -2.0).normalize();
365         final Vector2D v3 = Vector2D.of(2.0, -3.0).normalize();
366 
367         // act/assert
368         checkVector(v1.negate(), -1.0 / Math.sqrt(2.0), -1.0 / Math.sqrt(2.0));
369         checkVector(v2.negate(), 1.0 / Math.sqrt(5.0), 2.0 / Math.sqrt(5.0));
370         checkVector(v3.negate(), -2.0 / Math.sqrt(13.0), 3.0 / Math.sqrt(13.0));
371     }
372 
373     @Test
374     public void testScalarMultiply() {
375         // act/assert
376         checkVector(Vector2D.of(1, 2).multiply(0), 0, 0);
377 
378         checkVector(Vector2D.of(1, 2).multiply(3), 3, 6);
379         checkVector(Vector2D.of(1, 2).multiply(-3), -3, -6);
380 
381         checkVector(Vector2D.of(2, 3).multiply(1.5), 3, 4.5);
382         checkVector(Vector2D.of(2, 3).multiply(-1.5), -3, -4.5);
383     }
384 
385     @Test
386     public void testDistance() {
387         // arrange
388         final Vector2D v1 = Vector2D.of(1, 1);
389         final Vector2D v2 = Vector2D.of(4, 5);
390         final Vector2D v3 = Vector2D.of(-1, 0);
391 
392         // act/assert
393         Assert.assertEquals(0, v1.distance(v1), EPS);
394 
395         Assert.assertEquals(5, v1.distance(v2), EPS);
396         Assert.assertEquals(5, v2.distance(v1), EPS);
397 
398         Assert.assertEquals(Math.sqrt(5), v1.distance(v3), EPS);
399         Assert.assertEquals(Math.sqrt(5), v3.distance(v1), EPS);
400     }
401 
402     @Test
403     public void testDistanceSq() {
404         // arrange
405         final Vector2D v1 = Vector2D.of(1, 1);
406         final Vector2D v2 = Vector2D.of(4, 5);
407         final Vector2D v3 = Vector2D.of(-1, 0);
408 
409         // act/assert
410         Assert.assertEquals(0, v1.distanceSq(v1), EPS);
411 
412         Assert.assertEquals(25, v1.distanceSq(v2), EPS);
413         Assert.assertEquals(25, v2.distanceSq(v1), EPS);
414 
415         Assert.assertEquals(5, v1.distanceSq(v3), EPS);
416         Assert.assertEquals(5, v3.distanceSq(v1), EPS);
417     }
418 
419     @Test
420     public void testDotProduct() {
421         // arrange
422         final Vector2D v1 = Vector2D.of(1, 1);
423         final Vector2D v2 = Vector2D.of(4, 5);
424         final Vector2D v3 = Vector2D.of(-1, 0);
425 
426         // act/assert
427         Assert.assertEquals(2, v1.dot(v1), EPS);
428         Assert.assertEquals(41, v2.dot(v2), EPS);
429         Assert.assertEquals(1, v3.dot(v3), EPS);
430 
431         Assert.assertEquals(9, v1.dot(v2), EPS);
432         Assert.assertEquals(9, v2.dot(v1), EPS);
433 
434         Assert.assertEquals(-1, v1.dot(v3), EPS);
435         Assert.assertEquals(-1, v3.dot(v1), EPS);
436 
437         Assert.assertEquals(1, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.PLUS_X), EPS);
438         Assert.assertEquals(0, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.PLUS_Y), EPS);
439         Assert.assertEquals(-1, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.MINUS_X), EPS);
440         Assert.assertEquals(0, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.MINUS_Y), EPS);
441     }
442 
443     @Test
444     public void testOrthogonal() {
445         // arrange
446         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
447 
448         // act/assert
449         checkVector(Vector2D.of(3, 0).orthogonal(), 0.0, 1.0);
450         checkVector(Vector2D.of(1.0, 1.0).orthogonal(), -invSqrt2, invSqrt2);
451 
452         checkVector(Vector2D.of(0, 2).orthogonal(), -1.0, 0.0);
453         checkVector(Vector2D.of(-1.0, 1.0).orthogonal(), -invSqrt2, -invSqrt2);
454 
455         checkVector(Vector2D.Unit.MINUS_X.orthogonal(), 0.0, -1.0);
456         checkVector(Vector2D.of(-1.0, -1.0).orthogonal(), invSqrt2, -invSqrt2);
457 
458         checkVector(Vector2D.Unit.MINUS_Y.orthogonal(), 1.0, 0.0);
459         checkVector(Vector2D.of(1.0, -1.0).orthogonal(), invSqrt2, invSqrt2);
460     }
461 
462     @Test
463     public void testOrthogonal_fullCircle() {
464         for (double az = 0.0; az <= PlaneAngleRadians.TWO_PI; az += 0.25) {
465             // arrange
466             final Vector2D v = PolarCoordinates.toCartesian(Math.PI, az);
467 
468             //act
469             final Vector2D ortho = v.orthogonal();
470 
471             // assert
472             Assert.assertEquals(1.0, ortho.norm(), EPS);
473             Assert.assertEquals(0.0, v.dot(ortho), EPS);
474         }
475     }
476 
477     @Test
478     public void testOrthogonal_illegalNorm() {
479         // act/assert
480         GeometryTestUtils.assertThrows(Vector2D.ZERO::orthogonal, IllegalArgumentException.class);
481         GeometryTestUtils.assertThrows(Vector2D.NaN::orthogonal, IllegalArgumentException.class);
482         GeometryTestUtils.assertThrows(Vector2D.POSITIVE_INFINITY::orthogonal, IllegalArgumentException.class);
483         GeometryTestUtils.assertThrows(Vector2D.NEGATIVE_INFINITY::orthogonal, IllegalArgumentException.class);
484     }
485 
486     @Test
487     public void testOrthogonal_givenDirection() {
488         // arrange
489         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
490 
491         // act/assert
492         checkVector(Vector2D.Unit.PLUS_X.orthogonal(Vector2D.of(-1.0, 0.1)), 0.0, 1.0);
493         checkVector(Vector2D.Unit.PLUS_Y.orthogonal(Vector2D.of(2.0, 2.0)), 1.0, 0.0);
494 
495         checkVector(Vector2D.of(2.9, 2.9).orthogonal(Vector2D.of(1.0, 0.22)), invSqrt2, -invSqrt2);
496         checkVector(Vector2D.of(2.9, 2.9).orthogonal(Vector2D.of(0.22, 1.0)), -invSqrt2, invSqrt2);
497     }
498 
499     @Test
500     public void testOrthogonal_givenDirection_illegalNorm() {
501         // act/assert
502         GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.orthogonal(Vector2D.Unit.PLUS_X),
503                 IllegalArgumentException.class);
504         GeometryTestUtils.assertThrows(() -> Vector2D.NaN.orthogonal(Vector2D.Unit.PLUS_X),
505                 IllegalArgumentException.class);
506         GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.orthogonal(Vector2D.Unit.PLUS_X),
507                 IllegalArgumentException.class);
508         GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.orthogonal(Vector2D.Unit.PLUS_X),
509                 IllegalArgumentException.class);
510 
511         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.ZERO),
512                 IllegalArgumentException.class);
513         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.NaN),
514                 IllegalArgumentException.class);
515         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.POSITIVE_INFINITY),
516                 IllegalArgumentException.class);
517         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.NEGATIVE_INFINITY),
518                 IllegalArgumentException.class);
519     }
520 
521     @Test
522     public void testOrthogonal_givenDirection_directionIsCollinear() {
523         // act/assert
524         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.Unit.PLUS_X),
525                 IllegalArgumentException.class);
526         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.Unit.MINUS_X),
527                 IllegalArgumentException.class);
528         GeometryTestUtils.assertThrows(() -> Vector2D.of(1.0, 1.0).orthogonal(Vector2D.of(2.0, 2.0)),
529                 IllegalArgumentException.class);
530         GeometryTestUtils.assertThrows(() -> Vector2D.of(-1.01, -1.01).orthogonal(Vector2D.of(20.1, 20.1)),
531                 IllegalArgumentException.class);
532     }
533 
534     @Test
535     public void testAngle() {
536         // act/assert
537         Assert.assertEquals(0, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.PLUS_X), EPS);
538 
539         Assert.assertEquals(PlaneAngleRadians.PI, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.MINUS_X), EPS);
540         Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.PLUS_Y), EPS);
541         Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.MINUS_Y), EPS);
542 
543         Assert.assertEquals(PlaneAngleRadians.PI / 4, Vector2D.of(1, 1).angle(Vector2D.of(1, 0)), EPS);
544         Assert.assertEquals(PlaneAngleRadians.PI / 4, Vector2D.of(1, 0).angle(Vector2D.of(1, 1)), EPS);
545 
546         Assert.assertEquals(0.004999958333958323, Vector2D.of(20.0, 0.0).angle(Vector2D.of(20.0, 0.1)), EPS);
547     }
548 
549 
550     @Test
551     public void testAngle_illegalNorm() {
552         // arrange
553         final Vector2D v = Vector2D.of(1.0, 1.0);
554 
555         // act/assert
556         GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.angle(v),
557                 IllegalArgumentException.class);
558         GeometryTestUtils.assertThrows(() -> Vector2D.NaN.angle(v),
559                 IllegalArgumentException.class);
560         GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.angle(v),
561                 IllegalArgumentException.class);
562         GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.angle(v),
563                 IllegalArgumentException.class);
564 
565         GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.ZERO),
566                 IllegalArgumentException.class);
567         GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.NaN),
568                 IllegalArgumentException.class);
569         GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.POSITIVE_INFINITY),
570                 IllegalArgumentException.class);
571         GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.NEGATIVE_INFINITY),
572                 IllegalArgumentException.class);
573     }
574 
575     @Test
576     public void testSignedArea() {
577         // arrange
578         final double eps = 1e-10;
579 
580         final Vector2D a = Vector2D.Unit.PLUS_X;
581         final Vector2D b = Vector2D.Unit.PLUS_Y;
582         final Vector2D c = Vector2D.of(1, 1).withNorm(2.0);
583         final Vector2D d = Vector2D.of(-1, 1).withNorm(3.0);
584 
585         // act/assert
586         Assert.assertEquals(1.0, a.signedArea(b), eps);
587         Assert.assertEquals(-1.0, b.signedArea(a), eps);
588 
589         final double xAxisAndCArea = 2 * Math.cos(0.25 * PlaneAngleRadians.PI);
590         Assert.assertEquals(xAxisAndCArea, a.signedArea(c), eps);
591         Assert.assertEquals(-xAxisAndCArea, c.signedArea(a), eps);
592 
593         final double xAxisAndDArea = 3 * Math.cos(0.25 * PlaneAngleRadians.PI);
594         Assert.assertEquals(xAxisAndDArea, a.signedArea(d), eps);
595         Assert.assertEquals(-xAxisAndDArea, d.signedArea(a), eps);
596 
597         Assert.assertEquals(6.0, c.signedArea(d), eps);
598         Assert.assertEquals(-6.0, d.signedArea(c), eps);
599     }
600 
601     @Test
602     public void testSignedArea_collinear() {
603         // arrange
604         final Vector2D a = Vector2D.Unit.PLUS_X;
605         final Vector2D b = Vector2D.Unit.PLUS_Y;
606         final Vector2D c = Vector2D.of(-3, 8);
607 
608         // act/assert
609         Assert.assertEquals(0.0, a.signedArea(a), EPS);
610         Assert.assertEquals(0.0, b.signedArea(b), EPS);
611         Assert.assertEquals(0.0, c.signedArea(c), EPS);
612 
613         Assert.assertEquals(0.0, a.signedArea(a.multiply(100.0)), EPS);
614         Assert.assertEquals(0.0, b.signedArea(b.negate()), EPS);
615         Assert.assertEquals(0.0, c.signedArea(c.multiply(-0.03)), EPS);
616     }
617 
618     @Test
619     public void testProject() {
620         // arrange
621         final Vector2D v1 = Vector2D.of(3.0, 4.0);
622         final Vector2D v2 = Vector2D.of(1.0, 4.0);
623 
624         // act/assert
625         checkVector(Vector2D.ZERO.project(v1), 0.0, 0.0);
626 
627         checkVector(v1.project(v1), 3.0, 4.0);
628         checkVector(v1.project(v1.negate()), 3.0, 4.0);
629 
630         checkVector(v1.project(Vector2D.Unit.PLUS_X), 3.0, 0.0);
631         checkVector(v1.project(Vector2D.Unit.MINUS_X), 3.0, 0.0);
632 
633         checkVector(v1.project(Vector2D.Unit.PLUS_Y), 0.0, 4.0);
634         checkVector(v1.project(Vector2D.Unit.MINUS_Y), 0.0, 4.0);
635 
636         checkVector(v2.project(v1), (19.0 / 25.0) * 3.0, (19.0 / 25.0) * 4.0);
637     }
638 
639     @Test
640     public void testProject_baseHasIllegalNorm() {
641         // arrange
642         final Vector2D v = Vector2D.of(1.0, 1.0);
643 
644         // act/assert
645         GeometryTestUtils.assertThrows(() -> v.project(Vector2D.ZERO),
646                 IllegalArgumentException.class);
647         GeometryTestUtils.assertThrows(() -> v.project(Vector2D.NaN),
648                 IllegalArgumentException.class);
649         GeometryTestUtils.assertThrows(() -> v.project(Vector2D.POSITIVE_INFINITY),
650                 IllegalArgumentException.class);
651         GeometryTestUtils.assertThrows(() -> v.project(Vector2D.NEGATIVE_INFINITY),
652                 IllegalArgumentException.class);
653     }
654 
655     @Test
656     public void testReject() {
657         // arrange
658         final Vector2D v1 = Vector2D.of(3.0, 4.0);
659         final Vector2D v2 = Vector2D.of(1.0, 4.0);
660 
661         // act/assert
662         checkVector(Vector2D.ZERO.reject(v1), 0.0, 0.0);
663 
664         checkVector(v1.reject(v1), 0.0, 0.0);
665         checkVector(v1.reject(v1.negate()), 0.0, 0.0);
666 
667         checkVector(v1.reject(Vector2D.Unit.PLUS_X), 0.0, 4.0);
668         checkVector(v1.reject(Vector2D.Unit.MINUS_X), 0.0, 4.0);
669 
670         checkVector(v1.reject(Vector2D.Unit.PLUS_Y), 3.0, 0.0);
671         checkVector(v1.reject(Vector2D.Unit.MINUS_Y), 3.0, 0.0);
672 
673         checkVector(v2.reject(v1), -32.0 / 25.0, (6.0 / 25.0) * 4.0);
674     }
675 
676     @Test
677     public void testReject_baseHasIllegalNorm() {
678         // arrange
679         final Vector2D v = Vector2D.of(1.0, 1.0);
680 
681         // act/assert
682         GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.ZERO),
683                 IllegalArgumentException.class);
684         GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.NaN),
685                 IllegalArgumentException.class);
686         GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.POSITIVE_INFINITY),
687                 IllegalArgumentException.class);
688         GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.NEGATIVE_INFINITY),
689                 IllegalArgumentException.class);
690     }
691 
692     @Test
693     public void testProjectAndReject_areComplementary() {
694         // arrange
695         final double eps = 1e-12;
696 
697         // act/assert
698         checkProjectAndRejectFullCircle(Vector2D.of(1.0, 0.0), 1.0, eps);
699         checkProjectAndRejectFullCircle(Vector2D.of(0.0, 1.0), 2.0, eps);
700         checkProjectAndRejectFullCircle(Vector2D.of(1.0, 1.0), 3.0, eps);
701 
702         checkProjectAndRejectFullCircle(Vector2D.of(-2.0, 0.0), 4.0, eps);
703         checkProjectAndRejectFullCircle(Vector2D.of(0.0, -2.0), 5.0, eps);
704         checkProjectAndRejectFullCircle(Vector2D.of(-2.0, -2.0), 6.0, eps);
705     }
706 
707     private void checkProjectAndRejectFullCircle(final Vector2D vec, final double baseMag, final double eps) {
708         for (double theta = 0.0; theta <= PlaneAngleRadians.TWO_PI; theta += 0.5) {
709             final Vector2D base = PolarCoordinates.toCartesian(baseMag, theta);
710 
711             final Vector2D proj = vec.project(base);
712             final Vector2D rej = vec.reject(base);
713 
714             // ensure that the projection and rejection sum to the original vector
715             EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), eps);
716 
717             final double angle = base.angle(vec);
718 
719             // check the angle between the projection and the base; this will
720             // be undefined when the angle between the original vector and the
721             // base is pi/2 (which means that the projection is the zero vector)
722             if (angle < PlaneAngleRadians.PI_OVER_TWO) {
723                 Assert.assertEquals(0.0, proj.angle(base), eps);
724             } else if (angle > PlaneAngleRadians.PI_OVER_TWO) {
725                 Assert.assertEquals(PlaneAngleRadians.PI, proj.angle(base), eps);
726             }
727 
728             // check the angle between the rejection and the base; this should
729             // always be pi/2 except for when the angle between the original vector
730             // and the base is 0 or pi, in which case the rejection is the zero vector.
731             if (angle > 0.0 && angle < PlaneAngleRadians.PI) {
732                 Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, rej.angle(base), eps);
733             }
734         }
735     }
736 
737     @Test
738     public void testVectorTo() {
739         // arrange
740         final Vector2D p1 = Vector2D.of(1, 1);
741         final Vector2D p2 = Vector2D.of(4, 5);
742         final Vector2D p3 = Vector2D.of(-1, 0);
743 
744         // act/assert
745         checkVector(p1.vectorTo(p1), 0, 0);
746         checkVector(p1.vectorTo(p2), 3, 4);
747         checkVector(p2.vectorTo(p1), -3, -4);
748 
749         checkVector(p1.vectorTo(p3), -2, -1);
750         checkVector(p3.vectorTo(p1), 2, 1);
751     }
752 
753     @Test
754     public void testDirectionTo() {
755         // act/assert
756         final double invSqrt2 = 1.0 / Math.sqrt(2);
757 
758         final Vector2D p1 = Vector2D.of(1, 1);
759         final Vector2D p2 = Vector2D.of(1, 5);
760         final Vector2D p3 = Vector2D.of(-2, -2);
761 
762         // act/assert
763         checkVector(p1.directionTo(p2), 0, 1);
764         checkVector(p2.directionTo(p1), 0, -1);
765 
766         checkVector(p1.directionTo(p3), -invSqrt2, -invSqrt2);
767         checkVector(p3.directionTo(p1), invSqrt2, invSqrt2);
768     }
769 
770     @Test
771     public void testDirectionTo_illegalNorm() {
772         // arrange
773         final Vector2D p = Vector2D.of(1, 2);
774 
775         // act/assert
776         GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.directionTo(Vector2D.ZERO),
777                 IllegalArgumentException.class);
778         GeometryTestUtils.assertThrows(() -> p.directionTo(p),
779                 IllegalArgumentException.class);
780         GeometryTestUtils.assertThrows(() -> p.directionTo(Vector2D.NaN),
781                 IllegalArgumentException.class);
782         GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.directionTo(p),
783                 IllegalArgumentException.class);
784         GeometryTestUtils.assertThrows(() -> p.directionTo(Vector2D.POSITIVE_INFINITY),
785                 IllegalArgumentException.class);
786     }
787 
788     @Test
789     public void testLerp() {
790         // arrange
791         final Vector2D v1 = Vector2D.of(1, -5);
792         final Vector2D v2 = Vector2D.of(-4, 0);
793         final Vector2D v3 = Vector2D.of(10, -4);
794 
795         // act/assert
796         checkVector(v1.lerp(v1, 0), 1, -5);
797         checkVector(v1.lerp(v1, 1), 1, -5);
798 
799         checkVector(v1.lerp(v2, -0.25), 2.25, -6.25);
800         checkVector(v1.lerp(v2, 0), 1, -5);
801         checkVector(v1.lerp(v2, 0.25), -0.25, -3.75);
802         checkVector(v1.lerp(v2, 0.5), -1.5, -2.5);
803         checkVector(v1.lerp(v2, 0.75), -2.75, -1.25);
804         checkVector(v1.lerp(v2, 1), -4, 0);
805         checkVector(v1.lerp(v2, 1.25), -5.25, 1.25);
806 
807         checkVector(v1.lerp(v3, 0), 1, -5);
808         checkVector(v1.lerp(v3, 0.25), 3.25, -4.75);
809         checkVector(v1.lerp(v3, 0.5), 5.5, -4.5);
810         checkVector(v1.lerp(v3, 0.75), 7.75, -4.25);
811         checkVector(v1.lerp(v3, 1), 10, -4);
812     }
813 
814     @Test
815     public void testTransform() {
816         // arrange
817         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
818                 .scale(2)
819                 .translate(1, 2);
820 
821         final Vector2D v1 = Vector2D.of(1, 2);
822         final Vector2D v2 = Vector2D.of(-4, -5);
823 
824         // act/assert
825         checkVector(v1.transform(transform), 3, 6);
826         checkVector(v2.transform(transform), -7, -8);
827     }
828 
829     @Test
830     public void testPrecisionEquals() {
831         // arrange
832         final DoublePrecisionContext smallEps = new EpsilonDoublePrecisionContext(1e-6);
833         final DoublePrecisionContext largeEps = new EpsilonDoublePrecisionContext(1e-1);
834 
835         final Vector2D vec = Vector2D.of(1, -2);
836 
837         // act/assert
838         Assert.assertTrue(vec.eq(vec, smallEps));
839         Assert.assertTrue(vec.eq(vec, largeEps));
840 
841         Assert.assertTrue(vec.eq(Vector2D.of(1.0000007, -2.0000009), smallEps));
842         Assert.assertTrue(vec.eq(Vector2D.of(1.0000007, -2.0000009), largeEps));
843 
844         Assert.assertFalse(vec.eq(Vector2D.of(1.004, -2), smallEps));
845         Assert.assertFalse(vec.eq(Vector2D.of(1, -2.004), smallEps));
846         Assert.assertTrue(vec.eq(Vector2D.of(1.004, -2.004), largeEps));
847 
848         Assert.assertFalse(vec.eq(Vector2D.of(1, -3), smallEps));
849         Assert.assertFalse(vec.eq(Vector2D.of(2, -2), smallEps));
850         Assert.assertFalse(vec.eq(Vector2D.of(1, -3), largeEps));
851         Assert.assertFalse(vec.eq(Vector2D.of(2, -2), largeEps));
852     }
853 
854     @Test
855     public void testIsZero() {
856         // arrange
857         final DoublePrecisionContext smallEps = new EpsilonDoublePrecisionContext(1e-6);
858         final DoublePrecisionContext largeEps = new EpsilonDoublePrecisionContext(1e-1);
859 
860         // act/assert
861         Assert.assertTrue(Vector2D.of(0.0, -0.0).isZero(smallEps));
862         Assert.assertTrue(Vector2D.of(-0.0, 0.0).isZero(largeEps));
863 
864         Assert.assertTrue(Vector2D.of(-1e-7, 1e-7).isZero(smallEps));
865         Assert.assertTrue(Vector2D.of(1e-7, 1e-7).isZero(largeEps));
866 
867         Assert.assertFalse(Vector2D.of(1e-2, 0.0).isZero(smallEps));
868         Assert.assertFalse(Vector2D.of(0.0, 1e-2).isZero(smallEps));
869         Assert.assertTrue(Vector2D.of(1e-2, -1e-2).isZero(largeEps));
870 
871         Assert.assertFalse(Vector2D.of(0.2, 0.0).isZero(smallEps));
872         Assert.assertFalse(Vector2D.of(0.0, 0.2).isZero(smallEps));
873         Assert.assertFalse(Vector2D.of(0.2, 0.2).isZero(smallEps));
874         Assert.assertFalse(Vector2D.of(-0.2, 0.0).isZero(largeEps));
875         Assert.assertFalse(Vector2D.of(0.0, -0.2).isZero(largeEps));
876         Assert.assertFalse(Vector2D.of(-0.2, -0.2).isZero(largeEps));
877     }
878 
879     @Test
880     public void testHashCode() {
881         // arrange
882         final Vector2D u = Vector2D.of(1, 1);
883         final Vector2D v = Vector2D.of(1 + 10 * Precision.EPSILON, 1 + 10 * Precision.EPSILON);
884         final Vector2D w = Vector2D.of(1, 1);
885 
886         // act/assert
887         Assert.assertTrue(u.hashCode() != v.hashCode());
888         Assert.assertEquals(u.hashCode(), w.hashCode());
889 
890         Assert.assertEquals(Vector2D.of(0, Double.NaN).hashCode(), Vector2D.NaN.hashCode());
891         Assert.assertEquals(Vector2D.of(Double.NaN, 0).hashCode(), Vector2D.NaN.hashCode());
892         Assert.assertEquals(Vector2D.of(0, Double.NaN).hashCode(), Vector2D.of(Double.NaN, 0).hashCode());
893     }
894 
895     @Test
896     public void testEquals() {
897         // arrange
898         final Vector2D u1 = Vector2D.of(1, 2);
899         final Vector2D u2 = Vector2D.of(1, 2);
900 
901         // act/assert
902         Assert.assertFalse(u1.equals(null));
903         Assert.assertFalse(u1.equals(new Object()));
904 
905         Assert.assertEquals(u1, u1);
906         Assert.assertEquals(u1, u2);
907 
908         Assert.assertNotEquals(u1, Vector2D.of(-1, -2));
909         Assert.assertNotEquals(u1, Vector2D.of(1 + 10 * Precision.EPSILON, 2));
910         Assert.assertNotEquals(u1, Vector2D.of(1, 2 + 10 * Precision.EPSILON));
911 
912         Assert.assertEquals(Vector2D.of(0, Double.NaN), Vector2D.of(Double.NaN, 0));
913 
914         Assert.assertEquals(Vector2D.of(0, Double.POSITIVE_INFINITY), Vector2D.of(0, Double.POSITIVE_INFINITY));
915         Assert.assertNotEquals(Vector2D.of(Double.POSITIVE_INFINITY, 0), Vector2D.of(0, Double.POSITIVE_INFINITY));
916 
917         Assert.assertEquals(Vector2D.of(Double.NEGATIVE_INFINITY, 0), Vector2D.of(Double.NEGATIVE_INFINITY, 0));
918         Assert.assertNotEquals(Vector2D.of(0, Double.NEGATIVE_INFINITY), Vector2D.of(Double.NEGATIVE_INFINITY, 0));
919     }
920 
921     @Test
922     public void testEqualsAndHashCode_signedZeroConsistency() {
923         // arrange
924         final Vector2D a = Vector2D.of(0.0, 0.0);
925         final Vector2D b = Vector2D.of(-0.0, -0.0);
926         final Vector2D c = Vector2D.of(0.0, 0.0);
927         final Vector2D d = Vector2D.of(-0.0, -0.0);
928 
929         // act/assert
930         Assert.assertFalse(a.equals(b));
931 
932         Assert.assertTrue(a.equals(c));
933         Assert.assertEquals(a.hashCode(), c.hashCode());
934 
935         Assert.assertTrue(b.equals(d));
936         Assert.assertEquals(b.hashCode(), d.hashCode());
937     }
938 
939     @Test
940     public void testToString() {
941         // arrange
942         final Vector2D v = Vector2D.of(1, 2);
943         final Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)");
944 
945         // act
946         final String str = v.toString();
947 
948         // assert
949         Assert.assertTrue("Expected string " + str + " to match regex " + pattern,
950                     pattern.matcher(str).matches());
951     }
952 
953     @Test
954     public void testParse() {
955         // act/assert
956         checkVector(Vector2D.parse("(1, 2)"), 1, 2);
957         checkVector(Vector2D.parse("(-1, -2)"), -1, -2);
958 
959         checkVector(Vector2D.parse("(0.01, -1e-3)"), 1e-2, -1e-3);
960 
961         checkVector(Vector2D.parse("(NaN, -Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY);
962 
963         checkVector(Vector2D.parse(Vector2D.ZERO.toString()), 0, 0);
964         checkVector(Vector2D.parse(Vector2D.Unit.MINUS_X.toString()), -1, 0);
965     }
966 
967     @Test(expected = IllegalArgumentException.class)
968     public void testParse_failure() {
969         // act/assert
970         Vector2D.parse("abc");
971     }
972 
973     @Test
974     public void testOf() {
975         // act/assert
976         checkVector(Vector2D.of(0, 1), 0, 1);
977         checkVector(Vector2D.of(-1, -2), -1, -2);
978         checkVector(Vector2D.of(Math.PI, Double.NaN), Math.PI, Double.NaN);
979         checkVector(Vector2D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
980     }
981 
982     @Test
983     public void testOf_arrayArg() {
984         // act/assert
985         checkVector(Vector2D.of(new double[] {0, 1}), 0, 1);
986         checkVector(Vector2D.of(new double[] {-1, -2}), -1, -2);
987         checkVector(Vector2D.of(new double[] {Math.PI, Double.NaN}), Math.PI, Double.NaN);
988         checkVector(Vector2D.of(new double[] {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
989     }
990 
991     @Test(expected = IllegalArgumentException.class)
992     public void testOf_arrayArg_invalidDimensions() {
993         // act/assert
994         Vector2D.of(new double[] {0.0});
995     }
996 
997     @Test
998     public void testUnitFrom_coordinates() {
999         // arrange
1000         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
1001 
1002         // act/assert
1003         checkVector(Vector2D.Unit.from(2.0, -2.0), invSqrt2, -invSqrt2);
1004         checkVector(Vector2D.Unit.from(-4.0, 4.0), -invSqrt2, invSqrt2);
1005     }
1006 
1007     @Test
1008     public void testUnitFrom_vector() {
1009         // arrange
1010         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
1011         final Vector2D vec = Vector2D.of(2.0, -2.0);
1012         final Vector2D.Unit unitVec = Vector2D.Unit.from(2.0, -2.0);
1013 
1014         // act/assert
1015         checkVector(Vector2D.Unit.from(vec), invSqrt2, -invSqrt2);
1016         Assert.assertSame(unitVec, Vector2D.Unit.from(unitVec));
1017     }
1018 
1019     @Test
1020     public void testUnitFrom_illegalNorm() {
1021         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(0.0, 0.0),
1022                 IllegalArgumentException.class);
1023         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(Double.NaN, 1.0),
1024                 IllegalArgumentException.class);
1025         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(1.0, Double.NEGATIVE_INFINITY),
1026                 IllegalArgumentException.class);
1027         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(1.0, Double.POSITIVE_INFINITY),
1028                 IllegalArgumentException.class);
1029     }
1030 
1031     @Test
1032     public void testMax() {
1033         // act/assert
1034         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1),
1035                 Vector2D.max(Collections.singletonList(Vector2D.of(-100, 1))), EPS);
1036 
1037         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 1),
1038                 Vector2D.max(Arrays.asList(Vector2D.of(-100, 1), Vector2D.of(0, 1))), EPS);
1039 
1040         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 0),
1041                 Vector2D.max(Vector2D.of(-2, 0), Vector2D.of(-1, -5), Vector2D.of(-10, -10)), EPS);
1042     }
1043 
1044     @Test
1045     public void testMax_noPointsGiven() {
1046         // arrange
1047         final String msg = "Cannot compute vector max: no vectors given";
1048 
1049         // act/assert
1050         GeometryTestUtils.assertThrows(() -> {
1051             Vector2D.max(new ArrayList<>());
1052         }, IllegalArgumentException.class, msg);
1053     }
1054 
1055     @Test
1056     public void testMin() {
1057         // act/assert
1058         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1),
1059                 Vector2D.min(Collections.singletonList(Vector2D.of(-100, 1))), EPS);
1060 
1061         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1),
1062                 Vector2D.min(Arrays.asList(Vector2D.of(-100, 1), Vector2D.of(0, 1))), EPS);
1063 
1064         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-10, -10),
1065                 Vector2D.min(Vector2D.of(-2, 0), Vector2D.of(-1, -5), Vector2D.of(-10, -10)), EPS);
1066     }
1067 
1068     @Test
1069     public void testMin_noPointsGiven() {
1070         // arrange
1071         final String msg = "Cannot compute vector min: no vectors given";
1072 
1073         // act/assert
1074         GeometryTestUtils.assertThrows(() -> {
1075             Vector2D.min(new ArrayList<>());
1076         }, IllegalArgumentException.class, msg);
1077     }
1078 
1079     @Test
1080     public void testCentroid() {
1081         // act/assert
1082         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2),
1083                 Vector2D.centroid(Vector2D.of(1, 2)), EPS);
1084 
1085         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2.5, 3.5),
1086                 Vector2D.centroid(Vector2D.of(1, 2), Vector2D.of(2, 3),
1087                         Vector2D.of(3, 4), Vector2D.of(4, 5)), EPS);
1088 
1089         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2),
1090                 Vector2D.centroid(Collections.singletonList(Vector2D.of(1, 2))), EPS);
1091 
1092         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 1),
1093                 Vector2D.centroid(Arrays.asList(Vector2D.of(1, 2), Vector2D.of(1, 2),
1094                         Vector2D.ZERO, Vector2D.ZERO)), EPS);
1095     }
1096 
1097     @Test
1098     public void testCentroid_noPointsGiven() {
1099         // arrange
1100         final String msg = "Cannot compute centroid: no points given";
1101 
1102         // act/assert
1103         GeometryTestUtils.assertThrows(() -> {
1104             Vector2D.centroid(new ArrayList<>());
1105         }, IllegalArgumentException.class, msg);
1106     }
1107 
1108     @Test
1109     public void testLinearCombination1() {
1110         // arrange
1111         final Vector2D p1 = Vector2D.of(1, 2);
1112 
1113         // act/assert
1114         checkVector(Vector2D.linearCombination(0, p1), 0, 0);
1115 
1116         checkVector(Vector2D.linearCombination(1, p1), 1, 2);
1117         checkVector(Vector2D.linearCombination(-1, p1), -1, -2);
1118 
1119         checkVector(Vector2D.linearCombination(0.5, p1), 0.5, 1);
1120         checkVector(Vector2D.linearCombination(-0.5, p1), -0.5, -1);
1121     }
1122 
1123     @Test
1124     public void testLinearCombination2() {
1125         // arrange
1126         final Vector2D p1 = Vector2D.of(1, 2);
1127         final Vector2D p2 = Vector2D.of(-3, -4);
1128 
1129         // act/assert
1130         checkVector(Vector2D.linearCombination(2, p1, -3, p2), 11, 16);
1131         checkVector(Vector2D.linearCombination(-3, p1, 2, p2), -9, -14);
1132     }
1133 
1134     @Test
1135     public void testLinearCombination3() {
1136         // arrange
1137         final Vector2D p1 = Vector2D.of(1, 2);
1138         final Vector2D p2 = Vector2D.of(-3, -4);
1139         final Vector2D p3 = Vector2D.of(5, 6);
1140 
1141         // act/assert
1142         checkVector(Vector2D.linearCombination(2, p1, -3, p2, 4, p3), 31, 40);
1143         checkVector(Vector2D.linearCombination(-3, p1, 2, p2, -4, p3), -29, -38);
1144     }
1145 
1146     @Test
1147     public void testLinearCombination4() {
1148         // arrange
1149         final Vector2D p1 = Vector2D.of(1, 2);
1150         final Vector2D p2 = Vector2D.of(-3, -4);
1151         final Vector2D p3 = Vector2D.of(5, 6);
1152         final Vector2D p4 = Vector2D.of(-7, -8);
1153 
1154         // act/assert
1155         checkVector(Vector2D.linearCombination(2, p1, -3, p2, 4, p3, -5, p4), 66, 80);
1156         checkVector(Vector2D.linearCombination(-3, p1, 2, p2, -4, p3, 5, p4), -64, -78);
1157     }
1158 
1159     @Test
1160     public void testUnitFactoryOptimization() {
1161         // An already normalized vector will avoid unnecessary creation.
1162         final Vector2D v = Vector2D.of(4, 5).normalize();
1163         Assert.assertSame(v, v.normalize());
1164     }
1165 
1166     private void checkVector(final Vector2D v, final double x, final double y) {
1167         checkVector(v, x, y, EPS);
1168     }
1169 
1170     private void checkVector(final Vector2D v, final double x, final double y, final double eps) {
1171         Assert.assertEquals(x, v.getX(), eps);
1172         Assert.assertEquals(y, v.getY(), eps);
1173     }
1174 }