001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.math3.geometry.partitioning; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Comparator; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.TreeSet; 026 027import org.apache.commons.math3.geometry.Point; 028import org.apache.commons.math3.geometry.Space; 029import org.apache.commons.math3.geometry.Vector; 030 031/** Abstract class for all regions, independently of geometry type or dimension. 032 033 * @param <S> Type of the space. 034 * @param <T> Type of the sub-space. 035 036 * @since 3.0 037 */ 038public abstract class AbstractRegion<S extends Space, T extends Space> implements Region<S> { 039 040 /** Inside/Outside BSP tree. */ 041 private BSPTree<S> tree; 042 043 /** Tolerance below which points are considered to belong to hyperplanes. */ 044 private final double tolerance; 045 046 /** Size of the instance. */ 047 private double size; 048 049 /** Barycenter. */ 050 private Point<S> barycenter; 051 052 /** Build a region representing the whole space. 053 * @param tolerance tolerance below which points are considered identical. 054 */ 055 protected AbstractRegion(final double tolerance) { 056 this.tree = new BSPTree<S>(Boolean.TRUE); 057 this.tolerance = tolerance; 058 } 059 060 /** Build a region from an inside/outside BSP tree. 061 * <p>The leaf nodes of the BSP tree <em>must</em> have a 062 * {@code Boolean} attribute representing the inside status of 063 * the corresponding cell (true for inside cells, false for outside 064 * cells). In order to avoid building too many small objects, it is 065 * recommended to use the predefined constants 066 * {@code Boolean.TRUE} and {@code Boolean.FALSE}. The 067 * tree also <em>must</em> have either null internal nodes or 068 * internal nodes representing the boundary as specified in the 069 * {@link #getTree getTree} method).</p> 070 * @param tree inside/outside BSP tree representing the region 071 * @param tolerance tolerance below which points are considered identical. 072 */ 073 protected AbstractRegion(final BSPTree<S> tree, final double tolerance) { 074 this.tree = tree; 075 this.tolerance = tolerance; 076 } 077 078 /** Build a Region from a Boundary REPresentation (B-rep). 079 * <p>The boundary is provided as a collection of {@link 080 * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the 081 * interior part of the region on its minus side and the exterior on 082 * its plus side.</p> 083 * <p>The boundary elements can be in any order, and can form 084 * several non-connected sets (like for example polygons with holes 085 * or a set of disjoints polyhedrons considered as a whole). In 086 * fact, the elements do not even need to be connected together 087 * (their topological connections are not used here). However, if the 088 * boundary does not really separate an inside open from an outside 089 * open (open having here its topological meaning), then subsequent 090 * calls to the {@link #checkPoint(Point) checkPoint} method will not be 091 * meaningful anymore.</p> 092 * <p>If the boundary is empty, the region will represent the whole 093 * space.</p> 094 * @param boundary collection of boundary elements, as a 095 * collection of {@link SubHyperplane SubHyperplane} objects 096 * @param tolerance tolerance below which points are considered identical. 097 */ 098 protected AbstractRegion(final Collection<SubHyperplane<S>> boundary, final double tolerance) { 099 100 this.tolerance = tolerance; 101 102 if (boundary.size() == 0) { 103 104 // the tree represents the whole space 105 tree = new BSPTree<S>(Boolean.TRUE); 106 107 } else { 108 109 // sort the boundary elements in decreasing size order 110 // (we don't want equal size elements to be removed, so 111 // we use a trick to fool the TreeSet) 112 final TreeSet<SubHyperplane<S>> ordered = new TreeSet<SubHyperplane<S>>(new Comparator<SubHyperplane<S>>() { 113 /** {@inheritDoc} */ 114 public int compare(final SubHyperplane<S> o1, final SubHyperplane<S> o2) { 115 final double size1 = o1.getSize(); 116 final double size2 = o2.getSize(); 117 return (size2 < size1) ? -1 : ((o1 == o2) ? 0 : +1); 118 } 119 }); 120 ordered.addAll(boundary); 121 122 // build the tree top-down 123 tree = new BSPTree<S>(); 124 insertCuts(tree, ordered); 125 126 // set up the inside/outside flags 127 tree.visit(new BSPTreeVisitor<S>() { 128 129 /** {@inheritDoc} */ 130 public Order visitOrder(final BSPTree<S> node) { 131 return Order.PLUS_SUB_MINUS; 132 } 133 134 /** {@inheritDoc} */ 135 public void visitInternalNode(final BSPTree<S> node) { 136 } 137 138 /** {@inheritDoc} */ 139 public void visitLeafNode(final BSPTree<S> node) { 140 if (node.getParent() == null || node == node.getParent().getMinus()) { 141 node.setAttribute(Boolean.TRUE); 142 } else { 143 node.setAttribute(Boolean.FALSE); 144 } 145 } 146 }); 147 148 } 149 150 } 151 152 /** Build a convex region from an array of bounding hyperplanes. 153 * @param hyperplanes array of bounding hyperplanes (if null, an 154 * empty region will be built) 155 * @param tolerance tolerance below which points are considered identical. 156 */ 157 public AbstractRegion(final Hyperplane<S>[] hyperplanes, final double tolerance) { 158 this.tolerance = tolerance; 159 if ((hyperplanes == null) || (hyperplanes.length == 0)) { 160 tree = new BSPTree<S>(Boolean.FALSE); 161 } else { 162 163 // use the first hyperplane to build the right class 164 tree = hyperplanes[0].wholeSpace().getTree(false); 165 166 // chop off parts of the space 167 BSPTree<S> node = tree; 168 node.setAttribute(Boolean.TRUE); 169 for (final Hyperplane<S> hyperplane : hyperplanes) { 170 if (node.insertCut(hyperplane)) { 171 node.setAttribute(null); 172 node.getPlus().setAttribute(Boolean.FALSE); 173 node = node.getMinus(); 174 node.setAttribute(Boolean.TRUE); 175 } 176 } 177 178 } 179 180 } 181 182 /** {@inheritDoc} */ 183 public abstract AbstractRegion<S, T> buildNew(BSPTree<S> newTree); 184 185 /** Get the tolerance below which points are considered to belong to hyperplanes. 186 * @return tolerance below which points are considered to belong to hyperplanes 187 */ 188 public double getTolerance() { 189 return tolerance; 190 } 191 192 /** Recursively build a tree by inserting cut sub-hyperplanes. 193 * @param node current tree node (it is a leaf node at the beginning 194 * of the call) 195 * @param boundary collection of edges belonging to the cell defined 196 * by the node 197 */ 198 private void insertCuts(final BSPTree<S> node, final Collection<SubHyperplane<S>> boundary) { 199 200 final Iterator<SubHyperplane<S>> iterator = boundary.iterator(); 201 202 // build the current level 203 Hyperplane<S> inserted = null; 204 while ((inserted == null) && iterator.hasNext()) { 205 inserted = iterator.next().getHyperplane(); 206 if (!node.insertCut(inserted.copySelf())) { 207 inserted = null; 208 } 209 } 210 211 if (!iterator.hasNext()) { 212 return; 213 } 214 215 // distribute the remaining edges in the two sub-trees 216 final ArrayList<SubHyperplane<S>> plusList = new ArrayList<SubHyperplane<S>>(); 217 final ArrayList<SubHyperplane<S>> minusList = new ArrayList<SubHyperplane<S>>(); 218 while (iterator.hasNext()) { 219 final SubHyperplane<S> other = iterator.next(); 220 final SubHyperplane.SplitSubHyperplane<S> split = other.split(inserted); 221 switch (split.getSide()) { 222 case PLUS: 223 plusList.add(other); 224 break; 225 case MINUS: 226 minusList.add(other); 227 break; 228 case BOTH: 229 plusList.add(split.getPlus()); 230 minusList.add(split.getMinus()); 231 break; 232 default: 233 // ignore the sub-hyperplanes belonging to the cut hyperplane 234 } 235 } 236 237 // recurse through lower levels 238 insertCuts(node.getPlus(), plusList); 239 insertCuts(node.getMinus(), minusList); 240 241 } 242 243 /** {@inheritDoc} */ 244 public AbstractRegion<S, T> copySelf() { 245 return buildNew(tree.copySelf()); 246 } 247 248 /** {@inheritDoc} */ 249 public boolean isEmpty() { 250 return isEmpty(tree); 251 } 252 253 /** {@inheritDoc} */ 254 public boolean isEmpty(final BSPTree<S> node) { 255 256 // we use a recursive function rather than the BSPTreeVisitor 257 // interface because we can stop visiting the tree as soon as we 258 // have found an inside cell 259 260 if (node.getCut() == null) { 261 // if we find an inside node, the region is not empty 262 return !((Boolean) node.getAttribute()); 263 } 264 265 // check both sides of the sub-tree 266 return isEmpty(node.getMinus()) && isEmpty(node.getPlus()); 267 268 } 269 270 /** {@inheritDoc} */ 271 public boolean isFull() { 272 return isFull(tree); 273 } 274 275 /** {@inheritDoc} */ 276 public boolean isFull(final BSPTree<S> node) { 277 278 // we use a recursive function rather than the BSPTreeVisitor 279 // interface because we can stop visiting the tree as soon as we 280 // have found an outside cell 281 282 if (node.getCut() == null) { 283 // if we find an outside node, the region does not cover full space 284 return (Boolean) node.getAttribute(); 285 } 286 287 // check both sides of the sub-tree 288 return isFull(node.getMinus()) && isFull(node.getPlus()); 289 290 } 291 292 /** {@inheritDoc} */ 293 public boolean contains(final Region<S> region) { 294 return new RegionFactory<S>().difference(region, this).isEmpty(); 295 } 296 297 /** {@inheritDoc} 298 * @since 3.3 299 */ 300 public BoundaryProjection<S> projectToBoundary(final Point<S> point) { 301 final BoundaryProjector<S, T> projector = new BoundaryProjector<S, T>(point); 302 getTree(true).visit(projector); 303 return projector.getProjection(); 304 } 305 306 /** Check a point with respect to the region. 307 * @param point point to check 308 * @return a code representing the point status: either {@link 309 * Region.Location#INSIDE}, {@link Region.Location#OUTSIDE} or 310 * {@link Region.Location#BOUNDARY} 311 */ 312 public Location checkPoint(final Vector<S> point) { 313 return checkPoint((Point<S>) point); 314 } 315 316 /** {@inheritDoc} */ 317 public Location checkPoint(final Point<S> point) { 318 return checkPoint(tree, point); 319 } 320 321 /** Check a point with respect to the region starting at a given node. 322 * @param node root node of the region 323 * @param point point to check 324 * @return a code representing the point status: either {@link 325 * Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE 326 * OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY} 327 */ 328 protected Location checkPoint(final BSPTree<S> node, final Vector<S> point) { 329 return checkPoint(node, (Point<S>) point); 330 } 331 332 /** Check a point with respect to the region starting at a given node. 333 * @param node root node of the region 334 * @param point point to check 335 * @return a code representing the point status: either {@link 336 * Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE 337 * OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY} 338 */ 339 protected Location checkPoint(final BSPTree<S> node, final Point<S> point) { 340 final BSPTree<S> cell = node.getCell(point, tolerance); 341 if (cell.getCut() == null) { 342 // the point is in the interior of a cell, just check the attribute 343 return ((Boolean) cell.getAttribute()) ? Location.INSIDE : Location.OUTSIDE; 344 } 345 346 // the point is on a cut-sub-hyperplane, is it on a boundary ? 347 final Location minusCode = checkPoint(cell.getMinus(), point); 348 final Location plusCode = checkPoint(cell.getPlus(), point); 349 return (minusCode == plusCode) ? minusCode : Location.BOUNDARY; 350 351 } 352 353 /** {@inheritDoc} */ 354 public BSPTree<S> getTree(final boolean includeBoundaryAttributes) { 355 if (includeBoundaryAttributes && (tree.getCut() != null) && (tree.getAttribute() == null)) { 356 // compute the boundary attributes 357 tree.visit(new BoundaryBuilder<S>()); 358 } 359 return tree; 360 } 361 362 /** {@inheritDoc} */ 363 public double getBoundarySize() { 364 final BoundarySizeVisitor<S> visitor = new BoundarySizeVisitor<S>(); 365 getTree(true).visit(visitor); 366 return visitor.getSize(); 367 } 368 369 /** {@inheritDoc} */ 370 public double getSize() { 371 if (barycenter == null) { 372 computeGeometricalProperties(); 373 } 374 return size; 375 } 376 377 /** Set the size of the instance. 378 * @param size size of the instance 379 */ 380 protected void setSize(final double size) { 381 this.size = size; 382 } 383 384 /** {@inheritDoc} */ 385 public Point<S> getBarycenter() { 386 if (barycenter == null) { 387 computeGeometricalProperties(); 388 } 389 return barycenter; 390 } 391 392 /** Set the barycenter of the instance. 393 * @param barycenter barycenter of the instance 394 */ 395 protected void setBarycenter(final Vector<S> barycenter) { 396 setBarycenter((Point<S>) barycenter); 397 } 398 399 /** Set the barycenter of the instance. 400 * @param barycenter barycenter of the instance 401 */ 402 protected void setBarycenter(final Point<S> barycenter) { 403 this.barycenter = barycenter; 404 } 405 406 /** Compute some geometrical properties. 407 * <p>The properties to compute are the barycenter and the size.</p> 408 */ 409 protected abstract void computeGeometricalProperties(); 410 411 /** {@inheritDoc} */ 412 @Deprecated 413 public Side side(final Hyperplane<S> hyperplane) { 414 final InsideFinder<S> finder = new InsideFinder<S>(this); 415 finder.recurseSides(tree, hyperplane.wholeHyperplane()); 416 return finder.plusFound() ? 417 (finder.minusFound() ? Side.BOTH : Side.PLUS) : 418 (finder.minusFound() ? Side.MINUS : Side.HYPER); 419 } 420 421 /** {@inheritDoc} */ 422 public SubHyperplane<S> intersection(final SubHyperplane<S> sub) { 423 return recurseIntersection(tree, sub); 424 } 425 426 /** Recursively compute the parts of a sub-hyperplane that are 427 * contained in the region. 428 * @param node current BSP tree node 429 * @param sub sub-hyperplane traversing the region 430 * @return filtered sub-hyperplane 431 */ 432 private SubHyperplane<S> recurseIntersection(final BSPTree<S> node, final SubHyperplane<S> sub) { 433 434 if (node.getCut() == null) { 435 return (Boolean) node.getAttribute() ? sub.copySelf() : null; 436 } 437 438 final Hyperplane<S> hyperplane = node.getCut().getHyperplane(); 439 final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane); 440 if (split.getPlus() != null) { 441 if (split.getMinus() != null) { 442 // both sides 443 final SubHyperplane<S> plus = recurseIntersection(node.getPlus(), split.getPlus()); 444 final SubHyperplane<S> minus = recurseIntersection(node.getMinus(), split.getMinus()); 445 if (plus == null) { 446 return minus; 447 } else if (minus == null) { 448 return plus; 449 } else { 450 return plus.reunite(minus); 451 } 452 } else { 453 // only on plus side 454 return recurseIntersection(node.getPlus(), sub); 455 } 456 } else if (split.getMinus() != null) { 457 // only on minus side 458 return recurseIntersection(node.getMinus(), sub); 459 } else { 460 // on hyperplane 461 return recurseIntersection(node.getPlus(), 462 recurseIntersection(node.getMinus(), sub)); 463 } 464 465 } 466 467 /** Transform a region. 468 * <p>Applying a transform to a region consist in applying the 469 * transform to all the hyperplanes of the underlying BSP tree and 470 * of the boundary (and also to the sub-hyperplanes embedded in 471 * these hyperplanes) and to the barycenter. The instance is not 472 * modified, a new instance is built.</p> 473 * @param transform transform to apply 474 * @return a new region, resulting from the application of the 475 * transform to the instance 476 */ 477 public AbstractRegion<S, T> applyTransform(final Transform<S, T> transform) { 478 479 // transform the tree, except for boundary attribute splitters 480 final Map<BSPTree<S>, BSPTree<S>> map = new HashMap<BSPTree<S>, BSPTree<S>>(); 481 final BSPTree<S> transformedTree = recurseTransform(getTree(false), transform, map); 482 483 // set up the boundary attributes splitters 484 for (final Map.Entry<BSPTree<S>, BSPTree<S>> entry : map.entrySet()) { 485 if (entry.getKey().getCut() != null) { 486 @SuppressWarnings("unchecked") 487 BoundaryAttribute<S> original = (BoundaryAttribute<S>) entry.getKey().getAttribute(); 488 if (original != null) { 489 @SuppressWarnings("unchecked") 490 BoundaryAttribute<S> transformed = (BoundaryAttribute<S>) entry.getValue().getAttribute(); 491 for (final BSPTree<S> splitter : original.getSplitters()) { 492 transformed.getSplitters().add(map.get(splitter)); 493 } 494 } 495 } 496 } 497 498 return buildNew(transformedTree); 499 500 } 501 502 /** Recursively transform an inside/outside BSP-tree. 503 * @param node current BSP tree node 504 * @param transform transform to apply 505 * @param map transformed nodes map 506 * @return a new tree 507 */ 508 @SuppressWarnings("unchecked") 509 private BSPTree<S> recurseTransform(final BSPTree<S> node, final Transform<S, T> transform, 510 final Map<BSPTree<S>, BSPTree<S>> map) { 511 512 final BSPTree<S> transformedNode; 513 if (node.getCut() == null) { 514 transformedNode = new BSPTree<S>(node.getAttribute()); 515 } else { 516 517 final SubHyperplane<S> sub = node.getCut(); 518 final SubHyperplane<S> tSub = ((AbstractSubHyperplane<S, T>) sub).applyTransform(transform); 519 BoundaryAttribute<S> attribute = (BoundaryAttribute<S>) node.getAttribute(); 520 if (attribute != null) { 521 final SubHyperplane<S> tPO = (attribute.getPlusOutside() == null) ? 522 null : ((AbstractSubHyperplane<S, T>) attribute.getPlusOutside()).applyTransform(transform); 523 final SubHyperplane<S> tPI = (attribute.getPlusInside() == null) ? 524 null : ((AbstractSubHyperplane<S, T>) attribute.getPlusInside()).applyTransform(transform); 525 // we start with an empty list of splitters, it will be filled in out of recursion 526 attribute = new BoundaryAttribute<S>(tPO, tPI, new NodesSet<S>()); 527 } 528 529 transformedNode = new BSPTree<S>(tSub, 530 recurseTransform(node.getPlus(), transform, map), 531 recurseTransform(node.getMinus(), transform, map), 532 attribute); 533 } 534 535 map.put(node, transformedNode); 536 return transformedNode; 537 538 } 539 540}