Package com.badlogic.gdx.math

Source Code of com.badlogic.gdx.math.EarClippingTriangulator

/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/

package com.badlogic.gdx.math;

import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.ShortArray;

/** A simple implementation of the ear cutting algorithm to triangulate simple polygons without holes. For more information:
* <ul>
* <li><a href="http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html">http://cgm.cs.mcgill.ca/~godfried/
* teaching/cg-projects/97/Ian/algorithm2.html</a></li>
* <li><a
* href="http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf">http://www.geometrictools.com/Documentation
* /TriangulationByEarClipping.pdf</a></li>
* </ul>
* If the input polygon is not simple (self-intersects), there will be output but it is of unspecified quality (garbage in,
* garbage out).
* @author badlogicgames@gmail.com
* @author Nicolas Gramlich (optimizations, collinear edge support)
* @author Eric Spitz
* @author Thomas ten Cate (bugfixes, optimizations)
* @author Nathan Sweet (rewrite, return indices, no allocation, optimizations) */
public class EarClippingTriangulator {
  static private final int CONCAVE = -1;
  static private final int TANGENTIAL = 0;
  static private final int CONVEX = 1;

  private final ShortArray indicesArray = new ShortArray();
  private short[] indices;
  private float[] vertices;
  private int vertexCount;
  private final IntArray vertexTypes = new IntArray();
  private final ShortArray triangles = new ShortArray();

  /** @see #computeTriangles(float[], int, int) */
  public ShortArray computeTriangles (FloatArray vertices) {
    return computeTriangles(vertices.items, 0, vertices.size);
  }

  /** @see #computeTriangles(float[], int, int) */
  public ShortArray computeTriangles (float[] vertices) {
    return computeTriangles(vertices, 0, vertices.length);
  }

  /** Triangulates the given (convex or concave) simple polygon to a list of triangle vertices.
   * @param vertices pairs describing vertices of the polygon, in either clockwise or counterclockwise order.
   * @return triples of triangle indices in clockwise order. Note the returned array is reused for later calls to the same
   *         method. */
  public ShortArray computeTriangles (float[] vertices, int offset, int count) {
    this.vertices = vertices;
    int vertexCount = this.vertexCount = count / 2;

    ShortArray indicesArray = this.indicesArray;
    indicesArray.clear();
    indicesArray.ensureCapacity(vertexCount);
    indicesArray.size = vertexCount;
    short[] indices = this.indices = indicesArray.items;
    if (areVerticesClockwise(vertices, offset, count)) {
      for (short i = 0; i < vertexCount; i++)
        indices[i] = i;
    } else {
      for (int i = 0, n = vertexCount - 1; i < vertexCount; i++)
        indices[i] = (short)(n - i); // Reversed.
    }

    IntArray vertexTypes = this.vertexTypes;
    vertexTypes.clear();
    vertexTypes.ensureCapacity(vertexCount);
    for (int i = 0, n = vertexCount; i < n; ++i)
      vertexTypes.add(classifyVertex(i));

    // A polygon with n vertices has a triangulation of n-2 triangles.
    ShortArray triangles = this.triangles;
    triangles.clear();
    triangles.ensureCapacity(Math.max(0, vertexCount - 2) * 3);
    triangulate();
    return triangles;
  }

  private void triangulate () {
    int[] vertexTypes = this.vertexTypes.items;

    while (vertexCount > 3) {
      int earTipIndex = findEarTip();
      cutEarTip(earTipIndex);

      // The type of the two vertices adjacent to the clipped vertex may have changed.
      int previousIndex = previousIndex(earTipIndex);
      int nextIndex = earTipIndex == vertexCount ? 0 : earTipIndex;
      vertexTypes[previousIndex] = classifyVertex(previousIndex);
      vertexTypes[nextIndex] = classifyVertex(nextIndex);
    }

    if (vertexCount == 3) {
      ShortArray triangles = this.triangles;
      short[] indices = this.indices;
      triangles.add(indices[0]);
      triangles.add(indices[1]);
      triangles.add(indices[2]);
    }
  }

  /** @return {@link #CONCAVE}, {@link #TANGENTIAL} or {@link #CONVEX} */
  private int classifyVertex (int index) {
    short[] indices = this.indices;
    int previous = indices[previousIndex(index)] * 2;
    int current = indices[index] * 2;
    int next = indices[nextIndex(index)] * 2;
    float[] vertices = this.vertices;
    return computeSpannedAreaSign(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1],
      vertices[next], vertices[next + 1]);
  }

  private int findEarTip () {
    int vertexCount = this.vertexCount;
    for (int i = 0; i < vertexCount; i++)
      if (isEarTip(i)) return i;

    // Desperate mode: if no vertex is an ear tip, we are dealing with a degenerate polygon (e.g. nearly collinear).
    // Note that the input was not necessarily degenerate, but we could have made it so by clipping some valid ears.

    // Idea taken from Martin Held, "FIST: Fast industrial-strength triangulation of polygons", Algorithmica (1998),
    // http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.115.291

    // Return a convex or tangential vertex if one exists.
    int[] vertexTypes = this.vertexTypes.items;
    for (int i = 0; i < vertexCount; i++)
      if (vertexTypes[i] != CONCAVE) return i;
    return 0; // If all vertices are concave, just return the first one.
  }

  private boolean isEarTip (int earTipIndex) {
    int[] vertexTypes = this.vertexTypes.items;
    if (vertexTypes[earTipIndex] == CONCAVE) return false;

    int previousIndex = previousIndex(earTipIndex);
    int nextIndex = nextIndex(earTipIndex);
    short[] indices = this.indices;
    int p1 = indices[previousIndex] * 2;
    int p2 = indices[earTipIndex] * 2;
    int p3 = indices[nextIndex] * 2;
    float[] vertices = this.vertices;
    float p1x = vertices[p1], p1y = vertices[p1 + 1];
    float p2x = vertices[p2], p2y = vertices[p2 + 1];
    float p3x = vertices[p3], p3y = vertices[p3 + 1];

    // Check if any point is inside the triangle formed by previous, current and next vertices.
    // Only consider vertices that are not part of this triangle, or else we'll always find one inside.
    for (int i = nextIndex(nextIndex); i != previousIndex; i = nextIndex(i)) {
      // Concave vertices can obviously be inside the candidate ear, but so can tangential vertices
      // if they coincide with one of the triangle's vertices.
      if (vertexTypes[i] != CONVEX) {
        int v = indices[i] * 2;
        float vx = vertices[v];
        float vy = vertices[v + 1];
        // Because the polygon has clockwise winding order, the area sign will be positive if the point is strictly inside.
        // It will be 0 on the edge, which we want to include as well.
        // note: check the edge defined by p1->p3 first since this fails _far_ more then the other 2 checks.
        if (computeSpannedAreaSign(p3x, p3y, p1x, p1y, vx, vy) >= 0) {
          if (computeSpannedAreaSign(p1x, p1y, p2x, p2y, vx, vy) >= 0) {
            if (computeSpannedAreaSign(p2x, p2y, p3x, p3y, vx, vy) >= 0) return false;
          }
        }
      }
    }
    return true;
  }

  private void cutEarTip (int earTipIndex) {
    short[] indices = this.indices;
    ShortArray triangles = this.triangles;

    triangles.add(indices[previousIndex(earTipIndex)]);
    triangles.add(indices[earTipIndex]);
    triangles.add(indices[nextIndex(earTipIndex)]);

    indicesArray.removeIndex(earTipIndex);
    vertexTypes.removeIndex(earTipIndex);
    vertexCount--;
  }

  private int previousIndex (int index) {
    return (index == 0 ? vertexCount : index) - 1;
  }

  private int nextIndex (int index) {
    return (index + 1) % vertexCount;
  }

  static private boolean areVerticesClockwise (float[] vertices, int offset, int count) {
    if (count <= 2) return false;
    float area = 0, p1x, p1y, p2x, p2y;
    for (int i = offset, n = offset + count - 3; i < n; i += 2) {
      p1x = vertices[i];
      p1y = vertices[i + 1];
      p2x = vertices[i + 2];
      p2y = vertices[i + 3];
      area += p1x * p2y - p2x * p1y;
    }
    p1x = vertices[count - 2];
    p1y = vertices[count - 1];
    p2x = vertices[0];
    p2y = vertices[1];
    return area + p1x * p2y - p2x * p1y < 0;
  }

  static private int computeSpannedAreaSign (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
    float area = p1x * (p3y - p2y);
    area += p2x * (p1y - p3y);
    area += p3x * (p2y - p1y);
    return (int)Math.signum(area);
  }
}
TOP

Related Classes of com.badlogic.gdx.math.EarClippingTriangulator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.