azalea_world/
heightmap.rs

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