コード例 #1
0
        /// <summary>
        /// Compiles the stroke to the necessary triangles to draw it.
        /// </summary>
        /// <param name="path">The path to be compiled.</param>
        /// <param name="width">The width of the stroke.</param>
        /// <param name="lineCap">The line cap method for the stroke's ends.</param>
        /// <param name="lineJoin">The line join method for the stroke's midpoints</param>
        /// <param name="miterLimit">The miter limit value.</param>
        public static CompiledDrawing CompileStroke(Path path, double width,
                                                    StrokeLineCap lineCap   = StrokeLineCap.Butt,
                                                    StrokeLineJoin lineJoin = StrokeLineJoin.Bevel,
                                                    double miterLimit       = double.PositiveInfinity)
        {
            // Return empty if stroke width == 0
            if (width == 0)
            {
                return(CompiledDrawing.Empty);
            }

            // Divide the width by 2 to cope with the SVG documentation
            var halfWidth = width / 2;

            var curves = new List <Curve>();

            // Convert each split path to a fill
            foreach (var data in path.SplitCurves())
            {
                curves.AddRange(StrokeUtils.ConvertToFill(data, halfWidth, lineCap, lineJoin, miterLimit));
            }

            // And compile
            return(CompileCurves(curves, FillRule.Nonzero));
        }
コード例 #2
0
    /// <summary>
    /// Sets the current <see cref="StrokeLineCap"/>.
    /// </summary>
    /// <param name="cap">A <see cref="StrokeLineCap"/> value.</param>
    public async ValueTask SetLineCapAsync(StrokeLineCap cap)
    {
        if (_disposed)
        {
            throw new ObjectDisposedException(nameof(ImageEditorService));
        }
        if (!_moduleTask.IsValueCreated && cap == StrokeLineCap.butt)
        {
            return;
        }
        var module = await _moduleTask.Value.ConfigureAwait(false);

        await module.InvokeVoidAsync("setLineCap", cap.ToString())
        .ConfigureAwait(false);
    }
コード例 #3
0
        public PathStyle(Dictionary <string, string> properties, PathStyle?parent)
        {
            // Fill in the default values according to the default values on https://svgwg.org/svg-next/painting.htm
            // Special handling for paint servers (which now only accept colors)
            FillColor   = CSSColor.Parse(properties.GetOrDefault("fill")) ?? (parent.HasValue ? parent.Value.FillColor : Color.Black);
            StrokeColor = CSSColor.Parse(properties.GetOrDefault("stroke")) ?? parent?.StrokeColor;

            FillRule = CSSEnumPicker <FillRule> .Get(properties.GetOrDefault("fill-rule")) ?? parent?.FillRule ?? FillRule.Nonzero;

            StrokeWidth   = DoubleUtils.TryParse(properties.GetOrDefault("stroke-width")) ?? parent?.StrokeWidth ?? 1;
            StrokeLineCap = CSSEnumPicker <StrokeLineCap> .Get(properties.GetOrDefault("stroke-linecap")) ??
                            parent?.StrokeLineCap ?? StrokeLineCap.Butt;

            StrokeLineJoin = CSSEnumPicker <StrokeLineJoin> .Get(properties.GetOrDefault("stroke-linejoin")) ??
                             parent?.StrokeLineJoin ?? StrokeLineJoin.Miter;

            MiterLimit = DoubleUtils.TryParse(properties.GetOrDefault("stroke-miterlimit")) ?? parent?.StrokeWidth ?? 4;
        }
コード例 #4
0
        public static List<Vector2> Stroke(StrokeSegment[] segments, float thickness, Color32 color, StrokeLineJoin lineJoin, StrokeLineCap lineCap, float miterLimit = 4f, ClosePathRule closeLine = ClosePathRule.NEVER, float roundQuality = 10f)
        {
            if(segments == null || segments.Length == 0)
                return null;

            if(segments.Length == 1)
            {
                closeLine = ClosePathRule.NEVER;
            } else if (closeLine == ClosePathRule.AUTO)
            {
                if(segments[0].startPoint == segments[segments.Length-1].endPoint)
                {
                    closeLine = ClosePathRule.ALWAYS;
                } else {
                    closeLine = ClosePathRule.NEVER;
                }
            }

            if(segments[0].startPoint == segments[segments.Length-1].endPoint)
            {
                List<StrokeSegment> tempSegments = new List<StrokeSegment>(segments);
                tempSegments.RemoveAt(tempSegments.Count - 1);
                segments = tempSegments.ToArray();
            }

            List<Vector2> innerPoints = new List<Vector2>();
            List<Vector2> outerPoints = new List<Vector2>();
            
            if(closeLine == ClosePathRule.ALWAYS)
            {
                List<StrokeSegment> tempSegments = new List<StrokeSegment>(segments);
                tempSegments.Add(new StrokeSegment(segments[segments.Length -1].endPoint, segments[0].startPoint));
                tempSegments.Add(new StrokeSegment(segments[0].startPoint, segments[0].endPoint));
                segments = tempSegments.ToArray();
            }

            miterLimit = (miterLimit - 1f) * thickness * 2f;
            if(miterLimit < 1f) miterLimit = 1f;

            int i, i1, j, segmentsLength = segments.Length;
            
            float   segmentsAngle, segmentsAngleRotated, halfWidth = thickness * 0.5f, 
            angleProgress, radAngle = 0f, miterClipHalf = miterLimit * 0.5f, miterClipHalfDouble = miterClipHalf * miterClipHalf;
            Vector2 segmentLeftStartA, segmentLeftEndA = Vector2.zero, segmentRightStartA, segmentRightEndA = Vector2.zero,
            segmentLeftEndB, segmentRightEndB,
            intersectionLeft, intersectionRight, segmentStartCenter;
            
            Matrix4x4 rotationMatrix = Matrix4x4.TRS(Vector2.zero, Quaternion.Euler(0f, 0f, 90f), Vector2.one);
            
            if(lineCap == StrokeLineCap.butt || closeLine == ClosePathRule.ALWAYS)
            {                
                innerPoints.AddRange(new Vector2[]{
                    segments[0].startPoint - segments[0].directionNormalizedRotated * halfWidth,
                    segments[0].startPoint + segments[0].directionNormalizedRotated * halfWidth,
                });
            } else if(lineCap == StrokeLineCap.round)
            {
                segmentStartCenter = Vector2.Lerp(segments[0].startPoint - segments[0].directionNormalizedRotated * halfWidth, 
                                                  segments[0].startPoint + segments[0].directionNormalizedRotated * halfWidth, 0.5f);
                
                radAngle = Mathf.Atan2(segments[0].directionNormalizedRotated.y, segments[0].directionNormalizedRotated.x);
                
                float roundSegmentsfAlt = roundQuality * thickness;
                float roundSegmentsfAltMinusOne = roundSegmentsfAlt - 1;
                if(roundSegmentsfAltMinusOne > 0)
                {
                    for(j = 0; j <= roundSegmentsfAlt; j++)
                    {
                        angleProgress = 1f - Mathf.Clamp01(j / roundSegmentsfAltMinusOne);
                        innerPoints.Add(segmentStartCenter + new Vector2(Mathf.Cos(radAngle + angleProgress * Mathf.PI) * halfWidth, Mathf.Sin(radAngle + angleProgress * Mathf.PI) * halfWidth));
                    }
                }
                
                innerPoints.AddRange(new Vector2[]{
                    segments[0].startPoint + segments[0].directionNormalizedRotated * halfWidth,
                });
                
                outerPoints.AddRange(new Vector2[]{
                    segments[0].startPoint - segments[0].directionNormalizedRotated * halfWidth,
                });
                
            } else if(lineCap == StrokeLineCap.square)
            {
                innerPoints.AddRange(new Vector2[]{
                    segments[0].startPoint - segments[0].directionNormalized * halfWidth - segments[0].directionNormalizedRotated * halfWidth,
                    segments[0].startPoint - segments[0].directionNormalized * halfWidth + segments[0].directionNormalizedRotated * halfWidth,
                });
            }
            
            if(segmentsLength > 1)
            {
                for(i = 1; i < segmentsLength; i++)
                {
                    i1 = i - 1;
                    /*
                    if(segments[i1].length == 0f || segments[i].length == 0)
                    {
                        continue;
                    }
                    */
                    segmentsAngle = Vector2.Dot(segments[i].directionNormalized, segments[i1].directionNormalized);
                    segmentsAngleRotated = Vector2.Dot(segments[i].directionNormalized, segments[i1].directionNormalizedRotated);
                    
                    float miterLength = (1f / Mathf.Sin((Mathf.PI - Mathf.Acos(segmentsAngle)) * 0.5f)) * thickness;
                    float miterLengthHalf = miterLength * 0.5f;
                    Vector2 miterVector = Vector2.Lerp(segments[i1].directionNormalizedRotated, segments[i].directionNormalizedRotated, 0.5f).normalized;
                    Vector2 miterVectorLengthHalf = miterVector * miterLengthHalf;
                    Vector2 miterVectorRotated = rotationMatrix.MultiplyVector(miterVector);
                    
                    segmentLeftStartA = segments[i].startPoint - segments[i].directionNormalizedRotated * halfWidth;
                    segmentLeftEndA = segments[i].endPoint - segments[i].directionNormalizedRotated * halfWidth;
                    segmentRightStartA = segments[i].startPoint + segments[i].directionNormalizedRotated * halfWidth;
                    segmentRightEndA = segments[i].endPoint + segments[i].directionNormalizedRotated * halfWidth;
                    
                    //segmentLeftStartB = segments[i1].startPoint - segments[i1].directionNormalizedRotated * halfWidth;
                    segmentLeftEndB = segments[i1].endPoint - segments[i1].directionNormalizedRotated * halfWidth;
                    //segmentRightStartB = segments[i1].startPoint + segments[i1].directionNormalizedRotated * halfWidth;
                    segmentRightEndB = segments[i1].endPoint + segments[i1].directionNormalizedRotated * halfWidth;

                    if(lineJoin == StrokeLineJoin.miter)
                    {
                        if(miterLimit < miterLength)
                            lineJoin = StrokeLineJoin.bevel;
                    }

                    if(lineJoin == StrokeLineJoin.miter || lineJoin == StrokeLineJoin.miterClip)
                    {            
                        if(segmentsAngle == 1f || segmentsAngle == -1f)
                        {
                            innerPoints.AddRange(new Vector2[]{
                                segmentRightEndB,
                                segmentRightStartA,
                            });
                            
                            outerPoints.AddRange(new Vector2[]{
                                segmentLeftEndB,
                                segmentLeftStartA,
                            });
                        } else {
                            if(segmentsAngleRotated < 0)
                            {
                                if(miterLimit <= miterLength)
                                {
                                    Vector2 a = segments[i1].endPoint + miterVector * miterClipHalf;
                                    Vector2 b = segments[i1].endPoint + miterVectorLengthHalf;
                                    Vector2 c = a + miterVectorRotated;


                                    SVGMath.LineLineIntersection(out intersectionLeft, b, segmentRightEndB, a, c);
                                    SVGMath.LineLineIntersection(out intersectionRight, b, segmentRightStartA, a, c);
                                    
                                    if(miterClipHalfDouble <= (Vector2.Lerp(segmentRightEndB, segmentRightStartA, 0.5f) - segments[i1].endPoint).sqrMagnitude)
                                    {
                                        intersectionLeft = segmentRightEndB;
                                        intersectionRight = segmentRightStartA;
                                    }
                                    
                                    innerPoints.AddRange(new Vector2[]{
                                        intersectionLeft,
                                        intersectionRight,
                                    });
                                    
                                    outerPoints.AddRange(new Vector2[]{
                                        segmentLeftEndB,
                                        segmentLeftStartA,
                                    });
                                } else {
                                    intersectionRight = segments[i1].endPoint + miterVectorLengthHalf;
                                    
                                    innerPoints.AddRange(new Vector2[]{
                                        intersectionRight,
                                    });
                                    
                                    outerPoints.AddRange(new Vector2[]{
                                        segmentLeftEndB,
                                        segmentLeftStartA,
                                    });
                                }
                                
                            } else {
                                if(miterLimit <= miterLength)
                                {
                                    Vector2 a = segments[i1].endPoint - miterVector * miterClipHalf;
                                    Vector2 b = segments[i1].endPoint - miterVectorLengthHalf;
                                    Vector2 c = a + miterVectorRotated;
                                    
                                    SVGMath.LineLineIntersection(out intersectionLeft, b, segmentLeftStartA, a, c);
                                    SVGMath.LineLineIntersection(out intersectionRight, b, segmentLeftEndB, a, c);
                                    
                                    if(miterClipHalfDouble <= (Vector2.Lerp(segmentLeftStartA, segmentLeftEndB, 0.5f) - segments[i1].endPoint).sqrMagnitude)
                                    {
                                        intersectionLeft = segmentLeftStartA;
                                        intersectionRight = segmentLeftEndB;
                                    }
                                    
                                    outerPoints.AddRange(new Vector2[]{
                                        intersectionRight,
                                        intersectionLeft,
                                    });
                                    
                                    innerPoints.AddRange(new Vector2[]{
                                        segmentRightEndB,
                                        segmentRightStartA,
                                    });
                                } else {
                                    intersectionLeft = segments[i1].endPoint - miterVectorLengthHalf;
                                    
                                    outerPoints.AddRange(new Vector2[]{
                                        intersectionLeft,
                                    });
                                    
                                    innerPoints.AddRange(new Vector2[]{
                                        segmentRightEndB,
                                        segmentRightStartA,
                                    });
                                }
                            }
                            
                        }
                    } else if(lineJoin == StrokeLineJoin.bevel) {
                        innerPoints.AddRange(new Vector2[]{
                            segmentRightEndB,
                            segmentRightStartA,
                        });
                        
                        outerPoints.AddRange(new Vector2[]{
                            segmentLeftEndB,
                            segmentLeftStartA,
                        });
                    } else if(lineJoin == StrokeLineJoin.round)
                    {
                        if(segmentsAngle == 1f)
                        {
                            innerPoints.AddRange(new Vector2[]{
                                segmentRightEndB,
                                segmentRightStartA,
                            });
                            
                            outerPoints.AddRange(new Vector2[]{
                                segmentLeftEndB,
                                segmentLeftStartA,
                            });
                        } else {
                            if(segmentsAngleRotated < 0)
                            {
                                innerPoints.AddRange(new Vector2[]{
                                    segmentRightEndB,
                                });
                                
                                outerPoints.AddRange(new Vector2[]{
                                    segmentLeftEndB,
                                    segmentLeftStartA,
                                });
                                
                                segmentStartCenter = segments[i].startPoint;
                                Vector2 dir = segments[i1].directionNormalizedRotated;
                                
                                float angle = Mathf.Acos(Vector2.Dot(segments[i1].directionNormalized, segments[i].directionNormalized));
                                radAngle = Mathf.Atan2(dir.y, dir.x);
                                
                                float roundSegmentsfAlt = roundQuality * thickness * (Mathf.Acos(segmentsAngle)/ Mathf.PI);
                                if(roundSegmentsfAlt < 1) roundSegmentsfAlt = 1f;
                                float roundSegmentsfAltMinusOne = roundSegmentsfAlt;
                                if(roundSegmentsfAltMinusOne > 0)
                                {
                                    for(j = 0; j < roundSegmentsfAlt; j++)
                                    {
                                        angleProgress = Mathf.Clamp01(j / roundSegmentsfAltMinusOne);
                                        innerPoints.Add(segmentStartCenter + new Vector2(Mathf.Cos(radAngle - angleProgress * angle) * halfWidth, Mathf.Sin(radAngle - angleProgress * angle) * halfWidth));
                                    }
                                }
                                
                                innerPoints.AddRange(new Vector2[]{
                                    segmentRightStartA,
                                });
                            } else {
                                innerPoints.AddRange(new Vector2[]{
                                    segmentRightEndB,
                                    segmentRightStartA,
                                });
                                
                                outerPoints.AddRange(new Vector2[]{
                                    segmentLeftEndB,
                                    
                                });
                                
                                segmentStartCenter = segments[i].startPoint;
                                Vector2 dir = -segments[i].directionNormalizedRotated;
                                
                                float angle = Mathf.Acos(Vector2.Dot(segments[i1].directionNormalized, segments[i].directionNormalized));
                                radAngle = Mathf.Atan2(dir.y, dir.x);
                                
                                float roundSegmentsfAlt = roundQuality * thickness * (Mathf.Acos(segmentsAngle)/ Mathf.PI);
                                if(roundSegmentsfAlt < 1) roundSegmentsfAlt = 1f;
                                float roundSegmentsfAltMinusOne = roundSegmentsfAlt;
                                if(roundSegmentsfAltMinusOne > 0)
                                {
                                    for(j = 0; j < roundSegmentsfAlt; j++)
                                    {
                                        angleProgress = Mathf.Clamp01(1f -(j / roundSegmentsfAltMinusOne));
                                        outerPoints.Add(segmentStartCenter + new Vector2(Mathf.Cos(radAngle - angleProgress * angle) * halfWidth, Mathf.Sin(radAngle - angleProgress * angle) * halfWidth));
                                    }
                                }
                                
                                outerPoints.AddRange(new Vector2[]{
                                    segmentLeftStartA,
                                });
                            }
                        }
                    }
                }
            }
            
            int lastSegmentIndex = segments.Length - 1;
            
            segmentLeftStartA = segments[lastSegmentIndex].startPoint - segments[lastSegmentIndex].directionNormalizedRotated * halfWidth;
            segmentLeftEndA = segments[lastSegmentIndex].endPoint - segments[lastSegmentIndex].directionNormalizedRotated * halfWidth;
            segmentRightStartA = segments[lastSegmentIndex].startPoint + segments[lastSegmentIndex].directionNormalizedRotated * halfWidth;
            segmentRightEndA = segments[lastSegmentIndex].endPoint + segments[lastSegmentIndex].directionNormalizedRotated * halfWidth;
            
            if(closeLine == ClosePathRule.NEVER)
            {                
                if(lineCap == StrokeLineCap.butt)
                {
                    innerPoints.AddRange(new Vector2[]{
                        segmentRightEndA,
                        segmentLeftEndA,
                    });
                } else if(lineCap == StrokeLineCap.round)
                {
                    innerPoints.AddRange(new Vector2[]{
                        segmentRightEndA
                    });
                    
                    outerPoints.AddRange(new Vector2[]{
                        segmentLeftEndA,
                    });
                    
                    segmentStartCenter = Vector2.Lerp(segmentLeftEndA, segmentRightEndA, 0.5f);
                    
                    radAngle = Mathf.Atan2(-segments[lastSegmentIndex].directionNormalizedRotated.y, -segments[lastSegmentIndex].directionNormalizedRotated.x);
                    
                    float roundSegmentsfAlt = roundQuality * thickness;
                    float roundSegmentsfAltMinusOne = roundSegmentsfAlt - 1;
                    if(roundSegmentsfAltMinusOne > 0)
                    {
                        for(j = 0; j <= roundSegmentsfAlt; j++)
                        {
                            angleProgress = 1f - Mathf.Clamp01(j / roundSegmentsfAltMinusOne);
                            innerPoints.Add(segmentStartCenter + new Vector2(Mathf.Cos(radAngle + angleProgress * Mathf.PI) * halfWidth, Mathf.Sin(radAngle + angleProgress * Mathf.PI) * halfWidth));
                        }
                    }                    
                } else if(lineCap == StrokeLineCap.square)
                {
                    Vector2 lastSegmentOffset = segments[lastSegmentIndex].directionNormalized * halfWidth;
                    
                    innerPoints.AddRange(new Vector2[]{
                        segmentRightEndA + lastSegmentOffset,
                        segmentLeftEndA + lastSegmentOffset,
                    });
                }
            }
            
            if(closeLine == ClosePathRule.ALWAYS && lineJoin == StrokeLineJoin.miter || lineJoin == StrokeLineJoin.miterClip)
            {
                innerPoints.AddRange(new Vector2[]{
                    segmentRightEndA,
                    segmentLeftEndA,
                });
            }
            
            outerPoints.Reverse();
            innerPoints.AddRange(outerPoints);

            return innerPoints;
        }
