1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.Random;
24
25 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
26 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
27 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
28 import org.apache.commons.geometry.euclidean.twod.Line;
29 import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
30 import org.apache.commons.geometry.euclidean.twod.Lines;
31 import org.apache.commons.geometry.euclidean.twod.Segment;
32 import org.apache.commons.geometry.euclidean.twod.Vector2D;
33 import org.apache.commons.geometry.euclidean.twod.path.AbstractLinePathConnector.ConnectableLineSubset;
34 import org.apache.commons.numbers.angle.PlaneAngleRadians;
35 import org.junit.Assert;
36 import org.junit.Test;
37
38 public class AbstractLinePathConnectorTest {
39
40 private static final double TEST_EPS = 1e-10;
41
42 private static final DoublePrecisionContext TEST_PRECISION =
43 new EpsilonDoublePrecisionContext(TEST_EPS);
44
45 private static final Line Y_AXIS = Lines.fromPointAndAngle(Vector2D.ZERO, PlaneAngleRadians.PI_OVER_TWO,
46 TEST_PRECISION);
47
48 private final TestConnector connector = new TestConnector();
49
50 @Test
51 public void testConnectAll_emptyCollection() {
52
53 final List<LinePath> paths = connector.connectAll(Collections.emptyList());
54
55
56 Assert.assertEquals(0, paths.size());
57 }
58
59 @Test
60 public void testConnectAll_singleInfiniteLine() {
61
62 final LineConvexSubset segment = Y_AXIS.span();
63
64
65 final List<LinePath> paths = connector.connectAll(Collections.singletonList(segment));
66
67
68 Assert.assertEquals(1, paths.size());
69
70 final LinePath path = paths.get(0);
71 Assert.assertEquals(1, path.getElements().size());
72 Assert.assertSame(segment, path.getStart());
73 }
74
75 @Test
76 public void testConnectAll_singleHalfInfiniteLine_noEndPoint() {
77
78 final LineConvexSubset segment = Y_AXIS.rayFrom(Vector2D.ZERO);
79
80
81 final List<LinePath> paths = connector.connectAll(Collections.singletonList(segment));
82
83
84 Assert.assertEquals(1, paths.size());
85
86 final LinePath path = paths.get(0);
87 Assert.assertEquals(1, path.getElements().size());
88 Assert.assertSame(segment, path.getStart());
89 }
90
91 @Test
92 public void testConnectAll_singleHalfInfiniteLine_noStartPoint() {
93
94 final LineConvexSubset segment = Y_AXIS.reverseRayTo(Vector2D.ZERO);
95
96
97 final List<LinePath> paths = connector.connectAll(Collections.singletonList(segment));
98
99
100 Assert.assertEquals(1, paths.size());
101
102 final LinePath path = paths.get(0);
103 Assert.assertEquals(1, path.getElements().size());
104 Assert.assertSame(segment, path.getStart());
105 }
106
107 @Test
108 public void testConnectAll_disjointSegments() {
109
110 final LineConvexSubset a = Y_AXIS.segment(Vector2D.of(0, 1), Vector2D.of(0, 2));
111 final LineConvexSubset b = Y_AXIS.segment(Vector2D.of(0, -1), Vector2D.ZERO);
112
113 final List<LineConvexSubset> segments = Arrays.asList(a, b);
114
115
116 final List<LinePath> paths = connector.connectAll(segments);
117
118
119 Assert.assertEquals(2, paths.size());
120
121 assertFinitePath(paths.get(0), Vector2D.of(0, -1), Vector2D.ZERO);
122 assertFinitePath(paths.get(1), Vector2D.of(0, 1), Vector2D.of(0, 2));
123 }
124
125 @Test
126 public void testConnectAll_singleClosedPath() {
127
128 final LinePath input = LinePath.builder(TEST_PRECISION)
129 .appendVertices(Vector2D.of(1, 1), Vector2D.ZERO, Vector2D.of(1, 0))
130 .close();
131
132 final List<LineConvexSubset> segments = new ArrayList<>(input.getElements());
133 shuffle(segments);
134
135
136 final List<LinePath> paths = connector.connectAll(segments);
137
138
139 Assert.assertEquals(1, paths.size());
140
141 assertFinitePath(paths.get(0),
142 Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(1, 1), Vector2D.ZERO);
143 }
144
145 @Test
146 public void testConnectAll_multipleClosedPaths() {
147
148 final LinePath a = LinePath.builder(TEST_PRECISION)
149 .appendVertices(Vector2D.of(1, 1), Vector2D.ZERO, Vector2D.of(1, 0))
150 .close();
151
152 final LinePath b = LinePath.builder(TEST_PRECISION)
153 .appendVertices(Vector2D.of(0, 1), Vector2D.of(-1, 0), Vector2D.of(-0.5, 0))
154 .close();
155
156 final LinePath c = LinePath.builder(TEST_PRECISION)
157 .appendVertices(Vector2D.of(1, 3), Vector2D.of(0, 2), Vector2D.of(1, 2))
158 .close();
159
160 final List<LineConvexSubset> segments = new ArrayList<>();
161 segments.addAll(a.getElements());
162 segments.addAll(b.getElements());
163 segments.addAll(c.getElements());
164
165 shuffle(segments);
166
167
168 final List<LinePath> paths = connector.connectAll(segments);
169
170
171 Assert.assertEquals(3, paths.size());
172
173 assertFinitePath(paths.get(0),
174 Vector2D.of(-1, 0), Vector2D.of(-0.5, 0), Vector2D.of(0, 1), Vector2D.of(-1, 0));
175
176 assertFinitePath(paths.get(1),
177 Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(1, 1), Vector2D.ZERO);
178
179 assertFinitePath(paths.get(2),
180 Vector2D.of(0, 2), Vector2D.of(1, 2), Vector2D.of(1, 3), Vector2D.of(0, 2));
181 }
182
183 @Test
184 public void testConnectAll_singleOpenPath() {
185
186 final LinePath input = LinePath.builder(TEST_PRECISION)
187 .appendVertices(Vector2D.of(1, 1), Vector2D.ZERO, Vector2D.of(1, 0))
188 .build();
189
190 final List<LineConvexSubset> segments = new ArrayList<>(input.getElements());
191 shuffle(segments);
192
193
194 final List<LinePath> paths = connector.connectAll(segments);
195
196
197 Assert.assertEquals(1, paths.size());
198
199 assertFinitePath(paths.get(0),
200 Vector2D.of(1, 1), Vector2D.ZERO, Vector2D.of(1, 0));
201 }
202
203 @Test
204 public void testConnectAll_mixOfOpenConnectedAndInfinite() {
205
206 final LineConvexSubset inputYInf = Y_AXIS.reverseRayTo(Vector2D.ZERO);
207 final LineConvexSubset inputXInf = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.MINUS_X, TEST_PRECISION)
208 .rayFrom(Vector2D.ZERO);
209
210 final LinePath closedPath = LinePath.builder(TEST_PRECISION)
211 .appendVertices(Vector2D.of(0, 2), Vector2D.of(1, 2), Vector2D.of(1, 3))
212 .close();
213
214 final LinePath openPath = LinePath.builder(TEST_PRECISION)
215 .appendVertices(Vector2D.of(-1, 3), Vector2D.of(0, 1), Vector2D.of(1, 1))
216 .build();
217
218 final List<LineConvexSubset> segments = new ArrayList<>();
219 segments.add(inputYInf);
220 segments.add(inputXInf);
221 segments.addAll(closedPath.getElements());
222 segments.addAll(openPath.getElements());
223
224 shuffle(segments);
225
226
227 final List<LinePath> paths = connector.connectAll(segments);
228
229
230 Assert.assertEquals(3, paths.size());
231
232 assertFinitePath(paths.get(0),
233 Vector2D.of(-1, 3), Vector2D.of(0, 1), Vector2D.of(1, 1));
234
235 final LinePath infPath = paths.get(1);
236 Assert.assertTrue(infPath.isInfinite());
237 Assert.assertEquals(2, infPath.getElements().size());
238 Assert.assertSame(inputYInf, infPath.getElements().get(0));
239 Assert.assertSame(inputXInf, infPath.getElements().get(1));
240
241 assertFinitePath(paths.get(2),
242 Vector2D.of(0, 2), Vector2D.of(1, 2), Vector2D.of(1, 3), Vector2D.of(0, 2));
243 }
244
245 @Test
246 public void testConnectAll_pathWithSinglePoint() {
247
248 final Vector2D p0 = Vector2D.ZERO;
249
250 final List<LineConvexSubset> segments = Collections.singletonList(Lines.fromPointAndAngle(p0, 0, TEST_PRECISION).segment(p0, p0));
251
252
253 final List<LinePath> paths = connector.connectAll(segments);
254
255
256 Assert.assertEquals(1, paths.size());
257
258 assertFinitePath(paths.get(0), p0, p0);
259 }
260
261 @Test
262 public void testConnectAll_pathWithPointLikeConnectedSegments() {
263
264 final Vector2D p0 = Vector2D.ZERO;
265 final Vector2D p1 = Vector2D.of(1, 0);
266 final Vector2D p2 = Vector2D.of(1, 1);
267
268 final Vector2D almostP0 = Vector2D.of(-1e-20, -1e-20);
269 final Vector2D almostP1 = Vector2D.of(1 - 1e-15, 0);
270
271 final LinePath input = LinePath.builder(TEST_PRECISION)
272 .appendVertices(p0, p1)
273 .append(Lines.fromPointAndAngle(p1, 0.25 * PlaneAngleRadians.PI, TEST_PRECISION).segment(p1, p1))
274 .append(Lines.fromPointAndAngle(p1, -0.25 * PlaneAngleRadians.PI, TEST_PRECISION).segment(almostP1, almostP1))
275 .append(p2)
276 .append(p0)
277 .append(Lines.fromPointAndAngle(Vector2D.ZERO, -PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION)
278 .segment(almostP0, almostP0))
279 .build();
280
281 final List<LineConvexSubset> segments = new ArrayList<>(input.getElements());
282 shuffle(segments);
283
284
285 final List<LinePath> paths = connector.connectAll(segments);
286
287
288 Assert.assertEquals(1, paths.size());
289
290 assertFinitePath(paths.get(0), p0, p1, almostP1, p1, p2, p0, almostP0);
291 }
292
293 @Test
294 public void testConnectAll_flatLineRegion() {
295
296 final Vector2D p0 = Vector2D.ZERO;
297 final Vector2D p1 = Vector2D.of(1, 0);
298
299 final Segment seg0 = Lines.segmentFromPoints(p0, p1, TEST_PRECISION);
300 final Segment seg1 = Lines.segmentFromPoints(p1, p0, TEST_PRECISION);
301 final LineConvexSubset seg2 = Lines.fromPointAndAngle(p1, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION).segment(p1, p1);
302 final LineConvexSubset seg3 = Lines.fromPointAndAngle(p0, -PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION).segment(p0, p0);
303
304 final List<LineConvexSubset> segments = new ArrayList<>(Arrays.asList(seg0, seg1, seg2, seg3));
305 shuffle(segments);
306
307
308 final List<LinePath> paths = connector.connectAll(segments);
309
310
311 Assert.assertEquals(1, paths.size());
312
313 final LinePath path = paths.get(0);
314 Assert.assertSame(seg0, path.getElements().get(0));
315 Assert.assertSame(seg2, path.getElements().get(1));
316 Assert.assertSame(seg1, path.getElements().get(2));
317 Assert.assertSame(seg3, path.getElements().get(3));
318 }
319
320 @Test
321 public void testConnectAll_singlePointRegion() {
322
323 final Vector2D p0 = Vector2D.of(1, 0);
324
325 final LineConvexSubset seg0 = Lines.fromPointAndAngle(p0, 0.0, TEST_PRECISION).segment(p0, p0);
326 final LineConvexSubset seg1 = Lines.fromPointAndAngle(p0, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION).segment(p0, p0);
327 final LineConvexSubset seg2 = Lines.fromPointAndAngle(p0, PlaneAngleRadians.PI, TEST_PRECISION).segment(p0, p0);
328 final LineConvexSubset seg3 = Lines.fromPointAndAngle(p0, -PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION).segment(p0, p0);
329
330 final List<LineConvexSubset> segments = new ArrayList<>(Arrays.asList(seg0, seg1, seg2, seg3));
331 shuffle(segments);
332
333
334 final List<LinePath> paths = connector.connectAll(segments);
335
336
337 Assert.assertEquals(1, paths.size());
338
339 final LinePath path = paths.get(0);
340 Assert.assertSame(seg2, path.getElements().get(0));
341 Assert.assertSame(seg3, path.getElements().get(1));
342 Assert.assertSame(seg0, path.getElements().get(2));
343 Assert.assertSame(seg1, path.getElements().get(3));
344 }
345
346 @Test
347 public void testConnectAll_pathWithPointLikeUnconnectedSegments() {
348
349 final Vector2D p0 = Vector2D.ZERO;
350 final Vector2D p1 = Vector2D.of(1, 0);
351
352 final LineConvexSubset seg0 = Lines.fromPointAndAngle(p1, 0.0, TEST_PRECISION).segment(p1, p1);
353 final LineConvexSubset seg1 = Lines.fromPointAndAngle(p1, 0.25 * PlaneAngleRadians.PI, TEST_PRECISION).segment(p1, p1);
354 final LineConvexSubset seg2 = Lines.fromPointAndAngle(p0, 0, TEST_PRECISION).segment(p0, p0);
355
356 final List<LineConvexSubset> segments = new ArrayList<>(Arrays.asList(seg0, seg1, seg2));
357
358 shuffle(segments);
359
360
361 final List<LinePath> paths = connector.connectAll(segments);
362
363
364 Assert.assertEquals(2, paths.size());
365
366 final LinePath path0 = paths.get(0);
367 Assert.assertEquals(1, path0.getElements().size());
368 Assert.assertSame(seg2, path0.getElements().get(0));
369
370 final LinePath path1 = paths.get(1);
371 Assert.assertEquals(2, path1.getElements().size());
372 Assert.assertSame(seg0, path1.getElements().get(0));
373 Assert.assertSame(seg1, path1.getElements().get(1));
374 }
375
376 @Test
377 public void testConnectAll_pathStartingWithPoint() {
378
379 final Vector2D p0 = Vector2D.ZERO;
380 final Vector2D p1 = Vector2D.of(1, 0);
381 final Vector2D p2 = Vector2D.of(1, 1);
382
383 final LineConvexSubset seg0 = Lines.fromPointAndAngle(p0, PlaneAngleRadians.PI, TEST_PRECISION).segment(p0, p0);
384 final LineConvexSubset seg1 = Lines.segmentFromPoints(p0, p1, TEST_PRECISION);
385 final LineConvexSubset seg2 = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
386
387 final List<LineConvexSubset> segments = new ArrayList<>(Arrays.asList(seg0, seg1, seg2));
388
389 shuffle(segments);
390
391
392 final List<LinePath> paths = connector.connectAll(segments);
393
394
395 Assert.assertEquals(1, paths.size());
396
397 final LinePath path = paths.get(0);
398 Assert.assertSame(seg0, path.getElements().get(0));
399 Assert.assertSame(seg1, path.getElements().get(1));
400 Assert.assertSame(seg2, path.getElements().get(2));
401 }
402
403 @Test
404 public void testConnectAll_intersectingPaths() {
405
406 final LinePath a = LinePath.builder(TEST_PRECISION)
407 .appendVertices(Vector2D.of(-1, 1), Vector2D.of(0.5, 0), Vector2D.of(-1, -1))
408 .build();
409
410 final LinePath b = LinePath.builder(TEST_PRECISION)
411 .appendVertices(Vector2D.of(1, 1), Vector2D.of(-0.5, 0), Vector2D.of(1, -1))
412 .build();
413
414 final List<LineConvexSubset> segments = new ArrayList<>();
415 segments.addAll(a.getElements());
416 segments.addAll(b.getElements());
417
418 shuffle(segments);
419
420
421 final List<LinePath> paths = connector.connectAll(segments);
422
423
424 Assert.assertEquals(2, paths.size());
425
426 assertFinitePath(paths.get(0),
427 Vector2D.of(-1, 1), Vector2D.of(0.5, 0), Vector2D.of(-1, -1));
428
429 assertFinitePath(paths.get(1),
430 Vector2D.of(1, 1), Vector2D.of(-0.5, 0), Vector2D.of(1, -1));
431 }
432
433 @Test
434 public void testInstancesCanBeReused() {
435
436 final LineConvexSubset a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
437 final LineConvexSubset b = Lines.segmentFromPoints(Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
438
439
440 final List<LinePath> firstPaths = connector.connectAll(Collections.singletonList(a));
441 final List<LinePath> secondPaths = connector.connectAll(Collections.singletonList(b));
442
443
444 Assert.assertEquals(1, firstPaths.size());
445 Assert.assertEquals(1, secondPaths.size());
446
447 Assert.assertSame(a, firstPaths.get(0).getElements().get(0));
448 Assert.assertSame(b, secondPaths.get(0).getElements().get(0));
449 }
450
451 @Test
452 public void testAdd() {
453
454 final LineConvexSubset a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
455 final LineConvexSubset b = Lines.segmentFromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), TEST_PRECISION);
456 final LineConvexSubset c = Lines.segmentFromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(2, 0), TEST_PRECISION);
457
458
459 connector.add(Arrays.asList(a, b));
460 connector.add(Collections.singletonList(c));
461
462 final List<LinePath> paths = connector.connectAll();
463
464
465 Assert.assertEquals(2, paths.size());
466
467 assertFinitePath(paths.get(0), Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.of(2, 0));
468 assertFinitePath(paths.get(1), Vector2D.Unit.PLUS_X, Vector2D.of(1, 1));
469 }
470
471 @Test
472 public void testConnect() {
473
474 final LineConvexSubset a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
475 final LineConvexSubset b = Lines.segmentFromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), TEST_PRECISION);
476 final LineConvexSubset c = Lines.segmentFromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(2, 0), TEST_PRECISION);
477
478
479 connector.connect(Arrays.asList(a, b));
480 connector.connect(Collections.singletonList(c));
481
482 final List<LinePath> paths = connector.connectAll();
483
484
485 Assert.assertEquals(2, paths.size());
486
487 assertFinitePath(paths.get(0), Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.of(1, 1));
488 assertFinitePath(paths.get(1), Vector2D.Unit.PLUS_X, Vector2D.of(2, 0));
489 }
490
491 @Test
492 public void testConnectableSegment_hashCode() {
493
494 final LineConvexSubset segA = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
495 final LineConvexSubset segB = Lines.segmentFromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), TEST_PRECISION);
496
497 final ConnectableLineSubset a = new ConnectableLineSubset(segA);
498
499
500 final int hash = a.hashCode();
501
502
503 Assert.assertEquals(hash, a.hashCode());
504
505 Assert.assertNotEquals(hash, new ConnectableLineSubset(segB).hashCode());
506 Assert.assertNotEquals(hash, new ConnectableLineSubset(Vector2D.Unit.PLUS_X).hashCode());
507
508 Assert.assertEquals(hash, new ConnectableLineSubset(segA).hashCode());
509 }
510
511 @Test
512 public void testConnectableSegment_equals() {
513
514 final LineConvexSubset segA = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
515 final LineConvexSubset segB = Lines.segmentFromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), TEST_PRECISION);
516
517 final ConnectableLineSubset a = new ConnectableLineSubset(segA);
518
519
520 Assert.assertEquals(a, a);
521
522 Assert.assertFalse(a.equals(null));
523 Assert.assertFalse(a.equals(new Object()));
524
525 Assert.assertNotEquals(a, new ConnectableLineSubset(segB));
526 Assert.assertNotEquals(a, new ConnectableLineSubset(Vector2D.Unit.PLUS_X));
527
528 Assert.assertEquals(a, new ConnectableLineSubset(segA));
529 }
530
531 private static List<LineConvexSubset> shuffle(final List<LineConvexSubset> segments) {
532 return shuffle(segments, 1);
533 }
534
535 private static List<LineConvexSubset> shuffle(final List<LineConvexSubset> segments, final int seed) {
536 Collections.shuffle(segments, new Random(seed));
537
538 return segments;
539 }
540
541 private static void assertFinitePath(final LinePath path, final Vector2D... vertices) {
542 Assert.assertFalse(path.isInfinite());
543 Assert.assertTrue(path.isFinite());
544
545 assertPathVertices(path, vertices);
546 }
547
548 private static void assertPathVertices(final LinePath path, final Vector2D... vertices) {
549 final List<Vector2D> expectedVertices = Arrays.asList(vertices);
550 final List<Vector2D> actualVertices = path.getVertexSequence();
551
552 final String msg = "Expected path vertices to equal " + expectedVertices + " but was " + actualVertices;
553 Assert.assertEquals(msg, expectedVertices.size(), actualVertices.size());
554
555 for (int i = 0; i < expectedVertices.size(); ++i) {
556 EuclideanTestUtils.assertCoordinatesEqual(expectedVertices.get(i), actualVertices.get(i), TEST_EPS);
557 }
558 }
559
560 private static class TestConnector extends AbstractLinePathConnector {
561
562 @Override
563 protected ConnectableLineSubset selectConnection(final ConnectableLineSubset incoming, final List<ConnectableLineSubset> outgoing) {
564
565 return outgoing.get(0);
566 }
567 }
568 }