Browse Source

added Graphics triangulation for 2d mesh drawing

ncannasse 12 years ago
parent
commit
3118ddb514

+ 131 - 46
h2d/Graphics.hx

@@ -1,77 +1,162 @@
 package h2d;
 package h2d;
 
 
-#if flash
-@:allow(h2d.Graphics)
-class GraphicsContext {
-	
-	var g : Graphics;
-	var mc : flash.display.Sprite;
-	var mcg : flash.display.Graphics;
+private typedef GraphicsPoint = hxd.poly2tri.Point;
+
+private class GraphicsContent extends h3d.prim.Primitive {
+
+	var tmp : hxd.FloatBuffer;
+	var index : hxd.IndexBuffer;
 	
 	
-	function new(g) {
-		this.g = g;
-		this.mc = new flash.display.Sprite();
-		mcg = mc.graphics;
+	public function new() {
+		reset();
 	}
 	}
-	
-	public inline function beginFill( color : Int, alpha = 1. ) {
-		mcg.beginFill(color, alpha);
+
+	override public function triCount() {
+		if( indexes == null )
+			return Std.int(index.length / 3);
+		return Std.int(indexes.count / 3);
 	}
 	}
 	
 	
-	public inline function drawCircle(cx, cy, radius) {
-		mcg.drawCircle(cx, cy, radius);
+	public inline function addIndex(i) {
+		index.push(i);
+	}
+
+	public inline function add( x : Float, y : Float, u : Float, v : Float, r : Float, g : Float, b : Float, a : Float  ) {
+		tmp.push(x);
+		tmp.push(y);
+		tmp.push(u);
+		tmp.push(v);
+		tmp.push(r);
+		tmp.push(g);
+		tmp.push(b);
+		tmp.push(a);
 	}
 	}
 	
 	
-	public inline function drawRect(x,y,width,height) {
-		mcg.drawRect(x, y, width, height);
+	override function alloc( engine : h3d.Engine ) {
+		if( tmp == null ) reset();
+		buffer = engine.mem.allocVector(tmp, 8, 4);
+		indexes = engine.mem.allocIndex(index);
 	}
 	}
 	
 	
-	public inline function endFill() {
-		mcg.endFill();
+	public function reset() {
+		tmp = new hxd.FloatBuffer();
+		index = new hxd.IndexBuffer();
+		if( buffer != null ) {
+			buffer.dispose();
+			indexes.dispose();
+		}
+		buffer = null;
+		indexes = null;
 	}
 	}
 	
 	
 }
 }
 
 
 class Graphics extends Drawable {
 class Graphics extends Drawable {
 
 
-	var tile : Tile;
-	var ctx : GraphicsContext;
+	var content : GraphicsContent;
+	var pts : Array<GraphicsPoint>;
+	var pindex : Int;
+	var prev : Array<Array<GraphicsPoint>>;
+	var curR : Float;
+	var curG : Float;
+	var curB : Float;
+	var curA : Float;
+	
+	public var tile : h2d.Tile;
 	
 	
 	public function new(?parent) {
 	public function new(?parent) {
 		super(parent);
 		super(parent);
+		content = new GraphicsContent();
+		shader.hasVertexColor = true;
+		tile = h2d.Tile.fromColor(0xFFFFFFFF);
+		pts = [];
+		prev = [];
+	}
+	
+	public function clear() {
+		content.reset();
+		pts = [];
+		prev = [];
+		pindex = 0;
 	}
 	}
+
+	function flush() {
+		if( pts.length > 0 ) {
+			prev.push(pts);
+			pts = [];
+		}
+		if( prev.length == 0 )
+			return;
+		var ctx = new hxd.poly2tri.SweepContext();
+		for( p in prev )
+			ctx.addPolyline(p);
+			
+		var p = new hxd.poly2tri.Sweep(ctx);
+		p.triangulate();
 		
 		
-	public function beginDraw() {
-		return (ctx = new GraphicsContext(this));
+		for( t in ctx.triangles )
+			for( p in t.points )
+				content.addIndex(p.id);
+				
+		prev = [];
+	}
+	
+	public function beginFill( rgba : Int = 0 ) {
+		flush();
+		setColor(rgba);
+	}
+	
+	public function endFill() {
+		flush();
+	}
+	
+	public inline function setColor( rgba : Int ) {
+		curA = (rgba >>> 24) / 255.;
+		curR = ((rgba >> 16) & 0xFF) / 255.;
+		curG = ((rgba >> 8) & 0xFF) / 255.;
+		curB = (rgba & 0xFF) / 255.;
+	}
+	
+	public function drawRect( x : Float, y : Float, w : Float, h : Float ) {
+		addPoint(x, y);
+		addPoint(x + w, y);
+		addPoint(x + w, y + h);
+		addPoint(x, y + h);
 	}
 	}
 	
 	
-	override function onDelete() {
-		if( tile != null ) {
-			tile.dispose();
-			tile = null;
+	public function drawCircle( cx : Float, cy : Float, ray : Float, nsegments = 0 ) {
+		if( nsegments == 0 )
+			nsegments = Math.ceil(ray * 3.14 * 2 / 4);
+		if( nsegments < 3 ) nsegments = 3;
+		var angle = Math.PI * 2 / (nsegments + 1);
+		for( i in 0...nsegments ) {
+			var a = i * angle;
+			addPoint(cx + Math.cos(a) * ray, cy + Math.sin(a) * ray);
 		}
 		}
-		super.onDelete();
-	}
-	
-	public function endDraw( restoreOnContextLost = false, ?allocPos : h3d.impl.AllocPos ) {
-		if( ctx == null ) return;
-		if( tile != null ) tile.dispose();
-		tile = Tile.fromSprites([ctx.mc], allocPos)[0];
-		if( restoreOnContextLost ) {
-			var ctx = ctx;
-			tile.getTexture().onContextLost = function() {
-				this.ctx = ctx;
-				endDraw();
-			};
+	}
+	
+	public function addHole() {
+		if( pts.length > 0 ) {
+			prev.push(pts);
+			pts = [];
 		}
 		}
 	}
 	}
 	
 	
+	public inline function addPoint( x : Float, y : Float ) {
+		addPointFull(x, y, curR, curG, curB, curA);
+	}
+
+	public function addPointFull( x : Float, y : Float, r : Float, g : Float, b : Float, a : Float, u : Float = 0., v : Float = 0. ) {
+		var p = new GraphicsPoint(x, y);
+		p.id = pindex++;
+		pts.push(p);
+		content.add(x, y, u, v, r, g, b, a);
+	}
+	
 	override function draw(ctx:RenderContext) {
 	override function draw(ctx:RenderContext) {
-		if( tile == null ) endDraw();
-		if( tile == null ) return;
-		drawTile(ctx.engine, tile);
+		flush();
+		setupShader(ctx.engine, tile, 0);
+		content.render(ctx.engine);
 	}
 	}
 
 
 }
 }
-
-#end

+ 86 - 0
hxd/poly2tri/AdvancingFront.hx

@@ -0,0 +1,86 @@
+package hxd.poly2tri;
+
+class AdvancingFront
+{
+	public var head:Node;
+	public var tail:Node;
+	public var search_node:Node;
+
+	public function new(head:Node, tail:Node)
+	{
+		this.search_node = this.head = head;
+		this.tail = tail;
+	}
+
+	public function locateNode(x:Float):Node
+	{
+		var node:Node = this.search_node;
+
+		if (x < node.value)
+		{
+			while ((node = node.prev) != null)
+			{
+				if (x >= node.value)
+				{
+					this.search_node = node;
+					return node;
+				}
+			}
+		}
+		else
+		{
+			while ((node = node.next) != null)
+			{
+				if (x < node.value)
+				{
+					this.search_node = node.prev;
+					return node.prev;
+				}
+			}
+		}
+		
+		return null;
+	}
+
+	public function locatePoint(point:Point):Node
+	{
+		var px:Float = point.x;
+		//var node:* = this.FindSearchNode(px);
+		var node:Node = this.search_node;
+		var nx:Float = node.point.x;
+
+		if (px == nx)
+		{
+			if (!point.equals(node.point))
+			{
+				// We might have two nodes with same x value for a short time
+				if (point.equals(node.prev.point))
+				{
+					node = node.prev;
+				}
+				else if (point.equals(node.next.point))
+				{
+					node = node.next;
+				}
+				else
+				{
+					throw 'Invalid AdvancingFront.locatePoint call!';
+				}
+			}
+		}
+		else if (px < nx)
+		{
+			while ((node = node.prev) != null)
+				if (point.equals(node.point)) break;
+		}
+		else
+		{
+			while ((node = node.next) != null)
+				if (point.equals(node.point)) break;
+		}
+
+		if (node != null) this.search_node = node;
+		return node;
+
+	}
+}