コード例 #5
0
 public static Mesh StrokeMesh(List<StrokeSegment[]> segments, float thickness, Color32 color, StrokeLineJoin lineJoin, StrokeLineCap lineCap, float miterLimit = 4f, float[] dashArray = null, float dashOffset = 0f, ClosePathRule closeLine = ClosePathRule.NEVER, float roundQuality = 10f)
 {           
     List<List<Vector2>> finalPoints = StrokeShape(segments, thickness, color, lineJoin, lineCap, miterLimit, dashArray, dashOffset, closeLine, roundQuality);
     return TessellateStroke(finalPoints, color);
 }
コード例 #6
0
        public static List<List<Vector2>> StrokeShape(List<StrokeSegment[]> segments, float thickness, Color32 color, StrokeLineJoin lineJoin, StrokeLineCap lineCap, float miterLimit = 4f, float[] dashArray = null, float dashOffset = 0f, ClosePathRule closeLine = ClosePathRule.NEVER, float roundQuality = 10f)
        {
            if(segments == null || segments.Count == 0)
                return null;
            
            float totalCurveLength = 0f;
            int i, j;
            
            for(i = 0; i < segments.Count; i++)
            { 
                if(segments[i] == null)
                    continue;
                for(j = 0; j < segments[i].Length; j++)
                {
                    totalCurveLength += segments[i][j].length;
                }
            }

            if(totalCurveLength == 0f)
                return null;
            
            bool useDash;
            ProcessDashArray(ref dashArray, out useDash);
            
            ClosePathRule closeSegments = closeLine;
            List<StrokeSegment[]> finalSegments = new List<StrokeSegment[]>();

            for(i = 0; i < segments.Count; i++)
            {
                if(segments[i] == null || segments[i].Length == 0)
                    continue;

                if(!useDash)
                {
                    finalSegments.Add(segments[i]);
                } else {
                    finalSegments.AddRange(CreateDashedStroke(segments[i], dashArray, dashOffset, ref closeSegments));
                }
            }

            if(finalSegments.Count > 0)
            {
                List<List<Vector2>> finalPoints = new List<List<Vector2>>();
                for(i = 0; i < finalSegments.Count; i++)
                {
                    List<Vector2> points = Stroke(finalSegments[i], thickness, color, lineJoin, lineCap, miterLimit, closeSegments, roundQuality);
                    if(points == null || points.Count < 2)
                        continue;
                    
                    finalPoints.Add(points);
                }
                return finalPoints;
            } else {
                return null;
            }
        }
