Structure Generation System
To make my voxel world look more alive, I added simple structures like grass and trees. These sructures are randomly spawned when a new chunk is created, before building the chunk's mesh.
Spawning a structure is done by modifying the block types within a chunk. For example, generating a tree means setting a few blocks to Wood and others to Leave. Furthermore, before placing any structure, I check if another structure already exists at the intended location to prevent overlap.
Problem in Cross-Chunk Structure Generation & My Solution
You may see a structure like this:
This type of cut off structure occurs when a structure is generated across chunk borders, but the chunk hasn't been loaded yet or the boundary information of the block arrays is not updated.
This is a case like this:
Recall that I've made my voxel engine so that each chunk has a blocks array that is extended by 1 block in the x and z directions. These extra boundary layers are used to store block informations of neighboring chunks at the borders.
Since each chunk can only manage its own block data, updating a block at a chunk border does not automatically update the corresponding boundary data in adjacent chunks. Or, there isn't a chunk to update the block information because the chunk hasn't been created yet.
Therefore, the solution is manually updating the boundary block data in both the current chunk and its adjacent chunks, as well as caching the block update for unloaded chunks.
Code Implementation
This ensures block data is updated correctly for all involved chunks:
bool ChunkManager::set_block(ivec2 chunk_id, ivec3 block_index, BlockType type) {
Chunk* chunk = get_chunk(chunk_id);
if (chunk == nullptr) {
//if the chunk doesn't exist, cache the block update
unloaded_blocks[chunk_id].push_back({ block_index, type });
}
else {
//updates block type for the current chunk
chunk->set_block(block_index, type);
}
// if the block is at the chunk's edge along the X axis (left and right edges)
if (block_index.x == 1 || block_index.x == 16) {
ivec2 offset = ivec2(0.0);
// determine direction: -1 for left neighbor, +1 for right neighbor
offset.x = (block_index.x == 1) ? -1 : 1;
//adjacent chunk's id
ivec2 adj_id = chunk_id + offset;
// determine the corresponding block index in the neighboring chunk
// (17 is the right boundary in neighbor; 0 is the left boundary)
ivec3 adj_block_index = block_index;
adj_block_index.x = (block_index.x == 1) ? 17 : 0;
auto adj = chunks.find(adj_id);
if (adj != chunks.end()) {
// if neighbor chunk is loaded, update its boundary block
adj->second.set_block(adj_block_index, type);
}
else {
// If neighbor chunk is not loaded, cache the block update
unloaded_blocks[adj_id].push_back({ adj_block_index, type });
}
}
if (block_index.z == 1 || block_index.z == 16) {
//repeat the same logic for the Z axis (front and back edges)
...
}
return true;
}