Skip to content

Instantly share code, notes, and snippets.

@Kellojo
Created December 12, 2022 21:43
Show Gist options
  • Save Kellojo/6d4b0736cf4615133fd0ce8d58a479e3 to your computer and use it in GitHub Desktop.
Save Kellojo/6d4b0736cf4615133fd0ce8d58a479e3 to your computer and use it in GitHub Desktop.
A simple wave function collapse algorithm implementation useful for procedural generation
using Kellojo.Utility;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaveFunctionCollapse<T> where T : IWfcTile<T> {
public WfcCell<T>[,] Cells;
public int Entropy {
get {
var e = 0;
for (int x = 0; x < Cells.GetLength(0); x++)
for (int y = 0; y < Cells.GetLength(1); y++)
e += Cells[x, y].Entropy;
return e;
}
}
public WaveFunctionCollapse(WfcCell<T>[,] cells) {
Cells = cells;
}
public void Run() {
while (Entropy > 0) {
// find lowest entropy cell or pick random
var cell = GetLowestEntropyCell();
// collapse that cell
cell.Collapse();
// propagate changes
var stack = new Stack<WfcCell<T>>();
stack.Push(cell);
while (stack.Peek() != null) {
var curCell = stack.Pop();
var neighbours = GetNeighboursFor(curCell);
foreach(var neighbour in neighbours) {
if (neighbour.Constrain(curCell.GetAllowedNeighbours())) {
stack.Push(neighbour);
}
}
}
}
}
public WfcCell<T> GetLowestEntropyCell() {
var minEntropy = 0;
WfcCell<T> minEntropyCell = null;
for (int x = 0; x < Cells.GetLength(0); x++) {
for (int y = 0; y < Cells.GetLength(1); y++) {
var cell = Cells[x, y];
if (cell.Entropy < minEntropy) {
minEntropyCell = cell;
}
}
}
return minEntropyCell;
}
public WfcCell<T> TryGetCellAt(int x, int y) {
if (Cells.GetLength(0) < x && Cells.GetLength(1) < y) {
return Cells[x, y];
}
return null;
}
public List<WfcCell<T>> GetNeighboursFor(WfcCell<T> cell) {
var neighbours = new List<WfcCell<T>>();
neighbours.Add();
return neighbours;
}
}
public abstract class WfcCell<T> where T : IWfcTile<T> {
public List<T> PossibleStates;
public T ResultingState;
public int Entropy {
get {
return PossibleStates.Count - 1;
}
}
public void Collapse() {
ResultingState = PossibleStates.PickRandom();
}
public bool Constrain(List<T> possibleStates) {
bool modified = false;
foreach(var state in PossibleStates) {
if (!possibleStates.Contains(state)) {
PossibleStates.Remove(state);
modified = true;
}
}
return modified;
}
public List<T> GetAllowedNeighbours() {
var allowed = new List<T>();
PossibleStates.ForEach(state => allowed.AddRange(state.GetAllowedNeighbours()));
return allowed;
}
}
public interface IWfcTile<T> {
public abstract List<T> GetAllowedNeighbours();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment