From dab73d895099e4c1c7d632711e204971cbbb0590 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 18 Apr 2017 13:05:01 +0100 Subject: [PATCH] Implemented sky light generation-time population and updating, obsolete and close #160 --- src/pocketmine/block/Block.php | 55 ++++-- src/pocketmine/block/Cobweb.php | 4 + src/pocketmine/block/Ice.php | 4 + src/pocketmine/block/Leaves.php | 4 + src/pocketmine/block/Transparent.php | 4 + src/pocketmine/block/Water.php | 4 + src/pocketmine/level/BlockLightUpdate.php | 34 ++++ src/pocketmine/level/Level.php | 171 +++++++++--------- src/pocketmine/level/LightUpdate.php | 161 +++++++++++++++++ src/pocketmine/level/SkyLightUpdate.php | 34 ++++ src/pocketmine/level/format/Chunk.php | 76 ++++---- src/pocketmine/level/format/SubChunk.php | 8 +- .../level/generator/PopulationTask.php | 12 +- 13 files changed, 417 insertions(+), 154 deletions(-) create mode 100644 src/pocketmine/level/BlockLightUpdate.php create mode 100644 src/pocketmine/level/LightUpdate.php create mode 100644 src/pocketmine/level/SkyLightUpdate.php diff --git a/src/pocketmine/block/Block.php b/src/pocketmine/block/Block.php index e73a9492f..f2feab3ef 100644 --- a/src/pocketmine/block/Block.php +++ b/src/pocketmine/block/Block.php @@ -54,6 +54,8 @@ class Block extends Position implements BlockIds, Metadatable{ public static $hardness = null; /** @var \SplFixedArray */ public static $transparent = null; + /** @var \SplFixedArray */ + public static $diffusesSkyLight = null; protected $id; protected $meta = 0; @@ -70,6 +72,7 @@ class Block extends Position implements BlockIds, Metadatable{ self::$solid = new \SplFixedArray(256); self::$hardness = new \SplFixedArray(256); self::$transparent = new \SplFixedArray(256); + self::$diffusesSkyLight = new \SplFixedArray(256); self::$list[self::AIR] = Air::class; self::$list[self::STONE] = Stone::class; self::$list[self::GRASS] = Grass::class; @@ -254,31 +257,20 @@ class Block extends Position implements BlockIds, Metadatable{ for($data = 0; $data < 16; ++$data){ self::$fullList[($id << 4) | $data] = new $class($data); } - - self::$solid[$id] = $block->isSolid(); - self::$transparent[$id] = $block->isTransparent(); - self::$hardness[$id] = $block->getHardness(); - self::$light[$id] = $block->getLightLevel(); - - if($block->isSolid()){ - if($block->isTransparent()){ - if($block instanceof Liquid or $block instanceof Ice){ - self::$lightFilter[$id] = 2; - }else{ - self::$lightFilter[$id] = 1; - } - }else{ - self::$lightFilter[$id] = 15; - } - }else{ - self::$lightFilter[$id] = 1; - } }else{ - self::$lightFilter[$id] = 1; + $block = new UnknownBlock($id); + for($data = 0; $data < 16; ++$data){ self::$fullList[($id << 4) | $data] = new UnknownBlock($id, $data); } } + + self::$solid[$id] = $block->isSolid(); + self::$transparent[$id] = $block->isTransparent(); + self::$hardness[$id] = $block->getHardness(); + self::$light[$id] = $block->getLightLevel(); + self::$lightFilter[$id] = $block->getLightFilter() + 1; + self::$diffusesSkyLight[$id] = $block->diffusesSkyLight(); } } } @@ -419,6 +411,29 @@ class Block extends Position implements BlockIds, Metadatable{ return 0; } + /** + * Returns the amount of light this block will filter out when light passes through this block. + * This value is used in light spread calculation. + * + * @return int 0-15 + */ + public function getLightFilter() : int{ + return 15; + } + + /** + * Returns whether this block will diffuse sky light passing through it vertically. + * Diffusion means that full-strength sky light passing through this block will not be reduced, but will start being filtered below the block. + * Examples of this behaviour include leaves and cobwebs. + * + * Light-diffusing blocks are included by the heightmap. + * + * @return bool + */ + public function diffusesSkyLight() : bool{ + return false; + } + /** * AKA: Block->isPlaceable * diff --git a/src/pocketmine/block/Cobweb.php b/src/pocketmine/block/Cobweb.php index 883c5b1ba..290ac722d 100644 --- a/src/pocketmine/block/Cobweb.php +++ b/src/pocketmine/block/Cobweb.php @@ -57,4 +57,8 @@ class Cobweb extends Flowable{ //TODO: correct drops return []; } + + public function diffusesSkyLight() : bool{ + return true; + } } \ No newline at end of file diff --git a/src/pocketmine/block/Ice.php b/src/pocketmine/block/Ice.php index 2e04b6e6b..7c9a584d0 100644 --- a/src/pocketmine/block/Ice.php +++ b/src/pocketmine/block/Ice.php @@ -40,6 +40,10 @@ class Ice extends Transparent{ return 0.5; } + public function getLightFilter() : int{ + return 2; + } + public function getToolType(){ return Tool::TYPE_PICKAXE; } diff --git a/src/pocketmine/block/Leaves.php b/src/pocketmine/block/Leaves.php index e7c015a36..7afcd2b34 100644 --- a/src/pocketmine/block/Leaves.php +++ b/src/pocketmine/block/Leaves.php @@ -60,6 +60,10 @@ class Leaves extends Transparent{ return $names[$this->meta & 0x03]; } + public function diffusesSkyLight() : bool{ + return true; + } + private function findLog(Block $pos, array $visited, $distance, &$check, $fromSide = null){ ++$check; $index = $pos->x . "." . $pos->y . "." . $pos->z; diff --git a/src/pocketmine/block/Transparent.php b/src/pocketmine/block/Transparent.php index 2eac5534a..3b03cd911 100644 --- a/src/pocketmine/block/Transparent.php +++ b/src/pocketmine/block/Transparent.php @@ -27,4 +27,8 @@ abstract class Transparent extends Block{ public function isTransparent(){ return true; } + + public function getLightFilter() : int{ + return 0; + } } \ No newline at end of file diff --git a/src/pocketmine/block/Water.php b/src/pocketmine/block/Water.php index d71e4791a..236bb15a4 100644 --- a/src/pocketmine/block/Water.php +++ b/src/pocketmine/block/Water.php @@ -37,6 +37,10 @@ class Water extends Liquid{ return "Water"; } + public function getLightFilter() : int{ + return 2; + } + public function onEntityCollide(Entity $entity){ $entity->resetFallDistance(); if($entity->fireTicks > 0){ diff --git a/src/pocketmine/level/BlockLightUpdate.php b/src/pocketmine/level/BlockLightUpdate.php new file mode 100644 index 000000000..5f2d49aa7 --- /dev/null +++ b/src/pocketmine/level/BlockLightUpdate.php @@ -0,0 +1,34 @@ +level->getBlockLightAt($x, $y, $z); + } + + public function setLight(int $x, int $y, int $z, int $level){ + $this->level->setBlockLightAt($x, $y, $z, $level); + } +} \ No newline at end of file diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index e10ac248a..aacdd5f20 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -1253,10 +1253,10 @@ class Level implements ChunkManager, Metadatable{ $chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false); $level = 0; if($chunk !== null){ - $level = $chunk->getBlockSkyLight($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f); + $level = $chunk->getBlockSkyLight($pos->x & 0x0f, $pos->y, $pos->z & 0x0f); //TODO: decrease light level by time of day if($level < 15){ - $level = max($chunk->getBlockLight($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f)); + $level = max($chunk->getBlockLight($pos->x & 0x0f, $pos->y, $pos->z & 0x0f), $level); } } @@ -1271,7 +1271,7 @@ class Level implements ChunkManager, Metadatable{ * @return int bitmap, (id << 4) | data */ public function getFullBlock(int $x, int $y, int $z) : int{ - return $this->getChunk($x >> 4, $z >> 4, false)->getFullBlock($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); + return $this->getChunk($x >> 4, $z >> 4, false)->getFullBlock($x & 0x0f, $y, $z & 0x0f); } /** @@ -1308,14 +1308,64 @@ class Level implements ChunkManager, Metadatable{ $this->updateBlockLight($pos->x, $pos->y, $pos->z); } + /** + * Returns the highest block light level available in the positions adjacent to the specified block coordinates. + * + * @param int $x + * @param int $y + * @param int $z + * + * @return int + */ + public function getHighestAdjacentBlockSkyLight(int $x, int $y, int $z) : int{ + return max([ + $this->getBlockSkyLightAt($x + 1, $y, $z), + $this->getBlockSkyLightAt($x - 1, $y, $z), + $this->getBlockSkyLightAt($x, $y + 1, $z), + $this->getBlockSkyLightAt($x, $y - 1, $z), + $this->getBlockSkyLightAt($x, $y, $z + 1), + $this->getBlockSkyLightAt($x, $y, $z - 1) + ]); + } + public function updateBlockSkyLight(int $x, int $y, int $z){ $this->timings->doBlockSkyLightUpdates->startTiming(); - //TODO + + $oldHeightMap = $this->getHeightMap($x, $z); + $sourceId = $this->getBlockIdAt($x, $y, $z); + + $yPlusOne = $y + 1; + + if($yPlusOne === $oldHeightMap){ //Block changed directly beneath the heightmap. Check if a block was removed or changed to a different light-filter. + $newHeightMap = $this->getChunk($x >> 4, $z >> 4)->recalculateHeightMapColumn($x & 0x0f, $z & 0x0f); + }elseif($yPlusOne > $oldHeightMap){ //Block **placed** above the heightmap. + $this->setHeightMap($x, $z, $yPlusOne); + $newHeightMap = $yPlusOne; + }else{ //block changed below heightmap + $newHeightMap = $oldHeightMap; + } + + $update = new SkyLightUpdate($this); + + if($newHeightMap > $oldHeightMap){ //Heightmap increase, block placed, remove sky light + for($i = $y; $i >= $oldHeightMap; --$i){ + $update->setAndUpdateLight($x, $i, $z, 0); //Remove all light beneath, adjacent recalculation will handle the rest. + } + }elseif($newHeightMap < $oldHeightMap){ //Heightmap decrease, block changed or removed, add sky light + for($i = $y; $i >= $newHeightMap; --$i){ + $update->setAndUpdateLight($x, $i, $z, 15); + } + }else{ //No heightmap change, block changed "underground" + $update->setAndUpdateLight($x, $y, $z, max(0, $this->getHighestAdjacentBlockSkyLight($x, $y, $z) - Block::$lightFilter[$sourceId])); + } + + $update->execute(); + $this->timings->doBlockSkyLightUpdates->stopTiming(); } /** - * Returns the highest light level available in the positions adjacent to the specified block coordinates. + * Returns the highest block light level available in the positions adjacent to the specified block coordinates. * * @param int $x * @param int $y @@ -1337,98 +1387,16 @@ class Level implements ChunkManager, Metadatable{ public function updateBlockLight(int $x, int $y, int $z){ $this->timings->doBlockLightUpdates->startTiming(); - $lightPropagationQueue = new \SplQueue(); - $lightRemovalQueue = new \SplQueue(); - $visited = []; - $removalVisited = []; - $id = $this->getBlockIdAt($x, $y, $z); - $oldLevel = $this->getBlockLightAt($x, $y, $z); $newLevel = max(Block::$light[$id], $this->getHighestAdjacentBlockLight($x, $y, $z) - Block::$lightFilter[$id]); - if($oldLevel !== $newLevel){ - $this->setBlockLightAt($x, $y, $z, $newLevel); - - if($newLevel < $oldLevel){ - $removalVisited[Level::blockHash($x, $y, $z)] = true; - $lightRemovalQueue->enqueue([new Vector3($x, $y, $z), $oldLevel]); - }else{ - $visited[Level::blockHash($x, $y, $z)] = true; - $lightPropagationQueue->enqueue(new Vector3($x, $y, $z)); - } - } - - while(!$lightRemovalQueue->isEmpty()){ - /** @var Vector3 $node */ - $val = $lightRemovalQueue->dequeue(); - $node = $val[0]; - $lightLevel = $val[1]; - - $this->computeRemoveBlockLight($node->x - 1, $node->y, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); - $this->computeRemoveBlockLight($node->x + 1, $node->y, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); - $this->computeRemoveBlockLight($node->x, $node->y - 1, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); - $this->computeRemoveBlockLight($node->x, $node->y + 1, $node->z, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); - $this->computeRemoveBlockLight($node->x, $node->y, $node->z - 1, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); - $this->computeRemoveBlockLight($node->x, $node->y, $node->z + 1, $lightLevel, $lightRemovalQueue, $lightPropagationQueue, $removalVisited, $visited); - } - - while(!$lightPropagationQueue->isEmpty()){ - /** @var Vector3 $node */ - $node = $lightPropagationQueue->dequeue(); - - $lightLevel = $this->getBlockLightAt($node->x, $node->y, $node->z); - - if($lightLevel >= 1){ - $this->computeSpreadBlockLight($node->x - 1, $node->y, $node->z, $lightLevel, $lightPropagationQueue, $visited); - $this->computeSpreadBlockLight($node->x + 1, $node->y, $node->z, $lightLevel, $lightPropagationQueue, $visited); - $this->computeSpreadBlockLight($node->x, $node->y - 1, $node->z, $lightLevel, $lightPropagationQueue, $visited); - $this->computeSpreadBlockLight($node->x, $node->y + 1, $node->z, $lightLevel, $lightPropagationQueue, $visited); - $this->computeSpreadBlockLight($node->x, $node->y, $node->z - 1, $lightLevel, $lightPropagationQueue, $visited); - $this->computeSpreadBlockLight($node->x, $node->y, $node->z + 1, $lightLevel, $lightPropagationQueue, $visited); - } - } + $update = new BlockLightUpdate($this); + $update->setAndUpdateLight($x, $y, $z, $newLevel); + $update->execute(); $this->timings->doBlockLightUpdates->stopTiming(); } - private function computeRemoveBlockLight(int $x, int $y, int $z, int $currentLight, \SplQueue $queue, \SplQueue $spreadQueue, array &$visited, array &$spreadVisited){ - if($y < 0) return; - $current = $this->getBlockLightAt($x, $y, $z); - - if($current !== 0 and $current < $currentLight){ - $this->setBlockLightAt($x, $y, $z, 0); - - if(!isset($visited[$index = Level::blockHash($x, $y, $z)])){ - $visited[$index] = true; - if($current > 1){ - $queue->enqueue([new Vector3($x, $y, $z), $current]); - } - } - }elseif($current >= $currentLight){ - if(!isset($spreadVisited[$index = Level::blockHash($x, $y, $z)])){ - $spreadVisited[$index] = true; - $spreadQueue->enqueue(new Vector3($x, $y, $z)); - } - } - } - - private function computeSpreadBlockLight(int $x, int $y, int $z, int $currentLight, \SplQueue $queue, array &$visited){ - if($y < 0) return; - $current = $this->getBlockLightAt($x, $y, $z); - $currentLight -= Block::$lightFilter[$this->getBlockIdAt($x, $y, $z)]; - - if($current < $currentLight){ - $this->setBlockLightAt($x, $y, $z, $currentLight); - - if(!isset($visited[$index = Level::blockHash($x, $y, $z)])){ - $visited[$index] = true; - if($currentLight > 1){ - $queue->enqueue(new Vector3($x, $y, $z)); - } - } - } - } - /** * Sets on Vector3 the data from a Block object, * does block updates and puts the changes to the send queue. @@ -2179,6 +2147,29 @@ class Level implements ChunkManager, Metadatable{ return null; } + /** + * Returns the chunks adjacent to the specified chunk. + * + * @param int $x + * @param int $z + * + * @return Chunk[] + */ + public function getAdjacentChunks(int $x, int $z) : array{ + $result = []; + for($xx = 0; $xx <= 2; ++$xx){ + for($zz = 0; $zz <= 2; ++$zz){ + $i = $zz * 3 + $xx; + if($i === 4){ + continue; //center chunk + } + $result[$i] = $this->getChunk($x + $xx - 1, $z + $zz - 1, false); + } + } + + return $result; + } + public function generateChunkCallback(int $x, int $z, Chunk $chunk){ Timings::$generationCallbackTimer->startTiming(); if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)])){ diff --git a/src/pocketmine/level/LightUpdate.php b/src/pocketmine/level/LightUpdate.php new file mode 100644 index 000000000..486adf3bc --- /dev/null +++ b/src/pocketmine/level/LightUpdate.php @@ -0,0 +1,161 @@ +level = $level; + $this->removalQueue = new \SplQueue(); + $this->spreadQueue = new \SplQueue(); + } + + public function addSpreadNode(int $x, int $y, int $z){ + $this->spreadQueue->enqueue([$x, $y, $z]); + } + + public function addRemoveNode(int $x, int $y, int $z, int $oldLight){ + $this->spreadQueue->enqueue([$x, $y, $z, $oldLight]); + } + + abstract protected function getLight(int $x, int $y, int $z) : int; + + abstract protected function setLight(int $x, int $y, int $z, int $level); + + public function setAndUpdateLight(int $x, int $y, int $z, int $newLevel){ + if(isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)]) or isset($this->removalVisited[$index])){ + throw new \InvalidArgumentException("Already have a visit ready for this block"); + } + $oldLevel = $this->getLight($x, $y, $z); + + if($oldLevel !== $newLevel){ + $this->setLight($x, $y, $z, $newLevel); + if($oldLevel < $newLevel){ //light increased + $this->spreadVisited[$index] = true; + $this->spreadQueue->enqueue([$x, $y, $z]); + }else{ //light removed + $this->removalVisited[$index] = true; + $this->removalQueue->enqueue([$x, $y, $z, $oldLevel]); + } + } + } + + public function execute(){ + while(!$this->removalQueue->isEmpty()){ + list($x, $y, $z, $oldAdjacentLight) = $this->removalQueue->dequeue(); + + $points = [ + [$x + 1, $y, $z], + [$x - 1, $y, $z], + [$x, $y + 1, $z], + [$x, $y - 1, $z], + [$x, $y, $z + 1], + [$x, $y, $z - 1] + ]; + + foreach($points as list($cx, $cy, $cz)){ + if($cy < 0){ + continue; + } + $this->computeRemoveLight($cx, $cy, $cz, $oldAdjacentLight); + } + } + + while(!$this->spreadQueue->isEmpty()){ + list($x, $y, $z) = $this->spreadQueue->dequeue(); + + $newAdjacentLight = $this->getLight($x, $y, $z); + if($newAdjacentLight <= 0){ + continue; + } + + $points = [ + [$x + 1, $y, $z], + [$x - 1, $y, $z], + [$x, $y + 1, $z], + [$x, $y - 1, $z], + [$x, $y, $z + 1], + [$x, $y, $z - 1] + ]; + + foreach($points as list($cx, $cy, $cz)){ + if($cy < 0){ + continue; + } + $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight); + } + } + } + + protected function computeRemoveLight(int $x, int $y, int $z, int $oldAdjacentLevel){ + $current = $this->getLight($x, $y, $z); + + if($current !== 0 and $current < $oldAdjacentLevel){ + $this->setLight($x, $y, $z, 0); + + if(!isset($visited[$index = Level::blockHash($x, $y, $z)])){ + $this->removalVisited[$index] = true; + if($current > 1){ + $this->removalQueue->enqueue([$x, $y, $z, $current]); + } + } + }elseif($current >= $oldAdjacentLevel){ + if(!isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)])){ + $this->spreadVisited[$index] = true; + $this->spreadQueue->enqueue([$x, $y, $z]); + } + } + } + + protected function computeSpreadLight(int $x, int $y, int $z, int $newAdjacentLevel){ + $current = $this->getLight($x, $y, $z); + $potentialLight = $newAdjacentLevel - Block::$lightFilter[$this->level->getBlockIdAt($x, $y, $z)]; + + if($current < $potentialLight){ + $this->setLight($x, $y, $z, $potentialLight); + + if(!isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)])){ + $this->spreadVisited[$index] = true; + if($potentialLight > 1){ + $this->spreadQueue->enqueue([$x, $y, $z]); + } + } + } + } +} \ No newline at end of file diff --git a/src/pocketmine/level/SkyLightUpdate.php b/src/pocketmine/level/SkyLightUpdate.php new file mode 100644 index 000000000..a2bfc4dfe --- /dev/null +++ b/src/pocketmine/level/SkyLightUpdate.php @@ -0,0 +1,34 @@ +level->getBlockSkyLightAt($x, $y, $z); + } + + public function setLight(int $x, int $y, int $z, int $level){ + $this->level->setBlockSkyLightAt($x, $y, $z, $level); + } +} \ No newline at end of file diff --git a/src/pocketmine/level/format/Chunk.php b/src/pocketmine/level/format/Chunk.php index 9f47296cf..7226aad38 100644 --- a/src/pocketmine/level/format/Chunk.php +++ b/src/pocketmine/level/format/Chunk.php @@ -119,7 +119,7 @@ class Chunk{ $this->heightMap = $heightMap; }else{ assert(count($heightMap) === 0, "Wrong HeightMap value count, expected 256, got " . count($heightMap)); - $val = ($this->height * 16) - 1; + $val = ($this->height * 16); $this->heightMap = array_fill(0, 256, $val); } @@ -344,22 +344,13 @@ class Chunk{ * * @param int $x 0-15 * @param int $z 0-15 - * @param bool $useHeightMap whether to use pre-calculated heightmap values or not * - * @return int + * @return int 0-255, or -1 if there are no blocks in the column */ - public function getHighestBlockAt(int $x, int $z, bool $useHeightMap = true) : int{ - if($useHeightMap){ - $height = $this->getHeightMap($x, $z); - - if($height !== 0 and $height !== 255){ - return $height; - } - } - + public function getHighestBlockAt(int $x, int $z) : int{ $index = $this->getHighestSubChunkIndex(); - if($index < 0){ - return 0; + if($index === -1){ + return -1; } $height = $index << 4; @@ -367,12 +358,11 @@ class Chunk{ for($y = $index; $y >= 0; --$y){ $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z) | ($y << 4); if($height !== -1){ - break; + return $height; } } - $this->setHeightMap($x, $z, $height); - return $height; + return -1; } /** @@ -403,15 +393,37 @@ class Chunk{ public function recalculateHeightMap(){ for($z = 0; $z < 16; ++$z){ for($x = 0; $x < 16; ++$x){ - $this->setHeightMap($x, $z, $this->getHighestBlockAt($x, $z, false)); + $this->recalculateHeightMapColumn($x, $z); } } } /** - * Performs basic sky light population on the chunk. + * Recalculates the heightmap for the block column at the specified X/Z chunk coordinates * - * TODO: rewrite this, use block light filters and diffusion, actual proper sky light population + * @param int $x 0-15 + * @param int $z 0-15 + * + * @return int New calculated heightmap value (0-256 inclusive) + */ + public function recalculateHeightMapColumn(int $x, int $z) : int{ + $max = $this->getHighestBlockAt($x, $z); + for($y = $max; $y >= 0; --$y){ + if(Block::$lightFilter[$id = $this->getBlockId($x, $y, $z)] > 1 or Block::$diffusesSkyLight[$id]){ + break; + } + } + + $this->setHeightMap($x, $z, $y + 1); + return $y + 1; + } + + /** + * Performs basic sky light population on the chunk. + * This does not cater for adjacent sky light, this performs direct sky light population only. This may cause some strange visual artifacts + * if the chunk is light-populated after being terrain-populated. + * + * TODO: fast adjacent light spread */ public function populateSkyLight(){ for($x = 0; $x < 16; ++$x){ @@ -420,19 +432,19 @@ class Chunk{ $y = ($this->getHighestSubChunkIndex() + 1) << 4; - //TODO: replace a section of the array with a string in one call to improve performance - - for(; $y > $heightMap; --$y){ + for(; $y >= $heightMap; --$y){ $this->setBlockSkyLight($x, $y, $z, 15); } - for(; $y > 0 and $this->getBlockId($x, $y, $z) === Block::AIR; --$y){ - $this->setBlockSkyLight($x, $y, $z, 15); - } - $this->setHeightMap($x, $z, $y); - + $light = 15; for(; $y > 0; --$y){ - $this->setBlockSkyLight($x, $y, $z, 0); + if($light > 0){ + $light -= Block::$lightFilter[$this->getBlockId($x, $y, $z)]; + if($light < 0){ + $light = 0; + } + } + $this->setBlockSkyLight($x, $y, $z, $light); } } } @@ -920,9 +932,9 @@ class Chunk{ } $stream->putByte($count); $stream->put($subChunks); - $stream->put(pack("C*", ...$this->heightMap) . + $stream->put(pack("v*", ...$this->heightMap) . $this->biomeIds . - chr(($this->lightPopulated ? 1 << 2 : 0) | ($this->terrainPopulated ? 1 << 1 : 0) | ($this->terrainGenerated ? 1 : 0))); + chr(($this->lightPopulated ? 4 : 0) | ($this->terrainPopulated ? 2 : 0) | ($this->terrainGenerated ? 1 : 0))); return $stream->getBuffer(); } @@ -944,7 +956,7 @@ class Chunk{ for($y = 0; $y < $count; ++$y){ $subChunks[$stream->getByte()] = SubChunk::fastDeserialize($stream->get(10240)); } - $heightMap = array_values(unpack("C*", $stream->get(256))); + $heightMap = array_values(unpack("v*", $stream->get(512))); $biomeIds = $stream->get(256); $chunk = new Chunk($x, $z, $subChunks, [], [], $biomeIds, $heightMap); diff --git a/src/pocketmine/level/format/SubChunk.php b/src/pocketmine/level/format/SubChunk.php index 8a5cc5be6..4975a372d 100644 --- a/src/pocketmine/level/format/SubChunk.php +++ b/src/pocketmine/level/format/SubChunk.php @@ -159,9 +159,11 @@ class SubChunk{ } public function getHighestBlockAt(int $x, int $z) : int{ - for($y = 15; $y >= 0; --$y){ - if($this->ids{($x << 8) | ($z << 4) | $y} !== "\x00"){ - return $y; + $low = ($x << 8) | ($z << 4); + $i = $low | 0x0f; + for(; $i >= $low; --$i){ + if($this->ids{$i} !== "\x00"){ + return $i & 0x0f; } } diff --git a/src/pocketmine/level/generator/PopulationTask.php b/src/pocketmine/level/generator/PopulationTask.php index c40b31378..88b61e5c0 100644 --- a/src/pocketmine/level/generator/PopulationTask.php +++ b/src/pocketmine/level/generator/PopulationTask.php @@ -48,14 +48,8 @@ class PopulationTask extends AsyncTask{ $this->levelId = $level->getId(); $this->chunk = $chunk->fastSerialize(); - for($i = 0; $i < 9; ++$i){ - if($i === 4){ - continue; - } - $xx = -1 + $i % 3; - $zz = -1 + (int) ($i / 3); - $ck = $level->getChunk($chunk->getX() + $xx, $chunk->getZ() + $zz, false); - $this->{"chunk$i"} = $ck !== null ? $ck->fastSerialize() : null; + foreach($level->getAdjacentChunks($chunk->getX(), $chunk->getZ()) as $i => $c){ + $this->{"chunk$i"} = $c !== null ? $c->fastSerialize() : null; } } @@ -173,4 +167,4 @@ class PopulationTask extends AsyncTask{ $level->generateChunkCallback($chunk->getX(), $chunk->getZ(), $chunk); } } -} +} \ No newline at end of file