コード例 #7
0
        private static IEnumerable <Curve> GenerateLineCaps(Curve curve, bool atEnd, double halfWidth, StrokeLineCap lineCap)
        {
            // First, get the offset of the curve
            var tangent = atEnd ? curve.ExitTangent : curve.EntryTangent;
            var p       = curve.At(atEnd ? 1 : 0);

            // Now, get the offset, properly accounting for the direction
            var offset = halfWidth * tangent.CCWPerpendicular;

            // Finally, generate the line cap
            switch (lineCap)
            {
            case StrokeLineCap.Butt: yield return(Curve.Line(p + offset, p - offset)); break;

            case StrokeLineCap.Round:
                yield return(Curve.Circle(p, halfWidth, offset, -offset, !atEnd));

                break;

            case StrokeLineCap.Square:
            {
                // Generate a half-square
                var ext = offset.CCWPerpendicular;
                if (atEnd)
                {
                    ext = -ext;
                }
                yield return(Curve.Line(p + offset, p + offset + ext));

                yield return(Curve.Line(p + offset + ext, p - offset + ext));

                yield return(Curve.Line(p - offset + ext, p - offset));
            }
            break;

            default: throw new ArgumentException("Unrecognized lineCap", nameof(lineCap));
            }
        }
コード例 #8
0
        public static IEnumerable <Curve> ConvertToFill(CurveData data, double halfWidth, StrokeLineCap lineCap,
                                                        StrokeLineJoin lineJoin, double miterLimit)
        {
            // We are going to generate a "stroke fill"
            var leftCurves  = new List <Curve>();
            var rightCurves = new List <Curve>();

            // Offset each curve
            for (int i = 0; i < data.Curves.Length; i++)
            {
                leftCurves.AddRange(OffsetCurve(data.Curves[i], halfWidth));
                rightCurves.AddRange(OffsetCurve(data.Curves[i], -halfWidth));

                // Detect where the line join should be added
                if (data.IsClosed || i < data.Curves.Length - 1)
                {
                    var ik          = (i + 1) % data.Curves.Length;
                    var prevTangent = data.Curves[i].ExitTangent;
                    var nextTangent = data.Curves[ik].EntryTangent;

                    // If the angles are roughly equal, no line join
                    if (RoughlyEquals(prevTangent, nextTangent))
                    {
                        continue;
                    }
                    else
                    {
                        // Else, add to the appropriate list
                        var lineJoint = GenerateLineJoints(data.Curves[i], data.Curves[ik], halfWidth, lineJoin, miterLimit);
                        (prevTangent.Cross(nextTangent) > 0 ? rightCurves : leftCurves).AddRange(lineJoint);
                    }
                }
            }

            // If the curve is open, add the line caps
            if (!data.IsClosed)
            {
                rightCurves.InsertRange(0, GenerateLineCaps(data.Curves[0], false, halfWidth, lineCap));
                leftCurves.AddRange(GenerateLineCaps(data.Curves[data.Curves.Length - 1], true, halfWidth, lineCap));
            }

            // Reverse the inner list and each curve
            var reverseRightCurves = rightCurves.Select(c => c.Reverse).Reverse();

            // Generate the compiled fill
            return(leftCurves.Concat(reverseRightCurves).SelectMany(c => c.Simplify(true)));
        }
