diff --git a/src/chunkgenerator.cpp b/src/chunkgenerator.cpp index 556cbdc..94ccfbf 100644 --- a/src/chunkgenerator.cpp +++ b/src/chunkgenerator.cpp @@ -1,7 +1,6 @@ #include #include #include // for std::mt19937 -#include #include "block.hpp" #include "chunkgenerator.hpp" @@ -23,14 +22,16 @@ #define LEAVES_RADIUS 3 #define WOOD_CELL_SIZE 13 #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_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 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::mt19937 mt(dev()); @@ -38,6 +39,9 @@ OpenSimplexNoise::Noise noiseGen1(mt()); OpenSimplexNoise::Noise noiseGen2(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 constexpr int TREE_LUT_SIZE = std::ceil(static_cast(CHUNK_SIZE)/static_cast(WOOD_CELL_SIZE)) + 2; // Info on the tree cell to generate @@ -45,73 +49,31 @@ struct TreeCellInfo{ // Cell coordinates (in "tree cell space") int wcx, wcz; // 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 - int wx, wz; + int trunk_x, trunk_z; // Y of the center of the leaves sphere int leaves_y_pos; }; - // Lookup tables for generation std::array grassNoiseLUT; std::array dirtNoiseLUT; std::array 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) { - Block block; - int cx = chunk->getPosition().x * CHUNK_SIZE; int cy = chunk->getPosition().y * CHUNK_SIZE; int cz = chunk->getPosition().z * CHUNK_SIZE; // Precalculate 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++) { int bx = i / CHUNK_SIZE; @@ -135,10 +97,11 @@ void generateNoise(Chunk::Chunk *chunk) // 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}; - - // A space filling curve is continuous, so there is no particular order for (int s = 0; s < CHUNK_VOLUME; s++) { int bx = HILBERT_XYZ_DECODE[s][0]; @@ -147,10 +110,10 @@ void generateNoise(Chunk::Chunk *chunk) int x = bx + cx; int y = by + cy; int z = bz + cz; - int d2 = bx * CHUNK_SIZE + bz; + int lut_index = bx * CHUNK_SIZE + bz; - int grassNoise = grassNoiseLUT[d2]; - int dirtNoise = dirtNoiseLUT[d2]; + int grassNoise = grassNoiseLUT[lut_index]; + int dirtNoise = dirtNoiseLUT[lut_index]; int stoneLevel = grassNoise - dirtNoise; 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 struct TreeCellInfo info; - int wcx = (int)(x / WOOD_CELL_SIZE) - tree_lut_x_offset; - int wcz = (int)(z / WOOD_CELL_SIZE) - tree_lut_z_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; // wood cell z // Retrieve info on the cell from LUT 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 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}; // Check placing of leaves if(wood) leaf = y > info.leaves_y_pos && y < info.leaves_y_pos+LEAVES_RADIUS; 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 if(!leaf && wcx+1 < TREE_LUT_SIZE){ 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){ 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){ 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){ 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(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) { chunk->setBlocks(block_prev_start, s, block_prev); @@ -213,12 +178,60 @@ void generateNoise(Chunk::Chunk *chunk) } block_prev = block; } + // Insert the last run of blocks 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); } +// 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) { - Block block_prev{Block::AIR}; + Block block_prev{Block::AIR}, block; int block_prev_start{0}; // 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); } - -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); -}