#include "vf/EndGen.h" #include #include namespace vf { namespace { constexpr int unboundedSpan = 1 << 28; uint32_t mixCoord(uint32_t seed, int gx, int gy, int gz) { uint64_t h = 1469598103934665603ull; auto mix = [&h](uint32_t v) { h ^= v; h *= 1099511628211ull; }; mix(seed); mix(static_cast(gx)); mix(static_cast(gy)); mix(static_cast(gz)); h ^= h >> 33; return static_cast(h ^ (h >> 32)); } uint32_t nextState(uint32_t& state) { state += 0x9E3779B9u; uint32_t z = state; z = (z ^ (z >> 16)) * 0x85EBCA6Bu; z = (z ^ (z >> 13)) * 0xC2B2AE35u; return z ^ (z >> 16); } const Vec3i horizontalSteps[4] = {{1, 0, 0}, {-1, 0, 0}, {0, 0, 1}, {0, 0, -1}}; float horizontalDistance(int gx, int gz, const Vec3i& center) { float dx = static_cast(gx - center.x); float dz = static_cast(gz - center.z); return std::sqrt(dx * dx + dz * dz); } } // namespace EndGenerator::EndGenerator() : EndGenerator(Params{}) {} EndGenerator::EndGenerator(const Params& endParams) : params(endParams), edgeNoise(endParams.seed ^ 0x45444745u), surfaceNoise(endParams.seed ^ 0x53555246u), outerNoise(endParams.seed ^ 0x4f555445u) {} EndIsland EndGenerator::centralIsland() const { EndIsland island; island.center = {0, params.islandY, 0}; island.radius = params.centralRadius; island.thickness = params.centralThickness; island.central = true; return island; } EndIsland EndGenerator::outerIslandFor(int cellX, int cellZ) const { EndIsland island; uint32_t roll = mixCoord(params.seed ^ 0x49534c44u, cellX, 0, cellZ); if (roll % static_cast(params.outerChance) != 0u) return island; float presence = outerNoise.simplex2D(cellX * 0.31f, cellZ * 0.31f); if (presence < -0.55f) return island; int half = params.outerCellSize / 2; int jitter = half - 60; int offsetX = static_cast((roll >> 5) % static_cast(jitter * 2 + 1)) - jitter; int offsetZ = static_cast((roll >> 15) % static_cast(jitter * 2 + 1)) - jitter; island.center = {cellX * params.outerCellSize + half + offsetX, params.islandY + static_cast((roll >> 9) % 17u) - 8, cellZ * params.outerCellSize + half + offsetZ}; island.radius = 18.0f + static_cast((roll >> 21) % 21u); island.thickness = 7.0f + static_cast((roll >> 25) % 11u); island.central = false; return island; } EndIsland EndGenerator::islandAt(int gx, int gz) const { float fromOrigin = std::sqrt(static_cast(gx) * static_cast(gx) + static_cast(gz) * static_cast(gz)); if (fromOrigin < static_cast(params.outerDistance)) { EndIsland central = centralIsland(); if (fromOrigin <= central.radius + params.edgeAmplitude) return central; return EndIsland{}; } int cellX = floorDiv(gx, params.outerCellSize); int cellZ = floorDiv(gz, params.outerCellSize); EndIsland best; float bestDistance = 0.0f; for (int dx = -1; dx <= 1; ++dx) { for (int dz = -1; dz <= 1; ++dz) { EndIsland candidate = outerIslandFor(cellX + dx, cellZ + dz); if (!candidate.valid()) continue; float distance = horizontalDistance(gx, gz, candidate.center); if (distance > candidate.radius + params.edgeAmplitude) continue; if (!best.valid() || distance < bestDistance) { best = candidate; bestDistance = distance; } } } return best; } bool EndGenerator::columnExtent(int gx, int gz, int& topY, int& bottomY) const { EndIsland island = islandAt(gx, gz); if (!island.valid()) return false; float distance = horizontalDistance(gx, gz, island.center); float wobble = edgeNoise.fbm2D(gx * params.edgeFrequency, gz * params.edgeFrequency, 3, 2.0f, 0.5f); float reach = island.radius + wobble * params.edgeAmplitude; if (reach < 4.0f || distance > reach) return false; float falloff = 1.0f - distance / reach; float lens = std::sqrt(falloff); float bump = surfaceNoise.fbm2D(gx * 0.049f, gz * 0.049f, 3, 2.0f, 0.5f); topY = island.center.y + static_cast(std::lround(bump * 3.0f * lens)); bottomY = island.center.y - 1 - static_cast(std::lround(island.thickness * lens)); if (bottomY < params.floorY) bottomY = params.floorY; if (topY >= params.ceilingY) topY = params.ceilingY - 1; return topY >= bottomY; } bool EndGenerator::isVoid(int gx, int gz) const { int topY = 0, bottomY = 0; return !columnExtent(gx, gz, topY, bottomY); } int EndGenerator::surfaceHeight(int gx, int gz) const { int topY = 0, bottomY = 0; if (!columnExtent(gx, gz, topY, bottomY)) return params.floorY - 1; return topY; } bool EndGenerator::isStone(int gx, int gy, int gz) const { int topY = 0, bottomY = 0; if (!columnExtent(gx, gz, topY, bottomY)) return false; return gy >= bottomY && gy <= topY; } EndBiome EndGenerator::biomeAt(int gx, int gz) const { int topY = 0, bottomY = 0; if (!columnExtent(gx, gz, topY, bottomY)) return EndBiome::Void; return islandAt(gx, gz).central ? EndBiome::CentralIsland : EndBiome::OuterIsland; } std::vector EndGenerator::pillars() const { std::vector ring; ring.reserve(static_cast(params.pillarCount)); for (int index = 0; index < params.pillarCount; ++index) { float angle = 6.2831853f * static_cast(index) / static_cast(params.pillarCount); uint32_t roll = mixCoord(params.seed ^ 0x50494c4cu, index, 0, params.pillarRing); int spread = params.pillarRing + static_cast(roll % 9u) - 4; int gx = static_cast(std::lround(std::cos(angle) * static_cast(spread))); int gz = static_cast(std::lround(std::sin(angle) * static_cast(spread))); ObsidianPillar pillar; pillar.base = {gx, surfaceHeight(gx, gz), gz}; pillar.radius = 1 + static_cast((roll >> 8) % 3u); pillar.height = 22 + static_cast((roll >> 12) % 19u); pillar.crowned = pillar.height >= 34; int top = pillar.base.y + pillar.height; if (top >= params.ceilingY - 2) pillar.height = params.ceilingY - 3 - pillar.base.y; ring.push_back(pillar); } return ring; } void EndGenerator::generateChunk(Chunk& chunk, const Vec3i& chunkCoord) const { int ox = chunkCoord.x * chunkSize; int oy = chunkCoord.y * chunkSize; int oz = chunkCoord.z * chunkSize; for (int lx = 0; lx < chunkSize; ++lx) { for (int lz = 0; lz < chunkSize; ++lz) { int topY = 0, bottomY = 0; bool solidColumn = columnExtent(ox + lx, oz + lz, topY, bottomY); for (int ly = 0; ly < chunkSize; ++ly) { int gy = oy + ly; bool stone = solidColumn && gy >= bottomY && gy <= topY; chunk.set(lx, ly, lz, stone ? Block::EndStone : Block::Air); } } } } void EndGenerator::stampPillars(World& world, int minX, int minZ, int maxX, int maxZ) const { for (const ObsidianPillar& pillar : pillars()) { if (pillar.height <= 0) continue; int reach = pillar.radius; for (int dx = -reach; dx <= reach; ++dx) { for (int dz = -reach; dz <= reach; ++dz) { if (dx * dx + dz * dz > reach * reach) continue; int gx = pillar.base.x + dx; int gz = pillar.base.z + dz; if (gx < minX || gx > maxX || gz < minZ || gz > maxZ) continue; int bottom = pillar.base.y - 3; if (bottom < params.floorY) bottom = params.floorY; for (int y = bottom; y <= pillar.base.y + pillar.height; ++y) world.setBlock(gx, y, gz, Block::Obsidian); } } if (!pillar.crowned) continue; int crownX = pillar.base.x; int crownZ = pillar.base.z; if (crownX < minX || crownX > maxX || crownZ < minZ || crownZ > maxZ) continue; world.setBlock(crownX, pillar.base.y + pillar.height + 1, crownZ, Block::CryingObsidian); } } void EndGenerator::placePillars(World& world) const { stampPillars(world, -unboundedSpan, -unboundedSpan, unboundedSpan, unboundedSpan); } void EndGenerator::stampExitPortal(World& world, int minX, int minZ, int maxX, int maxZ) const { int surface = surfaceHeight(0, 0); if (surface < params.floorY) return; for (int dx = -2; dx <= 2; ++dx) { for (int dz = -2; dz <= 2; ++dz) { if (dx < minX || dx > maxX || dz < minZ || dz > maxZ) continue; world.setBlock(dx, surface - 1, dz, Block::Bedrock); bool inner = std::abs(dx) <= 1 && std::abs(dz) <= 1; world.setBlock(dx, surface, dz, inner ? Block::EndPortal : Block::Bedrock); for (int up = 1; up <= 3; ++up) world.setBlock(dx, surface + up, dz, Block::Air); } } const int cornerX[4] = {-2, -2, 2, 2}; const int cornerZ[4] = {-2, 2, -2, 2}; for (int corner = 0; corner < 4; ++corner) { int gx = cornerX[corner]; int gz = cornerZ[corner]; if (gx < minX || gx > maxX || gz < minZ || gz > maxZ) continue; for (int up = 1; up <= 3; ++up) world.setBlock(gx, surface + up, gz, Block::Bedrock); } } void EndGenerator::placeExitPortal(World& world) const { stampExitPortal(world, -unboundedSpan, -unboundedSpan, unboundedSpan, unboundedSpan); } int EndGenerator::growBranch(World& world, Vec3i pos, int depth, int maxDepth, uint32_t& rngState) const { if (pos.y >= params.ceilingY - 2 || depth >= maxDepth) { if (world.blockAt(pos.x, pos.y, pos.z) != Block::Air) return 0; world.setBlock(pos.x, pos.y, pos.z, Block::ChorusFlower); return 1; } int written = 0; int segments = 1 + static_cast(nextState(rngState) % 3u); for (int step = 0; step < segments && pos.y < params.ceilingY - 2; ++step) { if (world.blockAt(pos.x, pos.y, pos.z) != Block::Air) break; world.setBlock(pos.x, pos.y, pos.z, Block::ChorusPlant); ++written; pos.y += 1; } int branches = static_cast(nextState(rngState) % 3u); for (int branch = 0; branch < branches; ++branch) { const Vec3i& step = horizontalSteps[nextState(rngState) % 4u]; Vec3i side{pos.x + step.x, pos.y - 1, pos.z + step.z}; if (world.blockAt(side.x, side.y, side.z) != Block::Air) continue; world.setBlock(side.x, side.y, side.z, Block::ChorusPlant); ++written; written += growBranch(world, {side.x, side.y + 1, side.z}, depth + 1, maxDepth, rngState); } written += growBranch(world, pos, depth + 1, maxDepth, rngState); return written; } int EndGenerator::growChorus(World& world, const Vec3i& base, uint32_t roll, int maxDepth) const { if (world.blockAt(base.x, base.y - 1, base.z) != Block::EndStone) return 0; if (world.blockAt(base.x, base.y, base.z) != Block::Air) return 0; uint32_t state = mixCoord(params.seed ^ 0x43484f52u, base.x, base.y, base.z) ^ roll; return growBranch(world, base, 0, maxDepth, state); } void EndGenerator::raisePurpurSpire(World& world, int gx, int gz, int top, uint32_t roll) const { int tall = 2 + static_cast((roll >> 18) % 4u); for (int step = 0; step < tall; ++step) { int y = top + 1 + step; if (y >= params.ceilingY - 1) return; if (world.blockAt(gx, y, gz) != Block::Air) return; world.setBlock(gx, y, gz, Block::Purpur); } } void EndGenerator::decorateColumn(World& world, int columnX, int columnZ) const { int ox = columnX * chunkSize; int oz = columnZ * chunkSize; for (int lx = 4; lx < chunkSize - 4; ++lx) { for (int lz = 4; lz < chunkSize - 4; ++lz) { int gx = ox + lx; int gz = oz + lz; if (biomeAt(gx, gz) != EndBiome::OuterIsland) continue; int top = surfaceHeight(gx, gz); if (top < params.floorY) continue; uint32_t roll = mixCoord(params.seed ^ 0x44454330u, gx, 0, gz); if (params.chorus && roll % 19u == 0u) growChorus(world, {gx, top + 1, gz}, roll, 3); else if (roll % 53u == 7u) raisePurpurSpire(world, gx, gz, top, roll); } } } void EndGenerator::generateColumn(World& world, int columnX, int columnZ) const { for (int cy = 0; cy < stackChunkCount(); ++cy) { Chunk& chunk = world.ensureChunk({columnX, cy, columnZ}); generateChunk(chunk, {columnX, cy, columnZ}); } int minX = columnX * chunkSize; int minZ = columnZ * chunkSize; int maxX = minX + chunkSize - 1; int maxZ = minZ + chunkSize - 1; stampPillars(world, minX, minZ, maxX, maxZ); if (params.exitPortal) stampExitPortal(world, minX, minZ, maxX, maxZ); decorateColumn(world, columnX, columnZ); } } // namespace vf