コード例 #9
0
        public static List <Vector2> Stroke(StrokeSegment[] segments, float thickness, Color32 color, StrokeLineJoin lineJoin, StrokeLineCap lineCap, float miterLimit = 4f, ClosePathRule closeLine = ClosePathRule.NEVER, float roundQuality = 10f)
        {
            if (segments == null || segments.Length == 0)
            {
                return(null);
            }

            if (segments.Length == 1)
            {
                closeLine = ClosePathRule.NEVER;
            }
            else if (closeLine == ClosePathRule.AUTO)
            {
                if (segments[0].startPoint == segments[segments.Length - 1].endPoint)
                {
                    closeLine = ClosePathRule.ALWAYS;
                }
                else
                {
                    closeLine = ClosePathRule.NEVER;
                }
            }

            if (segments[0].startPoint == segments[segments.Length - 1].endPoint)
            {
                List <StrokeSegment> tempSegments = new List <StrokeSegment>(segments);
                tempSegments.RemoveAt(tempSegments.Count - 1);
                segments = tempSegments.ToArray();
            }

            List <Vector2> innerPoints = new List <Vector2>();
            List <Vector2> outerPoints = new List <Vector2>();

            if (closeLine == ClosePathRule.ALWAYS)
            {
                List <StrokeSegment> tempSegments = new List <StrokeSegment>(segments);
                tempSegments.Add(new StrokeSegment(segments[segments.Length - 1].endPoint, segments[0].startPoint));
                tempSegments.Add(new StrokeSegment(segments[0].startPoint, segments[0].endPoint));
                segments = tempSegments.ToArray();
            }

            miterLimit = (miterLimit - 1f) * thickness * 2f;
            if (miterLimit < 1f)
            {
                miterLimit = 1f;
            }

            int i, i1, j, segmentsLength = segments.Length;

            float segmentsAngle, segmentsAngleRotated, halfWidth = thickness * 0.5f,
                  angleProgress, radAngle = 0f, miterClipHalf = miterLimit * 0.5f, miterClipHalfDouble = miterClipHalf * miterClipHalf;
            Vector2 segmentLeftStartA, segmentLeftEndA = Vector2.zero, segmentRightStartA, segmentRightEndA = Vector2.zero,
                    segmentLeftEndB, segmentRightEndB,
                    intersectionLeft, intersectionRight, segmentStartCenter;

            Matrix4x4 rotationMatrix = Matrix4x4.TRS(Vector2.zero, Quaternion.Euler(0f, 0f, 90f), Vector2.one);

            if (lineCap == StrokeLineCap.butt || closeLine == ClosePathRule.ALWAYS)
            {
                innerPoints.AddRange(new Vector2[] {
                    segments[0].startPoint - segments[0].directionNormalizedRotated * halfWidth,
                    segments[0].startPoint + segments[0].directionNormalizedRotated * halfWidth,
                });
            }
            else if (lineCap == StrokeLineCap.round)
            {
                segmentStartCenter = Vector2.Lerp(segments[0].startPoint - segments[0].directionNormalizedRotated * halfWidth,
                                                  segments[0].startPoint + segments[0].directionNormalizedRotated * halfWidth, 0.5f);

                radAngle = Mathf.Atan2(segments[0].directionNormalizedRotated.y, segments[0].directionNormalizedRotated.x);

                float roundSegmentsfAlt         = roundQuality * thickness;
                float roundSegmentsfAltMinusOne = roundSegmentsfAlt - 1;
                if (roundSegmentsfAltMinusOne > 0)
                {
                    for (j = 0; j <= roundSegmentsfAlt; j++)
                    {
                        angleProgress = 1f - Mathf.Clamp01(j / roundSegmentsfAltMinusOne);
                        innerPoints.Add(segmentStartCenter + new Vector2(Mathf.Cos(radAngle + angleProgress * Mathf.PI) * halfWidth, Mathf.Sin(radAngle + angleProgress * Mathf.PI) * halfWidth));
                    }
                }

                innerPoints.AddRange(new Vector2[] {
                    segments[0].startPoint + segments[0].directionNormalizedRotated * halfWidth,
                });

                outerPoints.AddRange(new Vector2[] {
                    segments[0].startPoint - segments[0].directionNormalizedRotated * halfWidth,
                });
            }
            else if (lineCap == StrokeLineCap.square)
            {
                innerPoints.AddRange(new Vector2[] {
                    segments[0].startPoint - segments[0].directionNormalized * halfWidth - segments[0].directionNormalizedRotated * halfWidth,
                    segments[0].startPoint - segments[0].directionNormalized * halfWidth + segments[0].directionNormalizedRotated * halfWidth,
                });
            }

            if (segmentsLength > 1)
            {
                for (i = 1; i < segmentsLength; i++)
                {
                    i1 = i - 1;

                    /*
                     * if(segments[i1].length == 0f || segments[i].length == 0)
                     * {
                     *  continue;
                     * }
                     */
                    segmentsAngle        = Vector2.Dot(segments[i].directionNormalized, segments[i1].directionNormalized);
                    segmentsAngleRotated = Vector2.Dot(segments[i].directionNormalized, segments[i1].directionNormalizedRotated);

                    float   miterLength           = (1f / Mathf.Sin((Mathf.PI - Mathf.Acos(segmentsAngle)) * 0.5f)) * thickness;
                    float   miterLengthHalf       = miterLength * 0.5f;
                    Vector2 miterVector           = Vector2.Lerp(segments[i1].directionNormalizedRotated, segments[i].directionNormalizedRotated, 0.5f).normalized;
                    Vector2 miterVectorLengthHalf = miterVector * miterLengthHalf;
                    Vector2 miterVectorRotated    = rotationMatrix.MultiplyVector(miterVector);

                    segmentLeftStartA  = segments[i].startPoint - segments[i].directionNormalizedRotated * halfWidth;
                    segmentLeftEndA    = segments[i].endPoint - segments[i].directionNormalizedRotated * halfWidth;
                    segmentRightStartA = segments[i].startPoint + segments[i].directionNormalizedRotated * halfWidth;
                    segmentRightEndA   = segments[i].endPoint + segments[i].directionNormalizedRotated * halfWidth;

                    //segmentLeftStartB = segments[i1].startPoint - segments[i1].directionNormalizedRotated * halfWidth;
                    segmentLeftEndB = segments[i1].endPoint - segments[i1].directionNormalizedRotated * halfWidth;
                    //segmentRightStartB = segments[i1].startPoint + segments[i1].directionNormalizedRotated * halfWidth;
                    segmentRightEndB = segments[i1].endPoint + segments[i1].directionNormalizedRotated * halfWidth;

                    if (lineJoin == StrokeLineJoin.miter)
                    {
                        if (miterLimit < miterLength)
                        {
                            lineJoin = StrokeLineJoin.bevel;
                        }
                    }

                    if (lineJoin == StrokeLineJoin.miter || lineJoin == StrokeLineJoin.miterClip)
                    {
                        if (segmentsAngle == 1f || segmentsAngle == -1f)
                        {
                            innerPoints.AddRange(new Vector2[] {
                                segmentRightEndB,
                                segmentRightStartA,
                            });

                            outerPoints.AddRange(new Vector2[] {
                                segmentLeftEndB,
                                segmentLeftStartA,
                            });
                        }
                        else
                        {
                            if (segmentsAngleRotated < 0)
                            {
                                if (miterLimit <= miterLength)
                                {
                                    Vector2 a = segments[i1].endPoint + miterVector * miterClipHalf;
                                    Vector2 b = segments[i1].endPoint + miterVectorLengthHalf;
                                    Vector2 c = a + miterVectorRotated;


                                    SVGMath.LineLineIntersection(out intersectionLeft, b, segmentRightEndB, a, c);
                                    SVGMath.LineLineIntersection(out intersectionRight, b, segmentRightStartA, a, c);

                                    if (miterClipHalfDouble <= (Vector2.Lerp(segmentRightEndB, segmentRightStartA, 0.5f) - segments[i1].endPoint).sqrMagnitude)
                                    {
                                        intersectionLeft  = segmentRightEndB;
                                        intersectionRight = segmentRightStartA;
                                    }

                                    innerPoints.AddRange(new Vector2[] {
                                        intersectionLeft,
                                        intersectionRight,
                                    });

                                    outerPoints.AddRange(new Vector2[] {
                                        segmentLeftEndB,
                                        segmentLeftStartA,
                                    });
                                }
                                else
                                {
                                    intersectionRight = segments[i1].endPoint + miterVectorLengthHalf;

                                    innerPoints.AddRange(new Vector2[] {
                                        intersectionRight,
                                    });

                                    outerPoints.AddRange(new Vector2[] {
                                        segmentLeftEndB,
                                        segmentLeftStartA,
                                    });
                                }
                            }
                            else
                            {
                                if (miterLimit <= miterLength)
                                {
                                    Vector2 a = segments[i1].endPoint - miterVector * miterClipHalf;
                                    Vector2 b = segments[i1].endPoint - miterVectorLengthHalf;
                                    Vector2 c = a + miterVectorRotated;

                                    SVGMath.LineLineIntersection(out intersectionLeft, b, segmentLeftStartA, a, c);
                                    SVGMath.LineLineIntersection(out intersectionRight, b, segmentLeftEndB, a, c);

                                    if (miterClipHalfDouble <= (Vector2.Lerp(segmentLeftStartA, segmentLeftEndB, 0.5f) - segments[i1].endPoint).sqrMagnitude)
                                    {
                                        intersectionLeft  = segmentLeftStartA;
                                        intersectionRight = segmentLeftEndB;
                                    }

                                    outerPoints.AddRange(new Vector2[] {
                                        intersectionRight,
                                        intersectionLeft,
                                    });

                                    innerPoints.AddRange(new Vector2[] {
                                        segmentRightEndB,
                                        segmentRightStartA,
                                    });
                                }
                                else
                                {
                                    intersectionLeft = segments[i1].endPoint - miterVectorLengthHalf;

                                    outerPoints.AddRange(new Vector2[] {
                                        intersectionLeft,
                                    });

                                    innerPoints.AddRange(new Vector2[] {
                                        segmentRightEndB,
                                        segmentRightStartA,
                                    });
                                }
                            }
                        }
                    }
                    else if (lineJoin == StrokeLineJoin.bevel)
                    {
                        innerPoints.AddRange(new Vector2[] {
                            segmentRightEndB,
                            segmentRightStartA,
                        });

                        outerPoints.AddRange(new Vector2[] {
                            segmentLeftEndB,
                            segmentLeftStartA,
                        });
                    }
                    else if (lineJoin == StrokeLineJoin.round)
                    {
                        if (segmentsAngle == 1f)
                        {
                            innerPoints.AddRange(new Vector2[] {
                                segmentRightEndB,
                                segmentRightStartA,
                            });

                            outerPoints.AddRange(new Vector2[] {
                                segmentLeftEndB,
                                segmentLeftStartA,
                            });
                        }
                        else
                        {
                            if (segmentsAngleRotated < 0)
                            {
                                innerPoints.AddRange(new Vector2[] {
                                    segmentRightEndB,
                                });

                                outerPoints.AddRange(new Vector2[] {
                                    segmentLeftEndB,
                                    segmentLeftStartA,
                                });

                                segmentStartCenter = segments[i].startPoint;
                                Vector2 dir = segments[i1].directionNormalizedRotated;

                                float angle = Mathf.Acos(Vector2.Dot(segments[i1].directionNormalized, segments[i].directionNormalized));
                                radAngle = Mathf.Atan2(dir.y, dir.x);

                                float roundSegmentsfAlt = roundQuality * thickness * (Mathf.Acos(segmentsAngle) / Mathf.PI);
                                if (roundSegmentsfAlt < 1)
                                {
                                    roundSegmentsfAlt = 1f;
                                }
                                float roundSegmentsfAltMinusOne = roundSegmentsfAlt;
                                if (roundSegmentsfAltMinusOne > 0)
                                {
                                    for (j = 0; j < roundSegmentsfAlt; j++)
                                    {
                                        angleProgress = Mathf.Clamp01(j / roundSegmentsfAltMinusOne);
                                        innerPoints.Add(segmentStartCenter + new Vector2(Mathf.Cos(radAngle - angleProgress * angle) * halfWidth, Mathf.Sin(radAngle - angleProgress * angle) * halfWidth));
                                    }
                                }

                                innerPoints.AddRange(new Vector2[] {
                                    segmentRightStartA,
                                });
                            }
                            else
                            {
                                innerPoints.AddRange(new Vector2[] {
                                    segmentRightEndB,
                                    segmentRightStartA,
                                });

                                outerPoints.AddRange(new Vector2[] {
                                    segmentLeftEndB,
                                });

                                segmentStartCenter = segments[i].startPoint;
                                Vector2 dir = -segments[i].directionNormalizedRotated;

                                float angle = Mathf.Acos(Vector2.Dot(segments[i1].directionNormalized, segments[i].directionNormalized));
                                radAngle = Mathf.Atan2(dir.y, dir.x);

                                float roundSegmentsfAlt = roundQuality * thickness * (Mathf.Acos(segmentsAngle) / Mathf.PI);
                                if (roundSegmentsfAlt < 1)
                                {
                                    roundSegmentsfAlt = 1f;
                                }
                                float roundSegmentsfAltMinusOne = roundSegmentsfAlt;
                                if (roundSegmentsfAltMinusOne > 0)
                                {
                                    for (j = 0; j < roundSegmentsfAlt; j++)
                                    {
                                        angleProgress = Mathf.Clamp01(1f - (j / roundSegmentsfAltMinusOne));
                                        outerPoints.Add(segmentStartCenter + new Vector2(Mathf.Cos(radAngle - angleProgress * angle) * halfWidth, Mathf.Sin(radAngle - angleProgress * angle) * halfWidth));
                                    }
                                }

                                outerPoints.AddRange(new Vector2[] {
                                    segmentLeftStartA,
                                });
                            }
                        }
                    }
                }
            }

            int lastSegmentIndex = segments.Length - 1;

            segmentLeftStartA  = segments[lastSegmentIndex].startPoint - segments[lastSegmentIndex].directionNormalizedRotated * halfWidth;
            segmentLeftEndA    = segments[lastSegmentIndex].endPoint - segments[lastSegmentIndex].directionNormalizedRotated * halfWidth;
            segmentRightStartA = segments[lastSegmentIndex].startPoint + segments[lastSegmentIndex].directionNormalizedRotated * halfWidth;
            segmentRightEndA   = segments[lastSegmentIndex].endPoint + segments[lastSegmentIndex].directionNormalizedRotated * halfWidth;

            if (closeLine == ClosePathRule.NEVER)
            {
                if (lineCap == StrokeLineCap.butt)
                {
                    innerPoints.AddRange(new Vector2[] {
                        segmentRightEndA,
                        segmentLeftEndA,
                    });
                }
                else if (lineCap == StrokeLineCap.round)
                {
                    innerPoints.AddRange(new Vector2[] {
                        segmentRightEndA
                    });

                    outerPoints.AddRange(new Vector2[] {
                        segmentLeftEndA,
                    });

                    segmentStartCenter = Vector2.Lerp(segmentLeftEndA, segmentRightEndA, 0.5f);

                    radAngle = Mathf.Atan2(-segments[lastSegmentIndex].directionNormalizedRotated.y, -segments[lastSegmentIndex].directionNormalizedRotated.x);

                    float roundSegmentsfAlt         = roundQuality * thickness;
                    float roundSegmentsfAltMinusOne = roundSegmentsfAlt - 1;
                    if (roundSegmentsfAltMinusOne > 0)
                    {
                        for (j = 0; j <= roundSegmentsfAlt; j++)
                        {
                            angleProgress = 1f - Mathf.Clamp01(j / roundSegmentsfAltMinusOne);
                            innerPoints.Add(segmentStartCenter + new Vector2(Mathf.Cos(radAngle + angleProgress * Mathf.PI) * halfWidth, Mathf.Sin(radAngle + angleProgress * Mathf.PI) * halfWidth));
                        }
                    }
                }
                else if (lineCap == StrokeLineCap.square)
                {
                    Vector2 lastSegmentOffset = segments[lastSegmentIndex].directionNormalized * halfWidth;

                    innerPoints.AddRange(new Vector2[] {
                        segmentRightEndA + lastSegmentOffset,
                        segmentLeftEndA + lastSegmentOffset,
                    });
                }
            }

            if (closeLine == ClosePathRule.ALWAYS && lineJoin == StrokeLineJoin.miter || lineJoin == StrokeLineJoin.miterClip)
            {
                innerPoints.AddRange(new Vector2[] {
                    segmentRightEndA,
                    segmentLeftEndA,
                });
            }

            outerPoints.Reverse();
            innerPoints.AddRange(outerPoints);

            return(innerPoints);
        }
