From cfee7161823a1d24aa55e5807a6f31f1c3b6af7f Mon Sep 17 00:00:00 2001 From: Luc Maisonobe Date: Mon, 13 Jan 2014 12:42:40 +0000 Subject: [PATCH] Removed too tight link between edges and BSP tree. git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1557693 13f79535-47bb-0310-9956-ffa450edef68 --- .../spherical/twod/SphericalPolygonsSet.java | 122 ++++++++---------- .../twod/SphericalPolygonsSetTest.java | 76 +++++++++++ 2 files changed, 131 insertions(+), 67 deletions(-) diff --git a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java index ee86f3093..5ba2225e0 100644 --- a/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java +++ b/src/main/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSet.java @@ -19,7 +19,10 @@ package org.apache.commons.math3.geometry.spherical.twod; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; import org.apache.commons.math3.exception.MathIllegalStateException; import org.apache.commons.math3.exception.util.LocalizedFormats; @@ -225,16 +228,10 @@ public class SphericalPolygonsSet extends AbstractRegion { // find an edge with an hyperplane that can be inserted in the node int index = 0; - Edge inserted =null; + Edge inserted = null; while (inserted == null && index < edges.size()) { inserted = edges.get(index++); - if (inserted.getNode() == null) { - if (node.insertCut(inserted.getCircle())) { - inserted.setNode(node); - } else { - inserted = null; - } - } else { + if (!node.insertCut(inserted.getCircle())) { inserted = null; } } @@ -394,9 +391,6 @@ public class SphericalPolygonsSet extends AbstractRegion { /** Circle supporting the edge. */ private final Circle circle; - /** Node whose cut hyperplane contains this edge. */ - private BSPTree node; - /** Build an edge not contained in any node yet. * @param start start vertex * @param end end vertex @@ -409,7 +403,6 @@ public class SphericalPolygonsSet extends AbstractRegion { this.end = end; this.length = length; this.circle = circle; - this.node = null; // connect the vertices back to the edge start.setOutgoing(this); @@ -459,20 +452,6 @@ public class SphericalPolygonsSet extends AbstractRegion { return circle.getPointAt(alpha + circle.getPhase(start.getLocation().getVector())); } - /** Set the node whose cut hyperplane contains this edge. - * @param node node whose cut hyperplane contains this edge - */ - private void setNode(final BSPTree node) { - this.node = node; - } - - /** Get the node whose cut hyperplane contains this edge. - * @return node whose cut hyperplane contains this edge - */ - private BSPTree getNode() { - return node; - } - /** Connect the instance with a following edge. * @param next edge following the instance */ @@ -584,7 +563,6 @@ public class SphericalPolygonsSet extends AbstractRegion { // really add the edge subEnd.bindWith(splitCircle); final Edge edge = new Edge(subStart, subEnd, subLength, circle); - edge.setNode(node); list.add(edge); return subEnd; @@ -708,24 +686,32 @@ public class SphericalPolygonsSet extends AbstractRegion { root.visit(visitor); final List edges = visitor.getEdges(); + // convert the list of all edges into a list of start vertices loops = new ArrayList(); - for (final Edge edge : edges) { - if (edge.getNode() != null) { + while (!edges.isEmpty()) { - // this is an edge belonging to a new loop, store it - final Vertex startVertex = edge.getStart(); - loops.add(startVertex); + // this is an edge belonging to a new loop, store it + Edge edge = edges.get(0); + final Vertex startVertex = edge.getStart(); + loops.add(startVertex); - // disable all remaining edges in the same loop - for (Vertex vertex = edge.getEnd(); - vertex != startVertex; - vertex = vertex.getOutgoing().getEnd()) { - vertex.getIncoming().setNode(null); + // remove all remaining edges in the same loop + do { + + // remove one edge + for (final Iterator iterator = edges.iterator(); iterator.hasNext();) { + if (iterator.next() == edge) { + iterator.remove(); + break; + } } - startVertex.getIncoming().setNode(null); - } + // go to next edge following the boundary loop + edge = edge.getEnd().getOutgoing(); + + } while (edge.getStart() != startVertex); + } } @@ -744,17 +730,21 @@ public class SphericalPolygonsSet extends AbstractRegion { /** Tolerance below which points are consider to be identical. */ private final double tolerance; - /** Boundary edges. */ - private final List edges; + /** Built edges and their associated nodes. */ + private final Map> edgeToNode; + + /** Reversed map. */ + private final Map, List> nodeToEdgesList; /** Simple constructor. * @param root tree root * @param tolerance below which points are consider to be identical */ public EdgesBuilder(final BSPTree root, final double tolerance) { - this.root = root; - this.tolerance = tolerance; - this.edges = new ArrayList(); + this.root = root; + this.tolerance = tolerance; + this.edgeToNode = new IdentityHashMap>(); + this.nodeToEdgesList = new IdentityHashMap, List>(); } /** {@inheritDoc} */ @@ -764,6 +754,7 @@ public class SphericalPolygonsSet extends AbstractRegion { /** {@inheritDoc} */ public void visitInternalNode(final BSPTree node) { + nodeToEdgesList.put(node, new ArrayList()); @SuppressWarnings("unchecked") final BoundaryAttribute attribute = (BoundaryAttribute) node.getAttribute(); if (attribute.getPlusOutside() != null) { @@ -798,14 +789,14 @@ public class SphericalPolygonsSet extends AbstractRegion { } else { edge = new Edge(start, end, a.getSize(), circle); } - edge.setNode(node); - edges.add(edge); + edgeToNode.put(edge, node); + nodeToEdgesList.get(node).add(edge); } } /** Get the edge that should naturally follow another one. * @param previous edge to be continued - * @return other edge, starting where the other one ends (they + * @return other edge, starting where the previous one ends (they * have not been connected yet) * @exception MathIllegalStateException if there is not a single other edge */ @@ -816,31 +807,28 @@ public class SphericalPolygonsSet extends AbstractRegion { final S2Point point = previous.getEnd().getLocation(); final List> candidates = root.getCloseCuts(point, tolerance); - // filter the single other node we are interested in - BSPTree selected = null; + // the following edge we are looking for must start from one of the candidates nodes + double closest = tolerance; + Edge following = null; for (final BSPTree node : candidates) { - if (node != previous.getNode()) { - if (selected == null) { - selected = node; - } else { - throw new MathIllegalStateException(LocalizedFormats.OUTLINE_BOUNDARY_LOOP_OPEN); + for (final Edge edge : nodeToEdgesList.get(node)) { + if (edge != previous && edge.getStart().getIncoming() == null) { + final Vector3D edgeStart = edge.getStart().getLocation().getVector(); + final double gap = Vector3D.angle(point.getVector(), edgeStart); + if (gap <= closest) { + closest = gap; + following = edge; + } } } } - if (selected == null) { + + if (following == null) { + // this should never happen throw new MathIllegalStateException(LocalizedFormats.OUTLINE_BOUNDARY_LOOP_OPEN); } - // find the edge associated with the selected node - for (final Edge edge : edges) { - if (edge.getNode() == selected && - Vector3D.angle(point.getVector(), edge.getStart().getLocation().getVector()) <= tolerance) { - return edge; - } - } - - // we should never reach this point - throw new MathIllegalStateException(LocalizedFormats.OUTLINE_BOUNDARY_LOOP_OPEN); + return following; } @@ -851,11 +839,11 @@ public class SphericalPolygonsSet extends AbstractRegion { public List getEdges() throws MathIllegalStateException { // connect the edges - for (final Edge previous : edges) { + for (final Edge previous : edgeToNode.keySet()) { previous.setNextEdge(getFollowingEdge(previous)); } - return edges; + return new ArrayList(edgeToNode.keySet()); } diff --git a/src/test/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSetTest.java b/src/test/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSetTest.java index 58818cd65..c5af3973e 100644 --- a/src/test/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSetTest.java +++ b/src/test/java/org/apache/commons/math3/geometry/spherical/twod/SphericalPolygonsSetTest.java @@ -16,14 +16,20 @@ */ package org.apache.commons.math3.geometry.spherical.twod; +import java.util.ArrayList; import java.util.List; +import org.apache.commons.math3.geometry.euclidean.threed.Rotation; import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; import org.apache.commons.math3.geometry.partitioning.Region.Location; import org.apache.commons.math3.geometry.partitioning.RegionFactory; +import org.apache.commons.math3.geometry.partitioning.SubHyperplane; +import org.apache.commons.math3.geometry.spherical.oned.ArcsSet; +import org.apache.commons.math3.geometry.spherical.oned.Sphere1D; import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator; import org.apache.commons.math3.random.Well1024a; import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathUtils; import org.junit.Assert; import org.junit.Test; @@ -205,4 +211,74 @@ public class SphericalPolygonsSetTest { } + @Test + public void testModeratlyComplexShape() { + double tol = 0.01; + List> boundary = new ArrayList>(); + boundary.add(create(Vector3D.MINUS_J, Vector3D.PLUS_I, Vector3D.PLUS_K, tol, 0.0, 0.5 * FastMath.PI)); + boundary.add(create(Vector3D.MINUS_I, Vector3D.PLUS_K, Vector3D.PLUS_J, tol, 0.0, 0.5 * FastMath.PI)); + boundary.add(create(Vector3D.PLUS_K, Vector3D.PLUS_J, Vector3D.MINUS_I, tol, 0.0, 0.5 * FastMath.PI)); + boundary.add(create(Vector3D.MINUS_J, Vector3D.MINUS_I, Vector3D.MINUS_K, tol, 0.0, 0.5 * FastMath.PI)); + boundary.add(create(Vector3D.MINUS_I, Vector3D.MINUS_K, Vector3D.MINUS_J, tol, 0.0, 0.5 * FastMath.PI)); + boundary.add(create(Vector3D.PLUS_K, Vector3D.MINUS_J, Vector3D.PLUS_I, tol, 0.0, 0.5 * FastMath.PI)); + SphericalPolygonsSet polygon = new SphericalPolygonsSet(boundary, tol); + + Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1, 1, 1).normalize()))); + Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(new Vector3D(-1, 1, 1).normalize()))); + Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(new Vector3D(-1, -1, 1).normalize()))); + Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1, -1, 1).normalize()))); + Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1, 1, -1).normalize()))); + Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D(-1, 1, -1).normalize()))); + Assert.assertEquals(Location.INSIDE, polygon.checkPoint(new S2Point(new Vector3D(-1, -1, -1).normalize()))); + Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(new S2Point(new Vector3D( 1, -1, -1).normalize()))); + + Assert.assertEquals(MathUtils.TWO_PI, polygon.getSize(), 1.0e-10); + Assert.assertEquals(3 * FastMath.PI, polygon.getBoundarySize(), 1.0e-10); + + List loops = polygon.getBoundaryLoops(); + Assert.assertEquals(1, loops.size()); + boolean pXFound = false; + boolean mXFound = false; + boolean pYFound = false; + boolean mYFound = false; + boolean pZFound = false; + boolean mZFound = false; + SphericalPolygonsSet.Vertex first = loops.get(0); + int count = 0; + for (SphericalPolygonsSet.Vertex v = first; count == 0 || v != first; v = v.getOutgoing().getEnd()) { + ++count; + SphericalPolygonsSet.Edge e = v.getIncoming(); + Assert.assertTrue(v == e.getStart().getOutgoing().getEnd()); + pXFound = pXFound || v.getLocation().getVector().distance(Vector3D.PLUS_I) < 1.0e-10; + mXFound = mXFound || v.getLocation().getVector().distance(Vector3D.MINUS_I) < 1.0e-10; + pYFound = pYFound || v.getLocation().getVector().distance(Vector3D.PLUS_J) < 1.0e-10; + mYFound = mYFound || v.getLocation().getVector().distance(Vector3D.MINUS_J) < 1.0e-10; + pZFound = pZFound || v.getLocation().getVector().distance(Vector3D.PLUS_K) < 1.0e-10; + mZFound = mZFound || v.getLocation().getVector().distance(Vector3D.MINUS_K) < 1.0e-10; + Assert.assertEquals(0.5 * FastMath.PI, e.getLength(), 1.0e-10); + } + Assert.assertTrue(pXFound); + Assert.assertTrue(mXFound); + Assert.assertTrue(pYFound); + Assert.assertTrue(mYFound); + Assert.assertTrue(pZFound); + Assert.assertTrue(mZFound); + Assert.assertEquals(6, count); + + } + + private SubCircle create(Vector3D pole, Vector3D x, Vector3D y, + double tolerance, double ... limits) { + RegionFactory factory = new RegionFactory(); + Circle circle = new Circle(pole, tolerance); + Circle phased = + (Circle) Circle.getTransform(new Rotation(circle.getXAxis(), circle.getYAxis(), x, y)).apply(circle); + ArcsSet set = (ArcsSet) factory.getComplement(new ArcsSet(tolerance)); + for (int i = 0; i < limits.length; i += 2) { + set = (ArcsSet) factory.union(set, new ArcsSet(limits[i], limits[i + 1], tolerance)); + } + return new SubCircle(phased, set); + } + + }