1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.RegionLocation;
21 import org.apache.commons.geometry.core.partitioning.Split;
22 import org.apache.commons.geometry.core.partitioning.SplitLocation;
23 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
24 import org.apache.commons.geometry.euclidean.oned.Interval;
25 import org.apache.commons.numbers.core.Precision;
26 import org.junit.jupiter.api.Assertions;
27 import org.junit.jupiter.api.Test;
28
29 class SegmentTest {
30
31 private static final double TEST_EPS = 1e-10;
32
33 private static final Precision.DoubleEquivalence TEST_PRECISION =
34 Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
35
36 @Test
37 void testFromPoints() {
38
39 final Vector2D p1 = Vector2D.of(1, 2);
40 final Vector2D p2 = Vector2D.of(3, 2);
41
42
43 final Segment seg = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
44
45
46 Assertions.assertFalse(seg.isFull());
47 Assertions.assertFalse(seg.isEmpty());
48 Assertions.assertFalse(seg.isInfinite());
49 Assertions.assertTrue(seg.isFinite());
50
51 EuclideanTestUtils.assertCoordinatesEqual(p1, seg.getStartPoint(), TEST_EPS);
52 EuclideanTestUtils.assertCoordinatesEqual(p2, seg.getEndPoint(), TEST_EPS);
53 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 2), seg.getCentroid(), TEST_EPS);
54
55 Assertions.assertEquals(1, seg.getSubspaceStart(), TEST_EPS);
56 Assertions.assertEquals(3, seg.getSubspaceEnd(), TEST_EPS);
57
58 Assertions.assertEquals(2, seg.getSize(), TEST_EPS);
59 }
60
61 @Test
62 void testFromPoints_invalidArgs() {
63
64 final Vector2D p1 = Vector2D.of(0, 2);
65 final Vector2D p2 = Vector2D.of(1e-17, 2);
66
67
68 GeometryTestUtils.assertThrowsWithMessage(() -> {
69 Lines.segmentFromPoints(p1, p1, TEST_PRECISION);
70 }, IllegalArgumentException.class, "Line direction cannot be zero");
71
72 GeometryTestUtils.assertThrowsWithMessage(() -> {
73 Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
74 }, IllegalArgumentException.class, "Line direction cannot be zero");
75 }
76
77 @Test
78 void testFromPoints_givenLine() {
79
80 final Vector2D p1 = Vector2D.of(-1, 2);
81 final Vector2D p2 = Vector2D.of(3, 3);
82
83 final Line line = Lines.fromPointAndDirection(Vector2D.of(1, 0), Vector2D.Unit.PLUS_Y, TEST_PRECISION);
84
85
86 final Segment seg = Lines.segmentFromPoints(line, p2, p1);
87
88
89 Assertions.assertFalse(seg.isFull());
90 Assertions.assertFalse(seg.isEmpty());
91 Assertions.assertFalse(seg.isInfinite());
92 Assertions.assertTrue(seg.isFinite());
93
94 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), seg.getStartPoint(), TEST_EPS);
95 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 3), seg.getEndPoint(), TEST_EPS);
96 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2.5), seg.getCentroid(), TEST_EPS);
97
98 Assertions.assertEquals(2, seg.getSubspaceStart(), TEST_EPS);
99 Assertions.assertEquals(3, seg.getSubspaceEnd(), TEST_EPS);
100
101 Assertions.assertEquals(1, seg.getSize(), TEST_EPS);
102 }
103
104 @Test
105 void testFromPoints_givenLine_singlePoint() {
106
107 final Vector2D p1 = Vector2D.of(-1, 2);
108
109 final Line line = Lines.fromPointAndDirection(Vector2D.of(1, 0), Vector2D.Unit.PLUS_Y, TEST_PRECISION);
110
111
112 final Segment seg = Lines.segmentFromPoints(line, p1, p1);
113
114
115 Assertions.assertFalse(seg.isFull());
116 Assertions.assertFalse(seg.isEmpty());
117 Assertions.assertFalse(seg.isInfinite());
118 Assertions.assertTrue(seg.isFinite());
119
120 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), seg.getStartPoint(), TEST_EPS);
121 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), seg.getEndPoint(), TEST_EPS);
122 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), seg.getCentroid(), TEST_EPS);
123
124 Assertions.assertEquals(2, seg.getSubspaceStart(), TEST_EPS);
125 Assertions.assertEquals(2, seg.getSubspaceEnd(), TEST_EPS);
126
127 Assertions.assertEquals(0, seg.getSize(), TEST_EPS);
128 }
129
130 @Test
131 void testFromPoints_givenLine_invalidArgs() {
132
133 final Vector2D p0 = Vector2D.of(1, 0);
134 final Vector2D p1 = Vector2D.of(2, 0);
135
136 final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION);
137
138
139 GeometryTestUtils.assertThrowsWithMessage(() -> {
140 Lines.segmentFromPoints(line, Vector2D.NaN, p1);
141 }, IllegalArgumentException.class, "Invalid line segment locations: NaN, 2.0");
142
143 GeometryTestUtils.assertThrowsWithMessage(() -> {
144 Lines.segmentFromPoints(line, p0, Vector2D.NaN);
145 }, IllegalArgumentException.class, "Invalid line segment locations: 1.0, NaN");
146
147 GeometryTestUtils.assertThrowsWithMessage(() -> {
148 Lines.segmentFromPoints(line, Vector2D.NEGATIVE_INFINITY, p1);
149 }, IllegalArgumentException.class, "Invalid line segment locations: NaN, 2.0");
150
151 GeometryTestUtils.assertThrowsWithMessage(() -> {
152 Lines.segmentFromPoints(line, p0, Vector2D.POSITIVE_INFINITY);
153 }, IllegalArgumentException.class, "Invalid line segment locations: 1.0, NaN");
154 }
155
156 @Test
157 void testFromLocations() {
158
159 final Line line = Lines.fromPointAndDirection(Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y, TEST_PRECISION);
160
161
162 final Segment seg = Lines.segmentFromLocations(line, -1, 2);
163
164
165 Assertions.assertFalse(seg.isFull());
166 Assertions.assertFalse(seg.isEmpty());
167 Assertions.assertFalse(seg.isInfinite());
168 Assertions.assertTrue(seg.isFinite());
169
170 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -1), seg.getStartPoint(), TEST_EPS);
171 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 2), seg.getEndPoint(), TEST_EPS);
172 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 0.5), seg.getCentroid(), TEST_EPS);
173
174 Assertions.assertEquals(-1, seg.getSubspaceStart(), TEST_EPS);
175 Assertions.assertEquals(2, seg.getSubspaceEnd(), TEST_EPS);
176
177 Assertions.assertEquals(3, seg.getSize(), TEST_EPS);
178 }
179
180 @Test
181 void testFromLocations_reversedLocationOrder() {
182
183 final Line line = Lines.fromPointAndDirection(Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y, TEST_PRECISION);
184
185
186 final Segment seg = Lines.segmentFromLocations(line, 2, -1);
187
188
189 Assertions.assertFalse(seg.isFull());
190 Assertions.assertFalse(seg.isEmpty());
191 Assertions.assertFalse(seg.isInfinite());
192 Assertions.assertTrue(seg.isFinite());
193
194 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -1), seg.getStartPoint(), TEST_EPS);
195 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 2), seg.getEndPoint(), TEST_EPS);
196 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 0.5), seg.getCentroid(), TEST_EPS);
197
198 Assertions.assertEquals(-1, seg.getSubspaceStart(), TEST_EPS);
199 Assertions.assertEquals(2, seg.getSubspaceEnd(), TEST_EPS);
200
201 Assertions.assertEquals(3, seg.getSize(), TEST_EPS);
202 }
203
204 @Test
205 void testFromLocations_singlePoint() {
206
207 final Line line = Lines.fromPointAndDirection(Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y, TEST_PRECISION);
208
209
210 final Segment seg = Lines.segmentFromLocations(line, 1, 1);
211
212
213 Assertions.assertFalse(seg.isFull());
214 Assertions.assertFalse(seg.isEmpty());
215 Assertions.assertFalse(seg.isInfinite());
216 Assertions.assertTrue(seg.isFinite());
217
218 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1), seg.getStartPoint(), TEST_EPS);
219 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1), seg.getEndPoint(), TEST_EPS);
220 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1), seg.getCentroid(), TEST_EPS);
221
222 Assertions.assertEquals(1, seg.getSubspaceStart(), TEST_EPS);
223 Assertions.assertEquals(1, seg.getSubspaceEnd(), TEST_EPS);
224
225 Assertions.assertEquals(0, seg.getSize(), TEST_EPS);
226 }
227
228 @Test
229 void testFromLocations_invalidArgs() {
230
231 final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION);
232
233
234 GeometryTestUtils.assertThrowsWithMessage(() -> {
235 Lines.segmentFromLocations(line, Double.NaN, 2);
236 }, IllegalArgumentException.class, "Invalid line segment locations: NaN, 2.0");
237
238 GeometryTestUtils.assertThrowsWithMessage(() -> {
239 Lines.segmentFromLocations(line, 1, Double.NaN);
240 }, IllegalArgumentException.class, "Invalid line segment locations: 1.0, NaN");
241
242 GeometryTestUtils.assertThrowsWithMessage(() -> {
243 Lines.segmentFromLocations(line, Double.NEGATIVE_INFINITY, 2);
244 }, IllegalArgumentException.class, "Invalid line segment locations: -Infinity, 2.0");
245
246 GeometryTestUtils.assertThrowsWithMessage(() -> {
247 Lines.segmentFromLocations(line, 1, Double.POSITIVE_INFINITY);
248 }, IllegalArgumentException.class, "Invalid line segment locations: 1.0, Infinity");
249 }
250
251 @Test
252 void testGetBounds() {
253
254 final Segment seg = Lines.segmentFromPoints(Vector2D.of(-1, 4), Vector2D.of(2, -2), TEST_PRECISION);
255
256
257 final Bounds2D bounds = seg.getBounds();
258
259
260 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -2), bounds.getMin(), TEST_EPS);
261 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 4), bounds.getMax(), TEST_EPS);
262 }
263
264 @Test
265 void testTransform() {
266
267 final AffineTransformMatrix2D t = AffineTransformMatrix2D.createRotation(0.5 * Math.PI)
268 .translate(Vector2D.Unit.PLUS_X);
269
270 final Segment seg = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
271
272
273 final Segment result = seg.transform(t);
274
275
276 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 0), result.getStartPoint(), TEST_EPS);
277 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), result.getEndPoint(), TEST_EPS);
278 }
279
280 @Test
281 void testTransform_reflection() {
282
283 final AffineTransformMatrix2D t = AffineTransformMatrix2D.createRotation(0.5 * Math.PI)
284 .translate(Vector2D.Unit.PLUS_X)
285 .scale(1, -1);
286
287 final Segment seg = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
288
289
290 final Segment result = seg.transform(t);
291
292
293 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 0), result.getStartPoint(), TEST_EPS);
294 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -1), result.getEndPoint(), TEST_EPS);
295 }
296
297 @Test
298 void testReverse() {
299
300 final Vector2D start = Vector2D.of(1, 2);
301
302 EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (x, y) -> {
303 final Vector2D end = Vector2D.of(x, y).add(start);
304
305 final Segment seg = Lines.segmentFromPoints(start, end, TEST_PRECISION);
306
307
308 final Segment rev = seg.reverse();
309
310
311 Assertions.assertEquals(seg.getSize(), rev.getSize(), TEST_EPS);
312
313 EuclideanTestUtils.assertCoordinatesEqual(seg.getLine().getOrigin(), rev.getLine().getOrigin(), TEST_EPS);
314 Assertions.assertEquals(-1, seg.getLine().getDirection().dot(rev.getLine().getDirection()), TEST_EPS);
315
316 EuclideanTestUtils.assertCoordinatesEqual(seg.getEndPoint(), rev.getStartPoint(), TEST_EPS);
317 EuclideanTestUtils.assertCoordinatesEqual(seg.getStartPoint(), rev.getEndPoint(), TEST_EPS);
318 });
319 }
320
321 @Test
322 void testClosest() {
323
324 final Vector2D p1 = Vector2D.of(0, -1);
325 final Vector2D p2 = Vector2D.of(0, 1);
326 final Segment seg = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
327
328
329 EuclideanTestUtils.assertCoordinatesEqual(p1, seg.closest(p1), TEST_EPS);
330 EuclideanTestUtils.assertCoordinatesEqual(p1, seg.closest(Vector2D.of(0, -2)), TEST_EPS);
331 EuclideanTestUtils.assertCoordinatesEqual(p1, seg.closest(Vector2D.of(2, -2)), TEST_EPS);
332 EuclideanTestUtils.assertCoordinatesEqual(p1, seg.closest(Vector2D.of(-1, -1)), TEST_EPS);
333
334 EuclideanTestUtils.assertCoordinatesEqual(p2, seg.closest(p2), TEST_EPS);
335 EuclideanTestUtils.assertCoordinatesEqual(p2, seg.closest(Vector2D.of(0, 2)), TEST_EPS);
336 EuclideanTestUtils.assertCoordinatesEqual(p2, seg.closest(Vector2D.of(-2, 2)), TEST_EPS);
337 EuclideanTestUtils.assertCoordinatesEqual(p2, seg.closest(Vector2D.of(-1, 1)), TEST_EPS);
338
339 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, seg.closest(Vector2D.ZERO), TEST_EPS);
340 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 0.5), seg.closest(Vector2D.of(1, 0.5)), TEST_EPS);
341 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -0.5), seg.closest(Vector2D.of(-2, -0.5)), TEST_EPS);
342 }
343
344 @Test
345 void testClassify() {
346
347 final Segment seg = Lines.segmentFromPoints(Vector2D.of(1, 1), Vector2D.of(3, 1), TEST_PRECISION);
348
349
350 EuclideanTestUtils.assertRegionLocation(seg, RegionLocation.OUTSIDE,
351 Vector2D.of(2, 2), Vector2D.of(2, 0),
352 Vector2D.of(0, 1), Vector2D.of(4, 1));
353
354 EuclideanTestUtils.assertRegionLocation(seg, RegionLocation.BOUNDARY,
355 Vector2D.of(1, 1), Vector2D.of(3, 1),
356 Vector2D.of(1 + 1e-16, 1), Vector2D.of(3, 1 - 1e-12));
357
358 EuclideanTestUtils.assertRegionLocation(seg, RegionLocation.INSIDE, Vector2D.of(2, 1));
359 }
360
361 @Test
362 void testSplit() {
363
364 final Vector2D p0 = Vector2D.of(1, 1);
365 final Vector2D p1 = Vector2D.of(3, 1);
366 final Vector2D mid = p0.lerp(p1, 0.5);
367 final Vector2D low = Vector2D.of(0, 1);
368 final Vector2D high = Vector2D.of(3, 1);
369
370 final Vector2D delta = Vector2D.of(1e-11, 1e-11);
371
372 final Segment seg = Lines.segmentFromPoints(Vector2D.of(1, 1), Vector2D.of(3, 1), TEST_PRECISION);
373
374
375
376
377 checkSplit(seg.split(Lines.fromPointAndAngle(Vector2D.of(2, 2), 0, TEST_PRECISION)),
378 null, null,
379 p0, p1);
380 checkSplit(seg.split(Lines.fromPointAndAngle(Vector2D.of(2, 2), Math.PI, TEST_PRECISION)),
381 p0, p1,
382 null, null);
383
384
385 checkSplit(seg.split(Lines.fromPointAndAngle(p0.add(delta), 1e-20, TEST_PRECISION)),
386 null, null,
387 null, null);
388
389
390 checkSplit(seg.split(Lines.fromPointAndAngle(mid, 1, TEST_PRECISION)),
391 p0, mid,
392 mid, p1);
393 checkSplit(seg.split(Lines.fromPointAndAngle(mid, -1, TEST_PRECISION)),
394 mid, p1,
395 p0, mid);
396
397
398 checkSplit(seg.split(Lines.fromPointAndAngle(p0.subtract(delta), 1, TEST_PRECISION)),
399 null, null,
400 p0, p1);
401 checkSplit(seg.split(Lines.fromPointAndAngle(p0.add(delta), -1, TEST_PRECISION)),
402 p0, p1,
403 null, null);
404
405
406 checkSplit(seg.split(Lines.fromPointAndAngle(p1.subtract(delta), 1, TEST_PRECISION)),
407 p0, p1,
408 null, null);
409 checkSplit(seg.split(Lines.fromPointAndAngle(p1.add(delta), -1, TEST_PRECISION)),
410 null, null,
411 p0, p1);
412
413
414 checkSplit(seg.split(Lines.fromPointAndAngle(low, 1, TEST_PRECISION)),
415 null, null,
416 p0, p1);
417 checkSplit(seg.split(Lines.fromPointAndAngle(low, -1, TEST_PRECISION)),
418 p0, p1,
419 null, null);
420
421
422 checkSplit(seg.split(Lines.fromPointAndAngle(high, 1, TEST_PRECISION)),
423 p0, p1,
424 null, null);
425 checkSplit(seg.split(Lines.fromPointAndAngle(high, -1, TEST_PRECISION)),
426 null, null,
427 p0, p1);
428 }
429
430 @Test
431 void testSplit_pointsOnSplitterWithLineIntersection() {
432
433
434
435
436
437 final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-5);
438
439 final Segment seg = Lines.segmentFromPoints(Vector2D.of(1, 1e-8), Vector2D.of(1.01, 1e-6), precision);
440
441 final Line splitter = Lines.fromPointAndAngle(Vector2D.ZERO, 0, precision);
442
443
444 final Split<LineConvexSubset> split = seg.split(splitter);
445
446
447 Assertions.assertEquals(SplitLocation.NEITHER, split.getLocation());
448
449 Assertions.assertNull(split.getMinus());
450 Assertions.assertNull(split.getPlus());
451 }
452
453 @Test
454 void testGetInterval() {
455
456 final Segment seg = Lines.segmentFromPoints(Vector2D.of(2, -1), Vector2D.of(2, 2), TEST_PRECISION);
457
458
459 final Interval interval = seg.getInterval();
460
461
462 Assertions.assertEquals(-1, interval.getMin(), TEST_EPS);
463 Assertions.assertEquals(2, interval.getMax(), TEST_EPS);
464
465 Assertions.assertSame(seg.getLine().getPrecision(), interval.getMinBoundary().getPrecision());
466 }
467
468 @Test
469 void testGetInterval_singlePoint() {
470
471 final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION);
472 final Segment seg = Lines.segmentFromLocations(line, 1, 1);
473
474
475 final Interval interval = seg.getInterval();
476
477
478 Assertions.assertEquals(1, interval.getMin(), TEST_EPS);
479 Assertions.assertEquals(1, interval.getMax(), TEST_EPS);
480 Assertions.assertEquals(0, interval.getSize(), TEST_EPS);
481
482 Assertions.assertSame(seg.getLine().getPrecision(), interval.getMinBoundary().getPrecision());
483 }
484
485 @Test
486 void testToString() {
487
488 final Segment seg = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
489
490
491 final String str = seg.toString();
492
493
494 GeometryTestUtils.assertContains("Segment[startPoint= (0", str);
495 GeometryTestUtils.assertContains(", endPoint= (1", str);
496 }
497
498 private static void checkSplit(final Split<LineConvexSubset> split, final Vector2D minusStart, final Vector2D minusEnd,
499 final Vector2D plusStart, final Vector2D plusEnd) {
500
501 final Segment minus = (Segment) split.getMinus();
502 if (minusStart != null) {
503 EuclideanTestUtils.assertCoordinatesEqual(minusStart, minus.getStartPoint(), TEST_EPS);
504 EuclideanTestUtils.assertCoordinatesEqual(minusEnd, minus.getEndPoint(), TEST_EPS);
505 } else {
506 Assertions.assertNull(minus);
507 }
508
509 final Segment plus = (Segment) split.getPlus();
510 if (plusStart != null) {
511 EuclideanTestUtils.assertCoordinatesEqual(plusStart, plus.getStartPoint(), TEST_EPS);
512 EuclideanTestUtils.assertCoordinatesEqual(plusEnd, plus.getEndPoint(), TEST_EPS);
513 } else {
514 Assertions.assertNull(plus);
515 }
516 }
517 }