Example #1
0
        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);
            }
        }
Example #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)
            });
        }
 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);
        }
Example #7
0
        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);
        }
Example #9
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 #10
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());
        }
Example #11
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);
            }
        }
Example #13
0
        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));
            }
        }
Example #14
0
        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)));
        }