+ 26 - 0
hxd/poly2tri/Basin.hx

@@ -0,0 +1,26 @@
+package hxd.poly2tri;
+
+class Basin
+{
+	public var left_node:Node;
+	public var bottom_node:Node;
+	public var right_node:Node;
+	public var width:Float;
+	public var left_highest:Bool;
+	
+	public function new()
+	{
+		width = 0;
+	}
+
+	public function clear()
+	{
+
+		this.left_node    = null ;
+		this.bottom_node  = null ;
+		this.right_node   = null ;
+		this.width        = 0.0  ;
+		this.left_highest = false;
+	}
+
+}

+ 12 - 0
hxd/poly2tri/Constants.hx

@@ -0,0 +1,12 @@
+package hxd.poly2tri;
+
+class Constants {
+	/*
+	 * Inital triangle factor, seed triangle will extend 30% of
+	 * PointSet width to both left and right.
+	 */
+	static public var kAlpha:Float   = 0.3;
+	static public var EPSILON:Float  = 1e-12;
+	static public var PI_2:Float     = Math.PI / 2;
+	static public var PI_3div4:Float = 3 * Math.PI / 4;
+}

+ 47 - 0
hxd/poly2tri/Edge.hx

@@ -0,0 +1,47 @@
+package hxd.poly2tri;
+
+class Edge
+{
+	public var p:Point;
+	public var q:Point;
+
+	public function new(p1:Point, p2:Point)
+	{
+		if (p1==null || p2==null) throw "Edge::new p1 or p2 is null";
+
+		var swap = false;
+
+		if (p1.y > p2.y)
+		{
+			swap = true;
+		}
+		else if (p1.y == p2.y)
+		{
+			if (p1.x == p2.x) throw "Edge::repeat points " + p1;
+
+			swap = (p1.x > p2.x);
+		}
+
+
+		if (swap)
+		{
+			q = p1;
+			p = p2;
+		}
+		else
+		{
+			p = p1;
+			q = p2;
+		}
+
+		q.edge_list.push( this );
+	}
+
+
+
+	public function toString()
+	{
+		return "Edge(" + this.p + ", " + this.q + ")";
+	}
+
+}

+ 12 - 0
hxd/poly2tri/EdgeEvent.hx

@@ -0,0 +1,12 @@
+package hxd.poly2tri;
+
+class EdgeEvent
+{
+	public var constrained_edge:Edge;
+	public var right:Bool;
+
+	public function new()
+	{
+
+	}
+}

+ 57 - 0
hxd/poly2tri/Node.hx

@@ -0,0 +1,57 @@
+package hxd.poly2tri;
+
+class Node
+{
+
+
+	public var point:Point;
+	public var triangle:Triangle;
+	public var prev:Node;
+	public var next:Node;
+	public var value:Float;
+
+	public function new(point:Point = null, triangle:Triangle = null)
+	{
+
+		this.point = point;
+		this.triangle = triangle;
+		this.value = this.point.x;
+	}
+
+	/**
+	 *
+	 * @param node - middle node
+	 * @return the angle between 3 front nodes
+	 */
+	public function getHoleAngle():Float
+	{
+		/* Complex plane
+		 * ab = cosA +i*sinA
+		 * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx)
+		 * atan2(y,x) computes the principal value of the argument function
+		 * applied to the complex number x+iy
+		 * Where x = ax*bx + ay*by
+		 *       y = ax*by - ay*bx
+		 */
+		var ax:Float = this.next.point.x - this.point.x;
+		var ay:Float = this.next.point.y - this.point.y;
+		var bx:Float = this.prev.point.x - this.point.x;
+		var by:Float = this.prev.point.y - this.point.y;
+		return Math.atan2(
+			ax * by - ay * bx,
+			ax * bx + ay * by
+		);
+	}
+	
+	public function getBasinAngle():Float
+	{
+		return Math.atan2(
+			this.point.y - this.next.next.point.y, // ay
+			this.point.x - this.next.next.point.x  // ax
+		);
+	}
+
+
+
+
+}

+ 19 - 0
hxd/poly2tri/Orientation.hx

@@ -0,0 +1,19 @@
+package hxd.poly2tri;
+
+class Orientation
+{
+	inline public static var CW = 1;
+	inline public static var CCW = -1;
+	inline public static var COLLINEAR = 0;
+
+	public static function orient2d(pa:Point, pb:Point, pc:Point):Int
+	{
+		var detleft:Float  = (pa.x - pc.x) * (pb.y - pc.y);
+		var detright:Float = (pa.y - pc.y) * (pb.x - pc.x);
+		var val:Float = detleft - detright;
+
+		if ((val > -Constants.EPSILON) && (val < Constants.EPSILON)) return Orientation.COLLINEAR;
+		if (val > 0) return Orientation.CCW;
+		return Orientation.CW;
+	}
+}

+ 66 - 0
hxd/poly2tri/Point.hx

@@ -0,0 +1,66 @@
+package hxd.poly2tri;
+
+class Point
+{
+	public var id:Int;
+
+	public var x:Float;
+	public var y:Float;
+
+	/// The edges this point constitutes an upper ending point
+	#if haxe3
+	public var edge_list(get, null):Array<Edge>;
+	#else
+	public var edge_list(get_edge_list, null):Array<Edge>;
+	#end
+
+	public function new(x,y)
+	{
+		this.x = x;
+		this.y = y;
+
+		id = C_ID;
+		C_ID++;
+
+	}
+
+
+	function get_edge_list()
+	{
+		if (edge_list==null) edge_list = new Array();
+		return edge_list;
+	}
+
+
+
+	public function equals(that:Point):Bool
+	{
+		return (this.x == that.x) && (this.y == that.y);
+	}
+
+	public static function sortPoints(points:Array<Point>)
+	{
+		points.sort( cmpPoints );
+	}
+
+	public static function cmpPoints(l:Point,r:Point)
+	{
+		var ret:Float = l.y - r.y;
+		if (ret == 0) ret = l.x - r.x;
+		if (ret <  0) return -1;
+		if (ret >  0) return 1;
+		return 0;
+	}
+
+	public function toString()
+	{
+		return "Point(" + x + ", " + y + ")";
+	}
+
+	public static var C_ID = 0;
+
+
+
+
+}
+

+ 647 - 0
hxd/poly2tri/Sweep.hx

