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#[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 !block_state.is_air()
31}
32
33fn motion_blocking(block_state: BlockState) -> bool {
34 !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(®istry_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 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 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 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 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 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}