算法。地形生成-蒸发计算问题

发布于 2025-01-09 04:42:32 字数 7016 浏览 0 评论 0 原文

创建程序地形我无法获得蒸发矩阵并找出我的算法错误的原因。我的程序如何工作:

  • 用户加载地图块。
  • 如果存在,则加载其数据。
  • 如果不存在,它将创建块并返回生成的数据。

创建块步骤:

  • 创建高度场(钻石方形算法)。
  • 创建 [0,3] 河流(如果河流需要在不同的块上继续,则创建更多的块)。
  • 计算蒸发量。

生成地形时,我使用以下常量:

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 脚本和我的河流脚本都按预期工作。我尝试更新给定块的蒸发文件:

    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'];
    }

以下是一些解释:

  • 我加载所有块邻居(我假设到水的最大距离小于 0.5 x chunkSize),并且还加载目标块。
  • 我将它们存储在 $chunks 数组中,并以它们自己的坐标作为键。
  • 对于每个块,我在有水的地方用 0 更新蒸发矩阵:所以在有河流的地方(0 = 无河流,1 = 河流矩阵中的河流)或者高度场在海平面以下(高度 <= 0)。
  • 否则,我用 null 填充其他值。

这是第一个主循环。之后,我有一个 $chunks 列表,其中所有块都有高度场、河流矩阵和不完整的蒸发矩阵(其中蒸发值为 0 或 null)。

我的第二个主循环:

  • 循环所有存储块的所有坐标(如果该块存在),它具有给定的级别(级别从 0 = 海洋或河流开始)。
  • 循环除了具有给定级别的位置之外的所有邻居。
  • 如果相邻蒸发为空(因此尚未计算),则将蒸发设置为级别 + 1。
  • 将级别增加 1 并重新启动循环。
  • 一旦水平高于最大蒸发水平,我就停止循环并保存文件。当当前水平没有更多蒸发更新时,我曾经停止第二次循环(但它在循环中只进行了两次)。

加载 1 块后的结果:

evaporation

这里白色部分是 $pEvaporation[$y][$x 的位置] == null 剩下的是从 ( rgb( 0, 0, 0 )rgb( 255, 0, 0 ))。仅生成海洋(和河流)(由于我生成和获取数据的顺序,河流不可见)。打印文本对应于 => [<发现的空邻居数量>]

其他矩阵预览:

heights and water

  • 左侧为高度场、海平面和河流。
  • 右侧海域(蓝色)和河流区域(绿色)。

你知道我的脚本出了什么问题吗?

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 $chunks array 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 :

evaporation

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:

heights and water

  • 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

仅此而已 2025-01-16 04:42:32

我发现了我的错误:

PHP 认为 0 == null0 !== null,我在我的脚本中更正了这一点(用于蒸发测试)。

I found my mistake:

PHP considers 0 == null but 0 !== null, I corrected this on my script (for evaporation testing).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文