blob: 402c080330291489fd60ec13d480deb5422ce79e [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.bsp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import org.apache.commons.geometry.core.Point;
import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.internal.IteratorTransform;
import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.core.partitioning.SubHyperplane;
import org.apache.commons.geometry.core.partitioning.bsp.BSPTreeVisitor.ClosestFirstVisitor;
/** {@link BSPTree} specialized for representing regions of space. For example, this
* class can be used to represent polygons in Euclidean 2D space and polyhedrons
* in Euclidean 3D space.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
*/
public abstract class AbstractRegionBSPTree<
P extends Point<P>,
N extends AbstractRegionBSPTree.AbstractRegionNode<P, N>>
extends AbstractBSPTree<P, N> implements HyperplaneBoundedRegion<P> {
/** Value used to indicate an unknown size. */
private static final double UNKNOWN_SIZE = -1.0;
/** The region boundary size; this is computed when requested and then cached. */
private double boundarySize = UNKNOWN_SIZE;
/** The current size properties for the region. */
private RegionSizeProperties<P> regionSizeProperties;
/** Construct a new region will the given boolean determining whether or not the
* region will be full (including the entire space) or empty (excluding the entire
* space).
* @param full if true, the region will cover the entire space, otherwise it will
* be empty
*/
protected AbstractRegionBSPTree(final boolean full) {
getRoot().setLocation(full ? RegionLocation.INSIDE : RegionLocation.OUTSIDE);
}
/** {@inheritDoc} */
@Override
public boolean isEmpty() {
return !hasNodeWithLocationRecursive(getRoot(), RegionLocation.INSIDE);
}
/** {@inheritDoc} */
@Override
public boolean isFull() {
return !hasNodeWithLocationRecursive(getRoot(), RegionLocation.OUTSIDE);
}
/** Return true if any node in the subtree rooted at the given node has a location with the
* given value.
* @param node the node at the root of the subtree to search
* @param location the location to find
* @return true if any node in the subtree has the given location
*/
private boolean hasNodeWithLocationRecursive(final AbstractRegionNode<P, N> node, final RegionLocation location) {
if (node == null) {
return false;
}
return node.getLocation() == location ||
hasNodeWithLocationRecursive(node.getMinus(), location) ||
hasNodeWithLocationRecursive(node.getPlus(), location);
}
/** Modify this instance so that it contains the entire space.
* @see #isFull()
*/
public void setFull() {
final N root = getRoot();
root.clearCut();
root.setLocation(RegionLocation.INSIDE);
}
/** Modify this instance so that is is completely empty.
* @see #isEmpty()
*/
public void setEmpty() {
final N root = getRoot();
root.clearCut();
root.setLocation(RegionLocation.OUTSIDE);
}
/** {@inheritDoc} */
@Override
public double getSize() {
return getRegionSizeProperties().getSize();
}
/** {@inheritDoc} */
@Override
public double getBoundarySize() {
if (boundarySize < 0.0) {
double sum = 0.0;
RegionCutBoundary<P> boundary;
for (final AbstractRegionNode<P, N> node : this) {
boundary = node.getCutBoundary();
if (boundary != null) {
sum += boundary.getInsideFacing().getSize();
sum += boundary.getOutsideFacing().getSize();
}
}
boundarySize = sum;
}
return boundarySize;
}
/** Return an {@link Iterable} for iterating over the boundaries of the region.
* Each boundary is oriented such that its plus side points to the outside of the
* region. The exact ordering of the boundaries is determined by the internal structure
* of the tree.
* @return an {@link Iterable} for iterating over the boundaries of the region
* @see #getBoundaries()
*/
public Iterable<? extends ConvexSubHyperplane<P>> boundaries() {
return createBoundaryIterable(Function.identity());
}
/** Internal method for creating the iterable instances used to iterate the region boundaries.
* @param typeConverter function to convert the generic convex subhyperplane type into
* the type specific for this tree
* @param <C> ConvexSubhyperplane implementation type
* @return an iterable to iterating the region boundaries
*/
protected <C extends ConvexSubHyperplane<P>> Iterable<C> createBoundaryIterable(
final Function<ConvexSubHyperplane<P>, C> typeConverter) {
return new Iterable<C>() {
@Override
public Iterator<C> iterator() {
final NodeIterator<P, N> nodeIterator = new NodeIterator<>(getRoot());
return new RegionBoundaryIterator<>(nodeIterator, typeConverter);
}
};
}
/** Return a list containing the boundaries of the region. Each boundary is oriented such
* that its plus side points to the outside of the region. The exact ordering of
* the boundaries is determined by the internal structure of the tree.
* @return a list of the boundaries of the region
*/
public List<? extends ConvexSubHyperplane<P>> getBoundaries() {
return createBoundaryList(Function.identity());
}
/** Iternal method for creating a list of the region boundaries.
* @param typeConverter function to convert the generic convex subhyperplane type into
* the type specific for this tree
* @param <C> ConvexSubhyperplane implementation type
* @return a list of the region boundaries
*/
protected <C extends ConvexSubHyperplane<P>> List<C> createBoundaryList(
final Function<ConvexSubHyperplane<P>, C> typeConverter) {
final List<C> result = new ArrayList<>();
final RegionBoundaryIterator<P, C, N> it = new RegionBoundaryIterator<>(iterator(), typeConverter);
it.forEachRemaining(result::add);
return result;
}
/** {@inheritDoc} */
@Override
public P project(P pt) {
final BoundaryProjector<P, N> projector = new BoundaryProjector<>(pt);
accept(projector);
return projector.getProjected();
}
/** {@inheritDoc} */
@Override
public P getBarycenter() {
return getRegionSizeProperties().getBarycenter();
}
/** Helper method implementing the algorithm for splitting a tree by a hyperplane. Subclasses
* should call this method with two instantiated trees of the correct type.
* @param splitter splitting hyperplane
* @param minus tree that will contain the minus side of the split result
* @param plus tree that will contain the plus side of the split result
* @param <T> Tree implementation type
* @return result of splitting this tree with the given hyperplane
*/
protected <T extends AbstractRegionBSPTree<P, N>> Split<T> split(final Hyperplane<P> splitter,
final T minus, final T plus) {
splitIntoTrees(splitter, minus, plus);
T splitMinus = null;
T splitPlus = null;
if (minus != null) {
minus.getRoot().getPlus().setLocation(RegionLocation.OUTSIDE);
minus.condense();
splitMinus = minus.isEmpty() ? null : minus;
}
if (plus != null) {
plus.getRoot().getMinus().setLocation(RegionLocation.OUTSIDE);
plus.condense();
splitPlus = plus.isEmpty() ? null : plus;
}
return new Split<>(splitMinus, splitPlus);
}
/** Get the size-related properties for the region. The value is computed
* lazily and cached.
* @return the size-related properties for the region
*/
protected RegionSizeProperties<P> getRegionSizeProperties() {
if (regionSizeProperties == null) {
regionSizeProperties = computeRegionSizeProperties();
}
return regionSizeProperties;
}
/** Compute the size-related properties of the region.
* @return object containing size properties for the region
*/
protected abstract RegionSizeProperties<P> computeRegionSizeProperties();
/** {@inheritDoc}
*
* <p>If the point is {@link org.apache.commons.geometry.core.Spatial#isNaN() NaN}, then
* {@link RegionLocation#OUTSIDE} is returned.</p>
*/
@Override
public RegionLocation classify(final P point) {
if (point.isNaN()) {
return RegionLocation.OUTSIDE;
}
return classifyRecursive(getRoot(), point);
}
/** Recursively classify a point with respect to the region.
* @param node the node to classify against
* @param point the point to classify
* @return the classification of the point with respect to the region rooted
* at the given node
*/
private RegionLocation classifyRecursive(final AbstractRegionNode<P, N> node, final P point) {
if (node.isLeaf()) {
// the point is in a leaf, so the classification is just the leaf location
return node.getLocation();
} else {
final HyperplaneLocation cutLoc = node.getCutHyperplane().classify(point);
if (cutLoc == HyperplaneLocation.MINUS) {
return classifyRecursive(node.getMinus(), point);
} else if (cutLoc == HyperplaneLocation.PLUS) {
return classifyRecursive(node.getPlus(), point);
} else {
// the point is on the cut boundary; classify against both child
// subtrees and see if we end up with the same result or not
RegionLocation minusLoc = classifyRecursive(node.getMinus(), point);
RegionLocation plusLoc = classifyRecursive(node.getPlus(), point);
if (minusLoc == plusLoc) {
return minusLoc;
}
return RegionLocation.BOUNDARY;
}
}
}
/** Change this region into its complement. All inside nodes become outside
* nodes and vice versa. The orientation of the cut subhyperplanes is not modified.
*/
public void complement() {
complementRecursive(getRoot());
}
/** Set this instance to be the complement of the given tree. The argument
* is not modified.
* @param tree the tree to become the complement of
*/
public void complement(final AbstractRegionBSPTree<P, N> tree) {
copySubtree(tree.getRoot(), getRoot());
complementRecursive(getRoot());
}
/** Recursively switch all inside nodes to outside nodes and vice versa.
* @param node the node at the root of the subtree to switch
*/
private void complementRecursive(final AbstractRegionNode<P, N> node) {
if (node != null) {
final RegionLocation newLoc = (node.getLocationValue() == RegionLocation.INSIDE) ?
RegionLocation.OUTSIDE :
RegionLocation.INSIDE;
node.setLocation(newLoc);
complementRecursive(node.getMinus());
complementRecursive(node.getPlus());
}
}
/** Compute the union of this instance and the given region, storing the result back in
* this instance. The argument is not modified.
* @param other the tree to compute the union with
*/
public void union(final AbstractRegionBSPTree<P, N> other) {
new UnionOperator<P, N>().apply(this, other, this);
}
/** Compute the union of the two regions passed as arguments and store the result in
* this instance. Any nodes currently existing in this instance are removed.
* @param a first argument to the union operation
* @param b second argument to the union operation
*/
public void union(final AbstractRegionBSPTree<P, N> a, final AbstractRegionBSPTree<P, N> b) {
new UnionOperator<P, N>().apply(a, b, this);
}
/** Compute the intersection of this instance and the given region, storing the result back in
* this instance. The argument is not modified.
* @param other the tree to compute the intersection with
*/
public void intersection(final AbstractRegionBSPTree<P, N> other) {
new IntersectionOperator<P, N>().apply(this, other, this);
}
/** Compute the intersection of the two regions passed as arguments and store the result in
* this instance. Any nodes currently existing in this instance are removed.
* @param a first argument to the intersection operation
* @param b second argument to the intersection operation
*/
public void intersection(final AbstractRegionBSPTree<P, N> a, final AbstractRegionBSPTree<P, N> b) {
new IntersectionOperator<P, N>().apply(a, b, this);
}
/** Compute the difference of this instance and the given region, storing the result back in
* this instance. The argument is not modified.
* @param other the tree to compute the difference with
*/
public void difference(final AbstractRegionBSPTree<P, N> other) {
new DifferenceOperator<P, N>().apply(this, other, this);
}
/** Compute the difference of the two regions passed as arguments and store the result in
* this instance. Any nodes currently existing in this instance are removed.
* @param a first argument to the difference operation
* @param b second argument to the difference operation
*/
public void difference(final AbstractRegionBSPTree<P, N> a, final AbstractRegionBSPTree<P, N> b) {
new DifferenceOperator<P, N>().apply(a, b, this);
}
/** Compute the symmetric difference (xor) of this instance and the given region, storing the result back in
* this instance. The argument is not modified.
* @param other the tree to compute the symmetric difference with
*/
public void xor(final AbstractRegionBSPTree<P, N> other) {
new XorOperator<P, N>().apply(this, other, this);
}
/** Compute the symmetric difference (xor) of the two regions passed as arguments and store the result in
* this instance. Any nodes currently existing in this instance are removed.
* @param a first argument to the symmetric difference operation
* @param b second argument to the symmetric difference operation
*/
public void xor(final AbstractRegionBSPTree<P, N> a, final AbstractRegionBSPTree<P, N> b) {
new XorOperator<P, N>().apply(a, b, this);
}
/** Condense this tree by removing redundant subtrees.
*
* <p>This operation can be used to reduce the total number of nodes in the
* tree after performing node manipulations. For example, if two sibling leaf
* nodes both represent the same {@link RegionLocation}, then there is no reason
* from the perspective of the geometric region to retain both nodes. They are
* therefore both merged into their parent node. This method performs this
* simplification process.
* </p>
*/
protected void condense() {
condenseRecursive(getRoot());
}
/** Recursively condense nodes that have children with homogenous location attributes
* (eg, both inside, both outside) into single nodes.
* @param node the root of the subtree to condense
* @return the location of the successfully condensed subtree or null if no condensing was
* able to be performed
*/
private RegionLocation condenseRecursive(final N node) {
if (node.isLeaf()) {
return node.getLocation();
}
final RegionLocation minusLocation = condenseRecursive(node.getMinus());
final RegionLocation plusLocation = condenseRecursive(node.getPlus());
if (minusLocation != null && plusLocation != null && minusLocation == plusLocation) {
node.setLocation(minusLocation);
node.clearCut();
return minusLocation;
}
return null;
}
/** {@inheritDoc} */
@Override
protected void copyNodeProperties(final N src, final N dst) {
dst.setLocation(src.getLocationValue());
}
/** Compute the portion of the node's cut subhyperplane that lies on the boundary of
* the region.
* @param node the node to compute the cut subhyperplane boundary of
* @return object representing the portions of the node's cut subhyperplane that lie
* on the region's boundary
*/
private RegionCutBoundary<P> computeBoundary(final N node) {
if (node.isLeaf()) {
// no boundary for leaf nodes; they are either entirely in or
// entirely out
return null;
}
ConvexSubHyperplane<P> sub = node.getCut();
// find the portions of the node cut sub-hyperplane that touch inside and
// outside cells in the minus sub-tree
SubHyperplane.Builder<P> minusInBuilder = sub.builder();
SubHyperplane.Builder<P> minusOutBuilder = sub.builder();
characterizeSubHyperplane(sub, node.getMinus(), minusInBuilder, minusOutBuilder);
List<? extends ConvexSubHyperplane<P>> minusIn = minusInBuilder.build().toConvex();
List<? extends ConvexSubHyperplane<P>> minusOut = minusOutBuilder.build().toConvex();
// create the result boundary builders
SubHyperplane.Builder<P> insideFacing = sub.builder();
SubHyperplane.Builder<P> outsideFacing = sub.builder();
if (!minusIn.isEmpty()) {
// Add to the boundary anything that touches an inside cell in the minus sub-tree
// and an outside cell in the plus sub-tree. These portions are oriented with their
// plus side pointing to the outside of the region.
for (ConvexSubHyperplane<P> minusInFragment : minusIn) {
characterizeSubHyperplane(minusInFragment, node.getPlus(), null, outsideFacing);
}
}
if (!minusOut.isEmpty()) {
// Add to the boundary anything that touches an outside cell in the minus sub-tree
// and an inside cell in the plus sub-tree. These portions are oriented with their
// plus side pointing to the inside of the region.
for (ConvexSubHyperplane<P> minusOutFragment : minusOut) {
characterizeSubHyperplane(minusOutFragment, node.getPlus(), insideFacing, null);
}
}
return new RegionCutBoundary<>(insideFacing.build(), outsideFacing.build());
}
/** Recursive method to characterize a convex subhyperplane with respect to the region's
* boundaries.
* @param sub the subhyperplane to characterize
* @param node the node to characterize the subhyperplane against
* @param in the builder that will receive the portions of the subhyperplane that lie in the inside
* of the region; may be null
* @param out the builder that will receive the portions of the subhyperplane that lie on the outside
* of the region; may be null
*/
private void characterizeSubHyperplane(final ConvexSubHyperplane<P> sub, final AbstractRegionNode<P, N> node,
final SubHyperplane.Builder<P> in, final SubHyperplane.Builder<P> out) {
if (sub != null) {
if (node.isLeaf()) {
if (node.isInside() && in != null) {
in.add(sub);
} else if (node.isOutside() && out != null) {
out.add(sub);
}
} else {
final Split<? extends ConvexSubHyperplane<P>> split = sub.split(node.getCutHyperplane());
// Continue further on down the subtree with the same subhyperplane if the
// subhyperplane lies directly on the current node's cut
if (split.getLocation() == SplitLocation.NEITHER) {
characterizeSubHyperplane(sub, node.getPlus(), in, out);
characterizeSubHyperplane(sub, node.getMinus(), in, out);
} else {
characterizeSubHyperplane(split.getPlus(), node.getPlus(), in, out);
characterizeSubHyperplane(split.getMinus(), node.getMinus(), in, out);
}
}
}
}
/** {@inheritDoc} */
@Override
protected void initChildNode(final N parent, final N child, final boolean isPlus) {
super.initChildNode(parent, child, isPlus);
child.setLocation(isPlus ? RegionLocation.OUTSIDE : RegionLocation.INSIDE);
}
/** {@inheritDoc} */
@Override
protected void invalidate() {
super.invalidate();
// clear cached region properties
boundarySize = UNKNOWN_SIZE;
regionSizeProperties = null;
}
/** {@link BSPTree.Node} implementation for use with {@link AbstractRegionBSPTree}s.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
*/
public abstract static class AbstractRegionNode<P extends Point<P>, N extends AbstractRegionNode<P, N>>
extends AbstractBSPTree.AbstractNode<P, N> {
/** The location for the node. This will only be set on leaf nodes. */
private RegionLocation location;
/** Object representing the part of the node cut subhyperplane that lies on the
* region boundary. This is calculated lazily and is only present on internal nodes.
*/
private RegionCutBoundary<P> cutBoundary;
/** Simple constructor.
* @param tree owning tree instance
*/
protected AbstractRegionNode(AbstractBSPTree<P, N> tree) {
super(tree);
}
/** {@inheritDoc} */
@Override
public AbstractRegionBSPTree<P, N> getTree() {
// cast to our parent tree type
return (AbstractRegionBSPTree<P, N>) super.getTree();
}
/** Get the location of the node. This value will only be non-null for
* leaf nodes.
* @return the location of the node; will be null for internal nodes
*/
public RegionLocation getLocation() {
return isLeaf() ? location : null;
}
/** True if the node is a leaf node and has a location of {@link RegionLocation#INSIDE}.
* @return true if the node is a leaf node and has a location of
* {@link RegionLocation#INSIDE}
*/
public boolean isInside() {
return getLocation() == RegionLocation.INSIDE;
}
/** True if the node is a leaf node and has a location of {@link RegionLocation#OUTSIDE}.
* @return true if the node is a leaf node and has a location of
* {@link RegionLocation#OUTSIDE}
*/
public boolean isOutside() {
return getLocation() == RegionLocation.OUTSIDE;
}
/** Get the portion of the node's cut subhyperplane that lies on the boundary of the
* region.
* @return the portion of the node's cut subhyperplane that lies on the boundary of
* the region
*/
public RegionCutBoundary<P> getCutBoundary() {
if (!isLeaf()) {
checkValid();
if (cutBoundary == null) {
cutBoundary = getTree().computeBoundary(getSelf());
}
}
return cutBoundary;
}
/** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName())
.append("[cut= ")
.append(getCut())
.append(", location= ")
.append(getLocation())
.append("]");
return sb.toString();
}
/** {@inheritDoc} */
@Override
protected void nodeInvalidated() {
super.nodeInvalidated();
// null any computed boundary value since it is no longer valid
cutBoundary = null;
}
/** Set the location attribute for the node.
* @param location the location attribute for the node
*/
protected void setLocation(final RegionLocation location) {
this.location = location;
}
/** Get the value of the location property, unmodified based on the
* node's leaf state.
* @return the value of the location property
*/
protected RegionLocation getLocationValue() {
return location;
}
}
/** Class containing the basic algorithm for merging region BSP trees.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
*/
public abstract static class RegionMergeOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
extends AbstractBSPTreeMergeOperator<P, N> {
/** Merge two input trees, storing the output in the third. The output tree can be one of the
* input trees. The output tree is condensed before the method returns.
* @param inputTree1 first input tree
* @param inputTree2 second input tree
* @param outputTree the tree that will contain the result of the merge; may be one
* of the input trees
*/
public void apply(final AbstractRegionBSPTree<P, N> inputTree1, final AbstractRegionBSPTree<P, N> inputTree2,
final AbstractRegionBSPTree<P, N> outputTree) {
this.performMerge(inputTree1, inputTree2, outputTree);
outputTree.condense();
}
}
/** Class for performing boolean union operations on region trees.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
*/
public static class UnionOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
extends RegionMergeOperator<P, N> {
/** {@inheritDoc} */
@Override
protected N mergeLeaf(final N node1, final N node2) {
if (node1.isLeaf()) {
return node1.isInside() ? node1 : node2;
}
// call again with flipped arguments
return mergeLeaf(node2, node1);
}
}
/** Class for performing boolean intersection operations on region trees.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
*/
public static class IntersectionOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
extends RegionMergeOperator<P, N> {
/** {@inheritDoc} */
@Override
protected N mergeLeaf(final N node1, final N node2) {
if (node1.isLeaf()) {
return node1.isInside() ? node2 : node1;
}
// call again with flipped arguments
return mergeLeaf(node2, node1);
}
}
/** Class for performing boolean difference operations on region trees.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
*/
public static class DifferenceOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
extends RegionMergeOperator<P, N> {
/** {@inheritDoc} */
@Override
protected N mergeLeaf(final N node1, final N node2) {
// a region is included if it belongs in tree1 and is not in tree2
if (node1.isInside()) {
// this region is inside of tree1, so only include subregions that are
// not in tree2, ie include everything in node2's complement
final N output = outputSubtree(node2);
output.getTree().complementRecursive(output);
return output;
} else if (node2.isInside()) {
// this region is inside of tree2 and so cannot be in the result region
final N output = outputNode();
output.setLocation(RegionLocation.OUTSIDE);
return output;
}
// this region is not in tree2, so we can include everything in tree1
return node1;
}
}
/** Class for performing boolean symmetric difference (xor) operations on region trees.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
*/
public static class XorOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
extends RegionMergeOperator<P, N> {
/** {@inheritDoc} */
@Override
protected N mergeLeaf(final N node1, final N node2) {
// a region is included if it belongs in tree1 and is not in tree2 OR
// it belongs in tree2 and is not in tree1
if (node1.isLeaf()) {
if (node1.isInside()) {
// this region is inside node1, so only include subregions that are
// not in node2, ie include everything in node2's complement
final N output = outputSubtree(node2);
output.getTree().complementRecursive(output);
return output;
} else {
// this region is not in node1, so only include subregions that
// in node2
return node2;
}
}
// the operation is symmetric, so perform the same operation but with the
// nodes flipped
return mergeLeaf(node2, node1);
}
}
/** Class used to compute the point on the region's boundary that is closest to a target point.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
*/
protected static class BoundaryProjector<P extends Point<P>, N extends AbstractRegionNode<P, N>>
extends ClosestFirstVisitor<P, N> {
/** The projected point. */
private P projected;
/** The current closest distance to the boundary found. */
private double minDist = -1.0;
/** Simple constructor.
* @param point the point to project onto the region's boundary
*/
public BoundaryProjector(final P point) {
super(point);
}
/** {@inheritDoc} */
@Override
public VisitResult visit(final N node) {
final P point = getTarget();
if (node.isInternal() && (minDist < 0.0 || isPossibleClosestCut(node.getCut(), point, minDist))) {
final RegionCutBoundary<P> boundary = node.getCutBoundary();
final P boundaryPt = boundary.closest(point);
final double dist = boundaryPt.distance(point);
final int cmp = Double.compare(dist, minDist);
if (minDist < 0.0 || cmp < 0) {
projected = boundaryPt;
minDist = dist;
} else if (cmp == 0) {
// the two points are the _exact_ same distance from the reference point, so use
// a separate method to disambiguate them
projected = disambiguateClosestPoint(point, projected, boundaryPt);
}
}
return VisitResult.CONTINUE;
}
/** Return true if the given node cut subhyperplane is a possible candidate for containing the
* closest region boundary point to the target.
* @param cut the node cut subhyperplane to test
* @param target the target point being projected
* @param currentMinDist the smallest distance found so far to a region boundary; this value is guaranteed
* to be non-negative
* @return true if the subhyperplane is a possible candidate for containing the closest region
* boundary point to the target
*/
protected boolean isPossibleClosestCut(final SubHyperplane<P> cut, final P target,
final double currentMinDist) {
return Math.abs(cut.getHyperplane().offset(target)) <= currentMinDist;
}
/** Method used to determine which of points {@code a} and {@code b} should be considered
* as the "closest" point to {@code target} when the points are exactly equidistant.
* @param target the target point
* @param a first point to consider
* @param b second point to consider
* @return which of {@code a} or {@code b} should be considered as the one closest to
* {@code target}
*/
protected P disambiguateClosestPoint(final P target, final P a, final P b) {
return a;
}
/** Get the projected point on the region's boundary, or null if no point could be found.
* @return the projected point on the region's boundary
*/
public P getProjected() {
return projected;
}
}
/** Class containing the primary size-related properties of a region. These properties
* are typically computed at the same time, so this class serves to encapsulate the result
* of the combined computation.
* @param <P> Point implementation type
*/
protected static class RegionSizeProperties<P extends Point<P>> {
/** The size of the region. */
private final double size;
/** The barycenter of the region. */
private final P barycenter;
/** Simple constructor.
* @param size the region size
* @param barycenter the region barycenter
*/
public RegionSizeProperties(final double size, final P barycenter) {
this.size = size;
this.barycenter = barycenter;
}
/** Get the size of the region.
* @return the size of the region
*/
public double getSize() {
return size;
}
/** Get the barycenter of the region.
* @return the barycenter of the region
*/
public P getBarycenter() {
return barycenter;
}
}
/** Class that iterates over the boundary convex subhyperplanes from a set of region nodes.
* @param <P> Point implementation type
* @param <C> Boundary convex subhyperplane implementation type
* @param <N> BSP tree node implementation type
*/
protected static final class RegionBoundaryIterator<
P extends Point<P>,
C extends ConvexSubHyperplane<P>,
N extends AbstractRegionNode<P, N>>
extends IteratorTransform<N, C> {
/** Function that converts from the convex subhyperplane type to the output type. */
private final Function<ConvexSubHyperplane<P>, C> typeConverter;
/** Simple constructor.
* @param inputIterator iterator that will provide all nodes in the tree
* @param typeConverter function that converts from the convex subhyperplane type to the output type
*/
private RegionBoundaryIterator(final Iterator<N> inputIterator,
final Function<ConvexSubHyperplane<P>, C> typeConverter) {
super(inputIterator);
this.typeConverter = typeConverter;
}
/** {@inheritDoc} */
@Override
protected void acceptInput(final N input) {
if (input.isInternal()) {
final RegionCutBoundary<P> cutBoundary = input.getCutBoundary();
final SubHyperplane<P> outsideFacing = cutBoundary.getOutsideFacing();
final SubHyperplane<P> insideFacing = cutBoundary.getInsideFacing();
if (outsideFacing != null && !outsideFacing.isEmpty()) {
for (ConvexSubHyperplane<P> boundary : outsideFacing.toConvex()) {
addOutput(typeConverter.apply(boundary));
}
}
if (insideFacing != null && !insideFacing.isEmpty()) {
for (ConvexSubHyperplane<P> boundary : insideFacing.toConvex()) {
ConvexSubHyperplane<P> reversed = boundary.reverse();
addOutput(typeConverter.apply(reversed));
}
}
}
}
}
}