#include #include "MapGen.h" #include "util/Types.h" #include "world/World.h" #include "game/Subgame.h" #include "util/Structure.h" #include "game/def/BiomeDef.h" #include "game/def/BlockDef.h" #include "world/dim/Dimension.h" #include "game/atlas/BiomeAtlas.h" #include "world/dim/chunk/Chunk.h" #include "game/atlas/DefinitionAtlas.h" #define M_PI 3.14159265358979323846 MapGen::MapGen(Subgame& game, World& world, u32 seed, std::unordered_set biomes) : game(game), world(world), props(seed) { std::unordered_set biomeIndices {}; for (const auto& str : biomes) { if (str[0] == '#') for (auto& biome : game.getBiomes().biomesFromTag(str.substr(1, str.length() - 1))) biomeIndices.insert(biome->index); else biomeIndices.insert(game.getBiomes().biomeFromStr(str).index); } generateVoronoi(biomeIndices); let biomePerlin = FastNoise::New(); let biomeScale = FastNoise::New(); biomeScale->SetSource(biomePerlin); biomeScale->SetScale(1/2000.f); let biomeFractal = FastNoise::New(); biomeFractal->SetSource(biomeScale); biomeFractal->SetOctaveCount(5); biomeFractal->SetLacunarity(3); biomeGenerator = biomeFractal; } [[maybe_unused]] uptr MapGen::generateChunk(u16 dim, ivec3 pos) { return generateArea(dim, pos, 1); } uptr MapGen::generateMapBlock(u16 dim, ivec3 pos) { return generateArea(dim, Space::Chunk::world::fromMapBlock(pos), 4); } std::unique_ptr MapGen::generateArea(u16 dim, ivec3 origin, u16 size) { Job job(origin, size); job.temperature.generate({ job.pos.x * 16, 0, job.pos.z * 16 }, biomeGenerator); job.roughness.generate(ivec3 { job.pos.x * 16, 0, job.pos.z * 16 } + ivec3(2000), biomeGenerator); job.humidity.generate(ivec3 { job.pos.x * 16, 0, job.pos.z * 16 } + ivec3(-2000), biomeGenerator); let biomeMap = vec(pow(job.size * 16 + 1, 2)); u16vec3 bPos {}; for (bPos.x = 0; bPos.x < job.size * 16 + 1; bPos.x++) { for (bPos.z = 0; bPos.z < job.size * 16 + 1; bPos.z++) { biomeMap[bPos.z * (job.size * 16 + 1 ) + bPos.x] = getBiomeAt(job.temperature[bPos], job.humidity[bPos], job.roughness[bPos]); } } job.heightmap.fill([&](ivec3 pos) { return game.getBiomes().biomeFromId(biomeMap[pos.z * (job.size * 16 + 1) + pos.x]) .heightmap->GenSingle2D(job.pos.x * 16 + pos.x, job.pos.z * 16 + pos.z, 1337); }); job.volume.fill([&](ivec3 pos) { return game.getBiomes().biomeFromId(biomeMap[pos.z * (job.size * 16 + 1) + pos.x]) .volume->GenSingle3D(job.pos.x * 16 + pos.x, job.pos.y * 16 + pos.y, job.pos.z * 16 + pos.z, 1337); }); i16vec3 pos {}; for (pos.x = 0; pos.x < job.size; pos.x++) { for (pos.z = 0; pos.z < job.size; pos.z++) { uptr densityAbove = nullptr; for (pos.y = job.size; pos.y >= 0; pos.y--) { if (pos.y == job.size) { densityAbove = populateChunkDensity(job, pos); continue; } uptr density = populateChunkDensity(job, pos); uptr depth = populateChunkDepth(density, std::move(densityAbove)); generateChunkBlocks(job, pos, biomeMap, *depth); generateChunkDecorAndLight(job, pos, biomeMap, *depth); densityAbove = std::move(density); } } } // propogateSunlightNodes(job); for (let& chunk : *job.chunks) { chunk.second->compress(); } return std::move(job.chunks); } void MapGen::generateVoronoi(const std::unordered_set& biomes) { vec points {}; for (auto biomeInd : biomes) { auto& biome = game.getBiomes().biomeFromId(biomeInd); points.emplace_back(vec3 { static_cast(std::fmin(voronoiSize - 1, std::fmax(0, (biome.temperature + 1) / 2 * voronoiSize))), static_cast(std::fmin(voronoiSize - 1, std::fmax(0, biome.humidity * voronoiSize))), static_cast(std::fmin(voronoiSize - 1, std::fmax(0, biome.roughness * voronoiSize))) }, biomeInd, biome.tint); } voronoi.setData(points); voronoi.outputImage(8); } u16 MapGen::getBiomeAt(f32 temperature, f32 humidity, f32 roughness) { return voronoi[{ static_cast(std::fmin(voronoiSize - 1, std::fmax(0, (temperature + 1) / 2 * (voronoiSize - 1)))), static_cast(std::fmin(voronoiSize - 1, std::fmax(0, (humidity + 1) / 2 * (voronoiSize - 1)))), static_cast(std::fmin(voronoiSize - 1, std::fmax(0, (roughness + 1) / 2 * (voronoiSize - 1)))) }]; } uptr MapGen::populateChunkDensity(MapGen::Job& job, ivec3 localPos) { auto data = make_unique(); for (u16 i = 0; i < 4096; i++) { ivec3 indPos = Space::Block::fromIndex(i); let ind3d = localPos * 16 + indPos; let ind2d = (localPos.z * 16 + indPos.z) * (job.size * 16) + (localPos.x * 16 + indPos.x); (*data)[i] = (job.heightmap[ind2d]) + (job.volume[ind3d]) - ((job.pos.y + localPos.y) * 16 + indPos.y); } return data; } uptr MapGen::populateChunkDepth(uptr& chunkDensity, uptr chunkDensityAbove) { auto data = make_unique(); for (u16 i = 0; i < 256; i++) { ivec2 pos = { i / 16, i % 16 }; short depth = 16; if ((*chunkDensity)[Space::Block::index({ pos.x, 15, pos.y })] > 0) { for (u8 j = 0; j < 16; j++) { if ((*chunkDensityAbove)[Space::Block::index({ pos.x, j, pos.y })] <= 0) { depth = j; break; } } } else { depth = 0; } for (i8 y = 15; y >= 0; y--) { u16 ind = Space::Block::index({ pos.x, y, pos.y }); depth = ((*chunkDensity)[ind] > 0 ? std::min(depth + 1, 16) : 0); (*data)[ind] = depth; } } return data; } void MapGen::generateChunkBlocks(Job& job, ivec3 localPos, vec biomeMap, ChunkData& depthMap) { ivec3 chunkPos = job.pos + localPos; auto partial = (job.chunks->count(chunkPos) ? job.chunks->at(chunkPos) : nullptr); if (partial) job.chunks->erase(chunkPos); auto& chunk = *(*job.chunks->emplace(chunkPos, make_shared(chunkPos)).first).second; u16 partialBlock = DefinitionAtlas::INVALID; for (u16 i = 0; i < 4096; i++) { ivec3 indPos = Space::Block::fromIndex(i); u16 biomeId = biomeMap[(localPos.z * 16 + indPos.z) * (job.size * 16 + 1) + (localPos.x * 16 + indPos.x)]; auto& biome = game.getBiomes().biomeFromId(biomeId); chunk.d->biomes[i] = biomeId; f32 depth = depthMap[i]; u16 blockId = partialBlock > DefinitionAtlas::INVALID ? partialBlock : depth <= 1 ? DefinitionAtlas::AIR : depth <= 2 ? biome.topBlock : depth <= 4 ? biome.soilBlock : biome.rockBlock; assert(chunk.d != nullptr); chunk.d->blocks[i] = blockId; } chunk.countRenderableBlocks(); } void MapGen::generateChunkDecorAndLight(Job& job, ivec3 localPos, vec biomeMap, ChunkData& depthMap) { vec3 posFloat = job.pos + localPos; std::default_random_engine generator(posFloat.x + posFloat.y * M_PI + posFloat.z * (M_PI * 2)); std::uniform_real_distribution distribution(0, 1); auto& chunk = job.chunks->at(job.pos + localPos); ivec3 abovePos = job.pos + localPos + ivec3 { 0, 1, 0 }; sptr above = (localPos.y != job.size - 1) ? job.chunks->count(abovePos) ? job.chunks->at(abovePos) : nullptr : nullptr; for (u16 i = 0; i < 256; i++) { ivec3 indPos { i / 16, 15, i % 16 }; u16 biomeId = biomeMap[(localPos.z * 16 + indPos.z) * (job.size * 16 + 1) + (localPos.x * 16 + indPos.x)]; auto& biome = game.getBiomes().biomeFromId(biomeId); i16 schemID = -1; for (u16 j = 0; j < biome.schematics.size(); j++) { if (distribution(generator) > 1 - biome.schematics[j]->probability) { schemID = j; break; } } i8 light = -1; for (; indPos.y >= 0 && (light > -1 || schemID > -1); indPos.y--) { u16 ind = Space::Block::index(indPos); if (schemID > -1 && depthMap[ind] > 1 && depthMap[ind] <= 2) { ivec3 pos = (job.pos + localPos) * 16 + indPos; pos.y++; // Compensate for the fact that we're finding solid positions. auto& schematic = biome.schematics[schemID]; for (usize j = 0; j < schematic->length(); j++) { ivec3 off = schematic->getOffset(j); setBlock(job, pos + off - schematic->origin, schematic->layout[j], chunk); } break; } if (light == -1) light = above ? above->getLight(Space::Block::index(indPos), 3) : game.getDefs().blockFromId(chunk->getBlock(indPos)).lightPropagates ? 15 : 0; if (!light) continue; auto& blockDef = game.getDefs().blockFromId(chunk->getBlock(indPos)); if (!blockDef.lightPropagates) light = 0; else { chunk->setLight(ind, 3, light); job.sunlightQueue.emplace(ind, chunk.get()); } } } chunk->generationState = Chunk::GenerationState::GENERATED; } void MapGen::setBlock(MapGen::Job& job, ivec3 worldPos, u16 block, sptr hint) { if (block == DefinitionAtlas::INVALID) return; u16 ind = Space::Block::index(worldPos); if (hint && Space::Chunk::world::fromBlock(worldPos) == hint->getPos()) { if (hint->getBlock(ind) <= DefinitionAtlas::AIR) hint->setBlock(ind, block); } else { ivec3 chunkPos = Space::Chunk::world::fromBlock(worldPos); auto& chunk = *(*job.chunks->emplace(chunkPos, new Chunk(chunkPos, true)).first).second; if (chunk.getBlock(ind) <= DefinitionAtlas::AIR) chunk.setBlock(ind, block); } } void MapGen::propogateSunlightNodes(Job& job) { auto& defs = game.getDefs(); while (!job.sunlightQueue.empty()) { SunlightNode& node = job.sunlightQueue.front(); unsigned char lightLevel = node.chunk->getLight(node.index, 3); glm::ivec3 worldPos = node.chunk->pos * 16 + ivec3(Space::Block::fromIndex(node.index)); for (const auto& i : Vec::TO_VEC) { glm::ivec3 check = worldPos + i; Chunk* chunk; glm::ivec3 chunkPos = Space::Chunk::world::fromBlock(check); if (node.chunk->pos == chunkPos) chunk = node.chunk; else { auto found = job.chunks->find(chunkPos); if (found == job.chunks->end()) continue; chunk = found->second.get(); } auto ind = Space::Block::index(check); if (defs.blockFromId(chunk->getBlock(ind)).lightPropagates && chunk->getLight(ind, 3) + 2 <= lightLevel) { chunk->setLight(ind, 3, lightLevel - static_cast(!(lightLevel == 15 && i.y == -1))); job.sunlightQueue.emplace(ind, chunk); } } job.sunlightQueue.pop(); } }