View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.core.partitioning.bsp;
18  
19  import java.util.Deque;
20  import java.util.Iterator;
21  import java.util.LinkedList;
22  import java.util.NoSuchElementException;
23  
24  import org.apache.commons.geometry.core.Point;
25  import org.apache.commons.geometry.core.Transform;
26  import org.apache.commons.geometry.core.partitioning.Hyperplane;
27  import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
28  import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
29  import org.apache.commons.geometry.core.partitioning.Split;
30  import org.apache.commons.geometry.core.partitioning.SplitLocation;
31  
32  /** Abstract class for Binary Space Partitioning (BSP) tree implementations. This
33   * class contains basic structures and algorithms that should be common
34   * to all BSP tree implementations, regardless of the end use cases for the tree
35   * (eg, whether the tree is intended to represent polytopes, hold attributes like
36   * a map, etc).
37   *
38   * <h2>Implementation Notes</h2>
39   * <ul>
40   *      <li>The {@link AbstractBSPTree} class is designed to work closely with its nested node type,
41   *      {@link AbstractNode}. The tree class handles all tree manipulation operations while the node type
42   *      is primarily a simple data type and delegates tree operations to internal methods within its
43   *      parent tree. This allows for easier overriding of tree behavior in subclasses.</li>
44   *      <li>Each time the structure of the tree is modified, a {@link #getVersion() version number} is
45   *      incremented in the tree. This allows certain tree properties to be computed lazily and then
46   *      cached. The tree version number is incremented with the {@link #invalidate() invalidate} method. Properties
47   *      can be cached directly on nodes using the {@link AbstractBSPTree.AbstractNode#checkValid() checkValid}
48   *      and {@link AbstractBSPTree.AbstractNode#nodeInvalidated() nodeInvalidated} methods.</li>
49   *      <li>Since the methods used to construct and modify trees can vary by use case, no public API is provided
50   *      for manipulating the tree. Subclasses are expected to use the protected methods of this class to
51   *      create their own. For tree construction, subclasses are expected to pass their own {@link SubtreeInitializer}
52   *      instances to {@link #insert(HyperplaneConvexSubset, SubtreeInitializer) insert} and
53   *      {@link #cutNode(AbstractNode, Hyperplane, SubtreeInitializer) cutNode} in order to set the correct properties on
54   *      tree nodes. To support tree copying, subclasses must also override
55   *      {@link #copyNodeProperties(AbstractNode, AbstractNode) copyNodeProperties}.</li>
56   *      <li>This class is not thread safe.</li>
57   * </ul>
58   *
59   * @param <P> Point implementation type
60   * @param <N> BSP tree node implementation type
61   * @see BSPTree
62   */
63  public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPTree.AbstractNode<P, N>>
64      implements BSPTree<P, N> {
65  
66      /** Interface used to initialize newly created BSP subtrees, consisting of a single parent
67       * node and two child nodes.
68       * @param <N> BSP tree node implementation type
69       */
70      @FunctionalInterface
71      public interface SubtreeInitializer<N extends AbstractBSPTree.AbstractNode<?, ?>> {
72  
73          /** Initialize the given newly-created subtree. The subtree consists of a single root node and two
74           * child nodes.
75           * @param root the root node of the new subtree
76           */
77          void initSubtree(N root);
78      }
79  
80      /** The default number of levels to print when creating a string representation of the tree. */
81      private static final int DEFAULT_TREE_STRING_MAX_DEPTH = 8;
82  
83      /** Integer value set on various node fields when a value is unknown. */
84      private static final int UNKNOWN_VALUE = -1;
85  
86      /** The root node for the tree. */
87      private N root;
88  
89      /** The current modification version for the tree structure. This is incremented each time
90       * a structural change occurs in the tree and is used to determine when cached values
91       * must be recomputed.
92       */
93      private int version;
94  
95      /** {@inheritDoc} */
96      @Override
97      public N getRoot() {
98          if (root == null) {
99              setRoot(createNode());
100         }
101         return root;
102     }
103 
104     /** Set the root node for the tree. Cached tree properties are invalidated
105      * with {@link #invalidate()}.
106      * @param root new root node for the tree
107      */
108     protected void setRoot(final N root) {
109         this.root = root;
110 
111         this.root.makeRoot();
112 
113         invalidate();
114     }
115 
116     /** {@inheritDoc} */
117     @Override
118     public int count() {
119         return getRoot().count();
120     }
121 
122     /** {@inheritDoc} */
123     @Override
124     public int height() {
125         return getRoot().height();
126     }
127 
128     /** {@inheritDoc} */
129     @Override
130     public void accept(final BSPTreeVisitor<P, N> visitor) {
131         accept(getRoot(), visitor);
132     }
133 
134     /** {@inheritDoc} */
135     @Override
136     public N findNode(final P pt, final FindNodeCutRule cutBehavior) {
137         return findNode(getRoot(), pt, cutBehavior);
138     }
139 
140     /** {@inheritDoc} */
141     @Override
142     public Iterable<N> nodes() {
143         return () -> new NodeIterator<>(getRoot());
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public void copy(final BSPTree<P, N> src) {
149         copySubtree(src.getRoot(), getRoot());
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public void extract(final N node) {
155         // copy downward
156         final N extracted = importSubtree(node);
157 
158         // extract upward
159         final N newRoot = extractParentPath(node, extracted);
160 
161         // set the root of this tree
162         setRoot(newRoot);
163     }
164 
165     /** {@inheritDoc} */
166     @Override
167     public void transform(final Transform<P> transform) {
168         final boolean swapChildren = swapsInsideOutside(transform);
169         transformRecursive(getRoot(), transform, swapChildren);
170 
171         invalidate();
172     }
173 
174     /** Get a simple string representation of the tree structure. The returned string contains
175      * the tree structure down to the default max depth of {@value #DEFAULT_TREE_STRING_MAX_DEPTH}.
176      * @return a string representation of the tree
177      */
178     public String treeString() {
179         return treeString(DEFAULT_TREE_STRING_MAX_DEPTH);
180     }
181 
182     /** Get a simple string representation of the tree structure. The returned string contains
183      * the tree structure down to {@code maxDepth}.
184      * @param maxDepth the maximum depth in the tree to print; nodes below this depth are skipped
185      * @return a string representation of the tree
186      */
187     public String treeString(final int maxDepth) {
188         final BSPTreePrinter<P, N> printer = new BSPTreePrinter<>(maxDepth);
189         accept(printer);
190 
191         return printer.toString();
192     }
193 
194     /** {@inheritDoc} */
195     @Override
196     public String toString() {
197         return new StringBuilder()
198                 .append(getClass().getSimpleName())
199                 .append("[count= ")
200                 .append(count())
201                 .append(", height= ")
202                 .append(height())
203                 .append(']')
204                 .toString();
205     }
206 
207     /** Create a new node for this tree.
208      * @return a new node for this tree
209      */
210     protected abstract N createNode();
211 
212     /** Copy non-structural node properties from {@code src} to {@code dst}.
213      * Non-structural properties are those properties not directly related
214      * to the structure of the BSP tree, i.e. properties other than parent/child
215      * connections and cuts. Subclasses should override this method to copy additional
216      * properties stored on nodes.
217      * @param src source node
218      * @param dst destination node
219      */
220     protected abstract void copyNodeProperties(N src, N dst);
221 
222     /** Create a non-structural copy of the given node. Properties such as parent/child
223      * connections and cuts are <em>not</em> copied.
224      * @param src the node to copy; does not need to belong to the current tree
225      * @return the copied node
226      * @see AbstractBSPTree#copyNodeProperties(AbstractNode, AbstractNode)
227      */
228     protected N copyNode(final N src) {
229         final N copy = createNode();
230         copyNodeProperties(src, copy);
231 
232         return copy;
233     }
234 
235     /** Recursively copy a subtree. The returned node is not attached to the current tree.
236      * Structural <em>and</em> non-structural properties are copied from the source subtree
237      * to the destination subtree. This method does nothing if {@code src} and {@code dst}
238      * reference the same node.
239      * @param src the node representing the source subtree; does not need to belong to the
240      *      current tree
241      * @param dst the node representing the destination subtree
242      * @return the copied node, ie {@code dst}
243      */
244     protected N copySubtree(final N src, final N dst) {
245         // only copy if we're actually switching nodes
246         if (src != dst) {
247             // copy non-structural properties
248             copyNodeProperties(src, dst);
249 
250             // copy the subtree structure
251             HyperplaneConvexSubset<P> cut = null;
252             N minus = null;
253             N plus = null;
254 
255             if (!src.isLeaf()) {
256                 final AbstractBSPTree<P, N> dstTree = dst.getTree();
257 
258                 cut = src.getCut();
259                 minus = copySubtree(src.getMinus(), dstTree.createNode());
260                 plus = copySubtree(src.getPlus(), dstTree.createNode());
261             }
262 
263             dst.setSubtree(cut, minus, plus);
264         }
265 
266         return dst;
267     }
268 
269     /** Import the subtree represented by the given node into this tree. If the given node
270      * already belongs to this tree, then the node is returned directly without modification.
271      * If the node does <em>not</em> belong to this tree, a new node is created and the src node
272      * subtree is copied into it.
273      *
274      * <p>This method does not modify the current structure of the tree.</p>
275      * @param src node to import
276      * @return the given node if it belongs to this tree, otherwise a new node containing
277      *      a copy of the given node's subtree
278      * @see #copySubtree(AbstractNode, AbstractNode)
279      */
280     protected N importSubtree(final N src) {
281         // create a copy of the node if it's not already in this tree
282         if (src.getTree() != this) {
283             return copySubtree(src, createNode());
284         }
285 
286         return src;
287     }
288 
289     /** Extract the path from {@code src} to the root of its tree and
290      * set it as the parent path of {@code dst}. Leaf nodes created during
291      * the extraction are given the same node properties as their counterparts
292      * in the source tree but without the cuts and child nodes. The properties
293      * of {@code dst} are not modified, with the exception of its parent node
294      * reference.
295      * @param src the source node to copy the parent path from
296      * @param dst the destination node to place under the extracted path
297      * @return the root node of the extracted path
298      */
299     protected N extractParentPath(final N src, final N dst) {
300         N dstParent = dst;
301         N dstChild;
302 
303         N srcChild = src;
304         N srcParent = srcChild.getParent();
305 
306         while (srcParent != null) {
307             dstChild = dstParent;
308             dstParent = copyNode(srcParent);
309 
310             if (srcChild.isMinus()) {
311                 dstParent.setSubtree(
312                         srcParent.getCut(),
313                         dstChild,
314                         copyNode(srcParent.getPlus()));
315             } else {
316                 dstParent.setSubtree(
317                         srcParent.getCut(),
318                         copyNode(srcParent.getMinus()),
319                         dstChild);
320             }
321 
322             srcChild = srcParent;
323             srcParent = srcChild.getParent();
324         }
325 
326         return dstParent;
327     }
328 
329     /** Find the smallest node in the tree containing the point, starting
330      * at the given node.
331      * @param start the node to begin the search with
332      * @param pt the point to check
333      * @param cutRule value determining the search behavior when the test point
334      *      lies directly on the cut of an internal node
335      * @return the smallest node in the tree containing the point
336      */
337     protected N findNode(final N start, final P pt, final FindNodeCutRule cutRule) {
338         final Hyperplane<P> cutHyper = start.getCutHyperplane();
339         if (cutHyper != null) {
340             final HyperplaneLocation cutLoc = cutHyper.classify(pt);
341 
342             final boolean onPlusSide = cutLoc == HyperplaneLocation.PLUS;
343             final boolean onMinusSide = cutLoc == HyperplaneLocation.MINUS;
344             final boolean onCut = !onPlusSide && !onMinusSide;
345 
346             if (onMinusSide || (onCut && cutRule == FindNodeCutRule.MINUS)) {
347                 return findNode(start.getMinus(), pt, cutRule);
348             } else if (onPlusSide || cutRule == FindNodeCutRule.PLUS) {
349                 return findNode(start.getPlus(), pt, cutRule);
350             }
351         }
352         return start;
353     }
354 
355     /** Visit the nodes in a subtree.
356      * @param node the node to begin the visit process
357      * @param visitor the visitor to pass nodes to
358      */
359     protected void accept(final N node, final BSPTreeVisitor<P, N> visitor) {
360         acceptRecursive(node, visitor);
361     }
362 
363     /** Recursively visit the nodes in the subtree rooted at the given node.
364      * @param node the node located at the root of the subtree to visit
365      * @param visitor the visitor to pass nodes to
366      * @return true if the visit operation should continue
367      */
368     private boolean acceptRecursive(final N node, final BSPTreeVisitor<P, N> visitor) {
369         if (node.isLeaf()) {
370             return shouldContinueVisit(visitor.visit(node));
371         } else {
372             final BSPTreeVisitor.Order order = visitor.visitOrder(node);
373 
374             if (order != null) {
375 
376                 switch (order) {
377                 case PLUS_MINUS_NODE:
378                     return acceptRecursive(node.getPlus(), visitor) &&
379                             acceptRecursive(node.getMinus(), visitor) &&
380                             shouldContinueVisit(visitor.visit(node));
381                 case PLUS_NODE_MINUS:
382                     return acceptRecursive(node.getPlus(), visitor) &&
383                             shouldContinueVisit(visitor.visit(node)) &&
384                             acceptRecursive(node.getMinus(), visitor);
385                 case MINUS_PLUS_NODE:
386                     return acceptRecursive(node.getMinus(), visitor) &&
387                             acceptRecursive(node.getPlus(), visitor) &&
388                             shouldContinueVisit(visitor.visit(node));
389                 case MINUS_NODE_PLUS:
390                     return acceptRecursive(node.getMinus(), visitor) &&
391                             shouldContinueVisit(visitor.visit(node)) &&
392                             acceptRecursive(node.getPlus(), visitor);
393                 case NODE_PLUS_MINUS:
394                     return shouldContinueVisit(visitor.visit(node)) &&
395                             acceptRecursive(node.getPlus(), visitor) &&
396                             acceptRecursive(node.getMinus(), visitor);
397                 case  NODE_MINUS_PLUS:
398                     return shouldContinueVisit(visitor.visit(node)) &&
399                             acceptRecursive(node.getMinus(), visitor) &&
400                             acceptRecursive(node.getPlus(), visitor);
401                 default: // NONE
402                     break;
403                 }
404             }
405 
406             return true;
407         }
408     }
409 
410     /** Return true if the given BSP tree visit result indicates that the current visit
411      * operation should continue.
412      * @param result visit result from BSP tree node visit operation
413      * @return true if the visit operation should continue with remaining nodes in the
414      *      BSP tree
415      */
416     private boolean shouldContinueVisit(final BSPTreeVisitor.Result result) {
417         return result == BSPTreeVisitor.Result.CONTINUE;
418     }
419 
420     /** Cut a node with a hyperplane. The algorithm proceeds as follows:
421      * <ol>
422      *      <li>The hyperplane is trimmed by splitting it with each cut hyperplane on the
423      *      path from the given node to the root of the tree.</li>
424      *      <li>If the remaining portion of the hyperplane is <em>not</em> empty, then
425      *          <ul>
426      *              <li>the remaining portion becomes the cut hyperplane subset for the node,</li>
427      *              <li>two new child nodes are created and initialized with
428      *              {@code subtreeInitializer}, and</li>
429      *              <li>true is returned.</li>
430      *          </ul>
431      *      </li>
432      *      <li>If the remaining portion of the hyperplane <em>is</em> empty (ie, the
433      *      cutting hyperplane does not intersect the node's region), then
434      *          <ul>
435      *              <li>the node is converted to a leaf node (meaning that previous
436      *              child nodes are lost), and</li>
437      *              <li>false is returned.</li>
438      *          </ul>
439      *      </li>
440      * </ol>
441      *
442      * <p>It is important to note that since this method uses the path from given node
443      * to the tree root, it must only be used on nodes that are already inserted into
444      * the tree.</p>
445      *
446      * <p>This method calls {@link #invalidate()} to invalidate cached tree properties if the tree
447      * structure is changed.</p>
448      *
449      * @param node the node to cut
450      * @param cutter the hyperplane to cut the node with
451      * @param subtreeInitializer object used to initialize any newly-created subtrees
452      * @return true if the node was cut and two new child nodes were created;
453      *      otherwise false
454      * @see #trimToNode(AbstractNode, HyperplaneConvexSubset)
455      * @see #setNodeCut(AbstractNode, HyperplaneConvexSubset, SubtreeInitializer)
456      * @see #removeNodeCut(AbstractNode)
457      * @see #invalidate()
458      */
459     protected boolean cutNode(final N node, final Hyperplane<P> cutter,
460             final SubtreeInitializer<N> subtreeInitializer) {
461 
462         // cut the hyperplane using all hyperplanes from this node up
463         // to the root
464         final HyperplaneConvexSubset<P> cut = trimToNode(node, cutter.span());
465         if (cut == null || cut.isEmpty()) {
466             // insertion failed; the node was not cut
467             removeNodeCut(node);
468             return false;
469         }
470 
471         setNodeCut(node, cut, subtreeInitializer);
472         return true;
473     }
474 
475     /** Trim the given hyperplane convex subset to the region defined by the given node. This method
476      * cuts the subset with the cut hyperplanes (binary partitioners) of all parent nodes up to the
477      * root and returns the trimmed subset or {@code null} if the subset lies outside of the region
478      * defined by the node.
479      *
480      * <p>If the subset is directly coincident with a binary partitioner of a parent node,
481      * then the relative orientations of the associated hyperplanes are used to determine the behavior,
482      * as described below.
483      * <ul>
484      *      <li>If the orientations are <strong>similar</strong>, then the subset is determined to
485      *      lie <em>outside</em> of the node's region and {@code null} is returned.</li>
486      *      <li>If the orientations are <strong>different</strong> (ie, opposite), then the subset
487      *      is determined to lie <em>inside</em> of the node's region and the fit operation continues
488      *      with the remaining parent nodes.</li>
489      * </ul>
490      * These rules are designed to allow the creation of trees with node regions that are the thickness
491      * of a single hyperplane. For example, in two dimensions, a tree could be constructed with an internal
492      * node containing a cut along the x-axis in the positive direction and with a child node containing a
493      * cut along the x-axis in the opposite direction. If the nodes in the tree are given inside and outside
494      * attributes, then this tree could be used to represent a region consisting of a single line or a region
495      * consisting of the entire space except for the single line. This would not be possible if nodes were not
496      * able to have cut hyperplanes that were coincident with parent cuts but in opposite directions.
497      *
498      * <p>
499      * Another way of looking at the rules above is that inserting a hyperplane into the tree that exactly
500      * matches the hyperplane of a parent node does not add any information to the tree. However, adding a
501      * hyperplane to the tree that is coincident with a parent node but with the opposite orientation,
502      * <em>does</em> add information to the tree.
503      *
504      * @param node the node representing the region to fit the hyperplane subset to
505      * @param sub the hyperplane subset to trim to the node's region
506      * @return the trimmed hyperplane subset or null if the given hyperplane subset does not intersect
507      *      the node's region
508      */
509     protected HyperplaneConvexSubset<P> trimToNode(final N node, final HyperplaneConvexSubset<P> sub) {
510 
511         HyperplaneConvexSubset<P> result = sub;
512 
513         N parentNode = node.getParent();
514         N currentNode = node;
515 
516         while (parentNode != null && result != null) {
517             final Split<? extends HyperplaneConvexSubset<P>> split = result.split(parentNode.getCutHyperplane());
518 
519             if (split.getLocation() == SplitLocation.NEITHER) {
520                 // if we're directly on the splitter and have the same orientation, then
521                 // we say the subset does not lie in the node's region (no new information
522                 // is added to the tree in this case)
523                 if (result.getHyperplane().similarOrientation(parentNode.getCutHyperplane())) {
524                     result = null;
525                 }
526             } else {
527                 result = currentNode.isPlus() ? split.getPlus() : split.getMinus();
528             }
529 
530             currentNode = parentNode;
531             parentNode = parentNode.getParent();
532         }
533 
534         return result;
535     }
536 
537     /** Remove the cut from the given node. Returns true if the node had a cut before
538      * the call to this method. Any previous child nodes are lost.
539      *
540      * <p>This method calls {@link #invalidate()} to invalidate cached tree properties if the tree
541      * structure changed.</p>
542      * @param node the node to remove the cut from
543      * @return true if the node previously had a cut
544      */
545     protected boolean removeNodeCut(final N node) {
546         if (node.getCut() != null) {
547             node.setSubtree(null, null, null);
548 
549             invalidate();
550 
551             return true;
552         }
553 
554         return false;
555     }
556 
557     /** Set the cut hyperplane subset for the given node. Two new child nodes are created for the
558      * node and the new subtree is initialized with {@code subtreeInitializer}.
559      *
560      * <p>This method performs absolutely <em>no</em> validation on the given cut
561      * hyperplane subset. It is the responsibility of the caller to ensure that the
562      * hyperplane subset fits the region represented by the node.</p>
563      *
564      * <p>This method always calls {@link #invalidate()} to invalidate cached tree properties.</p>
565      * @param node the node to cut
566      * @param cut the hyperplane convex subset to set as the node cut
567      * @param subtreeInitializer object used to initialize the newly-created subtree
568      */
569     protected void setNodeCut(final N node, final HyperplaneConvexSubset<P> cut,
570             final SubtreeInitializer<N> subtreeInitializer) {
571 
572         node.setSubtree(cut, createNode(), createNode());
573 
574         subtreeInitializer.initSubtree(node);
575 
576         invalidate();
577     }
578 
579     /** Insert the given hyperplane convex subset into the tree, starting at the root node. Any subtrees
580      * created are initialized with {@code subtreeInit}.
581      * @param convexSub hyperplane convex subset to insert into the tree
582      * @param subtreeInit object used to initialize newly created subtrees
583      */
584     protected void insert(final HyperplaneConvexSubset<P> convexSub, final SubtreeInitializer<N> subtreeInit) {
585         insertRecursive(getRoot(), convexSub,
586                 convexSub.getHyperplane().span(), subtreeInit);
587     }
588 
589     /** Recursively insert a hyperplane convex subset into the tree at the given node.
590      * @param node the node to begin insertion with
591      * @param insert the hyperplane subset to insert
592      * @param trimmed hyperplane subset containing the result of splitting the entire
593      *      space with each hyperplane from this node to the root
594      * @param subtreeInit object used to initialize newly created subtrees
595      */
596     private void insertRecursive(final N node, final HyperplaneConvexSubset<P> insert,
597             final HyperplaneConvexSubset<P> trimmed, final SubtreeInitializer<N> subtreeInit) {
598         if (node.isLeaf()) {
599             setNodeCut(node, trimmed, subtreeInit);
600         } else {
601             final Split<? extends HyperplaneConvexSubset<P>> insertSplit = insert.split(node.getCutHyperplane());
602 
603             final HyperplaneConvexSubset<P> minus = insertSplit.getMinus();
604             final HyperplaneConvexSubset<P> plus = insertSplit.getPlus();
605 
606             if (minus != null || plus != null) {
607                 final Split<? extends HyperplaneConvexSubset<P>> trimmedSplit = trimmed.split(node.getCutHyperplane());
608 
609                 if (minus != null) {
610                     insertRecursive(node.getMinus(), minus, trimmedSplit.getMinus(), subtreeInit);
611                 }
612                 if (plus != null) {
613                     insertRecursive(node.getPlus(), plus, trimmedSplit.getPlus(), subtreeInit);
614                 }
615             }
616         }
617     }
618 
619     /** Return true if the given transform swaps the inside and outside of
620      * the region.
621      *
622      * <p>The default behavior of this method is to return true if the transform
623      * does not preserve spatial orientation (ie, {@link Transform#preservesOrientation()}
624      * is false). Subclasses may need to override this method to implement the correct
625      * behavior for their space and dimension.</p>
626      * @param transform transform to check
627      * @return true if the given transform swaps the interior and exterior of
628      *      the region
629      */
630     protected boolean swapsInsideOutside(final Transform<P> transform) {
631         return !transform.preservesOrientation();
632     }
633 
634     /** Transform the subtree rooted as {@code node} recursively.
635      * @param node the root node of the subtree to transform
636      * @param t the transform to apply
637      * @param swapChildren if true, the plus and minus child nodes of each internal node
638      *      will be swapped; this should be the case when the transform is a reflection
639      */
640     private void transformRecursive(final N node, final Transform<P> t, final boolean swapChildren) {
641         if (node.isInternal()) {
642             // transform our cut
643             final HyperplaneConvexSubset<P> transformedCut = node.getCut().transform(t);
644 
645             // transform our children
646             transformRecursive(node.getMinus(), t, swapChildren);
647             transformRecursive(node.getPlus(), t, swapChildren);
648 
649             final N transformedMinus = swapChildren ? node.getPlus() : node.getMinus();
650             final N transformedPlus = swapChildren ? node.getMinus() : node.getPlus();
651 
652             // set our new state
653             node.setSubtree(transformedCut, transformedMinus, transformedPlus);
654         }
655     }
656 
657     /** Split this tree with the given hyperplane, placing the split contents into the given
658      * target trees. One of the given trees may be null, in which case that portion of the split
659      * will not be exported. The current tree is not modified.
660      * @param splitter splitting hyperplane
661      * @param minus tree that will contain the portion of the tree on the minus side of the splitter
662      * @param plus tree that will contain the portion of the tree on the plus side of the splitter
663      */
664     protected void splitIntoTrees(final Hyperplane<P> splitter,
665             final AbstractBSPTree<P, N> minus, final AbstractBSPTree<P, N> plus) {
666 
667         final AbstractBSPTree<P, N> temp = (minus != null) ? minus : plus;
668 
669         final N splitRoot = temp.splitSubtree(this.getRoot(), splitter.span());
670 
671         if (minus != null) {
672             if (plus != null) {
673                 plus.extract(splitRoot.getPlus());
674             }
675             minus.extract(splitRoot.getMinus());
676         } else {
677             plus.extract(splitRoot.getPlus());
678         }
679     }
680 
681     /** Split the subtree rooted at the given node by a partitioning convex subset defined
682      * on the same region as the node. The subtree rooted at {@code node} is imported into
683      * this tree, meaning that if it comes from a different tree, the other tree is not
684      * modified.
685      * @param node the root node of the subtree to split; may come from a different tree,
686      *      in which case the other tree is not modified
687      * @param partitioner partitioning convex subset
688      * @return node containing the split subtree
689      */
690     protected N splitSubtree(final N node, final HyperplaneConvexSubset<P> partitioner) {
691         if (node.isLeaf()) {
692             return splitLeafNode(node, partitioner);
693         }
694         return splitInternalNode(node, partitioner);
695     }
696 
697     /** Split the given leaf node by a partitioning convex subset defined on the
698      * same region and import it into this tree.
699      * @param node the leaf node to split
700      * @param partitioner partitioning convex subset
701      * @return node containing the split subtree
702      */
703     private N splitLeafNode(final N node, final HyperplaneConvexSubset<P> partitioner) {
704         // in this case, we just create a new parent node with the partitioner as its
705         // cut and two copies of the original node as children
706         final N parent = createNode();
707         parent.setSubtree(partitioner, copyNode(node), copyNode(node));
708 
709         return parent;
710     }
711 
712     /** Split the given internal node by a partitioning convex subset defined on the same region
713      * as the node and import it into this tree.
714      * @param node the internal node to split
715      * @param partitioner partitioning convex subset
716      * @return node containing the split subtree
717      */
718     private N splitInternalNode(final N node, final HyperplaneConvexSubset<P> partitioner) {
719         // split the partitioner and node cut with each other's hyperplanes to determine their relative positions
720         final Split<? extends HyperplaneConvexSubset<P>> partitionerSplit = partitioner.split(node.getCutHyperplane());
721         final Split<? extends HyperplaneConvexSubset<P>> nodeCutSplit =
722                 node.getCut().split(partitioner.getHyperplane());
723 
724         final SplitLocation partitionerSplitSide = partitionerSplit.getLocation();
725         final SplitLocation nodeCutSplitSide = nodeCutSplit.getLocation();
726 
727         final N result = createNode();
728 
729         final N resultMinus;
730         final N resultPlus;
731 
732         if (partitionerSplitSide == SplitLocation.PLUS) {
733             if (nodeCutSplitSide == SplitLocation.PLUS) {
734                 // partitioner is on node cut plus side, node cut is on partitioner plus side
735                 final N nodePlusSplit = splitSubtree(node.getPlus(), partitioner);
736 
737                 resultMinus = nodePlusSplit.getMinus();
738 
739                 resultPlus = copyNode(node);
740                 resultPlus.setSubtree(node.getCut(), importSubtree(node.getMinus()), nodePlusSplit.getPlus());
741             } else {
742                 // partitioner is on node cut plus side, node cut is on partitioner minus side
743                 final N nodePlusSplit = splitSubtree(node.getPlus(), partitioner);
744 
745                 resultMinus = copyNode(node);
746                 resultMinus.setSubtree(node.getCut(), importSubtree(node.getMinus()), nodePlusSplit.getMinus());
747 
748                 resultPlus = nodePlusSplit.getPlus();
749             }
750         } else if (partitionerSplitSide == SplitLocation.MINUS) {
751             if (nodeCutSplitSide == SplitLocation.MINUS) {
752                 // partitioner is on node cut minus side, node cut is on partitioner minus side
753                 final N nodeMinusSplit = splitSubtree(node.getMinus(), partitioner);
754 
755                 resultMinus = copyNode(node);
756                 resultMinus.setSubtree(node.getCut(), nodeMinusSplit.getMinus(), importSubtree(node.getPlus()));
757 
758                 resultPlus = nodeMinusSplit.getPlus();
759             } else {
760                 // partitioner is on node cut minus side, node cut is on partitioner plus side
761                 final N nodeMinusSplit = splitSubtree(node.getMinus(), partitioner);
762 
763                 resultMinus = nodeMinusSplit.getMinus();
764 
765                 resultPlus = copyNode(node);
766                 resultPlus.setSubtree(node.getCut(), nodeMinusSplit.getPlus(), importSubtree(node.getPlus()));
767             }
768         } else if (partitionerSplitSide == SplitLocation.BOTH) {
769             // partitioner and node cut split each other
770             final N nodeMinusSplit = splitSubtree(node.getMinus(), partitionerSplit.getMinus());
771             final N nodePlusSplit = splitSubtree(node.getPlus(), partitionerSplit.getPlus());
772 
773             resultMinus = copyNode(node);
774             resultMinus.setSubtree(nodeCutSplit.getMinus(), nodeMinusSplit.getMinus(), nodePlusSplit.getMinus());
775 
776             resultPlus = copyNode(node);
777             resultPlus.setSubtree(nodeCutSplit.getPlus(), nodeMinusSplit.getPlus(), nodePlusSplit.getPlus());
778         } else {
779             // partitioner and node cut are parallel or anti-parallel
780             final boolean sameOrientation = partitioner.getHyperplane().similarOrientation(node.getCutHyperplane());
781 
782             resultMinus = importSubtree(sameOrientation ? node.getMinus() : node.getPlus());
783             resultPlus = importSubtree(sameOrientation ? node.getPlus() : node.getMinus());
784         }
785 
786         result.setSubtree(partitioner, resultMinus, resultPlus);
787 
788         return result;
789     }
790 
791     /** Invalidate any previously computed properties that rely on the internal structure of the tree.
792      * This method must be called any time the tree's internal structure changes in order to force cacheable
793      * tree and node properties to be recomputed the next time they are requested.
794      *
795      * <p>This method increments the tree's {@link #version} property.</p>
796      * @see #getVersion()
797      */
798     protected void invalidate() {
799         version = Math.max(0, version + 1); // positive values only
800     }
801 
802     /** Get the current structural version of the tree. This is incremented each time the
803      * tree structure is changes and can be used by nodes to allow caching of computed values.
804      * @return the current version of the tree structure
805      * @see #invalidate()
806      */
807     protected int getVersion() {
808         return version;
809     }
810 
811     /** Abstract implementation of {@link BSPTree.Node}. This class is intended for use with
812      * {@link AbstractBSPTree} and delegates tree mutation methods back to the parent tree object.
813      * @param <P> Point implementation type
814      * @param <N> BSP tree node implementation type
815      */
816     public abstract static class AbstractNode<P extends Point<P>, N extends AbstractNode<P, N>>
817         implements BSPTree.Node<P, N> {
818         /** The owning tree instance. */
819         private final AbstractBSPTree<P, N> tree;
820 
821         /** The parent node; this will be null for the tree root node. */
822         private N parent;
823 
824         /** The hyperplane convex subset cutting the node's region; this will be null for leaf nodes. */
825         private HyperplaneConvexSubset<P> cut;
826 
827         /** The node lying on the minus side of the cut hyperplane; this will be null
828          * for leaf nodes.
829          */
830         private N minus;
831 
832         /** The node lying on the plus side of the cut hyperplane; this will be null
833          * for leaf nodes.
834          */
835         private N plus;
836 
837         /** The current version of the node. This is set to track the tree's version
838          * and is used to detect when certain values need to be recomputed due to
839          * structural changes in the tree.
840          */
841         private int nodeVersion = -1;
842 
843         /** The depth of this node in the tree. This will be zero for the root node and
844          * {@link AbstractBSPTree#UNKNOWN_VALUE} when the value needs to be computed.
845          */
846         private int depth = UNKNOWN_VALUE;
847 
848         /** The total number of nodes in the subtree rooted at this node. This will be
849          * set to {@link AbstractBSPTree#UNKNOWN_VALUE} when the value needs
850          * to be computed.
851          */
852         private int count = UNKNOWN_VALUE;
853 
854         /** The height of the subtree rooted at this node. This will
855          * be set to {@link AbstractBSPTree#UNKNOWN_VALUE} when the value needs
856          * to be computed.
857          */
858         private int height = UNKNOWN_VALUE;
859 
860         /** Simple constructor.
861          * @param tree the tree instance that owns this node
862          */
863         protected AbstractNode(final AbstractBSPTree<P, N> tree) {
864             this.tree = tree;
865         }
866 
867         /** {@inheritDoc} */
868         @Override
869         public AbstractBSPTree<P, N> getTree() {
870             return tree;
871         }
872 
873         /** {@inheritDoc} */
874         @Override
875         public int depth() {
876             // Calculate our depth based on our parent's depth, if possible.
877             if (depth == UNKNOWN_VALUE &&
878                 parent != null) {
879                 final int parentDepth = parent.depth();
880                 if (parentDepth != UNKNOWN_VALUE) {
881                     depth = parentDepth + 1;
882                 }
883             }
884             return depth;
885         }
886 
887         /** {@inheritDoc} */
888         @Override
889         public int height() {
890             checkValid();
891 
892             if (height == UNKNOWN_VALUE) {
893                 if (isLeaf()) {
894                     height = 0;
895                 } else {
896                     height = Math.max(getMinus().height(), getPlus().height()) + 1;
897                 }
898             }
899 
900             return height;
901         }
902 
903         /** {@inheritDoc} */
904         @Override
905         public int count() {
906             checkValid();
907 
908             if (count == UNKNOWN_VALUE) {
909                 count = 1;
910 
911                 if (!isLeaf()) {
912                     count += minus.count() + plus.count();
913                 }
914             }
915 
916             return count;
917         }
918 
919         /** {@inheritDoc} */
920         @Override
921         public Iterable<N> nodes() {
922             return () -> new NodeIterator<>(getSelf());
923         }
924 
925         /** {@inheritDoc} */
926         @Override
927         public void accept(final BSPTreeVisitor<P, N> visitor) {
928             tree.accept(getSelf(), visitor);
929         }
930 
931         /** {@inheritDoc} */
932         @Override
933         public N getParent() {
934             return parent;
935         }
936 
937         /** {@inheritDoc} */
938         @Override
939         public boolean isLeaf() {
940             return cut == null;
941         }
942 
943         /** {@inheritDoc} */
944         @Override
945         public boolean isInternal() {
946             return cut != null;
947         }
948 
949         /** {@inheritDoc} */
950         @Override
951         public boolean isPlus() {
952             return parent != null && parent.getPlus() == this;
953         }
954 
955         /** {@inheritDoc} */
956         @Override
957         public boolean isMinus() {
958             return parent != null && parent.getMinus() == this;
959         }
960 
961         /** {@inheritDoc} */
962         @Override
963         public HyperplaneConvexSubset<P> getCut() {
964             return cut;
965         }
966 
967         /** {@inheritDoc} */
968         @Override
969         public Hyperplane<P> getCutHyperplane() {
970             return (cut != null) ? cut.getHyperplane() : null;
971         }
972 
973         /** {@inheritDoc} */
974         @Override
975         public N getPlus() {
976             return plus;
977         }
978 
979         /** {@inheritDoc} */
980         @Override
981         public N getMinus() {
982             return minus;
983         }
984 
985         /** {@inheritDoc} */
986         @Override
987         public HyperplaneConvexSubset<P> trim(final HyperplaneConvexSubset<P> sub) {
988             return getTree().trimToNode(getSelf(), sub);
989         }
990 
991         /** {@inheritDoc} */
992         @Override
993         public String toString() {
994             final StringBuilder sb = new StringBuilder();
995             sb.append(this.getClass().getSimpleName())
996                 .append("[cut= ")
997                 .append(getCut())
998                 .append(']');
999 
1000             return sb.toString();
1001         }
1002 
1003         /** Set the parameters for the subtree rooted at this node. The arguments should either be
1004          * all null (representing a leaf node) or all non-null (representing an internal node).
1005          *
1006          * <p>Absolutely no validation is performed on the arguments. Callers are responsible for
1007          * ensuring that any given hyperplane subset fits the region defined by the node and that
1008          * any child nodes belong to this tree and are correctly initialized.</p>
1009          *
1010          * @param newCut the new cut hyperplane subset for the node
1011          * @param newMinus the new minus child for the node
1012          * @param newPlus the new plus child for the node
1013          */
1014         protected void setSubtree(final HyperplaneConvexSubset<P> newCut, final N newMinus, final N newPlus) {
1015             this.cut = newCut;
1016 
1017             final N self = getSelf();
1018 
1019             // cast for access to private member
1020             final AbstractNode<P, N> minusNode = newMinus;
1021             final AbstractNode<P, N> plusNode = newPlus;
1022 
1023             // get the child depth now if we know it offhand, otherwise set it to the unknown value
1024             // and have the child pull it when needed
1025             final int childDepth = (depth != UNKNOWN_VALUE) ? depth + 1 : UNKNOWN_VALUE;
1026 
1027             if (newMinus != null) {
1028                 minusNode.parent = self;
1029                 minusNode.depth = childDepth;
1030             }
1031             this.minus = newMinus;
1032 
1033             if (newPlus != null) {
1034                 plusNode.parent = self;
1035                 plusNode.depth = childDepth;
1036             }
1037             this.plus = newPlus;
1038         }
1039 
1040         /**
1041          * Make this node a root node, detaching it from its parent and settings its depth to zero.
1042          * Any previous parent node will be left in an invalid state since one of its children now
1043          * does not have a reference back to it.
1044          */
1045         protected void makeRoot() {
1046             parent = null;
1047             depth = 0;
1048         }
1049 
1050         /** Check if cached node properties are valid, meaning that no structural updates have
1051          * occurred in the tree since the last call to this method. If updates have occurred, the
1052          * {@link #nodeInvalidated()} method is called to clear the cached properties. This method
1053          * should be called at the beginning of any method that fetches cacheable properties
1054          * to ensure that no stale values are returned.
1055          */
1056         protected void checkValid() {
1057             final int treeVersion = tree.getVersion();
1058 
1059             if (nodeVersion != treeVersion) {
1060                 // the tree structure changed somewhere
1061                 nodeInvalidated();
1062 
1063                 // store the current version
1064                 nodeVersion = treeVersion;
1065             }
1066         }
1067 
1068         /** Method called from {@link #checkValid()} when updates
1069          * are detected in the tree. This method should clear out any
1070          * computed properties that rely on the structure of the tree
1071          * and prepare them for recalculation.
1072          */
1073         protected void nodeInvalidated() {
1074             count = UNKNOWN_VALUE;
1075             height = UNKNOWN_VALUE;
1076         }
1077 
1078         /** Get a reference to the current instance, cast to type N.
1079          * @return a reference to the current instance, as type N.
1080          */
1081         protected abstract N getSelf();
1082     }
1083 
1084     /** Class for iterating through the nodes in a BSP subtree.
1085      * @param <P> Point implementation type
1086      * @param <N> Node implementation type
1087      */
1088     private static final class NodeIterator<P extends Point<P>, N extends AbstractNode<P, N>> implements Iterator<N> {
1089 
1090         /** The current node stack. */
1091         private final Deque<N> stack = new LinkedList<>();
1092 
1093         /** Create a new instance for iterating over the nodes in the given subtree.
1094          * @param subtreeRoot the root node of the subtree to iterate
1095          */
1096         NodeIterator(final N subtreeRoot) {
1097             stack.push(subtreeRoot);
1098         }
1099 
1100         /** {@inheritDoc} */
1101         @Override
1102         public boolean hasNext() {
1103             return !stack.isEmpty();
1104         }
1105 
1106         /** {@inheritDoc} */
1107         @Override
1108         public N next() {
1109             if (stack.isEmpty()) {
1110                 throw new NoSuchElementException();
1111             }
1112 
1113             final N result = stack.pop();
1114 
1115             if (result != null && !result.isLeaf()) {
1116                 stack.push(result.getPlus());
1117                 stack.push(result.getMinus());
1118             }
1119 
1120             return result;
1121         }
1122     }
1123 }