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;
use pocketmine\level\biome\Biome;
use pocketmine\level\biome\UnknownBiome;
use pocketmine\level\generator\noise\Simplex;
use pocketmine\utils\Random;
class BiomeSelector{
/** @var Biome */
private $fallback;
abstract class BiomeSelector{
/** @var Simplex */
private $temperature;
/** @var Simplex */
private $rainfall;
/** @var Biome[] */
private $biomes = [];
/** @var \SplFixedArray */
/** @var Biome[]|\SplFixedArray */
private $map = null;
/** @var callable */
private $lookup;
public function __construct(Random $random, callable $lookup, Biome $fallback){
$this->fallback = $fallback;
$this->lookup = $lookup;
public function __construct(Random $random){
$this->temperature = 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(){
$this->map = new \SplFixedArray(64 * 64);
for($i = 0; $i < 64; ++$i){
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){
return ($this->temperature->noise2D($x, $z, true) + 1) / 2;
}
@ -85,7 +84,6 @@ class BiomeSelector{
$temperature = (int) ($this->getTemperature($x, $z) * 63);
$rainfall = (int) ($this->getRainfall($x, $z) * 63);
$biomeId = $this->map[$temperature + ($rainfall << 6)];
return $this->biomes[$biomeId] ?? $this->fallback;
return $this->map[$temperature + ($rainfall << 6)];
}
}

View File

@ -107,56 +107,46 @@ class Normal extends Generator{
$this->random->setSeed($this->level->getSeed());
$this->noiseBase = new Simplex($this->random, 4, 1 / 4, 1 / 32);
$this->random->setSeed($this->level->getSeed());
$this->selector = new BiomeSelector($this->random, function($temperature, $rainfall){
if($rainfall < 0.25){
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.
$this->selector = new class($this->random) extends BiomeSelector{
protected function lookup(float $temperature, float $rainfall) : int{
if($rainfall < 0.25){
return Biome::MOUNTAINS;
}elseif($rainfall < 0.70){
return Biome::SMALL_MOUNTAINS;
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{
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();