How to implement Marching Cubes as IJobParallelFor

I don’t know how to get the Job System implemented in my code. Would be great if someone could give me a rough idea on how to do it.

Here’s my code rn:

for( int x=0 ; x<width-1 ; x++ )
for( int y=0 ; y<height-1 ; y++ )
for( int z=0 ; z<depth-1 ; z++ )
{
    // get the values in the 8 neighbours which make up a cube
    for( int i=0 ; i<8 ; i++ )
    {
        int ix = x + VertexOffset[i,0];
        int iy = y + VertexOffset[i,1];
        int iz = z + VertexOffset[i,2];
        Cube[i] = voxels[ ix + iy*width + iz*width * height ];
    }

    // perform algorithm
    March( x , y , z , Cube , verts , indices );
}

And the Vertex Offset array:

protected static readonly int[,] VertexOffset = new int[,]
{
    {0,0,0} , {1,0,0} , {1,1,0} , {0,1,0} ,
    {0,0,1} , {1,0,1} , {1,1,1} , {0,1,1}
};

Can i get this to work using IJobParallelFor and when yes, then how would i get the data needed for the March function?

Here is an example of how to read the voxel field in a IJobParallelFor and construct basic mesh data with 4 different IJob executed in parallel (thanks to scheduling setup and RO dependencies).

To make this code work, import this mesh into your project: 193032-marching-squares-prototypefbx.zip (6.4 KB) ( RMB/Save link as...)

And use it to fill the Template field as follows:

193033-screenshot-2022-02-23-221300

profiler timeline for 32/32/32 grid; 32k voxels @ i3-4170 cpu:

// LetsCubeMarch.cs

// web* src = https://gist.github.com/andrew-raphael-lukasik/cbf9d0097c3b4da67b5e0ecb3715e219
using UnityEngine;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine.Rendering;

using BurstCompile = Unity.Burst.BurstCompileAttribute;

[RequireComponent( typeof(MeshFilter) , typeof(MeshRenderer) )]
public class LetsCubeMarch : MonoBehaviour
{
    [SerializeField] int3 _numCells = new int3( 32 , 32 , 32 );
    [SerializeField] float3 _noiseRepetition = new float3( 10 , 10 , 10 );
    [SerializeField] float3 _noiseOffset = new float3( 0.5f , 1.4f , -0.35f );
    [SerializeField][Range(0,1)] float _fill = 0.45f;
    [SerializeField] Mesh[] _template = new Mesh[6];
    public JobHandle Dependency = default(JobHandle);
    NativeArray<byte> _voxels;
    NativeArray<int> _templateIndices0, _templateIndices1, _templateIndices2, _templateIndices3, _templateIndices4, _templateIndices5;
    NativeArray<Vector3> _templateVertices0, _templateVertices1, _templateVertices2, _templateVertices3, _templateVertices4, _templateVertices5;
    NativeArray<Vector3> _templateNormals0, _templateNormals1, _templateNormals2, _templateNormals3, _templateNormals4, _templateNormals5;
    NativeArray<Vector2> _templateUVs0, _templateUVs1, _templateUVs2, _templateUVs3, _templateUVs4, _templateUVs5;
    NativeList<int> _indices;
    NativeList<Vector3> _vertices, _normals;
    NativeList<Vector2> _uv;
    NativeList<VoxelsToBitmasksJob.Entry> _relevantVoxelData;
    Mesh _mesh = null;
    bool _voxelsChanged, _jobScheduled;

