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;
10
11use crate::{BitStorage, Section, chunk_storage::get_block_state_from_sections};
12
13// (wg stands for worldgen)
14
15#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, AzBuf)]
16pub enum HeightmapKind {
17    WorldSurfaceWg,
18    WorldSurface,
19    OceanFloorWg,
20    OceanFloor,
21    MotionBlocking,
22    MotionBlockingNoLeaves,
23}
24
25#[derive(Clone, Debug)]
26pub struct Heightmap {
27    pub data: BitStorage,
28    pub min_y: i32,
29    pub kind: HeightmapKind,
30}
31
32fn blocks_motion(block_state: BlockState) -> bool {
33    // TODO
34    !block_state.is_air()
35}
36
37fn motion_blocking(block_state: BlockState) -> bool {
38    // TODO
39    !block_state.is_air()
40        || block_state
41            .property::<azalea_block::properties::Waterlogged>()
42            .unwrap_or_default()
43}
44
45impl HeightmapKind {
46    pub fn is_opaque(self, block_state: BlockState) -> bool {
47        let block = Box::<dyn azalea_block::BlockTrait>::from(block_state);
48        let registry_block = block.as_registry_block();
49        match self {
50            HeightmapKind::WorldSurfaceWg => !block_state.is_air(),
51            HeightmapKind::WorldSurface => !block_state.is_air(),
52            HeightmapKind::OceanFloorWg => blocks_motion(block_state),
53            HeightmapKind::OceanFloor => blocks_motion(block_state),
54            HeightmapKind::MotionBlocking => motion_blocking(block_state),
55            HeightmapKind::MotionBlockingNoLeaves => {
56                motion_blocking(block_state) && !LEAVES.contains(&registry_block)
57            }
58        }
59    }
60}
61
62impl Heightmap {
63    pub fn new(kind: HeightmapKind, dimension_height: u32, min_y: i32, data: Box<[u64]>) -> Self {
64        let bits = math::ceil_log2(dimension_height + 1);
65        let data = BitStorage::new(bits as usize, 16 * 16, Some(data)).unwrap();
66        Self { kind, data, min_y }
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 self.kind.is_opaque(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 self.kind.is_opaque(
111                    get_block_state_from_sections(
112                        sections,
113                        &ChunkBlockPos::new(pos.x, y, pos.z),
114                        self.min_y,
115                    )
116                    .unwrap_or_default(),
117                ) {
118                    self.set_height(pos.x, pos.z, y + 1);
119                    return true;
120                }
121            }
122
123            self.set_height(pos.x, pos.z, self.min_y);
124            return true;
125        }
126
127        false
128    }
129
130    /// Get an iterator over the top available block positions in this
131    /// heightmap.
132    pub fn iter_first_available(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
133        self.data.iter().enumerate().map(move |(index, height)| {
134            let x = (index % 16) as u8;
135            let z = (index / 16) as u8;
136            ChunkBlockPos::new(x, height as i32 + self.min_y, z)
137        })
138    }
139
140    /// Get an iterator over the top block positions in this heightmap.
141    pub fn iter_highest_taken(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
142        self.data.iter().enumerate().map(move |(index, height)| {
143            let x = (index % 16) as u8;
144            let z = (index / 16) as u8;
145            ChunkBlockPos::new(x, height as i32 + self.min_y - 1, z)
146        })
147    }
148}
149
150impl FromStr for HeightmapKind {
151    type Err = ();
152
153    fn from_str(s: &str) -> Result<Self, Self::Err> {
154        match s {
155            "WORLD_SURFACE_WG" => Ok(HeightmapKind::WorldSurfaceWg),
156            "WORLD_SURFACE" => Ok(HeightmapKind::WorldSurface),
157            "OCEAN_FLOOR_WG" => Ok(HeightmapKind::OceanFloorWg),
158            "OCEAN_FLOOR" => Ok(HeightmapKind::OceanFloor),
159            "MOTION_BLOCKING" => Ok(HeightmapKind::MotionBlocking),
160            "MOTION_BLOCKING_NO_LEAVES" => Ok(HeightmapKind::MotionBlockingNoLeaves),
161            _ => Err(()),
162        }
163    }
164}
165
166impl Display for HeightmapKind {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        match self {
169            HeightmapKind::WorldSurfaceWg => write!(f, "WORLD_SURFACE_WG"),
170            HeightmapKind::WorldSurface => write!(f, "WORLD_SURFACE"),
171            HeightmapKind::OceanFloorWg => write!(f, "OCEAN_FLOOR_WG"),
172            HeightmapKind::OceanFloor => write!(f, "OCEAN_FLOOR"),
173            HeightmapKind::MotionBlocking => write!(f, "MOTION_BLOCKING"),
174            HeightmapKind::MotionBlockingNoLeaves => write!(f, "MOTION_BLOCKING_NO_LEAVES"),
175        }
176    }
177}