You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2019/07/26 11:47:32 UTC

[commons-geometry] 01/04: GEOMETRY-59: adding Plane.fromPoints(Collection, DoublePrecisionContext) to help fix issues with computed Plane orientation

This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git

commit 6b2951586a90b1405001c72462459b49d9ed17c9
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Thu Jul 25 21:55:18 2019 -0400

    GEOMETRY-59: adding Plane.fromPoints(Collection, DoublePrecisionContext) to help fix issues with computed Plane orientation
---
 .../commons/geometry/euclidean/threed/Plane.java   | 100 ++++++-
 .../geometry/euclidean/threed/PolyhedronsSet.java  |  20 +-
 .../geometry/euclidean/threed/PlaneTest.java       | 286 ++++++++++++++++++++-
 .../euclidean/threed/PolyhedronsSetTest.java       |  45 +++-
 4 files changed, 436 insertions(+), 15 deletions(-)

diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
index f82ae52..0be47cd 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
@@ -16,8 +16,12 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.Objects;
 
+import org.apache.commons.geometry.core.exception.GeometryException;
 import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.core.partitioning.Embedding;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
@@ -127,11 +131,103 @@ public final class Plane implements Hyperplane<Vector3D>, Embedding<Vector3D, Ve
      * @param p3        third point belonging to the plane
      * @param precision precision context used to compare floating point values
      * @return a new plane
-     * @throws IllegalNormException if the points do not constitute a plane
+     * @throws GeometryException if the points do not define a unique plane
      */
     public static Plane fromPoints(final Vector3D p1, final Vector3D p2, final Vector3D p3,
             final DoublePrecisionContext precision) {
-        return Plane.fromPointAndPlaneVectors(p1, p1.vectorTo(p2), p1.vectorTo(p3), precision);
+        return Plane.fromPoints(Arrays.asList(p1, p2, p3), precision);
+    }
+
+    /** Construct a plane from a collection of points lying on the plane. The plane orientation is
+     * determined by the overall orientation of the point sequence. For example, if the points wind
+     * around the z-axis in a counter-clockwise direction, then the plane normal will point up the
+     * +z axis. If the points wind in the opposite direction, then the plane normal will point down
+     * the -z axis. The {@code u} vector for the plane is set to the first non-zero vector between
+     * points in the sequence (ie, the first direction in the path).
+     *
+     * @param pts collection of sequenced points lying on the plane
+     * @param precision precision context used to compare floating point values
+     * @return a new plane containing the given points
+     * @throws IllegalArgumentException if the given collection does not contain at least 3 points
+     * @throws GeometryException if the points do not define a unique plane
+     */
+    public static Plane fromPoints(final Collection<Vector3D> pts, final DoublePrecisionContext precision) {
+
+        if (pts.size() < 3) {
+            throw new IllegalArgumentException("At least 3 points are required to define a plane; " +
+                    "argument contains only " + pts.size() + ".");
+        }
+
+        final Iterator<Vector3D> it = pts.iterator();
+
+        Vector3D startPt = it.next();
+
+        Vector3D u = null;
+        Vector3D w = null;
+
+        Vector3D currentPt;
+
+        Vector3D currentVector = null;
+        Vector3D prevVector = null;
+
+        Vector3D cross = null;
+        double crossNorm;
+        double crossSumX = 0.0;
+        double crossSumY = 0.0;
+        double crossSumZ = 0.0;
+
+        boolean nonPlanar = false;
+
+        while (it.hasNext()) {
+            currentPt = it.next();
+            currentVector = startPt.vectorTo(currentPt);
+
+            if (!precision.eqZero(currentVector.norm())) {
+
+                if (u == null) {
+                    // save the first non-zero vector as our u vector
+                    u = currentVector.normalize();
+                }
+                if (prevVector != null) {
+                    cross = prevVector.cross(currentVector);
+
+                    crossSumX += cross.getX();
+                    crossSumY += cross.getY();
+                    crossSumZ += cross.getZ();
+
+                    crossNorm = cross.norm();
+
+                    if (!precision.eqZero(crossNorm)) {
+                        // the cross product has non-zero magnitude
+                        if (w == null) {
+                            // save the first non-zero cross product as our normal
+                            w = cross.normalize();
+                        }
+                        else if (!precision.eq(1.0, Math.abs(w.dot(cross) / crossNorm))) {
+                            // if the normalized dot product is not either +1 or -1, then
+                            // the points are not coplanar
+                            nonPlanar = true;
+                            break;
+                        }
+                    }
+                }
+
+                prevVector = currentVector;
+            }
+        }
+
+        if (u == null || w == null || nonPlanar) {
+            throw new GeometryException("Points do not define a plane: " + pts);
+        }
+
+        if (w.dot(Vector3D.of(crossSumX, crossSumY, crossSumZ)) < 0) {
+            w = w.negate();
+        }
+
+        final Vector3D v = w.cross(u);
+        final double originOffset = -startPt.dot(w);
+
+        return new Plane(u, v, w, originOffset, precision);
     }
 
     // This should be removed after GEOMETRY-32 is done.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java
index 234f81d..995c9a9 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java
@@ -214,26 +214,26 @@ public class PolyhedronsSet extends AbstractRegion<Vector3D, Vector2D> {
         }
 
         final List<SubHyperplane<Vector3D>> boundary = new ArrayList<>();
+        final List<Vector3D> facetVertices = new ArrayList<>();
 
         for (final int[] facet : facets) {
 
-            // define facet plane from the first 3 points
-            Plane plane = Plane.fromPoints(vertices.get(facet[0]), vertices.get(facet[1]), vertices.get(facet[2]),
-                                    precision);
+            for (int i=0; i<facet.length; ++i) {
+                facetVertices.add(vertices.get(facet[i]));
+            }
+
+            Plane plane = Plane.fromPoints(facetVertices, precision);
 
-            // check all points are in the plane
+            // convert to subspace points
             final Vector2D[] two2Points = new Vector2D[facet.length];
-            for (int i = 0 ; i < facet.length; ++i) {
-                final Vector3D v = vertices.get(facet[i]);
-                if (!plane.contains(v)) {
-                    throw new IllegalArgumentException("Point " + v + " is out of plane");
-                }
-                two2Points[i] = plane.toSubSpace(v);
+            for (int i=0 ; i<facet.length; ++i) {
+                two2Points[i] = plane.toSubSpace(facetVertices.get(i));
             }
 
             // create the polygonal facet
             boundary.add(new SubPlane(plane, new PolygonsSet(precision, two2Points)));
 
+            facetVertices.clear();
         }
 
         return boundary;
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
index d250269..af76826 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
@@ -16,9 +16,16 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.exception.GeometryException;
 import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
 import org.junit.Assert;
 import org.junit.Test;
@@ -46,7 +53,7 @@ public class PlaneTest {
         Plane.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), Vector3D.of(0, 0, -2), TEST_PRECISION);
     }
 
-    @Test(expected=IllegalNormException.class)
+    @Test(expected=GeometryException.class)
     public void testPointsDoNotConstituteAPlane() {
         Plane.fromPoints(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), Vector3D.of(0, 1, 0), TEST_PRECISION);
     }
@@ -203,6 +210,246 @@ public class PlaneTest {
     }
 
     @Test
