using System;
using System.Linq;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Collections.LowLevel.Unsafe;
using Unity.SpriteShape.External.LibTessDotNet;

// We will enable this once Burst gets a verified final version as this attribute keeps changing.
#if ENABLE_SPRITESHAPE_BURST
using Unity.Burst;
#endif

namespace UnityEngine.U2D
{

#if ENABLE_SPRITESHAPE_BURST
    [BurstCompile]
#endif
    public struct SpriteShapeGenerator : IJob
    {

        struct JobParameters
        {
            public int4 shapeData;              // x : ClosedShape (bool) y : AdaptiveUV (bool) z : SpriteBorders (bool) w : Enable Fill Texture.
            public int4 splineData;             // x : StrtechUV. y : splineDetail z : AngleThreshold w: Collider On/Off
            public float4 curveData;            // x : ColliderPivot y : BorderPivot z : BevelCutoff w : BevelSize.
            public float4 fillData;             // x : fillScale  y : fillScale.x W z : fillScale.y H w: 0.
        }

        struct JobSpriteInfo
        {
            public float4 texRect;              // TextureRect.
            public float4 texData;              // x : GPUWidth y : GPUHeight z : TexelWidth w : TexelHeight
            public float4 uvInfo;               // x : x, y : y, z : width, w : height
            public float4 metaInfo;             // x : PPU, y : Pivot Y z : Original Rect Width w : Original Rect Height.
            public float4 border;               // Sprite Border.
        }

        struct JobAngleRange
        {
            public float4 spriteAngles;         // x, y | First Angle & z,w | Second Angle.
            public int4 spriteVariant1;         // First 4 variants here.
            public int4 spriteVariant2;         // Second 4 variants here. Total 8 max variants.
            public int4 spriteData;             // Additional Data. x : sorting Order. y : variant Count. z : render Order Max.
        };

        struct JobControlPoint
        {
            public int4 cpData;                 // x : Sprite Index y : Corner Type z : Mode w : Internal Sprite Index.
            public int4 exData;                 // x : Corner Type y: Corner Sprite z : Start/End Corner
            public float4 cpInfo;               // x : Height y : Bevel Cutoff z : Bevel Size. w : Render Order.
            public float2 position;
            public float2 tangentLt;
            public float2 tangentRt;
        };

        struct JobContourPoint
        {
            public float2 position;             // Position.
            public float2 ptData;               // x : height.
        }

        // Tessellation Structures.
        struct JobSegmentInfo
        {
            public int4 spInfo;                 // x : Begin y : End. z : Sprite w : First Sprite for that Angle Range.
            public float4 spriteInfo;           // x : width y : height z : Render Order. w: Distance of the Segment.
        };

        struct JobCornerInfo
        {
            public float2 bottom;
            public float2 top;
            public float2 left;
            public float2 right;
            public int2 cornerData;
        }

        struct JobShapeVertex
        {
            public float2 pos;
            public float2 uv;
            public float4 tan;
            public float2 meta;                 // x : height y : -
            public int2 sprite;                 // x : sprite y : is main Point.
        }

        /// <summary>
        /// Native Arrays : Scope : Initialized before and ReadOnly During Job 
        /// </summary>
        [ReadOnly]
        private JobParameters m_ShapeParams;

        [ReadOnly]
        [DeallocateOnJobCompletion]
        private NativeArray<JobSpriteInfo> m_SpriteInfos;

        [ReadOnly]
        [DeallocateOnJobCompletion]
        private NativeArray<JobSpriteInfo> m_CornerSpriteInfos;

        [ReadOnly]
        [DeallocateOnJobCompletion]
        private NativeArray<JobAngleRange> m_AngleRanges;

        /// <summary>
        /// Native Arrays : Scope : Job 
        /// </summary>
        [DeallocateOnJobCompletion]
        private NativeArray<JobSegmentInfo> m_Segments;
        private int m_SegmentCount;

        [DeallocateOnJobCompletion]
        private NativeArray<JobContourPoint> m_ContourPoints;
        private int m_ContourPointCount;

        [DeallocateOnJobCompletion]
        private NativeArray<JobCornerInfo> m_Corners;
        private int m_CornerCount;

        [DeallocateOnJobCompletion]
        private NativeArray<float2> m_TessPoints;
        private int m_TessPointCount;

        [DeallocateOnJobCompletion]
        NativeArray<JobShapeVertex> m_VertexData;

        [DeallocateOnJobCompletion]
        NativeArray<JobShapeVertex> m_OutputVertexData;

        [DeallocateOnJobCompletion]
        private NativeArray<JobControlPoint> m_ControlPoints;
        private int m_ControlPointCount;

        [DeallocateOnJobCompletion]
        private NativeArray<float2> m_CornerCoordinates;

        [DeallocateOnJobCompletion]
        private NativeArray<float2> m_TempPoints;

        [DeallocateOnJobCompletion]
        private NativeArray<JobControlPoint> m_GeneratedControlPoints;

        [DeallocateOnJobCompletion]
        private NativeArray<int2> m_SpriteIndices;

        /// <summary>
        /// Output Native Arrays : Scope : SpriteShapeRenderer / SpriteShapeController.
        /// </summary>

        private int m_IndexArrayCount;
        public NativeArray<ushort> m_IndexArray;

        private int m_VertexArrayCount;
        public NativeSlice<Vector3> m_PosArray;
        public NativeSlice<Vector2> m_Uv0Array;
        public NativeSlice<Vector4> m_TanArray;

        private int m_GeomArrayCount;
        public NativeArray<SpriteShapeSegment> m_GeomArray;

        private int m_ColliderPointCount;
        public NativeArray<float2> m_ColliderPoints;
        public NativeArray<Bounds> m_Bounds;

        int m_IndexDataCount;
        int m_VertexDataCount;
        int m_ColliderDataCount;
        int m_ActiveIndexCount;
        int m_ActiveVertexCount;

        float2 m_FirstLT;
        float2 m_FirstLB;
        float4x4 m_Transform;

        int kModeLinear;
        int kModeContinous;
        int kModeBroken;

        int kCornerTypeOuterTopLeft;
        int kCornerTypeOuterTopRight;
        int kCornerTypeOuterBottomLeft;
        int kCornerTypeOuterBottomRight;
        int kCornerTypeInnerTopLeft;
        int kCornerTypeInnerTopRight;
        int kCornerTypeInnerBottomLeft;
        int kCornerTypeInnerBottomRight;
        int kControlPointCount;

        float kEpsilon;
        float kEpsilonOrder;
        float kEpsilonRelaxed;
        float kExtendSegment;
        float kRenderQuality;
        float kOptimizeRender;
        float kColliderQuality;
        float kOptimizeCollider;
        float kLowestQualityTolerance;
        float kHighestQualityTolerance;

        #region Getters.

        // Return Vertex Data Count
        private int vertexDataCount
        {
            get { return m_VertexDataCount; }
        }

        // Return Index Data Count
        private int indexDataCount
        {
            get { return m_IndexDataCount; }
        }

        // Return Sprite Count
        private int spriteCount
        {
            get { return m_SpriteInfos.Length; }
        }

        private int cornerSpriteCount
        {
            get { return m_CornerSpriteInfos.Length; }
        }

        // Return Angle Range Count
        private int angleRangeCount
        {
            get { return m_AngleRanges.Length; }
        }

        // Return the Input Control Point Count.
        private int controlPointCount
        {
            get { return m_ControlPointCount; }
        }

        // Return the Contour Point Count.
        private int contourPointCount
        {
            get { return m_ContourPointCount; }
        }

        // Return Segment Count
        private int segmentCount
        {
            get { return m_SegmentCount; }
        }

        // Needs Collider Generaie.
        private bool hasCollider
        {
            get { return m_ShapeParams.splineData.w == 1; }
        }

        // Collider Pivot
        private float colliderPivot
        {
            get { return m_ShapeParams.curveData.x; }
        }

        // Border Pivot
        private float borderPivot
        {
            get { return m_ShapeParams.curveData.y; }
        }

        // Spline Detail
        private int splineDetail
        {
            get { return m_ShapeParams.splineData.y; }
        }

        // Is this Closed-Loop.
        private bool isCarpet
        {
            get { return m_ShapeParams.shapeData.x == 1; }
        }

        // Is Adaptive UV
        private bool isAdaptive
        {
            get { return m_ShapeParams.shapeData.y == 1; }
        }

        // Has Sprite Border.
        private bool hasSpriteBorder
        {
            get { return m_ShapeParams.shapeData.z == 1; }
        }

        #endregion

        #region Safe Getters.
        JobSpriteInfo GetSpriteInfo(int index)
        {
            if (index >= m_SpriteInfos.Length)
                throw new ArgumentException(string.Format("GetSpriteInfo accessed with invalid Index {0} / {1}", index, m_SpriteInfos.Length));
            return m_SpriteInfos[index];
        }

        JobSpriteInfo GetCornerSpriteInfo(int index)
        {
            int ai = index - 1;
            if (ai >= m_CornerSpriteInfos.Length || index == 0)
                throw new ArgumentException(string.Format("GetCornerSpriteInfo accessed with invalid Index {0} / {1}", index, m_CornerSpriteInfos.Length));
            return m_CornerSpriteInfos[ai];
        }

        JobAngleRange GetAngleRange(int index)
        {
            if (index >= m_AngleRanges.Length)
                throw new ArgumentException(string.Format("GetAngleRange accessed with invalid Index {0} / {1}", index, m_AngleRanges.Length));
            return m_AngleRanges[index];
        }

        JobControlPoint GetControlPoint(int index)
        {
            if (index >= m_ControlPoints.Length)
                throw new ArgumentException(string.Format("GetControlPoint accessed with invalid Index {0} / {1}", index, m_ControlPoints.Length));
            return m_ControlPoints[index];
        }

        JobContourPoint GetContourPoint(int index)
        {
            if (index >= m_ContourPointCount)
                throw new ArgumentException(string.Format("GetContourPoint accessed with invalid Index {0} / {1}", index, m_ContourPointCount));
            return m_ContourPoints[index];
        }

        JobSegmentInfo GetSegmentInfo(int index)
        {
            if (index >= m_SegmentCount)
                throw new ArgumentException(string.Format("GetSegmentInfo accessed with invalid Index {0} / {1}", index, m_SegmentCount));
            return m_Segments[index];
        }

        int GetContourIndex(int index)
        {
            if (index >= m_ControlPoints.Length)
                throw new ArgumentException(string.Format("GetContourIndex accessed with invalid Index {0} / {1}", index, m_ControlPoints.Length));
            return index * m_ShapeParams.splineData.y;
        }

