001/**
002 * 
003 */
004package icy.type.geom;
005
006import java.awt.Point;
007import java.awt.Polygon;
008import java.awt.Rectangle;
009import java.awt.Shape;
010import java.awt.geom.AffineTransform;
011import java.awt.geom.Line2D;
012import java.awt.geom.Path2D;
013import java.awt.geom.PathIterator;
014import java.awt.geom.Point2D;
015import java.awt.geom.Rectangle2D;
016import java.util.ArrayList;
017import java.util.List;
018
019/*
020 * Licensed to the Apache Software Foundation (ASF) under one or more
021 * contributor license agreements. See the NOTICE file distributed with
022 * this work for additional information regarding copyright ownership.
023 * The ASF licenses this file to You under the Apache License, Version 2.0
024 * (the "License"); you may not use this file except in compliance with
025 * the License. You may obtain a copy of the License at
026 * 
027 * http://www.apache.org/licenses/LICENSE-2.0
028 * 
029 * Unless required by applicable law or agreed to in writing, software
030 * distributed under the License is distributed on an "AS IS" BASIS,
031 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
032 * See the License for the specific language governing permissions and
033 * limitations under the License.
034 * 
035 * Modified by Stephane Dallongeville
036 */
037
038/**
039 * This class is a Polygon with double coordinates.
040 */
041public class Polygon2D implements Shape, Cloneable
042{
043    private static void addFarthestPoint(List<Point2D> result, List<Point2D> points, int start, int end,
044            double maxDeviation)
045    {
046        final Point2D p1 = points.get(start);
047        final Point2D p2 = points.get(end);
048        final double x1 = p1.getX();
049        final double y1 = p1.getY();
050        final double x2 = p2.getX();
051        final double y2 = p2.getY();
052        int farthest = -1;
053        // initialize with maximum allow deviation (squared)
054        double maxDist = maxDeviation * maxDeviation;
055
056        for (int i = start + 1; i < end; i++)
057        {
058            final Point2D p = points.get(i);
059            final double dist = Line2D.ptSegDistSq(x1, y1, x2, y2, p.getX(), p.getY());
060
061            if (dist > maxDist)
062            {
063                farthest = i;
064                maxDist = dist;
065            }
066        }
067
068        // found a point to add ?
069        if (farthest != -1)
070        {
071            // search before point
072            addFarthestPoint(result, points, start, farthest, maxDeviation);
073            // add point
074            result.add(points.get(farthest));
075            // search after point
076            addFarthestPoint(result, points, farthest, end, maxDeviation);
077        }
078    }
079
080    /**
081     * Returns a polygon2D corresponding to the polygon estimation of the given (closed) contour points with the
082     * specified <code>max deviation</code>.
083     * 
084     * @param points
085     *        the list of points representing the input closed contour to transform to polygon.
086     * @param maxDeviation
087     *        maximum allowed deviation/distance of resulting polygon from the input contour (in pixel).
088     * @return the polygon estimation from input contour
089     */
090    public static Polygon2D getPolygon2D(List<Point2D> points, double maxDeviation)
091    {
092        // just return
093        if (points.size() < 3)
094            return new Polygon2D(points);
095
096        final List<Point2D> result = new ArrayList<Point2D>(points.size() / 4);
097
098        int ind = points.size() / 2;
099
100        // close the contour
101        points.add(points.get(0));
102
103        // add first point
104        result.add(points.get(0));
105        // add points between first and medium
106        addFarthestPoint(result, points, 0, ind, maxDeviation);
107        // add medium point
108        result.add(points.get(ind));
109        // add points between medium and end
110        addFarthestPoint(result, points, ind, points.size() - 1, maxDeviation);
111
112        // restore original contour
113        points.remove(points.size() - 1);
114
115        return new Polygon2D(result);
116    }
117
118    /**
119     * The total number of points. The value of <code>npoints</code> represents the number of valid points in this
120     * <code>Polygon</code>.
121     */
122    public int npoints;
123
124    /**
125     * The array of <i>x</i> coordinates. The value of {@link #npoints} is equal to the
126     * number of points in this <code>Polygon2D</code>.
127     */
128    public double[] xpoints;
129
130    /**
131     * The array of <i>y</i> coordinates. The value of {@link #npoints} is equal to the
132     * number of points in this <code>Polygon2D</code>.
133     */
134    public double[] ypoints;
135
136    /**
137     * Bounds of the Polygon2D.
138     * 
139     * @see #getBounds()
140     */
141    protected Rectangle2D bounds;
142
143    protected Path2D.Double path;
144    protected Path2D.Double closedPath;
145
146    /**
147     * Creates an empty Polygon2D.
148     */
149    public Polygon2D()
150    {
151        super();
152
153        reset();
154    }
155
156    /**
157     * Constructs and initializes a <code>Polygon2D</code> from the specified
158     * Rectangle2D.
159     * 
160     * @param rec
161     *        the Rectangle2D
162     * @exception NullPointerException
163     *            rec is <code>null</code>.
164     */
165    public Polygon2D(Rectangle2D rec)
166    {
167        super();
168
169        if (rec == null)
170            throw new IllegalArgumentException("null Rectangle");
171
172        npoints = 4;
173        xpoints = new double[4];
174        ypoints = new double[4];
175
176        xpoints[0] = rec.getMinX();
177        ypoints[0] = rec.getMinY();
178        xpoints[1] = rec.getMaxX();
179        ypoints[1] = rec.getMinY();
180        xpoints[2] = rec.getMaxX();
181        ypoints[2] = rec.getMaxY();
182        xpoints[3] = rec.getMinX();
183        ypoints[3] = rec.getMaxY();
184
185        calculatePath();
186    }
187
188    /**
189     * Constructs and initializes a <code>Polygon2D</code> from the specified
190     * Polygon.
191     * 
192     * @param pol
193     *        the Polygon
194     * @exception NullPointerException
195     *            pol is <code>null</code>.
196     */
197    public Polygon2D(Polygon pol)
198    {
199        super();
200
201        if (pol == null)
202            throw new IllegalArgumentException("null Polygon");
203
204        this.npoints = pol.npoints;
205        this.xpoints = new double[pol.npoints];
206        this.ypoints = new double[pol.npoints];
207
208        for (int i = 0; i < pol.npoints; i++)
209        {
210            xpoints[i] = pol.xpoints[i];
211            ypoints[i] = pol.ypoints[i];
212        }
213
214        calculatePath();
215    }
216
217    /**
218     * Constructs and initializes a <code>Polygon2D</code> from the specified
219     * parameters.
220     * 
221     * @param xpoints
222     *        an array of <i>x</i> coordinates
223     * @param ypoints
224     *        an array of <i>y</i> coordinates
225     * @param npoints
226     *        the total number of points in the <code>Polygon2D</code>
227     * @exception NegativeArraySizeException
228     *            if the value of <code>npoints</code> is negative.
229     * @exception IndexOutOfBoundsException
230     *            if <code>npoints</code> is
231     *            greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>.
232     * @exception NullPointerException
233     *            if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>.
234     */
235    public Polygon2D(double[] xpoints, double[] ypoints, int npoints)
236    {
237        super();
238
239        if (npoints > xpoints.length || npoints > ypoints.length)
240            throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length");
241
242        this.npoints = npoints;
243        this.xpoints = new double[npoints];
244        this.ypoints = new double[npoints];
245
246        System.arraycopy(xpoints, 0, this.xpoints, 0, npoints);
247        System.arraycopy(ypoints, 0, this.ypoints, 0, npoints);
248
249        calculatePath();
250    }
251
252    /**
253     * Constructs and initializes a <code>Polygon2D</code> from the specified
254     * parameters.
255     * 
256     * @param xpoints
257     *        an array of <i>x</i> coordinates
258     * @param ypoints
259     *        an array of <i>y</i> coordinates
260     * @param npoints
261     *        the total number of points in the <code>Polygon2D</code>
262     * @exception NegativeArraySizeException
263     *            if the value of <code>npoints</code> is negative.
264     * @exception IndexOutOfBoundsException
265     *            if <code>npoints</code> is
266     *            greater than the length of <code>xpoints</code> or the length of <code>ypoints</code>.
267     * @exception NullPointerException
268     *            if <code>xpoints</code> or <code>ypoints</code> is <code>null</code>.
269     */
270    public Polygon2D(int[] xpoints, int[] ypoints, int npoints)
271    {
272        super();
273
274        if (npoints > xpoints.length || npoints > ypoints.length)
275            throw new IndexOutOfBoundsException("npoints > xpoints.length || npoints > ypoints.length");
276
277        this.npoints = npoints;
278        this.xpoints = new double[npoints];
279        this.ypoints = new double[npoints];
280
281        for (int i = 0; i < npoints; i++)
282        {
283            this.xpoints[i] = xpoints[i];
284            this.ypoints[i] = ypoints[i];
285        }
286
287        calculatePath();
288    }
289
290    public Polygon2D(List<Point2D> points)
291    {
292        super();
293
294        final int len = points.size();
295
296        this.npoints = len;
297        this.xpoints = new double[len];
298        this.ypoints = new double[len];
299
300        for (int i = 0; i < len; i++)
301        {
302            final Point2D pt = points.get(i);
303
304            this.xpoints[i] = pt.getX();
305            this.ypoints[i] = pt.getY();
306        }
307
308        calculatePath();
309    }
310
311    /**
312     * Resets this <code>Polygon</code> object to an empty polygon.
313     */
314    public void reset()
315    {
316        npoints = 0;
317        xpoints = new double[0];
318        ypoints = new double[0];
319        bounds = new Rectangle2D.Double();
320        path = null;
321        closedPath = null;
322    }
323
324    @Override
325    public Object clone()
326    {
327        Polygon2D pol = new Polygon2D();
328
329        for (int i = 0; i < npoints; i++)
330            pol.addPoint(xpoints[i], ypoints[i]);
331
332        return pol;
333    }
334
335    public void calculatePath()
336    {
337        path = new Path2D.Double();
338
339        path.moveTo(xpoints[0], ypoints[0]);
340        for (int i = 1; i < npoints; i++)
341            path.lineTo(xpoints[i], ypoints[i]);
342
343        bounds = path.getBounds2D();
344        closedPath = null;
345    }
346
347    protected void updatePath(double x, double y)
348    {
349        if (path == null)
350        {
351            path = new Path2D.Double(Path2D.WIND_EVEN_ODD);
352            path.moveTo(x, y);
353            bounds = new Rectangle2D.Double(x, y, 0, 0);
354        }
355        else
356        {
357            path.lineTo(x, y);
358
359            double _xmax = bounds.getMaxX();
360            double _ymax = bounds.getMaxY();
361            double _xmin = bounds.getMinX();
362            double _ymin = bounds.getMinY();
363
364            if (x < _xmin)
365                _xmin = x;
366            else if (x > _xmax)
367                _xmax = x;
368            if (y < _ymin)
369                _ymin = y;
370            else if (y > _ymax)
371                _ymax = y;
372
373            bounds = new Rectangle2D.Double(_xmin, _ymin, _xmax - _xmin, _ymax - _ymin);
374        }
375
376        closedPath = null;
377    }
378
379    /*
380     * get the associated {@link Polyline2D}.
381     */
382    public Polyline2D getPolyline2D()
383    {
384        Polyline2D pol = new Polyline2D(xpoints, ypoints, npoints);
385
386        pol.addPoint(xpoints[0], ypoints[0]);
387
388        return pol;
389    }
390
391    public Polygon getPolygon()
392    {
393        int[] _xpoints = new int[npoints];
394        int[] _ypoints = new int[npoints];
395
396        for (int i = 0; i < npoints; i++)
397        {
398            _xpoints[i] = (int) xpoints[i]; // todo maybe rounding is better ?
399            _ypoints[i] = (int) ypoints[i];
400        }
401
402        return new Polygon(_xpoints, _ypoints, npoints);
403    }
404
405    public void addPoint(Point2D p)
406    {
407        addPoint(p.getX(), p.getY());
408    }
409
410    /**
411     * Appends the specified coordinates to this <code>Polygon2D</code>.
412     * 
413     * @param x
414     *        the specified x coordinate
415     * @param y
416     *        the specified y coordinate
417     */
418    public void addPoint(double x, double y)
419    {
420        if (npoints == xpoints.length)
421        {
422            double[] tmp;
423
424            tmp = new double[(npoints * 2) + 1];
425            System.arraycopy(xpoints, 0, tmp, 0, npoints);
426            xpoints = tmp;
427
428            tmp = new double[(npoints * 2) + 1];
429            System.arraycopy(ypoints, 0, tmp, 0, npoints);
430            ypoints = tmp;
431        }
432
433        xpoints[npoints] = x;
434        ypoints[npoints] = y;
435        npoints++;
436
437        updatePath(x, y);
438    }
439
440    /**
441     * Determines whether the specified {@link Point} is inside this <code>Polygon</code>.
442     * 
443     * @param p
444     *        the specified <code>Point</code> to be tested
445     * @return <code>true</code> if the <code>Polygon</code> contains the <code>Point</code>; <code>false</code>
446     *         otherwise.
447     * @see #contains(double, double)
448     */
449    public boolean contains(Point p)
450    {
451        return contains(p.x, p.y);
452    }
453
454    /**
455     * Determines whether the specified coordinates are inside this <code>Polygon</code>.
456     * <p>
457     * 
458     * @param x
459     *        the specified x coordinate to be tested
460     * @param y
461     *        the specified y coordinate to be tested
462     * @return <code>true</code> if this <code>Polygon</code> contains
463     *         the specified coordinates, (<i>x</i>,&nbsp;<i>y</i>); <code>false</code> otherwise.
464     */
465    public boolean contains(int x, int y)
466    {
467        return contains((double) x, (double) y);
468    }
469
470    /**
471     * Returns the high precision bounding box of the {@link Shape}.
472     * 
473     * @return a {@link Rectangle2D} that precisely
474     *         bounds the <code>Shape</code>.
475     */
476    @Override
477    public Rectangle2D getBounds2D()
478    {
479        return (Rectangle2D) bounds.clone();
480    }
481
482    @Override
483    public Rectangle getBounds()
484    {
485        return bounds.getBounds();
486    }
487
488    /**
489     * Determines if the specified coordinates are inside this <code>Polygon</code>. For the definition of
490     * <i>insideness</i>, see the class comments of {@link Shape}.
491     * 
492     * @param x
493     *        the specified x coordinate
494     * @param y
495     *        the specified y coordinate
496     * @return <code>true</code> if the <code>Polygon</code> contains the
497     *         specified coordinates; <code>false</code> otherwise.
498     */
499    @Override
500    public boolean contains(double x, double y)
501    {
502        if (npoints <= 2 || !bounds.contains(x, y))
503            return false;
504
505        return updateComputingPath().contains(x, y);
506    }
507
508    protected Path2D.Double updateComputingPath()
509    {
510        Path2D.Double result = closedPath;
511
512        // need to recompute it ?
513        if (result == null)
514        {
515            if (path != null)
516            {
517                result = (Path2D.Double) path.clone();
518                result.closePath();
519            }
520            else
521                // empty path
522                result = new Path2D.Double();
523
524            closedPath = result;
525        }
526
527        return result;
528    }
529
530    /**
531     * Tests if a specified {@link Point2D} is inside the boundary of this <code>Polygon</code>.
532     * 
533     * @param p
534     *        a specified <code>Point2D</code>
535     * @return <code>true</code> if this <code>Polygon</code> contains the
536     *         specified <code>Point2D</code>; <code>false</code> otherwise.
537     * @see #contains(double, double)
538     */
539    @Override
540    public boolean contains(Point2D p)
541    {
542        return contains(p.getX(), p.getY());
543    }
544
545    /**
546     * Tests if the interior of this <code>Polygon</code> intersects the
547     * interior of a specified set of rectangular coordinates.
548     * 
549     * @param x
550     *        the x coordinate of the specified rectangular
551     *        shape's top-left corner
552     * @param y
553     *        the y coordinate of the specified rectangular
554     *        shape's top-left corner
555     * @param w
556     *        the width of the specified rectangular shape
557     * @param h
558     *        the height of the specified rectangular shape
559     * @return <code>true</code> if the interior of this <code>Polygon</code> and the interior of the
560     *         specified set of rectangular
561     *         coordinates intersect each other; <code>false</code> otherwise.
562     */
563    @Override
564    public boolean intersects(double x, double y, double w, double h)
565    {
566        if (npoints <= 0 || !bounds.intersects(x, y, w, h))
567            return false;
568
569        return updateComputingPath().intersects(x, y, w, h);
570    }
571
572    /**
573     * Tests if the interior of this <code>Polygon</code> intersects the
574     * interior of a specified <code>Rectangle2D</code>.
575     * 
576     * @param r
577     *        a specified <code>Rectangle2D</code>
578     * @return <code>true</code> if this <code>Polygon</code> and the
579     *         interior of the specified <code>Rectangle2D</code> intersect each other; <code>false</code> otherwise.
580     */
581    @Override
582    public boolean intersects(Rectangle2D r)
583    {
584        return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
585    }
586
587    /**
588     * Tests if the interior of this <code>Polygon</code> entirely
589     * contains the specified set of rectangular coordinates.
590     * 
591     * @param x
592     *        the x coordinate of the top-left corner of the
593     *        specified set of rectangular coordinates
594     * @param y
595     *        the y coordinate of the top-left corner of the
596     *        specified set of rectangular coordinates
597     * @param w
598     *        the width of the set of rectangular coordinates
599     * @param h
600     *        the height of the set of rectangular coordinates
601     * @return <code>true</code> if this <code>Polygon</code> entirely
602     *         contains the specified set of rectangular
603     *         coordinates; <code>false</code> otherwise.
604     */
605    @Override
606    public boolean contains(double x, double y, double w, double h)
607    {
608        if (npoints <= 0 || !bounds.intersects(x, y, w, h))
609            return false;
610
611        return updateComputingPath().contains(x, y, w, h);
612    }
613
614    /**
615     * Tests if the interior of this <code>Polygon</code> entirely
616     * contains the specified <code>Rectangle2D</code>.
617     * 
618     * @param r
619     *        the specified <code>Rectangle2D</code>
620     * @return <code>true</code> if this <code>Polygon</code> entirely
621     *         contains the specified <code>Rectangle2D</code>; <code>false</code> otherwise.
622     * @see #contains(double, double, double, double)
623     */
624    @Override
625    public boolean contains(Rectangle2D r)
626    {
627        return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
628    }
629
630    /**
631     * Returns an iterator object that iterates along the boundary of this <code>Polygon</code> and provides access to
632     * the geometry of the outline of this <code>Polygon</code>. An optional {@link AffineTransform} can be specified so
633     * that the coordinates returned in the iteration are transformed accordingly.
634     * 
635     * @param at
636     *        an optional <code>AffineTransform</code> to be applied to the
637     *        coordinates as they are returned in the iteration, or <code>null</code> if untransformed coordinates are
638     *        desired
639     * @return a {@link PathIterator} object that provides access to the
640     *         geometry of this <code>Polygon</code>.
641     */
642    @Override
643    public PathIterator getPathIterator(AffineTransform at)
644    {
645        return updateComputingPath().getPathIterator(at);
646    }
647
648    /**
649     * Returns an iterator object that iterates along the boundary of
650     * the <code>Polygon2D</code> and provides access to the geometry of the
651     * outline of the <code>Shape</code>. Only SEG_MOVETO, SEG_LINETO, and
652     * SEG_CLOSE point types are returned by the iterator.
653     * Since polygons are already flat, the <code>flatness</code> parameter
654     * is ignored.
655     * 
656     * @param at
657     *        an optional <code>AffineTransform</code> to be applied to the
658     *        coordinates as they are returned in the iteration, or <code>null</code> if untransformed coordinates are
659     *        desired
660     * @param flatness
661     *        the maximum amount that the control points
662     *        for a given curve can vary from colinear before a subdivided
663     *        curve is replaced by a straight line connecting the
664     *        endpoints. Since polygons are already flat the <code>flatness</code> parameter is ignored.
665     * @return a <code>PathIterator</code> object that provides access to the <code>Shape</code> object's geometry.
666     */
667    @Override
668    public PathIterator getPathIterator(AffineTransform at, double flatness)
669    {
670        return getPathIterator(at);
671    }
672}