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