Esempio n. 1
1
        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;
        }
 private bool AreNormalsEquivalent(ClipperLib.DoublePoint n0, ClipperLib.DoublePoint n1)
 {
     const double epsilon = 1.0f / 1024.0f;
     double ax = Math.Abs(n0.X - n1.X);
     double ay = Math.Abs(n0.Y - n1.Y);
     return (ax < epsilon) && (ay < epsilon);
 }
Esempio n. 3
0
        public void AddSlit(Point3d from, Point3d to, double thickness, double deeper)
        {
            var cut = new LineCurve(from, to);

            var points = Curves.SelectMany(o => SlitPoints(o, cut)).OrderBy(o => o.T).ToList();

            var yUnit = cut.Line.UnitTangent;
            var xUnit = Vector3d.CrossProduct(yUnit, Plane.Normal);

            for (int i = 1; i < points.Count; i += 1)
            {
                var slitA = points[i - 1];
                var slitB = points[i++];

                var a = slitA.Point;
                var b = slitB.Point;

                var mid = a + (b - a) / 2 + deeper * yUnit;
                var end = a - (b - a) / 2;

                var p00 = mid - thickness / 2 * xUnit;
                var p10 = p00 + thickness * xUnit;
                var p01 = end - thickness / 2 * xUnit;
                var p11 = p01 + thickness * xUnit;

                var slit = new[] { p00, p10, p11, p01, p00 }.ToPolygon(Plane, Unit);

                Slits.Add(slit);
            }
        }
        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 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);
        }
Esempio n. 6
0
        public void Clip(double x0, double x1, double y0, double y1)
        {
            var p00 = new Point3d(x0, y0, 0.0);
            var p01 = new Point3d(x0, y1, 0.0);
            var p11 = new Point3d(x1, y1, 0.0);
            var p10 = new Point3d(x1, y0, 0.0);

            var clip = new[] { p00, p10, p11, p01, p00 }.ToPolygon(Plane.WorldXY, Unit);

            var clipper = new Clipper();

            clipper.AddPaths(Polygons, PolyType.ptSubject, true);
            clipper.AddPath(clip, PolyType.ptClip, true);

            var solution = new List<List<IntPoint>>();

            clipper.Execute(ClipType.ctIntersection, solution, PolyFillType.pftEvenOdd, PolyFillType.pftNonZero);

            Polygons = solution;
            Curves = Polygons.ToCurves(Plane, Unit);
        }