    void Awake ()
    {
        _mesh = new Mesh();
        for( int i=0 ; i<_template.Length ; i++ )
            Debug.Log($"template {i} // topology:{_template[i].GetTopology(0)}, tri:{_template[i].triangles.Length}, vert:{_template[i].vertices.Length}");
        _mesh.MarkDynamic();
        GetComponent<MeshFilter>().sharedMesh = _mesh;

        var generateVoxelsJob = new GenerateVoxelsJob( numCells:_numCells , threshold:1.0f-_fill*2f , noiseRepetition:_noiseRepetition , noiseOffset:_noiseOffset , Allocator.TempJob );
        generateVoxelsJob.Schedule( _numCells.x*_numCells.y*_numCells.z , 128 ).Complete();
        _voxels = generateVoxelsJob.Results;
        _voxelsChanged = true;

        _indices = new NativeList<int>( Allocator.Persistent );
        _vertices = new NativeList<Vector3>( Allocator.Persistent );
        _normals = new NativeList<Vector3>( Allocator.Persistent );
        _uv = new NativeList<Vector2>( Allocator.Persistent );
        _relevantVoxelData = new NativeList<VoxelsToBitmasksJob.Entry>( Allocator.Persistent );

        _templateVertices0 = new NativeArray<Vector3>( _template[0].vertices , Allocator.Persistent );// x-
        _templateVertices1 = new NativeArray<Vector3>( _template[1].vertices , Allocator.Persistent );// x+
        _templateVertices2 = new NativeArray<Vector3>( _template[2].vertices , Allocator.Persistent );// y-
        _templateVertices3 = new NativeArray<Vector3>( _template[3].vertices , Allocator.Persistent );// y+
        _templateVertices4 = new NativeArray<Vector3>( _template[4].vertices , Allocator.Persistent );// z-
        _templateVertices5 = new NativeArray<Vector3>( _template[5].vertices , Allocator.Persistent );// z+

        _templateNormals0 = new NativeArray<Vector3>( _template[0].normals , Allocator.Persistent );// x-
        _templateNormals1 = new NativeArray<Vector3>( _template[1].normals , Allocator.Persistent );// x+
        _templateNormals2 = new NativeArray<Vector3>( _template[2].normals , Allocator.Persistent );// y-
        _templateNormals3 = new NativeArray<Vector3>( _template[3].normals , Allocator.Persistent );// y+
        _templateNormals4 = new NativeArray<Vector3>( _template[4].normals , Allocator.Persistent );// z-
        _templateNormals5 = new NativeArray<Vector3>( _template[5].normals , Allocator.Persistent );// z+

        _templateUVs0 = new NativeArray<Vector2>( _template[0].uv , Allocator.Persistent );// x-
        _templateUVs1 = new NativeArray<Vector2>( _template[1].uv , Allocator.Persistent );// x+
        _templateUVs2 = new NativeArray<Vector2>( _template[2].uv , Allocator.Persistent );// y-
        _templateUVs3 = new NativeArray<Vector2>( _template[3].uv , Allocator.Persistent );// y+
        _templateUVs4 = new NativeArray<Vector2>( _template[4].uv , Allocator.Persistent );// z-
        _templateUVs5 = new NativeArray<Vector2>( _template[5].uv , Allocator.Persistent );// z+
        
        _templateIndices0 = new NativeArray<int>( _template[0].triangles , Allocator.Persistent );// x-
        _templateIndices1 = new NativeArray<int>( _template[1].triangles , Allocator.Persistent );// x+
        _templateIndices2 = new NativeArray<int>( _template[2].triangles , Allocator.Persistent );// y-
        _templateIndices3 = new NativeArray<int>( _template[3].triangles , Allocator.Persistent );// y+
        _templateIndices4 = new NativeArray<int>( _template[4].triangles , Allocator.Persistent );// z-
        _templateIndices5 = new NativeArray<int>( _template[5].triangles , Allocator.Persistent );// z+
    }

