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);
        }