Package org.osm2world.core.math.algorithms

Source Code of org.osm2world.core.math.algorithms.EarClippingTriangulationUtil

package org.osm2world.core.math.algorithms;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.osm2world.core.math.GeometryUtil;
import org.osm2world.core.math.InvalidGeometryException;
import org.osm2world.core.math.LineSegmentXZ;
import org.osm2world.core.math.SimplePolygonXZ;
import org.osm2world.core.math.TriangleXZ;
import org.osm2world.core.math.VectorXZ;

public final class EarClippingTriangulationUtil {

  private EarClippingTriangulationUtil() { }
 
  /**
   * triangulates a two-dimensional polygon
   * by creating a simple polygon first
   * (integrating holes into the polygon outline),
   * then using Ear Clipping on that simple polygon.
   */
  public static final List<TriangleXZ> triangulate(
      SimplePolygonXZ polygon,
      Collection<SimplePolygonXZ> holes) {
   
    /*
     * store the outline of the polygon;
     * additional vertices from holes and terrain will be integrated later.
     * Use LinkedList for insertion performance.
     */
   
    List<VectorXZ> polygonOutline = new LinkedList<VectorXZ>(polygon.getVertexLoop());
   
    /* integrate holes */
   
    insertHolesInPolygonOutline(polygonOutline, holes);
   
//    if (simplify) {
//     * @param simplify  if true, the polygon will be modified in a way
//     *                  that improves the robustness of the algorithm,
//     *                  but does not preserve the original shape entirely.
//     *                  This should be used if a first attempt to triangulate failed.
//      simplifyOutline(polygonOutline);
//    }
   
    /* triangulate */
   
    if (polygonOutline.size() >= 3) {
      return triangulateSimplePolygon(polygonOutline);
    } else {
      return Collections.emptyList();
    }
   
  }

  /**
   * inserts multiple holes into a polygon outline using
   * {@link #insertHoleInPolygonOutline(List, SimplePolygonXZ, List)}
   *
   * TODO: public for debugging purposes
   */
  public static void insertHolesInPolygonOutline(
      List<VectorXZ> polygonOutline, Collection<SimplePolygonXZ> holes) {
   
    List<SimplePolygonXZ> remainingHoles = new LinkedList<SimplePolygonXZ>(holes);
   
    //It is possible that some holes block other holes' access to the outline.
    //However, at least one hole will be inserted during each loop execution.
    while (!remainingHoles.isEmpty()) {
      for (Iterator<SimplePolygonXZ> holeIt = remainingHoles.iterator(); holeIt.hasNext(); ) {
        SimplePolygonXZ hole = (SimplePolygonXZ) holeIt.next();
        boolean success = insertHoleInPolygonOutline(polygonOutline, hole, remainingHoles);
        if (success) {
          holeIt.remove();
        }
      }
    }
  }
 
  /**
   * @param polygonOutline  polygon outline; will be modified directly to perform the insertion
   * @param hole   hole to be inserted
   * @param holes  all holes that haven't been integrated into the polygon yet, including 'hole'
   * @return       true if inserting the hole was successful
   */
  static final boolean insertHoleInPolygonOutline(
      List<VectorXZ> polygonOutline, SimplePolygonXZ hole, List<SimplePolygonXZ> holes) {
   
    int innerIndex;
    Integer outerIndex = null;
   
    List<VectorXZ> holeVertices = hole.getVertices();
   
    for (innerIndex = 0; innerIndex < holeVertices.size(); innerIndex++) {
      outerIndex = findVisibleOutlineVertex(polygonOutline,
          holeVertices.get(innerIndex), holes);
      if (outerIndex != null) { break; }
    }
   
    if (outerIndex == null) {
      return false;
    } else {

      SimplePolygonXZ outerPolygon = new SimplePolygonXZ(new ArrayList<VectorXZ>(polygonOutline)); //TODO: avoid creating this every time
     
      polygonOutline.add(outerIndex+1, polygonOutline.get(outerIndex));
     
      if (hole.isClockwise() ^ outerPolygon.isClockwise()) {
        polygonOutline.addAll(outerIndex+1,
            rearrangeOutline(hole.getVertexLoop(), innerIndex, false));
      } else {
        polygonOutline.addAll(outerIndex+1,
            rearrangeOutline(hole.getVertexLoop(), innerIndex, true));
      }
     
      return true;
     
    }
   
  }

