Skip to content

Instantly share code, notes, and snippets.

@andrew-raphael-lukasik
Last active November 9, 2023 22:11
Show Gist options
  • Select an option

  • Save andrew-raphael-lukasik/cc9d61edbbb44ecb4956c6cb69363a8e to your computer and use it in GitHub Desktop.

Select an option

Save andrew-raphael-lukasik/cc9d61edbbb44ecb4956c6cb69363a8e to your computer and use it in GitHub Desktop.
How to create a Voronoi diagram texture, IJobParallelFor + Burst
  • euclidean (left), manhattan (right)

// web* src = https://gist.github.com/andrew-raphael-lukasik/cc9d61edbbb44ecb4956c6cb69363a8e
using UnityEngine;
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Collections;
using Unity.Profiling;
[ExecuteInEditMode]
public class VoronoiTextureBurstJobComponent : MonoBehaviour
{
[SerializeField][Min(1)] uint _seed = 769;
[SerializeField][Range(2,512)] int _width = 300, _height = 300;
[SerializeField][Range(1,64)] int _numRandomPoints = 3;
Texture2D _texture;
NativeList<ushort2> _points;
uint lastSeedUsed = 0;
void OnValidate ()
{
bool inputChanged = false;
if( !_points.IsCreated )
{
_points = new NativeList<ushort2>( Allocator.Persistent );
inputChanged = true;
}
if( _points.Length!=_numRandomPoints )
{
_points.Length = _numRandomPoints;
inputChanged = true;
}
if( _texture==null || _texture.width!=_width || _texture.height!=_height )
{
if( _texture==null ) DestroyItWillYou( _texture );
_texture = new Texture2D( _width , _height , TextureFormat.RGBA32 , mipCount:0 , linear:true );
inputChanged = true;
}
if( inputChanged || _seed!=lastSeedUsed )
{
lastSeedUsed = _seed;
var watch = System.Diagnostics.Stopwatch.StartNew();
var dep = new FillRandomPointsJob{
TextureWidth = _width ,
TextureHeight = _height ,
Seed = _seed ,
Points = _points ,
}.Schedule();
NativeArray<RGBA32> textureData = _texture.GetRawTextureData<RGBA32>();
new GenerateVoronoiDiagramJob{
Points = _points ,
TextureWidth = _width ,
TextureHeight = _height ,
TextureOutput = textureData ,
}.Schedule( textureData.Length , _width , dep ).Complete();
_texture.Apply();
Debug.Log( $"jobs took {watch.ElapsedMilliseconds} [ms] to complete");
}
}
void OnEnable ()
=> OnValidate();
void OnDisable ()
{
if( _texture!=null ) DestroyItWillYou(_texture);
if( _points.IsCreated ) _points.Dispose();
}
void OnGUI ()
=> GUI.DrawTexture( new Rect(0,0,_width,_height) , _texture );
void DestroyItWillYou ( Object obj )
{
if( Application.isPlaying ) Destroy( obj );
else DestroyImmediate( obj );
}
[Unity.Burst.BurstCompile]
struct FillRandomPointsJob : IJob
{
public int TextureWidth, TextureHeight;
public uint Seed;
[WriteOnly] public NativeArray<ushort2> Points;
void IJob.Execute ()
{
var rnd = new Unity.Mathematics.Random( Seed );
int len = Points.Length;
for( int i=0 ; i<len ; i++ )
Points[i] = new ushort2{ x=(ushort)rnd.NextInt(TextureWidth) , y=(ushort)rnd.NextInt(TextureHeight) };
}
}
[Unity.Burst.BurstCompile]
struct GenerateVoronoiDiagramJob : IJobParallelFor
{
[ReadOnly] public NativeArray<ushort2> Points;
public int TextureWidth, TextureHeight;
[WriteOnly] public NativeArray<RGBA32> TextureOutput;
void IJobParallelFor.Execute ( int jobIndex )
{
float2 pixelPos = new int2{ x=jobIndex%TextureWidth , y=jobIndex/TextureWidth };
float pointDist = float.MaxValue;
int pointIndex = -1;
for( int i=Points.Length-1 ; i!=-1 ; i-- )
{
float2 pointPos = new float2{ x=Points[i].x , y=Points[i].y };
float dist = math.lengthsq( pointPos - pixelPos );// dist sq
// float2 vec = math.abs( pointPos - pixelPos );
// float dist = vec.x + vec.y;// manhattan distance
if( dist<pointDist )
{
pointDist = dist;
pointIndex = i;
}
if( dist<math.max(math.min(TextureWidth*0.01f,TextureHeight*0.01f),1) )
{
TextureOutput[jobIndex] = new RGBA32{ r=0 , g=0 , b=0 , a=255 };
return;
}
}
RGBA32 colorFromCoord;
{
ushort2 pointCoord = Points[pointIndex];
float2 f = new float2{ x=pointCoord.x , y=pointCoord.y } / ( new float2{ x=TextureWidth , y=TextureHeight } - new float2{ x=1 , y=1 } );
byte r = (byte)( 255*f.x );
byte g = (byte)( 255*f.y );
byte b = (byte)( (r+g)%255 );
colorFromCoord = new RGBA32{ r=r , g=g , b=b , a=255 };
}
TextureOutput[jobIndex] = colorFromCoord;
}
}
struct ushort2 { public ushort x, y; }
struct RGBA32 { public byte r, g, b, a; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment