'Algo. terrain generation - evaporation calculation issue
Creating procedural terrain I cannot get an evaporation matrix and find why my algorithm is wrong. How my program works:
- A user loads a map chunk.
- If it exists it loads its data.
- If it doesn't exist, it creates the chunk and returns the generated data.
Creation of chunk steps:
- Create a heightfield (Diamond Square algorithm).
- Create [0,3] rivers (and more chunks if river needs to continue on a different chunk).
- Calculate evaporation.
When generating the terrain I use the following constants:
private const chunkHeights = array( 'max' => 64, 'min' => -64 ); // min and max height
private const chunkSize = 129; // width = height for 1 chunk
private const chunkVariation = 1; // max variation from a position to its neighbor
Heightfield script and my rivers script are both working as expected. I try updating the evaporation file of a given chunk:
private static function updateEvaporation( $pChunkX, $pChunkY ) {
$chunks = array();
$seaOrRiver = 0;
for( $y = ( $pChunkY - 1 ); $y <= ( $pChunkY + 1 ); $y++ ) {
for( $x = ( $pChunkX - 1 ); $x <= ( $pChunkX + 1 ); $x++ ) {
$chunkFolder = 'terrain/chunk'.$x.'x'.$y.'/';
$tmpChunkFolder = 'terrain/tmp_chunk'.$x.'x'.$y.'/';
$chunkHf = null;
$chunkRivers = null;
$chunkEvaporation = null;
if( file_exists( $chunkFolder ) ) {
$chunkHf = new HeightField();
$chunkHfData = file_get_contents( $chunkFolder.'heightfield.json' );
$chunkHf->fromString( $chunkHfData );
$chunkRivers = json_decode( file_get_contents( $chunkFolder.'rivers.json' ), true );
$chunkEvaporation = json_decode( file_get_contents( $chunkFolder.'evaporation.json' ), true );
} elseif( file_exists( $tmpChunkFolder ) ) {
$chunkHf = new HeightField();
$chunkHfData = file_get_contents( $tmpChunkFolder.'heightfield.json' );
$chunkHf->fromString( $chunkHfData );
$chunkRivers = json_decode( file_get_contents( $tmpChunkFolder.'rivers.json' ), true );
$chunkEvaporation = json_decode( file_get_contents( $tmpChunkFolder.'evaporation.json' ), true );
}
if( ( $chunkHf != null ) && ( $chunkRivers != null ) && ( $chunkEvaporation != null ) ) {
for( $chunkY = 0; $chunkY < self::chunkSize; $chunkY++ ) {
for( $chunkX = 0; $chunkX < self::chunkSize; $chunkX++ ) {
if( ( $chunkHf->getHeight( $chunkX, $chunkY ) <= 0 ) || ( $chunkRivers[$chunkY][$chunkX] == 1 ) ) {
$chunkEvaporation[$chunkY][$chunkX] = 0;
$seaOrRiver++;
} else {
$chunkEvaporation[$chunkY][$chunkX] = null;
}
}
}
$chunks[$x.'x'.$y] = array(
'hf' => $chunkHf,
'rivers' => $chunkRivers,
'evaporation' => $chunkEvaporation
);
}
}
}
echo '<br>0 => ['.$seaOrRiver.']<br>';
$eLevel = 0;
while( $eLevel <= max( 0, self::chunkHeights['max'] ) ) {
$byLevel = 0;
for( $y = 0; $y < ( 3 * self::chunkSize ); $y++ ) {
for( $x = 0; $x < ( 3 * self::chunkSize ); $x++ ) {
$chunkX = floor( $x / self::chunkSize ) + $pChunkX - 1;
$chunkY = floor( $y / self::chunkSize ) + $pChunkY - 1;
$chunkXx = $x % self::chunkSize;
$chunkYy = $y % self::chunkSize;
if( isset( $chunks[$chunkX.'x'.$chunkY] ) ) {
if( $chunks[$chunkX.'x'.$chunkY]['evaporation'][$chunkYy][$chunkXx] == $eLevel ) {
for( $yy = ( $y - 1 ); $yy <= ( $y + 1 ); $yy++ ) {
for( $xx = ( $x - 1 ); $xx <= ( $x + 1 ); $xx++ ) {
$chunkXX = floor( $xx / self::chunkSize ) + $pChunkX - 1;
$chunkYY = floor( $yy / self::chunkSize ) + $pChunkY - 1;
$chunkXXx = $xx % self::chunkSize;
$chunkYYy = $yy % self::chunkSize;
if( isset( $chunks[$chunkXX.'x'.$chunkYY]['evaporation'][$chunkYYy][$chunkXXx] ) ) {
if( $chunks[$chunkXX.'x'.$chunkYY]['evaporation'][$chunkYYy][$chunkXXx] == null ) {
$chunks[$chunkXX.'x'.$chunkYY]['evaporation'][$chunkYYy][$chunkXXx] = ( $eLevel + 1 );
$byLevel++;
}
}
}
}
}
}
}
}
echo ( $eLevel + 1 ).' => ['.$byLevel.']<br>';
$eLevel++;
}
if( file_exists( 'terrain/chunk'.$pChunkX.'x'.$pChunkY ) ) {
file_put_contents( 'terrain/chunk'.$pChunkX.'x'.$pChunkY.'/evaporation.json', json_encode( $chunks[$pChunkX.'x'.$pChunkY]['evaporation'] ) );
} else {
file_put_contents( 'terrain/tmp_chunk'.$pChunkX.'x'.$pChunkY.'/evaporation.json', json_encode( $chunks[$pChunkX.'x'.$pChunkY]['evaporation'] ) );
}
return $chunks[$pChunkX.'x'.$pChunkY]['evaporation'];
}
Here are some explanations:
- I load all chunk neighbors (I assume that the max distance to water is less that 0.5 x chunkSize) and I load as well the target chunk.
- I'm storing them in the
$chunksarray with their own coordinates as key. - For each chunk, I update evaporation matrix with 0 where there is water: so where there is a river (0 = no river, 1 = river in rivers matrix) OR where heightfield is under sea level (height <= 0).
- Otherwise, I fill in others values with
null.
This is the first main loop. After this I have a $chunks list with for all chunks a heightfield, rivers matrix and incomplete evaporation matrix (where evaporation values are 0 or null).
My second main loop :
- Loop on all coordinates of all stored chunks (if the chunk exists), which has a given level (level starts at 0 = sea or river).
- Loop on all neighbors aside the position with the given level.
- If neighbor evaporation is null (so not calculated yet) set evaporation to level + 1.
- Increase level by 1 and restart loop.
- Once the level is higher that the max evaporation level, I stop my loop and save the file. I used to stop the 2nd loop when there is no more evaporation updated for the current level (but it was going only 2 times in the loop).
Result once I load 1 chunk :

Here white parts are positions where $pEvaporation[$y][$x] == null and the rest is the evaporation ratio from ( rgb( 0, 0, 0 ) to rgb( 255, 0, 0 ) ). Only the sea (and rivers) are generated (river is not visible because of the order I generate and get the data). Printed text corresponds to <level I'm checking> => [<number of null neighbors found>].
Others matrix previewing:

- On the left side, heightfield, sea level and rivers.
- On the right sea area (in blue) and rivers area (in green).
Do you know what is going wrong with my script?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
