Advertisement
Guest User

Untitled

a guest
Apr 1st, 2012
353
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 10.22 KB | None | 0 0
  1. /*
  2.  * Copyright 2010 Mario Zechner (contact@badlogicgames.com), Nathan Sweet (admin@esotericsoftware.com), Nicolas Gramlich
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
  5.  * License. You may obtain a copy of the License at
  6.  *
  7.  * http://www.apache.org/licenses/LICENSE-2.0
  8.  *
  9.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
  10.  * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
  11.  * governing permissions and limitations under the License.
  12.  */
  13.  
  14. package com.badlogic.gdx.math;
  15.  
  16. import java.util.ArrayList;
  17. import java.util.Collections;
  18. import java.util.List;
  19.  
  20. /**
  21.  * A simple implementation of the ear cutting algorithm to triangulate simple
  22.  * polygons without holes. For more information:
  23.  * http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html
  24.  * http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
  25.  *
  26.  * @author badlogicgames@gmail.com
  27.  * @author Nicolas Gramlich (Improved performance. Collinear edges are now supported.)
  28.  */
  29. public final class EarClippingTriangulator {
  30.  
  31.   private static final int CONCAVE = 1;
  32.   private static final int CONVEX = -1;
  33.  
  34.   private int concaveVertexCount;
  35.  
  36.   /**
  37.    * Triangulates the given (concave) polygon to a list of triangles. The
  38.    * resulting triangles have clockwise order.
  39.    * @param polygon the polygon
  40.    * @return the triangles
  41.    */
  42.   public List<Vector2> computeTriangles(final List<Vector2> polygon) {
  43.     // TODO Check if LinkedList performs better
  44.     final ArrayList<Vector2> triangles = new ArrayList<Vector2>();
  45.     final ArrayList<Vector2> vertices = new ArrayList<Vector2>(polygon.size());
  46.     vertices.addAll(polygon);
  47.  
  48.     if(vertices.size() == 3) {
  49.       triangles.addAll(vertices);
  50.       return triangles;
  51.     }
  52.  
  53.     while(vertices.size() >= 3) {
  54.       // TODO Usually(Always?) only the Types of the vertices next to the ear change! --> Improve
  55.       final int vertexTypes[] = this.classifyVertices(vertices);
  56.  
  57.       final int vertexCount = vertices.size();
  58.       for(int index = 0; index < vertexCount; index++) {
  59.         if(this.isEarTip(vertices, index, vertexTypes)) {
  60.           this.cutEarTip(vertices, index, triangles);
  61.           break;
  62.         }
  63.       }
  64.     }
  65.  
  66.     return triangles;
  67.   }
  68.  
  69.   private static boolean areVerticesClockwise(final ArrayList<Vector2> pVertices) {
  70.     final int vertexCount = pVertices.size();
  71.  
  72.     float area = 0;
  73.     for(int i = 0; i < vertexCount; i++) {
  74.       final Vector2 p1 = pVertices.get(i);
  75.       final Vector2 p2 = pVertices.get(EarClippingTriangulator.computeNextIndex(pVertices, i));
  76.       area += p1.x * p2.y - p2.x * p1.y;
  77.     }
  78.  
  79.     if(area < 0) {
  80.       return true;
  81.     } else {
  82.       return false;
  83.     }
  84.   }
  85.  
  86.   /**
  87.    * @param pVertices
  88.    * @return An array of length <code>pVertices.size()</code> filled with either {@link EarClippingTriangulator#CONCAVE} or
  89.    * {@link EarClippingTriangulator#CONVEX}.
  90.    */
  91.   private int[] classifyVertices(final ArrayList<Vector2> pVertices) {
  92.     final int vertexCount = pVertices.size();
  93.  
  94.     final int[] vertexTypes = new int[vertexCount];
  95.     this.concaveVertexCount = 0;
  96.  
  97.     /* Ensure vertices are in clockwise order. */
  98.     if(!EarClippingTriangulator.areVerticesClockwise(pVertices)) {
  99.       Collections.reverse(pVertices);
  100.     }
  101.  
  102.     for(int index = 0; index < vertexCount; index++) {
  103.       final int previousIndex = EarClippingTriangulator.computePreviousIndex(pVertices, index);
  104.       final int nextIndex = EarClippingTriangulator.computeNextIndex(pVertices, index);
  105.  
  106.       final Vector2 previousVertex = pVertices.get(previousIndex);
  107.       final Vector2 currentVertex = pVertices.get(index);
  108.       final Vector2 nextVertex = pVertices.get(nextIndex);
  109.  
  110.       if(EarClippingTriangulator.isTriangleConvex(previousVertex.x, previousVertex.y, currentVertex.x, currentVertex.y, nextVertex.x, nextVertex.y)) {
  111.         vertexTypes[index] = CONVEX;
  112.       } else {
  113.         vertexTypes[index] = CONCAVE;
  114.         this.concaveVertexCount++;
  115.       }
  116.     }
  117.  
  118.     return vertexTypes;
  119.   }
  120.  
  121.   private static boolean isTriangleConvex(final float pX1, final float pY1, final float pX2, final float pY2, final float pX3, final float pY3) {
  122.     if(EarClippingTriangulator.computeSpannedAreaSign(pX1, pY1, pX2, pY2, pX3, pY3) < 0) {
  123.       return false;
  124.     } else {
  125.       return true;
  126.     }
  127.   }
  128.  
  129.   private static int computeSpannedAreaSign(final float pX1, final float pY1, final float pX2, final float pY2, final float pX3, final float pY3) {
  130.     float area = 0;
  131.  
  132.     area += pX1 * (pY3 - pY2);
  133.     area += pX2 * (pY1 - pY3);
  134.     area += pX3 * (pY2 - pY1);
  135.  
  136.     return (int)Math.signum(area);
  137.   }
  138.  
  139.   /**
  140.    * @return <code>true</code> when the Triangles contains one or more vertices, <code>false</code> otherwise.
  141.    */
  142.   private static boolean isAnyVertexInTriangle(final ArrayList<Vector2> pVertices, final int[] pVertexTypes, final float pX1, final float pY1, final float pX2, final float pY2, final float pX3, final float pY3) {
  143.     int i = 0;
  144.  
  145.     final int vertexCount = pVertices.size();
  146.     while(i < vertexCount - 1) {
  147.       if((pVertexTypes[i] == CONCAVE)) {
  148.         final Vector2 currentVertex = pVertices.get(i);
  149.  
  150.         final float currentVertexX = currentVertex.x;
  151.         final float currentVertexY = currentVertex.y;
  152.  
  153.         /* TODO The following condition fails for perpendicular, axis aligned triangles!
  154.          * Removing it doesn't seem to cause problems.
  155.          * Maybe it was an optimization?
  156.          * Maybe it tried to handle collinear pieces ? */
  157. //        if(((currentVertexX != pX1) && (currentVertexY != pY1)) || ((currentVertexX != pX2) && (currentVertexY != pY2)) || ((currentVertexX != pX3) && (currentVertexY != pY3))) {
  158.           final int areaSign1 = EarClippingTriangulator.computeSpannedAreaSign(pX1, pY1, pX2, pY2, currentVertexX, currentVertexY);
  159.           final int areaSign2 = EarClippingTriangulator.computeSpannedAreaSign(pX2, pY2, pX3, pY3, currentVertexX, currentVertexY);
  160.           final int areaSign3 = EarClippingTriangulator.computeSpannedAreaSign(pX3, pY3, pX1, pY1, currentVertexX, currentVertexY);
  161.  
  162.           if(areaSign1 > 0 && areaSign2 > 0 && areaSign3 > 0) {
  163.             return true;
  164.           } else if(areaSign1 <= 0 && areaSign2 <= 0 && areaSign3 <= 0) {
  165.             return true;
  166.           }
  167. //        }
  168.       }
  169.       i++;
  170.     }
  171.     return false;
  172.   }
  173.  
  174.   private boolean isEarTip(final ArrayList<Vector2> pVertices, final int pEarTipIndex, final int[] pVertexTypes) {
  175.     if(this.concaveVertexCount != 0) {
  176.       final Vector2 previousVertex = pVertices.get(EarClippingTriangulator.computePreviousIndex(pVertices, pEarTipIndex));
  177.       final Vector2 currentVertex = pVertices.get(pEarTipIndex);
  178.       final Vector2 nextVertex = pVertices.get(EarClippingTriangulator.computeNextIndex(pVertices, pEarTipIndex));
  179.  
  180.       if(EarClippingTriangulator.isAnyVertexInTriangle(pVertices, pVertexTypes, previousVertex.x, previousVertex.y, currentVertex.x, currentVertex.y, nextVertex.x, nextVertex.y)) {
  181.         return false;
  182.       } else {
  183.         return true;
  184.       }
  185.     } else {
  186.       return true;
  187.     }
  188.   }
  189.  
  190.   private void cutEarTip(final ArrayList<Vector2> pVertices, final int pEarTipIndex, final ArrayList<Vector2> pTriangles) {
  191.     final int previousIndex = EarClippingTriangulator.computePreviousIndex(pVertices, pEarTipIndex);
  192.     final int nextIndex = EarClippingTriangulator.computeNextIndex(pVertices, pEarTipIndex);
  193.  
  194.     if(!EarClippingTriangulator.isCollinear(pVertices, previousIndex, pEarTipIndex, nextIndex)) {
  195.       pTriangles.add(new Vector2(pVertices.get(previousIndex)));
  196.       pTriangles.add(new Vector2(pVertices.get(pEarTipIndex)));
  197.       pTriangles.add(new Vector2(pVertices.get(nextIndex)));
  198.     }
  199.  
  200.     pVertices.remove(pEarTipIndex);
  201.     if(pVertices.size() >= 3) {
  202.       EarClippingTriangulator.removeCollinearNeighborEarsAfterRemovingEarTip(pVertices, pEarTipIndex);
  203.     }
  204.   }
  205.  
  206.   private static void removeCollinearNeighborEarsAfterRemovingEarTip(final ArrayList<Vector2> pVertices, final int pEarTipCutIndex) {
  207.     final int collinearityCheckNextIndex = pEarTipCutIndex % pVertices.size();
  208.     int collinearCheckPreviousIndex = EarClippingTriangulator.computePreviousIndex(pVertices, collinearityCheckNextIndex);
  209.  
  210.     if(EarClippingTriangulator.isCollinear(pVertices, collinearityCheckNextIndex)) {
  211.       pVertices.remove(collinearityCheckNextIndex);
  212.  
  213.       if(pVertices.size() > 3) {
  214.         /* Update */
  215.         collinearCheckPreviousIndex = EarClippingTriangulator.computePreviousIndex(pVertices, collinearityCheckNextIndex);
  216.         if(EarClippingTriangulator.isCollinear(pVertices, collinearCheckPreviousIndex)){
  217.           pVertices.remove(collinearCheckPreviousIndex);
  218.         }
  219.       }
  220.     } else if(EarClippingTriangulator.isCollinear(pVertices, collinearCheckPreviousIndex)){
  221.       pVertices.remove(collinearCheckPreviousIndex);
  222.     }
  223.   }
  224.  
  225.   private static boolean isCollinear(final ArrayList<Vector2> pVertices, final int pIndex) {
  226.     final int previousIndex = EarClippingTriangulator.computePreviousIndex(pVertices, pIndex);
  227.     final int nextIndex = EarClippingTriangulator.computeNextIndex(pVertices, pIndex);
  228.  
  229.     return EarClippingTriangulator.isCollinear(pVertices, previousIndex, pIndex, nextIndex);
  230.   }
  231.  
  232.   private static boolean isCollinear(final ArrayList<Vector2> pVertices, final int pPreviousIndex, final int pIndex, final int pNextIndex) {
  233.     final Vector2 previousVertex = pVertices.get(pPreviousIndex);
  234.     final Vector2 vertex = pVertices.get(pIndex);
  235.     final Vector2 nextVertex = pVertices.get(pNextIndex);
  236.  
  237.     return EarClippingTriangulator.computeSpannedAreaSign(previousVertex.x, previousVertex.y, vertex.x, vertex.y, nextVertex.x, nextVertex.y) == 0;
  238.   }
  239.  
  240.   private static int computePreviousIndex(final List<Vector2> pVertices, final int pIndex) {
  241.     return pIndex == 0 ? pVertices.size() - 1 : pIndex - 1;
  242.   }
  243.  
  244.   private static int computeNextIndex(final List<Vector2> pVertices, final int pIndex) {
  245.     return pIndex == pVertices.size() - 1 ? 0 : pIndex + 1;
  246.   }
  247. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement