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#[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 !block_state.is_air()
35}
36
37fn motion_blocking(block_state: BlockState) -> bool {
38 !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(®istry_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 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 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 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 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 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}