diff --git a/core/src/StructureGen.cpp b/core/src/StructureGen.cpp new file mode 100644 index 0000000..bfccbd8 --- /dev/null +++ b/core/src/StructureGen.cpp @@ -0,0 +1,284 @@ +#include "vf/StructureGen.h" + +#include + +namespace vf { + +namespace { + +struct SplitMix32 { + uint32_t state; + + uint32_t next() { + state += 0x9E3779B9u; + uint32_t z = state; + z = (z ^ (z >> 16)) * 0x85EBCA6Bu; + z = (z ^ (z >> 13)) * 0xC2B2AE35u; + z = z ^ (z >> 16); + return z; + } + + int nextRange(int lo, int hiInclusive) { + uint32_t span = static_cast(hiInclusive - lo + 1); + return lo + static_cast(next() % span); + } +}; + +const Vec3i cardinalDirections[4] = {{1, 0, 0}, {-1, 0, 0}, {0, 0, 1}, {0, 0, -1}}; + +} // namespace + +uint32_t chunkStructureHash(uint32_t seed, int chunkX, int chunkZ, uint32_t salt) { + uint64_t h = 1469598103934665603ull; + auto mix = [&h](uint32_t v) { h ^= v; h *= 1099511628211ull; }; + mix(seed); + mix(static_cast(chunkX)); + mix(static_cast(chunkZ)); + mix(salt); + return static_cast(h ^ (h >> 32)); +} + +StructureGen::StructureGen(const StructureGenParams& structureParams) : params(structureParams) {} + +bool StructureGen::structureForChunk(int chunkX, int chunkZ, StructureGenKind& outKind) const { + if (chunkStructureHash(params.seed, chunkX, chunkZ, 0x11u) % static_cast(params.villageChance) == 0) { + outKind = StructureGenKind::Village; + return true; + } + if (chunkStructureHash(params.seed, chunkX, chunkZ, 0x22u) % static_cast(params.templeChance) == 0) { + outKind = StructureGenKind::DesertTemple; + return true; + } + if (chunkStructureHash(params.seed, chunkX, chunkZ, 0x33u) % static_cast(params.dungeonChance) == 0) { + outKind = StructureGenKind::Dungeon; + return true; + } + if (chunkStructureHash(params.seed, chunkX, chunkZ, 0x44u) % static_cast(params.mineshaftChance) == 0) { + outKind = StructureGenKind::Mineshaft; + return true; + } + if (chunkStructureHash(params.seed, chunkX, chunkZ, 0x55u) % static_cast(params.ravineChance) == 0) { + outKind = StructureGenKind::Ravine; + return true; + } + return false; +} + +BoundingBoxI StructureGen::computeBounds(const Vec3i& origin, const std::vector& pieces) const { + Vec3i first = origin + pieces.front().offset; + BoundingBoxI box{first, first}; + for (const StructurePiece& piece : pieces) { + Vec3i world = origin + piece.offset; + box.min.x = std::min(box.min.x, world.x); + box.min.y = std::min(box.min.y, world.y); + box.min.z = std::min(box.min.z, world.z); + box.max.x = std::max(box.max.x, world.x); + box.max.y = std::max(box.max.y, world.y); + box.max.z = std::max(box.max.z, world.z); + } + return box; +} + +StructurePlan StructureGen::buildVillage(int chunkX, int chunkZ, int groundY, uint32_t rngState) const { + SplitMix32 rng{rngState}; + StructurePlan plan; + plan.kind = StructureGenKind::Village; + plan.origin = {chunkX * params.chunkSize, groundY, chunkZ * params.chunkSize}; + + int houseCount = rng.nextRange(2, 4); + int spacing = 7; + + for (int house = 0; house < houseCount; ++house) { + int hx = (house % 2) * spacing; + int hz = (house / 2) * spacing; + int width = 5; + int depth = 5; + int height = 4; + + for (int x = 0; x < width; ++x) + for (int z = 0; z < depth; ++z) + plan.pieces.push_back({{hx + x, 0, hz + z}, Block::Planks}); + + for (int y = 1; y <= height; ++y) { + for (int x = 0; x < width; ++x) { + plan.pieces.push_back({{hx + x, y, hz}, Block::Cobblestone}); + plan.pieces.push_back({{hx + x, y, hz + depth - 1}, Block::Cobblestone}); + } + for (int z = 0; z < depth; ++z) { + plan.pieces.push_back({{hx, y, hz + z}, Block::Cobblestone}); + plan.pieces.push_back({{hx + width - 1, y, hz + z}, Block::Cobblestone}); + } + } + + plan.pieces.push_back({{hx + width / 2, 1, hz}, Block::Air}); + plan.pieces.push_back({{hx + width / 2, 2, hz}, Block::Air}); + + for (int x = 0; x < width; ++x) + for (int z = 0; z < depth; ++z) + plan.pieces.push_back({{hx + x, height + 1, hz + z}, Block::Wood}); + + plan.pieces.push_back({{hx + width / 2, height, hz + depth / 2}, Block::Glowstone}); + } + + int pathLength = spacing * ((houseCount + 1) / 2) + spacing; + for (int x = 0; x < pathLength; ++x) + plan.pieces.push_back({{x, 0, -1}, Block::Gravel}); + + plan.bounds = computeBounds(plan.origin, plan.pieces); + return plan; +} + +StructurePlan StructureGen::buildDungeon(int chunkX, int chunkZ, int groundY, uint32_t rngState) const { + SplitMix32 rng{rngState}; + StructurePlan plan; + plan.kind = StructureGenKind::Dungeon; + + int width = rng.nextRange(5, 8); + int depth = rng.nextRange(5, 8); + int height = 4; + int originY = groundY - height - 3; + plan.origin = {chunkX * params.chunkSize, originY, chunkZ * params.chunkSize}; + + for (int y = 0; y <= height; ++y) { + for (int x = 0; x < width; ++x) { + for (int z = 0; z < depth; ++z) { + bool shell = (x == 0 || x == width - 1 || z == 0 || z == depth - 1 || y == 0 || y == height); + Block block = shell ? Block::Cobblestone : Block::Air; + plan.pieces.push_back({{x, y, z}, block}); + } + } + } + + plan.pieces.push_back({{width / 2, 1, depth / 2}, Block::Glowstone}); // mob spawner marker + plan.pieces.push_back({{1, 1, 1}, Block::Chest}); + plan.pieces.push_back({{width - 2, 1, depth - 2}, Block::Chest}); + + plan.bounds = computeBounds(plan.origin, plan.pieces); + return plan; +} + +StructurePlan StructureGen::buildMineshaft(int chunkX, int chunkZ, int groundY, uint32_t rngState) const { + SplitMix32 rng{rngState}; + StructurePlan plan; + plan.kind = StructureGenKind::Mineshaft; + plan.origin = {chunkX * params.chunkSize, groundY - 20, chunkZ * params.chunkSize}; + + int segmentCount = rng.nextRange(4, 7); + int x = 0; + int z = 0; + + for (int segment = 0; segment < segmentCount; ++segment) { + Vec3i dir = cardinalDirections[static_cast(rng.nextRange(0, 3))]; + int length = rng.nextRange(5, 10); + + for (int step = 0; step < length; ++step) { + for (int y = 0; y < 3; ++y) { + for (int w = -1; w <= 1; ++w) { + int px = x + dir.z * w; + int pz = z + dir.x * w; + Block block = (y == 0) ? Block::Planks : Block::Air; + plan.pieces.push_back({{px, y, pz}, block}); + } + } + if (step % 4 == 0) { + plan.pieces.push_back({{x, 0, z}, Block::Wood}); + plan.pieces.push_back({{x, 1, z}, Block::Wood}); + plan.pieces.push_back({{x, 2, z}, Block::Wood}); + } + x += dir.x; + z += dir.z; + } + } + + plan.pieces.push_back({{0, 1, 0}, Block::Chest}); + plan.bounds = computeBounds(plan.origin, plan.pieces); + return plan; +} + +StructurePlan StructureGen::buildRavine(int chunkX, int chunkZ, int groundY, uint32_t rngState) const { + (void)groundY; + SplitMix32 rng{rngState}; + StructurePlan plan; + plan.kind = StructureGenKind::Ravine; + plan.origin = {chunkX * params.chunkSize, 5, chunkZ * params.chunkSize}; + + int length = rng.nextRange(20, 39); + float x = 0.0f; + float z = static_cast(params.chunkSize) * 0.5f; + float dx = 1.0f; + float dz = 0.0f; + + for (int step = 0; step < length; ++step) { + int width = rng.nextRange(2, 4); + int depth = rng.nextRange(15, 34); + int cx = static_cast(x); + int cz = static_cast(z); + + for (int y = 0; y < depth; ++y) + for (int w = -width; w <= width; ++w) + plan.pieces.push_back({{cx + w, y, cz}, Block::Air}); + + float turn = (static_cast(rng.nextRange(-100, 100))) / 800.0f; + dz += turn; + x += dx; + z += dz; + } + + plan.bounds = computeBounds(plan.origin, plan.pieces); + return plan; +} + +StructurePlan StructureGen::buildTemple(int chunkX, int chunkZ, int groundY, uint32_t rngState) const { + (void)rngState; + StructurePlan plan; + plan.kind = StructureGenKind::DesertTemple; + plan.origin = {chunkX * params.chunkSize, groundY, chunkZ * params.chunkSize}; + + int base = 11; + int height = 6; + for (int y = 0; y < height; ++y) { + int inset = y; + int size = base - inset * 2; + if (size < 1) break; + for (int x = 0; x < size; ++x) { + for (int z = 0; z < size; ++z) { + bool shell = (x == 0 || x == size - 1 || z == 0 || z == size - 1); + if (shell || y == 0) + plan.pieces.push_back({{inset + x, y, inset + z}, Block::Sandstone}); + } + } + } + + int chamberY = -4; + for (int x = 3; x < 8; ++x) + for (int z = 3; z < 8; ++z) + plan.pieces.push_back({{x, chamberY, z}, Block::Air}); + plan.pieces.push_back({{5, chamberY, 5}, Block::Chest}); + + plan.bounds = computeBounds(plan.origin, plan.pieces); + return plan; +} + +StructurePlan StructureGen::buildStructure(StructureGenKind kind, int chunkX, int chunkZ, int groundY) const { + uint32_t rngState = chunkStructureHash(params.seed, chunkX, chunkZ, 0x9E3779B9u ^ static_cast(kind)); + + switch (kind) { + case StructureGenKind::Village: return buildVillage(chunkX, chunkZ, groundY, rngState); + case StructureGenKind::Dungeon: return buildDungeon(chunkX, chunkZ, groundY, rngState); + case StructureGenKind::Mineshaft: return buildMineshaft(chunkX, chunkZ, groundY, rngState); + case StructureGenKind::Ravine: return buildRavine(chunkX, chunkZ, groundY, rngState); + case StructureGenKind::DesertTemple: return buildTemple(chunkX, chunkZ, groundY, rngState); + case StructureGenKind::Count: break; + } + return buildVillage(chunkX, chunkZ, groundY, rngState); +} + +void StructureGen::stamp(World& world, const StructurePlan& plan, int maxY) const { + for (const StructurePiece& piece : plan.pieces) { + Vec3i worldPos = plan.origin + piece.offset; + if (worldPos.y < 0 || worldPos.y >= maxY) continue; + world.setBlock(worldPos.x, worldPos.y, worldPos.z, piece.block); + } +} + +} // namespace vf