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;
64 protected array $tiles = [];
71 public function __construct(array $subChunks,
bool $terrainPopulated){
72 $this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
74 foreach($this->subChunks as $y => $null){
76 $this->subChunks[$y] = $subChunks[$y + self::MIN_SUBCHUNK_INDEX] ??
new SubChunk(Block::EMPTY_STATE_ID, [],
new PalettedBlockArray(BiomeIds::OCEAN));
79 $val = (self::MAX_SUBCHUNK_INDEX + 1) * SubChunk::EDGE_LENGTH;
80 $this->heightMap = HeightArray::fill($val);
82 $this->terrainPopulated = $terrainPopulated;
89 return $this->subChunks->getSize();
102 return $this->getSubChunk($y >>
SubChunk::COORD_BIT_SIZE)->getBlockStateId($x, $y &
SubChunk::COORD_MASK, $z);
109 $this->getSubChunk($y >>
SubChunk::COORD_BIT_SIZE)->setBlockStateId($x, $y &
SubChunk::COORD_MASK, $z, $block);
110 $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
122 for($y = self::MAX_SUBCHUNK_INDEX; $y >= self::MIN_SUBCHUNK_INDEX; --$y){
123 $height = $this->getSubChunk($y)->getHighestBlockAt($x, $z);
124 if($height !==
null){
125 return $height | ($y << SubChunk::COORD_BIT_SIZE);
139 return $this->heightMap->get($x, $z);
149 $this->heightMap->set($x, $z, $value);
162 return $this->getSubChunk($y >>
SubChunk::COORD_BIT_SIZE)->getBiomeArray()->get($x, $y, $z);
175 public function setBiomeId(
int $x,
int $y,
int $z,
int $biomeId) : void{
176 $this->getSubChunk($y >>
SubChunk::COORD_BIT_SIZE)->getBiomeArray()->set($x, $y, $z, $biomeId);
177 $this->terrainDirtyFlags |= self::DIRTY_FLAG_BIOMES;
180 public function isLightPopulated() : ?bool{
181 return $this->lightPopulated;
184 public function setLightPopulated(?
bool $value =
true) : void{
185 $this->lightPopulated = $value;
188 public function isPopulated() : bool{
189 return $this->terrainPopulated;
192 public function setPopulated(
bool $value =
true) : void{
193 $this->terrainPopulated = $value;
194 $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
197 public function addTile(Tile $tile) : void{
198 if($tile->isClosed()){
199 throw new \InvalidArgumentException(
"Attempted to add a garbage closed Tile to a chunk");
202 $pos = $tile->getPosition();
203 if(isset($this->tiles[$index = Chunk::blockHash($pos->x, $pos->y, $pos->z)]) && $this->tiles[$index] !== $tile){
204 throw new \InvalidArgumentException(
"Another tile is already at this location");
206 $this->tiles[$index] = $tile;
209 public function removeTile(Tile $tile) : void{
211 unset($this->tiles[Chunk::blockHash($pos->x, $pos->y, $pos->z)]);
230 return $this->tiles[
Chunk::blockHash($x, $y, $z)] ?? null;
237 foreach($this->getTiles() as $tile){
247 return $this->heightMap->getValues();
258 public function isTerrainDirty() : bool{
259 return $this->terrainDirtyFlags !== self::DIRTY_FLAGS_NONE;
262 public function getTerrainDirtyFlag(
int $flag) : bool{
263 return ($this->terrainDirtyFlags & $flag) !== 0;
266 public function getTerrainDirtyFlags() : int{
267 return $this->terrainDirtyFlags;
270 public function setTerrainDirtyFlag(
int $flag,
bool $value) : void{
272 $this->terrainDirtyFlags |= $flag;
274 $this->terrainDirtyFlags &= ~$flag;
278 public function setTerrainDirty() : void{
279 $this->terrainDirtyFlags = self::DIRTY_FLAGS_ALL;
282 public function clearTerrainDirtyFlags() : void{
283 $this->terrainDirtyFlags = self::DIRTY_FLAGS_NONE;
286 public function getSubChunk(
int $y) : SubChunk{
287 if($y < self::MIN_SUBCHUNK_INDEX || $y > self::MAX_SUBCHUNK_INDEX){
288 throw new \InvalidArgumentException(
"Invalid subchunk Y coordinate $y");
290 return $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX];
297 if($y < self::MIN_SUBCHUNK_INDEX || $y > self::MAX_SUBCHUNK_INDEX){
298 throw new \InvalidArgumentException(
"Invalid subchunk Y coordinate $y");
301 $this->subChunks[$y - self::MIN_SUBCHUNK_INDEX] = $subChunk ??
new SubChunk(Block::EMPTY_STATE_ID, [],
new PalettedBlockArray(BiomeIds::OCEAN));
302 $this->terrainDirtyFlags |= self::DIRTY_FLAG_BLOCKS;
311 foreach($this->subChunks as $yOffset => $subChunk){
312 $result[$yOffset + self::MIN_SUBCHUNK_INDEX] = $subChunk;
321 foreach($this->subChunks as $y => $subChunk){
322 $subChunk->collectGarbage();
326 public function __clone(){
328 $this->subChunks = \SplFixedArray::fromArray(array_map(
function(
SubChunk $subChunk) :
SubChunk{
329 return clone $subChunk;
330 }, $this->subChunks->toArray()));
331 $this->heightMap = clone $this->heightMap;
341 public static function blockHash(
int $x,
int $y,
int $z) : int{
342 return ($y << (2 *
SubChunk::COORD_BIT_SIZE)) |