Zepha/src/world/gen/MapGen.cpp

311 lines
10 KiB
C++

#include <random>
#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<string> biomes) :
game(game), world(world), props(seed) {
std::unordered_set<u16> 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<FastNoise::Simplex>();
let biomeScale = FastNoise::New<FastNoise::DomainScale>();
biomeScale->SetSource(biomePerlin);
biomeScale->SetScale(1/2000.f);
let biomeFractal = FastNoise::New<FastNoise::FractalFBm>();
biomeFractal->SetSource(biomeScale);
biomeFractal->SetOctaveCount(5);
biomeFractal->SetLacunarity(3);
biomeGenerator = biomeFractal;
}
[[maybe_unused]] uptr<MapGen::ChunkMap> MapGen::generateChunk(u16 dim, ivec3 pos) {
return generateArea(dim, pos, 1);
}
uptr<MapGen::ChunkMap> MapGen::generateMapBlock(u16 dim, ivec3 pos) {
return generateArea(dim, Space::Chunk::world::fromMapBlock(pos), 4);
}
std::unique_ptr<MapGen::ChunkMap> 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<u16>(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<ChunkData> densityAbove = nullptr;
for (pos.y = job.size; pos.y >= 0; pos.y--) {
if (pos.y == job.size) {
densityAbove = populateChunkDensity(job, pos);
continue;
}
uptr<ChunkData> density = populateChunkDensity(job, pos);
uptr<ChunkData> 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<u16>& biomes) {
vec<Voronoi3D::VoronoiPoint> points {};
for (auto biomeInd : biomes) {
auto& biome = game.getBiomes().biomeFromId(biomeInd);
points.emplace_back(vec3 {
static_cast<u16>(std::fmin(voronoiSize - 1, std::fmax(0, (biome.temperature + 1) / 2 * voronoiSize))),
static_cast<u16>(std::fmin(voronoiSize - 1, std::fmax(0, biome.humidity * voronoiSize))),
static_cast<u16>(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<u16>(std::fmin(voronoiSize - 1, std::fmax(0, (temperature + 1) / 2 * (voronoiSize - 1)))),
static_cast<u16>(std::fmin(voronoiSize - 1, std::fmax(0, (humidity + 1) / 2 * (voronoiSize - 1)))),
static_cast<u16>(std::fmin(voronoiSize - 1, std::fmax(0, (roughness + 1) / 2 * (voronoiSize - 1))))
}];
}
uptr<MapGen::ChunkData> MapGen::populateChunkDensity(MapGen::Job& job, ivec3 localPos) {
auto data = make_unique<ChunkData>();
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::ChunkData> MapGen::populateChunkDepth(uptr<ChunkData>& chunkDensity, uptr<ChunkData> chunkDensityAbove) {
auto data = make_unique<ChunkData>();
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<u16> 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<Chunk>(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<u16> 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<f32> distribution(0, 1);
auto& chunk = job.chunks->at(job.pos + localPos);
ivec3 abovePos = job.pos + localPos + ivec3 { 0, 1, 0 };
sptr<Chunk> 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<Chunk> 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<int>(!(lightLevel == 15 && i.y == -1)));
job.sunlightQueue.emplace(ind, chunk);
}
}
job.sunlightQueue.pop();
}
}