Home | History | Annotate | Download | only in math
      1 /*******************************************************************************
      2  * Copyright 2011 See AUTHORS file.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *   http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  ******************************************************************************/
     16 
     17 package com.badlogic.gdx.math;
     18 
     19 import com.badlogic.gdx.utils.FloatArray;
     20 import com.badlogic.gdx.utils.IntArray;
     21 import com.badlogic.gdx.utils.ShortArray;
     22 
     23 /** A simple implementation of the ear cutting algorithm to triangulate simple polygons without holes. For more information:
     24  * <ul>
     25  * <li><a href="http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html">http://cgm.cs.mcgill.ca/~godfried/
     26  * teaching/cg-projects/97/Ian/algorithm2.html</a></li>
     27  * <li><a
     28  * href="http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf">http://www.geometrictools.com/Documentation
     29  * /TriangulationByEarClipping.pdf</a></li>
     30  * </ul>
     31  * If the input polygon is not simple (self-intersects), there will be output but it is of unspecified quality (garbage in,
     32  * garbage out).
     33  * @author badlogicgames (at) gmail.com
     34  * @author Nicolas Gramlich (optimizations, collinear edge support)
     35  * @author Eric Spitz
     36  * @author Thomas ten Cate (bugfixes, optimizations)
     37  * @author Nathan Sweet (rewrite, return indices, no allocation, optimizations) */
     38 public class EarClippingTriangulator {
     39 	static private final int CONCAVE = -1;
     40 	static private final int TANGENTIAL = 0;
     41 	static private final int CONVEX = 1;
     42 
     43 	private final ShortArray indicesArray = new ShortArray();
     44 	private short[] indices;
     45 	private float[] vertices;
     46 	private int vertexCount;
     47 	private final IntArray vertexTypes = new IntArray();
     48 	private final ShortArray triangles = new ShortArray();
     49 
     50 	/** @see #computeTriangles(float[], int, int) */
     51 	public ShortArray computeTriangles (FloatArray vertices) {
     52 		return computeTriangles(vertices.items, 0, vertices.size);
     53 	}
     54 
     55 	/** @see #computeTriangles(float[], int, int) */
     56 	public ShortArray computeTriangles (float[] vertices) {
     57 		return computeTriangles(vertices, 0, vertices.length);
     58 	}
     59 
     60 	/** Triangulates the given (convex or concave) simple polygon to a list of triangle vertices.
     61 	 * @param vertices pairs describing vertices of the polygon, in either clockwise or counterclockwise order.
     62 	 * @return triples of triangle indices in clockwise order. Note the returned array is reused for later calls to the same
     63 	 *         method. */
     64 	public ShortArray computeTriangles (float[] vertices, int offset, int count) {
     65 		this.vertices = vertices;
     66 		int vertexCount = this.vertexCount = count / 2;
     67 		int vertexOffset = offset / 2;
     68 
     69 		ShortArray indicesArray = this.indicesArray;
     70 		indicesArray.clear();
     71 		indicesArray.ensureCapacity(vertexCount);
     72 		indicesArray.size = vertexCount;
     73 		short[] indices = this.indices = indicesArray.items;
     74 		if (areVerticesClockwise(vertices, offset, count)) {
     75 			for (short i = 0; i < vertexCount; i++)
     76 				indices[i] = (short)(vertexOffset + i);
     77 		} else {
     78 			for (int i = 0, n = vertexCount - 1; i < vertexCount; i++)
     79 				indices[i] = (short)(vertexOffset + n - i); // Reversed.
     80 		}
     81 
     82 		IntArray vertexTypes = this.vertexTypes;
     83 		vertexTypes.clear();
     84 		vertexTypes.ensureCapacity(vertexCount);
     85 		for (int i = 0, n = vertexCount; i < n; ++i)
     86 			vertexTypes.add(classifyVertex(i));
     87 
     88 		// A polygon with n vertices has a triangulation of n-2 triangles.
     89 		ShortArray triangles = this.triangles;
     90 		triangles.clear();
     91 		triangles.ensureCapacity(Math.max(0, vertexCount - 2) * 3);
     92 		triangulate();
     93 		return triangles;
     94 	}
     95 
     96 	private void triangulate () {
     97 		int[] vertexTypes = this.vertexTypes.items;
     98 
     99 		while (vertexCount > 3) {
    100 			int earTipIndex = findEarTip();
    101 			cutEarTip(earTipIndex);
    102 
    103 			// The type of the two vertices adjacent to the clipped vertex may have changed.
    104 			int previousIndex = previousIndex(earTipIndex);
    105 			int nextIndex = earTipIndex == vertexCount ? 0 : earTipIndex;
    106 			vertexTypes[previousIndex] = classifyVertex(previousIndex);
    107 			vertexTypes[nextIndex] = classifyVertex(nextIndex);
    108 		}
    109 
    110 		if (vertexCount == 3) {
    111 			ShortArray triangles = this.triangles;
    112 			short[] indices = this.indices;
    113 			triangles.add(indices[0]);
    114 			triangles.add(indices[1]);
    115 			triangles.add(indices[2]);
    116 		}
    117 	}
    118 
    119 	/** @return {@link #CONCAVE}, {@link #TANGENTIAL} or {@link #CONVEX} */
    120 	private int classifyVertex (int index) {
    121 		short[] indices = this.indices;
    122 		int previous = indices[previousIndex(index)] * 2;
    123 		int current = indices[index] * 2;
    124 		int next = indices[nextIndex(index)] * 2;
    125 		float[] vertices = this.vertices;
    126 		return computeSpannedAreaSign(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1],
    127 			vertices[next], vertices[next + 1]);
    128 	}
    129 
    130 	private int findEarTip () {
    131 		int vertexCount = this.vertexCount;
    132 		for (int i = 0; i < vertexCount; i++)
    133 			if (isEarTip(i)) return i;
    134 
    135 		// Desperate mode: if no vertex is an ear tip, we are dealing with a degenerate polygon (e.g. nearly collinear).
    136 		// Note that the input was not necessarily degenerate, but we could have made it so by clipping some valid ears.
    137 
    138 		// Idea taken from Martin Held, "FIST: Fast industrial-strength triangulation of polygons", Algorithmica (1998),
    139 		// http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.115.291
    140 
    141 		// Return a convex or tangential vertex if one exists.
    142 		int[] vertexTypes = this.vertexTypes.items;
    143 		for (int i = 0; i < vertexCount; i++)
    144 			if (vertexTypes[i] != CONCAVE) return i;
    145 		return 0; // If all vertices are concave, just return the first one.
    146 	}
    147 
    148 	private boolean isEarTip (int earTipIndex) {
    149 		int[] vertexTypes = this.vertexTypes.items;
    150 		if (vertexTypes[earTipIndex] == CONCAVE) return false;
    151 
    152 		int previousIndex = previousIndex(earTipIndex);
    153 		int nextIndex = nextIndex(earTipIndex);
    154 		short[] indices = this.indices;
    155 		int p1 = indices[previousIndex] * 2;
    156 		int p2 = indices[earTipIndex] * 2;
    157 		int p3 = indices[nextIndex] * 2;
    158 		float[] vertices = this.vertices;
    159 		float p1x = vertices[p1], p1y = vertices[p1 + 1];
    160 		float p2x = vertices[p2], p2y = vertices[p2 + 1];
    161 		float p3x = vertices[p3], p3y = vertices[p3 + 1];
    162 
    163 		// Check if any point is inside the triangle formed by previous, current and next vertices.
    164 		// Only consider vertices that are not part of this triangle, or else we'll always find one inside.
    165 		for (int i = nextIndex(nextIndex); i != previousIndex; i = nextIndex(i)) {
    166 			// Concave vertices can obviously be inside the candidate ear, but so can tangential vertices
    167 			// if they coincide with one of the triangle's vertices.
    168 			if (vertexTypes[i] != CONVEX) {
    169 				int v = indices[i] * 2;
    170 				float vx = vertices[v];
    171 				float vy = vertices[v + 1];
    172 				// Because the polygon has clockwise winding order, the area sign will be positive if the point is strictly inside.
    173 				// It will be 0 on the edge, which we want to include as well.
    174 				// note: check the edge defined by p1->p3 first since this fails _far_ more then the other 2 checks.
    175 				if (computeSpannedAreaSign(p3x, p3y, p1x, p1y, vx, vy) >= 0) {
    176 					if (computeSpannedAreaSign(p1x, p1y, p2x, p2y, vx, vy) >= 0) {
    177 						if (computeSpannedAreaSign(p2x, p2y, p3x, p3y, vx, vy) >= 0) return false;
    178 					}
    179 				}
    180 			}
    181 		}
    182 		return true;
    183 	}
    184 
    185 	private void cutEarTip (int earTipIndex) {
    186 		short[] indices = this.indices;
    187 		ShortArray triangles = this.triangles;
    188 
    189 		triangles.add(indices[previousIndex(earTipIndex)]);
    190 		triangles.add(indices[earTipIndex]);
    191 		triangles.add(indices[nextIndex(earTipIndex)]);
    192 
    193 		indicesArray.removeIndex(earTipIndex);
    194 		vertexTypes.removeIndex(earTipIndex);
    195 		vertexCount--;
    196 	}
    197 
    198 	private int previousIndex (int index) {
    199 		return (index == 0 ? vertexCount : index) - 1;
    200 	}
    201 
    202 	private int nextIndex (int index) {
    203 		return (index + 1) % vertexCount;
    204 	}
    205 
    206 	static private boolean areVerticesClockwise (float[] vertices, int offset, int count) {
    207 		if (count <= 2) return false;
    208 		float area = 0, p1x, p1y, p2x, p2y;
    209 		for (int i = offset, n = offset + count - 3; i < n; i += 2) {
    210 			p1x = vertices[i];
    211 			p1y = vertices[i + 1];
    212 			p2x = vertices[i + 2];
    213 			p2y = vertices[i + 3];
    214 			area += p1x * p2y - p2x * p1y;
    215 		}
    216 		p1x = vertices[offset + count - 2];
    217 		p1y = vertices[offset + count - 1];
    218 		p2x = vertices[offset];
    219 		p2y = vertices[offset + 1];
    220 		return area + p1x * p2y - p2x * p1y < 0;
    221 	}
    222 
    223 	static private int computeSpannedAreaSign (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
    224 		float area = p1x * (p3y - p2y);
    225 		area += p2x * (p1y - p3y);
    226 		area += p3x * (p2y - p1y);
    227 		return (int)Math.signum(area);
    228 	}
    229 }
    230