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