        int GetEndContourIndexOfSegment(JobSegmentInfo isi)
        {
            int contourIndex = GetContourIndex(isi.spInfo.y) - 1;
            if (isi.spInfo.y >= m_ControlPoints.Length || isi.spInfo.y == 0)
                throw new ArgumentException("GetEndContourIndexOfSegment accessed with invalid Index");
            return contourIndex;
        }
        #endregion

        #region Utility
        static void CopyToNativeArray<T>(NativeArray<T> from, int length, ref NativeArray<T> to) where T : struct
        {
            to = new NativeArray<T>(length, Allocator.TempJob);
            for (int i = 0; i < length; ++i)
                to[i] = from[i];
        }

        static void SafeDispose<T>(NativeArray<T> na) where T : struct
        {
            if (na.Length > 0)
                na.Dispose();
        }

        static bool IsPointOnLine(float epsilon, float2 a, float2 b, float2 c)
        {
            float cp = (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y);
            if (math.abs(cp) > epsilon)
                return false;

            float dp = (c.x - a.x) * (b.x - a.x) + (c.y - a.y) * (b.y - a.y);
            if (dp < 0)
                return false;

            float ba = (b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y);
            if (dp > ba)
                return false;
            return true;
        }

        static bool IsPointOnLines(float epsilon, float2 p1, float2 p2, float2 p3, float2 p4, float2 r)
        {
            return IsPointOnLine(epsilon, p1, p2, r) && IsPointOnLine(epsilon, p3, p4, r);
        }

        static bool LineIntersection(float epsilon, float2 p1, float2 p2, float2 p3, float2 p4, ref float2 result)
        {
            float bx = p2.x - p1.x;
            float by = p2.y - p1.y;
            float dx = p4.x - p3.x;
            float dy = p4.y - p3.y;
            float bDotDPerp = bx * dy - by * dx;
            if (math.abs(bDotDPerp) < epsilon)
            {
                return false;
            }
            float cx = p3.x - p1.x;
            float cy = p3.y - p1.y;
            float t = (cx * dy - cy * dx) / bDotDPerp;
            if ((t >= -epsilon) && (t <= 1.0f + epsilon))
            {
                result.x = p1.x + t * bx;
                result.y = p1.y + t * by;
                return true;
            }
            return false;
        }

        static float AngleBetweenVector(float2 a, float2 b)
        {
            float dot = math.dot(a, b);
            float det = (a.x * b.y) - (b.x * a.y);
            return math.atan2(det, dot) * Mathf.Rad2Deg;
        }

        static bool GenerateColumnsBi(float2 a, float2 b, float2 whsize, bool flip, ref float2 rt, ref float2 rb, float cph)
        {
            float2 v1 = flip ? (a - b) : (b - a);
            if (math.length(v1) < 1e-30f)
                return false;

            float2 rvxy = new float2(-1f, 1f);
            float2 v2 = v1.yx * rvxy;
            float2 whsizey = new float2(whsize.y * cph);
            v2 = math.normalize(v2);

            float2 v3 = v2 * whsizey;
            rt = a - v3;
            rb = a + v3;
            return true;
        }

        static bool GenerateColumnsTri(float2 a, float2 b, float2 c, float2 whsize, bool flip, ref float2 rt, ref float2 rb, float cph)
        {
            float2 rvxy = new float2(-1f, 1f);
            float2 v0 = b - a;
            float2 v1 = c - b;
            v0 = v0.yx * rvxy;
            v1 = v1.yx * rvxy;

            float2 v2 = math.normalize(v0) + math.normalize(v1);
            if (math.length(v2) < 1e-30f)
                return false;
            v2 = math.normalize(v2);
            float2 whsizey = new float2(whsize.y * cph);
            float2 v3 = v2 * whsizey;

            rt = b - v3;
            rb = b + v3;
            return true;
        }
        #endregion

        #region Input Preparation.
        void AppendCornerCoordinates(ref NativeArray<float2> corners, ref int cornerCount, float2 a, float2 b, float2 c, float2 d)
        {
            corners[cornerCount++] = a;
            corners[cornerCount++] = b;
            corners[cornerCount++] = c;
            corners[cornerCount++] = d;
        }

