PocketMine-MP 5.17.1 git-df4ada81e5d74a14046f27cf44a37dcee69d657e
SkyLightUpdate.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\world\light;
25
28use pocketmine\world\format\LightArray;
33use function max;
34
42 public function __construct(
43 SubChunkExplorer $subChunkExplorer,
44 array $lightFilters,
45 private array $directSkyLightBlockers
46 ){
47 parent::__construct($subChunkExplorer, $lightFilters);
48 }
49
50 protected function getCurrentLightArray() : LightArray{
51 return $this->subChunkExplorer->currentSubChunk->getBlockSkyLightArray();
52 }
53
54 protected function getEffectiveLight(int $x, int $y, int $z) : int{
55 if($y >= World::Y_MAX){
56 $this->subChunkExplorer->invalidate();
57 return 15;
58 }
59 return parent::getEffectiveLight($x, $y, $z);
60 }
61
62 public function recalculateNode(int $x, int $y, int $z) : void{
63 if($this->subChunkExplorer->moveTo($x, $y, $z) === SubChunkExplorerStatus::INVALID){
64 return;
65 }
66 $chunk = $this->subChunkExplorer->currentChunk;
67
68 $oldHeightMap = $chunk->getHeightMap($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK);
69 $source = $this->subChunkExplorer->currentSubChunk->getBlockStateId($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
70
71 $yPlusOne = $y + 1;
72
73 if($yPlusOne === $oldHeightMap){ //Block changed directly beneath the heightmap. Check if a block was removed or changed to a different light-filter.
74 $newHeightMap = self::recalculateHeightMapColumn($chunk, $x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK, $this->directSkyLightBlockers);
75 $chunk->setHeightMap($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK, $newHeightMap);
76 }elseif($yPlusOne > $oldHeightMap){ //Block changed above the heightmap.
77 if(isset($this->directSkyLightBlockers[$source])){
78 $chunk->setHeightMap($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK, $yPlusOne);
79 $newHeightMap = $yPlusOne;
80 }else{ //Block changed which has no effect on direct sky light, for example placing or removing glass.
81 return;
82 }
83 }else{ //Block changed below heightmap
84 $newHeightMap = $oldHeightMap;
85 }
86
87 if($newHeightMap >= $oldHeightMap){
88 for($i = $y - 1; $i >= $oldHeightMap; --$i){
89 $this->setAndUpdateLight($x, $i, $z, 0); //Remove all light beneath, adjacent recalculation will handle the rest.
90 }
91
92 //recalculate light for the placed block from its surroundings - this avoids having to check effective light during propagation
93 $this->setAndUpdateLight($x, $y, $z, max(0, $this->getHighestAdjacentLight($x, $y, $z) - ($this->lightFilters[$source] ?? self::BASE_LIGHT_FILTER)));
94 }else{ //Heightmap decrease, block changed or removed, add sky light
95 for($i = $y; $i >= $newHeightMap; --$i){
96 $this->setAndUpdateLight($x, $i, $z, 15);
97 }
98 }
99 }
100
101 public function recalculateChunk(int $chunkX, int $chunkZ) : int{
102 if($this->subChunkExplorer->moveToChunk($chunkX, 0, $chunkZ) === SubChunkExplorerStatus::INVALID){
103 throw new \InvalidArgumentException("Chunk $chunkX $chunkZ does not exist");
104 }
105 $chunk = $this->subChunkExplorer->currentChunk;
106
107 $newHeightMap = self::recalculateHeightMap($chunk, $this->directSkyLightBlockers);
108 $chunk->setHeightMapArray($newHeightMap->getValues());
109
110 //setAndUpdateLight() won't bother propagating from nodes that are already what we want to change them to, so we
111 //have to avoid filling full light for any subchunk that contains a heightmap Y coordinate
112 $highestHeightMapPlusOne = max($chunk->getHeightMapArray()) + 1;
113 $lowestClearSubChunk = ($highestHeightMapPlusOne >> SubChunk::COORD_BIT_SIZE) + (($highestHeightMapPlusOne & SubChunk::COORD_MASK) !== 0 ? 1 : 0);
114 for($y = Chunk::MIN_SUBCHUNK_INDEX; $y < $lowestClearSubChunk && $y <= Chunk::MAX_SUBCHUNK_INDEX; $y++){
115 $chunk->getSubChunk($y)->setBlockSkyLightArray(LightArray::fill(0));
116 }
117 for($y = $lowestClearSubChunk; $y <= Chunk::MAX_SUBCHUNK_INDEX; $y++){
118 $chunk->getSubChunk($y)->setBlockSkyLightArray(LightArray::fill(15));
119 }
120
121 $lightSources = 0;
122
123 $baseX = $chunkX << Chunk::COORD_BIT_SIZE;
124 $baseZ = $chunkZ << Chunk::COORD_BIT_SIZE;
125 for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){
126 for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){
127 $currentHeight = $chunk->getHeightMap($x, $z);
128
129 if($currentHeight === World::Y_MAX){
130 //this column has a light-filtering block in the top cell - make sure it's lit from above the world
131 //light from above the world bounds will not be checked during propagation
132 $y = $currentHeight - 1;
133 if($this->subChunkExplorer->moveTo($x + $baseX, $y, $z + $baseZ) !== SubChunkExplorerStatus::INVALID){
134 $block = $this->subChunkExplorer->currentSubChunk->getBlockStateId($x, $y & SubChunk::COORD_MASK, $z);
135 $this->setAndUpdateLight($x + $baseX, $y, $z + $baseZ, max(0, 15 - ($this->lightFilters[$block] ?? self::BASE_LIGHT_FILTER)));
136 }
137 }else{
138 $maxAdjacentHeight = World::Y_MIN;
139 if($x !== 0){
140 $maxAdjacentHeight = max($maxAdjacentHeight, $chunk->getHeightMap($x - 1, $z));
141 }
142 if($x !== 15){
143 $maxAdjacentHeight = max($maxAdjacentHeight, $chunk->getHeightMap($x + 1, $z));
144 }
145 if($z !== 0){
146 $maxAdjacentHeight = max($maxAdjacentHeight, $chunk->getHeightMap($x, $z - 1));
147 }
148 if($z !== 15){
149 $maxAdjacentHeight = max($maxAdjacentHeight, $chunk->getHeightMap($x, $z + 1));
150 }
151
152 /*
153 * We skip the top two blocks between current height and max adjacent (if there's a difference) because:
154 * - the block next to the highest adjacent will do nothing during propagation (it's surrounded by 15s)
155 * - the block below that block will do the same as the node in the highest adjacent
156 * NOTE: If block opacity becomes direction-aware in the future, the second point will become invalid.
157 */
158 $nodeColumnEnd = max($currentHeight, $maxAdjacentHeight - 2);
159 for($y = $currentHeight; $y <= $nodeColumnEnd; $y++){
160 $this->setAndUpdateLight($x + $baseX, $y, $z + $baseZ, 15);
161 $lightSources++;
162 }
163 for($y = $nodeColumnEnd + 1, $yMax = $lowestClearSubChunk * SubChunk::EDGE_LENGTH; $y < $yMax; $y++){
164 if($this->subChunkExplorer->moveTo($x + $baseX, $y, $z + $baseZ) !== SubChunkExplorerStatus::INVALID){
165 $this->getCurrentLightArray()->set($x, $y & SubChunk::COORD_MASK, $z, 15);
166 }
167 }
168 }
169 }
170 }
171
172 return $lightSources;
173 }
174
181 private static function recalculateHeightMap(Chunk $chunk, array $directSkyLightBlockers) : HeightArray{
182 $maxSubChunkY = Chunk::MAX_SUBCHUNK_INDEX;
183 for(; $maxSubChunkY >= Chunk::MIN_SUBCHUNK_INDEX; $maxSubChunkY--){
184 if(!$chunk->getSubChunk($maxSubChunkY)->isEmptyFast()){
185 break;
186 }
187 }
188 $result = HeightArray::fill(World::Y_MIN);
189 if($maxSubChunkY < Chunk::MIN_SUBCHUNK_INDEX){ //whole column is definitely empty
190 return $result;
191 }
192
193 for($z = 0; $z < Chunk::EDGE_LENGTH; ++$z){
194 for($x = 0; $x < Chunk::EDGE_LENGTH; ++$x){
195 $y = null;
196 for($subChunkY = $maxSubChunkY; $subChunkY >= Chunk::MIN_SUBCHUNK_INDEX; $subChunkY--){
197 $subHighestBlockY = $chunk->getSubChunk($subChunkY)->getHighestBlockAt($x, $z);
198 if($subHighestBlockY !== null){
199 $y = ($subChunkY * SubChunk::EDGE_LENGTH) + $subHighestBlockY;
200 break;
201 }
202 }
203
204 if($y === null){ //no blocks in the column
205 $result->set($x, $z, World::Y_MIN);
206 }else{
207 for(; $y >= World::Y_MIN; --$y){
208 if(isset($directSkyLightBlockers[$chunk->getBlockStateId($x, $y, $z)])){
209 $result->set($x, $z, $y + 1);
210 break;
211 }
212 }
213 }
214 }
215 }
216 return $result;
217 }
218
229 private static function recalculateHeightMapColumn(Chunk $chunk, int $x, int $z, array $directSkyLightBlockers) : int{
230 $y = $chunk->getHighestBlockAt($x, $z);
231 if($y === null){
232 return World::Y_MIN;
233 }
234 for(; $y >= World::Y_MIN; --$y){
235 if(isset($directSkyLightBlockers[$chunk->getBlockStateId($x, $y, $z)])){
236 break;
237 }
238 }
239
240 return $y + 1;
241 }
242}
recalculateChunk(int $chunkX, int $chunkZ)
__construct(SubChunkExplorer $subChunkExplorer, array $lightFilters, private array $directSkyLightBlockers)