1 /*
2 * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
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 package com.irurueta.geometry;
17
18 import java.util.ArrayList;
19 import java.util.LinkedList;
20 import java.util.List;
21
22 /**
23 * This class defines a triangulator for 2D polygons. Triangulators divide
24 * polygons into triangles, which are the simplest geometric figure.
25 * This implementation uses Van Gogh or Ear Cutting algorithm for triangulation.
26 */
27 public class VanGoghTriangulator2D extends Triangulator2D {
28
29 /**
30 * Returns triangulator method.
31 * Each method implementation will divide polygons into triangles using
32 * different techniques.
33 *
34 * @return Triangulator method.
35 */
36 @Override
37 public TriangulatorMethod getMethod() {
38 return TriangulatorMethod.VAN_GOGH_TRIANGULATOR;
39 }
40
41 /**
42 * Triangulates provided polygon by dividing it into a set of triangles.
43 *
44 * @param polygon Polygon to be triangulated.
45 * @return List of triangles forming the polygon that has been triangulated.
46 * @throws TriangulatorException Raised if triangulation cannot be done.
47 * Usually this indicates numerical instability or polygon degeneracy.
48 */
49 @Override
50 public List<Triangle2D> triangulate(final Polygon2D polygon) throws TriangulatorException {
51 // triangulation will modify provided list of vertices, so we make a copy
52 // of it
53
54 // original vertices
55 final var vertices = polygon.getVertices();
56 final var verticesCopy = new ArrayList<>(polygon.getVertices());
57 return internalTriangulate(verticesCopy, null, vertices);
58 }
59
60 /**
61 * Triangulates a polygon formed by provided vertices.
62 *
63 * @param vertices List of points considered as vertices of a polygon.
64 * @return List of triangles forming the polygon that has been triangulated.
65 * @throws TriangulatorException Raised if triangulation cannot be done.
66 * Usually this indicates numerical instability or polygon degeneracy.
67 */
68 @Override
69 public List<Triangle2D> triangulate(final List<Point2D> vertices) throws TriangulatorException {
70 if (vertices.size() < MIN_VERTICES) {
71 throw new TriangulatorException();
72 }
73
74 // triangulation will modify provided list of vertices, so we make a copy
75 // of it
76 final var verticesCopy = new ArrayList<>(vertices);
77 return internalTriangulate(verticesCopy, null, vertices);
78 }
79
80 /**
81 * Triangulates a polygon formed by provided vertices.
82 *
83 * @param vertices List of points considered as vertices of a polygon.
84 * @param indices List where indices of original vertices will be stored.
85 * This list can be used to refer to the original order of vertices. Notice
86 * that vertices indices might be repeated because vertices might appear in
87 * more than one triangle after triangulation. If this parameter is null,
88 * indices won't be stored in this list.
89 * @return List of triangles forming the polygon that has been triangulated
90 * @throws TriangulatorException Raised if triangulation cannot be done.
91 * Usually this indicates numerical instability or polygon degeneracy.
92 */
93 @Override
94 public List<Triangle2D> triangulate(final List<Point2D> vertices, final List<int[]> indices)
95 throws TriangulatorException {
96 if (vertices.size() < MIN_VERTICES) {
97 throw new TriangulatorException();
98 }
99
100 // triangulation will modify provided list of vertices, so we make a copy
101 // of it
102 final var verticesCopy = new ArrayList<>(vertices);
103 return internalTriangulate(verticesCopy, indices, vertices);
104 }
105
106 /**
107 * Internal method that computes the actual triangulation.
108 *
109 * @param verticesCopy List of points considered as verticesCopy of a
110 * polygon. This list will be modified after execution of this method.
111 * @param indices List where indices of original verticesCopy will be
112 * stored.
113 * This list can be used to refer to the original order of verticesCopy.
114 * Notice that verticesCopy indices might be repeated because verticesCopy
115 * might appear in more than one triangle after triangulation. If this
116 * parameter is null, indices won't be stored in this list.
117 * @param originalVertices Reference to original list of vertices that won't
118 * be modified.
119 * @return List of triangles forming the polygon that has been triangulated.
120 * @throws TriangulatorException Raised if triangulation cannot be done.
121 * Usually this indicates numerical instability or polygon degeneracy.
122 */
123 private static List<Triangle2D> internalTriangulate(
124 final List<Point2D> verticesCopy, final List<int[]> indices, final List<Point2D> originalVertices)
125 throws TriangulatorException {
126 if (verticesCopy.size() < MIN_VERTICES) {
127 throw new TriangulatorException();
128 }
129
130 final var result = new LinkedList<Triangle2D>();
131
132 boolean madeCut;
133
134 Triangle2D triangle = null;
135
136 // Second, apply algorithm
137 while (verticesCopy.size() > MIN_VERTICES) {
138 madeCut = false;
139 final var lastElement = verticesCopy.size() - 1;
140 for (int i = 0; i <= lastElement; i++) {
141
142 if (i == 0) {
143 if (triangle == null) {
144 // instantiate triangle if not already instantiated
145 triangle = new Triangle2D(verticesCopy.get(lastElement), verticesCopy.get(0),
146 verticesCopy.get(1));
147 } else {
148 triangle.setVertices(verticesCopy.get(lastElement), verticesCopy.get(0), verticesCopy.get(1));
149 }
150 } else if (i == lastElement) {
151 triangle.setVertices(verticesCopy.get(lastElement - 1), verticesCopy.get(lastElement),
152 verticesCopy.get(0));
153 } else {
154 triangle.setVertices(verticesCopy.get(i - 1), verticesCopy.get(i), verticesCopy.get(i + 1));
155 }
156
157 if (isEar(triangle, verticesCopy)) {
158 // If it is an ear, we build a face out of the triangle being
159 // cut and remove it from polygon by cutting it
160 result.add(triangle);
161 // so that it cannot be reused after being added
162 triangle = null;
163
164 // cut ear
165 verticesCopy.remove(i);
166 madeCut = true;
167
168 // Leave from FOR loop to loop again to new reduced vertices set
169 break;
170 }
171 }
172
173 // if arrived here but no cut was made and polygon size contains
174 // more than 3 vertices, then the algorithm failed for some reason
175 if (!madeCut) {
176 throw new TriangulatorException();
177 }
178 }
179
180 // instantiate final triangle
181 triangle = new Triangle2D(verticesCopy.get(0), verticesCopy.get(1), verticesCopy.get(2));
182
183
184 final var arePointsColinear = triangle.areVerticesColinear();
185
186 // only add final triangle if not co-linear (area greater than small
187 // threshold)
188 if (!arePointsColinear)
189 result.add(triangle);
190
191 // add indices of triangles verticesCopy
192 computeIndices(originalVertices, result, indices);
193
194 return result;
195 }
196
197 /**
198 * Computes indices of resulting triangles vertices respect to original
199 * polygon vertices. Indices are stored in provided indices list.
200 *
201 * @param vertices Vertices of polygon.
202 * @param triangles Triangles obtained after triangulation.
203 * @param indices Indices of original positions of resulting triangle's
204 * vertices.
205 */
206 private static void computeIndices(
207 final List<Point2D> vertices, final List<Triangle2D> triangles, final List<int[]> indices) {
208 if (indices != null) {
209 int vertexCounter;
210 int triangleVertexCounter;
211 int[] triangleIndices;
212 for (final var t : triangles) {
213 triangleVertexCounter = 0;
214 triangleIndices = new int[Triangle2D.NUM_VERTICES];
215 for (final var p1 : t.getVertices()) {
216 vertexCounter = 0;
217 for (final var p2 : vertices) {
218 if (p1 == p2) {
219 triangleIndices[triangleVertexCounter] = vertexCounter;
220 break;
221 }
222 vertexCounter++;
223 }
224 triangleVertexCounter++;
225 }
226 indices.add(triangleIndices);
227 }
228 }
229 }
230
231 /**
232 * Determines if provided triangle can be considered as an ear of the
233 * remaining polygon formed by provided vertices.
234 * An ear is usually a triangle located at a corner of a polygon.
235 * A triangle is considered an ear if no other vertex of the polygon lies
236 * within the triangle and if the triangle is not convex (is concave).
237 *
238 * @param triangle A triangle.
239 * @param polygonVertices A list of points forming the remaining polygon
240 * @return true if triangle is ear, false otherwise.
241 */
242 private static boolean isEar(final Triangle2D triangle, final List<Point2D> polygonVertices) {
243
244 boolean isInside;
245 boolean isNotConvex;
246 // in a counterclockwise polygon, reversed orientation means that
247 // triangle is not convex and cannot be an ear
248
249 final var triangleVertices = triangle.getVertices();
250
251 // check that no points in the polygon (aside from points belonging to
252 // the triangle) lie inside the triangle
253 for (final var testPoint : polygonVertices) {
254 // Do not compare with polygon elements which are triangle points
255 // if end is reached then polygon and triangle is equal and hence the
256 // polygon is the ear
257 if (triangleVertices.contains(testPoint)) {
258 continue;
259 }
260
261 isInside = triangle.isInside(testPoint);
262 isNotConvex = triangle.areVerticesClockwise();
263
264 // if a point is inside the triangle or orientation is reversed, then
265 // it is not an ear
266 if (isInside || isNotConvex) {
267 return false;
268 }
269 }
270
271 // no points in the polygon where found inside the triangle and
272 // orientation is the same, so an ear is detected
273 return true;
274 }
275 }