org.apache.commons.math3.geometry.partitioning.AbstractRegion Maven / Gradle / Ivy
Show all versions of commons-math3 Show documentation
/*
* 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.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import org.apache.commons.math3.exception.MathInternalError;
import org.apache.commons.math3.geometry.Space;
import org.apache.commons.math3.geometry.Vector;
/** Abstract class for all regions, independently of geometry type or dimension.
* @param Type of the space.
* @param Type of the sub-space.
* @version $Id: AbstractRegion.java 1416643 2012-12-03 19:37:14Z tn $
* @since 3.0
*/
public abstract class AbstractRegion implements Region {
/** Inside/Outside BSP tree. */
private BSPTree tree;
/** Size of the instance. */
private double size;
/** Barycenter. */
private Vector barycenter;
/** Build a region representing the whole space.
*/
protected AbstractRegion() {
tree = new BSPTree(Boolean.TRUE);
}
/** Build a region from an inside/outside BSP tree.
* The leaf nodes of the BSP tree must have a
* {@code Boolean} attribute representing the inside status of
* the corresponding cell (true for inside cells, false for outside
* cells). In order to avoid building too many small objects, it is
* recommended to use the predefined constants
* {@code Boolean.TRUE} and {@code Boolean.FALSE}. The
* tree also must have either null internal nodes or
* internal nodes representing the boundary as specified in the
* {@link #getTree getTree} method).
* @param tree inside/outside BSP tree representing the region
*/
protected AbstractRegion(final BSPTree tree) {
this.tree = tree;
}
/** Build a Region from a Boundary REPresentation (B-rep).
* The boundary is provided as a collection of {@link
* SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
* interior part of the region on its minus side and the exterior on
* its plus side.
* The boundary elements can be in any order, and can form
* several non-connected sets (like for example polygons with holes
* or a set of disjoints polyhedrons considered as a whole). In
* fact, the elements do not even need to be connected together
* (their topological connections are not used here). However, if the
* boundary does not really separate an inside open from an outside
* open (open having here its topological meaning), then subsequent
* calls to the {@link #checkPoint(Vector) checkPoint} method will not be
* meaningful anymore.
* If the boundary is empty, the region will represent the whole
* space.
* @param boundary collection of boundary elements, as a
* collection of {@link SubHyperplane SubHyperplane} objects
*/
protected AbstractRegion(final Collection> boundary) {
if (boundary.size() == 0) {
// the tree represents the whole space
tree = new BSPTree(Boolean.TRUE);
} else {
// sort the boundary elements in decreasing size order
// (we don't want equal size elements to be removed, so
// we use a trick to fool the TreeSet)
final TreeSet> ordered = new TreeSet>(new Comparator>() {
public int compare(final SubHyperplane o1, final SubHyperplane o2) {
final double size1 = o1.getSize();
final double size2 = o2.getSize();
return (size2 < size1) ? -1 : ((o1 == o2) ? 0 : +1);
}
});
ordered.addAll(boundary);
// build the tree top-down
tree = new BSPTree();
insertCuts(tree, ordered);
// set up the inside/outside flags
tree.visit(new BSPTreeVisitor() {
/** {@inheritDoc} */
public Order visitOrder(final BSPTree node) {
return Order.PLUS_SUB_MINUS;
}
/** {@inheritDoc} */
public void visitInternalNode(final BSPTree node) {
}
/** {@inheritDoc} */
public void visitLeafNode(final BSPTree node) {
node.setAttribute((node == node.getParent().getPlus()) ?
Boolean.FALSE : Boolean.TRUE);
}
});
}
}
/** Build a convex region from an array of bounding hyperplanes.
* @param hyperplanes array of bounding hyperplanes (if null, an
* empty region will be built)
*/
public AbstractRegion(final Hyperplane[] hyperplanes) {
if ((hyperplanes == null) || (hyperplanes.length == 0)) {
tree = new BSPTree(Boolean.FALSE);
} else {
// use the first hyperplane to build the right class
tree = hyperplanes[0].wholeSpace().getTree(false);
// chop off parts of the space
BSPTree node = tree;
node.setAttribute(Boolean.TRUE);
for (final Hyperplane hyperplane : hyperplanes) {
if (node.insertCut(hyperplane)) {
node.setAttribute(null);
node.getPlus().setAttribute(Boolean.FALSE);
node = node.getMinus();
node.setAttribute(Boolean.TRUE);
}
}
}
}
/** {@inheritDoc} */
public abstract AbstractRegion buildNew(BSPTree newTree);
/** Recursively build a tree by inserting cut sub-hyperplanes.
* @param node current tree node (it is a leaf node at the beginning
* of the call)
* @param boundary collection of edges belonging to the cell defined
* by the node
*/
private void insertCuts(final BSPTree node, final Collection> boundary) {
final Iterator> iterator = boundary.iterator();
// build the current level
Hyperplane inserted = null;
while ((inserted == null) && iterator.hasNext()) {
inserted = iterator.next().getHyperplane();
if (!node.insertCut(inserted.copySelf())) {
inserted = null;
}
}
if (!iterator.hasNext()) {
return;
}
// distribute the remaining edges in the two sub-trees
final ArrayList> plusList = new ArrayList>();
final ArrayList> minusList = new ArrayList>();
while (iterator.hasNext()) {
final SubHyperplane other = iterator.next();
switch (other.side(inserted)) {
case PLUS:
plusList.add(other);
break;
case MINUS:
minusList.add(other);
break;
case BOTH:
final SubHyperplane.SplitSubHyperplane split = other.split(inserted);
plusList.add(split.getPlus());
minusList.add(split.getMinus());
break;
default:
// ignore the sub-hyperplanes belonging to the cut hyperplane
}
}
// recurse through lower levels
insertCuts(node.getPlus(), plusList);
insertCuts(node.getMinus(), minusList);
}
/** {@inheritDoc} */
public AbstractRegion copySelf() {
return buildNew(tree.copySelf());
}
/** {@inheritDoc} */
public boolean isEmpty() {
return isEmpty(tree);
}
/** {@inheritDoc} */
public boolean isEmpty(final BSPTree node) {
// we use a recursive function rather than the BSPTreeVisitor
// interface because we can stop visiting the tree as soon as we
// have found an inside cell
if (node.getCut() == null) {
// if we find an inside node, the region is not empty
return !((Boolean) node.getAttribute());
}
// check both sides of the sub-tree
return isEmpty(node.getMinus()) && isEmpty(node.getPlus());
}
/** {@inheritDoc} */
public boolean contains(final Region region) {
return new RegionFactory().difference(region, this).isEmpty();
}
/** {@inheritDoc} */
public Location checkPoint(final Vector point) {
return checkPoint(tree, point);
}
/** Check a point with respect to the region starting at a given node.
* @param node root node of the region
* @param point point to check
* @return a code representing the point status: either {@link
* Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE
* OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY}
*/
protected Location checkPoint(final BSPTree node, final Vector point) {
final BSPTree cell = node.getCell(point);
if (cell.getCut() == null) {
// the point is in the interior of a cell, just check the attribute
return ((Boolean) cell.getAttribute()) ? Location.INSIDE : Location.OUTSIDE;
}
// the point is on a cut-sub-hyperplane, is it on a boundary ?
final Location minusCode = checkPoint(cell.getMinus(), point);
final Location plusCode = checkPoint(cell.getPlus(), point);
return (minusCode == plusCode) ? minusCode : Location.BOUNDARY;
}
/** {@inheritDoc} */
public BSPTree getTree(final boolean includeBoundaryAttributes) {
if (includeBoundaryAttributes && (tree.getCut() != null) && (tree.getAttribute() == null)) {
// we need to compute the boundary attributes
tree.visit(new BoundaryBuilder());
}
return tree;
}
/** Visitor building boundary shell tree.
*
* The boundary shell is represented as {@link BoundaryAttribute boundary attributes}
* at each internal node.
*
*/
private static class BoundaryBuilder implements BSPTreeVisitor {
/** {@inheritDoc} */
public Order visitOrder(BSPTree node) {
return Order.PLUS_MINUS_SUB;
}
/** {@inheritDoc} */
public void visitInternalNode(BSPTree node) {
SubHyperplane plusOutside = null;
SubHyperplane plusInside = null;
// characterize the cut sub-hyperplane,
// first with respect to the plus sub-tree
@SuppressWarnings("unchecked")
final SubHyperplane[] plusChar = (SubHyperplane[]) Array.newInstance(SubHyperplane.class, 2);
characterize(node.getPlus(), node.getCut().copySelf(), plusChar);
if (plusChar[0] != null && !plusChar[0].isEmpty()) {
// plusChar[0] corresponds to a subset of the cut sub-hyperplane known to have
// outside cells on its plus side, we want to check if parts of this subset
// do have inside cells on their minus side
@SuppressWarnings("unchecked")
final SubHyperplane[] minusChar = (SubHyperplane[]) Array.newInstance(SubHyperplane.class, 2);
characterize(node.getMinus(), plusChar[0], minusChar);
if (minusChar[1] != null && !minusChar[1].isEmpty()) {
// this part belongs to the boundary,
// it has the outside on its plus side and the inside on its minus side
plusOutside = minusChar[1];
}
}
if (plusChar[1] != null && !plusChar[1].isEmpty()) {
// plusChar[1] corresponds to a subset of the cut sub-hyperplane known to have
// inside cells on its plus side, we want to check if parts of this subset
// do have outside cells on their minus side
@SuppressWarnings("unchecked")
final SubHyperplane[] minusChar = (SubHyperplane[]) Array.newInstance(SubHyperplane.class, 2);
characterize(node.getMinus(), plusChar[1], minusChar);
if (minusChar[0] != null && !minusChar[0].isEmpty()) {
// this part belongs to the boundary,
// it has the inside on its plus side and the outside on its minus side
plusInside = minusChar[0];
}
}
// set the boundary attribute at non-leaf nodes
node.setAttribute(new BoundaryAttribute(plusOutside, plusInside));
}
/** {@inheritDoc} */
public void visitLeafNode(BSPTree node) {
}
/** Filter the parts of an hyperplane belonging to the boundary.
* The filtering consist in splitting the specified
* sub-hyperplane into several parts lying in inside and outside
* cells of the tree. The principle is to call this method twice for
* each cut sub-hyperplane in the tree, once one the plus node and
* once on the minus node. The parts that have the same flag
* (inside/inside or outside/outside) do not belong to the boundary
* while parts that have different flags (inside/outside or
* outside/inside) do belong to the boundary.
* @param node current BSP tree node
* @param sub sub-hyperplane to characterize
* @param characterization placeholder where to put the characterized parts
*/
private void characterize(final BSPTree node, final SubHyperplane sub,
final SubHyperplane[] characterization) {
if (node.getCut() == null) {
// we have reached a leaf node
final boolean inside = (Boolean) node.getAttribute();
if (inside) {
if (characterization[1] == null) {
characterization[1] = sub;
} else {
characterization[1] = characterization[1].reunite(sub);
}
} else {
if (characterization[0] == null) {
characterization[0] = sub;
} else {
characterization[0] = characterization[0].reunite(sub);
}
}
} else {
final Hyperplane hyperplane = node.getCut().getHyperplane();
switch (sub.side(hyperplane)) {
case PLUS:
characterize(node.getPlus(), sub, characterization);
break;
case MINUS:
characterize(node.getMinus(), sub, characterization);
break;
case BOTH:
final SubHyperplane.SplitSubHyperplane split = sub.split(hyperplane);
characterize(node.getPlus(), split.getPlus(), characterization);
characterize(node.getMinus(), split.getMinus(), characterization);
break;
default:
// this should not happen
throw new MathInternalError();
}
}
}
}
/** {@inheritDoc} */
public double getBoundarySize() {
final BoundarySizeVisitor visitor = new BoundarySizeVisitor();
getTree(true).visit(visitor);
return visitor.getSize();
}
/** {@inheritDoc} */
public double getSize() {
if (barycenter == null) {
computeGeometricalProperties();
}
return size;
}
/** Set the size of the instance.
* @param size size of the instance
*/
protected void setSize(final double size) {
this.size = size;
}
/** {@inheritDoc} */
public Vector getBarycenter() {
if (barycenter == null) {
computeGeometricalProperties();
}
return barycenter;
}
/** Set the barycenter of the instance.
* @param barycenter barycenter of the instance
*/
protected void setBarycenter(final Vector barycenter) {
this.barycenter = barycenter;
}
/** Compute some geometrical properties.
* The properties to compute are the barycenter and the size.
*/
protected abstract void computeGeometricalProperties();
/** {@inheritDoc} */
public Side side(final Hyperplane hyperplane) {
final Sides sides = new Sides();
recurseSides(tree, hyperplane.wholeHyperplane(), sides);
return sides.plusFound() ?
(sides.minusFound() ? Side.BOTH : Side.PLUS) :
(sides.minusFound() ? Side.MINUS : Side.HYPER);
}
/** Search recursively for inside leaf nodes on each side of the given hyperplane.
* The algorithm used here is directly derived from the one
* described in section III (Binary Partitioning of a BSP
* Tree) of the Bruce Naylor, John Amanatides and William
* Thibault paper Merging
* BSP Trees Yields Polyhedral Set Operations Proc. Siggraph
* '90, Computer Graphics 24(4), August 1990, pp 115-124, published
* by the Association for Computing Machinery (ACM)..
* @param node current BSP tree node
* @param sub sub-hyperplane
* @param sides object holding the sides found
*/
private void recurseSides(final BSPTree node, final SubHyperplane sub, final Sides sides) {
if (node.getCut() == null) {
if ((Boolean) node.getAttribute()) {
// this is an inside cell expanding across the hyperplane
sides.rememberPlusFound();
sides.rememberMinusFound();
}
return;
}
final Hyperplane hyperplane = node.getCut().getHyperplane();
switch (sub.side(hyperplane)) {
case PLUS :
// the sub-hyperplane is entirely in the plus sub-tree
if (node.getCut().side(sub.getHyperplane()) == Side.PLUS) {
if (!isEmpty(node.getMinus())) {
sides.rememberPlusFound();
}
} else {
if (!isEmpty(node.getMinus())) {
sides.rememberMinusFound();
}
}
if (!(sides.plusFound() && sides.minusFound())) {
recurseSides(node.getPlus(), sub, sides);
}
break;
case MINUS :
// the sub-hyperplane is entirely in the minus sub-tree
if (node.getCut().side(sub.getHyperplane()) == Side.PLUS) {
if (!isEmpty(node.getPlus())) {
sides.rememberPlusFound();
}
} else {
if (!isEmpty(node.getPlus())) {
sides.rememberMinusFound();
}
}
if (!(sides.plusFound() && sides.minusFound())) {
recurseSides(node.getMinus(), sub, sides);
}
break;
case BOTH :
// the sub-hyperplane extends in both sub-trees
final SubHyperplane.SplitSubHyperplane split = sub.split(hyperplane);
// explore first the plus sub-tree
recurseSides(node.getPlus(), split.getPlus(), sides);
// if needed, explore the minus sub-tree
if (!(sides.plusFound() && sides.minusFound())) {
recurseSides(node.getMinus(), split.getMinus(), sides);
}
break;
default :
// the sub-hyperplane and the cut sub-hyperplane share the same hyperplane
if (node.getCut().getHyperplane().sameOrientationAs(sub.getHyperplane())) {
if ((node.getPlus().getCut() != null) || ((Boolean) node.getPlus().getAttribute())) {
sides.rememberPlusFound();
}
if ((node.getMinus().getCut() != null) || ((Boolean) node.getMinus().getAttribute())) {
sides.rememberMinusFound();
}
} else {
if ((node.getPlus().getCut() != null) || ((Boolean) node.getPlus().getAttribute())) {
sides.rememberMinusFound();
}
if ((node.getMinus().getCut() != null) || ((Boolean) node.getMinus().getAttribute())) {
sides.rememberPlusFound();
}
}
}
}
/** Utility class holding the already found sides. */
private static final class Sides {
/** Indicator of inside leaf nodes found on the plus side. */
private boolean plusFound;
/** Indicator of inside leaf nodes found on the plus side. */
private boolean minusFound;
/** Simple constructor.
*/
public Sides() {
plusFound = false;
minusFound = false;
}
/** Remember the fact that inside leaf nodes have been found on the plus side.
*/
public void rememberPlusFound() {
plusFound = true;
}
/** Check if inside leaf nodes have been found on the plus side.
* @return true if inside leaf nodes have been found on the plus side
*/
public boolean plusFound() {
return plusFound;
}
/** Remember the fact that inside leaf nodes have been found on the minus side.
*/
public void rememberMinusFound() {
minusFound = true;
}
/** Check if inside leaf nodes have been found on the minus side.
* @return true if inside leaf nodes have been found on the minus side
*/
public boolean minusFound() {
return minusFound;
}
}
/** {@inheritDoc} */
public SubHyperplane intersection(final SubHyperplane sub) {
return recurseIntersection(tree, sub);
}
/** Recursively compute the parts of a sub-hyperplane that are
* contained in the region.
* @param node current BSP tree node
* @param sub sub-hyperplane traversing the region
* @return filtered sub-hyperplane
*/
private SubHyperplane recurseIntersection(final BSPTree node, final SubHyperplane sub) {
if (node.getCut() == null) {
return (Boolean) node.getAttribute() ? sub.copySelf() : null;
}
final Hyperplane hyperplane = node.getCut().getHyperplane();
switch (sub.side(hyperplane)) {
case PLUS :
return recurseIntersection(node.getPlus(), sub);
case MINUS :
return recurseIntersection(node.getMinus(), sub);
case BOTH :
final SubHyperplane.SplitSubHyperplane split = sub.split(hyperplane);
final SubHyperplane plus = recurseIntersection(node.getPlus(), split.getPlus());
final SubHyperplane minus = recurseIntersection(node.getMinus(), split.getMinus());
if (plus == null) {
return minus;
} else if (minus == null) {
return plus;
} else {
return plus.reunite(minus);
}
default :
return recurseIntersection(node.getPlus(),
recurseIntersection(node.getMinus(), sub));
}
}
/** Transform a region.
* Applying a transform to a region consist in applying the
* transform to all the hyperplanes of the underlying BSP tree and
* of the boundary (and also to the sub-hyperplanes embedded in
* these hyperplanes) and to the barycenter. The instance is not
* modified, a new instance is built.
* @param transform transform to apply
* @return a new region, resulting from the application of the
* transform to the instance
*/
public AbstractRegion applyTransform(final Transform transform) {
return buildNew(recurseTransform(getTree(false), transform));
}
/** Recursively transform an inside/outside BSP-tree.
* @param node current BSP tree node
* @param transform transform to apply
* @return a new tree
*/
@SuppressWarnings("unchecked")
private BSPTree recurseTransform(final BSPTree node, final Transform transform) {
if (node.getCut() == null) {
return new BSPTree(node.getAttribute());
}
final SubHyperplane sub = node.getCut();
final SubHyperplane tSub = ((AbstractSubHyperplane) sub).applyTransform(transform);
BoundaryAttribute attribute = (BoundaryAttribute) node.getAttribute();
if (attribute != null) {
final SubHyperplane tPO = (attribute.getPlusOutside() == null) ?
null : ((AbstractSubHyperplane) attribute.getPlusOutside()).applyTransform(transform);
final SubHyperplane tPI = (attribute.getPlusInside() == null) ?
null : ((AbstractSubHyperplane) attribute.getPlusInside()).applyTransform(transform);
attribute = new BoundaryAttribute(tPO, tPI);
}
return new BSPTree(tSub,
recurseTransform(node.getPlus(), transform),
recurseTransform(node.getMinus(), transform),
attribute);
}
}