    void OnDestroy ()
    {
        Dependency.Complete();

        if( _voxels.IsCreated ) _voxels.Dispose();
        if( _indices.IsCreated ) _indices.Dispose();
        if( _vertices.IsCreated ) _vertices.Dispose();
        if( _normals.IsCreated ) _normals.Dispose();
        if( _uv.IsCreated ) _uv.Dispose();
        if( _relevantVoxelData.IsCreated ) _relevantVoxelData.Dispose();

        if( _templateVertices0.IsCreated ) _templateVertices0.Dispose();
        if( _templateVertices1.IsCreated ) _templateVertices1.Dispose();
        if( _templateVertices2.IsCreated ) _templateVertices2.Dispose();
        if( _templateVertices3.IsCreated ) _templateVertices3.Dispose();
        if( _templateVertices4.IsCreated ) _templateVertices4.Dispose();
        if( _templateVertices5.IsCreated ) _templateVertices5.Dispose();

        if( _templateNormals0.IsCreated ) _templateNormals0.Dispose();
        if( _templateNormals1.IsCreated ) _templateNormals1.Dispose();
        if( _templateNormals2.IsCreated ) _templateNormals2.Dispose();
        if( _templateNormals3.IsCreated ) _templateNormals3.Dispose();
        if( _templateNormals4.IsCreated ) _templateNormals4.Dispose();
        if( _templateNormals5.IsCreated ) _templateNormals5.Dispose();

        if( _templateUVs0.IsCreated ) _templateUVs0.Dispose();
        if( _templateUVs1.IsCreated ) _templateUVs1.Dispose();
        if( _templateUVs2.IsCreated ) _templateUVs2.Dispose();
        if( _templateUVs3.IsCreated ) _templateUVs3.Dispose();
        if( _templateUVs4.IsCreated ) _templateUVs4.Dispose();
        if( _templateUVs5.IsCreated ) _templateUVs5.Dispose();

        if( _templateIndices0.IsCreated ) _templateIndices0.Dispose();
        if( _templateIndices1.IsCreated ) _templateIndices1.Dispose();
        if( _templateIndices2.IsCreated ) _templateIndices2.Dispose();
        if( _templateIndices3.IsCreated ) _templateIndices3.Dispose();
        if( _templateIndices4.IsCreated ) _templateIndices4.Dispose();
        if( _templateIndices5.IsCreated ) _templateIndices5.Dispose();

        if( _templateUVs0.IsCreated ) _templateUVs0.Dispose();
        if( _templateUVs1.IsCreated ) _templateUVs1.Dispose();
        if( _templateUVs2.IsCreated ) _templateUVs2.Dispose();
        if( _templateUVs3.IsCreated ) _templateUVs3.Dispose();
        if( _templateUVs4.IsCreated ) _templateUVs4.Dispose();
        if( _templateUVs5.IsCreated ) _templateUVs5.Dispose();

        Destroy( _mesh );
    }
    
