Skip to main content

azalea_world/
heightmap.rs

1use azalea_block::BlockState;
2use azalea_core::{heightmap_kind::HeightmapKind as HeightmapKind_, math, position::ChunkBlockPos};
3use azalea_registry::tags::blocks::LEAVES;
4use tracing::warn;
5
6use crate::{BitStorage, Section, chunk::get_block_state_from_sections};
7
8// TODO: when removing this deprecated marker, also rename `HeightmapKind_` back
9// to `HeightmapKind`.
10#[deprecated = "moved to `azalea_core::heightmap_kind::HeightmapKind`."]
11pub type HeightmapKind = azalea_core::heightmap_kind::HeightmapKind;
12
13#[derive(Clone, Debug)]
14pub struct Heightmap {
15    pub data: BitStorage,
16    pub min_y: i32,
17    pub kind: HeightmapKind_,
18}
19
20fn blocks_motion(block_state: BlockState) -> bool {
21    // TODO
22    !block_state.is_air()
23}
24
25fn motion_blocking(block_state: BlockState) -> bool {
26    // TODO
27    !block_state.is_air()
28        || block_state
29            .property::<azalea_block::properties::Waterlogged>()
30            .unwrap_or_default()
31}
32
33pub fn is_heightmap_opaque(heightmap: HeightmapKind_, block_state: BlockState) -> bool {
34    let registry_block = block_state.as_block_kind();
35    type K = HeightmapKind_;
36    match heightmap {
37        K::WorldSurfaceWg => !block_state.is_air(),
38        K::WorldSurface => !block_state.is_air(),
39        K::OceanFloorWg => blocks_motion(block_state),
40        K::OceanFloor => blocks_motion(block_state),
41        K::MotionBlocking => motion_blocking(block_state),
42        K::MotionBlockingNoLeaves => {
43            motion_blocking(block_state) && !LEAVES.contains(&registry_block)
44        }
45    }
46}
47
48impl Heightmap {
49    pub fn new(kind: HeightmapKind_, dimension_height: u32, min_y: i32, data: Box<[u64]>) -> Self {
50        let bits = math::ceil_log2(dimension_height + 1);
51        let mut bit_storage = BitStorage::new(bits as usize, 16 * 16, None)
52            .expect("data is empty, so this can't fail");
53        if bit_storage.data.len() != data.len() {
54            warn!(
55                "Ignoring heightmap data, size does not match; expected: {}, got: {}",
56                bit_storage.data.len(),
57                data.len()
58            );
59        } else {
60            bit_storage.data.copy_from_slice(&data);
61        }
62        Self {
63            kind,
64            data: bit_storage,
65            min_y,
66        }
67    }
68
69    pub fn get_index(x: u8, z: u8) -> usize {
70        (x as usize) + (z as usize) * 16
71    }
72
73    pub fn get_first_available_at_index(&self, index: usize) -> i32 {
74        self.data.get(index) as i32 + self.min_y
75    }
76
77    pub fn get_first_available(&self, x: u8, z: u8) -> i32 {
78        self.get_first_available_at_index(Self::get_index(x, z))
79    }
80
81    pub fn get_highest_taken(&self, x: u8, z: u8) -> i32 {
82        self.get_first_available(x, z) - 1
83    }
84
85    pub fn set_height(&mut self, x: u8, z: u8, height: i32) {
86        self.data
87            .set(Self::get_index(x, z), (height - self.min_y) as u64);
88    }
89
90    /// Updates the heightmap with the given block state at the given position.
91    pub fn update(
92        &mut self,
93        pos: &ChunkBlockPos,
94        block_state: BlockState,
95        sections: &[Section],
96    ) -> bool {
97        let first_available_y = self.get_first_available(pos.x, pos.z);
98        if pos.y <= first_available_y - 2 {
99            return false;
100        }
101        if is_heightmap_opaque(self.kind, block_state) {
102            // increase y
103            if pos.y >= first_available_y {
104                self.set_height(pos.x, pos.z, pos.y + 1);
105                return true;
106            }
107        } else if first_available_y - 1 == pos.y {
108            // decrease y
109            for y in (self.min_y..pos.y).rev() {
110                if is_heightmap_opaque(
111                    self.kind,
112                    get_block_state_from_sections(
113                        sections,
114                        &ChunkBlockPos::new(pos.x, y, pos.z),
115                        self.min_y,
116                    )
117                    .unwrap_or_default(),
118                ) {
119                    self.set_height(pos.x, pos.z, y + 1);
120                    return true;
121                }
122            }
123
124            self.set_height(pos.x, pos.z, self.min_y);
125            return true;
126        }
127
128        false
129    }
130
131    /// Get an iterator over the top available block positions in this
132    /// heightmap.
133    pub fn iter_first_available(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
134        self.data.iter().enumerate().map(move |(index, height)| {
135            let x = (index % 16) as u8;
136            let z = (index / 16) as u8;
137            ChunkBlockPos::new(x, height as i32 + self.min_y, z)
138        })
139    }
140
141    /// Get an iterator over the top block positions in this heightmap.
142    pub fn iter_highest_taken(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
143        self.data.iter().enumerate().map(move |(index, height)| {
144            let x = (index % 16) as u8;
145            let z = (index / 16) as u8;
146            ChunkBlockPos::new(x, height as i32 + self.min_y - 1, z)
147        })
148    }
149}