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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
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,   // 1.000000f, 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         // the following is a wrong usage of the constructor.
402         // as explained in the javadoc, the failure is NOT detected at construction
403         // time but occurs later on
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             // this is expected
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};  // bottom (-z)
433             faces[1]=new int[]{1,3,2};  // bottom (-z)
434             faces[2]=new int[]{5,7,4};  // top (+z)
435             faces[3]=new int[]{7,5,6};  // top (+z)
436             faces[4]=new int[]{2,5,1};  // right (+x)
437             faces[5]=new int[]{5,2,6};  // right (+x)
438             faces[6]=new int[]{4,3,0};  // left (-x)
439             faces[7]=new int[]{3,4,7};  // left (-x)
440             faces[8]=new int[]{4,1,5};  // front (-y)
441             faces[9]=new int[]{1,4,0};  // front (-y)
442             faces[10]=new int[]{3,6,2}; // back (+y)
443             faces[11]=new int[]{6,3,7}; // back (+y)
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     // issue GEOMETRY-38
513     @Test
514     void testFirstIntersectionLinesPassThroughBoundaries() {
515         // arrange
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         // act/assert
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 }