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);
            }
        }
Example #2
0
        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)));
        }
Example #3
0
        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)));
        }