/* * Copyright (c) 2003-2009 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package automenta.spacenet.space.geom.text3d.math; import automenta.spacenet.space.geom.text3d.math.Triangulator.YMonotonePolygon.Triangle; import com.ardor3d.math.FastMath; import com.ardor3d.math.Vector3; import com.ardor3d.util.geom.BufferUtils; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Comparator; import java.util.PriorityQueue; import java.util.SortedSet; import java.util.Stack; import java.util.TreeSet; import java.util.Vector; import java.util.logging.Logger; public class Triangulator extends DoublyConnectedEdgeList<TriangulationVertex, TriangulationEdge> { private static final Logger logger = Logger.getLogger(Triangulator.class .getName()); private IntBuffer complete_triangulation; private Vector<YMonotonePolygon> monotone_polygons = new Vector<YMonotonePolygon>(); int polyids = 0; float getXAtY(TriangulationEdge edge, float y) { float dx = edge.getDX(); float dy = edge.getDY(); if(dy == 0) { if(edge.getOrigin().point.getYf() == y) return edge.getOrigin().point.getXf(); //logger.warning("Degenerate case, dy == 0, no idea what will happen now...."); return edge.getOrigin().point.getXf(); } float t = (y - edge.getOrigin().point.getYf()) / dy; return edge.getOrigin().point.getXf() + dx * t; } public IntBuffer triangulate(boolean cleanrun) { if(cleanrun) complete_triangulation = null; return triangulate(); } public IntBuffer triangulate() { // Have we done it before ? if(complete_triangulation == null) { // Make sure we have a valid planar closed polygon (maybe with holes) checkTriangulation(); // Create the Y-monotone polygons generateMonotonePolygons(); // Triangulate them all and count how many triangles we are going to need. int tricount = triangulateMonotonePolygons(); complete_triangulation = BufferUtils.createIntBuffer(tricount * 3); complete_triangulation.rewind(); // Copy all triangles to the buffer for(YMonotonePolygon poly : monotone_polygons) { for(Triangle t : poly.poly_tris) { complete_triangulation.put(t.p1); complete_triangulation.put(t.p2); complete_triangulation.put(t.p3); } } } return complete_triangulation; } /** * This is the sweep-line algorithm outlined in section 3.2 of "Computational Geometry", ISBN: 3-540-65620-0. */ private void generateMonotonePolygons() { class SweepLineStatus extends TreeSet<TriangulationEdge> { private static final long serialVersionUID = 1L; SweepLineComparer sweep_comparer; public SweepLineStatus() { super(new SweepLineComparer()); sweep_comparer = (SweepLineComparer) comparator(); } @Override public boolean add(TriangulationEdge edge) { boolean result = super.add(edge); if(!result) { //logger.severe("The insertion of edge "+edge+" had already been done...."); } return result; } void printElements() { for(TriangulationEdge e : this) { //logger.info("EDGE: "+e+":"+getXAtY(e, sweep_comparer.currentvertex.point.getYf())); } } public boolean remove(TriangulationEdge edge) { boolean result = super.remove(edge); if(!result) { //logger.severe("The removal of edge "+edge+" did not succeed"); } return result; } public TriangulationEdge getLeftOf(TriangulationEdge edge) { SortedSet<TriangulationEdge> hset = headSet(edge); //logger.info("hset.size():"+hset.size()); if(hset.size() == 0) { //logger.warning("We could find no left of "+edge+": "+getXAtY(edge, sweep_comparer.currentvertex.point.getYf())); //logger.info("Vertex: prev:"+((TriangulationVertex)edge.getOrigin()).prev_vert+","+edge.getOrigin()+",next:"+((TriangulationVertex)edge.getOrigin()).next_vert+")"); // Print out the whole thing //printElements(); } return hset.last(); } public void setCurrentVertex(TriangulationVertex v) { sweep_comparer.currentvertex = v; } } // Initialize type/ingoing/outgoing and such for(TriangulationVertex v : getVertices()) { v.initializeType(); } // Initialize structures SweepLineStatus sweep_line = new SweepLineStatus(); PriorityQueue<TriangulationVertex> sweep_queue = new PriorityQueue<TriangulationVertex>(getVertices().size(), new SweepQueueComparator()); sweep_queue.addAll(getVertices()); class DiagnalEdge { int src,dst; public DiagnalEdge(int src, int dst) { this.src = src; this.dst = dst; } @Override public String toString() { return "("+src+"->"+dst+")"; } } Vector<DiagnalEdge> postponed_diagonals = new Vector<DiagnalEdge>(); // Empty the priority-queue TriangulationVertex v_i; while(!sweep_queue.isEmpty()) { v_i = sweep_queue.poll(); sweep_line.setCurrentVertex(v_i); // To make sure edges are ordered correctly //logger.info("NextVertex:"+v_i.toString()+""); switch(v_i.getType()) { case START: sweep_line.add(v_i.getOutGoingEdge()); v_i.getOutGoingEdge().helper = v_i; break; case END: if(v_i.getInGoingEdge().isHelperMergeVertex()) { postponed_diagonals.add(new DiagnalEdge(v_i.getIndex(), v_i.getInGoingEdge().helper.getIndex())); } sweep_line.remove(v_i.getInGoingEdge()); break; case SPLIT: { // Find edge directly left of v_i TriangulationEdge e_j = sweep_line.getLeftOf(v_i.getOutGoingEdge()); { postponed_diagonals.add(new DiagnalEdge(v_i.getIndex(), e_j.helper.getIndex())); } e_j.helper = v_i; sweep_line.add(v_i.getOutGoingEdge()); v_i.getOutGoingEdge().helper = v_i; } break; case MERGE: { if(v_i.getInGoingEdge().isHelperMergeVertex()) { postponed_diagonals.add(new DiagnalEdge(v_i.getIndex(), v_i.getInGoingEdge().helper.getIndex())); } sweep_line.remove(v_i.getInGoingEdge()); TriangulationEdge left_of = sweep_line.getLeftOf(v_i.getInGoingEdge()); if(left_of.isHelperMergeVertex()) { postponed_diagonals.add(new DiagnalEdge(v_i.getIndex(), left_of.helper.getIndex())); } left_of.helper = v_i; } break; case REGULAR_RIGHT: // The interior lies to the left of us { TriangulationEdge left_of = sweep_line.getLeftOf(v_i.getOutGoingEdge()); if(left_of.isHelperMergeVertex()) { postponed_diagonals.add(new DiagnalEdge(v_i.getIndex(), left_of.helper.getIndex())); } left_of.helper = v_i; } break; case REGULAR_LEFT: // The interior lies to the right of us { if(v_i.getInGoingEdge().isHelperMergeVertex()) { postponed_diagonals.add(new DiagnalEdge(v_i.getIndex(), v_i.getInGoingEdge().helper.getIndex())); } sweep_line.remove(v_i.getInGoingEdge()); sweep_line.add(v_i.getOutGoingEdge()); v_i.getOutGoingEdge().helper = v_i; } break; case UNSET: //logger.info("PANIX: the type of a vertex was: "+v_i.getType()); break; } //logger.info("After:"); //sweep_line.printElements(); //logger.info("Diags: "+postponed_diagonals); } // Now add the diagonals //logger.info("\nDIAGONALS: "); for(DiagnalEdge de : postponed_diagonals) { //logger.info("Diagonal:"+de); addDiagonal(de.src, de.dst); } checkTriangulation(); // Now extract all the monotone polygons monotone_polygons.clear(); for(TriangulationEdge e : getEdges()) { e.marked = false; } for(TriangulationEdge e : getEdges()) { if(!e.marked && e.isRealEdge()) { monotone_polygons.add(new YMonotonePolygon(e)); } } checkTriangulation(); } void addDiagonal(int src, int dst) { TriangulationEdge edge = addEdge(src, dst); edge.realedge = true; edge.getTwin().realedge = true; } private boolean checkTriangulation() { for(TriangulationVertex v : getVertices()) { if(v.getFirstEdge() == null) { throw new GeometricException("We have a vertex with no edges: "+v); } if(!v.checkAllEdges()) return false; } //logger.info("\n---- checkTriangulation() succeeded: v:"+getVertices().size()+",e:"+getEdges().size()+"\n"); return true; } private int triangulateMonotonePolygons() { int tricount = 0; //logger.info("About to triangulate "+monotone_polygons.size()+" polygons"); for(YMonotonePolygon poly : monotone_polygons) { tricount += poly.triangulate(); checkTriangulation(); } return tricount; } @Override public TriangulationEdge createEdge(TriangulationVertex origin, boolean real) { return new TriangulationEdge(origin, real); } @Override public TriangulationVertex createVertex(int index, Vector3 p) { return new TriangulationVertex(index, p); } /** * This class represents a monoton polygon with respect to the y-coordinate. * * @author emanuel */ class YMonotonePolygon { class Triangle { int p1,p2,p3; Triangle(int p1, int p2, int p3, boolean clockwise) { this.p1 = clockwise ? p1 : p2; this.p2 = clockwise ? p2 : p1; this.p3 = p3; } } ArrayList<TriangulationEdge> poly_edges = new ArrayList<TriangulationEdge>(); ArrayList<Triangle> poly_tris = new ArrayList<Triangle>(); private int polyid; public YMonotonePolygon(TriangulationEdge e) { polyid = polyids++; //logger.info("YMonoe, id:"+polyid); TriangulationEdge start_edge = e; TriangulationEdge next_edge = start_edge; do { //logger.info("next_edge.getOrigin():"+next_edge.getOrigin()); next_edge.marked = true; poly_edges.add(next_edge); next_edge = (TriangulationEdge) next_edge.getNext(); if(!next_edge.isRealEdge()) { throw new GeometricException("We cannot add a non-real edge to a polygon."); } } while(start_edge != next_edge); } /** * This is the linear-time algorithm outlined in section 3.2 of "Computational Geometry", ISBN: 3-540-65620-0. * @return */ public int triangulate() { int trianglecount = (poly_edges.size()-2); int triangle_index_count = trianglecount*3; //logger.info("TODO: triangulate this poly("+this.polyid+") ! ("+(tricount/3)+" triangles will be needed...)"); if(trianglecount == 1) { poly_tris.add(new Triangle(poly_edges.get(0).getOrigin().getIndex(), poly_edges.get(1).getOrigin().getIndex(), poly_edges.get(2).getOrigin().getIndex(), false)); return triangle_index_count; // Trivial, its one triangle. } // Create the sorted list by merging the two "paths" into one sorted list. ArrayList<TriangulationVertex> queue = createSortedVertexList(); Stack<TriangulationVertex> stack = new Stack<TriangulationVertex>(); // Push the first two onto the stack. stack.push(queue.get(0)); stack.push(queue.get(1)); for(int i = 2; i < queue.size()-1; i++) { TriangulationVertex u_j = queue.get(i); if(u_j.is_left_chain != stack.peek().is_left_chain) { //TriangulationVertex head = stack.peek(); while(stack.size() > 1) { TriangulationVertex popped = stack.pop(); poly_tris.add(new Triangle(u_j.getIndex(), popped.getIndex(), stack.peek().getIndex(), !u_j.is_left_chain)); } stack.pop(); // Remove the last one. stack.push(queue.get(i-1)); stack.push(u_j); } else { //logger.info("\nYEAAAAAHHHH(left:"+u_j.is_left_chain+")"); TriangulationVertex lastpopped = stack.pop(); while(!stack.isEmpty()) { boolean is_left_of = isLeftOf(u_j.getPoint(), lastpopped.getPoint(), stack.peek().getPoint()); if(u_j.is_left_chain == is_left_of) { poly_tris.add(new Triangle(u_j.getIndex(), lastpopped.getIndex(), stack.peek().getIndex(), u_j.is_left_chain)); lastpopped = stack.pop(); //addDiagonal(u_j.getIndex(), lastpopped.getIndex()); } else { break; } } stack.push(lastpopped); stack.push(u_j); } } // Add diagonals to all verts on the stack (except first and last) TriangulationVertex lastpopped = null; if(stack.size() > 1) lastpopped = stack.pop(); TriangulationVertex last = queue.get(queue.size()-1); while(stack.size() > 0) { //addDiagonal(last.getIndex(), popped.getIndex()); poly_tris.add(new Triangle(last.getIndex(), lastpopped.getIndex(), stack.peek().getIndex(), lastpopped.is_left_chain)); lastpopped = stack.pop(); } /* int required_no_of_diagonals = (tricount/3) - 1; if(no_of_diagonals != required_no_of_diagonals) { int i = 0; for(TriangulationVertex v : queue) { logger.info("Queue["+(i++)+"]:"+v+",is_left:"+v.is_left_chain); } throw new RuntimeException("Subdivision of monoton polygon: "+polyid+" did not add the required number of diagonals: ("+no_of_diagonals+" != "+required_no_of_diagonals+")"); } */ if(trianglecount != poly_tris.size()) { throw new GeometricException("Subdivision of monoton polygon: "+polyid+" did not give as many triangles as planned:("+trianglecount+" != "+poly_tris.size()+")"); } return triangle_index_count; } private ArrayList<TriangulationVertex> createSortedVertexList() { // Find the top and bottom node O(n) time, and set the outgoing to be the one in this polygon //SortedSet<TriangulationVertex> sortedset = new TreeSet<TriangulationVertex>(new SweepQueueComparator()); TriangulationEdge top = poly_edges.get(0); TriangulationEdge bottom = top; for(TriangulationEdge edge : poly_edges) { //logger.info("Edge: "+edge); TriangulationVertex vert = ((TriangulationVertex) edge.getOrigin()); if(((TriangulationVertex)top.getOrigin()).yLessThan(vert)) top = edge; if(vert.yLessThan(bottom.getOrigin())) bottom = edge; } // DEBUG /* if(true) { logger.info("Top: "+top.getOrigin().getIndex()); logger.info("Bottom: "+bottom.getOrigin().getIndex()); } */ /* { Sphere tmp = new Sphere("Top:"+top, 5, 5, 0.01f); tmp.setLocalTranslation(new Vector3(top.point)); debugNode.attachChild(tmp); Box box = new Box("Bottom:"+bottom,bottom.point,0.01f,0.01f,0.01f); debugNode.attachChild(box); } */ // Go from top to bottom, setting them all to be leftsiders ArrayList<TriangulationVertex> arr = new ArrayList<TriangulationVertex>(); int sanity = poly_edges.size()*2; arr.add((TriangulationVertex) top.getOrigin()); TriangulationEdge tmp_left = (TriangulationEdge) top.getNext(); TriangulationEdge tmp_right = (TriangulationEdge) top.getPrev(); while(tmp_left != bottom || tmp_right != bottom) { // Ok, what should be inserted next TriangulationVertex left = (TriangulationVertex) tmp_left.getOrigin(); TriangulationVertex right = (TriangulationVertex) tmp_right.getOrigin(); left.is_left_chain = true; right.is_left_chain = false; if(left.yLessThan(right)) { //logger.info("Added Right:"+right); arr.add(right); tmp_right = (TriangulationEdge) tmp_right.getPrev(); } else { //logger.info("Added Left:"+left); arr.add(left); tmp_left = (TriangulationEdge) tmp_left.getNext(); } if(sanity-- < 0) throw new RuntimeException("We could not get from top to bottom of the poly:"+this); } arr.add((TriangulationVertex) bottom.getOrigin()); if(arr.size() != poly_edges.size()) { //logger.warning("The number of vertices does not match the number of edges: " + arr.size() + " != " + poly_edges.size()); throw new RuntimeException("The number of vertices does not match the number of edges: "+arr.size()+" != "+poly_edges.size()); } return arr; } private boolean isLeftOf(Vector3 A, Vector3 B, Vector3 P) { //return 0> (v2.x - v1.x) * (v.y - v1.y) - (v.x - v1.x) * (v2.y - v1.y); return 0 > (B.getXf()-A.getXf()) * (P.getYf()-A.getYf()) - (P.getXf()-A.getXf()) * (B.getYf()-A.getYf()); } } /** * Sort the edges according to their X-coordinate from the y coordinate of the sweepline. * * @author emanuel */ class SweepLineComparer implements Comparator<TriangulationEdge> { TriangulationVertex currentvertex = null; public int compare(TriangulationEdge edge1, TriangulationEdge edge2) { if(edge1 == edge2) return 0; // Get the x coordinate from the y coordinate of the currentvertex float x1 = getXAtY(edge1, currentvertex.point.getYf()); float x2 = getXAtY(edge2, currentvertex.point.getYf()); //if(Math.abs(x1 - x2) < FastMath.FLT_EPSILON) if(x1 == x2) { // they share a vertex, and it is the currentvertex, // then we use the dot-product of the lines (normalized) and the X-axis, since that will tell us who is most // to the right. //logger.info("--------------------"); //logger.info("Edge1: "+edge1); //logger.info("Edge2: "+edge2); //logger.info("Edges share: "+currentvertex+", use other ends."); PlanarVertex x1_v = edge1.getOtherEnd(currentvertex); //logger.info("Other end(1):"+x1_v); PlanarVertex x2_v = edge2.getOtherEnd(currentvertex); //logger.info("Other end(2):"+x2_v); Vector3 x1_v_v = new Vector3(x1_v.getPoint()).subtractLocal(currentvertex.getPoint()).normalizeLocal(); Vector3 x2_v_v = new Vector3(x2_v.getPoint()).subtractLocal(currentvertex.getPoint()).normalizeLocal(); x1 = (float)x1_v_v.dot(Vector3.UNIT_X); // edge0.getOtherEnd(currentvertex).point.x; x2 = (float)x2_v_v.dot(Vector3.UNIT_X); // edge1.getOtherEnd(currentvertex).point.x; if(Math.abs(x1 - x2) < PlanarEdge.FLT_EPSILON) { // Even worse they are also on the same level here... //logger.info("Still the same, using Y-coordinates"); x1 = x1_v.getPoint().getYf(); x2 = x2_v.getPoint().getYf(); } } if(x1 == x2) { //logger.warning("Equal vertices: "+x1+" == "+x2); } return (x1 < x2) ? -1 : 1; } } /** * Simple y-sorting * * @author emanuel */ class SweepQueueComparator implements Comparator<TriangulationVertex> { public int compare(TriangulationVertex v0, TriangulationVertex v1) { if(v0 == v1) return 0; return v0.yLessThan(v1) ? 1 : -1; } } public ArrayList<TriangulationEdge> getEdges() { return edges; } }