View Javadoc
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 }