azalea_world/
heightmap.rs

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