Repository: commons-math
Updated Branches:
  refs/heads/master e4a613ffe -> 3ea45970d


MATH-1442: fixing size calculation in PolyhedronsSet and updating the code to 
make the algorithm clearer


Project: http://git-wip-us.apache.org/repos/asf/commons-math/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-math/commit/31e4a6df
Tree: http://git-wip-us.apache.org/repos/asf/commons-math/tree/31e4a6df
Diff: http://git-wip-us.apache.org/repos/asf/commons-math/diff/31e4a6df

Branch: refs/heads/master
Commit: 31e4a6df19cf4a13f8f0e9048198ce7488ad2614
Parents: 6d9bc1a
Author: darkma773r <[email protected]>
Authored: Tue Jan 23 22:11:30 2018 -0500
Committer: darkma773r <[email protected]>
Committed: Tue Jan 23 22:11:30 2018 -0500

----------------------------------------------------------------------
 .../euclidean/threed/PolyhedronsSet.java        |  94 ++++++++++-----
 .../euclidean/threed/PolyhedronsSetTest.java    | 117 +++++++++++++++++++
 2 files changed, 181 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-math/blob/31e4a6df/src/main/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSet.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSet.java
 
b/src/main/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSet.java
index cf15f22..4459d34 100644
--- 
a/src/main/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSet.java
+++ 
b/src/main/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSet.java
@@ -353,30 +353,65 @@ public class PolyhedronsSet extends 
AbstractRegion<Euclidean3D, Euclidean2D> {
     /** {@inheritDoc} */
     @Override
     protected void computeGeometricalProperties() {
-
-        // compute the contribution of all boundary facets
-        getTree(true).visit(new FacetsContributionVisitor());
-
-        if (getSize() < 0) {
-            // the polyhedrons set as a finite outside
-            // surrounded by an infinite inside
+        // check simple cases first
+        if (isEmpty()) {
+            setSize(0.0);
+            setBarycenter((Point<Euclidean3D>) Cartesian3D.NaN);
+        }
+        else if (isFull()) {
             setSize(Double.POSITIVE_INFINITY);
             setBarycenter((Point<Euclidean3D>) Cartesian3D.NaN);
-        } else {
-            // the polyhedrons set is finite, apply the remaining scaling 
factors
-            setSize(getSize() / 3.0);
-            setBarycenter((Point<Euclidean3D>) new Cartesian3D(1.0 / (4 * 
getSize()), (Cartesian3D) getBarycenter()));
         }
+        else {
+            // not empty or full; compute the contribution of all boundary 
facets
+            final FacetsContributionVisitor contributionVisitor = new 
FacetsContributionVisitor();
+            getTree(true).visit(contributionVisitor);
 
+            final double size = contributionVisitor.getSize();
+            final Cartesian3D barycenter = contributionVisitor.getBarycenter();
+
+            if (size < 0) {
+                // the polyhedrons set is a finite outside surrounded by an 
infinite inside
+                setSize(Double.POSITIVE_INFINITY);
+                setBarycenter((Point<Euclidean3D>) Cartesian3D.NaN);
+            } else {
+                // the polyhedrons set is finite
+                setSize(size);
+                setBarycenter((Point<Euclidean3D>) barycenter);
+            }
+        }
     }
 
-    /** Visitor computing geometrical properties. */
-    private class FacetsContributionVisitor implements 
BSPTreeVisitor<Euclidean3D> {
+    /** Visitor computing polyhedron geometrical properties.
+     *  The volume of the polyhedron is computed using the equation
+     *  <code>V = 
(1/3)*&Sigma;<sub>F</sub>[(C<sub>F</sub>&sdot;N<sub>F</sub>)*area(F)]</code>,
+     *  where <code>F</code> represents each face in the polyhedron, 
<code>C<sub>F</sub></code>
+     *  represents the barycenter of the face, and <code>N<sub>F</sub></code> 
represents the
+     *  normal of the face. (More details can be found in the article
+     *  <a href="https://en.wikipedia.org/wiki/Polyhedron#Volume";>here</a>.)
+     *  This essentially splits up the polyhedron into pyramids with a 
polyhedron
+     *  face forming the base of each pyramid.
+     *  The barycenter is computed in a similar way. The barycenter of each 
pyramid
+     *  is calculated using the fact that it is located 3/4 of the way along 
the
+     *  line from the apex to the base. The polyhedron barycenter then becomes
+     *  the volume-weighted average of these pyramid centers.
+     */
+    private static class FacetsContributionVisitor implements 
BSPTreeVisitor<Euclidean3D> {
+
+        private double volumeSum;
+        private Cartesian3D barycenterSum = Cartesian3D.ZERO;
+
+        public double getSize() {
+            // apply the 1/3 pyramid volume scaling factor
+            return volumeSum / 3.0;
+        }
 
-        /** Simple constructor. */
-        FacetsContributionVisitor() {
-            setSize(0);
-            setBarycenter((Point<Euclidean3D>) new Cartesian3D(0, 0, 0));
+        public Cartesian3D getBarycenter() {
+            // Since the volume we used when adding together the facet 
contributions
+            // was 3x the actual pyramid size, we'll multiply by 1/4 here 
instead
+            // of 3/4. This will make the overall polyhedron volume the 
weighted
+            // average of the pyramid barycenters.
+            return new Cartesian3D(1.0 / (4 * getSize()), barycenterSum);
         }
 
         /** {@inheritDoc} */
@@ -404,34 +439,33 @@ public class PolyhedronsSet extends 
AbstractRegion<Euclidean3D, Euclidean2D> {
         public void visitLeafNode(final BSPTree<Euclidean3D> node) {
         }
 
-        /** Add he contribution of a boundary facet.
+        /** Add the contribution of a boundary facet.
          * @param facet boundary facet
          * @param reversed if true, the facet has the inside on its plus side
          */
         private void addContribution(final SubHyperplane<Euclidean3D> facet, 
final boolean reversed) {
 
             final Region<Euclidean2D> polygon = ((SubPlane) 
facet).getRemainingRegion();
-            final double area    = polygon.getSize();
+            final double area = polygon.getSize();
 
             if (Double.isInfinite(area)) {
-                setSize(Double.POSITIVE_INFINITY);
-                setBarycenter((Point<Euclidean3D>) Cartesian3D.NaN);
+                volumeSum = Double.POSITIVE_INFINITY;
+                barycenterSum = Cartesian3D.NaN;
             } else {
+                final Plane plane = (Plane) facet.getHyperplane();
+                final Cartesian3D facetBarycenter = 
plane.toSpace(polygon.getBarycenter());
 
-                final Plane    plane  = (Plane) facet.getHyperplane();
-                final Cartesian3D facetB = 
plane.toSpace(polygon.getBarycenter());
-                double   scaled = area * facetB.dotProduct(plane.getNormal());
+                // the volume here is actually 3x the actual pyramid volume; 
we'll apply
+                // the final scaling all at once at the end
+                double scaledVolume = area * 
facetBarycenter.dotProduct(plane.getNormal());
                 if (reversed) {
-                    scaled = -scaled;
+                    scaledVolume = -scaledVolume;
                 }
 
-                setSize(getSize() + scaled);
-                setBarycenter((Point<Euclidean3D>) new Cartesian3D(1.0, 
(Cartesian3D) getBarycenter(), scaled, facetB));
-
+                volumeSum += scaledVolume;
+                barycenterSum = new Cartesian3D(1.0, barycenterSum, 
scaledVolume, facetBarycenter);
             }
-
         }
-
     }
 
     /** Get the first sub-hyperplane crossed by a semi-infinite line.

http://git-wip-us.apache.org/repos/asf/commons-math/blob/31e4a6df/src/test/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSetTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSetTest.java
 
b/src/test/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSetTest.java
index 43553ea..1a64d2a 100644
--- 
a/src/test/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSetTest.java
+++ 
b/src/test/java/org/apache/commons/math4/geometry/euclidean/threed/PolyhedronsSetTest.java
@@ -59,6 +59,76 @@ import org.junit.Test;
 public class PolyhedronsSetTest {
 
     @Test
+    public void testFull() {
+        // act
+        PolyhedronsSet tree = new PolyhedronsSet(1e-10);
+
+        // assert
+        Assert.assertEquals(Double.POSITIVE_INFINITY, tree.getSize(), 1.0e-10);
+        Assert.assertEquals(0.0, tree.getBoundarySize(), 1.0e-10);
+        Cartesian3D center = (Cartesian3D) tree.getBarycenter();
+        Assert.assertEquals(Double.NaN, center.getX(), 1e-10);
+        Assert.assertEquals(Double.NaN, center.getY(), 1e-10);
+        Assert.assertEquals(Double.NaN, center.getZ(), 1e-10);
+        Assert.assertEquals(true, tree.isFull());
+        Assert.assertEquals(false, tree.isEmpty());
+
+        checkPoints(Region.Location.INSIDE, tree, new Cartesian3D[] {
+                new Cartesian3D(Double.POSITIVE_INFINITY, 
Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY),
+                Cartesian3D.ZERO,
+                new Cartesian3D(Double.NEGATIVE_INFINITY, 
Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)
+        });
+    }
+
+    @Test
+    public void testEmpty() {
+        // act
+        PolyhedronsSet tree = new PolyhedronsSet(new 
BSPTree<Euclidean3D>(Boolean.FALSE), 1e-10);
+
+        // assert
+        Assert.assertEquals(0.0, tree.getSize(), 1.0e-10);
+        Assert.assertEquals(0.0, tree.getBoundarySize(), 1.0e-10);
+        Cartesian3D center = (Cartesian3D) tree.getBarycenter();
+        Assert.assertEquals(Double.NaN, center.getX(), 1e-10);
+        Assert.assertEquals(Double.NaN, center.getY(), 1e-10);
+        Assert.assertEquals(Double.NaN, center.getZ(), 1e-10);
+        Assert.assertEquals(false, tree.isFull());
+        Assert.assertEquals(true, tree.isEmpty());
+
+        checkPoints(Region.Location.OUTSIDE, tree, new Cartesian3D[] {
+                new Cartesian3D(Double.POSITIVE_INFINITY, 
Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY),
+                Cartesian3D.ZERO,
+                new Cartesian3D(Double.NEGATIVE_INFINITY, 
Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)
+        });
+    }
+
+    @Test
+    public void testSingleInfiniteBoundary() {
+        // arrange
+        Plane plane = new Plane(Cartesian3D.ZERO, Cartesian3D.PLUS_K, 1e-10);
+
+        List<SubHyperplane<Euclidean3D>> boundaries = new ArrayList<>();
+        boundaries.add(plane.wholeHyperplane());
+
+        // act
+        PolyhedronsSet tree = new PolyhedronsSet(boundaries, 1e-10);
+
+        // assert
+        Assert.assertEquals(Double.POSITIVE_INFINITY, tree.getSize(), 1.0e-10);
+        Assert.assertEquals(Double.POSITIVE_INFINITY, tree.getBoundarySize(), 
1.0e-10);
+        Cartesian3D center = (Cartesian3D) tree.getBarycenter();
+        Assert.assertEquals(Double.NaN, center.getX(), 1e-10);
+        Assert.assertEquals(Double.NaN, center.getY(), 1e-10);
+        Assert.assertEquals(Double.NaN, center.getZ(), 1e-10);
+        Assert.assertEquals(false, tree.isFull());
+        Assert.assertEquals(false, tree.isEmpty());
+
+        checkPoints(Region.Location.BOUNDARY, tree, new Cartesian3D[] {
+                Cartesian3D.ZERO
+        });
+    }
+
+    @Test
     public void testBox() {
         PolyhedronsSet tree = new PolyhedronsSet(0, 1, 0, 1, 0, 1, 1.0e-10);
         Assert.assertEquals(1.0, tree.getSize(), 1.0e-10);
@@ -98,6 +168,53 @@ public class PolyhedronsSetTest {
     }
 
     @Test
+    public void testInvertedBox() {
+        // arrange
+        PolyhedronsSet tree = new PolyhedronsSet(0, 1, 0, 1, 0, 1, 1.0e-10);
+
+        // act
+        tree = (PolyhedronsSet) new 
RegionFactory<Euclidean3D>().getComplement(tree);
+
+        // assert
+        Assert.assertEquals(Double.POSITIVE_INFINITY, tree.getSize(), 1.0e-10);
+        Assert.assertEquals(6.0, tree.getBoundarySize(), 1.0e-10);
+
+        Cartesian3D barycenter = (Cartesian3D) tree.getBarycenter();
+        Assert.assertEquals(Double.NaN, barycenter.getX(), 1.0e-10);
+        Assert.assertEquals(Double.NaN, barycenter.getY(), 1.0e-10);
+        Assert.assertEquals(Double.NaN, barycenter.getZ(), 1.0e-10);
+
+        for (double x = -0.25; x < 1.25; x += 0.1) {
+            boolean xOK = (x < 0.0) || (x > 1.0);
+            for (double y = -0.25; y < 1.25; y += 0.1) {
+                boolean yOK = (y < 0.0) || (y > 1.0);
+                for (double z = -0.25; z < 1.25; z += 0.1) {
+                    boolean zOK = (z < 0.0) || (z > 1.0);
+                    Region.Location expected =
+                        (xOK || yOK || zOK) ? Region.Location.INSIDE : 
Region.Location.OUTSIDE;
+                    Assert.assertEquals(expected, tree.checkPoint(new 
Cartesian3D(x, y, z)));
+                }
+            }
+        }
+        checkPoints(Region.Location.BOUNDARY, tree, new Cartesian3D[] {
+            new Cartesian3D(0.0, 0.5, 0.5),
+            new Cartesian3D(1.0, 0.5, 0.5),
+            new Cartesian3D(0.5, 0.0, 0.5),
+            new Cartesian3D(0.5, 1.0, 0.5),
+            new Cartesian3D(0.5, 0.5, 0.0),
+            new Cartesian3D(0.5, 0.5, 1.0)
+        });
+        checkPoints(Region.Location.INSIDE, tree, new Cartesian3D[] {
+            new Cartesian3D(0.0, 1.2, 1.2),
+            new Cartesian3D(1.0, 1.2, 1.2),
+            new Cartesian3D(1.2, 0.0, 1.2),
+            new Cartesian3D(1.2, 1.0, 1.2),
+            new Cartesian3D(1.2, 1.2, 0.0),
+            new Cartesian3D(1.2, 1.2, 1.0)
+        });
+    }
+
+    @Test
     public void testTetrahedron() throws MathArithmeticException {
         Cartesian3D vertex1 = new Cartesian3D(1, 2, 3);
         Cartesian3D vertex2 = new Cartesian3D(2, 2, 4);

Reply via email to