BiomeSelector: drastically simplified implementation and made more robust

- Doesn't need to be pre-populated with biomes prior to calculating the heatmap - now population of biomes is entirely dependent on the lookup function, improving consistency
- Uses an abstract class method for lookup instead of callback (use anonymous class instead)
- Faster because the heatmap is directly populated with biomes instead of biome IDs, removing an unnecessary lookup.
This commit is contained in:
Dylan K. Taylor 2018-06-08 10:13:29 +01:00
parent 4e3e807741
commit 78ec3937bf
2 changed files with 56 additions and 68 deletions

View File

@ -24,49 +24,48 @@ declare(strict_types=1);
namespace pocketmine\level\generator\biome; namespace pocketmine\level\generator\biome;
use pocketmine\level\biome\Biome; use pocketmine\level\biome\Biome;
use pocketmine\level\biome\UnknownBiome;
use pocketmine\level\generator\noise\Simplex; use pocketmine\level\generator\noise\Simplex;
use pocketmine\utils\Random; use pocketmine\utils\Random;
class BiomeSelector{ abstract class BiomeSelector{
/** @var Biome */
private $fallback;
/** @var Simplex */ /** @var Simplex */
private $temperature; private $temperature;
/** @var Simplex */ /** @var Simplex */
private $rainfall; private $rainfall;
/** @var Biome[] */ /** @var Biome[]|\SplFixedArray */
private $biomes = [];
/** @var \SplFixedArray */
private $map = null; private $map = null;
/** @var callable */ public function __construct(Random $random){
private $lookup;
public function __construct(Random $random, callable $lookup, Biome $fallback){
$this->fallback = $fallback;
$this->lookup = $lookup;
$this->temperature = new Simplex($random, 2, 1 / 16, 1 / 512); $this->temperature = new Simplex($random, 2, 1 / 16, 1 / 512);
$this->rainfall = new Simplex($random, 2, 1 / 16, 1 / 512); $this->rainfall = new Simplex($random, 2, 1 / 16, 1 / 512);
} }
/**
* Lookup function called by recalculate() to determine the biome to use for this temperature and rainfall.
*
* @param float $temperature
* @param float $rainfall
*
* @return int biome ID 0-255
*/
abstract protected function lookup(float $temperature, float $rainfall) : int;
public function recalculate(){ public function recalculate(){
$this->map = new \SplFixedArray(64 * 64); $this->map = new \SplFixedArray(64 * 64);
for($i = 0; $i < 64; ++$i){ for($i = 0; $i < 64; ++$i){
for($j = 0; $j < 64; ++$j){ for($j = 0; $j < 64; ++$j){
$this->map[$i + ($j << 6)] = call_user_func($this->lookup, $i / 63, $j / 63); $biome = Biome::getBiome($this->lookup($i / 63, $j / 63));
if($biome instanceof UnknownBiome){
throw new \RuntimeException("Unknown biome returned by selector with ID " . $biome->getId());
}
$this->map[$i + ($j << 6)] = $biome;
} }
} }
} }
public function addBiome(Biome $biome){
$this->biomes[$biome->getId()] = $biome;
}
public function getTemperature($x, $z){ public function getTemperature($x, $z){
return ($this->temperature->noise2D($x, $z, true) + 1) / 2; return ($this->temperature->noise2D($x, $z, true) + 1) / 2;
} }
@ -85,7 +84,6 @@ class BiomeSelector{
$temperature = (int) ($this->getTemperature($x, $z) * 63); $temperature = (int) ($this->getTemperature($x, $z) * 63);
$rainfall = (int) ($this->getRainfall($x, $z) * 63); $rainfall = (int) ($this->getRainfall($x, $z) * 63);
$biomeId = $this->map[$temperature + ($rainfall << 6)]; return $this->map[$temperature + ($rainfall << 6)];
return $this->biomes[$biomeId] ?? $this->fallback;
} }
} }

View File

@ -107,56 +107,46 @@ class Normal extends Generator{
$this->random->setSeed($this->level->getSeed()); $this->random->setSeed($this->level->getSeed());
$this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 32); $this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 32);
$this->random->setSeed($this->level->getSeed()); $this->random->setSeed($this->level->getSeed());
$this->selector = new BiomeSelector($this->random, function($temperature, $rainfall){ $this->selector = new class($this->random) extends BiomeSelector{
if($rainfall < 0.25){ protected function lookup(float $temperature, float $rainfall) : int{
if($temperature < 0.7){
return Biome::OCEAN;
}elseif($temperature < 0.85){
return Biome::RIVER;
}else{
return Biome::SWAMP;
}
}elseif($rainfall < 0.60){
if($temperature < 0.25){
return Biome::ICE_PLAINS;
}elseif($temperature < 0.75){
return Biome::PLAINS;
}else{
return Biome::DESERT;
}
}elseif($rainfall < 0.80){
if($temperature < 0.25){
return Biome::TAIGA;
}elseif($temperature < 0.75){
return Biome::FOREST;
}else{
return Biome::BIRCH_FOREST;
}
}else{
//FIXME: This will always cause River to be used since the rainfall is always greater than 0.8 if we
//reached this branch. However I don't think that substituting temperature for rainfall is correct given
//that mountain biomes are supposed to be pretty cold.
if($rainfall < 0.25){ if($rainfall < 0.25){
return Biome::MOUNTAINS; if($temperature < 0.7){
}elseif($rainfall < 0.70){ return Biome::OCEAN;
return Biome::SMALL_MOUNTAINS; }elseif($temperature < 0.85){
return Biome::RIVER;
}else{
return Biome::SWAMP;
}
}elseif($rainfall < 0.60){
if($temperature < 0.25){
return Biome::ICE_PLAINS;
}elseif($temperature < 0.75){
return Biome::PLAINS;
}else{
return Biome::DESERT;
}
}elseif($rainfall < 0.80){
if($temperature < 0.25){
return Biome::TAIGA;
}elseif($temperature < 0.75){
return Biome::FOREST;
}else{
return Biome::BIRCH_FOREST;
}
}else{ }else{
return Biome::RIVER; //FIXME: This will always cause River to be used since the rainfall is always greater than 0.8 if we
//reached this branch. However I don't think that substituting temperature for rainfall is correct given
//that mountain biomes are supposed to be pretty cold.
if($rainfall < 0.25){
return Biome::MOUNTAINS;
}elseif($rainfall < 0.70){
return Biome::SMALL_MOUNTAINS;
}else{
return Biome::RIVER;
}
} }
} }
}, Biome::getBiome(Biome::OCEAN)); };
$this->selector->addBiome(Biome::getBiome(Biome::OCEAN));
$this->selector->addBiome(Biome::getBiome(Biome::PLAINS));
$this->selector->addBiome(Biome::getBiome(Biome::DESERT));
$this->selector->addBiome(Biome::getBiome(Biome::MOUNTAINS));
$this->selector->addBiome(Biome::getBiome(Biome::FOREST));
$this->selector->addBiome(Biome::getBiome(Biome::TAIGA));
$this->selector->addBiome(Biome::getBiome(Biome::SWAMP));
$this->selector->addBiome(Biome::getBiome(Biome::RIVER));
$this->selector->addBiome(Biome::getBiome(Biome::ICE_PLAINS));
$this->selector->addBiome(Biome::getBiome(Biome::SMALL_MOUNTAINS));
$this->selector->addBiome(Biome::getBiome(Biome::BIRCH_FOREST));
$this->selector->recalculate(); $this->selector->recalculate();