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}