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 <op> 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 <op> 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 }