using Repoverse.Core.Generation;
using Repoverse.Core.Model;
using static Repoverse.Core.WorldConstants;
namespace Repoverse.Core.Voxels;
///
/// Procedural base state for any chunk, derived only from the manifest (§11): terrain,
/// district road grid and buildings. Player edits are layered on top by .
///
public sealed class WorldGenerator
{
private readonly Dictionary<(int, int), BuildingDefinition> _byPlotCell = new();
public WorldGenerator(WorldManifest manifest)
{
Manifest = manifest;
foreach (var b in manifest.Buildings)
_byPlotCell[(Math.DivRem(b.Footprint.X, PlotSize).Quotient, Math.DivRem(b.Footprint.Z, PlotSize).Quotient)] = b;
}
public WorldManifest Manifest { get; }
/// Top chunk layer that can contain anything.
public static int MaxChunkY => ChunkCoord.FloorDiv(GroundLevel + MaxFloors * FloorHeight + 1);
public Chunk Generate(ChunkCoord c, WorldEdits? edits = null, CancellationToken ct = default)
{
var chunk = new Chunk(c);
if (c.Y < 0 || c.Y > MaxChunkY) return chunk;
for (int z = 0; z < ChunkSize; z++)
for (int x = 0; x < ChunkSize; x++)
{
int wx = c.OriginX + x, wz = c.OriginZ + z;
var surface = Surface(wx, wz);
for (int y = 0; y < ChunkSize; y++)
{
int wy = c.OriginY + y;
if (wy >= GroundLevel) break;
chunk[x, y, z] = wy == 0 ? new Voxel(BlockType.Bedrock, MaterialFamily.Stone)
: wy < GroundLevel - 1 ? new Voxel(BlockType.Soil, MaterialFamily.Stone)
: surface;
}
}
ct.ThrowIfCancellationRequested();
foreach (var b in BuildingsTouching(c))
{
BuildingRasterizer.Rasterize(b, (x, y, z, v) =>
{
int lx = x - c.OriginX, ly = y - c.OriginY, lz = z - c.OriginZ;
if ((uint)lx < ChunkSize && (uint)ly < ChunkSize && (uint)lz < ChunkSize)
chunk[lx, ly, lz] = v;
});
ct.ThrowIfCancellationRequested();
}
edits?.ApplyTo(chunk);
return chunk;
}
public IEnumerable BuildingsTouching(ChunkCoord c)
{
int x0 = Math.DivRem(c.OriginX - PlotSize, PlotSize).Quotient, x1 = Math.DivRem(c.OriginX + ChunkSize + PlotSize, PlotSize).Quotient;
int z0 = Math.DivRem(c.OriginZ - PlotSize, PlotSize).Quotient, z1 = Math.DivRem(c.OriginZ + ChunkSize + PlotSize, PlotSize).Quotient;
for (int px = x0; px <= x1; px++)
for (int pz = z0; pz <= z1; pz++)
if (_byPlotCell.TryGetValue((px, pz), out var b) && Overlaps(b, c))
yield return b;
}
private static bool Overlaps(BuildingDefinition b, ChunkCoord c)
{
// Rasterized extent: footprint plus one voxel of foundation/scaffold, ground-1 to roof.
int minY = GroundLevel - 1, maxY = GroundLevel + b.Floors * FloorHeight;
return b.Footprint.X - 1 < c.OriginX + ChunkSize && b.Footprint.MaxX + 1 >= c.OriginX
&& b.Footprint.Z - 1 < c.OriginZ + ChunkSize && b.Footprint.MaxZ + 1 >= c.OriginZ
&& minY < c.OriginY + ChunkSize && maxY >= c.OriginY;
}
/// Ground block: roads ring every plot inside a district, grass elsewhere.
public static Voxel Surface(int wx, int wz)
{
const int stride = DistrictSpan + 2 * PlotSize;
int dx = Mod(wx, stride), dz = Mod(wz, stride);
if (dx >= DistrictSpan || dz >= DistrictSpan)
{
// Avenue between districts with a central road.
int ax = dx - DistrictSpan, az = dz - DistrictSpan;
bool onRoad = (dx >= DistrictSpan && Math.Abs(ax - PlotSize) < RoadWidth) || (dz >= DistrictSpan && Math.Abs(az - PlotSize) < RoadWidth);
return onRoad ? new Voxel(BlockType.Road, MaterialFamily.Concrete) : new Voxel(BlockType.Grass, MaterialFamily.Wood);
}
int px = dx % PlotSize, pz = dz % PlotSize;
int half = RoadWidth / 2;
bool road = px < half || px >= PlotSize - half || pz < half || pz >= PlotSize - half;
bool walk = !road && (px < RoadWidth || px >= PlotSize - RoadWidth || pz < RoadWidth || pz >= PlotSize - RoadWidth);
return road ? new Voxel(BlockType.Road, MaterialFamily.Concrete)
: walk ? new Voxel(BlockType.Sidewalk, MaterialFamily.Stone)
: new Voxel(BlockType.Grass, MaterialFamily.Wood);
}
private static int Mod(int a, int m) => ((a % m) + m) % m;
}
/// Persistent user modifications, stored separately from procedural state (§11, §22).
public sealed class WorldEdits
{
private readonly Dictionary> _edits = new();
private readonly object _gate = new();
public void Set(int x, int y, int z, Voxel v)
{
var c = ChunkCoord.FromVoxel(x, y, z);
lock (_gate)
{
if (!_edits.TryGetValue(c, out var map)) _edits[c] = map = new();
map[Chunk.Index(x - c.OriginX, y - c.OriginY, z - c.OriginZ)] = v.Packed;
}
}
public void ApplyTo(Chunk chunk)
{
lock (_gate)
{
if (!_edits.TryGetValue(chunk.Coord, out var map)) return;
foreach (var (i, packed) in map)
{
int x = i % ChunkSize, z = i / ChunkSize % ChunkSize, y = i / (ChunkSize * ChunkSize);
chunk[x, y, z] = new Voxel(packed);
}
}
}
public int Count { get { lock (_gate) return _edits.Values.Sum(m => m.Count); } }
public IReadOnlyDictionary> Snapshot()
{
lock (_gate)
return _edits.ToDictionary(kv => kv.Key, kv => (IReadOnlyDictionary)new Dictionary(kv.Value));
}
public void Load(IReadOnlyDictionary> data)
{
lock (_gate)
{
_edits.Clear();
foreach (var (c, map) in data) _edits[c] = new Dictionary(map);
}
}
}