azalea_world/
heightmap.rs

1use std::{
2    fmt::{self, Display},
3    str::FromStr,
4};
5
6use azalea_block::BlockState;
7use azalea_buf::AzBuf;
8use azalea_core::{math, position::ChunkBlockPos};
9use azalea_registry::tags::blocks::LEAVES;
10use tracing::warn;
11
12use crate::{BitStorage, Section, chunk_storage::get_block_state_from_sections};
13
14// (wg stands for worldgen)
15
16#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, AzBuf)]
17pub enum HeightmapKind {
18    WorldSurfaceWg,
19    WorldSurface,
20    OceanFloorWg,
21    OceanFloor,
22    MotionBlocking,
23    MotionBlockingNoLeaves,
24}
25
26#[derive(Clone, Debug)]
27pub struct Heightmap {
28    pub data: BitStorage,
29    pub min_y: i32,
30    pub kind: HeightmapKind,
31}
32
33fn blocks_motion(block_state: BlockState) -> bool {
34    // TODO
35    !block_state.is_air()
36}
37
38fn motion_blocking(block_state: BlockState) -> bool {
39    // TODO
40    !block_state.is_air()
41        || block_state
42            .property::<azalea_block::properties::Waterlogged>()
43            .unwrap_or_default()
44}
45
46impl HeightmapKind {
47    pub fn is_opaque(self, block_state: BlockState) -> bool {
48        let block = Box::<dyn azalea_block::BlockTrait>::from(block_state);
49        let registry_block = block.as_registry_block();
50        match self {
51            HeightmapKind::WorldSurfaceWg => !block_state.is_air(),
52            HeightmapKind::WorldSurface => !block_state.is_air(),
53            HeightmapKind::OceanFloorWg => blocks_motion(block_state),
54            HeightmapKind::OceanFloor => blocks_motion(block_state),
55            HeightmapKind::MotionBlocking => motion_blocking(block_state),
56            HeightmapKind::MotionBlockingNoLeaves => {
57                motion_blocking(block_state) && !LEAVES.contains(&registry_block)
58            }
59        }
60    }
61}
62
63impl Heightmap {
64    pub fn new(kind: HeightmapKind, dimension_height: u32, min_y: i32, data: Box<[u64]>) -> Self {
65        let bits = math::ceil_log2(dimension_height + 1);
66        let mut bit_storage = BitStorage::new(bits as usize, 16 * 16, None)
67            .expect("data is empty, so this can't fail");
68        if bit_storage.data.len() != data.len() {
69            warn!(
70                "Ignoring heightmap data, size does not match; expected: {}, got: {}",
71                bit_storage.data.len(),
72                data.len()
73            );
74        } else {
75            bit_storage.data.copy_from_slice(&data);
76        }
77        Self {
78            kind,
79            data: bit_storage,
80            min_y,
81        }
82    }
83
84    pub fn get_index(x: u8, z: u8) -> usize {
85        (x as usize) + (z as usize) * 16
86    }
87
88    pub fn get_first_available_at_index(&self, index: usize) -> i32 {
89        self.data.get(index) as i32 + self.min_y
90    }
91
92    pub fn get_first_available(&self, x: u8, z: u8) -> i32 {
93        self.get_first_available_at_index(Self::get_index(x, z))
94    }
95
96    pub fn get_highest_taken(&self, x: u8, z: u8) -> i32 {
97        self.get_first_available(x, z) - 1
98    }
99
100    pub fn set_height(&mut self, x: u8, z: u8, height: i32) {
101        self.data
102            .set(Self::get_index(x, z), (height - self.min_y) as u64);
103    }
104
105    /// Updates the heightmap with the given block state at the given position.
106    pub fn update(
107        &mut self,
108        pos: &ChunkBlockPos,
109        block_state: BlockState,
110        sections: &[Section],
111    ) -> bool {
112        let first_available_y = self.get_first_available(pos.x, pos.z);
113        if pos.y <= first_available_y - 2 {
114            return false;
115        }
116        if self.kind.is_opaque(block_state) {
117            // increase y
118            if pos.y >= first_available_y {
119                self.set_height(pos.x, pos.z, pos.y + 1);
120                return true;
121            }
122        } else if first_available_y - 1 == pos.y {
123            // decrease y
124            for y in (self.min_y..pos.y).rev() {
125                if self.kind.is_opaque(
126                    get_block_state_from_sections(
127                        sections,
128                        &ChunkBlockPos::new(pos.x, y, pos.z),
129                        self.min_y,
130                    )
131                    .unwrap_or_default(),
132                ) {
133                    self.set_height(pos.x, pos.z, y + 1);
134                    return true;
135                }
136            }
137
138            self.set_height(pos.x, pos.z, self.min_y);
139            return true;
140        }
141
142        false
143    }
144
145    /// Get an iterator over the top available block positions in this
146    /// heightmap.
147    pub fn iter_first_available(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
148        self.data.iter().enumerate().map(move |(index, height)| {
149            let x = (index % 16) as u8;
150            let z = (index / 16) as u8;
151            ChunkBlockPos::new(x, height as i32 + self.min_y, z)
152        })
153    }
154
155    /// Get an iterator over the top block positions in this heightmap.
156    pub fn iter_highest_taken(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
157        self.data.iter().enumerate().map(move |(index, height)| {
158            let x = (index % 16) as u8;
159            let z = (index / 16) as u8;
160            ChunkBlockPos::new(x, height as i32 + self.min_y - 1, z)
161        })
162    }
163}
164
165impl FromStr for HeightmapKind {
166    type Err = ();
167
168    fn from_str(s: &str) -> Result<Self, Self::Err> {
169        match s {
170            "WORLD_SURFACE_WG" => Ok(HeightmapKind::WorldSurfaceWg),
171            "WORLD_SURFACE" => Ok(HeightmapKind::WorldSurface),
172            "OCEAN_FLOOR_WG" => Ok(HeightmapKind::OceanFloorWg),
173            "OCEAN_FLOOR" => Ok(HeightmapKind::OceanFloor),
174            "MOTION_BLOCKING" => Ok(HeightmapKind::MotionBlocking),
175            "MOTION_BLOCKING_NO_LEAVES" => Ok(HeightmapKind::MotionBlockingNoLeaves),
176            _ => Err(()),
177        }
178    }
179}
180
181impl Display for HeightmapKind {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self {
184            HeightmapKind::WorldSurfaceWg => write!(f, "WORLD_SURFACE_WG"),
185            HeightmapKind::WorldSurface => write!(f, "WORLD_SURFACE"),
186            HeightmapKind::OceanFloorWg => write!(f, "OCEAN_FLOOR_WG"),
187            HeightmapKind::OceanFloor => write!(f, "OCEAN_FLOOR"),
188            HeightmapKind::MotionBlocking => write!(f, "MOTION_BLOCKING"),
189            HeightmapKind::MotionBlockingNoLeaves => write!(f, "MOTION_BLOCKING_NO_LEAVES"),
190        }
191    }
192}