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::ALL as $side){
164 [$ox, $oy, $oz] = Facing::OFFSET[$side->value];
169 $moveStatus = $subChunkExplorer->moveTo($cx, $cy, $cz);
170 if($moveStatus === SubChunkExplorerStatus::INVALID){
173 if($moveStatus === SubChunkExplorerStatus::MOVED){
174 $subChunk = $subChunkExplorer->currentSubChunk;
175 $lightArray = $this->getCurrentLightArray();
177 assert($subChunk !==
null);
178 $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight, $context, $lightArray, $subChunk, $side);
185 protected function computeRemoveLight(
int $x,
int $y,
int $z,
int $oldAdjacentLevel, LightPropagationContext $context, LightArray $lightArray) : void{
186 $lx = $x & SubChunk::COORD_MASK;
187 $ly = $y & SubChunk::COORD_MASK;
188 $lz = $z & SubChunk::COORD_MASK;
189 $current = $lightArray->get($lx, $ly, $lz);
191 if($current !== 0 && $current < $oldAdjacentLevel){
192 $lightArray->set($lx, $ly, $lz, 0);
194 if(!isset($context->removalVisited[$index = World::blockHash($x, $y, $z)])){
195 $context->removalVisited[$index] =
true;
197 $context->removalQueue->enqueue([$x, $y, $z, $current]);
200 }elseif($current >= $oldAdjacentLevel){
201 if(!isset($context->spreadVisited[$index = World::blockHash($x, $y, $z)])){
202 $context->spreadVisited[$index] =
true;
203 $context->spreadQueue->enqueue([$x, $y, $z]);
208 protected function computeSpreadLight(
int $x,
int $y,
int $z,
int $newAdjacentLevel, LightPropagationContext $context, LightArray $lightArray, SubChunk $subChunk, Facing $side) : void{
209 $lx = $x & SubChunk::COORD_MASK;
210 $ly = $y & SubChunk::COORD_MASK;
211 $lz = $z & SubChunk::COORD_MASK;
212 $current = $lightArray->get($lx, $ly, $lz);
213 $potentialLight = $newAdjacentLevel - ($this->lightFilters[$subChunk->getBlockStateId($lx, $ly, $lz)] ?? self::BASE_LIGHT_FILTER);
215 if($current < $potentialLight){
216 $lightArray->set($lx, $ly, $lz, $potentialLight);
218 if(!isset($context->spreadVisited[$index = World::blockHash($x, $y, $z)]) && $potentialLight > 1){
223 $context->spreadVisited[$index] = Facing::opposite($side);
224 $context->spreadQueue->enqueue([$x, $y, $z]);