Exemplo n.º 1
0
        /// <summary>Builds a BezierContour from a Rectangle.</summary>
        /// <param name="rect">The rectangle to build the contour from</param>
        /// <param name="radiusTL">The top-left radius of the rectangle</param>
        /// <param name="radiusTR">The top-right radius of the rectangle</param>
        /// <param name="radiusBR">The bottom-right radius of the rectangle</param>
        /// <param name="radiusBL">The bottom-left radius of the rectangle</param>
        /// <returns>A BezierContour that follows the rectangle contour</returns>
        public static BezierContour BuildRectangleContour(Rect rect, Vector2 radiusTL, Vector2 radiusTR, Vector2 radiusBR, Vector2 radiusBL)
        {
            var width  = rect.size.x;
            var height = rect.size.y;

            var halfSize = new Vector2(width / 2.0f, height / 2.0f);

            radiusTL = Vector2.Max(Vector2.Min(radiusTL, halfSize), Vector2.zero);
            radiusTR = Vector2.Max(Vector2.Min(radiusTR, halfSize), Vector2.zero);
            radiusBR = Vector2.Max(Vector2.Min(radiusBR, halfSize), Vector2.zero);
            radiusBL = Vector2.Max(Vector2.Min(radiusBL, halfSize), Vector2.zero);

            var leftSegmentSize   = height - (radiusBL.y + radiusTL.y);
            var topSegmentSize    = width - (radiusTL.x + radiusTR.x);
            var rightSegmentSize  = height - (radiusBR.y + radiusTR.y);
            var bottomSegmentSize = width - (radiusBL.x + radiusBR.x);

            var segments = new List <BezierPathSegment>(8);
            BezierPathSegment seg;

            if (leftSegmentSize > VectorUtils.Epsilon)
            {
                seg = MakePathLine(new Vector2(0.0f, radiusTL.y + leftSegmentSize), new Vector2(0.0f, radiusTL.y))[0];
                segments.Add(seg);
            }

            if (radiusTL.magnitude > VectorUtils.Epsilon)
            {
                var circleArc = VectorUtils.MakeArc(Vector2.zero, -Mathf.PI, Mathf.PI / 2.0f, 1.0f);
                circleArc = VectorUtils.TransformBezierPath(circleArc, radiusTL, 0.0f, radiusTL);
                segments.Add(circleArc[0]);
            }

            if (topSegmentSize > VectorUtils.Epsilon)
            {
                seg = MakePathLine(new Vector2(radiusTL.x, 0.0f), new Vector2(radiusTL.x + topSegmentSize, 0.0f))[0];
                segments.Add(seg);
            }

            if (radiusTR.magnitude > VectorUtils.Epsilon)
            {
                var topRight  = new Vector2(width - radiusTR.x, radiusTR.y);
                var circleArc = VectorUtils.MakeArc(Vector2.zero, -Mathf.PI / 2.0f, Mathf.PI / 2.0f, 1.0f);
                circleArc = VectorUtils.TransformBezierPath(circleArc, topRight, 0.0f, radiusTR);
                segments.Add(circleArc[0]);
            }

            if (rightSegmentSize > VectorUtils.Epsilon)
            {
                seg = MakePathLine(new Vector2(width, radiusTR.y), new Vector2(width, radiusTR.y + rightSegmentSize))[0];
                segments.Add(seg);
            }

            if (radiusBR.magnitude > VectorUtils.Epsilon)
            {
                var bottomRight = new Vector2(width - radiusBR.x, height - radiusBR.y);
                var circleArc   = VectorUtils.MakeArc(Vector2.zero, 0.0f, Mathf.PI / 2.0f, 1.0f);
                circleArc = VectorUtils.TransformBezierPath(circleArc, bottomRight, 0.0f, radiusBR);
                segments.Add(circleArc[0]);
            }

            if (bottomSegmentSize > VectorUtils.Epsilon)
            {
                seg = MakePathLine(new Vector2(width - radiusBR.x, height), new Vector2(width - (radiusBR.x + bottomSegmentSize), height))[0];
                segments.Add(seg);
            }

            if (radiusBL.magnitude > VectorUtils.Epsilon)
            {
                var bottomLeft = new Vector2(radiusBL.x, height - radiusBL.y);
                var circleArc  = VectorUtils.MakeArc(Vector2.zero, Mathf.PI / 2.0f, Mathf.PI / 2.0f, 1.0f);
                circleArc = VectorUtils.TransformBezierPath(circleArc, bottomLeft, 0.0f, radiusBL);
                segments.Add(circleArc[0]);
            }

            // Offset segments to position
            for (int i = 0; i < segments.Count; ++i)
            {
                var s = segments[i];
                s.P0       += rect.position;
                s.P1       += rect.position;
                s.P2       += rect.position;
                segments[i] = s;
            }

            return(new BezierContour()
            {
                Segments = segments.ToArray(), Closed = true
            });
        }
        internal static List <Vector2[]> TraceNodeHierarchyShapes(SceneNode root, TessellationOptions tessellationOptions)
        {
            var shapes = new List <Vector2[]>();

            foreach (var nodeInfo in WorldTransformedSceneNodes(root, null))
            {
                var node = nodeInfo.Node;

                // We process the drawables even though they are obsolete, until we remove the IDrawable interface entirely
                if (node.Drawables != null)
                {
                    foreach (var drawable in node.Drawables)
                    {
                        var vectorShape = drawable as Shape;
                        if (vectorShape != null)
                        {
                            foreach (var c in vectorShape.Contours)
                            {
                                var shape = VectorUtils.TraceShape(c, vectorShape.PathProps.Stroke, tessellationOptions);
                                if (shape.Length > 0)
                                {
                                    shapes.Add(shape.Select(v => nodeInfo.WorldTransform * v).ToArray());
                                }
                            }
                            continue;
                        }

                        var vectorPath = drawable as Path;
                        if (vectorPath != null)
                        {
                            var shape = VectorUtils.TraceShape(vectorPath.Contour, vectorPath.PathProps.Stroke, tessellationOptions);
                            if (shape.Length > 0)
                            {
                                shapes.Add(shape.Select(v => nodeInfo.WorldTransform * v).ToArray());
                            }
                            continue;
                        }

                        var vectorRect = drawable as Rectangle;
                        if (vectorRect != null)
                        {
                            var shape = VectorUtils.TraceRectangle(vectorRect, vectorRect.PathProps.Stroke, tessellationOptions);
                            if (shape.Length > 0)
                            {
                                shapes.Add(shape.Select(v => nodeInfo.WorldTransform * v).ToArray());
                            }
                            continue;
                        }
                    }
                }

                if (node.Shapes != null)
                {
                    foreach (var shape in node.Shapes)
                    {
                        foreach (var c in shape.Contours)
                        {
                            var tracedShape = VectorUtils.TraceShape(c, shape.PathProps.Stroke, tessellationOptions);
                            if (tracedShape.Length > 0)
                            {
                                shapes.Add(tracedShape.Select(v => nodeInfo.WorldTransform * v).ToArray());
                            }
                        }
                    }
                }
            }

            return(shapes);
        }