Esempio n. 7
0
		private static void ClipperPolyTreeToPolygonListRecursively(ClipperLib.PolyNode node, HashSet<ClipperLib.IntPoint> sharpPoints, HashSet<ClipperLib.IntPoint> allPoints, List<PolygonClosedD2D> polygonList, IDictionary<ClipperLib.PolyNode, PolygonClosedD2D> dictClipperNodeToNode)
		{
			if (node.Contour != null && node.Contour.Count != 0)
			{
				var pointsInThisPolygon = node.Contour.Select(clipperPt => new PointD2D(clipperPt.X / 65536.0, clipperPt.Y / 65536.0));
				var sharpPointsInThisPolygon = node.Contour.Where(clipperPt => sharpPoints.Contains(clipperPt)).Select(clipperPt => new PointD2D(clipperPt.X / 65536.0, clipperPt.Y / 65536.0));

				var polygon = new PolygonClosedD2D(pointsInThisPolygon.ToArray(), new HashSet<PointD2D>(sharpPointsInThisPolygon));
				polygon.IsHole = node.IsHole;
				polygonList.Add(polygon);

				if (node.IsHole)
				{
					polygon.Parent = dictClipperNodeToNode[node.Parent];
				}

				dictClipperNodeToNode.Add(node, polygon);
			}

			if (0 != node.ChildCount)
			{
				foreach (var childNode in node.Childs)
				{
					ClipperPolyTreeToPolygonListRecursively(childNode, sharpPoints, allPoints, polygonList, dictClipperNodeToNode);
				}
			}
		}
        public static List<List<Vector2>> GenerateClipperPathPoints(TileLayer tileLayer,
			bool simpleTileObjectCalculation = true,
			double clipperArcTolerance = 0.25, double clipperMiterLimit = 2.0,
			ClipperLib.JoinType clipperJoinType = ClipperLib.JoinType.jtRound,
			ClipperLib.EndType clipperEndType = ClipperLib.EndType.etClosedPolygon,
			float clipperDeltaOffset = 0)
        {
            ClipperLib.Clipper clipper = new ClipperLib.Clipper();
            List<List<ClipperLib.IntPoint>> pathsList = new List<List<ClipperLib.IntPoint>>();
            List<List<ClipperLib.IntPoint>> solution = new List<List<ClipperLib.IntPoint>>();
            List<List<Vector2>> points = new List<List<Vector2>>();

            for (int x = 0; x < tileLayer.Tiles.Width; x++)
            {
                for (int y = 0; y < tileLayer.Tiles.Height; y++)
                {
                    Tile t = tileLayer.Tiles[x, y];
                    if (t == null || t.TileSet == null || t.TileSet.TilesObjects == null)
                        continue;
                    if (t.TileSet.TilesObjects.ContainsKey(t.OriginalID))
                    {
                        List<TileObject> tileObjs = t.TileSet.TilesObjects[t.OriginalID];
                        foreach (var tileObj in tileObjs)
                        {
                            pathsList.Add(tileObj.GetPath(x, y, t.SpriteEffects, tileLayer.BaseMap.MapRenderParameter.TileWidth, tileLayer.BaseMap.MapRenderParameter.TileHeight));
                        }
                    }
                }
            }
            // Add the paths to be merged to ClipperLib
            clipper.AddPaths(pathsList, ClipperLib.PolyType.ptSubject, true);
            // Merge it!
            //clipper.PreserveCollinear = false;
            //clipper.ReverseSolution = true;
            clipper.StrictlySimple = simpleTileObjectCalculation;
            if (!clipper.Execute(ClipperLib.ClipType.ctUnion, solution))
                return points;
            clipper.Execute(ClipperLib.ClipType.ctUnion, solution);
            // Now solution should contain all vertices of the collision object, but they are still multiplied by TileObject.ClipperScale!

            #region Implementation of increase and decrease offset polygon.
            if (simpleTileObjectCalculation == false)
            {
                // Link of the example of ClipperLib:
                // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/_Body.htm

                ClipperLib.ClipperOffset co = new ClipperLib.ClipperOffset(clipperMiterLimit, clipperArcTolerance);
                foreach (List<ClipperLib.IntPoint> item in solution)
                {
                    co.AddPath(item, clipperJoinType, clipperEndType);
                }
                solution.Clear();
                co.Execute(ref solution, clipperDeltaOffset * TileObject.ClipperScale);
            }
            #endregion

            for (int i = 0; i < solution.Count; i++)
            {
                if (solution[i].Count < 1)
                    continue;
                points.Add(new List<Vector2>());
                for (int j = 0; j < solution[i].Count; j++)
                {
                    points[i].Add(
                        new Vector2(
                            solution[i][j].X / (float)TileObject.ClipperScale,
                            solution[i][j].Y / (float)TileObject.ClipperScale
                        )
                    );
                }
            }

            return points;
        }
        /// <summary>
        /// Generate 3D Colliders based on Tile Collisions
        /// </summary>
        /// <param name="isTrigger">True for Trigger Collider, false otherwise</param>
        /// <param name="generateClosedPolygon">True to generate a Polygon Collider. False will generate Edge Collider.</param>
        /// <param name="tag">Tag for the generated GameObjects</param>
        /// <param name="physicsLayer">Physics Layer for the generated GameObjects</param>
        /// <param name="physicsMaterial3D">Physics Material for 3D collider</param>
        /// <param name="zDepth">Z Depth of the collider.</param>
        /// <param name="colliderWidth">Width of the collider, in Units</param>
        /// <param name="innerCollision">If true, calculate normals facing the anchor of the collider (inside collisions), else, outside collisions.</param>
        /// <param name="simpleTileObjectCalculation">true to generate simplified tile collisions</param>
        /// <param name="clipperArcTolerance">Clipper arc angle tolerance</param>
        /// <param name="clipperMiterLimit">Clipper limit for Miter join type</param>
        /// <param name="clipperJoinType">Clipper join type</param>
        /// <param name="clipperEndType">Clipper Polygon end type</param>
        /// <param name="clipperDeltaOffset">Clipper delta offset</param>
        /// <returns></returns>
        public static GameObject[] GenerateTileCollisions3D(this Map map, bool isTrigger = false, bool generateClosedPolygon = true,
			string tag = "Untagged", int physicsLayer = 0, PhysicMaterial physicsMaterial3D = null,
			float zDepth = 0, float colliderWidth = 1, bool innerCollision = false,
			bool simpleTileObjectCalculation = true,
			double clipperArcTolerance = 0.25, double clipperMiterLimit = 2.0,
			ClipperLib.JoinType clipperJoinType = ClipperLib.JoinType.jtRound,
			ClipperLib.EndType clipperEndType = ClipperLib.EndType.etClosedPolygon,
			float clipperDeltaOffset = 0)
        {
            List<GameObject> tileCollisions = new List<GameObject>();
            // Iterate over each Tile Layer, grab all TileObjects inside this layer and use their Paths with ClipperLib to generate one polygon collider
            foreach (var layer in map.Layers)
            {
                if (layer is TileLayer)
                {
                    tileCollisions.AddRange(GenerateTileCollision3DFromLayer(map, layer as TileLayer, isTrigger, generateClosedPolygon, tag, physicsLayer, physicsMaterial3D, zDepth, colliderWidth, innerCollision, simpleTileObjectCalculation, clipperArcTolerance, clipperMiterLimit, clipperJoinType, clipperEndType, clipperDeltaOffset));
                }
            }

            return tileCollisions.ToArray();
        }
        /// <summary>
        /// Generate Colliders based on Tile Collisions
        /// </summary>
        /// <param name="used2DColider">True to generate a 2D collider, false to generate a 3D collider.</param>
        /// <param name="isTrigger">True for Trigger Collider, false otherwise</param>
        /// <param name="generateClosedPolygon">True to generate a Polygon Collider. False will generate Edge Collider.</param>
        /// <param name="tag">Tag for the generated GameObjects</param>
        /// <param name="physicsLayer">Physics Layer for the generated GameObjects</param>
        /// <param name="physicsMaterial3D">Physics Material for 3D collider</param>
        /// <param name="physicsMaterial2D">Physics Material for 2D collider</param>
        /// <param name="zDepth">Z Depth of the collider.</param>
        /// <param name="colliderWidth">Width of the collider, in Units</param>
        /// <param name="innerCollision">If true, calculate normals facing the anchor of the collider (inside collisions), else, outside collisions.</param>
        /// <param name="simpleTileObjectCalculation">true to generate simplified tile collisions</param>
        /// <param name="clipperArcTolerance">Clipper arc angle tolerance</param>
        /// <param name="clipperMiterLimit">Clipper limit for Miter join type</param>
        /// <param name="clipperJoinType">Clipper join type</param>
        /// <param name="clipperEndType">Clipper Polygon end type</param>
        /// <param name="clipperDeltaOffset">Clipper delta offset</param>
        /// <returns>A GameObject containing all generated mapObjects</returns>
        public static GameObject[] GenerateTileCollisions(this Map map, bool used2DColider = true, bool isTrigger = false, bool generateClosedPolygon = true,
			string tag = "Untagged", int physicsLayer = 0, PhysicMaterial physicsMaterial3D = null, PhysicsMaterial2D physicsMaterial2D = null,
			float zDepth = 0, float colliderWidth = 1, bool innerCollision = false,
			bool simpleTileObjectCalculation = true,
			double clipperArcTolerance = 0.25, double clipperMiterLimit = 2.0,
			ClipperLib.JoinType clipperJoinType = ClipperLib.JoinType.jtRound,
			ClipperLib.EndType clipperEndType = ClipperLib.EndType.etClosedPolygon,
			float clipperDeltaOffset = 0)
        {
            if (used2DColider)
                return GenerateTileCollisions2D(map, isTrigger, generateClosedPolygon, tag, physicsLayer, physicsMaterial2D, zDepth, simpleTileObjectCalculation, clipperArcTolerance, clipperMiterLimit, clipperJoinType, clipperEndType, clipperDeltaOffset);
            else
                return GenerateTileCollisions3D(map, isTrigger, generateClosedPolygon, tag, physicsLayer, physicsMaterial3D, zDepth, colliderWidth, innerCollision, simpleTileObjectCalculation, clipperArcTolerance, clipperMiterLimit, clipperJoinType, clipperEndType, clipperDeltaOffset);
        }
        public static GameObject[] GenerateTileCollision3DFromLayer(
			this Map map, string layer,
			bool isTrigger = false, bool generateClosedPolygon = true, string tag = "Untagged",
			int physicsLayer = 0, PhysicMaterial physicsMaterial = null, float zDepth = 1,
			float colliderWidth = 1, bool innerCollision = false,
			bool simpleTileObjectCalculation = true,
			double clipperArcTolerance = 0.25, double clipperMiterLimit = 2.0,
			ClipperLib.JoinType clipperJoinType = ClipperLib.JoinType.jtRound,
			ClipperLib.EndType clipperEndType = ClipperLib.EndType.etClosedPolygon,
			float clipperDeltaOffset = 0)
        {
            return GenerateTileCollision3DFromLayer(map, map.GetTileLayer(layer), isTrigger, generateClosedPolygon, tag, physicsLayer, physicsMaterial, zDepth, colliderWidth, innerCollision, simpleTileObjectCalculation, clipperArcTolerance, clipperMiterLimit, clipperJoinType, clipperEndType, clipperDeltaOffset);
        }
        public static GameObject[] GenerateTileCollision2DFromLayer(
			this Map map, TileLayer layer,
			bool isTrigger = false, bool generateClosedPolygon = true, string tag = "Untagged",
			int physicsLayer = 0, PhysicsMaterial2D physicsMaterial = null, float zDepth = 1,
			bool simpleTileObjectCalculation = true,
			double clipperArcTolerance = 0.25, double clipperMiterLimit = 2.0,
			ClipperLib.JoinType clipperJoinType = ClipperLib.JoinType.jtRound,
			ClipperLib.EndType clipperEndType = ClipperLib.EndType.etClosedPolygon,
			float clipperDeltaOffset = 0)
        {
            if (layer == null)
                return null;

            if (layer.LayerTileCollisions == null)
            {
                layer.LayerTileCollisions = new GameObject(layer.Name + " Tile Collisions");
                Transform t = layer.LayerTileCollisions.transform;
                if (layer.BaseMap != null)
                {
                    t.parent = layer.BaseMap.MapGameObject.transform;
                }
                t.localPosition = Vector3.zero;
                t.localRotation = Quaternion.identity;
                t.localScale = Vector3.one;
                layer.LayerTileCollisions.isStatic = true;
            }
            layer.LayerTileCollisions.tag = tag;
            layer.LayerTileCollisions.layer = physicsLayer;

            List<GameObject> newSubCollider = new List<GameObject>();

            List<List<Vector2>> points = GenerateClipperPathPoints(layer, simpleTileObjectCalculation, clipperArcTolerance, clipperMiterLimit, clipperJoinType, clipperEndType, clipperDeltaOffset);

            for (int i = 0; i < points.Count; i++)
            {
                newSubCollider.Add(new GameObject("Tile Collisions " + layer.Name + "_" + i));
                newSubCollider[i].transform.parent = layer.LayerTileCollisions.transform;
                newSubCollider[i].transform.localPosition = new Vector3(0, 0, zDepth);
                newSubCollider[i].transform.localScale = Vector3.one;
                newSubCollider[i].transform.localRotation = Quaternion.identity;
                newSubCollider[i].tag = tag;
                newSubCollider[i].layer = physicsLayer;

                // Add the last point equals to the first to close the collider area
                // it's necessary only if the first point is diffent from the first one
                if (points[i][0].x != points[i][points[i].Count - 1].x || points[i][0].y != points[i][points[i].Count - 1].y)
                {
                    points[i].Add(points[i][0]);
                }

                Vector2[] pointsVec = points[i].ToArray();

                for (int j = 0; j < pointsVec.Length; j++)
                {
                    pointsVec[j] = map.TiledPositionToWorldPoint(pointsVec[j]);
                }

                if (generateClosedPolygon)
                {
                    PolygonCollider2D polyCollider = newSubCollider[i].AddComponent<PolygonCollider2D>();
                    polyCollider.isTrigger = isTrigger;
                    polyCollider.points = pointsVec;

                    if (physicsMaterial != null)
                        polyCollider.sharedMaterial = physicsMaterial;
                }
                else
                {
                    EdgeCollider2D edgeCollider = newSubCollider[i].AddComponent<EdgeCollider2D>();
                    edgeCollider.isTrigger = isTrigger;
                    edgeCollider.points = pointsVec;

                    if (physicsMaterial != null)
                        edgeCollider.sharedMaterial = physicsMaterial;
                }
            }
            return newSubCollider.ToArray();
        }
