1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.hipparchus.geometry.euclidean.threed;
23
24 import org.hipparchus.exception.Localizable;
25 import org.hipparchus.exception.LocalizedCoreFormats;
26 import org.hipparchus.exception.MathIllegalArgumentException;
27 import org.hipparchus.exception.MathRuntimeException;
28 import org.hipparchus.geometry.LocalizedGeometryFormats;
29 import org.hipparchus.geometry.euclidean.twod.Euclidean2D;
30 import org.hipparchus.geometry.euclidean.twod.PolygonsSet;
31 import org.hipparchus.geometry.euclidean.twod.SubLine;
32 import org.hipparchus.geometry.euclidean.twod.Vector2D;
33 import org.hipparchus.geometry.partitioning.BSPTree;
34 import org.hipparchus.geometry.partitioning.BSPTreeVisitor;
35 import org.hipparchus.geometry.partitioning.BoundaryAttribute;
36 import org.hipparchus.geometry.partitioning.Region;
37 import org.hipparchus.geometry.partitioning.RegionDumper;
38 import org.hipparchus.geometry.partitioning.RegionFactory;
39 import org.hipparchus.geometry.partitioning.RegionParser;
40 import org.hipparchus.geometry.partitioning.SubHyperplane;
41 import org.hipparchus.random.RandomGenerator;
42 import org.hipparchus.random.Well1024a;
43 import org.hipparchus.util.FastMath;
44 import org.junit.jupiter.api.Assertions;
45 import org.junit.jupiter.api.Test;
46
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.InputStreamReader;
50 import java.io.Reader;
51 import java.nio.charset.StandardCharsets;
52 import java.text.ParseException;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.List;
56
57 import static org.junit.jupiter.api.Assertions.assertEquals;
58 import static org.junit.jupiter.api.Assertions.assertNotNull;
59 import static org.junit.jupiter.api.Assertions.assertNull;
60 import static org.junit.jupiter.api.Assertions.assertTrue;
61 import static org.junit.jupiter.api.Assertions.fail;
62
63 class PolyhedronsSetTest {
64
65 @Test
66 void testBox() {
67 PolyhedronsSet tree = new PolyhedronsSet(0, 1, 0, 1, 0, 1, 1.0e-10);
68 assertEquals(1.0, tree.getSize(), 1.0e-10);
69 assertEquals(6.0, tree.getBoundarySize(), 1.0e-10);
70 Vector3D barycenter = tree.getBarycenter();
71 assertEquals(0.5, barycenter.getX(), 1.0e-10);
72 assertEquals(0.5, barycenter.getY(), 1.0e-10);
73 assertEquals(0.5, barycenter.getZ(), 1.0e-10);
74 for (double x = -0.25; x < 1.25; x += 0.1) {
75 boolean xOK = (x >= 0.0) && (x <= 1.0);
76 for (double y = -0.25; y < 1.25; y += 0.1) {
77 boolean yOK = (y >= 0.0) && (y <= 1.0);
78 for (double z = -0.25; z < 1.25; z += 0.1) {
79 boolean zOK = (z >= 0.0) && (z <= 1.0);
80 Region.Location expected =
81 (xOK && yOK && zOK) ? Region.Location.INSIDE : Region.Location.OUTSIDE;
82 assertEquals(expected, tree.checkPoint(new Vector3D(x, y, z)));
83 }
84 }
85 }
86 checkPoints(Region.Location.BOUNDARY, tree, new Vector3D[] {
87 new Vector3D(0.0, 0.5, 0.5),
88 new Vector3D(1.0, 0.5, 0.5),
89 new Vector3D(0.5, 0.0, 0.5),
90 new Vector3D(0.5, 1.0, 0.5),
91 new Vector3D(0.5, 0.5, 0.0),
92 new Vector3D(0.5, 0.5, 1.0)
93 });
94 checkPoints(Region.Location.OUTSIDE, tree, new Vector3D[] {
95 new Vector3D(0.0, 1.2, 1.2),
96 new Vector3D(1.0, 1.2, 1.2),
97 new Vector3D(1.2, 0.0, 1.2),
98 new Vector3D(1.2, 1.0, 1.2),
99 new Vector3D(1.2, 1.2, 0.0),
100 new Vector3D(1.2, 1.2, 1.0)
101 });
102 assertEquals(Region.Location.INSIDE, tree.checkPoint(tree.getInteriorPoint()));
103 }
104
105 @Test
106 void testBRepExtractor() {
107 double x = 1.0;
108 double y = 2.0;
109 double z = 3.0;
110 double w = 0.1;
111 double l = 1.0;
112 PolyhedronsSet polyhedron =
113 new PolyhedronsSet(x - l, x + l, y - w, y + w, z - w, z + w, 1.0e-10);
114 PolyhedronsSet.BRep brep = polyhedron.getBRep();
115 assertEquals(6, brep.getFacets().size());
116 assertEquals(8, brep.getVertices().size());
117 Assertions.assertEquals(0.0,
118 Vector3D.distance(new Vector3D(x, y, z), polyhedron.getInteriorPoint()),
119 1.0e-15);
120 assertEquals(Region.Location.INSIDE, polyhedron.checkPoint(polyhedron.getInteriorPoint()));
121 }
122
123 @Test
124 void testEmptyBRepIfEmpty() {
125 PolyhedronsSet empty = (PolyhedronsSet) new RegionFactory<Euclidean3D, Vector3D, Plane, SubPlane>().
126 getComplement(new PolyhedronsSet(1.0e-10));
127 assertTrue(empty.isEmpty());
128 assertEquals(0.0, empty.getSize(), 1.0e-10);
129 PolyhedronsSet.BRep brep = empty.getBRep();
130 assertEquals(0, brep.getFacets().size());
131 assertEquals(0, brep.getVertices().size());
132 assertNull(empty.getInteriorPoint());
133 }
134
135 @Test
136 void testNoBRepHalfSpace() {
137 BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> bsp = new BSPTree<>();
138 bsp.insertCut(new Plane(Vector3D.PLUS_K, 1.0e-10));
139 bsp.getPlus().setAttribute(Boolean.FALSE);
140 bsp.getMinus().setAttribute(Boolean.TRUE);
141 PolyhedronsSet polyhedron = new PolyhedronsSet(bsp, 1.0e-10);
142 assertEquals(Double.POSITIVE_INFINITY, polyhedron.getSize(), 1.0e-10);
143 try {
144 polyhedron.getBRep();
145 fail("an exception should have been thrown");
146 } catch (MathRuntimeException mre) {
147 assertEquals(LocalizedGeometryFormats.OUTLINE_BOUNDARY_LOOP_OPEN, mre.getSpecifier());
148 }
149 }
150
151 @Test
152 void testNoBRepUnboundedOctant() {
153 BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> bsp = new BSPTree<>();
154 bsp.insertCut(new Plane(Vector3D.PLUS_K, 1.0e-10));
155 bsp.getPlus().setAttribute(Boolean.FALSE);
156 bsp.getMinus().insertCut(new Plane(Vector3D.PLUS_I, 1.0e-10));
157 bsp.getMinus().getPlus().setAttribute(Boolean.FALSE);
158 bsp.getMinus().getMinus().insertCut(new Plane(Vector3D.PLUS_J, 1.0e-10));
159 bsp.getMinus().getMinus().getPlus().setAttribute(Boolean.FALSE);
160 bsp.getMinus().getMinus().getMinus().setAttribute(Boolean.TRUE);
161 PolyhedronsSet polyhedron = new PolyhedronsSet(bsp, 1.0e-10);
162 assertEquals(Double.POSITIVE_INFINITY, polyhedron.getSize(), 1.0e-10);
163 try {
164 polyhedron.getBRep();
165 fail("an exception should have been thrown");
166 } catch (MathRuntimeException mre) {
167 assertEquals(LocalizedGeometryFormats.OUTLINE_BOUNDARY_LOOP_OPEN, mre.getSpecifier());
168 }
169 }
170
171 @Test
172 void testNoBRepHolesInFacet() {
173 double tolerance = 1.0e-10;
174 PolyhedronsSet cube = new PolyhedronsSet(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, tolerance);
175 PolyhedronsSet tubeAlongX = new PolyhedronsSet(-2.0, 2.0, -0.5, 0.5, -0.5, 0.5, tolerance);
176 PolyhedronsSet tubeAlongY = new PolyhedronsSet(-0.5, 0.5, -2.0, 2.0, -0.5, 0.5, tolerance);
177 PolyhedronsSet tubeAlongZ = new PolyhedronsSet(-0.5, 0.5, -0.5, 0.5, -2.0, 2.0, tolerance);
178 RegionFactory<Euclidean3D, Vector3D, Plane, SubPlane> factory = new RegionFactory<>();
179 PolyhedronsSet cubeWithHoles = (PolyhedronsSet) factory.difference(cube,
180 factory.union(tubeAlongX,
181 factory.union(tubeAlongY, tubeAlongZ)));
182 assertEquals(4.0, cubeWithHoles.getSize(), 1.0e-10);
183 try {
184 cubeWithHoles.getBRep();
185 fail("an exception should have been thrown");
186 } catch (MathRuntimeException mre) {
187 assertEquals(LocalizedGeometryFormats.FACET_WITH_SEVERAL_BOUNDARY_LOOPS, mre.getSpecifier());
188 }
189 }
190
191 @Test
192 void testTetrahedron() throws MathRuntimeException {
193 Vector3D vertex1 = new Vector3D(1, 2, 3);
194 Vector3D vertex2 = new Vector3D(2, 2, 4);
195 Vector3D vertex3 = new Vector3D(2, 3, 3);
196 Vector3D vertex4 = new Vector3D(1, 3, 4);
197 PolyhedronsSet tree =
198 (PolyhedronsSet) new RegionFactory<Euclidean3D, Vector3D, Plane, SubPlane>().
199 buildConvex(new Plane(vertex3, vertex2, vertex1, 1.0e-10),
200 new Plane(vertex2, vertex3, vertex4, 1.0e-10),
201 new Plane(vertex4, vertex3, vertex1, 1.0e-10),
202 new Plane(vertex1, vertex2, vertex4, 1.0e-10));
203 assertEquals(1.0 / 3.0, tree.getSize(), 1.0e-10);
204 assertEquals(2.0 * FastMath.sqrt(3.0), tree.getBoundarySize(), 1.0e-10);
205 Vector3D barycenter = tree.getBarycenter();
206 assertEquals(1.5, barycenter.getX(), 1.0e-10);
207 assertEquals(2.5, barycenter.getY(), 1.0e-10);
208 assertEquals(3.5, barycenter.getZ(), 1.0e-10);
209 double third = 1.0 / 3.0;
210 checkPoints(Region.Location.BOUNDARY, tree, new Vector3D[] {
211 vertex1, vertex2, vertex3, vertex4,
212 new Vector3D(third, vertex1, third, vertex2, third, vertex3),
213 new Vector3D(third, vertex2, third, vertex3, third, vertex4),
214 new Vector3D(third, vertex3, third, vertex4, third, vertex1),
215 new Vector3D(third, vertex4, third, vertex1, third, vertex2)
216 });
217 checkPoints(Region.Location.OUTSIDE, tree, new Vector3D[] {
218 new Vector3D(1, 2, 4),
219 new Vector3D(2, 2, 3),
220 new Vector3D(2, 3, 4),
221 new Vector3D(1, 3, 3)
222 });
223 }
224
225 @Test
226 void testIsometry() throws MathRuntimeException {
227 Vector3D vertex1 = new Vector3D(1.1, 2.2, 3.3);
228 Vector3D vertex2 = new Vector3D(2.0, 2.4, 4.2);
229 Vector3D vertex3 = new Vector3D(2.8, 3.3, 3.7);
230 Vector3D vertex4 = new Vector3D(1.0, 3.6, 4.5);
231 PolyhedronsSet tree =
232 (PolyhedronsSet) new RegionFactory<Euclidean3D, Vector3D, Plane, SubPlane>().
233 buildConvex(new Plane(vertex3, vertex2, vertex1, 1.0e-10),
234 new Plane(vertex2, vertex3, vertex4, 1.0e-10),
235 new Plane(vertex4, vertex3, vertex1, 1.0e-10),
236 new Plane(vertex1, vertex2, vertex4, 1.0e-10));
237 Vector3D barycenter = tree.getBarycenter();
238 Vector3D s = new Vector3D(10.2, 4.3, -6.7);
239 Vector3D c = new Vector3D(-0.2, 2.1, -3.2);
240 Rotation r = new Rotation(new Vector3D(6.2, -4.4, 2.1), 0.12, RotationConvention.VECTOR_OPERATOR);
241
242 tree = tree.rotate(c, r).translate(s);
243
244 Vector3D newB = new Vector3D(1.0, s,
245 1.0, c,
246 1.0, r.applyTo(barycenter.subtract(c)));
247 assertEquals(0.0, newB.subtract(tree.getBarycenter()).getNorm(), 1.0e-10);
248
249 final Vector3D[] expectedV = new Vector3D[] {
250 new Vector3D(1.0, s,
251 1.0, c,
252 1.0, r.applyTo(vertex1.subtract(c))),
253 new Vector3D(1.0, s,
254 1.0, c,
255 1.0, r.applyTo(vertex2.subtract(c))),
256 new Vector3D(1.0, s,
257 1.0, c,
258 1.0, r.applyTo(vertex3.subtract(c))),
259 new Vector3D(1.0, s,
260 1.0, c,
261 1.0, r.applyTo(vertex4.subtract(c)))
262 };
263 tree.getTree(true).visit(new BSPTreeVisitor<Euclidean3D, Vector3D, Plane, SubPlane>() {
264
265 public Order visitOrder(BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
266 return Order.MINUS_SUB_PLUS;
267 }
268
269 public void visitInternalNode(BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
270 @SuppressWarnings("unchecked")
271 BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane> attribute =
272 (BoundaryAttribute<Euclidean3D, Vector3D, Plane, SubPlane>) node.getAttribute();
273 if (attribute.getPlusOutside() != null) {
274 checkFacet(attribute.getPlusOutside());
275 }
276 if (attribute.getPlusInside() != null) {
277 checkFacet(attribute.getPlusInside());
278 }
279 }
280
281 public void visitLeafNode(BSPTree<Euclidean3D, Vector3D, Plane, SubPlane> node) {
282 }
283
284 private void checkFacet(SubPlane facet) {
285 Plane plane = facet.getHyperplane();
286 Vector2D[][] vertices =
287 ((PolygonsSet) facet.getRemainingRegion()).getVertices();
288 assertEquals(1, vertices.length);
289 for (int i = 0; i < vertices[0].length; ++i) {
290 Vector3D v = plane.toSpace(vertices[0][i]);
291 double d = Double.POSITIVE_INFINITY;
292 for (final Vector3D u : expectedV) {
293 d = FastMath.min(d, v.subtract(u).getNorm());
294 }
295 assertEquals(0, d, 1.0e-10);
296 }
297 }
298
299 });
300
301 }
302
303 @Test
304 void testBuildBox() {
305 double x = 1.0;
306 double y = 2.0;
307 double z = 3.0;
308 double w = 0.1;
309 double l = 1.0;
310 PolyhedronsSet tree =
311 new PolyhedronsSet(x - l, x + l, y - w, y + w, z - w, z + w, 1.0e-10);
312 Vector3D barycenter = tree.getBarycenter();
313 assertEquals(x, barycenter.getX(), 1.0e-10);
314 assertEquals(y, barycenter.getY(), 1.0e-10);
315 assertEquals(z, barycenter.getZ(), 1.0e-10);
316 assertEquals(8 * l * w * w, tree.getSize(), 1.0e-10);
317 assertEquals(8 * w * (2 * l + w), tree.getBoundarySize(), 1.0e-10);
318 }
319
320 @Test
321 void testCross() {
322
323 double x = 1.0;
324 double y = 2.0;
325 double z = 3.0;
326 double w = 0.1;
327 double l = 1.0;
328 PolyhedronsSet xBeam =
329 new PolyhedronsSet(x - l, x + l, y - w, y + w, z - w, z + w, 1.0e-10);
330 PolyhedronsSet yBeam =
331 new PolyhedronsSet(x - w, x + w, y - l, y + l, z - w, z + w, 1.0e-10);
332 PolyhedronsSet zBeam =
333 new PolyhedronsSet(x - w, x + w, y - w, y + w, z - l, z + l, 1.0e-10);
334 RegionFactory<Euclidean3D, Vector3D, Plane, SubPlane> factory = new RegionFactory<>();
335 PolyhedronsSet tree = (PolyhedronsSet) factory.union(xBeam, factory.union(yBeam, zBeam));
336 Vector3D barycenter = tree.getBarycenter();
337
338 assertEquals(x, barycenter.getX(), 1.0e-10);
339 assertEquals(y, barycenter.getY(), 1.0e-10);
340 assertEquals(z, barycenter.getZ(), 1.0e-10);
341 assertEquals(8 * w * w * (3 * l - 2 * w), tree.getSize(), 1.0e-10);
342 assertEquals(24 * w * (2 * l - w), tree.getBoundarySize(), 1.0e-10);
343 }
344
345 @Test
346 void testIssue780() throws MathRuntimeException {
347 float[] coords = {
348 1.000000f, -1.000000f, -1.000000f,
349 1.000000f, -1.000000f, 1.000000f,
350 -1.000000f, -1.000000f, 1.000000f,
351 -1.000000f, -1.000000f, -1.000000f,
352 1.000000f, 1.000000f, -1f,
353 0.999999f, 1.000000f, 1.000000f,
354 -1.000000f, 1.000000f, 1.000000f,
355 -1.000000f, 1.000000f, -1.000000f};
356 int[] indices = {
357 0, 1, 2, 0, 2, 3,
358 4, 7, 6, 4, 6, 5,
359 0, 4, 5, 0, 5, 1,
360 1, 5, 6, 1, 6, 2,
361 2, 6, 7, 2, 7, 3,
362 4, 0, 3, 4, 3, 7};
363 ArrayList<SubPlane> subHyperplaneList = new ArrayList<>();
364 for (int idx = 0; idx < indices.length; idx += 3) {
365 int idxA = indices[idx] * 3;
366 int idxB = indices[idx + 1] * 3;
367 int idxC = indices[idx + 2] * 3;
368 Vector3D v_1 = new Vector3D(coords[idxA], coords[idxA + 1], coords[idxA + 2]);
369 Vector3D v_2 = new Vector3D(coords[idxB], coords[idxB + 1], coords[idxB + 2]);
370 Vector3D v_3 = new Vector3D(coords[idxC], coords[idxC + 1], coords[idxC + 2]);
371 Vector3D[] vertices = {v_1, v_2, v_3};
372 Plane polyPlane = new Plane(v_1, v_2, v_3, 1.0e-10);
373 ArrayList<SubLine> lines = new ArrayList<>();
374
375 Vector2D[] projPts = new Vector2D[vertices.length];
376 for (int ptIdx = 0; ptIdx < projPts.length; ptIdx++) {
377 projPts[ptIdx] = polyPlane.toSubSpace(vertices[ptIdx]);
378 }
379
380 for (int ptIdx = 0; ptIdx < projPts.length; ptIdx++) {
381 lines.add(new SubLine(projPts[ptIdx], projPts[(ptIdx + 1) % projPts.length], 1.0e-10));
382 }
383 Region<Euclidean2D, Vector2D, org.hipparchus.geometry.euclidean.twod.Line, SubLine> polyRegion =
384 new PolygonsSet(lines, 1.0e-10);
385 SubPlane polygon = new SubPlane(polyPlane, polyRegion);
386 subHyperplaneList.add(polygon);
387 }
388 PolyhedronsSet polyhedronsSet = new PolyhedronsSet(subHyperplaneList, 1.0e-10);
389 assertEquals( 8.0, polyhedronsSet.getSize(), 3.0e-6);
390 assertEquals(24.0, polyhedronsSet.getBoundarySize(), 5.0e-6);
391
392 }
393
394 @Test
395 void testTooThinBox() {
396 assertEquals(0.0, new PolyhedronsSet(0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0e-10).getSize(), 1.0e-10);
397 }
398
399 @Test
400 void testWrongUsage() {
401
402
403
404 PolyhedronsSet ps = new PolyhedronsSet(new BSPTree<>(), 1.0e-10);
405 assertNotNull(ps);
406 try {
407 ps.checkPoint(Vector3D.ZERO);
408 fail("an exception should have been thrown");
409 } catch (NullPointerException npe) {
410
411 }
412 }
413
414 @Test
415 void testDumpParse() throws IOException, ParseException {
416 double tol=1e-8;
417
418 Vector3D[] verts=new Vector3D[8];
419 double xmin=-1,xmax=1;
420 double ymin=-1,ymax=1;
421 double zmin=-1,zmax=1;
422 verts[0]=new Vector3D(xmin,ymin,zmin);
423 verts[1]=new Vector3D(xmax,ymin,zmin);
424 verts[2]=new Vector3D(xmax,ymax,zmin);
425 verts[3]=new Vector3D(xmin,ymax,zmin);
426 verts[4]=new Vector3D(xmin,ymin,zmax);
427 verts[5]=new Vector3D(xmax,ymin,zmax);
428 verts[6]=new Vector3D(xmax,ymax,zmax);
429 verts[7]=new Vector3D(xmin,ymax,zmax);
430
431 int[][] faces=new int[12][];
432 faces[0]=new int[]{3,1,0};
433 faces[1]=new int[]{1,3,2};
434 faces[2]=new int[]{5,7,4};
435 faces[3]=new int[]{7,5,6};
436 faces[4]=new int[]{2,5,1};
437 faces[5]=new int[]{5,2,6};
438 faces[6]=new int[]{4,3,0};
439 faces[7]=new int[]{3,4,7};
440 faces[8]=new int[]{4,1,5};
441 faces[9]=new int[]{1,4,0};
442 faces[10]=new int[]{3,6,2};
443 faces[11]=new int[]{6,3,7};
444 PolyhedronsSet polyset = new PolyhedronsSet(Arrays.asList(verts), Arrays.asList(faces), tol);
445 assertEquals(8.0, polyset.getSize(), 1.0e-10);
446 assertEquals(24.0, polyset.getBoundarySize(), 1.0e-10);
447 String dump = RegionDumper.dump(polyset);
448 PolyhedronsSet parsed = RegionParser.parsePolyhedronsSet(dump);
449 assertEquals(8.0, parsed.getSize(), 1.0e-10);
450 assertEquals(24.0, parsed.getBoundarySize(), 1.0e-10);
451 assertTrue(new RegionFactory<Euclidean3D, Vector3D, Plane, SubPlane>().difference(polyset, parsed).isEmpty());
452 }
453
454 @Test
455 void testConnectedFacets() throws IOException, ParseException {
456 InputStream stream = getClass().getResourceAsStream("pentomino-N.ply");
457 PLYParser parser = new PLYParser(stream);
458 stream.close();
459 PolyhedronsSet polyhedron = new PolyhedronsSet(parser.getVertices(), parser.getFaces(), 1.0e-10);
460 assertEquals( 5.0, polyhedron.getSize(), 1.0e-10);
461 assertEquals(22.0, polyhedron.getBoundarySize(), 1.0e-10);
462 }
463
464 @Test
465 void testTooClose() {
466 checkError("pentomino-N-too-close.ply", LocalizedGeometryFormats.CLOSE_VERTICES);
467 }
468
469 @Test
470 void testHole() {
471 checkError("pentomino-N-hole.ply", LocalizedGeometryFormats.EDGE_CONNECTED_TO_ONE_FACET);
472 }
473
474 @Test
475 void testNonPlanar() {
476 checkError("pentomino-N-out-of-plane.ply", LocalizedGeometryFormats.OUT_OF_PLANE);
477 }
478
479 @Test
480 void testOrientation() {
481 checkError("pentomino-N-bad-orientation.ply", LocalizedGeometryFormats.FACET_ORIENTATION_MISMATCH);
482 }
483
484 @Test
485 void testFacet2Vertices() {
486 checkError(Arrays.asList(Vector3D.ZERO, Vector3D.PLUS_I, Vector3D.PLUS_J, Vector3D.PLUS_K),
487 Arrays.asList(new int[] { 0, 1, 2 }, new int[] {2, 3}),
488 LocalizedCoreFormats.WRONG_NUMBER_OF_POINTS);
489 }
490
491 private void checkError(final String resourceName, final Localizable expected) {
492 try {
493 InputStream stream = getClass().getResourceAsStream(resourceName);
494 PLYParser parser = new PLYParser(stream);
495 stream.close();
496 checkError(parser.getVertices(), parser.getFaces(), expected);
497 } catch (IOException | ParseException e) {
498 fail(e.getLocalizedMessage());
499 }
500 }
501
502 private void checkError(final List<Vector3D> vertices, final List<int[]> facets,
503 final Localizable expected) {
504 try {
505 new PolyhedronsSet(vertices, facets, 1.0e-10);
506 fail("an exception should have been thrown");
507 } catch (MathIllegalArgumentException miae) {
508 assertEquals(expected, miae.getSpecifier());
509 }
510 }
511
512
513 @Test
514 void testFirstIntersectionLinesPassThroughBoundaries() {
515
516 Vector3D lowerCorner = Vector3D.ZERO;
517 Vector3D upperCorner = new Vector3D(1, 1, 1);
518 Vector3D center = new Vector3D(0.5, lowerCorner, 0.5, upperCorner);
519
520 PolyhedronsSet polySet = new PolyhedronsSet(0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0e-15);
521
522 Line upDiagonal = new Line(lowerCorner, upperCorner, 1.0e-15);
523 Line downDiagonal = upDiagonal.revert();
524
525
526 SubPlane upFromOutsideResult = (SubPlane) polySet.firstIntersection(new Vector3D(-1, -1, -1), upDiagonal);
527 assertNotNull(upFromOutsideResult);
528 assertEquals(0.0,
529 Vector3D.distance(lowerCorner, upFromOutsideResult.getHyperplane().intersection(upDiagonal)),
530 1.0e-15);
531
532 SubPlane upFromCenterResult = (SubPlane) polySet.firstIntersection(center, upDiagonal);
533 assertNotNull(upFromCenterResult);
534 assertEquals(0.0,
535 Vector3D.distance(upperCorner, upFromCenterResult.getHyperplane().intersection(upDiagonal)),
536 1.0e-15);
537
538 SubPlane downFromOutsideResult = (SubPlane) polySet.firstIntersection(new Vector3D(2, 2, 2), downDiagonal);
539 assertNotNull(downFromOutsideResult);
540 assertEquals(0.0,
541 Vector3D.distance(upperCorner, downFromOutsideResult.getHyperplane().intersection(downDiagonal)),
542 1.0e-15);
543
544 SubPlane downFromCenterResult = (SubPlane) polySet.firstIntersection(center, downDiagonal);
545 assertNotNull(downFromCenterResult);
546 assertEquals(0.0,
547 Vector3D.distance(lowerCorner, downFromCenterResult.getHyperplane().intersection(downDiagonal)),
548 1.0e-15);
549 }
550
551 @Test
552 void testIssue1211() throws IOException, ParseException {
553
554 PolyhedronsSet polyset = RegionParser.parsePolyhedronsSet(loadTestData("issue-1211.bsp"));
555 RandomGenerator random = new Well1024a(0xb97c9d1ade21e40aL);
556 int nrays = 1000;
557 for (int i = 0; i < nrays; i++) {
558 Vector3D origin = Vector3D.ZERO;
559 Vector3D direction = new Vector3D(2 * random.nextDouble() - 1,
560 2 * random.nextDouble() - 1,
561 2 * random.nextDouble() - 1).normalize();
562 Line line = new Line(origin, origin.add(direction), polyset.getTolerance());
563 SubHyperplane<Euclidean3D, Vector3D, Plane, SubPlane> plane = polyset.firstIntersection(origin, line);
564 if (plane != null) {
565 Vector3D intersectionPoint = plane.getHyperplane().intersection(line);
566 double dotProduct = direction.dotProduct(intersectionPoint.subtract(origin));
567 assertTrue(dotProduct > 0);
568 }
569 }
570 }
571
572 private String loadTestData(final String resourceName)
573 throws IOException {
574 InputStream stream = getClass().getResourceAsStream(resourceName);
575 Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8);
576 StringBuilder builder = new StringBuilder();
577 for (int c = reader.read(); c >= 0; c = reader.read()) {
578 builder.append((char) c);
579 }
580 return builder.toString();
581 }
582
583 private void checkPoints(Region.Location expected, PolyhedronsSet tree, Vector3D[] points) {
584 for (final Vector3D point : points) {
585 assertEquals(expected, tree.checkPoint(point));
586 }
587 }
588
589 }