Exemplo n.º 3
0
        internal static void MakeVectorImageAsset(IEnumerable <VectorUtils.Geometry> geoms, uint rasterSize, out UnityEngine.Object outAsset, out Texture2D outTexAtlas)
        {
            var atlas = VectorUtils.GenerateAtlas(geoms, rasterSize, false, false);

            if (atlas != null)
            {
                VectorUtils.FillUVs(geoms, atlas);
            }

            bool hasTexture = atlas != null && atlas.Texture != null;

            outTexAtlas = hasTexture ? atlas.Texture : null;

            var vertices = new List <InternalBridge.VectorImageVertexBridge>(100);
            var indices  = new List <UInt16>(300);
            var settings = new List <InternalBridge.GradientSettingsBridge>();

            var min = new Vector2(float.MaxValue, float.MaxValue);
            var max = new Vector2(float.MinValue, float.MinValue);

            foreach (var geom in geoms)
            {
                if (geom.Vertices.Length == 0)
                {
                    continue;
                }
                var b = VectorUtils.Bounds(geom.Vertices.Select(v => geom.WorldTransform.MultiplyPoint(v)));
                min = Vector2.Min(min, b.min);
                max = Vector2.Max(max, b.max);
            }
            var bounds = Rect.zero;

            if (min.x != float.MaxValue)
            {
                bounds = new Rect(min, max - min);
            }

            // Save written settings to avoid duplicates
            var writtenSettings = new HashSet <int>();

            writtenSettings.Add(0);

            // Create a map of filling -> atlas entry
            var fillEntries = new Dictionary <IFill, VectorUtils.PackRectItem>();

            if (atlas != null && atlas.Entries != null)
            {
                foreach (var entry in atlas.Entries)
                {
                    if (entry.Fill != null)
                    {
                        fillEntries[entry.Fill] = entry;
                    }
                }
            }

            if (hasTexture && atlas != null && atlas.Entries != null && atlas.Entries.Count > 0)
            {
                // Write the 'white' texel info
                var entry = atlas.Entries[atlas.Entries.Count - 1];
                settings.Add(new InternalBridge.GradientSettingsBridge()
                {
                    gradientType = InternalBridge.GradientTypeBridge.Linear,
                    addressMode  = InternalBridge.AddressModeBridge.Wrap,
                    radialFocus  = Vector2.zero,
                    location     = new RectInt((int)entry.Position.x, (int)entry.Position.y, (int)entry.Size.x, (int)entry.Size.y)
                });
            }

            foreach (var geom in geoms)
            {
                for (int i = 0; i < geom.Vertices.Length; ++i)
                {
                    var v = geom.WorldTransform.MultiplyPoint(geom.Vertices[i]);
                    v -= bounds.position;
                    geom.Vertices[i] = v;
                }

                VectorUtils.AdjustWinding(geom.Vertices, geom.Indices, VectorUtils.WindingDir.CCW);

                var count = vertices.Count;
                for (int i = 0; i < geom.Vertices.Length; ++i)
                {
                    Vector3 p = (Vector3)geom.Vertices[i];
                    p.z = Vertex.nearZ;
                    vertices.Add(new InternalBridge.VectorImageVertexBridge()
                    {
                        position     = p,
                        uv           = hasTexture ? geom.UVs[i] : Vector2.zero,
                        tint         = geom.Color,
                        settingIndex = (uint)geom.SettingIndex
                    });
                }

                indices.AddRange(geom.Indices.Select(i => (UInt16)(i + count)));

                if (atlas != null && atlas.Entries != null && atlas.Entries.Count > 0)
                {
                    VectorUtils.PackRectItem entry;
                    if (geom.Fill == null || !fillEntries.TryGetValue(geom.Fill, out entry) || writtenSettings.Contains(entry.SettingIndex))
                    {
                        continue;
                    }

                    writtenSettings.Add(entry.SettingIndex);

                    var gradientType = GradientFillType.Linear;
                    var radialFocus  = Vector2.zero;
                    var addressMode  = AddressMode.Wrap;

                    var gradientFill = geom.Fill as GradientFill;
                    if (gradientFill != null)
                    {
                        gradientType = gradientFill.Type;
                        radialFocus  = gradientFill.RadialFocus;
                        addressMode  = gradientFill.Addressing;
                    }

                    var textureFill = geom.Fill as TextureFill;
                    if (textureFill != null)
                    {
                        addressMode = textureFill.Addressing;
                    }

                    settings.Add(new InternalBridge.GradientSettingsBridge()
                    {
                        gradientType = (InternalBridge.GradientTypeBridge)gradientType,
                        addressMode  = (InternalBridge.AddressModeBridge)addressMode,
                        radialFocus  = radialFocus,
                        location     = new RectInt((int)entry.Position.x, (int)entry.Position.y, (int)entry.Size.x, (int)entry.Size.y)
                    });
                }
            }

            outAsset = InternalBridge.MakeVectorImageAsset(vertices, indices, outTexAtlas, settings, bounds.size);
        }
