Files
TheriapolisV3/Theriapolis.Core/World/Generation/LandmassMap.cs
T

82 lines
2.7 KiB
C#
Raw Normal View History

namespace Theriapolis.Core.World.Generation;
/// <summary>
/// Computes 8-connected land-component IDs and sizes via flood fill. Settlement
/// placement uses this to confine settlements to the main landmass — roads
/// can't cross ocean, so a settlement on a disconnected island would either be
/// unreachable or get a sea-crossing straight-line connector stub from
/// <c>EnsureSettlementConnectivity</c>. 8-connected matches A*'s movement
/// model so "reachable by road" and "same component" coincide.
/// </summary>
internal static class LandmassMap
{
private const int W = C.WORLD_WIDTH_TILES;
private const int H = C.WORLD_HEIGHT_TILES;
private static readonly (int dx, int dy)[] Neighbors =
{
( 0,-1), ( 1,-1), ( 1, 0), ( 1, 1),
( 0, 1), (-1, 1), (-1, 0), (-1,-1),
};
/// <summary>
/// Returns a pair of arrays: <c>componentId[x,y]</c> gives the 8-connected
/// land component ID at (x,y), or -1 if (x,y) is ocean; <c>componentSize[id]</c>
/// gives the tile count of component <paramref name="id"/>.
/// </summary>
public static (int[,] componentId, int[] componentSize) Compute(WorldState world)
{
var compId = new int[W, H];
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
compId[x, y] = -1;
var sizes = new List<int>();
var queue = new Queue<(int x, int y)>();
for (int y = 0; y < H; y++)
for (int x = 0; x < W; x++)
{
if (compId[x, y] != -1) continue;
if (world.Tiles[x, y].Biome == BiomeId.Ocean) continue;
int id = sizes.Count;
int size = 0;
compId[x, y] = id;
queue.Enqueue((x, y));
while (queue.Count > 0)
{
var (cx, cy) = queue.Dequeue();
size++;
foreach (var (dx, dy) in Neighbors)
{
int nx = cx + dx, ny = cy + dy;
if ((uint)nx >= W || (uint)ny >= H) continue;
if (compId[nx, ny] != -1) continue;
if (world.Tiles[nx, ny].Biome == BiomeId.Ocean) continue;
compId[nx, ny] = id;
queue.Enqueue((nx, ny));
}
}
sizes.Add(size);
}
return (compId, sizes.ToArray());
}
/// <summary>
/// Returns the component ID of the largest land component, or -1 if the
/// world has no land.
/// </summary>
public static int LargestComponentId(int[] componentSizes)
{
int best = -1;
int bestSize = 0;
for (int i = 0; i < componentSizes.Length; i++)
if (componentSizes[i] > bestSize) { bestSize = componentSizes[i]; best = i; }
return best;
}
}