    void Update ()
    {
        Dependency.Complete();
        if( _jobScheduled )
        {
            Debug.Log($"new mesh data // indices:{_indices.Length}, vertices:{_vertices.Length}, normals:{_normals.Length}, uv:{_uv.Length} ");
            
            _mesh.Clear();
            _mesh.SetVertices( _vertices.AsArray() );
            _mesh.SetNormals( _normals.AsArray() );
            _mesh.SetUVs( 0 , _uv.AsArray() );
            _mesh.indexFormat = _indices.Length>ushort.MaxValue ? IndexFormat.UInt32 : IndexFormat.UInt16;
            _mesh.SetIndices( _indices.AsArray() , MeshTopology.Triangles , 0 );

            _jobScheduled = false;
        }

        if( _voxelsChanged )
        {
            _vertices.Clear();
            _indices.Clear();
            _normals.Clear();
            _uv.Clear();
            _relevantVoxelData.Clear();
            int maxCellCount = _numCells.x * _numCells.y * _numCells.z;
            int maxVerticesInSingleTemplateMesh = 4;// estimate the max number of vertices (you may want to change this when mesh changes)
            int maxVertices = maxCellCount * 6*maxVerticesInSingleTemplateMesh;
            _relevantVoxelData.Capacity = maxCellCount;

            var voxelsToBitmasksJob = new VoxelsToBitmasksJob{
                NumCells			= _numCells ,
                Voxels				= _voxels ,
                Results				= _relevantVoxelData.AsParallelWriter() ,
            };
            var vertJob = new VertJob{
                Entries				= _relevantVoxelData ,
                Vertices			= _vertices ,
                TemplateVertices0	= _templateVertices0 ,
                TemplateVertices1	= _templateVertices1 ,
                TemplateVertices2	= _templateVertices2 ,
                TemplateVertices3	= _templateVertices3 ,
                TemplateVertices4	= _templateVertices4 ,
                TemplateVertices5	= _templateVertices5 ,
            };
            var normJob = new NormalsJob{
                Entries				= _relevantVoxelData ,
                Normals				= _normals ,
                TemplateNormals0	= _templateNormals0 ,
                TemplateNormals1	= _templateNormals1 ,
                TemplateNormals2	= _templateNormals2 ,
                TemplateNormals3	= _templateNormals3 ,
                TemplateNormals4	= _templateNormals4 ,
                TemplateNormals5	= _templateNormals5 ,
            };
            var uvJob = new UVJob{
                Entries				= _relevantVoxelData ,
                UV					= _uv ,
                TemplateUVs0		= _templateUVs0 ,
                TemplateUVs1		= _templateUVs1 ,
                TemplateUVs2		= _templateUVs2 ,
                TemplateUVs3		= _templateUVs3 ,
                TemplateUVs4		= _templateUVs4 ,
                TemplateUVs5		= _templateUVs5 ,
            };
            var indicesJob = new IndicesJob{
                Entries				= _relevantVoxelData ,
                Indices				= _indices ,
                TemplateIndices0	= _templateIndices0 ,
                TemplateIndices1	= _templateIndices1 ,
                TemplateIndices2	= _templateIndices2 ,
                TemplateIndices3	= _templateIndices3 ,
                TemplateIndices4	= _templateIndices4 ,
                TemplateIndices5	= _templateIndices5 ,
                TemplateVertices0Length	= _templateVertices0.Length ,
                TemplateVertices1Length	= _templateVertices1.Length ,
                TemplateVertices2Length	= _templateVertices2.Length ,
                TemplateVertices3Length	= _templateVertices3.Length ,
                TemplateVertices4Length	= _templateVertices4.Length ,
                TemplateVertices5Length	= _templateVertices5.Length ,
            };
            
            Dependency = voxelsToBitmasksJob.Schedule( _voxels.Length , _numCells.x*_numCells.y , Dependency );
            var parallelJobs = new NativeArray<JobHandle>( 4 , Allocator.Temp );
            parallelJobs[0] = vertJob.Schedule( Dependency );
            parallelJobs[1] = normJob.Schedule( Dependency );
            parallelJobs[2] = uvJob.Schedule( Dependency );
            parallelJobs[3] = indicesJob.Schedule( Dependency );
            Dependency = JobHandle.CombineDependencies( parallelJobs );
            
            _voxelsChanged = false;
            _jobScheduled = true;
        }
    }

    #if UNITY_EDITOR
    void OnValidate ()
    {
        if( Application.isPlaying && _voxels.IsCreated )
        {
            if( _jobScheduled )
            {
                Dependency.Complete();
                _jobScheduled = false;
            }
            _voxels.Dispose();
            var generateVoxelsJob = new GenerateVoxelsJob( numCells:_numCells , threshold:1.0f-_fill*2f , noiseRepetition:_noiseRepetition , noiseOffset:_noiseOffset , Allocator.TempJob );
            generateVoxelsJob.Schedule( _numCells.x*_numCells.y*_numCells.z , 128 ).Complete();
            _voxels = generateVoxelsJob.Results;
            _voxelsChanged = true;
        }
    }
    void OnDrawGizmos ()
    {
        float3 cellSize = Vector3.one;
        Gizmos.matrix = transform.localToWorldMatrix;
        if( !Application.isPlaying )
        {
            int len = _numCells.x*_numCells.y*_numCells.z;
            var positionsNative = new NativeList<float3>( len , Allocator.TempJob );
            var job = new OnDrawGizmosJob{
                NumCells			= _numCells ,
                Threshold			= 1.0f-_fill*2f ,
                NoiseRepetition		= _noiseRepetition ,
                NoiseOffset			= _noiseOffset ,
                Results				= positionsNative.AsParallelWriter() ,
            };
            job.Schedule( len , _numCells.x*_numCells.y ).Complete();
            float3[] positions = positionsNative.ToArray();
            positionsNative.Dispose();

            Gizmos.color = Color.black;
            foreach( float3 point in positions )
                Gizmos.DrawCube( point , cellSize );
        }
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireCube( (float3)_numCells * 0.5f , (float3)_numCells * cellSize );
    }
    
