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.threed.line;
18  
19  import org.apache.commons.geometry.core.GeometryTestUtils;
20  import org.apache.commons.geometry.core.Transform;
21  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
22  import org.apache.commons.geometry.euclidean.oned.Vector1D;
23  import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
24  import org.apache.commons.geometry.euclidean.threed.Vector3D;
25  import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
26  import org.apache.commons.numbers.angle.Angle;
27  import org.apache.commons.numbers.core.Precision;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.Test;
30  
31  class Line3DTest {
32  
33      private static final double TEST_EPS = 1e-10;
34  
35      private static final Precision.DoubleEquivalence TEST_PRECISION =
36              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
37  
38      @Test
39      void testFromPointAndDirection() {
40          // arrange
41          final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(-1, 1, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
42  
43          // act/assert
44          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 0), line.getOrigin(), TEST_EPS);
45          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.PLUS_Y, line.getDirection(), TEST_EPS);
46          Assertions.assertSame(TEST_PRECISION, line.getPrecision());
47      }
48  
49      @Test
50      void testFromPointAndDirection_normalizesDirection() {
51          // arrange
52          final Line3D line = Lines3D.fromPointAndDirection(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION);
53  
54          // act/assert
55          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, line.getOrigin(), TEST_EPS);
56  
57          final double invSqrt3 = 1.0 / Math.sqrt(3);
58          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(invSqrt3, invSqrt3, invSqrt3), line.getDirection(), TEST_EPS);
59          Assertions.assertSame(TEST_PRECISION, line.getPrecision());
60      }
61  
62      @Test
63      void testFromPointAndDirection_illegalDirectionNorm() {
64          // act/assert
65          GeometryTestUtils.assertThrowsWithMessage(() -> {
66              Lines3D.fromPointAndDirection(Vector3D.ZERO, Vector3D.ZERO, TEST_PRECISION);
67          }, IllegalArgumentException.class, "Line direction cannot be zero");
68  
69          GeometryTestUtils.assertThrowsWithMessage(() -> {
70              Lines3D.fromPointAndDirection(Vector3D.ZERO, Vector3D.of(1e-12, 1e-12, 1e-12), TEST_PRECISION);
71          }, IllegalArgumentException.class, "Line direction cannot be zero");
72      }
73  
74      @Test
75      void testFromPoints() {
76          // arrange
77          final Line3D line = Lines3D.fromPoints(Vector3D.of(-1, 1, 0), Vector3D.of(-1, 7, 0), TEST_PRECISION);
78  
79          // act/assert
80          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 0), line.getOrigin(), TEST_EPS);
81          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.PLUS_Y, line.getDirection(), TEST_EPS);
82          Assertions.assertSame(TEST_PRECISION, line.getPrecision());
83      }
84  
85      @Test
86      void testFromPoints_pointsTooClose() {
87          // act/assert
88          Assertions.assertThrows(IllegalArgumentException.class, () -> Lines3D.fromPoints(Vector3D.of(1, 1, 1),
89                  Vector3D.of(1, 1, 1 + 1e-16), TEST_PRECISION));
90      }
91  
92      @Test
93      void testTransform() {
94          // arrange
95          final Vector3D pt = Vector3D.of(1, 2, 3);
96          final Line3D line = Lines3D.fromPointAndDirection(pt, Vector3D.of(1, 1, 1), TEST_PRECISION);
97  
98          final AffineTransformMatrix3D mat = AffineTransformMatrix3D.createRotation(pt,
99                  QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, Angle.PI_OVER_TWO));
100 
101         // act
102         final Line3D result = line.transform(mat);
103 
104         // assert
105         Assertions.assertTrue(result.contains(pt));
106         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, -1).normalize(), result.getDirection(), TEST_EPS);
107     }
108 
109     @Test
110     void testTransform_reflectionInOneAxis() {
111         // arrange
112         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 1), TEST_PRECISION);
113 
114         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(v.getX(), v.getY(), -v.getZ()));
115 
116         // act
117         final Line3D result = line.transform(transform);
118 
119         // assert
120         Assertions.assertTrue(result.contains(Vector3D.of(1, 0, 0)));
121         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, -1).normalize(), result.getDirection(), TEST_EPS);
122     }
123 
124     @Test
125     void testTransform_reflectionInTwoAxes() {
126         // arrange
127         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 1), TEST_PRECISION);
128 
129         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(v.getX(), -v.getY(), -v.getZ()));
130 
131         // act
132         final Line3D result = line.transform(transform);
133 
134         // assert
135         Assertions.assertTrue(result.contains(Vector3D.of(1, 0, 0)));
136         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, -1, -1).normalize(), result.getDirection(), TEST_EPS);
137     }
138 
139     @Test
140     void testTransform_reflectionInThreeAxes() {
141         // arrange
142         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 1), TEST_PRECISION);
143 
144         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(Vector3D::negate);
145 
146         // act
147         final Line3D result = line.transform(transform);
148 
149         // assert
150         Assertions.assertTrue(result.contains(Vector3D.of(-1, 0, 0)));
151         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, -1, -1).normalize(), result.getDirection(), TEST_EPS);
152     }
153 
154     @Test
155     void testSubspaceTransform() {
156         // arrange
157         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 0), TEST_PRECISION);
158 
159         final Transform<Vector3D> transform = AffineTransformMatrix3D.identity()
160                 .scale(2, 1, 1)
161                 .translate(0.5, 1, 0)
162                 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, Angle.PI_OVER_TWO));
163 
164         // act
165         final Line3D.SubspaceTransform result = line.subspaceTransform(transform);
166 
167         // assert
168         final Line3D tLine = result.getLine();
169         final Transform<Vector1D> tSub = result.getTransform();
170 
171         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 0), tLine.getOrigin(), TEST_EPS);
172         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, -1), tLine.getDirection(), TEST_EPS);
173 
174         Assertions.assertEquals(0.5, tSub.apply(Vector1D.ZERO).getX(), TEST_EPS);
175         Assertions.assertEquals(4.5, tSub.apply(Vector1D.of(2)).getX(), TEST_EPS);
176     }
177 
178     @Test
179     void testAbscissa() {
180         // arrange
181         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(0, 0, -1), Vector3D.of(4, 3, 0), TEST_PRECISION);
182 
183         // act/assert
184         Assertions.assertEquals(0.0, line.abscissa(line.getOrigin()), TEST_EPS);
185 
186         Assertions.assertEquals(5.0, line.abscissa(Vector3D.of(4, 3, 0)), TEST_EPS);
187         Assertions.assertEquals(5.0, line.abscissa(Vector3D.of(4, 3, 10)), TEST_EPS);
188 
189         Assertions.assertEquals(-5.0, line.abscissa(Vector3D.of(-4, -3, 0)), TEST_EPS);
190         Assertions.assertEquals(-5.0, line.abscissa(Vector3D.of(-4, -3, -10)), TEST_EPS);
191     }
192 
193     @Test
194     void testToSubspace() {
195         // arrange
196         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(0, 0, -1), Vector3D.of(4, 3, 0), TEST_PRECISION);
197 
198         // act/assert
199         Assertions.assertEquals(0.0, line.toSubspace(line.getOrigin()).getX(), TEST_EPS);
200 
201         Assertions.assertEquals(5.0, line.toSubspace(Vector3D.of(4, 3, -1)).getX(), TEST_EPS);
202         Assertions.assertEquals(5.0, line.toSubspace(Vector3D.of(4, 3, 10)).getX(), TEST_EPS);
203 
204         Assertions.assertEquals(-5.0, line.toSubspace(Vector3D.of(-4, -3, -1)).getX(), TEST_EPS);
205         Assertions.assertEquals(-5.0, line.toSubspace(Vector3D.of(-4, -3, -10)).getX(), TEST_EPS);
206     }
207 
208     @Test
209     void testPointAt() {
210         // arrange
211         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(0, 0, -1), Vector3D.of(4, 3, 0), TEST_PRECISION);
212 
213         // act/assert
214         EuclideanTestUtils.assertCoordinatesEqual(line.getOrigin(), line.pointAt(0.0), TEST_EPS);
215         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(4, 3, -1), line.pointAt(5.0), TEST_EPS);
216         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-4, -3, -1), line.pointAt(-5.0), TEST_EPS);
217     }
218 
219     @Test
220     void testToSpace() {
221         // arrange
222         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(0, 0, -1), Vector3D.of(4, 3, 0), TEST_PRECISION);
223 
224         // act/assert
225         EuclideanTestUtils.assertCoordinatesEqual(line.getOrigin(), line.toSpace(Vector1D.of(0.0)), TEST_EPS);
226         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(4, 3, -1), line.toSpace(Vector1D.of(5.0)), TEST_EPS);
227         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-4, -3, -1), line.toSpace(Vector1D.of(-5.0)), TEST_EPS);
228     }
229 
230     @Test
231     void testContains() {
232         final Vector3D p1 = Vector3D.of(0, 0, 1);
233         final Line3D l = Lines3D.fromPoints(p1, Vector3D.of(0, 0, 2), TEST_PRECISION);
234         Assertions.assertTrue(l.contains(p1));
235         Assertions.assertTrue(l.contains(Vector3D.Sum.of(p1).addScaled(0.3, l.getDirection()).get()));
236         final Vector3D u = l.getDirection().orthogonal();
237         final Vector3D v = l.getDirection().cross(u);
238         for (double alpha = 0; alpha < 2 * Math.PI; alpha += 0.3) {
239             Assertions.assertFalse(l.contains(p1.add(Vector3D.Sum.create().addScaled(Math.cos(alpha), u).addScaled(
240                     Math.sin(alpha), v).get())));
241         }
242     }
243 
244     @Test
245     void testSimilar() {
246         final Vector3D p1  = Vector3D.of(1.2, 3.4, -5.8);
247         final Vector3D p2  = Vector3D.of(3.4, -5.8, 1.2);
248         final Line3D lA  = Lines3D.fromPoints(p1, p2, TEST_PRECISION);
249         final Line3D lB  = Lines3D.fromPoints(p2, p1, TEST_PRECISION);
250         Assertions.assertTrue(lA.isSimilarTo(lB));
251         Assertions.assertFalse(lA.isSimilarTo(Lines3D.fromPoints(p1, p1.add(lA.getDirection().orthogonal()), TEST_PRECISION)));
252     }
253 
254     @Test
255     void testPointDistance() {
256         final Line3D l = Lines3D.fromPoints(Vector3D.of(0, 1, 1), Vector3D.of(0, 2, 2), TEST_PRECISION);
257         Assertions.assertEquals(Math.sqrt(3.0 / 2.0), l.distance(Vector3D.of(1, 0, 1)), TEST_EPS);
258         Assertions.assertEquals(0, l.distance(Vector3D.of(0, -4, -4)), TEST_EPS);
259     }
260 
261     @Test
262     void testLineDistance() {
263         final Line3D l = Lines3D.fromPoints(Vector3D.of(0, 1, 1), Vector3D.of(0, 2, 2), TEST_PRECISION);
264         Assertions.assertEquals(1.0,
265                             l.distance(Lines3D.fromPoints(Vector3D.of(1, 0, 1), Vector3D.of(1, 0, 2), TEST_PRECISION)),
266                             1.0e-10);
267         Assertions.assertEquals(0.5,
268                             l.distance(Lines3D.fromPoints(Vector3D.of(-0.5, 0, 0), Vector3D.of(-0.5, -1, -1), TEST_PRECISION)),
269                             1.0e-10);
270         Assertions.assertEquals(0.0,
271                             l.distance(l),
272                             1.0e-10);
273         Assertions.assertEquals(0.0,
274                             l.distance(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(0, -5, -5), TEST_PRECISION)),
275                             1.0e-10);
276         Assertions.assertEquals(0.0,
277                             l.distance(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(0, -3, -4), TEST_PRECISION)),
278                             1.0e-10);
279         Assertions.assertEquals(0.0,
280                             l.distance(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(1, -4, -4), TEST_PRECISION)),
281                             1.0e-10);
282         Assertions.assertEquals(Math.sqrt(8),
283                             l.distance(Lines3D.fromPoints(Vector3D.of(0, -4, 0), Vector3D.of(1, -4, 0), TEST_PRECISION)),
284                             1.0e-10);
285     }
286 
287     @Test
288     void testClosest() {
289         final Line3D l = Lines3D.fromPoints(Vector3D.of(0, 1, 1), Vector3D.of(0, 2, 2), TEST_PRECISION);
290         Assertions.assertEquals(0.0,
291                             l.closest(Lines3D.fromPoints(Vector3D.of(1, 0, 1), Vector3D.of(1, 0, 2), TEST_PRECISION)).distance(Vector3D.of(0, 0, 0)),
292                             1.0e-10);
293         Assertions.assertEquals(0.5,
294                             l.closest(Lines3D.fromPoints(Vector3D.of(-0.5, 0, 0), Vector3D.of(-0.5, -1, -1), TEST_PRECISION)).distance(Vector3D.of(-0.5, 0, 0)),
295                             1.0e-10);
296         Assertions.assertEquals(0.0,
297                             l.closest(l).distance(Vector3D.of(0, 0, 0)),
298                             1.0e-10);
299         Assertions.assertEquals(0.0,
300                             l.closest(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(0, -5, -5), TEST_PRECISION)).distance(Vector3D.of(0, 0, 0)),
301                             1.0e-10);
302         Assertions.assertEquals(0.0,
303                             l.closest(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(0, -3, -4), TEST_PRECISION)).distance(Vector3D.of(0, -4, -4)),
304                             1.0e-10);
305         Assertions.assertEquals(0.0,
306                             l.closest(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(1, -4, -4), TEST_PRECISION)).distance(Vector3D.of(0, -4, -4)),
307                             1.0e-10);
308         Assertions.assertEquals(0.0,
309                             l.closest(Lines3D.fromPoints(Vector3D.of(0, -4, 0), Vector3D.of(1, -4, 0), TEST_PRECISION)).distance(Vector3D.of(0, -2, -2)),
310                             1.0e-10);
311     }
312 
313     @Test
314     void testIntersection() {
315         final Line3D l = Lines3D.fromPoints(Vector3D.of(0, 1, 1), Vector3D.of(0, 2, 2), TEST_PRECISION);
316         Assertions.assertNull(l.intersection(Lines3D.fromPoints(Vector3D.of(1, 0, 1), Vector3D.of(1, 0, 2), TEST_PRECISION)));
317         Assertions.assertNull(l.intersection(Lines3D.fromPoints(Vector3D.of(-0.5, 0, 0), Vector3D.of(-0.5, -1, -1), TEST_PRECISION)));
318         Assertions.assertEquals(0.0,
319                             l.intersection(l).distance(Vector3D.of(0, 0, 0)),
320                             1.0e-10);
321         Assertions.assertEquals(0.0,
322                             l.intersection(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(0, -5, -5), TEST_PRECISION)).distance(Vector3D.of(0, 0, 0)),
323                             1.0e-10);
324         Assertions.assertEquals(0.0,
325                             l.intersection(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(0, -3, -4), TEST_PRECISION)).distance(Vector3D.of(0, -4, -4)),
326                             1.0e-10);
327         Assertions.assertEquals(0.0,
328                             l.intersection(Lines3D.fromPoints(Vector3D.of(0, -4, -4), Vector3D.of(1, -4, -4), TEST_PRECISION)).distance(Vector3D.of(0, -4, -4)),
329                             1.0e-10);
330         Assertions.assertNull(l.intersection(Lines3D.fromPoints(Vector3D.of(0, -4, 0), Vector3D.of(1, -4, 0), TEST_PRECISION)));
331     }
332 
333     @Test
334     void testReverse() {
335         // arrange
336         final Line3D line = Lines3D.fromPoints(Vector3D.of(1653345.6696423641, 6170370.041579291, 90000),
337                              Vector3D.of(1650757.5050732433, 6160710.879908984, 0.9),
338                              TEST_PRECISION);
339         final Vector3D expected = line.getDirection().negate();
340 
341         // act
342         final Line3D reversed = line.reverse();
343 
344         // assert
345         EuclideanTestUtils.assertCoordinatesEqual(expected, reversed.getDirection(), TEST_EPS);
346     }
347 
348     @Test
349     void testSpan() {
350         // arrange
351         final Line3D line = Lines3D.fromPoints(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
352 
353         // act
354         final LineConvexSubset3D span = line.span();
355 
356         // assert
357         Assertions.assertTrue(span.isInfinite());
358         Assertions.assertFalse(span.isFinite());
359 
360         Assertions.assertNull(span.getStartPoint());
361         Assertions.assertNull(span.getEndPoint());
362 
363         Assertions.assertNull(span.getCentroid());
364         Assertions.assertNull(span.getBounds());
365 
366         GeometryTestUtils.assertNegativeInfinity(span.getSubspaceStart());
367         GeometryTestUtils.assertPositiveInfinity(span.getSubspaceEnd());
368 
369         GeometryTestUtils.assertPositiveInfinity(span.getSize());
370 
371         Assertions.assertSame(line, span.getLine());
372         Assertions.assertTrue(span.getInterval().isFull());
373     }
374 
375     @Test
376     void testSpan_contains() {
377         // arrange
378         final double delta = 1e-12;
379 
380         final LineConvexSubset3D span = Lines3D.fromPoints(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION).span();
381 
382         for (double x = -10; x <= 10; x += 0.5) {
383 
384             // act/assert
385             Assertions.assertFalse(span.contains(Vector3D.of(0, 1, 0)));
386             Assertions.assertFalse(span.contains(Vector3D.of(0, 0, 1)));
387 
388             Assertions.assertTrue(span.contains(Vector3D.of(x, 0, 0)));
389             Assertions.assertTrue(span.contains(Vector3D.of(x + delta, delta, delta)));
390         }
391     }
392 
393     @Test
394     void testSpan_transform() {
395         // arrange
396         final AffineTransformMatrix3D t = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, 0.5 * Math.PI)
397                 .toMatrix()
398                 .translate(Vector3D.Unit.PLUS_Y);
399 
400         final LineConvexSubset3D span = Lines3D.fromPointAndDirection(Vector3D.of(1, 0, 0), Vector3D.Unit.PLUS_X, TEST_PRECISION)
401                 .span();
402 
403         // act
404         final LineConvexSubset3D result = span.transform(t);
405 
406         // assert
407         Assertions.assertNull(result.getStartPoint());
408         Assertions.assertNull(result.getEndPoint());
409 
410         Assertions.assertTrue(result.contains(Vector3D.of(0, 1, -1)));
411         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_Z, result.getLine().getDirection(), TEST_EPS);
412     }
413 
414     @Test
415     void testSpan_transform_reflection() {
416         // arrange
417         final AffineTransformMatrix3D t = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, 0.5 * Math.PI)
418                 .toMatrix()
419                 .translate(Vector3D.Unit.PLUS_Y)
420                 .scale(1, 1, -2);
421 
422         final LineConvexSubset3D span = Lines3D.fromPointAndDirection(Vector3D.of(1, 0, 0),
423                 Vector3D.Unit.PLUS_X, TEST_PRECISION).span();
424 
425         // act
426         final LineConvexSubset3D result = span.transform(t);
427 
428         // assert
429         Assertions.assertNull(result.getStartPoint());
430         Assertions.assertNull(result.getEndPoint());
431 
432         Assertions.assertTrue(result.contains(Vector3D.of(0, 1, 2)));
433         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.PLUS_Z, result.getLine().getDirection(), TEST_EPS);
434     }
435 
436     @Test
437     void testSpan_toString() {
438         // arrange
439         final LineConvexSubset3D span = Lines3D.fromPointAndDirection(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION)
440                 .span();
441 
442         // act
443         final String str = span.toString();
444 
445         // assert
446         GeometryTestUtils.assertContains("LineSpanningSubset3D[origin= (0", str);
447         GeometryTestUtils.assertContains(", direction= (1", str);
448     }
449 
450     @Test
451     void testSubsetMethods() {
452         // arrange
453         final Line3D line = Lines3D.fromPoints(Vector3D.of(0, 3, 0), Vector3D.of(1, 3, 0), TEST_PRECISION);
454 
455         // act/assert
456         final Segment3D doubleArgResult = line.segment(3, 4);
457         Assertions.assertSame(line, doubleArgResult.getLine());
458         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 3, 0), doubleArgResult.getStartPoint(), TEST_EPS);
459         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(4, 3, 0), doubleArgResult.getEndPoint(), TEST_EPS);
460 
461         final Segment3D ptArgResult = line.segment(Vector3D.of(0, 4, 0), Vector3D.of(2, 5, 1));
462         Assertions.assertSame(line, ptArgResult.getLine());
463         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 3, 0), ptArgResult.getStartPoint(), TEST_EPS);
464         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 0), ptArgResult.getEndPoint(), TEST_EPS);
465 
466         final Ray3D rayDoubleResult = line.rayFrom(2);
467         Assertions.assertSame(line, rayDoubleResult.getLine());
468         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 0), rayDoubleResult.getStartPoint(), TEST_EPS);
469         Assertions.assertNull(rayDoubleResult.getEndPoint());
470 
471         final Ray3D rayPtResult = line.rayFrom(Vector3D.of(1, 4, 0));
472         Assertions.assertSame(line, rayPtResult.getLine());
473         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 3, 0), rayPtResult.getStartPoint(), TEST_EPS);
474         Assertions.assertNull(rayPtResult.getEndPoint());
475 
476         final ReverseRay3D toDoubleResult = line.reverseRayTo(-1);
477         Assertions.assertSame(line, toDoubleResult.getLine());
478         Assertions.assertNull(toDoubleResult.getStartPoint());
479         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 3, 0), toDoubleResult.getEndPoint(), TEST_EPS);
480 
481         final ReverseRay3D toPtResult = line.reverseRayTo(Vector3D.of(1, 4, 0));
482         Assertions.assertSame(line, toPtResult.getLine());
483         Assertions.assertNull(toPtResult.getStartPoint());
484         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 3, 0), toPtResult.getEndPoint(), TEST_EPS);
485     }
486 
487     @Test
488     void testEq() {
489         // arrange
490         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-3);
491 
492         final Vector3D p = Vector3D.of(1, 2, 3);
493         final Vector3D dir = Vector3D.of(1, 0, 0);
494 
495         final Line3D a = Lines3D.fromPointAndDirection(p, dir, precision);
496         final Line3D b = Lines3D.fromPointAndDirection(Vector3D.ZERO, dir, precision);
497         final Line3D c = Lines3D.fromPointAndDirection(p, Vector3D.of(1, 1, 0), precision);
498 
499         final Line3D d = Lines3D.fromPointAndDirection(p, dir, precision);
500         final Line3D e = Lines3D.fromPointAndDirection(p.add(Vector3D.of(1e-4, 1e-4, 1e-4)), dir, precision);
501         final Line3D f = Lines3D.fromPointAndDirection(p, Vector3D.of(1 + 1e-4, 1e-4, 1e-4), precision);
502 
503         // act/assert
504         Assertions.assertTrue(a.eq(a, precision));
505 
506         Assertions.assertTrue(a.eq(d, precision));
507         Assertions.assertTrue(d.eq(a, precision));
508 
509         Assertions.assertTrue(a.eq(e, precision));
510         Assertions.assertTrue(e.eq(a, precision));
511 
512         Assertions.assertTrue(a.eq(f, precision));
513         Assertions.assertTrue(f.eq(a, precision));
514 
515         Assertions.assertFalse(a.eq(b, precision));
516         Assertions.assertFalse(a.eq(c, precision));
517     }
518 
519     @Test
520     void testHashCode() {
521         // arrange
522         final Line3D a = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
523         final Line3D b = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, -1), Vector3D.of(4, 5, 6), TEST_PRECISION);
524         final Line3D c = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, -1), TEST_PRECISION);
525         final Line3D d = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), Precision.doubleEquivalenceOfEpsilon(TEST_EPS + 1e-3));
526 
527         final Line3D e = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
528 
529         final int hash = a.hashCode();
530 
531         // act/assert
532         Assertions.assertEquals(hash, a.hashCode());
533 
534         Assertions.assertNotEquals(hash, b.hashCode());
535         Assertions.assertNotEquals(hash, c.hashCode());
536         Assertions.assertNotEquals(hash, d.hashCode());
537 
538         Assertions.assertEquals(hash, e.hashCode());
539     }
540 
541     @Test
542     void testEquals() {
543         // arrange
544         final Line3D a = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
545         final Line3D b = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, -1), Vector3D.of(4, 5, 6), TEST_PRECISION);
546         final Line3D c = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, -1), TEST_PRECISION);
547         final Line3D d = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), Precision.doubleEquivalenceOfEpsilon(TEST_EPS + 1e-3));
548 
549         final Line3D e = Lines3D.fromPointAndDirection(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
550 
551         // act/assert
552         GeometryTestUtils.assertSimpleEqualsCases(a);
553 
554         Assertions.assertNotEquals(a, b);
555         Assertions.assertNotEquals(a, c);
556         Assertions.assertNotEquals(a, d);
557 
558         Assertions.assertEquals(a, e);
559         Assertions.assertEquals(e, a);
560     }
561 
562     @Test
563     void testToString() {
564         // arrange
565         final Line3D line = Lines3D.fromPointAndDirection(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
566 
567         // act
568         final String str = line.toString();
569 
570         // assert
571         Assertions.assertTrue(str.contains("Line3D"));
572         Assertions.assertTrue(str.matches(".*origin= \\(0(\\.0)?, 0(\\.0)?, 0(\\.0)?\\).*"));
573         Assertions.assertTrue(str.matches(".*direction= \\(1(\\.0)?, 0(\\.0)?, 0(\\.0)?\\).*"));
574     }
575 }