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
This commit is contained in:
Luc Maisonobe 2014-01-13 12:42:40 +00:00
parent 0f13370f35
commit cfee716182
2 changed files with 131 additions and 67 deletions

View File

@ -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<Sphere2D, Sphere1D> {
// 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<Sphere2D, Sphere1D> {
/** Circle supporting the edge. */
private final Circle circle;
/** Node whose cut hyperplane contains this edge. */
private BSPTree<Sphere2D> 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<Sphere2D, Sphere1D> {
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<Sphere2D, Sphere1D> {
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<Sphere2D> node) {
this.node = node;
}
/** Get the node whose cut hyperplane contains this edge.
* @return node whose cut hyperplane contains this edge
*/
private BSPTree<Sphere2D> 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<Sphere2D, Sphere1D> {
// 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<Sphere2D, Sphere1D> {
root.visit(visitor);
final List<Edge> edges = visitor.getEdges();
// convert the list of all edges into a list of start vertices
loops = new ArrayList<Vertex>();
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<Edge> 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<Sphere2D, Sphere1D> {
/** Tolerance below which points are consider to be identical. */
private final double tolerance;
/** Boundary edges. */
private final List<Edge> edges;
/** Built edges and their associated nodes. */
private final Map<Edge, BSPTree<Sphere2D>> edgeToNode;
/** Reversed map. */
private final Map<BSPTree<Sphere2D>, List<Edge>> nodeToEdgesList;
/** Simple constructor.
* @param root tree root
* @param tolerance below which points are consider to be identical
*/
public EdgesBuilder(final BSPTree<Sphere2D> root, final double tolerance) {
this.root = root;
this.tolerance = tolerance;
this.edges = new ArrayList<Edge>();
this.root = root;
this.tolerance = tolerance;
this.edgeToNode = new IdentityHashMap<Edge, BSPTree<Sphere2D>>();
this.nodeToEdgesList = new IdentityHashMap<BSPTree<Sphere2D>, List<Edge>>();
}
/** {@inheritDoc} */
@ -764,6 +754,7 @@ public class SphericalPolygonsSet extends AbstractRegion<Sphere2D, Sphere1D> {
/** {@inheritDoc} */
public void visitInternalNode(final BSPTree<Sphere2D> node) {
nodeToEdgesList.put(node, new ArrayList<Edge>());
@SuppressWarnings("unchecked")
final BoundaryAttribute<Sphere2D> attribute = (BoundaryAttribute<Sphere2D>) node.getAttribute();
if (attribute.getPlusOutside() != null) {
@ -798,14 +789,14 @@ public class SphericalPolygonsSet extends AbstractRegion<Sphere2D, Sphere1D> {
} 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<Sphere2D, Sphere1D> {
final S2Point point = previous.getEnd().getLocation();
final List<BSPTree<Sphere2D>> candidates = root.getCloseCuts(point, tolerance);
// filter the single other node we are interested in
BSPTree<Sphere2D> 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<Sphere2D> 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<Sphere2D, Sphere1D> {
public List<Edge> 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<Edge>(edgeToNode.keySet());
}

View File

@ -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<SubHyperplane<Sphere2D>> boundary = new ArrayList<SubHyperplane<Sphere2D>>();
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<SphericalPolygonsSet.Vertex> 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<Sphere1D> factory = new RegionFactory<Sphere1D>();
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);
}
}