    [BurstCompile] struct OnDrawGizmosJob : IJobParallelFor
    {
        public int3 NumCells;
        public float Threshold;
        public float3 NoiseRepetition;
        public float3 NoiseOffset;
        [WriteOnly] public NativeList<float3>.ParallelWriter Results;

        void IJobParallelFor.Execute ( int i )
        {
            int3 coords = default(Utilities).IndexToCoords( i:i , numCells:NumCells );
            byte voxel = default(Utilities).CoordsToVoxel( coords:coords , numCells:NumCells , threshold:Threshold , noiseRepetition:NoiseRepetition , noiseOffset:NoiseOffset );
            if( voxel!=0 )
            {
                float3 cellCenter = (float3)coords + new float3{ x=0.5f , y=0.5f , z=0.5f };
                Results.AddNoResize( cellCenter );
            }
        }
    }
    #endif

    [BurstCompile] public struct GenerateVoxelsJob : IJobParallelFor
    {
        public int3 NumCells;
        public float Threshold;
        public float3 NoiseRepetition;
        public float3 NoiseOffset;
        [WriteOnly] public NativeArray<byte> Results;
        public GenerateVoxelsJob ( int3 numCells , float threshold , float3 noiseRepetition , float3 noiseOffset , Allocator allocator )
        {
            this.NumCells = numCells;
            this.Threshold = threshold;
            this.NoiseRepetition = noiseRepetition;
            this.NoiseOffset = noiseOffset;
            this.Results = new NativeArray<byte>( numCells.x*numCells.y*numCells.z , allocator );
        }
        void IJobParallelFor.Execute ( int index )
        {
            int3 coords = default(Utilities).IndexToCoords( i:index , numCells:NumCells );
            Results[index] = default(Utilities).CoordsToVoxel( coords:coords , numCells:NumCells , threshold:Threshold , noiseRepetition:NoiseRepetition , noiseOffset:NoiseOffset );
        }
    }

    [BurstCompile] public struct VoxelsToBitmasksJob : IJobParallelFor
    {
        public int3 NumCells;
        [ReadOnly] public NativeArray<byte> Voxels;
        [WriteOnly] public NativeList<Entry>.ParallelWriter Results;
        void IJobParallelFor.Execute ( int index )
        {
            if( Voxels[index]==0 )// iterate empty cells only
            {
                int3 cellCoords = default(Utilities).IndexToCoords( i:index , numCells:NumCells );
                int bitmask = 0;
                for( byte direction=0 ; direction<6 ; direction++ )
                {
                    int3 neighbourCoords = cellCoords + default(Utilities).Offset(direction);
                    if( !math.any( neighbourCoords<0 | neighbourCoords>=NumCells ) )// index bounds test
                        if( Voxels[default(Utilities).CoordsToIndex(neighbourCoords,NumCells)]!=0 )
                            bitmask |= 1<<direction;
                }
                if( bitmask!=0 )// ignore cells neighbouring empty space only
                {
                    Results.AddNoResize( new Entry{
                        Bitmask		= bitmask ,
                        Coords		= cellCoords
                    } );
                }
            }
        }
        public struct Entry
        {
            public int Bitmask;
            public int3 Coords;
        }
    }

