public static void DrawDebugGizmos <TCell, TColorMap>(
            Vector3 position, Matrix4x4 transform, List <VoxelMeshComponent> components, List <PackedIndex> componentIndices,
            List <VoxelMeshComponentVertex> componentVertices, ref TCell voxelCell, TColorMap materialColors, Mesh voxelMesh = null)
            where TCell : struct, IVoxelCell
            where TColorMap : struct, IMaterialColorMap
        {
            Matrix4x4 translatedTransform = Matrix4x4.Translate(position) * transform;

            Gizmos.matrix = translatedTransform;

            if (voxelMesh != null)
            {
                Gizmos.color = new Color(1, 0, 0);
                Gizmos.DrawWireMesh(voxelMesh);
            }

            Gizmos.matrix = transform;

            for (int i = 0; i < components.Count; i++)
            {
                var component = components[i];

                if (component.RegularFeature)
                {
                    Gizmos.color = Color.black;
                    Gizmos.DrawSphere(component.FeatureVertex, 0.015f);

                    Gizmos.color = new Color(1, 1, 0);
                    Gizmos.DrawSphere(component.FeatureVertex, 0.01f);
                }
                else if (component.MaterialTransitionFeature)
                {
                    Gizmos.color = Color.black;
                    Gizmos.DrawSphere(component.FeatureVertex, 0.015f);

                    Gizmos.color = new Color(0.2f, 0.8f, 1);
                    Gizmos.DrawSphere(component.FeatureVertex, 0.01f);
                }

                //Centroid/Feature point normal
                if (!component.RegularFeature)
                {
                    float3 triangleFanCenter = float3.zero;
                    int    vertexCount       = 0;
                    for (int j = component.Index; j < component.Index + component.Size; j++)
                    {
                        triangleFanCenter += (float3)componentVertices[componentIndices[j]].Position;
                        vertexCount++;
                    }
                    triangleFanCenter *= 1f / vertexCount;

                    float3 fullAverageNormal = float3.zero;

                    for (int j = component.Index; j < component.Index + component.Size; j++)
                    {
                        PackedIndex packedIndex = componentIndices[j];
                        if (packedIndex.IsNormalSet)
                        {
                            VoxelMeshComponentVertex meshVertex = componentVertices[packedIndex];
                            fullAverageNormal += meshVertex.Normal / math.length(meshVertex.Position - triangleFanCenter);
                        }
                    }

                    fullAverageNormal = math.normalize(fullAverageNormal);

                    Gizmos.color = new Color(1, 0, 1);
                    Gizmos.DrawRay(component.FeatureVertex, fullAverageNormal * 0.25f);
                }

                //TODO Render individual normals for RegularFeatures

                for (int j = component.Index; j < component.Index + component.Size; j++)
                {
                    var packedIndex = componentIndices[j];

                    if (packedIndex.IsMaterialTransition)
                    {
                        var meshVertex = componentVertices[packedIndex];

                        Gizmos.color = Color.black;
                        Gizmos.DrawSphere(meshVertex.Position, 0.015f);

                        Gizmos.color = new Color(0, 1, 1);
                        Gizmos.DrawSphere(meshVertex.Position, 0.01f);

                        Gizmos.DrawRay(meshVertex.Position, meshVertex.Normal * 0.25f);
                    }
                }
            }

            float width  = voxelCell.GetWidth();
            float height = voxelCell.GetHeight();
            float depth  = voxelCell.GetDepth();

            Gizmos.matrix = translatedTransform;

            Gizmos.color = new Color(1, 0, 0);
            Gizmos.DrawWireCube(new Vector3(width / 2f, height / 2f, depth / 2f), new Vector3(width, height, depth));

            Gizmos.matrix = transform;

            foreach (VoxelCellFace face in Enum.GetValues(typeof(VoxelCellFace)))
            {
                var cellCount = voxelCell.GetCellFaceCount(face);

                for (int k = 0; k < cellCount; k++)
                {
                    int      cell      = voxelCell.GetCellFace(face, k);
                    CellInfo info      = voxelCell.GetInfo(cell);
                    var      edges     = voxelCell.GetEdges(cell);
                    var      materials = voxelCell.GetMaterials(cell);

                    for (int i = 0; i < 4; i++)
                    {
                        var edge = edges[i];

                        float cx, cy;
                        switch (i)
                        {
                        default:
                        case 0:
                            cx = 0;
                            cy = 0;
                            break;

                        case 1:
                            cx = info.Width;
                            cy = 0;
                            break;

                        case 2:
                            cx = info.Width;
                            cy = info.Height;
                            break;

                        case 3:
                            cx = 0;
                            cy = info.Height;
                            break;
                        }

                        Vector3 cornerPos = info.Position + face.BasisX() * cx + face.BasisY() * cy;

                        Gizmos.color = Color.black;
                        Gizmos.DrawCube(cornerPos, 0.06f * Vector3.one);

                        Gizmos.color = materialColors.GetColor(materials[i]);
                        Gizmos.DrawCube(cornerPos, 0.05f * Vector3.one);

                        if (voxelCell.HasIntersection(cell, edge))
                        {
                            var intersection = voxelCell.GetIntersection(cell, edge);

                            float ix, iy;
                            switch (i)
                            {
                            default:
                            case 0:
                                ix = intersection;
                                iy = 0;
                                break;

                            case 1:
                                ix = info.Width;
                                iy = intersection;
                                break;

                            case 2:
                                ix = info.Width - intersection;
                                iy = info.Height;
                                break;

                            case 3:
                                ix = 0;
                                iy = info.Height - intersection;
                                break;
                            }

                            Vector3 intersectionPos = info.Position + face.BasisX() * ix + face.BasisY() * iy;

                            float3 edgeStart, edgeEnd;
                            switch (i)
                            {
                            default:
                            case 0:
                                edgeStart = info.Position;
                                edgeEnd   = info.Position + face.BasisX() * 1;
                                break;

                            case 1:
                                edgeStart = info.Position + face.BasisX() * 1;
                                edgeEnd   = info.Position + face.BasisX() * 1 + face.BasisY() * 1;
                                break;

                            case 2:
                                edgeStart = info.Position + face.BasisX() * 1 + face.BasisY() * 1;
                                edgeEnd   = info.Position + face.BasisY() * 1;
                                break;

                            case 3:
                                edgeStart = info.Position + face.BasisY() * 1;
                                edgeEnd   = info.Position;
                                break;
                            }
                            Gizmos.color = Color.yellow;
                            Gizmos.DrawRay(edgeStart, edgeEnd - edgeStart);

                            Gizmos.color = Color.black;
                            Gizmos.DrawSphere(intersectionPos, 0.015f);

                            Gizmos.color = new Color(1, 0, 1);
                            Gizmos.DrawSphere(intersectionPos, 0.01f);

                            Vector3 normal = voxelCell.GetNormal(cell, edge);

                            Gizmos.DrawRay(intersectionPos, normal * 0.25f);

                            Camera cam = Camera.current;
                            if (cam != null)
                            {
                                int facing = GetFacing(cam.transform.forward);

                                Vector3 planeX;
                                Vector3 planeY;

                                Gizmos.color = new Color(0, 0, 0);

                                switch (facing)
                                {
                                default:
                                case 0:
                                    planeX = new Vector3(0, 0, 1);
                                    planeY = new Vector3(0, 1, 0);
                                    Gizmos.DrawCube(intersectionPos, new Vector3(0.001f, 0.085f, 0.085f));
                                    Gizmos.color = new Color(1, 0, 0);
                                    Gizmos.DrawCube(intersectionPos, new Vector3(0.001f, 0.075f, 0.075f));
                                    break;

                                case 1:
                                    planeX = new Vector3(0, 0, 1);
                                    planeY = new Vector3(1, 0, 0);
                                    Gizmos.DrawCube(intersectionPos, new Vector3(0.085f, 0.001f, 0.085f));
                                    Gizmos.color = new Color(0, 1, 0);
                                    Gizmos.DrawCube(intersectionPos, new Vector3(0.075f, 0.001f, 0.075f));
                                    break;

                                case 2:
                                    planeX = new Vector3(1, 0, 0);
                                    planeY = new Vector3(0, 1, 0);
                                    Gizmos.DrawCube(intersectionPos, new Vector3(0.085f, 0.085f, 0.001f));
                                    Gizmos.color = new Color(0, 0, 1);
                                    Gizmos.DrawCube(intersectionPos, new Vector3(0.075f, 0.075f, 0.001f));
                                    break;
                                }

                                Vector3 projectedNormal = planeX * Vector3.Dot(planeX, normal) + planeY * Vector3.Dot(planeY, normal);
                                projectedNormal.Normalize();

                                Gizmos.DrawRay(intersectionPos, projectedNormal * 0.15f);
                            }
                        }
                    }
                }
            }

            Gizmos.matrix = Matrix4x4.identity;
            Gizmos.color  = new Color(1, 1, 1);
        }
        public static void Tessellate(VoxelMeshComponent component, NativeList <PackedIndex> componentIndices, NativeList <VoxelMeshComponentVertex> componentVertices,
                                      Matrix4x4 transform, NativeList <float3> vertices, int freeTriIndex, NativeList <int> triIndices,
                                      NativeList <float3> normals, NativeList <int> materials, NativeDeduplicationCache dedupeCache)
        {
            float3 triangleFanCenter;

            if (component.Feature)
            {
                triangleFanCenter = component.FeatureVertex;
            }
            else
            {
                triangleFanCenter = float3.zero;
                int vertexCount = 0;
                for (int i = component.Index; i < component.Index + component.Size; i++)
                {
                    triangleFanCenter += (float3)componentVertices[componentIndices[i]].Position;
                    vertexCount++;
                }
                triangleFanCenter *= 1f / vertexCount;
            }

            if (component.RegularFeature)
            {
                var sharpSections       = new NativeList <SharpSection>(Allocator.Temp);
                var sharpSectionNormals = new NativeList <float3>(Allocator.Temp);

                FindSharpComponentSections(component, componentIndices, componentVertices, sharpSections, sharpSectionNormals);

                for (int i = component.Index; i < component.Index + component.Size; i++)
                {
                    PackedIndex packedIndex1             = componentIndices[i];
                    VoxelMeshComponentVertex meshVertex1 = componentVertices[packedIndex1];
                    float3 v1 = meshVertex1.Position;
                    float3 n1 = meshVertex1.Normal;
                    int    m1 = meshVertex1.Material;

                    PackedIndex packedIndex2             = componentIndices[component.Index + ((i + 1 - component.Index) % component.Size)];
                    VoxelMeshComponentVertex meshVertex2 = componentVertices[packedIndex2];
                    float3 v2 = meshVertex2.Position;
                    float3 n2 = meshVertex2.Normal;
                    int    m2 = meshVertex2.Material;

                    float3 faceAverageNormal = float3.zero;
                    if (packedIndex1.IsNormalSet)
                    {
                        faceAverageNormal += n1 / math.length(v1 - triangleFanCenter);
                    }
                    if (packedIndex2.IsNormalSet)
                    {
                        faceAverageNormal += n2 / math.length(v2 - triangleFanCenter);
                    }
                    faceAverageNormal = math.normalize(faceAverageNormal);

                    float3 triangleFanCenterNormal    = float3.zero;
                    bool   triangleFanCenterNormalSet = false;

                    //Find the normal of the section we're in
                    for (int count = sharpSections.Length, j = 0; j < count; j++)
                    {
                        SharpSection section = sharpSections[j];

                        if (i >= section.startIndex && i < section.endIndex)
                        {
                            triangleFanCenterNormal    = sharpSectionNormals[j];
                            triangleFanCenterNormalSet = true;
                        }
                    }

                    if (!triangleFanCenterNormalSet)
                    {
                        triangleFanCenterNormal = faceAverageNormal;
                    }

                    var outV2 = transform.MultiplyPoint(triangleFanCenter);
                    var outN2 = transform.MultiplyVector(triangleFanCenterNormal);

                    var    outV1 = transform.MultiplyPoint(v1);
                    float3 outN1;
                    if (packedIndex1.IsNormalSet)
                    {
                        outN1 = transform.MultiplyVector(n1);
                    }
                    else
                    {
                        outN1 = outN2;
                    }
                    if (Deduplicate(outV1, outN1, m1, freeTriIndex, dedupeCache, out int outI1))
                    {
                        triIndices.Add(outI1);
                    }
                    else
                    {
                        vertices.Add(outV1);
                        normals.Add(outN1);
                        materials.Add(m1);
                        triIndices.Add(freeTriIndex);
                        freeTriIndex++;
                    }

                    if (Deduplicate(outV2, outN2, m1, freeTriIndex, dedupeCache, out int outI2))
                    {
                        triIndices.Add(outI2);
                    }
                    else
                    {
                        vertices.Add(outV2);
                        normals.Add(outN2);
                        materials.Add(m1);
                        triIndices.Add(freeTriIndex);
                        freeTriIndex++;
                    }

                    var    outV3 = transform.MultiplyPoint(v2);
                    float3 outN3;
                    if (packedIndex2.IsNormalSet)
                    {
                        outN3 = transform.MultiplyVector(n2);
                    }
                    else
                    {
                        outN3 = outN2;
                    }
                    if (Deduplicate(outV3, outN3, m2, freeTriIndex, dedupeCache, out int outI3))
                    {
                        triIndices.Add(outI3);
                    }
                    else
                    {
                        vertices.Add(outV3);
                        normals.Add(outN3);
                        materials.Add(m2);
                        triIndices.Add(freeTriIndex);
                        freeTriIndex++;
                    }
                }

                DisposeManaged(sharpSections);
                DisposeManaged(sharpSectionNormals);
            }
            else
            {
                float3 fullAverageNormal = float3.zero;

                for (int i = component.Index; i < component.Index + component.Size; i++)
                {
                    PackedIndex packedIndex = componentIndices[i];
                    if (packedIndex.IsNormalSet)
                    {
                        VoxelMeshComponentVertex meshVertex = componentVertices[packedIndex];
                        fullAverageNormal += meshVertex.Normal / math.length(meshVertex.Position - triangleFanCenter);
                    }
                }

                fullAverageNormal = math.normalize(fullAverageNormal);

                for (int i = component.Index; i < component.Index + component.Size; i++)
                {
                    PackedIndex packedIndex1             = componentIndices[i];
                    VoxelMeshComponentVertex meshVertex1 = componentVertices[packedIndex1];
                    float3 v1 = meshVertex1.Position;
                    float3 n1 = meshVertex1.Normal;
                    int    m1 = meshVertex1.Material;

                    PackedIndex packedIndex2             = componentIndices[component.Index + ((i + 1 - component.Index) % component.Size)];
                    VoxelMeshComponentVertex meshVertex2 = componentVertices[packedIndex2];
                    float3 v2 = meshVertex2.Position;
                    float3 n2 = meshVertex2.Normal;
                    int    m2 = meshVertex2.Material;

                    float3 faceAverageNormal = float3.zero;
                    if (packedIndex1.IsNormalSet)
                    {
                        faceAverageNormal += n1 / math.length(v1 - triangleFanCenter);
                    }
                    if (packedIndex2.IsNormalSet)
                    {
                        faceAverageNormal += n2 / math.length(v2 - triangleFanCenter);
                    }
                    faceAverageNormal = math.normalize(faceAverageNormal);

                    var    outV1 = transform.MultiplyPoint(v1);
                    float3 outN1;
                    if (packedIndex1.IsNormalSet)
                    {
                        outN1 = transform.MultiplyVector(n1);
                    }
                    else
                    {
                        outN1 = transform.MultiplyVector(faceAverageNormal);
                    }
                    if (Deduplicate(outV1, outN1, m1, freeTriIndex, dedupeCache, out int outI1))
                    {
                        triIndices.Add(outI1);
                    }
                    else
                    {
                        vertices.Add(outV1);
                        normals.Add(outN1);
                        materials.Add(m1);
                        triIndices.Add(freeTriIndex);
                        freeTriIndex++;
                    }

                    var outV2 = transform.MultiplyPoint(triangleFanCenter);
                    var outN2 = transform.MultiplyVector(fullAverageNormal);
                    if (Deduplicate(outV2, outN2, m1, freeTriIndex, dedupeCache, out int outI2))
                    {
                        triIndices.Add(outI2);
                    }
                    else
                    {
                        vertices.Add(outV2);
                        normals.Add(outN2);
                        materials.Add(m1);
                        triIndices.Add(freeTriIndex);
                        freeTriIndex++;
                    }

                    var    outV3 = transform.MultiplyPoint(v2);
                    float3 outN3;
                    if (packedIndex2.IsNormalSet)
                    {
                        outN3 = transform.MultiplyVector(n2);
                    }
                    else
                    {
                        outN3 = transform.MultiplyVector(faceAverageNormal);
                    }
                    if (Deduplicate(outV3, outN3, m2, freeTriIndex, dedupeCache, out int outI3))
                    {
                        triIndices.Add(outI3);
                    }
                    else
                    {
                        vertices.Add(outV3);
                        normals.Add(outN3);
                        materials.Add(m2);
                        triIndices.Add(freeTriIndex);
                        freeTriIndex++;
                    }
                }
            }
        }