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.Arrays; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037 038import org.openimaj.math.geometry.line.Line2d; 039import org.openimaj.math.geometry.line.Line2d.IntersectionResult; 040import org.openimaj.math.geometry.point.Point2d; 041import org.openimaj.math.geometry.point.Point2dImpl; 042 043import Jama.Matrix; 044 045/** 046 * A triangle shape 047 * 048 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 049 */ 050public class Triangle implements Shape { 051 /** The vertices of the triangle */ 052 public Point2d[] vertices; 053 054 /** 055 * Construct a Triangle with the given vertices. 056 * 057 * @param vertex1 058 * first vertex 059 * @param vertex2 060 * second vertex 061 * @param vertex3 062 * third vertex 063 */ 064 public Triangle(Point2d vertex1, Point2d vertex2, Point2d vertex3) { 065 this.vertices = new Point2d[3]; 066 067 this.vertices[0] = vertex1; 068 this.vertices[1] = vertex2; 069 this.vertices[2] = vertex3; 070 } 071 072 /** 073 * Construct a Triangle with the given vertices. 074 * 075 * @param vertices 076 * the vertices 077 */ 078 public Triangle(Point2d[] vertices) { 079 if (vertices.length != 3) 080 throw new IllegalArgumentException("Triangles must have three vertices"); 081 this.vertices = vertices; 082 } 083 084 private final int getOrientation(float v1x, float v1y, float v2x, float v2y, float px, float py) { 085 final float ori = (v2x - v1x) * (py - v1y) - (px - v1x) * (v2y - v1y); 086 087 if (ori == 0) 088 return 0; 089 return ori < 0 ? -1 : 1; 090 } 091 092 /* 093 * (non-Javadoc) 094 * 095 * Note: often called in tight loops, so optimised 096 * 097 * @see 098 * org.openimaj.math.geometry.shape.Shape#isInside(org.openimaj.math.geometry 099 * .point.Point2d) 100 */ 101 /** 102 * Like {@link #isInside(Point2d)} but counts being "on the line" as being 103 * inside also 104 * 105 * @param point 106 * the point being tested 107 * @return true if the point is inside or on the line; false otherwise 108 */ 109 public final boolean isInsideOnLine(Point2d point) { 110 final float v1x = vertices[0].getX(); 111 final float v1y = vertices[0].getY(); 112 final float v2x = vertices[1].getX(); 113 final float v2y = vertices[1].getY(); 114 final float v3x = vertices[2].getX(); 115 final float v3y = vertices[2].getY(); 116 final float px = point.getX(); 117 final float py = point.getY(); 118 119 if (px > v1x && px > v2x && px > v3x) 120 return false; 121 if (px < v1x && px < v2x && px < v3x) 122 return false; 123 if (py > v1y && py > v2y && py > v3y) 124 return false; 125 if (py < v1y && py < v2y && py < v3y) 126 return false; 127 128 final int o1 = getOrientation(v1x, v1y, v2x, v2y, px, py); 129 final int o2 = getOrientation(v2x, v2y, v3x, v3y, px, py); 130 final int o3 = getOrientation(v3x, v3y, v1x, v1y, px, py); 131 132 return ((o1 == 0 ? o2 : o1) == (o2 == 0 ? o1 : o2)) && ((o3 == 0 ? o2 : o3) == (o2 == 0 ? o1 : o2)); 133 } 134 135 /* 136 * (non-Javadoc) 137 * 138 * Note: often called in tight loops, so optimised 139 * 140 * @see 141 * org.openimaj.math.geometry.shape.Shape#isInside(org.openimaj.math.geometry 142 * .point.Point2d) 143 */ 144 @Override 145 public final boolean isInside(Point2d point) { 146 final float v1x = vertices[0].getX(); 147 final float v1y = vertices[0].getY(); 148 final float v2x = vertices[1].getX(); 149 final float v2y = vertices[1].getY(); 150 final float v3x = vertices[2].getX(); 151 final float v3y = vertices[2].getY(); 152 final float px = point.getX(); 153 final float py = point.getY(); 154 155 if (px > v1x && px > v2x && px > v3x) 156 return false; 157 if (px < v1x && px < v2x && px < v3x) 158 return false; 159 if (py > v1y && py > v2y && py > v3y) 160 return false; 161 if (py < v1y && py < v2y && py < v3y) 162 return false; 163 164 final int o1 = getOrientation(v1x, v1y, v2x, v2y, px, py); 165 final int o2 = getOrientation(v2x, v2y, v3x, v3y, px, py); 166 final int o3 = getOrientation(v3x, v3y, v1x, v1y, px, py); 167 168 return (o1 == o2) && (o2 == o3); 169 } 170 171 @Override 172 public Rectangle calculateRegularBoundingBox() { 173 return new Rectangle((int) Math.round(minX()), (int) Math.round(minY()), (int) Math.round(getWidth()), 174 (int) Math.round(getHeight())); 175 } 176 177 @Override 178 public void translate(float x, float y) { 179 for (final Point2d v : vertices) 180 v.translate(x, y); 181 } 182 183 @Override 184 public void scale(float sc) { 185 for (final Point2d v : vertices) { 186 v.setX(v.getX() * sc); 187 v.setY(v.getY() * sc); 188 } 189 } 190 191 @Override 192 public void scale(Point2d centre, float sc) { 193 translate(-centre.getX(), -centre.getY()); 194 scale(sc); 195 translate(centre.getX(), centre.getY()); 196 } 197 198 @Override 199 public void scaleCentroid(float sc) { 200 final Point2d centre = this.calculateCentroid(); 201 translate(-centre.getX(), -centre.getY()); 202 scale(sc); 203 translate(centre.getX(), centre.getY()); 204 } 205 206 @Override 207 public Point2d calculateCentroid() { 208 return new Point2dImpl( 209 (vertices[0].getX() + vertices[1].getX() + vertices[2].getX()) / 3, 210 (vertices[0].getY() + vertices[1].getY() + vertices[2].getY()) / 3); 211 } 212 213 @Override 214 public double calculateArea() { 215 final double xb = vertices[1].getX() - vertices[0].getX(); 216 final double yb = vertices[1].getY() - vertices[0].getY(); 217 final double xc = vertices[2].getX() - vertices[0].getX(); 218 final double yc = vertices[2].getY() - vertices[0].getY(); 219 220 return 0.5 * Math.abs(xb * yc - xc * yb); 221 } 222 223 @Override 224 public double minX() { 225 return Math.min(vertices[0].getX(), Math.min(vertices[1].getX(), vertices[2].getX())); 226 } 227 228 @Override 229 public double minY() { 230 return Math.min(vertices[0].getY(), Math.min(vertices[1].getY(), vertices[2].getY())); 231 } 232 233 @Override 234 public double maxX() { 235 return Math.max(vertices[0].getX(), Math.max(vertices[1].getX(), vertices[2].getX())); 236 } 237 238 @Override 239 public double maxY() { 240 return Math.max(vertices[0].getY(), Math.max(vertices[1].getY(), vertices[2].getY())); 241 } 242 243 @Override 244 public double getWidth() { 245 return maxX() - minX(); 246 } 247 248 @Override 249 public double getHeight() { 250 return maxY() - minY(); 251 } 252 253 @Override 254 public Triangle transform(Matrix transform) { 255 final Point2d[] newVertices = new Point2d[3]; 256 257 for (int i = 0; i < 3; i++) { 258 final Matrix p1 = new Matrix(3, 1); 259 p1.set(0, 0, vertices[i].getX()); 260 p1.set(1, 0, vertices[i].getY()); 261 p1.set(2, 0, 1); 262 263 final Matrix p2_est = transform.times(p1); 264 265 final Point2d out = new Point2dImpl((float) (p2_est.get(0, 0) / p2_est.get(2, 0)), 266 (float) (p2_est.get(1, 0) / p2_est.get(2, 0))); 267 268 newVertices[i] = out; 269 } 270 271 return new Triangle(newVertices); 272 } 273 274 @Override 275 public Polygon asPolygon() { 276 final Polygon polygon = new Polygon(vertices); 277 278 return polygon; 279 } 280 281 /** 282 * @return The first vertex. 283 */ 284 public Point2d firstVertex() { 285 return vertices[0]; 286 } 287 288 /** 289 * @return The second vertex. 290 */ 291 public Point2d secondVertex() { 292 return vertices[1]; 293 } 294 295 /** 296 * @return The third vertex. 297 */ 298 public Point2d thirdVertex() { 299 return vertices[2]; 300 } 301 302 /** 303 * @return The edges of the triangle. 304 */ 305 public List<Line2d> getEdges() { 306 final List<Line2d> edges = new ArrayList<Line2d>(); 307 edges.add(new Line2d(firstVertex(), secondVertex())); 308 edges.add(new Line2d(secondVertex(), thirdVertex())); 309 edges.add(new Line2d(thirdVertex(), firstVertex())); 310 return edges; 311 } 312 313 /** 314 * Test whether this triangle shares a vertex with another triangle. 315 * 316 * @param other 317 * the other triangle. 318 * @return true if a vertex is shared; false otherwise. 319 */ 320 public boolean sharesVertex(Triangle other) { 321 for (final Point2d v1 : vertices) 322 for (final Point2d v2 : other.vertices) 323 if (v1 == v2) 324 return true; 325 326 return false; 327 } 328 329 @Override 330 public double intersectionArea(Shape that) { 331 return intersectionArea(that, 1); 332 } 333 334 @Override 335 public double intersectionArea(Shape that, int nStepsPerDimention) { 336 final Rectangle overlapping = this.calculateRegularBoundingBox().overlapping(that.calculateRegularBoundingBox()); 337 if (overlapping == null) 338 return 0; 339 double intersection = 0; 340 final double step = Math.max(overlapping.width, overlapping.height) / (double) nStepsPerDimention; 341 double nReads = 0; 342 for (float x = overlapping.x; x < overlapping.x + overlapping.width; x += step) { 343 for (float y = overlapping.y; y < overlapping.y + overlapping.height; y += step) { 344 final boolean insideThis = this.isInside(new Point2dImpl(x, y)); 345 final boolean insideThat = that.isInside(new Point2dImpl(x, y)); 346 nReads++; 347 if (insideThis && insideThat) { 348 intersection++; 349 } 350 } 351 } 352 353 return (intersection / nReads) * (overlapping.width * overlapping.height); 354 } 355 356 @Override 357 public Triangle clone() { 358 final Point2d[] newVertices = { 359 vertices[0].copy(), 360 vertices[1].copy(), 361 vertices[2].copy() 362 }; 363 364 return new Triangle(newVertices); 365 } 366 367 @Override 368 public String toString() { 369 return String.format("((%2.3f %2.3f), (%2.3f %2.3f), (%2.3f %2.3f))", 370 vertices[0].getX(), vertices[0].getY(), 371 vertices[1].getX(), vertices[1].getY(), 372 vertices[2].getX(), vertices[2].getY() 373 ); 374 } 375 376 @Override 377 public int hashCode() { 378 return Arrays.hashCode(vertices); 379 } 380 381 @Override 382 public boolean equals(Object obj) { 383 if (!(obj instanceof Triangle)) 384 return false; 385 final Triangle tri = (Triangle) obj; 386 for (int i = 0; i < 3; i++) { 387 if (!tri.vertices[i].equals(this.vertices[i])) 388 return false; 389 } 390 return true; 391 } 392 393 /** 394 * The intersection of this triangle with the line defined by y = mx + c. 395 * The line crosses at either 0, 1 or 2 points. 396 * 397 * @param line 398 * the line 399 * 400 * @return the intersecting edges of the triangle and points of intersection 401 */ 402 public Map<Line2d, Point2d> intersectionSides(Line2d line) { 403 final Map<Line2d, Point2d> ret = new HashMap<Line2d, Point2d>(); 404 final Line2d first = new Line2d(this.vertices[0], this.vertices[1]); 405 final Line2d second = new Line2d(this.vertices[1], this.vertices[2]); 406 final Line2d third = new Line2d(this.vertices[2], this.vertices[0]); 407 408 addIntersect(ret, first, line); 409 addIntersect(ret, second, line); 410 addIntersect(ret, third, line); 411 412 return ret; 413 } 414 415 private void addIntersect(Map<Line2d, Point2d> ret, Line2d line, Line2d otherline) { 416 final IntersectionResult inter = line.getIntersection(otherline); 417 switch (inter.type) { 418 case INTERSECTING: 419 ret.put(line, inter.intersectionPoint); 420 break; 421 default: 422 break; 423 424 } 425 } 426 427 @Override 428 public double calculatePerimeter() { 429 return Line2d.distance(vertices[0], vertices[1]) + 430 Line2d.distance(vertices[1], vertices[2]) + 431 Line2d.distance(vertices[2], vertices[0]); 432 } 433 434 @Override 435 public RotatedRectangle minimumBoundingRectangle() { 436 return asPolygon().minimumBoundingRectangle(); 437 } 438 439 @Override 440 public boolean isConvex() { 441 return true; 442 } 443}