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