use std::io::Write;
use std::sync::Arc;
use std::io;
use glam::{IVec3, DVec3};
use crate::biome::Biome;
use crate::block;
pub const CHUNK_WIDTH: usize = 16;
pub const CHUNK_HEIGHT: usize = 128;
pub const CHUNK_2D_SIZE: usize = CHUNK_WIDTH * CHUNK_WIDTH;
pub const CHUNK_3D_SIZE: usize = CHUNK_HEIGHT * CHUNK_2D_SIZE;
#[inline]
fn calc_3d_index(pos: IVec3) -> usize {
debug_assert!(pos.y >= 0 && pos.y < CHUNK_HEIGHT as i32);
let x = pos.x as u32 & 0b1111;
let z = pos.z as u32 & 0b1111;
let y = pos.y as u32 & 0b1111111;
((x << 11) | (z << 7) | (y << 0)) as usize
}
#[inline]
fn calc_2d_index(pos: IVec3) -> usize {
let x = pos.x as u32 & 0b1111;
let z = pos.z as u32 & 0b1111;
((z << 4) | (x << 0)) as usize
}
#[inline]
pub fn calc_chunk_pos(pos: IVec3) -> Option<(i32, i32)> {
if pos.y < 0 || pos.y >= CHUNK_HEIGHT as i32 {
None
} else {
Some(calc_chunk_pos_unchecked(pos))
}
}
#[inline]
pub fn calc_chunk_pos_unchecked(pos: IVec3) -> (i32, i32) {
(pos.x >> 4, pos.z >> 4)
}
#[inline]
pub fn calc_entity_chunk_pos(pos: DVec3) -> (i32, i32) {
calc_chunk_pos_unchecked(pos.floor().as_ivec3())
}
#[derive(Clone)]
pub struct Chunk {
pub block: ChunkArray3<u8>,
pub metadata: ChunkNibbleArray3,
pub block_light: ChunkNibbleArray3,
pub sky_light: ChunkNibbleArray3,
pub height: ChunkArray2<u8>,
pub biome: ChunkArray2<Biome>,
}
impl Chunk {
pub fn new() -> Arc<Self> {
Arc::new(Self {
block: [block::AIR; CHUNK_3D_SIZE],
metadata: ChunkNibbleArray3::new(0),
block_light: ChunkNibbleArray3::new(0),
sky_light: ChunkNibbleArray3::new(15),
height: [0; CHUNK_2D_SIZE],
biome: [Biome::Void; CHUNK_2D_SIZE],
})
}
#[inline]
pub fn get_block(&self, pos: IVec3) -> (u8, u8) {
let index = calc_3d_index(pos);
(self.block[index], self.metadata.get(index))
}
#[inline]
pub fn set_block(&mut self, pos: IVec3, id: u8, metadata: u8) {
let index = calc_3d_index(pos);
self.block[index] = id;
self.metadata.set(index, metadata);
}
#[inline]
pub fn get_block_light(&self, pos: IVec3) -> u8 {
self.block_light.get(calc_3d_index(pos))
}
#[inline]
pub fn set_block_light(&mut self, pos: IVec3, light: u8) {
self.block_light.set(calc_3d_index(pos), light);
}
#[inline]
pub fn get_sky_light(&self, pos: IVec3) -> u8 {
self.sky_light.get(calc_3d_index(pos))
}
#[inline]
pub fn set_sky_light(&mut self, pos: IVec3, light: u8) {
self.sky_light.set(calc_3d_index(pos), light);
}
#[inline]
pub fn get_height(&self, pos: IVec3) -> u8 {
self.height[calc_2d_index(pos)]
}
#[inline]
pub fn set_height(&mut self, pos: IVec3, height: u8) {
debug_assert!(height <= 128, "illegal height");
self.height[calc_2d_index(pos)] = height;
}
#[inline]
pub fn get_biome(&self, pos: IVec3) -> Biome {
self.biome[calc_2d_index(pos)]
}
#[inline]
pub fn set_biome(&mut self, pos: IVec3, biome: Biome) {
self.biome[calc_2d_index(pos)] = biome;
}
pub fn fill_block(&mut self, from: IVec3, size: IVec3, id: u8, metadata: u8) {
for x in from.x..from.x + size.x {
for z in from.z..from.z + size.z {
for y in from.y..from.y + size.y {
let index = calc_3d_index(IVec3::new(x, y, z));
self.block[index] = id;
self.metadata.set(index, metadata);
}
}
}
}
pub fn fill_light(&mut self, from: IVec3, size: IVec3, block_light: u8, sky_light: u8) {
for x in from.x..from.x + size.x {
for z in from.z..from.z + size.z {
for y in from.y..from.y + size.y {
let index = calc_3d_index(IVec3::new(x, y, z));
self.block_light.set(index, block_light);
self.sky_light.set(index, sky_light);
}
}
}
}
pub fn recompute_height(&mut self, pos: IVec3) -> u8 {
assert!(pos.y >= 0 && pos.y < CHUNK_HEIGHT as i32);
let prev_height = self.get_height(pos) as i32;
let mut new_height = prev_height.max(pos.y + 1);
while new_height > 0 {
new_height -= 1;
let pos = IVec3::new(pos.x, new_height, pos.z);
let (id, _) = self.get_block(pos);
if block::material::get_light_opacity(id) != 0 {
new_height += 1;
break;
}
}
if new_height == prev_height {
return prev_height as u8;
}
if new_height < prev_height {
for y in new_height..prev_height {
let pos = IVec3::new(pos.x, y, pos.z);
self.set_sky_light(pos, 15);
}
}
let mut sky_light = 15u8;
for y in (0..new_height).rev() {
let pos = IVec3::new(pos.x, y, pos.z);
if sky_light > 0 {
let (id, _) = self.get_block(pos);
let opacity = block::material::get_light_opacity(id).max(1);
sky_light = sky_light.saturating_sub(opacity);
}
self.set_sky_light(pos, sky_light);
}
self.set_height(pos, new_height as u8);
new_height as u8
}
pub fn recompute_all_height(&mut self) {
for x in 0..CHUNK_WIDTH {
for z in 0..CHUNK_WIDTH {
self.recompute_height(IVec3::new(x as i32, 127, z as i32));
}
}
}
pub fn write_data(&self, mut writer: impl Write, from: &mut IVec3, size: &mut IVec3) -> io::Result<()> {
if from.y % 2 != 0 {
from.y -= 1;
size.y += 1;
}
size.y = (size.y + 1) & !1;
debug_assert!(from.y % 2 == 0);
debug_assert!(size.y % 2 == 0);
debug_assert!(size.x <= 16 && size.y <= 128 && size.z <= 16);
let height = size.y as usize;
let half_height = height / 2;
let from = *from;
let to = from + *size;
if size.x == 16 && size.z == 16 && size.y == 128 {
writer.write_all(&self.block)?;
writer.write_all(&self.metadata.inner)?;
writer.write_all(&self.block_light.inner)?;
writer.write_all(&self.sky_light.inner)?;
} else {
for x in from.x..to.x {
for z in from.z..to.z {
let index = calc_3d_index(IVec3::new(x, from.y, z));
writer.write_all(&self.block[index..index + height])?;
}
}
for x in from.x..to.x {
for z in from.z..to.z {
let index = calc_3d_index(IVec3::new(x, from.y, z)) / 2;
writer.write_all(&self.metadata.inner[index..index + half_height])?;
}
}
for x in from.x..to.x {
for z in from.z..to.z {
let index = calc_3d_index(IVec3::new(x, from.y, z)) / 2;
writer.write_all(&self.block_light.inner[index..index + half_height])?;
}
}
for x in from.x..to.x {
for z in from.z..to.z {
let index = calc_3d_index(IVec3::new(x, from.y, z)) / 2;
writer.write_all(&self.sky_light.inner[index..index + half_height])?;
}
}
}
Ok(())
}
}
pub type ChunkArray2<T> = [T; CHUNK_2D_SIZE];
pub type ChunkArray3<T> = [T; CHUNK_3D_SIZE];
#[derive(Clone)]
pub struct ChunkNibbleArray3 {
pub inner: [u8; CHUNK_3D_SIZE / 2]
}
impl ChunkNibbleArray3 {
pub const fn new(init: u8) -> Self {
debug_assert!(init <= 0x0F);
let init = init << 4 | init;
Self { inner: [init; CHUNK_3D_SIZE / 2] }
}
#[inline]
pub fn get(&self, index: usize) -> u8 {
let slot = self.inner[index >> 1];
if index & 1 == 0 {
slot & 0x0F
} else {
(slot & 0xF0) >> 4
}
}
#[inline]
pub fn set(&mut self, index: usize, value: u8) {
debug_assert!(value <= 0x0F);
let slot = &mut self.inner[index >> 1];
if index & 1 == 0 {
*slot = (*slot & 0xF0) | value;
} else {
*slot = (*slot & 0x0F) | (value << 4);
}
}
}