Exemplo n.º 4
0
        private static Texture2D BuildAtlasWithEncodedSettings(InternalBridge.GradientSettingsBridge[] settings, Texture2D atlas)
        {
            var oldActive = RenderTexture.active;

            int width  = atlas.width + 3;
            int height = Math.Max(settings.Length, atlas.height);

            var desc = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32, 0)
            {
                sRGB = QualitySettings.activeColorSpace == ColorSpace.Linear
            };
            var rt = RenderTexture.GetTemporary(desc);

            GL.Clear(false, true, Color.black, 1.0f);
            Graphics.Blit(atlas, rt, Vector2.one, new Vector2(-3.0f / width, 0.0f));
            RenderTexture.active = rt;

            Texture2D copy = new Texture2D(width, height, TextureFormat.RGBA32, false);

            copy.hideFlags = HideFlags.HideAndDontSave;
            copy.ReadPixels(new Rect(0, 0, width, height), 0, 0);

            // This encoding procedure is duplicated a few times, do something about it
            var rawSettingsTex = new VectorUtils.RawTexture()
            {
                Width = 3, Height = settings.Length,
                Rgba  = new Color32[3 * settings.Length]
            };

            for (int i = 0; i < settings.Length; ++i)
            {
                var g = settings[i];

                // There are 3 consecutive pixels to store the settings
                int destX = 0;
                int destY = i;

                if (g.gradientType == InternalBridge.GradientTypeBridge.Radial)
                {
                    var focus = g.radialFocus;
                    focus  += Vector2.one;
                    focus  /= 2.0f;
                    focus.y = 1.0f - focus.y;

                    VectorUtils.WriteRawFloat4Packed(rawSettingsTex, ((float)g.gradientType) / 255, ((float)g.addressMode) / 255, focus.x, focus.y, destX++, destY);
                }
                else
                {
                    VectorUtils.WriteRawFloat4Packed(rawSettingsTex, 0.0f, ((float)g.addressMode) / 255, 0.0f, 0.0f, destX++, destY);
                }

                var pos  = g.location.position;
                var size = g.location.size;
                size.x -= 1;
                size.y -= 1;
                VectorUtils.WriteRawInt2Packed(rawSettingsTex, (int)pos.x + 3, (int)pos.y, destX++, destY);
                VectorUtils.WriteRawInt2Packed(rawSettingsTex, (int)size.x, (int)size.y, destX++, destY);
            }

            copy.SetPixels32(0, 0, 3, settings.Length, rawSettingsTex.Rgba, 0);
            copy.Apply();

            RenderTexture.active = oldActive;
            RenderTexture.ReleaseTemporary(rt);

            return(copy);
        }
        /// <summary>
        /// Tessellates a path.
        /// </summary>
        /// <param name="contour">The path to tessellate</param>
        /// <param name="pathProps">The path properties</param>
        /// <param name="tessellateOptions">The tessellation options</param>
        /// <param name="vertices">The resulting vertices</param>
        /// <param name="indices">The resulting triangles</param>
        /// <remarks>
        /// The individual line segments generated during tessellation are made out of a set of ordered vertices. It is important
        /// to honor this ordering so joining and and capping connect properly with the existing vertices without generating dupes.
        /// The ordering assumed is as follows:
        /// The last two vertices of a piece must be such that the first is generated at the end with a positive half-thickness
        /// while the second vertex is at the end too but at a negative half-thickness.
        /// No assumptions are enforced for other vertices before the two last vertices.
        /// </remarks>
        public static void TessellatePath(BezierContour contour, PathProperties pathProps, TessellationOptions tessellateOptions, out Vector2[] vertices, out UInt16[] indices)
        {
            if (tessellateOptions.StepDistance < Epsilon)
            {
                throw new Exception("stepDistance too small");
            }

            if (contour.Segments.Length < 2)
            {
                vertices = new Vector2[0];
                indices  = new UInt16[0];
                return;
            }

            tessellateOptions.MaxCordDeviation     = Mathf.Max(0.0001f, tessellateOptions.MaxCordDeviation);
            tessellateOptions.MaxTanAngleDeviation = Mathf.Max(0.0001f, tessellateOptions.MaxTanAngleDeviation);

            UnityEngine.Profiling.Profiler.BeginSample("TessellatePath");

            float[] segmentLengths = VectorUtils.SegmentsLengths(contour.Segments, contour.Closed);

            // Approximate the number of vertices/indices we need to store the results so we reduce memory reallocations during work
            float approxTotalLength = 0.0f;

            foreach (var s in segmentLengths)
            {
                approxTotalLength += s;
            }

            int approxStepCount = Math.Max((int)(approxTotalLength / tessellateOptions.StepDistance + 0.5f), 2);

            if (pathProps.Stroke.Pattern != null)
            {
                approxStepCount += pathProps.Stroke.Pattern.Length * 2;
            }

            List <Vector2> verts = new List <Vector2>(approxStepCount * 2 + 32);    // A little bit possibly for the endings
            List <UInt16>  inds  = new List <UInt16>((int)(verts.Capacity * 1.5f)); // Usually every 4 verts represent a quad that uses 6 indices

            var patternIt = new PathPatternIterator(pathProps.Stroke.Pattern, pathProps.Stroke.PatternOffset);
            var pathIt    = new PathDistanceForwardIterator(contour.Segments, contour.Closed, tessellateOptions.MaxCordDeviationSquared, tessellateOptions.MaxTanAngleDeviationCosine, tessellateOptions.SamplingStepSize);

            JoiningInfo[] joiningInfo = new JoiningInfo[2];
            HandleNewSegmentJoining(pathIt, patternIt, joiningInfo, pathProps.Stroke.HalfThickness, segmentLengths);

            int rangeIndex = 0;

            while (!pathIt.Ended)
            {
                if (patternIt.IsSolid)
                {
                    TessellateRange(patternIt.SegmentLength, pathIt, patternIt, pathProps, tessellateOptions, joiningInfo, segmentLengths, approxTotalLength, rangeIndex++, verts, inds);
                }
                else
                {
                    SkipRange(patternIt.SegmentLength, pathIt, patternIt, pathProps, joiningInfo, segmentLengths);
                }
                patternIt.Advance();
            }

            vertices = verts.ToArray();
            indices  = inds.ToArray();

            UnityEngine.Profiling.Profiler.EndSample();
        }
        static void GenerateTip(BezierSegment segment, bool atStart, float t, PathEnding ending, float halfThickness, TessellationOptions tessellateOptions, List <Vector2> verts, List <UInt16> inds)
        {
            // The tip includes the vertices at the end itself
            Vector2 tan, nrm;
            var     pos        = VectorUtils.EvalFull(segment, t, out tan, out nrm);
            int     indexStart = verts.Count;

            switch (ending)
            {
            case PathEnding.Chop:
                if (atStart)
                {
                    verts.Add(pos + nrm * halfThickness);
                    verts.Add(pos - nrm * halfThickness);
                }
                else
                {
                    // Not much, path segments are always expected to be generated perpendicular to the path
                    // at the segment point location, so we don't have to do anything for the ending
                }
                break;

            case PathEnding.Square:
                if (atStart)
                {
                    verts.Add(pos + nrm * halfThickness - tan * halfThickness);
                    verts.Add(pos - nrm * halfThickness - tan * halfThickness);
                    verts.Add(pos + nrm * halfThickness);
                    verts.Add(pos - nrm * halfThickness);

                    inds.Add((UInt16)(indexStart + 0));
                    inds.Add((UInt16)(indexStart + 3));
                    inds.Add((UInt16)(indexStart + 1));
                    inds.Add((UInt16)(indexStart + 0));
                    inds.Add((UInt16)(indexStart + 2));
                    inds.Add((UInt16)(indexStart + 3));
                }
                else
                {
                    // Relying on the last two vertices, and just adding two of our own here
                    verts.Add(pos + nrm * halfThickness + tan * halfThickness);
                    verts.Add(pos - nrm * halfThickness + tan * halfThickness);

                    inds.Add((UInt16)(indexStart + 0 - 2));
                    inds.Add((UInt16)(indexStart + 3 - 2));
                    inds.Add((UInt16)(indexStart + 1 - 2));
                    inds.Add((UInt16)(indexStart + 0 - 2));
                    inds.Add((UInt16)(indexStart + 2 - 2));
                    inds.Add((UInt16)(indexStart + 3 - 2));
                }
                break;

            case PathEnding.Round:
                float arcSign     = atStart ? -1 : 1;
                int   arcSegments = CalculateArcSteps(halfThickness, 0, Mathf.PI, tessellateOptions);
                for (int i = 1; i < arcSegments; i++)
                {
                    float angle = Mathf.PI * (i / (float)arcSegments);
                    verts.Add(pos + Matrix2D.RotateLH(angle) * nrm * halfThickness * arcSign);
                }

                if (atStart)
                {
                    // Note how we maintain the last two vertices being setup for connection by the rest of the path vertices
                    int indexTipStart = verts.Count;
                    verts.Add(pos + nrm * halfThickness);
                    verts.Add(pos - nrm * halfThickness);

                    for (int i = 1; i < arcSegments; i++)
                    {
                        inds.Add((UInt16)(indexTipStart + 1));
                        inds.Add((UInt16)(indexStart + i - 1));
                        inds.Add((UInt16)(indexStart + i));
                    }
                }
                else
                {
                    inds.Add((UInt16)(indexStart - 1));
                    inds.Add((UInt16)(indexStart - 2));
                    inds.Add((UInt16)(indexStart + 0));
                    for (int i = 1; i < arcSegments - 1; i++)
                    {
                        inds.Add((UInt16)(indexStart - 1));
                        inds.Add((UInt16)(indexStart + i - 1));
                        inds.Add((UInt16)(indexStart + i));
                    }
                }
                break;

            default:
                System.Diagnostics.Debug.Assert(false);     // Joining has its own function
                break;
            }
        }
        static JoiningInfo ForeseeJoining(BezierSegment end, BezierSegment start, float halfThickness, float endSegmentLength)
        {
            JoiningInfo joinInfo = new JoiningInfo();

            // The joining generates the vertices at both ends as well as the joining itself
            joinInfo.JoinPos     = end.P3;
            joinInfo.TanAtEnd    = VectorUtils.EvalTangent(end, 1.0f);
            joinInfo.NormAtEnd   = Vector2.Perpendicular(joinInfo.TanAtEnd);
            joinInfo.TanAtStart  = VectorUtils.EvalTangent(start, 0.0f);
            joinInfo.NormAtStart = Vector2.Perpendicular(joinInfo.TanAtStart);

            // If the tangents are continuous at the join location, we don't have
            // to generate a corner, we do a "simple" join by just connecting the vertices
            // from the two segments directly
            float cosAngleBetweenTans = Vector2.Dot(joinInfo.TanAtEnd, joinInfo.TanAtStart);

            joinInfo.SimpleJoin = Mathf.Approximately(Mathf.Abs(cosAngleBetweenTans), 1.0f);
            if (joinInfo.SimpleJoin)
            {
                return(null);
            }

            joinInfo.PosThicknessEnd   = joinInfo.JoinPos + joinInfo.NormAtEnd * halfThickness;
            joinInfo.NegThicknessEnd   = joinInfo.JoinPos - joinInfo.NormAtEnd * halfThickness;
            joinInfo.PosThicknessStart = joinInfo.JoinPos + joinInfo.NormAtStart * halfThickness;
            joinInfo.NegThicknessStart = joinInfo.JoinPos - joinInfo.NormAtStart * halfThickness;

            if (joinInfo.SimpleJoin)
            {
                joinInfo.PosThicknessClosingPoint = Vector2.LerpUnclamped(joinInfo.PosThicknessEnd, joinInfo.PosThicknessStart, 0.5f);
                joinInfo.NegThicknessClosingPoint = Vector2.LerpUnclamped(joinInfo.NegThicknessEnd, joinInfo.NegThicknessStart, 0.5f);
            }
            else
            {
                joinInfo.PosThicknessClosingPoint = VectorUtils.IntersectLines(joinInfo.PosThicknessEnd, joinInfo.PosThicknessEnd + joinInfo.TanAtEnd, joinInfo.PosThicknessStart, joinInfo.PosThicknessStart + joinInfo.TanAtStart);
                joinInfo.NegThicknessClosingPoint = VectorUtils.IntersectLines(joinInfo.NegThicknessEnd, joinInfo.NegThicknessEnd + joinInfo.TanAtEnd, joinInfo.NegThicknessStart, joinInfo.NegThicknessStart + joinInfo.TanAtStart);

                if (float.IsInfinity(joinInfo.PosThicknessClosingPoint.x) || float.IsInfinity(joinInfo.PosThicknessClosingPoint.y))
                {
                    joinInfo.PosThicknessClosingPoint = joinInfo.JoinPos;
                }
                if (float.IsInfinity(joinInfo.NegThicknessClosingPoint.x) || float.IsInfinity(joinInfo.NegThicknessClosingPoint.y))
                {
                    joinInfo.NegThicknessClosingPoint = joinInfo.JoinPos;
                }
            }

            // Should we round the positive thickness side or the negative thickness side?
            joinInfo.RoundPosThickness = PointOnTheLeftOfLine(Vector2.zero, joinInfo.TanAtEnd, joinInfo.TanAtStart);

            // Inner corner vertex should be calculated by intersection of the inner segments
            Vector2[] startTrail = null, endTrail = null;
            Vector2   intersectionOnStart = Vector2.zero, intersectionOnEnd = Vector2.zero;

            if (!joinInfo.SimpleJoin)
            {
                BezierSegment endFlipped            = VectorUtils.FlipSegment(end);
                Vector2       thicknessClosingPoint = joinInfo.RoundPosThickness ? joinInfo.PosThicknessClosingPoint : joinInfo.NegThicknessClosingPoint;
                Vector2       meetingPoint          = end.P3;
                Vector2       thicknessDiagonalEnd  = meetingPoint + (thicknessClosingPoint - meetingPoint) * 10.0f;
                startTrail = LineBezierThicknessIntersect(
                    start, joinInfo.RoundPosThickness ? -halfThickness : halfThickness, meetingPoint, thicknessDiagonalEnd,
                    out joinInfo.InnerCornerDistFromStart, out intersectionOnStart);
                endTrail = LineBezierThicknessIntersect(
                    endFlipped, joinInfo.RoundPosThickness ? halfThickness : -halfThickness, meetingPoint, thicknessDiagonalEnd,
                    out joinInfo.InnerCornerDistToEnd, out intersectionOnEnd);
            }

            bool intersectionFound = false;

            if ((startTrail != null) && (endTrail != null))
            {
                var intersect      = VectorUtils.IntersectLines(startTrail[0], startTrail[1], endTrail[0], endTrail[1]);
                var isOnStartTrail = PointOnLineIsWithinSegment(startTrail[0], startTrail[1], intersect);
                var isOnEndTrail   = PointOnLineIsWithinSegment(endTrail[0], endTrail[1], intersect);
                if (!float.IsInfinity(intersect.x) && isOnStartTrail && isOnEndTrail)
                {
                    var vStart = intersectionOnStart - intersect;
                    var vEnd   = intersectionOnEnd - intersect;
                    joinInfo.InnerCornerDistFromStart += (vStart == Vector2.zero) ? 0.0f : vStart.magnitude;
                    joinInfo.InnerCornerDistToEnd     += (vEnd == Vector2.zero) ? 0.0f : vEnd.magnitude;
                    joinInfo.InnerCornerDistToEnd      = endSegmentLength - joinInfo.InnerCornerDistToEnd;
                    joinInfo.InnerCornerVertex         = intersect; // Found it!
                    intersectionFound = true;
                }
            }

            if (!intersectionFound)
            {
                joinInfo.InnerCornerVertex        = joinInfo.JoinPos + ((joinInfo.TanAtStart - joinInfo.TanAtEnd) / 2.0f).normalized * halfThickness;
                joinInfo.InnerCornerDistFromStart = 0;
                joinInfo.InnerCornerDistToEnd     = endSegmentLength;
            }
            return(joinInfo);
        }
        static void TessellateRange(
            float distance, PathDistanceForwardIterator pathIt, PathPatternIterator patternIt, PathProperties pathProps,
            TessellationOptions tessellateOptions, JoiningInfo[] joiningInfo, float[] segmentLengths, float totalLength, int rangeIndex, List <Vector2> verts, List <UInt16> inds)
        {
            bool startOfLoop = pathIt.Closed && (pathIt.CurrentSegment == 0) && (pathIt.CurrentT == 0.0f);

            if (startOfLoop && (joiningInfo[0] != null))
            {
                GenerateJoining(joiningInfo[0], pathProps.Corners, pathProps.Stroke.HalfThickness, pathProps.Stroke.TippedCornerLimit, tessellateOptions, verts, inds);
            }
            else
            {
                var pathEnding = pathProps.Head;

                // If pattern at the end will overlap with beginning, use a chopped ending to allow merging
                if (pathIt.Closed && rangeIndex == 0 && patternIt.IsSolidAt(pathIt.CurrentT) && patternIt.IsSolidAt(totalLength))
                {
                    pathEnding = PathEnding.Chop;
                }

                GenerateTip(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), true, pathIt.CurrentT, pathEnding, pathProps.Stroke.HalfThickness, tessellateOptions, verts, inds);
            }

            float startingLength  = pathIt.LengthSoFar;
            float unitsRemaining  = Mathf.Min(tessellateOptions.StepDistance, distance);
            bool  endedEntirePath = false;

            for (;;)
            {
                var result = pathIt.AdvanceBy(unitsRemaining, out unitsRemaining);
                if (result == PathDistanceForwardIterator.Result.Ended)
                {
                    endedEntirePath = true;
                    break;
                }
                else if (result == PathDistanceForwardIterator.Result.NewSegment)
                {
                    if (joiningInfo[1] != null)
                    {
                        GenerateJoining(joiningInfo[1], pathProps.Corners, pathProps.Stroke.HalfThickness, pathProps.Stroke.TippedCornerLimit, tessellateOptions, verts, inds);
                    }
                    else
                    {
                        AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, null, pathIt.SegmentLengthSoFar, verts, inds);
                    }
                    HandleNewSegmentJoining(pathIt, patternIt, joiningInfo, pathProps.Stroke.HalfThickness, segmentLengths);
                }

                if ((unitsRemaining <= Epsilon) &&
                    !TryGetMoreRemainingUnits(ref unitsRemaining, pathIt, startingLength, distance, tessellateOptions.StepDistance))
                {
                    break;
                }

                if (result == PathDistanceForwardIterator.Result.Stepped)
                {
                    AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, joiningInfo, pathIt.SegmentLengthSoFar, verts, inds);
                }
            }

            // Ending
            if (endedEntirePath && pathIt.Closed)
            {
                // No joining needed, the start and end of the path should just connect
                inds.Add(0);
                inds.Add(1);
                inds.Add((UInt16)(verts.Count - 2));
                inds.Add((UInt16)(verts.Count - 1));
                inds.Add((UInt16)(verts.Count - 2));
                inds.Add(1);
            }
            else
            {
                AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, joiningInfo, pathIt.SegmentLengthSoFar, verts, inds);
                GenerateTip(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), false, pathIt.CurrentT, pathProps.Tail, pathProps.Stroke.HalfThickness, tessellateOptions, verts, inds);
            }
        }
        static Vector2[] TraceShape(BezierContour contour, Stroke stroke, TessellationOptions tessellateOptions)
        {
            if (tessellateOptions.StepDistance < Epsilon)
            {
                throw new Exception("stepDistance too small");
            }

            if (contour.Segments.Length < 2)
            {
                return(new Vector2[0]);
            }

            float[] segmentLengths = VectorUtils.SegmentsLengths(contour.Segments, contour.Closed);

            // Approximate the number of vertices/indices we need to store the results so we reduce memory reallocations during work
            float approxTotalLength = 0.0f;

            foreach (var s in segmentLengths)
            {
                approxTotalLength += s;
            }

            int approxStepCount     = Math.Max((int)(approxTotalLength / tessellateOptions.StepDistance + 0.5f), 2);
            var strokePattern       = stroke != null ? stroke.Pattern : null;
            var strokePatternOffset = stroke != null ? stroke.PatternOffset : 0.0f;

            if (strokePattern != null)
            {
                approxStepCount += strokePattern.Length * 2;
            }

            List <Vector2> verts = new List <Vector2>(approxStepCount); // A little bit possibly for the endings

            var patternIt = new PathPatternIterator(strokePattern, strokePatternOffset);
            var pathIt    = new PathDistanceForwardIterator(contour.Segments, true, tessellateOptions.MaxCordDeviationSquared, tessellateOptions.MaxTanAngleDeviationCosine, tessellateOptions.SamplingStepSize);

            verts.Add(pathIt.EvalCurrent());

            while (!pathIt.Ended)
            {
                float distance        = patternIt.SegmentLength;
                float startingLength  = pathIt.LengthSoFar;
                float unitsRemaining  = Mathf.Min(tessellateOptions.StepDistance, distance);
                bool  endedEntirePath = false;
                for (;;)
                {
                    var result = pathIt.AdvanceBy(unitsRemaining, out unitsRemaining);
                    if (result == PathDistanceForwardIterator.Result.Ended)
                    {
                        endedEntirePath = true;
                        break;
                    }
                    else if (result == PathDistanceForwardIterator.Result.NewSegment)
                    {
                        verts.Add(pathIt.EvalCurrent());
                    }

                    if ((unitsRemaining <= Epsilon) &&
                        !TryGetMoreRemainingUnits(ref unitsRemaining, pathIt, startingLength, distance, tessellateOptions.StepDistance))
                    {
                        break;
                    }

                    if (result == PathDistanceForwardIterator.Result.Stepped)
                    {
                        verts.Add(pathIt.EvalCurrent());
                    }
                }

                // Ending
                if (endedEntirePath)
                {
                    break;
                }
                else
                {
                    verts.Add(pathIt.EvalCurrent());
                }
                patternIt.Advance();
            }

            if ((verts[0] - verts[verts.Count - 1]).sqrMagnitude < Epsilon)
            {
                verts.RemoveAt(verts.Count - 1);
            }
            return(verts.ToArray()); // Why not return verts itself?
        }