generator: add comments + a bit of refactor

pull/6/head
EmaMaker 2023-07-28 16:36:14 +02:00
parent baa8d14bb3
commit b0cf413baf
1 changed files with 81 additions and 76 deletions

View File

@ -1,7 +1,6 @@
#include <array> #include <array>
#include <iostream> #include <iostream>
#include <random> // for std::mt19937 #include <random> // for std::mt19937
#include <experimental/random>
#include "block.hpp" #include "block.hpp"
#include "chunkgenerator.hpp" #include "chunkgenerator.hpp"
@ -23,14 +22,16 @@
#define LEAVES_RADIUS 3 #define LEAVES_RADIUS 3
#define WOOD_CELL_SIZE 13 #define WOOD_CELL_SIZE 13
#define WOOD_CELL_CENTER 7 #define WOOD_CELL_CENTER 7
#define WOOD_CELL_BORDER (LEAVES_RADIUS-1)
#define WOOD_MAX_OFFSET (WOOD_CELL_SIZE-WOOD_CELL_CENTER-WOOD_CELL_BORDER)
#define TREE_STANDARD_HEIGHT 7 #define TREE_STANDARD_HEIGHT 7
#define TREE_HEIGHT_VARIATION 2 #define TREE_HEIGHT_VARIATION 2
#define WOOD_CELL_BORDER (LEAVES_RADIUS-1)
#define WOOD_MAX_OFFSET (WOOD_CELL_SIZE-WOOD_CELL_CENTER-WOOD_CELL_BORDER)
void generatePyramid(Chunk::Chunk *chunk);
void generateNoise(Chunk::Chunk *chunk); void generateNoise(Chunk::Chunk *chunk);
void generateNoise3D(Chunk::Chunk *chunk); void generateNoise3D(Chunk::Chunk *chunk);
double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, double amplitude, double
frequency, double persistence, double lacunarity, int octaves);
struct TreeCellInfo evaluateTreeCell(int wcx, int wcz);
std::random_device dev; std::random_device dev;
std::mt19937 mt(dev()); std::mt19937 mt(dev());
@ -38,6 +39,9 @@ OpenSimplexNoise::Noise noiseGen1(mt());
OpenSimplexNoise::Noise noiseGen2(mt()); OpenSimplexNoise::Noise noiseGen2(mt());
OpenSimplexNoise::Noise noiseGenWood(mt()); OpenSimplexNoise::Noise noiseGenWood(mt());
// Trees are generated by virtually dividing the world into cells. Each cell can contain exactly one
// tree, with some offset in the position. Having a border in the cell ensures that no trees are generated in
// adjacent blocks
// cover CHUNK_SIZE with WOOD_CELLS + 2 cells before and after the chunk // cover CHUNK_SIZE with WOOD_CELLS + 2 cells before and after the chunk
constexpr int TREE_LUT_SIZE = std::ceil(static_cast<float>(CHUNK_SIZE)/static_cast<float>(WOOD_CELL_SIZE)) + 2; constexpr int TREE_LUT_SIZE = std::ceil(static_cast<float>(CHUNK_SIZE)/static_cast<float>(WOOD_CELL_SIZE)) + 2;
// Info on the tree cell to generate // Info on the tree cell to generate
@ -45,73 +49,31 @@ struct TreeCellInfo{
// Cell coordinates (in "tree cell space") // Cell coordinates (in "tree cell space")
int wcx, wcz; int wcx, wcz;
// trunk offset from 0,0 in the cell // trunk offset from 0,0 in the cell
int wcx_offset, wcz_offset; int trunk_x_offset, trunk_z_offset;
// Global x,z position of the trunk // Global x,z position of the trunk
int wx, wz; int trunk_x, trunk_z;
// Y of the center of the leaves sphere // Y of the center of the leaves sphere
int leaves_y_pos; int leaves_y_pos;
}; };
// Lookup tables for generation // Lookup tables for generation
std::array<int, CHUNK_SIZE * CHUNK_SIZE> grassNoiseLUT; std::array<int, CHUNK_SIZE * CHUNK_SIZE> grassNoiseLUT;
std::array<int, CHUNK_SIZE * CHUNK_SIZE> dirtNoiseLUT; std::array<int, CHUNK_SIZE * CHUNK_SIZE> dirtNoiseLUT;
std::array<TreeCellInfo, TREE_LUT_SIZE*TREE_LUT_SIZE> treeLUT; std::array<TreeCellInfo, TREE_LUT_SIZE*TREE_LUT_SIZE> treeLUT;
void generateChunk(Chunk::Chunk *chunk)
{
generateNoise(chunk);
}
double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, double amplitude, double
frequency, double persistence, double lacunarity, int octaves){
double sum = 0;
for(int i = 0; i < octaves; i++){
sum += amplitude * noiseGen.eval(x*frequency, y*frequency);
amplitude *= persistence;
frequency *= lacunarity;
}
return sum;
}
const int TREE_MASTER_SEED_X = mt();
const int TREE_MASTER_SEED_Z = mt();
struct TreeCellInfo evaluateTreeCell(int wcx, int wcz){
int anglex = TREE_MASTER_SEED_X*wcx+TREE_MASTER_SEED_Z*wcz;
int anglez = TREE_MASTER_SEED_Z*wcz+TREE_MASTER_SEED_X*wcx;
// Start at the center of the cell, with a bit of random offset
int wcx_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * sines[anglex % 360];
int wcz_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * cosines[anglez % 360];
struct TreeCellInfo result{};
// Cell to world coordinates
result.wx = wcx * WOOD_CELL_SIZE + wcx_off;
result.wz = wcz * WOOD_CELL_SIZE + wcz_off;
result.wcx_offset = wcx_off;
result.wcz_offset = wcz_off;
result.leaves_y_pos = 1 + TREE_STANDARD_HEIGHT + GRASS_OFFSET + evaluateNoise(noiseGen1,
result.wx, result.wz, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5);
return result;
}
void generateNoise(Chunk::Chunk *chunk) void generateNoise(Chunk::Chunk *chunk)
{ {
Block block;
int cx = chunk->getPosition().x * CHUNK_SIZE; int cx = chunk->getPosition().x * CHUNK_SIZE;
int cy = chunk->getPosition().y * CHUNK_SIZE; int cy = chunk->getPosition().y * CHUNK_SIZE;
int cz = chunk->getPosition().z * CHUNK_SIZE; int cz = chunk->getPosition().z * CHUNK_SIZE;
// Precalculate LUTs // Precalculate LUTs
// Terrain LUTs // Terrain LUTs
// Noise value at a given (x,z), position represents:
// Grass Noise LUT: Height of the terrain: when the grass is placed and the player will stand
// Dirt Noise LUT: How many blocks of dirt to place before there is stone
// Anything below (grass-level - dirt_height) will be stone
for (int i = 0; i < grassNoiseLUT.size(); i++) for (int i = 0; i < grassNoiseLUT.size(); i++)
{ {
int bx = i / CHUNK_SIZE; int bx = i / CHUNK_SIZE;
@ -135,10 +97,11 @@ void generateNoise(Chunk::Chunk *chunk)
// Generation of terrain // Generation of terrain
Block block_prev{Block::AIR}; // March along the space-filling curve, calculate information about the block at every position
// A space-filling curve is continuous, so there is no particular order
// Take advantage of the interval-map structure by only inserting contigous runs of blocks
Block block_prev{Block::AIR}, block;
int block_prev_start{0}; int block_prev_start{0};
// A space filling curve is continuous, so there is no particular order
for (int s = 0; s < CHUNK_VOLUME; s++) for (int s = 0; s < CHUNK_VOLUME; s++)
{ {
int bx = HILBERT_XYZ_DECODE[s][0]; int bx = HILBERT_XYZ_DECODE[s][0];
@ -147,10 +110,10 @@ void generateNoise(Chunk::Chunk *chunk)
int x = bx + cx; int x = bx + cx;
int y = by + cy; int y = by + cy;
int z = bz + cz; int z = bz + cz;
int d2 = bx * CHUNK_SIZE + bz; int lut_index = bx * CHUNK_SIZE + bz;
int grassNoise = grassNoiseLUT[d2]; int grassNoise = grassNoiseLUT[lut_index];
int dirtNoise = dirtNoiseLUT[d2]; int dirtNoise = dirtNoiseLUT[lut_index];
int stoneLevel = grassNoise - dirtNoise; int stoneLevel = grassNoise - dirtNoise;
if (y < stoneLevel) if (y < stoneLevel)
@ -165,47 +128,49 @@ void generateNoise(Chunk::Chunk *chunk)
// Divide the world into cells, each with exactly one tree, so that no two trees will be adjacent of each other // Divide the world into cells, each with exactly one tree, so that no two trees will be adjacent of each other
struct TreeCellInfo info; struct TreeCellInfo info;
int wcx = (int)(x / WOOD_CELL_SIZE) - tree_lut_x_offset; int wcx = (int)(x / WOOD_CELL_SIZE) - tree_lut_x_offset; // wood cell x
int wcz = (int)(z / WOOD_CELL_SIZE) - tree_lut_z_offset; int wcz = (int)(z / WOOD_CELL_SIZE) - tree_lut_z_offset; // wood cell z
// Retrieve info on the cell from LUT // Retrieve info on the cell from LUT
info = treeLUT[wcx * TREE_LUT_SIZE + wcz]; info = treeLUT[wcx * TREE_LUT_SIZE + wcz];
// A tree is to be placed in this position if the coordinates are those of the tree of the current cell // A tree is to be placed in this position if the coordinates are those of the tree of the current cell
int wood_height = TREE_STANDARD_HEIGHT; int wood_height = TREE_STANDARD_HEIGHT;
bool wood = x == info.wx && z == info.wz && y > grassNoiseLUT[d2] && y <= info.leaves_y_pos; bool wood = x == info.trunk_x && z == info.trunk_z && y > grassNoiseLUT[lut_index] && y <= info.leaves_y_pos;
bool leaf{false}; bool leaf{false};
// Check placing of leaves // Check placing of leaves
if(wood) leaf = y > info.leaves_y_pos && y < info.leaves_y_pos+LEAVES_RADIUS; if(wood) leaf = y > info.leaves_y_pos && y < info.leaves_y_pos+LEAVES_RADIUS;
else{ else{
if(!leaf) leaf = utils::withinDistance(x,y,z, info.wx, info.leaves_y_pos, info.wz, LEAVES_RADIUS); if(!leaf) leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
// Eventually search neighboring cells // Eventually search neighboring cells
if(!leaf && wcx+1 < TREE_LUT_SIZE){ if(!leaf && wcx+1 < TREE_LUT_SIZE){
info = treeLUT[(wcx+1) * TREE_LUT_SIZE + wcz]; info = treeLUT[(wcx+1) * TREE_LUT_SIZE + wcz];
leaf = utils::withinDistance(x,y,z, info.wx, info.leaves_y_pos, info.wz, LEAVES_RADIUS); leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
} }
if(!leaf && wcx-1 >= 0){ if(!leaf && wcx-1 >= 0){
info = treeLUT[(wcx-1) * TREE_LUT_SIZE + wcz]; info = treeLUT[(wcx-1) * TREE_LUT_SIZE + wcz];
leaf = utils::withinDistance(x,y,z, info.wx, info.leaves_y_pos, info.wz, LEAVES_RADIUS); leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
} }
if(!leaf && wcz-1 >= 0){ if(!leaf && wcz-1 >= 0){
info = treeLUT[wcx * TREE_LUT_SIZE + (wcz-1)]; info = treeLUT[wcx * TREE_LUT_SIZE + (wcz-1)];
leaf = utils::withinDistance(x,y,z, info.wx, info.leaves_y_pos, info.wz, LEAVES_RADIUS); leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
} }
if(!leaf && wcz+1 < TREE_LUT_SIZE){ if(!leaf && wcz+1 < TREE_LUT_SIZE){
info = treeLUT[wcx * TREE_LUT_SIZE + (wcz+1)]; info = treeLUT[wcx * TREE_LUT_SIZE + (wcz+1)];
leaf = utils::withinDistance(x,y,z, info.wx, info.leaves_y_pos, info.wz, LEAVES_RADIUS); leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
} }
} }
if(wood) block = Block::WOOD; if(wood) block = Block::WOOD;
if(leaf) block = Block::LEAVES; if(leaf) block = Block::LEAVES;
// Use the interval-map structure of the chunk to compress the world: insert "runs" of
// equal blocks using indices in the hilbert curve
if (block != block_prev) if (block != block_prev)
{ {
chunk->setBlocks(block_prev_start, s, block_prev); chunk->setBlocks(block_prev_start, s, block_prev);
@ -213,12 +178,60 @@ void generateNoise(Chunk::Chunk *chunk)
} }
block_prev = block; block_prev = block;
} }
// Insert the last run of blocks
chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev); chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev);
// Mark the chunk as generated, is needed to trigger the next steps
chunk->setState(Chunk::CHUNK_STATE_GENERATED, true); chunk->setState(Chunk::CHUNK_STATE_GENERATED, true);
} }
// Noise evaluation with Fractal Brownian Motion
double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, double amplitude, double
frequency, double persistence, double lacunarity, int octaves){
double sum = 0;
for(int i = 0; i < octaves; i++){
sum += amplitude * noiseGen.eval(x*frequency, y*frequency);
amplitude *= persistence;
frequency *= lacunarity;
}
return sum;
}
// Tree cell Info
const int TREE_MASTER_SEED_X = mt();
const int TREE_MASTER_SEED_Z = mt();
struct TreeCellInfo evaluateTreeCell(int wcx, int wcz){
int anglex = TREE_MASTER_SEED_X*wcx+TREE_MASTER_SEED_Z*wcz;
int anglez = TREE_MASTER_SEED_Z*wcz+TREE_MASTER_SEED_X*wcx;
// Start at the center of the cell, with a bit of random offset
int wcx_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * sines[anglex % 360];
int wcz_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * cosines[anglez % 360];
struct TreeCellInfo result{};
// Cell to world coordinates
result.trunk_x = wcx * WOOD_CELL_SIZE + wcx_off;
result.trunk_z = wcz * WOOD_CELL_SIZE + wcz_off;
result.trunk_x_offset = wcx_off;
result.trunk_z_offset = wcz_off;
result.leaves_y_pos = 1 + TREE_STANDARD_HEIGHT + GRASS_OFFSET + evaluateNoise(noiseGen1,
result.trunk_x, result.trunk_z, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5);
return result;
}
void generateChunk(Chunk::Chunk *chunk)
{
generateNoise(chunk);
}
/* EXPERIMENTAL STUFF */
void generateNoise3D(Chunk::Chunk *chunk) { void generateNoise3D(Chunk::Chunk *chunk) {
Block block_prev{Block::AIR}; Block block_prev{Block::AIR}, block;
int block_prev_start{0}; int block_prev_start{0};
// A space filling curve is continuous, so there is no particular order // A space filling curve is continuous, so there is no particular order
@ -252,11 +265,3 @@ void generateNoise3D(Chunk::Chunk *chunk) {
chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev); chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev);
} }
void generatePyramid(Chunk::Chunk *chunk)
{
for (int i = 0; i < CHUNK_SIZE; i++)
for (int j = 0; j < CHUNK_SIZE; j++)
for (int k = 0; k < CHUNK_SIZE; k++)
chunk->setBlock(i >= j && i < CHUNK_SIZE - j && k >= j && k < CHUNK_SIZE - j ? (j & 1) == 0 ? Block::GRASS : Block::STONE : Block::AIR, i, j, k);
}