/* Poly2Tri * Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.poly2tri.triangulation.delaunay; import java.util.ArrayList; import org.poly2tri.triangulation.TriangulationPoint; import org.poly2tri.triangulation.delaunay.sweep.DTSweepConstraint; import org.poly2tri.triangulation.point.TPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DelaunayTriangle { private final static Logger logger = LoggerFactory .getLogger(DelaunayTriangle.class); /** Neighbor pointers */ public final DelaunayTriangle[] neighbors = new DelaunayTriangle[3]; /** Flags to determine if an edge is a Constrained edge */ public final boolean[] cEdge = new boolean[] { false, false, false }; /** Flags to determine if an edge is a Delauney edge */ public final boolean[] dEdge = new boolean[] { false, false, false }; /** Has this triangle been marked as an interior triangle? */ protected boolean interior = false; public final TriangulationPoint[] points = new TriangulationPoint[3]; public DelaunayTriangle(TriangulationPoint p1, TriangulationPoint p2, TriangulationPoint p3) { points[0] = p1; points[1] = p2; points[2] = p3; } public int index(TriangulationPoint p) { if (p == points[0]) { return 0; } else if (p == points[1]) { return 1; } else if (p == points[2]) { return 2; } throw new RuntimeException( "Calling index with a point that doesn't exist in triangle"); } public int indexCW(TriangulationPoint p) { int index = index(p); switch (index) { case 0: return 2; case 1: return 0; default: return 1; } } public int indexCCW(TriangulationPoint p) { int index = index(p); switch (index) { case 0: return 1; case 1: return 2; default: return 0; } } public boolean contains(TriangulationPoint p) { return (p == points[0] || p == points[1] || p == points[2]); } public boolean contains(DTSweepConstraint e) { return (contains(e.p) && contains(e.q)); } public boolean contains(TriangulationPoint p, TriangulationPoint q) { return (contains(p) && contains(q)); } // Update neighbor pointers private void markNeighbor(TriangulationPoint p1, TriangulationPoint p2, DelaunayTriangle t) { if ((p1 == points[2] && p2 == points[1]) || (p1 == points[1] && p2 == points[2])) { neighbors[0] = t; } else if ((p1 == points[0] && p2 == points[2]) || (p1 == points[2] && p2 == points[0])) { neighbors[1] = t; } else if ((p1 == points[0] && p2 == points[1]) || (p1 == points[1] && p2 == points[0])) { neighbors[2] = t; } else { logger.error("Neighbor error, please report!"); // throw new Exception("Neighbor error, please report!"); } } /* Exhaustive search to update neighbor pointers */ public void markNeighbor(DelaunayTriangle t) { if (t.contains(points[1], points[2])) { neighbors[0] = t; t.markNeighbor(points[1], points[2], this); } else if (t.contains(points[0], points[2])) { neighbors[1] = t; t.markNeighbor(points[0], points[2], this); } else if (t.contains(points[0], points[1])) { neighbors[2] = t; t.markNeighbor(points[0], points[1], this); } else { logger.error("markNeighbor failed"); } } public void clearNeighbors() { neighbors[0] = neighbors[1] = neighbors[2] = null; } public void clearNeighbor(DelaunayTriangle triangle) { if (neighbors[0] == triangle) { neighbors[0] = null; } else if (neighbors[1] == triangle) { neighbors[1] = null; } else { neighbors[2] = null; } } /** * Clears all references to all other triangles and points */ public void clear() { DelaunayTriangle t; for (int i = 0; i < 3; i++) { t = neighbors[i]; if (t != null) { t.clearNeighbor(this); } } clearNeighbors(); points[0] = points[1] = points[2] = null; } /** * @param t * - opposite triangle * @param p * - the point in t that isn't shared between the triangles * @return */ public TriangulationPoint oppositePoint(DelaunayTriangle t, TriangulationPoint p) { assert t != this : "self-pointer error"; return pointCW(t.pointCW(p)); } // The neighbor clockwise to given point public DelaunayTriangle neighborCW(TriangulationPoint point) { if (point == points[0]) { return neighbors[1]; } else if (point == points[1]) { return neighbors[2]; } return neighbors[0]; } // The neighbor counter-clockwise to given point public DelaunayTriangle neighborCCW(TriangulationPoint point) { if (point == points[0]) { return neighbors[2]; } else if (point == points[1]) { return neighbors[0]; } return neighbors[1]; } // The neighbor across to given point public DelaunayTriangle neighborAcross(TriangulationPoint opoint) { if (opoint == points[0]) { return neighbors[0]; } else if (opoint == points[1]) { return neighbors[1]; } return neighbors[2]; } // The point counter-clockwise to given point public TriangulationPoint pointCCW(TriangulationPoint point) { if (point == points[0]) { return points[1]; } else if (point == points[1]) { return points[2]; } else if (point == points[2]) { return points[0]; } logger.error("point location error"); throw new RuntimeException("[FIXME] point location error"); } // The point counter-clockwise to given point public TriangulationPoint pointCW(TriangulationPoint point) { if (point == points[0]) { return points[2]; } else if (point == points[1]) { return points[0]; } else if (point == points[2]) { return points[1]; } logger.error("point location error"); throw new RuntimeException("[FIXME] point location error"); } // Legalize triangle by rotating clockwise around oPoint public void legalize(TriangulationPoint oPoint, TriangulationPoint nPoint) { if (oPoint == points[0]) { points[1] = points[0]; points[0] = points[2]; points[2] = nPoint; } else if (oPoint == points[1]) { points[2] = points[1]; points[1] = points[0]; points[0] = nPoint; } else if (oPoint == points[2]) { points[0] = points[2]; points[2] = points[1]; points[1] = nPoint; } else { logger.error("legalization error"); throw new RuntimeException("legalization bug"); } } public void printDebug() { System.out.println(points[0] + "," + points[1] + "," + points[2]); } // Finalize edge marking public void markNeighborEdges() { for (int i = 0; i < 3; i++) { if (cEdge[i]) { switch (i) { case 0: if (neighbors[0] != null) neighbors[0].markConstrainedEdge(points[1], points[2]); break; case 1: if (neighbors[1] != null) neighbors[1].markConstrainedEdge(points[0], points[2]); break; case 2: if (neighbors[2] != null) neighbors[2].markConstrainedEdge(points[0], points[1]); break; } } } } public void markEdge(DelaunayTriangle triangle) { for (int i = 0; i < 3; i++) { if (cEdge[i]) { switch (i) { case 0: triangle.markConstrainedEdge(points[1], points[2]); break; case 1: triangle.markConstrainedEdge(points[0], points[2]); break; case 2: triangle.markConstrainedEdge(points[0], points[1]); break; } } } } public void markEdge(ArrayList<DelaunayTriangle> tList) { for (DelaunayTriangle t : tList) { for (int i = 0; i < 3; i++) { if (t.cEdge[i]) { switch (i) { case 0: markConstrainedEdge(t.points[1], t.points[2]); break; case 1: markConstrainedEdge(t.points[0], t.points[2]); break; case 2: markConstrainedEdge(t.points[0], t.points[1]); break; } } } } } public void markConstrainedEdge(int index) { cEdge[index] = true; } public void markConstrainedEdge(DTSweepConstraint edge) { markConstrainedEdge(edge.p, edge.q); if ((edge.q == points[0] && edge.p == points[1]) || (edge.q == points[1] && edge.p == points[0])) { cEdge[2] = true; } else if ((edge.q == points[0] && edge.p == points[2]) || (edge.q == points[2] && edge.p == points[0])) { cEdge[1] = true; } else if ((edge.q == points[1] && edge.p == points[2]) || (edge.q == points[2] && edge.p == points[1])) { cEdge[0] = true; } } // Mark edge as constrained public void markConstrainedEdge(TriangulationPoint p, TriangulationPoint q) { if ((q == points[0] && p == points[1]) || (q == points[1] && p == points[0])) { cEdge[2] = true; } else if ((q == points[0] && p == points[2]) || (q == points[2] && p == points[0])) { cEdge[1] = true; } else if ((q == points[1] && p == points[2]) || (q == points[2] && p == points[1])) { cEdge[0] = true; } } public double area() { double a = (points[0].getX() - points[2].getX()) * (points[1].getY() - points[0].getY()); double b = (points[0].getX() - points[1].getX()) * (points[2].getY() - points[0].getY()); return 0.5 * Math.abs(a - b); } public TPoint centroid() { double cx = (points[0].getX() + points[1].getX() + points[2].getX()) / 3d; double cy = (points[0].getY() + points[1].getY() + points[2].getY()) / 3d; return new TPoint(cx, cy); } public boolean inside(TPoint p) { return sameSide(p, points[0], points[1], points[2]) && sameSide(p, points[1], points[0], points[2]) && sameSide(p, points[2], points[0], points[1]); } public boolean sameSide(TPoint p1, TriangulationPoint p2, TriangulationPoint a, TriangulationPoint b) { TPoint ab = vec(a, b); TPoint ap1 = vec(a, p1); TPoint ap2 = vec(a, p2); float cp1 = crossProduct(ab, ap1); float cp2 = crossProduct(ab, ap2); return (cp1 * cp2 >= 0); } public float crossProduct(TriangulationPoint p1, TriangulationPoint p2) { return p1.getXf() * p2.getYf() - p1.getYf() * p2.getXf(); } public float dotProduct(TriangulationPoint p1, TriangulationPoint p2) { return p1.getXf() * p2.getXf() + p1.getYf() * p2.getYf(); } public TPoint vec(TriangulationPoint p1, TriangulationPoint p2) { return new TPoint(p2.getXf() - p1.getXf(), p2.getYf() - p1.getYf()); } /** * Get the neighbor that share this edge * * @param constrainedEdge * @return index of the shared edge or -1 if edge isn't shared */ public int edgeIndex(TriangulationPoint p1, TriangulationPoint p2) { if (points[0] == p1) { if (points[1] == p2) { return 2; } else if (points[2] == p2) { return 1; } } else if (points[1] == p1) { if (points[2] == p2) { return 0; } else if (points[0] == p2) { return 2; } } else if (points[2] == p1) { if (points[0] == p2) { return 1; } else if (points[1] == p2) { return 0; } } return -1; } public boolean getConstrainedEdgeCCW(TriangulationPoint p) { if (p == points[0]) { return cEdge[2]; } else if (p == points[1]) { return cEdge[0]; } return cEdge[1]; } public boolean getConstrainedEdgeCW(TriangulationPoint p) { if (p == points[0]) { return cEdge[1]; } else if (p == points[1]) { return cEdge[2]; } return cEdge[0]; } public boolean getConstrainedEdgeAcross(TriangulationPoint p) { if (p == points[0]) { return cEdge[0]; } else if (p == points[1]) { return cEdge[1]; } return cEdge[2]; } public void setConstrainedEdgeCCW(TriangulationPoint p, boolean ce) { if (p == points[0]) { cEdge[2] = ce; } else if (p == points[1]) { cEdge[0] = ce; } else { cEdge[1] = ce; } } public void setConstrainedEdgeCW(TriangulationPoint p, boolean ce) { if (p == points[0]) { cEdge[1] = ce; } else if (p == points[1]) { cEdge[2] = ce; } else { cEdge[0] = ce; } } public void setConstrainedEdgeAcross(TriangulationPoint p, boolean ce) { if (p == points[0]) { cEdge[0] = ce; } else if (p == points[1]) { cEdge[1] = ce; } else { cEdge[2] = ce; } } public boolean getDelunayEdgeCCW(TriangulationPoint p) { if (p == points[0]) { return dEdge[2]; } else if (p == points[1]) { return dEdge[0]; } return dEdge[1]; } public boolean getDelunayEdgeCW(TriangulationPoint p) { if (p == points[0]) { return dEdge[1]; } else if (p == points[1]) { return dEdge[2]; } return dEdge[0]; } public boolean getDelunayEdgeAcross(TriangulationPoint p) { if (p == points[0]) { return dEdge[0]; } else if (p == points[1]) { return dEdge[1]; } return dEdge[2]; } public void setDelunayEdgeCCW(TriangulationPoint p, boolean e) { if (p == points[0]) { dEdge[2] = e; } else if (p == points[1]) { dEdge[0] = e; } else { dEdge[1] = e; } } public void setDelunayEdgeCW(TriangulationPoint p, boolean e) { if (p == points[0]) { dEdge[1] = e; } else if (p == points[1]) { dEdge[2] = e; } else { dEdge[0] = e; } } public void setDelunayEdgeAcross(TriangulationPoint p, boolean e) { if (p == points[0]) { dEdge[0] = e; } else if (p == points[1]) { dEdge[1] = e; } else { dEdge[2] = e; } } public void clearDelunayEdges() { dEdge[0] = false; dEdge[1] = false; dEdge[2] = false; } public boolean isInterior() { return interior; } public void isInterior(boolean b) { interior = b; } public float side(float px, float py, float qx, float qy, float ax, float ay, float bx, float by) { float z1 = (bx - ax) * (py - ay) - (px - ax) * (by - ay); float z2 = (bx - ax) * (qy - ay) - (qx - ax) * (by - ay); return z1 * z2; } public boolean intersects(float x1, float y1, float x2, float y2) { /* Check whether segment is outside one of the three half-planes * delimited by the triangle. */ float f1 = side(x1, y1, points[2].getXf(), points[2].getYf(), points[0] .getXf(), points[0].getYf(), points[1].getXf(), points[1] .getYf()), f2 = side(x2, y2, points[2].getXf(), points[2] .getYf(), points[0].getXf(), points[0].getYf(), points[1] .getXf(), points[1].getYf()); float f3 = side(x1, y1, points[0].getXf(), points[0].getYf(), points[1] .getXf(), points[1].getYf(), points[2].getXf(), points[2] .getYf()), f4 = side(x2, y2, points[0].getXf(), points[0] .getYf(), points[1].getXf(), points[1].getYf(), points[2] .getXf(), points[2].getYf()); float f5 = side(x1, y1, points[1].getXf(), points[1].getYf(), points[2] .getXf(), points[2].getYf(), points[0].getXf(), points[0] .getYf()), f6 = side(x2, y2, points[1].getXf(), points[1] .getYf(), points[2].getXf(), points[2].getYf(), points[0] .getXf(), points[0].getYf()); /* Check whether triangle is totally inside one of the two half-planes * delimited by the segment. */ float f7 = side(points[0].getXf(), points[0].getYf(), points[1].getXf(), points[1].getYf(), x1, y1, x2, y2); float f8 = side(points[1].getXf(), points[1].getYf(), points[2].getXf(), points[2].getYf(), x1, y1, x2, y2); /* If segment is strictly outside triangle, or triangle is strictly * apart from the line, we're not intersecting */ if ((f1 < 0 && f2 < 0) || (f3 < 0 && f4 < 0) || (f5 < 0 && f6 < 0) || (f7 > 0 && f8 > 0)) //return NOT_INTERSECTING; return false; /* If segment is aligned with one of the edges, we're overlapping */ if ((f1 == 0 && f2 == 0) || (f3 == 0 && f4 == 0) || (f5 == 0 && f6 == 0)) //return OVERLAPPING; return false; /* If segment is outside but not strictly, or triangle is apart but * not strictly, we're touching */ if ((f1 <= 0 && f2 <= 0) || (f3 <= 0 && f4 <= 0) || (f5 <= 0 && f6 <= 0) || (f7 >= 0 && f8 >= 0)) //return TOUCHING; return false; /* If both segment points are strictly inside the triangle, we * are not intersecting either */ if (f1 > 0 && f2 > 0 && f3 > 0 && f4 > 0 && f5 > 0 && f6 > 0) // return NOT_INTERSECTING; return false; /* Otherwise we're intersecting with at least one edge */ //return INTERSECTING; return true; } public TriangulationPoint nearest(float lastX, float lastY) { float dist1 = (lastX - points[0].getXf()) * (lastX - points[0].getXf()) + (lastY - points[0].getYf()) * (lastY - points[0].getYf()); float dist2 = (lastX - points[1].getXf()) * (lastX - points[1].getXf()) + (lastY - points[1].getYf()) * (lastY - points[1].getYf()); float dist3 = (lastX - points[2].getXf()) * (lastX - points[2].getXf()) + (lastY - points[2].getYf()) * (lastY - points[2].getYf()); return dist1 < dist2 ? (dist1 < dist3 ? points[0] : points[2]) : (dist2 < dist3 ? points[1] : points[2]); } }