From 2b2effd31ea64051512351460d1d6e320d5de97a Mon Sep 17 00:00:00 2001 From: phil Date: Sat, 28 Sep 2024 10:21:19 -0500 Subject: [PATCH] Proposed clean-ups This is a partial set of clean-ups. There could be others, but they may be controversial (e.g. adopting LINQ in some areas, which may or may not hurt performance). I've also tried to be mindful of target framework versions and also some edge cases (e.g. hashing) where seemingly redundant chunks of code, if removed or changed for another option, may blow things up. See what you think. --- CSharp/Clipper2Lib.Benchmark/Benchmarks.cs | 6 +- CSharp/Clipper2Lib.Benchmark/Program.cs | 2 +- .../Clipper2Lib.Examples/ConsoleDemo/Main.cs | 62 ++-- .../Clipper2Lib.Examples/InflateDemo/Main.cs | 16 +- CSharp/Clipper2Lib/Clipper.Core.cs | 65 ++-- CSharp/Clipper2Lib/Clipper.Engine.cs | 304 +++++++++--------- CSharp/Clipper2Lib/Clipper.Minkowski.cs | 2 +- CSharp/Clipper2Lib/Clipper.Offset.cs | 114 ++++--- CSharp/Clipper2Lib/Clipper.RectClip.cs | 150 ++++----- CSharp/Clipper2Lib/Clipper.cs | 94 +++--- CSharp/Clipper2Lib/HashCode.cs | 2 +- CSharp/Utils/ClipFileIO/Clipper.FileIO.cs | 24 +- CSharp/Utils/SVG/Clipper.SVG.cs | 6 +- 13 files changed, 421 insertions(+), 426 deletions(-) diff --git a/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs b/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs index 778bb654..e122d9a4 100644 --- a/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs +++ b/CSharp/Clipper2Lib.Benchmark/Benchmarks.cs @@ -36,9 +36,9 @@ public void GlobalSetup() { Random rand = new (); - _subj = new (); - _clip = new (); - _solution = new (); + _subj = new Paths64(); + _clip = new Paths64(); + _solution = new Paths64(); _subj.Add(MakeRandomPath(DisplayWidth, DisplayHeight, EdgeCount, rand)); _clip.Add(MakeRandomPath(DisplayWidth, DisplayHeight, EdgeCount, rand)); diff --git a/CSharp/Clipper2Lib.Benchmark/Program.cs b/CSharp/Clipper2Lib.Benchmark/Program.cs index 9b43d714..7855429c 100644 --- a/CSharp/Clipper2Lib.Benchmark/Program.cs +++ b/CSharp/Clipper2Lib.Benchmark/Program.cs @@ -2,7 +2,7 @@ namespace Clipper2Lib.Benchmark { - public class Program + public static class Program { public static void Main() { diff --git a/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs b/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs index 14e7811f..b52330c9 100644 --- a/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/ConsoleDemo/Main.cs @@ -13,7 +13,7 @@ namespace ClipperDemo1 { - public class Application + public static class Application { public static void Main() { @@ -32,7 +32,7 @@ public static Paths64 Polytree_Union(Paths64 subjects, FillRule fillrule) { // of course this function is inefficient, // but it's purpose is simply to test polytrees. - PolyTree64 polytree = new PolyTree64(); + PolyTree64 polytree = new(); Clipper.BooleanOp(ClipType.Union, subjects, null, polytree, fillrule); return Clipper.PolyTreeToPaths64(polytree); } @@ -41,7 +41,7 @@ public static void SquaresTest(bool test_polytree = false) { const int size = 10; const int w = 800, h = 600; - FillRule fillrule = FillRule.NonZero; + const FillRule fillrule = FillRule.NonZero; Path64 shape = Clipper.MakePath(new int[] { 0, 0, size, 0, size, size, 0, size }); Paths64 subjects = new(), solution; @@ -61,10 +61,10 @@ public static void SquaresTest(bool test_polytree = false) else solution = Clipper.Union(subjects, fillrule); - SvgWriter svg = new SvgWriter(); + SvgWriter svg = new(); SvgUtils.AddSubject(svg, subjects); SvgUtils.AddSolution(svg, solution, false); - string filename = @"..\..\..\squares.svg"; + const string filename = @"..\..\..\squares.svg"; SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10); ClipperFileIO.OpenFileWithDefaultApp(filename); } @@ -73,7 +73,7 @@ public static void TrianglesTest(bool test_polytree = false) { const int size = 10; const int w = 800, h = 600; - FillRule fillrule = FillRule.NonZero; + const FillRule fillrule = FillRule.NonZero; Path64 tri1 = Clipper.MakePath(new int[] { 0,0, size * 2,0, size,size * 2 }); Path64 tri2 = Clipper.MakePath(new int[] { size * 2, 0, size, size * 2, size*3, size*2 }); @@ -98,10 +98,10 @@ public static void TrianglesTest(bool test_polytree = false) else solution = Clipper.Union(subjects, fillrule); - SvgWriter svg = new SvgWriter(); + SvgWriter svg = new(); SvgUtils.AddSubject(svg, subjects); SvgUtils.AddSolution(svg, solution, false); - string filename = @"..\..\..\triangles.svg"; + const string filename = @"..\..\..\triangles.svg"; SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10); ClipperFileIO.OpenFileWithDefaultApp(filename); } @@ -110,7 +110,7 @@ public static void DiamondsTest(bool test_polytree = false) { const int size = 10; const int w = 800, h = 600; - FillRule fillrule = FillRule.NonZero; + const FillRule fillrule = FillRule.NonZero; Path64 shape = Clipper.MakePath(new int[] { size, 0, size * 2, size, size, size * 2, 0, size }); Paths64 subjects = new(), solution; @@ -133,36 +133,34 @@ public static void DiamondsTest(bool test_polytree = false) else solution = Clipper.Union(subjects, fillrule); - SvgWriter svg = new SvgWriter(); + SvgWriter svg = new(); SvgUtils.AddSubject(svg, subjects); SvgUtils.AddSolution(svg, solution, false); - string filename = @"..\..\..\diamonds.svg"; + const string filename = @"..\..\..\diamonds.svg"; SvgUtils.SaveToFile(svg, filename, fillrule, w, h, 10); ClipperFileIO.OpenFileWithDefaultApp(filename); } public static void LoopThruTestPolygons(int start = 0, int end = 0) { - Paths64 subject = new Paths64(); - Paths64 subject_open = new Paths64(); - Paths64 clip = new Paths64(); - Paths64 solution = new Paths64(); - Paths64 solution_open = new Paths64(); - ClipType ct; - FillRule fr; + Paths64 subject = new(); + Paths64 subject_open = new(); + Paths64 clip = new(); + Paths64 solution = new(); + Paths64 solution_open = new(); bool do_all = (start == 0 && end == 0); if (do_all) { start = 1; end = 0xFFFF; } else if (end == 0) end = start; if (do_all) Console.WriteLine("\nCount and area differences (expected vs measured):\n"); - int test_number = start; + int test_number = start; for (; test_number <= end; ++test_number) { if (!ClipperFileIO.LoadTestNum(@"..\..\..\..\..\..\Tests\Polygons.txt", - test_number, subject, subject_open, clip, - out ct, out fr, out long area, out int cnt, out _)) break; - Clipper64 c64 = new Clipper64(); + test_number, subject, subject_open, clip, + out ClipType ct, out FillRule fr, out long area, out int cnt, out _)) break; + Clipper64 c64 = new(); c64.AddSubject(subject); c64.AddOpenSubject(subject_open); c64.AddClip(clip); @@ -179,15 +177,15 @@ public static void LoopThruTestPolygons(int start = 0, int end = 0) double area_diff = area <= 0 ? 0 : Math.Abs(measuredArea / area -1.0); if (count_diff > 0.05) - Console.WriteLine(string.Format("{0}: count {1} vs {2}", test_number, cnt, measuredCnt)); + Console.WriteLine($"{test_number}: count {cnt} vs {measuredCnt}"); if (area_diff > 0.1) - Console.WriteLine(string.Format("{0}: area {1} vs {2}", test_number, area, measuredArea)); + Console.WriteLine($"{test_number}: area {area} vs {measuredArea}"); // don't display when looping through every test continue; } - SvgWriter svg = new SvgWriter(); + SvgWriter svg = new(); SvgUtils.AddSubject(svg, subject); SvgUtils.AddClip(svg, clip); if (fr == FillRule.Negative) @@ -199,11 +197,9 @@ public static void LoopThruTestPolygons(int start = 0, int end = 0) ClipperFileIO.OpenFileWithDefaultApp(filename); } - if (do_all) - { - Console.WriteLine(string.Format("\ntest ended at polygon {0}.\n", test_number)); - Console.ReadKey(); - } + if (!do_all) return; + Console.WriteLine($"\ntest ended at polygon {test_number}.\n"); + Console.ReadKey(); } public static Paths64 LoadPathsFromResource(string resourceName) @@ -231,7 +227,7 @@ public static Paths64 LoadPathsFromResource(string resourceName) public static void ClipTestPolys() { - FillRule fillrule = FillRule.NonZero; + const FillRule fillrule = FillRule.NonZero; Paths64 subject = LoadPathsFromResource("ConsoleDemo.subj.bin"); Paths64 clip = LoadPathsFromResource("ConsoleDemo.clip.bin"); Paths64 solution = Clipper.Intersect(subject, clip, fillrule); @@ -240,8 +236,8 @@ public static void ClipTestPolys() SvgUtils.AddSubject(svg, subject); SvgUtils.AddClip(svg, clip); SvgUtils.AddSolution(svg, solution, false); - SvgUtils.SaveToFile(svg, "..\\..\\..\\clipperD.svg", fillrule, 800, 600, 20); - ClipperFileIO.OpenFileWithDefaultApp("..\\..\\..\\clipperD.svg"); + SvgUtils.SaveToFile(svg, @"..\..\..\clipperD.svg", fillrule, 800, 600, 20); + ClipperFileIO.OpenFileWithDefaultApp(@"..\..\..\clipperD.svg"); } diff --git a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs index 33655c3b..2820048c 100644 --- a/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs +++ b/CSharp/Clipper2Lib.Examples/InflateDemo/Main.cs @@ -6,7 +6,6 @@ * License : http://www.boost.org/LICENSE_1_0.txt * *******************************************************************************/ -using System; using System.IO; using System.Reflection; using Clipper2Lib; @@ -14,7 +13,7 @@ namespace ClipperDemo1 { - public class Application + public static class Application { public static void Main() @@ -30,7 +29,7 @@ public static void DoSimpleShapes() ClipperOffset co = new(); //triangle offset - with large miter - Paths64 p0 = new() { Clipper.MakePath(new int[] { 30,150, 60,350, 0,350 }) }; + Paths64 p0 = new() { Clipper.MakePath(new [] { 30,150, 60,350, 0,350 }) }; Paths64 p = new(); for (int i = 0; i < 5; ++i) { @@ -44,7 +43,7 @@ public static void DoSimpleShapes() //rectangle offset - both squared and rounded //nb: using the ClipperOffest class directly here to control //different join types within the same offset operation - p.Add(Clipper.MakePath(new int[] { 100,0, 340,0, 340,200, 100,200, 100, 0 })); + p.Add(Clipper.MakePath(new [] { 100,0, 340,0, 340,200, 100,200, 100, 0 })); SvgUtils.AddOpenSubject(svg, p); co.AddPaths(p, JoinType.Bevel, EndType.Joined); @@ -57,7 +56,7 @@ public static void DoSimpleShapes() co.Execute(10, p); - string filename = "../../../inflate.svg"; + const string filename = "../../../inflate.svg"; SvgUtils.AddSolution(svg, p, false); SvgUtils.AddCaption(svg, "Beveled join", 100, -27); SvgUtils.AddCaption(svg, "Squared join", 160, 23); @@ -81,7 +80,7 @@ public static void DoRabbit() solution.AddRange(pd); } - string filename = "../../../rabbit.svg"; + const string filename = "../../../rabbit.svg"; SvgWriter svg = new (); SvgUtils.AddSolution(svg, solution, false); SvgUtils.SaveToFile(svg, filename, FillRule.EvenOdd, 450, 720, 10); @@ -118,10 +117,9 @@ public static void DoVariableOffset() ClipperOffset co = new(); co.AddPaths(p, JoinType.Square, EndType.Butt); co.Execute( - delegate (Path64 path, PathD path_norms, int currPt, int prevPt) - { return currPt* currPt + 10; } , solution); + (path, path_norms, currPt, prevPt) => currPt * currPt + 10, solution); - string filename = "../../../variable_offset.svg"; + const string filename = "../../../variable_offset.svg"; SvgWriter svg = new(); SvgUtils.AddOpenSubject(svg, p); SvgUtils.AddSolution(svg, solution, true); diff --git a/CSharp/Clipper2Lib/Clipper.Core.cs b/CSharp/Clipper2Lib/Clipper.Core.cs index dc2ee7bd..af6d2c73 100644 --- a/CSharp/Clipper2Lib/Clipper.Core.cs +++ b/CSharp/Clipper2Lib/Clipper.Core.cs @@ -537,13 +537,13 @@ public enum ClipType Union, Difference, Xor - }; + } public enum PathType { Subject, Clip - }; + } // By far the most widely used filling rules for polygons are EvenOdd // and NonZero, sometimes called Alternate and Winding respectively. @@ -554,7 +554,7 @@ public enum FillRule NonZero, Positive, Negative - }; + } // PointInPolygon internal enum PipResult @@ -562,7 +562,7 @@ internal enum PipResult Inside, Outside, OnEdge - }; + } public static class InternalClipper { @@ -612,8 +612,7 @@ internal static bool IsAlmostZero(double value) internal static int TriSign(long x) // returns 0, 1 or -1 { if (x < 0) return -1; - else if (x > 1) return 1; - else return 0; + return x > 1 ? 1 : 0; } public struct MultiplyUInt64Result @@ -725,24 +724,19 @@ public static bool GetSegmentIntersectPt(Point64 ln1a, internal static bool SegsIntersect(Point64 seg1a, Point64 seg1b, Point64 seg2a, Point64 seg2b, bool inclusive = false) { - if (inclusive) - { - double res1 = CrossProduct(seg1a, seg2a, seg2b); - double res2 = CrossProduct(seg1b, seg2a, seg2b); - if (res1 * res2 > 0) return false; - double res3 = CrossProduct(seg2a, seg1a, seg1b); - double res4 = CrossProduct(seg2b, seg1a, seg1b); - if (res3 * res4 > 0) return false; - // ensure NOT collinear - return (res1 != 0 || res2 != 0 || res3 != 0 || res4 != 0); - } - else - { - return (CrossProduct(seg1a, seg2a, seg2b) * - CrossProduct(seg1b, seg2a, seg2b) < 0) && - (CrossProduct(seg2a, seg1a, seg1b) * - CrossProduct(seg2b, seg1a, seg1b) < 0); - } + if (!inclusive) + return (CrossProduct(seg1a, seg2a, seg2b) * + CrossProduct(seg1b, seg2a, seg2b) < 0) && + (CrossProduct(seg2a, seg1a, seg1b) * + CrossProduct(seg2b, seg1a, seg1b) < 0); + double res1 = CrossProduct(seg1a, seg2a, seg2b); + double res2 = CrossProduct(seg1b, seg2a, seg2b); + if (res1 * res2 > 0) return false; + double res3 = CrossProduct(seg2a, seg1a, seg1b); + double res4 = CrossProduct(seg2b, seg1a, seg1b); + if (res3 * res4 > 0) return false; + // ensure NOT collinear + return (res1 != 0 || res2 != 0 || res3 != 0 || res4 != 0); } public static Point64 GetClosestPtOnSegment(Point64 offPt, Point64 seg1, Point64 seg2) @@ -783,14 +777,14 @@ public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon) if (isAbove) { while (i < end && polygon[i].Y < pt.Y) i++; - if (i == end) continue; } else { while (i < end && polygon[i].Y > pt.Y) i++; - if (i == end) continue; } + if (i == end) continue; + Point64 curr = polygon[i], prev; if (i > 0) prev = polygon[i - 1]; else prev = polygon[len - 1]; @@ -823,20 +817,13 @@ public static PointInPolygonResult PointInPolygon(Point64 pt, Path64 polygon) i++; } - if (isAbove != startingAbove) - { - if (i == len) i = 0; - if (i == 0) - d = CrossProduct(polygon[len - 1], polygon[0], pt); - else - d = CrossProduct(polygon[i - 1], polygon[i], pt); - if (d == 0) return PointInPolygonResult.IsOn; - if ((d < 0) == isAbove) val = 1 - val; - } + if (isAbove == startingAbove) return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside; + if (i == len) i = 0; + d = i == 0 ? CrossProduct(polygon[len - 1], polygon[0], pt) : CrossProduct(polygon[i - 1], polygon[i], pt); + if (d == 0) return PointInPolygonResult.IsOn; + if ((d < 0) == isAbove) val = 1 - val; - if (val == 0) - return PointInPolygonResult.IsOutside; - return PointInPolygonResult.IsInside; + return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside; } } // InternalClipper diff --git a/CSharp/Clipper2Lib/Clipper.Engine.cs b/CSharp/Clipper2Lib/Clipper.Engine.cs index d227b166..ec3bd49d 100644 --- a/CSharp/Clipper2Lib/Clipper.Engine.cs +++ b/CSharp/Clipper2Lib/Clipper.Engine.cs @@ -27,7 +27,7 @@ public enum PointInPolygonResult IsOn = 0, IsInside = 1, IsOutside = 2 - }; + } [Flags] internal enum VertexFlags @@ -37,7 +37,7 @@ internal enum VertexFlags OpenEnd = 2, LocalMax = 4, LocalMin = 8 - }; + } internal class Vertex { @@ -53,7 +53,7 @@ public Vertex(Point64 pt, VertexFlags flags, Vertex? prev) next = null; this.prev = prev; } - }; + } internal readonly struct LocalMinima { @@ -87,7 +87,7 @@ public override int GetHashCode() { return vertex.GetHashCode(); } - }; + } // IntersectNode: a structure representing 2 intersecting edges. // Intersections must be sorted so they are processed from the largest @@ -104,7 +104,7 @@ public IntersectNode(Point64 pt, Active edge1, Active edge2) this.edge1 = edge1; this.edge2 = edge2; } - }; + } internal struct LocMinSorter : IComparer { @@ -131,10 +131,10 @@ public OutPt(Point64 pt, OutRec outrec) prev = this; horz = null; } - }; + } - internal enum JoinWith { None, Left, Right }; - internal enum HorzPosition { Bottom, Middle, Top }; + internal enum JoinWith { None, Left, Right } + internal enum HorzPosition { Bottom, Middle, Top } // OutRec: path data structure for clipping solutions @@ -151,7 +151,7 @@ internal class OutRec public bool isOpen; public List? splits; public OutRec? recursiveSplit; - }; + } internal class HorzSegment { @@ -210,7 +210,7 @@ internal class Active public LocalMinima localMin; // the bottom of an edge 'bound' (also Vatti) internal bool isLeftBound; internal JoinWith joinWith; - }; + } internal static class ClipperEngine { @@ -258,14 +258,14 @@ internal static void AddPathsToVertexList(Paths64 paths, PathType polytype, bool prev_v = curr_v; } } - if (prev_v == null || prev_v.prev == null) continue; + if (prev_v?.prev == null) continue; if (!isOpen && prev_v.pt == v0!.pt) prev_v = prev_v.prev; prev_v.next = v0; v0!.prev = prev_v; if (!isOpen && prev_v.next == prev_v) continue; // OK, we have a valid path - bool going_up, going_up0; + bool going_up; if (isOpen) { curr_v = v0.next; @@ -290,7 +290,7 @@ internal static void AddPathsToVertexList(Paths64 paths, PathType polytype, bool going_up = prev_v.pt.Y > v0.pt.Y; } - going_up0 = going_up; + bool going_up0 = going_up; prev_v = v0; curr_v = v0.next; while (curr_v != v0) @@ -488,9 +488,7 @@ private static double GetDx(Point64 pt1, Point64 pt2) double dy = pt2.Y - pt1.Y; if (dy != 0) return (pt2.X - pt1.X) / dy; - if (pt2.X > pt1.X) - return double.NegativeInfinity; - return double.PositiveInfinity; + return pt2.X > pt1.X ? double.NegativeInfinity : double.PositiveInfinity; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -549,17 +547,13 @@ private static void SetDx(Active ae) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vertex NextVertex(Active ae) { - if (ae.windDx > 0) - return ae.vertexTop!.next!; - return ae.vertexTop!.prev!; + return ae.windDx > 0 ? ae.vertexTop!.next! : ae.vertexTop!.prev!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vertex PrevPrevVertex(Active ae) { - if (ae.windDx > 0) - return ae.vertexTop!.prev!.prev!; - return ae.vertexTop!.next!.next!; + return ae.windDx > 0 ? ae.vertexTop!.prev!.prev! : ae.vertexTop!.next!.next!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -577,8 +571,7 @@ private static bool IsMaxima(Active ae) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Active? GetMaximaPair(Active ae) { - Active? ae2; - ae2 = ae.nextInAEL; + Active? ae2 = ae.nextInAEL; while (ae2 != null) { if (ae2.vertexTop == ae.vertexTop) return ae2; // Found! @@ -621,12 +614,9 @@ private struct IntersectListSort : IComparer { public readonly int Compare(IntersectNode a, IntersectNode b) { - if (a.pt.Y == b.pt.Y) - { - if (a.pt.X == b.pt.X) return 0; - return (a.pt.X < b.pt.X) ? -1 : 1; - } - return (a.pt.Y > b.pt.Y) ? -1 : 1; + if (a.pt.Y != b.pt.Y) return (a.pt.Y > b.pt.Y) ? -1 : 1; + if (a.pt.X == b.pt.X) return 0; + return (a.pt.X < b.pt.X) ? -1 : 1; } } @@ -910,7 +900,7 @@ private bool IsContributingClosed(Active ae) { FillRule.Positive => ae.windCount2 > 0, FillRule.Negative => ae.windCount2 < 0, - _ => ae.windCount2 != 0, + _ => ae.windCount2 != 0 }; case ClipType.Union: @@ -918,7 +908,7 @@ private bool IsContributingClosed(Active ae) { FillRule.Positive => ae.windCount2 <= 0, FillRule.Negative => ae.windCount2 >= 0, - _ => ae.windCount2 == 0, + _ => ae.windCount2 == 0 }; case ClipType.Difference: @@ -926,7 +916,7 @@ private bool IsContributingClosed(Active ae) { FillRule.Positive => (ae.windCount2 <= 0), FillRule.Negative => (ae.windCount2 >= 0), - _ => (ae.windCount2 == 0), + _ => (ae.windCount2 == 0) }; return (GetPolyType(ae) == PathType.Subject) ? result : !result; @@ -1122,8 +1112,6 @@ private static bool IsValidAelOrder(Active resident, Active newcomer) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void InsertLeftEdge(Active ae) { - Active ae2; - if (_actives == null) { ae.prevInAEL = null; @@ -1139,7 +1127,7 @@ private void InsertLeftEdge(Active ae) } else { - ae2 = _actives; + Active ae2 = _actives; while (ae2.nextInAEL != null && IsValidAelOrder(ae2.nextInAEL, ae)) ae2 = ae2.nextInAEL; //don't separate joined edges @@ -1162,13 +1150,12 @@ private static void InsertRightEdge(Active ae, Active ae2) private void InsertLocalMinimaIntoAEL(long botY) { - LocalMinima localMinima; - Active? leftBound, rightBound; // Add any local minima (if any) at BotY ... // NB horizontal local minima edges should contain locMin.vertex.prev while (HasLocMinAtY(botY)) { - localMinima = PopLocalMinima(); + LocalMinima localMinima = PopLocalMinima(); + Active? leftBound; if ((localMinima.vertex.flags & VertexFlags.OpenStart) != VertexFlags.None) { leftBound = null; @@ -1188,6 +1175,7 @@ private void InsertLocalMinimaIntoAEL(long botY) SetDx(leftBound); } + Active? rightBound; if ((localMinima.vertex.flags & VertexFlags.OpenEnd) != VertexFlags.None) { rightBound = null; @@ -1461,8 +1449,13 @@ private static OutPt AddOutPt(Active ae, Point64 pt) OutPt opFront = outrec.pts!; OutPt opBack = opFront.next!; - if (toFront && (pt == opFront.pt)) return opFront; - else if (!toFront && (pt == opBack.pt)) return opBack; + switch (toFront) + { + case true when (pt == opFront.pt): + return opFront; + case false when (pt == opBack.pt): + return opBack; + } OutPt newOp = new OutPt(pt, outrec); opBack.prev = newOp; @@ -1829,10 +1822,8 @@ private void AdjustCurrXAndCopyToSEL(long topY) ae.prevInSEL = ae.prevInAEL; ae.nextInSEL = ae.nextInAEL; ae.jump = ae.nextInSEL; - if (ae.joinWith == JoinWith.Left) - ae.curX = ae.prevInAEL!.curX; // this also avoids complications - else - ae.curX = TopX(ae, topY); + ae.curX = ae.joinWith == JoinWith.Left ? ae.prevInAEL!.curX : // this also avoids complications + TopX(ae, topY); // NB don't update ae.curr.Y yet (see AddNewIntersectNode) ae = ae.nextInAEL; } @@ -1868,11 +1859,9 @@ protected void ExecuteInternal(ClipType ct, FillRule fillRule) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DoIntersections(long topY) { - if (BuildIntersectList(topY)) - { - ProcessIntersectList(); - DisposeIntersectNodes(); - } + if (!BuildIntersectList(topY)) return; + ProcessIntersectList(); + DisposeIntersectNodes(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1892,23 +1881,33 @@ private void AddNewIntersectNode(Active ae1, Active ae2, long topY) { double absDx1 = Math.Abs(ae1.dx); double absDx2 = Math.Abs(ae2.dx); - if (absDx1 > 100 && absDx2 > 100) + switch (absDx1 > 100) { - if (absDx1 > absDx2) + case true when absDx2 > 100: + { + if (absDx1 > absDx2) + ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top); + else + ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top); + break; + } + case true: ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top); - else - ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top); - } - else if (absDx1 > 100) - ip = InternalClipper.GetClosestPtOnSegment(ip, ae1.bot, ae1.top); - else if (absDx2 > 100) - ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top); - else - { - if (ip.Y < topY) ip.Y = topY; - else ip.Y = _currentBotY; - if (absDx1 < absDx2) ip.X = TopX(ae1, ip.Y); - else ip.X = TopX(ae2, ip.Y); + break; + default: + { + if (absDx2 > 100) + ip = InternalClipper.GetClosestPtOnSegment(ip, ae2.bot, ae2.top); + else + { + if (ip.Y < topY) ip.Y = topY; + else ip.Y = _currentBotY; + if (absDx1 < absDx2) ip.X = TopX(ae1, ip.Y); + else ip.X = TopX(ae2, ip.Y); + } + + break; + } } } IntersectNode node = new IntersectNode(ip, ae1, ae2); @@ -1937,7 +1936,7 @@ private static void Insert1Before2InSEL(Active ae1, Active ae2) private bool BuildIntersectList(long topY) { - if (_actives == null || _actives.nextInAEL == null) return false; + if (_actives?.nextInAEL == null) return false; // Calculate edge positions at the top of the current scanbeam, and from this // we will determine the intersections required to reach these new positions. @@ -1948,23 +1947,23 @@ private bool BuildIntersectList(long topY) // stored in FIntersectList ready to be processed in ProcessIntersectList. // Re merge sorts see https://stackoverflow.com/a/46319131/359538 - Active? left = _sel, right, lEnd, rEnd, currBase, prevBase, tmp; + Active? left = _sel; while (left!.jump != null) { - prevBase = null; - while (left != null && left.jump != null) + Active? prevBase = null; + while (left?.jump != null) { - currBase = left; - right = left.jump; - lEnd = right; - rEnd = right.jump; + Active? currBase = left; + Active? right = left.jump; + Active? lEnd = right; + Active? rEnd = right.jump; left.jump = rEnd; while (left != lEnd && right != rEnd) { if (right!.curX < left!.curX) { - tmp = right.prevInSEL!; + Active? tmp = right.prevInSEL!; for (; ; ) { AddNewIntersectNode(tmp, right, topY); @@ -1976,13 +1975,11 @@ private bool BuildIntersectList(long topY) right = ExtractFromSEL(tmp); lEnd = right; Insert1Before2InSEL(tmp, left); - if (left == currBase) - { - currBase = tmp; - currBase.jump = rEnd; - if (prevBase == null) _sel = currBase; - else prevBase.jump = currBase; - } + if (left != currBase) continue; + currBase = tmp; + currBase.jump = rEnd; + if (prevBase == null) _sel = currBase; + else prevBase.jump = currBase; } else left = left.nextInSEL; } @@ -2126,7 +2123,6 @@ private void DoHorizontal(Active horz) * / | / | / * *******************************************************************************/ { - Point64 pt; bool horzIsOpen = IsOpen(horz); long Y = horz.bot.Y; @@ -2178,6 +2174,7 @@ private void DoHorizontal(Active horz) // if horzEdge is a maxima, keep going until we reach // its maxima pair, otherwise check for break conditions + Point64 pt; if (vertex_max != horz.vertexTop || IsOpenEnd(horz)) { // otherwise stop when 'ae' is beyond the end of the horizontal line @@ -2243,7 +2240,7 @@ private void DoHorizontal(Active horz) DeleteFromAEL(horz); return; } - else if (NextVertex(horz).pt.Y != horz.top.Y) + if (NextVertex(horz).pt.Y != horz.top.Y) break; //still more horizontals in bound to process ... @@ -2300,30 +2297,26 @@ private void DoTopOfScanbeam(long y) [MethodImpl(MethodImplOptions.AggressiveInlining)] private Active? DoMaxima(Active ae) { - Active? prevE; - Active? nextE, maxPair; - prevE = ae.prevInAEL; - nextE = ae.nextInAEL; + Active? prevE = ae.prevInAEL; + Active? nextE = ae.nextInAEL; if (IsOpenEnd(ae)) { if (IsHotEdge(ae)) AddOutPt(ae, ae.top); - if (!IsHorizontal(ae)) + if (IsHorizontal(ae)) return nextE; + if (IsHotEdge(ae)) { - if (IsHotEdge(ae)) - { - if (IsFront(ae)) - ae.outrec!.frontEdge = null; - else - ae.outrec!.backEdge = null; - ae.outrec = null; - } - DeleteFromAEL(ae); + if (IsFront(ae)) + ae.outrec!.frontEdge = null; + else + ae.outrec!.backEdge = null; + ae.outrec = null; } + DeleteFromAEL(ae); return nextE; } - maxPair = GetMaximaPair(ae); + Active? maxPair = GetMaximaPair(ae); if (maxPair == null) return nextE; // eMaxPair is horizontal if (IsJoined(ae)) Split(ae, ae.top); @@ -2528,10 +2521,9 @@ private int HorzSegSort(HorzSegment? hs1, HorzSegment? hs2) { return hs2.rightOp == null ? 0 : 1; } - else if (hs2.rightOp == null) + if (hs2.rightOp == null) return -1; - else - return hs1.leftOp!.pt.X.CompareTo(hs2.leftOp!.pt.X); + return hs1.leftOp!.pt.X.CompareTo(hs2.leftOp!.pt.X); } private void ConvertHorzSegsToJoins() @@ -2664,29 +2656,34 @@ private static PointInPolygonResult PointInOpPolygon(Point64 pt, OutPt op) op2 = op2.next!; } - if (isAbove != startingAbove) + if (isAbove == startingAbove) return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside; { double d = InternalClipper.CrossProduct(op2.prev.pt, op2.pt, pt); if (d == 0) return PointInPolygonResult.IsOn; if ((d < 0) == isAbove) val = 1 - val; } - if (val == 0) return PointInPolygonResult.IsOutside; - else return PointInPolygonResult.IsInside; + return val == 0 ? PointInPolygonResult.IsOutside : PointInPolygonResult.IsInside; } private static bool Path1InsidePath2(OutPt op1, OutPt op2) { // we need to make some accommodation for rounding errors // so we won't jump if the first vertex is found outside - PointInPolygonResult result; int outside_cnt = 0; OutPt op = op1; do { - result = PointInOpPolygon(op.pt, op2); - if (result == PointInPolygonResult.IsOutside) ++outside_cnt; - else if (result == PointInPolygonResult.IsInside) --outside_cnt; + PointInPolygonResult result = PointInOpPolygon(op.pt, op2); + switch (result) + { + case PointInPolygonResult.IsOutside: + ++outside_cnt; + break; + case PointInPolygonResult.IsInside: + --outside_cnt; + break; + } op = op.next!; } while (op != op1 && Math.Abs(outside_cnt) < 2); if (Math.Abs(outside_cnt) > 1) return (outside_cnt < 0); @@ -2696,7 +2693,7 @@ private static bool Path1InsidePath2(OutPt op1, OutPt op2) return InternalClipper.PointInPolygon(mp, path2) != PointInPolygonResult.IsOutside; } - private void MoveSplits(OutRec fromOr, OutRec toOr) + private static void MoveSplits(OutRec fromOr, OutRec toOr) { if (fromOr.splits == null) return; toOr.splits ??= new List(); @@ -2889,33 +2886,29 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp) // So the only way for these areas to have the same sign is if // the split triangle is larger than the path containing prevOp or // if there's more than one self=intersection. - if (absArea2 > 1 && - (absArea2 > absArea1 || - ((area2 > 0) == (area1 > 0)))) - { - OutRec newOutRec = NewOutRec(); - newOutRec.owner = outrec.owner; - splitOp.outrec = newOutRec; - splitOp.next.outrec = newOutRec; + if (!(absArea2 > 1) || + (!(absArea2 > absArea1) && + ((area2 > 0) != (area1 > 0)))) return; + OutRec newOutRec = NewOutRec(); + newOutRec.owner = outrec.owner; + splitOp.outrec = newOutRec; + splitOp.next.outrec = newOutRec; - OutPt newOp = new OutPt(ip, newOutRec) { prev = splitOp.next, next = splitOp }; - newOutRec.pts = newOp; - splitOp.prev = newOp; - splitOp.next.next = newOp; + OutPt newOp = new OutPt(ip, newOutRec) { prev = splitOp.next, next = splitOp }; + newOutRec.pts = newOp; + splitOp.prev = newOp; + splitOp.next.next = newOp; - if (_using_polytree) - { - if (Path1InsidePath2(prevOp, newOp)) - { - newOutRec.splits ??= new List(); - newOutRec.splits.Add(outrec.idx); - } - else - { - outrec.splits ??= new List(); - outrec.splits.Add(newOutRec.idx); - } - } + if (!_using_polytree) return; + if (Path1InsidePath2(prevOp, newOp)) + { + newOutRec.splits ??= new List(); + newOutRec.splits.Add(outrec.idx); + } + else + { + outrec.splits ??= new List(); + outrec.splits.Add(newOutRec.idx); } //else { splitOp = null; splitOp.next = null; } } @@ -2936,8 +2929,8 @@ private void FixSelfIntersects(OutRec outrec) op2 = outrec.pts; continue; } - else - op2 = op2.next; + + op2 = op2.next; if (op2 == outrec.pts) break; } } @@ -2975,8 +2968,7 @@ internal static bool BuildPath(OutPt? op, bool reverse, bool isOpen, Path64 path op2 = op2.next!; } - if (path.Count == 3 && !isOpen && IsVerySmallTriangle(op2)) return false; - else return true; + return path.Count != 3 || isOpen || !IsVerySmallTriangle(op2); } protected bool BuildPaths(Paths64 solutionClosed, Paths64 solutionOpen) @@ -3048,14 +3040,12 @@ private bool CheckSplitOwner(OutRec outrec, List? splits) if (split == null || split == outrec || split.recursiveSplit == outrec) continue; split.recursiveSplit = outrec; //#599 if (split.splits != null && CheckSplitOwner(outrec, split.splits)) return true; - if (IsValidOwner(outrec, split) && - CheckBounds(split) && - split.bounds.Contains(outrec.bounds) && - Path1InsidePath2(outrec.pts!, split.pts!)) - { - outrec.owner = split; //found in split - return true; - } + if (!IsValidOwner(outrec, split) || + !CheckBounds(split) || + !split.bounds.Contains(outrec.bounds) || + !Path1InsidePath2(outrec.pts!, split.pts!)) continue; + outrec.owner = split; //found in split + return true; } return false; } @@ -3070,7 +3060,7 @@ private void RecursiveCheckOwners(OutRec outrec, PolyPathBase polypath) { if (outrec.owner.splits != null && CheckSplitOwner(outrec, outrec.owner.splits)) break; - else if (outrec.owner.pts != null && CheckBounds(outrec.owner) && + if (outrec.owner.pts != null && CheckBounds(outrec.owner) && Path1InsidePath2(outrec.pts!, outrec.owner.pts!)) break; outrec.owner = outrec.owner.owner; } @@ -3235,7 +3225,7 @@ public ZCallback64? ZCallback { public class ClipperD : ClipperBase { - private readonly string precision_range_error = "Error: Precision is out of range."; + private const string precision_range_error = "Error: Precision is out of range."; private readonly double _scale; private readonly double _invScale; @@ -3393,12 +3383,10 @@ public bool Execute(ClipType clipType, FillRule fillRule, PolyTreeD polytree, Pa } ClearSolutionOnly(); if (!success) return false; - if (oPaths.Count > 0) - { - openPaths.EnsureCapacity(oPaths.Count); - foreach (Path64 path in oPaths) - openPaths.Add(Clipper.ScalePathD(path, _invScale)); - } + if (oPaths.Count <= 0) return true; + openPaths.EnsureCapacity(oPaths.Count); + foreach (Path64 path in oPaths) + openPaths.Add(Clipper.ScalePathD(path, _invScale)); return true; } @@ -3453,7 +3441,7 @@ public object Current } } - }; + } public bool IsHole => GetIsHole(); @@ -3492,9 +3480,9 @@ internal string ToStringInternal(int idx, int level) if (_childs.Count == 1) plural = ""; padding = padding.PadLeft(level * 2); if ((level & 1) == 0) - result += string.Format("{0}+- hole ({1}) contains {2} nested polygon{3}.\n", padding, idx, _childs.Count, plural); + result += $"{padding}+- hole ({idx}) contains {_childs.Count} nested polygon{plural}.\n"; else - result += string.Format("{0}+- polygon ({1}) contains {2} hole{3}.\n", padding, idx, _childs.Count, plural); + result += $"{padding}+- polygon ({idx}) contains {_childs.Count} hole{plural}.\n"; for (int i = 0; i < Count; i++) if (_childs[i].Count > 0) @@ -3507,7 +3495,7 @@ public override string ToString() if (Level > 0) return ""; //only accept tree root string plural = "s"; if (_childs.Count == 1) plural = ""; - string result = string.Format("Polytree with {0} polygon{1}.\n", _childs.Count, plural); + string result = $"Polytree with {_childs.Count} polygon{plural}.\n"; for (int i = 0; i < Count; i++) if (_childs[i].Count > 0) result += _childs[i].ToStringInternal(i, 1); diff --git a/CSharp/Clipper2Lib/Clipper.Minkowski.cs b/CSharp/Clipper2Lib/Clipper.Minkowski.cs index 5b38c739..26206422 100644 --- a/CSharp/Clipper2Lib/Clipper.Minkowski.cs +++ b/CSharp/Clipper2Lib/Clipper.Minkowski.cs @@ -12,7 +12,7 @@ namespace Clipper2Lib { - public class Minkowski + public static class Minkowski { private static Paths64 MinkowskiInternal(Path64 pattern, Path64 path, bool isSum, bool isClosed) { diff --git a/CSharp/Clipper2Lib/Clipper.Offset.cs b/CSharp/Clipper2Lib/Clipper.Offset.cs index b4b51fe9..8741322a 100644 --- a/CSharp/Clipper2Lib/Clipper.Offset.cs +++ b/CSharp/Clipper2Lib/Clipper.Offset.cs @@ -19,7 +19,7 @@ public enum JoinType Square, Bevel, Round - }; + } public enum EndType { @@ -28,7 +28,7 @@ public enum EndType Butt, Square, Round - }; + } public class ClipperOffset { @@ -67,7 +67,7 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon } } - private static readonly double Tolerance = 1.0E-12; + private const double Tolerance = 1.0E-12; private readonly List _groupList = new List(); private Path64 pathOut = new Path64(); @@ -91,7 +91,7 @@ public Group(Paths64 paths, JoinType joinType, EndType endType = EndType.Polygon public delegate double DeltaCallback64(Path64 path, PathD path_norms, int currPt, int prevPt); - public ClipperOffset.DeltaCallback64? DeltaCallback { get; set; } + public DeltaCallback64? DeltaCallback { get; set; } #if USINGZ internal void ZCB(Point64 bot1, Point64 top1, @@ -185,10 +185,8 @@ private void ExecuteInternal(double delta) FillRule fillRule = pathsReversed ? FillRule.Negative : FillRule.Positive; // clean up self-intersections ... - Clipper64 c = new Clipper64(); - c.PreserveCollinear = PreserveCollinear; - // the solution should retain the orientation of the input - c.ReverseSolution = ReverseSolution != pathsReversed; + Clipper64 c = new Clipper64 { PreserveCollinear = PreserveCollinear, // the solution should retain the orientation of the input + ReverseSolution = ReverseSolution != pathsReversed }; #if USINGZ c.ZCallback = ZCB; #endif @@ -564,18 +562,25 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k) // almost straight - less than 2.5 degree (#424, #482, #526 & #724) DoMiter(path, j, k, cosA); } - else if (_joinType == JoinType.Miter) + else switch (_joinType) { // miter unless the angle is sufficiently acute to exceed ML - if (cosA > _mitLimSqr - 1) DoMiter(path, j, k, cosA); - else DoSquare(path, j, k); + case JoinType.Miter when cosA > _mitLimSqr - 1: + DoMiter(path, j, k, cosA); + break; + case JoinType.Miter: + DoSquare(path, j, k); + break; + case JoinType.Round: + DoRound(path, j, k, Math.Atan2(sinA, cosA)); + break; + case JoinType.Bevel: + DoBevel(path, j, k); + break; + default: + DoSquare(path, j, k); + break; } - else if (_joinType == JoinType.Round) - DoRound(path, j, k, Math.Atan2(sinA, cosA)); - else if (_joinType == JoinType.Bevel) - DoBevel(path, j, k); - else - DoSquare(path, j, k); k = j; } @@ -701,50 +706,61 @@ private void DoGroupOffset(Group group) pathOut = new Path64(); int cnt = p.Count; - if (cnt == 1) + switch (cnt) { - Point64 pt = p[0]; - - if (DeltaCallback != null) - { - _groupDelta = DeltaCallback(p, _normals, 0, 0); - if (group.pathsReversed) _groupDelta = -_groupDelta; - absDelta = Math.Abs(_groupDelta); - } - - // single vertex so build a circle or square ... - if (group.endType == EndType.Round) + case 1: { - double r = absDelta; - int steps = (int) Math.Ceiling(_stepsPerRad * 2 * Math.PI); - pathOut = Clipper.Ellipse(pt, r, r, steps); + Point64 pt = p[0]; + + if (DeltaCallback != null) + { + _groupDelta = DeltaCallback(p, _normals, 0, 0); + if (group.pathsReversed) _groupDelta = -_groupDelta; + absDelta = Math.Abs(_groupDelta); + } + + // single vertex so build a circle or square ... + if (group.endType == EndType.Round) + { + int steps = (int) Math.Ceiling(_stepsPerRad * 2 * Math.PI); + pathOut = Clipper.Ellipse(pt, absDelta, absDelta, steps); #if USINGZ pathOut = InternalClipper.SetZ(pathOut, pt.Z); #endif - } - else - { - int d = (int) Math.Ceiling(_groupDelta); - Rect64 r = new Rect64(pt.X - d, pt.Y - d, pt.X + d, pt.Y + d); - pathOut = r.AsPath(); + } + else + { + int d = (int) Math.Ceiling(_groupDelta); + Rect64 r = new Rect64(pt.X - d, pt.Y - d, pt.X + d, pt.Y + d); + pathOut = r.AsPath(); #if USINGZ pathOut = InternalClipper.SetZ(pathOut, pt.Z); #endif + } + _solution.Add(pathOut); + continue; // end of offsetting a single point } - _solution.Add(pathOut); - continue; - } // end of offsetting a single point - + case 2 when group.endType == EndType.Joined: + _endType = (group.joinType == JoinType.Round) ? + EndType.Round : + EndType.Square; + break; + } - if (cnt == 2 && group.endType == EndType.Joined) - _endType = (group.joinType == JoinType.Round) ? - EndType.Round : - EndType.Square; BuildNormals(p); - if (_endType == EndType.Polygon) OffsetPolygon(group, p); - else if (_endType == EndType.Joined) OffsetOpenJoined(group, p); - else OffsetOpenPath(group, p); + switch (_endType) + { + case EndType.Polygon: + OffsetPolygon(group, p); + break; + case EndType.Joined: + OffsetOpenJoined(group, p); + break; + default: + OffsetOpenPath(group, p); + break; + } } } } diff --git a/CSharp/Clipper2Lib/Clipper.RectClip.cs b/CSharp/Clipper2Lib/Clipper.RectClip.cs index face68f9..27f13e27 100644 --- a/CSharp/Clipper2Lib/Clipper.RectClip.cs +++ b/CSharp/Clipper2Lib/Clipper.RectClip.cs @@ -33,7 +33,7 @@ public class RectClip64 protected enum Location { left, top, right, bottom, inside - }; + } readonly protected Rect64 rect_; readonly protected Point64 mp_; @@ -113,8 +113,7 @@ private static bool IsClockwise(Location prev, Location curr, { if (AreOpposites(prev, curr)) return InternalClipper.CrossProduct(prevPt, rectMidPoint, currPt) < 0; - else - return HeadingClockwise(prev, curr); + return HeadingClockwise(prev, curr); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -173,7 +172,7 @@ private static bool IsHeadingClockwise(Point64 pt1, Point64 pt2, int edgeIdx) 0 => pt2.Y < pt1.Y, 1 => pt2.X > pt1.X, 2 => pt2.Y > pt1.Y, - _ => pt2.X < pt1.X, + _ => pt2.X < pt1.X }; } @@ -206,11 +205,9 @@ private static void UncoupleEdge(OutPt2 op) for (int i = 0; i < op.edge.Count; i++) { OutPt2? op2 = op.edge[i]; - if (op2 == op) - { - op.edge[i] = null; - break; - } + if (op2 != op) continue; + op.edge[i] = null; + break; } op.edge = null; } @@ -229,10 +226,7 @@ private static void SetNewOwner(OutPt2 op, int newIdx) private void AddCorner(Location prev, Location curr) { - if (HeadingClockwise(prev, curr)) - Add(rectPath_[(int) prev]); - else - Add(rectPath_[(int) curr]); + Add(HeadingClockwise(prev, curr) ? rectPath_[(int) prev] : rectPath_[(int) curr]); } private void AddCorner(ref Location loc, bool isClockwise) @@ -290,17 +284,17 @@ private static bool GetSegmentIntersection(Point64 p1, { ip = p1; if (res2 == 0) return false; // segments are collinear - else if (p1 == p3 || p1 == p4) return true; + if (p1 == p3 || p1 == p4) return true; //else if (p2 == p3 || p2 == p4) { ip = p2; return true; } - else if (IsHorizontal(p3, p4)) return ((p1.X > p3.X) == (p1.X < p4.X)); - else return ((p1.Y > p3.Y) == (p1.Y < p4.Y)); + if (IsHorizontal(p3, p4)) return ((p1.X > p3.X) == (p1.X < p4.X)); + return ((p1.Y > p3.Y) == (p1.Y < p4.Y)); } - else if (res2 == 0) + if (res2 == 0) { ip = p2; if (p2 == p3 || p2 == p4) return true; - else if (IsHorizontal(p3, p4)) return ((p2.X > p3.X) == (p2.X < p4.X)); - else return ((p2.Y > p3.Y) == (p2.Y < p4.Y)); + if (IsHorizontal(p3, p4)) return ((p2.X > p3.X) == (p2.X < p4.X)); + return ((p2.Y > p3.Y) == (p2.Y < p4.Y)); } if ((res1 > 0) == (res2 > 0)) @@ -315,15 +309,15 @@ private static bool GetSegmentIntersection(Point64 p1, { ip = p3; if (p3 == p1 || p3 == p2) return true; - else if (IsHorizontal(p1, p2)) return ((p3.X > p1.X) == (p3.X < p2.X)); - else return ((p3.Y > p1.Y) == (p3.Y < p2.Y)); + if (IsHorizontal(p1, p2)) return ((p3.X > p1.X) == (p3.X < p2.X)); + return ((p3.Y > p1.Y) == (p3.Y < p2.Y)); } - else if (res4 == 0) + if (res4 == 0) { ip = p4; if (p4 == p1 || p4 == p2) return true; - else if (IsHorizontal(p1, p2)) return ((p4.X > p1.X) == (p4.X < p2.X)); - else return ((p4.Y > p1.Y) == (p4.Y < p2.Y)); + if (IsHorizontal(p1, p2)) return ((p4.X > p1.X) == (p4.X < p2.X)); + return ((p4.Y > p1.Y) == (p4.Y < p2.Y)); } if ((res3 > 0) == (res4 > 0)) { @@ -346,62 +340,54 @@ static protected bool GetIntersection(Path64 rectPath, Point64 p, Point64 p2, re case Location.left: if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) return true; - else if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) + if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) { loc = Location.top; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) - { - loc = Location.bottom; - return true; - } - else return false; + + if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false; + loc = Location.bottom; + return true; case Location.right: if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) return true; - else if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) + if (p.Y < rectPath[0].Y && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) { loc = Location.top; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) - { - loc = Location.bottom; - return true; - } - else return false; + + if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false; + loc = Location.bottom; + return true; case Location.top: if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) return true; - else if (p.X < rectPath[0].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) + if (p.X < rectPath[0].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) { loc = Location.left; return true; } - else if (p.X > rectPath[1].X && GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) - { - loc = Location.right; - return true; - } - else return false; + + if (p.X <= rectPath[1].X || !GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) return false; + loc = Location.right; + return true; case Location.bottom: if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return true; - else if (p.X < rectPath[3].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) + if (p.X < rectPath[3].X && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) { loc = Location.left; return true; } - else if (p.X > rectPath[2].X && GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) - { - loc = Location.right; - return true; - } - else return false; + + if (p.X <= rectPath[2].X || !GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) return false; + loc = Location.right; + return true; default: if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], out ip)) @@ -409,22 +395,20 @@ static protected bool GetIntersection(Path64 rectPath, Point64 p, Point64 p2, re loc = Location.left; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) + if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], out ip)) { loc = Location.top; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) + if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], out ip)) { loc = Location.right; return true; } - else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) - { - loc = Location.bottom; - return true; - } - else return false; + + if (!GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], out ip)) return false; + loc = Location.bottom; + return true; } } @@ -636,19 +620,15 @@ private void ExecuteInternal(Path64 path) if (firstCross == Location.inside) { // path never intersects - if (startingLoc != Location.inside) + if (startingLoc == Location.inside) return; + if (!pathBounds_.Contains(rect_) || + !Path1ContainsPath2(path, rectPath_)) return; + bool startLocsClockwise = StartLocsAreClockwise(startLocs); + for (int j = 0; j < 4; j++) { - if (pathBounds_.Contains(rect_) && - Path1ContainsPath2(path, rectPath_)) - { - bool startLocsClockwise = StartLocsAreClockwise(startLocs); - for (int j = 0; j < 4; j++) - { - int k = startLocsClockwise ? j : 3 - j; // ie reverse result path - Add(rectPath_[k]); - AddToEdge(edges_[k * 2], results_[0]!); - } - } + int k = startLocsClockwise ? j : 3 - j; // ie reverse result path + Add(rectPath_[k]); + AddToEdge(edges_[k * 2], results_[0]!); } } else if (loc != Location.inside && @@ -680,7 +660,7 @@ public Paths64 Execute(Paths64 paths) pathBounds_ = Clipper.GetBounds(path); if (!rect_.Intersects(pathBounds_)) continue; // the path must be completely outside fRect - else if (rect_.Contains(pathBounds_)) + if (rect_.Contains(pathBounds_)) { // the path must be completely inside rect_ result.Add(path); @@ -749,13 +729,11 @@ private void CheckEdges() uint combinedSet = (edgeSet1 & edgeSet2); for (int j = 0; j < 4; ++j) { - if ((combinedSet & (1 << j)) != 0) - { - if (IsHeadingClockwise(op2.prev!.pt, op2.pt, j)) - AddToEdge(edges_[j * 2], op2); - else - AddToEdge(edges_[j * 2 + 1], op2); - } + if ((combinedSet & (1 << j)) == 0) continue; + if (IsHeadingClockwise(op2.prev!.pt, op2.pt, j)) + AddToEdge(edges_[j * 2], op2); + else + AddToEdge(edges_[j * 2 + 1], op2); } } edgeSet1 = edgeSet2; @@ -770,11 +748,10 @@ private void TidyEdgePair(int idx, List cw, List ccw) bool isHorz = ((idx == 1) || (idx == 3)); bool cwIsTowardLarger = ((idx == 1) || (idx == 2)); int i = 0, j = 0; - OutPt2? p1, p2, p1a, p2a, op, op2; while (i < cw.Count) { - p1 = cw[i]; + OutPt2? p1 = cw[i]; if (p1 == null || p1.next == p1.prev) { cw[i++] = null; @@ -793,6 +770,9 @@ private void TidyEdgePair(int idx, List cw, List ccw) continue; } + OutPt2? p2; + OutPt2? p1a; + OutPt2? p2a; if (cwIsTowardLarger) { // p1 >>>> p1a; @@ -855,6 +835,8 @@ private void TidyEdgePair(int idx, List cw, List ccw) SetNewOwner(p1a, new_idx); } + OutPt2? op; + OutPt2? op2; if (cwIsTowardLarger) { op = p2; @@ -942,7 +924,7 @@ private void TidyEdgePair(int idx, List cw, List ccw) } } - private Path64 GetPath(OutPt2? op) + private static Path64 GetPath(OutPt2? op) { Path64 result = new Path64(); if (op == null || op.prev == op.next) return result; @@ -1005,7 +987,7 @@ internal RectClipLines64(Rect64 rect) : base(rect) { } return result; } - private Path64 GetPath(OutPt2? op) + private static Path64 GetPath(OutPt2? op) { Path64 result = new Path64(); if (op == null || op == op.next) return result; diff --git a/CSharp/Clipper2Lib/Clipper.cs b/CSharp/Clipper2Lib/Clipper.cs index bb15959b..44bbce4a 100644 --- a/CSharp/Clipper2Lib/Clipper.cs +++ b/CSharp/Clipper2Lib/Clipper.cs @@ -813,21 +813,31 @@ public static double PerpendicDistFromLineSqrd(Point64 pt, Point64 line1, Point6 internal static void RDP(Path64 path, int begin, int end, double epsSqrd, List flags) { - int idx = 0; - double max_d = 0; - while (end > begin && path[begin] == path[end]) flags[end--] = false; - for (int i = begin + 1; i < end; ++i) + while (true) { - // PerpendicDistFromLineSqrd - avoids expensive Sqrt() - double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); - if (d <= max_d) continue; - max_d = d; - idx = i; + int idx = 0; + double max_d = 0; + while (end > begin && path[begin] == path[end]) flags[end--] = false; + for (int i = begin + 1; i < end; ++i) + { + // PerpendicDistFromLineSqrd - avoids expensive Sqrt() + double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); + if (d <= max_d) continue; + max_d = d; + idx = i; + } + + if (max_d <= epsSqrd) return; + flags[idx] = true; + if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags); + if (idx < end - 1) + { + begin = idx; + continue; + } + + break; } - if (max_d <= epsSqrd) return; - flags[idx] = true; - if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags); - if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags); } public static Path64 RamerDouglasPeucker(Path64 path, double epsilon) @@ -852,21 +862,31 @@ public static Paths64 RamerDouglasPeucker(Paths64 paths, double epsilon) internal static void RDP(PathD path, int begin, int end, double epsSqrd, List flags) { - int idx = 0; - double max_d = 0; - while (end > begin && path[begin] == path[end]) flags[end--] = false; - for (int i = begin + 1; i < end; ++i) + while (true) { - // PerpendicDistFromLineSqrd - avoids expensive Sqrt() - double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); - if (d <= max_d) continue; - max_d = d; - idx = i; + int idx = 0; + double max_d = 0; + while (end > begin && path[begin] == path[end]) flags[end--] = false; + for (int i = begin + 1; i < end; ++i) + { + // PerpendicDistFromLineSqrd - avoids expensive Sqrt() + double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); + if (d <= max_d) continue; + max_d = d; + idx = i; + } + + if (max_d <= epsSqrd) return; + flags[idx] = true; + if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags); + if (idx < end - 1) + { + begin = idx; + continue; + } + + break; } - if (max_d <= epsSqrd) return; - flags[idx] = true; - if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags); - if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags); } public static PathD RamerDouglasPeucker(PathD path, double epsilon) @@ -921,7 +941,7 @@ public static Path64 SimplifyPath(Path64 path, bool[] flags = new bool[len]; double[] dsq = new double[len]; - int curr = 0, prev, start, next, prior2; + int curr = 0; if (isClosedPath) { @@ -941,7 +961,7 @@ public static Path64 SimplifyPath(Path64 path, { if (dsq[curr] > epsSqr) { - start = curr; + int start = curr; do { curr = GetNext(curr, high, ref flags); @@ -949,10 +969,11 @@ public static Path64 SimplifyPath(Path64 path, if (curr == start) break; } - prev = GetPrior(curr, high, ref flags); - next = GetNext(curr, high, ref flags); + int prev = GetPrior(curr, high, ref flags); + int next = GetNext(curr, high, ref flags); if (next == prev) break; + int prior2; if (dsq[next] < dsq[curr]) { prior2 = prev; @@ -995,7 +1016,7 @@ public static PathD SimplifyPath(PathD path, bool[] flags = new bool[len]; double[] dsq = new double[len]; - int curr = 0, prev, start, next, prior2; + int curr = 0; if (isClosedPath) { dsq[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); @@ -1013,7 +1034,7 @@ public static PathD SimplifyPath(PathD path, { if (dsq[curr] > epsSqr) { - start = curr; + int start = curr; do { curr = GetNext(curr, high, ref flags); @@ -1021,10 +1042,11 @@ public static PathD SimplifyPath(PathD path, if (curr == start) break; } - prev = GetPrior(curr, high, ref flags); - next = GetNext(curr, high, ref flags); + int prev = GetPrior(curr, high, ref flags); + int next = GetNext(curr, high, ref flags); if (next == prev) break; + int prior2; if (dsq[next] < dsq[curr]) { prior2 = prev; @@ -1181,7 +1203,7 @@ private static void ShowPolyPathStructure(PolyPath64 pp, int level) } else { - Console.WriteLine(spaces + caption + string.Format("({0})", pp.Count)); + Console.WriteLine(spaces + caption + $"({pp.Count})"); foreach (PolyPath64 child in pp) { ShowPolyPathStructure(child, level + 1); } } } @@ -1202,7 +1224,7 @@ private static void ShowPolyPathStructure(PolyPathD pp, int level) } else { - Console.WriteLine(spaces + caption + string.Format("({0})", pp.Count)); + Console.WriteLine(spaces + caption + $"({pp.Count})"); foreach (PolyPathD child in pp) { ShowPolyPathStructure(child, level + 1); } } } diff --git a/CSharp/Clipper2Lib/HashCode.cs b/CSharp/Clipper2Lib/HashCode.cs index dd209738..62889210 100644 --- a/CSharp/Clipper2Lib/HashCode.cs +++ b/CSharp/Clipper2Lib/HashCode.cs @@ -50,7 +50,7 @@ public struct HashCode { private static readonly uint s_seed = GenerateGlobalSeed(); - private const uint Prime1 = 2654435761U; + // private const uint Prime1 = 2654435761U; private const uint Prime2 = 2246822519U; private const uint Prime3 = 3266489917U; private const uint Prime4 = 668265263U; diff --git a/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs b/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs index 6c49c7a8..f5cdc471 100644 --- a/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs +++ b/CSharp/Utils/ClipFileIO/Clipper.FileIO.cs @@ -20,17 +20,16 @@ public static Paths64 PathFromStr(string? s) if (s == null) return new Paths64(); Path64 p = new Path64(); Paths64 pp = new Paths64(); - int len = s.Length, i = 0, j; + int len = s.Length, i = 0; while (i < len) { - bool isNeg; while (s[i] < 33 && i < len) i++; if (i >= len) break; //get X ... - isNeg = s[i] == 45; + bool isNeg = s[i] == 45; if (isNeg) i++; if (i >= len || s[i] < 48 || s[i] > 57) break; - j = i + 1; + int j = i + 1; while (j < len && s[j] > 47 && s[j] < 58) j++; if (!long.TryParse(s.Substring(i, j - i), out long x)) break; if (isNeg) x = -x; @@ -80,7 +79,6 @@ public static bool LoadTestNum(string filename, int num, ct = ClipType.Intersection; fillRule = FillRule.EvenOdd; bool result = false; - int GetIdx; if (num < 1) num = 1; caption = ""; area = 0; @@ -141,6 +139,7 @@ public static bool LoadTestNum(string filename, int num, continue; } + int GetIdx; if (s.IndexOf("SUBJECTS_OPEN", StringComparison.Ordinal) == 0) GetIdx = 2; else if (s.IndexOf("SUBJECTS", StringComparison.Ordinal) == 0) GetIdx = 1; else if (s.IndexOf("CLIPS", StringComparison.Ordinal) == 0) GetIdx = 3; @@ -159,9 +158,18 @@ public static bool LoadTestNum(string filename, int num, else return result; continue; } - if (GetIdx == 1) subj.Add(paths[0]); - else if (GetIdx == 2) subj_open.Add(paths[0]); - else clip.Add(paths[0]); + switch (GetIdx) + { + case 1: + subj.Add(paths[0]); + break; + case 2: + subj_open.Add(paths[0]); + break; + default: + clip.Add(paths[0]); + break; + } } } return result; diff --git a/CSharp/Utils/SVG/Clipper.SVG.cs b/CSharp/Utils/SVG/Clipper.SVG.cs index e3481da0..4d101ba2 100644 --- a/CSharp/Utils/SVG/Clipper.SVG.cs +++ b/CSharp/Utils/SVG/Clipper.SVG.cs @@ -207,9 +207,7 @@ private RectD GetBounds() if (pt.y < bounds.top) bounds.top = pt.y; if (pt.y > bounds.bottom) bounds.bottom = pt.y; } - if (!IsValidRect(bounds)) - return RectEmpty; - return bounds; + return !IsValidRect(bounds) ? RectEmpty : bounds; } private static string ColorToHtml(uint clr) @@ -281,7 +279,7 @@ public bool SaveToFile(string filename, int maxWidth = 0, int maxHeight = 0, int writer.Write(string.Format(NumberFormatInfo.InvariantInfo, svg_path_format2, ColorToHtml(pi.PenClr), GetAlpha(pi.PenClr), pi.PenWidth)); - if (pi.ShowCoords) + if (!pi.ShowCoords) continue; { writer.Write("\n", coordStyle.FontName, coordStyle.FontSize, ColorToHtml(coordStyle.FontColor));