public static ClipperLib.IntPoint[] ScaleUpPaths(SvgPoint[] points, double scale = 1) { var result = new ClipperLib.IntPoint[points.Length]; Parallel.For(0, points.Length, i => result[i] = new ClipperLib.IntPoint((long)Math.Round((decimal)points[i].x * (decimal)scale), (long)Math.Round((decimal)points[i].y * (decimal)scale))); return(result.ToArray()); } // 2 secs
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); }
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>(); AddPolygonCollider2DElements(ClipperLib.Clipper.ClosedPathsFromPolyTree(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; }
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 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); }
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); 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."); Program.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; }
public static Vector2 clipperToUnity(ClipperLib.IntPoint pt) { return(new Vector2(pt.X / (float)scalar, pt.Y / (float)scalar)); }
public const long scalar = 10000000; //Value by which to scale integers to represent their floating point counterparts, aiming to preserve 7 decimal points of precision (Unity's float precision) public static Poly2Tri.Point2D clipperToPoly2tri(ClipperLib.IntPoint pt) { return(new Poly2Tri.Point2D(pt.X / (double)scalar, pt.Y / (double)scalar)); }
public static LibTessDotNet.Vec3 ToVectorTess(this ClipperLib.IntPoint point) { return(new LibTessDotNet.Vec3 { X = point.X, Y = point.Y, Z = 0 }); }
public static Vector2 ToVector2D(this ClipperLib.IntPoint point) { return(new Vector2(point.X / INT_SCALE, point.Y / INT_SCALE)); }
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); }