org.apache.commons.math3.geometry.partitioning.RegionFactory Maven / Gradle / Ivy
The newest version!
/*
* 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.HashMap;
import java.util.Map;
import org.apache.commons.math3.exception.MathIllegalArgumentException;
import org.apache.commons.math3.exception.util.LocalizedFormats;
import org.apache.commons.math3.geometry.Point;
import org.apache.commons.math3.geometry.Space;
import org.apache.commons.math3.geometry.partitioning.BSPTree.VanishingCutHandler;
import org.apache.commons.math3.geometry.partitioning.Region.Location;
import org.apache.commons.math3.geometry.partitioning.SubHyperplane.SplitSubHyperplane;
/** This class is a factory for {@link Region}.
* @param Type of the space.
* @since 3.0
*/
public class RegionFactory {
/** 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
*/
public Region buildConvex(final Hyperplane ... hyperplanes) {
if ((hyperplanes == null) || (hyperplanes.length == 0)) {
return null;
}
// use the first hyperplane to build the right class
final Region region = hyperplanes[0].wholeSpace();
// chop off parts of the space
BSPTree node = region.getTree(false);
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);
} 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 s = hyperplane.wholeHyperplane();
for (BSPTree tree = node; tree.getParent() != null && s != null; tree = tree.getParent()) {
final Hyperplane other = tree.getParent().getCut().getHyperplane();
final SplitSubHyperplane 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 MathIllegalArgumentException(LocalizedFormats.NOT_CONVEX_HYPERPLANES);
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 union(final Region region1, final Region region2) {
final BSPTree 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 intersection(final Region region1, final Region region2) {
final BSPTree 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 xor(final Region region1, final Region region2) {
final BSPTree 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 difference(final Region region1, final Region region2) {
final BSPTree 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 getComplement(final Region 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 recurseComplement(final BSPTree node) {
// transform the tree, except for boundary attribute splitters
final Map, BSPTree> map = new HashMap, BSPTree>();
final BSPTree transformedTree = recurseComplement(node, map);
// set up the boundary attributes splitters
for (final Map.Entry, BSPTree> entry : map.entrySet()) {
if (entry.getKey().getCut() != null) {
@SuppressWarnings("unchecked")
BoundaryAttribute original = (BoundaryAttribute) entry.getKey().getAttribute();
if (original != null) {
@SuppressWarnings("unchecked")
BoundaryAttribute transformed = (BoundaryAttribute) entry.getValue().getAttribute();
for (final BSPTree 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 recurseComplement(final BSPTree node,
final Map, BSPTree> map) {
final BSPTree transformedNode;
if (node.getCut() == null) {
transformedNode = new BSPTree(((Boolean) node.getAttribute()) ? Boolean.FALSE : Boolean.TRUE);
} else {
@SuppressWarnings("unchecked")
BoundaryAttribute attribute = (BoundaryAttribute) node.getAttribute();
if (attribute != null) {
final SubHyperplane plusOutside =
(attribute.getPlusInside() == null) ? null : attribute.getPlusInside().copySelf();
final SubHyperplane 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());
}
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 {
/** {@inheritDoc} */
public BSPTree merge(final BSPTree leaf, final BSPTree tree,
final BSPTree 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 {
/** {@inheritDoc} */
public BSPTree merge(final BSPTree leaf, final BSPTree tree,
final BSPTree 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 {
/** {@inheritDoc} */
public BSPTree merge(final BSPTree leaf, final BSPTree tree,
final BSPTree parentTree, final boolean isPlusChild,
final boolean leafFromInstance) {
BSPTree 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, VanishingCutHandler {
/** Region to subtract from. */
private final Region region1;
/** Region to subtract. */
private final Region region2;
/** Simple constructor.
* @param region1 region to subtract from
* @param region2 region to subtract
*/
DifferenceMerger(final Region region1, final Region region2) {
this.region1 = region1.copySelf();
this.region2 = region2.copySelf();
}
/** {@inheritDoc} */
public BSPTree merge(final BSPTree leaf, final BSPTree tree,
final BSPTree parentTree, final boolean isPlusChild,
final boolean leafFromInstance) {
if ((Boolean) leaf.getAttribute()) {
// the leaf node represents an inside cell
final BSPTree argTree =
recurseComplement(leafFromInstance ? tree : leaf);
argTree.insertInTree(parentTree, isPlusChild, this);
return argTree;
}
// the leaf node represents an outside cell
final BSPTree instanceTree =
leafFromInstance ? leaf : tree;
instanceTree.insertInTree(parentTree, isPlusChild, this);
return instanceTree;
}
/** {@inheritDoc} */
public BSPTree fixNode(final BSPTree node) {
// get a representative point in the degenerate cell
final BSPTree cell = node.pruneAroundConvexCell(Boolean.TRUE, Boolean.FALSE, null);
final Region r = region1.buildNew(cell);
final Point 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 {
/** {@inheritDoc} */
public Order visitOrder(final BSPTree node) {
return Order.PLUS_SUB_MINUS;
}
/** {@inheritDoc} */
public void visitInternalNode(final BSPTree node) {
node.setAttribute(null);
}
/** {@inheritDoc} */
public void visitLeafNode(final BSPTree node) {
}
}
/** Handler replacing nodes with vanishing cuts with leaf nodes. */
private class VanishingToLeaf implements VanishingCutHandler {
/** 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} */
public BSPTree fixNode(final BSPTree node) {
if (node.getPlus().getAttribute().equals(node.getMinus().getAttribute())) {
// no ambiguity
return new BSPTree(node.getPlus().getAttribute());
} else {
// ambiguous node
return new BSPTree(inside);
}
}
}
}