Esempio n. 13
0
        // 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();
            }
        }
Esempio n. 14
0
 // 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();
     }
 }
Esempio n. 15
0
 public void MakeConvextSetFromClipperSolution(ClipperLib.PolyTree solution)
 {
     var triangles = GetTriangleListFromClipperSolution(solution);
     MakeConvexSetFromTriangles(triangles);
 }
Esempio n. 16
0
        /// <summary>
        /// Create a Tiled Map using TextAsset as parameter
        /// </summary>
        /// <param name="mapText">Map's TextAsset</param>
        /// <param name="mapPath">Path to XML folder, so we can read relative paths for tilesets</param>
        /// <param name="parent">This map's gameobject parent</param>
        /// <param name="baseTileMaterial">Base material to be used for the Tiles</param>
        /// <param name="sortingOrder">Base sorting order for the tile layers</param>
        /// <param name="mapPath">Path to XML folder, so we can read relative paths for tilesets</param>
        /// <param name="makeUnique">array with bools to make unique tiles for each tile layer</param>
        /// <param name="onMapFinishedLoading">Callback for when map finishes loading</param>
        /// <param name="simpleTileObjectCalculation">true to generate simplified tile collisions</param>
        /// <param name="tileObjectEllipsePrecision">Tile collisions ellipsoide approximation precision</param>
        /// <param name="clipperArcTolerance">Clipper arc angle tolerance</param>
        /// <param name="clipperDeltaOffset">Clipper delta offset</param>
        /// <param name="clipperEndType">Clipper Polygon end type</param>
        /// <param name="clipperJoinType">Clipper join type</param>
        /// <param name="clipperMiterLimit">Clipper limit for Miter join type</param>
        public Map(TextAsset mapText, string mapPath, GameObject parent,
					Material baseTileMaterial, int sortingOrder, bool makeUnique, 
					Action<Map> onMapFinishedLoading = null,
					int tileObjectEllipsePrecision = 16, bool simpleTileObjectCalculation = true,
					double clipperArcTolerance = 0.25, double clipperMiterLimit = 2.0,
					ClipperLib.JoinType clipperJoinType = ClipperLib.JoinType.jtRound,
					ClipperLib.EndType clipperEndType = ClipperLib.EndType.etClosedPolygon,
					float clipperDeltaOffset = 0)
        {
            _tileObjectEllipsePrecision = tileObjectEllipsePrecision;
            _simpleTileObjectCalculation = simpleTileObjectCalculation;
            _clipperArcTolerance = clipperArcTolerance;
            _clipperDeltaOffset = clipperDeltaOffset;
            _clipperEndType = clipperEndType;
            _clipperJoinType = clipperJoinType;
            _clipperMiterLimit = clipperMiterLimit;

            NanoXMLDocument document = new NanoXMLDocument(mapText.text);

            _mapName = mapText.name;

            Parent = parent;

            DefaultSortingOrder = sortingOrder;
            GlobalMakeUniqueTiles = makeUnique;
            BaseTileMaterial = baseTileMaterial;
            _mapPath = mapPath;

            OnMapFinishedLoading = onMapFinishedLoading;

            Initialize(document);
        }
