1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.threed.shape;
18
19 import java.io.IOException;
20 import java.util.List;
21 import java.util.function.DoubleSupplier;
22 import java.util.regex.Pattern;
23 import java.util.stream.Collectors;
24
25 import org.apache.commons.geometry.core.GeometryTestUtils;
26 import org.apache.commons.geometry.core.RegionLocation;
27 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
28 import org.apache.commons.geometry.euclidean.threed.Bounds3D;
29 import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
30 import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
31 import org.apache.commons.geometry.euclidean.threed.SphericalCoordinates;
32 import org.apache.commons.geometry.euclidean.threed.Triangle3D;
33 import org.apache.commons.geometry.euclidean.threed.Vector3D;
34 import org.apache.commons.geometry.euclidean.threed.line.Line3D;
35 import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
36 import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
37 import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
38 import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
39 import org.apache.commons.numbers.angle.Angle;
40 import org.apache.commons.numbers.core.Precision;
41 import org.apache.commons.rng.UniformRandomProvider;
42 import org.apache.commons.rng.simple.RandomSource;
43 import org.junit.jupiter.api.Assertions;
44 import org.junit.jupiter.api.Test;
45
46 class SphereTest {
47
48 private static final double TEST_EPS = 1e-10;
49
50 private static final Precision.DoubleEquivalence TEST_PRECISION =
51 Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
52
53 @Test
54 void testFrom() {
55
56 final Vector3D center = Vector3D.of(1, 2, 3);
57
58
59 final Sphere s = Sphere.from(center, 3, TEST_PRECISION);
60
61
62 Assertions.assertFalse(s.isFull());
63 Assertions.assertFalse(s.isEmpty());
64
65 Assertions.assertSame(center, s.getCenter());
66 Assertions.assertSame(center, s.getCentroid());
67
68 Assertions.assertEquals(3, s.getRadius(), 0.0);
69
70 Assertions.assertSame(TEST_PRECISION, s.getPrecision());
71 }
72
73 @Test
74 void testFrom_illegalCenter() {
75
76 Assertions.assertThrows(IllegalArgumentException.class, () -> Sphere.from(Vector3D.of(Double.POSITIVE_INFINITY, 1, 2), 1, TEST_PRECISION));
77 Assertions.assertThrows(IllegalArgumentException.class, () -> Sphere.from(Vector3D.of(Double.NaN, 1, 2), 1, TEST_PRECISION));
78 }
79
80 @Test
81 void testFrom_illegalRadius() {
82
83 final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-2);
84
85
86 Assertions.assertThrows(IllegalArgumentException.class, () -> Sphere.from(Vector3D.ZERO, -1, TEST_PRECISION));
87 Assertions.assertThrows(IllegalArgumentException.class, () -> Sphere.from(Vector3D.ZERO, 0, TEST_PRECISION));
88 Assertions.assertThrows(IllegalArgumentException.class, () -> Sphere.from(Vector3D.ZERO, Double.POSITIVE_INFINITY, TEST_PRECISION));
89 Assertions.assertThrows(IllegalArgumentException.class, () -> Sphere.from(Vector3D.ZERO, Double.NaN, TEST_PRECISION));
90 Assertions.assertThrows(IllegalArgumentException.class, () -> Sphere.from(Vector3D.ZERO, 1e-3, precision));
91 }
92
93 @Test
94 void testGeometricProperties() {
95
96 final double r = 2;
97 final Sphere s = Sphere.from(Vector3D.of(1, 2, 3), r, TEST_PRECISION);
98
99
100 Assertions.assertEquals(4 * Math.PI * r * r, s.getBoundarySize(), TEST_EPS);
101 Assertions.assertEquals((4.0 * Math.PI * r * r * r) / 3.0, s.getSize(), TEST_EPS);
102 }
103
104 @Test
105 void testClassify() {
106
107 final Vector3D center = Vector3D.of(1, 2, 3);
108 final double radius = 4;
109 final Sphere s = Sphere.from(center, radius, TEST_PRECISION);
110
111 EuclideanTestUtils.permute(0, Angle.TWO_PI, 0.2, (azimuth, polar) -> {
112
113 EuclideanTestUtils.assertRegionLocation(s, RegionLocation.OUTSIDE,
114 SphericalCoordinates.of(radius + 1, azimuth, polar)
115 .toVector()
116 .add(center));
117
118 EuclideanTestUtils.assertRegionLocation(s, RegionLocation.BOUNDARY,
119 SphericalCoordinates.of(radius + 1e-12, azimuth, polar)
120 .toVector()
121 .add(center));
122
123 EuclideanTestUtils.assertRegionLocation(s, RegionLocation.INSIDE,
124 SphericalCoordinates.of(radius - 1, azimuth, polar)
125 .toVector()
126 .add(center));
127 });
128 }
129
130 @Test
131 void testContains() {
132
133 final Vector3D center = Vector3D.of(1, 2, 3);
134 final double radius = 4;
135 final Sphere s = Sphere.from(center, radius, TEST_PRECISION);
136
137 EuclideanTestUtils.permute(0, Angle.TWO_PI, 0.2, (azimuth, polar) -> {
138
139 checkContains(s, false,
140 SphericalCoordinates.of(radius + 1, azimuth, polar)
141 .toVector()
142 .add(center));
143
144 checkContains(s, true,
145 SphericalCoordinates.of(radius - 1, azimuth, polar)
146 .toVector()
147 .add(center),
148 SphericalCoordinates.of(radius + 1e-12, azimuth, polar)
149 .toVector()
150 .add(center));
151 });
152 }
153
154 @Test
155 void testProject() {
156
157 final Vector3D center = Vector3D.of(1.5, 2.5, 3.5);
158 final double radius = 3;
159 final Sphere s = Sphere.from(center, radius, TEST_PRECISION);
160
161 EuclideanTestUtils.permute(-4, 4, 1, (x, y, z) -> {
162 final Vector3D pt = Vector3D.of(x, y, z);
163
164
165 final Vector3D projection = s.project(pt);
166
167
168 Assertions.assertEquals(radius, center.distance(projection), TEST_EPS);
169 EuclideanTestUtils.assertCoordinatesEqual(center.directionTo(pt),
170 center.directionTo(projection), TEST_EPS);
171 });
172 }
173
174 @Test
175 void testProject_argumentEqualsCenter() {
176
177 final Sphere c = Sphere.from(Vector3D.of(1, 2, 3), 2, TEST_PRECISION);
178
179
180 final Vector3D projection = c.project(Vector3D.of(1, 2, 3));
181
182
183 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 2, 3), projection, TEST_EPS);
184 }
185
186 @Test
187 void testIntersections() {
188
189 final Sphere s = Sphere.from(Vector3D.of(2, 1, 3), 2, TEST_PRECISION);
190 final double sqrt3 = Math.sqrt(3);
191
192
193
194 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(-1, 4, 3), Vector3D.of(5, 4, 3), TEST_PRECISION));
195 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(-1, 3, 3), Vector3D.of(5, 3, 3), TEST_PRECISION),
196 Vector3D.of(2, 3, 3));
197 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(-1, 2, 3), Vector3D.of(5, 2, 3), TEST_PRECISION),
198 Vector3D.of(2 - sqrt3, 2, 3), Vector3D.of(2 + sqrt3, 2, 3));
199 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(-1, 1, 3), Vector3D.of(5, 1, 3), TEST_PRECISION),
200 Vector3D.of(0, 1, 3), Vector3D.of(4, 1, 3));
201 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(-1, 0, 3), Vector3D.of(5, 0, 3), TEST_PRECISION),
202 Vector3D.of(2 - sqrt3, 0, 3), Vector3D.of(2 + sqrt3, 0, 3));
203 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(-1, -1, 3), Vector3D.of(5, -1, 3), TEST_PRECISION),
204 Vector3D.of(2, -1, 3));
205 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(-1, -2, 3), Vector3D.of(5, -2, 3), TEST_PRECISION));
206
207
208 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(-1, -2, 3), Vector3D.of(-1, 5, 3), TEST_PRECISION));
209 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(0, -2, 3), Vector3D.of(0, 5, 3), TEST_PRECISION),
210 Vector3D.of(0, 1, 3));
211 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(1, -2, 3), Vector3D.of(1, 5, 3), TEST_PRECISION),
212 Vector3D.of(1, 1 - sqrt3, 3), Vector3D.of(1, 1 + sqrt3, 3));
213 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(2, -2, 3), Vector3D.of(2, 5, 3), TEST_PRECISION),
214 Vector3D.of(2, -1, 3), Vector3D.of(2, 3, 3));
215 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(3, -2, 3), Vector3D.of(3, 5, 3), TEST_PRECISION),
216 Vector3D.of(3, 1 - sqrt3, 3), Vector3D.of(3, 1 + sqrt3, 3));
217 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(4, -2, 3), Vector3D.of(4, 5, 3), TEST_PRECISION),
218 Vector3D.of(4, 1, 3));
219 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(5, -2, 3), Vector3D.of(5, 5, 3), TEST_PRECISION));
220
221
222 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(2, -2, 6), Vector3D.of(2, 4, 6), TEST_PRECISION));
223 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(2, -2, 5), Vector3D.of(2, 4, 5), TEST_PRECISION),
224 Vector3D.of(2, 1, 5));
225 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(2, -2, 4), Vector3D.of(2, 4, 4), TEST_PRECISION),
226 Vector3D.of(2, 1 - sqrt3, 4), Vector3D.of(2, 1 + sqrt3, 4));
227 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(2, -2, 3), Vector3D.of(2, 4, 3), TEST_PRECISION),
228 Vector3D.of(2, -1, 3), Vector3D.of(2, 3, 3));
229 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(2, -2, 2), Vector3D.of(2, 4, 2), TEST_PRECISION),
230 Vector3D.of(2, 1 - sqrt3, 2), Vector3D.of(2, 1 + sqrt3, 2));
231 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(2, -2, 1), Vector3D.of(2, 4, 1), TEST_PRECISION),
232 Vector3D.of(2, 1, 1));
233 checkIntersections(s, Lines3D.fromPoints(Vector3D.of(2, -2, 0), Vector3D.of(2, 4, 0), TEST_PRECISION));
234
235
236 final Vector3D center = s.getCenter();
237 checkIntersections(s, Lines3D.fromPoints(Vector3D.ZERO, s.getCenter(), TEST_PRECISION),
238 center.withNorm(center.norm() - s.getRadius()), center.withNorm(center.norm() + s.getRadius()));
239 }
240
241 @Test
242 void testLinecast() {
243
244 final Sphere s = Sphere.from(Vector3D.of(2, 1, 3), 2, TEST_PRECISION);
245 final double sqrt3 = Math.sqrt(3);
246
247
248 checkLinecast(s, Lines3D.segmentFromPoints(Vector3D.of(-1, 0, 3), Vector3D.of(5, 0, 3), TEST_PRECISION),
249 Vector3D.of(2 - sqrt3, 0, 3), Vector3D.of(2 + sqrt3, 0, 3));
250 checkLinecast(s, Lines3D.segmentFromPoints(Vector3D.of(-1, 3, 3), Vector3D.of(5, 3, 3), TEST_PRECISION),
251 Vector3D.of(2, 3, 3));
252 checkLinecast(s, Lines3D.segmentFromPoints(Vector3D.of(-1, -2, 3), Vector3D.of(5, -2, 3), TEST_PRECISION));
253 }
254
255 @Test
256 void testLinecast_intersectionsNotInSegment() {
257
258 final Sphere s = Sphere.from(Vector3D.of(2, 1, 3), 2, TEST_PRECISION);
259 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(0, 0, 3), Vector3D.Unit.PLUS_X, TEST_PRECISION);
260
261
262 checkLinecast(s, line.segment(-1, 0));
263 checkLinecast(s, line.segment(1.5, 2.5));
264 checkLinecast(s, line.segment(1.5, 2.5));
265 checkLinecast(s, line.segment(4, 5));
266 }
267
268 @Test
269 void testLinecast_segmentPointOnBoundary() {
270
271 final Sphere s = Sphere.from(Vector3D.of(2, 1, 3), 2, TEST_PRECISION);
272 final Line3D line = Lines3D.fromPointAndDirection(Vector3D.of(0, 0, 3), Vector3D.Unit.PLUS_X, TEST_PRECISION);
273 final double sqrt3 = Math.sqrt(3);
274 final double start = 2 - sqrt3;
275 final double end = 2 + sqrt3;
276
277
278 checkLinecast(s, line.segment(start, 2), Vector3D.of(start, 0, 3));
279 checkLinecast(s, line.segment(start, end), Vector3D.of(start, 0, 3), Vector3D.of(end, 0, 3));
280 checkLinecast(s, line.segment(end, 5), Vector3D.of(end, 0, 3));
281 }
282
283 @Test
284 void testToTree_zeroSubdivisions() throws IOException {
285
286 final double r = 2;
287 final Sphere s = Sphere.from(Vector3D.of(2, 1, 3), r, TEST_PRECISION);
288
289
290 final RegionBSPTree3D tree = s.toTree(0);
291
292
293 checkBasicApproximationProperties(s, tree);
294
295 final List<PlaneConvexSubset> boundaries = tree.getBoundaries();
296 Assertions.assertEquals(8, boundaries.size());
297
298 final List<Triangle3D> triangles = tree.triangleStream().collect(Collectors.toList());
299 Assertions.assertEquals(8, triangles.size());
300
301 final double expectedSize = (4.0 / 3.0) * r * r * r;
302 Assertions.assertEquals(expectedSize, tree.getSize(), TEST_EPS);
303 }
304
305 @Test
306 void testToTree_oneSubdivision() throws IOException {
307
308 final double r = 2;
309 final Sphere s = Sphere.from(Vector3D.of(2, 1, 3), r, TEST_PRECISION);
310
311
312 final RegionBSPTree3D tree = s.toTree(1);
313
314
315 checkBasicApproximationProperties(s, tree);
316
317 final List<PlaneConvexSubset> boundaries = tree.getBoundaries();
318 Assertions.assertEquals(32, boundaries.size());
319
320 final List<Triangle3D> triangles = tree.triangleStream().collect(Collectors.toList());
321 Assertions.assertEquals(32, triangles.size());
322
323 Assertions.assertTrue(tree.getSize() <= s.getSize());
324 }
325
326 @Test
327 void testToTree_multipleSubdivisionCounts() {
328
329 final Sphere s = Sphere.from(Vector3D.of(-3, 5, 1), 10, TEST_PRECISION);
330
331 final int min = 0;
332 final int max = 5;
333
334 RegionBSPTree3D tree;
335
336 double sizeDiff;
337 double prevSizeDiff = Double.POSITIVE_INFINITY;
338
339 for (int n = min; n <= max; ++n) {
340
341 tree = s.toTree(n);
342
343
344 checkBasicApproximationProperties(s, tree);
345
346 final int expectedTriangles = (int) (8 * Math.pow(4, n));
347 final List<PlaneConvexSubset> boundaries = tree.getBoundaries();
348 Assertions.assertEquals(expectedTriangles, boundaries.size());
349
350 final List<Triangle3D> triangles = tree.triangleStream().collect(Collectors.toList());
351 Assertions.assertEquals(expectedTriangles, triangles.size());
352
353
354 sizeDiff = s.getSize() - tree.getSize();
355 Assertions.assertTrue(sizeDiff < prevSizeDiff, "Expected size difference to decrease: n= " +
356 n + ", prevSizeDiff= " + prevSizeDiff + ", sizeDiff= " + sizeDiff);
357
358 prevSizeDiff = sizeDiff;
359 }
360 }
361
362 @Test
363 void testToTree_randomSpheres() {
364
365 final UniformRandomProvider rand = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP, 1L);
366 final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-10);
367 final double min = 1e-1;
368 final double max = 1e2;
369
370 final DoubleSupplier randDouble = () -> (rand.nextDouble() * (max - min)) + min;
371
372 final int count = 10;
373 for (int i = 0; i < count; ++i) {
374 final Vector3D center = Vector3D.of(
375 randDouble.getAsDouble(),
376 randDouble.getAsDouble(),
377 randDouble.getAsDouble());
378
379 final double radius = randDouble.getAsDouble();
380 final Sphere sphere = Sphere.from(center, radius, precision);
381
382 for (int s = 0; s < 7; ++s) {
383
384 final RegionBSPTree3D tree = sphere.toTree(s);
385
386
387 Assertions.assertEquals((int) (8 * Math.pow(4, s)), tree.getBoundaries().size());
388 Assertions.assertTrue(tree.isFinite());
389 Assertions.assertFalse(tree.isEmpty());
390 Assertions.assertTrue(tree.getSize() < sphere.getSize());
391 }
392 }
393 }
394
395 @Test
396 void testToTree_closeApproximation() throws IOException {
397
398 final Sphere s = Sphere.from(Vector3D.ZERO, 1, TEST_PRECISION);
399
400
401 final RegionBSPTree3D tree = s.toTree(8);
402
403
404 checkBasicApproximationProperties(s, tree);
405
406 final double eps = 1e-3;
407 Assertions.assertTrue(tree.isFinite());
408 Assertions.assertEquals(s.getSize(), tree.getSize(), eps);
409 Assertions.assertEquals(s.getBoundarySize(), tree.getBoundarySize(), eps);
410 EuclideanTestUtils.assertCoordinatesEqual(s.getCentroid(), tree.getCentroid(), eps);
411 }
412
413 @Test
414 void testToTree_subdivideFails() {
415
416 final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-5);
417 final Sphere s = Sphere.from(Vector3D.ZERO, 1, precision);
418
419
420 GeometryTestUtils.assertThrowsWithMessage(() -> {
421 s.toTree(6);
422 }, IllegalStateException.class,
423 Pattern.compile("^Failed to construct sphere approximation with subdivision count 6:.*"));
424 }
425
426 @Test
427 void testToTree_invalidArgs() {
428
429 final Sphere s = Sphere.from(Vector3D.of(2, 1, 3), 2, TEST_PRECISION);
430
431
432 GeometryTestUtils.assertThrowsWithMessage(() -> {
433 s.toTree(-1);
434 }, IllegalArgumentException.class,
435 "Number of sphere approximation subdivisions must be greater than or equal to zero; was -1");
436 }
437
438 @Test
439 void testToMesh_zeroSubdivisions() {
440
441 final Sphere s = Sphere.from(Vector3D.of(1, 2, 3), 2, TEST_PRECISION);
442
443
444 final TriangleMesh mesh = s.toTriangleMesh(0);
445
446
447 Assertions.assertEquals(6, mesh.getVertexCount());
448 Assertions.assertEquals(8, mesh.getFaceCount());
449
450 final Bounds3D bounds = mesh.getBounds();
451 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 1), bounds.getMin(), TEST_EPS);
452 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 4, 5), bounds.getMax(), TEST_EPS);
453
454 Assertions.assertTrue(mesh.toTree().isFinite());
455 }
456
457 @Test
458 void testToMesh_manySubdivisions() {
459
460 final Sphere s = Sphere.from(Vector3D.of(1, 2, 3), 2, TEST_PRECISION);
461 final int subdivisions = 5;
462
463
464 final TriangleMesh mesh = s.toTriangleMesh(subdivisions);
465
466
467 Assertions.assertEquals((int) (8 * Math.pow(4, subdivisions)), mesh.getFaceCount());
468
469 final Bounds3D bounds = mesh.getBounds();
470 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 1), bounds.getMin(), TEST_EPS);
471 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 4, 5), bounds.getMax(), TEST_EPS);
472
473 final RegionBSPTree3D tree = RegionBSPTree3D.partitionedRegionBuilder()
474 .insertAxisAlignedGrid(bounds, 3, TEST_PRECISION)
475 .insertBoundaries(mesh)
476 .build();
477
478 Assertions.assertTrue(tree.isFinite());
479
480 final double approximationEps = 0.1;
481 Assertions.assertEquals(s.getSize(), tree.getSize(), approximationEps);
482 Assertions.assertEquals(s.getBoundarySize(), tree.getBoundarySize(), approximationEps);
483
484 EuclideanTestUtils.assertCoordinatesEqual(s.getCentroid(), tree.getCentroid(), TEST_EPS);
485 }
486
487 @Test
488 void testToMesh_invalidArgs() {
489
490 final Sphere s = Sphere.from(Vector3D.of(2, 1, 3), 2, TEST_PRECISION);
491
492
493 GeometryTestUtils.assertThrowsWithMessage(() -> {
494 s.toTriangleMesh(-1);
495 }, IllegalArgumentException.class,
496 "Number of sphere approximation subdivisions must be greater than or equal to zero; was -1");
497 }
498
499 @Test
500 void testHashCode() {
501
502 final Precision.DoubleEquivalence otherPrecision = Precision.doubleEquivalenceOfEpsilon(1e-2);
503
504 final Sphere a = Sphere.from(Vector3D.of(1, 2, 3), 3, TEST_PRECISION);
505 final Sphere b = Sphere.from(Vector3D.of(1, 1, 3), 3, TEST_PRECISION);
506 final Sphere c = Sphere.from(Vector3D.of(1, 2, 3), 4, TEST_PRECISION);
507 final Sphere d = Sphere.from(Vector3D.of(1, 2, 3), 3, otherPrecision);
508 final Sphere e = Sphere.from(Vector3D.of(1, 2, 3), 3, TEST_PRECISION);
509
510
511 final int hash = a.hashCode();
512
513
514 Assertions.assertEquals(hash, a.hashCode());
515
516 Assertions.assertNotEquals(hash, b.hashCode());
517 Assertions.assertNotEquals(hash, c.hashCode());
518 Assertions.assertNotEquals(hash, d.hashCode());
519
520 Assertions.assertEquals(hash, e.hashCode());
521 }
522
523 @Test
524 void testEquals() {
525
526 final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-2);
527
528 final Sphere a = Sphere.from(Vector3D.of(1, 2, 3), 3, TEST_PRECISION);
529 final Sphere b = Sphere.from(Vector3D.of(1, 1, 3), 3, TEST_PRECISION);
530 final Sphere c = Sphere.from(Vector3D.of(1, 2, 3), 4, TEST_PRECISION);
531 final Sphere d = Sphere.from(Vector3D.of(1, 2, 3), 3, precision);
532 final Sphere e = Sphere.from(Vector3D.of(1, 2, 3), 3, TEST_PRECISION);
533
534
535 GeometryTestUtils.assertSimpleEqualsCases(a);
536
537 Assertions.assertNotEquals(a, b);
538 Assertions.assertNotEquals(a, c);
539 Assertions.assertNotEquals(a, d);
540
541 Assertions.assertEquals(a, e);
542 }
543
544 @Test
545 void testToString() {
546
547 final Sphere c = Sphere.from(Vector3D.of(1, 2, 3), 3, TEST_PRECISION);
548
549
550 final String str = c.toString();
551
552
553 Assertions.assertEquals("Sphere[center= (1.0, 2.0, 3.0), radius= 3.0]", str);
554 }
555
556 private static void checkContains(final Sphere sphere, final boolean contains, final Vector3D... pts) {
557 for (final Vector3D pt : pts) {
558 Assertions.assertEquals(contains, sphere.contains(pt),
559 "Expected circle to " + (contains ? "" : "not") + "contain point " + pt);
560 }
561 }
562
563 private static void checkIntersections(final Sphere sphere, final Line3D line, final Vector3D... expectedPts) {
564
565
566 final List<Vector3D> actualPtsForward = sphere.intersections(line);
567 final List<Vector3D> actualPtsReverse = sphere.intersections(line.reverse());
568
569 final Vector3D actualFirstForward = sphere.firstIntersection(line);
570 final Vector3D actualFirstReverse = sphere.firstIntersection(line.reverse());
571
572
573 final int len = expectedPts.length;
574
575
576 Assertions.assertEquals(len, actualPtsForward.size());
577 Assertions.assertEquals(len, actualPtsReverse.size());
578
579 for (int i = 0; i < len; ++i) {
580 EuclideanTestUtils.assertCoordinatesEqual(expectedPts[i], actualPtsForward.get(i), TEST_EPS);
581 Assertions.assertEquals(sphere.getRadius(), sphere.getCenter().distance(actualPtsForward.get(i)), TEST_EPS);
582
583 EuclideanTestUtils.assertCoordinatesEqual(expectedPts[len - i - 1], actualPtsReverse.get(i), TEST_EPS);
584 Assertions.assertEquals(sphere.getRadius(), sphere.getCenter().distance(actualPtsReverse.get(i)), TEST_EPS);
585 }
586
587
588 if (len > 0) {
589 Assertions.assertNotNull(actualFirstForward);
590 Assertions.assertNotNull(actualFirstReverse);
591
592 EuclideanTestUtils.assertCoordinatesEqual(expectedPts[0], actualFirstForward, TEST_EPS);
593 EuclideanTestUtils.assertCoordinatesEqual(expectedPts[len - 1], actualFirstReverse, TEST_EPS);
594 } else {
595 Assertions.assertNull(actualFirstForward);
596 Assertions.assertNull(actualFirstReverse);
597 }
598 }
599
600 private static void checkLinecast(final Sphere s, final LineConvexSubset3D segment, final Vector3D... expectedPts) {
601
602 final List<LinecastPoint3D> results = s.linecast(segment);
603 Assertions.assertEquals(expectedPts.length, results.size());
604
605 LinecastPoint3D actual;
606 Vector3D expected;
607 for (int i = 0; i < expectedPts.length; ++i) {
608 expected = expectedPts[i];
609 actual = results.get(i);
610
611 EuclideanTestUtils.assertCoordinatesEqual(expected, actual.getPoint(), TEST_EPS);
612 EuclideanTestUtils.assertCoordinatesEqual(s.getCenter().directionTo(expected), actual.getNormal(), TEST_EPS);
613 Assertions.assertSame(segment.getLine(), actual.getLine());
614 }
615
616
617 final LinecastPoint3D firstResult = s.linecastFirst(segment);
618 if (expectedPts.length > 0) {
619 Assertions.assertEquals(results.get(0), firstResult);
620 } else {
621 Assertions.assertNull(firstResult);
622 }
623 }
624
625
626
627
628 private static void checkBasicApproximationProperties(final Sphere s, final RegionBSPTree3D tree) {
629 Assertions.assertFalse(tree.isFull());
630 Assertions.assertFalse(tree.isEmpty());
631 Assertions.assertTrue(tree.isFinite());
632 Assertions.assertFalse(tree.isInfinite());
633
634
635 Assertions.assertTrue(tree.getSize() < s.getSize(), "Expected approximation volume to be less than circle");
636
637
638 for (final PlaneConvexSubset boundary : tree.getBoundaries()) {
639 Assertions.assertTrue(boundary.isFinite());
640
641 for (final Vector3D vertex : boundary.getVertices()) {
642 Assertions.assertTrue(s.contains(vertex), "Expected vertex to be contained in sphere: " + vertex);
643 }
644 }
645
646
647 EuclideanTestUtils.assertRegionLocation(s, RegionLocation.INSIDE, tree.getCentroid());
648 }
649 }