  /**
   * rearranges a polygon outline.
   *
   * @param outline  old polygon outline, will not be modified.
   *                 First and last vertex need to be identical.
   * @param newStart index of the new first/last outline vertex
   * @param invert   determines whether the order of vertices is inverted
   * @return  New polygon outline.
   *       The new first and last vertex are identical.
   *
   */
  static final List<VectorXZ> rearrangeOutline(
      List<VectorXZ> outline, int newStart, boolean invert) {
       
    List<VectorXZ> newOutline = new ArrayList<VectorXZ>(outline.size());
   
    boolean complete = false;
    boolean oldStartAdded = false;
    int currentIndex = newStart;
   
    while (!complete) {
     
      /* add vertex to new outline */
     
      if (currentIndex == 0 || currentIndex == outline.size() - 1) {
        if (!oldStartAdded) {//remove original duplication of first/last node
          newOutline.add(outline.get(0));
          oldStartAdded = true;
        }
      } else {
        newOutline.add(outline.get(currentIndex));
      }
     
      /* shift current index */
     
      if (!invert) {
        currentIndex += 1;
        if (currentIndex >= outline.size()) {
          currentIndex = 0;
        }
      } else {
        currentIndex -= 1;
        if (currentIndex < 0) {
          currentIndex = outline.size() - 1;
        }
      }
     
      if (currentIndex == newStart) {
        complete = true;
      }
     
    }
   
    newOutline.add(outline.get(newStart));
   
    return newOutline;
  }

  static final void insertVertexInPolygonOutline(
      List<VectorXZ> polygonOutline, VectorXZ point) {
   
    int index = findVisibleOutlineVertex(polygonOutline, point,
        Collections.<SimplePolygonXZ>emptyList());
   
    polygonOutline.add(index+1, point);
    polygonOutline.add(index+2, polygonOutline.get(index));
   
  }

  /**
   * finds a vertex in the polygon outline that is visible from a given point.
   * Visibility means that the connection between the point and the outline
   * vertex does not intersect the outline or any hole polygon.
   *
   * @return  index of a vertex in the polygon outline
   *          or null if none was found.
   *          The method will always find an outline vertex
   *          if there are no holes.
   */
  static final Integer findVisibleOutlineVertex(
      List<VectorXZ> polygonOutline, VectorXZ point,
      Iterable<SimplePolygonXZ> holes) {
   
    // TODO (performance): replace primitive algorithm with more efficient one
   
    int outerIndex = -1;
   
    checkOuterVertex:
    for (VectorXZ outerVertex : polygonOutline) {
     
      outerIndex += 1;
     
      for (int i=0; i+1 < polygonOutline.size(); i++) {
        if (null != GeometryUtil.getTrueLineSegmentIntersection(
            point, outerVertex,
            polygonOutline.get(i), polygonOutline.get(i+1))) {
          continue checkOuterVertex;
        }
      }
     
      for (SimplePolygonXZ hole : holes) {
        if (hole.intersects(point, outerVertex)) {
          continue checkOuterVertex;
        }
      }
     
      return outerIndex;
     
    }
   
    return null;
   
  }

  /**
   * "simplifies" the outline by removing vertices that are (almost)
   * on a straight edge, or very close to each other,
   * because the triangulation method cannot handle these situations
   *  - they produce zero-area polygons.
   *
   * Using this method <em>does</em> affect the result, especially if the
   * elevation data associated with the eliminated vertices isn't properly
   * represented by linear interpolation between the adjacent vertices.
   */
  private static void simplifyOutline(List<VectorXZ> outline) {
   
    outline.remove(outline.size()-1);
   
    Set<Integer> removeIndizes = new HashSet<Integer>();
   
    /* find vertices on straight lines */
   
    for (int i = 0; i < outline.size(); i++) {
      if (GeometryUtil.distanceFromLineSegment(
        outline.get(i),
        new LineSegmentXZ(vertexBefore(outline, i),
          vertexAfter(outline, i))) < 0.001) {
        removeIndizes.add(i);
      }
    }
   
//    these didn't have a noticeable effect yet
//
//    /* remove two vertices if they are very close and
//     * also close to each other's position in the sequence */
//
//    for (int i = 0; i < outline.size(); i++) {
//      if (VectorXZ.distance(outline.get(i),
//          vertexAfter(outline, i)) < 0.001) {
//        removeIndizes.add(i);
//      }
//    }
//
//    for (int i = 0; i < outline.size(); i++) {
//      if (VectorXZ.distance(vertexBefore(outline, i),
//          vertexAfter(outline, i)) < 0.001) {
//        removeIndizes.add(indexBefore(outline, i));
//        removeIndizes.add(i);
//      }
//    }
       
    /* actually remove the vertices */
   
    for (int i = outline.size(); i >= 0; i--) {
      if (removeIndizes.contains(i)) {
        System.out.println("simplify removes vertex #" + i + " in outline: " + outline);
        outline.remove(i);
      }
    }
   
    /* restore the polygon loop property */
   
    outline.add(outline.get(0));
   
  }
 
