From a76be6cf38894acb001f436f07452ad97a75d29b Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Date: Wed, 7 Jan 2015 04:07:11 +0100 Subject: [PATCH] First biome based generator iteration --- src/pocketmine/Server.php | 2 +- src/pocketmine/level/Level.php | 2 +- .../generator/GenerationChunkManager.php | 2 +- .../generator/GenerationLevelManager.php | 22 +++ .../level/generator/GenerationManager.php | 4 +- src/pocketmine/level/generator/Generator.php | 156 +++++++++++++++++- src/pocketmine/level/generator/Normal.php | 90 +++++----- .../noise/{Generator.php => Noise.php} | 42 ++++- .../level/generator/noise/Perlin.php | 5 +- .../level/generator/noise/Simplex.php | 4 +- src/pocketmine/utils/Random.php | 2 +- 11 files changed, 274 insertions(+), 57 deletions(-) rename src/pocketmine/level/generator/noise/{Generator.php => Noise.php} (64%) diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index e246cdf4b..69da40c92 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -1120,7 +1120,7 @@ class Server{ * * @param string $name * @param int $seed - * @param string $generator Class name that extends pocketmine\level\generator\Generator + * @param string $generator Class name that extends pocketmine\level\generator\Noise * @param array $options * * @return bool diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 38cadbb81..1297cb95e 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -2117,7 +2117,7 @@ class Level implements ChunkManager, Metadatable{ * @return bool|Position */ public function getSafeSpawn($spawn = null){ - if(!($spawn instanceof Vector3)){ + if(!($spawn instanceof Vector3) or $spawn->y <= 0){ $spawn = $this->getSpawnLocation(); } if($spawn instanceof Vector3){ diff --git a/src/pocketmine/level/generator/GenerationChunkManager.php b/src/pocketmine/level/generator/GenerationChunkManager.php index e9c1d3c0a..3e5063a92 100644 --- a/src/pocketmine/level/generator/GenerationChunkManager.php +++ b/src/pocketmine/level/generator/GenerationChunkManager.php @@ -46,7 +46,7 @@ class GenerationChunkManager implements ChunkManager{ public function __construct(GenerationManager $manager, $levelID, $seed, $class, array $options){ if(!class_exists($class, true) or !is_subclass_of($class, Generator::class)){ - throw new \InvalidStateException("Class $class does not exists or is not a subclass of Generator"); + throw new \InvalidStateException("Class $class does not exists or is not a subclass of Noise"); } $this->levelID = $levelID; diff --git a/src/pocketmine/level/generator/GenerationLevelManager.php b/src/pocketmine/level/generator/GenerationLevelManager.php index bdb5f6271..c7e8cb249 100644 --- a/src/pocketmine/level/generator/GenerationLevelManager.php +++ b/src/pocketmine/level/generator/GenerationLevelManager.php @@ -41,6 +41,10 @@ class GenerationLevelManager extends GenerationManager{ protected $maxCount; + protected $splitCount; + + protected $count; + /** * @param Server $server * @param GenerationInstanceManager $manager @@ -49,6 +53,15 @@ class GenerationLevelManager extends GenerationManager{ $this->server = $server; $this->manager = $manager; $this->maxCount = $this->server->getProperty("chunk-generation.per-tick", 1); + + if($this->maxCount < 1){ + $this->splitCount = $this->maxCount; + $this->maxCount = 1; + }else{ + $this->splitCount = 1; + } + + $this->count = 0; } public function openLevel($levelID, $seed, $class, array $options){ @@ -74,6 +87,15 @@ class GenerationLevelManager extends GenerationManager{ public function process(){ if(count($this->requestQueue) > 0){ + if($this->splitCount < 1){ + $this->count += $this->splitCount; + if($this->count < 1){ + return; + }else{ + $this->count = 0; + } + } + $count = 0; foreach($this->requestQueue as $levelID => $chunks){ if($count >= $this->maxCount){ diff --git a/src/pocketmine/level/generator/GenerationManager.php b/src/pocketmine/level/generator/GenerationManager.php index 692152959..8ea4ec089 100644 --- a/src/pocketmine/level/generator/GenerationManager.php +++ b/src/pocketmine/level/generator/GenerationManager.php @@ -71,7 +71,7 @@ class GenerationManager{ * byte[] payload: * int32 levelID * int32 seed - * string class that extends pocketmine\level\generator\Generator + * string class that extends pocketmine\level\generator\Noise * byte[] serialized options array */ const PACKET_OPEN_LEVEL = 0x03; @@ -136,7 +136,7 @@ class GenerationManager{ $this->readPacket(); } }catch(\Exception $e){ - $this->logger->warning("[Generator Thread] Exception: " . $e->getMessage() . " on file \"" . $e->getFile() . "\" line " . $e->getLine()); + $this->logger->warning("[Noise Thread] Exception: " . $e->getMessage() . " on file \"" . $e->getFile() . "\" line " . $e->getLine()); } } } diff --git a/src/pocketmine/level/generator/Generator.php b/src/pocketmine/level/generator/Generator.php index d151782c4..2da87339c 100644 --- a/src/pocketmine/level/generator/Generator.php +++ b/src/pocketmine/level/generator/Generator.php @@ -20,10 +20,11 @@ */ /** - * Generator classes used in Levels + * Noise classes used in Levels */ namespace pocketmine\level\generator; +use pocketmine\level\generator\noise\Noise; use pocketmine\utils\Random; abstract class Generator{ @@ -57,6 +58,159 @@ abstract class Generator{ return "unknown"; } + /** + * @param Noise $noise + * @param int $xSize + * @param int $samplingRate + * @param int $x + * @param int $y + * @param int $z + * + * @return \SplFixedArray + */ + public static function getFastNoise1D(Noise $noise, $xSize, $samplingRate, $x, $y, $z){ + if($samplingRate === 0){ + throw new \InvalidArgumentException("samplingRate cannot be 0"); + } + if ($xSize % $samplingRate !== 0) { + throw new \InvalidArgumentCountException("xSize % samplingRate must return 0"); + } + + $noiseArray = new \SplFixedArray($xSize + 1); + + for($xx = 0; $xx <= $xSize; $xx += $samplingRate){ + $noiseArray[$xx] = $noise->noise3D($xx + $x, $y, $z); + } + + for($xx = 0; $xx < $xSize; ++$xx){ + if($xx % $samplingRate !== 0){ + $nx = (int) ($xx / $samplingRate) * $samplingRate; + $noiseArray[$xx] = Noise::linearLerp($xx, $nx, $nx + $samplingRate, $noiseArray[$nx], $noiseArray[$nx + $samplingRate]); + } + } + + return $noiseArray; + } + + /** + * @param Noise $noise + * @param int $xSize + * @param int $zSize + * @param int $samplingRate + * @param int $x + * @param int $y + * @param int $z + * + * @return \SplFixedArray + */ + public static function getFastNoise2D(Noise $noise, $xSize, $zSize, $samplingRate, $x, $y, $z){ + if($samplingRate === 0){ + throw new \InvalidArgumentException("samplingRate cannot be 0"); + } + if ($xSize % $samplingRate !== 0) { + throw new \InvalidArgumentCountException("xSize % samplingRate must return 0"); + } + if ($zSize % $samplingRate !== 0) { + throw new \InvalidArgumentCountException("zSize % samplingRate must return 0"); + } + + $noiseArray = new \SplFixedArray($xSize + 1); + + for($xx = 0; $xx <= $xSize; $xx += $samplingRate){ + $noiseArray[$xx] = new \SplFixedArray($zSize + 1); + for($zz = 0; $zz <= $zSize; $zz += $samplingRate){ + $noiseArray[$xx][$zz] = $noise->noise3D($x + $xx, $y, $z + $zz); + } + } + + for($xx = 0; $xx < $xSize; ++$xx){ + if($xx % $samplingRate !== 0){ + $noiseArray[$xx] = new \SplFixedArray($zSize + 1); + } + + for($zz = 0; $zz < $zSize; ++$zz){ + if($xx % $samplingRate !== 0 or $zz % $samplingRate !== 0){ + $nx = (int) ($xx / $samplingRate) * $samplingRate; + $nz = (int) ($zz / $samplingRate) * $samplingRate; + $noiseArray[$xx][$zz] = Noise::bilinearLerp( + $xx, $zz, $noiseArray[$nx][$nz], $noiseArray[$nx][$nz + $samplingRate], + $noiseArray[$nx + $samplingRate][$nz], $noiseArray[$nx + $samplingRate][$nz + $samplingRate], + $nx, $nx + $samplingRate, $nz, $nz + $samplingRate + ); + } + } + } + + return $noiseArray; + } + + /** + * @param Noise $noise + * @param int $xSize + * @param int $ySize + * @param int $zSize + * @param int $xSamplingRate + * @param int $ySamplingRate + * @param int $zSamplingRate + * @param int $x + * @param int $y + * @param int $z + * + * @return \SplFixedArray + */ + public static function getFastNoise3D(Noise $noise, $xSize, $ySize, $zSize, $xSamplingRate, $ySamplingRate, $zSamplingRate, $x, $y, $z){ + if($xSamplingRate === 0){ + throw new \InvalidArgumentException("xSamplingRate cannot be 0"); + } + if($zSamplingRate === 0){ + throw new \InvalidArgumentException("zSamplingRate cannot be 0"); + } + if($ySamplingRate === 0){ + throw new \InvalidArgumentException("ySamplingRate cannot be 0"); + } + if ($xSize % $xSamplingRate !== 0) { + throw new \InvalidArgumentCountException("xSize % xSamplingRate must return 0"); + } + if ($zSize % $zSamplingRate !== 0) { + throw new \InvalidArgumentCountException("zSize % zSamplingRate must return 0"); + } + if ($ySize % $ySamplingRate !== 0) { + throw new \InvalidArgumentCountException("ySize % ySamplingRate must return 0"); + } + + $noiseArray = array_fill(0, $xSize + 1, array_fill(0, $zSize + 1, [])); + + for($xx = 0; $xx <= $xSize; $xx += $xSamplingRate){ + for($zz = 0; $zz <= $zSize; $zz += $zSamplingRate){ + for($yy = 0; $yy <= $ySize; $yy += $ySamplingRate){ + $noiseArray[$xx][$zz][$yy] = $noise->noise3D($x + $xx, $y + $yy, $z + $zz); + } + } + } + + for($xx = 0; $xx < $xSize; ++$xx){ + for($zz = 0; $zz < $zSize; ++$zz){ + for($yy = 0; $yy < $ySize; ++$yy){ + if($xx % $xSamplingRate !== 0 or $zz % $zSamplingRate !== 0 or $yy % $ySamplingRate !== 0){ + $nx = (int) ($xx / $xSamplingRate) * $xSamplingRate; + $nz = (int) ($zz / $zSamplingRate) * $zSamplingRate; + $ny = (int) ($yy / $ySamplingRate) * $ySamplingRate; + $noiseArray[$xx][$zz][$yy] = Noise::trilinearLerp( + $xx, $yy, $zz, $noiseArray[$nx][$nz][$ny], $noiseArray[$nx][$nz][$ny + $ySamplingRate], + $noiseArray[$nx][$nz + $zSamplingRate][$ny], $noiseArray[$nx][$nz + $zSamplingRate][$ny + $ySamplingRate], + $noiseArray[$nx + $xSamplingRate][$nz][$ny], $noiseArray[$nx + $xSamplingRate][$nz][$ny + $ySamplingRate], + $noiseArray[$nx + $xSamplingRate][$nz + $zSamplingRate][$ny], + $noiseArray[$nx + $xSamplingRate][$nz + $zSamplingRate][$ny + $ySamplingRate], + $nx, $nx + $zSamplingRate, $ny, $ny + $ySamplingRate, $nz, $nz + $zSamplingRate + ); + } + } + } + } + + return $noiseArray; + } + public abstract function __construct(array $settings = []); public abstract function init(GenerationChunkManager $level, Random $random); diff --git a/src/pocketmine/level/generator/Normal.php b/src/pocketmine/level/generator/Normal.php index d48949203..c24da6988 100644 --- a/src/pocketmine/level/generator/Normal.php +++ b/src/pocketmine/level/generator/Normal.php @@ -30,6 +30,7 @@ use pocketmine\block\Gravel; use pocketmine\block\IronOre; use pocketmine\block\LapisOre; use pocketmine\block\RedstoneOre; +use pocketmine\level\generator\noise\Perlin; use pocketmine\level\generator\noise\Simplex; use pocketmine\level\generator\object\OreType; use pocketmine\level\generator\populator\Ore; @@ -47,15 +48,31 @@ class Normal extends Generator{ private $level; /** @var Random */ private $random; - private $worldHeight = 65; - private $waterHeight = 63; - /** @var Simplex */ - private $noiseHills; + private $waterHeight = 62; + private $bedrockDepth = 5; /** @var Simplex */ private $noiseBase; - public function __construct(array $options = []){ + private static $GAUSSIAN_KERNEL = null; + private static $SMOOTH_SIZE = 2; + public function __construct(array $options = []){ + if(self::$GAUSSIAN_KERNEL === null){ + self::generateKernel(); + } + } + + private static function generateKernel(){ + self::$GAUSSIAN_KERNEL = []; + $bellSize = 1 / self::$SMOOTH_SIZE; + $bellHeight = 2 * self::$SMOOTH_SIZE; + for($sx = -self::$SMOOTH_SIZE; $sx <= self::$SMOOTH_SIZE; ++$sx) { + for($sz = -self::$SMOOTH_SIZE; $sz <= self::$SMOOTH_SIZE; ++$sz) { + $bx = $bellSize * $sx; + $bz = $bellSize * $sz; + self::$GAUSSIAN_KERNEL[$sx + self::$SMOOTH_SIZE][$sz + self::$SMOOTH_SIZE] = $bellHeight * exp(-($bx * $bx + $bz * $bz) / 2); + } + } } public function getName(){ @@ -70,8 +87,7 @@ class Normal extends Generator{ $this->level = $level; $this->random = $random; $this->random->setSeed($this->level->getSeed()); - $this->noiseHills = new Simplex($this->random, 3, 0.1, 12); - $this->noiseBase = new Simplex($this->random, 16, 0.6, 16); + $this->noiseBase = new Simplex($this->random, 16, 0.012, 0.5, 2); $ores = new Ore(); @@ -100,52 +116,48 @@ class Normal extends Generator{ public function generateChunk($chunkX, $chunkZ){ $this->random->setSeed(0xdeadbeef ^ ($chunkX << 8) ^ $chunkZ ^ $this->level->getSeed()); - $hills = []; - $base = []; - for($z = 0; $z < 16; ++$z){ - for($x = 0; $x < 16; ++$x){ - $i = ($z << 4) + $x; - $hills[$i] = $this->noiseHills->noise2D($x + ($chunkX << 4), $z + ($chunkZ << 4), true); - $base[$i] = $this->noiseBase->noise2D($x + ($chunkX << 4), $z + ($chunkZ << 4), true); + //$hills = []; + //$base = []; - if($base[$i] < 0){ - $base[$i] *= 0.5; - } - } - } + $start = microtime(true); + + $noise = Generator::getFastNoise3D($this->noiseBase, 16, 128, 16, 4, 16, 4, $chunkX * 16, 0, $chunkZ * 16); $chunk = $this->level->getChunk($chunkX, $chunkZ); - for($z = 0; $z < 16; ++$z){ - for($x = 0; $x < 16; ++$x){ - $i = ($z << 4) + $x; - $height = $this->worldHeight + $hills[$i] * 14 + $base[$i] * 7; - $height = (int) $height; + var_dump((microtime(true) - $start) * 1000); + + for($x = 0; $x < 16; ++$x){ + for($z = 0; $z < 16; ++$z){ + $minSum = 0; + $maxSum = 0; + $weightSum = 0; + + //TODO: biome things + $minSum = 63; + $maxSum = 127; + $weightSum += 1; + + $minElevation = $minSum / $weightSum; + $smoothHeight = ($maxSum / $weightSum - $minElevation) / 2; for($y = 0; $y < 128; ++$y){ - $diff = $height - $y; - if($y <= 4 and ($y === 0 or $this->random->nextFloat() < 0.75)){ + if($y === 0){ $chunk->setBlockId($x, $y, $z, Block::BEDROCK); - }elseif($diff > 2){ + continue; + } + $noiseValue = $noise[$x][$z][$y] - 1 / $smoothHeight * ($y - $smoothHeight - $minElevation); + + if($noiseValue >= 0){ $chunk->setBlockId($x, $y, $z, Block::STONE); - }elseif($diff > 0){ - $chunk->setBlockId($x, $y, $z, Block::DIRT); - }elseif($y <= $this->waterHeight){ - if(($this->waterHeight - $y) <= 1 and $diff === 0){ - $chunk->setBlockId($x, $y, $z, Block::SAND); - }elseif($diff === 0){ - $chunk->setBlockId($x, $y, $z, Block::DIRT); - }else{ + }else{ + if($y <= $this->waterHeight){ $chunk->setBlockId($x, $y, $z, Block::STILL_WATER); } - }elseif($diff === 0){ - $chunk->setBlockId($x, $y, $z, Block::GRASS); } } - } } - } public function populateChunk($chunkX, $chunkZ){ diff --git a/src/pocketmine/level/generator/noise/Generator.php b/src/pocketmine/level/generator/noise/Noise.php similarity index 64% rename from src/pocketmine/level/generator/noise/Generator.php rename to src/pocketmine/level/generator/noise/Noise.php index 47c848bc5..1d0fab140 100644 --- a/src/pocketmine/level/generator/noise/Generator.php +++ b/src/pocketmine/level/generator/noise/Noise.php @@ -28,13 +28,14 @@ namespace pocketmine\level\generator\noise; -abstract class Generator{ +abstract class Noise{ protected $perm = []; protected $offsetX = 0; protected $offsetY = 0; protected $offsetZ = 0; protected $octaves = 8; protected $frequency; + protected $lacunarity; protected $amplitude; public static function floor($x){ @@ -49,6 +50,26 @@ abstract class Generator{ return $y + $x * ($z - $y); } + public static function linearLerp($x, $x1, $x2, $q0, $q1){ + return (($x2 - $x) / ($x2 - $x1)) * $q0 + (($x - $x1) / ($x2 - $x1)) * $q1; + } + + public static function bilinearLerp($x, $y, $q00, $q01, $q10, $q11, $x1, $x2, $y1, $y2){ + $q0 = self::linearLerp($x, $x1, $x2, $q00, $q10); + $q1 = self::linearLerp($x, $x1, $x2, $q01, $q11); + return self::linearLerp($y, $y1, $y2, $q0, $q1); + } + + public static function trilinearLerp($x, $y, $z, $q000, $q001, $q010, $q011, $q100, $q101, $q110, $q111, $x1, $x2, $y1, $y2, $z1, $z2) { + $q00 = self::linearLerp($x, $x1, $x2, $q000, $q100); + $q01 = self::linearLerp($x, $x1, $x2, $q010, $q110); + $q10 = self::linearLerp($x, $x1, $x2, $q001, $q101); + $q11 = self::linearLerp($x, $x1, $x2, $q011, $q111); + $q0 = self::linearLerp($y, $y1, $y2, $q00, $q10); + $q1 = self::linearLerp($y, $y1, $y2, $q01, $q11); + return self::linearLerp($z, $z1, $z2, $q0, $q1); + } + public static function grad($hash, $x, $y, $z){ $hash &= 15; $u = $hash < 8 ? $x : $y; @@ -64,13 +85,16 @@ abstract class Generator{ public function noise2D($x, $z, $normalized = false){ $result = 0; $amp = 1; - $freq = 1; + $laq = 1; $max = 0; + $x *= $this->frequency; + $z *= $this->frequency; + for($i = 0; $i < $this->octaves; ++$i){ - $result += $this->getNoise2D($x * $freq, $z * $freq) * $amp; + $result += $this->getNoise2D($x * $laq, $z * $laq) * $amp; $max += $amp; - $freq *= $this->frequency; + $laq *= $this->lacunarity; $amp *= $this->amplitude; } if($normalized === true){ @@ -83,13 +107,17 @@ abstract class Generator{ public function noise3D($x, $y, $z, $normalized = false){ $result = 0; $amp = 1; - $freq = 1; + $laq = 1; $max = 0; + $x *= $this->frequency; + $y *= $this->frequency; + $z *= $this->frequency; + for($i = 0; $i < $this->octaves; ++$i){ - $result += $this->getNoise3D($x * $freq, $y * $freq, $z * $freq) * $amp; + $result += $this->getNoise3D($x * $laq, $y * $laq, $z * $laq) * $amp; $max += $amp; - $freq *= $this->frequency; + $laq *= $this->lacunarity; $amp *= $this->amplitude; } if($normalized === true){ diff --git a/src/pocketmine/level/generator/noise/Perlin.php b/src/pocketmine/level/generator/noise/Perlin.php index 1b7c653e7..034953f2b 100644 --- a/src/pocketmine/level/generator/noise/Perlin.php +++ b/src/pocketmine/level/generator/noise/Perlin.php @@ -23,7 +23,7 @@ namespace pocketmine\level\generator\noise; use pocketmine\utils\Random; -class Perlin extends Generator{ +class Perlin extends Noise{ public static $grad3 = [ [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0], [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1], @@ -31,9 +31,10 @@ class Perlin extends Generator{ ]; - public function __construct(Random $random, $octaves, $frequency, $amplitude){ + public function __construct(Random $random, $octaves, $frequency, $amplitude, $lacunarity){ $this->octaves = $octaves; $this->frequency = $frequency; + $this->lacunarity = $lacunarity; $this->amplitude = $amplitude; $this->offsetX = $random->nextFloat() * 256; $this->offsetY = $random->nextFloat() * 256; diff --git a/src/pocketmine/level/generator/noise/Simplex.php b/src/pocketmine/level/generator/noise/Simplex.php index 8cda255c8..421137490 100644 --- a/src/pocketmine/level/generator/noise/Simplex.php +++ b/src/pocketmine/level/generator/noise/Simplex.php @@ -63,8 +63,8 @@ class Simplex extends Perlin{ protected $offsetW; - public function __construct(Random $random, $octaves, $frequency, $amplitude){ - parent::__construct($random, $octaves, $frequency, $amplitude); + public function __construct(Random $random, $octaves, $frequency, $amplitude, $lacunarity){ + parent::__construct($random, $octaves, $frequency, $amplitude, $lacunarity); $this->offsetW = $random->nextFloat() * 256; self::$SQRT_3 = sqrt(3); self::$SQRT_5 = sqrt(5); diff --git a/src/pocketmine/utils/Random.php b/src/pocketmine/utils/Random.php index f47fb5c4e..fe2b0cf8c 100644 --- a/src/pocketmine/utils/Random.php +++ b/src/pocketmine/utils/Random.php @@ -23,7 +23,7 @@ namespace pocketmine\utils; /** - * Unsecure Random Number Generator, used for fast seeded values + * Unsecure Random Number Noise, used for fast seeded values * WARNING: This class is available on the PocketMine-MP Zephir project. * If this class is modified, remember to modify the PHP C extension. */