/// <summary> /// build polygons from given list of segments /// if want to represent arcs, add them as dummy lines to segs /// polys returned are ordered anticlockwise /// </summary> public static IEnumerable <IReadOnlyList <Vector3D> > ClosedPolys2D(this IEnumerable <Line3D> segs, double tolLen, int polyMaxPoints = 0) { var minCoord = new BBox3D(segs.SelectMany(r => new[] { r.From, r.To })).Min; var vcmp = new Vector3DEqualityComparer(tolLen); var lcmp = new Line3DEqualityComparer(tolLen); var segsDict = segs.ToDictionary(k => k.ToString(tolLen), v => v); var segsFromDict = segs.GroupBy(g => g.From, v => v, vcmp).ToDictionary(k => k.Key, v => v.ToList(), vcmp); var segsToDict = segs.GroupBy(g => g.To, v => v, vcmp).ToDictionary(k => k.Key, v => v.ToList(), vcmp); var segsLeft = segs.OrderBy(w => w.MidPoint.Distance(minCoord)).ToList(); var polys = new List <List <Vector3D> >(); var polyCentroidDone = new HashSet <Vector3D>(vcmp); while (segsLeft.Count > 0) { Console.WriteLine($"segsLeft: {segsLeft.Count} polys:{polys.Count}"); var seg = segsLeft.First(); segsLeft.Remove(seg); var poly = new List <Vector3D>() { seg.From, seg.To }; var rotDir = 1.0; // 1=+Z, -1=-Z while (true) { List <Line3D> segsNext = null; { var hs = new HashSet <Line3D>(lcmp); { List <Line3D> tmp = null; if (segsFromDict.TryGetValue(seg.To, out tmp)) { foreach (var x in tmp.Where(r => !r.EqualsTol(tolLen, seg))) { hs.Add(x); } } if (segsToDict.TryGetValue(seg.To, out tmp)) { foreach (var x in tmp.Where(r => !r.EqualsTol(tolLen, seg))) { hs.Add(x); } } } segsNext = hs.Select(w => w.EnsureFrom(tolLen, seg.To)).ToList(); } Line3D segNext = null; var force_close_poly = false; if (polyMaxPoints > 0 && poly.Count > polyMaxPoints) { throw new Exception($"polygon [{poly.PolygonSegments(tolLen).ToCadScript()}] max point exceeded"); } //#if DEBUG // if (//poly.Count >= 2 && // poly.Any(r => r.EqualsTol(1e-2, 31.0626,-0.0018)) // //&& // //poly.Any(r => r.EqualsTol(tolLen, -42.9561, 0)) // ) // ; //#endif if (poly.Count == 2) { if (segsNext.Count == 0) { throw new Exception($"check singular segment [{seg}] cadscript [{seg.CadScript}]"); } segNext = segsNext .OrderBy(w => (-seg.V).AngleRad(tolLen, w.V)) .First(); rotDir = seg.V.CrossProduct(segNext.V).Z > 0 ? 1 : -1; } else { var qSegsNext = segsNext .Select(w => new { arad = (seg.V).AngleToward(tolLen, w.V, Vector3D.ZAxis * rotDir), seg = w }) .Where(r => r.arad <= PI).ToList(); if (qSegsNext.Count == 0) { force_close_poly = true; } else { // retrieve next segment with current rotation direction w/acutest angle segNext = qSegsNext .OrderByDescending(r => r.arad) .First().seg; } } if (force_close_poly) { break; } segsLeft.Remove(segNext); if (segNext.To.EqualsTol(tolLen, poly[0])) { break; } poly.Add(segNext.To); seg = segNext; } if (poly.Count > 2) { poly = poly.SortPoly(tolLen, Vector3D.ZAxis).ToList(); var polyCentroid = poly.Centroid(tolLen); if (!polyCentroidDone.Contains(polyCentroid)) { polys.Add(poly); polyCentroidDone.Add(polyCentroid); } } else { // todo warning } } return(polys.OrderByDescending(w => w.Count)); }
/// <summary> /// autointersect given list of segments /// ( duplicates and overlapping are removed ) /// TODO : dummy function, optimize /// </summary> public static IReadOnlyList <Line3D> AutoIntersect(this IReadOnlyList <Line3D> segs, double tolLen, bool mergeColinearSegments = true, IEnumerable <Vector3D> addictionalSplitPoints = null) { segs = segs.MergeColinearSegments(tolLen).ToList(); var segCmp = new Line3DEqualityComparer(tolLen); var vecCmp = new Vector3DEqualityComparer(tolLen); // line_hs -> split points var splitPts = new Dictionary <Line3D, HashSet <Vector3D> >(segCmp); // fill splitPts dictionary with list of segments split points for (int i = 0; i < segs.Count; ++i) { for (int j = 0; j < segs.Count; ++j) { if (i == j) { continue; } var seg_i = segs[i]; var seg_j = segs[j]; var q = seg_i.Intersect(tolLen, seg_j, true, true); if (q != null) { HashSet <Vector3D> i_hs = null; HashSet <Vector3D> j_hs = null; if (!q.EqualsTol(tolLen, seg_i.From) && !q.EqualsTol(tolLen, seg_i.To)) { if (!splitPts.TryGetValue(seg_i, out i_hs)) { i_hs = new HashSet <Vector3D>(vecCmp); splitPts.Add(seg_i, i_hs); } i_hs.Add(q); } if (!q.EqualsTol(tolLen, seg_j.From) && !q.EqualsTol(tolLen, seg_j.To)) { if (!splitPts.TryGetValue(seg_j, out j_hs)) { j_hs = new HashSet <Vector3D>(vecCmp); splitPts.Add(seg_j, j_hs); } j_hs.Add(q); } } } } // process addictional split points if (addictionalSplitPoints != null) { foreach (var pt in addictionalSplitPoints) { foreach (var seg in segs) { if (seg.SegmentContainsPoint(tolLen, pt, excludeExtreme: true)) { HashSet <Vector3D> hs = null; if (!splitPts.TryGetValue(seg, out hs)) { hs = new HashSet <Vector3D>(vecCmp); splitPts.Add(seg, hs); } hs.Add(pt); } } } } // split segment by split points and rebuild res list if (splitPts.Count > 0) { HashSet <Vector3D> qSplitPts = null; var res = new List <Line3D>(); for (int i = 0; i < segs.Count; ++i) { if (splitPts.TryGetValue(segs[i], out qSplitPts)) { res.AddRange(segs[i].Split(tolLen, qSplitPts.ToList())); } else { res.Add(segs[i]); } } segs = res; } return(segs); }