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.euclidean.twod;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.List;
022
023import org.apache.commons.math3.geometry.Point;
024import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
025import org.apache.commons.math3.geometry.euclidean.oned.Interval;
026import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
027import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
028import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
029import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
030import org.apache.commons.math3.geometry.partitioning.BSPTree;
031import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor;
032import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute;
033import org.apache.commons.math3.geometry.partitioning.Hyperplane;
034import org.apache.commons.math3.geometry.partitioning.Side;
035import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
036import org.apache.commons.math3.util.FastMath;
037import org.apache.commons.math3.util.Precision;
038
039/** This class represents a 2D region: a set of polygons.
040 * @since 3.0
041 */
042public class PolygonsSet extends AbstractRegion<Euclidean2D, Euclidean1D> {
043
044    /** Default value for tolerance. */
045    private static final double DEFAULT_TOLERANCE = 1.0e-10;
046
047    /** Vertices organized as boundary loops. */
048    private Vector2D[][] vertices;
049
050    /** Build a polygons set representing the whole plane.
051     * @param tolerance tolerance below which points are considered identical
052     * @since 3.3
053     */
054    public PolygonsSet(final double tolerance) {
055        super(tolerance);
056    }
057
058    /** Build a polygons set from a BSP tree.
059     * <p>The leaf nodes of the BSP tree <em>must</em> have a
060     * {@code Boolean} attribute representing the inside status of
061     * the corresponding cell (true for inside cells, false for outside
062     * cells). In order to avoid building too many small objects, it is
063     * recommended to use the predefined constants
064     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
065     * <p>
066     * This constructor is aimed at expert use, as building the tree may
067     * be a difficult task. It is not intended for general use and for
068     * performances reasons does not check thoroughly its input, as this would
069     * require walking the full tree each time. Failing to provide a tree with
070     * the proper attributes, <em>will</em> therefore generate problems like
071     * {@link NullPointerException} or {@link ClassCastException} only later on.
072     * This limitation is known and explains why this constructor is for expert
073     * use only. The caller does have the responsibility to provided correct arguments.
074     * </p>
075     * @param tree inside/outside BSP tree representing the region
076     * @param tolerance tolerance below which points are considered identical
077     * @since 3.3
078     */
079    public PolygonsSet(final BSPTree<Euclidean2D> tree, final double tolerance) {
080        super(tree, tolerance);
081    }
082
083    /** Build a polygons set from a Boundary REPresentation (B-rep).
084     * <p>The boundary is provided as a collection of {@link
085     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
086     * interior part of the region on its minus side and the exterior on
087     * its plus side.</p>
088     * <p>The boundary elements can be in any order, and can form
089     * several non-connected sets (like for example polygons with holes
090     * or a set of disjoint polygons considered as a whole). In
091     * fact, the elements do not even need to be connected together
092     * (their topological connections are not used here). However, if the
093     * boundary does not really separate an inside open from an outside
094     * open (open having here its topological meaning), then subsequent
095     * calls to the {@link
096     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
097     * checkPoint} method will not be meaningful anymore.</p>
098     * <p>If the boundary is empty, the region will represent the whole
099     * space.</p>
100     * @param boundary collection of boundary elements, as a
101     * collection of {@link SubHyperplane SubHyperplane} objects
102     * @param tolerance tolerance below which points are considered identical
103     * @since 3.3
104     */
105    public PolygonsSet(final Collection<SubHyperplane<Euclidean2D>> boundary, final double tolerance) {
106        super(boundary, tolerance);
107    }
108
109    /** Build a parallellepipedic box.
110     * @param xMin low bound along the x direction
111     * @param xMax high bound along the x direction
112     * @param yMin low bound along the y direction
113     * @param yMax high bound along the y direction
114     * @param tolerance tolerance below which points are considered identical
115     * @since 3.3
116     */
117    public PolygonsSet(final double xMin, final double xMax,
118                       final double yMin, final double yMax,
119                       final double tolerance) {
120        super(boxBoundary(xMin, xMax, yMin, yMax, tolerance), tolerance);
121    }
122
123    /** Build a polygon from a simple list of vertices.
124     * <p>The boundary is provided as a list of points considering to
125     * represent the vertices of a simple loop. The interior part of the
126     * region is on the left side of this path and the exterior is on its
127     * right side.</p>
128     * <p>This constructor does not handle polygons with a boundary
129     * forming several disconnected paths (such as polygons with holes).</p>
130     * <p>For cases where this simple constructor applies, it is expected to
131     * be numerically more robust than the {@link #PolygonsSet(Collection) general
132     * constructor} using {@link SubHyperplane subhyperplanes}.</p>
133     * <p>If the list is empty, the region will represent the whole
134     * space.</p>
135     * <p>
136     * Polygons with thin pikes or dents are inherently difficult to handle because
137     * they involve lines with almost opposite directions at some vertices. Polygons
138     * whose vertices come from some physical measurement with noise are also
139     * difficult because an edge that should be straight may be broken in lots of
140     * different pieces with almost equal directions. In both cases, computing the
141     * lines intersections is not numerically robust due to the almost 0 or almost
142     * &pi; angle. Such cases need to carefully adjust the {@code hyperplaneThickness}
143     * parameter. A too small value would often lead to completely wrong polygons
144     * with large area wrongly identified as inside or outside. Large values are
145     * often much safer. As a rule of thumb, a value slightly below the size of the
146     * most accurate detail needed is a good value for the {@code hyperplaneThickness}
147     * parameter.
148     * </p>
149     * @param hyperplaneThickness tolerance below which points are considered to
150     * belong to the hyperplane (which is therefore more a slab)
151     * @param vertices vertices of the simple loop boundary
152     */
153    public PolygonsSet(final double hyperplaneThickness, final Vector2D ... vertices) {
154        super(verticesToTree(hyperplaneThickness, vertices), hyperplaneThickness);
155    }
156
157    /** Build a polygons set representing the whole real line.
158     * @deprecated as of 3.3, replaced with {@link #PolygonsSet(double)}
159     */
160    @Deprecated
161    public PolygonsSet() {
162        this(DEFAULT_TOLERANCE);
163    }
164
165    /** Build a polygons set from a BSP tree.
166     * <p>The leaf nodes of the BSP tree <em>must</em> have a
167     * {@code Boolean} attribute representing the inside status of
168     * the corresponding cell (true for inside cells, false for outside
169     * cells). In order to avoid building too many small objects, it is
170     * recommended to use the predefined constants
171     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
172     * @param tree inside/outside BSP tree representing the region
173     * @deprecated as of 3.3, replaced with {@link #PolygonsSet(BSPTree, double)}
174     */
175    @Deprecated
176    public PolygonsSet(final BSPTree<Euclidean2D> tree) {
177        this(tree, DEFAULT_TOLERANCE);
178    }
179
180    /** Build a polygons set from a Boundary REPresentation (B-rep).
181     * <p>The boundary is provided as a collection of {@link
182     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
183     * interior part of the region on its minus side and the exterior on
184     * its plus side.</p>
185     * <p>The boundary elements can be in any order, and can form
186     * several non-connected sets (like for example polygons with holes
187     * or a set of disjoint polygons considered as a whole). In
188     * fact, the elements do not even need to be connected together
189     * (their topological connections are not used here). However, if the
190     * boundary does not really separate an inside open from an outside
191     * open (open having here its topological meaning), then subsequent
192     * calls to the {@link
193     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
194     * checkPoint} method will not be meaningful anymore.</p>
195     * <p>If the boundary is empty, the region will represent the whole
196     * space.</p>
197     * @param boundary collection of boundary elements, as a
198     * collection of {@link SubHyperplane SubHyperplane} objects
199     * @deprecated as of 3.3, replaced with {@link #PolygonsSet(Collection, double)}
200     */
201    @Deprecated
202    public PolygonsSet(final Collection<SubHyperplane<Euclidean2D>> boundary) {
203        this(boundary, DEFAULT_TOLERANCE);
204    }
205
206    /** Build a parallellepipedic box.
207     * @param xMin low bound along the x direction
208     * @param xMax high bound along the x direction
209     * @param yMin low bound along the y direction
210     * @param yMax high bound along the y direction
211     * @deprecated as of 3.3, replaced with {@link #PolygonsSet(double, double, double, double, double)}
212     */
213    @Deprecated
214    public PolygonsSet(final double xMin, final double xMax,
215                       final double yMin, final double yMax) {
216        this(xMin, xMax, yMin, yMax, DEFAULT_TOLERANCE);
217    }
218
219    /** Create a list of hyperplanes representing the boundary of a box.
220     * @param xMin low bound along the x direction
221     * @param xMax high bound along the x direction
222     * @param yMin low bound along the y direction
223     * @param yMax high bound along the y direction
224     * @param tolerance tolerance below which points are considered identical
225     * @return boundary of the box
226     */
227    private static Line[] boxBoundary(final double xMin, final double xMax,
228                                      final double yMin, final double yMax,
229                                      final double tolerance) {
230        if ((xMin >= xMax - tolerance) || (yMin >= yMax - tolerance)) {
231            // too thin box, build an empty polygons set
232            return null;
233        }
234        final Vector2D minMin = new Vector2D(xMin, yMin);
235        final Vector2D minMax = new Vector2D(xMin, yMax);
236        final Vector2D maxMin = new Vector2D(xMax, yMin);
237        final Vector2D maxMax = new Vector2D(xMax, yMax);
238        return new Line[] {
239            new Line(minMin, maxMin, tolerance),
240            new Line(maxMin, maxMax, tolerance),
241            new Line(maxMax, minMax, tolerance),
242            new Line(minMax, minMin, tolerance)
243        };
244    }
245
246    /** Build the BSP tree of a polygons set from a simple list of vertices.
247     * <p>The boundary is provided as a list of points considering to
248     * represent the vertices of a simple loop. The interior part of the
249     * region is on the left side of this path and the exterior is on its
250     * right side.</p>
251     * <p>This constructor does not handle polygons with a boundary
252     * forming several disconnected paths (such as polygons with holes).</p>
253     * <p>For cases where this simple constructor applies, it is expected to
254     * be numerically more robust than the {@link #PolygonsSet(Collection) general
255     * constructor} using {@link SubHyperplane subhyperplanes}.</p>
256     * @param hyperplaneThickness tolerance below which points are consider to
257     * belong to the hyperplane (which is therefore more a slab)
258     * @param vertices vertices of the simple loop boundary
259     * @return the BSP tree of the input vertices
260     */
261    private static BSPTree<Euclidean2D> verticesToTree(final double hyperplaneThickness,
262                                                       final Vector2D ... vertices) {
263
264        final int n = vertices.length;
265        if (n == 0) {
266            // the tree represents the whole space
267            return new BSPTree<Euclidean2D>(Boolean.TRUE);
268        }
269
270        // build the vertices
271        final Vertex[] vArray = new Vertex[n];
272        for (int i = 0; i < n; ++i) {
273            vArray[i] = new Vertex(vertices[i]);
274        }
275
276        // build the edges
277        List<Edge> edges = new ArrayList<Edge>(n);
278        for (int i = 0; i < n; ++i) {
279
280            // get the endpoints of the edge
281            final Vertex start = vArray[i];
282            final Vertex end   = vArray[(i + 1) % n];
283
284            // get the line supporting the edge, taking care not to recreate it
285            // if it was already created earlier due to another edge being aligned
286            // with the current one
287            Line line = start.sharedLineWith(end);
288            if (line == null) {
289                line = new Line(start.getLocation(), end.getLocation(), hyperplaneThickness);
290            }
291
292            // create the edge and store it
293            edges.add(new Edge(start, end, line));
294
295            // check if another vertex also happens to be on this line
296            for (final Vertex vertex : vArray) {
297                if (vertex != start && vertex != end &&
298                    FastMath.abs(line.getOffset((Point<Euclidean2D>) vertex.getLocation())) <= hyperplaneThickness) {
299                    vertex.bindWith(line);
300                }
301            }
302
303        }
304
305        // build the tree top-down
306        final BSPTree<Euclidean2D> tree = new BSPTree<Euclidean2D>();
307        insertEdges(hyperplaneThickness, tree, edges);
308
309        return tree;
310
311    }
312
313    /** Recursively build a tree by inserting cut sub-hyperplanes.
314     * @param hyperplaneThickness tolerance below which points are consider to
315     * belong to the hyperplane (which is therefore more a slab)
316     * @param node current tree node (it is a leaf node at the beginning
317     * of the call)
318     * @param edges list of edges to insert in the cell defined by this node
319     * (excluding edges not belonging to the cell defined by this node)
320     */
321    private static void insertEdges(final double hyperplaneThickness,
322                                    final BSPTree<Euclidean2D> node,
323                                    final List<Edge> edges) {
324
325        // find an edge with an hyperplane that can be inserted in the node
326        int index = 0;
327        Edge inserted =null;
328        while (inserted == null && index < edges.size()) {
329            inserted = edges.get(index++);
330            if (inserted.getNode() == null) {
331                if (node.insertCut(inserted.getLine())) {
332                    inserted.setNode(node);
333                } else {
334                    inserted = null;
335                }
336            } else {
337                inserted = null;
338            }
339        }
340
341        if (inserted == null) {
342            // no suitable edge was found, the node remains a leaf node
343            // we need to set its inside/outside boolean indicator
344            final BSPTree<Euclidean2D> parent = node.getParent();
345            if (parent == null || node == parent.getMinus()) {
346                node.setAttribute(Boolean.TRUE);
347            } else {
348                node.setAttribute(Boolean.FALSE);
349            }
350            return;
351        }
352
353        // we have split the node by inserting an edge as a cut sub-hyperplane
354        // distribute the remaining edges in the two sub-trees
355        final List<Edge> plusList  = new ArrayList<Edge>();
356        final List<Edge> minusList = new ArrayList<Edge>();
357        for (final Edge edge : edges) {
358            if (edge != inserted) {
359                final double startOffset = inserted.getLine().getOffset((Point<Euclidean2D>) edge.getStart().getLocation());
360                final double endOffset   = inserted.getLine().getOffset((Point<Euclidean2D>) edge.getEnd().getLocation());
361                Side startSide = (FastMath.abs(startOffset) <= hyperplaneThickness) ?
362                                 Side.HYPER : ((startOffset < 0) ? Side.MINUS : Side.PLUS);
363                Side endSide   = (FastMath.abs(endOffset) <= hyperplaneThickness) ?
364                                 Side.HYPER : ((endOffset < 0) ? Side.MINUS : Side.PLUS);
365                switch (startSide) {
366                    case PLUS:
367                        if (endSide == Side.MINUS) {
368                            // we need to insert a split point on the hyperplane
369                            final Vertex splitPoint = edge.split(inserted.getLine());
370                            minusList.add(splitPoint.getOutgoing());
371                            plusList.add(splitPoint.getIncoming());
372                        } else {
373                            plusList.add(edge);
374                        }
375                        break;
376                    case MINUS:
377                        if (endSide == Side.PLUS) {
378                            // we need to insert a split point on the hyperplane
379                            final Vertex splitPoint = edge.split(inserted.getLine());
380                            minusList.add(splitPoint.getIncoming());
381                            plusList.add(splitPoint.getOutgoing());
382                        } else {
383                            minusList.add(edge);
384                        }
385                        break;
386                    default:
387                        if (endSide == Side.PLUS) {
388                            plusList.add(edge);
389                        } else if (endSide == Side.MINUS) {
390                            minusList.add(edge);
391                        }
392                        break;
393                }
394            }
395        }
396
397        // recurse through lower levels
398        if (!plusList.isEmpty()) {
399            insertEdges(hyperplaneThickness, node.getPlus(),  plusList);
400        } else {
401            node.getPlus().setAttribute(Boolean.FALSE);
402        }
403        if (!minusList.isEmpty()) {
404            insertEdges(hyperplaneThickness, node.getMinus(), minusList);
405        } else {
406            node.getMinus().setAttribute(Boolean.TRUE);
407        }
408
409    }
410
411    /** Internal class for holding vertices while they are processed to build a BSP tree. */
412    private static class Vertex {
413
414        /** Vertex location. */
415        private final Vector2D location;
416
417        /** Incoming edge. */
418        private Edge incoming;
419
420        /** Outgoing edge. */
421        private Edge outgoing;
422
423        /** Lines bound with this vertex. */
424        private final List<Line> lines;
425
426        /** Build a non-processed vertex not owned by any node yet.
427         * @param location vertex location
428         */
429        Vertex(final Vector2D location) {
430            this.location = location;
431            this.incoming = null;
432            this.outgoing = null;
433            this.lines    = new ArrayList<Line>();
434        }
435
436        /** Get Vertex location.
437         * @return vertex location
438         */
439        public Vector2D getLocation() {
440            return location;
441        }
442
443        /** Bind a line considered to contain this vertex.
444         * @param line line to bind with this vertex
445         */
446        public void bindWith(final Line line) {
447            lines.add(line);
448        }
449
450        /** Get the common line bound with both the instance and another vertex, if any.
451         * <p>
452         * When two vertices are both bound to the same line, this means they are
453         * already handled by node associated with this line, so there is no need
454         * to create a cut hyperplane for them.
455         * </p>
456         * @param vertex other vertex to check instance against
457         * @return line bound with both the instance and another vertex, or null if the
458         * two vertices do not share a line yet
459         */
460        public Line sharedLineWith(final Vertex vertex) {
461            for (final Line line1 : lines) {
462                for (final Line line2 : vertex.lines) {
463                    if (line1 == line2) {
464                        return line1;
465                    }
466                }
467            }
468            return null;
469        }
470
471        /** Set incoming edge.
472         * <p>
473         * The line supporting the incoming edge is automatically bound
474         * with the instance.
475         * </p>
476         * @param incoming incoming edge
477         */
478        public void setIncoming(final Edge incoming) {
479            this.incoming = incoming;
480            bindWith(incoming.getLine());
481        }
482
483        /** Get incoming edge.
484         * @return incoming edge
485         */
486        public Edge getIncoming() {
487            return incoming;
488        }
489
490        /** Set outgoing edge.
491         * <p>
492         * The line supporting the outgoing edge is automatically bound
493         * with the instance.
494         * </p>
495         * @param outgoing outgoing edge
496         */
497        public void setOutgoing(final Edge outgoing) {
498            this.outgoing = outgoing;
499            bindWith(outgoing.getLine());
500        }
501
502        /** Get outgoing edge.
503         * @return outgoing edge
504         */
505        public Edge getOutgoing() {
506            return outgoing;
507        }
508
509    }
510
511    /** Internal class for holding edges while they are processed to build a BSP tree. */
512    private static class Edge {
513
514        /** Start vertex. */
515        private final Vertex start;
516
517        /** End vertex. */
518        private final Vertex end;
519
520        /** Line supporting the edge. */
521        private final Line line;
522
523        /** Node whose cut hyperplane contains this edge. */
524        private BSPTree<Euclidean2D> node;
525
526        /** Build an edge not contained in any node yet.
527         * @param start start vertex
528         * @param end end vertex
529         * @param line line supporting the edge
530         */
531        Edge(final Vertex start, final Vertex end, final Line line) {
532
533            this.start = start;
534            this.end   = end;
535            this.line  = line;
536            this.node  = null;
537
538            // connect the vertices back to the edge
539            start.setOutgoing(this);
540            end.setIncoming(this);
541
542        }
543
544        /** Get start vertex.
545         * @return start vertex
546         */
547        public Vertex getStart() {
548            return start;
549        }
550
551        /** Get end vertex.
552         * @return end vertex
553         */
554        public Vertex getEnd() {
555            return end;
556        }
557
558        /** Get the line supporting this edge.
559         * @return line supporting this edge
560         */
561        public Line getLine() {
562            return line;
563        }
564
565        /** Set the node whose cut hyperplane contains this edge.
566         * @param node node whose cut hyperplane contains this edge
567         */
568        public void setNode(final BSPTree<Euclidean2D> node) {
569            this.node = node;
570        }
571
572        /** Get the node whose cut hyperplane contains this edge.
573         * @return node whose cut hyperplane contains this edge
574         * (null if edge has not yet been inserted into the BSP tree)
575         */
576        public BSPTree<Euclidean2D> getNode() {
577            return node;
578        }
579
580        /** Split the edge.
581         * <p>
582         * Once split, this edge is not referenced anymore by the vertices,
583         * it is replaced by the two half-edges and an intermediate splitting
584         * vertex is introduced to connect these two halves.
585         * </p>
586         * @param splitLine line splitting the edge in two halves
587         * @return split vertex (its incoming and outgoing edges are the two halves)
588         */
589        public Vertex split(final Line splitLine) {
590            final Vertex splitVertex = new Vertex(line.intersection(splitLine));
591            splitVertex.bindWith(splitLine);
592            final Edge startHalf = new Edge(start, splitVertex, line);
593            final Edge endHalf   = new Edge(splitVertex, end, line);
594            startHalf.node = node;
595            endHalf.node   = node;
596            return splitVertex;
597        }
598
599    }
600
601    /** {@inheritDoc} */
602    @Override
603    public PolygonsSet buildNew(final BSPTree<Euclidean2D> tree) {
604        return new PolygonsSet(tree, getTolerance());
605    }
606
607    /** {@inheritDoc} */
608    @Override
609    protected void computeGeometricalProperties() {
610
611        final Vector2D[][] v = getVertices();
612
613        if (v.length == 0) {
614            final BSPTree<Euclidean2D> tree = getTree(false);
615            if (tree.getCut() == null && (Boolean) tree.getAttribute()) {
616                // the instance covers the whole space
617                setSize(Double.POSITIVE_INFINITY);
618                setBarycenter((Point<Euclidean2D>) Vector2D.NaN);
619            } else {
620                setSize(0);
621                setBarycenter((Point<Euclidean2D>) new Vector2D(0, 0));
622            }
623        } else if (v[0][0] == null) {
624            // there is at least one open-loop: the polygon is infinite
625            setSize(Double.POSITIVE_INFINITY);
626            setBarycenter((Point<Euclidean2D>) Vector2D.NaN);
627        } else {
628            // all loops are closed, we compute some integrals around the shape
629
630            double sum  = 0;
631            double sumX = 0;
632            double sumY = 0;
633
634            for (Vector2D[] loop : v) {
635                double x1 = loop[loop.length - 1].getX();
636                double y1 = loop[loop.length - 1].getY();
637                for (final Vector2D point : loop) {
638                    final double x0 = x1;
639                    final double y0 = y1;
640                    x1 = point.getX();
641                    y1 = point.getY();
642                    final double factor = x0 * y1 - y0 * x1;
643                    sum  += factor;
644                    sumX += factor * (x0 + x1);
645                    sumY += factor * (y0 + y1);
646                }
647            }
648
649            if (sum < 0) {
650                // the polygon as a finite outside surrounded by an infinite inside
651                setSize(Double.POSITIVE_INFINITY);
652                setBarycenter((Point<Euclidean2D>) Vector2D.NaN);
653            } else {
654                setSize(sum / 2);
655                setBarycenter((Point<Euclidean2D>) new Vector2D(sumX / (3 * sum), sumY / (3 * sum)));
656            }
657
658        }
659
660    }
661
662    /** Get the vertices of the polygon.
663     * <p>The polygon boundary can be represented as an array of loops,
664     * each loop being itself an array of vertices.</p>
665     * <p>In order to identify open loops which start and end by
666     * infinite edges, the open loops arrays start with a null point. In
667     * this case, the first non null point and the last point of the
668     * array do not represent real vertices, they are dummy points
669     * intended only to get the direction of the first and last edge. An
670     * open loop consisting of a single infinite line will therefore be
671     * represented by a three elements array with one null point
672     * followed by two dummy points. The open loops are always the first
673     * ones in the loops array.</p>
674     * <p>If the polygon has no boundary at all, a zero length loop
675     * array will be returned.</p>
676     * <p>All line segments in the various loops have the inside of the
677     * region on their left side and the outside on their right side
678     * when moving in the underlying line direction. This means that
679     * closed loops surrounding finite areas obey the direct
680     * trigonometric orientation.</p>
681     * @return vertices of the polygon, organized as oriented boundary
682     * loops with the open loops first (the returned value is guaranteed
683     * to be non-null)
684     */
685    public Vector2D[][] getVertices() {
686        if (vertices == null) {
687            if (getTree(false).getCut() == null) {
688                vertices = new Vector2D[0][];
689            } else {
690
691                // build the unconnected segments
692                final SegmentsBuilder visitor = new SegmentsBuilder(getTolerance());
693                getTree(true).visit(visitor);
694                final List<ConnectableSegment> segments = visitor.getSegments();
695
696                // connect all segments, using topological criteria first
697                // and using Euclidean distance only as a last resort
698                int pending = segments.size();
699                pending -= naturalFollowerConnections(segments);
700                if (pending > 0) {
701                    pending -= splitEdgeConnections(segments);
702                }
703                if (pending > 0) {
704                    pending -= closeVerticesConnections(segments);
705                }
706
707                // create the segment loops
708                final ArrayList<List<Segment>> loops = new ArrayList<List<Segment>>();
709                for (ConnectableSegment s = getUnprocessed(segments); s != null; s = getUnprocessed(segments)) {
710                    final List<Segment> loop = followLoop(s);
711                    if (loop != null) {
712                        if (loop.get(0).getStart() == null) {
713                            // this is an open loop, we put it on the front
714                            loops.add(0, loop);
715                        } else {
716                            // this is a closed loop, we put it on the back
717                            loops.add(loop);
718                        }
719                    }
720                }
721
722                // transform the loops in an array of arrays of points
723                vertices = new Vector2D[loops.size()][];
724                int i = 0;
725
726                for (final List<Segment> loop : loops) {
727                    if (loop.size() < 2 ||
728                        (loop.size() == 2 && loop.get(0).getStart() == null && loop.get(1).getEnd() == null)) {
729                        // single infinite line
730                        final Line line = loop.get(0).getLine();
731                        vertices[i++] = new Vector2D[] {
732                            null,
733                            line.toSpace((Point<Euclidean1D>) new Vector1D(-Float.MAX_VALUE)),
734                            line.toSpace((Point<Euclidean1D>) new Vector1D(+Float.MAX_VALUE))
735                        };
736                    } else if (loop.get(0).getStart() == null) {
737                        // open loop with at least one real point
738                        final Vector2D[] array = new Vector2D[loop.size() + 2];
739                        int j = 0;
740                        for (Segment segment : loop) {
741
742                            if (j == 0) {
743                                // null point and first dummy point
744                                double x = segment.getLine().toSubSpace((Point<Euclidean2D>) segment.getEnd()).getX();
745                                x -= FastMath.max(1.0, FastMath.abs(x / 2));
746                                array[j++] = null;
747                                array[j++] = segment.getLine().toSpace((Point<Euclidean1D>) new Vector1D(x));
748                            }
749
750                            if (j < (array.length - 1)) {
751                                // current point
752                                array[j++] = segment.getEnd();
753                            }
754
755                            if (j == (array.length - 1)) {
756                                // last dummy point
757                                double x = segment.getLine().toSubSpace((Point<Euclidean2D>) segment.getStart()).getX();
758                                x += FastMath.max(1.0, FastMath.abs(x / 2));
759                                array[j++] = segment.getLine().toSpace((Point<Euclidean1D>) new Vector1D(x));
760                            }
761
762                        }
763                        vertices[i++] = array;
764                    } else {
765                        final Vector2D[] array = new Vector2D[loop.size()];
766                        int j = 0;
767                        for (Segment segment : loop) {
768                            array[j++] = segment.getStart();
769                        }
770                        vertices[i++] = array;
771                    }
772                }
773
774            }
775        }
776
777        return vertices.clone();
778
779    }
780
781    /** Connect the segments using only natural follower information.
782     * @param segments segments complete segments list
783     * @return number of connections performed
784     */
785    private int naturalFollowerConnections(final List<ConnectableSegment> segments) {
786        int connected = 0;
787        for (final ConnectableSegment segment : segments) {
788            if (segment.getNext() == null) {
789                final BSPTree<Euclidean2D> node = segment.getNode();
790                final BSPTree<Euclidean2D> end  = segment.getEndNode();
791                for (final ConnectableSegment candidateNext : segments) {
792                    if (candidateNext.getPrevious()  == null &&
793                        candidateNext.getNode()      == end &&
794                        candidateNext.getStartNode() == node) {
795                        // connect the two segments
796                        segment.setNext(candidateNext);
797                        candidateNext.setPrevious(segment);
798                        ++connected;
799                        break;
800                    }
801                }
802            }
803        }
804        return connected;
805    }
806
807    /** Connect the segments resulting from a line splitting a straight edge.
808     * @param segments segments complete segments list
809     * @return number of connections performed
810     */
811    private int splitEdgeConnections(final List<ConnectableSegment> segments) {
812        int connected = 0;
813        for (final ConnectableSegment segment : segments) {
814            if (segment.getNext() == null) {
815                final Hyperplane<Euclidean2D> hyperplane = segment.getNode().getCut().getHyperplane();
816                final BSPTree<Euclidean2D> end  = segment.getEndNode();
817                for (final ConnectableSegment candidateNext : segments) {
818                    if (candidateNext.getPrevious()                      == null &&
819                        candidateNext.getNode().getCut().getHyperplane() == hyperplane &&
820                        candidateNext.getStartNode()                     == end) {
821                        // connect the two segments
822                        segment.setNext(candidateNext);
823                        candidateNext.setPrevious(segment);
824                        ++connected;
825                        break;
826                    }
827                }
828            }
829        }
830        return connected;
831    }
832
833    /** Connect the segments using Euclidean distance.
834     * <p>
835     * This connection heuristic should be used last, as it relies
836     * only on a fuzzy distance criterion.
837     * </p>
838     * @param segments segments complete segments list
839     * @return number of connections performed
840     */
841    private int closeVerticesConnections(final List<ConnectableSegment> segments) {
842        int connected = 0;
843        for (final ConnectableSegment segment : segments) {
844            if (segment.getNext() == null && segment.getEnd() != null) {
845                final Vector2D end = segment.getEnd();
846                ConnectableSegment selectedNext = null;
847                double min = Double.POSITIVE_INFINITY;
848                for (final ConnectableSegment candidateNext : segments) {
849                    if (candidateNext.getPrevious() == null && candidateNext.getStart() != null) {
850                        final double distance = Vector2D.distance(end, candidateNext.getStart());
851                        if (distance < min) {
852                            selectedNext = candidateNext;
853                            min          = distance;
854                        }
855                    }
856                }
857                if (min <= getTolerance()) {
858                    // connect the two segments
859                    segment.setNext(selectedNext);
860                    selectedNext.setPrevious(segment);
861                    ++connected;
862                }
863            }
864        }
865        return connected;
866    }
867
868    /** Get first unprocessed segment from a list.
869     * @param segments segments list
870     * @return first segment that has not been processed yet
871     * or null if all segments have been processed
872     */
873    private ConnectableSegment getUnprocessed(final List<ConnectableSegment> segments) {
874        for (final ConnectableSegment segment : segments) {
875            if (!segment.isProcessed()) {
876                return segment;
877            }
878        }
879        return null;
880    }
881
882    /** Build the loop containing a segment.
883     * <p>
884     * The segment put in the loop will be marked as processed.
885     * </p>
886     * @param defining segment used to define the loop
887     * @return loop containing the segment (may be null if the loop is a
888     * degenerated infinitely thin 2 points loop
889     */
890    private List<Segment> followLoop(final ConnectableSegment defining) {
891
892        final List<Segment> loop = new ArrayList<Segment>();
893        loop.add(defining);
894        defining.setProcessed(true);
895
896        // add segments in connection order
897        ConnectableSegment next = defining.getNext();
898        while (next != defining && next != null) {
899            loop.add(next);
900            next.setProcessed(true);
901            next = next.getNext();
902        }
903
904        if (next == null) {
905            // the loop is open and we have found its end,
906            // we need to find its start too
907            ConnectableSegment previous = defining.getPrevious();
908            while (previous != null) {
909                loop.add(0, previous);
910                previous.setProcessed(true);
911                previous = previous.getPrevious();
912            }
913        }
914
915        // filter out spurious vertices
916        filterSpuriousVertices(loop);
917
918        if (loop.size() == 2 && loop.get(0).getStart() != null) {
919            // this is a degenerated infinitely thin closed loop, we simply ignore it
920            return null;
921        } else {
922            return loop;
923        }
924
925    }
926
927    /** Filter out spurious vertices on straight lines (at machine precision).
928     * @param loop segments loop to filter (will be modified in-place)
929     */
930    private void filterSpuriousVertices(final List<Segment> loop) {
931        for (int i = 0; i < loop.size(); ++i) {
932            final Segment previous = loop.get(i);
933            int j = (i + 1) % loop.size();
934            final Segment next = loop.get(j);
935            if (next != null &&
936                Precision.equals(previous.getLine().getAngle(), next.getLine().getAngle(), Precision.EPSILON)) {
937                // the vertex between the two edges is a spurious one
938                // replace the two segments by a single one
939                loop.set(j, new Segment(previous.getStart(), next.getEnd(), previous.getLine()));
940                loop.remove(i--);
941            }
942        }
943    }
944
945    /** Private extension of Segment allowing connection. */
946    private static class ConnectableSegment extends Segment {
947
948        /** Node containing segment. */
949        private final BSPTree<Euclidean2D> node;
950
951        /** Node whose intersection with current node defines start point. */
952        private final BSPTree<Euclidean2D> startNode;
953
954        /** Node whose intersection with current node defines end point. */
955        private final BSPTree<Euclidean2D> endNode;
956
957        /** Previous segment. */
958        private ConnectableSegment previous;
959
960        /** Next segment. */
961        private ConnectableSegment next;
962
963        /** Indicator for completely processed segments. */
964        private boolean processed;
965
966        /** Build a segment.
967         * @param start start point of the segment
968         * @param end end point of the segment
969         * @param line line containing the segment
970         * @param node node containing the segment
971         * @param startNode node whose intersection with current node defines start point
972         * @param endNode node whose intersection with current node defines end point
973         */
974        ConnectableSegment(final Vector2D start, final Vector2D end, final Line line,
975                           final BSPTree<Euclidean2D> node,
976                           final BSPTree<Euclidean2D> startNode,
977                           final BSPTree<Euclidean2D> endNode) {
978            super(start, end, line);
979            this.node      = node;
980            this.startNode = startNode;
981            this.endNode   = endNode;
982            this.previous  = null;
983            this.next      = null;
984            this.processed = false;
985        }
986
987        /** Get the node containing segment.
988         * @return node containing segment
989         */
990        public BSPTree<Euclidean2D> getNode() {
991            return node;
992        }
993
994        /** Get the node whose intersection with current node defines start point.
995         * @return node whose intersection with current node defines start point
996         */
997        public BSPTree<Euclidean2D> getStartNode() {
998            return startNode;
999        }
1000
1001        /** Get the node whose intersection with current node defines end point.
1002         * @return node whose intersection with current node defines end point
1003         */
1004        public BSPTree<Euclidean2D> getEndNode() {
1005            return endNode;
1006        }
1007
1008        /** Get the previous segment.
1009         * @return previous segment
1010         */
1011        public ConnectableSegment getPrevious() {
1012            return previous;
1013        }
1014
1015        /** Set the previous segment.
1016         * @param previous previous segment
1017         */
1018        public void setPrevious(final ConnectableSegment previous) {
1019            this.previous = previous;
1020        }
1021
1022        /** Get the next segment.
1023         * @return next segment
1024         */
1025        public ConnectableSegment getNext() {
1026            return next;
1027        }
1028
1029        /** Set the next segment.
1030         * @param next previous segment
1031         */
1032        public void setNext(final ConnectableSegment next) {
1033            this.next = next;
1034        }
1035
1036        /** Set the processed flag.
1037         * @param processed processed flag to set
1038         */
1039        public void setProcessed(final boolean processed) {
1040            this.processed = processed;
1041        }
1042
1043        /** Check if the segment has been processed.
1044         * @return true if the segment has been processed
1045         */
1046        public boolean isProcessed() {
1047            return processed;
1048        }
1049
1050    }
1051
1052    /** Visitor building segments. */
1053    private static class SegmentsBuilder implements BSPTreeVisitor<Euclidean2D> {
1054
1055        /** Tolerance for close nodes connection. */
1056        private final double tolerance;
1057
1058        /** Built segments. */
1059        private final List<ConnectableSegment> segments;
1060
1061        /** Simple constructor.
1062         * @param tolerance tolerance for close nodes connection
1063         */
1064        SegmentsBuilder(final double tolerance) {
1065            this.tolerance = tolerance;
1066            this.segments  = new ArrayList<ConnectableSegment>();
1067        }
1068
1069        /** {@inheritDoc} */
1070        public Order visitOrder(final BSPTree<Euclidean2D> node) {
1071            return Order.MINUS_SUB_PLUS;
1072        }
1073
1074        /** {@inheritDoc} */
1075        public void visitInternalNode(final BSPTree<Euclidean2D> node) {
1076            @SuppressWarnings("unchecked")
1077            final BoundaryAttribute<Euclidean2D> attribute = (BoundaryAttribute<Euclidean2D>) node.getAttribute();
1078            final Iterable<BSPTree<Euclidean2D>> splitters = attribute.getSplitters();
1079            if (attribute.getPlusOutside() != null) {
1080                addContribution(attribute.getPlusOutside(), node, splitters, false);
1081            }
1082            if (attribute.getPlusInside() != null) {
1083                addContribution(attribute.getPlusInside(), node, splitters, true);
1084            }
1085        }
1086
1087        /** {@inheritDoc} */
1088        public void visitLeafNode(final BSPTree<Euclidean2D> node) {
1089        }
1090
1091        /** Add the contribution of a boundary facet.
1092         * @param sub boundary facet
1093         * @param node node containing segment
1094         * @param splitters splitters for the boundary facet
1095         * @param reversed if true, the facet has the inside on its plus side
1096         */
1097        private void addContribution(final SubHyperplane<Euclidean2D> sub,
1098                                     final BSPTree<Euclidean2D> node,
1099                                     final Iterable<BSPTree<Euclidean2D>> splitters,
1100                                     final boolean reversed) {
1101            @SuppressWarnings("unchecked")
1102            final AbstractSubHyperplane<Euclidean2D, Euclidean1D> absSub =
1103                (AbstractSubHyperplane<Euclidean2D, Euclidean1D>) sub;
1104            final Line line      = (Line) sub.getHyperplane();
1105            final List<Interval> intervals = ((IntervalsSet) absSub.getRemainingRegion()).asList();
1106            for (final Interval i : intervals) {
1107
1108                // find the 2D points
1109                final Vector2D startV = Double.isInfinite(i.getInf()) ?
1110                                        null : (Vector2D) line.toSpace((Point<Euclidean1D>) new Vector1D(i.getInf()));
1111                final Vector2D endV   = Double.isInfinite(i.getSup()) ?
1112                                        null : (Vector2D) line.toSpace((Point<Euclidean1D>) new Vector1D(i.getSup()));
1113
1114                // recover the connectivity information
1115                final BSPTree<Euclidean2D> startN = selectClosest(startV, splitters);
1116                final BSPTree<Euclidean2D> endN   = selectClosest(endV, splitters);
1117
1118                if (reversed) {
1119                    segments.add(new ConnectableSegment(endV, startV, line.getReverse(),
1120                                                        node, endN, startN));
1121                } else {
1122                    segments.add(new ConnectableSegment(startV, endV, line,
1123                                                        node, startN, endN));
1124                }
1125
1126            }
1127        }
1128
1129        /** Select the node whose cut sub-hyperplane is closest to specified point.
1130         * @param point reference point
1131         * @param candidates candidate nodes
1132         * @return node closest to point, or null if no node is closer than tolerance
1133         */
1134        private BSPTree<Euclidean2D> selectClosest(final Vector2D point, final Iterable<BSPTree<Euclidean2D>> candidates) {
1135
1136            BSPTree<Euclidean2D> selected = null;
1137            double min = Double.POSITIVE_INFINITY;
1138
1139            for (final BSPTree<Euclidean2D> node : candidates) {
1140                final double distance = FastMath.abs(node.getCut().getHyperplane().getOffset(point));
1141                if (distance < min) {
1142                    selected = node;
1143                    min      = distance;
1144                }
1145            }
1146
1147            return min <= tolerance ? selected : null;
1148
1149        }
1150
1151        /** Get the segments.
1152         * @return built segments
1153         */
1154        public List<ConnectableSegment> getSegments() {
1155            return segments;
1156        }
1157
1158    }
1159
1160}