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++;
                    }
                }
            }
        }
        public static void Tessellate <TColorMap>(NativeList <VoxelMeshComponent> components, NativeList <PackedIndex> componentIndices, NativeList <VoxelMeshComponentVertex> componentVertices,
                                                  Matrix4x4 transform, NativeList <float3> vertices, NativeList <int> triIndices,
                                                  NativeList <float3> normals, NativeList <int> materials, TColorMap materialColors, NativeList <Color32> colors, NativeDeduplicationCache dedupeCache)
            where TColorMap : struct, IMaterialColorMap
        {
            var newMaterials = new NativeList <int>(Allocator.Temp);

            for (int count = components.Length, i = 0; i < count; i++)
            {
                Tessellate(components[i], componentIndices, componentVertices, transform, vertices, vertices.Length, triIndices, normals, newMaterials, dedupeCache);
            }

            materials.AddRange(newMaterials);

            for (int count = newMaterials.Length, i = 0; i < count; i++)
            {
                colors.Add(materialColors.GetColor(newMaterials[i]));
            }

            DisposeManaged(newMaterials);
        }
        private static bool Deduplicate(float3 position, float3 normal, int material, int freeTriIndex, NativeDeduplicationCache dedupeCache, out int dedupedTriIndex)
        {
            //TODO Find sensible value
            const float quantization = 1024f;

            int hash = HashVertex(position, normal, material, quantization);

            if (!dedupeCache.table.TryGetValue(hash, out int index))
            {
                index = -1;
            }

            if (index >= 0)
            {
                int nodeIndex          = index;
                DedupedVertexNode node = dedupeCache.nodes[nodeIndex];

                while (true)
                {
                    DedupedVertex deduped = node.vertex;

                    if (IsVertexEqual(position, normal, material, deduped.position, deduped.normal, deduped.material, quantization))
                    {
                        //Equal vertex found, return index
                        dedupedTriIndex = deduped.triIndex;
                        return(true);
                    }

                    if (node.next >= 0)
                    {
                        nodeIndex = node.next;
                        node      = dedupeCache.nodes[nodeIndex];
                    }
                    else
                    {
                        //Hash contained but no equal vertex, append new node to linked list
                        dedupeCache.nodes[nodeIndex] = new DedupedVertexNode(deduped, dedupeCache.nodes.Length);
                        dedupeCache.nodes.Add(new DedupedVertexNode(new DedupedVertex(position, normal, material, freeTriIndex)));
                        break;
                    }
                }
            }
            else
            {
                //Hash not yet contained, add a new initial node
                Assert.IsTrue(dedupeCache.table.TryAdd(hash, dedupeCache.nodes.Length));
                dedupeCache.nodes.Add(new DedupedVertexNode(new DedupedVertex(position, normal, material, freeTriIndex)));
            }

            dedupedTriIndex = freeTriIndex;
            return(false);
        }