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 org.apache.commons.geometry.core.GeometryTestUtils;
20  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
21  import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
22  import org.apache.commons.geometry.euclidean.oned.Vector1D;
23  import org.apache.commons.geometry.euclidean.twod.Line.SubspaceTransform;
24  import org.apache.commons.numbers.angle.Angle;
25  import org.apache.commons.numbers.core.Precision;
26  import org.junit.jupiter.api.Assertions;
27  import org.junit.jupiter.api.Test;
28  
29  class LineTest {
30  
31      private static final double TEST_EPS = 1e-10;
32  
33      private static final Precision.DoubleEquivalence TEST_PRECISION =
34              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
35  
36      @Test
37      void testFromPoints() {
38          // act/assert
39          checkLine(Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION),
40                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
41          checkLine(Lines.fromPoints(Vector2D.ZERO, Vector2D.of(100, 0), TEST_PRECISION),
42                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
43          checkLine(Lines.fromPoints(Vector2D.of(100, 0), Vector2D.ZERO, TEST_PRECISION),
44                  Vector2D.ZERO, Vector2D.Unit.MINUS_X);
45          checkLine(Lines.fromPoints(Vector2D.of(-100, 0), Vector2D.of(100, 0), TEST_PRECISION),
46                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
47  
48          checkLine(Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 2), TEST_PRECISION),
49                  Vector2D.of(-1, 1), Vector2D.of(1, 1).normalize());
50          checkLine(Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(-2, 0), TEST_PRECISION),
51                  Vector2D.of(-1, 1), Vector2D.of(-1, -1).normalize());
52      }
53  
54      @Test
55      void testFromPoints_pointsTooClose() {
56          // act/assert
57          GeometryTestUtils.assertThrowsWithMessage(() -> Lines.fromPoints(Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X, TEST_PRECISION),
58                  IllegalArgumentException.class, "Line direction cannot be zero");
59          GeometryTestUtils.assertThrowsWithMessage(() -> Lines.fromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(1 + 1e-11, 1e-11), TEST_PRECISION),
60                  IllegalArgumentException.class, "Line direction cannot be zero");
61      }
62  
63      @Test
64      void testFromPointAndDirection() {
65          // act/assert
66          checkLine(Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION),
67                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
68          checkLine(Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.of(100, 0), TEST_PRECISION),
69                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
70          checkLine(Lines.fromPointAndDirection(Vector2D.of(-100, 0), Vector2D.of(100, 0), TEST_PRECISION),
71                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
72  
73          checkLine(Lines.fromPointAndDirection(Vector2D.of(-2, 0), Vector2D.of(1, 1), TEST_PRECISION),
74                  Vector2D.of(-1, 1), Vector2D.of(1, 1).normalize());
75          checkLine(Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(-1, -1), TEST_PRECISION),
76                  Vector2D.of(-1, 1), Vector2D.of(-1, -1).normalize());
77      }
78  
79      @Test
80      void testFromPointAndDirection_directionIsZero() {
81          // act/assert
82          GeometryTestUtils.assertThrowsWithMessage(() -> Lines.fromPointAndDirection(Vector2D.Unit.PLUS_X, Vector2D.ZERO, TEST_PRECISION),
83                  IllegalArgumentException.class, "Line direction cannot be zero");
84          GeometryTestUtils.assertThrowsWithMessage(() -> Lines.fromPointAndDirection(Vector2D.Unit.PLUS_X, Vector2D.of(1e-11, -1e-12), TEST_PRECISION),
85                  IllegalArgumentException.class, "Line direction cannot be zero");
86      }
87  
88      @Test
89      void testFromPointAndAngle() {
90          // act/assert
91          checkLine(Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION),
92                  Vector2D.ZERO, Vector2D.Unit.PLUS_X);
93          checkLine(Lines.fromPointAndAngle(Vector2D.of(1, 1), Angle.PI_OVER_TWO, TEST_PRECISION),
94                  Vector2D.of(1, 0), Vector2D.Unit.PLUS_Y);
95          checkLine(Lines.fromPointAndAngle(Vector2D.of(-1, -1), Math.PI, TEST_PRECISION),
96                  Vector2D.of(0, -1), Vector2D.Unit.MINUS_X);
97          checkLine(Lines.fromPointAndAngle(Vector2D.of(1, -1), -Angle.PI_OVER_TWO, TEST_PRECISION),
98                  Vector2D.of(1, 0), Vector2D.Unit.MINUS_Y);
99          checkLine(Lines.fromPointAndAngle(Vector2D.of(-1, 1), Angle.TWO_PI, TEST_PRECISION),
100                 Vector2D.of(0, 1), Vector2D.Unit.PLUS_X);
101     }
102 
103     @Test
104     void testGetAngle() {
105         // arrange
106         final Vector2D vec = Vector2D.of(1, 2);
107 
108         for (double theta = -4 * Math.PI; theta < 2 * Math.PI; theta += 0.1) {
109             final Line line = Lines.fromPointAndAngle(vec, theta, TEST_PRECISION);
110 
111             // act/assert
112             Assertions.assertEquals(Angle.Rad.WITHIN_0_AND_2PI.applyAsDouble(theta),
113                     line.getAngle(), TEST_EPS);
114         }
115     }
116 
117     @Test
118     void testGetAngle_multiplesOfPi() {
119         // arrange
120         final Vector2D vec = Vector2D.of(-1, -2);
121 
122         // act/assert
123         Assertions.assertEquals(0, Lines.fromPointAndAngle(vec, 0.0, TEST_PRECISION).getAngle(), TEST_EPS);
124         Assertions.assertEquals(Math.PI, Lines.fromPointAndAngle(vec, Math.PI, TEST_PRECISION).getAngle(), TEST_EPS);
125         Assertions.assertEquals(0, Lines.fromPointAndAngle(vec, Angle.TWO_PI, TEST_PRECISION).getAngle(), TEST_EPS);
126 
127         Assertions.assertEquals(0, Lines.fromPointAndAngle(vec, -2 * Math.PI, TEST_PRECISION).getAngle(), TEST_EPS);
128         Assertions.assertEquals(Math.PI, Lines.fromPointAndAngle(vec, -3 * Math.PI, TEST_PRECISION).getAngle(), TEST_EPS);
129         Assertions.assertEquals(0, Lines.fromPointAndAngle(vec, -4 * Angle.TWO_PI, TEST_PRECISION).getAngle(), TEST_EPS);
130     }
131 
132     @Test
133     void testGetDirection() {
134         // act/assert
135         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
136                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getDirection(), TEST_EPS);
137         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
138                 Lines.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getDirection(), TEST_EPS);
139 
140         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
141                 Lines.fromPoints(Vector2D.of(2, 2), Vector2D.of(1, 2), TEST_PRECISION).getDirection(), TEST_EPS);
142         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
143                 Lines.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getDirection(), TEST_EPS);
144 
145         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
146                 Lines.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getDirection(), TEST_EPS);
147         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y,
148                 Lines.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getDirection(), TEST_EPS);
149 
150         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -1).normalize(),
151                 Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getDirection(), TEST_EPS);
152         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1).normalize(),
153                 Lines.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getDirection(), TEST_EPS);
154     }
155 
156     @Test
157     void testGetOffsetDirection() {
158         // act/assert
159         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
160                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
161         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
162                 Lines.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
163 
164         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y,
165                 Lines.fromPoints(Vector2D.of(2, 2), Vector2D.of(1, 2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
166         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
167                 Lines.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
168 
169         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
170                 Lines.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
171         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
172                 Lines.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
173 
174         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -1).normalize(),
175                 Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
176         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1).normalize(),
177                 Lines.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
178     }
179 
180     @Test
181     void testGetOrigin() {
182         // act/assert
183         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO,
184                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getOrigin(), TEST_EPS);
185         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO,
186                 Lines.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getOrigin(), TEST_EPS);
187 
188         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2),
189                 Lines.fromPoints(Vector2D.of(2, 2), Vector2D.of(3, 2), TEST_PRECISION).getOrigin(), TEST_EPS);
190         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -2),
191                 Lines.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getOrigin(), TEST_EPS);
192 
193         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 0),
194                 Lines.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getOrigin(), TEST_EPS);
195         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0),
196                 Lines.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getOrigin(), TEST_EPS);
197 
198         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1),
199                 Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getOrigin(), TEST_EPS);
200         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1),
201                 Lines.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getOrigin(), TEST_EPS);
202     }
203 
204     @Test
205     void testGetOriginOffset() {
206         // arrange
207         final double sqrt2 = Math.sqrt(2);
208 
209         // act/assert
210         Assertions.assertEquals(0.0,
211                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
212         Assertions.assertEquals(0.0,
213                 Lines.fromPoints(Vector2D.of(0, 0), Vector2D.of(-1, -1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
214 
215         Assertions.assertEquals(sqrt2,
216                 Lines.fromPoints(Vector2D.of(-1, 1), Vector2D.of(0, 2), TEST_PRECISION).getOriginOffset(), TEST_EPS);
217         Assertions.assertEquals(-sqrt2,
218                 Lines.fromPoints(Vector2D.of(0, -2), Vector2D.of(1, -1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
219 
220         Assertions.assertEquals(-sqrt2,
221                 Lines.fromPoints(Vector2D.of(0, 2), Vector2D.of(-1, 1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
222         Assertions.assertEquals(sqrt2,
223                 Lines.fromPoints(Vector2D.of(1, -1), Vector2D.of(0, -2), TEST_PRECISION).getOriginOffset(), TEST_EPS);
224     }
225 
226     @Test
227     void testGetPrecision() {
228         // act/assert
229         Assertions.assertSame(TEST_PRECISION, Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).getPrecision());
230         Assertions.assertSame(TEST_PRECISION, Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).getPrecision());
231         Assertions.assertSame(TEST_PRECISION, Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION).getPrecision());
232     }
233 
234     @Test
235     void testReverse() {
236         // arrange
237         final Vector2D pt = Vector2D.of(0, 1);
238         final Vector2D dir = Vector2D.Unit.PLUS_X;
239         final Line line = Lines.fromPointAndDirection(pt, dir, TEST_PRECISION);
240 
241         // act
242         final Line reversed = line.reverse();
243         final Line doubleReversed = reversed.reverse();
244 
245         // assert
246         checkLine(reversed, pt, dir.negate());
247         Assertions.assertEquals(-1, reversed.getOriginOffset(), TEST_EPS);
248 
249         checkLine(doubleReversed, pt, dir);
250         Assertions.assertEquals(1, doubleReversed.getOriginOffset(), TEST_EPS);
251     }
252 
253     @Test
254     void testAbscissa() {
255         // arrange
256         final Line line = Lines.fromPoints(Vector2D.of(-2, -2), Vector2D.of(2, 1), TEST_PRECISION);
257 
258         // act/assert
259         Assertions.assertEquals(0.0, line.abscissa(Vector2D.of(-3, 4)), TEST_EPS);
260         Assertions.assertEquals(0.0, line.abscissa(Vector2D.of(3, -4)), TEST_EPS);
261         Assertions.assertEquals(5.0, line.abscissa(Vector2D.of(7, -1)), TEST_EPS);
262         Assertions.assertEquals(-5.0, line.abscissa(Vector2D.of(-1, -7)), TEST_EPS);
263     }
264 
265     @Test
266     void testToSubspace() {
267         // arrange
268         final Line line = Lines.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
269 
270         // act/assert
271         Assertions.assertEquals(0.0, line.toSubspace(Vector2D.of(-3, 4)).getX(), TEST_EPS);
272         Assertions.assertEquals(0.0, line.toSubspace(Vector2D.of(3, -4)).getX(), TEST_EPS);
273         Assertions.assertEquals(-5.0, line.toSubspace(Vector2D.of(7, -1)).getX(), TEST_EPS);
274         Assertions.assertEquals(5.0, line.toSubspace(Vector2D.of(-1, -7)).getX(), TEST_EPS);
275     }
276 
277     @Test
278     void testToSpace_throughOrigin() {
279         // arrange
280         final double invSqrt2 = 1 / Math.sqrt(2);
281         final Vector2D dir = Vector2D.of(invSqrt2, invSqrt2);
282 
283         final Line line = Lines.fromPoints(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION);
284 
285         // act/assert
286         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, line.toSpace(Vector1D.of(0)), TEST_EPS);
287 
288         for (int i = 0; i < 100; ++i) {
289             EuclideanTestUtils.assertCoordinatesEqual(dir.multiply(i), line.toSpace(Vector1D.of(i)), TEST_EPS);
290             EuclideanTestUtils.assertCoordinatesEqual(dir.multiply(-i), line.toSpace(Vector1D.of(-i)), TEST_EPS);
291         }
292     }
293 
294     @Test
295     void testToSpace_offsetFromOrigin() {
296         // arrange
297         final double angle = Math.PI / 6;
298         final double cos = Math.cos(angle);
299         final double sin = Math.sin(angle);
300         final Vector2D pt = Vector2D.of(-5, 0);
301 
302         final double h = Math.abs(pt.getX()) * cos;
303         final double d = h * cos;
304         final Vector2D origin = Vector2D.of(
305                     pt.getX() + d,
306                     h * sin
307                 );
308         final Vector2D dir = Vector2D.of(cos, sin);
309 
310         final Line line = Lines.fromPointAndAngle(pt, angle, TEST_PRECISION);
311 
312         // act/assert
313         EuclideanTestUtils.assertCoordinatesEqual(origin, line.toSpace(Vector1D.of(0)), TEST_EPS);
314 
315         for (int i = 0; i < 100; ++i) {
316             EuclideanTestUtils.assertCoordinatesEqual(origin.add(dir.multiply(i)), line.toSpace(Vector1D.of(i)), TEST_EPS);
317             EuclideanTestUtils.assertCoordinatesEqual(origin.add(dir.multiply(-i)), line.toSpace(Vector1D.of(-i)), TEST_EPS);
318         }
319     }
320 
321     @Test
322     void testIntersection() {
323         // arrange
324         final Line a = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
325         final Line b = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
326         final Line c = Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
327         final Line d = Lines.fromPointAndDirection(Vector2D.of(0, -1), Vector2D.of(2, -1), TEST_PRECISION);
328 
329         // act/assert
330         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, a.intersection(b), TEST_EPS);
331         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, b.intersection(a), TEST_EPS);
332 
333         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 0), a.intersection(c), TEST_EPS);
334         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 0), c.intersection(a), TEST_EPS);
335 
336         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), a.intersection(d), TEST_EPS);
337         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), d.intersection(a), TEST_EPS);
338 
339         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), b.intersection(c), TEST_EPS);
340         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), c.intersection(b), TEST_EPS);
341 
342         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -1), b.intersection(d), TEST_EPS);
343         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -1), d.intersection(b), TEST_EPS);
344 
345         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0.5), c.intersection(d), TEST_EPS);
346         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0.5), d.intersection(c), TEST_EPS);
347     }
348 
349     @Test
350     void testIntersection_parallel() {
351         // arrange
352         final Line a = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
353         final Line b = Lines.fromPointAndDirection(Vector2D.of(0, 1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
354 
355         final Line c = Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
356         final Line d = Lines.fromPointAndDirection(Vector2D.of(0, -1), Vector2D.of(2, 1), TEST_PRECISION);
357 
358         // act/assert
359         Assertions.assertNull(a.intersection(b));
360         Assertions.assertNull(b.intersection(a));
361 
362         Assertions.assertNull(c.intersection(d));
363         Assertions.assertNull(d.intersection(c));
364     }
365 
366     @Test
367     void testIntersection_coincident() {
368         // arrange
369         final Line a = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
370         final Line b = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
371 
372         final Line c = Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
373         final Line d = Lines.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
374 
375         // act/assert
376         Assertions.assertNull(a.intersection(b));
377         Assertions.assertNull(b.intersection(a));
378 
379         Assertions.assertNull(c.intersection(d));
380         Assertions.assertNull(d.intersection(c));
381     }
382 
383     @Test
384     void testAngle() {
385         // arrange
386         final Line a = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
387         final Line b = Lines.fromPointAndAngle(Vector2D.of(1, 4), Math.PI, TEST_PRECISION);
388         final Line c = Lines.fromPointAndDirection(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION);
389 
390         // act/assert
391         Assertions.assertEquals(0.0, a.angle(a), TEST_EPS);
392         Assertions.assertEquals(-Math.PI, a.angle(b), TEST_EPS);
393         Assertions.assertEquals(0.25 * Math.PI, a.angle(c), TEST_EPS);
394 
395         Assertions.assertEquals(0.0, b.angle(b), TEST_EPS);
396         Assertions.assertEquals(-Math.PI, b.angle(a), TEST_EPS);
397         Assertions.assertEquals(-0.75 * Math.PI, b.angle(c), TEST_EPS);
398 
399         Assertions.assertEquals(0.0, c.angle(c), TEST_EPS);
400         Assertions.assertEquals(-0.25 * Math.PI, c.angle(a), TEST_EPS);
401         Assertions.assertEquals(0.75 * Math.PI, c.angle(b), TEST_EPS);
402     }
403 
404     @Test
405     void testProject() {
406         // --- arrange
407         final Line xAxis = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
408         final Line yAxis = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
409 
410         final double diagonalYIntercept = 1;
411         final Vector2D diagonalDir = Vector2D.of(1, 2);
412         final Line diagonal = Lines.fromPointAndDirection(Vector2D.of(0, diagonalYIntercept), diagonalDir, TEST_PRECISION);
413 
414         EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
415             final Vector2D pt = Vector2D.of(x, y);
416 
417             // --- act/assert
418             EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(x, 0), xAxis.project(pt), TEST_EPS);
419             EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, y), yAxis.project(pt), TEST_EPS);
420 
421             final Vector2D diagonalPt = diagonal.project(pt);
422             Assertions.assertTrue(diagonal.contains(diagonalPt));
423             Assertions.assertEquals(diagonal.distance(pt), pt.distance(diagonalPt), TEST_EPS);
424 
425             // check that y = mx + b is true
426             Assertions.assertEquals(diagonalPt.getY(),
427                     (diagonalDir.getY() * diagonalPt.getX() / diagonalDir.getX()) + diagonalYIntercept, TEST_EPS);
428         });
429     }
430 
431     @Test
432     void testSpan() {
433         // arrange
434         final Line line = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
435 
436         // act
437         final LineConvexSubset result = line.span();
438 
439         // assert
440         Assertions.assertSame(line, result.getHyperplane());
441         Assertions.assertSame(line, result.getLine());
442     }
443 
444     @Test
445     void testSegment_doubles() {
446         // arrange
447         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
448 
449         // act
450         final Segment segment = line.segment(1, 2);
451 
452         // assert
453         Assertions.assertSame(line, segment.getLine());
454         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segment.getStartPoint(), TEST_EPS);
455         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getEndPoint(), TEST_EPS);
456     }
457 
458     @Test
459     void testSegment_pointsOnLine() {
460         // arrange
461         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
462 
463         // act
464         final Segment segment = line.segment(Vector2D.of(3, 1), Vector2D.of(2, 1));
465 
466         // assert
467         Assertions.assertSame(line, segment.getLine());
468         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getStartPoint(), TEST_EPS);
469         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 1), segment.getEndPoint(), TEST_EPS);
470     }
471 
472     @Test
473     void testSegment_pointsProjectedOnLine() {
474         // arrange
475         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
476 
477         // act
478         final Segment segment = line.segment(Vector2D.of(-3, 2), Vector2D.of(2, -1));
479 
480         // assert
481         Assertions.assertSame(line, segment.getLine());
482         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), segment.getStartPoint(), TEST_EPS);
483         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getEndPoint(), TEST_EPS);
484     }
485 
486     @Test
487     void testLineTo_pointOnLine() {
488         // arrange
489         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION);
490 
491         // act
492         final ReverseRay halfLine = line.reverseRayTo(Vector2D.of(-3, 1));
493 
494         // assert
495         Assertions.assertSame(line, halfLine.getLine());
496         Assertions.assertTrue(halfLine.isInfinite());
497         Assertions.assertNull(halfLine.getStartPoint());
498         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), halfLine.getEndPoint(), TEST_EPS);
499 
500         Assertions.assertTrue(halfLine.contains(Vector2D.of(1, 1)));
501         Assertions.assertFalse(halfLine.contains(Vector2D.of(-4, 1)));
502     }
503 
504     @Test
505     void testLineTo_pointProjectedOnLine() {
506         // arrange
507         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION);
508 
509         // act
510         final ReverseRay halfLine = line.reverseRayTo(Vector2D.of(-3, 5));
511 
512         // assert
513         Assertions.assertSame(line, halfLine.getLine());
514         Assertions.assertTrue(halfLine.isInfinite());
515         Assertions.assertNull(halfLine.getStartPoint());
516         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), halfLine.getEndPoint(), TEST_EPS);
517 
518         Assertions.assertTrue(halfLine.contains(Vector2D.of(1, 1)));
519         Assertions.assertFalse(halfLine.contains(Vector2D.of(-4, 1)));
520     }
521 
522     @Test
523     void testLineTo_double() {
524         // arrange
525         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION);
526 
527         // act
528         final ReverseRay halfLine = line.reverseRayTo(-1);
529 
530         // assert
531         Assertions.assertSame(line, halfLine.getLine());
532         Assertions.assertTrue(halfLine.isInfinite());
533         Assertions.assertNull(halfLine.getStartPoint());
534         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), halfLine.getEndPoint(), TEST_EPS);
535 
536         Assertions.assertTrue(halfLine.contains(Vector2D.of(2, 1)));
537         Assertions.assertFalse(halfLine.contains(Vector2D.of(-4, 1)));
538     }
539 
540     @Test
541     void testRayFrom_pointOnLine() {
542         // arrange
543         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION);
544 
545         // act
546         final Ray ray = line.rayFrom(Vector2D.of(-3, 1));
547 
548         // assert
549         Assertions.assertSame(line, ray.getLine());
550         Assertions.assertTrue(ray.isInfinite());
551         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), ray.getStartPoint(), TEST_EPS);
552         Assertions.assertNull(ray.getEndPoint());
553 
554         Assertions.assertFalse(ray.contains(Vector2D.of(1, 1)));
555         Assertions.assertTrue(ray.contains(Vector2D.of(-4, 1)));
556     }
557 
558     @Test
559     void testRayFrom_pointProjectedOnLine() {
560         // arrange
561         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION);
562 
563         // act
564         final Ray ray = line.rayFrom(Vector2D.of(-3, 5));
565 
566         // assert
567         Assertions.assertSame(line, ray.getLine());
568         Assertions.assertTrue(ray.isInfinite());
569         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), ray.getStartPoint(), TEST_EPS);
570         Assertions.assertNull(ray.getEndPoint());
571 
572         Assertions.assertFalse(ray.contains(Vector2D.of(1, 1)));
573         Assertions.assertTrue(ray.contains(Vector2D.of(-4, 1)));
574     }
575 
576     @Test
577     void testRayFrom_double() {
578         // arrange
579         final Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION);
580 
581         // act
582         final Ray ray = line.rayFrom(-1);
583 
584         // assert
585         Assertions.assertSame(line, ray.getLine());
586         Assertions.assertTrue(ray.isInfinite());
587         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), ray.getStartPoint(), TEST_EPS);
588         Assertions.assertNull(ray.getEndPoint());
589 
590         Assertions.assertFalse(ray.contains(Vector2D.of(2, 1)));
591         Assertions.assertTrue(ray.contains(Vector2D.of(-4, 1)));
592     }
593 
594     @Test
595     void testOffset_parallelLines() {
596         // arrange
597         final double dist = Math.sin(Math.atan2(2, 1));
598 
599         final Line a = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
600         final Line b = Lines.fromPoints(Vector2D.of(-3, 0), Vector2D.of(0, 6), TEST_PRECISION);
601         final Line c = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
602         final Line d = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, -2), TEST_PRECISION);
603 
604         // act/assert
605         Assertions.assertEquals(-dist, a.offset(b), TEST_EPS);
606         Assertions.assertEquals(dist, b.offset(a), TEST_EPS);
607 
608         Assertions.assertEquals(dist, a.offset(c), TEST_EPS);
609         Assertions.assertEquals(-dist, c.offset(a), TEST_EPS);
610 
611         Assertions.assertEquals(3 * dist, a.offset(d), TEST_EPS);
612         Assertions.assertEquals(3 * dist, d.offset(a), TEST_EPS);
613     }
614 
615     @Test
616     void testOffset_coincidentLines() {
617         // arrange
618         final Line a = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
619         final Line b = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
620         final Line c = b.reverse();
621 
622         // act/assert
623         Assertions.assertEquals(0, a.offset(a), TEST_EPS);
624 
625         Assertions.assertEquals(0, a.offset(b), TEST_EPS);
626         Assertions.assertEquals(0, b.offset(a), TEST_EPS);
627 
628         Assertions.assertEquals(0, a.offset(c), TEST_EPS);
629         Assertions.assertEquals(0, c.offset(a), TEST_EPS);
630     }
631 
632     @Test
633     void testOffset_nonParallelLines() {
634         // arrange
635         final Line a = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
636         final Line b = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
637         final Line c = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
638         final Line d = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, 4), TEST_PRECISION);
639 
640         // act/assert
641         Assertions.assertEquals(0, a.offset(b), TEST_EPS);
642         Assertions.assertEquals(0, b.offset(a), TEST_EPS);
643 
644         Assertions.assertEquals(0, a.offset(c), TEST_EPS);
645         Assertions.assertEquals(0, c.offset(a), TEST_EPS);
646 
647         Assertions.assertEquals(0, a.offset(d), TEST_EPS);
648         Assertions.assertEquals(0, d.offset(a), TEST_EPS);
649     }
650 
651     @Test
652     void testOffset_point() {
653         // arrange
654         final Line line = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
655         final Line reversed = line.reverse();
656 
657         // act/assert
658         Assertions.assertEquals(0.0, line.offset(Vector2D.of(-0.5, 1)), TEST_EPS);
659         Assertions.assertEquals(0.0, line.offset(Vector2D.of(-1.5, -1)), TEST_EPS);
660         Assertions.assertEquals(0.0, line.offset(Vector2D.of(0.5, 3)), TEST_EPS);
661 
662         final double d = Math.sin(Math.atan2(2, 1));
663 
664         Assertions.assertEquals(d, line.offset(Vector2D.ZERO), TEST_EPS);
665         Assertions.assertEquals(-d, line.offset(Vector2D.of(-1, 2)), TEST_EPS);
666 
667         Assertions.assertEquals(-d, reversed.offset(Vector2D.ZERO), TEST_EPS);
668         Assertions.assertEquals(d, reversed.offset(Vector2D.of(-1, 2)), TEST_EPS);
669     }
670 
671     @Test
672     void testOffset_point_permute() {
673         // arrange
674         final Line line = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
675         final Vector2D lineOrigin = line.getOrigin();
676 
677         EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
678             final Vector2D pt = Vector2D.of(x, y);
679 
680             // act
681             final double offset = line.offset(pt);
682 
683             // arrange
684             final Vector2D vec = lineOrigin.vectorTo(pt).reject(line.getDirection());
685             final double dot = vec.dot(line.getOffsetDirection());
686             final double expected = Math.signum(dot) * vec.norm();
687 
688             Assertions.assertEquals(expected, offset, TEST_EPS);
689         });
690     }
691 
692     @Test
693     void testSimilarOrientation() {
694         // arrange
695         final Line a = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
696         final Line b = Lines.fromPointAndAngle(Vector2D.of(4, 5), 0.0, TEST_PRECISION);
697         final Line c = Lines.fromPointAndAngle(Vector2D.of(-1, -3), 0.4 * Math.PI, TEST_PRECISION);
698         final Line d = Lines.fromPointAndAngle(Vector2D.of(1, 0), -0.4 * Math.PI, TEST_PRECISION);
699 
700         final Line e = Lines.fromPointAndAngle(Vector2D.of(6, -3), Math.PI, TEST_PRECISION);
701         final Line f = Lines.fromPointAndAngle(Vector2D.of(8, 5), 0.8 * Math.PI, TEST_PRECISION);
702         final Line g = Lines.fromPointAndAngle(Vector2D.of(6, -3), -0.8 * Math.PI, TEST_PRECISION);
703 
704         // act/assert
705         Assertions.assertTrue(a.similarOrientation(a));
706         Assertions.assertTrue(a.similarOrientation(b));
707         Assertions.assertTrue(b.similarOrientation(a));
708         Assertions.assertTrue(a.similarOrientation(c));
709         Assertions.assertTrue(c.similarOrientation(a));
710         Assertions.assertTrue(a.similarOrientation(d));
711         Assertions.assertTrue(d.similarOrientation(a));
712 
713         Assertions.assertFalse(c.similarOrientation(d));
714         Assertions.assertFalse(d.similarOrientation(c));
715 
716         Assertions.assertTrue(e.similarOrientation(f));
717         Assertions.assertTrue(f.similarOrientation(e));
718         Assertions.assertTrue(e.similarOrientation(g));
719         Assertions.assertTrue(g.similarOrientation(e));
720 
721         Assertions.assertFalse(a.similarOrientation(e));
722         Assertions.assertFalse(e.similarOrientation(a));
723     }
724 
725     @Test
726     void testSimilarOrientation_orthogonal() {
727         // arrange
728         final Line a = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
729         final Line b = Lines.fromPointAndDirection(Vector2D.of(4, 5), Vector2D.Unit.PLUS_Y, TEST_PRECISION);
730         final Line c = Lines.fromPointAndDirection(Vector2D.of(-4, -5), Vector2D.Unit.MINUS_Y, TEST_PRECISION);
731 
732         // act/assert
733         Assertions.assertTrue(a.similarOrientation(b));
734         Assertions.assertTrue(b.similarOrientation(a));
735         Assertions.assertTrue(a.similarOrientation(c));
736         Assertions.assertTrue(c.similarOrientation(a));
737     }
738 
739     @Test
740     void testDistance_parallelLines() {
741         // arrange
742         final double dist = Math.sin(Math.atan2(2, 1));
743 
744         final Line a = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
745         final Line b = Lines.fromPoints(Vector2D.of(-3, 0), Vector2D.of(0, 6), TEST_PRECISION);
746         final Line c = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
747         final Line d = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, -2), TEST_PRECISION);
748 
749         // act/assert
750         Assertions.assertEquals(dist, a.distance(b), TEST_EPS);
751         Assertions.assertEquals(dist, b.distance(a), TEST_EPS);
752 
753         Assertions.assertEquals(dist, a.distance(c), TEST_EPS);
754         Assertions.assertEquals(dist, c.distance(a), TEST_EPS);
755 
756         Assertions.assertEquals(3 * dist, a.distance(d), TEST_EPS);
757         Assertions.assertEquals(3 * dist, d.distance(a), TEST_EPS);
758     }
759 
760     @Test
761     void testDistance_coincidentLines() {
762         // arrange
763         final Line a = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
764         final Line b = Lines.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
765         final Line c = b.reverse();
766 
767         // act/assert
768         Assertions.assertEquals(0, a.distance(a), TEST_EPS);
769 
770         Assertions.assertEquals(0, a.distance(b), TEST_EPS);
771         Assertions.assertEquals(0, b.distance(a), TEST_EPS);
772 
773         Assertions.assertEquals(0, a.distance(c), TEST_EPS);
774         Assertions.assertEquals(0, c.distance(a), TEST_EPS);
775     }
776 
777     @Test
778     void testDistance_nonParallelLines() {
779         // arrange
780         final Line a = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
781         final Line b = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
782         final Line c = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
783         final Line d = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, 4), TEST_PRECISION);
784 
785         // act/assert
786         Assertions.assertEquals(0, a.distance(b), TEST_EPS);
787         Assertions.assertEquals(0, b.distance(a), TEST_EPS);
788 
789         Assertions.assertEquals(0, a.distance(c), TEST_EPS);
790         Assertions.assertEquals(0, c.distance(a), TEST_EPS);
791 
792         Assertions.assertEquals(0, a.distance(d), TEST_EPS);
793         Assertions.assertEquals(0, d.distance(a), TEST_EPS);
794     }
795 
796     @Test
797     void testDistance() {
798         // arrange
799         final Line line = Lines.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
800 
801         // act/assert
802         Assertions.assertEquals(0, line.distance(line.getOrigin()), TEST_EPS);
803         Assertions.assertEquals(+5.0, line.distance(Vector2D.of(5, -3)), TEST_EPS);
804         Assertions.assertEquals(+5.0, line.distance(Vector2D.of(-5, 2)), TEST_EPS);
805     }
806 
807     @Test
808     void testPointAt() {
809         // arrange
810         final Vector2D origin = Vector2D.of(-1, 1);
811         final double d = Math.sqrt(2);
812         final Line line = Lines.fromPointAndDirection(origin, Vector2D.of(1, 1), TEST_PRECISION);
813 
814         // act/assert
815         EuclideanTestUtils.assertCoordinatesEqual(origin, line.pointAt(0, 0), TEST_EPS);
816         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, line.pointAt(0, d), TEST_EPS);
817         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 2), line.pointAt(0, -d), TEST_EPS);
818 
819         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), line.pointAt(-d, 0), TEST_EPS);
820         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), line.pointAt(d, 0), TEST_EPS);
821 
822         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), line.pointAt(d, d), TEST_EPS);
823         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), line.pointAt(-d, -d), TEST_EPS);
824     }
825 
826     @Test
827     void testPointAt_abscissaOffsetRoundtrip() {
828         // arrange
829         final Line line = Lines.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
830 
831         for (double abscissa = -2.0; abscissa < 2.0; abscissa += 0.2) {
832             for (double offset = -2.0; offset < 2.0; offset += 0.2) {
833 
834                 // act
835                 final Vector2D point = line.pointAt(abscissa, offset);
836 
837                 // assert
838                 Assertions.assertEquals(abscissa, line.toSubspace(point).getX(), TEST_EPS);
839                 Assertions.assertEquals(offset, line.offset(point), TEST_EPS);
840             }
841         }
842     }
843 
844     @Test
845     void testContains_line() {
846         // arrange
847         final Vector2D pt = Vector2D.of(1, 2);
848         final Vector2D dir = Vector2D.of(3, 7);
849         final Line a = Lines.fromPointAndDirection(pt, dir, TEST_PRECISION);
850         final Line b = Lines.fromPointAndDirection(Vector2D.of(0, -4), dir, TEST_PRECISION);
851         final Line c = Lines.fromPointAndDirection(Vector2D.of(-2, -2), dir.negate(), TEST_PRECISION);
852         final Line d = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
853 
854         final Line e = Lines.fromPointAndDirection(pt, dir, TEST_PRECISION);
855         final Line f = Lines.fromPointAndDirection(pt, dir.negate(), TEST_PRECISION);
856 
857         // act/assert
858         Assertions.assertTrue(a.contains(a));
859 
860         Assertions.assertTrue(a.contains(e));
861         Assertions.assertTrue(e.contains(a));
862 
863         Assertions.assertTrue(a.contains(f));
864         Assertions.assertTrue(f.contains(a));
865 
866         Assertions.assertFalse(a.contains(b));
867         Assertions.assertFalse(a.contains(c));
868         Assertions.assertFalse(a.contains(d));
869     }
870 
871     @Test
872     void testIsParallel_closeToEpsilon() {
873         // arrange
874         final double eps = 1e-3;
875         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(eps);
876 
877         final Vector2D p = Vector2D.of(1, 2);
878 
879         final Line line = Lines.fromPointAndAngle(p, 0.0, precision);
880 
881         // act/assert
882         final Vector2D offset1 = Vector2D.of(0, 1e-4);
883         final Vector2D offset2 = Vector2D.of(0, 2e-3);
884 
885         Assertions.assertTrue(line.contains(Lines.fromPointAndAngle(p.add(offset1), 0.0, precision)));
886         Assertions.assertTrue(line.contains(Lines.fromPointAndAngle(p.subtract(offset1), 0.0, precision)));
887 
888         Assertions.assertFalse(line.contains(Lines.fromPointAndAngle(p.add(offset2), 0.0, precision)));
889         Assertions.assertFalse(line.contains(Lines.fromPointAndAngle(p.subtract(offset2), 0.0, precision)));
890 
891         Assertions.assertTrue(line.contains(Lines.fromPointAndAngle(p, 1e-4, precision)));
892         Assertions.assertFalse(line.contains(Lines.fromPointAndAngle(p, 1e-2, precision)));
893     }
894 
895     @Test
896     void testContains_point() {
897         // arrange
898         final Vector2D p1 = Vector2D.of(-1, 0);
899         final Vector2D p2 = Vector2D.of(0, 2);
900         final Line line = Lines.fromPoints(p1, p2, TEST_PRECISION);
901 
902         // act/assert
903         Assertions.assertTrue(line.contains(p1));
904         Assertions.assertTrue(line.contains(p2));
905 
906         Assertions.assertFalse(line.contains(Vector2D.ZERO));
907         Assertions.assertFalse(line.contains(Vector2D.of(100, 79)));
908 
909         final Vector2D offset1 = Vector2D.of(0.1, 0);
910         final Vector2D offset2 = Vector2D.of(0, -0.1);
911         Vector2D v;
912         for (double t = -2; t <= 2; t += 0.1) {
913             v = p1.lerp(p2, t);
914 
915             Assertions.assertTrue(line.contains(v));
916 
917             Assertions.assertFalse(line.contains(v.add(offset1)));
918             Assertions.assertFalse(line.contains(v.add(offset2)));
919         }
920     }
921 
922     @Test
923     void testContains_point_closeToEpsilon() {
924         // arrange
925         final double eps = 1e-3;
926         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(eps);
927 
928         final Vector2D p1 = Vector2D.of(-1, 0);
929         final Vector2D p2 = Vector2D.of(0, 2);
930         final Vector2D mid = p1.lerp(p2, 0.5);
931 
932         final Line line = Lines.fromPoints(p1, p2, precision);
933         final Vector2D dir = line.getOffsetDirection();
934 
935         // act/assert
936         Assertions.assertTrue(line.contains(mid.add(dir.multiply(1e-4))));
937         Assertions.assertTrue(line.contains(mid.add(dir.multiply(-1e-4))));
938 
939         Assertions.assertFalse(line.contains(mid.add(dir.multiply(2e-3))));
940         Assertions.assertFalse(line.contains(mid.add(dir.multiply(-2e-3))));
941     }
942 
943     @Test
944     void testDistance_point() {
945         // arrange
946         final Line line = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
947         final Line reversed = line.reverse();
948 
949         // act/assert
950         Assertions.assertEquals(0.0, line.distance(Vector2D.of(-0.5, 1)), TEST_EPS);
951         Assertions.assertEquals(0.0, line.distance(Vector2D.of(-1.5, -1)), TEST_EPS);
952         Assertions.assertEquals(0.0, line.distance(Vector2D.of(0.5, 3)), TEST_EPS);
953 
954         final double d = Math.sin(Math.atan2(2, 1));
955 
956         Assertions.assertEquals(d, line.distance(Vector2D.ZERO), TEST_EPS);
957         Assertions.assertEquals(d, line.distance(Vector2D.of(-1, 2)), TEST_EPS);
958 
959         Assertions.assertEquals(d, reversed.distance(Vector2D.ZERO), TEST_EPS);
960         Assertions.assertEquals(d, reversed.distance(Vector2D.of(-1, 2)), TEST_EPS);
961     }
962 
963     @Test
964     void testDistance_point_permute() {
965         // arrange
966         final Line line = Lines.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
967         final Vector2D lineOrigin = line.getOrigin();
968 
969         EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
970             final Vector2D pt = Vector2D.of(x, y);
971 
972             // act
973             final double dist = line.distance(pt);
974 
975             // arrange
976             final Vector2D vec = lineOrigin.vectorTo(pt).reject(line.getDirection());
977             Assertions.assertEquals(vec.norm(), dist, TEST_EPS);
978         });
979     }
980 
981     @Test
982     void testIsParallel() {
983         // arrange
984         final Vector2D dir = Vector2D.of(3, 7);
985         final Line a = Lines.fromPointAndDirection(Vector2D.of(1, 2), dir, TEST_PRECISION);
986         final Line b = Lines.fromPointAndDirection(Vector2D.of(0, -4), dir, TEST_PRECISION);
987         final Line c = Lines.fromPointAndDirection(Vector2D.of(-2, -2), dir.negate(), TEST_PRECISION);
988         final Line d = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
989 
990         // act/assert
991         Assertions.assertTrue(a.isParallel(a));
992 
993         Assertions.assertTrue(a.isParallel(b));
994         Assertions.assertTrue(b.isParallel(a));
995 
996         Assertions.assertTrue(a.isParallel(c));
997         Assertions.assertTrue(c.isParallel(a));
998 
999         Assertions.assertFalse(a.isParallel(d));
1000         Assertions.assertFalse(d.isParallel(a));
1001     }
1002 
1003     @Test
1004     void testIsParallel_closeToParallel() {
1005         // arrange
1006         final double eps = 1e-3;
1007         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(eps);
1008 
1009         final Vector2D p1 = Vector2D.of(1, 2);
1010         final Vector2D p2 = Vector2D.of(1, -2);
1011 
1012         final Line line = Lines.fromPointAndAngle(p1, 0.0, precision);
1013 
1014         // act/assert
1015         Assertions.assertTrue(line.isParallel(Lines.fromPointAndAngle(p2, 1e-4, precision)));
1016         Assertions.assertFalse(line.isParallel(Lines.fromPointAndAngle(p2, 1e-2, precision)));
1017     }
1018 
1019     @Test
1020     void testTransform() {
1021         // arrange
1022         final AffineTransformMatrix2D scale = AffineTransformMatrix2D.createScale(2, 3);
1023         final AffineTransformMatrix2D reflect = AffineTransformMatrix2D.createScale(-1, 1);
1024         final AffineTransformMatrix2D translate = AffineTransformMatrix2D.createTranslation(3, 4);
1025         final AffineTransformMatrix2D rotate = AffineTransformMatrix2D.createRotation(Angle.PI_OVER_TWO);
1026         final AffineTransformMatrix2D rotateAroundPt = AffineTransformMatrix2D.createRotation(Vector2D.of(0, 1), Angle.PI_OVER_TWO);
1027 
1028         final Vector2D p1 = Vector2D.of(0, 1);
1029         final Vector2D p2 = Vector2D.of(1, 0);
1030 
1031         final Line horizontal = Lines.fromPointAndDirection(p1, Vector2D.Unit.PLUS_X, TEST_PRECISION);
1032         final Line vertical = Lines.fromPointAndDirection(p2, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
1033         final Line diagonal = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION);
1034 
1035         // act/assert
1036         Assertions.assertSame(TEST_PRECISION, horizontal.transform(scale).getPrecision());
1037 
1038         checkLine(horizontal.transform(scale), Vector2D.of(0, 3), Vector2D.Unit.PLUS_X);
1039         checkLine(vertical.transform(scale), Vector2D.of(2, 0), Vector2D.Unit.PLUS_Y);
1040         checkLine(diagonal.transform(scale), Vector2D.ZERO, Vector2D.of(2, 3).normalize());
1041 
1042         checkLine(horizontal.transform(reflect), p1, Vector2D.Unit.MINUS_X);
1043         checkLine(vertical.transform(reflect), Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y);
1044         checkLine(diagonal.transform(reflect), Vector2D.ZERO, Vector2D.of(-1, 1).normalize());
1045 
1046         checkLine(horizontal.transform(translate), Vector2D.of(0, 5), Vector2D.Unit.PLUS_X);
1047         checkLine(vertical.transform(translate), Vector2D.of(4, 0), Vector2D.Unit.PLUS_Y);
1048         checkLine(diagonal.transform(translate), Vector2D.of(-0.5, 0.5), Vector2D.of(1, 1).normalize());
1049 
1050         checkLine(horizontal.transform(rotate), Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y);
1051         checkLine(vertical.transform(rotate), Vector2D.of(0, 1), Vector2D.Unit.MINUS_X);
1052         checkLine(diagonal.transform(rotate), Vector2D.ZERO, Vector2D.of(-1, 1).normalize());
1053 
1054         checkLine(horizontal.transform(rotateAroundPt), Vector2D.ZERO, Vector2D.Unit.PLUS_Y);
1055         checkLine(vertical.transform(rotateAroundPt), Vector2D.of(0, 2), Vector2D.Unit.MINUS_X);
1056         checkLine(diagonal.transform(rotateAroundPt), Vector2D.of(1, 1), Vector2D.of(-1, 1).normalize());
1057     }
1058 
1059     @Test
1060     void testTransform_collapsedPoints() {
1061         // arrange
1062         final AffineTransformMatrix2D scaleCollapse = AffineTransformMatrix2D.createScale(0, 1);
1063         final Line line = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
1064 
1065         // act/assert
1066         GeometryTestUtils.assertThrowsWithMessage(() -> {
1067             line.transform(scaleCollapse);
1068         }, IllegalArgumentException.class, "Line direction cannot be zero");
1069     }
1070 
1071     @Test
1072     void testSubspaceTransform() {
1073         // arrange
1074         final Line line = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION);
1075 
1076         // act/assert
1077         checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createScale(2, 3)),
1078                 Vector2D.of(2, 0), Vector2D.Unit.PLUS_Y,
1079                 Vector2D.of(2, 0), Vector2D.of(2, 3));
1080 
1081         checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createTranslation(2, 3)),
1082                 Vector2D.of(3, 0), Vector2D.Unit.PLUS_Y,
1083                 Vector2D.of(3, 3), Vector2D.of(3, 4));
1084 
1085         checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createRotation(Angle.PI_OVER_TWO)),
1086                 Vector2D.of(0, 1), Vector2D.Unit.MINUS_X,
1087                 Vector2D.of(0, 1), Vector2D.of(-1, 1));
1088     }
1089 
1090     private void checkSubspaceTransform(final SubspaceTransform st, final Vector2D origin, final Vector2D dir, final Vector2D tZero, final Vector2D tOne) {
1091 
1092         final Line line = st.getLine();
1093         final AffineTransformMatrix1D transform = st.getTransform();
1094 
1095         checkLine(line, origin, dir);
1096 
1097         EuclideanTestUtils.assertCoordinatesEqual(tZero, line.toSpace(transform.apply(Vector1D.ZERO)), TEST_EPS);
1098         EuclideanTestUtils.assertCoordinatesEqual(tOne, line.toSpace(transform.apply(Vector1D.Unit.PLUS)), TEST_EPS);
1099     }
1100 
1101     @Test
1102     void testSubspaceTransform_transformsPointsCorrectly() {
1103         // arrange
1104         final Line line = Lines.fromPointAndDirection(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION);
1105 
1106         EuclideanTestUtils.permuteSkipZero(-2, 2, 0.5, (a, b) -> {
1107             // create a somewhat complicate transform to try to hit all of the edge cases
1108             final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(Vector2D.of(a, b))
1109                     .rotate(a * b)
1110                     .scale(0.1, 4);
1111 
1112             // act
1113             final SubspaceTransform st = line.subspaceTransform(transform);
1114 
1115             // assert
1116             for (double x = -5.0; x <= 5.0; x += 1) {
1117                 final Vector1D subPt = Vector1D.of(x);
1118                 final Vector2D expected = transform.apply(line.toSpace(subPt));
1119                 final Vector2D actual = st.getLine().toSpace(
1120                         st.getTransform().apply(subPt));
1121 
1122                 EuclideanTestUtils.assertCoordinatesEqual(expected, actual, TEST_EPS);
1123             }
1124         });
1125     }
1126 
1127     @Test
1128     void testEq() {
1129         // arrange
1130         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-3);
1131 
1132         final Vector2D p = Vector2D.of(1, 2);
1133         final double angle = 1.0;
1134 
1135         final Line a = Lines.fromPointAndAngle(p, angle, precision);
1136         final Line b = Lines.fromPointAndAngle(Vector2D.ZERO, angle, precision);
1137         final Line c = Lines.fromPointAndAngle(p, angle + 1.0, precision);
1138 
1139         final Line d = Lines.fromPointAndAngle(p, angle, precision);
1140         final Line e = Lines.fromPointAndAngle(p.add(Vector2D.of(1e-4, 1e-4)), angle, precision);
1141         final Line f = Lines.fromPointAndAngle(p, angle + 1e-4, precision);
1142 
1143         // act/assert
1144         Assertions.assertTrue(a.eq(a, precision));
1145 
1146         Assertions.assertTrue(a.eq(d, precision));
1147         Assertions.assertTrue(d.eq(a, precision));
1148 
1149         Assertions.assertTrue(a.eq(e, precision));
1150         Assertions.assertTrue(e.eq(a, precision));
1151 
1152         Assertions.assertTrue(a.eq(f, precision));
1153         Assertions.assertTrue(f.eq(a, precision));
1154 
1155         Assertions.assertFalse(a.eq(b, precision));
1156         Assertions.assertFalse(a.eq(c, precision));
1157     }
1158 
1159     @Test
1160     void testHashCode() {
1161         // arrange
1162         final Precision.DoubleEquivalence precision1 = Precision.doubleEquivalenceOfEpsilon(1e-4);
1163         final Precision.DoubleEquivalence precision2 = Precision.doubleEquivalenceOfEpsilon(1e-5);
1164 
1165         final Vector2D p = Vector2D.of(1, 2);
1166         final Vector2D v = Vector2D.of(1, 1);
1167 
1168         final Line a = Lines.fromPointAndDirection(p, v, precision1);
1169         final Line b = Lines.fromPointAndDirection(Vector2D.ZERO, v, precision1);
1170         final Line c = Lines.fromPointAndDirection(p, v.negate(), precision1);
1171         final Line d = Lines.fromPointAndDirection(p, v, precision2);
1172         final Line e = Lines.fromPointAndDirection(p, v, precision1);
1173 
1174         // act/assert
1175         final int aHash = a.hashCode();
1176 
1177         Assertions.assertEquals(aHash, a.hashCode());
1178         Assertions.assertEquals(aHash, e.hashCode());
1179 
1180         Assertions.assertNotEquals(aHash, b.hashCode());
1181         Assertions.assertNotEquals(aHash, c.hashCode());
1182         Assertions.assertNotEquals(aHash, d.hashCode());
1183     }
1184 
1185     @Test
1186     void testEquals() {
1187      // arrange
1188         final Precision.DoubleEquivalence precision1 = Precision.doubleEquivalenceOfEpsilon(1e-4);
1189         final Precision.DoubleEquivalence precision2 = Precision.doubleEquivalenceOfEpsilon(1e-5);
1190 
1191         final Vector2D p = Vector2D.of(1, 2);
1192         final Vector2D v = Vector2D.of(1, 1);
1193 
1194         final Line a = Lines.fromPointAndDirection(p, v, precision1);
1195         final Line b = Lines.fromPointAndDirection(Vector2D.ZERO, v, precision1);
1196         final Line c = Lines.fromPointAndDirection(p, v.negate(), precision1);
1197         final Line d = Lines.fromPointAndDirection(p, v, precision2);
1198         final Line e = Lines.fromPointAndDirection(p, v, precision1);
1199 
1200         // act/assert
1201         GeometryTestUtils.assertSimpleEqualsCases(a);
1202         Assertions.assertEquals(a, e);
1203         Assertions.assertEquals(e, a);
1204 
1205         Assertions.assertNotEquals(a, b);
1206         Assertions.assertNotEquals(a, c);
1207         Assertions.assertNotEquals(a, d);
1208     }
1209 
1210     @Test
1211     void testToString() {
1212         // arrange
1213         final Line line = Lines.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
1214 
1215         // act
1216         final String str = line.toString();
1217 
1218         // assert
1219         Assertions.assertTrue(str.contains("Line"));
1220         Assertions.assertTrue(str.contains("origin= (0.0, 0.0)"));
1221         Assertions.assertTrue(str.contains("direction= (1.0, 0.0)"));
1222     }
1223 
1224     /**
1225      * Check that the line has the given defining properties.
1226      * @param line
1227      * @param origin
1228      * @param dir
1229      */
1230     private void checkLine(final Line line, final Vector2D origin, final Vector2D dir) {
1231         EuclideanTestUtils.assertCoordinatesEqual(origin, line.getOrigin(), TEST_EPS);
1232         EuclideanTestUtils.assertCoordinatesEqual(dir, line.getDirection(), TEST_EPS);
1233     }
1234 }