public List <Vector2[]> TriangulateClipperSolution(ClipperLib.PolyTree solution) { var tess = new Tess(); tess.NoEmptyPolygons = true; // Add a contour for each part of the solution tree ClipperLib.PolyNode node = solution.GetFirst(); while (node != null) { // Only interested in closed paths if (!node.IsOpen) { // Add a new countor. Holes are automatically generated. var vertices = node.Contour.Select(pt => new ContourVertex { Position = new Vec3 { X = pt.X, Y = pt.Y, Z = 0 } }).ToArray(); tess.AddContour(vertices); } node = node.GetNext(); } return(TrianglesFromTessellator(tess)); }
// Put the closed path polygons into an enumerable collection of an array of points. // Each array of points in a path in a "complex" polygon that supports convace edges and holes public static IEnumerable <PointF[]> SolutionPolygons_Complex(ClipperLib.PolyTree solution) { foreach (var points in ClipperLib.Clipper.ClosedPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); yield return(pointfs.ToArray()); } }
private XElement CreateCollisionElementForLayer(TmxLayer layer) { // Collision elements look like this // (Can also have EdgeCollider2Ds) // <GameOject name="Collision"> // <PolygonCollider2D> // <Path>list of points</Path> // <Path>another list of points</Path> // </PolygonCollider2D> // </GameOject> LayerClipper.TransformPointFunc xfFunc = delegate(float x, float y) { // Transform point to Unity space PointF pointUnity3d = PointFToUnityVector_NoScale(new PointF(x, y)); ClipperLib.IntPoint point = new ClipperLib.IntPoint(pointUnity3d.X, pointUnity3d.Y); return(point); }; LayerClipper.ProgressFunc progFunc = delegate(string prog) { Program.WriteLine(prog); }; ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); // Add our polygon and edge colliders List <XElement> polyColliderElements = new List <XElement>(); if (layer.IsExportingConvexPolygons()) { AddPolygonCollider2DElements_Convex(solution, polyColliderElements); } else { AddPolygonCollider2DElements_Complex(solution, polyColliderElements); } AddEdgeCollider2DElements(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), polyColliderElements); if (polyColliderElements.Count() == 0) { // No collisions on this layer return(null); } XElement gameObjectCollision = new XElement("GameObject", new XAttribute("name", "Collision"), polyColliderElements); return(gameObjectCollision); }
// Put the closed path polygons into an enumerable collection of an array of points. // Each array of points in a separate convex polygon public static IEnumerable <PointF[]> SolutionPolygons_Simple(ClipperLib.PolyTree solution) { ConvexPolygonSet convexPolygonSet = new ConvexPolygonSet(); convexPolygonSet.MakeConvextSetFromClipperSolution(solution); foreach (var polygon in convexPolygonSet.Polygons) { var pointfs = polygon.Select(pt => new PointF(pt.Xf, pt.Yf)); yield return(pointfs.ToArray()); } }
private static void DrawCollisionLayer(SKCanvas canvas, TmxLayer tmxLayer, SKColor polyColor, SKColor lineColor) { LayerClipper.TransformPointFunc xfFunc = (x, y) => new ClipperLib.IntPoint(x, y); LayerClipper.ProgressFunc progFunc = (prog) => { }; // do nothing ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(tmxLayer.TmxMap, tmxLayer, xfFunc, progFunc); using (SKPaint paint = new SKPaint()) { // Draw all closed polygons // First, add them to the path // (But are we using convex polygons are complex polygons? using (SKPath path = new SKPath()) { var polygons = tmxLayer.IsExportingConvexPolygons() ? LayerClipper.SolutionPolygons_Simple(solution) : LayerClipper.SolutionPolygons_Complex(solution); foreach (var pointfArray in polygons) { var pts = pointfArray.ToSkPointArray(); path.AddPoly(pts, true); } // Then, fill and draw the path full of polygons if (path.PointCount > 0) { paint.Style = SKPaintStyle.Fill; paint.Color = polyColor; canvas.DrawPath(path, paint); paint.Style = SKPaintStyle.Stroke; paint.StrokeWidth = StrokeWidthThick; paint.Color = lineColor; canvas.DrawPath(path, paint); } } // Draw all lines (open polygons) using (SKPath path = new SKPath()) { foreach (var points in ClipperLib.Clipper.OpenPathsFromPolyTree(solution)) { var pts = points.Select(pt => new SKPoint(pt.X, pt.Y)).ToArray(); path.AddPoly(pts, false); } if (path.PointCount > 0) { paint.Style = SKPaintStyle.Stroke; paint.StrokeWidth = StrokeWidthThick; paint.Color = lineColor; canvas.DrawPath(path, paint); } } } }
private void DrawLayerColliders(Graphics g, TmxLayer layer, Color polyColor, Color lineColor) { LayerClipper.TransformPointFunc xfFunc = (x, y) => new ClipperLib.IntPoint(x, y); LayerClipper.ProgressFunc progFunc = (prog) => { }; // do nothing ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); float inverseScale = 1.0f / this.scale; if (inverseScale > 1) { inverseScale = 1; } using (GraphicsPath path = new GraphicsPath()) using (Pen pen = new Pen(lineColor, 2.0f * inverseScale)) using (Brush brush = new HatchBrush(HatchStyle.Percent60, polyColor, Color.Transparent)) { pen.Alignment = PenAlignment.Inset; // Draw all closed polygons // First, add them to the path // (But are we using convex polygons are complex polygons? var polygons = layer.IsExportingConvexPolygons() ? LayerClipper.SolutionPolygons_Simple(solution) : LayerClipper.SolutionPolygons_Complex(solution); foreach (var pointfArray in polygons) { path.AddPolygon(pointfArray); } // Then, fill and draw the path full of polygons if (path.PointCount > 0) { g.FillPath(brush, path); g.DrawPath(pen, path); } // Draw all lines (open polygons) path.Reset(); foreach (var points in ClipperLib.Clipper.OpenPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.StartFigure(); path.AddLines(pointfs.ToArray()); } if (path.PointCount > 0) { g.DrawPath(pen, path); } } }
private RawCharacterOutline InternalGetCharacterOutlineForCaching(char textChar, FontX font) { var glyphTypeface = font.InvariantDescriptionStringWithoutSizeInformation; var rawOutline = GetRawCharacterOutline(textChar, font, FontSizeForCaching); var clipperPolygonsInput = new List <List <ClipperLib.IntPoint> >(); var sharpPoints = new HashSet <ClipperLib.IntPoint>(); var allPoints = new HashSet <ClipperLib.IntPoint>(); // allPoints to determine whether after the simplification new points were added foreach (var polygon in rawOutline.Outline) { foreach (var p in polygon.SharpPoints) { sharpPoints.Add(new ClipperLib.IntPoint(p.X * 65536, p.Y * 65536)); } var clipperPolygon = new List <ClipperLib.IntPoint>(polygon.Points.Select((x) => new ClipperLib.IntPoint(x.X * 65536, x.Y * 65536))); clipperPolygonsInput.Add(clipperPolygon); foreach (var clipperPoint in clipperPolygon) { allPoints.Add(clipperPoint); } } //clipperPolygons = ClipperLib.Clipper.SimplifyPolygons(clipperPolygons, ClipperLib.PolyFillType.pftEvenOdd); var clipperPolygons = new ClipperLib.PolyTree(); var clipper = new ClipperLib.Clipper { StrictlySimple = true }; clipper.AddPaths(clipperPolygonsInput, ClipperLib.PolyType.ptSubject, true); clipper.Execute(ClipperLib.ClipType.ctUnion, clipperPolygons, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNegative); var polygons = new List <PolygonClosedD2D>(); var dictClipperNodeToNode = new Dictionary <ClipperLib.PolyNode, PolygonClosedD2D>(); // helper dictionary ClipperPolyTreeToPolygonListRecursively(clipperPolygons, sharpPoints, allPoints, polygons, dictClipperNodeToNode); var result = rawOutline; result.Outline = polygons; return(result); }
private void AddPolygonCollider2DElements_Convex(ClipperLib.PolyTree solution, List <XElement> xmlList) { // This may generate many convex polygons as opposed to one "complicated" one var polygons = LayerClipper.SolutionPolygons_Simple(solution); // Each PointF array is a polygon with a single path foreach (var pointfArray in polygons) { string data = String.Join(" ", pointfArray.Select(pt => String.Format("{0},{1}", pt.X * Tiled2Unity.Settings.Scale, pt.Y * Tiled2Unity.Settings.Scale))); XElement pathElement = new XElement("Path", data); XElement polyColliderElement = new XElement("PolygonCollider2D", pathElement); xmlList.Add(polyColliderElement); } }
private List <Poly2Tri.DelaunayTriangle> GetTriangleListFromClipperSolution(ClipperLib.PolyTree solution) { Func <ClipperLib.IntPoint, Poly2Tri.PolygonPoint> xfToPolygonPoint = (p) => new Poly2Tri.PolygonPoint(p.X, p.Y); Poly2Tri.PolygonSet polygonSet = new Poly2Tri.PolygonSet(); ClipperLib.PolyNode node = solution.GetFirst(); while (node != null) { // Only interested in closed paths if (!node.IsOpen) { if (node.IsHole) { if (polygonSet.Polygons.Count() > 0) { // Add hole to last polygon entered var polyPoints = node.Contour.Select(xfToPolygonPoint).ToArray(); Poly2Tri.Polygon hole = new Poly2Tri.Polygon(polyPoints); Poly2Tri.Polygon polygon = polygonSet.Polygons.Last(); polygon.AddHole(hole); } } else { // Add a new polygon to the set var polyPoints = node.Contour.Select(xfToPolygonPoint).ToList(); Poly2Tri.Polygon polygon = new Poly2Tri.Polygon(polyPoints); polygonSet.Add(polygon); } } node = node.GetNext(); } // Now triangulate the whole set Poly2Tri.P2T.Triangulate(polygonSet); // Combine all the triangles into one list List <Poly2Tri.DelaunayTriangle> triangles = new List <Poly2Tri.DelaunayTriangle>(); foreach (var polygon in polygonSet.Polygons) { triangles.AddRange(polygon.Triangles); } return(triangles); }
public static long ExecuteOriginalClipper(int testIterationCount, List <ClipExecutionData> executionData) { var stopwatch = new Stopwatch(); stopwatch.Start(); for (var i = 0; i < testIterationCount; i++) { foreach (var clipPath in executionData) { var subject = new List <List <ClipperLib.IntPoint> >( clipPath .Subject .Select(poly => new List <ClipperLib.IntPoint>(poly.Select(pt => new ClipperLib.IntPoint( pt.X * Scale, pt.Y * Scale))))); var clip = new List <List <ClipperLib.IntPoint> >( clipPath .Clip .Select(poly => new List <ClipperLib.IntPoint>(poly.Select(pt => new ClipperLib.IntPoint( pt.X * Scale, pt.Y * Scale))))); var solution = new ClipperLib.PolyTree(); var clipper = new ClipperLib.Clipper(); clipper.AddPaths(subject, ClipperLib.PolyType.ptSubject, true); clipper.AddPaths(clip, ClipperLib.PolyType.ptClip, true); // Convert performance test library operation enum to ClipperLib operation enum. var operation = (ClipperLib.ClipType)Enum.Parse(typeof(ClipperLib.ClipType), $"ct{clipPath.Operation}", true); Assert.IsTrue(clipper.Execute(operation, solution)); } } stopwatch.Stop(); return(stopwatch.Elapsed.Ticks); }
// Put the closed path polygons into an enumerable collection of an array of points. // Each array of points in a separate convex polygon public static IEnumerable <PointF[]> SolutionPolygons_Simple(ClipperLib.PolyTree solution) { // Triangulate the solution polygon Geometry.TriangulateClipperSolution triangulation = new Geometry.TriangulateClipperSolution(); List <PointF[]> triangles = triangulation.Triangulate(solution); #if T2U_TRIANGLES // Force triangle output foreach (var tri in triangles) { yield return(tri); } #else // Group the triangles into convex polygons Geometry.ComposeConvexPolygons composition = new Geometry.ComposeConvexPolygons(); List <PointF[]> polygons = composition.Compose(triangles); foreach (var poly in polygons) { yield return(poly); } #endif }
private void DrawLayerColliders(Graphics g, TmxLayer layer, Color polyColor, Color lineColor) { LayerClipper.TransformPointFunc xfFunc = (x, y) => new ClipperLib.IntPoint(x, y); LayerClipper.ProgressFunc progFunc = (prog) => { }; // do nothing ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); using (GraphicsPath path = new GraphicsPath()) using (Pen pen = new Pen(lineColor, 1.0f)) using (Brush brush = new HatchBrush(HatchStyle.ForwardDiagonal, lineColor, polyColor)) { pen.Alignment = PenAlignment.Inset; // Draw all closed polygons foreach (var points in ClipperLib.Clipper.ClosedPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.AddPolygon(pointfs.ToArray()); } if (path.PointCount > 0) { g.FillPath(brush, path); g.DrawPath(pen, path); } // Draw all lines (open polygons) path.Reset(); foreach (var points in ClipperLib.Clipper.OpenPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.StartFigure(); path.AddLines(pointfs.ToArray()); } if (path.PointCount > 0) { g.DrawPath(pen, path); } } }
private void AddPolygonCollider2DElements_Complex(ClipperLib.PolyTree solution, List <XElement> xmlList) { // This should generate one "complicated" polygon which may contain holes and concave edges var polygons = ClipperLib.Clipper.ClosedPathsFromPolyTree(solution); if (polygons.Count == 0) { return; } // Add just one polygon collider that has all paths in it. List <XElement> pathElements = new List <XElement>(); foreach (var path in polygons) { string data = String.Join(" ", path.Select(pt => String.Format("{0},{1}", pt.X * Tiled2Unity.Settings.Scale, pt.Y * Tiled2Unity.Settings.Scale))); XElement pathElement = new XElement("Path", data); pathElements.Add(pathElement); } XElement polyColliderElement = new XElement("PolygonCollider2D", pathElements); xmlList.Add(polyColliderElement); }
public static List <PointLatLngAlt> CreateRotary(List <PointLatLngAlt> polygon, double altitude, double distance, double spacing, double angle, double overshoot1, double overshoot2, StartPosition startpos, bool shutter, float minLaneSeparation, float leadin, PointLatLngAlt HomeLocation, int clockwise_laps, bool match_spiral_perimeter, int laps) { spacing = 0; if (distance < 0.1) { distance = 0.1; } if (polygon.Count == 0) { return(new List <PointLatLngAlt>()); } List <utmpos> ans = new List <utmpos>(); // utm zone distance calcs will be done in int utmzone = polygon[0].GetUTMZone(); // utm position list List <utmpos> utmpositions = utmpos.ToList(PointLatLngAlt.ToUTM(utmzone, polygon), utmzone); // close the loop if its not already if (utmpositions[0] != utmpositions[utmpositions.Count - 1]) { utmpositions.Add(utmpositions[0]); // make a full loop } var maxlane = laps; // (Centroid(utmpositions).GetDistance(utmpositions[0]) / distance); ClipperLib.ClipperOffset clipperOffset = new ClipperLib.ClipperOffset(); clipperOffset.AddPath(utmpositions.Select(a => { return(new ClipperLib.IntPoint(a.x * 1000.0, a.y * 1000.0)); }).ToList(), ClipperLib.JoinType.jtMiter, ClipperLib.EndType.etClosedPolygon); for (int lane = 0; lane < maxlane; lane++) { List <utmpos> ans1 = new List <utmpos>(); ClipperLib.PolyTree tree = new ClipperLib.PolyTree(); clipperOffset.Execute(ref tree, (Int64)(distance * 1000.0 * -lane)); if (tree.ChildCount == 0) { break; } if (lane < clockwise_laps || clockwise_laps < 0) { ClipperLib.Clipper.ReversePaths(ClipperLib.Clipper.PolyTreeToPaths(tree)); } foreach (var treeChild in tree.Childs) { ans1 = treeChild.Contour.Select(a => new utmpos(a.X / 1000.0, a.Y / 1000.0, utmzone)) .ToList(); if (lane == 0 && clockwise_laps != 1 && match_spiral_perimeter) { ans1.Insert(0, ans1.Last <utmpos>()); // start at the last point of the first calculated lap // to make a closed polygon on the first trip around } if (lane == clockwise_laps - 1) { ans1.Add(ans1.First <utmpos>()); // revisit the first waypoint on this lap to cleanly exit the CW pattern } if (ans.Count() > 2) { var start1 = ans[ans.Count() - 1]; var end1 = ans[ans.Count() - 2]; var start2 = ans1[0]; var end2 = ans1[ans1.Count() - 1]; } ans.AddRange(ans1); } } // set the altitude on all points return(ans.Select(plla => { var a = plla.ToLLA(); a.Alt = altitude; a.Tag = "S"; return a; }).ToList()); }
public static ClipperLib.PolyTree ExecuteClipper(TmxMap tmxMap, TmxLayer tmxLayer, TransformPointFunc xfFunc, ProgressFunc progFunc) { // The "fullClipper" combines the clipper results from the smaller pieces ClipperLib.Clipper fullClipper = new ClipperLib.Clipper(); // From the perspective of Clipper lines are polygons too // Closed paths == polygons // Open paths == lines var polygonGroups = from y in Enumerable.Range(0, tmxLayer.Height) from x in Enumerable.Range(0, tmxLayer.Width) let rawTileId = tmxLayer.GetRawTileIdAt(x, y) let tileId = TmxMath.GetTileIdWithoutFlags(rawTileId) where tileId != 0 let tile = tmxMap.Tiles[tileId] from polygon in tile.ObjectGroup.Objects where (polygon as TmxHasPoints) != null let groupX = x / LayerClipper.GroupBySize let groupY = y / LayerClipper.GroupBySize group new { PositionOnMap = tmxMap.GetMapPositionAt(x, y, tile), HasPointsInterface = polygon as TmxHasPoints, TmxObjectInterface = polygon, IsFlippedDiagnoally = TmxMath.IsTileFlippedDiagonally(rawTileId), IsFlippedHorizontally = TmxMath.IsTileFlippedHorizontally(rawTileId), IsFlippedVertically = TmxMath.IsTileFlippedVertically(rawTileId), TileCenter = new PointF(tile.TileSize.Width * 0.5f, tile.TileSize.Height * 0.5f), } by Tuple.Create(groupX, groupY); int groupIndex = 0; int groupCount = polygonGroups.Count(); foreach (var polyGroup in polygonGroups) { if (groupIndex % 5 == 0) { progFunc(String.Format("Clipping '{0}' polygons: {1}%", tmxLayer.Name, (groupIndex / (float)groupCount) * 100)); } groupIndex++; // The "groupClipper" clips the polygons in a smaller part of the world ClipperLib.Clipper groupClipper = new ClipperLib.Clipper(); // Add all our polygons to the Clipper library so it can reduce all the polygons to a (hopefully small) number of paths foreach (var poly in polyGroup) { // Create a clipper library polygon out of each and add it to our collection ClipperPolygon clipperPolygon = new ClipperPolygon(); // Our points may be transformed due to tile flipping/rotation // Before we transform them we put all the points into local space relative to the tile SizeF offset = new SizeF(poly.TmxObjectInterface.Position); PointF[] transformedPoints = poly.HasPointsInterface.Points.Select(pt => PointF.Add(pt, offset)).ToArray(); // Now transform the points relative to the tile TmxMath.TransformPoints(transformedPoints, poly.TileCenter, poly.IsFlippedDiagnoally, poly.IsFlippedHorizontally, poly.IsFlippedVertically); foreach (var pt in transformedPoints) { float x = poly.PositionOnMap.X + pt.X; float y = poly.PositionOnMap.Y + pt.Y; ClipperLib.IntPoint point = xfFunc(x, y); clipperPolygon.Add(point); } // Because of Unity's cooridnate system, the winding order of the polygons must be reversed clipperPolygon.Reverse(); // Add the "subject" groupClipper.AddPath(clipperPolygon, ClipperLib.PolyType.ptSubject, poly.HasPointsInterface.ArePointsClosed()); } // Get a solution for this group ClipperLib.PolyTree solution = new ClipperLib.PolyTree(); groupClipper.Execute(ClipperLib.ClipType.ctUnion, solution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); // Combine the solutions into the full clipper fullClipper.AddPaths(ClipperLib.Clipper.ClosedPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, true); fullClipper.AddPaths(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, false); } progFunc(String.Format("Clipping '{0}' polygons: 100%", tmxLayer.Name)); ClipperLib.PolyTree fullSolution = new ClipperLib.PolyTree(); fullClipper.Execute(ClipperLib.ClipType.ctUnion, fullSolution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); return fullSolution; }
private RawCharacterOutline InternalGetCharacterOutlineForCaching(char textChar, FontX font) { var glyphTypeface = font.InvariantDescriptionStringWithoutSizeInformation; var rawOutline = GetRawCharacterOutline(textChar, font, FontSizeForCaching); List<List<ClipperLib.IntPoint>> clipperPolygonsInput = new List<List<ClipperLib.IntPoint>>(); var sharpPoints = new HashSet<ClipperLib.IntPoint>(); var allPoints = new HashSet<ClipperLib.IntPoint>(); // allPoints to determine whether after the simplification new points were added foreach (var polygon in rawOutline.Outline) { foreach (var p in polygon.SharpPoints) { sharpPoints.Add(new ClipperLib.IntPoint(p.X * 65536, p.Y * 65536)); } var clipperPolygon = new List<ClipperLib.IntPoint>(polygon.Points.Select((x) => new ClipperLib.IntPoint(x.X * 65536, x.Y * 65536))); clipperPolygonsInput.Add(clipperPolygon); foreach (var clipperPoint in clipperPolygon) allPoints.Add(clipperPoint); } //clipperPolygons = ClipperLib.Clipper.SimplifyPolygons(clipperPolygons, ClipperLib.PolyFillType.pftEvenOdd); var clipperPolygons = new ClipperLib.PolyTree(); ClipperLib.Clipper clipper = new ClipperLib.Clipper(); clipper.StrictlySimple = true; clipper.AddPaths(clipperPolygonsInput, ClipperLib.PolyType.ptSubject, true); clipper.Execute(ClipperLib.ClipType.ctUnion, clipperPolygons, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNegative); var polygons = new List<PolygonClosedD2D>(); var dictClipperNodeToNode = new Dictionary<ClipperLib.PolyNode, PolygonClosedD2D>(); // helper dictionary ClipperPolyTreeToPolygonListRecursively(clipperPolygons, sharpPoints, allPoints, polygons, dictClipperNodeToNode); var result = rawOutline; result.Outline = polygons; return result; }
public static List <PointLatLngAlt> CreateRotary(List <PointLatLngAlt> polygon, double altitude, double distance, double spacing, double angle, double overshoot1, double overshoot2, StartPosition startpos, bool shutter, float minLaneSeparation, float leadin, PointLatLngAlt HomeLocation) { spacing = 0; if (distance < 0.1) { distance = 0.1; } if (polygon.Count == 0) { return(new List <PointLatLngAlt>()); } List <utmpos> ans = new List <utmpos>(); // utm zone distance calcs will be done in int utmzone = polygon[0].GetUTMZone(); // utm position list List <utmpos> utmpositions = utmpos.ToList(PointLatLngAlt.ToUTM(utmzone, polygon), utmzone); // close the loop if its not already if (utmpositions[0] != utmpositions[utmpositions.Count - 1]) { utmpositions.Add(utmpositions[0]); // make a full loop } var maxlane = 200; // (Centroid(utmpositions).GetDistance(utmpositions[0]) / distance); ClipperLib.ClipperOffset clipperOffset = new ClipperLib.ClipperOffset(); clipperOffset.AddPath(utmpositions.Select(a => { return(new ClipperLib.IntPoint(a.x * 1000.0, a.y * 1000.0)); }).ToList(), ClipperLib.JoinType.jtMiter, ClipperLib.EndType.etClosedPolygon); for (int lane = 0; lane < maxlane; lane++) { List <utmpos> ans1 = new List <utmpos>(); ClipperLib.PolyTree tree = new ClipperLib.PolyTree(); clipperOffset.Execute(ref tree, (Int64)(distance * 1000.0 * -lane)); if (tree.ChildCount == 0) { break; } foreach (var treeChild in tree.Childs) { ans1 = treeChild.Contour.Select(a => new utmpos(a.X / 1000.0, a.Y / 1000.0, utmzone)) .ToList(); if (ans.Count() > 2) { var start1 = ans[ans.Count() - 1]; var end1 = ans[ans.Count() - 2]; var start2 = ans1[0]; var end2 = ans1[ans1.Count() - 1]; } ans.AddRange(ans1); } } // set the altitude on all points return(ans.Select(plla => { var a = plla.ToLLA(); a.Alt = altitude; a.Tag = "S"; return a; }).ToList()); }
public static ClipperLib.PolyTree ExecuteClipper(TmxMap tmxMap, TmxLayer tmxLayer, TransformPointFunc xfFunc, ProgressFunc progFunc) { // The "fullClipper" combines the clipper results from the smaller pieces ClipperLib.Clipper fullClipper = new ClipperLib.Clipper(); // From the perspective of Clipper lines are polygons too // Closed paths == polygons // Open paths == lines var polygonGroups = from y in Enumerable.Range(0, tmxLayer.Height) from x in Enumerable.Range(0, tmxLayer.Width) let rawTileId = tmxLayer.GetRawTileIdAt(x, y) let tileId = TmxMath.GetTileIdWithoutFlags(rawTileId) where tileId != 0 let tile = tmxMap.Tiles[tileId] from polygon in tile.ObjectGroup.Objects where (polygon as TmxHasPoints) != null let groupX = x / LayerClipper.GroupBySize let groupY = y / LayerClipper.GroupBySize group new { PositionOnMap = tmxMap.GetMapPositionAt(x, y, tile), HasPointsInterface = polygon as TmxHasPoints, TmxObjectInterface = polygon, IsFlippedDiagnoally = TmxMath.IsTileFlippedDiagonally(rawTileId), IsFlippedHorizontally = TmxMath.IsTileFlippedHorizontally(rawTileId), IsFlippedVertically = TmxMath.IsTileFlippedVertically(rawTileId), TileCenter = new PointF(tile.TileSize.Width * 0.5f, tile.TileSize.Height * 0.5f), } by Tuple.Create(groupX, groupY); int groupIndex = 0; int groupCount = polygonGroups.Count(); foreach (var polyGroup in polygonGroups) { if (groupIndex % 5 == 0) { progFunc(String.Format("Clipping '{0}' polygons: {1}%", tmxLayer.Name, (groupIndex / (float)groupCount) * 100)); } groupIndex++; // The "groupClipper" clips the polygons in a smaller part of the world ClipperLib.Clipper groupClipper = new ClipperLib.Clipper(); // Add all our polygons to the Clipper library so it can reduce all the polygons to a (hopefully small) number of paths foreach (var poly in polyGroup) { // Create a clipper library polygon out of each and add it to our collection ClipperPolygon clipperPolygon = new ClipperPolygon(); // Our points may be transformed due to tile flipping/rotation // Before we transform them we put all the points into local space relative to the tile SizeF offset = new SizeF(poly.TmxObjectInterface.Position); PointF[] transformedPoints = poly.HasPointsInterface.Points.Select(pt => PointF.Add(pt, offset)).ToArray(); // Now transform the points relative to the tile TmxMath.TransformPoints(transformedPoints, poly.TileCenter, poly.IsFlippedDiagnoally, poly.IsFlippedHorizontally, poly.IsFlippedVertically); foreach (var pt in transformedPoints) { float x = poly.PositionOnMap.X + pt.X; float y = poly.PositionOnMap.Y + pt.Y; ClipperLib.IntPoint point = xfFunc(x, y); clipperPolygon.Add(point); } // Because of Unity's cooridnate system, the winding order of the polygons must be reversed clipperPolygon.Reverse(); // Add the "subject" groupClipper.AddPath(clipperPolygon, ClipperLib.PolyType.ptSubject, poly.HasPointsInterface.ArePointsClosed()); } // Get a solution for this group ClipperLib.PolyTree solution = new ClipperLib.PolyTree(); groupClipper.Execute(ClipperLib.ClipType.ctUnion, solution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); // Combine the solutions into the full clipper fullClipper.AddPaths(ClipperLib.Clipper.ClosedPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, true); fullClipper.AddPaths(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, false); } progFunc(String.Format("Clipping '{0}' polygons: 100%", tmxLayer.Name)); ClipperLib.PolyTree fullSolution = new ClipperLib.PolyTree(); fullClipper.Execute(ClipperLib.ClipType.ctUnion, fullSolution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); return(fullSolution); }
public List <PointF[]> Triangulate(ClipperLib.PolyTree solution) { List <PointF[]> triangles = new List <PointF[]>(); var tess = new LibTessDotNet.Tess(); tess.NoEmptyPolygons = true; // Transformation function from ClipperLip Point to LibTess contour vertex Func <ClipperLib.IntPoint, LibTessDotNet.ContourVertex> xfToContourVertex = (p) => new LibTessDotNet.ContourVertex() { Position = new LibTessDotNet.Vec3 { X = p.X, Y = p.Y, Z = 0 } }; // Add a contour for each part of the solution tree ClipperLib.PolyNode node = solution.GetFirst(); while (node != null) { // Only interested in closed paths if (!node.IsOpen) { // Add a new countor. Holes are automatically generated. var vertices = node.Contour.Select(xfToContourVertex).ToArray(); tess.AddContour(vertices); } node = node.GetNext(); } // Do the tessellation tess.Tessellate(LibTessDotNet.WindingRule.EvenOdd, LibTessDotNet.ElementType.Polygons, 3); // Extract the triangles int numTriangles = tess.ElementCount; for (int i = 0; i < numTriangles; i++) { var v0 = tess.Vertices[tess.Elements[i * 3 + 0]].Position; var v1 = tess.Vertices[tess.Elements[i * 3 + 1]].Position; var v2 = tess.Vertices[tess.Elements[i * 3 + 2]].Position; List <PointF> triangle = new List <PointF>() { new PointF(v0.X, v0.Y), new PointF(v1.X, v1.Y), new PointF(v2.X, v2.Y), }; // Assre each triangle needs to be CCW float cross = Geometry.Math.Cross(triangle[0], triangle[1], triangle[2]); if (cross > 0) { triangle.Reverse(); } triangles.Add(triangle.ToArray()); } return(triangles); }
public static ClipperLib.PolyTree ExecuteClipper(TmxMap tmxMap, TmxLayer tmxLayer, TransformPointFunc xfFunc, ProgressFunc progFunc) { // The "fullClipper" combines the clipper results from the smaller pieces ClipperLib.Clipper fullClipper = new ClipperLib.Clipper(); // Limit to polygon "type" that matches the collision layer name (unless we are overriding the whole layer to a specific Unity Layer Name) bool usingUnityLayerOverride = !String.IsNullOrEmpty(tmxLayer.UnityLayerOverrideName); // From the perspective of Clipper lines are polygons too // Closed paths == polygons // Open paths == lines Dictionary <TupleInt2, List <PolygonGroup> > polygonGroups = new Dictionary <TupleInt2, List <PolygonGroup> >(); foreach (int y in Enumerable.Range(0, tmxLayer.Height)) { foreach (int x in Enumerable.Range(0, tmxLayer.Width)) { uint rawTileId = tmxLayer.GetRawTileIdAt(x, y); if (rawTileId == 0) { continue; } uint tileId = TmxMath.GetTileIdWithoutFlags(rawTileId); TmxTile tile = tmxMap.Tiles[tileId]; foreach (TmxObject polygon in tile.ObjectGroup.Objects) { if (typeof(TmxHasPoints).IsAssignableFrom(polygon.GetType()) && (usingUnityLayerOverride || String.Compare(polygon.Type, tmxLayer.Name, true) == 0)) { int groupX = x / LayerClipper.GroupBySize; int groupY = y / LayerClipper.GroupBySize; PolygonGroup poly = new PolygonGroup(); poly.PositionOnMap = tmxMap.GetMapPositionAt(x, y, tile); poly.HasPointsInterface = polygon as TmxHasPoints; poly.TmxObjectInterface = polygon; poly.IsFlippedDiagnoally = TmxMath.IsTileFlippedDiagonally(rawTileId); poly.IsFlippedHorizontally = TmxMath.IsTileFlippedHorizontally(rawTileId); poly.IsFlippedVertically = TmxMath.IsTileFlippedVertically(rawTileId); poly.TileCenter = new PointF(tile.TileSize.Width * 0.5f, tile.TileSize.Height * 0.5f); TupleInt2 key = new TupleInt2(groupX, groupY); if (!polygonGroups.ContainsKey(key)) { polygonGroups[key] = new List <PolygonGroup>(); } polygonGroups[key].Add(poly); } } } } // Tuple not supported in Mono 2.0 so doing this the old fashioned way, sorry //var polygonGroups = from y in Enumerable.Range(0, tmxLayer.Height) // from x in Enumerable.Range(0, tmxLayer.Width) // let rawTileId = tmxLayer.GetRawTileIdAt(x, y) // where rawTileId != 0 // let tileId = TmxMath.GetTileIdWithoutFlags(rawTileId) // let tile = tmxMap.Tiles[tileId] // from polygon in tile.ObjectGroup.Objects // where (polygon as TmxHasPoints) != null // where usingUnityLayerOverride || String.Compare(polygon.Type, tmxLayer.Name, true) == 0 // let groupX = x / LayerClipper.GroupBySize // let groupY = y / LayerClipper.GroupBySize // group new // { // PositionOnMap = tmxMap.GetMapPositionAt(x, y, tile), // HasPointsInterface = polygon as TmxHasPoints, // TmxObjectInterface = polygon, // IsFlippedDiagnoally = TmxMath.IsTileFlippedDiagonally(rawTileId), // IsFlippedHorizontally = TmxMath.IsTileFlippedHorizontally(rawTileId), // IsFlippedVertically = TmxMath.IsTileFlippedVertically(rawTileId), // TileCenter = new PointF(tile.TileSize.Width * 0.5f, tile.TileSize.Height * 0.5f), // } // by Tuple.Create(groupX, groupY); int groupIndex = 0; int groupCount = polygonGroups.Count(); foreach (TupleInt2 key in polygonGroups.Keys) { if (groupIndex % 5 == 0) { progFunc(String.Format("Clipping '{0}' polygons: {1}%", tmxLayer.Name, (groupIndex / (float)groupCount) * 100)); } groupIndex++; // The "groupClipper" clips the polygons in a smaller part of the world ClipperLib.Clipper groupClipper = new ClipperLib.Clipper(); // Add all our polygons to the Clipper library so it can reduce all the polygons to a (hopefully small) number of paths foreach (PolygonGroup poly in polygonGroups[key]) { // Create a clipper library polygon out of each and add it to our collection ClipperPolygon clipperPolygon = new ClipperPolygon(); // Our points may be transformed due to tile flipping/rotation // Before we transform them we put all the points into local space relative to the tile SizeF offset = new SizeF(poly.TmxObjectInterface.Position); PointF[] transformedPoints = poly.HasPointsInterface.Points.Select(pt => PointF.Add(pt, offset)).ToArray(); // Now transform the points relative to the tile TmxMath.TransformPoints(transformedPoints, poly.TileCenter, poly.IsFlippedDiagnoally, poly.IsFlippedHorizontally, poly.IsFlippedVertically); foreach (var pt in transformedPoints) { float x = poly.PositionOnMap.X + pt.X; float y = poly.PositionOnMap.Y + pt.Y; ClipperLib.IntPoint point = xfFunc(x, y); clipperPolygon.Add(point); } // Because of Unity's cooridnate system, the winding order of the polygons must be reversed clipperPolygon.Reverse(); // Add the "subject" groupClipper.AddPath(clipperPolygon, ClipperLib.PolyType.ptSubject, poly.HasPointsInterface.ArePointsClosed()); } // Get a solution for this group ClipperLib.PolyTree solution = new ClipperLib.PolyTree(); groupClipper.Execute(ClipperLib.ClipType.ctUnion, solution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); // Combine the solutions into the full clipper fullClipper.AddPaths(ClipperLib.Clipper.ClosedPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, true); fullClipper.AddPaths(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, false); } progFunc(String.Format("Clipping '{0}' polygons: 100%", tmxLayer.Name)); ClipperLib.PolyTree fullSolution = new ClipperLib.PolyTree(); fullClipper.Execute(ClipperLib.ClipType.ctUnion, fullSolution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); return(fullSolution); }
public void MakeConvextSetFromClipperSolution(ClipperLib.PolyTree solution) { var triangles = GetTriangleListFromClipperSolution(solution); MakeConvexSetFromTriangles(triangles); }
private void FixAndSetShapes(IShape[] outlines, IShape[] holes) { // if any outline doesn't overlap another shape then we don't have to bother with sending them through clipper // as sending then though clipper will turn them into generic polygons and loose thier shape specific optimisations int outlineLength = outlines.Length; int holesLength = holes?.Length ?? 0; bool[] overlappingOutlines = new bool[outlineLength]; bool[] overlappingHoles = new bool[holesLength]; bool anyOutlinesOverlapping = false; bool anyHolesOverlapping = false; for (int i = 0; i < outlineLength; i++) { for (int j = i + 1; j < outlineLength; j++) { // skip the bounds check if they are already tested if (overlappingOutlines[i] == false || overlappingOutlines[j] == false) { if (this.OverlappingBoundingBoxes(outlines[i], outlines[j])) { overlappingOutlines[i] = true; overlappingOutlines[j] = true; anyOutlinesOverlapping = true; } } } for (int k = 0; k < holesLength; k++) { if (overlappingOutlines[i] == false || overlappingHoles[k] == false) { if (this.OverlappingBoundingBoxes(outlines[i], holes[k])) { overlappingOutlines[i] = true; overlappingHoles[k] = true; anyOutlinesOverlapping = true; anyHolesOverlapping = true; } } } } if (anyOutlinesOverlapping) { var clipper = new ClipperLib.Clipper(); // add the outlines and the holes to clipper, scaling up from the float source to the int based system clipper uses this.AddPoints(clipper, outlines, overlappingOutlines, ClipperLib.PolyType.ptSubject); if (anyHolesOverlapping) { this.AddPoints(clipper, holes, overlappingHoles, ClipperLib.PolyType.ptClip); } var tree = new ClipperLib.PolyTree(); clipper.Execute(ClipperLib.ClipType.ctDifference, tree); List <IShape> newShapes = new List <IShape>(); // convert the 'tree' back to shapes this.ExtractOutlines(tree, newShapes); // add the origional outlines that where not overlapping for (int i = 0; i < outlineLength - 1; i++) { if (!overlappingOutlines[i]) { newShapes.Add(outlines[i]); } } this.shapes = newShapes.ToArray(); } else { this.shapes = outlines; } var paths = new List <IPath>(); foreach (var o in this.shapes) { paths.AddRange(o); } this.paths = paths; }
private XElement CreateCollisionElementForLayer(TmxLayer layer) { // Collision elements look like this // (Can also have EdgeCollider2Ds) // <GameOject name="Collision"> // <PolygonCollider2D> // <Path>list of points</Path> // <Path>another list of points</Path> // </PolygonCollider2D> // </GameOject> LayerClipper.TransformPointFunc xfFunc = delegate(float x, float y) { // Transform point to Unity space PointF pointUnity3d = PointFToUnityVector_NoScale(new PointF(x, y)); ClipperLib.IntPoint point = new ClipperLib.IntPoint(pointUnity3d.X, pointUnity3d.Y); return(point); }; LayerClipper.ProgressFunc progFunc = delegate(string prog) { Logger.WriteLine(prog); }; ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); var paths = ClipperLib.Clipper.ClosedPathsFromPolyTree(solution); if (paths.Count >= MaxNumberOfSafePaths) { StringBuilder warning = new StringBuilder(); warning.AppendFormat("Layer '{0}' has a large number of polygon paths ({1}).", layer.Name, paths.Count); warning.AppendLine(" Importing this layer may be slow in Unity. (Can take an hour or more for +1000 paths.)"); warning.AppendLine(" Check polygon/rectangle objects in Tile Collision Editor in Tiled and use 'Snap to Grid' or 'Snap to Fine Grid'."); warning.AppendLine(" You want colliders to be set up so they can be merged with colliders on neighboring tiles, reducing path count considerably."); warning.AppendLine(" In some cases the size of the map may need to be reduced."); Logger.WriteWarning(warning.ToString()); } // Add our polygon and edge colliders List <XElement> polyColliderElements = new List <XElement>(); if (layer.IsExportingConvexPolygons()) { AddPolygonCollider2DElements_Convex(solution, polyColliderElements); } else { AddPolygonCollider2DElements_Complex(solution, polyColliderElements); } AddEdgeCollider2DElements(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), polyColliderElements); if (polyColliderElements.Count() == 0) { // No collisions on this layer return(null); } XElement gameObjectCollision = new XElement("GameObject", new XAttribute("name", "Collision"), polyColliderElements); // Collision layer may have a name and "unity physics layer" to go with it // (But not if we're using unity:layer override) if (String.IsNullOrEmpty(layer.UnityLayerOverrideName) && !String.IsNullOrEmpty(layer.Name)) { gameObjectCollision.SetAttributeValue("name", "Collision_" + layer.Name); gameObjectCollision.SetAttributeValue("layer", layer.Name); } return(gameObjectCollision); }