Added dump/parse utilities for BSP trees.

These utilities are for test and debug purposes only.
This commit is contained in:
Luc Maisonobe 2015-04-09 22:13:45 +02:00
parent b4d2c75d39
commit 9744a812bd
3 changed files with 604 additions and 0 deletions

View File

@ -16,7 +16,11 @@
*/
package org.apache.commons.math3.geometry.euclidean.threed;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.math3.exception.MathArithmeticException;
import org.apache.commons.math3.exception.MathIllegalArgumentException;
@ -29,7 +33,9 @@ import org.apache.commons.math3.geometry.partitioning.BSPTree;
import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor;
import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute;
import org.apache.commons.math3.geometry.partitioning.Region;
import org.apache.commons.math3.geometry.partitioning.RegionDumper;
import org.apache.commons.math3.geometry.partitioning.RegionFactory;
import org.apache.commons.math3.geometry.partitioning.RegionParser;
import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
import org.apache.commons.math3.util.FastMath;
import org.junit.Assert;
@ -307,6 +313,57 @@ public class PolyhedronsSetTest {
}
}
@Test
public void testDumpParse() throws IOException, ParseException {
double tol=1e-8;
Vector3D[] verts=new Vector3D[8];
double xmin=-1,xmax=1;
double ymin=-1,ymax=1;
double zmin=-1,zmax=1;
verts[0]=new Vector3D(xmin,ymin,zmin);
verts[1]=new Vector3D(xmax,ymin,zmin);
verts[2]=new Vector3D(xmax,ymax,zmin);
verts[3]=new Vector3D(xmin,ymax,zmin);
verts[4]=new Vector3D(xmin,ymin,zmax);
verts[5]=new Vector3D(xmax,ymin,zmax);
verts[6]=new Vector3D(xmax,ymax,zmax);
verts[7]=new Vector3D(xmin,ymax,zmax);
//
int[][] faces=new int[12][];
faces[0]=new int[]{3,1,0}; // bottom (-z)
faces[1]=new int[]{1,3,2}; // bottom (-z)
faces[2]=new int[]{5,7,4}; // top (+z)
faces[3]=new int[]{7,5,6}; // top (+z)
faces[4]=new int[]{2,5,1}; // right (+x)
faces[5]=new int[]{5,2,6}; // right (+x)
faces[6]=new int[]{4,3,0}; // left (-x)
faces[7]=new int[]{3,4,7}; // left (-x)
faces[8]=new int[]{4,1,5}; // front (-y)
faces[9]=new int[]{1,4,0}; // front (-y)
faces[10]=new int[]{3,6,2}; // back (+y)
faces[11]=new int[]{6,3,7}; // back (+y)
//
Set<SubHyperplane<Euclidean3D>> pset=new HashSet<>();
for (int f=0; f<faces.length; f++) {
int[] vidx=faces[f];
Plane p=new Plane(verts[vidx[0]],verts[vidx[1]],verts[vidx[2]],tol);
Vector2D p0=p.toSubSpace(verts[vidx[0]]);
Vector2D p1=p.toSubSpace(verts[vidx[1]]);
Vector2D p2=p.toSubSpace(verts[vidx[2]]);
PolygonsSet lset=new PolygonsSet(tol,p0,p1,p2);
pset.add(new SubPlane(p,lset));
}
PolyhedronsSet polyset=new PolyhedronsSet(pset,tol);
Assert.assertEquals(8.0, polyset.getSize(), 1.0e-10);
Assert.assertEquals(24.0, polyset.getBoundarySize(), 1.0e-10);
String dump = RegionDumper.dump(polyset);
PolyhedronsSet parsed = RegionParser.parsePolyhedronsSet(dump);
Assert.assertEquals(8.0, parsed.getSize(), 1.0e-10);
Assert.assertEquals(24.0, parsed.getBoundarySize(), 1.0e-10);
Assert.assertTrue(new RegionFactory<Euclidean3D>().difference(polyset, parsed).isEmpty());
}
private void checkPoints(Region.Location expected, PolyhedronsSet tree, Vector3D[] points) {
for (int i = 0; i < points.length; ++i) {
Assert.assertEquals(expected, tree.checkPoint(points[i]));

View File

@ -0,0 +1,244 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.math3.geometry.partitioning;
import java.util.Formatter;
import java.util.Locale;
import org.apache.commons.math3.geometry.Space;
import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
import org.apache.commons.math3.geometry.euclidean.oned.OrientedPoint;
import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
import org.apache.commons.math3.geometry.euclidean.threed.Euclidean3D;
import org.apache.commons.math3.geometry.euclidean.threed.Plane;
import org.apache.commons.math3.geometry.euclidean.threed.PolyhedronsSet;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
import org.apache.commons.math3.geometry.euclidean.twod.Line;
import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.geometry.spherical.oned.ArcsSet;
import org.apache.commons.math3.geometry.spherical.oned.LimitAngle;
import org.apache.commons.math3.geometry.spherical.oned.Sphere1D;
import org.apache.commons.math3.geometry.spherical.twod.Circle;
import org.apache.commons.math3.geometry.spherical.twod.Sphere2D;
import org.apache.commons.math3.geometry.spherical.twod.SphericalPolygonsSet;
/** Class dumping a string representation of an {@link AbstractRegion}.
* <p>
* This class is intended for tests and debug purposes only.
* </p>
* @see RegionParser
* @since 3.5
*/
public class RegionDumper {
/** Private constructor for a utility class
*/
private RegionDumper() {
}
/** Get a string representation of an {@link ArcsSet}.
* @param arcsSet region to dump
* @return string representation of the region
*/
public static String dump(final ArcsSet arcsSet) {
final TreeDumper<Sphere1D> visitor = new TreeDumper<Sphere1D>("ArcsSet", arcsSet.getTolerance()) {
/** {@inheritDoc} */
@Override
protected void formatHyperplane(final Hyperplane<Sphere1D> hyperplane) {
final LimitAngle h = (LimitAngle) hyperplane;
getFormatter().format("%22.15e %b %22.15e",
h.getLocation().getAlpha(), h.isDirect(), h.getTolerance());
}
};
arcsSet.getTree(false).visit(visitor);
return visitor.getDump();
}
/** Get a string representation of a {@link SphericalPolygonsSet}.
* @param sphericalPolygonsSet region to dump
* @return string representation of the region
*/
public static String dump(final SphericalPolygonsSet sphericalPolygonsSet) {
final TreeDumper<Sphere2D> visitor = new TreeDumper<Sphere2D>("SphericalPolygonsSet", sphericalPolygonsSet.getTolerance()) {
/** {@inheritDoc} */
@Override
protected void formatHyperplane(final Hyperplane<Sphere2D> hyperplane) {
final Circle h = (Circle) hyperplane;
getFormatter().format("%22.15e %22.15e %22.15e %22.15e",
h.getPole().getX(), h.getPole().getY(), h.getPole().getZ(),
h.getTolerance());
}
};
sphericalPolygonsSet.getTree(false).visit(visitor);
return visitor.getDump();
}
/** Get a string representation of an {@link IntervalsSet}.
* @param intervalsSet region to dump
* @return string representation of the region
*/
public static String dump(final IntervalsSet intervalsSet) {
final TreeDumper<Euclidean1D> visitor = new TreeDumper<Euclidean1D>("IntervalsSet", intervalsSet.getTolerance()) {
/** {@inheritDoc} */
@Override
protected void formatHyperplane(final Hyperplane<Euclidean1D> hyperplane) {
final OrientedPoint h = (OrientedPoint) hyperplane;
getFormatter().format("%22.15e %b %22.15e",
h.getLocation().getX(), h.isDirect(), h.getTolerance());
}
};
intervalsSet.getTree(false).visit(visitor);
return visitor.getDump();
}
/** Get a string representation of a {@link PolygonsSet}.
* @param polygonsSet region to dump
* @return string representation of the region
*/
public static String dump(final PolygonsSet polygonsSet) {
final TreeDumper<Euclidean2D> visitor = new TreeDumper<Euclidean2D>("PolygonsSet", polygonsSet.getTolerance()) {
/** {@inheritDoc} */
@Override
protected void formatHyperplane(final Hyperplane<Euclidean2D> hyperplane) {
final Line h = (Line) hyperplane;
final Vector2D p = h.toSpace(Vector1D.ZERO);
getFormatter().format("%22.15e %22.15e %22.15e %22.15e",
p.getX(), p.getY(), h.getAngle(), h.getTolerance());
}
};
polygonsSet.getTree(false).visit(visitor);
return visitor.getDump();
}
/** Get a string representation of a {@link PolyhedronsSet}.
* @param polyhedronsSet region to dump
* @return string representation of the region
*/
public static String dump(final PolyhedronsSet polyhedronsSet) {
final TreeDumper<Euclidean3D> visitor = new TreeDumper<Euclidean3D>("PolyhedronsSet", polyhedronsSet.getTolerance()) {
/** {@inheritDoc} */
@Override
protected void formatHyperplane(final Hyperplane<Euclidean3D> hyperplane) {
final Plane h = (Plane) hyperplane;
final Vector3D p = h.toSpace(Vector2D.ZERO);
getFormatter().format("%22.15e %22.15e %22.15e %22.15e %22.15e %22.15e %22.15e",
p.getX(), p.getY(), p.getZ(),
h.getNormal().getX(), h.getNormal().getY(), h.getNormal().getZ(),
h.getTolerance());
}
};
polyhedronsSet.getTree(false).visit(visitor);
return visitor.getDump();
}
/** Dumping visitor.
* @param <S> Type of the space.
*/
private abstract static class TreeDumper<S extends Space> implements BSPTreeVisitor<S> {
/** Builder for the string representation of the dumped tree. */
private final StringBuilder dump;
/** Formatter for strings. */
private final Formatter formatter;
/** Current indentation prefix. */
private String prefix;
/** Simple constructor.
* @param type type of the region to dump
* @param tolerance tolerance of the region
*/
public TreeDumper(final String type, final double tolerance) {
this.dump = new StringBuilder();
this.formatter = new Formatter(dump, Locale.US);
this.prefix = "";
formatter.format("%s%n", type);
formatter.format("tolerance %22.15e%n", tolerance);
}
/** Get the string representation of the tree.
* @return string representation of the tree.
*/
public String getDump() {
return dump.toString();
}
/** Get the formatter to use.
* @return formatter to use
*/
protected Formatter getFormatter() {
return formatter;
}
/** Format a string representation of the hyperplane underlying a cut sub-hyperplane.
* @param hyperplane hyperplane to format
*/
protected abstract void formatHyperplane(Hyperplane<S> hyperplane);
/** {@inheritDoc} */
@Override
public Order visitOrder(final BSPTree<S> node) {
return Order.SUB_MINUS_PLUS;
}
/** {@inheritDoc} */
@Override
public void visitInternalNode(final BSPTree<S> node) {
formatter.format("%s %s internal ", prefix, type(node));
formatHyperplane(node.getCut().getHyperplane());
formatter.format("%n");
prefix = prefix + " ";
}
/** {@inheritDoc} */
@Override
public void visitLeafNode(final BSPTree<S> node) {
formatter.format("%s %s leaf %s%n",
prefix, type(node), node.getAttribute());
for (BSPTree<S> n = node;
n.getParent() != null && n == n.getParent().getPlus();
n = n.getParent()) {
prefix = prefix.substring(0, prefix.length() - 2);
}
}
/** Get the type of the node.
* @param node node to check
* @return "plus " or "minus" depending on the node being the plus or minus
* child of its parent ("plus " is arbitrarily returned for the root node)
*/
private String type(final BSPTree<S> node) {
return (node.getParent() != null && node == node.getParent().getMinus()) ? "minus" : "plus ";
}
}
}

View File

@ -0,0 +1,303 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.math3.geometry.partitioning;
import java.io.IOException;
import java.text.ParseException;
import java.util.StringTokenizer;
import org.apache.commons.math3.geometry.Space;
import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
import org.apache.commons.math3.geometry.euclidean.oned.OrientedPoint;
import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
import org.apache.commons.math3.geometry.euclidean.threed.Euclidean3D;
import org.apache.commons.math3.geometry.euclidean.threed.Plane;
import org.apache.commons.math3.geometry.euclidean.threed.PolyhedronsSet;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
import org.apache.commons.math3.geometry.euclidean.twod.Line;
import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.geometry.spherical.oned.ArcsSet;
import org.apache.commons.math3.geometry.spherical.oned.LimitAngle;
import org.apache.commons.math3.geometry.spherical.oned.S1Point;
import org.apache.commons.math3.geometry.spherical.oned.Sphere1D;
import org.apache.commons.math3.geometry.spherical.twod.Circle;
import org.apache.commons.math3.geometry.spherical.twod.Sphere2D;
import org.apache.commons.math3.geometry.spherical.twod.SphericalPolygonsSet;
/** Class parsing a string representation of an {@link AbstractRegion}.
* <p>
* This class is intended for tests and debug purposes only.
* </p>
* @see RegionDumper
* @since 3.5
*/
public class RegionParser {
/** Private constructor for a utility class
*/
private RegionParser() {
}
/** Parse a string representation of an {@link ArcsSet}.
* @param s string to parse
* @return parsed region
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
public static ArcsSet parseArcsSet(final String s)
throws IOException, ParseException {
final TreeBuilder<Sphere1D> builder = new TreeBuilder<Sphere1D>("ArcsSet", s) {
/** {@inheritDoc} */
@Override
protected LimitAngle parseHyperplane()
throws IOException, ParseException {
return new LimitAngle(new S1Point(getNumber()), getBoolean(), getNumber());
}
};
return new ArcsSet(builder.getTree(), builder.getTolerance());
}
/** Parse a string representation of a {@link SphericalPolygonsSet}.
* @param s string to parse
* @return parsed region
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
public static SphericalPolygonsSet parseSphericalPolygonsSet(final String s)
throws IOException, ParseException {
final TreeBuilder<Sphere2D> builder = new TreeBuilder<Sphere2D>("SphericalPolygonsSet", s) {
/** {@inheritDoc} */
@Override
public Circle parseHyperplane()
throws IOException, ParseException {
return new Circle(new Vector3D(getNumber(), getNumber(), getNumber()), getNumber());
}
};
return new SphericalPolygonsSet(builder.getTree(), builder.getTolerance());
}
/** Parse a string representation of an {@link IntervalsSet}.
* @param s string to parse
* @return parsed region
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
public static IntervalsSet parseIntervalsSet(final String s)
throws IOException, ParseException {
final TreeBuilder<Euclidean1D> builder = new TreeBuilder<Euclidean1D>("IntervalsSet", s) {
/** {@inheritDoc} */
@Override
public OrientedPoint parseHyperplane()
throws IOException, ParseException {
return new OrientedPoint(new Vector1D(getNumber()), getBoolean(), getNumber());
}
};
return new IntervalsSet(builder.getTree(), builder.getTolerance());
}
/** Parse a string representation of a {@link PolygonsSet}.
* @param s string to parse
* @return parsed region
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
public static PolygonsSet parsePolygonsSet(final String s)
throws IOException, ParseException {
final TreeBuilder<Euclidean2D> builder = new TreeBuilder<Euclidean2D>("PolygonsSet", s) {
/** {@inheritDoc} */
@Override
public Line parseHyperplane()
throws IOException, ParseException {
return new Line(new Vector2D(getNumber(), getNumber()), getNumber(), getNumber());
}
};
return new PolygonsSet(builder.getTree(), builder.getTolerance());
}
/** Parse a string representation of a {@link PolyhedronsSet}.
* @param s string to parse
* @return parsed region
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
public static PolyhedronsSet parsePolyhedronsSet(final String s)
throws IOException, ParseException {
final TreeBuilder<Euclidean3D> builder = new TreeBuilder<Euclidean3D>("PolyhedronsSet", s) {
/** {@inheritDoc} */
@Override
public Plane parseHyperplane()
throws IOException, ParseException {
return new Plane(new Vector3D(getNumber(), getNumber(), getNumber()),
new Vector3D(getNumber(), getNumber(), getNumber()),
getNumber());
}
};
return new PolyhedronsSet(builder.getTree(), builder.getTolerance());
}
/** Local class for building an {@link AbstractRegion} tree.
* @param <S> Type of the space.
*/
private abstract static class TreeBuilder<S extends Space> {
/** Keyword for tolerance. */
private static final String TOLERANCE = "tolerance";
/** Keyword for internal nodes. */
private static final String INTERNAL = "internal";
/** Keyword for leaf nodes. */
private static final String LEAF = "leaf";
/** Keyword for plus children trees. */
private static final String PLUS = "plus";
/** Keyword for minus children trees. */
private static final String MINUS = "minus";
/** Keyword for true flags. */
private static final String TRUE = "true";
/** Keyword for false flags. */
private static final String FALSE = "false";
/** Tree root. */
private BSPTree<S> root;
/** Tolerance. */
private final double tolerance;
/** Tokenizer parsing string representation. */
private final StringTokenizer tokenizer;
/** Simple constructor.
* @param type type of the expected representation
* @param reader reader for the string representation
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
public TreeBuilder(final String type, final String s)
throws IOException, ParseException {
root = null;
tokenizer = new StringTokenizer(s);
getWord(type);
getWord(TOLERANCE);
tolerance = getNumber();
getWord(PLUS);
root = new BSPTree<S>();
parseTree(root);
if (tokenizer.hasMoreTokens()) {
throw new ParseException("unexpected " + tokenizer.nextToken(), 0);
}
}
/** Parse a tree.
* @param node start node
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
private void parseTree(final BSPTree<S> node)
throws IOException, ParseException {
if (INTERNAL.equals(getWord(INTERNAL, LEAF))) {
// this is an internal node, it has a cut sub-hyperplane (stored as a whole hyperplane)
// then a minus tree, then a plus tree
node.insertCut(parseHyperplane());
getWord(MINUS);
parseTree(node.getMinus());
getWord(PLUS);
parseTree(node.getPlus());
} else {
// this is a leaf node, it has only an inside/outside flag
node.setAttribute(getBoolean());
}
}
/** Get next word.
* @param allowed allowed values
* @return parsed word
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
protected String getWord(final String ... allowed)
throws IOException, ParseException {
final String token = tokenizer.nextToken();
for (final String a : allowed) {
if (a.equals(token)) {
return token;
}
}
throw new ParseException(token + " != " + allowed[0], 0);
}
/** Get next number.
* @return parsed number
* @exception IOException if the string cannot be read
* @exception NumberFormatException if the string cannot be parsed
*/
protected double getNumber()
throws IOException, NumberFormatException {
return Double.parseDouble(tokenizer.nextToken());
}
/** Get next boolean.
* @return parsed boolean
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
protected boolean getBoolean()
throws IOException, ParseException {
return getWord(TRUE, FALSE).equals(TRUE);
}
/** Get the built tree.
* @return built tree
*/
public BSPTree<S> getTree() {
return root;
}
/** Get the tolerance.
* @return tolerance
*/
public double getTolerance() {
return tolerance;
}
/** Parse an hyperplane.
* @return next hyperplane from the stream
* @exception IOException if the string cannot be read
* @exception ParseException if the string cannot be parsed
*/
protected abstract Hyperplane<S> parseHyperplane()
throws IOException, ParseException;
}
}