RegionFactory.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.HashMap;
  23. import java.util.Map;

  24. import org.hipparchus.exception.MathIllegalArgumentException;
  25. import org.hipparchus.geometry.LocalizedGeometryFormats;
  26. import org.hipparchus.geometry.Point;
  27. import org.hipparchus.geometry.Space;
  28. import org.hipparchus.geometry.partitioning.BSPTree.VanishingCutHandler;
  29. import org.hipparchus.geometry.partitioning.Region.Location;
  30. import org.hipparchus.geometry.partitioning.SubHyperplane.SplitSubHyperplane;

  31. /** This class is a factory for {@link Region}.

  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.  */
  37. public class RegionFactory<S extends Space, P extends Point<S, P>, H extends Hyperplane<S, P, H, I>, I extends SubHyperplane<S, P, H, I>> {

  38.     /** Visitor removing internal nodes attributes. */
  39.     private final NodesCleaner nodeCleaner;

  40.     /** Simple constructor.
  41.      */
  42.     public RegionFactory() {
  43.         nodeCleaner = new NodesCleaner();
  44.     }

  45.     /** Build a convex region from a collection of bounding hyperplanes.
  46.      * @param hyperplanes collection of bounding hyperplanes
  47.      * @return a new convex region, or null if the collection is empty
  48.      */
  49.     @SafeVarargs
  50.     public final Region<S, P, H, I> buildConvex(final H... hyperplanes) {
  51.         if ((hyperplanes == null) || (hyperplanes.length == 0)) {
  52.             return null;
  53.         }

  54.         // use the first hyperplane to build the right class
  55.         final Region<S, P, H, I> region = hyperplanes[0].wholeSpace();

  56.         // chop off parts of the space
  57.         BSPTree<S, P, H, I> node = region.getTree(false);
  58.         node.setAttribute(Boolean.TRUE);
  59.         for (final H hyperplane : hyperplanes) {
  60.             if (node.insertCut(hyperplane)) {
  61.                 node.setAttribute(null);
  62.                 node.getPlus().setAttribute(Boolean.FALSE);
  63.                 node = node.getMinus();
  64.                 node.setAttribute(Boolean.TRUE);
  65.             } else {
  66.                 // the hyperplane could not be inserted in the current leaf node
  67.                 // either it is completely outside (which means the input hyperplanes
  68.                 // are wrong), or it is parallel to a previous hyperplane
  69.                 I s = hyperplane.wholeHyperplane();
  70.                 for (BSPTree<S, P, H, I> tree = node; tree.getParent() != null && s != null; tree = tree.getParent()) {
  71.                     final H         other = tree.getParent().getCut().getHyperplane();
  72.                     final SplitSubHyperplane<S, P, H, I> split = s.split(other);
  73.                     switch (split.getSide()) {
  74.                         case HYPER :
  75.                             // the hyperplane is parallel to a previous hyperplane
  76.                             if (!hyperplane.sameOrientationAs(other)) {
  77.                                 // this hyperplane is opposite to the other one,
  78.                                 // the region is thinner than the tolerance, we consider it empty
  79.                                 return getComplement(hyperplanes[0].wholeSpace());
  80.                             }
  81.                             // the hyperplane is an extension of an already known hyperplane, we just ignore it
  82.                             break;
  83.                         case PLUS :
  84.                         // the hyperplane is outside of the current convex zone,
  85.                         // the input hyperplanes are inconsistent
  86.                         throw new MathIllegalArgumentException(LocalizedGeometryFormats.NOT_CONVEX_HYPERPLANES);
  87.                         default :
  88.                             s = split.getMinus();
  89.                     }
  90.                 }
  91.             }
  92.         }

  93.         return region;

  94.     }

  95.     /** Compute the union of two regions.
  96.      * @param region1 first region (will be unusable after the operation as
  97.      * parts of it will be reused in the new region)
  98.      * @param region2 second region (will be unusable after the operation as
  99.      * parts of it will be reused in the new region)
  100.      * @return a new region, result of {@code region1 union region2}
  101.      */
  102.     public Region<S, P, H, I> union(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
  103.         final BSPTree<S, P, H, I> tree =
  104.             region1.getTree(false).merge(region2.getTree(false), new UnionMerger());
  105.         tree.visit(nodeCleaner);
  106.         return region1.buildNew(tree);
  107.     }

  108.     /** Compute the intersection of two regions.
  109.      * @param region1 first region (will be unusable after the operation as
  110.      * parts of it will be reused in the new region)
  111.      * @param region2 second region (will be unusable after the operation as
  112.      * parts of it will be reused in the new region)
  113.      * @return a new region, result of {@code region1 intersection region2}
  114.      */
  115.     public Region<S, P, H, I> intersection(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
  116.         final BSPTree<S, P, H, I> tree =
  117.             region1.getTree(false).merge(region2.getTree(false), new IntersectionMerger(region1, region2));
  118.         tree.visit(nodeCleaner);
  119.         return region1.buildNew(tree);
  120.     }

  121.     /** Compute the symmetric difference (exclusive or) of two regions.
  122.      * @param region1 first region (will be unusable after the operation as
  123.      * parts of it will be reused in the new region)
  124.      * @param region2 second region (will be unusable after the operation as
  125.      * parts of it will be reused in the new region)
  126.      * @return a new region, result of {@code region1 xor region2}
  127.      */
  128.     public Region<S, P, H, I> xor(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
  129.         final BSPTree<S, P, H, I> tree =
  130.             region1.getTree(false).merge(region2.getTree(false), new XorMerger());
  131.         tree.visit(nodeCleaner);
  132.         return region1.buildNew(tree);
  133.     }

  134.     /** Compute the difference of two regions.
  135.      * @param region1 first region (will be unusable after the operation as
  136.      * parts of it will be reused in the new region)
  137.      * @param region2 second region (will be unusable after the operation as
  138.      * parts of it will be reused in the new region)
  139.      * @return a new region, result of {@code region1 minus region2}
  140.      */
  141.     public Region<S, P, H, I> difference(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
  142.         final BSPTree<S, P, H, I> tree =
  143.             region1.getTree(false).merge(region2.getTree(false), new DifferenceMerger(region1, region2));
  144.         tree.visit(nodeCleaner);
  145.         return region1.buildNew(tree);
  146.     }

  147.      /** Get the complement of the region (exchanged interior/exterior).
  148.      * @param region region to complement, it will not be modified, a new
  149.      * region independent region will be built
  150.      * @return a new region, complement of the specified one
  151.      */
  152.     public Region<S, P, H, I> getComplement(final Region<S, P, H, I> region) {
  153.         return region.buildNew(recurseComplement(region.getTree(false)));
  154.     }

  155.     /** Recursively build the complement of a BSP tree.
  156.      * @param node current node of the original tree
  157.      * @return new tree, complement of the node
  158.      */
  159.     private BSPTree<S, P, H, I> recurseComplement(final BSPTree<S, P, H, I> node) {

  160.         // transform the tree, except for boundary attribute splitters
  161.         final Map<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> map = new HashMap<>();
  162.         final BSPTree<S, P, H, I> transformedTree = recurseComplement(node, map);

  163.         // set up the boundary attributes splitters
  164.         for (final Map.Entry<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> entry : map.entrySet()) {
  165.             if (entry.getKey().getCut() != null) {
  166.                 @SuppressWarnings("unchecked")
  167.                 BoundaryAttribute<S, P, H, I> original = (BoundaryAttribute<S, P, H, I>) entry.getKey().getAttribute();
  168.                 if (original != null) {
  169.                     @SuppressWarnings("unchecked")
  170.                     BoundaryAttribute<S, P, H, I> transformed = (BoundaryAttribute<S, P, H, I>) entry.getValue().getAttribute();
  171.                     for (final BSPTree<S, P, H, I> splitter : original.getSplitters()) {
  172.                         transformed.getSplitters().add(map.get(splitter));
  173.                     }
  174.                 }
  175.             }
  176.         }

  177.         return transformedTree;

  178.     }

  179.     /** Recursively build the complement of a BSP tree.
  180.      * @param node current node of the original tree
  181.      * @param map transformed nodes map
  182.      * @return new tree, complement of the node
  183.      */
  184.     private BSPTree<S, P, H, I> recurseComplement(final BSPTree<S, P, H, I> node,
  185.                                                   final Map<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> map) {

  186.         final BSPTree<S, P, H, I> transformedNode;
  187.         if (node.getCut() == null) {
  188.             transformedNode = new BSPTree<>(((Boolean) node.getAttribute()) ? Boolean.FALSE : Boolean.TRUE);
  189.         } else {

  190.             @SuppressWarnings("unchecked")
  191.             BoundaryAttribute<S, P, H, I> attribute = (BoundaryAttribute<S, P, H, I>) node.getAttribute();
  192.             if (attribute != null) {
  193.                 final I plusOutside = (attribute.getPlusInside() == null) ? null : attribute.getPlusInside().copySelf();
  194.                 final I plusInside  = (attribute.getPlusOutside() == null) ? null : attribute.getPlusOutside().copySelf();
  195.                 // we start with an empty list of splitters, it will be filled in out of recursion
  196.                 attribute = new BoundaryAttribute<>(plusOutside, plusInside, new NodesSet<>());
  197.             }

  198.             transformedNode = new BSPTree<>(node.getCut().copySelf(),
  199.                                             recurseComplement(node.getPlus(),  map),
  200.                                             recurseComplement(node.getMinus(), map),
  201.                                             attribute);
  202.         }

  203.         map.put(node, transformedNode);
  204.         return transformedNode;

  205.     }

  206.     /** BSP tree leaf merger computing intersection of two regions. */
  207.     private abstract class FixingMerger implements BSPTree.LeafMerger<S, P, H, I>, VanishingCutHandler<S, P, H, I> {

  208.         /** First region. */
  209.         private final Region<S, P, H, I> region1;

  210.         /** Second region. */
  211.         private final Region<S, P, H, I> region2;

  212.         /** Simple constructor.
  213.          * @param region1 first region
  214.          * @param region2 second region
  215.          */
  216.         protected FixingMerger(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
  217.             this.region1 = region1.copySelf();
  218.             this.region2 = region2.copySelf();
  219.         }

  220.         /** {@inheritDoc} */
  221.         @Override
  222.         public BSPTree<S, P, H, I> fixNode(final BSPTree<S, P, H, I> node) {
  223.             // get a representative point in the degenerate cell
  224.             final BSPTree.InteriorPoint<S, P> interior = node.getInteriorPoint(node.getParent().getCut().getHyperplane().arbitraryPoint());
  225.             return new BSPTree<>(shouldBeInside(region1.checkPoint(interior.getPoint()), region2.checkPoint(interior.getPoint())));
  226.         }

  227.         /**
  228.          * Check if node should be an inside or outside node.
  229.          * @param location1 location of representative point in region1
  230.          * @param location2 location of representative point in region2
  231.          * @return true if node should be an inside node
  232.          */
  233.         protected abstract boolean shouldBeInside(Location location1, Location location2);

  234.     }

  235.     /** BSP tree leaf merger computing union of two regions. */
  236.     private class UnionMerger implements BSPTree.LeafMerger<S, P, H, I> {
  237.         /** {@inheritDoc} */
  238.         @Override
  239.         public BSPTree<S, P, H, I> merge(final BSPTree<S, P, H, I> leaf, final BSPTree<S, P, H, I> tree,
  240.                                          final BSPTree<S, P, H, I> parentTree,
  241.                                          final boolean isPlusChild, final boolean leafFromInstance) {
  242.             if ((Boolean) leaf.getAttribute()) {
  243.                 // the leaf node represents an inside cell
  244.                 leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
  245.                 return leaf;
  246.             }
  247.             // the leaf node represents an outside cell
  248.             tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false));
  249.             return tree;
  250.         }
  251.     }

  252.     /** BSP tree leaf merger computing intersection of two regions. */
  253.     private class IntersectionMerger extends FixingMerger {

  254.         /** Simple constructor.
  255.          * @param region1 first region
  256.          * @param region2 second region
  257.          */
  258.         IntersectionMerger(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
  259.             super(region1, region2);
  260.         }

  261.         /** {@inheritDoc} */
  262.         @Override
  263.         public BSPTree<S, P, H, I> merge(final BSPTree<S, P, H, I> leaf, final BSPTree<S, P, H, I> tree,
  264.                                          final BSPTree<S, P, H, I> parentTree,
  265.                                          final boolean isPlusChild, final boolean leafFromInstance) {
  266.             if ((Boolean) leaf.getAttribute()) {
  267.                 // the leaf node represents an inside cell
  268.                 tree.insertInTree(parentTree, isPlusChild, this);
  269.                 return tree;
  270.             }
  271.             // the leaf node represents an outside cell
  272.             leaf.insertInTree(parentTree, isPlusChild, this);
  273.             return leaf;
  274.         }

  275.         /** {@inheritDoc} */
  276.         @Override
  277.         protected boolean shouldBeInside(final Location location1, final Location location2)
  278.         {
  279.             return !(location1.equals(Location.OUTSIDE) || location2.equals(Location.OUTSIDE));
  280.         }

  281.     }

  282.     /** BSP tree leaf merger computing symmetric difference (exclusive or) of two regions. */
  283.     private class XorMerger implements BSPTree.LeafMerger<S, P, H, I> {
  284.         /** {@inheritDoc} */
  285.         @Override
  286.         public BSPTree<S, P, H, I> merge(final BSPTree<S, P, H, I> leaf, final BSPTree<S, P, H, I> tree,
  287.                                          final BSPTree<S, P, H, I> parentTree, final boolean isPlusChild,
  288.                                          final boolean leafFromInstance) {
  289.             BSPTree<S, P, H, I> t = tree;
  290.             if ((Boolean) leaf.getAttribute()) {
  291.                 // the leaf node represents an inside cell
  292.                 t = recurseComplement(t);
  293.             }
  294.             t.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
  295.             return t;
  296.         }
  297.     }

  298.     /** BSP tree leaf merger computing difference of two regions. */
  299.     private class DifferenceMerger extends FixingMerger {

  300.         /** Simple constructor.
  301.          * @param region1 region to subtract from
  302.          * @param region2 region to subtract
  303.          */
  304.         DifferenceMerger(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
  305.             super(region1, region2);
  306.         }

  307.         /** {@inheritDoc} */
  308.         @Override
  309.         public BSPTree<S, P, H, I> merge(final BSPTree<S, P, H, I> leaf, final BSPTree<S, P, H, I> tree,
  310.                                          final BSPTree<S, P, H, I> parentTree, final boolean isPlusChild,
  311.                                          final boolean leafFromInstance) {
  312.             if ((Boolean) leaf.getAttribute()) {
  313.                 // the leaf node represents an inside cell
  314.                 final BSPTree<S, P, H, I> argTree =
  315.                     recurseComplement(leafFromInstance ? tree : leaf);
  316.                 argTree.insertInTree(parentTree, isPlusChild, this);
  317.                 return argTree;
  318.             }
  319.             // the leaf node represents an outside cell
  320.             final BSPTree<S, P, H, I> instanceTree =
  321.                 leafFromInstance ? leaf : tree;
  322.             instanceTree.insertInTree(parentTree, isPlusChild, this);
  323.             return instanceTree;
  324.         }

  325.         /** {@inheritDoc} */
  326.         @Override
  327.         protected boolean shouldBeInside(final Location location1, final Location location2) {
  328.             return location1 == Location.INSIDE && location2 == Location.OUTSIDE;
  329.         }

  330.     }

  331.     /** Visitor removing internal nodes attributes. */
  332.     private class NodesCleaner implements  BSPTreeVisitor<S, P, H, I> {

  333.         /** {@inheritDoc} */
  334.         @Override
  335.         public Order visitOrder(final BSPTree<S, P, H, I> node) {
  336.             return Order.PLUS_SUB_MINUS;
  337.         }

  338.         /** {@inheritDoc} */
  339.         @Override
  340.         public void visitInternalNode(final BSPTree<S, P, H, I> node) {
  341.             node.setAttribute(null);
  342.         }

  343.         /** {@inheritDoc} */
  344.         @Override
  345.         public void visitLeafNode(final BSPTree<S, P, H, I> node) {
  346.         }

  347.     }

  348.     /** Handler replacing nodes with vanishing cuts with leaf nodes. */
  349.     private class VanishingToLeaf implements VanishingCutHandler<S, P, H, I> {

  350.         /** Inside/outside indicator to use for ambiguous nodes. */
  351.         private final boolean inside;

  352.         /** Simple constructor.
  353.          * @param inside inside/outside indicator to use for ambiguous nodes
  354.          */
  355.         VanishingToLeaf(final boolean inside) {
  356.             this.inside = inside;
  357.         }

  358.         /** {@inheritDoc} */
  359.         @Override
  360.         public BSPTree<S, P, H, I> fixNode(final BSPTree<S, P, H, I> node) {
  361.             if (node.getPlus().getAttribute().equals(node.getMinus().getAttribute())) {
  362.                 // no ambiguity
  363.                 return new BSPTree<>(node.getPlus().getAttribute());
  364.             } else {
  365.                 // ambiguous node
  366.                 return new BSPTree<>(inside);
  367.             }
  368.         }

  369.     }

  370. }