37 public const BASE_LIGHT_FILTER = 1;
43 protected array $updateNodes = [];
51 protected array $lightFilters
54 abstract protected function getCurrentLightArray() : LightArray;
56 abstract public function recalculateNode(
int $x,
int $y,
int $z) : void;
64 protected function getEffectiveLight(
int $x,
int $y,
int $z) : int{
66 return $this->getCurrentLightArray()->get($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
71 protected function getHighestAdjacentLight(
int $x,
int $y,
int $z) : int{
73 foreach(Facing::OFFSET as [$ox, $oy, $oz]){
74 if(($adjacent = max($adjacent, $this->getEffectiveLight($x + $ox, $y + $oy, $z + $oz))) === 15){
81 public function setAndUpdateLight(
int $x,
int $y,
int $z,
int $newLevel) : void{
82 $this->updateNodes[World::blockHash($x, $y, $z)] = [$x, $y, $z, $newLevel];
85 private function prepareNodes() : LightPropagationContext{
86 $context = new LightPropagationContext();
87 foreach($this->updateNodes as $blockHash => [$x, $y, $z, $newLevel]){
88 if($this->subChunkExplorer->moveTo($x, $y, $z) !== SubChunkExplorerStatus::INVALID){
89 $lightArray = $this->getCurrentLightArray();
90 $oldLevel = $lightArray->get($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
92 if($oldLevel !== $newLevel){
93 $lightArray->set($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK, $newLevel);
94 if($oldLevel < $newLevel){
95 $context->spreadVisited[$blockHash] =
true;
96 $context->spreadQueue->enqueue([$x, $y, $z]);
98 $context->removalVisited[$blockHash] =
true;
99 $context->removalQueue->enqueue([$x, $y, $z, $oldLevel]);
107 public function execute() : int{
108 $context = $this->prepareNodes();
112 $subChunkExplorer = $this->subChunkExplorer;
113 $subChunkExplorer->invalidate();
114 while(!$context->removalQueue->isEmpty()){
116 [$x, $y, $z, $oldAdjacentLight] = $context->removalQueue->dequeue();
118 foreach(Facing::OFFSET as [$ox, $oy, $oz]){
123 $moveStatus = $subChunkExplorer->moveTo($cx, $cy, $cz);
124 if($moveStatus === SubChunkExplorerStatus::INVALID){
127 if($moveStatus === SubChunkExplorerStatus::MOVED){
128 $lightArray = $this->getCurrentLightArray();
130 assert($lightArray !== null);
131 $this->computeRemoveLight($cx, $cy, $cz, $oldAdjacentLight, $context, $lightArray);
136 $subChunkExplorer->invalidate();
137 while(!$context->spreadQueue->isEmpty()){
139 [$x, $y, $z] = $context->spreadQueue->dequeue();
140 $from = $context->spreadVisited[World::blockHash($x, $y, $z)];
142 unset($context->spreadVisited[World::blockHash($x, $y, $z)]);
144 $moveStatus = $subChunkExplorer->moveTo($x, $y, $z);
145 if($moveStatus === SubChunkExplorerStatus::INVALID){
148 if($moveStatus === SubChunkExplorerStatus::MOVED){
149 $subChunk = $subChunkExplorer->currentSubChunk;
150 $lightArray = $this->getCurrentLightArray();
152 assert($lightArray !==
null);
154 $newAdjacentLight = $lightArray->get($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
155 if($newAdjacentLight <= 0){
159 foreach(Facing::OFFSET as $side => [$ox, $oy, $oz]){
168 $moveStatus = $subChunkExplorer->moveTo($cx, $cy, $cz);
169 if($moveStatus === SubChunkExplorerStatus::INVALID){
172 if($moveStatus === SubChunkExplorerStatus::MOVED){
173 $subChunk = $subChunkExplorer->currentSubChunk;
174 $lightArray = $this->getCurrentLightArray();
176 assert($subChunk !==
null);
177 $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight, $context, $lightArray, $subChunk, $side);
184 protected function computeRemoveLight(
int $x,
int $y,
int $z,
int $oldAdjacentLevel, LightPropagationContext $context, LightArray $lightArray) : void{
185 $lx = $x & SubChunk::COORD_MASK;
186 $ly = $y & SubChunk::COORD_MASK;
187 $lz = $z & SubChunk::COORD_MASK;
188 $current = $lightArray->get($lx, $ly, $lz);
190 if($current !== 0 && $current < $oldAdjacentLevel){
191 $lightArray->set($lx, $ly, $lz, 0);
193 if(!isset($context->removalVisited[$index = World::blockHash($x, $y, $z)])){
194 $context->removalVisited[$index] =
true;
196 $context->removalQueue->enqueue([$x, $y, $z, $current]);
199 }elseif($current >= $oldAdjacentLevel){
200 if(!isset($context->spreadVisited[$index = World::blockHash($x, $y, $z)])){
201 $context->spreadVisited[$index] =
true;
202 $context->spreadQueue->enqueue([$x, $y, $z]);
207 protected function computeSpreadLight(
int $x,
int $y,
int $z,
int $newAdjacentLevel, LightPropagationContext $context, LightArray $lightArray, SubChunk $subChunk,
int $side) : void{
208 $lx = $x & SubChunk::COORD_MASK;
209 $ly = $y & SubChunk::COORD_MASK;
210 $lz = $z & SubChunk::COORD_MASK;
211 $current = $lightArray->get($lx, $ly, $lz);
212 $potentialLight = $newAdjacentLevel - ($this->lightFilters[$subChunk->getBlockStateId($lx, $ly, $lz)] ?? self::BASE_LIGHT_FILTER);
214 if($current < $potentialLight){
215 $lightArray->set($lx, $ly, $lz, $potentialLight);
217 if(!isset($context->spreadVisited[$index = World::blockHash($x, $y, $z)]) && $potentialLight > 1){
222 $context->spreadVisited[$index] = Facing::opposite($side);
223 $context->spreadQueue->enqueue([$x, $y, $z]);