View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.geometry.partitioning;
23  
24  import java.util.HashMap;
25  import java.util.Map;
26  
27  import org.hipparchus.exception.MathIllegalArgumentException;
28  import org.hipparchus.geometry.LocalizedGeometryFormats;
29  import org.hipparchus.geometry.Point;
30  import org.hipparchus.geometry.Space;
31  import org.hipparchus.geometry.partitioning.BSPTree.VanishingCutHandler;
32  import org.hipparchus.geometry.partitioning.Region.Location;
33  import org.hipparchus.geometry.partitioning.SubHyperplane.SplitSubHyperplane;
34  
35  /** This class is a factory for {@link Region}.
36  
37   * @param <S> Type of the space.
38   * @param <P> Type of the points in space.
39   * @param <H> Type of the hyperplane.
40   * @param <I> Type of the sub-hyperplane.
41  
42   */
43  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>> {
44  
45      /** Visitor removing internal nodes attributes. */
46      private final NodesCleaner nodeCleaner;
47  
48      /** Simple constructor.
49       */
50      public RegionFactory() {
51          nodeCleaner = new NodesCleaner();
52      }
53  
54      /** Build a convex region from a collection of bounding hyperplanes.
55       * @param hyperplanes collection of bounding hyperplanes
56       * @return a new convex region, or null if the collection is empty
57       */
58      @SafeVarargs
59      public final Region<S, P, H, I> buildConvex(final H... hyperplanes) {
60          if ((hyperplanes == null) || (hyperplanes.length == 0)) {
61              return null;
62          }
63  
64          // use the first hyperplane to build the right class
65          final Region<S, P, H, I> region = hyperplanes[0].wholeSpace();
66  
67          // chop off parts of the space
68          BSPTree<S, P, H, I> node = region.getTree(false);
69          node.setAttribute(Boolean.TRUE);
70          for (final H hyperplane : hyperplanes) {
71              if (node.insertCut(hyperplane)) {
72                  node.setAttribute(null);
73                  node.getPlus().setAttribute(Boolean.FALSE);
74                  node = node.getMinus();
75                  node.setAttribute(Boolean.TRUE);
76              } else {
77                  // the hyperplane could not be inserted in the current leaf node
78                  // either it is completely outside (which means the input hyperplanes
79                  // are wrong), or it is parallel to a previous hyperplane
80                  I s = hyperplane.wholeHyperplane();
81                  for (BSPTree<S, P, H, I> tree = node; tree.getParent() != null && s != null; tree = tree.getParent()) {
82                      final H         other = tree.getParent().getCut().getHyperplane();
83                      final SplitSubHyperplane<S, P, H, I> split = s.split(other);
84                      switch (split.getSide()) {
85                          case HYPER :
86                              // the hyperplane is parallel to a previous hyperplane
87                              if (!hyperplane.sameOrientationAs(other)) {
88                                  // this hyperplane is opposite to the other one,
89                                  // the region is thinner than the tolerance, we consider it empty
90                                  return getComplement(hyperplanes[0].wholeSpace());
91                              }
92                              // the hyperplane is an extension of an already known hyperplane, we just ignore it
93                              break;
94                          case PLUS :
95                          // the hyperplane is outside of the current convex zone,
96                          // the input hyperplanes are inconsistent
97                          throw new MathIllegalArgumentException(LocalizedGeometryFormats.NOT_CONVEX_HYPERPLANES);
98                          default :
99                              s = split.getMinus();
100                     }
101                 }
102             }
103         }
104 
105         return region;
106 
107     }
108 
109     /** Compute the union of two regions.
110      * @param region1 first region (will be unusable after the operation as
111      * parts of it will be reused in the new region)
112      * @param region2 second region (will be unusable after the operation as
113      * parts of it will be reused in the new region)
114      * @return a new region, result of {@code region1 union region2}
115      */
116     public Region<S, P, H, I> union(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
117         final BSPTree<S, P, H, I> tree =
118             region1.getTree(false).merge(region2.getTree(false), new UnionMerger());
119         tree.visit(nodeCleaner);
120         return region1.buildNew(tree);
121     }
122 
123     /** Compute the intersection of two regions.
124      * @param region1 first region (will be unusable after the operation as
125      * parts of it will be reused in the new region)
126      * @param region2 second region (will be unusable after the operation as
127      * parts of it will be reused in the new region)
128      * @return a new region, result of {@code region1 intersection region2}
129      */
130     public Region<S, P, H, I> intersection(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
131         final BSPTree<S, P, H, I> tree =
132             region1.getTree(false).merge(region2.getTree(false), new IntersectionMerger(region1, region2));
133         tree.visit(nodeCleaner);
134         return region1.buildNew(tree);
135     }
136 
137     /** Compute the symmetric difference (exclusive or) of two regions.
138      * @param region1 first region (will be unusable after the operation as
139      * parts of it will be reused in the new region)
140      * @param region2 second region (will be unusable after the operation as
141      * parts of it will be reused in the new region)
142      * @return a new region, result of {@code region1 xor region2}
143      */
144     public Region<S, P, H, I> xor(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
145         final BSPTree<S, P, H, I> tree =
146             region1.getTree(false).merge(region2.getTree(false), new XorMerger());
147         tree.visit(nodeCleaner);
148         return region1.buildNew(tree);
149     }
150 
151     /** Compute the difference of two regions.
152      * @param region1 first region (will be unusable after the operation as
153      * parts of it will be reused in the new region)
154      * @param region2 second region (will be unusable after the operation as
155      * parts of it will be reused in the new region)
156      * @return a new region, result of {@code region1 minus region2}
157      */
158     public Region<S, P, H, I> difference(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
159         final BSPTree<S, P, H, I> tree =
160             region1.getTree(false).merge(region2.getTree(false), new DifferenceMerger(region1, region2));
161         tree.visit(nodeCleaner);
162         return region1.buildNew(tree);
163     }
164 
165      /** Get the complement of the region (exchanged interior/exterior).
166      * @param region region to complement, it will not be modified, a new
167      * region independent region will be built
168      * @return a new region, complement of the specified one
169      */
170     public Region<S, P, H, I> getComplement(final Region<S, P, H, I> region) {
171         return region.buildNew(recurseComplement(region.getTree(false)));
172     }
173 
174     /** Recursively build the complement of a BSP tree.
175      * @param node current node of the original tree
176      * @return new tree, complement of the node
177      */
178     private BSPTree<S, P, H, I> recurseComplement(final BSPTree<S, P, H, I> node) {
179 
180         // transform the tree, except for boundary attribute splitters
181         final Map<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> map = new HashMap<>();
182         final BSPTree<S, P, H, I> transformedTree = recurseComplement(node, map);
183 
184         // set up the boundary attributes splitters
185         for (final Map.Entry<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> entry : map.entrySet()) {
186             if (entry.getKey().getCut() != null) {
187                 @SuppressWarnings("unchecked")
188                 BoundaryAttribute<S, P, H, I> original = (BoundaryAttribute<S, P, H, I>) entry.getKey().getAttribute();
189                 if (original != null) {
190                     @SuppressWarnings("unchecked")
191                     BoundaryAttribute<S, P, H, I> transformed = (BoundaryAttribute<S, P, H, I>) entry.getValue().getAttribute();
192                     for (final BSPTree<S, P, H, I> splitter : original.getSplitters()) {
193                         transformed.getSplitters().add(map.get(splitter));
194                     }
195                 }
196             }
197         }
198 
199         return transformedTree;
200 
201     }
202 
203     /** Recursively build the complement of a BSP tree.
204      * @param node current node of the original tree
205      * @param map transformed nodes map
206      * @return new tree, complement of the node
207      */
208     private BSPTree<S, P, H, I> recurseComplement(final BSPTree<S, P, H, I> node,
209                                                   final Map<BSPTree<S, P, H, I>, BSPTree<S, P, H, I>> map) {
210 
211         final BSPTree<S, P, H, I> transformedNode;
212         if (node.getCut() == null) {
213             transformedNode = new BSPTree<>(((Boolean) node.getAttribute()) ? Boolean.FALSE : Boolean.TRUE);
214         } else {
215 
216             @SuppressWarnings("unchecked")
217             BoundaryAttribute<S, P, H, I> attribute = (BoundaryAttribute<S, P, H, I>) node.getAttribute();
218             if (attribute != null) {
219                 final I plusOutside = (attribute.getPlusInside() == null) ? null : attribute.getPlusInside().copySelf();
220                 final I plusInside  = (attribute.getPlusOutside() == null) ? null : attribute.getPlusOutside().copySelf();
221                 // we start with an empty list of splitters, it will be filled in out of recursion
222                 attribute = new BoundaryAttribute<>(plusOutside, plusInside, new NodesSet<>());
223             }
224 
225             transformedNode = new BSPTree<>(node.getCut().copySelf(),
226                                             recurseComplement(node.getPlus(),  map),
227                                             recurseComplement(node.getMinus(), map),
228                                             attribute);
229         }
230 
231         map.put(node, transformedNode);
232         return transformedNode;
233 
234     }
235 
236     /** BSP tree leaf merger computing intersection of two regions. */
237     private abstract class FixingMerger implements BSPTree.LeafMerger<S, P, H, I>, VanishingCutHandler<S, P, H, I> {
238 
239         /** First region. */
240         private final Region<S, P, H, I> region1;
241 
242         /** Second region. */
243         private final Region<S, P, H, I> region2;
244 
245         /** Simple constructor.
246          * @param region1 first region
247          * @param region2 second region
248          */
249         protected FixingMerger(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
250             this.region1 = region1.copySelf();
251             this.region2 = region2.copySelf();
252         }
253 
254         /** {@inheritDoc} */
255         @Override
256         public BSPTree<S, P, H, I> fixNode(final BSPTree<S, P, H, I> node) {
257             // get a representative point in the degenerate cell
258             final BSPTree.InteriorPoint<S, P> interior = node.getInteriorPoint(node.getParent().getCut().getHyperplane().arbitraryPoint());
259             return new BSPTree<>(shouldBeInside(region1.checkPoint(interior.getPoint()), region2.checkPoint(interior.getPoint())));
260         }
261 
262         /**
263          * Check if node should be an inside or outside node.
264          * @param location1 location of representative point in region1
265          * @param location2 location of representative point in region2
266          * @return true if node should be an inside node
267          */
268         protected abstract boolean shouldBeInside(Location location1, Location location2);
269 
270     }
271 
272     /** BSP tree leaf merger computing union of two regions. */
273     private class UnionMerger implements BSPTree.LeafMerger<S, P, H, I> {
274         /** {@inheritDoc} */
275         @Override
276         public BSPTree<S, P, H, I> merge(final BSPTree<S, P, H, I> leaf, final BSPTree<S, P, H, I> tree,
277                                          final BSPTree<S, P, H, I> parentTree,
278                                          final boolean isPlusChild, final boolean leafFromInstance) {
279             if ((Boolean) leaf.getAttribute()) {
280                 // the leaf node represents an inside cell
281                 leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
282                 return leaf;
283             }
284             // the leaf node represents an outside cell
285             tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false));
286             return tree;
287         }
288     }
289 
290     /** BSP tree leaf merger computing intersection of two regions. */
291     private class IntersectionMerger extends FixingMerger {
292 
293         /** Simple constructor.
294          * @param region1 first region
295          * @param region2 second region
296          */
297         IntersectionMerger(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
298             super(region1, region2);
299         }
300 
301         /** {@inheritDoc} */
302         @Override
303         public BSPTree<S, P, H, I> merge(final BSPTree<S, P, H, I> leaf, final BSPTree<S, P, H, I> tree,
304                                          final BSPTree<S, P, H, I> parentTree,
305                                          final boolean isPlusChild, final boolean leafFromInstance) {
306             if ((Boolean) leaf.getAttribute()) {
307                 // the leaf node represents an inside cell
308                 tree.insertInTree(parentTree, isPlusChild, this);
309                 return tree;
310             }
311             // the leaf node represents an outside cell
312             leaf.insertInTree(parentTree, isPlusChild, this);
313             return leaf;
314         }
315 
316         /** {@inheritDoc} */
317         @Override
318         protected boolean shouldBeInside(final Location location1, final Location location2)
319         {
320             return !(location1.equals(Location.OUTSIDE) || location2.equals(Location.OUTSIDE));
321         }
322 
323     }
324 
325     /** BSP tree leaf merger computing symmetric difference (exclusive or) of two regions. */
326     private class XorMerger implements BSPTree.LeafMerger<S, P, H, I> {
327         /** {@inheritDoc} */
328         @Override
329         public BSPTree<S, P, H, I> merge(final BSPTree<S, P, H, I> leaf, final BSPTree<S, P, H, I> tree,
330                                          final BSPTree<S, P, H, I> parentTree, final boolean isPlusChild,
331                                          final boolean leafFromInstance) {
332             BSPTree<S, P, H, I> t = tree;
333             if ((Boolean) leaf.getAttribute()) {
334                 // the leaf node represents an inside cell
335                 t = recurseComplement(t);
336             }
337             t.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
338             return t;
339         }
340     }
341 
342     /** BSP tree leaf merger computing difference of two regions. */
343     private class DifferenceMerger extends FixingMerger {
344 
345         /** Simple constructor.
346          * @param region1 region to subtract from
347          * @param region2 region to subtract
348          */
349         DifferenceMerger(final Region<S, P, H, I> region1, final Region<S, P, H, I> region2) {
350             super(region1, region2);
351         }
352 
353         /** {@inheritDoc} */
354         @Override
355         public BSPTree<S, P, H, I> merge(final BSPTree<S, P, H, I> leaf, final BSPTree<S, P, H, I> tree,
356                                          final BSPTree<S, P, H, I> parentTree, final boolean isPlusChild,
357                                          final boolean leafFromInstance) {
358             if ((Boolean) leaf.getAttribute()) {
359                 // the leaf node represents an inside cell
360                 final BSPTree<S, P, H, I> argTree =
361                     recurseComplement(leafFromInstance ? tree : leaf);
362                 argTree.insertInTree(parentTree, isPlusChild, this);
363                 return argTree;
364             }
365             // the leaf node represents an outside cell
366             final BSPTree<S, P, H, I> instanceTree =
367                 leafFromInstance ? leaf : tree;
368             instanceTree.insertInTree(parentTree, isPlusChild, this);
369             return instanceTree;
370         }
371 
372         /** {@inheritDoc} */
373         @Override
374         protected boolean shouldBeInside(final Location location1, final Location location2) {
375             return location1 == Location.INSIDE && location2 == Location.OUTSIDE;
376         }
377 
378     }
379 
380     /** Visitor removing internal nodes attributes. */
381     private class NodesCleaner implements  BSPTreeVisitor<S, P, H, I> {
382 
383         /** {@inheritDoc} */
384         @Override
385         public Order visitOrder(final BSPTree<S, P, H, I> node) {
386             return Order.PLUS_SUB_MINUS;
387         }
388 
389         /** {@inheritDoc} */
390         @Override
391         public void visitInternalNode(final BSPTree<S, P, H, I> node) {
392             node.setAttribute(null);
393         }
394 
395         /** {@inheritDoc} */
396         @Override
397         public void visitLeafNode(final BSPTree<S, P, H, I> node) {
398         }
399 
400     }
401 
402     /** Handler replacing nodes with vanishing cuts with leaf nodes. */
403     private class VanishingToLeaf implements VanishingCutHandler<S, P, H, I> {
404 
405         /** Inside/outside indicator to use for ambiguous nodes. */
406         private final boolean inside;
407 
408         /** Simple constructor.
409          * @param inside inside/outside indicator to use for ambiguous nodes
410          */
411         VanishingToLeaf(final boolean inside) {
412             this.inside = inside;
413         }
414 
415         /** {@inheritDoc} */
416         @Override
417         public BSPTree<S, P, H, I> fixNode(final BSPTree<S, P, H, I> node) {
418             if (node.getPlus().getAttribute().equals(node.getMinus().getAttribute())) {
419                 // no ambiguity
420                 return new BSPTree<>(node.getPlus().getAttribute());
421             } else {
422                 // ambiguous node
423                 return new BSPTree<>(inside);
424             }
425         }
426 
427     }
428 
429 }