Esempio n. 17
0
        /// <summary>
        /// Create a Tiled Map loading the XML from a StreamingAssetPath or a HTTP path (in the pc or web)
        /// </summary>
        /// <param name="wwwPath">Map's path with http for web files or without streaming assets path for local files</param>
        /// <param name="parent">This map's gameobject parent</param>
        /// <param name="baseTileMaterial">Base material to be used for the Tiles</param>
        /// <param name="sortingOrder">Base sorting order for the tile layers</param>
        /// <param name="makeUnique">Make unique tiles for all tile layers.</param>
        /// <param name="onMapFinishedLoading">Callback for when map finishes loading</param>
        /// <param name="simpleTileObjectCalculation">true to generate simplified tile collisions</param>
        /// <param name="tileObjectEllipsePrecision">Tile collisions ellipsoide approximation precision</param>
        /// <param name="clipperArcTolerance">Clipper arc angle tolerance</param>
        /// <param name="clipperDeltaOffset">Clipper delta offset</param>
        /// <param name="clipperEndType">Clipper Polygon end type</param>
        /// <param name="clipperJoinType">Clipper join type</param>
        /// <param name="clipperMiterLimit">Clipper limit for Miter join type</param>
        public Map(string wwwPath, GameObject parent,
					Material baseTileMaterial, int sortingOrder, bool makeUnique, 
					Action<Map> onMapFinishedLoading = null,
					int tileObjectEllipsePrecision = 16, bool simpleTileObjectCalculation = true,
					double clipperArcTolerance = 0.25, double clipperMiterLimit = 2.0,
					ClipperLib.JoinType clipperJoinType = ClipperLib.JoinType.jtRound,
					ClipperLib.EndType clipperEndType = ClipperLib.EndType.etClosedPolygon,
					float clipperDeltaOffset = 0)
        {
            _tileObjectEllipsePrecision = tileObjectEllipsePrecision;
            _simpleTileObjectCalculation = simpleTileObjectCalculation;
            _clipperArcTolerance = clipperArcTolerance;
            _clipperDeltaOffset = clipperDeltaOffset;
            _clipperEndType = clipperEndType;
            _clipperJoinType = clipperJoinType;
            _clipperMiterLimit = clipperMiterLimit;
            _mapName = Path.GetFileNameWithoutExtension(wwwPath);
            _mapExtension = Path.GetExtension(wwwPath);
            if (string.IsNullOrEmpty(_mapExtension))
                _mapExtension = ".tmx";

            Parent = parent;

            DefaultSortingOrder = sortingOrder;
            GlobalMakeUniqueTiles = makeUnique;
            BaseTileMaterial = baseTileMaterial;
            if (!wwwPath.Contains("://"))
                _mapPath = string.Concat(Application.streamingAssetsPath, Path.AltDirectorySeparatorChar);
            else
            {
                // remove _mapName from wwwPath
                _mapPath = wwwPath.Replace(string.Concat(_mapName, _mapExtension), "");
            }

            OnMapFinishedLoading = onMapFinishedLoading;

            new Task(LoadFromPath(wwwPath), true);
        }
        public static GameObject[] GenerateTileCollision3DFromLayer(
			this Map map, TileLayer layer,
			bool isTrigger = false, bool generateClosedPolygon = true, string tag = "Untagged", 
			int physicsLayer = 0, PhysicMaterial physicsMaterial = null, float zDepth = 1, 
			float colliderWidth = 1, bool innerCollision = false,
			bool simpleTileObjectCalculation = true,
			double clipperArcTolerance = 0.25, double clipperMiterLimit = 2.0,
			ClipperLib.JoinType clipperJoinType = ClipperLib.JoinType.jtRound,
			ClipperLib.EndType clipperEndType = ClipperLib.EndType.etClosedPolygon,
			float clipperDeltaOffset = 0)
        {
            if (layer == null)
                return null;

            if (layer.LayerTileCollisions == null)
            {
                layer.LayerTileCollisions = new GameObject(layer.Name + " Tile Collisions");
                Transform t = layer.LayerTileCollisions.transform;
                if (layer.BaseMap != null)
                {
                    t.parent = layer.BaseMap.MapGameObject.transform;
                }
                t.localPosition = Vector3.zero;
                t.localRotation = Quaternion.identity;
                t.localScale = Vector3.one;
                layer.LayerTileCollisions.isStatic = true;
            }
            layer.LayerTileCollisions.tag = tag;
            layer.LayerTileCollisions.layer = physicsLayer;
            layer.LayerTileCollisions.transform.localScale = Vector3.one;

            List<GameObject> newSubCollider = new List<GameObject>();

            List<Vector3> vertices = new List<Vector3>();
            List<int> triangles = new List<int>();
            List<List<Vector2>> points = GenerateClipperPathPoints(layer, simpleTileObjectCalculation, clipperArcTolerance, clipperMiterLimit, clipperJoinType, clipperEndType, clipperDeltaOffset);

            for (int i = 0; i < points.Count; i++)
            {
                newSubCollider.Add(new GameObject("Tile Collisions " + layer.Name + "_" + i));
                newSubCollider[i].transform.parent = layer.LayerTileCollisions.transform;
                newSubCollider[i].transform.localPosition = new Vector3(0, 0, zDepth);
                newSubCollider[i].transform.localScale = Vector3.one;
                newSubCollider[i].transform.localRotation = Quaternion.identity;
                newSubCollider[i].tag = tag;
                newSubCollider[i].layer = physicsLayer;

                vertices.Clear();
                triangles.Clear();
                Mesh colliderMesh = new Mesh();
                colliderMesh.name = "TileCollider_" + layer.Name + "_" + i;
                MeshCollider mc = newSubCollider[i].AddComponent<MeshCollider>();

                mc.isTrigger = isTrigger;

                GenerateVerticesAndTris(map, points[i], vertices, triangles, zDepth, colliderWidth, innerCollision, true, true);

                // Connect last point with first point (create the face between them)
                triangles.Add(vertices.Count - 1);
                triangles.Add(1);
                triangles.Add(0);

                triangles.Add(0);
                triangles.Add(vertices.Count - 2);
                triangles.Add(vertices.Count - 1);

                if (generateClosedPolygon)
                    FillFaces(points[i], triangles);

                colliderMesh.vertices = vertices.ToArray();
                colliderMesh.uv = new Vector2[colliderMesh.vertices.Length];
                //colliderMesh.uv1 = colliderMesh.uv;
                colliderMesh.uv2 = colliderMesh.uv;
                colliderMesh.triangles = triangles.ToArray();
                colliderMesh.RecalculateNormals();

                mc.sharedMesh = colliderMesh;

                if (physicsMaterial != null)
                    mc.sharedMaterial = physicsMaterial;

                newSubCollider[i].isStatic = true;
            }

            return newSubCollider.ToArray();
        }