// TODO: https://github.com/rust-lang/rust/issues/88581 for div_ceil use std::io::Write; use bitvec::bitvec; use bitvec::vec::BitVec; use num::Integer; use crate::glm::DVec2; use crate::packets::play::{ BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange, }; use crate::protocol::{Encode, Nbt}; use crate::var_int::VarInt; use crate::BiomeId; pub struct Chunk { sections: Box<[ChunkSection]>, // TODO block_entities: HashMap, heightmap: Vec, modified: bool, created_this_tick: bool, } impl Chunk { pub(crate) fn new(num_sections: usize) -> Self { let sect = ChunkSection { blocks: [0; 4096], biomes: [0; 64], compact_data: Vec::new(), modified: true, }; let mut chunk = Self { sections: vec![sect; num_sections].into(), heightmap: Vec::new(), modified: true, created_this_tick: true, }; chunk.apply_modifications(); chunk } pub(crate) fn deallocate(&mut self) { self.sections = Box::new([]); } pub fn created_this_tick(&self) -> bool { self.created_this_tick } pub(crate) fn clear_created_this_tick(&mut self) { self.created_this_tick = false; } pub fn height(&self) -> usize { self.sections.len() * 16 } pub fn get_block_state(&self, x: usize, y: usize, z: usize) -> u16 { if x < 16 && y < self.height() && z < 16 { self.sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] } else { 0 } } pub fn set_block_state(&mut self, x: usize, y: usize, z: usize, block: u16) { if x < 16 && y < self.height() && z < 16 { let sec = &mut self.sections[y / 16]; let idx = x + z * 16 + y % 16 * 16 * 16; if block != sec.blocks[idx] { sec.blocks[idx] = block; // TODO: set the modified bit. sec.modified = true; self.modified = true; // TODO: update block entity if b could have block entity data. } } } pub fn get_biome(&self, x: usize, y: usize, z: usize) -> BiomeId { if x < 4 && y < self.height() / 4 && z < 4 { BiomeId(self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4]) } else { BiomeId::default() } } pub fn set_biome(&mut self, x: usize, y: usize, z: usize, b: BiomeId) { if x < 4 && y < self.height() / 4 && z < 4 { self.sections[y / 4].biomes[x + z * 4 + y % 4 * 4 * 4] = b.0; } } /// Gets the chunk data packet with the given number of sections for this /// chunk. This does not include unapplied changes. pub(crate) fn chunk_data_packet( &self, pos: ChunkPos, num_sections: usize, ) -> ChunkDataAndUpdateLight { let mut blocks_and_biomes = Vec::new(); for i in 0..num_sections { match self.sections.get(i) { Some(sect) => { blocks_and_biomes.extend_from_slice(§.compact_data); } None => { // Extra chunk sections are encoded as empty with the default biome. // non air block count 0i16.encode(&mut blocks_and_biomes).unwrap(); // blocks encode_paletted_container_single(0, &mut blocks_and_biomes).unwrap(); // biomes encode_paletted_container_single(0, &mut blocks_and_biomes).unwrap(); } } } let motion_blocking = if num_sections == self.sections.len() { self.heightmap.clone() } else { // This is bad for two reasons: // - Rebuilding the heightmap from scratch is slow. // - The new heightmap is subtly wrong because we are building it from blocks // with the modifications applied already. // But you shouldn't be using chunks with heights different than the dimensions // they're residing in anyway, so whatever. let mut heightmap = Vec::new(); build_heightmap(&self.sections, &mut heightmap); heightmap }; ChunkDataAndUpdateLight { chunk_x: pos.x, chunk_z: pos.z, heightmaps: Nbt(ChunkDataHeightmaps { motion_blocking }), blocks_and_biomes, block_entities: Vec::new(), // TODO trust_edges: true, sky_light_mask: bitvec![u64, _; 1; num_sections + 2], block_light_mask: BitVec::new(), empty_sky_light_mask: BitVec::new(), empty_block_light_mask: BitVec::new(), sky_light_arrays: vec![[0xff; 2048]; num_sections + 2], block_light_arrays: Vec::new(), } } /// Gets the unapplied changes to this chunk as a block change packet. pub(crate) fn block_change_packet(&self, pos: ChunkPos) -> Option { if !self.modified { return None; } // TODO None } pub(crate) fn apply_modifications(&mut self) { if self.modified { self.modified = false; for sect in self.sections.iter_mut() { if sect.modified { sect.modified = false; sect.compact_data.clear(); // TODO: consider cave_air and void_air. let non_air_block_count = sect.blocks.iter().filter(|&&b| b != 0).count(); (non_air_block_count as i16) .encode(&mut sect.compact_data) .unwrap(); encode_paletted_container(§.blocks, 4, 9, 15, &mut sect.compact_data) .unwrap(); // TODO: The direct bits per idx changes depending on the number of biomes in // the biome registry. encode_paletted_container(§.biomes, 0, 4, 6, &mut sect.compact_data) .unwrap(); } } build_heightmap(&self.sections, &mut self.heightmap); } } } #[derive(Clone, Debug)] pub(crate) enum BlockChangePacket { Single(BlockChange), Multi(MultiBlockChange), } impl From for ClientPlayPacket { fn from(p: BlockChangePacket) -> Self { match p { BlockChangePacket::Single(p) => p.into(), BlockChangePacket::Multi(p) => p.into(), } } } /// The X and Z position of a chunk in a world. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct ChunkPos { /// The X position of the chunk. pub x: i32, /// The Z position of the chunk. pub z: i32, } impl ChunkPos { pub const fn new(x: i32, z: i32) -> Self { Self { x, z } } /// Returns the chunk position of the chunk that the given coordinates are /// contained within. pub fn from_xz(xz: DVec2) -> Self { Self { x: (xz.x / 16.0) as i32, z: (xz.y / 16.0) as i32, } } } impl From<(i32, i32)> for ChunkPos { fn from((x, z): (i32, i32)) -> Self { ChunkPos { x, z } } } /// A 16x16x16 section of blocks, biomes, and light in a chunk. #[derive(Clone)] struct ChunkSection { /// The blocks in this section, stored in x, z, y order. blocks: [u16; 4096], biomes: [u16; 64], compact_data: Vec, /// If the blocks or biomes were modified. modified: bool, } /// Builds the MOTION_BLOCKING heightmap. fn build_heightmap(sections: &[ChunkSection], heightmap: &mut Vec) { let height = sections.len() * 16; let bits_per_val = log2_ceil(height); let vals_per_u64 = 64 / bits_per_val; let num_u64s = Integer::div_ceil(&256, &vals_per_u64); heightmap.clear(); heightmap.resize(num_u64s, 0); for x in 0..16 { for z in 0..16 { for y in (0..height).rev() { // TODO: is_solid || is_fluid heuristic for motion blocking. if sections[y / 16].blocks[x + z * 16 + y % 16 * 16 * 16] != 0 { let column_height = y as u64; let i = x * 16 + z; // TODO: X or Z major? heightmap[i / vals_per_u64] |= (column_height << (i % vals_per_u64 * bits_per_val)) as i64; break; } } } } } fn encode_paletted_container( entries: &[u16], min_bits_per_idx: usize, direct_threshold: usize, direct_bits_per_idx: usize, w: &mut impl Write, ) -> anyhow::Result<()> { let mut palette = Vec::new(); for &entry in entries { if !palette.contains(&entry) { palette.push(entry); } } let bits_per_idx = log2_ceil(palette.len()); (bits_per_idx as u8).encode(w)?; if bits_per_idx == 0 { // Single value case debug_assert_eq!(palette.len(), 1); VarInt(palette[0] as i32).encode(w)?; VarInt(0).encode(w)?; // data array length } else if bits_per_idx >= direct_threshold { // Direct case // Skip the palette let idxs_per_u64 = 64 / direct_bits_per_idx; let num_u64s = Integer::div_ceil(&entries.len(), &idxs_per_u64); VarInt(num_u64s as i32).encode(w)?; for &entry in entries { let mut val = 0u64; for i in 0..idxs_per_u64 { val |= (entry as u64) << (i * direct_bits_per_idx); } val.encode(w)?; } } else { // Indirect case VarInt(palette.len() as i32).encode(w)?; for &val in &palette { VarInt(val as i32).encode(w)?; } let bits_per_idx = bits_per_idx.max(min_bits_per_idx); let idxs_per_u64 = 64 / bits_per_idx; let num_u64s = Integer::div_ceil(&entries.len(), &idxs_per_u64); VarInt(num_u64s as i32).encode(w)?; for &entry in entries { let palette_idx = palette .iter() .position(|&e| e == entry) .expect("entry should be in the palette") as u64; let mut val = 0u64; for i in 0..idxs_per_u64 { val |= palette_idx << (i * bits_per_idx); } val.encode(w)?; } } Ok(()) } /// Encode a paletted container where all values are the same. fn encode_paletted_container_single(entry: u16, w: &mut impl Write) -> anyhow::Result<()> { 0u8.encode(w)?; // bits per idx VarInt(entry as i32).encode(w)?; // single value VarInt(0).encode(w)?; // data array length Ok(()) } /// Calculates the log base 2 rounded up. fn log2_ceil(n: usize) -> usize { n.next_power_of_two().trailing_zeros() as usize }