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

            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();
        }
Example #2
0
        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 = ShapeUtils.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?
        }
        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);
            }
        }