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); } }
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))); }
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); } }
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))); }