// Check if two curves are eligible for double curve promotion public static bool AreCurvesFusable(Curve c1, Curve c2) { bool Eligible(Curve ca, Curve cb) { // 1) The curves must have a common endpoint if (!DoubleUtils.RoughlyEquals(ca.At(1), cb.At(0))) { return(false); } // 2) The tangents on that endpoint must be similar if (ca.ExitTangent.Dot(cb.EntryTangent) >= -0.99) { return(false); } // 3) Both must not have the same convexity if (ca.IsConvex == cb.IsConvex) { return(false); } return(true); } // If any combination is eligible, return true return(Eligible(c1, c2) || Eligible(c2, c1)); }
// Use disjoint sets to create the clusters private static SortedDictionary <double, int>[] DerivePointClustersFromRootSets(SortedDictionary <double, Double2>[] curveRootSets) { // First, gather all points and create the disjoint sets data structure var allPoints = curveRootSets.SelectMany(set => set.Values).ToArray(); var disjointSets = new DisjointSets(allPoints.Length); // Now, reunite the clusters for (int i = 0; i < allPoints.Length; i++) { for (int j = i + 1; j < allPoints.Length; j++) { if (DoubleUtils.RoughlyEquals(allPoints[i], allPoints[j])) { disjointSets.UnionSets(i, j); } } } // Finally, attribute the clusters to the original curves int length = curveRootSets.Length; var clusters = new SortedDictionary <double, int> [length]; int k = 0; for (int i = 0; i < length; i++) { clusters[i] = new SortedDictionary <double, int>(); foreach (var kvp in curveRootSets[i]) { clusters[i][kvp.Key] = disjointSets.FindParentOfSets(k++); } } return(clusters); }
static CurveIntersectionInfo GetIntersectionInfoFromCurves(Curve c1, Curve c2) { // Utility function to test for intersection from convex polygon bool StrictlyInsideConvexPolygon(Double2[] poly, Double2 pt) { var length = poly.Length; for (int i = 0; i < length; i++) { var ik = (i + 1) % length; if (DoubleUtils.RoughlyEquals(poly[i], poly[ik])) { continue; } if ((poly[ik] - poly[i]).Cross(pt - poly[i]) <= 0) { return(false); } } return(true); } // Utility function to test for intersection with curve bool CurveIntersects(Double2[] poly, Curve curve) { var length = poly.Length; for (int i = 0; i < length; i++) { var ik = (i + 1) % length; if (DoubleUtils.RoughlyEquals(poly[i], poly[ik])) { continue; } if (curve.IntersectionsWithSegment(poly[i], poly[ik]).Any(t => t >= 0 && t <= 1)) { return(true); } } return(false); } // Get the curves' enclosing polygons var p1 = GeometricUtils.EnsureCounterclockwise(c1.EnclosingPolygon); var p2 = GeometricUtils.EnsureCounterclockwise(c2.EnclosingPolygon); // If there is no intersection, no info if (!GeometricUtils.PolygonsOverlap(p1, p2, true)) { return(null); } return(new CurveIntersectionInfo { InteriorPoints1 = p2.Where(p => StrictlyInsideConvexPolygon(p1, p)).ToArray(), InteriorPoints2 = p1.Where(p => StrictlyInsideConvexPolygon(p2, p)).ToArray(), CurveIntersects1 = CurveIntersects(p2, c1), CurveIntersects2 = CurveIntersects(p1, c2) }); }
public static int FindOrAddPoint(List <Double2> vertices, Double2 vertex) { int ind = vertices.FindIndex(x => DoubleUtils.RoughlyEquals(x, vertex)); if (ind == -1) { ind = vertices.Count; vertices.Add(vertex); } return(ind); }
/// <summary> /// Determine the filled simple segments of a path, splitting lines and curves appropriately. /// </summary> /// <param name="path">The path that is supposed to be compiled.</param> /// <param name="fillRule">The fill rule used to determine the filled components</param> /// <returns>The set of simple path components.</returns> public static CompiledDrawing CompileFill(SvgPathSegmentList path, SvgFillRule fillRule = SvgFillRule.EvenOdd) { var curveData = path.SplitCurves(); var curves = new List <Curve>(); foreach (var data in curveData) { // Add all open curves curves.AddRange(data.Curves); // Force close the open curves var p0 = data.Curves[0].At(0); var p1 = data.Curves[data.Curves.Length - 1].At(1); if (!DoubleUtils.RoughlyEquals(p0, p1)) { curves.Add(Curve.Line(p1, p0)); } } return(CompileCurves(curves, fillRule)); }
private static Double2[] FillPolygon(Curve[] contour) { // "Guess" a capacity for the list, add the first point var list = new List <Double2>((int)(1.4 * contour.Length)); // Check first if the last and first curve aren't joinable bool lastFirstJoin = FillFace.AreCurvesFusable(contour[contour.Length - 1], contour[0]); if (lastFirstJoin) { list.Add(contour[0].At(1)); } // Now, scan the curve list for pairs of scannable curves var k = lastFirstJoin ? 1 : 0; for (int i = k; i < contour.Length - k; i++) { if (i < contour.Length - 1 && FillFace.AreCurvesFusable(contour[i], contour[i + 1])) { // If they describe a positive winding on the plane, add only its endpoint var endp0 = contour[i].At(0); var endp1 = contour[i + 1].At(1); var pth = (endp0 + endp1) / 2; if (contour[i].WindingRelativeTo(pth) + contour[i + 1].WindingRelativeTo(pth) > 0) { list.Add(endp1); } else { // Else, compute the convex hull and add the correct point sequence var points = contour[i].EnclosingPolygon.Concat(contour[i + 1].EnclosingPolygon).ToArray(); var hull = GeometricUtils.ConvexHull(points); var hl = hull.Length; // We have to go through the hull clockwise. Find the first point int ik; for (ik = 0; ik < hl; ik++) { if (hull[ik] == endp0) { break; } } // And run through it for (int i0 = ik; i0 != (ik + 1) % hl; i0 = (i0 + hl - 1) % hl) { list.Add(hull[i0]); } } i++; } else if (contour[i].Type == CurveType.Line || contour[i].IsConvex) { list.Add(contour[i].At(1)); } else { list.AddRange(contour[i].EnclosingPolygon.Skip(1)); } } // Sanitize the list; identical vertices var indices = new List <int>(); for (int i = 0; i < list.Count; i++) { if (DoubleUtils.RoughlyEquals(list[i], list[(i + 1) % list.Count])) { indices.Add(i); } } list.ExtractIndices(indices.ToArray()); return(list.ToArray()); }
internal static CompiledDrawing CompileCurves(List <Curve> curves, FillRule fillRule) { // Reunite all intersections to subdivide the curves var curveRootSets = new SortedDictionary <double, Double2> [curves.Count]; for (int i = 0; i < curveRootSets.Length; i++) { curveRootSets[i] = new SortedDictionary <double, Double2>() { [0] = curves[i].At(0), [1] = curves[i].At(1) } } ; // Get all intersections for (int i = 0; i < curves.Count; i++) { for (int j = i + 1; j < curves.Count; j++) { foreach (var pair in Curve.Intersections(curves[i], curves[j])) { if (!GeometricUtils.Inside01(pair.A) || !GeometricUtils.Inside01(pair.B)) { continue; } var a = curves[i].At(pair.A); var b = curves[j].At(pair.B); if (!DoubleUtils.RoughlyEquals(a / 16, b / 16)) { //Console.WriteLine(string.Join(" ", Curve.Intersections(curves[i], curves[j]).Select(c => $"({c.A} {c.B})"))); Debug.Assert(false, "Problem here..."); } curveRootSets[i][pair.A] = curveRootSets[j][pair.B] = (a + b) / 2; } } } //for (int i = 0; i < curves.Count; i++) //Curve.RemoveSimilarRoots(curveRootSets[i]); // Finally, we can start building the DCEL var dcel = new DCEL.DCEL(); for (int i = 0; i < curves.Count; i++) { KeyValuePair <double, Double2> prevPair = new KeyValuePair <double, Double2>(double.NaN, new Double2()); foreach (var curPair in curveRootSets[i]) { if (!double.IsNaN(prevPair.Key)) { Curve curve; if (prevPair.Key == 0 && curPair.Key == 1) { curve = curves[i]; } else { curve = curves[i].Subcurve(prevPair.Key, curPair.Key); } foreach (var c in curve.Simplify()) { //if (c.IsDegenerate) continue; dcel.AddCurve(c, prevPair.Value, curPair.Value); //Console.WriteLine(dcel); //Console.ReadLine(); } } prevPair = curPair; } } //Console.WriteLine(dcel); //Console.ReadLine(); // Now, we remove wedges and assign the fill numbers dcel.RemoveWedges(); //Console.WriteLine(dcel); //Console.ReadLine(); dcel.AssignFillNumbers(); //Console.WriteLine(dcel); //Console.ReadLine(); // Pick the appropriate predicate for the fill rule Func <DCEL.Face, bool> facePredicate; if (fillRule == FillRule.Evenodd) { facePredicate = f => f.FillNumber % 2 != 0; } else { facePredicate = f => f.FillNumber != 0; } // Simplify the faces dcel.SimplifyFaces(facePredicate); //Console.WriteLine(dcel); //Console.ReadLine(); // Generate the filled faces var fills = dcel.Faces.Where(facePredicate).Select(face => new FillFace(face.Contours.Select(contour => contour.CyclicalSequence.Select(e => e.Curve).ToArray()).ToArray())); // Generace the filled faces return(CompiledDrawing.ConcatMany(fills.Select(CompiledDrawing.FromFace))); }