diff --git a/core/src/NetherGen.cpp b/core/src/NetherGen.cpp new file mode 100644 index 0000000..498ee3d --- /dev/null +++ b/core/src/NetherGen.cpp @@ -0,0 +1,159 @@ +#include "vf/NetherGen.h" + +#include +#include + +namespace vf { + +namespace { + +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)); +} + +bool replaceable(Block block) { + return block == Block::Air || block == Block::Lava; +} + +} // namespace + +NetherGenerator::NetherGenerator() : NetherGenerator(Params{}) {} + +NetherGenerator::NetherGenerator(const Params& netherParams) + : params(netherParams), + floorNoise(netherParams.seed ^ 0x4e455448u), + ceilingNoise(netherParams.seed ^ 0x524f4f46u), + caveNoise(netherParams.seed ^ 0x43415645u), + blobNoise(netherParams.seed ^ 0x424c4f42u), + oreNoise(netherParams.seed ^ 0x5155415au), + biomeNoise(netherParams.seed ^ 0x42494f4du) {} + +NetherBiome NetherGenerator::biomeAt(int gx, int gz) const { + float f = params.biomeFrequency; + float heat = biomeNoise.simplex2D(gx * f, gz * f); + float damp = biomeNoise.simplex2D(gx * f * 1.7f + 91.3f, gz * f * 1.7f - 44.7f); + + if (damp < -0.30f) return NetherBiome::SoulSandValley; + if (heat > 0.34f) return NetherBiome::BasaltDeltas; + if (damp > 0.26f) return NetherBiome::CrimsonForest; + return NetherBiome::NetherWastes; +} + +NetherColumn NetherGenerator::columnAt(int gx, int gz) const { + NetherColumn column; + column.biome = biomeAt(gx, gz); + column.lavaLevel = params.lavaLevel; + + float ff = params.floorFrequency; + float broad = floorNoise.fbm2D(gx * ff, gz * ff, 4, 2.0f, 0.5f); + float rough = floorNoise.noise2D(gx * ff * 3.1f + 17.0f, gz * ff * 3.1f - 9.0f); + + int floorCentre = params.lavaLevel + 3; + float amplitude = 12.0f; + switch (column.biome) { + case NetherBiome::SoulSandValley: + floorCentre -= 8; + amplitude = 15.0f; + break; + case NetherBiome::BasaltDeltas: + floorCentre -= 3; + amplitude = 9.0f; + break; + case NetherBiome::CrimsonForest: + floorCentre += 4; + amplitude = 8.0f; + break; + default: + break; + } + + float shaped = broad * 0.78f + rough * 0.22f; + column.floorHeight = floorCentre + static_cast(std::lround(shaped * amplitude)); + + float cf = params.ceilingFrequency; + float roof = ceilingNoise.fbm2D(gx * cf, gz * cf, 3, 2.0f, 0.5f); + column.ceilingHeight = params.ceilingY - params.roofThickness + + static_cast(std::lround(roof * 7.0f)); + + int lowest = params.floorY + params.bedrockThickness + 1; + int highest = params.ceilingY - params.bedrockThickness - 2; + if (column.floorHeight < lowest) column.floorHeight = lowest; + if (column.floorHeight > highest - 12) column.floorHeight = highest - 12; + if (column.ceilingHeight > highest) column.ceilingHeight = highest; + if (column.ceilingHeight < column.floorHeight + 10) column.ceilingHeight = column.floorHeight + 10; + + column.drowned = column.floorHeight < params.lavaLevel; + return column; +} + +bool NetherGenerator::isCarved(int gx, int gy, int gz) const { + float f = params.caveFrequency; + float first = 1.0f - std::fabs(caveNoise.noise3D(gx * f, gy * f * 1.9f, gz * f)); + float second = 1.0f - std::fabs(caveNoise.noise3D(gx * f + 41.3f, gy * f * 1.9f - 17.9f, + gz * f + 23.1f)); + return first > params.caveThreshold && second > params.caveThreshold; +} + +bool NetherGenerator::isBlob(int gx, int gy, int gz) const { + float broad = blobNoise.noise3D(gx * 0.026f, gy * 0.019f, gz * 0.026f); + float detail = blobNoise.noise3D(gx * 0.056f + 7.3f, gy * 0.048f - 3.1f, gz * 0.056f + 2.7f); + return broad + detail * 0.42f > params.blobThreshold; +} + +bool NetherGenerator::isBedrock(int gx, int gy, int gz) const { + if (gy == params.floorY || gy == params.ceilingY - 1) return true; + + int fromFloor = gy - params.floorY; + int fromCeiling = params.ceilingY - 1 - gy; + int band = -1; + if (fromFloor > 0 && fromFloor < params.bedrockThickness) band = fromFloor; + else if (fromCeiling > 0 && fromCeiling < params.bedrockThickness) band = fromCeiling; + if (band < 0) return false; + + uint32_t roll = mixCoord(params.seed ^ 0x42454452u, gx, gy, gz); + return static_cast(roll % static_cast(params.bedrockThickness)) >= band; +} + +Block NetherGenerator::topFor(NetherBiome biome) const { + switch (biome) { + case NetherBiome::SoulSandValley: return Block::SoulSand; + case NetherBiome::BasaltDeltas: return Block::Basalt; + case NetherBiome::CrimsonForest: return Block::Moss; + default: return Block::Netherrack; + } +} + +Block NetherGenerator::fillerFor(NetherBiome biome) const { + switch (biome) { + case NetherBiome::SoulSandValley: return Block::SoulSoil; + case NetherBiome::BasaltDeltas: return Block::Blackstone; + default: return Block::Netherrack; + } +} + +Block NetherGenerator::blockAt(const NetherColumn& column, int gx, int gy, int gz) const { + if (gy < params.floorY || gy >= params.ceilingY) return Block::Air; + if (isBedrock(gx, gy, gz)) return Block::Bedrock; + + bool inMass = gy <= column.floorHeight || gy >= column.ceilingHeight; + bool solid = inMass || isBlob(gx, gy, gz); + if (solid && isCarved(gx, gy, gz)) solid = false; + if (!solid) return gy <= params.lavaLevel ? Block::Lava : Block::Air; + + Block material = Block::Netherrack; + if (gy <= column.floorHeight) { + int depth = column.floorHeight - gy; + if (depth == 0) material = topFor(column.biome); + else if (depth <= 3) material = fillerFor(column.biome); + } +