2
0
Эх сурвалжийг харах

add clipperOffset class
add Minkowski sum/diff algorithms

bstouls 9 жил өмнө
parent
commit
0bfd5b5969
1 өөрчлөгдсөн 466 нэмэгдсэн , 100 устгасан
  1. 466 100
      hxd/clipper/Clipper.hx

+ 466 - 100
hxd/clipper/Clipper.hx

@@ -272,6 +272,10 @@ private class ClipperBase
 		return i < 0 ? -i : i;
 	}
 
+    static function near_zero(val : Float) {
+		return (val > -TOLERANCE) && (val < TOLERANCE);
+	}
+
 	//------------------------------------------------------------------------------
 
 	public function nearZero (v : Float) : Bool {
@@ -283,7 +287,7 @@ private class ClipperBase
 	function PointIsVertex(pt:Point, pp:OutPt) {
 		var pp2 = pp;
 		do {
-			if( pp2.pt == pt ) return true;
+			if( equals(pp2.pt, pt) ) return true;
 			pp2 = pp2.next;
 		} while (pp2 != pp);
 		return false;
@@ -295,8 +299,7 @@ private class ClipperBase
 			((pt.x == linePt2.x) && (pt.y == linePt2.y)) ||
 			(((pt.x > linePt1.x) == (pt.x < linePt2.x)) &&
 			((pt.y > linePt1.y) == (pt.y < linePt2.y)) &&
-			((pt.x - linePt1.x) * (linePt2.y - linePt1.y) ==
-			(linePt2.x - linePt1.x) * (pt.y - linePt1.y)));
+			((pt.x - linePt1.x) * (linePt2.y - linePt1.y) == (linePt2.x - linePt1.x) * (pt.y - linePt1.y)));
 	}
 	//------------------------------------------------------------------------------
 
@@ -672,11 +675,10 @@ private class ClipperBase
 		e.bot.x = tmp;
 	}
 
-
     //------------------------------------------------------------------------------
 
     function Pt2IsBetweenPt1AndPt3(pt1 : Point, pt2 : Point, pt3 : Point) {
-      if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) return false;
+      if (equals(pt1, pt3) || equals(pt1, pt2) || equals(pt3,  pt2)) return false;
       else if (pt1.x != pt3.x) return (pt2.x > pt1.x) == (pt2.x < pt3.x);
       else return (pt2.y > pt1.y) == (pt2.y < pt3.y);
     }
@@ -731,6 +733,11 @@ private class ClipperBase
 	}
 	//------------------------------------------------------------------------------
 
+
+	inline function equals(pt1 : Point, pt2 : Point) {
+		return pt1.x == pt2.x && pt1.y == pt2.y;
+	}
+
 	function Reset()
 	{
 		m_CurrentLM = m_MinimaList;
@@ -763,7 +770,7 @@ private class ClipperBase
 	}
 	//------------------------------------------------------------------------------
 
