public static List<List<Vector2>> clip(List<Vector2> boundary, List<Vector2> region) { Polygons boundaryPoly = createPolygons(boundary); Polygons regionPoly = createPolygons(region); //clip triangular polygon against the boundary polygon Polygons result = new Polygons(); Clipper c = new Clipper(); c.AddPaths(regionPoly, PolyType.ptClip, true); c.AddPaths(boundaryPoly, PolyType.ptSubject, true); c.Execute(ClipType.ctIntersection, result, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); List<List<Vector2>> clippedPolygons = new List<List<Vector2>>(); foreach (Polygon poly in result) { List<Vector2> clippedPoly = new List<Vector2>(); foreach (IntPoint p in poly) { clippedPoly.Add(new Vector2(p.X, p.Y) / multiplier); } clippedPolygons.Add(clippedPoly); } return clippedPolygons; }
/// <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(); } }
//Apply a polygon clipper operation on subject vertices using cut vertices public static List<Vector2[]> ClipPoly(Vector2[] subject, Vector2[] cut, ClipType operation) { List<Vector2[]> cutPolygons = new List<Vector2[]>(); Paths subj = new Paths(1); subj.Add(Vector2ToIntList(subject)); Paths clip = new Paths(1); clip.Add(Vector2ToIntList(cut)); Paths solution = new Paths(); Clipper c = new Clipper(); c.AddPaths(subj, PolyType.ptSubject, true); c.AddPaths(clip, PolyType.ptClip, true); c.Execute(operation,solution, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); /* for(int i = 0; i<solution.Count; i++){ if( Mathf.Abs((float)Clipper.Area(solution[i])) > ignoreArea){ cutPolygons.Add( IntListToVector2( solution[i] )); } } */ return IntListsToVector2(solution); }
public static XYPolygon GetIntersection(XYPolygon pol1, XYPolygon pol2) { List<List<IntPoint>> subj = new List<List<IntPoint>>(); subj.Add(new List<IntPoint>(pol1.Points.Count)); foreach(var p in pol1.Points) subj[0].Add(new IntPoint(p.X, p.Y)); List<List<IntPoint>> clip = new List<List<IntPoint>>(); clip.Add(new List<IntPoint>(pol2.Points.Count)); foreach (var p in pol2.Points) clip[0].Add(new IntPoint(p.X, p.Y)); List<List<IntPoint>> solution = new List<List<IntPoint>>(); Clipper c = new Clipper(); c.AddPaths(subj, PolyType.ptSubject, true); c.AddPaths(clip, PolyType.ptClip, true); c.Execute(ClipType.ctIntersection, solution, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); XYPolygon ToReturn = new XYPolygon(); if (solution.Count > 0) foreach (var p in solution[0]) ToReturn.Points.Add(new XYPoint(p.X,p.Y)); return ToReturn; }
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 List<List<IntPoint>> Run (List<List<IntPoint>> subject, double scale) { var c = ToClipper (scale, clip); var clipper = new Clipper (); clipper.AddPaths (subject, PolyType.ptSubject, subjectClosed); clipper.AddPaths (c, PolyType.ptClip, true); var solution = new List<List<IntPoint>> (); clipper.Execute (type, solution, subjectFill, clipFill); return solution; }
public double FitnessFunctionG() { // return 3; double fitnessFunctionVar = 0; int precision = 100; Clipper cc = new ClipperLib.Clipper(); Paths solution = new Paths(); Paths subjects = new Paths(); Paths clips = new Paths(); foreach (Room room in collection) { subjects.Add(new Path(room.GetClipperLibPath(precision))); } IntPoint boundaryA = new IntPoint( (int)((boundary.GetBoundingBox(false).Center.X - boundary.GetBoundingBox(false).Diagonal.X / 2) * precision) , (int)((boundary.GetBoundingBox(false).Center.Y - boundary.GetBoundingBox(false).Diagonal.Y / 2) * precision)); IntPoint boundaryB = new IntPoint( (int)((boundary.GetBoundingBox(false).Center.X - boundary.GetBoundingBox(false).Diagonal.X / 2) * precision) , (int)((boundary.GetBoundingBox(false).Center.Y + boundary.GetBoundingBox(false).Diagonal.Y / 2) * precision)); IntPoint boundaryC = new IntPoint( (int)((boundary.GetBoundingBox(false).Center.X + boundary.GetBoundingBox(false).Diagonal.X / 2) * precision) , (int)((boundary.GetBoundingBox(false).Center.Y + boundary.GetBoundingBox(false).Diagonal.Y / 2) * precision)); IntPoint boundaryD = new IntPoint( (int)((boundary.GetBoundingBox(false).Center.X + boundary.GetBoundingBox(false).Diagonal.X / 2) * precision) , (int)((boundary.GetBoundingBox(false).Center.Y - boundary.GetBoundingBox(false).Diagonal.Y / 2) * precision)); clips.Add(new Path(new List <IntPoint>() { boundaryA, boundaryB, boundaryC, boundaryD })); cc.AddPaths(subjects, PolyType.ptSubject, true); cc.AddPaths(clips, PolyType.ptClip, true); cc.Execute(ClipType.ctIntersection, solution, PolyFillType.pftNonZero, PolyFillType.pftNonZero); foreach (Path path in solution) { fitnessFunctionVar += Clipper.Area(path); } return(fitnessFunctionVar); }
public double RelativeAreaDiff (List<List<IntPoint>> actual, List<List<IntPoint>>expected) { var expectedArea = expected.Sum (path => Clipper.Area (path)); var difference = new List<List<IntPoint>> (); var clipper = new Clipper (); clipper.AddPaths (actual, PolyType.ptSubject, true); clipper.AddPaths (expected, PolyType.ptClip, true); clipper.Execute (ClipType.ctXor, difference, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); var differenceArea = difference.Sum (path => Clipper.Area (path)); return Math.Abs (differenceArea) / Math.Abs (expectedArea); }
/// <summary> /// Checks if polygons are intersecting /// </summary> /// <param name="p1">Subject polygon</param> /// <param name="p2">Clip polygon(s)</param> /// <returns>true if intersects</returns> public static bool IsIntersects(Paths p1, params Paths[] p2) { Clipper c = new Clipper(); Paths solution = new Paths(); c.AddPaths(p1, PolyType.ptSubject, true); for (int i = 0; i < p2.Length; i++) c.AddPaths(p2[i], PolyType.ptClip, true); c.Execute(ClipType.ctIntersection, solution, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); return solution.Count != 0; }
public static Paths ClipPolygons(List<Polygon> polygons) { var subj = new Paths(polygons.Count); var clip = new Paths(polygons.Count); foreach (var polygon in polygons) { subj.Add(polygon.ToClipperPath()); clip.Add(polygon.ToClipperPath()); } var solution = new Paths(); var c = new Clipper(); c.AddPaths(subj, PolyType.ptSubject, true); c.AddPaths(clip, PolyType.ptClip, true); c.Execute(ClipType.ctUnion, solution, PolyFillType.pftPositive, PolyFillType.pftEvenOdd); return solution; }
public static List<Vector2> Add(this Shape shape, Shape secondShape, Action<Shape> completed) { List<Vector2> points = new List<Vector2>(); Clipper c = new Clipper(); List<List<IntPoint>> subj = new List<List<IntPoint>>(); List<List<IntPoint>> clip = new List<List<IntPoint>>(); List<List<IntPoint>> solution = new List<List<IntPoint>>(); List<IntPoint> p1 = new List<IntPoint>(); List<IntPoint> p2 = new List<IntPoint>(); int i = 0, l = shape.Points.Length; Vector2 pos = shape.BuiltGameObject.transform.position; for(;i<l;++i) { IntPoint ip = new IntPoint(shape.Points[i].x + pos.x,shape.Points[i].y + pos.y); p1.Add(ip); } p1.Add(p1[0]); pos = secondShape.BuiltGameObject.transform.position; i = 0; l = secondShape.Points.Length; for(;i<l;++i) { IntPoint ip = new IntPoint(secondShape.Points[i].x + pos.x,secondShape.Points[i].y + pos.y); p2.Add(ip); } p2.Add(p2[0]); subj.Add(p1); clip.Add(p2); c.AddPaths(subj,PolyType.ptSubject,true); c.AddPaths(clip,PolyType.ptClip,true); c.Execute(ClipType.ctUnion,solution); i = 0; l = solution[0].Count; for(;i<l;++i) { float x = System.Convert.ToSingle(solution[0][i].X); float y = System.Convert.ToSingle(solution[0][i].Y); points.Add(new Vector2(x,y)); } Mesh2D.Instance.ReBuild(shape.BuiltGameObject,points,completed,shape.Col); return points; }
private PathStorage CombinePaths(IVertexSource a, IVertexSource b, ClipType clipType) { List<List<IntPoint>> aPolys = VertexSourceToClipperPolygons.CreatePolygons(a); List<List<IntPoint>> bPolys = VertexSourceToClipperPolygons.CreatePolygons(b); Clipper clipper = new Clipper(); clipper.AddPaths(aPolys, PolyType.ptSubject, true); clipper.AddPaths(bPolys, PolyType.ptClip, true); List<List<IntPoint>> intersectedPolys = new List<List<IntPoint>>(); clipper.Execute(clipType, intersectedPolys); PathStorage output = VertexSourceToClipperPolygons.CreatePathStorage(intersectedPolys); output.Add(0, 0, ShapePath.FlagsAndCommand.CommandStop); return output; }
List<IntPoint> GetArea(Mesh mesh) { int[] triangles = mesh.triangles; Vector3[] vertices = mesh.vertices; List<Vector2> list = new List<Vector2>(); //Debug.Log("ver count: " + vertices.Length + " triangle: " + triangles.Length); float y = float.MinValue; for(int i=0; i < triangles.Length; i = i+3){ Vector3 p0 = vertices[triangles[i]]; Vector3 p1 = vertices[triangles[i+1]]; Vector3 p2 = vertices[triangles[i+2]]; if(Approximately(p0.y, p1.y) && Approximately(p0.y, p2.y)){ //Debug.Log(string.Format("({0}, {1}, {2})", p0, p1, p2)); if(y == float.MinValue){ y = p0.y; } if(Approximately(p0.y, y)){ list.Add(new Vector2(p0.x, p0.z)); list.Add(new Vector2(p1.x, p1.z)); list.Add(new Vector2(p2.x, p2.z)); } } } //Debug.Log("list: " + list.Count); List<List<IntPoint>> paths = new List<List<IntPoint>>(); for(int i=0; i < list.Count; i = i+3){ List<IntPoint> path = new List<IntPoint>(); for(int j=0; j < 3; j++){ path.Add(new IntPoint(list[i+j].x * clipScalling, list[i+j].y * clipScalling)); } paths.Add(path); } Clipper clipper = new Clipper(); List<List<IntPoint>> solution = new List<List<IntPoint>>(); clipper.AddPaths(paths, PolyType.ptSubject, true); clipper.Execute(ClipType.ctUnion, solution); if(solution.Count > 0){ return solution[0]; } return null; }
/// <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())); }
public static void GenerateLinePaths(Polygons polygonToInfill, ref Polygons infillLinesToPrint, int lineSpacing, int infillExtendIntoPerimeter_um, double rotation, long rotationOffset = 0) { if (polygonToInfill.Count > 0) { Polygons outlines = polygonToInfill.Offset(infillExtendIntoPerimeter_um); if (outlines.Count > 0) { PointMatrix matrix = new PointMatrix(-(rotation + 90)); // we are rotating the part so we rotate by the negative so the lines go the way we expect outlines.ApplyMatrix(matrix); Aabb boundary = new Aabb(outlines); boundary.min.X = ((boundary.min.X / lineSpacing) - 1) * lineSpacing - rotationOffset; int xLineCount = (int)((boundary.max.X - boundary.min.X + (lineSpacing - 1)) / lineSpacing); Polygons unclipedPatern = new Polygons(); long firstX = boundary.min.X / lineSpacing * lineSpacing; for (int lineIndex = 0; lineIndex < xLineCount; lineIndex++) { Polygon line = new Polygon(); line.Add(new IntPoint(firstX + lineIndex * lineSpacing, boundary.min.Y)); line.Add(new IntPoint(firstX + lineIndex * lineSpacing, boundary.max.Y)); unclipedPatern.Add(line); } PolyTree ret = new PolyTree(); Clipper clipper = new Clipper(); clipper.AddPaths(unclipedPatern, PolyType.ptSubject, false); clipper.AddPaths(outlines, PolyType.ptClip, true); clipper.Execute(ClipType.ctIntersection, ret, PolyFillType.pftPositive, PolyFillType.pftEvenOdd); Polygons newSegments = Clipper.OpenPathsFromPolyTree(ret); PointMatrix inversematrix = new PointMatrix((rotation + 90)); newSegments.ApplyMatrix(inversematrix); infillLinesToPrint.AddRange(newSegments); } } }
public void OriginalClipperFileBasedTest() { var testData = LoadTestHelper.LoadFromFile("TestData/tests.txt"); foreach (var test in testData.Values) { var subjects = test.Subjects.ToOriginal(); var clips = test.Clips.ToOriginal(); var clipper = new ClipperLib.Clipper(); clipper.AddPaths(subjects, PolyType.ptSubject, true); clipper.AddPaths(clips, PolyType.ptClip, true); var originalSolution = new List <List <ClipperLib.IntPoint> >(); var clipType = (ClipType)Enum.Parse(typeof(ClipType), $"ct{test.ClipOperation}", true); var fillType = (PolyFillType)Enum.Parse(typeof(PolyFillType), $"pft{test.FillType}", true); Assert.IsTrue(clipper.Execute(clipType, originalSolution, fillType)); Assert.AreEqual(test.Solution.Count, originalSolution.Count, test.Caption); var solution = originalSolution.ToNew(); // TODO: reinclude these tests once test data is verified. var ignoreTestNumbers = new[] { 36, 38, 39, 44, 46, 48, 51, 52, 59, 64, 67, 69 }; if (ignoreTestNumbers.Contains(test.TestNumber)) { continue; } Assert.AreEqual(test.Solution.Count, solution.Count, $"{test.TestNumber}: {test.Caption}"); // Match points, THIS IS DESTRUCTIVE TO BOTH THE TEST DATA AND RESULT DATA. Assert.IsTrue(AreSame(test, solution)); // If we had an exact match then both solutions should now be empty. Assert.AreEqual(0, test.Solution.Count, $"{test.TestNumber}: {test.Caption}"); Assert.AreEqual(0, solution.Count, $"{test.TestNumber}: {test.Caption}"); } }
private PathStorage CombinePaths(IVertexSource a, IVertexSource b, ClipType clipType) { List<List<IntPoint>> aPolys = CreatePolygons(a); List<List<IntPoint>> bPolys = CreatePolygons(b); Clipper clipper = new Clipper(); clipper.AddPaths(aPolys, PolyType.ptSubject, true); clipper.AddPaths(bPolys, PolyType.ptClip, true); List<List<IntPoint>> intersectedPolys = new List<List<IntPoint>>(); clipper.Execute(clipType, intersectedPolys); PathStorage output = new PathStorage(); foreach (List<IntPoint> polygon in intersectedPolys) { bool first = true; foreach (IntPoint point in polygon) { if (first) { output.Add(point.X / 1000.0, point.Y / 1000.0, ShapePath.FlagsAndCommand.CommandMoveTo); first = false; } else { output.Add(point.X / 1000.0, point.Y / 1000.0, ShapePath.FlagsAndCommand.CommandLineTo); } } output.ClosePolygon(); } output.Add(0, 0, ShapePath.FlagsAndCommand.CommandStop); return output; }
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); }
private static void TestCliper() { Paths subj = new Paths(2); subj.Add(new Path(4)); subj[0].Add(new IntPoint(180, 200)); subj[0].Add(new IntPoint(260, 200)); subj[0].Add(new IntPoint(260, 150)); subj[0].Add(new IntPoint(180, 150)); subj.Add(new Path(3)); subj[1].Add(new IntPoint(215, 160)); subj[1].Add(new IntPoint(230, 190)); subj[1].Add(new IntPoint(200, 190)); Paths clip = new Paths(1); clip.Add(new Path(4)); clip[0].Add(new IntPoint(190, 210)); clip[0].Add(new IntPoint(240, 210)); clip[0].Add(new IntPoint(240, 130)); clip[0].Add(new IntPoint(190, 130)); //DrawPolygons(subj, Color.FromArgb(0x16, 0, 0, 0xFF), // Color.FromArgb(0x60, 0, 0, 0xFF)); //DrawPolygons(clip, Color.FromArgb(0x20, 0xFF, 0xFF, 0), // Color.FromArgb(0x30, 0xFF, 0, 0)); Paths solution = new Paths(); Clipper c = new Clipper(); c.AddPaths(subj, PolyType.ptSubject, true); c.AddPaths(clip, PolyType.ptClip, true); c.Execute(ClipType.ctIntersection, solution, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); //DrawPolygons(solution, Color.FromArgb(0x30, 0, 0xFF, 0), // Color.FromArgb(0xFF, 0, 0x66, 0)); }
public static List<Polygons> ProcessIntoSeparatIslands(this Polygons polygons) { List<Polygons> ret = new List<Polygons>(); Clipper clipper = new Clipper(); PolyTree resultPolyTree = new PolyTree(); clipper.AddPaths(polygons, PolyType.ptSubject, true); clipper.Execute(ClipType.ctUnion, resultPolyTree); polygons.ProcessPolyTreeNodeIntoSeparatIslands(resultPolyTree, ret); return ret; }
public static Polygons CreateLineIntersections(this Polygons polygons, Polygons other) { Clipper clipper = new Clipper(); clipper.AddPaths(other, PolyType.ptSubject, false); clipper.AddPaths(polygons, PolyType.ptClip, true); PolyTree clippedLines = new PolyTree(); clipper.Execute(ClipType.ctIntersection, clippedLines); return Clipper.OpenPathsFromPolyTree(clippedLines); }
public static Polygons CreateIntersection(this Polygons polygons, Polygons other) { Polygons ret = new Polygons(); Clipper clipper = new Clipper(); clipper.AddPaths(polygons, PolyType.ptSubject, true); clipper.AddPaths(other, PolyType.ptClip, true); clipper.Execute(ClipType.ctIntersection, ret); return ret; }
public JsonResult GetBySponsor(ObjectId id) { var spots = Context.SponsorSpots.GetSpotsBySponsors(id); Clipper clipper = new Clipper(); var polygons = new List<List<IntPoint>>(); var scale = 100000000.0; foreach (var spot in spots) { var polygon = new List<IntPoint>(); foreach (var coord in spot.SpotShape) { polygon.Add(new IntPoint(coord.Longitude * scale, coord.Latitude * scale)); } polygons.Add(polygon); } var solution = new List<List<IntPoint>>(); clipper.AddPaths(polygons, PolyType.ptSubject, true); clipper.Execute(ClipType.ctUnion, solution, PolyFillType.pftNonZero, PolyFillType.pftNonZero); var results = new List<Spot>(); foreach (var shape in solution) { var resultShape = new Spot(); foreach (var item in shape) { resultShape.SpotShape.Add(new Coordinate { Latitude = item.Y / scale, Longitude = item.X / scale }); } results.Add(resultShape); } return Json(new { success = true, results = results }, JsonRequestBehavior.AllowGet); }
/// <summary> /// Joins all the polygones in the list in one polygone if they interect. /// </summary> /// <param name="sList">The polygon list.</param> /// <returns></returns> public static List<Polygon> ELJoinPolygons(this List<Polygon> sList) { var p = ELClipPolygons(sList); var tList = new List<List<IntPoint>>(); var c = new Clipper(); c.AddPaths(p, PolyType.ptClip, true); c.Execute(ClipType.ctUnion, tList, PolyFillType.pftNonZero, PolyFillType.pftNonZero); return ToPolygons(tList); }
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; }
//////////////////////////////////////////////// static void Main(string[] args) { ////quick test with random polygons ... //Paths ss = new Paths(1), cc = new Paths(1), sss = new Paths(); //Random r = new Random((int)DateTime.Now.Ticks); //int scale = 1000000000; //tests 128bit math //ss.Add(MakeRandomPolygon(r, 400, 350, 9, scale)); //cc.Add(MakeRandomPolygon(r, 400, 350, 9, scale)); //Clipper cpr = new Clipper(); //cpr.AddPaths(ss, PolyType.ptSubject, true); //cpr.AddPaths(cc, PolyType.ptClip, true); //cpr.Execute(ClipType.ctUnion, sss, PolyFillType.pftNonZero, PolyFillType.pftNonZero); //sss = Clipper.OffsetPolygons(sss, -5.0 * scale, JoinType.jtMiter, 4); //SVGBuilder svg1 = new SVGBuilder(); //svg1.style.brushClr = Color.FromArgb(0x20, 0, 0, 0x9c); //svg1.style.penClr = Color.FromArgb(0xd3, 0xd3, 0xda); //svg1.AddPaths(ss); //svg1.style.brushClr = Color.FromArgb(0x20, 0x9c, 0, 0); //svg1.style.penClr = Color.FromArgb(0xff, 0xa0, 0x7a); //svg1.AddPaths(cc); //svg1.style.brushClr = Color.FromArgb(0xAA, 0x80, 0xff, 0x9c); //svg1.style.penClr = Color.FromArgb(0, 0x33, 0); //svg1.AddPaths(sss); //svg1.SaveToFile("solution.svg", 1.0 / scale); //return; if (args.Length < 5) { string appname = System.Environment.GetCommandLineArgs()[0]; appname = System.IO.Path.GetFileName(appname); Console.WriteLine(""); Console.WriteLine("Usage:"); Console.WriteLine(" {0} CLIPTYPE s_file c_file INPUT_DEC_PLACES SVG_SCALE [S_FILL, C_FILL]", appname); Console.WriteLine(" where ..."); Console.WriteLine(" CLIPTYPE = INTERSECTION|UNION|DIFFERENCE|XOR"); Console.WriteLine(" FILLMODE = NONZERO|EVENODD"); Console.WriteLine(" INPUT_DEC_PLACES = signific. decimal places for subject & clip coords."); Console.WriteLine(" SVG_SCALE = scale of SVG image as power of 10. (Fractions are accepted.)"); Console.WriteLine(" both S_FILL and C_FILL are optional. The default is EVENODD."); Console.WriteLine("Example:"); Console.WriteLine(" Intersect polygons, rnd to 4 dec places, SVG is 1/100 normal size ..."); Console.WriteLine(" {0} INTERSECTION subj.txt clip.txt 0 0 NONZERO NONZERO", appname); return; } ClipType ct; switch (args[0].ToUpper()) { case "INTERSECTION": ct = ClipType.ctIntersection; break; case "UNION": ct = ClipType.ctUnion; break; case "DIFFERENCE": ct = ClipType.ctDifference; break; case "XOR": ct = ClipType.ctXor; break; default: Console.WriteLine("Error: invalid operation - {0}", args[0]); return; } string subjFilename = args[1]; string clipFilename = args[2]; if (!File.Exists(subjFilename)) { Console.WriteLine("Error: file - {0} - does not exist.", subjFilename); return; } if (!File.Exists(clipFilename)) { Console.WriteLine("Error: file - {0} - does not exist.", clipFilename); return; } int decimal_places = 0; if (!Int32.TryParse(args[3], out decimal_places)) { Console.WriteLine("Error: invalid number of decimal places - {0}", args[3]); return; } if (decimal_places > 8) decimal_places = 8; else if (decimal_places < 0) decimal_places = 0; double svg_scale = 0; if (!double.TryParse(args[4], out svg_scale)) { Console.WriteLine("Error: invalid value for SVG_SCALE - {0}", args[4]); return; } if (svg_scale < -18) svg_scale = -18; else if (svg_scale > 18) svg_scale = 18; svg_scale = Math.Pow(10, svg_scale - decimal_places);//nb: also compensate for decimal places PolyFillType pftSubj = PolyFillType.pftEvenOdd; PolyFillType pftClip = PolyFillType.pftEvenOdd; if (args.Length > 6) { switch (args[5].ToUpper()) { case "EVENODD": pftSubj = PolyFillType.pftEvenOdd; break; case "NONZERO": pftSubj = PolyFillType.pftNonZero; break; default: Console.WriteLine("Error: invalid cliptype - {0}", args[5]); return; } switch (args[6].ToUpper()) { case "EVENODD": pftClip = PolyFillType.pftEvenOdd; break; case "NONZERO": pftClip = PolyFillType.pftNonZero; break; default: Console.WriteLine("Error: invalid cliptype - {0}", args[6]); return; } } Paths subjs = new Paths(); Paths clips = new Paths(); if (!LoadFromFile(subjFilename, subjs, decimal_places)) { Console.WriteLine("Error processing subject polygons file - {0} ", subjFilename); OutputFileFormat(); return; } if (!LoadFromFile(clipFilename, clips, decimal_places)) { Console.WriteLine("Error processing clip polygons file - {0} ", clipFilename); OutputFileFormat(); return; } Console.WriteLine("wait ..."); Clipper cp = new Clipper(); cp.AddPaths(subjs, PolyType.ptSubject, true); cp.AddPaths(clips, PolyType.ptClip, true); Paths solution = new Paths(); //Paths solution = new Paths(); if (cp.Execute(ct, solution, pftSubj, pftClip)) { SaveToFile("solution.txt", solution, decimal_places); //solution = Clipper.OffsetPolygons(solution, -4, JoinType.jtRound); SVGBuilder svg = new SVGBuilder(); svg.style.brushClr = Color.FromArgb(0x20, 0, 0, 0x9c); svg.style.penClr = Color.FromArgb(0xd3, 0xd3, 0xda); svg.AddPaths(subjs); svg.style.brushClr = Color.FromArgb(0x20, 0x9c, 0, 0); svg.style.penClr = Color.FromArgb(0xff, 0xa0, 0x7a); svg.AddPaths(clips); svg.style.brushClr = Color.FromArgb(0xAA, 0x80, 0xff, 0x9c); svg.style.penClr = Color.FromArgb(0, 0x33, 0); svg.AddPaths(solution); svg.SaveToFile("solution.svg", svg_scale); Console.WriteLine("finished!"); } else { Console.WriteLine("failed!"); } }
private void SetTest() { if (!_testData.ContainsKey(_testNumber)) { return; } // Get test of interest. var test = _testData[_testNumber]; _testSubject = test.Subjects.FirstOrDefault(); _testClip = test.Clips.FirstOrDefault(); _testSolution = test.Solution; _newClipperSolution = new PolygonPath(); var clipper = new Clipper.Clipper(); clipper.AddPath(test.Subjects, PolygonKind.Subject); clipper.AddPath(test.Clips, PolygonKind.Clip); clipper.Execute(ClipOperation.Union, _newClipperSolution); _testBoundary = _testSolution.Any() ? BoundaryBuilder .BuildPolygonBoundary(_testSolution.First(), PolygonKind.Subject) .ToList() : null; _newClipperBoundary = _newClipperSolution.Any() ? BoundaryBuilder .BuildPolygonBoundary(_newClipperSolution.First(), PolygonKind.Subject) .ToList() : null; var originalClipperSolution = new List <List <IntPoint> >(); var originalClipper = new ClipperLib.Clipper(); originalClipper.AddPaths(test.Subjects.ToOriginal(), PolyType.ptSubject, true); originalClipper.AddPaths(test.Clips.ToOriginal(), PolyType.ptClip, true); originalClipper.Execute(ClipType.ctUnion, originalClipperSolution); _originalClipperSolution = originalClipperSolution.ToNew(); _originalClipperBoundary = _originalClipperSolution.Any() ? BoundaryBuilder .BuildPolygonBoundary(_originalClipperSolution.First(), PolygonKind.Subject) .ToList() : null; Program.VisualizerForm.ClipperViewControl.Subjects = new[] { new PolygonViewModel { IsOpen = false, EdgeColor = Color.LawnGreen, VertexColor = Color.DarkGreen, Items = _testSubject?.ToVertices() } }; Program.VisualizerForm.ClipperViewControl.Clips = new[] { new PolygonViewModel { IsOpen = false, EdgeColor = Color.Blue, VertexColor = Color.DarkBlue, Items = _testClip?.ToVertices() } }; _solutionType = SolutionType.Test; SetSolution(); solutionComboBox.SelectedIndex = 0; }
private static Vector2 getCollisionPointClosest(PolygonSet p1, PolygonSet p2, Vector2 point) { Clipper clipper = new Clipper(); PolygonSet result = new PolygonSet(); clipper.AddPaths(p1, PolyType.ptClip, true); clipper.AddPaths(p2, PolyType.ptSubject, true); bool succeeded = clipper.Execute(ClipType.ctIntersection, result, PolyFillType.pftPositive, PolyFillType.pftPositive); clipper.Clear(); return getClosestPoint(result, new Vector2(point.X,point.Y)); }
public static void GenerateHexLinePaths(Polygons in_outline, ref Polygons result, int lineSpacing, int infillExtendIntoPerimeter_um, double rotationDegrees, int layerIndex) { int extraRotationAngle = 0; if (in_outline.Count > 0) { Polygons outlines = in_outline.Offset(infillExtendIntoPerimeter_um); if (outlines.Count > 0) { int perIncrementOffset = (int)(lineSpacing * Math.Sqrt(3) / 2 + .5); PointMatrix matrix = new PointMatrix(-(rotationDegrees + extraRotationAngle)); // we are rotating the part so we rotate by the negative so the lines go the way we expect outlines.ApplyMatrix(matrix); Aabb boundary = new Aabb(outlines); boundary.min.X = ((boundary.min.X / lineSpacing) - 1) * lineSpacing; boundary.min.Y = ((boundary.min.Y / perIncrementOffset) - 2) * perIncrementOffset; boundary.max.X += lineSpacing; boundary.max.Y += perIncrementOffset; Polygons unclipedPatern = new Polygons(); foreach (IntPoint startPoint in StartPositionIterator(boundary, lineSpacing, layerIndex)) { Polygon attachedLine = new Polygon(); foreach (IntPoint center in IncrementPositionIterator(startPoint, boundary, lineSpacing, layerIndex)) { // what we are adding are the little plusses that define the points // | top // | // /\ center // left/ \ right // IntPoint left = center + new IntPoint(-lineSpacing / 2, -perIncrementOffset / 3); IntPoint right = center + new IntPoint(lineSpacing / 2, -perIncrementOffset / 3); IntPoint top = center + new IntPoint(0, perIncrementOffset * 2 / 3); switch (layerIndex % 3) { case 0: // left to right attachedLine.Add(left); attachedLine.Add(center); attachedLine.Add(center); attachedLine.Add(right); unclipedPatern.Add(new Polygon() { top, center }); break; case 1: // left to top attachedLine.Add(left); attachedLine.Add(center); attachedLine.Add(center); attachedLine.Add(top); unclipedPatern.Add(new Polygon() { center, right }); break; case 2: // top to right attachedLine.Add(top); attachedLine.Add(center); attachedLine.Add(center); attachedLine.Add(right); unclipedPatern.Add(new Polygon() { left, center }); break; } } if (attachedLine.Count > 0) { unclipedPatern.Add(attachedLine); } } PolyTree ret = new PolyTree(); Clipper clipper = new Clipper(); clipper.AddPaths(unclipedPatern, PolyType.ptSubject, false); clipper.AddPaths(outlines, PolyType.ptClip, true); clipper.Execute(ClipType.ctIntersection, ret, PolyFillType.pftPositive, PolyFillType.pftEvenOdd); Polygons newSegments = Clipper.OpenPathsFromPolyTree(ret); PointMatrix inversematrix = new PointMatrix((rotationDegrees + extraRotationAngle)); newSegments.ApplyMatrix(inversematrix); result.AddRange(newSegments); } } }
public static Polygons CreateUnion(this Polygons polygons, Polygons other) { Clipper clipper = new Clipper(); clipper.AddPaths(polygons, PolyType.ptSubject, true); clipper.AddPaths(other, PolyType.ptSubject, true); Polygons ret = new Polygons(); clipper.Execute(ClipType.ctUnion, ret, PolyFillType.pftNonZero, PolyFillType.pftNonZero); return ret; }
public static Polygons ProcessEvenOdd(this Polygons polygons) { Polygons ret = new Polygons(); Clipper clipper = new Clipper(); clipper.AddPaths(polygons, PolyType.ptSubject, true); clipper.Execute(ClipType.ctUnion, ret); return ret; }
/// <summary> /// Checks if polygons are intersecting /// </summary> /// <param name="p1">Subject polygon</param> /// <param name="p2">Clip polygon(s)</param> /// <returns>true if intersects</returns> public static bool IsIntersects(Paths p1, params Paths[] p2) { var c = new Clipper(); var solution = new Paths(); c.AddPaths(p1, PolyType.ptSubject, true); foreach (Paths t in p2) c.AddPaths(t, PolyType.ptClip, true); c.Execute(ClipType.ctIntersection, solution, PolyFillType.pftEvenOdd, PolyFillType.pftEvenOdd); return solution.Count != 0; }
private static void InterceptionQ(Obj_AI_Hero Enemy) { Geometry.Polygon.Circle Qspellpoly = new Geometry.Polygon.Circle(PreCastPos(Enemy, 0.6f), 130f); Paths subjs = new Paths(); foreach (var bla in WPPolygon(Enemy).ToPolygons()) { subjs.Add(bla.ToClipperPath()); } Paths clips = new Paths(1); clips.Add(Qspellpoly.ToClipperPath()); Paths solution = new Paths(); Clipper c = new Clipper(); c.AddPaths(subjs, PolyType.ptSubject, true); c.AddPaths(clips, PolyType.ptClip, true); c.Execute(ClipType.ctIntersection, solution); foreach (var bli in solution.ToPolygons()) { bli.Draw(System.Drawing.Color.Blue); } }
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); }
/// <summary> /// Joins all the polygones. /// ClipType: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/ClipType.htm /// PolyFillType: http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/PolyFillType.htm /// </summary> /// <param name="sList">The s list.</param> /// <param name="cType">Type of the c.</param> /// <param name="pType">Type of the p.</param> /// <param name="pFType1">The p f type1.</param> /// <param name="pFType2">The p f type2.</param> /// <returns></returns> public static List<Polygon> ELJoinPolygons(this List<Polygon> sList, ClipType cType, PolyType pType = PolyType.ptClip, PolyFillType pFType1 = PolyFillType.pftNonZero, PolyFillType pFType2 = PolyFillType.pftNonZero) { var p = ELClipPolygons(sList); var tList = new List<List<IntPoint>>(); var c = new Clipper(); c.AddPaths(p, pType, true); c.Execute(cType, tList, pFType1, pFType2); return ToPolygons(tList); }
public static Polygons CreateLineDifference(this Polygons linePolygons, Polygons removePolygons) { Clipper clipper = new Clipper(); clipper.AddPaths(linePolygons, PolyType.ptSubject, false); clipper.AddPaths(removePolygons, PolyType.ptClip, true); PolyTree clippedLines = new PolyTree(); clipper.Execute(ClipType.ctDifference, clippedLines); return Clipper.OpenPathsFromPolyTree(clippedLines); }
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; }
/// <summary> /// Performs the Boolean Operations from the Clipper Library /// </summary> /// <param name="clipType"></param> /// <param name="subject"></param> /// <param name="clip"></param> /// <param name="simplifyPriorToBooleanOperation"></param> /// <param name="scale"></param> /// <param name="fillMethod"></param> /// <returns></returns> /// <exception cref="Exception"></exception> private static List <Polygon> BooleanViaClipper(PolyFillType fillMethod, ClipType clipType, IEnumerable <Polygon> subject, IEnumerable <Polygon> clip = null, bool subjectIsClosed = true, bool clipIsClosed = true) { //Remove any polygons that are only a line. //subject = subject.Where(p => p.Count > 2); //clip = clip?.Where(p => p.Count > 2); var simplifyPriorToBooleanOperation = true; if (simplifyPriorToBooleanOperation) { //subject = subject.Select(p=>SimplifyFuzzy(p)); subject = subject.Select(p => Simplify(p, 0.0000003)); } if (simplifyPriorToBooleanOperation) { //If not null //clip = clip?.Select(p => SimplifyFuzzy(p)); clip = clip?.Select(p => Simplify(p, 0.0000003)); } if (!subject.Any()) { if (clip == null || !clip.Any()) { return(new List <Polygon>()); } //Use the clip as the subject if this is a union operation and the clip is not null. if (clipType == ClipType.ctUnion) { subject = clip; clip = null; } } var subjectAll = subject.SelectMany(p => p.AllPolygons).ToList(); var clipperSolution = new List <List <IntPoint> >(); //Convert Points (TVGL) to IntPoints (Clipper) var clipperSubject = subjectAll.Select(loop => loop.Vertices.Select(point => new IntPoint(point.X * scale, point.Y * scale)).ToList()).ToList(); //Setup Clipper var clipper = new ClipperLib.Clipper() { StrictlySimple = true }; clipper.AddPaths(clipperSubject, PolyType.ptSubject, subjectIsClosed); if (clip != null) { var clipAll = clip.SelectMany(p => p.AllPolygons).ToList(); var clipperClip = clipAll.Select(loop => loop.Vertices.Select(point => new IntPoint(point.X * scale, point.Y * scale)).ToList()).ToList(); clipper.AddPaths(clipperClip, PolyType.ptClip, clipIsClosed); } //Begin an evaluation var result = clipper.Execute(clipType, clipperSolution, fillMethod, fillMethod); if (!result) { throw new Exception("Clipper Union Failed"); } //Convert back to points and return solution var solution = clipperSolution.Select(clipperPath => new Polygon(clipperPath.Select(point => new Vector2(point.X / scale, point.Y / scale)))); return(solution.CreateShallowPolygonTrees(true)); }