        unsafe void PrepareInput(SpriteShapeParameters shapeParams, int maxArrayCount, NativeArray<ShapeControlPoint> shapePoints, bool optimizeGeometry, bool updateCollider, bool optimizeCollider, float colliderPivot, float colliderDetail)
        {
            kModeLinear = 0;
            kModeContinous = 1;
            kModeBroken = 2;

            kCornerTypeOuterTopLeft = 1;
            kCornerTypeOuterTopRight = 2;
            kCornerTypeOuterBottomLeft = 3;
            kCornerTypeOuterBottomRight = 4;
            kCornerTypeInnerTopLeft = 5;
            kCornerTypeInnerTopRight = 6;
            kCornerTypeInnerBottomLeft = 7;
            kCornerTypeInnerBottomRight = 8;

            m_IndexDataCount = 0;
            m_VertexDataCount = 0;
            m_ColliderDataCount = 0;
            m_ActiveIndexCount = 0;
            m_ActiveVertexCount = 0;

            kEpsilon = 0.00001f;
            kEpsilonOrder = -0.0001f; 
            kEpsilonRelaxed = 0.001f;
            kExtendSegment = 10000.0f;

            kLowestQualityTolerance = 4.0f;
            kHighestQualityTolerance = 16.0f;

            kColliderQuality = math.clamp(colliderDetail, kLowestQualityTolerance, kHighestQualityTolerance);
            kOptimizeCollider = optimizeCollider ? 1 : 0;
            kColliderQuality = (kHighestQualityTolerance - kColliderQuality + 2.0f) * 0.002f;
            colliderPivot = (colliderPivot == 0) ? 0.001f : colliderPivot;

            kOptimizeRender = optimizeGeometry ? 1 : 0;
            kRenderQuality = math.clamp(shapeParams.splineDetail, kLowestQualityTolerance, kHighestQualityTolerance);
            kRenderQuality = (kHighestQualityTolerance - kRenderQuality + 2.0f) * 0.0002f;

            m_ShapeParams.shapeData = new int4(shapeParams.carpet ? 1 : 0, shapeParams.adaptiveUV ? 1 : 0, shapeParams.spriteBorders ? 1 : 0, shapeParams.fillTexture != null ? 1 : 0);
            m_ShapeParams.splineData = new int4(shapeParams.stretchUV ? 1 : 0, (shapeParams.splineDetail > 4) ? (int)shapeParams.splineDetail : 4, (int)shapeParams.angleThreshold, updateCollider ? 1 : 0);
            m_ShapeParams.curveData = new float4(colliderPivot, shapeParams.borderPivot, shapeParams.bevelCutoff, shapeParams.bevelSize);
            float fx = 0, fy = 0;
            if (shapeParams.fillTexture != null)
            {
                fx = (float)shapeParams.fillTexture.width * (1.0f / (float)shapeParams.fillScale);
                fy = (float)shapeParams.fillTexture.height * (1.0f / (float)shapeParams.fillScale);
            }
            m_ShapeParams.fillData = new float4(shapeParams.fillScale, fx, fy, 0);
            UnsafeUtility.MemClear(m_GeomArray.GetUnsafePtr(), m_GeomArray.Length * UnsafeUtility.SizeOf<SpriteShapeSegment>());

            m_Transform = new float4x4(shapeParams.transform.m00, shapeParams.transform.m01, shapeParams.transform.m02, shapeParams.transform.m03,
                                       shapeParams.transform.m10, shapeParams.transform.m11, shapeParams.transform.m12, shapeParams.transform.m13,
                                       shapeParams.transform.m20, shapeParams.transform.m21, shapeParams.transform.m22, shapeParams.transform.m23,
                                       shapeParams.transform.m30, shapeParams.transform.m31, shapeParams.transform.m32, shapeParams.transform.m33);

            kControlPointCount = shapePoints.Length * (int)shapeParams.splineDetail * 32;
            m_Segments = new NativeArray<JobSegmentInfo>(shapePoints.Length * 2, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            m_ContourPoints = new NativeArray<JobContourPoint>(kControlPointCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            m_TessPoints = new NativeArray<float2>(shapePoints.Length * (int)shapeParams.splineDetail * 128, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            m_VertexData = new NativeArray<JobShapeVertex>(maxArrayCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            m_OutputVertexData = new NativeArray<JobShapeVertex>(maxArrayCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            m_CornerCoordinates = new NativeArray<float2>(32, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            
            m_TempPoints = new NativeArray<float2>(kControlPointCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            m_GeneratedControlPoints = new NativeArray<JobControlPoint>(kControlPointCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            m_SpriteIndices = new NativeArray<int2>(kControlPointCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            int cornerCount = 0;
            AppendCornerCoordinates(ref m_CornerCoordinates, ref cornerCount, new float2(1f, 1f), new float2(0, 1f), new float2(1f, 0), new float2(0, 0));
            AppendCornerCoordinates(ref m_CornerCoordinates, ref cornerCount, new float2(1f, 0), new float2(1f, 1f), new float2(0, 0), new float2(0, 1f));
            AppendCornerCoordinates(ref m_CornerCoordinates, ref cornerCount, new float2(0, 1f), new float2(0, 0), new float2(1f, 1f), new float2(1f, 0));
            AppendCornerCoordinates(ref m_CornerCoordinates, ref cornerCount, new float2(0, 0), new float2(1f, 0), new float2(0, 1f), new float2(1f, 1f));
            AppendCornerCoordinates(ref m_CornerCoordinates, ref cornerCount, new float2(0, 0), new float2(0, 1f), new float2(1f, 0), new float2(1f, 1f));
            AppendCornerCoordinates(ref m_CornerCoordinates, ref cornerCount, new float2(0, 1f), new float2(1f, 1f), new float2(0, 0), new float2(1f, 0));
            AppendCornerCoordinates(ref m_CornerCoordinates, ref cornerCount, new float2(1f, 0), new float2(0, 0), new float2(1f, 1f), new float2(0, 1f));
            AppendCornerCoordinates(ref m_CornerCoordinates, ref cornerCount, new float2(1f, 1f), new float2(1f, 0), new float2(0, 1f), new float2(0, 0));
        }

        void TransferSprites(ref NativeArray<JobSpriteInfo> spriteInfos, Sprite[] sprites, int maxCount)
        {

            for (int i = 0; i < sprites.Length && i < maxCount; ++i)
            {
                JobSpriteInfo spriteInfo = spriteInfos[i];
                Sprite spr = sprites[i];
                if (spr != null)
                {
                    Texture2D tex = spr.texture;
                    spriteInfo.texRect = new float4(spr.textureRect.x, spr.textureRect.y, spr.textureRect.width, spr.textureRect.height);
                    spriteInfo.texData = new float4(tex.width, tex.height, tex.texelSize.x, tex.texelSize.y);
                    spriteInfo.border = new float4(spr.border.x, spr.border.y, spr.border.z, spr.border.w);
                    spriteInfo.uvInfo = new float4(spriteInfo.texRect.x / spriteInfo.texData.x, spriteInfo.texRect.y / spriteInfo.texData.y, spriteInfo.texRect.z / spriteInfo.texData.x, spriteInfo.texRect.w / spriteInfo.texData.y);
                    spriteInfo.metaInfo = new float4(spr.pixelsPerUnit, spr.pivot.y / spr.textureRect.height, spr.rect.width, spr.rect.height);

                    if (!math.any(spriteInfo.texRect))
                    {
                        Cleanup();
                        throw new ArgumentException(string.Format("{0} is packed with Tight packing or mesh type set to Tight. Please check input sprites", spr.name));
                    }
                }
                spriteInfos[i] = spriteInfo;
            }

        }

        void PrepareSprites(Sprite[] edgeSprites, Sprite[] cornerSprites)
        {
            m_SpriteInfos = new NativeArray<JobSpriteInfo>(edgeSprites.Length, Allocator.TempJob);
            TransferSprites(ref m_SpriteInfos, edgeSprites, edgeSprites.Length);

            m_CornerSpriteInfos = new NativeArray<JobSpriteInfo>(kCornerTypeInnerBottomRight, Allocator.TempJob);
            TransferSprites(ref m_CornerSpriteInfos, cornerSprites, cornerSprites.Length);
        }

        void PrepareAngleRanges(AngleRangeInfo[] angleRanges)
        {
            m_AngleRanges = new NativeArray<JobAngleRange>(angleRanges.Length, Allocator.TempJob);

            for (int i = 0; i < angleRanges.Length; ++i)
            {
                JobAngleRange angleRange = m_AngleRanges[i];
                AngleRangeInfo ari = angleRanges[i];
                int[] spr = ari.sprites;
                if (ari.start > ari.end)
                {
                    var sw = ari.start;
                    ari.start = ari.end;
                    ari.end = sw;
                }
                angleRange.spriteAngles = new float4(ari.start + 90f, ari.end + 90f, 0, 0);
                angleRange.spriteVariant1 = new int4(spr.Length > 0 ? spr[0] : -1, spr.Length > 1 ? spr[1] : -1, spr.Length > 2 ? spr[2] : -1, spr.Length > 3 ? spr[3] : -1);
                angleRange.spriteVariant2 = new int4(spr.Length > 4 ? spr[4] : -1, spr.Length > 5 ? spr[5] : -1, spr.Length > 6 ? spr[6] : -1, spr.Length > 7 ? spr[7] : -1);
                angleRange.spriteData = new int4((int)ari.order, spr.Length, 32, 0);
                m_AngleRanges[i] = angleRange;
            }
        }

        void PrepareControlPoints(NativeArray<ShapeControlPoint> shapePoints, NativeArray<SpriteShapeMetaData> metaData)
        {
            float2 zero = new float2(0, 0);
            m_ControlPoints = new NativeArray<JobControlPoint>(kControlPointCount, Allocator.TempJob);

            for (int i = 0; i < shapePoints.Length; ++i)
            {
                JobControlPoint shapePoint = m_ControlPoints[i];
                ShapeControlPoint sp = shapePoints[i];
                SpriteShapeMetaData md = metaData[i];
                shapePoint.position = new float2(sp.position.x, sp.position.y);
                shapePoint.tangentLt = (sp.mode == kModeLinear) ? zero : new float2(sp.leftTangent.x, sp.leftTangent.y);
                shapePoint.tangentRt = (sp.mode == kModeLinear) ? zero : new float2(sp.rightTangent.x, sp.rightTangent.y);
                shapePoint.cpInfo = new float4(md.height, md.bevelCutoff, md.bevelSize, 0);
                shapePoint.cpData = new int4((int)md.spriteIndex, md.corner ? 1 : 0, sp.mode, 0);
                shapePoint.exData = new int4(-1, 0, 0, 0);
                m_ControlPoints[i] = shapePoint;
            }
            m_ControlPointCount = shapePoints.Length;
            m_Corners = new NativeArray<JobCornerInfo>(shapePoints.Length, Allocator.TempJob);
            GenerateControlPoints();
        }
        #endregion 

        #region Resolve Angles for Points.
        bool WithinRange(JobAngleRange angleRange, float inputAngle)
        {
            float range = angleRange.spriteAngles.y - angleRange.spriteAngles.x;
            float angle = Mathf.Repeat(inputAngle - angleRange.spriteAngles.x, 360f);
            return (angle >= 0f && angle <= range);
        }

        bool AngleWithinRange(float t, float a, float b)
        {
            return (a != 0 && b != 0) && (t >= a && t <= b);
        }

        static float2 BezierPoint(float2 st, float2 sp, float2 ep, float2 et, float t)
        {
            float2 xt = new float2(t);
            float2 nt = new float2(1.0f - t);
            float2 x3 = new float2(3.0f);
            return (sp * nt * nt * nt) + (st * nt * nt * xt * x3) + (et * nt * xt * xt * x3) + (ep * xt * xt * xt);
        }

        static float SlopeAngle(float2 dirNormalized)
        {
            float2 dvup = new float2(0, 1f);
            float2 dvrt = new float2(1f, 0);

            float dr = math.dot(dirNormalized, dvrt);
            float du = math.dot(dirNormalized, dvup);
            float cu = math.acos(du);
            float sn = dr >= 0 ? 1.0f : -1.0f;
            float an = cu * Mathf.Rad2Deg * sn;

            // Adjust angles when direction is parallel to Up Axis.
            an = (du != 1f) ? an : 0;
            an = (du != -1f) ? an : -180f;
            return an;
        }

        static float SlopeAngle(float2 start, float2 end)
        {
            float2 dir = math.normalize(start - end);
            return SlopeAngle(dir);
        }

        bool ResolveAngle(float angle, int activeIndex, ref float renderOrder, ref int spriteIndex, ref int firstSpriteIndex)
        {
            int localRenderOrder = 0;
            int localSpriteIndex = 0;
            for (int i = 0; i < m_AngleRanges.Length; ++i)
            {
                bool withinRange = WithinRange(m_AngleRanges[i], angle);
                if (withinRange)
                {
                    int validIndex = (activeIndex < m_AngleRanges[i].spriteData.y) ? activeIndex : 0;
                    renderOrder = localRenderOrder + validIndex;
                    spriteIndex = localSpriteIndex + validIndex;
                    firstSpriteIndex = localSpriteIndex;
                    return true;
                }
                localRenderOrder += m_AngleRanges[i].spriteData.z;
                localSpriteIndex += m_AngleRanges[i].spriteData.y;
            }
            return false;
        }

        int GetSpriteIndex(int index, int previousIndex, ref int resolved)
        {
            int next = (index + 1) % controlPointCount, spriteIndex = -1, firstSpriteIndex = -1;
            float order = 0;
            var cp = GetControlPoint(index);
            float angle = SlopeAngle(GetControlPoint(next).position, cp.position);
            bool resolve = ResolveAngle(angle, cp.cpData.x, ref order, ref spriteIndex, ref firstSpriteIndex);
            resolved = resolve ? 1 : 0;
            return resolve ? spriteIndex : previousIndex;
        }
        #endregion

        #region Segments.
        void GenerateSegments()
        {
            int activeSpriteIndex = 0, activeSegmentIndex = 0, firstSpriteIndex = -1;
            JobSegmentInfo activeSegment = m_Segments[0];
            activeSegment.spInfo = int4.zero;
            activeSegment.spriteInfo = int4.zero;
            float angle = 0;

            // Generate Segments.
            for (int i = 0; i < controlPointCount; ++i)
            {
                int next = (i + 1) % controlPointCount;

                // Check for Last Point and see if we need loop-back.
                bool skipSegmenting = false;
                if (next == 0)
                {
                    if (!isCarpet)
                        continue;
                    next = 1;
                    skipSegmenting = true;
                }

                JobControlPoint iscp = GetControlPoint(i);
                JobControlPoint iscpNext = GetControlPoint(next);

                // If this segment is corner, continue.
                if (iscp.exData.x > 0 && iscp.exData.x == iscpNext.exData.x && iscp.exData.z == 1)
                    continue;

                // Resolve Angle and Order.
                int4 pointData = iscp.cpData;
                float4 pointInfo = iscp.cpInfo;

                // Get Min Max Segment.
                int mn = (i < next) ? i : next;
                int mx = (i > next) ? i : next;
                bool continueStrip = (iscp.cpData.z == kModeContinous), edgeUpdated = false;

                if (false == continueStrip || 0 == activeSegmentIndex)
                    angle = SlopeAngle(iscpNext.position, iscp.position);
                bool resolved = ResolveAngle(angle, pointData.x, ref pointInfo.w, ref pointData.w, ref firstSpriteIndex);
                if (!resolved && !skipSegmenting)
                {
                    // If we do not resolve SpriteIndex (AngleRange) just continue existing segment.
                    pointData.w = activeSpriteIndex;
                    iscp.cpData = pointData;
                    m_ControlPoints[i] = iscp;

                    // Insert Dummy Segment.
                    activeSegment = m_Segments[activeSegmentIndex];
                    activeSegment.spInfo.x = mn;
                    activeSegment.spInfo.y = mx;
                    activeSegment.spInfo.z = -1;
                    m_Segments[activeSegmentIndex] = activeSegment;
                    activeSegmentIndex++;
                    continue;
                }

                // Update current Point.
                activeSpriteIndex = pointData.w;
                iscp.cpData = pointData;
                m_ControlPoints[i] = iscp;
                if (skipSegmenting)
                    continue;

                // Check for Segments. Also check if the Segment Start has been resolved. Otherwise simply start with the next one.
                if (activeSegmentIndex != 0)
                    continueStrip = continueStrip && (m_SpriteIndices[activeSegment.spInfo.x].y != 0 && activeSpriteIndex == activeSegment.spInfo.z);

                if (continueStrip && i != (controlPointCount - 1))
                {
                    for (int s = 0; s < activeSegmentIndex; ++s)
                    {
                        activeSegment = m_Segments[s];
                        if (activeSegment.spInfo.x - mn == 1)
                        {
                            edgeUpdated = true;
                            activeSegment.spInfo.x = mn;
                            m_Segments[s] = activeSegment;
                            break;
                        }
                        if (mx - activeSegment.spInfo.y == 1)
                        {
                            edgeUpdated = true;
                            activeSegment.spInfo.y = mx;
                            m_Segments[s] = activeSegment;
                            break;
                        }
                    }
                }

                if (!edgeUpdated)
                {
                    activeSegment = m_Segments[activeSegmentIndex];
                    JobSpriteInfo sprLt = GetSpriteInfo(iscp.cpData.w);
                    activeSegment.spInfo.x = mn;
                    activeSegment.spInfo.y = mx;
                    activeSegment.spInfo.z = activeSpriteIndex;
                    activeSegment.spInfo.w = firstSpriteIndex;
                    activeSegment.spriteInfo.x = sprLt.texRect.z;
                    activeSegment.spriteInfo.y = sprLt.texRect.w;
                    activeSegment.spriteInfo.z = pointInfo.w;
                    m_Segments[activeSegmentIndex] = activeSegment;
                    activeSegmentIndex++;
                }
            }

            m_SegmentCount = activeSegmentIndex;

        }

        bool GenerateControlPoints()
        {
            // Globals.
            int activePoint = 0, activeIndex = 0;
            int startPoint = 0, endPoint = controlPointCount, lastPoint = (controlPointCount - 1);

            float2 rvxy = new float2(-1f, 1f);
            int2 sprData = new int2(0, 0);
            bool useSlice = true;
            int spriteCount = m_SpriteInfos.Length;

            // Calc and calculate Indices.
            for (int i = 0; i < controlPointCount; ++i)
            {
                var resolved = 0;
                int spriteIndex = GetSpriteIndex(i, activeIndex, ref resolved);
                sprData.x = activeIndex = spriteIndex;
                sprData.y = resolved;
                m_SpriteIndices[i] = sprData;
            }

            // Open-Ended. We simply dont allow Continous mode in End-points.
            if (!isCarpet)
            {
                JobControlPoint cp = GetControlPoint(0);
                cp.cpData.z = (cp.cpData.z == kModeContinous) ? kModeBroken : cp.cpData.z;
                m_GeneratedControlPoints[activePoint++] = cp;
                // If its not carpet, we already pre-insert start and endpoint.
                startPoint = 1;
                endPoint = controlPointCount - 1;
            }

            // Generate Intermediates.
            for (int i = startPoint; i < endPoint; ++i)
            {

                // Check if the Neighbor Points are all in Linear Mode,
                bool vc = InsertCorner(i, ref m_SpriteIndices, ref m_GeneratedControlPoints, ref activePoint);
                if (vc)
                    continue;

                // NO Corners.
                m_GeneratedControlPoints[activePoint++] = GetControlPoint(i);
            }

            // Open-Ended.
            if (!isCarpet)
            {
                JobControlPoint cp = GetControlPoint(endPoint);
                cp.cpData.z = (cp.cpData.z == kModeContinous) ? kModeBroken : cp.cpData.z;
                m_GeneratedControlPoints[activePoint++] = cp;
            }
            // If Closed Shape
            else
            {
                JobControlPoint cp = m_GeneratedControlPoints[0];
                m_GeneratedControlPoints[activePoint++] = cp;
            }

            // Copy from these intermediate Points to main Control Points.
            for (int i = 0; i < activePoint; ++i)
                m_ControlPoints[i] = m_GeneratedControlPoints[i];
            m_ControlPointCount = activePoint;

            // Calc and calculate Indices.
            for (int i = 0; i < controlPointCount; ++i)
            {
                var resolved = 0;
                int spriteIndex = GetSpriteIndex(i, activeIndex, ref resolved);
                sprData.x = activeIndex = spriteIndex;
                sprData.y = resolved;
                m_SpriteIndices[i] = sprData;
            }

            // End
            return useSlice;
        }

        float SegmentDistance(JobSegmentInfo isi)
        {
            float distance = 0;
            int stIx = GetContourIndex(isi.spInfo.x);
            int enIx = GetEndContourIndexOfSegment(isi);

            for (int i = stIx; i < enIx; ++i)
            {
                int j = i + 1;
                JobContourPoint lt = GetContourPoint(i);
                JobContourPoint rt = GetContourPoint(j);
                distance = distance + math.distance(lt.position, rt.position);
            }

            return distance;
        }

        void GenerateContour()
        {
            int controlPointContour = controlPointCount - 1;

            // Expand the Bezier.
            int ap = 0;
            float fmax = (float)(splineDetail - 1);
            for (int i = 0; i < controlPointContour; ++i)
            {
                int j = i + 1;
                JobControlPoint cp = GetControlPoint(i);
                JobControlPoint pp = GetControlPoint(j);

                float2 p0 = cp.position;
                float2 p1 = pp.position;
                float2 sp = p0;
                float2 rt = p0 + cp.tangentRt;
                float2 lt = p1 + pp.tangentLt;
                int cap = ap;
                float spd = 0, cpd = 0;

                for (int n = 0; n < splineDetail; ++n)
                {
                    JobContourPoint xp = m_ContourPoints[ap];
                    float t = (float)n / fmax;
                    float2 bp = BezierPoint(rt, p0, p1, lt, t);
                    xp.position = bp;
                    spd += math.distance(bp, sp);
                    m_ContourPoints[ap++] = xp;
                    sp = bp;
                }

                sp = p0;
                for (int n = 0; n < splineDetail; ++n)
                {
                    JobContourPoint xp = m_ContourPoints[cap];
                    cpd += math.distance(xp.position, sp);
                    xp.ptData.x = math.lerp(cp.cpInfo.x, pp.cpInfo.x, cpd / spd);
                    m_ContourPoints[cap++] = xp;
                    sp = xp.position;
                }

            }

            // End
            m_ContourPointCount = ap;
        }

        void TessellateContour()
        {

            int tessPoints = 0;

            // Create Tessallator if required.
            for (int i = 0; i < contourPointCount; ++i)
            {
                if ((i + 1) % splineDetail == 0)
                    continue;
                int h = (i == 0) ? (contourPointCount - 1) : (i - 1);
                int j = (i + 1) % contourPointCount;
                h = (i % splineDetail == 0) ? (h - 1) : h;

                JobContourPoint pp = GetContourPoint(h);
                JobContourPoint cp = GetContourPoint(i);
                JobContourPoint np = GetContourPoint(j);

                float2 cpd = cp.position - pp.position;
                float2 npd = np.position - cp.position;
                if (math.length(cpd) < kEpsilon || math.length(npd) < kEpsilon)
                    continue;

                float2 vl = math.normalize(cpd);
                float2 vr = math.normalize(npd);

                vl = new float2(-vl.y, vl.x);
                vr = new float2(-vr.y, vr.x);

                float2 va = math.normalize(vl) + math.normalize(vr);
                float2 vn = math.normalize(va);

                if (math.any(va) && math.any(vn))
                    m_TessPoints[tessPoints++] = cp.position + (vn * borderPivot);
            }

            m_TessPointCount = tessPoints;

            // Fill Geom. Generate in Native code until we have a reasonably fast enough Tessellation in NativeArray based Jobs.
            SpriteShapeSegment geom = m_GeomArray[0];
            Vector3 pos = m_PosArray[0];

            geom.vertexCount = 0;
            geom.geomIndex = 0;
            geom.indexCount = 0;
            geom.spriteIndex = -1;

            // Fill Geometry. Check if Fill Texture and Fill Scale is Valid.
            if (math.all(m_ShapeParams.shapeData.xw))
            {
                // Fill Geometry. Check if Fill Texture and Fill Scale is Valid.
                if (m_TessPointCount > 0)
                {
                    if (kOptimizeRender > 0)
                        OptimizePoints(kRenderQuality, ref m_TessPoints, ref m_TessPointCount);

                    var inputs = new ContourVertex[m_TessPointCount];
                    for (int i = 0; i < m_TessPointCount; ++i)
                        inputs[i] = new ContourVertex() { Position = new Vec3() { X = m_TessPoints[i].x, Y = m_TessPoints[i].y } };

                    Tess tess = new Tess();
                    tess.AddContour(inputs, ContourOrientation.Original);
                    tess.Tessellate(WindingRule.NonZero, ElementType.Polygons, 3);

                    var indices = tess.Elements.Select(i => (UInt16)i).ToArray();
                    var vertices = tess.Vertices.Select(v => new Vector2(v.Position.X, v.Position.Y)).ToArray();
                    m_IndexDataCount = indices.Length;
                    m_VertexDataCount = vertices.Length;

                    if (vertices.Length > 0)
                    {
                        for (m_ActiveIndexCount = 0; m_ActiveIndexCount < m_IndexDataCount; ++m_ActiveIndexCount)
                        {
                            m_IndexArray[m_ActiveIndexCount] = indices[m_ActiveIndexCount];
                        }
                        for (m_ActiveVertexCount = 0; m_ActiveVertexCount < m_VertexDataCount; ++m_ActiveVertexCount)
                        {
                            pos = new Vector3(vertices[m_ActiveVertexCount].x, vertices[m_ActiveVertexCount].y, 0);
                            m_PosArray[m_ActiveVertexCount] = pos;
                        }

                        geom.indexCount = m_ActiveIndexCount;
                        geom.vertexCount = m_ActiveVertexCount;
                    }
                }
            }

            if (m_TanArray.Length > 1)
            {
                for (int i = 0; i < m_ActiveVertexCount; ++i)
                    m_TanArray[i] = new Vector4(1.0f, 0, 0, -1.0f);
            }

            m_GeomArray[0] = geom;

        }

        void CalculateBoundingBox()
        {
            Bounds bounds = new Bounds();

            if (vertexDataCount > 0)
            { 
                for (int i = 0; i < vertexDataCount; ++i)
                {
                    Vector3 pos = m_PosArray[i];
                    bounds.Encapsulate(pos);
                }
            }            
            {
                for (int i = 0; i < contourPointCount; ++i)
                {
                    Vector3 pos = new Vector3(m_ContourPoints[i].position.x, m_ContourPoints[i].position.y, 0);
                    bounds.Encapsulate(pos);
                }
            }

            m_Bounds[0] = bounds;
        }

        void CalculateTexCoords()
        {

            SpriteShapeSegment geom = m_GeomArray[0];
            if (m_ShapeParams.splineData.x > 0)
            {
                float3 ext = m_Bounds[0].extents * 2;
                float3 min = m_Bounds[0].center - m_Bounds[0].extents;
                for (int i = 0; i < geom.vertexCount; ++i)
                {
                    Vector3 pos = m_PosArray[i];
                    Vector2 uv0 = m_Uv0Array[i];
                    float3 uv = ((new float3(pos.x, pos.y, pos.z) - min) / ext) * m_ShapeParams.fillData.x;
                    uv0.x = uv.x;
                    uv0.y = uv.y;
                    m_Uv0Array[i] = uv0;
                }
            }
            else
            {
                for (int i = 0; i < geom.vertexCount; ++i)
                {
                    Vector3 pos = m_PosArray[i];
                    Vector2 uv0 = m_Uv0Array[i];
                    float3 uv = math.transform(m_Transform, new float3(pos.x, pos.y, pos.z));
                    uv0.x = uv.x / m_ShapeParams.fillData.y;
                    uv0.y = uv.y / m_ShapeParams.fillData.z;
                    m_Uv0Array[i] = uv0;
                }
            }

        }

        void CopyVertexData(ref NativeSlice<Vector3> outPos, ref NativeSlice<Vector2> outUV0, ref NativeSlice<Vector4> outTan, int outIndex, NativeArray<JobShapeVertex> inVertices, int inIndex, float pivot, float sOrder)
        {
            Vector3 iscp = outPos[outIndex];
            Vector2 iscu = outUV0[outIndex];

            float3 v0 = new float3(inVertices[inIndex].pos.x, inVertices[inIndex].pos.y, sOrder);
            float3 v1 = new float3(inVertices[inIndex + 1].pos.x, inVertices[inIndex + 1].pos.y, sOrder);
            float3 v2 = new float3(inVertices[inIndex + 2].pos.x, inVertices[inIndex + 2].pos.y, sOrder);
            float3 v3 = new float3(inVertices[inIndex + 3].pos.x, inVertices[inIndex + 3].pos.y, sOrder);

            float3 lt = (v2 - v0) * pivot;
            float3 rt = (v3 - v1) * pivot;
            v0 = v0 + lt;
            v2 = v2 + lt;
            v1 = v1 + rt;
            v3 = v3 + rt;

            outPos[outIndex] = v0;            
            outUV0[outIndex] = inVertices[inIndex].uv;
            outPos[outIndex + 1] = v1;
            outUV0[outIndex + 1] = inVertices[inIndex + 1].uv;
            outPos[outIndex + 2] = v2;
            outUV0[outIndex + 2] = inVertices[inIndex + 2].uv;
            outPos[outIndex + 3] = v3;
            outUV0[outIndex + 3] = inVertices[inIndex + 3].uv;

            if (outTan.Length > 1)
            {
                outTan[outIndex] = inVertices[inIndex].tan;
                outTan[outIndex + 1] = inVertices[inIndex + 1].tan;
                outTan[outIndex + 2] = inVertices[inIndex + 2].tan;
                outTan[outIndex + 3] = inVertices[inIndex + 3].tan;
            }
        }

        int CopySegmentRenderData(JobSpriteInfo ispr, ref NativeSlice<Vector3> outPos, ref NativeSlice<Vector2> outUV0, ref NativeSlice<Vector4> outTan, ref int outCount, ref NativeArray<ushort> indexData, ref int indexCount, NativeArray<JobShapeVertex> inVertices, int inCount, float sOrder)
        {
            if (inCount < 4)
                return -1;

            int localVertex = 0;
            float pivot = 0.5f - ispr.metaInfo.y;
            int finalCount = indexCount + inCount;
            if (finalCount >= indexData.Length)
                throw new InvalidOperationException("Mesh data has reached Limits. Please try dividing shape into smaller blocks.");

            for (int i = 0; i < inCount; i = i + 4, outCount = outCount + 4, localVertex = localVertex + 4)
            {
                CopyVertexData(ref outPos, ref outUV0, ref outTan, outCount, inVertices, i, pivot, sOrder);
                indexData[indexCount++] = (ushort)(localVertex);
                indexData[indexCount++] = (ushort)(3 + localVertex);
                indexData[indexCount++] = (ushort)(1 + localVertex);
                indexData[indexCount++] = (ushort)(localVertex);
                indexData[indexCount++] = (ushort)(2 + localVertex);
                indexData[indexCount++] = (ushort)(3 + localVertex);
            }
            return outCount;
        }

        void TessellateSegment(JobSpriteInfo sprInfo, JobSegmentInfo segment, float2 whsize, float4 border, float pxlWidth, bool useClosure, bool validHead, bool validTail, NativeArray<JobShapeVertex> vertices, int vertexCount, ref NativeArray<JobShapeVertex> outputVertices, ref int outputCount)
        {
            int outputVertexCount = 0;
            float2 zero = new float2(0, 0);
            float2 lt = zero, lb = zero, rt = zero, rb = zero;
            var column0 = new JobShapeVertex();
            var column1 = new JobShapeVertex();
            var column2 = new JobShapeVertex();
            var column3 = new JobShapeVertex();


            int cms = vertexCount - 1;
            int lcm = cms - 1;
            int expectedCount = outputCount + (cms * 4);
            var sprite = vertices[0].sprite;

            float uvDist = 0;
            float uvStart = border.x;
            float uvEnd = whsize.x - border.z;
            float uvTotal = whsize.x;
            float uvInter = uvEnd - uvStart;
            float uvNow = uvStart / uvTotal;
            float dt = uvInter / pxlWidth;

            if (expectedCount >= outputVertices.Length)
                throw new InvalidOperationException("Mesh data has reached Limits. Please try dividing shape into smaller blocks.");            
            
            // Generate Render Inputs.
            for (int i = 0; i < cms; ++i)
            {
                bool lc = (cms > 1) && (i == lcm);
                bool im = (i != 0 && !lc);
                bool sa = false, sb = false;

                JobShapeVertex cs = vertices[i];
                JobShapeVertex ns = vertices[i + 1];

                float2 es = lc ? cs.pos : vertices[i + 2].pos;
                lt = column1.pos;
                lb = column3.pos;
                sa = true;

                if (im)
                {
                    // Left from Previous.
                    sb = GenerateColumnsTri(cs.pos, ns.pos, es, whsize, lc, ref rt, ref rb, ns.meta.x * 0.5f);
                }
                else
                {
                    if (!lc)
                    {
                        JobControlPoint icp = GetControlPoint(segment.spInfo.x);
                        var nsPos = ns.pos;
                        if (math.any(icp.tangentRt))
                            nsPos = icp.tangentRt + cs.pos;
                        sa = GenerateColumnsBi(cs.pos, nsPos, whsize, false, ref lt, ref lb, cs.meta.x * 0.5f);
                    }
                    if (lc && useClosure)
                    {
                        rb = m_FirstLB;
                        rt = m_FirstLT;
                    }
                    else
                    {
                        var esPos = es;
                        if (i == lcm)
                        { 
                            JobControlPoint jcp = GetControlPoint(segment.spInfo.y);
                            if (math.any(jcp.tangentLt))
                                esPos = jcp.tangentLt + ns.pos;
                        }
                        sb = GenerateColumnsBi(ns.pos, esPos, whsize, lc, ref rt, ref rb, ns.meta.x * 0.5f);
                    }
                }

                if (i == 0 && segment.spInfo.x == 0)
                {
                    m_FirstLB = lb;
                    m_FirstLT = lt;
                }

                if (!((math.any(lt) || math.any(lb)) && (math.any(rt) || math.any(rb))))                
                    continue;
                
                // default tan (1, 0, 0, -1) which is along uv. same here.
                float2 nlt = math.normalize(rt - lt);
                float4 tan = new float4(nlt.x, nlt.y, 0, -1.0f);
                column0.pos = lt;
                column0.meta = cs.meta;
                column0.sprite = sprite;
                column0.tan = tan;
                column1.pos = rt;
                column1.meta = ns.meta;
                column1.sprite = sprite;
                column1.tan = tan;
                column2.pos = lb;
                column2.meta = cs.meta;
                column2.sprite = sprite;
                column2.tan = tan;
                column3.pos = rb;
                column3.meta = ns.meta;
                column3.sprite = sprite;
                column3.tan = tan;

                // Calculate UV.
                if (validHead && i == 0)
                {
                    column0.uv.x = column0.uv.y = column1.uv.y = column2.uv.x = 0;
                    column1.uv.x = column3.uv.x = border.x / whsize.x;
                    column2.uv.y = column3.uv.y = 1.0f;
                }
                else if (validTail && i == lcm)
                {
                    column0.uv.y = column1.uv.y = 0;
                    column0.uv.x = column2.uv.x = (whsize.x - border.z) / whsize.x;
                    column1.uv.x = column2.uv.y = column3.uv.x = column3.uv.y = 1.0f;
                }
                else
                {
                    if ((uvInter - uvDist) < kEpsilonRelaxed)
                    {
                        uvNow = uvStart / uvTotal;
                        uvDist = 0;
                    }

                    uvDist = uvDist + (math.distance(ns.pos, cs.pos) * dt);
                    float uvNext = (uvDist + uvStart) / uvTotal;

                    if ((uvDist - uvInter) > kEpsilonRelaxed)
                    {
                        uvNext = uvEnd / uvTotal;
                        uvDist = uvEnd;
                    }

                    column0.uv.y = column1.uv.y = 0;
                    column0.uv.x = column2.uv.x = uvNow;
                    column1.uv.x = column3.uv.x = uvNext;
                    column2.uv.y = column3.uv.y = 1.0f;
                    uvNow = uvNext;
                }

                {
                    // Fix UV and Copy.
                    column0.uv.x = (column0.uv.x * sprInfo.uvInfo.z) + sprInfo.uvInfo.x;
                    column0.uv.y = (column0.uv.y * sprInfo.uvInfo.w) + sprInfo.uvInfo.y;
                    outputVertices[outputVertexCount++] = column0;

                    column1.uv.x = (column1.uv.x * sprInfo.uvInfo.z) + sprInfo.uvInfo.x;
                    column1.uv.y = (column1.uv.y * sprInfo.uvInfo.w) + sprInfo.uvInfo.y;
                    outputVertices[outputVertexCount++] = column1;

                    column2.uv.x = (column2.uv.x * sprInfo.uvInfo.z) + sprInfo.uvInfo.x;
                    column2.uv.y = (column2.uv.y * sprInfo.uvInfo.w) + sprInfo.uvInfo.y;
                    outputVertices[outputVertexCount++] = column2;

                    column3.uv.x = (column3.uv.x * sprInfo.uvInfo.z) + sprInfo.uvInfo.x;
                    column3.uv.y = (column3.uv.y * sprInfo.uvInfo.w) + sprInfo.uvInfo.y;
                    outputVertices[outputVertexCount++] = column3;
                }
            }
            outputCount = outputVertexCount;
        }

        bool SkipSegment(JobSegmentInfo isi)
        {
            // Start the Generation.            
            bool skip = (isi.spInfo.z < 0);
            if (!skip)
            {
                JobSpriteInfo ispr = GetSpriteInfo(isi.spInfo.z);
                skip = (math.any(ispr.uvInfo) == false);
            }
            if (skip)
            {
                int cis = GetContourIndex(isi.spInfo.x);
                int cie = GetEndContourIndexOfSegment(isi);
                while (cis < cie)
                {
                    JobContourPoint icp = GetContourPoint(cis);
                    m_ColliderPoints[m_ColliderDataCount++] = icp.position;
                    cis++;
                }
            }
            return skip;
        }

        void TessellateSegments()
        {

            JobControlPoint iscp = GetControlPoint(0);
            bool disableHead = (iscp.cpData.z == kModeContinous && isCarpet);

            // Determine Distance of Segment.
            for (int i = 0; i < segmentCount; ++i)
            {
                // Calculate Segment Distances.
                JobSegmentInfo isi = GetSegmentInfo(i);
                if (isi.spriteInfo.z >= 0)
                {
                    isi.spriteInfo.w = SegmentDistance(isi);
                    m_Segments[i] = isi;
                }
            }

            float2 zero = new float2(0, 0);
            float2 firstLT = zero;
            float2 firstLB = zero;
            float2 ec = zero;

            for (int i = 0; i < segmentCount; ++i)
            {
                // Tessellate the Segment.
                JobSegmentInfo isi = GetSegmentInfo(i);
                bool skip = SkipSegment(isi);
                if (skip)
                    continue;

                // Internal Data : x, y : pos z : height w : renderIndex
                JobShapeVertex isv = m_VertexData[0];
                JobSpriteInfo ispr = GetSpriteInfo(isi.spInfo.z);

                int vertexCount = 0;
                int sprIx = isi.spInfo.z;
                float rpunits = 1.0f / ispr.metaInfo.x;
                float2 whsize = new float2(ispr.metaInfo.z, ispr.metaInfo.w) * rpunits;
                float4 border = ispr.border * rpunits;

                bool validHead = hasSpriteBorder && (border.x > 0);
                bool validTail = hasSpriteBorder && (border.z > 0);

                // Generate the UV Increments.
                float extendUV = 0;
                float stPixelU = border.x;
                float enPixelU = whsize.x - border.z;
                float pxlWidth = enPixelU - stPixelU;
                float segmentD = isi.spriteInfo.w;
                float uIncStep = math.floor(segmentD / pxlWidth);
                uIncStep = uIncStep == 0 ? 1f : uIncStep;
                pxlWidth = isAdaptive ? (segmentD / uIncStep) : pxlWidth;

                // Check for any invalid Sizes.
                if (pxlWidth < kEpsilon)
                {
                    Cleanup();
                    throw new ArgumentException("One of the sprites seem to have Invalid Borders. Please check Input Sprites.");
                }

                // Start the Generation.
                int stIx = GetContourIndex(isi.spInfo.x);
                int enIx = GetEndContourIndexOfSegment(isi);

                // Single Segment Loop.
                if (stIx == 0)
                    validHead = (validHead && !disableHead);

                // Do we have a Sprite Head Slice
                if (validHead)
                {
                    JobContourPoint icp = GetContourPoint(stIx);
                    float2 v1 = icp.position;
                    float2 v2 = GetContourPoint(stIx + 1).position;
                    isv.pos = v1 + (math.normalize(v1 - v2) * border.x);
                    isv.meta.x = icp.ptData.x;
                    isv.sprite.x = sprIx;
                    m_VertexData[vertexCount++] = isv;
                }

                // Generate the Strip.
                float sl = 0;
                int it = stIx, nt = 0;
                while (it < enIx)
                {
                    nt = it + 1;
                    JobContourPoint icp = GetContourPoint(it);
                    JobContourPoint ncp = GetContourPoint(nt);

                    float2 sp = icp.position;
                    float2 ip = sp;
                    float2 ep = ncp.position;
                    float2 df = ep - sp;
                    float al = math.length(df);
                    if (al > kEpsilon)
                    {
                        float sh = icp.ptData.x, eh = ncp.ptData.x, hl = 0;
                        sl = sl + al;

                        // Connect previously left out space when sl < pxlWidth
                        var addtail = (0 == vertexCount);
                        float2 step = math.normalize(df);
                        isv.pos = icp.position;
                        isv.meta.x = icp.ptData.x;
                        isv.sprite.x = sprIx;
                        
                        if (vertexCount > 0)
                        { 
                            var dt = math.length(m_VertexData[vertexCount-1].pos - isv.pos);
                            addtail = dt > kEpsilonRelaxed;
                        }
                        if (addtail)
                            m_VertexData[vertexCount++] = isv;

                        while (sl > pxlWidth)
                        {
                            float _uv = pxlWidth - extendUV;
                            float2 uv = new float2(_uv);
                            ip = sp + (step * uv);
                            hl = hl + math.length(ip - sp);

                            isv.pos = ip;
                            isv.meta.x = math.lerp(sh, eh, hl / al);
                            isv.sprite.x = sprIx;
                            if (math.any(m_VertexData[vertexCount-1].pos - isv.pos))
                                m_VertexData[vertexCount++] = isv;

                            sl = sl - pxlWidth;
                            sp = ip;
                            extendUV = 0;
                        }
                        extendUV = sl;
                    }
                    it++;
                }

                // The Remains from the above Loop. Finish the Curve.
                if (sl > kEpsilon)
                {
                    JobContourPoint ecp = GetContourPoint(enIx);
                    isv.pos = ecp.position;
                    isv.meta.x = ecp.ptData.x;
                    isv.sprite.x = sprIx;
                    m_VertexData[vertexCount++] = isv;
                }

                // Generate Tail
                if (validTail)
                {
                    JobContourPoint icp = GetContourPoint(enIx);
                    float2 v1 = icp.position;
                    float2 v2 = GetContourPoint(enIx - 1).position;
                    isv.pos = v1 + (math.normalize(v1 - v2) * border.z);
                    isv.meta.x = icp.ptData.x;
                    isv.sprite.x = sprIx;
                    m_VertexData[vertexCount++] = isv;
                }

                // Generate the Renderer Data.
                int outputCount = 0;
                bool useClosure = (m_ControlPoints[0].cpData.z == kModeContinous) && (isi.spInfo.y == controlPointCount - 1);
                TessellateSegment(ispr, isi, whsize, border, pxlWidth, useClosure, validHead, validTail, m_VertexData, vertexCount, ref m_OutputVertexData, ref outputCount);
                if (outputCount == 0)
                    continue;
                var z = ((float)(i + 1) * kEpsilonOrder) + ((float)isi.spInfo.z * kEpsilonOrder * 0.001f);
                CopySegmentRenderData(ispr, ref m_PosArray, ref m_Uv0Array, ref m_TanArray, ref m_VertexDataCount, ref m_IndexArray, ref m_IndexDataCount, m_OutputVertexData, outputCount, z);

                if (hasCollider)
                {
                    JobSpriteInfo isprc = (ispr.metaInfo.x == 0) ? GetSpriteInfo(isi.spInfo.w) : ispr;
                    outputCount = 0;
                    rpunits = 1.0f / isprc.metaInfo.x;
                    whsize = new float2(isprc.metaInfo.z, isprc.metaInfo.w) * rpunits;
                    border = isprc.border * rpunits;
                    stPixelU = border.x;
                    enPixelU = whsize.x - border.z;
                    pxlWidth = enPixelU - stPixelU;
                    TessellateSegment(isprc, isi, whsize, border, pxlWidth, useClosure, validHead, validTail, m_VertexData, vertexCount, ref m_OutputVertexData, ref outputCount);
                    ec = UpdateCollider(isi, isprc, m_OutputVertexData, outputCount, ref m_ColliderPoints, ref m_ColliderDataCount);
                }

                // Geom Data
                var geom = m_GeomArray[i + 1];
                geom.geomIndex = i + 1;
                geom.indexCount = m_IndexDataCount - m_ActiveIndexCount;
                geom.vertexCount = m_VertexDataCount - m_ActiveVertexCount;
                geom.spriteIndex = isi.spInfo.z;
                m_GeomArray[i + 1] = geom;

                // Exit
                m_ActiveIndexCount = m_IndexDataCount;
                m_ActiveVertexCount = m_VertexDataCount;
            }

            // Copy Collider, Copy Render Data.
            m_GeomArrayCount = segmentCount + 1;
            m_IndexArrayCount = m_IndexDataCount;
            m_VertexArrayCount = m_VertexDataCount;
            m_ColliderPointCount = m_ColliderDataCount;
        }

        #endregion

        #region Corners

        bool AttachCorner(int cp, int ct, JobSpriteInfo ispr, ref NativeArray<JobControlPoint> newPoints, ref int activePoint)
        {
            // Correct Left.
            float2 zero = new float2(0, 0);
            int pp = (cp == 0) ? (controlPointCount - 1) : (cp - 1);
            int np = (cp + 1) % controlPointCount;

            JobControlPoint lcp = GetControlPoint(pp);
            JobControlPoint ccp = GetControlPoint(cp);
            JobControlPoint rcp = GetControlPoint(np);

            float rpunits = 1.0f / ispr.metaInfo.x;
            float2 whsize = new float2(ispr.texRect.z, ispr.texRect.w) * rpunits;
            float4 border = ispr.border * rpunits;

            // Generate the UV Increments.
            float stPixelV = border.y;
            float enPixelV = whsize.y - border.y;
            float pxlWidth = enPixelV - stPixelV;   // pxlWidth is the square size of the corner sprite.

            // Generate the LeftTop, LeftBottom, RightTop & RightBottom for both sides.
            float2 lt0 = zero;
            float2 lb0 = zero;
            float2 rt0 = zero;
            float2 rb0 = zero;
            GenerateColumnsBi(lcp.position, ccp.position, whsize, false, ref lb0, ref lt0, 0.5f);
            GenerateColumnsBi(ccp.position, lcp.position, whsize, false, ref rt0, ref rb0, 0.5f);

            float2 lt1 = zero;
            float2 lb1 = zero;
            float2 rt1 = zero;
            float2 rb1 = zero;
            GenerateColumnsBi(ccp.position, rcp.position, whsize, false, ref lb1, ref lt1, 0.5f);
            GenerateColumnsBi(rcp.position, ccp.position, whsize, false, ref rt1, ref rb1, 0.5f);

            rt0 = rt0 + (math.normalize(rt0 - lt0) * kExtendSegment);
            rb0 = rb0 + (math.normalize(rb0 - lb0) * kExtendSegment);
            lt1 = lt1 + (math.normalize(lt1 - rt1) * kExtendSegment);
            lb1 = lb1 + (math.normalize(lb1 - rb1) * kExtendSegment);

            float2 tp = zero;
            float2 bt = zero;

            // Generate Intersection of the Bottom Line Segments.
            bool t = LineIntersection(kEpsilon, lt0, rt0, lt1, rt1, ref tp);
            bool b = LineIntersection(kEpsilon, lb0, rb0, lb1, rb1, ref bt);
            if (!b && !t)
                return false;

            float2 pt = ccp.position;
            float2 lt = lcp.position - pt;
            float2 rt = rcp.position - pt;

            float ld = math.length(lt);
            float rd = math.length(rt);

            if (ld < pxlWidth || rd < pxlWidth)
                return false;

            float lrd = 0, rrd = 0;
            float a = AngleBetweenVector(math.normalize(lcp.position - ccp.position), math.normalize(rcp.position - ccp.position));
            if (a > 0)
            {
                lrd = ld - math.distance(lb0, bt);
                rrd = rd - math.distance(bt, rb1);
            }
            else
            {
                lrd = ld - math.distance(lt0, tp);
                rrd = rd - math.distance(tp, rt1);
            }

            float2 la = pt + (math.normalize(lt) * lrd);
            float2 ra = pt + (math.normalize(rt) * rrd);

            ccp.exData.x = ct;
            ccp.exData.z = 1;
            ccp.position = la;
            newPoints[activePoint++] = ccp;

            ccp.exData.x = ct;
            ccp.exData.z = 0;
            ccp.position = ra;
            newPoints[activePoint++] = ccp;

            JobCornerInfo iscp = m_Corners[m_CornerCount];
            if (a > 0)
            {
                iscp.bottom = bt;
                iscp.top = tp;
                GenerateColumnsBi(la, lcp.position, whsize, false, ref lt0, ref lb0, ispr.metaInfo.y);
                GenerateColumnsBi(ra, rcp.position, whsize, false, ref lt1, ref lb1, ispr.metaInfo.y);
                iscp.left = lt0;
                iscp.right = lb1;
            }
            else
            {
                iscp.bottom = tp;
                iscp.top = bt;
                GenerateColumnsBi(la, lcp.position, whsize, false, ref lt0, ref lb0, ispr.metaInfo.y);
                GenerateColumnsBi(ra, rcp.position, whsize, false, ref lt1, ref lb1, ispr.metaInfo.y);
                iscp.left = lb0;
                iscp.right = lt1;
            }
            iscp.cornerData.x = ct;
            iscp.cornerData.y = activePoint;
            m_Corners[m_CornerCount] = iscp;

            m_CornerCount++;
            return true;
        }

        float2 CornerTextureCoordinate(int cornerType, int index)
        {
            int cornerArrayIndex = (cornerType - 1) * 4;
            return m_CornerCoordinates[cornerArrayIndex + index];
        }

        int CalculateCorner(int index, float angle, float2 lt, float2 rt)
        {
            var ct = 0;
            float slope = SlopeAngle(lt);
            var slopePairs = new float2[] 
            {
                new float2(-135.0f, -35.0f),
                new float2(35.0f, 135.0f),
                new float2(-35.0f, 35.0f),
                new float2(-135.0f, 135.0f)
            };
            var cornerPairs = new int2[] 
            {
                new int2(kCornerTypeInnerTopLeft, kCornerTypeOuterBottomLeft),
                new int2(kCornerTypeInnerBottomRight, kCornerTypeOuterTopRight),
                new int2(kCornerTypeInnerTopRight, kCornerTypeOuterTopLeft),
                new int2(kCornerTypeInnerBottomLeft, kCornerTypeOuterBottomRight)
            };
            for (int i = 0; i < 3; ++i)
            {
                if ( slope > slopePairs[i].x && slope < slopePairs[i].y )
                {
                    ct = (angle > 0) ? cornerPairs[i].x : cornerPairs[i].y;
                    break;
                }
            }
            if (ct == 0)
            {
                ct = (angle > 0) ? kCornerTypeInnerBottomLeft : kCornerTypeOuterBottomRight;
            }
            return ct;

        }

        bool InsertCorner(int index, ref NativeArray<int2> cpSpriteIndices, ref NativeArray<JobControlPoint> newPoints, ref int activePoint)
        {
            int i = (index == 0) ? (controlPointCount - 1) : (index - 1);
            int k = (index + 1) % controlPointCount;

            // Check if we have valid Sprites.
            if (cpSpriteIndices[i].x >= spriteCount || cpSpriteIndices[index].x >= spriteCount)
                return false;

            // Check if they have been resolved.
            if (cpSpriteIndices[i].y == 0 || cpSpriteIndices[index].y == 0)
                return false;

            JobControlPoint pcp = GetControlPoint(i);
            JobControlPoint icp = GetControlPoint(index);
            JobControlPoint ncp = GetControlPoint(k);

            // Check if the Mode of control Point and previous neighbor is same. Also check if Corner Toggle is enabled.
            if (icp.cpData.y == 0 || pcp.cpData.z != kModeLinear || icp.cpData.z != kModeLinear || ncp.cpData.z != kModeLinear)
                return false;

            // Check if the Height of the Control Points match
            if (pcp.cpInfo.x != icp.cpInfo.x || icp.cpInfo.x != ncp.cpInfo.x)
                return false;

            JobSpriteInfo psi = GetSpriteInfo(cpSpriteIndices[i].x);
            JobSpriteInfo isi = GetSpriteInfo(cpSpriteIndices[index].x);

            // Check if the Sprites Pivot matches. Otherwise not allowed. // psi.uvInfo.w != isi.uvInfo.w (no more height checks)
            if (psi.metaInfo.y != 0.5f || psi.metaInfo.y != isi.metaInfo.y)
                return false;

            // Now perform expensive stuff like angles etc..
            float2 idir = math.normalize(ncp.position - icp.position);
            float2 ndir = math.normalize(pcp.position - icp.position);
            float angle = AngleBetweenVector(idir, ndir);
            float angleAbs = math.abs(angle);
            bool corner = AngleWithinRange(angleAbs, (90f - m_ShapeParams.splineData.z), (90f + m_ShapeParams.splineData.z));
            if (corner)
            {
                float2 rdir = math.normalize(icp.position - pcp.position);
                int ct = CalculateCorner(index, angle, rdir, idir);
                // Check if we have a valid Sprite.
                if (ct > 0)
                {
                    JobSpriteInfo cspr = GetCornerSpriteInfo(ct);
                    return AttachCorner(index, ct, cspr, ref newPoints, ref activePoint);
                }
            }

            return false;
        }

        void TessellateCorners()
        {

            for (int corner = 1; corner <= kCornerTypeInnerBottomRight; ++corner)
            {
                JobSpriteInfo isi = GetCornerSpriteInfo(corner);
                if (isi.metaInfo.x == 0)
                    continue;

                int ic = 0;
                int vc = 0;
                Vector3 pos = m_PosArray[ic];
                Vector2 uv0 = m_Uv0Array[ic];
                bool ccw = (corner <= kCornerTypeOuterBottomRight);
                int vertexArrayCount = m_VertexArrayCount;

                for (int i = 0; i < m_CornerCount; ++i)
                {
                    JobCornerInfo isc = m_Corners[i];
                    if (isc.cornerData.x == corner)
                    {
                        // Vertices.
                        pos.x = isc.top.x;
                        pos.y = isc.top.y;
                        uv0.x = (CornerTextureCoordinate(corner, 1).x * isi.uvInfo.z) + isi.uvInfo.x;
                        uv0.y = (CornerTextureCoordinate(corner, 1).y * isi.uvInfo.w) + isi.uvInfo.y;
                        m_PosArray[m_VertexArrayCount] = pos;
                        m_Uv0Array[m_VertexArrayCount++] = uv0;


                        pos.x = isc.right.x;
                        pos.y = isc.right.y;
                        uv0.x = (CornerTextureCoordinate(corner, 0).x * isi.uvInfo.z) + isi.uvInfo.x;
                        uv0.y = (CornerTextureCoordinate(corner, 0).y * isi.uvInfo.w) + isi.uvInfo.y;
                        m_PosArray[m_VertexArrayCount] = pos;
                        m_Uv0Array[m_VertexArrayCount++] = uv0;

                        pos.x = isc.left.x;
                        pos.y = isc.left.y;
                        uv0.x = (CornerTextureCoordinate(corner, 3).x * isi.uvInfo.z) + isi.uvInfo.x;
                        uv0.y = (CornerTextureCoordinate(corner, 3).y * isi.uvInfo.w) + isi.uvInfo.y;
                        m_PosArray[m_VertexArrayCount] = pos;
                        m_Uv0Array[m_VertexArrayCount++] = uv0;

                        pos.x = isc.bottom.x;
                        pos.y = isc.bottom.y;
                        uv0.x = (CornerTextureCoordinate(corner, 2).x * isi.uvInfo.z) + isi.uvInfo.x;
                        uv0.y = (CornerTextureCoordinate(corner, 2).y * isi.uvInfo.w) + isi.uvInfo.y;
                        m_PosArray[m_VertexArrayCount] = pos;
                        m_Uv0Array[m_VertexArrayCount++] = uv0;

                        // Indices.
                        m_IndexArray[m_IndexArrayCount++] = (ushort)(vc + 0);
                        m_IndexArray[m_IndexArrayCount++] = (ushort)(vc + (ccw ? 1 : 3));
                        m_IndexArray[m_IndexArrayCount++] = (ushort)(vc + (ccw ? 3 : 1));

                        m_IndexArray[m_IndexArrayCount++] = (ushort)(vc + 0);
                        m_IndexArray[m_IndexArrayCount++] = (ushort)(vc + (ccw ? 3 : 2));
                        m_IndexArray[m_IndexArrayCount++] = (ushort)(vc + (ccw ? 2 : 3));

                        vc = vc + 4;
                        ic = ic + 6;
                    }
                }

                if (m_TanArray.Length > 1)
                {
                    for (int i = vertexArrayCount; i < m_VertexArrayCount; ++i)
                        m_TanArray[i] = new Vector4(1.0f, 0, 0, -1.0f);
                }

                // Geom Data
                if (ic > 0 && vc > 0)
                {
                    var geom = m_GeomArray[m_GeomArrayCount];
                    geom.geomIndex = m_GeomArrayCount;
                    geom.indexCount = ic;
                    geom.vertexCount = vc;
                    geom.spriteIndex = m_SpriteInfos.Length + (corner - 1);
                    m_GeomArray[m_GeomArrayCount++] = geom;
                }
            }
        }

        #endregion

        #region Fast Optimizations

        bool AreCollinear(float2 a, float2 b, float2 c, float t)
        {
            float ax = (a.y - b.y) * (a.x - c.x);
            float bx = (a.y - c.y) * (a.x - b.x);
            float aa = math.abs(ax - bx);
            return aa < t;
        }

        // Check if points are co linear and reduce.
        void OptimizePoints(float tolerance, ref NativeArray<float2> pointSet, ref int pointCount)
        {
            int kMinimumPointsRequired = 8;
            if (pointCount < kMinimumPointsRequired)
                return;

            int optimizedColliderPointCount = 0;
            int endColliderPointCount = pointCount - 2;
            bool val = true;
            m_TempPoints[0] = pointSet[0];
            for (int i = 0; i < endColliderPointCount; ++i)
            {
                int j = i;
                float2 v0 = pointSet[i];
                float2 v1 = pointSet[i + 1];
                float2 v2 = pointSet[i + 2];
                val = true;
                while (val && j < endColliderPointCount)
                {
                    val = AreCollinear(v0, v1, v2, tolerance);
                    if (false == val)
                    {
                        m_TempPoints[++optimizedColliderPointCount] = v1;
                        i = j;
                        break;
                    }
                    j++;
                    v1 = pointSet[j + 1];
                    v2 = pointSet[j + 2];
                }
            }
            m_TempPoints[++optimizedColliderPointCount] = pointSet[endColliderPointCount];
            m_TempPoints[++optimizedColliderPointCount] = pointSet[endColliderPointCount + 1];
            if (isCarpet)
                m_TempPoints[++optimizedColliderPointCount] = pointSet[0];
            pointCount = optimizedColliderPointCount + 1;
            for (int i = 0; i < pointCount; ++i)
                pointSet[i] = m_TempPoints[i];
        }

        #endregion

        #region Collider Specific.
        void AttachCornerToCollider(JobSegmentInfo isi, float pivot, ref NativeArray<float2> colliderPoints, ref int colliderPointCount)
        {
            float2 zero = new float2(0, 0);
            int cornerIndex = isi.spInfo.x + 1;
            for (int i = 0; i < m_CornerCount; ++i)
            {
                JobCornerInfo isc = m_Corners[i];
                if (cornerIndex == isc.cornerData.y)
                {
                    float2 cp = zero;
                    float2 v0 = zero;
                    if (isc.cornerData.x > kCornerTypeOuterBottomRight)
                        v0 = isc.top;
                    else
                        v0 = isc.bottom;

                    float2 v2 = zero;
                    if (isc.cornerData.x > kCornerTypeOuterBottomRight)
                        v2 = isc.bottom;
                    else
                        v2 = isc.top;
                    cp = (v2 - v0) * pivot;
                    cp = (v2 + cp + v0 + cp) * 0.5f;
                    colliderPoints[colliderPointCount++] = cp;
                    break;
                }
            }
        }

        float2 UpdateCollider(JobSegmentInfo isi, JobSpriteInfo ispr, NativeArray<JobShapeVertex> vertices, int count, ref NativeArray<float2> colliderPoints, ref int colliderPointCount)
        {
            float2 zero = new float2(0, 0);
            float pivot = 0.5f - ispr.metaInfo.y;
            pivot = pivot + colliderPivot;
            AttachCornerToCollider(isi, pivot, ref colliderPoints, ref colliderPointCount);

            float2 cp = zero;
            float2 v0 = zero;
            float2 v2 = zero;

            for (int k = 0; k < count; k = k + 4)
            {
                v0 = vertices[k].pos;
                v2 = vertices[k + 2].pos;
                cp = (v2 - v0) * pivot;
                colliderPoints[colliderPointCount++] = (v2 + cp + v0 + cp) * 0.5f;
            }

            float2 v1 = vertices[count - 1].pos;
            float2 v3 = vertices[count - 3].pos;
            cp = (v1 - v3) * pivot;
            colliderPoints[colliderPointCount++] = (v1 + cp + v3 + cp) * 0.5f;
            return cp;
        }

        void TrimOverlaps()
        {
            int kMinimumPointTolerance = 4;
            if (m_ColliderPointCount < kMinimumPointTolerance)
                return;
            int trimmedPointCount = 0;
            int i = 0;
            int kColliderPointCountClamped = m_ColliderPointCount / 2;
            int kSplineDetailClamped = math.clamp(splineDetail * 3, 0, 8);
            int kNeighbors = kSplineDetailClamped > kColliderPointCountClamped ? kColliderPointCountClamped : kSplineDetailClamped;
            // Debug.Log(kSplineDetailClamped + " : " + kNeighbors + " = " + m_ColliderPointCount);

            if (!isCarpet)
                m_TempPoints[trimmedPointCount++] = m_ColliderPoints[0];

            while (i < m_ColliderPointCount)
            {
                int h = (i > 0) ? (i - 1) : (m_ColliderPointCount - 1);
                bool noIntersection = true;
                float2 v0 = m_ColliderPoints[h];
                float2 v1 = m_ColliderPoints[i];

                for (int n = kNeighbors; n > 1; --n)
                {
                    int j = (i + n - 1) % m_ColliderPointCount;
                    int k = (i + n) % m_ColliderPointCount;
                    if (k == 0 || i == 0)
                        continue;

                    float2 v2 = m_ColliderPoints[j];
                    float2 v3 = m_ColliderPoints[k];
                    float2 vx = v0 - v3;

                    if (math.abs(math.length(vx)) < kEpsilon)
                        break;

                    float2 vi = v0;

                    bool overLaps = LineIntersection(kEpsilonRelaxed, v0, v1, v2, v3, ref vi);
                    if (overLaps && IsPointOnLines(kEpsilonRelaxed, v0, v1, v2, v3, vi))
                    {
                        // Debug.Log(v0 + " = " + v1 + " : " + v2 + " = " + v3 + " & " + h + " = " + i + " : " + j + " = " + k + " => " + vi + " : " + n);
                        noIntersection = false;
                        m_TempPoints[trimmedPointCount++] = vi;
                        i = i + n;
                        break;
                    }
                }

                if (noIntersection)
                {
                    m_TempPoints[trimmedPointCount++] = v1;
                    i = i + 1;
                }
            }
            for (; i < m_ColliderPointCount; ++i)
                m_TempPoints[trimmedPointCount++] = m_ColliderPoints[i];

            for (int j = 0; j < trimmedPointCount; ++j)
                m_ColliderPoints[j] = m_TempPoints[j];

            // Check intersection of first line Segment and last.
            float2 vin = m_ColliderPoints[0];
            LineIntersection(kEpsilonRelaxed, m_ColliderPoints[0], m_ColliderPoints[1], m_ColliderPoints[trimmedPointCount - 1], m_ColliderPoints[trimmedPointCount - 2], ref vin);
            m_ColliderPoints[0] = vin;

            m_ColliderPointCount = trimmedPointCount;
        }

        void OptimizeCollider()
        {
            if (hasCollider)
            {
                if (kColliderQuality > 0)
                {                    
                    if (kOptimizeCollider > 0)
                    { 
                        OptimizePoints(kColliderQuality, ref m_ColliderPoints, ref m_ColliderPointCount);
                        TrimOverlaps();
                    }
                    m_ColliderPoints[m_ColliderPointCount++] = new float2(0, 0);
                    m_ColliderPoints[m_ColliderPointCount++] = new float2(0, 0);
                }
                if (m_ColliderPointCount <= 2)
                {
                    for (int i = 0; i < m_TessPointCount; ++i)
                        m_ColliderPoints[i] = m_TessPoints[i];
                    m_ColliderPoints[m_TessPointCount] = new float2(0, 0);
                    m_ColliderPoints[m_TessPointCount + 1] = new float2(0, 0);
                    m_ColliderPointCount = m_TessPointCount + 2;
                }
            }
        }

        #endregion

        #region Entry, Exit Points.

        public void Prepare(UnityEngine.U2D.SpriteShapeController controller, SpriteShapeParameters shapeParams, int maxArrayCount, NativeArray<ShapeControlPoint> shapePoints, NativeArray<SpriteShapeMetaData> metaData, AngleRangeInfo[] angleRanges, Sprite[] segmentSprites, Sprite[] cornerSprites)
        {
            // Prepare Inputs.
            PrepareInput(shapeParams, maxArrayCount, shapePoints, controller.optimizeGeometry, controller.autoUpdateCollider, controller.optimizeCollider, controller.colliderOffset, controller.colliderDetail);
            PrepareSprites(segmentSprites, cornerSprites);
            PrepareAngleRanges(angleRanges);
            PrepareControlPoints(shapePoints, metaData);

            // Generate Intermediates.
            GenerateContour();
            TessellateContour();
        }

        public void Execute()
        {
            // BURST
            GenerateSegments();
            TessellateSegments();
            TessellateCorners();
            CalculateBoundingBox();
            CalculateTexCoords();
            OptimizeCollider();
        }

        // Only needed if Burst is disabled.
        // [BurstDiscard]
        public void Cleanup()
        {
            SafeDispose(m_Corners);
            SafeDispose(m_CornerSpriteInfos);
            SafeDispose(m_SpriteInfos);
            SafeDispose(m_AngleRanges);
            SafeDispose(m_Segments);
            SafeDispose(m_ControlPoints);
            SafeDispose(m_ContourPoints);
            SafeDispose(m_TempPoints);
            SafeDispose(m_GeneratedControlPoints);
            SafeDispose(m_SpriteIndices);

            SafeDispose(m_TessPoints);
            SafeDispose(m_VertexData);
            SafeDispose(m_OutputVertexData);
            SafeDispose(m_CornerCoordinates);
        }

        #endregion

    }
};
