internal static bool DetectSelfIntersections(Double2[][] contours) { var totalLines = contours.Sum(c => c.Length); var la = new Double2[totalLines]; var lb = new Double2[totalLines]; int k = 0; foreach (var contour in contours) { for (int i = 0; i < contour.Length; i++) { la[k + i] = contour[i]; lb[k + i] = contour[(i + 1) % contour.Length]; } k += contour.Length; } for (int j = 0; j < totalLines; j++) { for (int i = j + 1; i < totalLines; i++) { // Check for intersections at the midpoints if (GeometricUtils.SegmentsIntersect(la[i], lb[i], la[j], lb[j], true)) { return(true); } } } return(false); }
private IEnumerable <Curve> EllipticArc_Simplify() { // If the ellipse arc is a (rotated) line if (RoughlyZero(Radii.X) || RoughlyZero(Radii.Y)) { // Find the maximum points var tests = new List <double>() { 0f, 1f }; for (int k = (int)Math.Ceiling(LesserAngle / Pi_2); k <= (int)Math.Floor(GreaterAngle / Pi_2); k++) { var t = AngleToParameter(k * Pi_2); if (GeometricUtils.Inside01(t)) { tests.Add(t); } } tests.Sort(); // Subdivide the lines for (int i = 1; i < tests.Count; i++) { yield return(Line(EllipticArc_At(tests[i - 1]), EllipticArc_At(tests[i]))); } } // Not degenerate else { yield return(this); } }
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) }); }
private static void Validate <TPixelType>(this Image <TPixelType> image, int x, int y, int width, int height) { if (!GeometricUtils.SectionsCommon(x, x + width, 0, image.Width) || !GeometricUtils.SectionsCommon(y, y + height, 0, image.Height)) { throw new ArgumentException("Cropped area has no common part with given image"); } }
private static void Validate <TPixelType>(Image <TPixelType> image, Image <TPixelType> imageToInsert, int x, int y) { if (!GeometricUtils.SectionsCommon(x, x + imageToInsert.Width, 0, image.Width) || !GeometricUtils.SectionsCommon(y, y + imageToInsert.Height, 0, image.Height)) { throw new ArgumentException("Inserted image has no common part with given image"); } }
public static Image <TPixelType> Insert <TPixelType>(this Image <TPixelType> image, Image <TPixelType> imageToInsert, int x, int y) { Validate(image, imageToInsert, x, y); GeometricUtils.SectionsCommon(x, x + imageToInsert.Width, 0, image.Width, out int w0, out int w1); GeometricUtils.SectionsCommon(y, y + imageToInsert.Height, 0, image.Height, out int h0, out int h1); int commonWidth = w1 - w0; int commonHeight = h1 - h0; image.ForBlock(w0, h0, commonWidth, commonHeight, (i, j) => { var pixel = imageToInsert.Get(i - x, j - y); image.Set(i, j, pixel); }); return(image); }
private static IEnumerable <DoubleCurveTriangle> FuseCurveTriangles(Curve c1, Curve c2) { // Extract the curve vertices var t1 = c1.CurveVertices; var t2 = c2.CurveVertices; // Pick their extrapolators var ext1 = CoordExtrapolator(t1); var ext2 = CoordExtrapolator(t2); // Now, we are going to pick the convex hull of the polygon var hull = GeometricUtils.ConvexHull(t1.Concat(t2).Select(v => v.Position).ToArray()); // Finally, calculate the new texture coordinates var vertices = hull.Select(p => new DoubleCurveVertex(p, ext1(p), ext2(p))); // And return the triangle return(DoubleCurveVertex.MakeTriangleFan(vertices.ToArray())); }
public static Image <TPixelType> Crop <TPixelType>(this Image <TPixelType> image, int x, int y, int width, int height) { Validate(image, x, y, width, height); GeometricUtils.SectionsCommon(x, x + width, 0, image.Width, out int w0, out int w1); GeometricUtils.SectionsCommon(y, y + height, 0, image.Height, out int h0, out int h1); var newWidth = w1 - w0; var newHeight = h1 - h0; var originalImage = image.Copy(); image.Initialize(newWidth, newHeight); image.ForEach((i, j) => { var pixel = originalImage.Get(w0 + i, h0 + j); image.Set(i, j, pixel); }); return(image); }
internal static CompiledDrawing CompileCurves(List <Curve> curves, SvgFillRule 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; } curveRootSets[i][pair.A] = curves[i].At(pair.A); curveRootSets[j][pair.B] = curves[j].At(pair.B); } } } // Cluster the intersections var curveRootClusters = DerivePointClustersFromRootSets(curveRootSets); // Finally, we can start building the DCEL var dcel = new DCEL.DCEL(); for (int i = 0; i < curves.Count; i++) { var prevPair = new KeyValuePair <double, int>(double.NaN, 0); foreach (var curPair in curveRootClusters[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()) { // Skip degenerate curves 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 == SvgFillRule.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))); }
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()); }
public static IEnumerable <Double2[]> RemovePolygonWedges(Double2[] polygon) { // Quickly discard degenerate polygons if (polygon.Length < 3) { return(Enumerable.Empty <Double2[]>()); } var curves = new Curve[polygon.Length]; double winding = 0; for (int i = 0; i < polygon.Length; i++) { curves[i] = Curve.Line(polygon[i], polygon[(i + 1) % polygon.Length]); winding += polygon[i].Cross(polygon[(i + 1) % polygon.Length]); } // Reunite all intersections to subdivide the curves var curveRootSets = new SortedDictionary <double, Double2> [curves.Length]; 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.Length; i++) { for (int j = i + 1; j < curves.Length; 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); curveRootSets[i][pair.A] = curveRootSets[j][pair.B] = (a + b) / 2; } } } for (int i = 0; i < curves.Length; i++) { Curve.RemoveSimilarRoots(curveRootSets[i]); } // Build the DCEL var dcel = new DCEL.DCEL(); for (int i = 0; i < curves.Length; 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].SubcurveCorrectEndpoints(prevPair, curPair); } foreach (var c in curve.Simplify()) { if (c.IsDegenerate) { continue; } dcel.AddCurve(c, prevPair.Value, curPair.Value); } } prevPair = curPair; } } // Now, remove the wedges dcel.RemoveWedges(); // Now, ensure the windings are coherent with the original face's winding Double2[] ConstructPolygon(PathCompiler.DCEL.Face face) { var array = face.Edges.Select(p => p.Curve.A).ToArray(); if (winding < 0) { Array.Reverse(array); } return(array); } // And collect the faces return(dcel.Faces.Where(f => !f.IsOuterFace).Select(ConstructPolygon)); }
private IEnumerable <Curve> CubicBezier_Simplify(bool split = false) { // If the Bézier is lines if (RoughlyEquals(A, B) && RoughlyEquals(C, D)) { yield return(Line((A + B) / 2, (C + D) / 2)); } else if ((RoughlyEquals(A, B) || RoughlyZero((B - A).Normalized.Cross((C - B).Normalized))) && (RoughlyEquals(C, D) || RoughlyZero((D - C).Normalized.Cross((C - B).Normalized)))) { var d = Derivative; var rx = Equations.SolveQuadratic(d.A.X - 2 * d.B.X + d.C.X, 2 * (d.B.X - d.A.X), d.A.X); // Get the tests var tests = rx.Concat(new[] { 0d, 1d }).Where(GeometricUtils.Inside01).ToArray(); Array.Sort(tests); // Subdivide the lines for (int i = 1; i < tests.Length; i++) { yield return(Line(CubicBezier_At(tests[i - 1]), CubicBezier_At(tests[i]))); } } // If the Bézier should be a quadratic instead else if (RoughlyZero(A - 3 * B + 3 * C - D)) { // Estimates var b1 = 3 * B - A; var b2 = 3 * C - D; // Subdivide as if it were a cube yield return(QuadraticBezier(A, (b1 + b2) / 4, D)); } // Detect loops, cusps and inflection points if required else if (split) { var roots = new List <double>(7) { 0f, 1f }; // Here we use the method based on https://stackoverflow.com/a/38644407 var da = B - A; var db = C - B; var dc = D - C; // Check for no loops on cross product double ab = da.Cross(db), ac = da.Cross(dc), bc = db.Cross(dc); if (ac * ac <= 4 * ab * bc) { // The coefficients of the canonical form var c3 = da + dc - 2 * db; // Rotate it beforehand if necessary if (!RoughlyZero(c3.Y)) { c3 = c3.NegateY; da = da.RotScale(c3); db = db.RotScale(c3); dc = dc.RotScale(c3); c3 = da + dc - 2 * db; } var c2 = 3 * (db - da); var c1 = 3 * da; // Calculate the coefficients of the loop polynomial var bb = -c1.Y / c2.Y; var s1 = c1.X / c3.X; var s2 = c2.X / c3.X; // Find the roots (that happen to be the loop points) roots.AddRange(Equations.SolveQuadratic(1, -bb, bb * (bb + s2) + s1)); } // The inflection point polynom double axby = A.X * B.Y, axcy = A.X * C.Y, axdy = A.X * D.Y; double bxay = B.X * A.Y, bxcy = B.X * C.Y, bxdy = B.X * D.Y; double cxay = C.X * A.Y, cxby = C.X * B.Y, cxdy = C.X * D.Y; double dxay = D.X * A.Y, dxby = D.X * B.Y, dxcy = D.X * C.Y; double k2 = axby - 2 * axcy + axdy - bxay + 3 * bxcy - 2 * bxdy + 2 * cxay - 3 * cxby + cxdy - dxay + 2 * dxby - dxcy; double k1 = -2 * axby + 3 * axcy - axdy + 2 * bxay - 3 * bxcy + bxdy - 3 * cxay + 3 * cxby + dxay - dxby; double k0 = axby - axcy - bxay + bxcy + cxay - cxby; // Add the roots of the inflection points roots.AddRange(Equations.SolveQuadratic(k2, k1, k0)); // Sort and the roots and remove duplicates, subdivide the curve roots.RemoveAll(r => !GeometricUtils.Inside01(r)); roots.RemoveDuplicatedValues(); for (int i = 1; i < roots.Count; i++) { yield return(CubicBezier_Subcurve(roots[i - 1], roots[i])); } } else { yield return(this); } }
private static IEnumerable <Curve> GenerateLineJoints(Curve prevCurve, Curve nextCurve, double halfWidth, StrokeLineJoin lineJoin, double miterLimit) { // First, calculate the cross-product between the tangents var exitTangent = prevCurve.ExitTangent; var entryTangent = nextCurve.EntryTangent; var diff = entryTangent.Cross(exitTangent); var sd = diff < 0 ? -1 : 1; // Account for half-turns here too // The common point and the offset vectors var p = (prevCurve.At(1) + nextCurve.At(0)) / 2; var entryOffset = sd * halfWidth * entryTangent.CCWPerpendicular; var exitOffset = sd * halfWidth * exitTangent.CCWPerpendicular; // Now, create the next triangles if necessary switch (lineJoin) { case StrokeLineJoin.Bevel: // Just the bevel line yield return(Curve.Line(p + exitOffset, p + entryOffset)); break; case StrokeLineJoin.Miter: case StrokeLineJoin.MiterClip: { // Calculate the bisector and miter length var cos = exitOffset.Dot(entryOffset) / Math.Sqrt(exitOffset.LengthSquared * entryOffset.LengthSquared); var miter = halfWidth / Math.Sqrt(0.5 + 0.5 * cos); // Check the conditions for the miter (only clip if miter-clip is explicity selected) if (lineJoin == StrokeLineJoin.Miter && miter >= halfWidth * miterLimit) { goto case StrokeLineJoin.Bevel; } // Generate the miter var miterWidth = halfWidth * miterLimit; var bisectorOffset = miter * (entryOffset + exitOffset).Normalized; if (miter < miterWidth) { yield return(Curve.Line(p + exitOffset, p + bisectorOffset)); yield return(Curve.Line(p + bisectorOffset, p + entryOffset)); } else { // Clip the miter Double2 p1, p2; // Account for the special case of a 180 degree turn if (double.IsInfinity(miter)) { var outwardOffset = entryOffset.Normalized.CCWPerpendicular; p1 = entryOffset + miterWidth * outwardOffset; p2 = exitOffset + miterWidth * outwardOffset; } else { p1 = entryOffset + miterWidth * (bisectorOffset - entryOffset) / miter; p2 = exitOffset + miterWidth * (bisectorOffset - exitOffset) / miter; } yield return(Curve.Line(p + exitOffset, p + p2)); yield return(Curve.Line(p + p2, p + p1)); yield return(Curve.Line(p + p1, p + entryOffset)); } break; } case StrokeLineJoin.Round: // Generate the circle yield return(Curve.Circle(p, halfWidth, exitOffset, entryOffset, sd < 0)); break; case StrokeLineJoin.Arcs: { // Compute the curvatures of the curves var exitKappa = prevCurve.ExitCurvature; var entryKappa = nextCurve.EntryCurvature; // If one of the curvatures is too large, fall back to round if (Math.Abs(exitKappa) * halfWidth >= 1 || Math.Abs(entryKappa) * halfWidth >= 1) { goto case StrokeLineJoin.Round; } // If both of them are zero, fall back to miter if (RoughlyZero(exitKappa) && RoughlyZero(entryKappa)) { goto case StrokeLineJoin.MiterClip; } // If one (or both) are nonzero, build the possible circles else { // The "arcs" must use a different sign convention var sign = diff > 0 ? 1 : -1; var exitRadius = 1 / exitKappa - sign * halfWidth; var entryRadius = 1 / entryKappa - sign * halfWidth; var exitCenter = exitTangent.CCWPerpendicular / exitKappa; var entryCenter = entryTangent.CCWPerpendicular / entryKappa; // Find the intersection points Double2[] points; if (!RoughlyZero(exitKappa) && !RoughlyZero(entryKappa)) { points = GeometricUtils.CircleIntersection(exitCenter, exitRadius, entryCenter, entryRadius); } else if (!RoughlyZero(exitKappa)) { points = GeometricUtils.CircleLineIntersection(exitCenter, exitRadius, entryOffset, entryTangent); } else { points = GeometricUtils.CircleLineIntersection(entryCenter, entryRadius, exitOffset, exitTangent); } // If there are no intersections, adjust the curves if (points.Length == 0) { if (!RoughlyZero(exitKappa) && !RoughlyZero(entryKappa)) { points = AdjustCircles(ref exitCenter, ref exitRadius, exitTangent.CCWPerpendicular, ref entryCenter, ref entryRadius, entryTangent.CCWPerpendicular); } else if (!RoughlyZero(exitKappa)) { points = AdjustCircleLine(ref exitCenter, ref exitRadius, exitTangent.CCWPerpendicular, entryOffset, entryTangent); } else { points = AdjustCircleLine(ref entryCenter, ref entryRadius, entryTangent.CCWPerpendicular, exitOffset, exitTangent); } } // If still no solutions are found, go to the miter-clip case if (points.Length == 0) { goto case StrokeLineJoin.MiterClip; } // Check both which point have less travelled distance double PointParameter(Double2 pt) { if (RoughlyZero(exitKappa)) { var d = exitTangent.Dot(pt - exitOffset); return(d < 0 ? double.PositiveInfinity : d); } return((Math.Sign(exitKappa) * (exitOffset - exitCenter).AngleBetween(pt - exitCenter)).WrapAngle360()); } var angles = points.Select(PointParameter).ToArray(); // Pick the point with the least travelled angle var point = angles[0] <= angles[1] ? points[0] : points[1]; // Generate the arcs to be rendered if (!RoughlyZero(exitKappa)) { yield return(Curve.Circle(p + exitCenter, Math.Abs(exitRadius), exitOffset - exitCenter, point - exitCenter, exitKappa > 0)); } else { yield return(Curve.Line(p + exitOffset, p + point)); } if (!RoughlyZero(entryKappa)) { yield return(Curve.Circle(p + entryCenter, Math.Abs(entryRadius), point - entryCenter, entryOffset - entryCenter, entryKappa > 0)); } else { yield return(Curve.Line(p + point, p + entryOffset)); } } } break; default: throw new ArgumentException("Unrecognized lineJoin", nameof(lineJoin)); } }
public bool Overlaps(Triangle other) { // Check containment if (ContainsPoint(other.A)) { return(true); } if (ContainsPoint(other.B)) { return(true); } if (ContainsPoint(other.B)) { return(true); } if (other.ContainsPoint(A)) { return(true); } if (other.ContainsPoint(B)) { return(true); } if (other.ContainsPoint(B)) { return(true); } // Check overlap if (GeometricUtils.SegmentsIntersect(A, B, other.A, other.B)) { return(true); } if (GeometricUtils.SegmentsIntersect(A, B, other.B, other.C)) { return(true); } if (GeometricUtils.SegmentsIntersect(A, B, other.C, other.A)) { return(true); } if (GeometricUtils.SegmentsIntersect(B, C, other.A, other.B)) { return(true); } if (GeometricUtils.SegmentsIntersect(B, C, other.B, other.C)) { return(true); } if (GeometricUtils.SegmentsIntersect(B, C, other.C, other.A)) { return(true); } if (GeometricUtils.SegmentsIntersect(C, A, other.A, other.B)) { return(true); } if (GeometricUtils.SegmentsIntersect(C, A, other.B, other.C)) { return(true); } if (GeometricUtils.SegmentsIntersect(C, A, other.C, other.A)) { return(true); } // Otherwise... return(false); }
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))); }