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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.geometry.partitioning;
23  
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import org.hipparchus.exception.MathRuntimeException;
28  import org.hipparchus.geometry.Point;
29  import org.hipparchus.geometry.Space;
30  import org.hipparchus.util.FastMath;
31  
32  /** This class represent a Binary Space Partition tree.
33  
34   * <p>BSP trees are an efficient way to represent space partitions and
35   * to associate attributes with each cell. Each node in a BSP tree
36   * represents a convex region which is partitioned in two convex
37   * sub-regions at each side of a cut hyperplane. The root tree
38   * contains the complete space.</p>
39  
40   * <p>The main use of such partitions is to use a boolean attribute to
41   * define an inside/outside property, hence representing arbitrary
42   * polytopes (line segments in 1D, polygons in 2D and polyhedrons in
43   * 3D) and to operate on them.</p>
44  
45   * <p>Another example would be to represent Voronoi tesselations, the
46   * attribute of each cell holding the defining point of the cell.</p>
47  
48   * <p>The application-defined attributes are shared among copied
49   * instances and propagated to split parts. These attributes are not
50   * used by the BSP-tree algorithms themselves, so the application can
51   * use them for any purpose. Since the tree visiting method holds
52   * internal and leaf nodes differently, it is possible to use
53   * different classes for internal nodes attributes and leaf nodes
54   * attributes. This should be used with care, though, because if the
55   * tree is modified in any way after attributes have been set, some
56   * internal nodes may become leaf nodes and some leaf nodes may become
57   * internal nodes.</p>
58  
59   * <p>One of the main sources for the development of this package was
60   * Bruce Naylor, John Amanatides and William Thibault paper <a
61   * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
62   * BSP Trees Yields Polyhedral Set Operations</a> Proc. Siggraph '90,
63   * Computer Graphics 24(4), August 1990, pp 115-124, published by the
64   * Association for Computing Machinery (ACM).</p>
65  
66   * @param <S> Type of the space.
67  
68   */
69  public class BSPTree<S extends Space> {
70  
71      /** Cut sub-hyperplane. */
72      private SubHyperplane<S> cut;
73  
74      /** Tree at the plus side of the cut hyperplane. */
75      private BSPTree<S> plus;
76  
77      /** Tree at the minus side of the cut hyperplane. */
78      private BSPTree<S> minus;
79  
80      /** Parent tree. */
81      private BSPTree<S> parent;
82  
83      /** Application-defined attribute. */
84      private Object attribute;
85  
86      /** Build a tree having only one root cell representing the whole space.
87       */
88      public BSPTree() {
89          cut       = null;
90          plus      = null;
91          minus     = null;
92          parent    = null;
93          attribute = null;
94      }
95  
96      /** Build a tree having only one root cell representing the whole space.
97       * @param attribute attribute of the tree (may be null)
98       */
99      public BSPTree(final Object attribute) {
100         cut    = null;
101         plus   = null;
102         minus  = null;
103         parent = null;
104         this.attribute = attribute;
105     }
106 
107     /** Build a BSPTree from its underlying elements.
108      * <p>This method does <em>not</em> perform any verification on
109      * consistency of its arguments, it should therefore be used only
110      * when then caller knows what it is doing.</p>
111      * <p>This method is mainly useful to build trees
112      * bottom-up. Building trees top-down is realized with the help of
113      * method {@link #insertCut insertCut}.</p>
114      * @param cut cut sub-hyperplane for the tree
115      * @param plus plus side sub-tree
116      * @param minus minus side sub-tree
117      * @param attribute attribute associated with the node (may be null)
118      * @see #insertCut
119      */
120     public BSPTree(final SubHyperplane<S> cut, final BSPTree<S> plus, final BSPTree<S> minus,
121                    final Object attribute) {
122         this.cut       = cut;
123         this.plus      = plus;
124         this.minus     = minus;
125         this.parent    = null;
126         this.attribute = attribute;
127         plus.parent    = this;
128         minus.parent   = this;
129     }
130 
131     /** Insert a cut sub-hyperplane in a node.
132      * <p>The sub-tree starting at this node will be completely
133      * overwritten. The new cut sub-hyperplane will be built from the
134      * intersection of the provided hyperplane with the cell. If the
135      * hyperplane does intersect the cell, the cell will have two
136      * children cells with {@code null} attributes on each side of
137      * the inserted cut sub-hyperplane. If the hyperplane does not
138      * intersect the cell then <em>no</em> cut hyperplane will be
139      * inserted and the cell will be changed to a leaf cell. The
140      * attribute of the node is never changed.</p>
141      * <p>This method is mainly useful when called on leaf nodes
142      * (i.e. nodes for which {@link #getCut getCut} returns
143      * {@code null}), in this case it provides a way to build a
144      * tree top-down (whereas the {@link #BSPTree(SubHyperplane,
145      * BSPTree, BSPTree, Object) 4 arguments constructor} is devoted to
146      * build trees bottom-up).</p>
147      * @param hyperplane hyperplane to insert, it will be chopped in
148      * order to fit in the cell defined by the parent nodes of the
149      * instance
150      * @return true if a cut sub-hyperplane has been inserted (i.e. if
151      * the cell now has two leaf child nodes)
152      * @see #BSPTree(SubHyperplane, BSPTree, BSPTree, Object)
153      */
154     public boolean insertCut(final Hyperplane<S> hyperplane) {
155 
156         if (cut != null) {
157             plus.parent  = null;
158             minus.parent = null;
159         }
160 
161         final SubHyperplane<S> chopped = fitToCell(hyperplane.wholeHyperplane());
162         if (chopped == null || chopped.isEmpty()) {
163             cut          = null;
164             plus         = null;
165             minus        = null;
166             return false;
167         }
168 
169         cut          = chopped;
170         plus         = new BSPTree<>();
171         plus.parent  = this;
172         minus        = new BSPTree<>();
173         minus.parent = this;
174         return true;
175 
176     }
177 
178     /** Copy the instance.
179      * <p>The instance created is completely independent of the original
180      * one. A deep copy is used, none of the underlying objects are
181      * shared (except for the nodes attributes and immutable
182      * objects).</p>
183      * @return a new tree, copy of the instance
184      */
185     public BSPTree<S> copySelf() {
186 
187         if (cut == null) {
188             return new BSPTree<S>(attribute);
189         }
190 
191         return new BSPTree<S>(cut.copySelf(), plus.copySelf(), minus.copySelf(),
192                            attribute);
193 
194     }
195 
196     /** Get the cut sub-hyperplane.
197      * @return cut sub-hyperplane, null if this is a leaf tree
198      */
199     public SubHyperplane<S> getCut() {
200         return cut;
201     }
202 
203     /** Get the tree on the plus side of the cut hyperplane.
204      * @return tree on the plus side of the cut hyperplane, null if this
205      * is a leaf tree
206      */
207     public BSPTree<S> getPlus() {
208         return plus;
209     }
210 
211     /** Get the tree on the minus side of the cut hyperplane.
212      * @return tree on the minus side of the cut hyperplane, null if this
213      * is a leaf tree
214      */
215     public BSPTree<S> getMinus() {
216         return minus;
217     }
218 
219     /** Get the parent node.
220      * @return parent node, null if the node has no parents
221      */
222     public BSPTree<S> getParent() {
223         return parent;
224     }
225 
226     /** Associate an attribute with the instance.
227      * @param attribute attribute to associate with the node
228      * @see #getAttribute
229      */
230     public void setAttribute(final Object attribute) {
231         this.attribute = attribute;
232     }
233 
234     /** Get the attribute associated with the instance.
235      * @return attribute associated with the node or null if no
236      * attribute has been explicitly set using the {@link #setAttribute
237      * setAttribute} method
238      * @see #setAttribute
239      */
240     public Object getAttribute() {
241         return attribute;
242     }
243 
244     /** Visit the BSP tree nodes.
245      * @param visitor object visiting the tree nodes
246      */
247     public void visit(final BSPTreeVisitor<S> visitor) {
248         if (cut == null) {
249             visitor.visitLeafNode(this);
250         } else {
251             switch (visitor.visitOrder(this)) {
252             case PLUS_MINUS_SUB:
253                 plus.visit(visitor);
254                 minus.visit(visitor);
255                 visitor.visitInternalNode(this);
256                 break;
257             case PLUS_SUB_MINUS:
258                 plus.visit(visitor);
259                 visitor.visitInternalNode(this);
260                 minus.visit(visitor);
261                 break;
262             case MINUS_PLUS_SUB:
263                 minus.visit(visitor);
264                 plus.visit(visitor);
265                 visitor.visitInternalNode(this);
266                 break;
267             case MINUS_SUB_PLUS:
268                 minus.visit(visitor);
269                 visitor.visitInternalNode(this);
270                 plus.visit(visitor);
271                 break;
272             case SUB_PLUS_MINUS:
273                 visitor.visitInternalNode(this);
274                 plus.visit(visitor);
275                 minus.visit(visitor);
276                 break;
277             case SUB_MINUS_PLUS:
278                 visitor.visitInternalNode(this);
279                 minus.visit(visitor);
280                 plus.visit(visitor);
281                 break;
282             default:
283                 throw MathRuntimeException.createInternalError();
284             }
285 
286         }
287     }
288 
289     /** Fit a sub-hyperplane inside the cell defined by the instance.
290      * <p>Fitting is done by chopping off the parts of the
291      * sub-hyperplane that lie outside of the cell using the
292      * cut-hyperplanes of the parent nodes of the instance.</p>
293      * @param sub sub-hyperplane to fit
294      * @return a new sub-hyperplane, guaranteed to have no part outside
295      * of the instance cell
296      */
297     private SubHyperplane<S> fitToCell(final SubHyperplane<S> sub) {
298         SubHyperplane<S> s = sub;
299         for (BSPTree<S> tree = this; tree.parent != null && s != null; tree = tree.parent) {
300             if (tree == tree.parent.plus) {
301                 s = s.split(tree.parent.cut.getHyperplane()).getPlus();
302             } else {
303                 s = s.split(tree.parent.cut.getHyperplane()).getMinus();
304             }
305         }
306         return s;
307     }
308 
309     /** Get the cell to which a point belongs.
310      * <p>If the returned cell is a leaf node the points belongs to the
311      * interior of the node, if the cell is an internal node the points
312      * belongs to the node cut sub-hyperplane.</p>
313      * @param point point to check
314      * @param tolerance tolerance below which points close to a cut hyperplane
315      * are considered to belong to the hyperplane itself
316      * @return the tree cell to which the point belongs
317      */
318     public BSPTree<S> getCell(final Point<S> point, final double tolerance) {
319 
320         if (cut == null) {
321             return this;
322         }
323 
324         // position of the point with respect to the cut hyperplane
325         final double offset = cut.getHyperplane().getOffset(point);
326 
327         if (FastMath.abs(offset) < tolerance) {
328             return this;
329         } else if (offset <= 0) {
330             // point is on the minus side of the cut hyperplane
331             return minus.getCell(point, tolerance);
332         } else {
333             // point is on the plus side of the cut hyperplane
334             return plus.getCell(point, tolerance);
335         }
336 
337     }
338 
339     /** Get the cells whose cut sub-hyperplanes are close to the point.
340      * @param point point to check
341      * @param maxOffset offset below which a cut sub-hyperplane is considered
342      * close to the point (in absolute value)
343      * @return close cells (may be empty if all cut sub-hyperplanes are farther
344      * than maxOffset from the point)
345      */
346     public List<BSPTree<S>> getCloseCuts(final Point<S> point, final double maxOffset) {
347         final List<BSPTree<S>> close = new ArrayList<>();
348         recurseCloseCuts(point, maxOffset, close);
349         return close;
350     }
351 
352     /** Get the cells whose cut sub-hyperplanes are close to the point.
353      * @param point point to check
354      * @param maxOffset offset below which a cut sub-hyperplane is considered
355      * close to the point (in absolute value)
356      * @param close list to fill
357      */
358     private void recurseCloseCuts(final Point<S> point, final double maxOffset,
359                                   final List<BSPTree<S>> close) {
360         if (cut != null) {
361 
362             // position of the point with respect to the cut hyperplane
363             final double offset = cut.getHyperplane().getOffset(point);
364 
365             if (offset < -maxOffset) {
366                 // point is on the minus side of the cut hyperplane
367                 minus.recurseCloseCuts(point, maxOffset, close);
368             } else if (offset > maxOffset) {
369                 // point is on the plus side of the cut hyperplane
370                 plus.recurseCloseCuts(point, maxOffset, close);
371             } else {
372                 // point is close to the cut hyperplane
373                 close.add(this);
374                 minus.recurseCloseCuts(point, maxOffset, close);
375                 plus.recurseCloseCuts(point, maxOffset, close);
376             }
377 
378         }
379     }
380 
381     /** Perform condensation on a tree.
382      * <p>The condensation operation is not recursive, it must be called
383      * explicitly from leaves to root.</p>
384      */
385     private void condense() {
386         if ((cut != null) && (plus.cut == null) && (minus.cut == null) &&
387             (((plus.attribute == null) && (minus.attribute == null)) ||
388              ((plus.attribute != null) && plus.attribute.equals(minus.attribute)))) {
389             attribute = (plus.attribute == null) ? minus.attribute : plus.attribute;
390             cut       = null;
391             plus      = null;
392             minus     = null;
393         }
394     }
395 
396     /** Merge a BSP tree with the instance.
397      * <p>All trees are modified (parts of them are reused in the new
398      * tree), it is the responsibility of the caller to ensure a copy
399      * has been done before if any of the former tree should be
400      * preserved, <em>no</em> such copy is done here!</p>
401      * <p>The algorithm used here is directly derived from the one
402      * described in the Naylor, Amanatides and Thibault paper (section
403      * III, Binary Partitioning of a BSP Tree).</p>
404      * @param tree other tree to merge with the instance (will be
405      * <em>unusable</em> after the operation, as well as the
406      * instance itself)
407      * @param leafMerger object implementing the final merging phase
408      * (this is where the semantic of the operation occurs, generally
409      * depending on the attribute of the leaf node)
410      * @return a new tree, result of <code>instance &lt;op&gt;
411      * tree</code>, this value can be ignored if parentTree is not null
412      * since all connections have already been established
413      */
414     public BSPTree<S> merge(final BSPTree<S> tree, final LeafMerger<S> leafMerger) {
415         return merge(tree, leafMerger, null, false);
416     }
417 
418     /** Merge a BSP tree with the instance.
419      * @param tree other tree to merge with the instance (will be
420      * <em>unusable</em> after the operation, as well as the
421      * instance itself)
422      * @param leafMerger object implementing the final merging phase
423      * (this is where the semantic of the operation occurs, generally
424      * depending on the attribute of the leaf node)
425      * @param parentTree parent tree to connect to (may be null)
426      * @param isPlusChild if true and if parentTree is not null, the
427      * resulting tree should be the plus child of its parent, ignored if
428      * parentTree is null
429      * @return a new tree, result of <code>instance &lt;op&gt;
430      * tree</code>, this value can be ignored if parentTree is not null
431      * since all connections have already been established
432      */
433     private BSPTree<S> merge(final BSPTree<S> tree, final LeafMerger<S> leafMerger,
434                              final BSPTree<S> parentTree, final boolean isPlusChild) {
435         if (cut == null) {
436             // cell/tree operation
437             return leafMerger.merge(this, tree, parentTree, isPlusChild, true);
438         } else if (tree.cut == null) {
439             // tree/cell operation
440             return leafMerger.merge(tree, this, parentTree, isPlusChild, false);
441         } else {
442             // tree/tree operation
443             final BSPTree<S> merged = tree.split(cut);
444             if (parentTree != null) {
445                 merged.parent = parentTree;
446                 if (isPlusChild) {
447                     parentTree.plus = merged;
448                 } else {
449                     parentTree.minus = merged;
450                 }
451             }
452 
453             // merging phase
454             plus.merge(merged.plus, leafMerger, merged, true);
455             minus.merge(merged.minus, leafMerger, merged, false);
456             merged.condense();
457             if (merged.cut != null) {
458                 merged.cut = merged.fitToCell(merged.cut.getHyperplane().wholeHyperplane());
459             }
460 
461             return merged;
462 
463         }
464     }
465 
466     /** This interface gather the merging operations between a BSP tree
467      * leaf and another BSP tree.
468      * <p>As explained in Bruce Naylor, John Amanatides and William
469      * Thibault paper <a
470      * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
471      * BSP Trees Yields Polyhedral Set Operations</a>,
472      * the operations on {@link BSPTree BSP trees} can be expressed as a
473      * generic recursive merging operation where only the final part,
474      * when one of the operand is a leaf, is specific to the real
475      * operation semantics. For example, a tree representing a region
476      * using a boolean attribute to identify inside cells and outside
477      * cells would use four different objects to implement the final
478      * merging phase of the four set operations union, intersection,
479      * difference and symmetric difference (exclusive or).</p>
480      * @param <S> Type of the space.
481      */
482     public interface LeafMerger<S extends Space> {
483 
484         /** Merge a leaf node and a tree node.
485          * <p>This method is called at the end of a recursive merging
486          * resulting from a {@code tree1.merge(tree2, leafMerger)}
487          * call, when one of the sub-trees involved is a leaf (i.e. when
488          * its cut-hyperplane is null). This is the only place where the
489          * precise semantics of the operation are required. For all upper
490          * level nodes in the tree, the merging operation is only a
491          * generic partitioning algorithm.</p>
492          * <p>Since the final operation may be non-commutative, it is
493          * important to know if the leaf node comes from the instance tree
494          * ({@code tree1}) or the argument tree
495          * ({@code tree2}). The third argument of the method is
496          * devoted to this. It can be ignored for commutative
497          * operations.</p>
498          * <p>The {@link BSPTree#insertInTree BSPTree.insertInTree} method
499          * may be useful to implement this method.</p>
500          * @param leaf leaf node (its cut hyperplane is guaranteed to be
501          * null)
502          * @param tree tree node (its cut hyperplane may be null or not)
503          * @param parentTree parent tree to connect to (may be null)
504          * @param isPlusChild if true and if parentTree is not null, the
505          * resulting tree should be the plus child of its parent, ignored if
506          * parentTree is null
507          * @param leafFromInstance if true, the leaf node comes from the
508          * instance tree ({@code tree1}) and the tree node comes from
509          * the argument tree ({@code tree2})
510          * @return the BSP tree resulting from the merging (may be one of
511          * the arguments)
512          */
513         BSPTree<S> merge(BSPTree<S> leaf, BSPTree<S> tree, BSPTree<S> parentTree,
514                          boolean isPlusChild, boolean leafFromInstance);
515 
516     }
517 
518     /** This interface handles the corner cases when an internal node cut sub-hyperplane vanishes.
519      * <p>
520      * Such cases happens for example when a cut sub-hyperplane is inserted into
521      * another tree (during a merge operation), and is split in several parts,
522      * some of which becomes smaller than the tolerance. The corresponding node
523      * as then no cut sub-hyperplane anymore, but does have children. This interface
524      * specifies how to handle this situation.
525      * setting
526      * </p>
527      * @param <S> Type of the space.
528      */
529     public interface VanishingCutHandler<S extends Space> {
530 
531         /** Fix a node with both vanished cut and children.
532          * @param node node to fix
533          * @return fixed node
534          */
535         BSPTree<S> fixNode(BSPTree<S> node);
536 
537     }
538 
539     /** Split a BSP tree by an external sub-hyperplane.
540      * <p>Split a tree in two halves, on each side of the
541      * sub-hyperplane. The instance is not modified.</p>
542      * <p>The tree returned is not upward-consistent: despite all of its
543      * sub-trees cut sub-hyperplanes (including its own cut
544      * sub-hyperplane) are bounded to the current cell, it is <em>not</em>
545      * attached to any parent tree yet. This tree is intended to be
546      * later inserted into an higher level tree.</p>
547      * <p>The algorithm used here is the one given in Naylor, Amanatides
548      * and Thibault paper (section III, Binary Partitioning of a BSP
549      * Tree).</p>
550      * @param sub partitioning sub-hyperplane, must be already clipped
551      * to the convex region represented by the instance, will be used as
552      * the cut sub-hyperplane of the returned tree
553      * @return a tree having the specified sub-hyperplane as its cut
554      * sub-hyperplane, the two parts of the split instance as its two
555      * sub-trees and a null parent
556      */
557     public BSPTree<S> split(final SubHyperplane<S> sub) {
558 
559         if (cut == null) {
560             return new BSPTree<S>(sub, copySelf(), new BSPTree<S>(attribute), null);
561         }
562 
563         final Hyperplane<S> cHyperplane = cut.getHyperplane();
564         final Hyperplane<S> sHyperplane = sub.getHyperplane();
565         final SubHyperplane.SplitSubHyperplane<S> subParts = sub.split(cHyperplane);
566         switch (subParts.getSide()) {
567         case PLUS :
568         { // the partitioning sub-hyperplane is entirely in the plus sub-tree
569             final BSPTree<S> split = plus.split(sub);
570             if (cut.split(sHyperplane).getSide() == Side.PLUS) {
571                 split.plus =
572                     new BSPTree<>(cut.copySelf(), split.plus, minus.copySelf(), attribute);
573                 split.plus.condense();
574                 split.plus.parent = split;
575             } else {
576                 split.minus =
577                     new BSPTree<>(cut.copySelf(), split.minus, minus.copySelf(), attribute);
578                 split.minus.condense();
579                 split.minus.parent = split;
580             }
581             return split;
582         }
583         case MINUS :
584         { // the partitioning sub-hyperplane is entirely in the minus sub-tree
585             final BSPTree<S> split = minus.split(sub);
586             if (cut.split(sHyperplane).getSide() == Side.PLUS) {
587                 split.plus =
588                     new BSPTree<>(cut.copySelf(), plus.copySelf(), split.plus, attribute);
589                 split.plus.condense();
590                 split.plus.parent = split;
591             } else {
592                 split.minus =
593                     new BSPTree<>(cut.copySelf(), plus.copySelf(), split.minus, attribute);
594                 split.minus.condense();
595                 split.minus.parent = split;
596             }
597             return split;
598         }
599         case BOTH :
600         {
601             final SubHyperplane.SplitSubHyperplane<S> cutParts = cut.split(sHyperplane);
602             final BSPTree<S> split =
603                             new BSPTree<>(sub, plus.split(subParts.getPlus()), minus.split(subParts.getMinus()),
604                                           null);
605             final BSPTree<S> tmp    = split.plus.minus;
606             split.plus.minus        = split.minus.plus;
607             split.plus.minus.parent = split.plus;
608             split.minus.plus        = tmp;
609             split.minus.plus.parent = split.minus;
610             if (cutParts.getPlus() == null) {
611                 split.plus.cut = cut.getHyperplane().emptyHyperplane();
612             } else {
613                 split.plus.cut = cutParts.getPlus();
614             }
615             if (cutParts.getMinus() == null) {
616                 split.minus.cut = cut.getHyperplane().emptyHyperplane();
617             } else {
618                 split.minus.cut = cutParts.getMinus();
619             }
620             split.plus.condense();
621             split.minus.condense();
622             return split;
623 
624         }
625         default :
626             return cHyperplane.sameOrientationAs(sHyperplane) ?
627                    new BSPTree<>(sub, plus.copySelf(),  minus.copySelf(), attribute) :
628                    new BSPTree<>(sub, minus.copySelf(), plus.copySelf(),  attribute);
629         }
630 
631     }
632 
633     /** Insert the instance into another tree.
634      * <p>The instance itself is modified so its former parent should
635      * not be used anymore.</p>
636      * @param parentTree parent tree to connect to (may be null)
637      * @param isPlusChild if true and if parentTree is not null, the
638      * resulting tree should be the plus child of its parent, ignored if
639      * parentTree is null
640      * @param vanishingHandler handler to use for handling very rare corner
641      * cases of vanishing cut sub-hyperplanes in internal nodes during merging
642      * @see LeafMerger
643      */
644     public void insertInTree(final BSPTree<S> parentTree, final boolean isPlusChild,
645                              final VanishingCutHandler<S> vanishingHandler) {
646 
647         // set up parent/child links
648         parent = parentTree;
649         if (parentTree != null) {
650             if (isPlusChild) {
651                 parentTree.plus = this;
652             } else {
653                 parentTree.minus = this;
654             }
655         }
656 
657         // make sure the inserted tree lies in the cell defined by its parent nodes
658         if (cut != null) {
659 
660             // explore the parent nodes from here towards tree root
661             for (BSPTree<S> tree = this; tree.parent != null; tree = tree.parent) {
662 
663                 // this is an hyperplane of some parent node
664                 final Hyperplane<S> hyperplane = tree.parent.cut.getHyperplane();
665 
666                 // chop off the parts of the inserted tree that extend
667                 // on the wrong side of this parent hyperplane
668                 if (tree == tree.parent.plus) {
669                     cut = cut.split(hyperplane).getPlus();
670                     plus.chopOffMinus(hyperplane, vanishingHandler);
671                     minus.chopOffMinus(hyperplane, vanishingHandler);
672                 } else {
673                     cut = cut.split(hyperplane).getMinus();
674                     plus.chopOffPlus(hyperplane, vanishingHandler);
675                     minus.chopOffPlus(hyperplane, vanishingHandler);
676                 }
677 
678                 if (cut == null) {
679                     // the cut sub-hyperplane has vanished
680                     final BSPTree<S> fixed = vanishingHandler.fixNode(this);
681                     cut       = fixed.cut;
682                     plus      = fixed.plus;
683                     minus     = fixed.minus;
684                     attribute = fixed.attribute;
685                     if (cut == null) {
686                         break;
687                     }
688                 }
689 
690             }
691 
692             // since we may have drop some parts of the inserted tree,
693             // perform a condensation pass to keep the tree structure simple
694             condense();
695 
696         }
697 
698     }
699 
700     /** Prune a tree around a cell.
701      * <p>
702      * This method can be used to extract a convex cell from a tree.
703      * The original cell may either be a leaf node or an internal node.
704      * If it is an internal node, it's subtree will be ignored (i.e. the
705      * extracted cell will be a leaf node in all cases). The original
706      * tree to which the original cell belongs is not touched at all,
707      * a new independent tree will be built.
708      * </p>
709      * @param cellAttribute attribute to set for the leaf node
710      * corresponding to the initial instance cell
711      * @param otherLeafsAttributes attribute to set for the other leaf
712      * nodes
713      * @param internalAttributes attribute to set for the internal nodes
714      * @return a new tree (the original tree is left untouched) containing
715      * a single branch with the cell as a leaf node, and other leaf nodes
716      * as the remnants of the pruned branches
717      */
718     public BSPTree<S> pruneAroundConvexCell(final Object cellAttribute,
719                                             final Object otherLeafsAttributes,
720                                             final Object internalAttributes) {
721 
722         // build the current cell leaf
723         BSPTree<S> tree = new BSPTree<>(cellAttribute);
724 
725         // build the pruned tree bottom-up
726         for (BSPTree<S> current = this; current.parent != null; current = current.parent) {
727             final SubHyperplane<S> parentCut = current.parent.cut.copySelf();
728             final BSPTree<S>       sibling   = new BSPTree<>(otherLeafsAttributes);
729             if (current == current.parent.plus) {
730                 tree = new BSPTree<>(parentCut, tree, sibling, internalAttributes);
731             } else {
732                 tree = new BSPTree<>(parentCut, sibling, tree, internalAttributes);
733             }
734         }
735 
736         return tree;
737 
738     }
739 
740     /** Chop off parts of the tree.
741      * <p>The instance is modified in place, all the parts that are on
742      * the minus side of the chopping hyperplane are discarded, only the
743      * parts on the plus side remain.</p>
744      * @param hyperplane chopping hyperplane
745      * @param vanishingHandler handler to use for handling very rare corner
746      * cases of vanishing cut sub-hyperplanes in internal nodes during merging
747      */
748     private void chopOffMinus(final Hyperplane<S> hyperplane, final VanishingCutHandler<S> vanishingHandler) {
749         if (cut != null) {
750 
751             cut = cut.split(hyperplane).getPlus();
752             plus.chopOffMinus(hyperplane, vanishingHandler);
753             minus.chopOffMinus(hyperplane, vanishingHandler);
754 
755             if (cut == null) {
756                 // the cut sub-hyperplane has vanished
757                 final BSPTree<S> fixed = vanishingHandler.fixNode(this);
758                 cut       = fixed.cut;
759                 plus      = fixed.plus;
760                 minus     = fixed.minus;
761                 attribute = fixed.attribute;
762             }
763 
764         }
765     }
766 
767     /** Chop off parts of the tree.
768      * <p>The instance is modified in place, all the parts that are on
769      * the plus side of the chopping hyperplane are discarded, only the
770      * parts on the minus side remain.</p>
771      * @param hyperplane chopping hyperplane
772      * @param vanishingHandler handler to use for handling very rare corner
773      * cases of vanishing cut sub-hyperplanes in internal nodes during merging
774      */
775     private void chopOffPlus(final Hyperplane<S> hyperplane, final VanishingCutHandler<S> vanishingHandler) {
776         if (cut != null) {
777 
778             cut = cut.split(hyperplane).getMinus();
779             plus.chopOffPlus(hyperplane, vanishingHandler);
780             minus.chopOffPlus(hyperplane, vanishingHandler);
781 
782             if (cut == null) {
783                 // the cut sub-hyperplane has vanished
784                 final BSPTree<S> fixed = vanishingHandler.fixNode(this);
785                 cut       = fixed.cut;
786                 plus      = fixed.plus;
787                 minus     = fixed.minus;
788                 attribute = fixed.attribute;
789             }
790 
791         }
792     }
793 
794 }