All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.geometry.core.partitioning.bsp.AbstractPartitionedRegionBuilder Maven / Gradle / Ivy

There is a newer version: 1.0
Show 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.geometry.core.partitioning.bsp;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;

import org.apache.commons.geometry.core.Point;
import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree.SubtreeInitializer;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree.AbstractRegionNode;

/** Class encapsulating logic for building regions by inserting boundaries into a BSP
 * tree containing structural cuts, i.e. cuts where both sides of the cut have the same region
 * location. This technique only produces accurate results when the inserted boundaries define
 * the entire surface of the region. However, for valid input boundaries, significant performance
 * improvements can be achieved due to the reduced height of the tree, especially where large
 * numbers of boundaries are involved and/or the defined region is convex.
 *
 * 

Implementation Notes

* *

This class constructs regions in two phases: (1) partition insertion and (2) boundary insertion. * Instances begin in the partition insertion phase. Here, partitions can be inserted into the empty tree * using the standard BSP insertion logic. The {@link RegionCutRule#INHERIT INHERIT} cut rule is used so that the * represented region remains empty even as partitions are inserted. *

* *

The instance moves into the boundary insertion phase when the caller inserts the first region boundary. * Attempting to insert a partition after this point results in an {@code IllegalStateException}. This ensures that * partitioning cuts are always located higher up the tree than boundary cuts.

* *

After all boundaries are inserted, the tree undergoes final processing to ensure that the region is consistent * and that unnecessary nodes are removed.

* *

This class does not expose any public methods so that subclasses can present their own * public API, tailored to the specific types being worked with. In particular, most subclasses * will want to restrict the tree types used with the algorithm, which is difficult to implement * cleanly at this level.

* @param

Point implementation type * @param BSP tree node implementation type */ public abstract class AbstractPartitionedRegionBuilder< P extends Point

