001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.math.geometry.shape;
031
032import java.util.ArrayList;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Iterator;
036import java.util.List;
037
038import org.openimaj.citation.annotation.Reference;
039import org.openimaj.citation.annotation.ReferenceType;
040import org.openimaj.math.geometry.line.Line2d;
041import org.openimaj.math.geometry.point.Point2d;
042import org.openimaj.math.geometry.point.Point2dImpl;
043import org.openimaj.math.geometry.point.PointList;
044import org.openimaj.math.geometry.shape.util.PolygonUtils;
045import org.openimaj.math.geometry.shape.util.RotatingCalipers;
046
047import Jama.Matrix;
048
049/**
050 * A polygon, modelled as a list of vertices. Polygon extends {@link PointList},
051 * so the vertices are the underlying {@link PointList#points}, and they are
052 * considered to be joined in order.
053 *
054 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
055 */
056public class Polygon extends PointList implements Shape {
057        /**
058         * Polygons can contain other polygons. If the polygon is representing a
059         * shape, then the inner polygons can represent holes in the polygon or
060         * other polygons within the polygon.
061         */
062        private List<Polygon> innerPolygons = new ArrayList<Polygon>();
063
064        /** If this polygon is a hole within another polygon, this is set to true */
065        private boolean isHole = false;
066
067        /**
068         * Constructs an empty polygon to which vertices may be added.
069         */
070        public Polygon() {
071                this(false);
072        }
073
074        /**
075         * Constructs an empty polygon to which vertices may be added. The boolean
076         * parameter determines whether this polygon will represent a hole (rather
077         * than a solid).
078         *
079         * @param representsHole
080         *            Whether the polygon will represent a hole.
081         */
082        public Polygon(boolean representsHole) {
083                this.isHole = representsHole;
084        }
085
086        /**
087         * Construct a Polygon from vertices
088         *
089         * @param vertices
090         *            the vertices
091         */
092        public Polygon(Point2d... vertices) {
093                super(vertices);
094        }
095
096        /**
097         * Construct a Polygon from vertices
098         *
099         * @param vertices
100         *            the vertices
101         */
102        public Polygon(Collection<? extends Point2d> vertices) {
103                this(vertices, false);
104        }
105
106        /**
107         * Construct a Polygon from the vertices, possibly copying the vertices
108         * first
109         *
110         * @param vertices
111         *            the vertices
112         * @param copy
113         *            should the vertices be copied
114         */
115        public Polygon(Collection<? extends Point2d> vertices, boolean copy) {
116                super(vertices, copy);
117        }
118
119        /**
120         * Get the vertices of the polygon
121         *
122         * @return the vertices
123         */
124        public List<Point2d> getVertices() {
125                return points;
126        }
127
128        /**
129         * Get the number of vertices
130         *
131         * @return the number of vertices
132         */
133        public int nVertices() {
134                if (isClosed())
135                        return points.size() - 1;
136                return points.size();
137        }
138
139        /**
140         * Is the polygon closed (i.e. is the last vertex equal to the first)?
141         *
142         * @return true if closed; false if open
143         */
144        public boolean isClosed() {
145                if (points.size() > 0 && points.get(0).getX() == points.get(points.size() - 1).getX()
146                                && points.get(0).getY() == points.get(points.size() - 1).getY())
147                        return true;
148                return false;
149        }
150
151        /**
152         * Close the polygon if it's not already closed
153         */
154        public void close() {
155                if (!isClosed() && points.size() > 0)
156                        points.add(points.get(0));
157        }
158
159        /**
160         * Open the polygon if it's closed
161         */
162        public void open() {
163                if (isClosed() && points.size() > 1)
164                        points.remove(points.size() - 1);
165        }
166
167        /**
168         * Test whether the point p is inside the polygon using the winding rule
169         * algorithm. Also tests whether the point is in any of the inner polygons.
170         * If the inner polygon represents a hole and the point is within that
171         * polygon then the point is not within this polygon.
172         *
173         * @param point
174         *            the point to test
175         * @return true if the point is inside; false otherwise
176         */
177        @Override
178        public boolean isInside(Point2d point) {
179                final boolean isClosed = isClosed();
180                if (!isClosed)
181                        close();
182
183                boolean isOdd = false;
184
185                final float px = point.getX();
186                final float py = point.getY();
187
188                for (int pp = 0; pp < getNumInnerPoly(); pp++) {
189                        final List<Point2d> v = getInnerPoly(pp).getVertices();
190                        int j = v.size() - 1;
191                        float vjx = v.get(j).getX();
192                        float vjy = v.get(j).getY();
193
194                        for (int i = 0; i < v.size(); i++) {
195                                final float vix = v.get(i).getX();
196                                final float viy = v.get(i).getY();
197
198                                if (viy < py && vjy >= py ||
199                                                vjy < py && viy >= py)
200                                {
201                                        if (vix + (py - viy) / (vjy - viy) * (vjx - vix) < px) {
202                                                isOdd = !isOdd;
203                                        }
204                                }
205
206                                j = i;
207                                vjx = vix;
208                                vjy = viy;
209                        }
210                }
211
212                if (!isClosed)
213                        open();
214                return isOdd;
215        }
216
217        /**
218         * {@inheritDoc}
219         */
220        @Override
221        public Polygon clone() {
222                final Polygon clone = new Polygon();
223                clone.setIsHole(isHole);
224
225                for (final Point2d p : points)
226                        clone.points.add(p.copy());
227
228                for (final Polygon innerPoly : innerPolygons)
229                        clone.addInnerPolygon(innerPoly.clone());
230
231                return clone;
232        }
233
234        /**
235         * Calculates the difference between two polygons and returns a new polygon.
236         * It assumes that the given polygon and this polygon have the same number
237         * of vertices.
238         *
239         * @param p
240         *            the polygon to subtract.
241         * @return the difference polygon
242         */
243        public Polygon difference(Polygon p) {
244                final List<Point2d> v = new ArrayList<Point2d>();
245
246                for (int i = 0; i < nVertices(); i++)
247                        v.add(new Point2dImpl(
248                                        points.get(i).getX() - p.points.get(i).getX(),
249                                        points.get(i).getY() - p.points.get(i).getY()));
250
251                final Polygon p2 = new Polygon(v);
252                for (int i = 0; i < innerPolygons.size(); i++)
253                        p2.addInnerPolygon(innerPolygons.get(i).difference(
254                                        p2.getInnerPoly(i + 1)));
255
256                return p2;
257        }
258
259        @Override
260        public double calculateArea() {
261                return Math.abs(calculateSignedArea());
262        }
263
264        /**
265         * Calculate the area of the polygon. This does not take into account holes
266         * in the polygon.
267         *
268         * @return the area of the polygon
269         */
270        public double calculateSignedArea() {
271                final boolean closed = isClosed();
272                double area = 0;
273
274                if (!closed)
275                        close();
276
277                // TODO: This does not take into account the winding
278                // rule and therefore holes
279                for (int k = 0; k < points.size() - 1; k++) {
280                        final float ik = points.get(k).getX();
281                        final float jk = points.get(k).getY();
282                        final float ik1 = points.get(k + 1).getX();
283                        final float jk1 = points.get(k + 1).getY();
284
285                        area += ik * jk1 - ik1 * jk;
286                }
287
288                if (!closed)
289                        open();
290
291                return 0.5 * area;
292        }
293
294        /**
295         * Calls {@link Polygon#intersectionArea(Shape, int)} with 1 step per pixel
296         * dimension. Subsequently this function returns the shared whole pixels of
297         * this polygon and that.
298         *
299         * @param that
300         * @return intersection area
301         */
302        @Override
303        public double intersectionArea(Shape that) {
304                return this.intersectionArea(that, 1);
305        }
306
307        /**
308         * Return an estimate for the area of the intersection of this polygon and
309         * another polygon. For each pixel step 1 is added if the point is inside
310         * both polygons. For each pixel, perPixelPerDimension steps are taken.
311         * Subsequently the intersection is:
312         *
313         * sumIntersections / (perPixelPerDimension * perPixelPerDimension)
314         *
315         * @param that
316         * @return normalised intersection area
317         */
318        @Override
319        public double intersectionArea(Shape that, int nStepsPerDimension) {
320                final Rectangle overlapping = this.calculateRegularBoundingBox().overlapping(that.calculateRegularBoundingBox());
321                if (overlapping == null)
322                        return 0;
323                double intersection = 0;
324                final double step = Math.max(overlapping.width, overlapping.height) / (double) nStepsPerDimension;
325                double nReads = 0;
326                for (float x = overlapping.x; x < overlapping.x + overlapping.width; x += step) {
327                        for (float y = overlapping.y; y < overlapping.y + overlapping.height; y += step) {
328                                final boolean insideThis = this.isInside(new Point2dImpl(x, y));
329                                final boolean insideThat = that.isInside(new Point2dImpl(x, y));
330                                nReads++;
331                                if (insideThis && insideThat) {
332                                        intersection++;
333                                }
334                        }
335                }
336
337                return (intersection / nReads) * (overlapping.width * overlapping.height);
338        }
339
340        /**
341         * {@inheritDoc}
342         *
343         * @see org.openimaj.math.geometry.shape.Shape#asPolygon()
344         */
345        @Override
346        public Polygon asPolygon() {
347                return this;
348        }
349
350        /**
351         * Add a vertex to the polygon
352         *
353         * @param x
354         *            x-coordinate of the vertex
355         * @param y
356         *            y-coordinate of the vertex
357         */
358        public void addVertex(float x, float y) {
359                points.add(new Point2dImpl(x, y));
360        }
361
362        /**
363         * Add a vertex to the polygon
364         *
365         * @param pt
366         *            coordinate of the vertex
367         */
368        public void addVertex(Point2d pt) {
369                points.add(pt);
370        }
371
372        /**
373         * Iterates through the vertices and rounds all vertices to round integers.
374         * Side-affects this polygon.
375         *
376         * @return this polygon
377         */
378        public Polygon roundVertices() {
379                final Iterator<Point2d> i = this.iterator();
380                while (i.hasNext()) {
381                        final Point2d p = i.next();
382                        p.setX(Math.round(p.getX()));
383                        p.setY(Math.round(p.getY()));
384                }
385
386                for (final Polygon pp : innerPolygons)
387                        pp.roundVertices();
388
389                return this;
390        }
391
392        /**
393         * Return whether this polygon has no vertices or not.
394         *
395         * @return TRUE if this polygon has no vertices
396         */
397        public boolean isEmpty() {
398                return this.points.isEmpty() && innerPolygons.isEmpty();
399        }
400
401        /**
402         * Returns the number of inner polygons in this polygon including this
403         * polygon.
404         *
405         * @return the number of inner polygons in this polygon.
406         */
407        public int getNumInnerPoly() {
408                return innerPolygons.size() + 1;
409        }
410
411        /**
412         * Get the inner polygon at the given index. Note that index 0 will return
413         * this polygon, while index i+1 will return the inner polygon i.
414         *
415         * @param index
416         *            the index of the polygon to get
417         * @return The inner polygon at the given index.
418         */
419        public Polygon getInnerPoly(int index) {
420                if (index == 0)
421                        return this;
422                return innerPolygons.get(index - 1);
423        }
424
425        /**
426         * Add an inner polygon to this polygon. If there is no main polygon defined
427         * (the number of vertices is zero) then the given inner polygon will become
428         * the main polygon if the <code>inferOuter</code> boolean is true. If this
429         * variable is false, the inner polygon will be added to the list of inner
430         * polygons for this polygon regardless of whether a main polygon exists.
431         * When the main polygon is inferred from the given polygon, the vertices
432         * are copied into this polygon's vertex list.
433         *
434         * @param p
435         *            The inner polygon to add
436         * @param inferOuter
437         *            Whether to infer the outer polygon from this inner one
438         */
439        public void addInnerPolygon(Polygon p, boolean inferOuter) {
440                if (!inferOuter) {
441                        this.innerPolygons.add(p);
442                } else {
443                        if (this.points.size() == 0) {
444                                this.points.addAll(p.points);
445                                this.isHole = p.isHole;
446                        } else {
447                                this.addInnerPolygon(p, false);
448                        }
449                }
450        }
451
452        /**
453         * Add an inner polygon to this polygon. If there is no main polygon defined
454         * (the number of vertices is zero) then the given inner polygon will become
455         * the main polygon.
456         *
457         * @param p
458         *            The inner polygon to add
459         */
460        public void addInnerPolygon(Polygon p) {
461                this.addInnerPolygon(p, true);
462        }
463
464        /**
465         * Returns the list of inner polygons.
466         *
467         * @return the list of inner polygons
468         */
469        public List<Polygon> getInnerPolys() {
470                return this.innerPolygons;
471        }
472
473        /**
474         * Set whether this polygon represents a hole in another polygon.
475         *
476         * @param isHole
477         *            Whether this polygon is a whole.
478         */
479        public void setIsHole(boolean isHole) {
480                this.isHole = isHole;
481        }
482
483        /**
484         * Returns whether this polygon is representing a hole in another polygon.
485         *
486         * @return Whether this polygon is representing a hole in another polygon.
487         */
488        public boolean isHole() {
489                return this.isHole;
490        }
491
492        /**
493         * {@inheritDoc}
494         *
495         * @see java.lang.Object#equals(java.lang.Object)
496         */
497        @Override
498        public boolean equals(Object obj) {
499                return (obj instanceof Polygon) &&
500                                this.equals((Polygon) obj);
501        }
502
503        /**
504         * Specific equals method for polygons where the polgyons are tested for the
505         * vertices being in the same order. If the vertices are not in the vertex
506         * list in the same manner but are in the same order (when wrapped around)
507         * the method will return true. So the triangles below will return true:
508         *
509         * {[1,1],[2,2],[1,2]} and {[1,2],[1,1],[2,2]}
510         *
511         * @param p
512         *            The polygon to test against
513         * @return TRUE if the polygons are the same.
514         */
515        public boolean equals(Polygon p) {
516                if (isHole() != p.isHole())
517                        return false;
518                if (this.points.size() != p.points.size())
519                        return false;
520                if (this.isEmpty() && p.isEmpty())
521                        return true;
522
523                final int i = this.points.indexOf(p.points.get(0));
524                if (i == -1)
525                        return false;
526
527                final int s = this.points.size();
528                for (int n = 0; n < s; n++) {
529                        if (!p.points.get(n).equals(this.points.get((n + i) % s)))
530                                return false;
531                }
532
533                return true;
534        }
535
536        /**
537         * {@inheritDoc}
538         *
539         * @see java.lang.Object#hashCode()
540         */
541        @Override
542        public int hashCode() {
543                return points.hashCode() * (isHole() ? -1 : 1);
544        }
545
546        /**
547         * Displays the complete list of vertices unless the number of vertices is
548         * greater than 10 - then a sublist is shown of 5 from the start and 5 from
549         * the end separated by ellipses.
550         *
551         * {@inheritDoc}
552         *
553         * @see java.lang.Object#toString()
554         */
555        @Override
556        public String toString() {
557                final StringBuffer sb = new StringBuffer();
558                if (isHole())
559                        sb.append("H");
560                final int len = 10;
561                if (points.size() < len)
562                        sb.append(points.toString());
563                else
564                        sb.append(points.subList(0, len / 2).toString() + "..." +
565                                        points.subList(points.size() - len / 2, points.size())
566                                                        .toString());
567
568                if (innerPolygons.size() > 0) {
569                        sb.append("\n    - " + innerPolygons.size() + " inner polygons:");
570                        for (final Polygon ip : innerPolygons)
571                                sb.append("\n       + " + ip.toString());
572                }
573
574                return sb.toString();
575        }
576
577        /**
578         * Returns the intersection of this polygon and the given polygon.
579         *
580         * @param p2
581         *            The polygon to intersect with.
582         * @return The resulting polygon intersection
583         */
584        public Polygon intersect(Polygon p2) {
585                // FIXME: PolygonUtils should handle inner polys itself, but it seems
586                // there are bugs... This is a hack to work around those problems
587                // without having to understand the ins and outs of how the GPC works,
588                // but it should really be fixed properly in the future!
589                final Polygon clone = new PolygonUtils().intersection(new Polygon(this.points), p2);
590                clone.setIsHole(isHole);
591
592                for (final Polygon innerPoly : innerPolygons)
593                        clone.addInnerPolygon(innerPoly.intersect(p2));
594
595                return clone;
596        }
597
598        /**
599         * Returns the union of this polygon and the given polygon.
600         *
601         * @param p2
602         *            The polygon to union with.
603         * @return The resulting polygon union
604         */
605        public Polygon union(Polygon p2) {
606                return new PolygonUtils().union(this, p2);
607        }
608
609        /**
610         * Returns the XOR of this polygon and the given polygon.
611         *
612         * @param p2
613         *            The polygon to XOR with.
614         * @return The resulting polygon
615         */
616        public Polygon xor(Polygon p2) {
617                return new PolygonUtils().xor(this, p2);
618        }
619
620        /**
621         * Reduce the number of vertices in a polygon. This implementation is based
622         * on a modified Ramer-Douglas–Peucker algorithm that is designed to work
623         * with polygons rather than polylines. The implementation searches for two
624         * initialisation points that are approximatatly maximally distant, then
625         * performs the polyline algorithm on the two segments, and finally ensures
626         * that the joins in the segments are consistent with the given epsilon
627         * value.
628         *
629         * @param eps
630         *            distance value below which a vertex can be ignored
631         * @return new polygon that approximates this polygon, but with fewer
632         *         vertices
633         */
634        public Polygon reduceVertices(double eps) {
635                if (eps == 0 || nVertices() <= 3)
636                        return this.clone();
637
638                int prevStartIndex = 0;
639                int startIndex = 0;
640                for (int init = 0; init < 3; init++) {
641                        double dmax = 0;
642                        prevStartIndex = startIndex;
643
644                        final Point2d first = points.get(startIndex);
645                        for (int i = 0; i < points.size(); i++) {
646                                final double d;
647                                d = Line2d.distance(first, points.get(i));
648
649                                if (d > dmax) {
650                                        startIndex = i;
651                                        dmax = d;
652                                }
653                        }
654                }
655
656                if (prevStartIndex > startIndex) {
657                        final int tmp = prevStartIndex;
658                        prevStartIndex = startIndex;
659                        startIndex = tmp;
660                }
661
662                final List<Point2d> l1 = new ArrayList<Point2d>();
663                l1.addAll(points.subList(prevStartIndex, startIndex + 1));
664
665                final List<Point2d> l2 = new ArrayList<Point2d>();
666                l2.addAll(points.subList(startIndex, points.size()));
667                l2.addAll(points.subList(0, prevStartIndex + 1));
668
669                final Polygon newPoly = new Polygon();
670                final List<Point2d> line1 = ramerDouglasPeucker(l1, eps);
671                final List<Point2d> line2 = ramerDouglasPeucker(l2, eps);
672                newPoly.points.addAll(line1.subList(0, line1.size() - 1));
673                newPoly.points.addAll(line2.subList(0, line2.size() - 1));
674
675                // deal with the joins
676                // if (newPoly.nVertices() > 3) {
677                // Point2d r1 = null, r2 = null;
678                // Point2d p0 = newPoly.points.get(newPoly.points.size() - 1);
679                // Point2d p1 = newPoly.points.get(0);
680                // Point2d p2 = newPoly.points.get(1);
681                //
682                // Line2d l = new Line2d(p0, p2);
683                // Line2d norm = l.getNormal(p1);
684                // Point2d intersect = l.getIntersection(norm).intersectionPoint;
685                // if (intersect != null && Line2d.distance(p1, intersect) <= eps) {
686                // // remove p1
687                // r1 = p1;
688                // }
689                //
690                // p0 = newPoly.points.get(line1.size() - 1);
691                // p1 = newPoly.points.get(line1.size());
692                // p2 = newPoly.points.get((line1.size() + 1) >= newPoly.size() ? 0 :
693                // (line1.size() + 1));
694                //
695                // l = new Line2d(p0, p2);
696                // norm = l.getNormal(p1);
697                // intersect = l.getIntersection(norm).intersectionPoint;
698                // if (intersect != null && Line2d.distance(p1, intersect) <= eps) {
699                // // remove p2
700                // r2 = p2;
701                // }
702                //
703                // if (r1 != null) {
704                // newPoly.points.remove(r1);
705                // }
706                // if (newPoly.nVertices() > 3 && r2 != null) {
707                // newPoly.points.remove(r2);
708                // }
709                // }
710
711                if (!newPoly.isClockwise()) {
712                        Collections.reverse(newPoly.points);
713                }
714
715                for (final Polygon ppp : innerPolygons)
716                        newPoly.addInnerPolygon(ppp.reduceVertices(eps));
717
718                return newPoly;
719        }
720
721        /**
722         * Ramer Douglas Peucker polyline algorithm
723         *
724         * @param p
725         *            the polyline to simplify
726         * @param eps
727         *            distance value below which a vertex can be ignored
728         * @return the simplified polyline
729         */
730        private static List<Point2d> ramerDouglasPeucker(List<Point2d> p, double eps) {
731                // Find the point with the maximum distance
732                double dmax = 0;
733                int index = 0;
734                final int end = p.size() - 1;
735                final Line2d l = new Line2d(p.get(0), p.get(end));
736                for (int i = 1; i < end - 1; i++) {
737                        final double d;
738
739                        final Point2d p1 = p.get(i);
740                        final Line2d norm = l.getNormal(p1);
741                        final Point2d p2 = l.getIntersection(norm).intersectionPoint;
742                        if (p2 == null)
743                                continue;
744                        d = Line2d.distance(p1, p2);
745
746                        if (d > dmax) {
747                                index = i;
748                                dmax = d;
749                        }
750                }
751
752                final List<Point2d> newPoly = new ArrayList<Point2d>();
753
754                // If max distance is greater than epsilon, recursively simplify
755                if (dmax > eps) {
756                        // Recursively call RDP
757                        final List<Point2d> line1 = ramerDouglasPeucker(p.subList(0, index + 1), eps);
758                        final List<Point2d> line2 = ramerDouglasPeucker(p.subList(index, end + 1), eps);
759                        newPoly.addAll(line1.subList(0, line1.size() - 1));
760                        newPoly.addAll(line2);
761                } else {
762                        newPoly.add(p.get(0));
763                        newPoly.add(p.get(end));
764                }
765
766                // Return the result
767                return newPoly;
768        }
769
770        /**
771         * Apply a 3x3 transform matrix to a copy of the polygon and return it
772         *
773         * @param transform
774         *            3x3 transform matrix
775         * @return the transformed polygon
776         */
777        @Override
778        public Polygon transform(Matrix transform) {
779                final List<Point2d> newVertices = new ArrayList<Point2d>();
780
781                for (final Point2d p : points) {
782                        final Matrix p1 = new Matrix(3, 1);
783                        p1.set(0, 0, p.getX());
784                        p1.set(1, 0, p.getY());
785                        p1.set(2, 0, 1);
786
787                        final Matrix p2_est = transform.times(p1);
788
789                        final Point2d out = new Point2dImpl((float) (p2_est.get(0, 0) / p2_est.get(2, 0)),
790                                        (float) (p2_est.get(1, 0) / p2_est.get(2, 0)));
791
792                        newVertices.add(out);
793                }
794
795                final Polygon p = new Polygon(newVertices);
796                for (final Polygon pp : innerPolygons)
797                        p.addInnerPolygon(pp.transform(transform));
798                return p;
799        }
800
801        /**
802         * Compute the regular (oriented to the axes) bounding box of the polygon.
803         *
804         * @return the regular bounding box as [x,y,width,height]
805         */
806        @Override
807        public Rectangle calculateRegularBoundingBox() {
808                float xmin = Float.MAX_VALUE, xmax = -Float.MAX_VALUE, ymin = Float.MAX_VALUE, ymax = -Float.MAX_VALUE;
809
810                for (int pp = 0; pp < getNumInnerPoly(); pp++) {
811                        final Polygon ppp = getInnerPoly(pp);
812                        for (int i = 0; i < ppp.nVertices(); i++) {
813                                final Point2d p = ppp.points.get(i);
814                                final float px = p.getX();
815                                final float py = p.getY();
816                                if (px < xmin)
817                                        xmin = px;
818                                if (px > xmax)
819                                        xmax = px;
820                                if (py < ymin)
821                                        ymin = py;
822                                if (py > ymax)
823                                        ymax = py;
824                        }
825                }
826
827                return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
828        }
829
830        /**
831         * Translate the polygons position
832         *
833         * @param x
834         *            x-translation
835         * @param y
836         *            y-translation
837         */
838        @Override
839        public void translate(float x, float y) {
840                for (int pp = 0; pp < getNumInnerPoly(); pp++) {
841                        final Polygon ppp = getInnerPoly(pp);
842                        for (int i = 0; i < ppp.nVertices(); i++) {
843                                final Point2d p = ppp.points.get(i);
844                                p.setX(p.getX() + x);
845                                p.setY(p.getY() + y);
846                        }
847                }
848        }
849
850        /**
851         * Scale the polygon by the given amount about (0,0). Scalefactors between 0
852         * and 1 shrink the polygon.
853         *
854         * @param sc
855         *            the scale factor.
856         */
857        @Override
858        public void scale(float sc) {
859                for (int pp = 0; pp < getNumInnerPoly(); pp++) {
860                        final Polygon ppp = getInnerPoly(pp);
861                        for (int i = 0; i < ppp.nVertices(); i++) {
862                                final Point2d p = ppp.points.get(i);
863                                p.setX(p.getX() * sc);
864                                p.setY(p.getY() * sc);
865                        }
866                }
867        }
868
869        /**
870         * Scale the polygon only in the x-direction by the given amount about
871         * (0,0). Scale factors between 0 and 1 will shrink the polygon
872         *
873         * @param sc
874         *            The scale factor
875         * @return this polygon
876         */
877        @Override
878        public Polygon scaleX(float sc) {
879                for (int pp = 0; pp < getNumInnerPoly(); pp++) {
880                        final Polygon ppp = getInnerPoly(pp);
881                        for (int i = 0; i < ppp.nVertices(); i++) {
882                                final Point2d p = ppp.points.get(i);
883                                p.setX(p.getX() * sc);
884                        }
885                }
886                return this;
887        }
888
889        /**
890         * Scale the polygon only in the y-direction by the given amount about
891         * (0,0). Scale factors between 0 and 1 will shrink the polygon
892         *
893         * @param sc
894         *            The scale factor
895         * @return this polygon
896         */
897        @Override
898        public Polygon scaleY(float sc) {
899                for (int pp = 0; pp < getNumInnerPoly(); pp++) {
900                        final Polygon ppp = getInnerPoly(pp);
901                        for (int i = 0; i < ppp.nVertices(); i++) {
902                                final Point2d p = ppp.points.get(i);
903                                p.setY(p.getY() * sc);
904                        }
905                }
906                return this;
907        }
908
909        /**
910         * Scale the polygon by the given amount about (0,0). Scale factors between
911         * 0 and 1 shrink the polygon.
912         *
913         * @param scx
914         *            the scale factor in the x direction
915         * @param scy
916         *            the scale factor in the y direction.
917         * @return this polygon
918         */
919        @Override
920        public Polygon scaleXY(float scx, float scy) {
921                for (int pp = 0; pp < getNumInnerPoly(); pp++) {
922                        final Polygon ppp = getInnerPoly(pp);
923                        for (int i = 0; i < ppp.nVertices(); i++) {
924                                final Point2d p = ppp.points.get(i);
925                                p.setX(p.getX() * scx);
926                                p.setY(p.getY() * scy);
927                        }
928                }
929                return this;
930        }
931
932        /**
933         * Scale the polygon by the given amount about the given point. Scalefactors
934         * between 0 and 1 shrink the polygon.
935         *
936         * @param centre
937         *            the centre of the scaling operation
938         * @param sc
939         *            the scale factor
940         */
941        @Override
942        public void scale(Point2d centre, float sc) {
943                this.translate(-centre.getX(), -centre.getY());
944                for (int pp = 0; pp < getNumInnerPoly(); pp++) {
945                        final Polygon ppp = getInnerPoly(pp);
946                        for (int i = 0; i < ppp.nVertices(); i++) {
947                                final Point2d p = ppp.points.get(i);
948                                p.setX(p.getX() * sc);
949                                p.setY(p.getY() * sc);
950                        }
951                }
952                this.translate(centre.getX(), centre.getY());
953        }
954
955        /**
956         * Calculate the centroid of the polygon.
957         *
958         * @return calls {@link #calculateFirstMoment()};
959         */
960        @Override
961        public Point2d calculateCentroid() {
962                final double[] pt = calculateFirstMoment();
963                return new Point2dImpl((float) pt[0], (float) pt[1]);
964        }
965
966        /**
967         * Treating the polygon as a continuous piecewise function, calculate
968         * exactly the first moment. This follows working presented by Carsten
969         * Steger in "On the Calculation of Moments of Polygons" ,
970         *
971         * @return the first moment
972         */
973        @Reference(
974                        author = { "Carsten Steger" },
975                        title = "On the Calculation of Moments of Polygons",
976                        type = ReferenceType.Techreport,
977                        month = "August",
978                        year = "1996",
979                        url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.29.8765&rep=rep1&type=pdf")
980        public double[] calculateFirstMoment() {
981                final boolean closed = isClosed();
982
983                if (!closed)
984                        close();
985
986                double area = 0;
987                double ax = 0;
988                double ay = 0;
989                // TODO: This does not take into account the winding
990                // rule and therefore holes
991                for (int k = 0; k < points.size() - 1; k++) {
992                        final float xk = points.get(k).getX();
993                        final float yk = points.get(k).getY();
994                        final float xk1 = points.get(k + 1).getX();
995                        final float yk1 = points.get(k + 1).getY();
996
997                        final float shared = xk * yk1 - xk1 * yk;
998                        area += shared;
999                        ax += (xk + xk1) * shared;
1000                        ay += (yk + yk1) * shared;
1001                }
1002
1003                if (!closed)
1004                        open();
1005
1006                area *= 0.5;
1007
1008                return new double[] { ax / (6 * area), ay / (6 * area) };
1009        }
1010
1011        /**
1012         * Treating the polygon as a continuous piecewise function, calculate
1013         * exactly the second moment. This follows working presented by Carsten
1014         * Steger in "On the Calculation of Moments of Polygons" ,
1015         *
1016         * @return the second moment as an array with values: (axx,axy,ayy)
1017         */
1018        @Reference(
1019                        author = { "Carsten Steger" },
1020                        title = "On the Calculation of Moments of Polygons",
1021                        type = ReferenceType.Techreport,
1022                        month = "August",
1023                        year = "1996",
1024                        url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.29.8765&rep=rep1&type=pdf")
1025        public double[] calculateSecondMoment() {
1026                final boolean closed = isClosed();
1027                final double area = calculateSignedArea();
1028
1029                if (!closed)
1030                        close();
1031
1032                double axx = 0;
1033                double ayy = 0;
1034                double axy = 0;
1035                // TODO: This does not take into account the winding
1036                // rule and therefore holes
1037                for (int k = 0; k < points.size() - 1; k++) {
1038                        final float xk = points.get(k).getX();
1039                        final float yk = points.get(k).getY();
1040                        final float xk1 = points.get(k + 1).getX();
1041                        final float yk1 = points.get(k + 1).getY();
1042
1043                        final float shared = xk * yk1 - xk1 * yk;
1044                        axx += (xk * xk + xk * xk1 + xk1 * xk1) * shared;
1045                        ayy += (yk * yk + yk * yk1 + yk1 * yk1) * shared;
1046                        axy += (2 * xk * yk + xk * yk1 + xk1 * yk + 2 * xk1 * yk1) * shared;
1047                }
1048
1049                if (!closed)
1050                        open();
1051
1052                return new double[] {
1053                                axx / (12 * area),
1054                                axy / (24 * area),
1055                                ayy / (12 * area)
1056                };
1057        }
1058
1059        /**
1060         * Treating the polygon as a continuous piecewise function, calculate
1061         * exactly the centralised second moment. This follows working presented by
1062         * Carsten Steger in "On the Calculation of Moments of Polygons" ,
1063         *
1064         * @return the second moment as an array with values: (axx,axy,ayy)
1065         */
1066        @Reference(
1067                        author = { "Carsten Steger" },
1068                        title = "On the Calculation of Moments of Polygons",
1069                        type = ReferenceType.Techreport,
1070                        month = "August",
1071                        year = "1996",
1072                        url = "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.29.8765&rep=rep1&type=pdf")
1073        public double[] calculateSecondMomentCentralised() {
1074                final double[] firstMoment = this.calculateFirstMoment();
1075                final double[] secondMoment = this.calculateSecondMoment();
1076
1077                return new double[] {
1078                                secondMoment[0] - firstMoment[0] * firstMoment[0],
1079                                secondMoment[1] - firstMoment[0] * firstMoment[1],
1080                                secondMoment[2] - firstMoment[1] * firstMoment[1],
1081                };
1082
1083        }
1084
1085        /**
1086         * Calculates the principle direction of the connected component. This is
1087         * given by
1088         * <code>0.5 * atan( (M<sub>20</sub>-M<sub>02</sub>) / 2 * M<sub>11</sub> )</code>
1089         * so results in an angle between -PI and +PI.
1090         *
1091         * @return The principle direction (-PI/2 to +PI/2 radians) of the connected
1092         *         component.
1093         */
1094        public double calculateDirection() {
1095                final double[] secondMoment = calculateSecondMomentCentralised();
1096                final double u20 = secondMoment[0];
1097                final double u02 = secondMoment[1];
1098                final double u11 = secondMoment[2];
1099                final double theta = 0.5 * Math.atan2((2 * u11), (u20 - u02));
1100
1101                return theta;
1102        }
1103
1104        /**
1105         * Using
1106         * {@link EllipseUtilities#ellipseFromCovariance(float, float, Matrix, float)}
1107         * and the {@link #calculateSecondMomentCentralised()} return the Ellipse
1108         * best fitting the shape of this polygon.
1109         *
1110         * @return {@link Ellipse} defining the shape of the polygon
1111         */
1112        public Ellipse toEllipse() {
1113                final double[] secondMoment = calculateSecondMomentCentralised();
1114                final double u20 = secondMoment[0];
1115                final double u11 = secondMoment[1];
1116                final double u02 = secondMoment[2];
1117                final Point2d center = calculateCentroid();
1118                final Matrix sm = new Matrix(new double[][] {
1119                                new double[] { u20, u11 },
1120                                new double[] { u11, u02 },
1121                });
1122                // Used the sqrt(3) as the scale, not sure why. This is not correct.
1123                // Find the correct value!
1124                return EllipseUtilities.ellipseFromCovariance(
1125                                center.getX(),
1126                                center.getY(),
1127                                sm,
1128                                (float) Math.sqrt(3));
1129        }
1130
1131        /**
1132         * Test if the outer polygon is convex.
1133         *
1134         * @return true if the outer polygon is convex; false if non-convex or less
1135         *         than two vertices
1136         */
1137        @Override
1138        public boolean isConvex() {
1139                final boolean isOriginallyClosed = this.isClosed();
1140                if (isOriginallyClosed)
1141                        this.open();
1142
1143                final int size = size();
1144
1145                if (size < 3)
1146                        return false;
1147
1148                float res = 0;
1149                for (int i = 0; i < size; i++) {
1150                        final Point2d p = points.get(i);
1151                        final Point2d tmp = points.get((i + 1) % size);
1152                        final Point2dImpl v = new Point2dImpl();
1153                        v.x = tmp.getX() - p.getX();
1154                        v.y = tmp.getY() - p.getY();
1155                        final Point2d u = points.get((i + 2) % size);
1156
1157                        if (i == 0) // in first loop direction is unknown, so save it in res
1158                                res = u.getX() * v.y - u.getY() * v.x + v.x * p.getY() - v.y * p.getX();
1159                        else {
1160                                final float newres = u.getX() * v.y - u.getY() * v.x + v.x * p.getY() - v.y * p.getX();
1161                                if ((newres > 0 && res < 0) || (newres < 0 && res > 0))
1162                                        return false;
1163                        }
1164                }
1165
1166                if (isOriginallyClosed)
1167                        close();
1168
1169                return true;
1170        }
1171
1172        /**
1173         * Test if this has no inner polygons or sub-parts
1174         *
1175         * @see Polygon#getInnerPolys()
1176         *
1177         * @return true if this is polygon has no inner polygons; false otherwise.
1178         */
1179        public boolean hasNoInnerPolygons() {
1180                return innerPolygons == null || innerPolygons.size() == 0;
1181        }
1182
1183        @Override
1184        public double calculatePerimeter() {
1185                final Point2d p1 = points.get(0);
1186                float p1x = p1.getX();
1187                float p1y = p1.getY();
1188
1189                Point2d p2 = points.get(points.size() - 1);
1190                float p2x = p2.getX();
1191                float p2y = p2.getY();
1192
1193                double peri = Line2d.distance(p1x, p1y, p2x, p2y);
1194                for (int i = 1; i < this.points.size(); i++) {
1195                        p2 = points.get(i);
1196                        p2x = p2.getX();
1197                        p2y = p2.getY();
1198                        peri += Line2d.distance(p1x, p1y, p2x, p2y);
1199                        p1x = p2x;
1200                        p1y = p2y;
1201                }
1202
1203                return peri;
1204        }
1205
1206        /**
1207         * Test (outer) polygon path direction
1208         *
1209         * @return true is the outer path direction is clockwise w.r.t OpenIMAJ
1210         *         coordinate system
1211         */
1212        public boolean isClockwise() {
1213                double signedArea = 0;
1214                for (int i = 0; i < this.points.size() - 1; i++) {
1215                        final float x1 = points.get(i).getX();
1216                        final float y1 = points.get(i).getY();
1217                        final float x2 = points.get(i + 1).getX();
1218                        final float y2 = points.get(i + 1).getY();
1219
1220                        signedArea += (x1 * y2 - x2 * y1);
1221                }
1222
1223                final float x1 = points.get(points.size() - 1).getX();
1224                final float y1 = points.get(points.size() - 1).getY();
1225                final float x2 = points.get(0).getX();
1226                final float y2 = points.get(0).getY();
1227                signedArea += (x1 * y2 - x2 * y1);
1228
1229                return signedArea >= 0;
1230        }
1231
1232        /**
1233         * Calculate convex hull using Melkman's algorithm. This is faster than the
1234         * {@link #calculateConvexHull()} method, but will only work for simple
1235         * polygons (those that don't self-intersect)
1236         * <p>
1237         * Based on http://softsurfer.com/Archive/algorithm_0203/algorithm_0203.htm,
1238         * but modified (fixed) to deal with the problem of the initialisation
1239         * points potentially being co-linear.
1240         * <p>
1241         * Copyright 2001, softSurfer (www.softsurfer.com) This code may be freely
1242         * used and modified for any purpose providing that this copyright notice is
1243         * included with it. SoftSurfer makes no warranty for this code, and cannot
1244         * be held liable for any real or imagined damage resulting from its use.
1245         * Users of this code must verify correctness for their application.
1246         *
1247         * @return A polygon defining the shape of the convex hull
1248         */
1249        public Polygon calculateConvexHullMelkman() {
1250                if (this.points.size() <= 3)
1251                        return new Polygon(this.points);
1252
1253                final int n = this.points.size();
1254                int i = 1;
1255                while (i + 1 < n && isLeft(points.get(0), points.get(i), points.get(i + 1)) == 0)
1256                        i++;
1257
1258                if (n - i <= 3)
1259                        return new Polygon(this.points);
1260
1261                // initialize a deque D[] from bottom to top so that the
1262                // 1st three vertices of V[] are a counterclockwise triangle
1263                final Point2d[] D = new Point2d[2 * n + 1];
1264                int bot = n - 2, top = bot + 3; // initial bottom and top deque indices
1265                D[bot] = D[top] = points.get(i + 1); // 3rd vertex is at both bot and
1266                // top
1267                if (isLeft(points.get(0), points.get(i), points.get(i + 1)) > 0) {
1268                        D[bot + 1] = points.get(0);
1269                        D[bot + 2] = points.get(i); // ccw vertices are: 2,0,1,2
1270                } else {
1271                        D[bot + 1] = points.get(i);
1272                        D[bot + 2] = points.get(0); // ccw vertices are: 2,1,0,2
1273                }
1274
1275                i += 2;
1276
1277                // compute the hull on the deque D[]
1278                for (; i < n; i++) { // process the rest of vertices
1279                        // test if next vertex is inside the deque hull
1280                        if ((isLeft(D[bot], D[bot + 1], points.get(i)) > 0) &&
1281                                        (isLeft(D[top - 1], D[top], points.get(i)) > 0))
1282                                continue; // skip an interior vertex
1283
1284                        // incrementally add an exterior vertex to the deque hull
1285                        // get the rightmost tangent at the deque bot
1286                        while (isLeft(D[bot], D[bot + 1], points.get(i)) <= 0)
1287                                ++bot; // remove bot of deque
1288                        D[--bot] = points.get(i); // insert V[i] at bot of deque
1289
1290                        // get the leftmost tangent at the deque top
1291                        while (isLeft(D[top - 1], D[top], points.get(i)) <= 0)
1292                                --top; // pop top of deque
1293                        D[++top] = points.get(i); // push V[i] onto top of deque
1294                }
1295
1296                // transcribe deque D[] to the output hull array H[]
1297                final Polygon H = new Polygon();
1298                final List<Point2d> vertices = H.getVertices();
1299                for (int h = 0; h <= (top - bot); h++)
1300                        vertices.add(D[bot + h]);
1301
1302                return H;
1303        }
1304
1305        private float isLeft(Point2d P0, Point2d P1, Point2d P2) {
1306                return (P1.getX() - P0.getX()) * (P2.getY() - P0.getY()) - (P2.getX() -
1307                                P0.getX()) * (P1.getY() - P0.getY());
1308        }
1309
1310        /**
1311         * Compute the minimum size rotated bounding rectangle that contains this
1312         * shape using the rotating calipers approach.
1313         *
1314         * @see org.openimaj.math.geometry.shape.Shape#minimumBoundingRectangle()
1315         * @see RotatingCalipers#getMinimumBoundingRectangle(Polygon, boolean)
1316         */
1317        @Override
1318        public RotatedRectangle minimumBoundingRectangle() {
1319                return RotatingCalipers.getMinimumBoundingRectangle(this, false);
1320        }
1321
1322        /**
1323         * Compute the minimum size rotated bounding rectangle that contains this
1324         * shape using the rotating calipers approach.
1325         *
1326         * @see RotatingCalipers#getMinimumBoundingRectangle(Polygon, boolean)
1327         *
1328         * @param assumeSimple
1329         *            can the algorithm assume the polygon is simple and use an
1330         *            optimised (Melkman's) convex hull?
1331         *
1332         * @return the minimum bounding box
1333         */
1334        public RotatedRectangle minimumBoundingRectangle(boolean assumeSimple) {
1335                return RotatingCalipers.getMinimumBoundingRectangle(this, assumeSimple);
1336        }
1337
1338        /**
1339         * Find the closest point on the polygon to the given point
1340         *
1341         * @param pt
1342         *            the point
1343         * @return the closest point
1344         */
1345        public Point2d closestPoint(Point2d pt) {
1346                final boolean closed = isClosed();
1347
1348                if (!closed)
1349                        close();
1350
1351                final float x = pt.getX();
1352                final float y = pt.getY();
1353                float minDist = Float.MAX_VALUE;
1354                final Point2dImpl min = new Point2dImpl();
1355                final Point2dImpl tpt = new Point2dImpl();
1356
1357                for (int k = 0; k < points.size() - 1; k++) {
1358                        final float vx = points.get(k).getX();
1359                        final float vy = points.get(k).getY();
1360                        final float wx = points.get(k + 1).getX();
1361                        final float wy = points.get(k + 1).getY();
1362
1363                        // Return minimum distance between line segment vw and point p
1364                        final float l2 = (wx - vx) * (wx - vx) + (wy - vy) * (wy - vy);
1365
1366                        if (l2 == 0.0) {
1367                                tpt.x = vx;
1368                                tpt.y = vy;
1369                        } else {
1370                                final float t = ((x - vx) * (wx - vx) + (y - vy) * (wy - vy)) / l2;
1371
1372                                if (t < 0.0) {
1373                                        tpt.x = vx;
1374                                        tpt.y = vy;
1375                                } else if (t > 1.0) {
1376                                        tpt.x = wx;
1377                                        tpt.y = wy;
1378                                } else {
1379                                        tpt.x = vx + t * (wx - vx);
1380                                        tpt.y = vy + t * (wy - vy);
1381                                }
1382                        }
1383
1384                        final float dist = (float) Line2d.distance(x, y, tpt.x, tpt.y);
1385                        if (dist < minDist) {
1386                                minDist = dist;
1387                                min.x = tpt.x;
1388                                min.y = tpt.y;
1389                        }
1390                }
1391
1392                if (!closed)
1393                        open();
1394
1395                return min;
1396        }
1397}