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}