blob: c15676c1233128cf61ae1632cf0eb0791ebfde35 [file] [log] [blame]
/*
* 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.geometry.core.partitioning;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.geometry.core.Point;
import org.apache.commons.geometry.core.partitioning.BSPTree.VanishingCutHandler;
import org.apache.commons.geometry.core.partitioning.Region.Location;
import org.apache.commons.geometry.core.partitioning.SubHyperplane.SplitSubHyperplane;
/** This class is a factory for {@link Region}.
* @param <P> Point type defining the space
*/
public class RegionFactory<P extends Point<P>> {
/** Visitor removing internal nodes attributes. */
private final NodesCleaner nodeCleaner;
/** Simple constructor.
*/
public RegionFactory() {
nodeCleaner = new NodesCleaner();
}
/** Build a convex region from a collection of bounding hyperplanes.
* @param hyperplanes collection of bounding hyperplanes
* @return a new convex region, or null if the collection is empty
*/
@SafeVarargs
public final Region<P> buildConvex(final Hyperplane<P> ... hyperplanes) {
if ((hyperplanes == null) || (hyperplanes.length == 0)) {
return null;
}
// use the first hyperplane to build the right class
final Region<P> region = hyperplanes[0].wholeSpace();
// chop off parts of the space
BSPTree<P> node = region.getTree(false);
node.setAttribute(Boolean.TRUE);
for (final Hyperplane<P> hyperplane : hyperplanes) {
if (node.insertCut(hyperplane)) {
node.setAttribute(null);
node.getPlus().setAttribute(Boolean.FALSE);
node = node.getMinus();
node.setAttribute(Boolean.TRUE);
} else {
// the hyperplane could not be inserted in the current leaf node
// either it is completely outside (which means the input hyperplanes
// are wrong), or it is parallel to a previous hyperplane
SubHyperplane<P> s = hyperplane.wholeHyperplane();
for (BSPTree<P> tree = node; tree.getParent() != null && s != null; tree = tree.getParent()) {
final Hyperplane<P> other = tree.getParent().getCut().getHyperplane();
final SplitSubHyperplane<P> split = s.split(other);
switch (split.getSide()) {
case HYPER :
// the hyperplane is parallel to a previous hyperplane
if (!hyperplane.sameOrientationAs(other)) {
// this hyperplane is opposite to the other one,
// the region is thinner than the tolerance, we consider it empty
return getComplement(hyperplanes[0].wholeSpace());
}
// the hyperplane is an extension of an already known hyperplane, we just ignore it
break;
case PLUS :
// the hyperplane is outside of the current convex zone,
// the input hyperplanes are inconsistent
throw new IllegalArgumentException("Hyperplanes do not define a convex region");
default :
s = split.getMinus();
}
}
}
}
return region;
}
/** Compute the union of two regions.
* @param region1 first region (will be unusable after the operation as
* parts of it will be reused in the new region)
* @param region2 second region (will be unusable after the operation as
* parts of it will be reused in the new region)
* @return a new region, result of {@code region1 union region2}
*/
public Region<P> union(final Region<P> region1, final Region<P> region2) {
final BSPTree<P> tree =
region1.getTree(false).merge(region2.getTree(false), new UnionMerger());
tree.visit(nodeCleaner);
return region1.buildNew(tree);
}
/** Compute the intersection of two regions.
* @param region1 first region (will be unusable after the operation as
* parts of it will be reused in the new region)
* @param region2 second region (will be unusable after the operation as
* parts of it will be reused in the new region)
* @return a new region, result of {@code region1 intersection region2}
*/
public Region<P> intersection(final Region<P> region1, final Region<P> region2) {
final BSPTree<P> tree =
region1.getTree(false).merge(region2.getTree(false), new IntersectionMerger());
tree.visit(nodeCleaner);
return region1.buildNew(tree);
}
/** Compute the symmetric difference (exclusive or) of two regions.
* @param region1 first region (will be unusable after the operation as
* parts of it will be reused in the new region)
* @param region2 second region (will be unusable after the operation as
* parts of it will be reused in the new region)
* @return a new region, result of {@code region1 xor region2}
*/
public Region<P> xor(final Region<P> region1, final Region<P> region2) {
final BSPTree<P> tree =
region1.getTree(false).merge(region2.getTree(false), new XorMerger());
tree.visit(nodeCleaner);
return region1.buildNew(tree);
}
/** Compute the difference of two regions.
* @param region1 first region (will be unusable after the operation as
* parts of it will be reused in the new region)
* @param region2 second region (will be unusable after the operation as
* parts of it will be reused in the new region)
* @return a new region, result of {@code region1 minus region2}
*/
public Region<P> difference(final Region<P> region1, final Region<P> region2) {
final BSPTree<P> tree =
region1.getTree(false).merge(region2.getTree(false), new DifferenceMerger(region1, region2));
tree.visit(nodeCleaner);
return region1.buildNew(tree);
}
/** Get the complement of the region (exchanged interior/exterior).
* @param region region to complement, it will not modified, a new
* region independent region will be built
* @return a new region, complement of the specified one
*/
/** Get the complement of the region (exchanged interior/exterior).
* @param region region to complement, it will not modified, a new
* region independent region will be built
* @return a new region, complement of the specified one
*/
public Region<P> getComplement(final Region<P> region) {
return region.buildNew(recurseComplement(region.getTree(false)));
}
/** Recursively build the complement of a BSP tree.
* @param node current node of the original tree
* @return new tree, complement of the node
*/
private BSPTree<P> recurseComplement(final BSPTree<P> node) {
// transform the tree, except for boundary attribute splitters
final Map<BSPTree<P>, BSPTree<P>> map = new HashMap<>();
final BSPTree<P> transformedTree = recurseComplement(node, map);
// set up the boundary attributes splitters
for (final Map.Entry<BSPTree<P>, BSPTree<P>> entry : map.entrySet()) {
if (entry.getKey().getCut() != null) {
@SuppressWarnings("unchecked")
BoundaryAttribute<P> original = (BoundaryAttribute<P>) entry.getKey().getAttribute();
if (original != null) {
@SuppressWarnings("unchecked")
BoundaryAttribute<P> transformed = (BoundaryAttribute<P>) entry.getValue().getAttribute();
for (final BSPTree<P> splitter : original.getSplitters()) {
transformed.getSplitters().add(map.get(splitter));
}
}
}
}
return transformedTree;
}
/** Recursively build the complement of a BSP tree.
* @param node current node of the original tree
* @param map transformed nodes map
* @return new tree, complement of the node
*/
private BSPTree<P> recurseComplement(final BSPTree<P> node,
final Map<BSPTree<P>, BSPTree<P>> map) {
final BSPTree<P> transformedNode;
if (node.getCut() == null) {
transformedNode = new BSPTree<>(((Boolean) node.getAttribute()) ? Boolean.FALSE : Boolean.TRUE);
} else {
@SuppressWarnings("unchecked")
BoundaryAttribute<P> attribute = (BoundaryAttribute<P>) node.getAttribute();
if (attribute != null) {
final SubHyperplane<P> plusOutside =
(attribute.getPlusInside() == null) ? null : attribute.getPlusInside().copySelf();
final SubHyperplane<P> plusInside =
(attribute.getPlusOutside() == null) ? null : attribute.getPlusOutside().copySelf();
// we start with an empty list of splitters, it will be filled in out of recursion
attribute = new BoundaryAttribute<>(plusOutside, plusInside, new NodesSet<P>());
}
transformedNode = new BSPTree<>(node.getCut().copySelf(),
recurseComplement(node.getPlus(), map),
recurseComplement(node.getMinus(), map),
attribute);
}
map.put(node, transformedNode);
return transformedNode;
}
/** BSP tree leaf merger computing union of two regions. */
private class UnionMerger implements BSPTree.LeafMerger<P> {
/** {@inheritDoc} */
@Override
public BSPTree<P> merge(final BSPTree<P> leaf, final BSPTree<P> tree,
final BSPTree<P> parentTree,
final boolean isPlusChild, final boolean leafFromInstance) {
if ((Boolean) leaf.getAttribute()) {
// the leaf node represents an inside cell
leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
return leaf;
}
// the leaf node represents an outside cell
tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false));
return tree;
}
}
/** BSP tree leaf merger computing intersection of two regions. */
private class IntersectionMerger implements BSPTree.LeafMerger<P> {
/** {@inheritDoc} */
@Override
public BSPTree<P> merge(final BSPTree<P> leaf, final BSPTree<P> tree,
final BSPTree<P> parentTree,
final boolean isPlusChild, final boolean leafFromInstance) {
if ((Boolean) leaf.getAttribute()) {
// the leaf node represents an inside cell
tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
return tree;
}
// the leaf node represents an outside cell
leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false));
return leaf;
}
}
/** BSP tree leaf merger computing symmetric difference (exclusive or) of two regions. */
private class XorMerger implements BSPTree.LeafMerger<P> {
/** {@inheritDoc} */
@Override
public BSPTree<P> merge(final BSPTree<P> leaf, final BSPTree<P> tree,
final BSPTree<P> parentTree, final boolean isPlusChild,
final boolean leafFromInstance) {
BSPTree<P> t = tree;
if ((Boolean) leaf.getAttribute()) {
// the leaf node represents an inside cell
t = recurseComplement(t);
}
t.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
return t;
}
}
/** BSP tree leaf merger computing difference of two regions. */
private class DifferenceMerger implements BSPTree.LeafMerger<P>, VanishingCutHandler<P> {
/** Region to subtract from. */
private final Region<P> region1;
/** Region to subtract. */
private final Region<P> region2;
/** Simple constructor.
* @param region1 region to subtract from
* @param region2 region to subtract
*/
DifferenceMerger(final Region<P> region1, final Region<P> region2) {
this.region1 = region1.copySelf();
this.region2 = region2.copySelf();
}
/** {@inheritDoc} */
@Override
public BSPTree<P> merge(final BSPTree<P> leaf, final BSPTree<P> tree,
final BSPTree<P> parentTree, final boolean isPlusChild,
final boolean leafFromInstance) {
if ((Boolean) leaf.getAttribute()) {
// the leaf node represents an inside cell
final BSPTree<P> argTree =
recurseComplement(leafFromInstance ? tree : leaf);
argTree.insertInTree(parentTree, isPlusChild, this);
return argTree;
}
// the leaf node represents an outside cell
final BSPTree<P> instanceTree =
leafFromInstance ? leaf : tree;
instanceTree.insertInTree(parentTree, isPlusChild, this);
return instanceTree;
}
/** {@inheritDoc} */
@Override
public BSPTree<P> fixNode(final BSPTree<P> node) {
// get a representative point in the degenerate cell
final BSPTree<P> cell = node.pruneAroundConvexCell(Boolean.TRUE, Boolean.FALSE, null);
final Region<P> r = region1.buildNew(cell);
final P p = r.getBarycenter();
return new BSPTree<>(region1.checkPoint(p) == Location.INSIDE &&
region2.checkPoint(p) == Location.OUTSIDE);
}
}
/** Visitor removing internal nodes attributes. */
private class NodesCleaner implements BSPTreeVisitor<P> {
/** {@inheritDoc} */
@Override
public Order visitOrder(final BSPTree<P> node) {
return Order.PLUS_SUB_MINUS;
}
/** {@inheritDoc} */
@Override
public void visitInternalNode(final BSPTree<P> node) {
node.setAttribute(null);
}
/** {@inheritDoc} */
@Override
public void visitLeafNode(final BSPTree<P> node) {
}
}
/** Handler replacing nodes with vanishing cuts with leaf nodes. */
private class VanishingToLeaf implements VanishingCutHandler<P> {
/** Inside/outside indocator to use for ambiguous nodes. */
private final boolean inside;
/** Simple constructor.
* @param inside inside/outside indicator to use for ambiguous nodes
*/
VanishingToLeaf(final boolean inside) {
this.inside = inside;
}
/** {@inheritDoc} */
@Override
public BSPTree<P> fixNode(final BSPTree<P> node) {
if (node.getPlus().getAttribute().equals(node.getMinus().getAttribute())) {
// no ambiguity
return new BSPTree<>(node.getPlus().getAttribute());
} else {
// ambiguous node
return new BSPTree<>(inside);
}
}
}
}