, N extends AbstractRegionNode> { /** Comparator for sorting nodes with the deepest nodes first. */ private static final Comparator> DEEPEST_FIRST_ORDER = (a, b) -> Integer.compare(b.depth(), a.depth()); /** Tree being constructed. */ private final AbstractRegionBSPTree tree; /** Subtree initializer for inserted boundaries. */ private final SubtreeInitializer subtreeInit; /** Flag indicating whether or not partitions may still be inserted into the tree. */ private boolean insertingPartitions = true; /** Set of all internal nodes used as partitioning nodes. */ private final Set partitionNodes = new HashSet<>(); /** Construct a new instance that builds a partitioned region in the given tree. The tree must * be empty. * @param tree tree to build the region in; must be empty * @throws IllegalArgumentException if the tree is not empty */ protected AbstractPartitionedRegionBuilder(final AbstractRegionBSPTree tree) { if (!tree.isEmpty()) { throw new IllegalArgumentException("Tree must be empty"); } this.tree = tree; this.subtreeInit = tree.getSubtreeInitializer(RegionCutRule.MINUS_INSIDE); } /** Internal method to build and return the tree representing the final partitioned region. * @return the partitioned region */ protected AbstractRegionBSPTree buildInternal() { // condense to combine homogenous leaf nodes tree.condense(); // propagate region interiors to partitioned nodes that have not received // a boundary if (propagateRegionInterior()) { // condense again since some leaf nodes changed tree.condense(); } return tree; } /** Internal method to insert a partition into the tree. * @param partition partition to insert * @throws IllegalStateException if a boundary has previously been inserted */ protected void insertPartitionInternal(final HyperplaneConvexSubset

partition) { ensureInsertingPartitions(); tree.insert(partition, RegionCutRule.INHERIT); } /** Internal method to insert a region boundary into the tree. * @param boundary boundary to insert */ protected void insertBoundaryInternal(final HyperplaneConvexSubset

boundary) { if (insertingPartitions) { // switch to inserting boundaries; place all current internal nodes into // a set for easy identification for (final N node : tree.nodes()) { if (node.isInternal()) { partitionNodes.add(node); } } insertingPartitions = false; } insertBoundaryRecursive(tree.getRoot(), boundary, boundary.getHyperplane().span(), (leaf, cut) -> tree.setNodeCut(leaf, cut, subtreeInit)); } /** Insert a region boundary into the tree. * @param node node to insert into * @param insert the hyperplane convex subset to insert * @param trimmed version of the hyperplane convex subset filling the entire space of {@code node} * @param leafFn function to apply to leaf nodes */ private void insertBoundaryRecursive(final N node, final HyperplaneConvexSubset

insert, final HyperplaneConvexSubset

trimmed, final BiConsumer> leafFn) { if (node.isLeaf()) { leafFn.accept(node, trimmed); } else { final Split> insertSplit = insert.split(node.getCutHyperplane()); final HyperplaneConvexSubset

minus = insertSplit.getMinus(); final HyperplaneConvexSubset

plus = insertSplit.getPlus(); if (minus == null && plus == null && isPartitionNode(node)) { // the inserted boundary lies directly on a partition; proceed down the tree with the // rest of the insertion algorithm but instead of cutting the final leaf nodes, just // set the location // remove this node from the set of partition nodes since this is now a boundary cut partitionNodes.remove(node); final boolean sameOrientation = node.getCutHyperplane().similarOrientation(insert.getHyperplane()); final N insertMinus = sameOrientation ? node.getMinus() : node.getPlus(); final N insertPlus = sameOrientation ? node.getPlus() : node.getMinus(); insertBoundaryRecursive(insertMinus, insert, trimmed, (leaf, cut) -> leaf.setLocation(RegionLocation.INSIDE)); insertBoundaryRecursive(insertPlus, insert, trimmed, (leaf, cut) -> leaf.setLocation(RegionLocation.OUTSIDE)); } else if (minus != null || plus != null) { final Split> trimmedSplit = trimmed.split(node.getCutHyperplane()); final HyperplaneConvexSubset

trimmedMinus = trimmedSplit.getMinus(); final HyperplaneConvexSubset

trimmedPlus = trimmedSplit.getPlus(); if (minus != null) { insertBoundaryRecursive(node.getMinus(), minus, trimmedMinus, leafFn); } if (plus != null) { insertBoundaryRecursive(node.getPlus(), plus, trimmedPlus, leafFn); } } } } /** Propagate the region interior to partitioned leaf nodes that have not had a boundary * inserted. * @return true if any nodes were changed */ private boolean propagateRegionInterior() { final List outsidePartitionedLeaves = getOutsidePartitionedLeaves(); outsidePartitionedLeaves.sort(DEEPEST_FIRST_ORDER); int changeCount = 0; N parent; N sibling; for (final N leaf : outsidePartitionedLeaves) { parent = leaf.getParent(); // check if the parent cut touches the inside anywhere on the side opposite of // this leaf; if so, then this node should also be inside sibling = leaf.isMinus() ? parent.getPlus() : parent.getMinus(); if (touchesInside(parent.getCut(), sibling)) { leaf.setLocation(RegionLocation.INSIDE); ++changeCount; } } return changeCount > 0; } /** Return a list containing all outside leaf nodes that have a parent marked as a partition node. * @return a list containing all outside leaf nodes that have a parent marked as a partition node */ private List getOutsidePartitionedLeaves() { final List result = new ArrayList<>(); final N root = tree.getRoot(); collectOutsidePartitionedLeavesRecursive(root, false, result); return result; } /** Recursively collect all outside leaf nodes that have a parent marked as a partition node. * @param node root of the subtree to collect nodes from * @param parentIsPartitionNode true if the parent of {@code node} is a partition node * @param result list of accumulated results */ private void collectOutsidePartitionedLeavesRecursive(final N node, final boolean parentIsPartitionNode, final List result) { if (node != null) { if (parentIsPartitionNode && node.isOutside()) { result.add(node); } final boolean partitionNode = isPartitionNode(node); collectOutsidePartitionedLeavesRecursive(node.getMinus(), partitionNode, result); collectOutsidePartitionedLeavesRecursive(node.getPlus(), partitionNode, result); } } /** Return true if {@code sub} touches an inside leaf node anywhere in the subtree rooted at {@code node}. * @param sub convex subset to check * @param node root node of the subtree to test against * @return true if {@code sub} touches an inside leaf node anywhere in the subtree rooted at {@code node} */ private boolean touchesInside(final HyperplaneConvexSubset

sub, final N node) { if (sub != null) { if (node.isLeaf()) { return node.isInside(); } else { final Split> split = sub.split(node.getCutHyperplane()); return touchesInside(split.getMinus(), node.getMinus()) || touchesInside(split.getPlus(), node.getPlus()); } } return false; } /** Return true if the given node is marked as a partition node. * @param node node to check * @return true if the given node is marked as a partition node */ private boolean isPartitionNode(final N node) { return partitionNodes.contains(node); } /** Throw an exception if the instance is no longer accepting partitions. * @throws IllegalStateException if the instance is no longer accepting partitions */ private void ensureInsertingPartitions() { if (!insertingPartitions) { throw new IllegalStateException("Cannot insert partitions after boundaries have been inserted"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy