mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-16 02:38:54 +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;
|
public static $hardness = null;
|
||||||
/** @var \SplFixedArray */
|
/** @var \SplFixedArray */
|
||||||
public static $transparent = null;
|
public static $transparent = null;
|
||||||
|
/** @var \SplFixedArray */
|
||||||
|
public static $diffusesSkyLight = null;
|
||||||
|
|
||||||
protected $id;
|
protected $id;
|
||||||
protected $meta = 0;
|
protected $meta = 0;
|
||||||
@ -70,6 +72,7 @@ class Block extends Position implements BlockIds, Metadatable{
|
|||||||
self::$solid = new \SplFixedArray(256);
|
self::$solid = new \SplFixedArray(256);
|
||||||
self::$hardness = new \SplFixedArray(256);
|
self::$hardness = new \SplFixedArray(256);
|
||||||
self::$transparent = new \SplFixedArray(256);
|
self::$transparent = new \SplFixedArray(256);
|
||||||
|
self::$diffusesSkyLight = new \SplFixedArray(256);
|
||||||
self::$list[self::AIR] = Air::class;
|
self::$list[self::AIR] = Air::class;
|
||||||
self::$list[self::STONE] = Stone::class;
|
self::$list[self::STONE] = Stone::class;
|
||||||
self::$list[self::GRASS] = Grass::class;
|
self::$list[self::GRASS] = Grass::class;
|
||||||
@ -254,31 +257,20 @@ class Block extends Position implements BlockIds, Metadatable{
|
|||||||
for($data = 0; $data < 16; ++$data){
|
for($data = 0; $data < 16; ++$data){
|
||||||
self::$fullList[($id << 4) | $data] = new $class($data);
|
self::$fullList[($id << 4) | $data] = new $class($data);
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
$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::$solid[$id] = $block->isSolid();
|
||||||
self::$transparent[$id] = $block->isTransparent();
|
self::$transparent[$id] = $block->isTransparent();
|
||||||
self::$hardness[$id] = $block->getHardness();
|
self::$hardness[$id] = $block->getHardness();
|
||||||
self::$light[$id] = $block->getLightLevel();
|
self::$light[$id] = $block->getLightLevel();
|
||||||
|
self::$lightFilter[$id] = $block->getLightFilter() + 1;
|
||||||
if($block->isSolid()){
|
self::$diffusesSkyLight[$id] = $block->diffusesSkyLight();
|
||||||
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;
|
|
||||||
for($data = 0; $data < 16; ++$data){
|
|
||||||
self::$fullList[($id << 4) | $data] = new UnknownBlock($id, $data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,6 +411,29 @@ class Block extends Position implements BlockIds, Metadatable{
|
|||||||
return 0;
|
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
|
* AKA: Block->isPlaceable
|
||||||
*
|
*
|
||||||
|
@ -57,4 +57,8 @@ class Cobweb extends Flowable{
|
|||||||
//TODO: correct drops
|
//TODO: correct drops
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function diffusesSkyLight() : bool{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
@ -40,6 +40,10 @@ class Ice extends Transparent{
|
|||||||
return 0.5;
|
return 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getLightFilter() : int{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
public function getToolType(){
|
public function getToolType(){
|
||||||
return Tool::TYPE_PICKAXE;
|
return Tool::TYPE_PICKAXE;
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,10 @@ class Leaves extends Transparent{
|
|||||||
return $names[$this->meta & 0x03];
|
return $names[$this->meta & 0x03];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function diffusesSkyLight() : bool{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private function findLog(Block $pos, array $visited, $distance, &$check, $fromSide = null){
|
private function findLog(Block $pos, array $visited, $distance, &$check, $fromSide = null){
|
||||||
++$check;
|
++$check;
|
||||||
$index = $pos->x . "." . $pos->y . "." . $pos->z;
|
$index = $pos->x . "." . $pos->y . "." . $pos->z;
|
||||||
|
@ -27,4 +27,8 @@ abstract class Transparent extends Block{
|
|||||||
public function isTransparent(){
|
public function isTransparent(){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getLightFilter() : int{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
@ -37,6 +37,10 @@ class Water extends Liquid{
|
|||||||
return "Water";
|
return "Water";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getLightFilter() : int{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
public function onEntityCollide(Entity $entity){
|
public function onEntityCollide(Entity $entity){
|
||||||
$entity->resetFallDistance();
|
$entity->resetFallDistance();
|
||||||
if($entity->fireTicks > 0){
|
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);
|
$chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false);
|
||||||
$level = 0;
|
$level = 0;
|
||||||
if($chunk !== null){
|
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
|
//TODO: decrease light level by time of day
|
||||||
if($level < 15){
|
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
|
* @return int bitmap, (id << 4) | data
|
||||||
*/
|
*/
|
||||||
public function getFullBlock(int $x, int $y, int $z) : int{
|
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);
|
$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){
|
public function updateBlockSkyLight(int $x, int $y, int $z){
|
||||||
$this->timings->doBlockSkyLightUpdates->startTiming();
|
$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();
|
$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 $x
|
||||||
* @param int $y
|
* @param int $y
|
||||||
@ -1337,98 +1387,16 @@ class Level implements ChunkManager, Metadatable{
|
|||||||
public function updateBlockLight(int $x, int $y, int $z){
|
public function updateBlockLight(int $x, int $y, int $z){
|
||||||
$this->timings->doBlockLightUpdates->startTiming();
|
$this->timings->doBlockLightUpdates->startTiming();
|
||||||
|
|
||||||
$lightPropagationQueue = new \SplQueue();
|
|
||||||
$lightRemovalQueue = new \SplQueue();
|
|
||||||
$visited = [];
|
|
||||||
$removalVisited = [];
|
|
||||||
|
|
||||||
$id = $this->getBlockIdAt($x, $y, $z);
|
$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]);
|
$newLevel = max(Block::$light[$id], $this->getHighestAdjacentBlockLight($x, $y, $z) - Block::$lightFilter[$id]);
|
||||||
|
|
||||||
if($oldLevel !== $newLevel){
|
$update = new BlockLightUpdate($this);
|
||||||
$this->setBlockLightAt($x, $y, $z, $newLevel);
|
$update->setAndUpdateLight($x, $y, $z, $newLevel);
|
||||||
|
$update->execute();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->timings->doBlockLightUpdates->stopTiming();
|
$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,
|
* Sets on Vector3 the data from a Block object,
|
||||||
* does block updates and puts the changes to the send queue.
|
* does block updates and puts the changes to the send queue.
|
||||||
@ -2179,6 +2147,29 @@ class Level implements ChunkManager, Metadatable{
|
|||||||
return null;
|
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){
|
public function generateChunkCallback(int $x, int $z, Chunk $chunk){
|
||||||
Timings::$generationCallbackTimer->startTiming();
|
Timings::$generationCallbackTimer->startTiming();
|
||||||
if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)])){
|
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;
|
$this->heightMap = $heightMap;
|
||||||
}else{
|
}else{
|
||||||
assert(count($heightMap) === 0, "Wrong HeightMap value count, expected 256, got " . count($heightMap));
|
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);
|
$this->heightMap = array_fill(0, 256, $val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,22 +344,13 @@ class Chunk{
|
|||||||
*
|
*
|
||||||
* @param int $x 0-15
|
* @param int $x 0-15
|
||||||
* @param int $z 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{
|
public function getHighestBlockAt(int $x, int $z) : int{
|
||||||
if($useHeightMap){
|
|
||||||
$height = $this->getHeightMap($x, $z);
|
|
||||||
|
|
||||||
if($height !== 0 and $height !== 255){
|
|
||||||
return $height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$index = $this->getHighestSubChunkIndex();
|
$index = $this->getHighestSubChunkIndex();
|
||||||
if($index < 0){
|
if($index === -1){
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$height = $index << 4;
|
$height = $index << 4;
|
||||||
@ -367,12 +358,11 @@ class Chunk{
|
|||||||
for($y = $index; $y >= 0; --$y){
|
for($y = $index; $y >= 0; --$y){
|
||||||
$height = $this->getSubChunk($y)->getHighestBlockAt($x, $z) | ($y << 4);
|
$height = $this->getSubChunk($y)->getHighestBlockAt($x, $z) | ($y << 4);
|
||||||
if($height !== -1){
|
if($height !== -1){
|
||||||
break;
|
return $height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setHeightMap($x, $z, $height);
|
return -1;
|
||||||
return $height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -403,15 +393,37 @@ class Chunk{
|
|||||||
public function recalculateHeightMap(){
|
public function recalculateHeightMap(){
|
||||||
for($z = 0; $z < 16; ++$z){
|
for($z = 0; $z < 16; ++$z){
|
||||||
for($x = 0; $x < 16; ++$x){
|
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(){
|
public function populateSkyLight(){
|
||||||
for($x = 0; $x < 16; ++$x){
|
for($x = 0; $x < 16; ++$x){
|
||||||
@ -420,19 +432,19 @@ class Chunk{
|
|||||||
|
|
||||||
$y = ($this->getHighestSubChunkIndex() + 1) << 4;
|
$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);
|
$this->setBlockSkyLight($x, $y, $z, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(; $y > 0 and $this->getBlockId($x, $y, $z) === Block::AIR; --$y){
|
$light = 15;
|
||||||
$this->setBlockSkyLight($x, $y, $z, 15);
|
|
||||||
}
|
|
||||||
$this->setHeightMap($x, $z, $y);
|
|
||||||
|
|
||||||
for(; $y > 0; --$y){
|
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->putByte($count);
|
||||||
$stream->put($subChunks);
|
$stream->put($subChunks);
|
||||||
$stream->put(pack("C*", ...$this->heightMap) .
|
$stream->put(pack("v*", ...$this->heightMap) .
|
||||||
$this->biomeIds .
|
$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();
|
return $stream->getBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -944,7 +956,7 @@ class Chunk{
|
|||||||
for($y = 0; $y < $count; ++$y){
|
for($y = 0; $y < $count; ++$y){
|
||||||
$subChunks[$stream->getByte()] = SubChunk::fastDeserialize($stream->get(10240));
|
$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);
|
$biomeIds = $stream->get(256);
|
||||||
|
|
||||||
$chunk = new Chunk($x, $z, $subChunks, [], [], $biomeIds, $heightMap);
|
$chunk = new Chunk($x, $z, $subChunks, [], [], $biomeIds, $heightMap);
|
||||||
|
@ -159,9 +159,11 @@ class SubChunk{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getHighestBlockAt(int $x, int $z) : int{
|
public function getHighestBlockAt(int $x, int $z) : int{
|
||||||
for($y = 15; $y >= 0; --$y){
|
$low = ($x << 8) | ($z << 4);
|
||||||
if($this->ids{($x << 8) | ($z << 4) | $y} !== "\x00"){
|
$i = $low | 0x0f;
|
||||||
return $y;
|
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->levelId = $level->getId();
|
||||||
$this->chunk = $chunk->fastSerialize();
|
$this->chunk = $chunk->fastSerialize();
|
||||||
|
|
||||||
for($i = 0; $i < 9; ++$i){
|
foreach($level->getAdjacentChunks($chunk->getX(), $chunk->getZ()) as $i => $c){
|
||||||
if($i === 4){
|
$this->{"chunk$i"} = $c !== null ? $c->fastSerialize() : null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user