Skip to content

Instantly share code, notes, and snippets.

@andrew-raphael-lukasik
Last active September 28, 2023 18:51
Show Gist options
  • Save andrew-raphael-lukasik/cbf9d0097c3b4da67b5e0ecb3715e219 to your computer and use it in GitHub Desktop.
Save andrew-raphael-lukasik/cbf9d0097c3b4da67b5e0ecb3715e219 to your computer and use it in GitHub Desktop.
multi-threaded cube marching where geometry is sourced from a mesh asset template (artist-friendly)

4re4y77i96o6kjerewarezarhea323ef2

Screenshot 2022-02-23 232256

Screenshot 2022-02-23 221300

mesh asset: marching-cubes-prototype-fbx.zip disguised as png

(RMB/Save link as then change file extension from .png to .zip)

// 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;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment