diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 438d92973..790631cc5 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -54,11 +54,12 @@ use pocketmine\level\format\anvil\Anvil; use pocketmine\level\format\leveldb\LevelDB; use pocketmine\level\format\LevelProviderManager; use pocketmine\level\format\mcregion\McRegion; +use pocketmine\level\generator\biome\Biome; use pocketmine\level\generator\Flat; use pocketmine\level\generator\GenerationInstanceManager; use pocketmine\level\generator\GenerationRequestManager; use pocketmine\level\generator\Generator; -use pocketmine\level\generator\Normal; +use pocketmine\level\generator\normal\Normal; use pocketmine\level\Level; use pocketmine\metadata\EntityMetadataStore; use pocketmine\metadata\LevelMetadataStore; @@ -1127,7 +1128,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 @@ -1609,6 +1610,7 @@ class Server{ InventoryType::init(); Block::init(); Item::init(); + Biome::init(); /** TODO: @deprecated */ TextWrapper::init(); $this->craftingManager = new CraftingManager(); diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index c0e9ea29a..9f9e79f76 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -2160,7 +2160,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 0c0bd9feb..d25402a13 100644 --- a/src/pocketmine/level/generator/GenerationManager.php +++ b/src/pocketmine/level/generator/GenerationManager.php @@ -63,7 +63,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 = 0x02; @@ -128,7 +128,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..8e8a07ebd 100644 --- a/src/pocketmine/level/generator/Generator.php +++ b/src/pocketmine/level/generator/Generator.php @@ -20,10 +20,12 @@ */ /** - * Generator classes used in Levels + * Noise classes used in Levels */ namespace pocketmine\level\generator; +use pocketmine\level\generator\noise\Noise; +use pocketmine\level\generator\normal\Normal; use pocketmine\utils\Random; abstract class Generator{ @@ -57,6 +59,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][$nny = $ny + $ySamplingRate], + $noiseArray[$nx][$nnz = $nz + $zSamplingRate][$ny], $noiseArray[$nx][$nnz][$nny], + $noiseArray[$nnx = $nx + $xSamplingRate][$nz][$ny], $noiseArray[$nnx][$nz][$nny], + $noiseArray[$nnx][$nnz][$ny], + $noiseArray[$nnx][$nnz][$nny], + $nx, $nnx, $ny, $nny, $nz, $nnz + ); + } + } + } + } + + return $noiseArray; + } + public abstract function __construct(array $settings = []); public abstract function init(GenerationChunkManager $level, Random $random); diff --git a/src/pocketmine/level/generator/biome/Biome.php b/src/pocketmine/level/generator/biome/Biome.php new file mode 100644 index 000000000..a29b35b65 --- /dev/null +++ b/src/pocketmine/level/generator/biome/Biome.php @@ -0,0 +1,123 @@ +setId((int) $id); + } + + public static function init(){ + if(self::$setup === false){ + self::$setup = true; + self::register(self::OCEAN, new OceanBiome()); + self::register(self::PLAINS, new PlainBiome()); + self::register(self::DESERT, new DesertBiome()); + self::register(self::MOUNTAINS, new MountainsBiome()); + self::register(self::FOREST, new ForestBiome()); + + self::register(self::RIVER, new RiverBiome()); + + self::register(self::BEACH, new BeachBiome()); + + self::register(self::SMALL_MOUNTAINS, new SmallMountainsBiome()); + } + } + + /** + * @param $id + * + * @return Biome + */ + public static function getBiome($id){ + return isset(self::$biomes[$id]) ? self::$biomes[$id] : null; + } + + public function clearPopulators(){ + $this->populators = []; + } + + public function addPopulator(Populator $populator){ + $this->populators[] = $populator; + } + + public function populateChunk(ChunkManager $level, $chunkX, $chunkZ, Random $random){ + foreach($this->populators as $populator){ + $populator->populate($level, $chunkX, $chunkZ, $random); + } + } + + public function getPopulators(){ + return $this->populators; + } + + public function setId($id){ + if(!$this->registered){ + $this->registered = true; + $this->id = $id; + } + } + + public function getId(){ + return $this->id; + } + + public abstract function getName(); +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/biome/BiomeSelector.php b/src/pocketmine/level/generator/biome/BiomeSelector.php new file mode 100644 index 000000000..70ce44fd6 --- /dev/null +++ b/src/pocketmine/level/generator/biome/BiomeSelector.php @@ -0,0 +1,78 @@ +fallback = $fallback; + $this->temperature = new Simplex($random, 1, 0.004, 0.5, 2); + $this->rainfall = new Simplex($random, 1, 0.004, 0.5, 2); + } + + public function addBiome(Biome $biome, $start, $end){ + $this->biomes[$biome->getId()] = $biome; + $this->select[$biome->getId()] = [$biome->getId(), $start, $end]; + } + + /** + * @param $x + * @param $z + * + * @return Biome + */ + public function pickBiome($x, $z){ + //$temperature = $this->temperature->noise2D($x, $z); + $rainfall = $this->rainfall->noise2D($x, $z); + + if($rainfall > 0.9){ + return Biome::getBiome(Biome::OCEAN); + }elseif($rainfall > 0.7){ + return Biome::getBiome(Biome::RIVER); + }elseif($rainfall > 0.6){ + return Biome::getBiome(Biome::BEACH); + }elseif($rainfall > 0.2){ + return Biome::getBiome(Biome::PLAINS); + }elseif($rainfall > -0.3){ + return Biome::getBiome(Biome::FOREST); + }else{ + return Biome::getBiome(Biome::DESERT); + } + + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/noise/Generator.php b/src/pocketmine/level/generator/noise/Noise.php similarity index 62% rename from src/pocketmine/level/generator/noise/Generator.php rename to src/pocketmine/level/generator/noise/Noise.php index 47c848bc5..2c5a36483 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,42 @@ 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){ + $dx1 = (($x2 - $x) / ($x2 - $x1)); + $dx2 = (($x - $x1) / ($x2 - $x1)); + + return (($y2 - $y) / ($y2 - $y1)) * ( + $dx1 * $q00 + $dx2 * $q10 + ) + (($y - $y1) / ($y2 - $y1)) * ( + $dx1 * $q01 + $dx2 * $q11 + ); + } + + public static function trilinearLerp($x, $y, $z, $q000, $q001, $q010, $q011, $q100, $q101, $q110, $q111, $x1, $x2, $y1, $y2, $z1, $z2) { + $dx1 = (($x2 - $x) / ($x2 - $x1)); + $dx2 = (($x - $x1) / ($x2 - $x1)); + $dy1 = (($y2 - $y) / ($y2 - $y1)); + $dy2 = (($y - $y1) / ($y2 - $y1)); + + return (($z2 - $z) / ($z2 - $z1)) * ( + $dy1 * ( + $dx1 * $q000 + $dx2 * $q100 + ) + $dy2 * ( + $dx1 * $q001 + $dx2 * $q101 + ) + ) + (($z - $z1) / ($z2 - $z1)) * ( + $dy1 * ( + $dx1 * $q010 + $dx2 * $q110 + ) + $dy2 * ( + $dx1 * $q011 + $dx2 * $q111 + ) + ); + } + public static function grad($hash, $x, $y, $z){ $hash &= 15; $u = $hash < 8 ? $x : $y; @@ -64,13 +101,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 +123,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/level/generator/Normal.php b/src/pocketmine/level/generator/normal/Normal.php similarity index 55% rename from src/pocketmine/level/generator/Normal.php rename to src/pocketmine/level/generator/normal/Normal.php index d48949203..6bd5ab793 100644 --- a/src/pocketmine/level/generator/Normal.php +++ b/src/pocketmine/level/generator/normal/Normal.php @@ -19,7 +19,7 @@ * */ -namespace pocketmine\level\generator; +namespace pocketmine\level\generator\normal; use pocketmine\block\Block; use pocketmine\block\CoalOre; @@ -30,12 +30,19 @@ use pocketmine\block\Gravel; use pocketmine\block\IronOre; use pocketmine\block\LapisOre; use pocketmine\block\RedstoneOre; +use pocketmine\level\generator\biome\Biome; +use pocketmine\level\generator\biome\BiomeSelector; +use pocketmine\level\generator\GenerationChunkManager; +use pocketmine\level\generator\Generator; +use pocketmine\level\generator\noise\Perlin; use pocketmine\level\generator\noise\Simplex; +use pocketmine\level\generator\normal\biome\NormalBiome; use pocketmine\level\generator\object\OreType; use pocketmine\level\generator\populator\Ore; use pocketmine\level\generator\populator\Populator; use pocketmine\level\generator\populator\TallGrass; use pocketmine\level\generator\populator\Tree; +use pocketmine\level\Level; use pocketmine\math\Vector3 as Vector3; use pocketmine\utils\Random; @@ -47,15 +54,34 @@ 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 = []){ + /** @var BiomeSelector */ + private $selector; + 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 +96,9 @@ 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); + $this->random->setSeed($this->level->getSeed()); + $this->selector = new BiomeSelector($this->random, Biome::getBiome(Biome::OCEAN)); $ores = new Ore(); @@ -100,52 +127,63 @@ 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, 8, 8, 8, $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; + $biomeCache = []; - for($y = 0; $y < 128; ++$y){ - $diff = $height - $y; - if($y <= 4 and ($y === 0 or $this->random->nextFloat() < 0.75)){ - $chunk->setBlockId($x, $y, $z, Block::BEDROCK); - }elseif($diff > 2){ - $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); + for($x = 0; $x < 16; ++$x){ + for($z = 0; $z < 16; ++$z){ + $minSum = 0; + $maxSum = 0; + $weightSum = 0; + + for($sx = -self::$SMOOTH_SIZE; $sx <= self::$SMOOTH_SIZE; ++$sx){ + for($sz = -self::$SMOOTH_SIZE; $sz <= self::$SMOOTH_SIZE; ++$sz){ + if(isset($biomeCache[$index = (($chunkX * 16 + $x + $sx * 16) >> 2) . ":" . (($chunkZ * 16 + $z + $sz * 16) >> 2)])){ + $adjacent = $biomeCache[$index]; }else{ - $chunk->setBlockId($x, $y, $z, Block::STILL_WATER); + $adjacent = $this->selector->pickBiome($chunkX * 16 + $x + $sx * 16, $chunkZ * 16 + $z + $sz * 16); + $biomeCache[$index] = $adjacent; } - }elseif($diff === 0){ - $chunk->setBlockId($x, $y, $z, Block::GRASS); + /** @var NormalBiome $adjacent */ + $weight = self::$GAUSSIAN_KERNEL[$sx + self::$SMOOTH_SIZE][$sz + self::$SMOOTH_SIZE]; + $minSum += $adjacent->getMinElevation() * $weight; + $maxSum += $adjacent->getMaxElevation() * $weight; + $weightSum += $weight; } } + $minElevation = $minSum / $weightSum; + $smoothHeight = ($maxSum / $weightSum - $minElevation) / 2; + + for($y = 0; $y < 128; ++$y){ + if($y === 0){ + $chunk->setBlockId($x, $y, $z, Block::BEDROCK); + continue; + } + $noiseValue = $noise[$x][$z][$y] - 1 / $smoothHeight * ($y - $smoothHeight - $minElevation); + + if($noiseValue >= 0){ + $chunk->setBlockId($x, $y, $z, Block::STONE); + }else{ + if($y <= $this->waterHeight){ + $chunk->setBlockId($x, $y, $z, Block::STILL_WATER); + } + $chunk->setBlockSkyLight($x, $y, $z, 15); + } + } } } + + var_dump((microtime(true) - $start) * 1000); } public function populateChunk($chunkX, $chunkZ){ diff --git a/src/pocketmine/level/generator/normal/biome/BeachBiome.php b/src/pocketmine/level/generator/normal/biome/BeachBiome.php new file mode 100644 index 000000000..2c67883da --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/BeachBiome.php @@ -0,0 +1,35 @@ +setElevation(62, 65); + } + + public function getName(){ + return "Beach"; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/DesertBiome.php b/src/pocketmine/level/generator/normal/biome/DesertBiome.php new file mode 100644 index 000000000..180db1fa7 --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/DesertBiome.php @@ -0,0 +1,35 @@ +setElevation(63, 74); + } + + public function getName(){ + return "Desert"; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/ForestBiome.php b/src/pocketmine/level/generator/normal/biome/ForestBiome.php new file mode 100644 index 000000000..be18d7498 --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/ForestBiome.php @@ -0,0 +1,47 @@ +setBaseAmount(5); + $this->addPopulator($trees); + + $tallGrass = new TallGrass(); + $tallGrass->setBaseAmount(3); + + $this->addPopulator($tallGrass); + + $this->setElevation(63, 81); + } + + public function getName(){ + return "Forest"; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/GrassyBiome.php b/src/pocketmine/level/generator/normal/biome/GrassyBiome.php new file mode 100644 index 000000000..6431cd8a0 --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/GrassyBiome.php @@ -0,0 +1,37 @@ +setGroundCover([ + Block::get(Block::GRASS, 0), + Block::get(Block::DIRT, 0), + Block::get(Block::DIRT, 0), + Block::get(Block::DIRT, 0), + Block::get(Block::DIRT, 0), + ]); + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/MountainsBiome.php b/src/pocketmine/level/generator/normal/biome/MountainsBiome.php new file mode 100644 index 000000000..f0854ab1f --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/MountainsBiome.php @@ -0,0 +1,49 @@ +setBaseAmount(1); + $this->addPopulator($trees); + + $tallGrass = new TallGrass(); + $tallGrass->setBaseAmount(1); + + $this->addPopulator($tallGrass); + + //TODO: add emerald + + $this->setElevation(63, 127); + } + + public function getName(){ + return "Mountains"; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/NormalBiome.php b/src/pocketmine/level/generator/normal/biome/NormalBiome.php new file mode 100644 index 000000000..581bc6795 --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/NormalBiome.php @@ -0,0 +1,53 @@ +minElevation; + } + + public function getMaxElevation(){ + return $this->maxElevation; + } + + public function setElevation($min, $max){ + $this->minElevation = $min; + $this->maxElevation = $max; + } + + public function getGroundCover(){ + return $this->groundCover; + } + + public function setGroundCover(array $covers){ + $this->groundCover = $covers; + } +} diff --git a/src/pocketmine/level/generator/normal/biome/OceanBiome.php b/src/pocketmine/level/generator/normal/biome/OceanBiome.php new file mode 100644 index 000000000..92bb99d2a --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/OceanBiome.php @@ -0,0 +1,42 @@ +setBaseAmount(5); + + $this->addPopulator($tallGrass); + + $this->setElevation(46, 58); + } + + public function getName(){ + return "Ocean"; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/PlainBiome.php b/src/pocketmine/level/generator/normal/biome/PlainBiome.php new file mode 100644 index 000000000..e48a6eee8 --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/PlainBiome.php @@ -0,0 +1,42 @@ +setBaseAmount(5); + + $this->addPopulator($tallGrass); + + $this->setElevation(63, 74); + } + + public function getName(){ + return "Plains"; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/RiverBiome.php b/src/pocketmine/level/generator/normal/biome/RiverBiome.php new file mode 100644 index 000000000..fcedca423 --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/RiverBiome.php @@ -0,0 +1,42 @@ +setBaseAmount(5); + + $this->addPopulator($tallGrass); + + $this->setElevation(58, 62); + } + + public function getName(){ + return "River"; + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/SandyBiome.php b/src/pocketmine/level/generator/normal/biome/SandyBiome.php new file mode 100644 index 000000000..cb3b41021 --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/SandyBiome.php @@ -0,0 +1,36 @@ +setGroundCover([ + Block::get(Block::SAND, 0), + Block::get(Block::SANDSTONE, 0), + Block::get(Block::SANDSTONE, 0), + Block::get(Block::SANDSTONE, 0), + ]); + } +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/normal/biome/SmallMountainsBiome.php b/src/pocketmine/level/generator/normal/biome/SmallMountainsBiome.php new file mode 100644 index 000000000..9321d19a4 --- /dev/null +++ b/src/pocketmine/level/generator/normal/biome/SmallMountainsBiome.php @@ -0,0 +1,38 @@ +setElevation(63, 97); + } + + public function getName(){ + return "Small Mountains"; + } +} \ No newline at end of file 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. */