-	public function GetBounds(pols : Polygons) {
+	public static function getBounds(pols : Polygons) {
 		var result = new Rect();
 		var i = 0;
 		var count = pols.length;
@@ -794,12 +801,19 @@ enum NodeType {
 	Closed;
 }
 
+enum ResultKind {
+	All;
+	NoHoles;
+	HolesOnly;
+}
+
 
 @:allow(hxd.clipper)
 class Clipper extends ClipperBase {
 
 	public var strictlySimple : Bool;
 	public var reverseSolution : Bool;
+	public var resultKind : ResultKind;
 
 	var m_PolyOuts : Array<OutRec>;
 	var m_ClipType : ClipType;
@@ -830,6 +844,7 @@ class Clipper extends ClipperBase {
 		reverseSolution = false;
 		strictlySimple = false;
 		preserveCollinear = false;
+		resultKind = All;
 	}
 
 	inline function xor(a, b) {
@@ -906,13 +921,8 @@ class Clipper extends ClipperBase {
 		m_ClipType = clipType;
 		m_UsingPolyTree = false;
 
-		var succeeded = false;
-		//try {
-			succeeded = ExecuteInternal();
-			//build the return polygons
-			if (succeeded) solution = BuildResult();
-		//}
-		//catch(e : Dynamic) { trace(e); }
+		var succeeded = ExecuteInternal();
+		if (succeeded) solution = BuildResult();
 
 		DisposeAllPolyPts();
 		m_ExecuteLocked = false;
@@ -977,7 +987,9 @@ class Clipper extends ClipperBase {
 				ReversePolyPtLinks(outRec.pts);
 		}
 
+
 		JoinCommonEdges();
+
 		for( outRec in m_PolyOuts )
 			if(outRec.pts != null)
 				FixupOutPolygon(outRec);
@@ -985,6 +997,7 @@ class Clipper extends ClipperBase {
 		if(strictlySimple)
 			DoSimplePolygons();
 
+
 		m_Joins = [];
 		m_GhostJoins = [];
 
@@ -1558,6 +1571,7 @@ class Clipper extends ClipperBase {
 	private function AddOutPt(e:TEdge,pt:Point)
 	{
 		var ToFront = (e.side == EdgeSide.Left);
+
 		if(	e.outIdx < 0 )
 		{
 			var outRec = CreateOutRec();
@@ -1574,14 +1588,12 @@ class Clipper extends ClipperBase {
 		{
 			var outRec = m_PolyOuts[e.outIdx];
 			var op:OutPt = outRec.pts;
-			if (ToFront && pt == op.pt)
-				return op;
-			else if(!ToFront && pt == op.prev.pt)
-				return op.prev;
+			if (ToFront && equals(pt, op.pt)) return op;
+			else if(!ToFront && equals(pt, op.prev.pt)) return op.prev;
 
 			var op2 = new OutPt();
 			op2.idx = outRec.idx;
-			op2.pt = pt;
+			op2.pt = pt.clone();
 			op2.next = op;
 			op2.prev = op.prev;
 			op2.prev.next = op2;
@@ -1606,36 +1618,6 @@ class Clipper extends ClipperBase {
 
 		return seg1a < seg2b && seg2a < seg1b;
 	}
-
-	//------------------------------------------------------------------------------
-
-	function Pt3IsBetweenPt1AndPt2(pt1:Point, pt2:Point, pt3:Point)
-	{
-		if (pt1 == pt3 || pt2 == pt3) return true;
-		else if (pt1.x != pt2.x) return (pt1.x < pt3.x) == (pt3.x < pt2.x);
-		else return (pt1.y < pt3.y) == (pt3.y < pt2.y);
-	}
-	//------------------------------------------------------------------------------
-
-	private function InsertPolyPtBetween(p1:OutPt,p2:OutPt,pt:Point) : OutPt
-	{
-		var result:OutPt = new OutPt();
-		result.pt = pt;
-		if (p2 == p1.next)
-		{
-			p1.next = result;
-			p2.prev = result;
-			result.next = p2;
-			result.prev = p1;
-		} else
-		{
-			p2.next = result;
-			p1.prev = result;
-			result.next = p1;
-			result.prev = p2;
-		}
-		return result;
-	}
 	//------------------------------------------------------------------------------
 
 	private function SetHoleState(e:TEdge,outRec:OutRec)
@@ -1666,17 +1648,17 @@ class Clipper extends ClipperBase {
 	private function FirstIsBottomPt(btmPt1:OutPt,btmPt2:OutPt) : Bool
 	{
 		var p:OutPt = btmPt1.prev;
-		while (p.pt == btmPt1.pt && (p != btmPt1)) p = p.prev;
+		while (equals(p.pt, btmPt1.pt) && (p != btmPt1)) p = p.prev;
 		var dx1p = Math.abs(GetDx(btmPt1.pt, p.pt));
 		p = btmPt1.next;
-		while (p.pt == btmPt1.pt && (p != btmPt1)) p = p.next;
+		while (equals(p.pt, btmPt1.pt) && (p != btmPt1)) p = p.next;
 		var dx1n = Math.abs(GetDx(btmPt1.pt, p.pt));
 
 		p = btmPt2.prev;
-		while (p.pt == btmPt2.pt && (p != btmPt2)) p = p.prev;
+		while (equals(p.pt, btmPt2.pt) && (p != btmPt2)) p = p.prev;
 		var dx2p = Math.abs(GetDx(btmPt2.pt, p.pt));
 		p = btmPt2.next;
-		while (p.pt == btmPt2.pt && (p != btmPt2)) p = p.next;
+		while (equals(p.pt, btmPt2.pt) && (p != btmPt2)) p = p.next;
 		var dx2n = Math.abs(GetDx(btmPt2.pt, p.pt));
 		return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
 	}
@@ -2017,8 +1999,7 @@ class Clipper extends ClipperBase {
 						if (e1Wc2 <= 0 && e2Wc2 <= 0)
 							AddLocalMinPoly(e1, e2, pt);
 					case ClipType.Difference:
-						if (((e1.polyType == PolyType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
-							((e1.polyType == PolyType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
+						if (((e1.polyType == PolyType.Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || ((e1.polyType == PolyType.Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
 								AddLocalMinPoly(e1, e2, pt);
 					case ClipType.Xor:
 						AddLocalMinPoly(e1, e2, pt);
@@ -2633,7 +2614,8 @@ class Clipper extends ClipperBase {
 		for( outRec in m_PolyOuts )
 		{
 			if (outRec.pts == null) continue;
-			if (outRec.isHole) continue; //TODO : add parameter for keeping holes or not !!!
+			if (resultKind == NoHoles && outRec.isHole) continue;
+			if (resultKind == HolesOnly && !outRec.isHole) continue;
 			var p:OutPt = outRec.pts.prev;
 			var cnt:Int = PointCount(p);
 			if (cnt < 2) continue;
@@ -2697,7 +2679,7 @@ class Clipper extends ClipperBase {
 				return;
 			}
 			//test for duplicate points and for same slope (cross-product)
-			if ((pp.pt == pp.next.pt) || (pp.pt == pp.prev.pt) || (SlopesEqual3(pp.prev.pt, pp.pt, pp.next.pt) && (!preserveCollinear || !Pt2IsBetweenPt1AndPt3(pp.prev.pt, pp.pt, pp.next.pt))))
+			if (equals(pp.pt, pp.next.pt) || equals(pp.pt, pp.prev.pt) || (SlopesEqual3(pp.prev.pt, pp.pt, pp.next.pt) && (!preserveCollinear || !Pt2IsBetweenPt1AndPt3(pp.prev.pt, pp.pt, pp.next.pt))))
              {
 				lastOK = null;
 				var tmp:OutPt = pp;
@@ -2871,16 +2853,16 @@ class Clipper extends ClipperBase {
 		//3. StrictlySimple joins where edges touch but are not collinear and where
 		//Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
 		var isHorizontal = (j.outPt1.pt.y == j.offPt.y);
-		if (isHorizontal && (j.offPt == j.outPt1.pt) && (j.offPt == j.outPt2.pt))
+		if (isHorizontal && equals(j.offPt, j.outPt1.pt) && equals(j.offPt, j.outPt2.pt))
 		{
 			//Strictly Simple join ...
 			if (outRec1 != outRec2) return false;
 			op1b = j.outPt1.next;
-			while (op1b != op1 && (op1b.pt == j.offPt))
+			while (op1b != op1 && equals(op1b.pt, j.offPt))
 				op1b = op1b.next;
 			var reverse1 = (op1b.pt.y > j.offPt.y);
 			op2b = j.outPt2.next;
-			while (op2b != op2 && (op2b.pt == j.offPt))
+			while (op2b != op2 && equals(op2b.pt, j.offPt))
 				op2b = op2b.next;
 			var reverse2 = (op2b.pt.y > j.offPt.y);
 			if (reverse1 == reverse2) return false;
@@ -2966,24 +2948,24 @@ class Clipper extends ClipperBase {
 
 			//make sure the polygons are correctly oriented ...
 			op1b = op1.next;
-			while ((op1b.pt == op1.pt) && (op1b != op1)) op1b = op1b.next;
+			while (equals(op1b.pt, op1.pt) && (op1b != op1)) op1b = op1b.next;
 			var Reverse1 = ((op1b.pt.y > op1.pt.y) || !SlopesEqual3(op1.pt, op1b.pt, j.offPt));
 			if (Reverse1)
 			{
 				op1b = op1.prev;
-				while ((op1b.pt == op1.pt) && (op1b != op1)) op1b = op1b.prev;
+				while (equals(op1b.pt, op1.pt) && (op1b != op1)) op1b = op1b.prev;
 				if ((op1b.pt.y > op1.pt.y) ||
 				!SlopesEqual3(op1.pt, op1b.pt, j.offPt)) return false;
 			};
 
 			op2b = op2.next;
-			while ((op2b.pt == op2.pt) && (op2b != op2)) op2b = op2b.next;
+			while (equals(op2b.pt, op2.pt) && (op2b != op2)) op2b = op2b.next;
 			var Reverse2 = ((op2b.pt.y > op2.pt.y) ||
 			!SlopesEqual3(op2.pt, op2b.pt, j.offPt));
 			if (Reverse2)
 			{
 				op2b = op2.prev;
-				while ((op2b.pt == op2.pt) && (op2b != op2)) op2b = op2b.prev;
+				while (equals(op2b.pt, op2.pt) && (op2b != op2)) op2b = op2b.prev;
 				if ((op2b.pt.y > op2.pt.y) ||
 				!SlopesEqual3(op2.pt, op2b.pt, j.offPt)) return false;
 			}
@@ -3266,8 +3248,8 @@ class Clipper extends ClipperBase {
 		var op:OutPt = outrec.pts;
 		do
 		{
-		op.idx = outrec.idx;
-		op = op.prev;
+			op.idx = outrec.idx;
+			op = op.prev;
 		}
 		while(op != outrec.pts);
 	}
@@ -3286,7 +3268,7 @@ class Clipper extends ClipperBase {
 				var op2 = op.next;
 				while (op2 != outrec.pts)
 				{
-					if (op.pt == op2.pt && op2.next != op && op2.prev != op)
+					if (equals(op.pt, op2.pt) && op2.next != op && op2.prev != op)
 					{
 						//split the polygon into two
 						var op3 = op.prev;
@@ -3515,45 +3497,41 @@ class Clipper extends ClipperBase {
         var polyCnt = pattern.length;
         var pathCnt = path.length;
         var result = new Polygons();
-        if (IsSum)
-          for (i in 0...pathCnt)
-          {
-            var p = new Polygon();
-            for (ip in pattern)
-              p.addPoint(new Point(path[i].x + ip.x, path[i].y + ip.y));
-            result.push(p);
-          }
-        else
-          for (i in 0...pathCnt)
-          {
-            var p = new Polygon();
-            for (ip in pattern)
-              p.addPoint(new Point(path[i].x - ip.x, path[i].y - ip.y));
-            result.push(p);
-          }
+
+		for (i in 0...pathCnt) {
+			var p = new Polygon();
+			for (ip in pattern) {
+				if (IsSum)
+					p.addPoint(new Point(path[i].x + ip.x, path[i].y + ip.y));
+				else
+					p.addPoint(new Point(path[i].x - ip.x, path[i].y - ip.y));
+			}
+			result.push(p);
+		}
 
         var quads = new Polygons();
         for (i in 0...pathCnt)
-          for (j in 0...polyCnt)
-          {
-            var quad = new Polygon();
-            quad.addPoint(result[i % pathCnt][j % polyCnt]);
-            quad.addPoint(result[(i + 1) % pathCnt][j % polyCnt]);
-            quad.addPoint(result[(i + 1) % pathCnt][(j + 1) % polyCnt]);
-            quad.addPoint(result[i % pathCnt][(j + 1) % polyCnt]);
-            if (!Orientation(quad)) quad.reverse();
-            quads.push(quad);
-          }
+			for (j in 0...polyCnt)
+			{
+				var quad = new Polygon();
+				quad.addPoint(result[i % pathCnt][j % polyCnt]);
+				quad.addPoint(result[(i + 1) % pathCnt][j % polyCnt]);
+				quad.addPoint(result[(i + 1) % pathCnt][(j + 1) % polyCnt]);
+				quad.addPoint(result[i % pathCnt][(j + 1) % polyCnt]);
+				if (!Orientation(quad)) quad.reverse();
+				quads.push(quad);
+			}
         return quads;
       }
 
       //------------------------------------------------------------------------------
 
-      public static function MinkowskiSum(pattern : Polygon, pol : Polygon)
+      public static function MinkowskiSum(pattern : Polygon, pol : Polygon, ?kind : ResultKind)
       {
         var paths = Minkowski(pattern, pol, true);
         var c = new Clipper();
-        c.addPolygons(paths, PolyType.Subject);
+		c.resultKind = kind == null ? All : kind;
+		c.addPolygons(paths, PolyType.Subject);
         return c.execute(ClipType.Union, PolyFillType.NonZero, PolyFillType.NonZero);
       }
 
@@ -3568,10 +3546,11 @@ class Clipper extends ClipperBase {
       }
       //------------------------------------------------------------------------------
 
-      public static function MinkowskiSums(pattern : Polygon, pols : Polygons)
+      public static function MinkowskiSums(pattern : Polygon, pols : Polygons, ?kind : ResultKind)
       {
         var c = new Clipper();
-        for (i in 0...pols.length)
+        c.resultKind = kind == null ? All : kind;
+		for (i in 0...pols.length)
         {
           var tmp = Minkowski(pattern, pols[i], true);
           c.addPolygons(tmp, PolyType.Subject);
@@ -3582,12 +3561,13 @@ class Clipper extends ClipperBase {
       }
       //------------------------------------------------------------------------------
 
-      public static function MinkowskiDiff(poly1 : Polygon, poly2 : Polygon)
+      public static function MinkowskiDiff(pattern : Polygon, pol : Polygon, ?kind : ResultKind)
       {
-        var paths = Minkowski(poly1, poly2, false);
+        var paths = Minkowski(pattern, pol, false);
         var c = new Clipper();
-        c.addPolygons(paths, PolyType.Subject);
-        return c.execute(ClipType.Union, PolyFillType.NonZero, PolyFillType.NonZero);
+        c.resultKind = kind == null ? All : kind;
+		c.addPolygons(paths, PolyType.Subject);
+		return c.execute(ClipType.Union, PolyFillType.NonZero, PolyFillType.NonZero);
       }
 
 
@@ -3619,5 +3599,391 @@ class Clipper extends ClipperBase {
 
       //------------------------------------------------------------------------------
 
+
 }
 
+
+@:allow(hxd.clipper)
+class ClipperOffset
+{
+	private var m_destPolys : Polygons;
+	private var m_srcPoly : Polygon;
+	private var m_destPoly : Polygon;
+	private var m_normals : Array<h2d.col.Point>;
+	private var m_delta : Float;
+	private var m_sinA : Float;
+	private var m_sin : Float;
+	private var m_cos : Float;
+	private var m_miterLim : Float;
+	private var m_StepsPerRad : Float;
+
+	private var m_lowest : Point;
+	private var m_polyNodes : PolyNode;
+
+	public var ArcTolerance : Float;
+	public var MiterLimit : Float;
+	public var resultKind : ResultKind;
+
+	private var def_arc_tolerance = 0.25;
+	private var two_pi = Math.PI * 2;
+
+	public function new (miterLimit = 2.0, arcTolerance = 0.25) {
+		MiterLimit = miterLimit;
+		ArcTolerance = arcTolerance;
+		m_lowest = new Point( -1, 0);
+		m_normals = [];
+		m_polyNodes = new PolyNode();
+		resultKind = All;
+	}
+    //------------------------------------------------------------------------------
+
+    public function clear() {
+		m_polyNodes = new PolyNode();
+		m_lowest.x = -1;
+    }
+
+    //------------------------------------------------------------------------------
+
+    public function addPolygon(pol : Polygon, joinType : JoinType, endType : EndType) {
+		var highI = pol.length - 1;
+		if (highI < 0) return;
+		var newNode = new PolyNode();
+		newNode.jointype = joinType;
+		newNode.endtype = endType;
+
+		//strip duplicate points from path and also get index to the lowest point ...
+		if (endType == EndType.ClosedLine || endType == EndType.ClosedPol)
+			while (highI > 0 && pol[0] == pol[highI])
+				highI--;
+		newNode.polygon.addPoint(pol[0]);
+		var j = 0, k = 0;
+		for (i in 1...highI + 1)
+			if (newNode.polygon[j] != pol[i]) {
+				j++;
+				newNode.polygon.addPoint(pol[i]);
+				if (pol[i].y > newNode.polygon[k].y || (pol[i].y == newNode.polygon[k].y && pol[i].x < newNode.polygon[k].x))
+					k = j;
+			}
+		if (endType == EndType.ClosedPol && j < 2) return;
+
+		m_polyNodes.addChild(newNode);
+
+		//if this path's lowest pt is lower than all the others then update m_lowest
+		if (endType != EndType.ClosedPol) return;
+		if (m_lowest.x < 0)
+			m_lowest = new Point(m_polyNodes.childCount - 1, k);
+		else
+		{
+			var ip = m_polyNodes.childs[m_lowest.x].polygon[m_lowest.y];
+			if (newNode.polygon[k].y > ip.y || (newNode.polygon[k].y == ip.y && newNode.polygon[k].x < ip.x))
+				m_lowest = new Point(m_polyNodes.childCount - 1, k);
+		}
+    }
+    //------------------------------------------------------------------------------
+
+    public function addPolygons(pols : Polygons, joinType : JoinType, endType : EndType)
+    {
+		for( p in pols)
+			addPolygon(p, joinType, endType);
+    }
+
+    //------------------------------------------------------------------------------
+
+    function fixOrientations()
+    {
+		//fixup orientations of all closed paths if the orientation of the
+		//closed path with the lowermost vertex is wrong ...
+		if (m_lowest.x >= 0 && !Clipper.Orientation(m_polyNodes.childs[m_lowest.x].polygon)) {
+			for (node in m_polyNodes.childs)
+				if (node.endtype == EndType.ClosedPol || (node.endtype == EndType.ClosedLine && Clipper.Orientation(node.polygon)))
+					node.polygon.reverse();
+		}
+		else
+		{
+			for (node in m_polyNodes.childs)
+			{
+				if (node.endtype == EndType.ClosedLine && !Clipper.Orientation(node.polygon))
+					node.polygon.reverse();
+			}
+		}
+    }
+    //------------------------------------------------------------------------------
+
+    static function getUnitNormal(pt1 : Point, pt2 : Point)
+    {
+		var dx : Float = (pt2.x - pt1.x);
+		var dy : Float = (pt2.y - pt1.y);
+		if ((dx == 0) && (dy == 0)) return new h2d.col.Point();
+
+		var f = 1 / Math.distance(dx, dy);
+		dx *= f;
+		dy *= f;
+
+		return new h2d.col.Point(dy, -dx);
+    }
+    //------------------------------------------------------------------------------
+
+	function doOffset(delta : Float) {
+		m_destPolys = new Polygons();
+		m_delta = delta;
+
+		//if Zero offset, just copy any CLOSED polygons to m_p and return ...
+		if (ClipperBase.near_zero(delta)) {
+			for (node in m_polyNodes.childs) {
+				if (node.endtype == EndType.ClosedPol)
+				m_destPolys.push(node.polygon);
+			}
+			return;
+		}
+
+		//see offset_triginometry3.svg in the documentation folder ...
+		if (MiterLimit > 2) m_miterLim = 2 / (MiterLimit * MiterLimit);
+		else m_miterLim = 0.5;
+
+		var y : Float;
+		if (ArcTolerance <= 0.0)
+			y = def_arc_tolerance;
+		else if (ArcTolerance > Math.abs(delta) * def_arc_tolerance)
+			y = Math.abs(delta) * def_arc_tolerance;
+		else
+			y = ArcTolerance;
+		//see offset_triginometry2.svg in the documentation folder ...
+		var steps = Std.int(Math.PI / Math.acos(1 - y / Math.abs(delta)));
+		m_sin = Math.sin(two_pi / steps);
+		m_cos = Math.cos(two_pi / steps);
+		m_StepsPerRad = steps / two_pi;
+		if (delta < 0.) m_sin = -m_sin;
+
+		for (node in m_polyNodes.childs)
+		{
+			m_srcPoly = node.polygon;
+			var len = m_srcPoly.length;
+
+			if (len == 0 || (delta <= 0 && (len < 3 || node.endtype != EndType.ClosedPol)))
+				continue;
+
+			m_destPoly = new Polygon();
+
+			if (len == 1) {
+				if (node.jointype == JoinType.Round) {
+					var X = 1., Y = 0.;
+					for (j in 1...steps + 1) {
+						m_destPoly.addPoint(new Point( Math.round(m_srcPoly[0].x + X * delta), Math.round(m_srcPoly[0].y + Y * delta)));
+						var X2 = X;
+						X = X * m_cos - m_sin * Y;
+						Y = X2 * m_sin + Y * m_cos;
+					}
+				}
+				else {
+					var X = -1., Y = -1.;
+					for (j in 0...4) {
+						m_destPoly.addPoint(new Point( Math.round(m_srcPoly[0].x + X * delta), Math.round(m_srcPoly[0].y + Y * delta)));
+						if (X < 0) X = 1;
+						else if (Y < 0) Y = 1;
+						else X = -1;
+					}
+				}
+				m_destPolys.push(m_destPoly);
+				continue;
+			}
+
+			//build m_normals ...
+			m_normals = [];
+			for (j in 0...len - 1)
+				m_normals.push(getUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
+			if (node.endtype == EndType.ClosedLine || node.endtype == EndType.ClosedPol)
+				m_normals.push(getUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
+			else
+				m_normals.push(m_normals[len - 2]);
+
+			if (node.endtype == EndType.ClosedPol)  {
+				var k = len - 1;
+				for (j in 0...len)
+					k = offsetPoint(j, k, node.jointype);
+				m_destPolys.push(m_destPoly);
+			}
+			else if (node.endtype == EndType.ClosedLine) {
+				var k = len - 1;
+				for (j in 0...len)
+				k = offsetPoint(j, k, node.jointype);
+				m_destPolys.push(m_destPoly);
+				m_destPoly = new Polygon();
+				//re-build m_normals ...
+				var n = m_normals[len - 1];
+				var j = len - 1;
+				while(j > 0) {
+					m_normals[j] = new h2d.col.Point(-m_normals[j - 1].x, -m_normals[j - 1].y);
+					j--;
+				}
+				m_normals[0] = new DoublePoint(-n.x, -n.y);
+				k = 0;
+				var j = len - 1;
+				while(j > 0) {
+					k = offsetPoint(j, k, node.jointype);
+					j--;
+				}
+				m_destPolys.push(m_destPoly);
+			}
+			else {
+				var k = 0;
+				for (j in 1...len - 1)
+					k = offsetPoint(j, k, node.jointype);
+
+				var pt1 : Point;
+				if (node.endtype == EndType.OpenButt) {
+					var j = len - 1;
+					pt1 = new Point(Math.round(m_srcPoly[j].x + m_normals[j].x * delta), Math.round(m_srcPoly[j].y + m_normals[j].y * delta));
+					m_destPoly.addPoint(pt1);
+					pt1 = new Point(Math.round(m_srcPoly[j].x - m_normals[j].x * delta), Math.round(m_srcPoly[j].y - m_normals[j].y * delta));
+					m_destPoly.addPoint(pt1);
+				}
+				else {
+					var j = len - 1;
+					k = len - 2;
+					m_sinA = 0;
+					m_normals[j] = new DoublePoint(-m_normals[j].x, -m_normals[j].y);
+					if (node.endtype == EndType.OpenSquare)
+						doSquare(j, k);
+					else
+						doRound(j, k);
+				}
+
+				//re-build m_normals ...
+				var j = len - 1;
+				while(j > 0) {
+					m_normals[j] = new h2d.col.Point( -m_normals[j - 1].x, -m_normals[j - 1].y);
+					j--;
+				}
+
+				m_normals[0] = new DoublePoint(-m_normals[1].x, -m_normals[1].y);
+
+				k = len - 1;
+				var j = k - 1;
+				while(j > 0) {
+					k = offsetPoint(j, k, node.jointype);
+					j--;
+				}
+
+				if (node.endtype == EndType.OpenButt) {
+					pt1 = new Point(Math.round(m_srcPoly[0].x - m_normals[0].x * delta), Math.round(m_srcPoly[0].y - m_normals[0].y * delta));
+					m_destPoly.addPoint(pt1);
+					pt1 = new Point(Math.round(m_srcPoly[0].x + m_normals[0].x * delta), Math.round(m_srcPoly[0].y + m_normals[0].y * delta));
+					m_destPoly.addPoint(pt1);
+				}
+				else {
+					k = 1;
+					m_sinA = 0;
+					if (node.endtype == EndType.OpenSquare)
+						doSquare(0, 1);
+					else
+						doRound(0, 1);
+				}
+				m_destPolys.push(m_destPoly);
+			}
+		}
+    }
+    //------------------------------------------------------------------------------
+
+    public function execute(delta : Float) {
+		fixOrientations();
+		doOffset(delta);
+
+		//now clean up 'corners' ...
+		var clpr = new Clipper();
+		clpr.resultKind = resultKind;
+		clpr.addPolygons(m_destPolys, PolyType.Subject);
+		if (delta > 0) return clpr.execute(ClipType.Union, PolyFillType.Positive, PolyFillType.Positive);
+		else {
+			var r = ClipperBase.getBounds(m_destPolys);
+			var outer = new Polygon();
+
+			outer.addPoint(new Point(r.left - 10, r.bottom + 10));
+			outer.addPoint(new Point(r.right + 10, r.bottom + 10));
+			outer.addPoint(new Point(r.right + 10, r.top - 10));
+			outer.addPoint(new Point(r.left - 10, r.top - 10));
+
+			clpr.addPolygon(outer, PolyType.Subject);
+			clpr.reverseSolution = true;
+			var out = clpr.execute(ClipType.Union, PolyFillType.Negative, PolyFillType.Negative);
+			if (out.length > 0) out.shift();
+/*
+			trace(out.length);
+			for(o in out)
+				for(p in o.points)
+					trace(p);*/
+			return out;
+		}
+    }
+    //------------------------------------------------------------------------------
+
+    function offsetPoint(j : Int, k : Int, jointype : JoinType)
+    {
+		//cross product ...
+		m_sinA = (m_normals[k].x * m_normals[j].y - m_normals[j].x * m_normals[k].y);
+
+		if (Math.abs(m_sinA * m_delta) < 1.0)
+		{
+			//dot product ...
+			var cosA = (m_normals[k].x * m_normals[j].x + m_normals[j].y * m_normals[k].y);
+			if (cosA > 0) // angle ==> 0 degrees
+			{
+				m_destPoly.addPoint(new Point(Math.round(m_srcPoly[j].x + m_normals[k].x * m_delta), Math.round(m_srcPoly[j].y + m_normals[k].y * m_delta)));
+				return k;
+			}
+			//else angle ==> 180 degrees
+		}
+		else if (m_sinA > 1.0) m_sinA = 1.0;
+		else if (m_sinA < -1.0) m_sinA = -1.0;
+
+		if (m_sinA * m_delta < 0)
+		{
+			m_destPoly.addPoint(new Point(Math.round(m_srcPoly[j].x + m_normals[k].x * m_delta), Math.round(m_srcPoly[j].y + m_normals[k].y * m_delta)));
+			m_destPoly.addPoint(m_srcPoly[j]);
+			m_destPoly.addPoint(new Point(Math.round(m_srcPoly[j].x + m_normals[j].x * m_delta), Math.round(m_srcPoly[j].y + m_normals[j].y * m_delta)));
+		}
+		else
+		switch (jointype)
+		{
+			case JoinType.Miter:
+				var r = 1 + (m_normals[j].x * m_normals[k].x + m_normals[j].y * m_normals[k].y);
+				if (r >= m_miterLim)
+					doMiter(j, k, r);
+				else doSquare(j, k);
+			case JoinType.Square: doSquare(j, k);
+			case JoinType.Round: doRound(j, k);
+		}
+
+		return j;
+    }
+    //------------------------------------------------------------------------------
+
+    inline function doSquare(j : Int, k : Int)
+    {
+		var dx = Math.tan(Math.atan2(m_sinA, m_normals[k].x * m_normals[j].x + m_normals[k].y * m_normals[j].y) * 0.25);
+		m_destPoly.addPoint(new Point(Math.round(m_srcPoly[j].x + m_delta * (m_normals[k].x - m_normals[k].y * dx)), Math.round(m_srcPoly[j].y + m_delta * (m_normals[k].y + m_normals[k].x * dx))));
+		m_destPoly.addPoint(new Point(Math.round(m_srcPoly[j].x + m_delta * (m_normals[j].x + m_normals[j].y * dx)), Math.round(m_srcPoly[j].y + m_delta * (m_normals[j].y - m_normals[j].x * dx))));
+    }
+    //------------------------------------------------------------------------------
+
+   inline function doMiter(j : Int, k : Int, r : Float)
+    {
+		var q = m_delta / r;
+		m_destPoly.addPoint(new Point(Math.round(m_srcPoly[j].x + (m_normals[k].x + m_normals[j].x) * q), Math.round(m_srcPoly[j].y + (m_normals[k].y + m_normals[j].y) * q)));
+    }
+    //------------------------------------------------------------------------------
+
+   inline function doRound(j : Int, k : Int)
+    {
+		var a = Math.atan2(m_sinA, m_normals[k].x * m_normals[j].x + m_normals[k].y * m_normals[j].y);
+		var steps = Math.imax(Math.round(m_StepsPerRad * Math.abs(a)), 1);
+
+		var X = m_normals[k].x, Y = m_normals[k].y, X2;
+		for (i in 0...steps) {
+			m_destPoly.addPoint(new Point(Math.round(m_srcPoly[j].x + X * m_delta), Math.round(m_srcPoly[j].y + Y * m_delta)));
+			X2 = X;
+			X = X * m_cos - m_sin * Y;
+			Y = X2 * m_sin + Y * m_cos;
+		}
+		m_destPoly.addPoint(new Point(Math.round(m_srcPoly[j].x + m_normals[j].x * m_delta), Math.round(m_srcPoly[j].y + m_normals[j].y * m_delta)));
+    }
+}