Last active
December 23, 2023 11:54
-
-
Save zulfajuniadi/d766d3e06d9052e5c7e6a2ae0df2860a to your computer and use it in GitHub Desktop.
Cellular Automata using Unity's Job System
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Unity.Burst; | |
using Unity.Collections; | |
using Unity.Jobs; | |
using UnityEngine; | |
/// <summary> | |
/// Cellular Automata using Unity Job System | |
/// </summary> | |
/// <example> | |
/// This sample shows how to call the <see cref="Run"/> method. | |
/// <code> | |
/// class TestClass : MonoBehaviour | |
/// { | |
/// void Start() | |
/// { | |
/// var cellularAutomata = new CellularAutomata (); | |
/// var trees = cellularAutomata.Run (194, 194, 55, 5, 1.5f, new Vector2 (1.5f, 1.5f), 100, false); | |
/// for (int i = 0; i < trees.Length; i++) | |
/// { | |
/// var tree = trees[i]; | |
/// generateTrees(new Vector3(tree.x, 0, tree.y)); | |
/// } | |
/// trees.Dispose (); | |
/// } | |
/// } | |
/// </code> | |
/// </example> | |
public class CellularAutomata | |
{ | |
[BurstCompile] | |
private struct CellularAutomataJob : IJobParallelFor | |
{ | |
[ReadOnly] public NativeHashMap<int, int> input; | |
[WriteOnly] public NativeHashMap<int, int>.Concurrent output; | |
public CellularAutomata.GridUtils gridUtils; | |
public void Execute (int key) | |
{ | |
var position = gridUtils.GetPosition (key); | |
var neighbors = GetNeighbors (position); | |
if (neighbors > 4) | |
{ | |
output.TryAdd (key, 1); | |
} | |
else if (neighbors < 4) | |
{ | |
output.TryAdd (key, 0); | |
} | |
else if (input.TryGetValue (key, out int exsiting)) | |
{ | |
output.TryAdd (key, exsiting); | |
} | |
else | |
{ | |
output.TryAdd (key, 0); | |
} | |
} | |
int GetNeighbors (Vector2Int position) | |
{ | |
int wallCount = 0; | |
for (int neighbourX = position.x - 1; neighbourX <= position.x + 1; neighbourX++) | |
{ | |
for (int neighbourY = position.y - 1; neighbourY <= position.y + 1; neighbourY++) | |
{ | |
if (neighbourX >= 0 && neighbourX < gridUtils.gridWidth && neighbourY >= 0 && neighbourY < gridUtils.gridLength) | |
{ | |
var key = gridUtils.GetKey (neighbourX, neighbourY); | |
if (neighbourX != position.x || neighbourY != position.y) | |
{ | |
if (input.TryGetValue (key, out int existing)) | |
{ | |
wallCount += existing; | |
} | |
} | |
} | |
else | |
{ | |
wallCount++; | |
} | |
} | |
} | |
return wallCount; | |
} | |
} | |
/// <summary> | |
/// Run the cellular automator job | |
/// </summary> | |
/// <param name="gridWidth">The grid width</param> | |
/// <param name="gridLength">The grid length</param> | |
/// <param name="fillPercentage">Initial fill percentage. 50 +- 10 is a good range</param> | |
/// <param name="iterations">How many iterations the cleanup cycle is ran. 5 +- 2 is a good range</param> | |
/// <param name="worldPositionMultiplier">The resulting position multiplier</param> | |
/// <param name="worldPositionOffset">The resulting position offset</param> | |
/// <param name="seed">The random seed of the generation</param> | |
/// <param name="inverse">Inverse the job's results. Set false to generate Caves, true to generate Islands</param> | |
/// <returns>NativeList<Vector2></returns> | |
public NativeList<Vector2> Run ( | |
int gridWidth, | |
int gridLength, | |
int fillPercentage = 50, | |
int iterations = 5, | |
float worldPositionMultiplier = 1.0f, | |
Vector2 worldPositionOffset = new Vector2 (), | |
int seed = 100, | |
bool inverse = false | |
) | |
{ | |
System.Random pseudoRandom = new System.Random (seed.GetHashCode ()); | |
int keyLength = gridWidth * gridLength; | |
var gridUtils = new GridUtils { gridWidth = gridWidth, gridLength = gridLength, keyLength = keyLength }; | |
NativeHashMap<int, int> input = new NativeHashMap<int, int> (keyLength, Allocator.Temp); | |
for (int x = 0; x < gridWidth; x++) | |
{ | |
for (int y = 0; y < gridLength; y++) | |
{ | |
var key = gridUtils.GetKey (x, y); | |
if ( | |
x == 0 || | |
x == gridWidth - 1 || | |
y == 0 || | |
y == gridLength - 1 || | |
pseudoRandom.Next (0, 100) <= fillPercentage | |
) | |
{ | |
input.TryAdd (key, 1); | |
} | |
else | |
{ | |
input.TryAdd (key, 0); | |
} | |
} | |
} | |
NativeHashMap<int, int> output = new NativeHashMap<int, int> (keyLength, Allocator.Temp); | |
if (iterations == 0) | |
{ | |
output.Dispose (); | |
output = input; | |
} | |
else | |
{ | |
for (int i = 1; i <= iterations; i++) | |
{ | |
new CellularAutomataJob { input = input, output = output, gridUtils = gridUtils }.Schedule (keyLength, 64).Complete (); | |
input.Dispose (); | |
input = output; | |
if (i != iterations) | |
{ | |
output = new NativeHashMap<int, int> (keyLength, Allocator.Temp); | |
} | |
} | |
} | |
var results = new NativeList<Vector2> (Allocator.Temp); | |
for (int x = 0; x < gridWidth; x++) | |
{ | |
for (int y = 0; y < gridLength; y++) | |
{ | |
var key = gridUtils.GetKey (x, y); | |
var hasExisting = false; | |
if (output.TryGetValue (key, out int existing)) | |
{ | |
if (existing == 1) | |
{ | |
hasExisting = true; | |
} | |
} | |
var worldPosition = worldPositionOffset + | |
new Vector2 ( | |
(float) x * worldPositionMultiplier, | |
(float) y * worldPositionMultiplier | |
); | |
if ( | |
(inverse && !hasExisting) || | |
(!inverse && hasExisting) | |
) | |
{ | |
results.Add (worldPosition); | |
} | |
} | |
} | |
output.Dispose (); | |
// be sure to dispose the results!!! | |
return results; | |
} | |
/// <summary> | |
/// Generic grid utils functions | |
/// </summary> | |
public struct GridUtils | |
{ | |
public int gridWidth; | |
public int gridLength; | |
public int keyLength; | |
public Vector2Int GetPosition (int key) | |
{ | |
var pos = new Vector2Int (); | |
pos.x = key % gridWidth; | |
pos.y = Mathf.FloorToInt (key / gridWidth); | |
return pos; | |
} | |
public int GetKey (Vector2Int position) | |
{ | |
return position.y * gridWidth + position.x; | |
} | |
public int GetKey (int x, int y) | |
{ | |
return y * gridWidth + x; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment