mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-15 18:29:46 +00:00
Implemented sky light generation-time population and updating, obsolete and close #160
This commit is contained in:
parent
5e6a0e7ba0
commit
dab73d8950
@ -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
|
||||
*
|
||||
|
@ -57,4 +57,8 @@ class Cobweb extends Flowable{
|
||||
//TODO: correct drops
|
||||
return [];
|
||||
}
|
||||
|
||||
public function diffusesSkyLight() : bool{
|
||||
return true;
|
||||
}
|
||||
}
|
@ -40,6 +40,10 @@ class Ice extends Transparent{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
public function getLightFilter() : int{
|
||||
return 2;
|
||||
}
|
||||
|
||||
public function getToolType(){
|
||||
return Tool::TYPE_PICKAXE;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -27,4 +27,8 @@ abstract class Transparent extends Block{
|
||||
public function isTransparent(){
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getLightFilter() : int{
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -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){
|
||||
|
34
src/pocketmine/level/BlockLightUpdate.php
Normal file
34
src/pocketmine/level/BlockLightUpdate.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
namespace pocketmine\level;
|
||||
|
||||
|
||||
class BlockLightUpdate extends LightUpdate{
|
||||
|
||||
public function getLight(int $x, int $y, int $z) : int{
|
||||
return $this->level->getBlockLightAt($x, $y, $z);
|
||||
}
|
||||
|
||||
public function setLight(int $x, int $y, int $z, int $level){
|
||||
$this->level->setBlockLightAt($x, $y, $z, $level);
|
||||
}
|
||||
}
|
@ -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)])){
|
||||
|
161
src/pocketmine/level/LightUpdate.php
Normal file
161
src/pocketmine/level/LightUpdate.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
namespace pocketmine\level;
|
||||
|
||||
|
||||
use pocketmine\block\Block;
|
||||
|
||||
//TODO: make light updates asynchronous
|
||||
abstract class LightUpdate{
|
||||
|
||||
/** @var Level */
|
||||
protected $level;
|
||||
|
||||
/** @var \SplQueue */
|
||||
protected $spreadQueue;
|
||||
/** @var bool[] */
|
||||
protected $spreadVisited = [];
|
||||
|
||||
/** @var \SplQueue */
|
||||
protected $removalQueue;
|
||||
/** @var bool[] */
|
||||
protected $removalVisited = [];
|
||||
|
||||
public function __construct(Level $level){
|
||||
$this->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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
src/pocketmine/level/SkyLightUpdate.php
Normal file
34
src/pocketmine/level/SkyLightUpdate.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
namespace pocketmine\level;
|
||||
|
||||
|
||||
class SkyLightUpdate extends LightUpdate{
|
||||
|
||||
public function getLight(int $x, int $y, int $z) : int{
|
||||
return $this->level->getBlockSkyLightAt($x, $y, $z);
|
||||
}
|
||||
|
||||
public function setLight(int $x, int $y, int $z, int $level){
|
||||
$this->level->setBlockSkyLightAt($x, $y, $z, $level);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user