  /**
   * Triangulates a simple polygon using Ear Clipping.
   * The implementation is based on the paper
   * "Triangulation by Ear Clipping" by David Eberly.
   *
   * @param outline  outline of the polygon to triangulate;
   *                 can be arbitrarily modified by this method.
   */
  static final List<TriangleXZ> triangulateSimplePolygon(
      List<VectorXZ> outline) {
   
    if (outline.size() == 3) {
      return Collections.singletonList(
          new TriangleXZ(outline.get(1), outline.get(2), outline.get(3)));
    }
       
    outline.remove(0); //TODO: only while first/last vertex is still duplicated
   
    List<TriangleXZ> triangles = new ArrayList<TriangleXZ>(outline.size() - 2);
   
    boolean progress = true;
   
    while(outline.size() >= 3 && progress) {
      progress = false;
      for (int i=0; i < outline.size(); i++) {
        if (isEarTip(i, outline)) {
          triangles.add(triangleAtTip(i, outline));
          outline.remove(i);
          progress = true;
          break;
        }
      }
    }
   
    if (outline.size() >= 3) {
      throw new InvalidGeometryException("failed to triangulate outline."
          + "\nRemaining: " + outline
          + "\nTriangles: " + triangles);
    }
           
//    TODO: try to remove the need for progress check
//      TODO (performance) use better algorithm instead of the n^3 above

    return triangles;
   
  }

  static boolean isEarTip(int i, List<VectorXZ> outline) {
   
    if (isConvex(i, outline)) {
     
      TriangleXZ triangleAtTip = triangleAtTip(i, outline);
     
      for (VectorXZ vertex : outline) {
        if (vertex != triangleAtTip.v1
            && vertex != triangleAtTip.v2
            && vertex != triangleAtTip.v3
            && triangleAtTip.contains(vertex)) {
          return false;
        }
      }
     
      return true;
     
    }
   
    return false;
   
  }

  /**
   * //TODO: outline expects NO duplication of first/last node
   */
  static boolean isConvex(int i, List<VectorXZ> outline) {
   
    List<VectorXZ> tempVertices = new ArrayList<VectorXZ>(outline);
    tempVertices.add(outline.get(0));
    SimplePolygonXZ tempPolygon = new SimplePolygonXZ(tempVertices);

    //TODO (performance) avoid creating polygon by passing clockwise information
   
    VectorXZ segBefore = outline.get(i).subtract(vertexBefore(outline, i));
    VectorXZ segAfter = vertexAfter(outline, i).subtract(outline.get(i));
   
    return tempPolygon.isClockwise() ^
      segBefore.z * segAfter.x - segBefore.x * segAfter.z < 0;
   
  }
   
  static final TriangleXZ triangleAtTip(int i, List<VectorXZ> outline) {
   
    return new TriangleXZ(vertexBefore(outline, i),
        outline.get(i), vertexAfter(outline, i));
   
  }

  private static final VectorXZ vertexBefore(List<VectorXZ> outline, int i) {
    int beforeI = indexBefore(outline, i);
    return outline.get(beforeI);
  }

  private static int indexBefore(List<VectorXZ> outline, int i) {
    return (outline.size() + i - 1) % outline.size();
  }

  private static final VectorXZ vertexAfter(List<VectorXZ> outline, int i) {
    int afterI = indexAfter(outline, i);
    return outline.get(afterI);
  }

  private static int indexAfter(List<VectorXZ> outline, int i) {
    return (i + 1) % outline.size();
  }
 
  /**
   * TODO (documentation)
   */
  static final Collection<SimplePolygonXZ> splitAlong(SimplePolygonXZ splitPolygon,
      Collection<VectorXZ[]> splitLines) {
   
    Collection<SimplePolygonXZ> polygons = new ArrayList<SimplePolygonXZ>(1);
    polygons.add(splitPolygon);
   
    for (VectorXZ[] splitLine : splitLines) {
     
      Collection<SimplePolygonXZ> newPolygons = new ArrayList<SimplePolygonXZ>(polygons.size());
     
      for (SimplePolygonXZ polygon : polygons) {
       
        Map<Integer, VectorXZ> intersectionPos;
        Map<Integer, LineSegmentXZ> intersectionPartner;
       
        for (int lineVertexI = 0; lineVertexI + 1 < splitLine.length; lineVertexI++) {
         
        }
       
        //TODO: implement (but check whether this method is actually needed first)
       
      }
       
      polygons = newPolygons;
     
    }
   
    return polygons;
   
  }
 
}
TOP

Related Classes of org.osm2world.core.math.algorithms.EarClippingTriangulationUtil

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.