    [BurstCompile] public struct IndicesJob : IJob
    {
        [WriteOnly] public NativeList<int> Indices;
        [ReadOnly] public NativeList<VoxelsToBitmasksJob.Entry> Entries;
        [ReadOnly] public NativeArray<int> TemplateIndices0, TemplateIndices1, TemplateIndices2, TemplateIndices3, TemplateIndices4, TemplateIndices5;
        public int TemplateVertices0Length, TemplateVertices1Length, TemplateVertices2Length, TemplateVertices3Length, TemplateVertices4Length, TemplateVertices5Length;
        void IJob.Execute ()
        {
            int baseIndex = 0;
            foreach( var next in Entries.AsArray() )
            {
                int bitmask = next.Bitmask;
                for( byte direction=0 ; direction<6 ; direction++ )
                if( (bitmask&1<<direction)==1<<direction )// the same as Voxels[neighbourIndex]!=0, but read from bitmask, so it's faster and you can test many directions at once
                switch( direction )
                {
                    case 0: foreach( int index in TemplateIndices0 ) Indices.Add( baseIndex + index ); baseIndex += TemplateVertices0Length; break;// x-
                    case 1: foreach( int index in TemplateIndices1 ) Indices.Add( baseIndex + index ); baseIndex += TemplateVertices1Length; break;// x+
                    case 2: foreach( int index in TemplateIndices2 ) Indices.Add( baseIndex + index ); baseIndex += TemplateVertices2Length; break;// y-
                    case 3: foreach( int index in TemplateIndices3 ) Indices.Add( baseIndex + index ); baseIndex += TemplateVertices3Length; break;// y+
                    case 4: foreach( int index in TemplateIndices4 ) Indices.Add( baseIndex + index ); baseIndex += TemplateVertices4Length; break;// z-
                    case 5: foreach( int index in TemplateIndices5 ) Indices.Add( baseIndex + index ); baseIndex += TemplateVertices5Length; break;// z+
                }
            }
        }
    }

    [BurstCompile] public struct VertJob : IJob
    {
        [WriteOnly] public NativeList<Vector3> Vertices;
        [ReadOnly] public NativeList<VoxelsToBitmasksJob.Entry> Entries;
        [ReadOnly] public NativeArray<Vector3> TemplateVertices0, TemplateVertices1, TemplateVertices2, TemplateVertices3, TemplateVertices4, TemplateVertices5;
        void IJob.Execute ()
        {
            foreach( var next in Entries.AsArray() )
            {
                int3 cellCoords = next.Coords;
                int bitmask = next.Bitmask;
                float3 cellCenter = (float3) cellCoords + new float3{ x=0.5f , y=0.5f , z=0.5f };
                for( byte direction=0 ; direction<6 ; direction++ )
                if( (bitmask&1<<direction)==1<<direction )// the same as Voxels[neighbourIndex]!=0, but read from bitmask, so it's faster and you can test many directions at once
                switch( direction )
                {
                    case 0: foreach( Vector3 vert in TemplateVertices0 ) Vertices.Add( cellCenter + (float3)vert ); break;// x-
                    case 1: foreach( Vector3 vert in TemplateVertices1 ) Vertices.Add( cellCenter + (float3)vert ); break;// x+
                    case 2: foreach( Vector3 vert in TemplateVertices2 ) Vertices.Add( cellCenter + (float3)vert ); break;// y-
                    case 3: foreach( Vector3 vert in TemplateVertices3 ) Vertices.Add( cellCenter + (float3)vert ); break;// y+
                    case 4: foreach( Vector3 vert in TemplateVertices4 ) Vertices.Add( cellCenter + (float3)vert ); break;// z-
                    case 5: foreach( Vector3 vert in TemplateVertices5 ) Vertices.Add( cellCenter + (float3)vert ); break;// z+
                }
            }
        }
    }

    [BurstCompile] public struct NormalsJob : IJob
    {
        [WriteOnly] public NativeList<Vector3> Normals;
        [ReadOnly] public NativeList<VoxelsToBitmasksJob.Entry> Entries;
        [ReadOnly] public NativeArray<Vector3> TemplateNormals0, TemplateNormals1, TemplateNormals2, TemplateNormals3, TemplateNormals4, TemplateNormals5;
        void IJob.Execute ()
        {
            foreach( var next in Entries.AsArray() )
            {
                int3 cellCoords = next.Coords;
                int bitmask = next.Bitmask;
                for( byte direction=0 ; direction<6 ; direction++ )
                if( (bitmask&1<<direction)==1<<direction )// the same as Voxels[neighbourIndex]!=0, but read from bitmask, so it's faster and you can test many directions at once
                switch( direction )
                {
                    case 0: Normals.AddRange( TemplateNormals0 ); break;// x-
                    case 1: Normals.AddRange( TemplateNormals1 ); break;// x+
                    case 2: Normals.AddRange( TemplateNormals2 ); break;// y-
                    case 3: Normals.AddRange( TemplateNormals3 ); break;// y+
                    case 4: Normals.AddRange( TemplateNormals4 ); break;// z-
                    case 5: Normals.AddRange( TemplateNormals5 ); break;// z+
                }
            }
        }
    }

