private double ClippedArea() { if (this.Voids == null || this.Voids.Count == 0) { return(this.Perimeter.Area()); } var clipper = new ClipperLib.Clipper(); var normal = Perimeter.Normal(); if (normal.IsAlmostEqualTo(Vector3.ZAxis)) { clipper.AddPath(Perimeter.ToClipperPath(), ClipperLib.PolyType.ptSubject, true); clipper.AddPaths(this.Voids.Select(p => p.ToClipperPath()).ToList(), ClipperLib.PolyType.ptClip, true); } else { var transform = new Transform(Perimeter.Start, normal); transform.Invert(); var perimeter = Perimeter.TransformedPolygon(transform); clipper.AddPath(perimeter.ToClipperPath(), ClipperLib.PolyType.ptSubject, true); clipper.AddPaths(this.Voids.Select(p => p.TransformedPolygon(transform).ToClipperPath()).ToList(), ClipperLib.PolyType.ptClip, true); } var solution = new List <List <ClipperLib.IntPoint> >(); clipper.Execute(ClipperLib.ClipType.ctDifference, solution, ClipperLib.PolyFillType.pftEvenOdd); return(solution.Sum(s => ClipperLib.Clipper.Area(s)) / Math.Pow(1.0 / Vector3.EPSILON, 2)); }
public static Vector3[] MeshDifference(Vector3[] mesh1, Vector3[] mesh2) { List<IntPoint> subj = MeshToPath3D(mesh1); List<IntPoint> clip = MeshToPath3D(mesh2); List<List<IntPoint>> solution = new List<List<IntPoint>>(); Clipper c = new Clipper(); c.AddPath(subj, PolyType.ptSubject, true); c.AddPath(clip, PolyType.ptClip, true); c.Execute(ClipType.ctDifference, solution, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); return PathToMesh3D(solution[0]); }
/// <summary> /// Conduct a clip operation on this profile. /// </summary> internal void Clip(IEnumerable <Profile> additionalHoles = null, double tolerance = Vector3.EPSILON) { var clipper = new ClipperLib.Clipper(); clipper.AddPath(this.Perimeter.ToClipperPath(tolerance), ClipperLib.PolyType.ptSubject, true); if (this.Voids != null) { clipper.AddPaths(this.Voids.Select(p => p.ToClipperPath(tolerance)).ToList(), ClipperLib.PolyType.ptClip, true); } if (additionalHoles != null) { clipper.AddPaths(additionalHoles.Select(h => h.Perimeter.ToClipperPath(tolerance)).ToList(), ClipperLib.PolyType.ptClip, true); } var solution = new List <List <ClipperLib.IntPoint> >(); var result = clipper.Execute(ClipperLib.ClipType.ctDifference, solution, ClipperLib.PolyFillType.pftEvenOdd); // Completely disjoint polygons like a circular pipe // profile will result in an empty solution. if (solution.Count > 0) { var polys = solution.Select(s => s.ToPolygon(tolerance)).ToArray(); this.Perimeter = polys[0]; this.Voids = polys.Skip(1).ToArray(); } }
/// <summary> /// Perform a union operation, returning a new profile that is the union of the current profile with the other profile /// <param name="profile">The profile with which to create a union.</param> /// <param name="tolerance">An optional tolerance.</param> /// </summary> public Profile Union(Profile profile, double tolerance = Vector3.EPSILON) { var clipper = new ClipperLib.Clipper(); clipper.AddPath(this.Perimeter.ToClipperPath(tolerance), PolyType.ptSubject, true); clipper.AddPath(profile.Perimeter.ToClipperPath(tolerance), PolyType.ptClip, true); if (this.Voids != null && this.Voids.Count > 0) { clipper.AddPaths(this.Voids.Select(v => v.ToClipperPath(tolerance)).ToList(), PolyType.ptSubject, true); } if (profile.Voids != null && profile.Voids.Count > 0) { clipper.AddPaths(profile.Voids.Select(v => v.ToClipperPath(tolerance)).ToList(), PolyType.ptClip, true); } var solution = new List <List <ClipperLib.IntPoint> >(); clipper.Execute(ClipType.ctUnion, solution); return(new Profile(solution.Select(s => s.ToPolygon(tolerance)).ToList())); }
private double ClippedArea() { if (this.Voids == null || this.Voids.Count == 0) { return(this.Perimeter.Area()); } var clipper = new ClipperLib.Clipper(); clipper.AddPath(this.Perimeter.ToClipperPath(), ClipperLib.PolyType.ptSubject, true); clipper.AddPaths(this.Voids.Select(p => p.ToClipperPath()).ToList(), ClipperLib.PolyType.ptClip, true); var solution = new List <List <ClipperLib.IntPoint> >(); clipper.Execute(ClipperLib.ClipType.ctDifference, solution, ClipperLib.PolyFillType.pftEvenOdd); return(solution.Sum(s => ClipperLib.Clipper.Area(s)) / Math.Pow(1.0 / Vector3.EPSILON, 2)); }
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); }
//------------------------------------------------------------------------------ #endif //------------------------------------------------------------------------------ // SimplifyPolygon functions ... // Convert self-intersecting polygons into simple polygons //------------------------------------------------------------------------------ public static Paths SimplifyPolygon(Path poly, PolyFillType fillType = PolyFillType.pftEvenOdd) { Paths result = new Paths(); Clipper c = new Clipper(); c.StrictlySimple = true; c.AddPath(poly, PolyType.ptSubject, true); c.Execute(ClipType.ctUnion, result, fillType, fillType); return result; }
public static List<List<IntPoint>> ClipPolygon(List<List<IntPoint>> polygons, List<IntPoint> clipPath) { Clipper clipper = new Clipper(); clipper.AddPaths(polygons, PolyType.ptSubject, true); clipper.AddPath(clipPath, PolyType.ptClip, true); List<List<IntPoint>> solution = new List<List<IntPoint>>(); clipper.Execute(ClipType.ctIntersection, solution); return solution; }
public void Generate() { generationMessage = "Generating points..."; //Generate some points... List<Point> generationPoints = new List<Point>(); for(int i = 0; i < numberOfPoints; i++) { Point randomPoint = new Point(Random.Range(-levelRadius, levelRadius), Random.Range(-levelRadius, levelRadius)); randomPoint = new Point( (randomPoint.x * delaunayPointSnap) / delaunayPointSnap, (randomPoint.y * delaunayPointSnap) / delaunayPointSnap); if(!generationPoints.Contains(randomPoint)) { generationPoints.Add(randomPoint); } } //Clean up duplicates generationMessage = "Triangulating..."; //Generate a delaunay triangulation of the points Poly2Tri.PointSet ps = new Poly2Tri.PointSet(generationPoints.Select(d=>(Poly2Tri.TriangulationPoint)d).ToList()); Poly2Tri.P2T.Triangulate (ps); List<Poly2Tri.DelaunayTriangle> delaunayTriangles = ps.Triangles.ToList(); generationMessage = "Performing Brownian Walk through triangulation..."; //Brownian Walk through the triangles. Poly2TriDelaunayTriangulationWalker walker = new Poly2TriDelaunayTriangulationWalker (delaunayTriangles); List<Poly2Tri.DelaunayTriangle> levelTriangleSet = new List<Poly2Tri.DelaunayTriangle>(); for (int i = 0; i < numberOfStepsForBrownianWalk; i++) { Poly2Tri.DelaunayTriangle nextTriangle = walker.Next(); if(!levelTriangleSet.Contains(nextTriangle)) { levelTriangleSet.Add(nextTriangle); } } generationMessage = "Performing Tendril Walks..."; for (int i = 0; i < numberOfTendrilWalksToComplete; i++) { generationMessage = "Performing Tendril Walk: " + i; walker.SetCurrentTriangleIndex(delaunayTriangles.IndexOf(levelTriangleSet[Mathf.FloorToInt(Random.value * levelTriangleSet.Count)])); for (int j = 0; j < numberOfStepsForBrownianWalk; j++) { Poly2Tri.DelaunayTriangle nextTriangle = walker.Next(); if(!levelTriangleSet.Contains(nextTriangle)) { levelTriangleSet.Add(nextTriangle); } } } generationMessage = "Getting level boolean..."; //List<Poly2Tri.DelaunayTriangle> booleanFromLevelSet = delaunayTriangles.Where (x => !levelTriangleSet.Contains (x)).ToList (); /* Debugging delaunay triangulation foreach (Poly2Tri.DelaunayTriangle tri in booleanFromLevelSet) { Gizmos.DrawLine(new Vector2(tri.Points[0].Xf, tri.Points[0].Yf), new Vector2(tri.Points[1].Xf, tri.Points[1].Yf)); Gizmos.DrawLine(new Vector2(tri.Points[1].Xf, tri.Points[1].Yf), new Vector2(tri.Points[2].Xf, tri.Points[2].Yf)); Gizmos.DrawLine(new Vector2(tri.Points[2].Xf, tri.Points[2].Yf), new Vector2(tri.Points[0].Xf, tri.Points[0].Yf)); } */ //Generate colliders... ClipperLib.Clipper clipper = new ClipperLib.Clipper (); foreach(Poly2Tri.DelaunayTriangle tri in levelTriangleSet) { Polygon trianglePoints = new Polygon(); for(int i = 0; i < 3; i++) { trianglePoints.points.Add(tri.Points[i]); } clipper.AddPath(trianglePoints, ClipperLib.PolyType.ptSubject, true); } List<List<ClipperLib.IntPoint>> solution = new List<List<ClipperLib.IntPoint>> (); clipper.Execute (ClipperLib.ClipType.ctUnion, solution, ClipperLib.PolyFillType.pftPositive, ClipperLib.PolyFillType.pftPositive); generationMessage = "Generating mesh from boolean..."; DestructibleLevel level = GetComponent<DestructibleLevel> (); if (level == null) { level = gameObject.AddComponent<DestructibleLevel>(); } foreach (List<ClipperLib.IntPoint> polygon in solution) { level.booleanPolygons.Add(new Tinkerbox.Geometry.Polygon(polygon)); } level.UpdateMesh(); generationMessage = "Calling Callback.."; OnFinishedGeneration(); GameObject[] players = GameObject.FindGameObjectsWithTag ("Player"); Poly2Tri.DelaunayTriangle spawnTriangle = levelTriangleSet [Mathf.FloorToInt (Random.value * levelTriangleSet.Count)]; generationMessage = "Spawning Players..."; Vector2 spawnPoint = new Vector2 (spawnTriangle.Centroid ().Xf, spawnTriangle.Centroid ().Yf); foreach (GameObject player in players) { player.transform.position = spawnPoint + Random.insideUnitCircle; } generationMessage = "Spawning Decorations..."; for (int i = 0; i < decorationsToSpawn; i++) { GameObject decoration = (GameObject) Instantiate(decorations[Mathf.FloorToInt(Random.value * decorations.Length)]); Poly2Tri.DelaunayTriangle decorSpawnTriangle = levelTriangleSet [Mathf.FloorToInt (Random.value * levelTriangleSet.Count)]; Point decorSpawnPoint = decorSpawnTriangle.Centroid(); decoration.transform.position = decorSpawnPoint + Random.insideUnitCircle; } //yield return 0; }
//------------------------------------------------------------------------------ public static List<List<IntPoint>> MinkowskiSum(List<IntPoint> pattern, List<List<IntPoint>> paths, bool pathIsClosed) { List<List<IntPoint>> solution = new List<List<IntPoint>>(); Clipper c = new Clipper(); for (int i = 0; i < paths.Count; ++i) { List<List<IntPoint>> tmp = Minkowski(pattern, paths[i], true, pathIsClosed); c.AddPaths(tmp, PolyType.ptSubject, true); if (pathIsClosed) { List<IntPoint> path = TranslatePath(paths[i], pattern[0]); c.AddPath(path, PolyType.ptClip, true); } } c.Execute(ClipType.ctUnion, solution, PolyFillType.pftNonZero, PolyFillType.pftNonZero); return solution; }
/// <summary> /// Returns a list of fixtures and their shapes with regions inside portals clipped away. /// This excludes portal fixtures and the shapes are in world coordinates. /// </summary> /// <param name="body"></param> /// <returns></returns> private static List<Tuple<Fixture, Vector2[]>> GetClippedFixtures(Body body) { BodyData data = GetData(body); /* If this body isn't colliding with any portals then we just return a list of * fixtures and vertices.*/ if (data.PortalCollisions().Count <= 0) { List<Tuple<Fixture, Vector2[]>> fixtures = new List<Tuple<Fixture, Vector2[]>>(); foreach (Fixture f in body.FixtureList) { fixtures.Add(new Tuple<Fixture, Vector2[]>(f, FixtureExt.GetWorldPoints(f))); } return fixtures; } Vector2 center = GetLocalOrigin(body); List<List<IntPoint>> clipPaths = new List<List<IntPoint>>(); foreach (IPortal p in data.PortalCollisions()) { Vector2[] verts = Portal.GetWorldVerts(p); float scale = 100; Vector2 v0 = verts[0] + (verts[1] - verts[0]).Normalized() * scale; Vector2 v1 = verts[1] - (verts[1] - verts[0]).Normalized() * scale; Vector2 depth = (verts[1] - verts[0]).PerpendicularLeft.Normalized() * scale; if (new LineF(v0, v1).GetSideOf(v1 + depth) == new LineF(v0, v1).GetSideOf(center)) { depth *= -1; } Vector2[] box = new Vector2[] { v0, v1, v1 + depth, v0 + depth }; box = MathExt.SetWinding(box, true); clipPaths.Add(ClipperConvert.ToIntPoint(box)); } List<Tuple<Fixture, Vector2[]>> clippedFixtures = new List<Tuple<Fixture, Vector2[]>>(); Clipper clipper = new Clipper(); foreach (Fixture f in body.FixtureList) { if (!FixtureExt.GetData(f).IsPortalParentless()) { continue; } clipper.Clear(); clipper.AddPaths(clipPaths, PolyType.ptClip, true); clipper.AddPath( ClipperConvert.ToIntPoint(FixtureExt.GetWorldPoints(f)), PolyType.ptSubject, true); List<List<IntPoint>> result = new List<List<IntPoint>>(); clipper.Execute(ClipType.ctDifference, result, PolyFillType.pftEvenOdd, PolyFillType.pftNonZero); Debug.Assert( result.Count <= 1, "This fixture is too large for the portal masking or something has gone wrong with the clipper."); if (result.Count > 0) { clippedFixtures.Add(new Tuple<Fixture, Vector2[]>(f, ClipperConvert.ToVector2(result[0]))); } } Debug.Assert(clippedFixtures.Count > 0); return clippedFixtures; }
private PolyTreeEdgesRetained PrepairPolyTree(Collider2D[] floorColliders, Collider2D[] wallColliders) { if(floorColliders.Length == 0) { throw new System.Exception("No colliders in the scene on the floor layer."); } PolyTreeEdgesRetained TreeAndEdges = new PolyTreeEdgesRetained(); TreeAndEdges.Edges = new List<List<IntPoint>>(); TreeAndEdges.Tree = new PolyTree(); Clipper finalClipper = new Clipper(); if (floorColliders.Length > 0) { Clipper tempC = new Clipper(); ClipperOffset tempCo = new ClipperOffset(); foreach (Collider2D d in floorColliders) { List<IntPoint> p = PolygonFromCollider2D(d, false); if (p != null && p.Count != 0) { if (ClipperLib.Clipper.Orientation(p)) p.Reverse(); tempC.AddPath(p, PolyType.ptSubject, true); } } List<List<IntPoint>> solution = new List<List<IntPoint>>(); tempC.Execute(ClipType.ctUnion, solution, PolyFillType.pftNonZero, PolyFillType.pftNonZero); tempC.Clear(); foreach (List<IntPoint> intPoints in solution) { tempCo.AddPath(intPoints, (JoinType)GenerationInformation.JoinType, EndType.etClosedPolygon); TreeAndEdges.Edges.Add(intPoints); } solution.Clear(); tempCo.Execute(ref solution, -GenerationInformation.ColliderPadding * GenerationInformation.CalculationScaleFactor); finalClipper.AddPaths(solution, PolyType.ptSubject, true); } if(wallColliders.Length > 0) { Clipper tempC = new Clipper(); ClipperOffset tempCo = new ClipperOffset(); foreach (Collider2D d in wallColliders) { List<IntPoint> p = PolygonFromCollider2D(d, false); if (p != null && p.Count != 0) { if (ClipperLib.Clipper.Orientation(p)) p.Reverse(); tempC.AddPath(p, PolyType.ptSubject, true); } } List<List<IntPoint>> solution = new List<List<IntPoint>>(); tempC.Execute(ClipType.ctUnion, solution, PolyFillType.pftNonZero, PolyFillType.pftNonZero); tempC.Clear(); foreach (List<IntPoint> intPoints in solution) { tempCo.AddPath(intPoints, (JoinType)GenerationInformation.JoinType, EndType.etClosedPolygon); TreeAndEdges.Edges.Add(intPoints); } solution.Clear(); tempCo.Execute(ref solution, GenerationInformation.ColliderPadding * GenerationInformation.CalculationScaleFactor); finalClipper.AddPaths(solution, PolyType.ptClip, true); } finalClipper.Execute(ClipType.ctDifference, TreeAndEdges.Tree, PolyFillType.pftPositive, PolyFillType.pftEvenOdd); return TreeAndEdges; }
public static 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.UniqueName, (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 then 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); // Combine the solutions into the full clipper fullClipper.AddPaths(Clipper.ClosedPathsFromPolyTree(solution), PolyType.ptSubject, true); fullClipper.AddPaths(Clipper.OpenPathsFromPolyTree(solution), PolyType.ptSubject, false); } progFunc(String.Format("Clipping '{0}' polygons: 100%", tmxLayer.UniqueName)); ClipperLib.PolyTree fullSolution = new ClipperLib.PolyTree(); fullClipper.Execute(ClipperLib.ClipType.ctUnion, fullSolution); return(fullSolution); }
public void UpdateMesh() { //clean up booleanUnionSolution.Clear (); //solutions.Clear (); EdgeCollider2D[] cols = GetComponents<EdgeCollider2D> (); foreach (EdgeCollider2D col in cols) { DestroyImmediate(col); } PolygonCollider2D[] polyCols = GetComponents<PolygonCollider2D> (); foreach (PolygonCollider2D polyCol in polyCols) { DestroyImmediate(polyCol); } //Wind the square to boolean from. levelArea.points.Clear (); levelArea.points.Add (new Point (-1000f, 1000f)); levelArea.points.Add (new Point (1000f, 1000f)); levelArea.points.Add (new Point (1000f, -1000f)); levelArea.points.Add (new Point (-1000f, -1000f)); ClipperLib.Clipper clipper = new ClipperLib.Clipper (); //Strictly simple is computationally expensive, but prevents points from appearing too close together and making Poly2Tri Shit the bed. clipper.StrictlySimple = true; foreach (Polygon polygon in booleanPolygons) { clipper.AddPath(polygon,ClipperLib.PolyType.ptClip, true); } clipper.Execute (ClipperLib.ClipType.ctUnion, booleanUnionSolution, ClipperLib.PolyFillType.pftNonZero,ClipperLib.PolyFillType.pftNonZero); clipper.Clear (); clipper.AddPath (levelArea, ClipperLib.PolyType.ptSubject, true); foreach (List<ClipperLib.IntPoint> polygon in booleanUnionSolution) { clipper.AddPath(polygon,ClipperLib.PolyType.ptClip, true); } List<List<ClipperLib.IntPoint>> solution = new List<List<ClipperLib.IntPoint>> (); clipper.Execute (ClipperLib.ClipType.ctDifference, solution, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero); mesh = Utils.PolygonsToMesh (solution.Select(x=> (Polygon)x).ToList()); solutions = solution.Select (x => (Polygon)x).ToList (); GetComponent<MeshFilter> ().sharedMesh = mesh; foreach (Polygon polygon in solution) { Vector2[] unityPoints = new Vector2[polygon.points.Count + 1]; for(int i = 0; i < polygon.points.Count; i++) { unityPoints[i] = new Vector2(polygon.points[i].x,polygon.points[i].y); } unityPoints[polygon.points.Count] = new Vector2(polygon.points[0].x,polygon.points[0].y); EdgeCollider2D collider = gameObject.AddComponent<EdgeCollider2D>(); collider.points = unityPoints; } solutionCount = booleanUnionSolution.Count; //No need to store polygons that we won't need to reference... booleanPolygons = booleanUnionSolution.Select(x=>(Polygon)x).ToList(); }
public void FillPath(Color fillColor) { var fillClipper = new Clipper(Clipper.ioStrictlySimple); var points = pathSegments.SelectMany(s => new[] { new IntPoint(s.Head.Location.X * 1000, s.Head.Location.Y * 1000), new IntPoint(s.Tail.Location.X * 1000, s.Tail.Location.Y * 1000) }); fillClipper.AddPath(points.ToList(), PolyType.ptSubject, true); var polytree = new PolyTree(); fillClipper.Execute(ClipType.ctUnion, polytree, PolyFillType.pftEvenOdd, PolyFillType.pftNonZero); var triangles = new Triangulator().TriangulateComplex(polytree); using (RenderTargetSwap()) { GraphicsDevice.SetRasterizerState(rasterizerState); foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); var x = new PrimitiveBatch<VertexPositionColor>(GraphicsDevice); x.Begin(); foreach (var triangle in triangles) { var p1 = triangle.Points[0]; var p2 = triangle.Points[1]; var p3 = triangle.Points[2]; x.DrawTriangle( new VertexPositionColor(new Vector3((float)p1.X, (float)p1.Y, 100), fillColor), new VertexPositionColor(new Vector3((float)p2.X, (float)p2.Y, 100), fillColor), new VertexPositionColor(new Vector3((float)p3.X, (float)p3.Y, 100), fillColor) ); } x.End(); } } }
/// <summary> /// Applies a buoyant force, drag and lift to an object submerged in the water. /// </summary> /// <param name="subjectPoly"> The polygon of the object in the water.</param> /// <param name="minIndex"> The min index for a value in the "waterLinePoints" list. </param> /// <param name="maxIndex"> The max index for a value in the "waterLinePoints" list. </param> /// <param name="isIntersecting"> Are the subject and clipping polygon intersecting?. </param> private List<List<Vector2>> GetIntersectionPolygon(Vector2[] subjectPoly, int minIndex, int maxIndex, out bool isIntersecting) { Vector2 bottomHandleGlobalPos = transform.TransformPoint(water2D.handlesPosition[1]); List<List<Vector2>> intersectionPoly = new List<List<Vector2>>(); List<Vector2> clipPolygon = new List<Vector2>(); Clipper clipper = new Clipper(); Paths solutionPath = new Paths(); Path subjPath = new Path(); Path clipPath = new Path(); int len, len2, min, max; isIntersecting = true; if (surfaceVertsCount > meshSegmentsPerWaterLineSegment) { min = (int)Mathf.Floor(minIndex / meshSegmentsPerWaterLineSegment); max = (int)Mathf.Floor(maxIndex / meshSegmentsPerWaterLineSegment) + 1; if (max > waterLinePoints.Count - 2) max = waterLinePoints.Count - 2; for (int i = min; i <= max; i++) { clipPolygon.Add(waterLinePoints[i]); } int last = clipPolygon.Count - 1; clipPolygon.Add(new Vector2(clipPolygon[last].x, bottomHandleGlobalPos.y)); clipPolygon.Add(new Vector2(clipPolygon[0].x, bottomHandleGlobalPos.y)); } else { Vector2 vertGlobalPos = transform.TransformPoint(vertices[surfaceVertsCount]); clipPolygon.Add(vertGlobalPos); vertGlobalPos = transform.TransformPoint(vertices[surfaceVertsCount + surfaceVertsCount - 1]); clipPolygon.Add(new Vector2(vertGlobalPos.x, vertGlobalPos.y)); int last = clipPolygon.Count - 1; clipPolygon.Add(new Vector2(clipPolygon[last].x, bottomHandleGlobalPos.y)); clipPolygon.Add(new Vector2(clipPolygon[0].x, bottomHandleGlobalPos.y)); } if (showClippingPlolygon) { for (int i = 0; i < clipPolygon.Count; i++) { if (i < clipPolygon.Count - 1) Debug.DrawLine(clipPolygon[i], clipPolygon[i + 1], Color.green); else Debug.DrawLine(clipPolygon[i], clipPolygon[0], Color.green); } } len = subjectPoly.Length; for (int i = 0; i < len; i++) { subjPath.Add(new IntPoint(subjectPoly[i].x * scaleFactor, subjectPoly[i].y * scaleFactor)); } len = clipPolygon.Count; for (int i = 0; i < len; i++) { clipPath.Add(new IntPoint(clipPolygon[i].x * scaleFactor, clipPolygon[i].y * scaleFactor)); } clipper.AddPath(subjPath, PolyType.ptSubject, true); clipper.AddPath(clipPath, PolyType.ptClip, true); clipper.Execute(ClipType.ctIntersection, solutionPath, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); if (solutionPath.Count != 0) { len = solutionPath.Count; for (int i = 0; i < len; i++) { len2 = solutionPath[i].Count; List<Vector2> list = new List<Vector2>(); for (int j = 0; j < len2; j++) { list.Add(new Vector2(solutionPath[i][j].X / scaleFactor, solutionPath[i][j].Y / scaleFactor)); } intersectionPoly.Add(list); } return intersectionPoly; } else { isIntersecting = false; return null; } }
public PolyTree UnionPolygonsToTree(IReadOnlyList<IReadOnlyList<TriangulationPoint>> polygons) { if (polygons == null) return null; var clipper = new Clipper(Clipper.ioStrictlySimple); foreach (var polygon in polygons) { clipper.AddPath(polygon.MapList(UpscalePoint), PolyType.ptSubject, true); } PolyTree polyTree = new PolyTree(); clipper.Execute(ClipType.ctUnion, polyTree, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); // Console.WriteLine(polygons.Count + " vs " + polyTree.Total + " " + polyTree.Childs.Count); return polyTree; }
private static List<List<Point>> Remove_Overlap_In_XY_Plane(List<Point> points1, List<List<Point>> otherPolygonsPoints) { var clipper = new Clipper(); clipper.StrictlySimple = true; //we only handle so-called simple polygons, this helps ensure that the only results clipper returns are simple polygons clipper.AddPath(ToClipperPath(points1), PolyType.ptSubject, true); foreach (var pointList in otherPolygonsPoints) { clipper.AddPath(ToClipperPath(pointList), PolyType.ptClip, true); } var soln = new Paths(); // clipper offers 4 operations: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/ClipType.htm var success = clipper.Execute(ClipType.ctDifference, soln); var resultantPolygonPoints = new List<List<Point>>(); if (success.Not()) { return null; } foreach (var pointList in soln) { resultantPolygonPoints.Add(ToPoints(pointList)); } return resultantPolygonPoints; }
private PolyTree Punch(PolyTree input, PolyTree hole) { var subtractClipper = new Clipper(Clipper.ioStrictlySimple); for (var it = input.GetFirst(); it != null; it = it.GetNext()) { subtractClipper.AddPath(it.Contour, PolyType.ptSubject, true); } for (var it = hole.GetFirst(); it != null; it = it.GetNext()) { subtractClipper.AddPath(it.Contour, PolyType.ptClip, true); } var result = new PolyTree(); subtractClipper.Execute(ClipType.ctDifference, result, PolyFillType.pftNonZero, PolyFillType.pftNonZero); return result; }
//------------------------------------------------------------------------------ // SimplifyPolygon functions ... // Convert self-intersecting polygons into simple polygons //------------------------------------------------------------------------------ public static List<List<IntPoint>> SimplifyPolygon(List<IntPoint> poly, PolyFillType fillType = PolyFillType.pftEvenOdd) { List<List<IntPoint>> result = new List<List<IntPoint>>(); Clipper c = new Clipper(); c.StrictlySimple = true; c.AddPath(poly, PolyType.ptSubject, true); c.Execute(ClipType.ctUnion, result, fillType, fillType); return result; }
private PolyTree Union(PolyTree a, PolyTree b) { var landUnionClipper = new Clipper(Clipper.ioStrictlySimple); var s = new Stack<PolyNode>(); s.Push(a); s.Push(b); while (s.Any()) { var node = s.Pop(); if (!node.IsHole) { landUnionClipper.AddPath(node.Contour, PolyType.ptSubject, true); } node.Childs.ForEach(s.Push); } PolyTree landUnionPolyTree = new PolyTree(); landUnionClipper.Execute(ClipType.ctUnion, landUnionPolyTree, PolyFillType.pftNonZero, PolyFillType.pftNonZero); return landUnionPolyTree; }
/// <summary> /// Checks whether this key overlaps with another specified key definition. /// </summary> /// <param name="otherKey">The other key to check for overlapping on.</param> /// <returns><c>True</c> if the keys overlap, <c>false</c> otherwise.</returns> public bool BordersWith(KeyDefinition otherKey) { var clipper = new Clipper(); clipper.AddPath(this.GetPath(), PolyType.ptSubject, true); clipper.AddPath(otherKey.GetPath(), PolyType.ptClip, true); var union = new List<List<IntPoint>>(); clipper.Execute(ClipType.ctUnion, union); return union.Count == 1; }
public static 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.UniqueName, (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 then 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(Clipper.ClosedPathsFromPolyTree(solution), PolyType.ptSubject, true); fullClipper.AddPaths(Clipper.OpenPathsFromPolyTree(solution), PolyType.ptSubject, false); } progFunc(String.Format("Clipping '{0}' polygons: 100%", tmxLayer.UniqueName)); ClipperLib.PolyTree fullSolution = new ClipperLib.PolyTree(); fullClipper.Execute(ClipperLib.ClipType.ctUnion, fullSolution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); return fullSolution; }
public static List<List<IntPoint>> MergePolygon(List<List<IntPoint>> polygonA, List<IntPoint> polygonB) { Clipper clipper = new Clipper(); clipper.AddPaths(polygonA, PolyType.ptSubject, true); clipper.AddPath(polygonB, PolyType.ptClip, true); List<List<IntPoint>> solution = new List<List<IntPoint>>(); clipper.Execute(ClipType.ctUnion, solution); return solution; }
public static PolyTree ExecuteClipper(TmxMap map, TmxChunk chunk, TransformPointFunc xfFunc) { ////for(int i=0;i<chunk.Height;i++) //// { //// for(int j=0; j<chunk.Width;j++) //// { //// var raw = chunk.GetRawTileIdAt(j, i); //// if(raw!=0) //// { //// var tid = TmxMath.GetTileIdWithoutFlags(raw); //// var tile = map.Tiles[tid]; //// foreach(var p in tile.ObjectGroup.Objects) //// { //// if(p is TmxHasPoints) //// { //// p.ToEnumerable().Where((x) => //// { //// if (!usingUnityLayerOverride) //// { //// return string.Compare(tuple.Item1.Type, chunk.ParentData.ParentLayer.Name, true) == 0; //// } //// return true; //// }); //// } //// } //// } //// } //// } // Clipper clipper = new Clipper(0); // Tuple<TmxObject, TmxTile, uint> tuple = new Tuple<TmxObject, TmxTile, uint>(null, null, 0); // bool usingUnityLayerOverride = !string.IsNullOrEmpty(chunk.ParentData.ParentLayer.UnityLayerOverrideName); // foreach (var item2 in from h__TransparentIdentifier4 in (from y in Enumerable.Range(0, chunk.Height) // from x in Enumerable.Range(0, chunk.Width) // let rawTileId = chunk.GetRawTileIdAt(x, y) // where rawTileId != 0 // let tileId = TmxMath.GetTileIdWithoutFlags(rawTileId) // let tile = map.Tiles[tileId] // from polygon in tile.ObjectGroup.Objects // where polygon is TmxHasPoints // select polygon.ToEnumerable().ToList().TrueForAll // (h__TransparentIdentifier4 => // { // UnityEngine.Debug.Log("liudaodelh"); // tuple = new Tuple<TmxObject, TmxTile, uint>(polygon, tile, rawTileId); // if (!usingUnityLayerOverride) // { // return string.Compare(tuple.Item1.Type, chunk.ParentData.ParentLayer.Name, true) == 0; // } // return true; // })) // select new // { // PositionOnMap = map.GetMapPositionAt((int)tuple.Item1.Position.X + chunk.X, (int)tuple.Item1.Position.Y + chunk.Y, tuple.Item2), // HasPointsInterface = (tuple.Item1 as TmxHasPoints), // TmxObjectInterface = tuple.Item1, // IsFlippedDiagnoally = TmxMath.IsTileFlippedDiagonally(tuple.Item3), // IsFlippedHorizontally = TmxMath.IsTileFlippedHorizontally(tuple.Item3), // IsFlippedVertically = TmxMath.IsTileFlippedVertically(tuple.Item3), // TileCenter = new PointF((float)tuple.Item2.TileSize.Width * 0.5f, (float)tuple.Item2.TileSize.Height * 0.5f) // }) // { // List<IntPoint> list = new List<IntPoint>(); // SizeF offset = new SizeF(item2.TmxObjectInterface.Position); // PointF[] array = item2.HasPointsInterface.Points.Select((PointF pt) => PointF.Add(pt, offset)).ToArray(); // TmxMath.TransformPoints(array, item2.TileCenter, item2.IsFlippedDiagnoally, item2.IsFlippedHorizontally, item2.IsFlippedVertically); // PointF[] array2 = array; // for (int i = 0; i < array2.Length; i++) // { // PointF pointF = array2[i]; // float x2 = (float)item2.PositionOnMap.X + pointF.X; // float y2 = (float)item2.PositionOnMap.Y + pointF.Y; // IntPoint item = xfFunc(x2, y2); // list.Add(item); // } // list.Reverse(); // clipper.AddPath(list, PolyType.ptSubject, item2.HasPointsInterface.ArePointsClosed()); // } // PolyTree polyTree = new PolyTree(); // clipper.Execute(ClipType.ctUnion, polyTree, SubjectFillRule, ClipFillRule); // return polyTree; ClipperLib.Clipper clipper = 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(chunk.ParentData.ParentLayer.UnityLayerOverrideName); var polygons = from y in Enumerable.Range(0, chunk.Height) from x in Enumerable.Range(0, chunk.Width) let rawTileId = chunk.GetRawTileIdAt(x, y) where rawTileId != 0 let tileId = TmxMath.GetTileIdWithoutFlags(rawTileId) let tile = map.Tiles[tileId] from polygon in tile.ObjectGroup.Objects where (polygon as TmxHasPoints) != null where usingUnityLayerOverride || String.Compare(polygon.Type, chunk.ParentData.ParentLayer.Name, true) == 0 select new { PositionOnMap = map.GetMapPositionAt(x + chunk.X, y + chunk.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), }; // 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 polygons) { // 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" clipper.AddPath(clipperPolygon, ClipperLib.PolyType.ptSubject, poly.HasPointsInterface.ArePointsClosed()); } ClipperLib.PolyTree solution = new ClipperLib.PolyTree(); clipper.Execute(ClipperLib.ClipType.ctUnion, solution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); return(solution); }
//------------------------------------------------------------------------------ public PolyOffsetBuilder(Paths pts, out Paths solution, double delta, JoinType jointype, EndType endtype, double limit = 0) { //precondition: solution != pts solution = new Paths(); if (ClipperBase.near_zero(delta)) {solution = pts; return; } m_p = pts; if (endtype != EndType.etClosed && delta < 0) delta = -delta; m_delta = delta; if (jointype == JoinType.jtMiter) { //m_miterVal: see offset_triginometry.svg in the documentation folder ... if (limit > 2) m_miterLim = 2 / (limit * limit); else m_miterLim = 0.5; if (endtype == EndType.etRound) limit = 0.25; } if (jointype == JoinType.jtRound || endtype == EndType.etRound) { if (limit <= 0) limit = 0.25; else if (limit > Math.Abs(delta)*0.25) limit = Math.Abs(delta)*0.25; //m_roundVal: see offset_triginometry2.svg in the documentation folder ... m_Steps360 = Math.PI / Math.Acos(1 - limit / Math.Abs(delta)); m_sin = Math.Sin(2 * Math.PI / m_Steps360); m_cos = Math.Cos(2 * Math.PI / m_Steps360); m_Steps360 /= Math.PI * 2; if (delta < 0) m_sin = -m_sin; } double deltaSq = delta * delta; solution.Capacity = pts.Count; for (m_i = 0; m_i < pts.Count; m_i++) { int len = pts[m_i].Count; if (len == 0 || (len < 3 && delta <= 0)) continue; if (len == 1) { if (jointype == JoinType.jtRound) { double X = 1.0, Y = 0.0; for (cInt j = 1; j <= Round(m_Steps360 * 2 * Math.PI); j++) { AddPoint(new IntPoint( Round(m_p[m_i][0].X + X * delta), Round(m_p[m_i][0].Y + Y * delta))); double X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } } else { double X = -1.0, Y = -1.0; for (int j = 0; j < 4; ++j) { AddPoint(new IntPoint(Round(m_p[m_i][0].X + X * delta), Round(m_p[m_i][0].Y + Y * delta))); if (X < 0) X = 1; else if (Y < 0) Y = 1; else X = -1; } } continue; } //build normals ... normals.Clear(); normals.Capacity = len; for (int j = 0; j < len -1; ++j) normals.Add(GetUnitNormal(pts[m_i][j], pts[m_i][j+1])); if (endtype == EndType.etClosed) normals.Add(GetUnitNormal(pts[m_i][len - 1], pts[m_i][0])); else normals.Add(new DoublePoint(normals[len - 2])); currentPoly = new Path(); if (endtype == EndType.etClosed) { m_k = len - 1; for (m_j = 0; m_j < len; ++m_j) OffsetPoint(jointype); solution.Add(currentPoly); } else { m_k = 0; for (m_j = 1; m_j < len - 1; ++m_j) OffsetPoint(jointype); IntPoint pt1; if (endtype == EndType.etButt) { m_j = len - 1; pt1 = new IntPoint((cInt)Round(pts[m_i][m_j].X + normals[m_j].X * delta), (cInt)Round(pts[m_i][m_j].Y + normals[m_j].Y * delta)); AddPoint(pt1); pt1 = new IntPoint((cInt)Round(pts[m_i][m_j].X - normals[m_j].X * delta), (cInt)Round(pts[m_i][m_j].Y - normals[m_j].Y * delta)); AddPoint(pt1); } else { m_j = len - 1; m_k = len - 2; m_sinA = 0; normals[m_j] = new DoublePoint(-normals[m_j].X, -normals[m_j].Y); if (endtype == EndType.etSquare) DoSquare(); else DoRound(); } //re-build Normals ... for (int j = len - 1; j > 0; j--) normals[j] = new DoublePoint(-normals[j - 1].X, -normals[j - 1].Y); normals[0] = new DoublePoint(-normals[1].X, -normals[1].Y); m_k = len - 1; for (m_j = m_k - 1; m_j > 0; --m_j) OffsetPoint(jointype); if (endtype == EndType.etButt) { pt1 = new IntPoint((cInt)Round(pts[m_i][0].X - normals[0].X * delta), (cInt)Round(pts[m_i][0].Y - normals[0].Y * delta)); AddPoint(pt1); pt1 = new IntPoint((cInt)Round(pts[m_i][0].X + normals[0].X * delta), (cInt)Round(pts[m_i][0].Y + normals[0].Y * delta)); AddPoint(pt1); } else { m_k = 1; m_sinA = 0; if (endtype == EndType.etSquare) DoSquare(); else DoRound(); } solution.Add(currentPoly); } } //finally, clean up untidy corners ... Clipper clpr = new Clipper(); clpr.AddPaths(solution, PolyType.ptSubject, true); if (delta > 0) { clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftPositive); } else { IntRect r = clpr.GetBounds(); Path outer = new Path(4); outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); outer.Add(new IntPoint(r.right + 10, r.top - 10)); outer.Add(new IntPoint(r.left - 10, r.top - 10)); clpr.AddPath(outer, PolyType.ptSubject, true); clpr.ReverseSolution = true; clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); if (solution.Count > 0) solution.RemoveAt(0); } }
private static bool CalculatePortalViews(IPortal portal, IPortal portalEnter, IList<IPortal> portals, Matrix4 viewMatrix, Vector2 viewPos, Vector2 viewPosPrevious, PortalView portalView, Matrix4 portalMatrix, List<Func<bool>> actionList) { const float AREA_EPSILON = 0.0001f; Clipper c = new Clipper(); //The clipper must be set to strictly simple. Otherwise polygons might have duplicate vertices which causes poly2tri to generate incorrect results. c.StrictlySimple = true; if (!_isPortalValid(portalEnter, portal, viewPos)) { return false; } Vector2[] fov = Vector2Ext.Transform(Portal.GetFov(portal, viewPos, 500, 3), portalMatrix); if (MathExt.GetArea(fov) < AREA_EPSILON) { return false; } List<IntPoint> pathFov = ClipperConvert.ToIntPoint(fov); var viewNew = new List<List<IntPoint>>(); c.AddPath(pathFov, PolyType.ptSubject, true); c.AddPaths(portalView.Paths, PolyType.ptClip, true); c.Execute(ClipType.ctIntersection, viewNew); c.Clear(); if (viewNew.Count <= 0) { return false; } c.AddPaths(viewNew, PolyType.ptSubject, true); foreach (IPortal other in portals) { if (other == portal) { continue; } if (!_isPortalValid(portalEnter, other, viewPos)) { continue; } //Skip this portal if it's inside the current portal's FOV. LineF portalLine = new LineF(Portal.GetWorldVerts(portal)); LineF portalOtherLine = new LineF(Portal.GetWorldVerts(other)); if (portalLine.IsInsideFOV(viewPos, portalOtherLine)) { continue; } Vector2[] otherFov = Vector2Ext.Transform(Portal.GetFov(other, viewPos, 500, 3), portalMatrix); if (MathExt.GetArea(otherFov) < AREA_EPSILON) { continue; } otherFov = MathExt.SetWinding(otherFov, true); List<IntPoint> otherPathFov = ClipperConvert.ToIntPoint(otherFov); c.AddPath(otherPathFov, PolyType.ptClip, true); } var viewNewer = new List<List<IntPoint>>(); c.Execute(ClipType.ctDifference, viewNewer, PolyFillType.pftNonZero, PolyFillType.pftNonZero); c.Clear(); if (viewNewer.Count <= 0) { return false; } Vector2 viewPosNew = Vector2Ext.Transform(viewPos, Portal.GetLinkedMatrix(portal, portal.Linked)); Vector2 viewPosPreviousNew = Vector2Ext.Transform(viewPosPrevious, Portal.GetLinkedMatrix(portal, portal.Linked)); Matrix4 portalMatrixNew = Portal.GetLinkedMatrix(portal.Linked, portal) * portalMatrix; Matrix4 viewMatrixNew = portalMatrixNew * viewMatrix; LineF[] lines = Portal.GetFovLines(portal, viewPos, 500); lines[0] = lines[0].Transform(portalMatrix); lines[1] = lines[1].Transform(portalMatrix); LineF[] linesPrevious = Portal.GetFovLines(portal, viewPosPrevious, 500); linesPrevious[0] = linesPrevious[0].Transform(portalMatrix); linesPrevious[1] = linesPrevious[1].Transform(portalMatrix); LineF portalWorldLine = new LineF(Portal.GetWorldVerts(portal)); portalWorldLine = portalWorldLine.Transform(portalMatrix); PortalView portalViewNew = new PortalView(portalView, viewMatrixNew, viewNewer, lines, linesPrevious, portalWorldLine); foreach (IPortal p in portals) { actionList.Add(() => CalculatePortalViews(p, portal, portals, viewMatrix, viewPosNew, viewPosPreviousNew, portalViewNew, portalMatrixNew, actionList) ); } return true; }
public static NFP simplifyFunction(NFP polygon, bool inside) { var tolerance = 4 * Config.curveTolerance; // give special treatment to line segments above this length (squared) var fixedTolerance = 40 * Config.curveTolerance * 40 * Config.curveTolerance; int i, j, k; if (Config.simplify) { /* * // use convex hull * var hull = new ConvexHullGrahamScan(); * for(var i=0; i<polygon.length; i++){ * hull.addPoint(polygon[i].x, polygon[i].y); * } * * return hull.getHull();*/ var hull = Background.getHull(polygon); if (hull != null) { return(hull); } else { return(polygon); } } var cleaned = cleanPolygon2(polygon); if (cleaned != null && cleaned.length > 1) { polygon = cleaned; } else { return(polygon); } // polygon to polyline var copy = polygon.slice(0); copy.push(copy[0]); // mark all segments greater than ~0.25 in to be kept // the PD simplification algo doesn't care about the accuracy of long lines, only the absolute distance of each point // we care a great deal for (i = 0; i < copy.length - 1; i++) { var p1 = copy[i]; var p2 = copy[i + 1]; var sqd = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y); if (sqd > fixedTolerance) { p1.marked = true; p2.marked = true; } } var simple = Simplify.simplify(copy, tolerance, true); // now a polygon again //simple.pop(); simple.Points = simple.Points.Take(simple.Points.Count() - 1).ToArray(); // could be dirty again (self intersections and/or coincident points) simple = cleanPolygon2(simple); // simplification process reduced poly to a line or point if (simple == null) { simple = polygon; } var offsets = polygonOffsetDeepNest(simple, inside ? -tolerance : tolerance); NFP offset = null; double offsetArea = 0; List <NFP> holes = new List <NFP>(); for (i = 0; i < offsets.Length; i++) { var area = GeometryUtil.polygonArea(offsets[i]); if (offset == null || area < offsetArea) { offset = offsets[i]; offsetArea = area; } if (area > 0) { holes.Add(offsets[i]); } } // mark any points that are exact for (i = 0; i < simple.length; i++) { var seg = new NFP(); seg.AddPoint(simple[i]); seg.AddPoint(simple[i + 1 == simple.length ? 0 : i + 1]); var index1 = find(seg[0], polygon); var index2 = find(seg[1], polygon); if (index1 + 1 == index2 || index2 + 1 == index1 || (index1 == 0 && index2 == polygon.length - 1) || (index2 == 0 && index1 == polygon.length - 1)) { seg[0].exact = true; seg[1].exact = true; } } var numshells = 4; NFP[] shells = new NFP[numshells]; for (j = 1; j < numshells; j++) { var delta = j * (tolerance / numshells); delta = inside ? -delta : delta; var shell = polygonOffsetDeepNest(simple, delta); if (shell.Count() > 0) { shells[j] = shell.First(); } else { //shells[j] = shell; } } if (offset == null) { return(polygon); } // selective reversal of offset for (i = 0; i < offset.length; i++) { var o = offset[i]; var target = getTarget(o, simple, 2 * tolerance); // reverse point offset and try to find exterior points var test = clone(offset); test.Points[i] = new SvgPoint(target.x, target.y); if (!exterior(test, polygon, inside)) { o.x = target.x; o.y = target.y; } else { // a shell is an intermediate offset between simple and offset for (j = 1; j < numshells; j++) { if (shells[j] != null) { var shell = shells[j]; var delta = j * (tolerance / numshells); target = getTarget(o, shell, 2 * delta); test = clone(offset); test.Points[i] = new SvgPoint(target.x, target.y); if (!exterior(test, polygon, inside)) { o.x = target.x; o.y = target.y; break; } } } } } // straighten long lines // a rounded rectangle would still have issues at this point, as the long sides won't line up straight var straightened = false; for (i = 0; i < offset.length; i++) { var p1 = offset[i]; var p2 = offset[i + 1 == offset.length ? 0 : i + 1]; var sqd = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y); if (sqd < fixedTolerance) { continue; } for (j = 0; j < simple.length; j++) { var s1 = simple[j]; var s2 = simple[j + 1 == simple.length ? 0 : j + 1]; var sqds = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y); if (sqds < fixedTolerance) { continue; } if ((GeometryUtil._almostEqual(s1.x, s2.x) || GeometryUtil._almostEqual(s1.y, s2.y)) && // we only really care about vertical and horizontal lines GeometryUtil._withinDistance(p1, s1, 2 * tolerance) && GeometryUtil._withinDistance(p2, s2, 2 * tolerance) && (!GeometryUtil._withinDistance(p1, s1, Config.curveTolerance / 1000) || !GeometryUtil._withinDistance(p2, s2, Config.curveTolerance / 1000))) { p1.x = s1.x; p1.y = s1.y; p2.x = s2.x; p2.y = s2.y; straightened = true; } } } //if(straightened){ var Ac = _Clipper.ScaleUpPaths(offset, 10000000); var Bc = _Clipper.ScaleUpPaths(polygon, 10000000); var combined = new List <List <IntPoint> >(); var clipper = new ClipperLib.Clipper(); clipper.AddPath(Ac.ToList(), ClipperLib.PolyType.ptSubject, true); clipper.AddPath(Bc.ToList(), ClipperLib.PolyType.ptSubject, true); // the line straightening may have made the offset smaller than the simplified if (clipper.Execute(ClipperLib.ClipType.ctUnion, combined, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero)) { double?largestArea = null; for (i = 0; i < combined.Count; i++) { var n = Background.toNestCoordinates(combined[i].ToArray(), 10000000); var sarea = -GeometryUtil.polygonArea(n); if (largestArea == null || largestArea < sarea) { offset = n; largestArea = sarea; } } } //} cleaned = cleanPolygon2(offset); if (cleaned != null && cleaned.length > 1) { offset = cleaned; } // mark any points that are exact (for line merge detection) for (i = 0; i < offset.length; i++) { var seg = new SvgPoint[] { offset[i], offset[i + 1 == offset.length ? 0 : i + 1] }; var index1 = find(seg[0], polygon); var index2 = find(seg[1], polygon); if (index1 == null) { index1 = 0; } if (index2 == null) { index2 = 0; } if (index1 + 1 == index2 || index2 + 1 == index1 || (index1 == 0 && index2 == polygon.length - 1) || (index2 == 0 && index1 == polygon.length - 1)) { seg[0].exact = true; seg[1].exact = true; } } if (!inside && holes != null && holes.Count > 0) { offset.children = holes; } return(offset); }
/// <summary> /// Updates the key definition to occupy a region of itself plus the specified other keys. /// </summary> /// <param name="keys">The keys to union with.</param> /// <returns>A new key definition with the updated region.</returns> private MouseScrollDefinition UnionWith(IList<MouseScrollDefinition> keys) { var newBoundaries = this.Boundaries.Select(b => new TPoint(b.X, b.Y)).ToList(); if (keys.Any()) { var cl = new Clipper(); cl.AddPath(this.GetPath(), PolyType.ptSubject, true); cl.AddPaths(keys.Select(x => x.GetPath()).ToList(), PolyType.ptClip, true); var union = new List<List<IntPoint>>(); cl.Execute(ClipType.ctUnion, union); if (union.Count > 1) throw new ArgumentException("Cannot union two non-overlapping keys."); newBoundaries = union.Single().ConvertAll<TPoint>(x => x); } return new MouseScrollDefinition(this.Id, newBoundaries, this.KeyCodes.Single(), this.Text); }
//------------------------------------------------------------------------------ public void Execute(ref List<List<IntPoint>> solution, double delta) { solution.Clear(); FixOrientations(); DoOffset(delta); //now clean up 'corners' ... Clipper clpr = new Clipper(); clpr.AddPaths(m_destPolys, PolyType.ptSubject, true); if (delta > 0) { clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftPositive); } else { IntRect r = Clipper.GetBounds(m_destPolys); List<IntPoint> outer = new List<IntPoint>(4); outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); outer.Add(new IntPoint(r.right + 10, r.top - 10)); outer.Add(new IntPoint(r.left - 10, r.top - 10)); clpr.AddPath(outer, PolyType.ptSubject, true); clpr.ReverseSolution = true; clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); if (solution.Count > 0) solution.RemoveAt(0); } }
//------------------------------------------------------------------------------ public void Execute(ref PolyTree solution, double delta) { solution.Clear(); FixOrientations(); DoOffset(delta); //now clean up 'corners' ... Clipper clpr = new Clipper(); clpr.AddPaths(m_destPolys, PolyType.ptSubject, true); if (delta > 0) { clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftPositive); } else { IntRect r = Clipper.GetBounds(m_destPolys); List<IntPoint> outer = new List<IntPoint>(4); outer.Add(new IntPoint(r.left - 10, r.bottom + 10)); outer.Add(new IntPoint(r.right + 10, r.bottom + 10)); outer.Add(new IntPoint(r.right + 10, r.top - 10)); outer.Add(new IntPoint(r.left - 10, r.top - 10)); clpr.AddPath(outer, PolyType.ptSubject, true); clpr.ReverseSolution = true; clpr.Execute(ClipType.ctUnion, solution, PolyFillType.pftNegative, PolyFillType.pftNegative); //remove the outer PolyNode rectangle ... if (solution.ChildCount == 1 && solution.Childs[0].ChildCount > 0) { PolyNode outerNode = solution.Childs[0]; solution.Childs.Capacity = outerNode.ChildCount; solution.Childs[0] = outerNode.Childs[0]; solution.Childs[0].m_Parent = solution; for (int i = 1; i < outerNode.ChildCount; i++) solution.AddChild(outerNode.Childs[i]); } else solution.Clear(); } }
public static Polygons GetCorrectedWinding(this Polygons polygonsToFix) { polygonsToFix = Clipper.CleanPolygons(polygonsToFix); Polygon boundsPolygon = new Polygon(); IntRect bounds = Clipper.GetBounds(polygonsToFix); bounds.left -= 10; bounds.bottom += 10; bounds.right += 10; bounds.top -= 10; boundsPolygon.Add(new IntPoint(bounds.left, bounds.top)); boundsPolygon.Add(new IntPoint(bounds.right, bounds.top)); boundsPolygon.Add(new IntPoint(bounds.right, bounds.bottom)); boundsPolygon.Add(new IntPoint(bounds.left, bounds.bottom)); Clipper clipper = new Clipper(); clipper.AddPaths(polygonsToFix, PolyType.ptSubject, true); clipper.AddPath(boundsPolygon, PolyType.ptClip, true); PolyTree intersectionResult = new PolyTree(); clipper.Execute(ClipType.ctIntersection, intersectionResult); Polygons outputPolygons = Clipper.ClosedPathsFromPolyTree(intersectionResult); return outputPolygons; }
// Only works if the points are in the XY plane. // Link to their Documentation: http://www.angusj.com/delphi/clipper/documentation/Docs/_Body.htm private static List<Point> Overlap_In_XY_Plane(List<Point> points1, List<Point> points2) { var clipper = new Clipper(); clipper.AddPath(ToClipperPath(points1), PolyType.ptClip, true); clipper.AddPath(ToClipperPath(points2), PolyType.ptSubject, true); var soln = new Paths(); // clipper offers 4 operations: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/ClipType.htm var success = clipper.Execute(ClipType.ctIntersection, soln); if (success.Not()) { return null; } switch (soln.Count) { case 0: return null; case 1: return ToPoints(soln[0]); default: throw new ArgumentException("The passed polygons had multiple regions of overlap."); } }
static public PolyTree FindDistictObjectBounds(ImageBuffer image) { MarchingSquaresByte marchingSquaresData = new MarchingSquaresByte(image, 5, 0); marchingSquaresData.CreateLineSegments(); Polygons lineLoops = marchingSquaresData.CreateLineLoops(1); if (lineLoops.Count == 1) { return null; } // create a bounding polygon to clip against IntPoint min = new IntPoint(long.MaxValue, long.MaxValue); IntPoint max = new IntPoint(long.MinValue, long.MinValue); foreach (Polygon polygon in lineLoops) { foreach (IntPoint point in polygon) { min.X = Math.Min(point.X - 10, min.X); min.Y = Math.Min(point.Y - 10, min.Y); max.X = Math.Max(point.X + 10, max.X); max.Y = Math.Max(point.Y + 10, max.Y); } } Polygon boundingPoly = new Polygon(); boundingPoly.Add(min); boundingPoly.Add(new IntPoint(min.X, max.Y)); boundingPoly.Add(max); boundingPoly.Add(new IntPoint(max.X, min.Y)); // now clip the polygons to get the inside and outside polys Clipper clipper = new Clipper(); clipper.AddPaths(lineLoops, PolyType.ptSubject, true); clipper.AddPath(boundingPoly, PolyType.ptClip, true); PolyTree polyTreeForPlate = new PolyTree(); clipper.Execute(ClipType.ctIntersection, polyTreeForPlate); return polyTreeForPlate; }
private Polygons FixWinding(Polygons polygonsToPathAround) { polygonsToPathAround = Clipper.CleanPolygons(polygonsToPathAround); Polygon boundsPolygon = new Polygon(); IntRect bounds = Clipper.GetBounds(polygonsToPathAround); bounds.left -= 10; bounds.bottom += 10; bounds.right += 10; bounds.top -= 10; boundsPolygon.Add(new IntPoint(bounds.left, bounds.top)); boundsPolygon.Add(new IntPoint(bounds.right, bounds.top)); boundsPolygon.Add(new IntPoint(bounds.right, bounds.bottom)); boundsPolygon.Add(new IntPoint(bounds.left, bounds.bottom)); Clipper clipper = new Clipper(); clipper.AddPaths(polygonsToPathAround, PolyType.ptSubject, true); clipper.AddPath(boundsPolygon, PolyType.ptClip, true); PolyTree intersectionResult = new PolyTree(); clipper.Execute(ClipType.ctIntersection, intersectionResult); Polygons outputPolygons = Clipper.ClosedPathsFromPolyTree(intersectionResult); Clipper.ReversePaths(outputPolygons); return outputPolygons; }