コード例 #10
0
        public static List <List <Vector2> > StrokeShape(List <StrokeSegment[]> segments, float thickness, Color32 color, StrokeLineJoin lineJoin, StrokeLineCap lineCap, float miterLimit = 4f, float[] dashArray = null, float dashOffset = 0f, ClosePathRule closeLine = ClosePathRule.NEVER, float roundQuality = 10f)
        {
            if (segments == null || segments.Count == 0)
            {
                return(null);
            }

            float totalCurveLength = 0f;
            int   i, j;

            for (i = 0; i < segments.Count; i++)
            {
                if (segments[i] == null)
                {
                    continue;
                }
                for (j = 0; j < segments[i].Length; j++)
                {
                    totalCurveLength += segments[i][j].length;
                }
            }

            if (totalCurveLength == 0f)
            {
                return(null);
            }

            bool useDash;

            ProcessDashArray(ref dashArray, out useDash);

            ClosePathRule          closeSegments = closeLine;
            List <StrokeSegment[]> finalSegments = new List <StrokeSegment[]>();

            for (i = 0; i < segments.Count; i++)
            {
                if (segments[i] == null || segments[i].Length == 0)
                {
                    continue;
                }

                if (!useDash)
                {
                    finalSegments.Add(segments[i]);
                }
                else
                {
                    finalSegments.AddRange(CreateDashedStroke(segments[i], dashArray, dashOffset, ref closeSegments));
                }
            }

            if (finalSegments.Count > 0)
            {
                List <List <Vector2> > finalPoints = new List <List <Vector2> >();
                for (i = 0; i < finalSegments.Count; i++)
                {
                    List <Vector2> points = Stroke(finalSegments[i], thickness, color, lineJoin, lineCap, miterLimit, closeSegments, roundQuality);
                    if (points == null || points.Count < 2)
                    {
                        continue;
                    }

                    List <List <Vector2> > simplifiedShape = SVGGeom.SimplifyPolygon(points);
                    for (j = 0; j < simplifiedShape.Count; j++)
                    {
                        if (simplifiedShape[j] == null || simplifiedShape[j].Count == 0)
                        {
                            continue;
                        }
                        finalPoints.Add(simplifiedShape[j]);
                    }
                }
                return(finalPoints);
            }
            else
            {
                return(null);
            }
        }
