Example #1
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));
            }
        }