+    public void testFromPoints_collection_threePoints() {
+        // arrange
+        List<Vector3D> pts = Arrays.asList(
+                    Vector3D.of(1, 1, 0),
+                    Vector3D.of(1, 1, -1),
+                    Vector3D.of(0, 1, 0)
+                );
+
+        // act
+        Plane plane = Plane.fromPoints(pts, TEST_PRECISION);
+
+        // assert
+        checkPlane(plane, Vector3D.PLUS_Y, Vector3D.MINUS_Z, Vector3D.MINUS_X);
+
+        Assert.assertTrue(plane.contains(pts.get(0)));
+        Assert.assertTrue(plane.contains(pts.get(1)));
+        Assert.assertTrue(plane.contains(pts.get(2)));
+    }
+
+    @Test
+    public void testFromPoints_collection_someCollinearPoints() {
+        // arrange
+        List<Vector3D> pts = Arrays.asList(
+                    Vector3D.of(1, 0, 2),
+                    Vector3D.of(2, 0, 2),
+                    Vector3D.of(3, 0, 2),
+                    Vector3D.of(0, 1, 2)
+                );
+
+        // act
+        Plane plane = Plane.fromPoints(pts, TEST_PRECISION);
+
+        // assert
+        checkPlane(plane, Vector3D.of(0, 0, 2), Vector3D.PLUS_X, Vector3D.PLUS_Y);
+
+        Assert.assertTrue(plane.contains(pts.get(0)));
+        Assert.assertTrue(plane.contains(pts.get(1)));
+        Assert.assertTrue(plane.contains(pts.get(2)));
+        Assert.assertTrue(plane.contains(pts.get(3)));
+    }
+
+    @Test
+    public void testFromPoints_collection_concaveWithCollinearAndDuplicatePoints() {
+        // arrange
+        List<Vector3D> pts = Arrays.asList(
+                    Vector3D.of(1, 0, 1),
+                    Vector3D.of(1, 0, 0.5),
+
+                    Vector3D.of(1, 0, 0),
+                    Vector3D.of(1, 1, -1),
+                    Vector3D.of(1, 2, 0),
+                    Vector3D.of(1, 2, 1e-15),
+                    Vector3D.of(1, 1, -0.5),
+                    Vector3D.of(1, 1 + 1e-15, -0.5),
+                    Vector3D.of(1 - 1e-15, 1, -0.5),
+                    Vector3D.of(1, 0, 0),
+
+                    Vector3D.of(1, 0, 0.5),
+                    Vector3D.of(1, 0, 1)
+                );
+
+        Vector3D origin = Vector3D.of(1, 0, 0);
+
+        // act
+        checkPlane(Plane.fromPoints(pts, TEST_PRECISION),
+                origin, Vector3D.MINUS_Z, Vector3D.PLUS_Y);
+        checkPlane(Plane.fromPoints(rotate(pts, 1), TEST_PRECISION),
+                origin, Vector3D.MINUS_Z, Vector3D.PLUS_Y);
+
+        checkPlane(Plane.fromPoints(rotate(pts, 2), TEST_PRECISION),
+                origin, Vector3D.normalize(0, 1, -1), Vector3D.normalize(0, 1, 1));
+
+        checkPlane(Plane.fromPoints(rotate(pts, 3), TEST_PRECISION),
+                origin, Vector3D.normalize(0, 1, 1), Vector3D.normalize(0, -1, 1));
+
+        checkPlane(Plane.fromPoints(rotate(pts, 4), TEST_PRECISION),
+                origin, Vector3D.normalize(0, -1, -0.5), Vector3D.normalize(0, 0.5, -1));
+        checkPlane(Plane.fromPoints(rotate(pts, 5), TEST_PRECISION),
+                origin, Vector3D.normalize(0, -1, -0.5), Vector3D.normalize(0, 0.5, -1));
+
+        checkPlane(Plane.fromPoints(rotate(pts, 6), TEST_PRECISION),
+                origin, Vector3D.normalize(0, -1, 0.5), Vector3D.normalize(0, -0.5, -1));
+        checkPlane(Plane.fromPoints(rotate(pts, 7), TEST_PRECISION),
+                origin, Vector3D.normalize(0, -1, 0.5), Vector3D.normalize(0, -0.5, -1));
+        checkPlane(Plane.fromPoints(rotate(pts, 8), TEST_PRECISION),
+                origin, Vector3D.normalize(0, -1, 0.5), Vector3D.normalize(0, -0.5, -1));
+
+        checkPlane(Plane.fromPoints(rotate(pts, 9), TEST_PRECISION),
+                origin, Vector3D.PLUS_Z, Vector3D.MINUS_Y);
+        checkPlane(Plane.fromPoints(rotate(pts, 10), TEST_PRECISION),
+                origin, Vector3D.PLUS_Z, Vector3D.MINUS_Y);
+
+        checkPlane(Plane.fromPoints(rotate(pts, 11), TEST_PRECISION),
+                origin, Vector3D.MINUS_Z, Vector3D.PLUS_Y);
+    }
+
+    @Test
+    public void testFromPoints_collection_choosesBestOrientation() {
+        // act/assert
+        checkPlane(Plane.fromPoints(Arrays.asList(
+                Vector3D.of(1, 0, 2),
+                Vector3D.of(2, 0, 2),
+                Vector3D.of(3, 0, 2),
+                Vector3D.of(3.5, 1, 2)
+            ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.PLUS_X, Vector3D.PLUS_Y);
+
+        checkPlane(Plane.fromPoints(Arrays.asList(
+                Vector3D.of(1, 0, 2),
+                Vector3D.of(2, 0, 2),
+                Vector3D.of(3, 0, 2),
+                Vector3D.of(3.5, -1, 2)
+            ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.PLUS_X, Vector3D.MINUS_Y);
+
+        checkPlane(Plane.fromPoints(Arrays.asList(
+                Vector3D.of(1, 0, 2),
+                Vector3D.of(2, 0, 2),
+                Vector3D.of(3, 0, 2),
+                Vector3D.of(3.5, -1, 2),
+                Vector3D.of(4, 0, 2)
+            ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.PLUS_X, Vector3D.PLUS_Y);
+
+        checkPlane(Plane.fromPoints(Arrays.asList(
+                Vector3D.of(1, 0, 2),
+                Vector3D.of(2, 0, 2),
+                Vector3D.of(3, 0, 2),
+                Vector3D.of(3.5, 1, 2),
+                Vector3D.of(4, -1, 2)
+            ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.PLUS_X, Vector3D.MINUS_Y);
+
+        checkPlane(Plane.fromPoints(Arrays.asList(
+                Vector3D.of(0, 0, 2),
+                Vector3D.of(1, 0, 2),
+                Vector3D.of(1, 1, 2),
+                Vector3D.of(0, 1, 2),
+                Vector3D.of(0, 0, 2)
+            ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.PLUS_X, Vector3D.PLUS_Y);
+
+        checkPlane(Plane.fromPoints(Arrays.asList(
+                Vector3D.of(0, 0, 2),
+                Vector3D.of(0, 1, 2),
+                Vector3D.of(1, 1, 2),
+                Vector3D.of(1, 0, 2),
+                Vector3D.of(0, 0, 2)
+            ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.PLUS_Y, Vector3D.PLUS_X);
+
+        checkPlane(Plane.fromPoints(Arrays.asList(
+                Vector3D.of(0, 0, 2),
+                Vector3D.of(1, 0, 2),
+                Vector3D.of(2, 1, 2),
+                Vector3D.of(3, 0, 2),
+                Vector3D.of(2, 4, 2),
+                Vector3D.of(0, 0, 2)
+            ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.PLUS_X, Vector3D.PLUS_Y);
+
+        checkPlane(Plane.fromPoints(Arrays.asList(
+                Vector3D.of(0, 0, 2),
+                Vector3D.of(0, 1, 2),
+                Vector3D.of(2, 4, 2),
+                Vector3D.of(3, 0, 2),
+                Vector3D.of(2, 1, 2),
+                Vector3D.of(0, 0, 2)
+            ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.PLUS_Y, Vector3D.PLUS_X);
+    }
+
+    @Test
+    public void testFromPoints_collection_illegalArguments() {
+        // arrange
+        Vector3D a = Vector3D.ZERO;
+        Vector3D b = Vector3D.PLUS_X;
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            Plane.fromPoints(Arrays.asList(), TEST_PRECISION);
+        }, IllegalArgumentException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            Plane.fromPoints(Arrays.asList(a), TEST_PRECISION);
+        }, IllegalArgumentException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            Plane.fromPoints(Arrays.asList(a, b), TEST_PRECISION);
+        }, IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testFromPoints_collection_allPointsCollinear() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            Plane.fromPoints(Arrays.asList(
+                        Vector3D.ZERO,
+                        Vector3D.PLUS_X,
+                        Vector3D.of(2, 0, 0)
+                    ), TEST_PRECISION);
+        }, GeometryException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            Plane.fromPoints(Arrays.asList(
+                        Vector3D.ZERO,
+                        Vector3D.PLUS_X,
+                        Vector3D.of(2, 0, 0),
+                        Vector3D.of(3, 0, 0)
+                    ), TEST_PRECISION);
+        }, GeometryException.class);
+    }
+
+    @Test
+    public void testFromPoints_collection_notEnoughUniquePoints() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            Plane.fromPoints(Arrays.asList(
+                        Vector3D.ZERO,
+                        Vector3D.ZERO,
+                        Vector3D.of(1e-12, 1e-12, 0),
+                        Vector3D.PLUS_X
+                    ), TEST_PRECISION);
+        }, GeometryException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            Plane.fromPoints(Arrays.asList(
+                        Vector3D.ZERO,
+                        Vector3D.of(1e-12, 0, 0),
+                        Vector3D.ZERO
+                    ), TEST_PRECISION);
+        }, GeometryException.class);
+    }
+
+    @Test
+    public void testFromPoints_collection_pointsNotOnSamePlane() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            Plane.fromPoints(Arrays.asList(
+                        Vector3D.ZERO,
+                        Vector3D.PLUS_X,
+                        Vector3D.PLUS_Y,
+                        Vector3D.PLUS_Z
+                    ), TEST_PRECISION);
+        }, GeometryException.class);
+    }
+
+    @Test
     public void testRotate() {
         Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
         Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
@@ -304,4 +551,41 @@ public class PlaneTest {
                                                      TEST_PRECISION)));
     }
 
+    private static void checkPlane(Plane plane, Vector3D origin, Vector3D u, Vector3D v) {
+        u = u.normalize();
+        v = v.normalize();
+        Vector3D w = u.cross(v);
+
+        EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getOrigin(), TEST_EPS);
+        Assert.assertTrue(plane.contains(origin));
+
+        EuclideanTestUtils.assertCoordinatesEqual(u, plane.getU(), TEST_EPS);
+        Assert.assertEquals(1.0, plane.getU().norm(), TEST_EPS);
+
+        EuclideanTestUtils.assertCoordinatesEqual(v, plane.getV(), TEST_EPS);
+        Assert.assertEquals(1.0, plane.getV().norm(), TEST_EPS);
+
+        EuclideanTestUtils.assertCoordinatesEqual(w, plane.getW(), TEST_EPS);
+        Assert.assertEquals(1.0, plane.getW().norm(), TEST_EPS);
+
+        EuclideanTestUtils.assertCoordinatesEqual(w, plane.getNormal(), TEST_EPS);
+        Assert.assertEquals(1.0, plane.getNormal().norm(), TEST_EPS);
+
+        double offset = plane.getOriginOffset();
+        Assert.assertEquals(Vector3D.ZERO.distance(plane.getOrigin()), Math.abs(offset), TEST_EPS);
+        EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getNormal().multiply(-offset), TEST_EPS);
+    }
+
+
+    private static <T> List<T> rotate(List<T> list, int shift) {
+        int size = list.size();
+
+        List<T> result = new ArrayList<>(size);
+
+        for (int i=0; i<size; ++i) {
+            result.add(list.get((i + shift) % size));
+        }
+
+        return result;
+    }
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java
index 093501e..fe38389 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java
@@ -799,6 +799,47 @@ public class PolyhedronsSetTest {
         Assert.assertEquals(22.0, polyhedron.getBoundarySize(), TEST_EPS);
     }
 
+    // GEOMETRY-59
+    @Test
+    public void testCreateFromBRep_slightlyConcavePrism() {
+        // arrange
+        Vector3D vertices[] = {
+                Vector3D.of( 0, 0, 0 ),
+                Vector3D.of( 2, 1e-7, 0 ),
+                Vector3D.of( 4, 0, 0 ),
+                Vector3D.of( 2, 2, 0 ),
+                Vector3D.of( 0, 0, 2 ),
+                Vector3D.of( 2, 1e-7, 2 ),
+                Vector3D.of( 4, 0, 2 ),
+                Vector3D.of( 2, 2, 2 )
+        };
+
+        int facets[][] = {
+                { 4, 5, 6, 7 },
+                { 3, 2, 1, 0 },
+                { 0, 1, 5, 4 },
+                { 1, 2, 6, 5 },
+                { 2, 3, 7, 6 },
+                { 3, 0, 4, 7 }
+        };
+
+        // act
+        PolyhedronsSet prism = new PolyhedronsSet(
+                Arrays.asList(vertices),
+                Arrays.asList(facets),
+                TEST_PRECISION);
+
+
+        // assert
+        Assert.assertTrue(Double.isFinite(prism.getSize()));
+
+        checkPoints(Region.Location.INSIDE, prism, Vector3D.of(2, 1, 1));
+        checkPoints(Region.Location.OUTSIDE, prism,
+                Vector3D.of(2, 1, 3), Vector3D.of(2, 1, -3),
+                Vector3D.of(2, -1, 1), Vector3D.of(2, 3, 1),
+                Vector3D.of(-1, 1, 1), Vector3D.of(4, 1, 1));
+    }
+
     @Test
     public void testCreateFromBRep_verticesTooClose() throws IOException, ParseException {
         checkError("pentomino-N-too-close.ply", "Vertices are too close");
@@ -811,7 +852,7 @@ public class PolyhedronsSetTest {
 
     @Test
     public void testCreateFromBRep_nonPlanar() throws IOException, ParseException {
-        checkError("pentomino-N-out-of-plane.ply", "out of plane");
+        checkError("pentomino-N-out-of-plane.ply", "do not define a plane");
     }
 
     @Test
@@ -842,7 +883,7 @@ public class PolyhedronsSetTest {
         try {
             new PolyhedronsSet(vertices, facets, TEST_PRECISION);
             Assert.fail("an exception should have been thrown");
-        } catch (IllegalArgumentException e) {
+        } catch (RuntimeException e) {
             String actual = e.getMessage();
             Assert.assertTrue("Expected string to contain \"" + expected + "\" but was \"" + actual + "\"",
                     actual.contains(expected));