コード例 #11
0
        public static Mesh StrokeMesh(StrokeSegment[] segments, float thickness, Color32 color, StrokeLineJoin lineJoin, StrokeLineCap lineCap, float miterLimit = 4f, float[] dashArray = null, float dashOffset = 0f, ClosePathRule closeLine = ClosePathRule.NEVER, float roundQuality = 10f)
        {
            if (segments == null || segments.Length == 0)
            {
                return(null);
            }

            List <List <Vector2> > finalPoints = StrokeShape(new List <StrokeSegment[]>()
            {
                segments
            }, thickness, color, lineJoin, lineCap, miterLimit, dashArray, dashOffset, closeLine, roundQuality);

            return(TessellateStroke(finalPoints, color));
        }
コード例 #12
0
 private static void GenerateLineCaps(List <Triangle> triangles, List <CurveTriangle> curveTriangles,
                                      Curve curve, bool atEnd, double width, StrokeLineCap lineCap)
 {
     throw new NotImplementedException();
 }
コード例 #13
0
        public static CompiledStroke FromSegment(Curve[] curves, bool closed, double halfWidth, StrokeLineCap lineCap,
                                                 StrokeLineJoin lineJoin, double miterLimit)
        {
            var triangles      = new List <Triangle>();
            var curveTriangles = new List <CurveTriangle>();

            // Generate the main lines
            for (int i = 0; i < curves.Length; i++)
            {
                var prevAngle = curves[(i + curves.Length - 1) % curves.Length].ExitAngle;
                var nextAngle = curves[(i + 1) % curves.Length].EntryAngle;

                if (!closed && i == 0)
                {
                    prevAngle = curves[0].EntryAngle;
                }
                if (!closed && i == curves.Length - 1)
                {
                    nextAngle = curves[curves.Length - 1].ExitAngle;
                }

                // Generate the right triangles for the right curve types
                if (curves[i].Type == CurveType.Line)
                {
                    GenerateLineTriangles(triangles, curves[i], halfWidth, prevAngle, nextAngle);
                }
                else
                {
                    GenerateCurveTriangles(curveTriangles, curves[i], halfWidth, prevAngle, nextAngle);
                }
            }

            // Generate the line joins
            for (int i = 1; i < curves.Length; i++)
            {
                GenerateLineJoints(triangles, curveTriangles, curves[i - 1], curves[i], halfWidth, lineJoin, miterLimit);
            }

            if (closed)
            {
                GenerateLineJoints(triangles, curveTriangles, curves[curves.Length - 1], curves[0], halfWidth, lineJoin, miterLimit);
            }
            else
            {
                // Generate the line caps
                GenerateLineCaps(triangles, curveTriangles, curves[0], false, halfWidth, lineCap);
                GenerateLineCaps(triangles, curveTriangles, curves[curves.Length - 1], true, halfWidth, lineCap);
            }

            // Generate the new compiled stroke
            return(new CompiledStroke()
            {
                Triangles = triangles.ToArray(),
                CurveTriangles = curveTriangles.ToArray()
            });
        }
コード例 #14
0
 /// <summary>
 /// Sets the current <see cref="Blazor.ImageEditor.StrokeLineCap"/>.
 /// </summary>
 /// <param name="cap">A <see cref="Blazor.ImageEditor.StrokeLineCap"/> value.</param>
 public async Task SetLineCapAsync(StrokeLineCap cap)
 {
     StrokeLineCap = cap;
     await Service.SetLineCapAsync(cap).ConfigureAwait(false);
 }