From 9bc5bab3b243ebe19850af9675470b75f011d04c Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sun, 23 Jul 2023 12:36:28 +0200 Subject: [PATCH 1/5] mountain terrain with multiple octaves of noise --- src/chunkgenerator.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/chunkgenerator.cpp b/src/chunkgenerator.cpp index 16d5c4e..7521def 100644 --- a/src/chunkgenerator.cpp +++ b/src/chunkgenerator.cpp @@ -8,10 +8,10 @@ #include "OpenSimplexNoise.h" #include "utils.hpp" -#define GRASS_OFFSET 40 -#define NOISE_GRASS_MULT 20 +#define GRASS_OFFSET 100 +#define NOISE_GRASS_MULT 50 #define NOISE_DIRT_MULT 3 -#define NOISE_DIRT_MIN 2 +#define NOISE_DIRT_MIN 3 #define NOISE_DIRT_X_MULT 0.001f #define NOISE_DIRT_Z_MULT 0.001f #define NOISE_GRASS_X_MULT 0.018f @@ -36,6 +36,19 @@ OpenSimplexNoise::Noise noiseGen2(mt()); std::array grassNoiseLUT; std::array dirtNoiseLUT; +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; +} + void generateNoise(Chunk::Chunk *chunk) { for (int i = 0; i < grassNoiseLUT.size(); i++) @@ -56,13 +69,14 @@ void generateNoise(Chunk::Chunk *chunk) int z = HILBERT_XYZ_DECODE[s][2] + CHUNK_SIZE * chunk->getPosition().z; int d2 = HILBERT_XYZ_DECODE[s][0] * CHUNK_SIZE + HILBERT_XYZ_DECODE[s][2]; - if (grassNoiseLUT[d2] == -1){ - grassNoiseLUT[d2] = GRASS_OFFSET + (int)((0.5 + noiseGen1.eval(x * NOISE_GRASS_X_MULT, z * NOISE_GRASS_Z_MULT) * NOISE_GRASS_MULT)); - } - if (dirtNoiseLUT[d2] == -1){ - dirtNoiseLUT[d2] = NOISE_DIRT_MIN + (int)((0.5 + noiseGen2.eval(x * NOISE_DIRT_X_MULT, z * NOISE_DIRT_Z_MULT) * NOISE_DIRT_MULT)); - } + if (grassNoiseLUT[d2] == -1) + grassNoiseLUT[d2] = GRASS_OFFSET + evaluateNoise(noiseGen1, x, z, NOISE_GRASS_MULT, + 0.01, 0.35, 2.1, 5); + if (dirtNoiseLUT[d2] == -1) + dirtNoiseLUT[d2] = NOISE_DIRT_MIN + (int)((1 + noiseGen2.eval(x + * NOISE_DIRT_X_MULT, z * NOISE_DIRT_Z_MULT)) * NOISE_DIRT_MULT); + int grassNoise = grassNoiseLUT[d2]; int dirtNoise = dirtNoiseLUT[d2]; int stoneLevel = grassNoise - dirtNoise; -- 2.40.1 From 381cd698c7e240c09a1758bd3bd6390f51646333 Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sun, 23 Jul 2023 11:49:40 +0200 Subject: [PATCH 2/5] Initial tree generation Still very slow because multiple noise evaluations are needed --- include/block.hpp | 6 +- include/globals.hpp | 3 + src/chunkgenerator.cpp | 148 +++++++++++++++++++++++------ src/main.cpp | 6 +- src/renderer.cpp | 6 +- textures/leaves.png | Bin 0 -> 256 bytes textures/{oak_log.png => wood.png} | Bin 7 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 textures/leaves.png rename textures/{oak_log.png => wood.png} (100%) diff --git a/include/block.hpp b/include/block.hpp index 7a36a03..7d31874 100644 --- a/include/block.hpp +++ b/include/block.hpp @@ -6,7 +6,9 @@ enum class Block{ AIR, STONE, DIRT, - GRASS + GRASS, + WOOD, + LEAVES }; -#endif \ No newline at end of file +#endif diff --git a/include/globals.hpp b/include/globals.hpp index 470e729..4b9c243 100644 --- a/include/globals.hpp +++ b/include/globals.hpp @@ -16,6 +16,9 @@ extr Camera theCamera; constexpr int chunks_volume = static_cast(1.333333333333*M_PI*(RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE)); extr bool wireframe; +extr float sines[360]; +extr float cosines[360]; + extr uint32_t MORTON_XYZ_ENCODE[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE]; extr uint32_t MORTON_XYZ_DECODE[CHUNK_VOLUME][3]; extr uint32_t HILBERT_XYZ_ENCODE[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE]; diff --git a/src/chunkgenerator.cpp b/src/chunkgenerator.cpp index 7521def..3435f60 100644 --- a/src/chunkgenerator.cpp +++ b/src/chunkgenerator.cpp @@ -1,6 +1,7 @@ #include #include #include // for std::mt19937 +#include #include "block.hpp" #include "chunkgenerator.hpp" @@ -8,34 +9,43 @@ #include "OpenSimplexNoise.h" #include "utils.hpp" -#define GRASS_OFFSET 100 -#define NOISE_GRASS_MULT 50 +#define GRASS_OFFSET 40 +#define NOISE_GRASS_MULT 30 #define NOISE_DIRT_MULT 3 #define NOISE_DIRT_MIN 3 #define NOISE_DIRT_X_MULT 0.001f #define NOISE_DIRT_Z_MULT 0.001f #define NOISE_GRASS_X_MULT 0.018f #define NOISE_GRASS_Z_MULT 0.018f +#define NOISE_TREE_X_MULT 0.01f +#define NOISE_TREE_Z_MULT 0.01f + +#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 void generatePyramid(Chunk::Chunk *chunk); void generateNoise(Chunk::Chunk *chunk); void generateNoise3D(Chunk::Chunk *chunk); -void generateChunk(Chunk::Chunk *chunk) -{ - generateNoise(chunk); -} - -Block block; - std::random_device dev; std::mt19937 mt(dev()); OpenSimplexNoise::Noise noiseGen1(mt()); OpenSimplexNoise::Noise noiseGen2(mt()); +OpenSimplexNoise::Noise noiseGenWood(mt()); std::array grassNoiseLUT; std::array dirtNoiseLUT; +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; @@ -49,33 +59,76 @@ double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, doubl return sum; } +const int TREE_MASTER_SEED_X = mt(); +const int TREE_MASTER_SEED_Z = mt(); + +void evaluateTreeCell(int cx, int cz, int wcx, int wcz, int* wcx_offset, int* + wcz_offset, int* wx, int* wz, int* bwx, int* bwz, int* leaves_noise){ + + static int old_cx = -1, old_cz = -1, old_leaves_noise = -1; + + 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]; + + //std::cout << "cell: (" << wcx << "," << wcz << "): offset: (" << (int)wcx_off << "," << (int)wcz_off << ")\n"; + + // Cell to world coordinates + *wx = wcx * WOOD_CELL_SIZE + wcx_off; + *wz = wcz * WOOD_CELL_SIZE + wcz_off; + + *wcx_offset = wcx_off; + *wcz_offset = wcz_off; + + *bwx = *wx - cx; + *bwz = *wz - cz; + + if(old_leaves_noise == -1 || old_cx != cx || old_cz != cx || *bwx < 0 || *bwz < 0 || *bwx >= CHUNK_SIZE || *bwz >= CHUNK_SIZE) + *leaves_noise = TREE_STANDARD_HEIGHT + GRASS_OFFSET + evaluateNoise(noiseGen1, *wx, *wz, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5); + else *leaves_noise = TREE_STANDARD_HEIGHT + grassNoiseLUT[*bwx * CHUNK_SIZE + *bwz]; + + old_leaves_noise = *leaves_noise; + old_cx = cx; + old_cz = cz; +} + + +Block block; + void generateNoise(Chunk::Chunk *chunk) { + int cx = chunk->getPosition().x * CHUNK_SIZE; + int cy = chunk->getPosition().y * CHUNK_SIZE; + int cz = chunk->getPosition().z * CHUNK_SIZE; + + // Precalculate LUTs for (int i = 0; i < grassNoiseLUT.size(); i++) { - grassNoiseLUT[i] = -1; - dirtNoiseLUT[i] = -1; + int bx = i / CHUNK_SIZE; + int bz = i % CHUNK_SIZE; + + grassNoiseLUT[i] = GRASS_OFFSET + evaluateNoise(noiseGen1, cx+bx, cz+bz, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5); + dirtNoiseLUT[i] = NOISE_DIRT_MIN + (int)((1 + noiseGen2.eval(cx+bx * NOISE_DIRT_X_MULT, + cz+bz * NOISE_DIRT_Z_MULT)) * NOISE_DIRT_MULT); } Block block_prev{Block::AIR}; 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 x = HILBERT_XYZ_DECODE[s][0] + CHUNK_SIZE * chunk->getPosition().x; - int y = HILBERT_XYZ_DECODE[s][1] + CHUNK_SIZE * chunk->getPosition().y; - int z = HILBERT_XYZ_DECODE[s][2] + CHUNK_SIZE * chunk->getPosition().z; - int d2 = HILBERT_XYZ_DECODE[s][0] * CHUNK_SIZE + HILBERT_XYZ_DECODE[s][2]; - - if (grassNoiseLUT[d2] == -1) - grassNoiseLUT[d2] = GRASS_OFFSET + evaluateNoise(noiseGen1, x, z, NOISE_GRASS_MULT, - 0.01, 0.35, 2.1, 5); - if (dirtNoiseLUT[d2] == -1) - dirtNoiseLUT[d2] = NOISE_DIRT_MIN + (int)((1 + noiseGen2.eval(x - * NOISE_DIRT_X_MULT, z * NOISE_DIRT_Z_MULT)) * NOISE_DIRT_MULT); - + int bx = HILBERT_XYZ_DECODE[s][0]; + int by = HILBERT_XYZ_DECODE[s][1]; + int bz = HILBERT_XYZ_DECODE[s][2]; + int x = bx + cx; + int y = by + cy; + int z = bz + cz; + int d2 = bx * CHUNK_SIZE + bz; int grassNoise = grassNoiseLUT[d2]; int dirtNoise = dirtNoiseLUT[d2]; @@ -87,18 +140,59 @@ void generateNoise(Chunk::Chunk *chunk) block = Block::DIRT; else if (y == grassNoise) block = Block::GRASS; - else + else block = Block::AIR; + + // Divide the world into cells, so that no two trees will be adjacent of each other + int wcx = x / WOOD_CELL_SIZE, wcz = z / WOOD_CELL_SIZE; + int wcx_offset, wcz_offset, wx, wz, bwx, bwz, leavesNoise; + + evaluateTreeCell(cx, cz, wcx, wcz, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, + &leavesNoise); + + // A tree is to be places if the coordinates are those of the tree of the current cell + int wood_height = TREE_STANDARD_HEIGHT;// + noiseGenWood.eval(wcx * NOISE_TREE_X_MULT, wcz * NOISE_TREE_Z_MULT) * TREE_HEIGHT_VARIATION; + bool wood = x == wx && z == wz && y > grassNoiseLUT[d2] && y <= leavesNoise; + bool leaf = false; + + leaf = wood && y > leavesNoise && y < leavesNoise+LEAVES_RADIUS; + if(!leaf) leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, LEAVES_RADIUS); + + if(!leaf){ + evaluateTreeCell(cx, cz, wcx+1, wcz, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, + &leavesNoise); + leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, LEAVES_RADIUS); + } + + if(!leaf){ + evaluateTreeCell(cx, cz, wcx, wcz+1, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, + &leavesNoise); + leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, LEAVES_RADIUS); + } + + if(!leaf){ + evaluateTreeCell(cx, cz, wcx-1, wcz, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, + &leavesNoise); + leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, LEAVES_RADIUS); + } + + if(!leaf){ + evaluateTreeCell(cx, cz, wcx, wcz-1, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, + &leavesNoise); + leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, LEAVES_RADIUS); + } + + if(wood) block = Block::WOOD; + if(leaf) block = Block::LEAVES; + if (block != block_prev) { chunk->setBlocks(block_prev_start, s, block_prev); block_prev_start = s; } - block_prev = block; } - chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev); chunk->setState(Chunk::CHUNK_STATE_GENERATED, true); } diff --git a/src/main.cpp b/src/main.cpp index 8769986..f2ce389 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -63,8 +63,12 @@ int main() std::cout << "Using GPU: " << glGetString(GL_VENDOR) << " " << glGetString(GL_RENDERER) << "\n"; - SpaceFilling::initLUT(); wireframe = false; + for(int i = 0; i < 360; i++){ + sines[i] = sin(3.14 / 180 * i); + cosines[i] = cos(3.14 / 180 * i); + } + SpaceFilling::initLUT(); renderer::init(); std::thread chunkmanager_thread = chunkmanager::init(); diff --git a/src/renderer.cpp b/src/renderer.cpp index 6df7c4a..f5b084c 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -26,7 +26,7 @@ namespace renderer{ theShader = new Shader{"shaders/shader-texture.gs", "shaders/shader-texture.vs", "shaders/shader-texture.fs"}; // Create 3d array texture - constexpr int layerCount = 3; + constexpr int layerCount = 5; glGenTextures(1, &chunkTexture); glBindTexture(GL_TEXTURE_2D_ARRAY, chunkTexture); @@ -38,6 +38,10 @@ namespace renderer{ glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels1); unsigned char *texels2 = stbi_load("textures/grass_top.png", &width, &height, &nrChannels, 0); glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 2, width, height, 1, GL_RGB, GL_UNSIGNED_BYTE, texels2); + unsigned char *texels3 = stbi_load("textures/wood.png", &width, &height, &nrChannels, 0); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 3, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels3); + unsigned char *texels4 = stbi_load("textures/leaves.png", &width, &height, &nrChannels, 0); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 4, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels4); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_NEAREST); diff --git a/textures/leaves.png b/textures/leaves.png new file mode 100644 index 0000000000000000000000000000000000000000..02e8b865de71c5cf078066f6ff273d4b4c20206f GIT binary patch literal 256 zcmV+b0ssDqP)~pOAjZO_es6(G==3bYzcEZL?M4&xUF}tlXL(*{;`#3I_Jv-n6@kfvPw1k@i84(er zJ_~mp5_(pm)TiQ3fvTz);n*{igY6@NEajwK33b;Ce3Xfo&NG#Ny)a9eie~`Jxv0+$ zB!aASC8m92W`;X7q{RwM3JI48*aPK9qN>XWDJqh*boT?!UCs}=mvJ}%0000 Date: Fri, 28 Jul 2023 16:25:57 +0200 Subject: [PATCH 3/5] generator: optimize leaves generation routine with LUT --- src/chunkgenerator.cpp | 126 ++++++++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 53 deletions(-) diff --git a/src/chunkgenerator.cpp b/src/chunkgenerator.cpp index 3435f60..556cbdc 100644 --- a/src/chunkgenerator.cpp +++ b/src/chunkgenerator.cpp @@ -38,8 +38,25 @@ OpenSimplexNoise::Noise noiseGen1(mt()); OpenSimplexNoise::Noise noiseGen2(mt()); OpenSimplexNoise::Noise noiseGenWood(mt()); +// 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 +struct TreeCellInfo{ + // Cell coordinates (in "tree cell space") + int wcx, wcz; + // trunk offset from 0,0 in the cell + int wcx_offset, wcz_offset; + // Global x,z position of the trunk + int wx, wz; + // 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) { @@ -61,12 +78,7 @@ double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, doubl const int TREE_MASTER_SEED_X = mt(); const int TREE_MASTER_SEED_Z = mt(); - -void evaluateTreeCell(int cx, int cz, int wcx, int wcz, int* wcx_offset, int* - wcz_offset, int* wx, int* wz, int* bwx, int* bwz, int* leaves_noise){ - - static int old_cx = -1, old_cz = -1, old_leaves_noise = -1; - +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; @@ -74,37 +86,32 @@ void evaluateTreeCell(int cx, int cz, int wcx, int wcz, int* wcx_offset, int* int wcx_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * sines[anglex % 360]; int wcz_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * cosines[anglez % 360]; - //std::cout << "cell: (" << wcx << "," << wcz << "): offset: (" << (int)wcx_off << "," << (int)wcz_off << ")\n"; + struct TreeCellInfo result{}; // Cell to world coordinates - *wx = wcx * WOOD_CELL_SIZE + wcx_off; - *wz = wcz * WOOD_CELL_SIZE + wcz_off; + result.wx = wcx * WOOD_CELL_SIZE + wcx_off; + result.wz = wcz * WOOD_CELL_SIZE + wcz_off; - *wcx_offset = wcx_off; - *wcz_offset = wcz_off; + result.wcx_offset = wcx_off; + result.wcz_offset = wcz_off; - *bwx = *wx - cx; - *bwz = *wz - cz; + 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); - if(old_leaves_noise == -1 || old_cx != cx || old_cz != cx || *bwx < 0 || *bwz < 0 || *bwx >= CHUNK_SIZE || *bwz >= CHUNK_SIZE) - *leaves_noise = TREE_STANDARD_HEIGHT + GRASS_OFFSET + evaluateNoise(noiseGen1, *wx, *wz, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5); - else *leaves_noise = TREE_STANDARD_HEIGHT + grassNoiseLUT[*bwx * CHUNK_SIZE + *bwz]; - - old_leaves_noise = *leaves_noise; - old_cx = cx; - old_cz = cz; + return result; } -Block block; - 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 for (int i = 0; i < grassNoiseLUT.size(); i++) { int bx = i / CHUNK_SIZE; @@ -115,13 +122,25 @@ void generateNoise(Chunk::Chunk *chunk) cz+bz * NOISE_DIRT_Z_MULT)) * NOISE_DIRT_MULT); } + // Tree LUT + int tree_lut_x_offset = cx / WOOD_CELL_SIZE - 1; + int tree_lut_z_offset = cz / WOOD_CELL_SIZE - 1; + + for(int i = 0; i < TREE_LUT_SIZE; i++) + for(int k = 0; k < TREE_LUT_SIZE; k++){ + int wcx = (tree_lut_x_offset + i); + int wcz = (tree_lut_z_offset + k); + treeLUT[i * TREE_LUT_SIZE + k] = evaluateTreeCell(wcx, wcz); + } + + + // Generation of terrain Block block_prev{Block::AIR}; 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]; int by = HILBERT_XYZ_DECODE[s][1]; int bz = HILBERT_XYZ_DECODE[s][2]; @@ -144,43 +163,44 @@ void generateNoise(Chunk::Chunk *chunk) block = Block::AIR; - // Divide the world into cells, so that no two trees will be adjacent of each other - int wcx = x / WOOD_CELL_SIZE, wcz = z / WOOD_CELL_SIZE; - int wcx_offset, wcz_offset, wx, wz, bwx, bwz, leavesNoise; + // 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; - evaluateTreeCell(cx, cz, wcx, wcz, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, - &leavesNoise); + // Retrieve info on the cell from LUT + info = treeLUT[wcx * TREE_LUT_SIZE + wcz]; - // A tree is to be places if the coordinates are those of the tree of the current cell - int wood_height = TREE_STANDARD_HEIGHT;// + noiseGenWood.eval(wcx * NOISE_TREE_X_MULT, wcz * NOISE_TREE_Z_MULT) * TREE_HEIGHT_VARIATION; - bool wood = x == wx && z == wz && y > grassNoiseLUT[d2] && y <= leavesNoise; - bool leaf = false; + // 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 leaf{false}; - leaf = wood && y > leavesNoise && y < leavesNoise+LEAVES_RADIUS; - if(!leaf) leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, LEAVES_RADIUS); + // 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){ - evaluateTreeCell(cx, cz, wcx+1, wcz, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, - &leavesNoise); - leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, 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); + } - if(!leaf){ - evaluateTreeCell(cx, cz, wcx, wcz+1, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, - &leavesNoise); - leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, 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); + } - if(!leaf){ - evaluateTreeCell(cx, cz, wcx-1, wcz, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, - &leavesNoise); - leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, 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); + } - if(!leaf){ - evaluateTreeCell(cx, cz, wcx, wcz-1, &wcx_offset, &wcz_offset, &wx, &wz, &bwx, &bwz, - &leavesNoise); - leaf = utils::withinDistance(x,y,z, wx, leavesNoise, wz, 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); + } } if(wood) block = Block::WOOD; -- 2.40.1 From b0cf413baf5cbdbc76f90e985647722f50986860 Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Fri, 28 Jul 2023 16:36:14 +0200 Subject: [PATCH 4/5] generator: add comments + a bit of refactor --- src/chunkgenerator.cpp | 157 +++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 76 deletions(-) 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); -} -- 2.40.1 From 83f0aafba0f2d8d8e2576d10c6bc749f57b1fcec Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Fri, 28 Jul 2023 16:39:24 +0200 Subject: [PATCH 5/5] give leaves a sprinkle of color --- shaders/shader-texture.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/shaders/shader-texture.fs b/shaders/shader-texture.fs index cd854e3..9794267 100644 --- a/shaders/shader-texture.fs +++ b/shaders/shader-texture.fs @@ -23,6 +23,7 @@ void main(){ // Load the texture // anti-gamma-correction of the texture. Without this it would be gamma corrected twice! vec3 vColor = pow(texture(textureArray, TexCoord).rgb, vec3(gamma)); + if(TexCoord.z == 4) vColor = vColor * normalize(vec3(10, 250, 10)); vec3 normal = normalize(Normal); -- 2.40.1