コード例 #1
0
        // 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));
        }
コード例 #2
0
        // 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);
        }
コード例 #3
0
        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)
            });
        }
コード例 #4
0
        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);
        }
コード例 #5
0
        /// <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));
        }
コード例 #6
0
        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());
        }
コード例 #7
0
        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)));
        }