Added WFC and respective parts (Tile, World, Layer, WFC, ResourceLoader)

This commit is contained in:
=
2026-04-19 13:03:56 +02:00
parent 3e823cecbd
commit e6522f2db9
16 changed files with 749 additions and 0 deletions
+231
View File
@@ -0,0 +1,231 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using static WFC;
public partial class Layer : Node3D
{
public Tile[,] tiles;
int layerSize;
Tile tile;
int level;
bool updateFailed = false;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
}
public void SetupLayer(int layerSize, int level, Dictionary<string, MeshInstance3D> tileMeshes)
{
this.layerSize = layerSize;
this.level = level;
tiles = new Tile[layerSize, layerSize];
GenerateBaseStructure(tileMeshes);
int safetyCounter = 0;
while (true)
{
if (GenerateLayer())
{
GD.Print("Layer generated");
break;
}
else
{
GD.PrintErr("Layer failed... trying again");
}
ResetLayer(tileMeshes);
safetyCounter++;
if (safetyCounter > 1000) break;
}
}
private void GenerateBaseStructure(Dictionary<string, MeshInstance3D> tileMeshes)
{
Vector3 position;
float offsetX;
float offsetY = level * 4 * -1;
float offsetZ;
for (int x = 0; x < layerSize; x++)
{
offsetX = x * 4;
for (int y = 0; y < layerSize; y++)
{
offsetZ = y * 4;
position = new Vector3(offsetX, offsetY, offsetZ);
tile = new Tile();
tile.SetMeshes(tileMeshes);
tile.Position = position;
tile.GridPosition = new Vector2I(x, y);
tiles[x, y] = tile;
}
}
}
private void ResetLayer(Dictionary<string, MeshInstance3D> tileMeshes)
{
for (int x = 0; x < layerSize; x++)
{
for (int y = 0; y < layerSize; y++)
{
tiles[x, y].Reset(tileMeshes);
}
}
}
private bool GenerateBorder()
{
for (int x = 0; x < layerSize; x++)
{
for (int y = 0; y < layerSize; y++)
{
if (x == 0 || y == 0 || x == layerSize - 1 || y == layerSize - 1)
{
var tile = tiles[x, y];
string result = tile.Collapse("border");
if (result == "ERR")
return false;
NewPropagate(new Vector2I(x, y));
}
}
}
return true;
}
public bool GenerateLayer()
{
bool result = true;
int safetyCounter = 0;
if (!GenerateBorder())
{
return false;
}
Vector2I position = GetSmallestPossibilities();
while (true)
{
string keyword = "";
if (position.X == 0 || position.X == layerSize - 1 || position.Y == 0 || position.Y == layerSize - 1)
{
keyword = tiles[position.X, position.Y].Collapse("border");
}
else
{
keyword = tiles[position.X, position.Y].Collapse("");
}
if (keyword == "ERR")
{
GD.Print("Error in WFC during collapse!");
return false;
}
if (keyword != "")
{
NewPropagate(position);
if (updateFailed) break;
position = GetSmallestPossibilities();
if (position == new Vector2(-100, -100))
{
break;
}
continue;
}
safetyCounter++;
if (safetyCounter == layerSize * layerSize)
{
GD.Print("Error in WFC, overflow!");
return false;
}
}
if (updateFailed) return false;
//Spawn is a tile border, redo world generation
if (tiles[1, 1].collapsedMesh == "border") return false;
//Player has over 80% of tiles available without destroying walls => Results in about 95% success rate
//Not necessarily needed but improves the overall generation percentage at a low resource cost
if (!WFC.IsMapConnected(tiles, 0.8f)) return false;
return result;
}
private void NewPropagate(Vector2I startPos)
{
Queue<Vector2I> queue = new Queue<Vector2I>();
queue.Enqueue(startPos);
while (queue.Count > 0)
{
Vector2I currentPos = queue.Dequeue();
Tile currentTile = tiles[currentPos.X, currentPos.Y];
// Use CURRENT state of tile
var currentPossibilities = currentTile.collapsedMesh != null
? new HashSet<string> { currentTile.collapsedMesh }
: new HashSet<string>(currentTile.tileMeshes.Keys);
for (int i = 0; i < dirs.Length; i++)
{
Vector2I newPos = currentPos + offsets[i];
if (!InBounds(newPos, layerSize, true)) continue;
Tile neighborTile = tiles[newPos.X, newPos.Y];
HashSet<string> allowed = new HashSet<string>();
foreach (string neighborOption in neighborTile.tileMeshes.Keys)
{
foreach (string item in currentPossibilities)
{
if (WFC.CanConnect(item, neighborOption, dirs[i], false))
{
allowed.Add(neighborOption);
break;
}
}
}
int updateCount = neighborTile.Propagate(allowed);
if (updateCount == int.MaxValue)
{
GD.Print("WFC Error! No meshes left");
updateFailed = true;
return;
}
// ONLY enqueue if something changed
if (updateCount > 0)
{
queue.Enqueue(newPos);
}
}
}
}
private Vector2I GetSmallestPossibilities()
{
Vector2I result = new Vector2I(-100, -100);
int lowest = 100;
int current;
for (int x = 0; x < layerSize; x++)
{
for (int y = 0; y < layerSize; y++)
{
if (tiles[x, y].collapsedMesh != null) continue;
current = tiles[x, y].tileMeshes.Count;
if (current < lowest)
{
result = new Vector2I(x, y);
lowest = current;
}
}
}
return result;
}
}