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.path;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.stream.Collectors;
24  
25  import org.apache.commons.geometry.core.GeometryTestUtils;
26  import org.apache.commons.geometry.core.RegionLocation;
27  import org.apache.commons.geometry.core.partitioning.Split;
28  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
29  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
30  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
31  import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
32  import org.apache.commons.geometry.euclidean.twod.Line;
33  import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
34  import org.apache.commons.geometry.euclidean.twod.LinecastChecker2D;
35  import org.apache.commons.geometry.euclidean.twod.Lines;
36  import org.apache.commons.geometry.euclidean.twod.Ray;
37  import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
38  import org.apache.commons.geometry.euclidean.twod.ReverseRay;
39  import org.apache.commons.geometry.euclidean.twod.Segment;
40  import org.apache.commons.geometry.euclidean.twod.Vector2D;
41  import org.apache.commons.geometry.euclidean.twod.path.LinePath.Builder;
42  import org.apache.commons.numbers.angle.PlaneAngleRadians;
43  import org.junit.Assert;
44  import org.junit.Test;
45  
46  public class LinePathTest {
47  
48      private static final double TEST_EPS = 1e-10;
49  
50      private static final DoublePrecisionContext TEST_PRECISION =
51              new EpsilonDoublePrecisionContext(TEST_EPS);
52  
53      @Test
54      public void testFrom_empty() {
55          // act
56          final LinePath path = LinePath.from(new ArrayList<>());
57  
58          // assert
59          Assert.assertTrue(path.isEmpty());
60          Assert.assertFalse(path.isInfinite());
61          Assert.assertTrue(path.isFinite());
62          Assert.assertFalse(path.isClosed());
63  
64          Assert.assertEquals(0, path.getSize(), TEST_EPS);
65  
66          Assert.assertNull(path.getStart());
67          Assert.assertNull(path.getEnd());
68  
69          Assert.assertEquals(0, path.getElements().size());
70  
71          Assert.assertEquals(0, path.getVertexSequence().size());
72      }
73  
74      @Test
75      public void testFrom_singleFiniteSegment() {
76          // arrange
77          final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
78  
79          // act
80          final LinePath path = LinePath.from(a);
81  
82          // assert
83          Assert.assertFalse(path.isEmpty());
84          Assert.assertFalse(path.isInfinite());
85          Assert.assertTrue(path.isFinite());
86          Assert.assertFalse(path.isClosed());
87  
88          Assert.assertEquals(1, path.getSize(), TEST_EPS);
89  
90          Assert.assertSame(a, path.getStart());
91          Assert.assertSame(a, path.getEnd());
92  
93          final List<LineConvexSubset> segments = path.getElements();
94          Assert.assertEquals(1, segments.size());
95          Assert.assertSame(a, segments.get(0));
96  
97          Assert.assertEquals(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0)), path.getVertexSequence());
98      }
99  
100     @Test
101     public void testFrom_singleInfiniteSegment() {
102         // arrange
103         final LineConvexSubset a = Lines.fromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION).span();
104 
105         // act
106         final LinePath path = LinePath.from(a);
107 
108         // assert
109         Assert.assertFalse(path.isEmpty());
110         Assert.assertTrue(path.isInfinite());
111         Assert.assertFalse(path.isFinite());
112         Assert.assertFalse(path.isClosed());
113 
114         GeometryTestUtils.assertPositiveInfinity(path.getSize());
115 
116         Assert.assertSame(a, path.getStart());
117         Assert.assertSame(a, path.getEnd());
118 
119         final List<LineConvexSubset> segments = path.getElements();
120         Assert.assertEquals(1, segments.size());
121         Assert.assertSame(a, segments.get(0));
122 
123         Assert.assertEquals(0, path.getVertexSequence().size());
124     }
125 
126     @Test
127     public void testFrom_finiteSegments_notClosed() {
128         // arrange
129         final Vector2D p1 = Vector2D.ZERO;
130         final Vector2D p2 = Vector2D.of(1, 0);
131         final Vector2D p3 = Vector2D.of(1, 1);
132 
133         final Segment a = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
134         final Segment b = Lines.segmentFromPoints(p2, p3, TEST_PRECISION);
135 
136         // act
137         final LinePath path = LinePath.from(a, b);
138 
139         // assert
140         Assert.assertFalse(path.isEmpty());
141         Assert.assertFalse(path.isInfinite());
142         Assert.assertTrue(path.isFinite());
143         Assert.assertFalse(path.isClosed());
144 
145         Assert.assertEquals(2, path.getSize(), TEST_EPS);
146 
147         Assert.assertSame(a, path.getStart());
148         Assert.assertSame(b, path.getEnd());
149 
150         final List<LineConvexSubset> segments = path.getElements();
151         Assert.assertEquals(2, segments.size());
152         Assert.assertSame(a, segments.get(0));
153         Assert.assertSame(b, segments.get(1));
154 
155         Assert.assertEquals(Arrays.asList(p1, p2, p3), path.getVertexSequence());
156     }
157 
158     @Test
159     public void testFrom_finiteSegments_closed() {
160         // arrange
161         final Vector2D p1 = Vector2D.ZERO;
162         final Vector2D p2 = Vector2D.of(1, 0);
163         final Vector2D p3 = Vector2D.of(1, 1);
164 
165         final Segment a = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
166         final Segment b = Lines.segmentFromPoints(p2, p3, TEST_PRECISION);
167         final Segment c = Lines.segmentFromPoints(p3, p1, TEST_PRECISION);
168 
169         // act
170         final LinePath path = LinePath.from(Arrays.asList(a, b, c));
171 
172         // assert
173         Assert.assertFalse(path.isEmpty());
174         Assert.assertFalse(path.isInfinite());
175         Assert.assertTrue(path.isFinite());
176         Assert.assertTrue(path.isClosed());
177 
178         Assert.assertSame(a, path.getStart());
179         Assert.assertSame(c, path.getEnd());
180 
181         Assert.assertEquals(2 + Math.sqrt(2), path.getSize(), TEST_EPS);
182 
183         final List<LineConvexSubset> segments = path.getElements();
184         Assert.assertEquals(3, segments.size());
185         Assert.assertSame(a, segments.get(0));
186         Assert.assertSame(b, segments.get(1));
187         Assert.assertSame(c, segments.get(2));
188 
189         Assert.assertEquals(Arrays.asList(p1, p2, p3, p1), path.getVertexSequence());
190     }
191 
192     @Test
193     public void testFrom_infiniteSegments() {
194         // arrange
195         final ReverseRay a = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION)
196                 .reverseRayTo(1.0);
197         final Ray b = Lines.fromPointAndAngle(Vector2D.of(1, 0), PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION)
198                 .rayFrom(0.0);
199 
200         // act
201         final LinePath path = LinePath.from(Arrays.asList(a, b));
202 
203         // assert
204         Assert.assertFalse(path.isEmpty());
205         Assert.assertTrue(path.isInfinite());
206         Assert.assertFalse(path.isFinite());
207         Assert.assertFalse(path.isClosed());
208 
209         GeometryTestUtils.assertPositiveInfinity(path.getSize());
210 
211         Assert.assertSame(a, path.getStart());
212         Assert.assertSame(b, path.getEnd());
213 
214         final List<LineConvexSubset> segments = path.getElements();
215         Assert.assertEquals(2, segments.size());
216         Assert.assertSame(a, segments.get(0));
217         Assert.assertSame(b, segments.get(1));
218 
219         Assert.assertEquals(Collections.singletonList(Vector2D.of(1, 0)), path.getVertexSequence());
220     }
221 
222     @Test
223     public void testFrom_finiteAndInfiniteSegments_startInfinite() {
224         // arrange
225         final ReverseRay a = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION).reverseRayTo(1.0);
226         final Segment b = Lines.segmentFromPoints(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION);
227 
228         // act
229         final LinePath path = LinePath.from(Arrays.asList(a, b));
230 
231         // assert
232         Assert.assertFalse(path.isEmpty());
233         Assert.assertTrue(path.isInfinite());
234         Assert.assertFalse(path.isFinite());
235         Assert.assertFalse(path.isClosed());
236 
237         Assert.assertSame(a, path.getStart());
238         Assert.assertSame(b, path.getEnd());
239 
240         final List<LineConvexSubset> segments = path.getElements();
241         Assert.assertEquals(2, segments.size());
242         Assert.assertSame(a, segments.get(0));
243         Assert.assertSame(b, segments.get(1));
244 
245         Assert.assertEquals(Arrays.asList(Vector2D.of(1, 0), Vector2D.of(1, 1)), path.getVertexSequence());
246     }
247 
248     @Test
249     public void testFrom_finiteAndInfiniteSegments_endInfinite() {
250         // arrange
251         final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
252         final Ray b = Lines.fromPointAndAngle(Vector2D.of(1, 0), PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION)
253                 .rayFrom(0.0);
254 
255         // act
256         final LinePath path = LinePath.from(Arrays.asList(a, b));
257 
258         // assert
259         Assert.assertFalse(path.isEmpty());
260         Assert.assertTrue(path.isInfinite());
261         Assert.assertFalse(path.isFinite());
262         Assert.assertFalse(path.isClosed());
263 
264         Assert.assertSame(a, path.getStart());
265         Assert.assertSame(b, path.getEnd());
266 
267         final List<LineConvexSubset> segments = path.getElements();
268         Assert.assertEquals(2, segments.size());
269         Assert.assertSame(a, segments.get(0));
270         Assert.assertSame(b, segments.get(1));
271 
272         Assert.assertEquals(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0)), path.getVertexSequence());
273     }
274 
275     @Test
276     public void testFrom_segmentsNotConnected() {
277         // arrange
278         final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
279         final Segment b = Lines.segmentFromPoints(Vector2D.of(1.01, 0), Vector2D.of(1, 0), TEST_PRECISION);
280 
281         final LineConvexSubset c = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION).span();
282         final LineConvexSubset d = Lines.fromPointAndAngle(Vector2D.of(1, 0), PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION).span();
283 
284         // act/assert
285         GeometryTestUtils.assertThrows(() -> {
286             LinePath.from(a, b);
287         }, IllegalStateException.class);
288 
289         GeometryTestUtils.assertThrows(() -> {
290             LinePath.from(c, b);
291         }, IllegalStateException.class);
292 
293         GeometryTestUtils.assertThrows(() -> {
294             LinePath.from(a, d);
295         }, IllegalStateException.class);
296     }
297 
298     @Test
299     public void testFromVertices_empty() {
300         // act
301         final LinePath path = LinePath.fromVertices(new ArrayList<>(), TEST_PRECISION);
302 
303         // assert
304         Assert.assertTrue(path.isEmpty());
305         Assert.assertFalse(path.isInfinite());
306         Assert.assertTrue(path.isFinite());
307         Assert.assertFalse(path.isClosed());
308 
309         Assert.assertNull(path.getStart());
310         Assert.assertNull(path.getEnd());
311 
312         Assert.assertEquals(0, path.getElements().size());
313 
314         Assert.assertEquals(0, path.getVertexSequence().size());
315     }
316 
317     @Test
318     public void testFromVertices_singleVertex_failsToCreatePath() {
319         // act/assert
320         GeometryTestUtils.assertThrows(() -> {
321             LinePath.fromVertices(Collections.singletonList(Vector2D.ZERO), TEST_PRECISION);
322         }, IllegalStateException.class);
323     }
324 
325     @Test
326     public void testFromVertices_twoVertices() {
327         // arrange
328         final Vector2D p1 = Vector2D.ZERO;
329         final Vector2D p2 = Vector2D.of(1, 0);
330 
331         // act
332         final LinePath path = LinePath.fromVertices(Arrays.asList(p1, p2), TEST_PRECISION);
333 
334         // assert
335         Assert.assertFalse(path.isEmpty());
336         Assert.assertFalse(path.isInfinite());
337         Assert.assertTrue(path.isFinite());
338         Assert.assertFalse(path.isClosed());
339 
340         assertFiniteSegment(path.getStart(), p1, p2);
341         Assert.assertSame(path.getStart(), path.getEnd());
342 
343         final List<LineConvexSubset> segments = path.getElements();
344         Assert.assertEquals(1, segments.size());
345         assertFiniteSegment(segments.get(0), p1, p2);
346 
347         Assert.assertEquals(Arrays.asList(p1, p2), path.getVertexSequence());
348     }
349 
350     @Test
351     public void testFromVertices_multipleVertices_notClosed() {
352         // arrange
353         final Vector2D p1 = Vector2D.ZERO;
354         final Vector2D p2 = Vector2D.of(1, 0);
355         final Vector2D p3 = Vector2D.of(1, 1);
356         final Vector2D p4 = Vector2D.of(0, 1);
357 
358         // act
359         final LinePath path = LinePath.fromVertices(Arrays.asList(p1, p2, p3, p4), TEST_PRECISION);
360 
361         // assert
362         Assert.assertFalse(path.isEmpty());
363         Assert.assertFalse(path.isInfinite());
364         Assert.assertTrue(path.isFinite());
365         Assert.assertFalse(path.isClosed());
366 
367         assertFiniteSegment(path.getStart(), p1, p2);
368         assertFiniteSegment(path.getEnd(), p3, p4);
369 
370         final List<LineConvexSubset> segments = path.getElements();
371         Assert.assertEquals(3, segments.size());
372         assertFiniteSegment(segments.get(0), p1, p2);
373         assertFiniteSegment(segments.get(1), p2, p3);
374         assertFiniteSegment(segments.get(2), p3, p4);
375 
376         Assert.assertEquals(Arrays.asList(p1, p2, p3, p4), path.getVertexSequence());
377     }
378 
379     @Test
380     public void testFromVertices_multipleVertices_closed() {
381         // arrange
382         final Vector2D p1 = Vector2D.ZERO;
383         final Vector2D p2 = Vector2D.of(1, 0);
384         final Vector2D p3 = Vector2D.of(1, 1);
385         final Vector2D p4 = Vector2D.of(0, 1);
386 
387         // act
388         final LinePath path = LinePath.fromVertices(Arrays.asList(p1, p2, p3, p4, p1), TEST_PRECISION);
389 
390         // assert
391         Assert.assertFalse(path.isEmpty());
392         Assert.assertFalse(path.isInfinite());
393         Assert.assertTrue(path.isFinite());
394         Assert.assertTrue(path.isClosed());
395 
396         assertFiniteSegment(path.getStart(), p1, p2);
397         assertFiniteSegment(path.getEnd(), p4, p1);
398 
399         final List<LineConvexSubset> segments = path.getElements();
400         Assert.assertEquals(4, segments.size());
401         assertFiniteSegment(segments.get(0), p1, p2);
402         assertFiniteSegment(segments.get(1), p2, p3);
403         assertFiniteSegment(segments.get(2), p3, p4);
404         assertFiniteSegment(segments.get(3), p4, p1);
405 
406         Assert.assertEquals(Arrays.asList(p1, p2, p3, p4, p1), path.getVertexSequence());
407     }
408 
409     @Test
410     public void testFromVertexLoop_empty() {
411         // act
412         final LinePath path = LinePath.fromVertexLoop(new ArrayList<>(), TEST_PRECISION);
413 
414         // assert
415         Assert.assertTrue(path.isEmpty());
416         Assert.assertFalse(path.isInfinite());
417         Assert.assertTrue(path.isFinite());
418         Assert.assertFalse(path.isClosed());
419 
420         Assert.assertNull(path.getStart());
421         Assert.assertNull(path.getEnd());
422 
423         Assert.assertEquals(0, path.getElements().size());
424 
425         Assert.assertEquals(0, path.getVertexSequence().size());
426     }
427 
428     @Test
429     public void testFromVertexLoop_singleVertex_failsToCreatePath() {
430         // act/assert
431         GeometryTestUtils.assertThrows(() -> {
432             LinePath.fromVertexLoop(Collections.singletonList(Vector2D.ZERO), TEST_PRECISION);
433         }, IllegalStateException.class);
434     }
435 
436     @Test
437     public void testFromVertexLoop_closeRequired() {
438         // arrange
439         final Vector2D p1 = Vector2D.ZERO;
440         final Vector2D p2 = Vector2D.of(1, 0);
441         final Vector2D p3 = Vector2D.of(1, 1);
442 
443         // act
444         final LinePath path = LinePath.fromVertexLoop(Arrays.asList(p1, p2, p3), TEST_PRECISION);
445 
446         // assert
447         Assert.assertFalse(path.isEmpty());
448         Assert.assertFalse(path.isInfinite());
449         Assert.assertTrue(path.isFinite());
450         Assert.assertTrue(path.isClosed());
451 
452         final List<LineConvexSubset> segments = path.getElements();
453         Assert.assertEquals(3, segments.size());
454         assertFiniteSegment(segments.get(0), p1, p2);
455         assertFiniteSegment(segments.get(1), p2, p3);
456         assertFiniteSegment(segments.get(2), p3, p1);
457 
458         Assert.assertEquals(Arrays.asList(p1, p2, p3, p1), path.getVertexSequence());
459     }
460 
461     @Test
462     public void testFromVertexLoop_closeNotRequired() {
463         // arrange
464         final Vector2D p1 = Vector2D.ZERO;
465         final Vector2D p2 = Vector2D.of(1, 0);
466         final Vector2D p3 = Vector2D.of(1, 1);
467 
468         // act
469         final LinePath path = LinePath.fromVertexLoop(Arrays.asList(p1, p2, p3, Vector2D.of(0, 0)), TEST_PRECISION);
470 
471         // assert
472         Assert.assertFalse(path.isEmpty());
473         Assert.assertFalse(path.isInfinite());
474         Assert.assertTrue(path.isFinite());
475         Assert.assertTrue(path.isClosed());
476 
477         final List<LineConvexSubset> segments = path.getElements();
478         Assert.assertEquals(3, segments.size());
479         assertFiniteSegment(segments.get(0), p1, p2);
480         assertFiniteSegment(segments.get(1), p2, p3);
481         assertFiniteSegment(segments.get(2), p3, p1);
482 
483         Assert.assertEquals(Arrays.asList(p1, p2, p3, p1), path.getVertexSequence());
484     }
485 
486     @Test
487     public void testFromVertices_booleanArg() {
488         // arrange
489         final Vector2D p1 = Vector2D.ZERO;
490         final Vector2D p2 = Vector2D.of(1, 0);
491         final Vector2D p3 = Vector2D.of(0, 1);
492 
493         // act
494         final LinePath open = LinePath.fromVertices(Arrays.asList(p1, p2, p3), false, TEST_PRECISION);
495         final LinePath closed = LinePath.fromVertices(Arrays.asList(p1, p2, p3), true, TEST_PRECISION);
496 
497         // assert
498         Assert.assertFalse(open.isClosed());
499 
500         final List<LineConvexSubset> openSegments = open.getElements();
501         Assert.assertEquals(2, openSegments.size());
502         assertFiniteSegment(openSegments.get(0), p1, p2);
503         assertFiniteSegment(openSegments.get(1), p2, p3);
504 
505         Assert.assertTrue(closed.isClosed());
506 
507         final List<LineConvexSubset> closedSegments = closed.getElements();
508         Assert.assertEquals(3, closedSegments.size());
509         assertFiniteSegment(closedSegments.get(0), p1, p2);
510         assertFiniteSegment(closedSegments.get(1), p2, p3);
511         assertFiniteSegment(closedSegments.get(2), p3, p1);
512     }
513 
514     @Test
515     public void testGetElements_listIsNotModifiable() {
516         // arrange
517         final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
518         final List<LineConvexSubset> inputSegments = new ArrayList<>(Collections.singletonList(a));
519 
520         // act
521         final LinePath path = LinePath.from(inputSegments);
522 
523         inputSegments.clear();
524 
525         // assert
526         Assert.assertNotSame(inputSegments, path.getElements());
527         Assert.assertEquals(1, path.getElements().size());
528 
529         GeometryTestUtils.assertThrows(() -> {
530             path.getElements().add(a);
531         }, UnsupportedOperationException.class);
532     }
533 
534     @Test
535     public void testBoundaryStream() {
536         // arrange
537         final Segment seg = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
538         final LinePath path = LinePath.from(Collections.singletonList(seg));
539 
540         // act
541         final List<LineConvexSubset> segments = path.boundaryStream().collect(Collectors.toList());
542 
543         // assert
544         Assert.assertEquals(1, segments.size());
545         Assert.assertSame(seg, segments.get(0));
546     }
547 
548     @Test
549     public void testBoundaryStream_empty() {
550         // arrange
551         final LinePath path = LinePath.empty();
552 
553         // act
554         final List<LineConvexSubset> segments = path.boundaryStream().collect(Collectors.toList());
555 
556         // assert
557         Assert.assertEquals(0, segments.size());
558     }
559 
560     @Test
561     public void testTransform_empty() {
562         // arrange
563         final LinePath path = LinePath.empty();
564         final AffineTransformMatrix2D t = AffineTransformMatrix2D.createTranslation(Vector2D.Unit.PLUS_X);
565 
566         // act/assert
567         Assert.assertSame(path, path.transform(t));
568     }
569 
570     @Test
571     public void testTransform_finite() {
572         // arrange
573         final LinePath path = LinePath.builder(TEST_PRECISION)
574                 .append(Vector2D.Unit.ZERO)
575                 .append(Vector2D.Unit.PLUS_X)
576                 .append(Vector2D.Unit.PLUS_Y)
577                 .close();
578 
579         final AffineTransformMatrix2D t =
580                 AffineTransformMatrix2D.createRotation(Vector2D.of(1, 1), PlaneAngleRadians.PI_OVER_TWO);
581 
582         // act
583         final LinePath result = path.transform(t);
584 
585         // assert
586         Assert.assertNotSame(path, result);
587         Assert.assertTrue(result.isClosed());
588         Assert.assertTrue(result.isFinite());
589 
590         final List<LineConvexSubset> segments = result.getElements();
591 
592         Assert.assertEquals(3, segments.size());
593         assertFiniteSegment(segments.get(0), Vector2D.of(2, 0), Vector2D.of(2, 1));
594         assertFiniteSegment(segments.get(1), Vector2D.of(2, 1), Vector2D.Unit.PLUS_X);
595         assertFiniteSegment(segments.get(2), Vector2D.Unit.PLUS_X, Vector2D.of(2, 0));
596     }
597 
598     @Test
599     public void testTransform_infinite() {
600         // arrange
601         final LinePath path = LinePath.from(
602                 Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION).span());
603 
604         final AffineTransformMatrix2D t = AffineTransformMatrix2D.createTranslation(Vector2D.Unit.PLUS_X);
605 
606         // act
607         final LinePath result = path.transform(t);
608 
609         // assert
610         Assert.assertNotSame(path, result);
611         Assert.assertFalse(result.isClosed());
612         Assert.assertFalse(result.isFinite());
613 
614         final List<LineConvexSubset> segments = result.getElements();
615 
616         Assert.assertEquals(1, segments.size());
617         final LineConvexSubset segment = segments.get(0);
618         Assert.assertTrue(segment.isInfinite());
619         Assert.assertNull(segment.getStartPoint());
620         Assert.assertNull(segment.getEndPoint());
621         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X, segment.getLine().getOrigin(), TEST_EPS);
622         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y, segment.getLine().getDirection(), TEST_EPS);
623     }
624 
625     @Test
626     public void testReverse_empty() {
627         // arrange
628         final LinePath path = LinePath.empty();
629 
630         // act/assert
631         Assert.assertSame(path, path.reverse());
632     }
633 
634     @Test
635     public void testReverse() {
636         // arrange
637         final LinePath path = LinePath.builder(TEST_PRECISION)
638                 .append(Vector2D.Unit.ZERO)
639                 .append(Vector2D.Unit.PLUS_X)
640                 .append(Vector2D.Unit.PLUS_Y)
641                 .close();
642 
643         // act
644         final LinePath result = path.reverse();
645 
646         // assert
647         Assert.assertNotSame(path, result);
648         Assert.assertTrue(result.isClosed());
649         Assert.assertTrue(result.isFinite());
650 
651         final List<LineConvexSubset> segments = result.getElements();
652 
653         Assert.assertEquals(3, segments.size());
654         assertFiniteSegment(segments.get(0), Vector2D.Unit.ZERO, Vector2D.Unit.PLUS_Y);
655         assertFiniteSegment(segments.get(1), Vector2D.Unit.PLUS_Y, Vector2D.Unit.PLUS_X);
656         assertFiniteSegment(segments.get(2), Vector2D.Unit.PLUS_X, Vector2D.Unit.ZERO);
657     }
658 
659     @Test
660     public void testReverse_singleInfinite() {
661         // arrange
662         final LinePath path = LinePath.from(
663                 Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION).span());
664 
665         // act
666         final LinePath result = path.reverse();
667 
668         // assert
669         Assert.assertNotSame(path, result);
670         Assert.assertFalse(result.isClosed());
671         Assert.assertFalse(result.isFinite());
672 
673         final List<LineConvexSubset> segments = result.getElements();
674 
675         Assert.assertEquals(1, segments.size());
676         final LineConvexSubset segment = segments.get(0);
677         Assert.assertTrue(segment.isInfinite());
678         Assert.assertNull(segment.getStartPoint());
679         Assert.assertNull(segment.getEndPoint());
680         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.ZERO, segment.getLine().getOrigin(), TEST_EPS);
681         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y, segment.getLine().getDirection(), TEST_EPS);
682     }
683 
684     @Test
685     public void testReverse_doubleInfinite() {
686         // arrange
687         final LineConvexSubset a = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION).reverseRayTo(Vector2D.ZERO);
688         final LineConvexSubset b = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).rayFrom(Vector2D.ZERO);
689 
690         final LinePath path = LinePath.from(a, b);
691 
692         // act
693         final LinePath result = path.reverse();
694 
695         // assert
696         Assert.assertNotSame(path, result);
697         Assert.assertFalse(result.isClosed());
698         Assert.assertFalse(result.isFinite());
699 
700         final List<LineConvexSubset> segments = result.getElements();
701         Assert.assertEquals(2, segments.size());
702 
703         final LineConvexSubset bResult = segments.get(0);
704         Assert.assertTrue(bResult.isInfinite());
705         Assert.assertNull(bResult.getStartPoint());
706         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, bResult.getEndPoint(), TEST_EPS);
707         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, bResult.getLine().getOrigin(), TEST_EPS);
708         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X, bResult.getLine().getDirection(), TEST_EPS);
709 
710         final LineConvexSubset aResult = segments.get(1);
711         Assert.assertTrue(aResult.isInfinite());
712         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, aResult.getStartPoint(), TEST_EPS);
713         Assert.assertNull(aResult.getEndPoint());
714         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, aResult.getLine().getOrigin(), TEST_EPS);
715         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y, aResult.getLine().getDirection(), TEST_EPS);
716     }
717 
718     @Test
719     public void testToTree() {
720         // arrange
721         final LinePath path = LinePath.builder(TEST_PRECISION)
722                 .appendVertices(Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), Vector2D.of(0, 1))
723                 .close();
724 
725         // act
726         final RegionBSPTree2D tree = path.toTree();
727 
728         // assert
729         Assert.assertEquals(1, tree.getSize(), TEST_EPS);
730         Assert.assertEquals(4, tree.getBoundarySize(), TEST_EPS);
731 
732         Assert.assertEquals(RegionLocation.INSIDE, tree.classify(Vector2D.of(0.5, 0.5)));
733 
734         Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(Vector2D.of(0.5, -1)));
735         Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(Vector2D.of(0.5, 2)));
736         Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(Vector2D.of(-1, 0.5)));
737         Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(Vector2D.of(2, 0.5)));
738     }
739 
740     @Test
741     public void testSimplify() {
742         // arrange
743         final Builder builder = LinePath.builder(TEST_PRECISION);
744 
745         final LinePath path = builder.appendVertices(
746                 Vector2D.of(-1, 0),
747                 Vector2D.ZERO,
748                 Vector2D.of(1, 0),
749                 Vector2D.of(1, 1),
750                 Vector2D.of(1, 2))
751             .build();
752 
753         // act
754         final LinePath result = path.simplify();
755 
756         // assert
757         final List<LineConvexSubset> segments = result.getElements();
758         Assert.assertEquals(2, segments.size());
759         assertFiniteSegment(segments.get(0), Vector2D.of(-1, 0), Vector2D.of(1, 0));
760         assertFiniteSegment(segments.get(1), Vector2D.of(1, 0), Vector2D.of(1, 2));
761     }
762 
763     @Test
764     public void testSimplify_startAndEndCombined() {
765         // arrange
766         final Builder builder = LinePath.builder(TEST_PRECISION);
767 
768         final LinePath path = builder.appendVertices(
769                 Vector2D.ZERO,
770                 Vector2D.of(1, 0),
771                 Vector2D.of(0, 1),
772                 Vector2D.of(-1, 0))
773             .close();
774 
775         // act
776         final LinePath result = path.simplify();
777 
778         // assert
779         Assert.assertNotSame(path, result);
780         Assert.assertTrue(result.isClosed());
781         Assert.assertFalse(result.isInfinite());
782 
783         final List<LineConvexSubset> segments = result.getElements();
784         Assert.assertEquals(3, segments.size());
785         assertFiniteSegment(segments.get(0), Vector2D.of(-1, 0), Vector2D.of(1, 0));
786         assertFiniteSegment(segments.get(1), Vector2D.of(1, 0), Vector2D.of(0, 1));
787         assertFiniteSegment(segments.get(2), Vector2D.of(0, 1), Vector2D.of(-1, 0));
788     }
789 
790     @Test
791     public void testSimplify_empty() {
792         // arrange
793         final Builder builder = LinePath.builder(TEST_PRECISION);
794 
795         final LinePath path = builder.build();
796 
797         // act
798         final LinePath result = path.simplify();
799 
800         // assert
801         Assert.assertNotSame(path, result);
802         Assert.assertFalse(result.isClosed());
803         Assert.assertFalse(result.isInfinite());
804 
805         final List<LineConvexSubset> segments = result.getElements();
806         Assert.assertEquals(0, segments.size());
807     }
808 
809     @Test
810     public void testSimplify_infiniteSegment() {
811         // arrange
812         final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
813 
814         final Builder builder = LinePath.builder(TEST_PRECISION);
815         final LinePath path = builder
816                 .append(line.span())
817                 .build();
818 
819         // act
820         final LinePath result = path.simplify();
821 
822         // assert
823         Assert.assertNotSame(path, result);
824         Assert.assertFalse(result.isClosed());
825         Assert.assertTrue(result.isInfinite());
826 
827         Assert.assertNotNull(path.getStart());
828         Assert.assertNotNull(path.getEnd());
829         Assert.assertSame(path.getStart(), path.getEnd());
830 
831         final List<LineConvexSubset> segments = result.getElements();
832         Assert.assertEquals(1, segments.size());
833         Assert.assertSame(line, segments.get(0).getLine());
834     }
835 
836     @Test
837     public void testSimplify_combinedInfiniteSegment() {
838         // arrange
839         final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
840         final Split<LineConvexSubset> split = line.span().split(
841                 Lines.fromPointAndAngle(Vector2D.ZERO, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
842 
843         final Builder builder = LinePath.builder(TEST_PRECISION);
844         final LinePath path = builder
845                 .append(split.getMinus())
846                 .append(split.getPlus())
847                 .build();
848 
849         // act
850         final LinePath result = path.simplify();
851 
852         // assert
853         Assert.assertNotSame(path, result);
854         Assert.assertFalse(result.isClosed());
855         Assert.assertTrue(result.isInfinite());
856 
857         Assert.assertNotNull(result.getStart());
858         Assert.assertNotNull(result.getEnd());
859         Assert.assertSame(result.getStart(), result.getEnd());
860 
861         final List<LineConvexSubset> segments = result.getElements();
862         Assert.assertEquals(1, segments.size());
863         Assert.assertSame(line, segments.get(0).getLine());
864     }
865 
866     @Test
867     public void testSimplify_startAndEndNotCombinedWhenNotClosed() {
868         // arrange
869         final Line xAxis = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
870         final Builder builder = LinePath.builder(TEST_PRECISION);
871 
872         final LinePath path = builder
873                 .append(xAxis.segment(0, 1))
874                 .appendVertices(
875                         Vector2D.of(2, 1),
876                         Vector2D.of(3, 0))
877                 .append(xAxis.segment(3, 4))
878             .build();
879 
880         // act
881         final LinePath result = path.simplify();
882 
883         // assert
884         Assert.assertNotSame(path, result);
885         Assert.assertFalse(result.isClosed());
886         Assert.assertFalse(result.isInfinite());
887 
888         final List<LineConvexSubset> segments = result.getElements();
889         Assert.assertEquals(4, segments.size());
890         assertFiniteSegment(segments.get(0), Vector2D.ZERO, Vector2D.of(1, 0));
891         assertFiniteSegment(segments.get(1), Vector2D.of(1, 0), Vector2D.of(2, 1));
892         assertFiniteSegment(segments.get(2), Vector2D.of(2, 1), Vector2D.of(3, 0));
893         assertFiniteSegment(segments.get(3), Vector2D.of(3, 0), Vector2D.of(4, 0));
894     }
895 
896     @Test
897     public void testSimplify_subsequentCallsToReturnedObjectReturnSameObject() {
898         // arrange
899         final Builder builder = LinePath.builder(TEST_PRECISION);
900         final LinePath path = builder.appendVertices(
901                     Vector2D.ZERO,
902                     Vector2D.of(1, 0),
903                     Vector2D.of(2, 0))
904                 .build();
905 
906         // act
907         final LinePath result = path.simplify();
908 
909         // assert
910         Assert.assertNotSame(path, result);
911         Assert.assertSame(result, result.simplify());
912     }
913 
914     @Test
915     public void testLinecast_empty() {
916         // arrange
917         final LinePath path = LinePath.empty();
918 
919         // act/assert
920         LinecastChecker2D.with(path)
921             .expectNothing()
922             .whenGiven(Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION));
923 
924         LinecastChecker2D.with(path)
925             .expectNothing()
926             .whenGiven(Lines.segmentFromPoints(Vector2D.Unit.MINUS_X, Vector2D.Unit.PLUS_X, TEST_PRECISION));
927     }
928 
929     @Test
930     public void testLinecast() {
931         // arrange
932         final LinePath path = LinePath.fromVertexLoop(Arrays.asList(
933                     Vector2D.ZERO, Vector2D.of(1, 0),
934                     Vector2D.of(1, 1), Vector2D.of(0, 1)
935                 ), TEST_PRECISION);
936 
937         // act/assert
938         LinecastChecker2D.with(path)
939             .expectNothing()
940             .whenGiven(Lines.fromPoints(Vector2D.of(0, 5), Vector2D.of(1, 6), TEST_PRECISION));
941 
942         LinecastChecker2D.with(path)
943             .expect(Vector2D.ZERO, Vector2D.Unit.MINUS_X)
944             .and(Vector2D.ZERO, Vector2D.Unit.MINUS_Y)
945             .and(Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y)
946             .and(Vector2D.of(1, 1), Vector2D.Unit.PLUS_X)
947             .whenGiven(Lines.fromPoints(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION));
948 
949         LinecastChecker2D.with(path)
950             .expect(Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y)
951             .and(Vector2D.of(1, 1), Vector2D.Unit.PLUS_X)
952             .whenGiven(Lines.segmentFromPoints(Vector2D.of(0.5, 0.5), Vector2D.of(1, 1), TEST_PRECISION));
953     }
954 
955     @Test
956     public void testToString() {
957         // arrange
958         final Line yAxis = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
959         final Line xAxis = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
960 
961         final LinePath empty = LinePath.empty();
962 
963         final LinePath singleFullSegment = LinePath.from(xAxis.span());
964         final LinePath singleFiniteSegment = LinePath.from(
965                 Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION));
966 
967         final LinePath startOpenPath = LinePath.builder(TEST_PRECISION)
968                 .append(xAxis.reverseRayTo(Vector2D.Unit.PLUS_X))
969                 .append(Vector2D.of(1, 1))
970                 .build();
971 
972         final LinePath endOpenPath = LinePath.builder(TEST_PRECISION)
973                 .append(Vector2D.of(0, 1))
974                 .append(Vector2D.ZERO)
975                 .append(xAxis.rayFrom(Vector2D.ZERO))
976                 .build();
977 
978         final LinePath doubleOpenPath = LinePath.from(yAxis.reverseRayTo(Vector2D.ZERO),
979                 xAxis.rayFrom(Vector2D.ZERO));
980 
981         final LinePath nonOpenPath = LinePath.builder(TEST_PRECISION)
982                 .append(Vector2D.ZERO)
983                 .append(Vector2D.Unit.PLUS_X)
984                 .append(Vector2D.of(1, 1))
985                 .build();
986 
987         // act/assert
988         final String emptyStr = empty.toString();
989         GeometryTestUtils.assertContains("LinePath[empty= true", emptyStr);
990 
991         final String singleFullStr = singleFullSegment.toString();
992         GeometryTestUtils.assertContains("LinePath[single= LineSpanningSubset[", singleFullStr);
993 
994         final String singleFiniteStr = singleFiniteSegment.toString();
995         GeometryTestUtils.assertContains("LinePath[single= Segment[", singleFiniteStr);
996 
997         final String startOpenStr = startOpenPath.toString();
998         GeometryTestUtils.assertContains("LinePath[startDirection= ", startOpenStr);
999         GeometryTestUtils.assertContains("vertexSequence=", startOpenStr);
1000 
1001         final String endOpenStr = endOpenPath.toString();
1002         GeometryTestUtils.assertContains("LinePath[vertexSequence= ", endOpenStr);
1003         GeometryTestUtils.assertContains("endDirection= ", endOpenStr);
1004 
1005         final String doubleOpenStr = doubleOpenPath.toString();
1006         GeometryTestUtils.assertContains("startDirection= ", doubleOpenStr);
1007         GeometryTestUtils.assertContains("vertexSequence= ", doubleOpenStr);
1008         GeometryTestUtils.assertContains("endDirection= ", doubleOpenStr);
1009 
1010         final String nonOpenStr = nonOpenPath.toString();
1011         GeometryTestUtils.assertContains("LinePath[vertexSequence= ", nonOpenStr);
1012     }
1013 
1014     @Test
1015     public void testBuilder_prependAndAppend_segments() {
1016         // arrange
1017         final Vector2D p1 = Vector2D.ZERO;
1018         final Vector2D p2 = Vector2D.of(1, 0);
1019         final Vector2D p3 = Vector2D.of(1, 1);
1020         final Vector2D p4 = Vector2D.of(1, 0);
1021 
1022         final Segment a = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
1023         final Segment b = Lines.segmentFromPoints(p2, p3, TEST_PRECISION);
1024         final Segment c = Lines.segmentFromPoints(p3, p4, TEST_PRECISION);
1025         final Segment d = Lines.segmentFromPoints(p4, p1, TEST_PRECISION);
1026 
1027         final Builder builder = LinePath.builder(null);
1028 
1029         // act
1030         builder.prepend(b)
1031             .append(c)
1032             .prepend(a)
1033             .append(d);
1034 
1035         final LinePath path = builder.build();
1036 
1037         // assert
1038         final List<LineConvexSubset> segments = path.getElements();
1039         Assert.assertEquals(4, segments.size());
1040         Assert.assertSame(a, segments.get(0));
1041         Assert.assertSame(b, segments.get(1));
1042         Assert.assertSame(c, segments.get(2));
1043         Assert.assertSame(d, segments.get(3));
1044     }
1045 
1046     @Test
1047     public void testBuilder_prependAndAppend_disconnectedSegments() {
1048         // arrange
1049         final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
1050 
1051         final Builder builder = LinePath.builder(null);
1052         builder.append(a);
1053 
1054         // act
1055         GeometryTestUtils.assertThrows(() -> {
1056             builder.append(a);
1057         }, IllegalStateException.class);
1058 
1059         GeometryTestUtils.assertThrows(() -> {
1060             builder.prepend(a);
1061         }, IllegalStateException.class);
1062     }
1063 
1064     @Test
1065     public void testBuilder_prependAndAppend_vertices() {
1066         // arrange
1067         final Vector2D p1 = Vector2D.ZERO;
1068         final Vector2D p2 = Vector2D.of(1, 0);
1069         final Vector2D p3 = Vector2D.of(1, 1);
1070         final Vector2D p4 = Vector2D.of(1, 0);
1071 
1072         final Builder builder = LinePath.builder(TEST_PRECISION);
1073 
1074         // act
1075         builder.prepend(p2)
1076             .append(p3)
1077             .prepend(p1)
1078             .append(p4)
1079             .append(p1);
1080 
1081         final LinePath path = builder.build();
1082 
1083         // assert
1084         final List<LineConvexSubset> segments = path.getElements();
1085         Assert.assertEquals(4, segments.size());
1086         assertFiniteSegment(segments.get(0), p1, p2);
1087         assertFiniteSegment(segments.get(1), p2, p3);
1088         assertFiniteSegment(segments.get(2), p3, p4);
1089         assertFiniteSegment(segments.get(3), p4, p1);
1090     }
1091 
1092     @Test
1093     public void testBuilder_prependAndAppend_noPrecisionSpecified() {
1094         // arrange
1095         final Vector2D p = Vector2D.ZERO;
1096         final Builder builder = LinePath.builder(null);
1097 
1098         final String msg = "Unable to create line segment: no vertex precision specified";
1099 
1100         // act/assert
1101         GeometryTestUtils.assertThrows(() -> {
1102             builder.append(p);
1103         }, IllegalStateException.class, msg);
1104 
1105         GeometryTestUtils.assertThrows(() -> {
1106             builder.prepend(p);
1107         }, IllegalStateException.class, msg);
1108     }
1109 
1110     @Test
1111     public void testBuilder_prependAndAppend_addingToInfinitePath() {
1112         // arrange
1113         final Vector2D p = Vector2D.Unit.PLUS_X;
1114         final Builder builder = LinePath.builder(TEST_PRECISION);
1115 
1116         builder.append(Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION).span());
1117 
1118         // act/assert
1119         GeometryTestUtils.assertThrows(() -> {
1120             builder.append(p);
1121         }, IllegalStateException.class);
1122 
1123         GeometryTestUtils.assertThrows(() -> {
1124             builder.prepend(p);
1125         }, IllegalStateException.class);
1126     }
1127 
1128     @Test
1129     public void testBuilder_prependAndAppend_ignoresEquivalentVertices() {
1130         // arrange
1131         final Vector2D p = Vector2D.ZERO;
1132 
1133         final Builder builder = LinePath.builder(TEST_PRECISION);
1134         builder.append(p);
1135 
1136         // act
1137         builder.append(p)
1138             .prepend(p)
1139             .append(Vector2D.of(0, 1e-20))
1140             .prepend(Vector2D.of(1e-20, 0));
1141 
1142         builder.append(Vector2D.Unit.PLUS_X);
1143 
1144         // assert
1145         final LinePath path = builder.build();
1146 
1147         final List<LineConvexSubset> segments = path.getElements();
1148         Assert.assertEquals(1, segments.size());
1149         assertFiniteSegment(segments.get(0), p, Vector2D.Unit.PLUS_X);
1150     }
1151 
1152     @Test
1153     public void testBuilder_prependAndAppend_mixedVerticesAndSegments() {
1154         // arrange
1155         final Vector2D p1 = Vector2D.ZERO;
1156         final Vector2D p2 = Vector2D.of(1, 0);
1157         final Vector2D p3 = Vector2D.of(1, 1);
1158         final Vector2D p4 = Vector2D.of(0, 1);
1159 
1160         final Segment a = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
1161         final Segment c = Lines.segmentFromPoints(p3, p4, TEST_PRECISION);
1162 
1163         final Builder builder = LinePath.builder(TEST_PRECISION);
1164 
1165         // act
1166         builder.prepend(p2)
1167             .append(p3)
1168             .append(c)
1169             .prepend(a)
1170             .append(p1);
1171 
1172         final LinePath path = builder.build();
1173 
1174         // assert
1175         final List<LineConvexSubset> segments = path.getElements();
1176         Assert.assertEquals(4, segments.size());
1177         assertFiniteSegment(segments.get(0), p1, p2);
1178         assertFiniteSegment(segments.get(1), p2, p3);
1179         assertFiniteSegment(segments.get(2), p3, p4);
1180         assertFiniteSegment(segments.get(3), p4, p1);
1181     }
1182 
1183     @Test
1184     public void testBuilder_appendVertices() {
1185         // arrange
1186         final Vector2D p1 = Vector2D.ZERO;
1187         final Vector2D p2 = Vector2D.of(1, 0);
1188         final Vector2D p3 = Vector2D.of(1, 1);
1189         final Vector2D p4 = Vector2D.of(0, 1);
1190 
1191         final Builder builder = LinePath.builder(TEST_PRECISION);
1192 
1193         // act
1194         builder.appendVertices(p1, p2)
1195             .appendVertices(Arrays.asList(p3, p4, p1));
1196 
1197         final LinePath path = builder.build();
1198 
1199         // assert
1200         final List<LineConvexSubset> segments = path.getElements();
1201         Assert.assertEquals(4, segments.size());
1202         assertFiniteSegment(segments.get(0), p1, p2);
1203         assertFiniteSegment(segments.get(1), p2, p3);
1204         assertFiniteSegment(segments.get(2), p3, p4);
1205         assertFiniteSegment(segments.get(3), p4, p1);
1206     }
1207 
1208     @Test
1209     public void testBuilder_prependVertices() {
1210         // arrange
1211         final Vector2D p1 = Vector2D.ZERO;
1212         final Vector2D p2 = Vector2D.of(1, 0);
1213         final Vector2D p3 = Vector2D.of(1, 1);
1214         final Vector2D p4 = Vector2D.of(0, 1);
1215 
1216         final Builder builder = LinePath.builder(TEST_PRECISION);
1217 
1218         // act
1219         builder.prependVertices(p3, p4, p1)
1220             .prependVertices(Arrays.asList(p1, p2));
1221 
1222         final LinePath path = builder.build();
1223 
1224         // assert
1225         final List<LineConvexSubset> segments = path.getElements();
1226         Assert.assertEquals(4, segments.size());
1227         assertFiniteSegment(segments.get(0), p1, p2);
1228         assertFiniteSegment(segments.get(1), p2, p3);
1229         assertFiniteSegment(segments.get(2), p3, p4);
1230         assertFiniteSegment(segments.get(3), p4, p1);
1231     }
1232 
1233     @Test
1234     public void testBuilder_close_notYetClosed() {
1235         // arrange
1236         final Vector2D p1 = Vector2D.ZERO;
1237         final Vector2D p2 = Vector2D.of(1, 0);
1238         final Vector2D p3 = Vector2D.of(1, 1);
1239 
1240         final Builder builder = LinePath.builder(TEST_PRECISION);
1241 
1242         // act
1243         builder.append(p1)
1244             .append(p2)
1245             .append(p3);
1246 
1247         final LinePath path = builder.close();
1248 
1249         // assert
1250         final List<LineConvexSubset> segments = path.getElements();
1251         Assert.assertEquals(3, segments.size());
1252         assertFiniteSegment(segments.get(0), p1, p2);
1253         assertFiniteSegment(segments.get(1), p2, p3);
1254         assertFiniteSegment(segments.get(2), p3, p1);
1255     }
1256 
1257     @Test
1258     public void testBuilder_close_alreadyClosed() {
1259         // arrange
1260         final Vector2D p1 = Vector2D.ZERO;
1261         final Vector2D p2 = Vector2D.of(1, 0);
1262         final Vector2D p3 = Vector2D.of(1, 1);
1263 
1264         final Builder builder = LinePath.builder(TEST_PRECISION);
1265 
1266         // act
1267         builder.append(p1)
1268             .append(p2)
1269             .append(p3)
1270             .append(p1);
1271 
1272         final LinePath path = builder.close();
1273 
1274         // assert
1275         final List<LineConvexSubset> segments = path.getElements();
1276         Assert.assertEquals(3, segments.size());
1277         assertFiniteSegment(segments.get(0), p1, p2);
1278         assertFiniteSegment(segments.get(1), p2, p3);
1279         assertFiniteSegment(segments.get(2), p3, p1);
1280     }
1281 
1282     @Test
1283     public void testBuilder_close_infiniteSegmentAtStart() {
1284         // arrange
1285         final Builder builder = LinePath.builder(TEST_PRECISION);
1286 
1287         builder.append(Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION)
1288                 .reverseRayTo(1))
1289             .append(Vector2D.of(1, 1));
1290 
1291         // act/assert
1292         GeometryTestUtils.assertThrows(builder::close, IllegalStateException.class,
1293                 "Unable to close line path: line path is infinite");
1294     }
1295 
1296     @Test
1297     public void testBuilder_close_infiniteSegmentAtEnd() {
1298         // arrange
1299         final Builder builder = LinePath.builder(TEST_PRECISION);
1300 
1301         builder
1302             .append(Vector2D.ZERO)
1303             .append(Vector2D.Unit.PLUS_X)
1304             .append(Lines.fromPointAndAngle(Vector2D.Unit.PLUS_X, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION)
1305                 .rayFrom(0));
1306 
1307         // act/assert
1308         GeometryTestUtils.assertThrows(builder::close, IllegalStateException.class,
1309                 "Unable to close line path: line path is infinite");
1310     }
1311 
1312     @Test
1313     public void testBuilder_close_emptyPath() {
1314         // arrange
1315         final Builder builder = LinePath.builder(TEST_PRECISION);
1316 
1317         // act
1318         final LinePath path = builder.close();
1319 
1320         // assert
1321         Assert.assertEquals(0, path.getElements().size());
1322     }
1323 
1324     @Test
1325     public void testBuilder_close_obtuseTriangle() {
1326         // arrange
1327         final Builder builder = LinePath.builder(TEST_PRECISION);
1328         builder.appendVertices(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(2, 1));
1329 
1330         // act
1331         final LinePath path = builder.close();
1332 
1333         // assert
1334         Assert.assertEquals(3, path.getElements().size());
1335         assertFiniteSegment(path.getElements().get(0), Vector2D.ZERO, Vector2D.of(1, 0));
1336         assertFiniteSegment(path.getElements().get(1), Vector2D.of(1, 0), Vector2D.of(2, 1));
1337         assertFiniteSegment(path.getElements().get(2), Vector2D.of(2, 1), Vector2D.ZERO);
1338     }
1339 
1340     private static void assertFiniteSegment(final LineConvexSubset segment, final Vector2D start, final Vector2D end) {
1341         Assert.assertFalse(segment.isInfinite());
1342         Assert.assertTrue(segment.isFinite());
1343 
1344         EuclideanTestUtils.assertCoordinatesEqual(start, segment.getStartPoint(), TEST_EPS);
1345         EuclideanTestUtils.assertCoordinatesEqual(end, segment.getEndPoint(), TEST_EPS);
1346     }
1347 }