1
votes

I have a voxel based game in development right now and I generate my world by using Simplex Noise so far. Now I want to generate some other structures like rivers, cities and other stuff, which can't be easily generated because I split my world (which is practically infinite) into chunks of 64x128x64. I already generated trees (the leaves can grow into neighbouring chunks), by generating the trees for a chunk, plus the trees for the 8 chunks surrounding it, so leaves wouldn't be missing. But if I go into higher dimensions that can get difficult, when I have to calculate one chunk, considering chunks in an radius of 16 other chunks.

Is there a way to do this a better way?

3
If I understood well, when you generate a tree (for example), you have to check the 8 neighboors chunks in case the tree has grown inside a neighboor chunk, isn't it? what I still don't get is that about 'if I go into higher dimensions that can get difficult...', could you elaborate? maybe you meant, higher dimension objects? - alesegdia
Well when I generate a city for example, which could extend in 8x8 chunks, I would have to calculate 64 chunks to generate one chunk. And I need to calculate 64 of them which means I would calculate 4096 chunks to generate 64 (if I would do it the same as the trees). - user3088126

3 Answers

1
votes

Depending on the desired complexity of the generated structure, you may find it useful to first generate it in a separate array, perhaps even a map (a location-to-contents dictionary, useful in case of high sparseness), and then transfer the structure to the world?

As for natural land features, you may want to google how fractals are used in landscape generation.

0
votes

I read something about this on a book and what they did in these cases was to make a finer division of chunks depending on the application, i.e.: if you are going to grow very big objects, it may be useful to have another separated logic division of, for example, 128x128x128, just for this specific application.

In essence, the data resides is in the same place, you just use different logical divisions.

To be honest, never did any voxel, so don't take my answer too serious, just throwing ideas. By the way, the book is game engine gems 1, they have a gem on voxel engines there.

About rivers, can't you just set a level for water and let rivers autogenerate in mountain-side-mountain ladders? To avoid placing water inside mountain caveats, you could perform a raycast up to check if it's free N blocks up.

0
votes

I know this thread is old and I suck at explaining, but I'll share my approach.

So for example 5x5x5 trees. What you want is for your noise function to return the same value for an area of 5x5 blocks, so that even outside of the chunk, you can still check if you should generate a tree or not.

// Here the returned value is different for every block
float value = simplexNoise(x * frequency, z * frequency) * amplitude; 

// Here it will return the same value for an area of blocks (you should use floorDiv instead of dividing, or you it will get negative coordinates wrong (-3 / 5 should be -1, not 0 like in normal division))
float value = simplexNoise(Math.floorDiv(x, 5) * frequency, Math.floorDiv(z, 5) * frequency) * amplitude;

And now we'll plant a tree. For this we need to check what x y z position this current block is relative to the tree's starting position, so we can know what part of the tree this block is.

if(value > 0.8) { // A certain threshold (checking if tree should be generated at this area)
    int startX = Math.floorDiv(x, 5) * 5; // flooring the x value to every 5 units to get the start position
    int startZ = Math.floorDiv(z, 5) * 5; // flooring the z value to every 5 units to get the start position
    // Getting the starting height of the trunk (middle of the tree , that's why I'm adding 2 to the starting x and starting z), which is 1 block over the grass surface
    int startY = height(startX + 2, startZ + 2) + 1;

    int relx = x - startX; // block pos relative to starting position
    int relz = z - startZ;

    for(int j = startY; j < startY + 5; j++) {
        int rely = j - startY;
        byte tile = tree[relx][rely][relz]; // Get the needing block at this part of the tree
        tiles[i][j][k] = tile;
    }
}

The tree 3d array here is almost like a "prefab" of the tree, which you can use to know what block to set at the position relative to the starting point. (God I don't know how to explain this, and having english as my fifth language doesn't help me either ;-; feel free to improve my answer or create a new one). I've implemented this in my engine, and it's totally working. The structures can be as big as you want, with no chunk pre loading needed. The one problem with this method is that the trees or structures will we spawned almost within a grid, but this can easily be solved with multiple octaves with different offsets.

So recap

for (int i = 0; i < 64; i++) {
    for (int k = 0; k < 64; k++) {
        int x = chunkPosToWorldPosX(i); // Get world position
        int z = chunkPosToWorldPosZ(k);

        // Here the returned value is different for every block
        // float value = simplexNoise(x * frequency, z * frequency) * amplitude; 

        // Here it will return the same value for an area of blocks (you should use floorDiv instead of dividing, or you it will get negative coordinates wrong (-3 / 5 should be -1, not 0 like in normal division))
        float value = simplexNoise(Math.floorDiv(x, 5) * frequency, Math.floorDiv(z, 5) * frequency) * amplitude;

        if(value > 0.8) { // A certain threshold (checking if tree should be generated at this area)
            int startX = Math.floorDiv(x, 5) * 5; // flooring the x value to every 5 units to get the start position
            int startZ = Math.floorDiv(z, 5) * 5; // flooring the z value to every 5 units to get the start position
            // Getting the starting height of the trunk (middle of the tree , that's why I'm adding 2 to the starting x and starting z), which is 1 block over the grass surface
            int startY = height(startX + 2, startZ + 2) + 1;

            int relx = x - startX; // block pos relative to starting position
            int relz = z - startZ;

            for(int j = startY; j < startY + 5; j++) {
                int rely = j - startY;
                byte tile = tree[relx][rely][relz]; // Get the needing block at this part of the tree
                tiles[i][j][k] = tile;
            }
        }
    }
}

So 'i' and 'k' are looping withing the chunk, and 'j' is looping inside the structure. This is pretty much how it should work.

And about the rivers, I personally haven't done it yet, and I'm not sure why you need to set the blocks around the chunk when generating them ( you could just use perlin worms and it would solve problem), but it's pretty much the same idea, and for your cities too.