/// <summary> /// Create the list of polygon segments (not closed) that represent the parts of the source polygons that are close (almost touching). /// </summary> /// <param name="polygons">The polygons to search for thin lines</param> /// <param name="overlapMergeAmount">If edges under consideration, are this distance or less apart (but greater than minimumRequiredWidth) they will generate edges</param> /// <param name="minimumRequiredWidth">If the distance between edges is less this they will not be generated. This lets us avoid considering very thin lines.</param> /// <param name="onlyMergeLines">The output segments that are calculated</param> /// <param name="pathIsClosed">Is the source path closed (does not contain the last edge but assumes it).</param> /// <returns>If thin lines were detected</returns> public static bool FindThinLines(this Polygons polygons, long overlapMergeAmount, long minimumRequiredWidth, out Polygons onlyMergeLines, bool pathIsClosed = true) { polygons = Clipper.CleanPolygons(polygons, overlapMergeAmount / 8); bool pathHasMergeLines = false; polygons = MakeCloseSegmentsMergable(polygons, overlapMergeAmount, pathIsClosed); // make a copy that has every point duplicated (so that we have them as segments). List <Segment> polySegments = Segment.ConvertToSegments(polygons); var markedAltered = new Altered[polySegments.Count]; var touchingEnumerator = new CloseSegmentsIterator(polySegments, overlapMergeAmount); int segmentCount = polySegments.Count; // now walk every segment and check if there is another segment that is similar enough to merge them together for (int firstSegmentIndex = 0; firstSegmentIndex < segmentCount; firstSegmentIndex++) { foreach (int checkSegmentIndex in touchingEnumerator.GetTouching(firstSegmentIndex, segmentCount)) { // The first point of start and the last point of check (the path will be coming back on itself). long startDelta = (polySegments[firstSegmentIndex].Start - polySegments[checkSegmentIndex].End).Length(); // if the segments are similar enough if (startDelta < overlapMergeAmount) { // The last point of start and the first point of check (the path will be coming back on itself). long endDelta = (polySegments[firstSegmentIndex].End - polySegments[checkSegmentIndex].Start).Length(); if (endDelta < overlapMergeAmount) { // move the first segments points to the average of the merge positions long startEndWidth = Math.Abs((polySegments[firstSegmentIndex].Start - polySegments[checkSegmentIndex].End).Length()); long endStartWidth = Math.Abs((polySegments[firstSegmentIndex].End - polySegments[checkSegmentIndex].Start).Length()); long width = Math.Min(startEndWidth, endStartWidth); if (width > minimumRequiredWidth) { // We need to check if the new start position is on the inside of the curve. We can only add thin lines on the insides of our existing curves. IntPoint newStartPosition = (polySegments[firstSegmentIndex].Start + polySegments[checkSegmentIndex].End) / 2; // the start; IntPoint newStartDirection = newStartPosition - polySegments[firstSegmentIndex].Start; IntPoint normalLeft = (polySegments[firstSegmentIndex].End - polySegments[firstSegmentIndex].Start).GetPerpendicularLeft(); long dotProduct = normalLeft.Dot(newStartDirection); if (dotProduct > 0) { pathHasMergeLines = true; polySegments[firstSegmentIndex].Start = newStartPosition; polySegments[firstSegmentIndex].Start.Width = width; polySegments[firstSegmentIndex].End = (polySegments[firstSegmentIndex].End + polySegments[checkSegmentIndex].Start) / 2; // the end polySegments[firstSegmentIndex].End.Width = width; markedAltered[firstSegmentIndex] = Altered.Merged; // mark this segment for removal markedAltered[checkSegmentIndex] = Altered.Remove; // We only expect to find one match for each segment, so move on to the next segment break; } } } } } } // remove the marked segments for (int segmentIndex = segmentCount - 1; segmentIndex >= 0; segmentIndex--) { // remove every segment that has not been merged if (markedAltered[segmentIndex] != Altered.Merged) { polySegments.RemoveAt(segmentIndex); } } // go through the polySegments and create a new polygon for every connected set of segments onlyMergeLines = new Polygons(); var currentPolygon = new Polygon(); onlyMergeLines.Add(currentPolygon); // put in the first point for (int segmentIndex = 0; segmentIndex < polySegments.Count; segmentIndex++) { // add the start point currentPolygon.Add(polySegments[segmentIndex].Start); // if the next segment is not connected to this one if (segmentIndex < polySegments.Count - 1 && polySegments[segmentIndex].End != polySegments[segmentIndex + 1].Start) { // add the end point currentPolygon.Add(polySegments[segmentIndex].End); // create a new polygon currentPolygon = new Polygon(); onlyMergeLines.Add(currentPolygon); } } // add the end point if (polySegments.Count > 0) { currentPolygon.Add(polySegments[polySegments.Count - 1].End); } long cleanDistance = overlapMergeAmount / 40; Clipper.CleanPolygons(onlyMergeLines, cleanDistance); return(pathHasMergeLines); }