Опубликовано Оставить комментарий

Фрактальные ландшафты на PHP (Алгоритм Diamond-Square)

Реализация Diamond-Square на PHP
Реализация Diamond-Square на PHP

Когда попались на глаза первые упоминания об этом алгоритме (Diamond-Square), заинтересовало. Алгоритм начинает работу с  двумерного массива, затем, из четырех начальных значений, случайным образом генерирует карту высот, упорядоченную в виде сетки из точек так, чтобы весь массив (массив описывает условную карту поверхности) была покрыта квадратами. Строить «бесконечные» ландшафты островов, гор, рек и морей — ценная возможность при создании игрушек. В рунете одна из самых первых заметок, которая находится по запросу, это заметка Дениса Ольшина на хабре —
Алгоритм «diamond-square» для построения фрактальных ландшафтов (автору спасибо!). Поскольку итоговая демка не доступна, нашел отличную джаваскритовую реализацию Diamond-Square на jsfiddle. Не знакомясь с самим алгоритмом, поменяйте в фидле параметр smooth c 0.9 на 0.4 и нажмите «Run» в верхней левой части страницы сервиса: Diamond-Square превратит пустыню в островной ландшафт! Теперь вы точно чувствуете что хотите реализовать этот алгоритм на PHP? Тогда вперед!

П.с. разобраться в нём все же придётся до  построения, потому найдите время — может пригодится.

PHP-код Diamond-Square

Код Diamond-Square на гитхабе. При переписывании кода на PHP, стояла задача внести минимальное количество изменений в исходный алгоритм. Теперь разобравшись с вариантом создания ландшафта на одном языке, будет проще рассмотреть вариант на другом.  С другой стороны, есть потенциал по изменению скрипта: переделка кода на ООП, задание других цветов для карты высот, использование алгоритмов аппроксимации изображений (сглаживание углов, использование спрайтов).

<?php

$size = 8;
$zoom = 5;
$smooth = .9;
$dim = '3d';

$size = pow(2, $size) + 1;
$matrix = [];
$length = $size*$size;
for($i = 0; $i < $length; $i++) $matrix[] = 0;

$width = $height = $size * $zoom;
header ('Content-Type: image/png');
$im = imagecreatetruecolor ( $width , $height );

function toI($x, $y) {
    global $size;
    return $x + $y * $size;
}
function toXY($i) {
    return [
        "x" => floor($i / $size),
        "y" => $i % $size
    ];
}

function genColor($w) {
    global $im;
    $w = ($w - 0.7) * 1700;
    if ($w < 0) return imagecolorallocate($im, 117,212,242);    
    if ($w < 200) return imagecolorallocate($im, 180,198,20);
    if ($w < 500) return imagecolorallocate($im, 199,210,26);
    if ($w < 1000) return imagecolorallocate($im, 254,231,115);
    if ($w < 2000) return imagecolorallocate($im, 249,209,49);
    if ($w < 3000) return imagecolorallocate($im, 236,168,32);
    if ($w < 4000) return imagecolorallocate($im, 176,141,119);
    if ($w < 5000) return imagecolorallocate($im, 199,176,162);
    if ($w < 6000) return imagecolorallocate($im, 224,208,195);
    return imagecolorallocate($im, 255, 255, 255);
}

function drawPoint($x, $y, $s, $step) {
    global $im,$matrix,$smooth,$zoom;
    $points = [];

    if ($step == 'square') {
        if (is_numeric($matrix[toI($x-$s, $y-$s)])) 
            array_push($points,$matrix[toI($x-$s, $y-$s)]);
        if (is_numeric($matrix[toI($x+$s, $y-$s)])) 
            array_push($points,$matrix[toI($x+$s, $y-$s)]);
        if (is_numeric($matrix[toI($x-$s, $y+$s)])) 
            array_push($points,$matrix[toI($x-$s, $y+$s)]);
        if (is_numeric($matrix[toI($x+$s, $y+$s)])) 
            array_push($points,$matrix[toI($x+$s, $y+$s)]);
    } else {
        if (is_numeric($matrix[toI($x-$s, $y)])) 
            array_push($points,$matrix[toI($x-$s, $y)]);
        if (is_numeric($matrix[toI($x+$s, $y)])) 
            array_push($points,$matrix[toI($x+$s, $y)]);
        if (is_numeric($matrix[toI($x, $y-$s)])) 
            array_push($points,$matrix[toI($x, $y-$s)]);
        if (is_numeric($matrix[toI($x, $y+$s)]))
            array_push($points,$matrix[toI($x, $y+$s)]);
    }
    $sum = array_sum($points);
    if($points)
     $avg = $sum / count($points);
    else 
     $avg = 0;

    $matrix[toI($x, $y)] = $weight = $avg + rand(0, $s*$smooth*100)/100;
    
    $color = genColor($weight);
    
    if ($dim == '2d') {
      imagefilledrectangle ( $im , $x * $zoom, $y * $zoom, ($x+1) * $zoom, ($y+1) * $zoom , $color );
    } else {  
        imagefilledrectangle ( $im , $x * $zoom, $y * $zoom, $x * $zoom+($x+1) * $zoom, $y * $zoom+($y+1) * $zoom , $color );
    }
}

$s = $size;
while($s > 1)
{ 
    $s = ceil($s / 2);    
    
    for($x=0; $x<$size; $x+=$s*2) {
        for($y=0; $y<$size; $y+=$s*2) {
            drawPoint($x+$s, $y+$s, $s, 'square');
        }
    }
    
    for($x=0; $x<$size; $x+=$s*2) {
        for($y=0; $y<$size; $y+=$s*2) {
            drawPoint($x+$s, $y, $s, 'diamond');
            drawPoint($x+$s, $y+$s*2, $s, 'diamond');
            drawPoint($x, $y+$s, $s, 'diamond');
            drawPoint($x+$s*2, $y+$s, $s, 'diamond');
        }
    }    
}
imagepng($im);
imagedestroy($im);
Добавить комментарий