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}