File size: 6,319 Bytes
3ce1a40 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | using Repoverse.Core.Generation;
using Repoverse.Core.Model;
using static Repoverse.Core.WorldConstants;
namespace Repoverse.Core.Voxels;
/// <summary>
/// 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 <see cref="WorldEdits"/>.
/// </summary>
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; }
/// <summary>Top chunk layer that can contain anything.</summary>
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<BuildingDefinition> 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;
}
/// <summary>Ground block: roads ring every plot inside a district, grass elsewhere.</summary>
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;
}
/// <summary>Persistent user modifications, stored separately from procedural state (§11, §22).</summary>
public sealed class WorldEdits
{
private readonly Dictionary<ChunkCoord, Dictionary<int, ushort>> _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<ChunkCoord, IReadOnlyDictionary<int, ushort>> Snapshot()
{
lock (_gate)
return _edits.ToDictionary(kv => kv.Key, kv => (IReadOnlyDictionary<int, ushort>)new Dictionary<int, ushort>(kv.Value));
}
public void Load(IReadOnlyDictionary<ChunkCoord, IReadOnlyDictionary<int, ushort>> data)
{
lock (_gate)
{
_edits.Clear();
foreach (var (c, map) in data) _edits[c] = new Dictionary<int, ushort>(map);
}
}
}
|