35 public const DIRTY_FLAG_BLOCKS = 1 << 0;
36 public const DIRTY_FLAG_BIOMES = 1 << 3;
38 public const DIRTY_FLAGS_ALL = ~0;
39 public const DIRTY_FLAGS_NONE = 0;
41 public const MIN_SUBCHUNK_INDEX = -4;
42 public const MAX_SUBCHUNK_INDEX = 19;
43 public const MAX_SUBCHUNKS = self::MAX_SUBCHUNK_INDEX - self::MIN_SUBCHUNK_INDEX + 1;
45 public const EDGE_LENGTH = SubChunk::EDGE_LENGTH;
46 public const COORD_BIT_SIZE = SubChunk::COORD_BIT_SIZE;
47 public const COORD_MASK = SubChunk::COORD_MASK;
49 private int $terrainDirtyFlags = self::DIRTY_FLAGS_ALL;
51 protected ?
bool $lightPopulated =
false;
52 protected bool $terrainPopulated =
false;
58 protected \SplFixedArray $subChunks;
61 protected array $tiles = [];
68 public function __construct(array $subChunks,
bool $terrainPopulated){
69 $this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
71 foreach($this->subChunks as $y => $null){
73 $this->subChunks[$y] = $subChunks[$y + self::MIN_SUBCHUNK_INDEX] ??
new SubChunk(Block::EMPTY_STATE_ID, [],
new PalettedBlockArray(BiomeIds::OCEAN));
76 $val = (self::MAX_SUBCHUNK_INDEX + 1) * SubChunk::EDGE_LENGTH;
77 $this->heightMap = HeightArray::fill($val);
79 $this->terrainPopulated = $terrainPopulated;
86 return $this->subChunks->getSize();
99 return $this->getSubChunk($y >>
SubChunk::COORD_BIT_SIZE)->getBlockStateId($x, $y &
SubChunk::COORD_MASK, $z);
106 $this->getSubChunk($y >>
SubChunk::COORD_BIT_SIZE)->setBlockStateId($x, $y &
SubChunk::COORD_MASK, $z, $block);
107 $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
119 for($y = self::MAX_SUBCHUNK_INDEX; $y >= self::MIN_SUBCHUNK_INDEX; --$y){
120 $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z);
121 if($height !==
null){
122 return $height | ($y << SubChunk::COORD_BIT_SIZE);
136 return $this->heightMap->get($x, $z);
146 $this->heightMap->set($x, $z, $value);
159 return $this->getSubChunk($y >>
SubChunk::COORD_BIT_SIZE)->getBiomeArray()->get($x, $y, $z);
172 public function setBiomeId(
int $x,
int $y,
int $z,
int $biomeId) : void{
173 $this->getSubChunk($y >>
SubChunk::COORD_BIT_SIZE)->getBiomeArray()->set($x, $y, $z, $biomeId);
174 $this->terrainDirtyFlags |= self::DIRTY_FLAG_BIOMES;
177 public function isLightPopulated() : ?bool{
178 return $this->lightPopulated;
181 public function setLightPopulated(?
bool $value =
true) : void{
182 $this->lightPopulated = $value;
185 public function isPopulated() : bool{
186 return $this->terrainPopulated;
189 public function setPopulated(
bool $value =
true) : void{
190 $this->terrainPopulated = $value;
191 $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
194 public function addTile(Tile $tile) : void{
195 if($tile->isClosed()){
196 throw new \InvalidArgumentException(
"Attempted to add a garbage closed Tile to a chunk");
199 $pos = $tile->getPosition();
200 if(isset($this->tiles[$index = Chunk::blockHash($pos->x, $pos->y, $pos->z)]) && $this->tiles[$index] !== $tile){
201 throw new \InvalidArgumentException(
"Another tile is already at this location");
203 $this->tiles[$index] = $tile;
206 public function removeTile(Tile $tile) : void{
208 unset($this->tiles[Chunk::blockHash($pos->x, $pos->y, $pos->z)]);
226 return $this->tiles[
Chunk::blockHash($x, $y, $z)] ?? null;
233 foreach($this->getTiles() as $tile){
242 return $this->heightMap->getValues();
252 public function isTerrainDirty() : bool{
253 return $this->terrainDirtyFlags !== self::DIRTY_FLAGS_NONE;
256 public function getTerrainDirtyFlag(
int $flag) : bool{
257 return ($this->terrainDirtyFlags & $flag) !== 0;
260 public function getTerrainDirtyFlags() : int{
261 return $this->terrainDirtyFlags;
264 public function setTerrainDirtyFlag(
int $flag,
bool $value) : void{
266 $this->terrainDirtyFlags |= $flag;
268 $this->terrainDirtyFlags &= ~$flag;
272 public function setTerrainDirty() : void{
273 $this->terrainDirtyFlags = self::DIRTY_FLAGS_ALL;
276 public function clearTerrainDirtyFlags() : void{
277 $this->terrainDirtyFlags = self::DIRTY_FLAGS_NONE;
280 public function getSubChunk(
int $y) : SubChunk{
281 if($y < self::MIN_SUBCHUNK_INDEX || $y > self::MAX_SUBCHUNK_INDEX){
282 throw new \InvalidArgumentException(
"Invalid subchunk Y coordinate $y");
284 return $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX];
291 if($y < self::MIN_SUBCHUNK_INDEX || $y > self::MAX_SUBCHUNK_INDEX){
292 throw new \InvalidArgumentException(
"Invalid subchunk Y coordinate $y");
295 $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX] = $subChunk ??
new SubChunk(Block::EMPTY_STATE_ID, [],
new PalettedBlockArray(BiomeIds::OCEAN));
296 $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
305 foreach($this->subChunks as $yOffset => $subChunk){
306 $result[$yOffset + self::MIN_SUBCHUNK_INDEX] = $subChunk;
315 foreach($this->subChunks as $y => $subChunk){
316 $subChunk->collectGarbage();
320 public function __clone(){
322 $this->subChunks = \SplFixedArray::fromArray(array_map(
function(
SubChunk $subChunk) :
SubChunk{
323 return clone $subChunk;
324 }, $this->subChunks->toArray()));
325 $this->heightMap = clone $this->heightMap;
335 public static function blockHash(
int $x,
int $y,
int $z) : int{
336 return ($y << (2 *
SubChunk::COORD_BIT_SIZE)) |