AbstractRegion.java

  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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.geometry.partitioning;

  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.Comparator;
  25. import java.util.HashMap;
  26. import java.util.Iterator;
  27. import java.util.Map;
  28. import java.util.TreeSet;

  29. import org.hipparchus.geometry.Point;
  30. import org.hipparchus.geometry.Space;

  31. /** Abstract class for all regions, independently of geometry type or dimension.

  32.  * @param <S> Type of the space.
  33.  * @param <P> Type of the points in space.
  34.  * @param <H> Type of the hyperplane.
  35.  * @param <I> Type of the sub-hyperplane.
  36.  * @param <T> Type of the sub-space.
  37.  * @param <Q> Type of the points in sub-space.
  38.  * @param <F> Type of the hyperplane.
  39.  * @param <J> Type of the sub-hyperplane.

  40.  */
  41. public abstract class AbstractRegion<S extends Space,
  42.                                      P extends Point<S, P>,
  43.                                      H extends Hyperplane<S, P, H, I>,
  44.                                      I extends SubHyperplane<S, P, H, I>,
  45.                                      T extends Space, Q extends Point<T, Q>,
  46.                                      F extends Hyperplane<T, Q, F, J>,
  47.                                      J extends SubHyperplane<T, Q, F, J>>
  48.     implements Region<S, P, H, I> {

  49.     /** Inside/Outside BSP tree. */
  50.     private final BSPTree<S, P, H, I> tree;

  51.     /** Tolerance below which points are considered to belong to hyperplanes. */
  52.     private final double tolerance;

  53.     /** Size of the instance. */
  54.     private double size;

  55.     /** Barycenter. */
  56.     private P barycenter;

  57.     /** Build a region representing the whole space.
  58.      * @param tolerance tolerance below which points are considered identical.
  59.      */
  60.     protected AbstractRegion(final double tolerance) {
  61.         this.tree      = new BSPTree<>(Boolean.TRUE);
  62.         this.tolerance = tolerance;
  63.     }

  64.     /** Build a region from an inside/outside BSP tree.
  65.      * <p>The leaf nodes of the BSP tree <em>must</em> have a
  66.      * {@code Boolean} attribute representing the inside status of
  67.      * the corresponding cell (true for inside cells, false for outside
  68.      * cells). In order to avoid building too many small objects, it is
  69.      * recommended to use the predefined constants
  70.      * {@code Boolean.TRUE} and {@code Boolean.FALSE}. The
  71.      * tree also <em>must</em> have either null internal nodes or
  72.      * internal nodes representing the boundary as specified in the
  73.      * {@link #getTree getTree} method).</p>
  74.      * @param tree inside/outside BSP tree representing the region
  75.      * @param tolerance tolerance below which points are considered identical.
  76.      */
  77.     protected AbstractRegion(final BSPTree<S, P, H, I> tree, final double tolerance) {
  78.         this.tree      = tree;
  79.         this.tolerance = tolerance;
  80.     }

  81.     /** Build a Region from a Boundary REPresentation (B-rep).
  82.      * <p>The boundary is provided as a collection of {@link
  83.      * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
  84.      * interior part of the region on its minus side and the exterior on
  85.      * its plus side.</p>
  86.      * <p>The boundary elements can be in any order, and can form
  87.      * several non-connected sets (like for example polygons with holes
  88.      * or a set of disjoints polyhedrons considered as a whole). In
  89.      * fact, the elements do not even need to be connected together
  90.      * (their topological connections are not used here). However, if the
  91.      * boundary does not really separate an inside open from an outside
  92.      * open (open having here its topological meaning), then subsequent
  93.      * calls to the {@link #checkPoint(Point) checkPoint} method will not be
  94.      * meaningful anymore.</p>
  95.      * <p>If the boundary is empty, the region will represent the whole
  96.      * space.</p>
  97.      * @param boundary collection of boundary elements, as a
  98.      * collection of {@link SubHyperplane SubHyperplane} objects
  99.      * @param tolerance tolerance below which points are considered identical.
  100.      */
  101.     protected AbstractRegion(final Collection<I> boundary, final double tolerance) {

  102.         this.tolerance = tolerance;

  103.         if (boundary.isEmpty()) {

  104.             // the tree represents the whole space
  105.             tree = new BSPTree<>(Boolean.TRUE);

  106.         } else {

  107.             // sort the boundary elements in decreasing size order
  108.             // (we don't want equal size elements to be removed, so
  109.             // we use a trick to fool the TreeSet)
  110.             final TreeSet<I> ordered = new TreeSet<>(new Comparator<I>() {
  111.                 /** {@inheritDoc} */
  112.                 @Override
  113.                 public int compare(final I o1, final I o2) {
  114.                     final double size1 = o1.getSize();
  115.                     final double size2 = o2.getSize();
  116.                     return (size2 < size1) ? -1 : ((o1 == o2) ? 0 : +1);
  117.                 }
  118.             });
  119.             ordered.addAll(boundary);

  120.             // build the tree top-down
  121.             tree = new BSPTree<>();
  122.             insertCuts(tree, ordered);

  123.             // set up the inside/outside flags
  124.             tree.visit(new BSPTreeVisitor<S, P, H, I>() {

  125.                 /** {@inheritDoc} */
  126.                 @Override
  127.                 public Order visitOrder(final BSPTree<S, P, H, I> node) {
  128.                     return Order.PLUS_SUB_MINUS;
  129.                 }

  130.                 /** {@inheritDoc} */
  131.                 @Override
  132.                 public void visitInternalNode(final BSPTree<S, P, H, I> node) {
  133.                 }

  134.                 /** {@inheritDoc} */
  135.                 @Override
  136.                 public void visitLeafNode(final BSPTree<S, P, H, I> node) {
  137.                     if (node.getParent() == null || node == node.getParent().getMinus()) {
  138.                         node.setAttribute(Boolean.TRUE);
  139.                     } else {
  140.                         node.setAttribute(Boolean.FALSE);
  141.                     }
  142.                 }
  143.             });

  144.         }

  145.     }

  146.     /** Build a convex region from an array of bounding hyperplanes.
  147.      * @param hyperplanes array of bounding hyperplanes (if null, an
  148.      * empty region will be built)
  149.      * @param tolerance tolerance below which points are considered identical.
  150.      */
  151.     public AbstractRegion(final H[] hyperplanes, final double tolerance) {
  152.         this.tolerance = tolerance;
  153.         if ((hyperplanes == null) || (hyperplanes.length == 0)) {
  154.             tree = new BSPTree<>(Boolean.FALSE);
  155.         } else {

  156.             // use the first hyperplane to build the right class
  157.             tree = hyperplanes[0].wholeSpace().getTree(false);

  158.             // chop off parts of the space
  159.             BSPTree<S, P, H, I> node = tree;
  160.             node.setAttribute(Boolean.TRUE);
  161.             for (final H hyperplane : hyperplanes) {
  162.                 if (node.insertCut(hyperplane)) {
  163.                     node.setAttribute(null);
  164.                     node.getPlus().setAttribute(Boolean.FALSE);
  165.                     node = node.getMinus();
  166.                     node.setAttribute(Boolean.TRUE);
  167.                 }
  168.             }

  169.         }

  170.     }

  171.     /** {@inheritDoc} */
  172.     @Override
  173.     public abstract AbstractRegion<S, P, H, I, T, Q, F, J> buildNew(BSPTree<S, P, H, I> newTree);

  174.     /** Get the tolerance below which points are considered to belong to hyperplanes.
  175.      * @return tolerance below which points are considered to belong to hyperplanes
  176.      */
  177.     public double getTolerance() {
  178.         return tolerance;
  179.     }

  180.     /** Recursively build a tree by inserting cut sub-hyperplanes.
  181.      * @param node current tree node (it is a leaf node at the beginning
  182.      * of the call)
  183.      * @param boundary collection of edges belonging to the cell defined
  184.      * by the node
  185.      */
  186.     private void insertCuts(final BSPTree<S, P, H, I> node, final Collection<I> boundary) {

  187.         final Iterator<I> iterator = boundary.iterator();

  188.         // build the current level
  189.         H inserted = null;
  190.         while ((inserted == null) && iterator.hasNext()) {
  191.             inserted = iterator.next().getHyperplane();
  192.             if (!node.insertCut(inserted.copySelf())) {
  193.                 inserted = null;
  194.             }
  195.         }

  196.         if (!iterator.hasNext()) {
  197.             return;
  198.         }

  199.         // distribute the remaining edges in the two sub-trees
  200.         final ArrayList<I> plusList  = new ArrayList<>();
  201.         final ArrayList<I> minusList = new ArrayList<>();
  202.         while (iterator.hasNext()) {
  203.             final I other = iterator.next();
  204.             final SubHyperplane.SplitSubHyperplane<S, P, H, I> split = other.split(inserted);
  205.             switch (split.getSide()) {
  206.             case PLUS:
  207.                 plusList.add(other);
  208.                 break;
  209.             case MINUS:
  210.                 minusList.add(other);
  211.                 break;
  212.             case BOTH:
  213.                 plusList.add(split.getPlus());
  214.                 minusList.add(split.getMinus());
  215.                 break;
  216.             default:
  217.                 // ignore the sub-hyperplanes belonging to the cut hyperplane
  218.             }
  219.         }

  220.         // recurse through lower levels
  221.         insertCuts(node.getPlus(),  plusList);
  222.         insertCuts(node.getMinus(), minusList);

  223.     }

  224.     /** {@inheritDoc} */
  225.     @Override
  226.     public AbstractRegion<S, P, H, I, T, Q, F, J> copySelf() {
  227.         return buildNew(tree.copySelf());
  228.     }

  229.     /** {@inheritDoc} */
  230.     @Override
  231.     public boolean isEmpty() {
  232.         return isEmpty(tree);
  233.     }

  234.     /** {@inheritDoc} */
  235.     @Override
  236.     public boolean isEmpty(final BSPTree<S, P, H, I> node) {

  237.         // we use a recursive function rather than the BSPTreeVisitor
  238.         // interface because we can stop visiting the tree as soon as we
  239.         // have found an inside cell

  240.         if (node.getCut() == null) {
  241.             // if we find an inside node, the region is not empty
  242.             return !((Boolean) node.getAttribute());
  243.         }

  244.         // check both sides of the sub-tree
  245.         return isEmpty(node.getMinus()) && isEmpty(node.getPlus());

  246.     }

  247.     /** {@inheritDoc} */
  248.     @Override
  249.     public boolean isFull() {
  250.         return isFull(tree);
  251.     }

  252.     /** {@inheritDoc} */
  253.     @Override
  254.     public boolean isFull(final BSPTree<S, P, H, I> node) {

  255.         // we use a recursive function rather than the BSPTreeVisitor
  256.         // interface because we can stop visiting the tree as soon as we
  257.         // have found an outside cell

  258.         if (node.getCut() == null) {
  259.             // if we find an outside node, the region does not cover full space
  260.             return (Boolean) node.getAttribute();
  261.         }

  262.         // check both sides of the sub-tree
  263.         return isFull(node.getMinus()) && isFull(node.getPlus());

  264.     }

  265.     /** {@inheritDoc} */
  266.     @Override
  267.     public boolean contains(final Region<S, P, H, I> region) {
  268.         return new RegionFactory<S, P, H, I>().difference(region, this).isEmpty();
  269.     }

  270.     /** {@inheritDoc}
  271.      */
  272.     @Override
  273.     public BoundaryProjection<S, P> projectToBoundary(final P point) {
  274.         final BoundaryProjector<S, P, H, I, T, Q, F, J> projector = new BoundaryProjector<>(point);
  275.         getTree(true).visit(projector);
  276.         return projector.getProjection();
  277.     }

  278.     /** {@inheritDoc} */
  279.     @Override
  280.     public Location checkPoint(final P point) {
  281.         return checkPoint(tree, point);
  282.     }

  283.     /** Check a point with respect to the region starting at a given node.
  284.      * @param node root node of the region
  285.      * @param point point to check
  286.      * @return a code representing the point status: either {@link
  287.      * Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE
  288.      * OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY}
  289.      */
  290.     protected Location checkPoint(final BSPTree<S, P, H, I> node, final P point) {
  291.         final BSPTree<S, P, H, I> cell = node.getCell(point, tolerance);
  292.         if (cell.getCut() == null) {
  293.             // the point is in the interior of a cell, just check the attribute
  294.             return ((Boolean) cell.getAttribute()) ? Location.INSIDE : Location.OUTSIDE;
  295.         }

  296.         // the point is on a cut-sub-hyperplane, is it on a boundary ?
  297.         final Location minusCode = checkPoint(cell.getMinus(), point);
  298.         final Location plusCode  = checkPoint(cell.getPlus(),  point);
  299.         return (minusCode == plusCode) ? minusCode : Location.BOUNDARY;

  300.     }

  301.     /** {@inheritDoc} */
  302.     @Override
  303.     public BSPTree<S, P, H, I> getTree(final boolean includeBoundaryAttributes) {
  304.         if (includeBoundaryAttributes && (tree.getCut() != null) && (tree.getAttribute() == null)) {
  305.             // compute the boundary attributes
  306.             tree.visit(new BoundaryBuilder<>());
  307.         }
  308.         return tree;
  309.     }

  310.     /** {@inheritDoc} */
  311.     @Override
  312.     public double getBoundarySize() {
  313.         final BoundarySizeVisitor<S, P, H, I> visitor = new BoundarySizeVisitor<>();
  314.         getTree(true).visit(visitor);
  315.         return visitor.getSize();
  316.     }

  317.     /** {@inheritDoc} */
  318.     @Override
  319.     public double getSize() {
  320.         if (barycenter == null) {
  321.             computeGeometricalProperties();
  322.         }
  323.         return size;
  324.     }

  325.     /** Set the size of the instance.
  326.      * @param size size of the instance
  327.      */
  328.     protected void setSize(final double size) {
  329.         this.size = size;
  330.     }

  331.     /** {@inheritDoc} */
  332.     @Override
  333.     public P getBarycenter() {
  334.         if (barycenter == null) {
  335.             computeGeometricalProperties();
  336.         }
  337.         return barycenter;
  338.     }

  339.     /** Set the barycenter of the instance.
  340.      * @param barycenter barycenter of the instance
  341.      */
  342.     protected void setBarycenter(final P barycenter) {
  343.         this.barycenter = barycenter;
  344.     }

  345.     /** Compute some geometrical properties.
  346.      * <p>The properties to compute are the barycenter and the size.</p>
  347.      */
  348.     protected abstract void computeGeometricalProperties();

  349.     /** {@inheritDoc} */
  350.     @Override
  351.     public I intersection(final I sub) {
  352.         return recurseIntersection(tree, sub);
  353.     }

  354.     /** Recursively compute the parts of a sub-hyperplane that are
  355.      * contained in the region.
  356.      * @param node current BSP tree node
  357.      * @param sub sub-hyperplane traversing the region
  358.      * @return filtered sub-hyperplane
  359.      */
  360.     private I recurseIntersection(final BSPTree<S, P, H, I> node, final I sub) {

  361.         if (node.getCut() == null) {
  362.             return (Boolean) node.getAttribute() ? sub.copySelf() : null;
  363.         }

  364.         final H hyperplane = node.getCut().getHyperplane();
  365.         final SubHyperplane.SplitSubHyperplane<S, P, H, I> split = sub.split(hyperplane);
  366.         if (split.getPlus() != null) {
  367.             if (split.getMinus() != null) {
  368.                 // both sides
  369.                 final I plus  = recurseIntersection(node.getPlus(),  split.getPlus());
  370.                 final I minus = recurseIntersection(node.getMinus(), split.getMinus());
  371.                 if (plus == null) {
  372.                     return minus;
  373.                 } else if (minus == null) {
  374.                     return plus;
  375.                 } else {
  376.                     return plus.reunite(minus);
  377.                 }
  378.             } else {
  379.                 // only on plus side
  380.                 return recurseIntersection(node.getPlus(), sub);
  381.             }
  382.         } else if (split.getMinus() != null) {
  383.             // only on minus side
  384.             return recurseIntersection(node.getMinus(), sub);
  385.         } else {
  386.             // on hyperplane
  387.             return recurseIntersection(node.getPlus(),
  388.                                        recurseIntersection(node.getMinus(), sub));
  389.         }

  390.     }

  391.     /** Transform a region.
  392.      * <p>Applying a transform to a region consist in applying the
  393.      * transform to all the hyperplanes of the underlying BSP tree and
  394.      * of the boundary (and also to the sub-hyperplanes embedded in
  395.      * these hyperplanes) and to the barycenter. The instance is not
  396.      * modified, a new instance is built.</p>
  397.      * @param transform transform to apply
  398.      * @return a new region, resulting from the application of the
  399.      * transform to the instance
  400.      */
  401.     public AbstractRegion<S, P, H, I, T, Q, F, J> applyTransform(final Transform<S, P, H, I, T, Q, F, J> transform) {

  402.         // transform the tree, except for boundary attribute splitters
  403.         final Map<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> map = new HashMap<>();
  404.         final BSPTree<S, P, H, I> transformedTree = recurseTransform(getTree(false), transform, map);

  405.         // set up the boundary attributes splitters
  406.         for (final Map.Entry<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> entry : map.entrySet()) {
  407.             if (entry.getKey().getCut() != null) {
  408.                 @SuppressWarnings("unchecked")
  409.                 BoundaryAttribute<S, P, H, I> original = (BoundaryAttribute<S, P, H, I>) entry.getKey().getAttribute();
  410.                 if (original != null) {
  411.                     @SuppressWarnings("unchecked")
  412.                     BoundaryAttribute<S, P, H, I> transformed = (BoundaryAttribute<S, P, H, I>) entry.getValue().getAttribute();
  413.                     for (final BSPTree<S, P, H, I> splitter : original.getSplitters()) {
  414.                         transformed.getSplitters().add(map.get(splitter));
  415.                     }
  416.                 }
  417.             }
  418.         }

  419.         return buildNew(transformedTree);

  420.     }

  421.     /** Recursively transform an inside/outside BSP-tree.
  422.      * @param node current BSP tree node
  423.      * @param transform transform to apply
  424.      * @param map transformed nodes map
  425.      * @return a new tree
  426.      */
  427.     @SuppressWarnings("unchecked")
  428.     private BSPTree<S, P, H, I> recurseTransform(final BSPTree<S, P, H, I> node, final Transform<S, P, H, I, T, Q, F, J> transform,
  429.                                         final Map<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> map) {

  430.         final BSPTree<S, P, H, I> transformedNode;
  431.         if (node.getCut() == null) {
  432.             transformedNode = new BSPTree<>(node.getAttribute());
  433.         } else {

  434.             final I  sub = node.getCut();
  435.             final I tSub = ((AbstractSubHyperplane<S, P, H, I, T, Q, F, J>) sub).applyTransform(transform);
  436.             BoundaryAttribute<S, P, H, I> attribute = (BoundaryAttribute<S, P, H, I>) node.getAttribute();
  437.             if (attribute != null) {
  438.                 final I tPO = (attribute.getPlusOutside() == null) ?
  439.                     null : ((AbstractSubHyperplane<S, P, H, I, T, Q, F, J>) attribute.getPlusOutside()).applyTransform(transform);
  440.                 final I tPI = (attribute.getPlusInside()  == null) ?
  441.                     null  : ((AbstractSubHyperplane<S, P, H, I, T, Q, F, J>) attribute.getPlusInside()).applyTransform(transform);
  442.                 // we start with an empty list of splitters, it will be filled in out of recursion
  443.                 attribute = new BoundaryAttribute<>(tPO, tPI, new NodesSet<>());
  444.             }

  445.             transformedNode = new BSPTree<>(tSub,
  446.                                             recurseTransform(node.getPlus(),  transform, map),
  447.                                             recurseTransform(node.getMinus(), transform, map),
  448.                                             attribute);
  449.         }

  450.         map.put(node, transformedNode);
  451.         return transformedNode;

  452.     }

  453. }