    [BurstCompile] public struct UVJob : IJob
    {
        [WriteOnly] public NativeList<Vector2> UV;
        [ReadOnly] public NativeList<VoxelsToBitmasksJob.Entry> Entries;
        [ReadOnly] public NativeArray<Vector2> TemplateUVs0, TemplateUVs1, TemplateUVs2, TemplateUVs3, TemplateUVs4, TemplateUVs5;
        void IJob.Execute ()
        {
            foreach( var next in Entries.AsArray() )
            {
                int bitmask = next.Bitmask;
                for( byte direction=0 ; direction<6 ; direction++ )
                if( (bitmask&1<<direction)==1<<direction )// the same as Voxels[neighbourIndex]!=0, but read from bitmask, so it's faster and you can test many directions at once
                switch( direction )
                {
                    case 0: UV.AddRange( TemplateUVs0 ); break;// x-
                    case 1: UV.AddRange( TemplateUVs1 ); break;// x+
                    case 2: UV.AddRange( TemplateUVs2 ); break;// y-
                    case 3: UV.AddRange( TemplateUVs3 ); break;// y+
                    case 4: UV.AddRange( TemplateUVs4 ); break;// z-
                    case 5: UV.AddRange( TemplateUVs5 ); break;// z+
                }
            }
        }
    }

    public struct Utilities
    {
        public int3 Offset ( int direction )
        {
            switch( direction )
            {
                case 0:		return new int3{ x=-1 };// x-
                case 1:		return new int3{ x=+1 };// x+
                case 2:		return new int3{ y=-1 };// y-
                case 3:		return new int3{ y=+1 };// y+
                case 4:		return new int3{ z=-1 };// z-
                case 5:		return new int3{ z=+1 };// z+
                default:	throw new System.ArgumentOutOfRangeException();
            }
        }
        public int CoordsToIndex ( int x , int y , int z , int3 numCells ) => z*numCells.x*numCells.y + y*numCells.x + x;
        public int CoordsToIndex ( int3 coords , int3 numCells ) => this.CoordsToIndex( x:coords.x , y:coords.y , z:coords.z , numCells:numCells );
        public int3 IndexToCoords ( int i , int3 numCells )
        {
            int numSlices = numCells.x * numCells.y;
            int z = i / numSlices;
            int ilayer = i % numSlices;
            int y = ilayer / numCells.x;
            int x = ilayer % numCells.x;
            return new int3{ x=x , y=y , z=z };
        }
        public void IndexToCoords ( int i , int3 numCells , out int x , out int y , out int z )
        {
            int3 coords = this.IndexToCoords( i:i , numCells:numCells );
            x = coords.x;
            y = coords.y;
            z = coords.z;
        }
        public byte CoordsToVoxel ( int3 coords , int3 numCells , float threshold , float3 noiseRepetition , float3 noiseOffset )
        {
            float3 noisePos = (float3)coords/(float3)numCells + noiseOffset;
            return noise.pnoise(noisePos,noiseRepetition)>threshold ? (byte)1 : (byte)0;
        }
    }

}

I figure i’ll have to make the March function into an IJobParallelFor but how would i start them parallel and how would i give them the data they need?

Ok first of all, thanks for this amazing help. It’s nice to see such nice people online.
Second of all, i think i’ve nearly understood your code now, but if i want to maker a “real” marching cubes algorithm, then i’d have to get Native Arrays with multiple dimension for for example the Triangle Connection Table. So i searched online a bit but i can’t seem to find a way to do x dimensional Natives Arrays. Any tipps? Do i just flatten/unflatten it? Thanks in advance