1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.euclidean.EuclideanTestUtils;
21 import org.apache.commons.geometry.euclidean.oned.Interval;
22 import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
23 import org.apache.commons.geometry.euclidean.threed.Bounds3D;
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.core.Precision;
27 import org.junit.jupiter.api.Assertions;
28 import org.junit.jupiter.api.Test;
29
30 class Segment3DTest {
31
32 private static final double TEST_EPS = 1e-10;
33
34 private static final Precision.DoubleEquivalence TEST_PRECISION =
35 Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
36
37 @Test
38 void testFromPoints() {
39
40 final Vector3D p1 = Vector3D.of(1, 1, 2);
41 final Vector3D p2 = Vector3D.of(1, 3, 2);
42
43
44 final Segment3D seg = Lines3D.segmentFromPoints(p1, p2, TEST_PRECISION);
45
46
47 Assertions.assertFalse(seg.isInfinite());
48 Assertions.assertTrue(seg.isFinite());
49
50 EuclideanTestUtils.assertCoordinatesEqual(p1, seg.getStartPoint(), TEST_EPS);
51 EuclideanTestUtils.assertCoordinatesEqual(p2, seg.getEndPoint(), TEST_EPS);
52
53 Assertions.assertEquals(1, seg.getSubspaceStart(), TEST_EPS);
54 Assertions.assertEquals(3, seg.getSubspaceEnd(), TEST_EPS);
55
56 Assertions.assertEquals(2, seg.getSize(), TEST_EPS);
57
58 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 2), seg.getCentroid(), TEST_EPS);
59 final Bounds3D bounds = seg.getBounds();
60 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 2), bounds.getMin(), TEST_EPS);
61 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 3, 2), bounds.getMax(), TEST_EPS);
62 }
63
64 @Test
65 void testFromPoints_invalidArgs() {
66
67 final Vector3D p1 = Vector3D.of(0, 2, 4);
68 final Vector3D p2 = Vector3D.of(1e-17, 2, 4);
69
70
71 GeometryTestUtils.assertThrowsWithMessage(() -> {
72 Lines3D.segmentFromPoints(p1, p1, TEST_PRECISION);
73 }, IllegalArgumentException.class, "Line direction cannot be zero");
74
75 GeometryTestUtils.assertThrowsWithMessage(() -> {
76 Lines3D.segmentFromPoints(p1, p2, TEST_PRECISION);
77 }, IllegalArgumentException.class, "Line direction cannot be zero");
78 }
79
80 @Test
81 void testFromPoints_givenLine() {
82
83 final Vector3D p1 = Vector3D.of(-1, -1, 2);
84 final Vector3D p2 = Vector3D.of(3, 3, 3);
85
86 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(1, 0, 2), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
87
88
89 final Segment3D seg = Lines3D.segmentFromPoints(line, p2, p1);
90
91
92 Assertions.assertFalse(seg.isInfinite());
93 Assertions.assertTrue(seg.isFinite());
94
95 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, -1, 2), seg.getStartPoint(), TEST_EPS);
96 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 3, 2), seg.getEndPoint(), TEST_EPS);
97
98 Assertions.assertEquals(-1, seg.getSubspaceStart(), TEST_EPS);
99 Assertions.assertEquals(3, seg.getSubspaceEnd(), TEST_EPS);
100
101 Assertions.assertEquals(4, seg.getSize(), TEST_EPS);
102
103 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 2), seg.getCentroid(), TEST_EPS);
104 final Bounds3D bounds = seg.getBounds();
105 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, -1, 2), bounds.getMin(), TEST_EPS);
106 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 3, 2), bounds.getMax(), TEST_EPS);
107 }
108
109 @Test
110 void testFromPoints_givenLine_singlePoint() {
111
112 final Vector3D p1 = Vector3D.of(-1, 2, 0);
113
114 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(1, 0, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
115
116
117 final Segment3D seg = Lines3D.segmentFromPoints(line, p1, p1);
118
119
120 Assertions.assertFalse(seg.isInfinite());
121 Assertions.assertTrue(seg.isFinite());
122
123 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 0), seg.getStartPoint(), TEST_EPS);
124 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 0), seg.getEndPoint(), TEST_EPS);
125
126 Assertions.assertEquals(2, seg.getSubspaceStart(), TEST_EPS);
127 Assertions.assertEquals(2, seg.getSubspaceEnd(), TEST_EPS);
128
129 Assertions.assertEquals(0, seg.getSize(), TEST_EPS);
130
131 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 0), seg.getCentroid(), TEST_EPS);
132 final Bounds3D bounds = seg.getBounds();
133 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 0), bounds.getMin(), TEST_EPS);
134 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 0), bounds.getMax(), TEST_EPS);
135 }
136
137 @Test
138 void testFromPoints_givenLine_invalidArgs() {
139
140 final Vector3D p0 = Vector3D.of(1, 0, 0);
141 final Vector3D p1 = Vector3D.of(2, 0, 0);
142
143 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
144
145
146 GeometryTestUtils.assertThrowsWithMessage(() -> {
147 Lines3D.segmentFromPoints(line, Vector3D.NaN, p1);
148 }, IllegalArgumentException.class, "Invalid line segment locations: NaN, 2.0");
149
150 GeometryTestUtils.assertThrowsWithMessage(() -> {
151 Lines3D.segmentFromPoints(line, p0, Vector3D.NaN);
152 }, IllegalArgumentException.class, "Invalid line segment locations: 1.0, NaN");
153
154 GeometryTestUtils.assertThrowsWithMessage(() -> {
155 Lines3D.segmentFromPoints(line, Vector3D.NEGATIVE_INFINITY, p1);
156 }, IllegalArgumentException.class, "Invalid line segment locations: NaN, 2.0");
157
158 GeometryTestUtils.assertThrowsWithMessage(() -> {
159 Lines3D.segmentFromPoints(line, p0, Vector3D.POSITIVE_INFINITY);
160 }, IllegalArgumentException.class, "Invalid line segment locations: 1.0, NaN");
161 }
162
163 @Test
164 void testFromLocations() {
165
166 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(-1, 0, 0), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
167
168
169 final Segment3D seg = Lines3D.segmentFromLocations(line, -1, 2);
170
171
172 Assertions.assertFalse(seg.isInfinite());
173 Assertions.assertTrue(seg.isFinite());
174
175 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, -1), seg.getStartPoint(), TEST_EPS);
176 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 2), seg.getEndPoint(), TEST_EPS);
177
178 Assertions.assertEquals(-1, seg.getSubspaceStart(), TEST_EPS);
179 Assertions.assertEquals(2, seg.getSubspaceEnd(), TEST_EPS);
180
181 Assertions.assertEquals(3, seg.getSize(), TEST_EPS);
182
183 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 0.5), seg.getCentroid(), TEST_EPS);
184 final Bounds3D bounds = seg.getBounds();
185 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, -1), bounds.getMin(), TEST_EPS);
186 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 2), bounds.getMax(), TEST_EPS);
187 }
188
189 @Test
190 void testFromLocations_reversedLocationOrder() {
191
192 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(-1, 0, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
193
194
195 final Segment3D seg = Lines3D.segmentFromLocations(line, 2, -1);
196
197
198 Assertions.assertFalse(seg.isInfinite());
199 Assertions.assertTrue(seg.isFinite());
200
201 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, -1), seg.getStartPoint(), TEST_EPS);
202 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 2), seg.getEndPoint(), TEST_EPS);
203
204 Assertions.assertEquals(-1, seg.getSubspaceStart(), TEST_EPS);
205 Assertions.assertEquals(2, seg.getSubspaceEnd(), TEST_EPS);
206
207 Assertions.assertEquals(3, seg.getSize(), TEST_EPS);
208
209 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 0.5), seg.getCentroid(), TEST_EPS);
210 final Bounds3D bounds = seg.getBounds();
211 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, -1), bounds.getMin(), TEST_EPS);
212 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 2), bounds.getMax(), TEST_EPS);
213 }
214
215 @Test
216 void testFromLocations_singlePoint() {
217
218 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(-1, 0, 0), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
219
220
221 final Segment3D seg = Lines3D.segmentFromLocations(line, 1, 1);
222
223
224 Assertions.assertFalse(seg.isInfinite());
225 Assertions.assertTrue(seg.isFinite());
226
227 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 1), seg.getStartPoint(), TEST_EPS);
228 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 1), seg.getEndPoint(), TEST_EPS);
229
230 Assertions.assertEquals(1, seg.getSubspaceStart(), TEST_EPS);
231 Assertions.assertEquals(1, seg.getSubspaceEnd(), TEST_EPS);
232
233 Assertions.assertEquals(0, seg.getSize(), TEST_EPS);
234
235 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 1), seg.getCentroid(), TEST_EPS);
236 final Bounds3D bounds = seg.getBounds();
237 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 1), bounds.getMin(), TEST_EPS);
238 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 1), bounds.getMax(), TEST_EPS);
239 }
240
241 @Test
242 void testFromLocations_invalidArgs() {
243
244 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.ZERO, Vector3D.Unit.MINUS_Z, TEST_PRECISION);
245
246
247 GeometryTestUtils.assertThrowsWithMessage(() -> {
248 Lines3D.segmentFromLocations(line, Double.NaN, 2);
249 }, IllegalArgumentException.class, "Invalid line segment locations: NaN, 2.0");
250
251 GeometryTestUtils.assertThrowsWithMessage(() -> {
252 Lines3D.segmentFromLocations(line, 1, Double.NaN);
253 }, IllegalArgumentException.class, "Invalid line segment locations: 1.0, NaN");
254
255 GeometryTestUtils.assertThrowsWithMessage(() -> {
256 Lines3D.segmentFromLocations(line, Double.NEGATIVE_INFINITY, 2);
257 }, IllegalArgumentException.class, "Invalid line segment locations: -Infinity, 2.0");
258
259 GeometryTestUtils.assertThrowsWithMessage(() -> {
260 Lines3D.segmentFromLocations(line, 1, Double.POSITIVE_INFINITY);
261 }, IllegalArgumentException.class, "Invalid line segment locations: 1.0, Infinity");
262 }
263
264 @Test
265 void testTransform() {
266
267 final AffineTransformMatrix3D t = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, 0.5 * Math.PI)
268 .toMatrix()
269 .translate(Vector3D.Unit.PLUS_Y);
270
271 final Segment3D seg = Lines3D.segmentFromPoints(Vector3D.of(1, 0, 0), Vector3D.of(2, 0, 0), TEST_PRECISION);
272
273
274 final Segment3D result = seg.transform(t);
275
276
277 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, -1), result.getStartPoint(), TEST_EPS);
278 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, -2), result.getEndPoint(), TEST_EPS);
279 }
280
281 @Test
282 void testTransform_reflection() {
283
284 final AffineTransformMatrix3D t = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, 0.5 * Math.PI)
285 .toMatrix()
286 .translate(Vector3D.Unit.PLUS_Y)
287 .scale(1, 1, -2);
288
289 final Segment3D seg = Lines3D.segmentFromPoints(Vector3D.of(1, 0, 0), Vector3D.of(2, 0, 0), TEST_PRECISION);
290
291
292 final Segment3D result = seg.transform(t);
293
294
295 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 2), result.getStartPoint(), TEST_EPS);
296 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 4), result.getEndPoint(), TEST_EPS);
297 }
298
299 @Test
300 void testContains() {
301
302 final Vector3D p0 = Vector3D.of(1, 1, 1);
303 final Vector3D p1 = Vector3D.of(3, 1, 1);
304
305 final Vector3D delta = Vector3D.of(1e-12, 1e-12, 1e-12);
306
307 final Segment3D seg = Lines3D.segmentFromPoints(Vector3D.of(1, 1, 1), Vector3D.of(3, 1, 1), TEST_PRECISION);
308
309
310 Assertions.assertFalse(seg.contains(Vector3D.of(2, 2, 2)));
311 Assertions.assertFalse(seg.contains(Vector3D.of(0.9, 1, 1)));
312 Assertions.assertFalse(seg.contains(Vector3D.of(3.1, 1, 1)));
313
314 Assertions.assertTrue(seg.contains(p0));
315 Assertions.assertTrue(seg.contains(p1));
316
317 Assertions.assertTrue(seg.contains(p0.subtract(delta)));
318 Assertions.assertTrue(seg.contains(p1.add(delta)));
319
320 Assertions.assertTrue(seg.contains(p0.lerp(p1, 0.5)));
321 }
322
323 @Test
324 void testGetInterval() {
325
326 final Segment3D seg = Lines3D.segmentFromPoints(Vector3D.of(2, -1, 3), Vector3D.of(2, 2, 3), TEST_PRECISION);
327
328
329 final Interval interval = seg.getInterval();
330
331
332 Assertions.assertEquals(-1, interval.getMin(), TEST_EPS);
333 Assertions.assertEquals(2, interval.getMax(), TEST_EPS);
334
335 Assertions.assertSame(seg.getLine().getPrecision(), interval.getMinBoundary().getPrecision());
336 }
337
338 @Test
339 void testGetInterval_singlePoint() {
340
341 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
342 final Segment3D seg = Lines3D.segmentFromLocations(line, 1, 1);
343
344
345 final Interval interval = seg.getInterval();
346
347
348 Assertions.assertEquals(1, interval.getMin(), TEST_EPS);
349 Assertions.assertEquals(1, interval.getMax(), TEST_EPS);
350 Assertions.assertEquals(0, interval.getSize(), TEST_EPS);
351
352 Assertions.assertSame(seg.getLine().getPrecision(), interval.getMinBoundary().getPrecision());
353 }
354
355 @Test
356 void testToString() {
357
358 final Segment3D seg = Lines3D.segmentFromPoints(Vector3D.ZERO, Vector3D.of(1, 0, 0), TEST_PRECISION);
359
360
361 final String str = seg.toString();
362
363
364 GeometryTestUtils.assertContains("Segment3D[startPoint= (0", str);
365 GeometryTestUtils.assertContains(", endPoint= (1", str);
366 }
367 }