@@ -0,0 +1,647 @@
+package hxd.poly2tri;
+
+class Sweep
+{
+	var context:SweepContext;
+
+	public function new(context)
+	{
+		this.context = context;
+	}
+
+	public function triangulate()
+	{
+		context.initTriangulation();
+		context.createAdvancingFront();
+		sweepPoints();// Sweep points; build mesh
+		finalizationPolygon();            // Clean up
+	}
+
+	public function sweepPoints()
+	{
+		for (i in 1...context.points.length)
+		{
+			// trace("i"+i);
+			var point = this.context.points[i];
+			var node  = this.pointEvent(point);
+			for (j in 0...point.edge_list.length)
+			{
+				// trace("j"+j);
+				this.edgeEventByEdge(point.edge_list[j], node);
+			}
+		}
+	}
+
+	public function finalizationPolygon()
+	{
+		// Get an Internal triangle to start with
+		var t:Triangle = this.context.front.head.next.triangle;
+		var p:Point    = this.context.front.head.next.point;
+		while (!t.getConstrainedEdgeCW(p)) t = t.neighborCCW(p);
+		
+		// Collect interior triangles constrained by edges
+		this.context.meshClean(t);
+	}
+
+	/**
+	 * Find closes node to the left of the new point and
+	 * create a new triangle. If needed new holes and basins
+	 * will be filled to.
+	 */
+	public function pointEvent(point:Point) :Node
+	{
+		// trace(point);
+		var node = this.context.locateNode(point);
+		var new_node = newFrontTriangle(point, node);
+
+		// Only need to check +epsilon since point never have smaller
+		// x value than node due to how we fetch nodes from the front
+		if (point.x <= (node.point.x + Constants.EPSILON)) fill(node);
+		
+		fillAdvancingFront(new_node);
+		return new_node;
+	}
+
+
+	public function edgeEventByEdge(edge:Edge, node:Node)
+	{
+		this.context.edge_event.constrained_edge = edge;
+		this.context.edge_event.right = (edge.p.x > edge.q.x);
+
+		if (node.triangle.isEdgeSide(edge.p, edge.q)) return;
+
+		// For now we will do all needed filling
+		// TODO: integrate with flip process might give some better performance
+		//       but for now this avoid the issue with cases that needs both flips and fills
+		this.fillEdgeEvent(edge, node);
+		
+		this.edgeEventByPoints(edge.p, edge.q, node.triangle, edge.q);
+	}
+	
+	public function edgeEventByPoints(ep:Point, eq:Point, triangle:Triangle, point:Point)
+	{
+		if (triangle.isEdgeSide(ep, eq)) return;
+
+		var p1:Point = triangle.pointCCW(point);
+		var o1:Int   = Orientation.orient2d(eq, p1, ep);
+		if (o1 == Orientation.COLLINEAR) throw 'Sweep.edgeEvent: Collinear not supported!';
+
+		var p2:Point = triangle.pointCW(point);
+		var o2:Int   = Orientation.orient2d(eq, p2, ep);
+		if (o2 == Orientation.COLLINEAR) throw'Sweep.edgeEvent: Collinear not supported!';
+
+		if (o1 == o2)
+		{
+			// Need to decide if we are rotating CW or CCW to get to a triangle
+			// that will cross edge
+			triangle = ((o1 == Orientation.CW)
+				? triangle.neighborCCW(point)
+				: triangle.neighborCW(point)
+			);
+			edgeEventByPoints(ep, eq, triangle, point);
+		}
+		else
+		{
+			// This triangle crosses constraint so lets flippin start!
+			flipEdgeEvent(ep, eq, triangle, point);
+		}
+	}
+
+
+
+	public function newFrontTriangle(point:Point, node:Node) :Node
+	{
+		var triangle = new Triangle(point, node.point, node.next.point);
+
+		// trace(node.id);
+		// trace(node.triangle);
+		triangle.markNeighborTriangle(node.triangle);
+		this.context.addToMap(triangle);
+
+		var new_node = new Node(point);
+		new_node.next  = node.next;
+		new_node.prev  = node;
+		node.next.prev = new_node;
+		node.next      = new_node;
+
+		if (!legalize(triangle)) this.context.mapTriangleToNodes(triangle);
+
+		return new_node;
+
+	}
+
+
+	/**
+	 * Adds a triangle to the advancing front to fill a hole.
+	 * @param tcx
+	 * @param node - middle node, that is the bottom of the hole
+	 */
+	public function fill(node:Node)
+	{
+		var triangle = new Triangle(node.prev.point, node.point, node.next.point);
+
+		// TODO: should copy the constrained_edge value from neighbor triangles
+		//       for now constrained_edge values are copied during the legalize
+		triangle.markNeighborTriangle(node.prev.triangle);
+		triangle.markNeighborTriangle(node.triangle);
+			
+		this.context.addToMap(triangle);
+
+		// Update the advancing front
+		node.prev.next = node.next;
+		node.next.prev = node.prev;
+
+		// If it was legalized the triangle has already been mapped
+		if (!legalize(triangle))
+		{
+			this.context.mapTriangleToNodes(triangle);
+		}
+
+		this.context.removeNode(node);
+	}
+
+	/**
+	 * Fills holes in the Advancing Front
+	 */
+	public function fillAdvancingFront(n:Node)
+	{
+		var node:Node;
+		var angle:Float;
+		
+		// Fill right holes
+		node = n.next;
+		while (node.next != null)
+		{
+			angle = node.getHoleAngle();
+			if ((angle > Constants.PI_2) || (angle < -Constants.PI_2)) break;
+			this.fill(node);
+			node = node.next;
+		}
+
+		// Fill left holes
+		node = n.prev;
+		while (node.prev != null)
+		{
+			angle = node.getHoleAngle();
+			if ((angle > Constants.PI_2) || (angle < -Constants.PI_2)) break;
+			this.fill(node);
+			node = node.prev;
+		}
+
+		// Fill right basins
+		if ((n.next != null) && (n.next.next != null))
+		{
+			angle = n.getBasinAngle();
+			if (angle < Constants.PI_3div4) this.fillBasin(n);
+		}
+	}
+
+
+	/**
+	 * Returns true if triangle was legalized
+	 */
+	public function legalize(t:Triangle):Bool
+	{
+		for (i in 0...3)
+		{
+			if (t.delaunay_edge[i]) continue;
+
+			var ot:Triangle = t.neighbors[i];
+			if (ot != null)
+			{
+				var p:Point  = t.points[i];
+				var op:Point = ot.oppositePoint(t, p);
+				var oi:Int = ot.index(op);
+
+				// If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization)
+				// then we should not try to legalize
+				if (ot.constrained_edge[oi] || ot.delaunay_edge[oi])
+				{
+					t.constrained_edge[i] = ot.constrained_edge[oi];
+					continue;
+				}
+
+
+				if (Utils.insideIncircle(p, t.pointCCW(p), t.pointCW(p), op))
+				{
+					// Lets mark this shared edge as Delaunay
+					t.delaunay_edge[i]   = true;
+					ot.delaunay_edge[oi] = true;
+
+					// Lets rotate shared edge one vertex CW to legalize it
+					Triangle.rotateTrianglePair(t, p, ot, op);
+
+					// We now got one valid Delaunay Edge shared by two triangles
+					// This gives us 4 new edges to check for Delaunay
+						
+					var not_legalized:Bool;
+
+					// Make sure that triangle to node mapping is done only one time for a specific triangle
+					not_legalized = !this.legalize(t);
+					if (not_legalized) this.context.mapTriangleToNodes(t);
+
+					not_legalized = !this.legalize(ot);
+					if (not_legalized) this.context.mapTriangleToNodes(ot);
+
+					// Reset the Delaunay edges, since they only are valid Delaunay edges
+					// until we add a new triangle or point.
+					// XXX: need to think about this. Can these edges be tried after we
+					//      return to previous recursive level?
+					t.delaunay_edge[i]   = false;
+					ot.delaunay_edge[oi] = false;
+
+					// If triangle have been legalized no need to check the other edges since
+					// the recursive legalization will handles those so we can end here.
+					return true;
+				}
+
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Fills a basin that has formed on the Advancing Front to the right
+	 * of given node.<br>
+	 * First we decide a left,bottom and right node that forms the
+	 * boundaries of the basin. Then we do a reqursive fill.
+	 *
+	 * @param tcx
+	 * @param node - starting node, this or next node will be left node
+	 */
+	public function fillBasin(node:Node)
+	{
+		this.context.basin.left_node = ((Orientation.orient2d(node.point, node.next.point, node.next.next.point) == Orientation.CCW)
+			? node.next.next
+			: node.next
+		);
+
+		// Find the bottom and right node
+		this.context.basin.bottom_node = this.context.basin.left_node;
+		while ((this.context.basin.bottom_node.next != null) && (this.context.basin.bottom_node.point.y >= this.context.basin.bottom_node.next.point.y))
+		{
+			this.context.basin.bottom_node = this.context.basin.bottom_node.next;
+		}
+
+		// No valid basin
+		if (this.context.basin.bottom_node == this.context.basin.left_node) return;
+
+		this.context.basin.right_node = this.context.basin.bottom_node;
+		while ((this.context.basin.right_node.next != null) && (this.context.basin.right_node.point.y < this.context.basin.right_node.next.point.y))
+		{
+			this.context.basin.right_node = this.context.basin.right_node.next;
+		}
+
+		// No valid basins
+		if (this.context.basin.right_node == this.context.basin.bottom_node) return;
+
+		this.context.basin.width        = (this.context.basin.right_node.point.x - this.context.basin.left_node.point.x);
+		this.context.basin.left_highest = (this.context.basin.left_node.point.y > this.context.basin.right_node.point.y);
+
+		this.fillBasinReq(this.context.basin.bottom_node);
+	}
+
+	/**
+	 * Recursive algorithm to fill a Basin with triangles
+	 *
+	 * @param tcx
+	 * @param node - bottom_node
+	 */
+	public function fillBasinReq(node:Node)
+	{
+		// if shallow stop filling
+		if (this.isShallow(node)) return;
+
+		this.fill(node);
+
+		if (node.prev == this.context.basin.left_node && node.next == this.context.basin.right_node)
+		{
+			return;
+		}
+		else if (node.prev == this.context.basin.left_node)
+		{
+			if (Orientation.orient2d(node.point, node.next.point, node.next.next.point) == Orientation.CW) return;
+			node = node.next;
+		}
+		else if (node.next == this.context.basin.right_node)
+		{
+			if (Orientation.orient2d(node.point, node.prev.point, node.prev.prev.point) == Orientation.CCW) return;
+			node = node.prev;
+		}
+		else
+		{
+			// Continue with the neighbor node with lowest Y value
+			node = ((node.prev.point.y < node.next.point.y)
+				? node.prev
+				: node.next
+			);
+		}
+
+		this.fillBasinReq(node);
+	}
+
+
+	public function isShallow(node:Node):Bool
+	{
+		var height:Float = ( (this.context.basin.left_highest)
+			? this.context.basin.left_node.point.y - node.point.y
+			: this.context.basin.right_node.point.y - node.point.y
+		);
+
+		// if shallow stop filling
+		return (this.context.basin.width > height);
+	}
+
+	public function fillEdgeEvent(edge:Edge, node:Node)
+	{
+		if (this.context.edge_event.right)
+		{
+			this.fillRightAboveEdgeEvent(edge, node);
+		}
+		else
+		{
+			this.fillLeftAboveEdgeEvent(edge, node);
+		}
+	}
+
+	public function fillRightAboveEdgeEvent(edge:Edge, node:Node)
+	{
+		while (node.next.point.x < edge.p.x)
+		{
+			// Check if next node is below the edge
+			if (Orientation.orient2d(edge.q, node.next.point, edge.p) == Orientation.CCW)
+			{
+				this.fillRightBelowEdgeEvent(edge, node);
+			}
+			else
+			{
+				node = node.next;
+			}
+		}
+	}
+
+	public function fillRightBelowEdgeEvent(edge:Edge, node:Node)
+	{
+		if (node.point.x >= edge.p.x) return;
+		if (Orientation.orient2d(node.point, node.next.point, node.next.next.point) == Orientation.CCW)
+		{
+			// Concave
+			this.fillRightConcaveEdgeEvent(edge, node);
+		}
+		else
+		{
+			this.fillRightConvexEdgeEvent(edge, node); // Convex
+			this.fillRightBelowEdgeEvent(edge, node); // Retry this one
+		}
+	}
+	
+	public function fillRightConcaveEdgeEvent(edge:Edge, node:Node)
+	{
+		this.fill(node.next);
+		if (node.next.point != edge.p)
+		{
+			// Next above or below edge?
+			if (Orientation.orient2d(edge.q, node.next.point, edge.p) == Orientation.CCW)
+			{
+				// Below
+				if (Orientation.orient2d(node.point, node.next.point, node.next.next.point) == Orientation.CCW)
+				{
+					// Next is concave
+					this.fillRightConcaveEdgeEvent(edge, node);
+				}
+				else
+				{
+					// Next is convex
+				}
+			}
+		}
+	}
+
+	public function fillRightConvexEdgeEvent(edge:Edge, node:Node)
+	{
+		// Next concave or convex?
+		if (Orientation.orient2d(node.next.point, node.next.next.point, node.next.next.next.point) == Orientation.CCW)
+		{
+			// Concave
+			this.fillRightConcaveEdgeEvent(edge, node.next);
+		}
+		else
+		{
+			// Convex
+			// Next above or below edge?
+			if (Orientation.orient2d(edge.q, node.next.next.point, edge.p) == Orientation.CCW)
+			{
+				// Below
+				this.fillRightConvexEdgeEvent(edge, node.next);
+			}
+			else
+			{
+				// Above
+			}
+		}
+	}
+
+	public function fillLeftAboveEdgeEvent(edge:Edge, node:Node)
+	{
+		while (node.prev.point.x > edge.p.x)
+		{
+			// Check if next node is below the edge
+			if (Orientation.orient2d(edge.q, node.prev.point, edge.p) == Orientation.CW)
+			{
+				this.fillLeftBelowEdgeEvent(edge, node);
+			}
+			else
+			{
+				node = node.prev;
+			}
+		}
+	}
+
+	public function fillLeftBelowEdgeEvent(edge:Edge, node:Node)
+	{
+		if (node.point.x > edge.p.x)
+		{
+			if (Orientation.orient2d(node.point, node.prev.point, node.prev.prev.point) == Orientation.CW)
+			{
+				// Concave
+				this.fillLeftConcaveEdgeEvent(edge, node);
+			}
+			else
+			{
+				// Convex
+				this.fillLeftConvexEdgeEvent(edge, node);
+				// Retry this one
+				this.fillLeftBelowEdgeEvent(edge, node);
+			}
+		}
+	}
+
+	public function fillLeftConvexEdgeEvent(edge:Edge, node:Node)
+	{
+		// Next concave or convex?
+		if (Orientation.orient2d(node.prev.point, node.prev.prev.point, node.prev.prev.prev.point) == Orientation.CW)
+		{
+			// Concave
+			this.fillLeftConcaveEdgeEvent(edge, node.prev);
+		}
+		else
+		{
+			// Convex
+			// Next above or below edge?
+			if (Orientation.orient2d(edge.q, node.prev.prev.point, edge.p) == Orientation.CW)
+			{
+				// Below
+				this.fillLeftConvexEdgeEvent(edge, node.prev);
+			}
+			else
+			{
+				// Above
+			}
+		}
+	}
+
+	public function fillLeftConcaveEdgeEvent(edge:Edge, node:Node)
+	{
+		this.fill(node.prev);
+		if (node.prev.point != edge.p)
+		{
+			// Next above or below edge?
+			if (Orientation.orient2d(edge.q, node.prev.point, edge.p) == Orientation.CW)
+			{
+				// Below
+				if (Orientation.orient2d(node.point, node.prev.point, node.prev.prev.point) == Orientation.CW)
+				{
+					// Next is concave
+					this.fillLeftConcaveEdgeEvent(edge, node);
+				}
+				else
+				{
+					// Next is convex
+				}
+			}
+		}
+	}
+
+
+
+	public function flipEdgeEvent(ep:Point, eq:Point, t:Triangle, p:Point)
+	{
+		var ot:Triangle = t.neighborAcross(p);
+		if (ot == null)
+		{
+			// If we want to integrate the fillEdgeEvent do it here
+			// With current implementation we should never get here
+			throw 'Sweep::[BUG:FIXME] FLIP failed due to missing triangle!';
+		}
+		var op:Point = ot.oppositePoint(t, p);
+
+		if (Utils.inScanArea(p, t.pointCCW(p), t.pointCW(p), op))
+		{
+			// Lets rotate shared edge one vertex CW
+			Triangle.rotateTrianglePair(t, p, ot, op);
+			this.context.mapTriangleToNodes(t);
+			this.context.mapTriangleToNodes(ot);
+
+			// @TODO: equals?
+			if ((p == eq) && (op == ep))
+			{
+				if ((eq == this.context.edge_event.constrained_edge.q) && (ep == this.context.edge_event.constrained_edge.p)) {
+					 t.markConstrainedEdgeByPoints(ep, eq);
+					ot.markConstrainedEdgeByPoints(ep, eq);
+					this.legalize( t);
+					this.legalize(ot);
+				}
+				else
+				{
+					// XXX: I think one of the triangles should be legalized here?
+				}
+			}
+			else
+			{
+				var o:Int = Orientation.orient2d(eq, op, ep);
+				t = this.nextFlipTriangle(o, t, ot, p, op);
+				this.flipEdgeEvent(ep, eq, t, p);
+			}
+		}
+		else
+		{
+			var newP:Point = Sweep.nextFlipPoint(ep, eq, ot, op);
+			this.flipScanEdgeEvent(ep, eq, t, ot, newP);
+			this.edgeEventByPoints(ep, eq, t, p);
+		}
+	}
+
+
+
+	public function nextFlipTriangle(o:Int, t:Triangle, ot:Triangle, p:Point, op:Point):Triangle {
+		var edge_index:Int;
+		if (o == Orientation.CCW)
+		{
+			// ot is not crossing edge after flip
+			edge_index = ot.edgeIndex(p, op);
+			ot.delaunay_edge[edge_index] = true;
+			this.legalize(ot);
+			ot.clearDelunayEdges();
+			return t;
+		}
+
+		// t is not crossing edge after flip
+		edge_index = t.edgeIndex(p, op);
+
+		t.delaunay_edge[edge_index] = true;
+		this.legalize(t);
+		t.clearDelunayEdges();
+		return ot;
+	}
+
+	static public function nextFlipPoint(ep:Point, eq:Point, ot:Triangle, op:Point):Point
+	{
+		var o2d:Int = Orientation.orient2d(eq, op, ep);
+		if (o2d == Orientation.CW)
+		{
+			// Right
+			return ot.pointCCW(op);
+		}
+		else if (o2d == Orientation.CCW)
+		{
+			// Left
+			return ot.pointCW(op);
+		}
+		else
+		{
+			throw "Sweep:: [Unsupported] Sweep.NextFlipPoint: opposing point on constrained edge!";
+		}
+	}
+
+	public function flipScanEdgeEvent(ep:Point, eq:Point, flip_triangle:Triangle, t:Triangle, p:Point)
+	{
+		var ot:Triangle = t.neighborAcross(p);
+
+		// If we want to integrate the fillEdgeEvent do it here
+		// With current implementation we should never get here
+		if (ot == null) throw 'Sweep::[BUG:FIXME] FLIP failed due to missing triangle';
+		var op:Point = ot.oppositePoint(t, p);
+
+		if (Utils.inScanArea(eq, flip_triangle.pointCCW(eq), flip_triangle.pointCW(eq), op))
+		{
+			// flip with new edge op.eq
+			this.flipEdgeEvent(eq, op, ot, op);
+			// TODO: Actually I just figured out that it should be possible to
+			//       improve this by getting the next ot and op before the the above
+			//       flip and continue the flipScanEdgeEvent here
+			// set new ot and op here and loop back to inScanArea test
+			// also need to set a new flip_triangle first
+			// Turns out at first glance that this is somewhat complicated
+			// so it will have to wait.
+		}
+		else
+		{
+			var newP:Point = nextFlipPoint(ep, eq, ot, op);
+			this.flipScanEdgeEvent(ep, eq, flip_triangle, ot, newP);
+		}
+	}
+	
+
+
+}

+ 163 - 0
hxd/poly2tri/SweepContext.hx

@@ -0,0 +1,163 @@
+package hxd.poly2tri;
+
+class SweepContext
+{
+	public var triangles:Array<Triangle>;
+	public var points:Array<Point>;
+	public var edge_list:Array<Edge>;
+
+	#if haxe3
+	public var map:Map<String,Triangle>;
+	#else
+	public var map:Hash<Triangle>;
+	#end
+
+	public var front:AdvancingFront;
+	public var head:Point;
+	public var tail:Point;
+
+	public var basin:Basin;
+	public var edge_event:EdgeEvent;
+
+
+
+	public function new()
+	{
+		triangles = new Array();
+		points = new Array();
+		edge_list = new Array();
+		#if haxe3
+		map = new Map();
+		#else
+		map = new Hash();
+		#end
+
+
+		basin = new Basin();
+		edge_event = new EdgeEvent();
+
+	}
+
+
+	function addPoints(points:Array<Point>)
+	{
+		for (point in points)
+		{
+			//OPT use concat instead
+			this.points.push( point );
+		}
+	}
+
+
+	public function addPolyline(polyline:Array<Point>)
+	{
+		initEdges(polyline);
+		addPoints(polyline);
+	}
+
+
+	function initEdges(polyline:Array<Point>)
+	{
+
+		for (n in 0...polyline.length)
+		{
+
+			var nx = polyline[(n + 1) % polyline.length];
+			edge_list.push ( new Edge(polyline[n], nx) );
+									
+		}
+	}
+
+
+	public function addToMap(triangle:Triangle)
+	{
+		map.set( triangle.toString(), triangle );
+	}
+
+
+	public function initTriangulation()
+	{
+		//OPT
+		var xmin = points[0].x;
+		var xmax = points[0].x;
+		var ymin = points[0].y;
+		var ymax = points[0].y;
+
+		// Calculate bounds
+		for (p in points)
+		{
+			if (p.x > xmax) xmax = p.x;
+			if (p.x < xmin) xmin = p.x;
+			if (p.y > ymax) ymax = p.y;
+			if (p.y < ymin) ymin = p.y;
+		}
+
+		var dx = Constants.kAlpha * (xmax - xmin);
+		var dy = Constants.kAlpha * (ymax - ymin);
+
+		this.head = new Point(xmax + dx, ymin - dy);
+		this.tail = new Point(xmin - dy, ymin - dy);
+
+
+		// Sort points along y-axis
+		Point.sortPoints(points);
+
+	}
+
+	public function locateNode(point:Point):Node
+	{
+		return this.front.locateNode(point.x);
+	}
+
+	public function createAdvancingFront()
+	{
+		// Initial triangle
+		var triangle = new Triangle(points[0], this.tail, this.head);
+
+		addToMap(triangle);
+
+		var head = new Node( triangle.points[1], triangle );
+		var middle = new Node( triangle.points[0], triangle );
+		var tail = new Node( triangle.points[2] );
+
+		this.front = new AdvancingFront(head, tail);
+
+		head.next   = middle;
+		middle.next = tail;
+		middle.prev = head;
+		tail.prev   = middle;
+
+	}
+
+	public function removeNode(node:Node)
+	{
+		// do nothing
+	}
+
+	public function mapTriangleToNodes(triangle:Triangle)
+	{
+		for (n in 0...3)
+		{
+			if (triangle.neighbors[n] == null)
+			{
+				var neighbor = this.front.locatePoint(triangle.pointCW(triangle.points[n]));
+				if (neighbor != null) neighbor.triangle = triangle;
+			}
+		}
+	}
+
+	public function meshClean(t:Triangle)
+	{
+		var tmp = [t];
+		while( true ) {
+			var t = tmp.pop();
+			if( t == null ) break;
+			if( t.id >= 0 ) continue;
+			t.id = this.triangles.length;
+			this.triangles.push(t);
+			for (n in 0...3)
+				if (!t.constrained_edge[n])
+					tmp.push(t.neighbors[n]);
+		}
+	}
+}

+ 396 - 0
hxd/poly2tri/Triangle.hx

@@ -0,0 +1,396 @@
+package hxd.poly2tri;
+
+class Triangle
+{
+	public var points:Array<Point>;
+
+	// Neighbor list
+	public var neighbors:Array<Triangle>;
+
+	// Has this triangle been marked as an interior triangle?
+	public var id:Int = -1;
+
+	// Flags to determine if an edge is a Constrained edge
+	public var constrained_edge:Array<Bool>;
+
+	// Flags to determine if an edge is a Delauney edge
+	public var delaunay_edge:Array<Bool>;
+
+
+
+	public function new(p1:Point, p2:Point, p3:Point, fixOrientation = false, checkOrientation = true)
+	{
+		if (fixOrientation)
+		{
+			if (Orientation.orient2d(p1,p2,p3) == Orientation.CW)
+			{
+				var pt = p3;
+				p3 = p2;
+				p2 = pt;
+			}
+		}
+
+		if (checkOrientation && Orientation.orient2d(p3,p2,p1) != Orientation.CW)
+			throw "Triangle::Triangle must defined with Orientation.CW";
+
+		points = [p1,p2,p3];
+
+
+
+		neighbors = [null, null, null];
+		constrained_edge = [false, false, false];
+		delaunay_edge = [false, false, false];
+		
+	}
+
+	/**
+	 * Test if this Triangle contains the Point object given as parameter as its vertices.
+	 *
+	 * @return <code>True</code> if the Point objects are of the Triangle's vertices,
+	 *         <code>false</code> otherwise.
+	 */
+	public function containsPoint(point:Point):Bool
+	{
+		return point.equals(points[0]) || point.equals(points[1]) || point.equals(points[2]);
+	}
+
+	public function containsEdgePoints(p1:Point, p2:Point):Bool
+	{
+		// In a triangle to check if contains and edge is enough to check if it contains the two vertices.
+		return containsPoint(p1) && containsPoint(p2);
+	}
+
+
+	/**
+	 * Update neighbor pointers.<br>
+	 * This method takes either 3 parameters (<code>p1</code>, <code>p2</code> and
+	 * <code>t</code>) or 1 parameter (<code>t</code>).
+	 * @param   t   Triangle object.
+	 * @param   p1  Point object.
+	 * @param   p2  Point object.
+	 */
+	public function markNeighbor(t:Triangle, p1:Point, p2:Point)
+	{
+		if ((p1.equals(this.points[2]) && p2.equals(this.points[1])) || (p1.equals(this.points[1]) && p2.equals(this.points[2])))
+		{
+			this.neighbors[0] = t; return;
+		}
+		if ((p1.equals(this.points[0]) && p2.equals(this.points[2])) || (p1.equals(this.points[2]) && p2.equals(this.points[0])))
+		{
+			this.neighbors[1] = t; return;
+		}
+		if ((p1.equals(this.points[0]) && p2.equals(this.points[1])) || (p1.equals(this.points[1]) && p2.equals(this.points[0])))
+		{
+			this.neighbors[2] = t; return;
+		}
+		throw 'Invalid markNeighbor call (1)!';
+	}
+
+
+	public function markNeighborTriangle(that:Triangle)
+	{
+		// exhaustive search to update neighbor pointers
+		if (that.containsEdgePoints( this.points[1], this.points[2]))
+		{
+			this.neighbors[0] = that;
+			that.markNeighbor(this, this.points[1], this.points[2]);
+			return;
+		}
+		
+		if (that.containsEdgePoints(this.points[0], this.points[2])) {
+			this.neighbors[1] = that;
+			that.markNeighbor(this, this.points[0], this.points[2]);
+			return;
+		}
+		
+		if (that.containsEdgePoints(this.points[0], this.points[1])) {
+			this.neighbors[2] = that;
+			that.markNeighbor(this, this.points[0], this.points[1]);
+			return;
+		}
+	}
+
+
+
+	// Optimized?
+	public function getPointIndexOffset(p:Point, offset:Int = 0):Int
+	{
+		var no:Int = offset;
+		for (n in 0...3)
+		{
+			while (no < 0) no += 3;
+			while (no > 2) no -= 3;
+			if (p.equals(this.points[n])) return no;
+			no++;
+		}
+
+		throw "Triangle::Point not in triangle";
+		// for (var n:uint = 0; n < 3; n++, no++) {
+		// 	while (no < 0) no += 3;
+		// 	while (no > 2) no -= 3;
+		// 	if (p.equals(this.points[n])) return no;
+		// }
+		// throw(new Error("Point not in triangle"));
+	}
+
+
+
+
+	/**
+	 * Return the point clockwise to the given point.
+	 * Return the point counter-clockwise to the given point.
+	 *
+	 * Return the neighbor clockwise to given point.
+	 * Return the neighbor counter-clockwise to given point.
+	 */
+	
+	inline static private var CW_OFFSET = 1;
+	inline static private var CCW_OFFSET = -1;
+
+	public function pointCW (p:Point):Point
+	{
+		return this.points[getPointIndexOffset(p, CCW_OFFSET)];
+	}
+
+	public function pointCCW(p:Point):Point
+	{
+		return this.points[getPointIndexOffset(p, CW_OFFSET)];
+	}
+
+	public function neighborCW(p:Point):Triangle
+	{
+	 	return this.neighbors[getPointIndexOffset(p, CW_OFFSET)];
+	}
+	
+	public function neighborCCW(p:Point):Triangle
+	{
+		return this.neighbors[getPointIndexOffset(p, CCW_OFFSET)];
+	}
+
+	public function getConstrainedEdgeCW(p:Point):Bool              { return this.constrained_edge[getPointIndexOffset(p, CW_OFFSET)]; }
+	public function setConstrainedEdgeCW(p:Point, ce:Bool):Bool  { return this.constrained_edge[getPointIndexOffset(p, CW_OFFSET)] = ce; }
+
+	public function getConstrainedEdgeCCW(p:Point):Bool             { return this.constrained_edge[getPointIndexOffset(p, CCW_OFFSET)]; }
+	public function setConstrainedEdgeCCW(p:Point, ce:Bool):Bool { return this.constrained_edge[getPointIndexOffset(p, CCW_OFFSET)] = ce; }
+
+	public function getDelaunayEdgeCW(p:Point):Bool                 { return this.delaunay_edge[getPointIndexOffset(p, CW_OFFSET)]; }
+	public function setDelaunayEdgeCW(p:Point, e:Bool):Bool      { return this.delaunay_edge[getPointIndexOffset(p, CW_OFFSET)] = e; }
+	
+	public function getDelaunayEdgeCCW(p:Point):Bool                { return this.delaunay_edge[getPointIndexOffset(p, CCW_OFFSET)]; }
+	public function setDelaunayEdgeCCW(p:Point, e:Bool):Bool     { return this.delaunay_edge[getPointIndexOffset(p, CCW_OFFSET)] = e; }
+
+
+	/**
+	 * The neighbor across to given point.
+	 */
+	public function neighborAcross(p:Point):Triangle { return this.neighbors[getPointIndexOffset(p, 0)]; }
+
+	public function oppositePoint(t:Triangle, p:Point):Point
+	{
+		return this.pointCW(t.pointCW(p));
+	}
+
+	/**
+	 * Legalize triangle by rotating clockwise.<br>
+	 * This method takes either 1 parameter (then the triangle is rotated around
+	 * points(0)) or 2 parameters (then the triangle is rotated around the first
+	 * parameter).
+	 */
+	public function legalize(opoint:Point, npoint:Point = null)
+	{
+		if (npoint == null)
+		{
+			this.legalize(this.points[0], opoint);
+			return;
+		}
+
+		if (opoint.equals(this.points[0])) {
+			this.points[1] = this.points[0];
+			this.points[0] = this.points[2];
+			this.points[2] = npoint;
+		} else if (opoint.equals(this.points[1])) {
+			this.points[2] = this.points[1];
+			this.points[1] = this.points[0];
+			this.points[0] = npoint;
+		} else if (opoint.equals(this.points[2])) {
+			this.points[0] = this.points[2];
+			this.points[2] = this.points[1];
+			this.points[1] = npoint;
+		} else {
+			throw 'Invalid js.poly2tri.Triangle.Legalize call!';
+		}
+	}
+	
+
+	/**
+	 * Alias for getPointIndexOffset
+	 *
+	 * @param	p
+	 */
+	public function index(p:Point):Int
+	{
+		try {
+			return this.getPointIndexOffset(p, 0);
+		} catch (msg:String) {
+			trace(msg);
+		}
+		return -1;
+	}
+
+
+	public function edgeIndex(p1:Point, p2:Point):Int
+	{
+		if (p1.equals(this.points[0]))
+		{
+			if (p2.equals(this.points[1])) return 2;
+			if (p2.equals(this.points[2])) return 1;
+		}
+		else if (p1.equals(this.points[1]))
+		{
+			if (p2.equals(this.points[2])) return 0;
+			if (p2.equals(this.points[0])) return 2;
+		}
+		else if (p1.equals(this.points[2]))
+		{
+			if (p2.equals(this.points[0])) return 1;
+			if (p2.equals(this.points[1])) return 0;
+		}
+		return -1;
+	}
+
+
+	/**
+	 * Mark an edge of this triangle as constrained.<br>
+	 * This method takes either 1 parameter (an edge index or an Edge instance) or
+	 * 2 parameters (two Point instances defining the edge of the triangle).
+	 */
+	public function markConstrainedEdgeByIndex(index:Int)
+	{
+		this.constrained_edge[index] = true;
+	}
+
+	public function markConstrainedEdgeByEdge(edge:Edge)
+	{
+		this.markConstrainedEdgeByPoints(edge.p, edge.q);
+	}
+
+	public function markConstrainedEdgeByPoints(p:Point, q:Point)
+	{
+		if ((q.equals(this.points[0]) && p.equals(this.points[1])) || (q.equals(this.points[1]) && p.equals(this.points[0]))) {
+			this.constrained_edge[2] = true;
+			return;
+		}
+
+		if ((q.equals(this.points[0]) && p.equals(this.points[2])) || (q.equals(this.points[2]) && p.equals(this.points[0]))) {
+			this.constrained_edge[1] = true;
+			return;
+		}
+		
+		if ((q.equals(this.points[1]) && p.equals(this.points[2])) || (q.equals(this.points[2]) && p.equals(this.points[1]))) {
+			this.constrained_edge[0] = true;
+			return;
+		}
+	}
+	
+
+
+	/**
+	 * Checks if a side from this triangle is an edge side.
+	 * If sides are not marked they will be marked.
+	 *
+	 * @param	ep
+	 * @param	eq
+	 * @return
+	 */
+	public function isEdgeSide(ep:Point, eq:Point):Bool
+	{
+		var index:Int = this.edgeIndex(ep, eq);
+		if (index == -1) return false;
+		
+		this.markConstrainedEdgeByIndex(index);
+		var that:Triangle = this.neighbors[index];
+		if (that != null) that.markConstrainedEdgeByPoints(ep, eq);
+		return true;
+	}
+
+
+	/**
+	 * Rotates a triangle pair one vertex CW
+	 *<pre>
+	 *       n2                    n2
+	 *  P +-----+             P +-----+
+	 *    | t  /|               |\  t |
+	 *    |   / |               | \   |
+	 *  n1|  /  |n3           n1|  \  |n3
+	 *    | /   |    after CW   |   \ |
+	 *    |/ oT |               | oT \|
+	 *    +-----+ oP            +-----+
+	 *       n4                    n4
+	 * </pre>
+	 */
+	static public function rotateTrianglePair(t:Triangle, p:Point, ot:Triangle, op:Point)
+	{
+		var n1:Triangle =  t.neighborCCW( p);
+		var n2:Triangle =  t.neighborCW ( p);
+		var n3:Triangle = ot.neighborCCW(op);
+		var n4:Triangle = ot.neighborCW (op);
+
+		var ce1:Bool =  t.getConstrainedEdgeCCW( p);
+		var ce2:Bool =  t.getConstrainedEdgeCW ( p);
+		var ce3:Bool = ot.getConstrainedEdgeCCW(op);
+		var ce4:Bool = ot.getConstrainedEdgeCW (op);
+
+		var de1:Bool =  t.getDelaunayEdgeCCW( p);
+		var de2:Bool =  t.getDelaunayEdgeCW ( p);
+		var de3:Bool = ot.getDelaunayEdgeCCW(op);
+		var de4:Bool = ot.getDelaunayEdgeCW (op);
+
+		t.legalize( p, op);
+		ot.legalize(op,  p);
+
+		// Remap delaunay_edge
+		ot.setDelaunayEdgeCCW( p, de1);
+		 t.setDelaunayEdgeCW ( p, de2);
+		 t.setDelaunayEdgeCCW(op, de3);
+		ot.setDelaunayEdgeCW (op, de4);
+
+		// Remap constrained_edge
+		ot.setConstrainedEdgeCCW( p, ce1);
+		 t.setConstrainedEdgeCW ( p, ce2);
+		 t.setConstrainedEdgeCCW(op, ce3);
+		ot.setConstrainedEdgeCW (op, ce4);
+
+		// Remap neighbors
+		// XXX: might optimize the markNeighbor by keeping track of
+		//      what side should be assigned to what neighbor after the
+		//      rotation. Now mark neighbor does lots of testing to find
+		//      the right side.
+		 t.clearNeigbors();
+		ot.clearNeigbors();
+		if (n1 != null) ot.markNeighborTriangle(n1);
+		if (n2 != null)  t.markNeighborTriangle(n2);
+		if (n3 != null)  t.markNeighborTriangle(n3);
+		if (n4 != null) ot.markNeighborTriangle(n4);
+		t.markNeighborTriangle(ot);
+	}
+
+	public function clearNeigbors()
+	{
+		this.neighbors[0] = null;
+		this.neighbors[1] = null;
+		this.neighbors[2] = null;
+	}
+
+	public function clearDelunayEdges()
+	{
+		this.delaunay_edge[0] = false;
+		this.delaunay_edge[1] = false;
+		this.delaunay_edge[2] = false;
+	}
+
+
+	public function toString():String
+	{
+		return "Triangle(" + this.points[0] + ", " + this.points[1] + ", " + this.points[2] + ")";
+	}
+}

+ 90 - 0
hxd/poly2tri/Utils.hx

@@ -0,0 +1,90 @@
+package hxd.poly2tri;
+
+class Utils
+{
+	/**
+	 * <b>Requirement</b>:<br>
+	 * 1. a, b and c form a triangle.<br>
+	 * 2. a and d is know to be on opposite side of bc<br>
+	 * <pre>
+	 *                a
+	 *                +
+	 *               / \
+	 *              /   \
+	 *            b/     \c
+	 *            +-------+
+	 *           /    d    \
+	 *          /           \
+	 * </pre>
+	 * <b>Fact</b>: d has to be in area B to have a chance to be inside the circle formed by
+	 *  a,b and c<br>
+	 *  d is outside B if orient2d(a,b,d) or orient2d(c,a,d) is CW<br>
+	 *  This preknowledge gives us a way to optimize the incircle test
+	 * @param pa - triangle point, opposite d
+	 * @param pb - triangle point
+	 * @param pc - triangle point
+	 * @param pd - point opposite a
+	 * @return true if d is inside circle, false if on circle edge
+	 */
+	static public function insideIncircle(pa:Point, pb:Point, pc:Point, pd:Point):Bool
+	{
+		var adx:Float    = pa.x - pd.x;
+		var ady:Float    = pa.y - pd.y;
+		var bdx:Float    = pb.x - pd.x;
+		var bdy:Float    = pb.y - pd.y;
+
+		var adxbdy:Float = adx * bdy;
+		var bdxady:Float = bdx * ady;
+		var oabd:Float   = adxbdy - bdxady;
+
+		if (oabd <= 0) return false;
+
+		var cdx:Float    = pc.x - pd.x;
+		var cdy:Float    = pc.y - pd.y;
+
+		var cdxady:Float = cdx * ady;
+		var adxcdy:Float = adx * cdy;
+		var ocad:Float   = cdxady - adxcdy;
+
+		if (ocad <= 0) return false;
+
+		var bdxcdy:Float = bdx * cdy;
+		var cdxbdy:Float = cdx * bdy;
+
+		var alift:Float  = adx * adx + ady * ady;
+		var blift:Float  = bdx * bdx + bdy * bdy;
+		var clift:Float  = cdx * cdx + cdy * cdy;
+
+		var det:Float = alift * (bdxcdy - cdxbdy) + blift * ocad + clift * oabd;
+		return det > 0;
+	}
+
+	static public function inScanArea(pa:Point, pb:Point, pc:Point, pd:Point):Bool
+	{
+		var pdx:Float = pd.x;
+		var pdy:Float = pd.y;
+		var adx:Float = pa.x - pdx;
+		var ady:Float = pa.y - pdy;
+		var bdx:Float = pb.x - pdx;
+		var bdy:Float = pb.y - pdy;
+
+		var adxbdy:Float = adx * bdy;
+		var bdxady:Float = bdx * ady;
+		var oabd:Float = adxbdy - bdxady;
+
+		if (oabd <= Constants.EPSILON) return false;
+
+		var cdx:Float = pc.x - pdx;
+		var cdy:Float = pc.y - pdy;
+
+		var cdxady:Float = cdx * ady;
+		var adxcdy:Float = adx * cdy;
+		var ocad:Float = cdxady - adxcdy;
+
+		if (ocad <= Constants.EPSILON) return false;
+
+		return true;
+	}
+
+
+}

+ 111 - 0
hxd/poly2tri/VisiblePolygon.hx

@@ -0,0 +1,111 @@
+package hxd.poly2tri;
+
+class VisiblePolygon
+{
+
+	var sweepContext:SweepContext;
+	var sweep:Sweep;
+	var triangulated:Bool;
+
+	public function new()
+	{
+		reset();
+	}
+
+
+	public function addPolyline(polyline:Array<Point>)
+	{
+		sweepContext.addPolyline(polyline);
+	}
+
+	public function reset()
+	{
+		sweepContext = new SweepContext();
+		sweep = new Sweep(sweepContext);
+		triangulated = false;
+	}
+
+	public function performTriangulationOnce()
+	{
+		if (this.triangulated) return;
+		triangulated = true;
+		sweep.triangulate();
+	}
+
+	//returns vertices in a 3D engine-friendly, XYZ format
+	public function getVerticesAndTriangles()
+	{
+		if (!this.triangulated) return null;
+
+		var vertices = new Array();
+		var ids = new Array();
+
+		for (i in 0...sweepContext.points.length)
+		{
+			var p = sweepContext.points[i];
+			vertices.push(p.x);
+			vertices.push(p.y);
+			vertices.push(0);
+			ids[p.id] = i;
+		}
+
+		var tris = new Array();
+		for (t in sweepContext.triangles)
+		{
+			for (i in 0...3)
+			{
+				tris.push( ids[ t.points[i].id ] );
+			}
+		}
+	
+		return { vertices: vertices, triangles:tris };
+	}
+
+	public function getNumTriangles()
+	{
+		return sweepContext.triangles.length;
+	}
+
+
+	#if (nme || flash)
+	public function drawShape(g:flash.display.Graphics)
+	{
+		var t:Triangle;
+		var pl:Array<Point>;
+		
+		performTriangulationOnce();
+
+		for (t in sweepContext.triangles)
+		{
+			pl = t.points;
+
+			g.beginFill( 0xefb83d, .9 );
+			g.moveTo(pl[0].x, pl[0].y);
+			g.lineTo(pl[1].x, pl[1].y);
+			g.lineTo(pl[2].x, pl[2].y);
+			g.lineTo(pl[0].x, pl[0].y);
+			g.endFill();
+		}
+
+		g.lineStyle(1, 0xd31205, 1);
+
+		for (t in sweepContext.triangles)
+		{
+			pl = t.points;
+
+			g.moveTo(pl[0].x, pl[0].y);
+			g.lineTo(pl[1].x, pl[1].y);
+			g.lineTo(pl[2].x, pl[2].y);
+			g.lineTo(pl[0].x, pl[0].y);
+		}
+
+		g.lineStyle(2, 0x945922, 2);
+
+		for (e in sweepContext.edge_list)
+		{
+			g.moveTo(e.p.x, e.p.y);
+			g.lineTo(e.q.x, e.q.y);
+		}
+	}
+	#end
+}

+ 36 - 0
samples/draw/Draw.hx

@@ -0,0 +1,36 @@
+class Draw {
+	
+	var engine : h3d.Engine;
+	var scene : h2d.Scene;
+	
+	function new() {
+		engine = new h3d.Engine();
+		engine.onReady = init;
+		engine.backgroundColor = 0xFF000000;
+		engine.init();
+	}
+	
+	function init() {
+		scene = new h2d.Scene();
+		
+		var g = new h2d.Graphics(scene);
+		g.beginFill(0xFFFF0000);
+		g.drawRect(10, 10, 100, 100);
+		g.addHole();
+		g.drawRect(20, 20, 80, 80);
+		g.beginFill(0x8000FF00);
+		g.drawCircle(100, 100, 30);
+		g.endFill();
+		
+		hxd.System.setLoop(update);
+	}
+	
+	function update() {
+		engine.render(scene);
+	}
+	
+	static function main() {
+		new Draw();
+	}
+	
+}

+ 6 - 0
samples/draw/draw.hxml

@@ -0,0 +1,6 @@
+-swf draw.swf
+-swf-header 800:600:60:FFFFFF
+--flash-strict
+-swf-version 11
+-main Draw
+-lib h3d

+ 56 - 0
samples/draw/draw.hxproj

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project version="2">
+  <!-- Output SWF options -->
+  <output>
+    <movie outputType="Application" />
+    <movie input="" />
+    <movie path="draw.swf" />
+    <movie fps="60" />
+    <movie width="800" />
+    <movie height="600" />
+    <movie version="11" />
+    <movie minorVersion="0" />
+    <movie platform="Flash Player" />
+    <movie background="#FFFFFF" />
+  </output>
+  <!-- Other classes to be compiled into your SWF -->
+  <classpaths>
+    <!-- example: <class path="..." /> -->
+  </classpaths>
+  <!-- Build options -->
+  <build>
+    <option directives="" />
+    <option flashStrict="True" />
+    <option mainClass="Draw" />
+    <option enabledebug="False" />
+    <option additional="-lib h3d" />
+  </build>
+  <!-- haxelib libraries -->
+  <haxelib>
+    <!-- example: <library name="..." /> -->
+  </haxelib>
+  <!-- Class files to compile (other referenced classes will automatically be included) -->
+  <compileTargets>
+    <!-- example: <compile path="..." /> -->
+  </compileTargets>
+  <!-- Assets to embed into the output SWF -->
+  <library>
+    <!-- example: <asset path="..." id="..." update="..." glyphs="..." mode="..." place="..." sharepoint="..." /> -->
+  </library>
+  <!-- Paths to exclude from the Project Explorer tree -->
+  <hiddenPaths>
+    <hidden path="draw.swf" />
+    <hidden path="draw.hxml" />
+  </hiddenPaths>
+  <!-- Executed before build -->
+  <preBuildCommand />
+  <!-- Executed after build -->
+  <postBuildCommand alwaysRun="False" />
+  <!-- Other project options -->
+  <options>
+    <option showHiddenPaths="False" />
+    <option testMovie="Default" />
+  </options>
+  <!-- Plugin storage -->
+  <storage />
+</project>