azalea_world/
heightmap.rs1use azalea_block::BlockState;
2use azalea_core::{heightmap_kind::HeightmapKind as HeightmapKind_, math, position::ChunkBlockPos};
3use azalea_registry::tags::blocks::LEAVES;
4use tracing::warn;
5
6use crate::{BitStorage, Section, chunk::get_block_state_from_sections};
7
8#[deprecated = "moved to `azalea_core::heightmap_kind::HeightmapKind`."]
11pub type HeightmapKind = azalea_core::heightmap_kind::HeightmapKind;
12
13#[derive(Clone, Debug)]
14pub struct Heightmap {
15 pub data: BitStorage,
16 pub min_y: i32,
17 pub kind: HeightmapKind_,
18}
19
20fn blocks_motion(block_state: BlockState) -> bool {
21 !block_state.is_air()
23}
24
25fn motion_blocking(block_state: BlockState) -> bool {
26 !block_state.is_air()
28 || block_state
29 .property::<azalea_block::properties::Waterlogged>()
30 .unwrap_or_default()
31}
32
33pub fn is_heightmap_opaque(heightmap: HeightmapKind_, block_state: BlockState) -> bool {
34 let registry_block = block_state.as_block_kind();
35 type K = HeightmapKind_;
36 match heightmap {
37 K::WorldSurfaceWg => !block_state.is_air(),
38 K::WorldSurface => !block_state.is_air(),
39 K::OceanFloorWg => blocks_motion(block_state),
40 K::OceanFloor => blocks_motion(block_state),
41 K::MotionBlocking => motion_blocking(block_state),
42 K::MotionBlockingNoLeaves => {
43 motion_blocking(block_state) && !LEAVES.contains(®istry_block)
44 }
45 }
46}
47
48impl Heightmap {
49 pub fn new(kind: HeightmapKind_, dimension_height: u32, min_y: i32, data: Box<[u64]>) -> Self {
50 let bits = math::ceil_log2(dimension_height + 1);
51 let mut bit_storage = BitStorage::new(bits as usize, 16 * 16, None)
52 .expect("data is empty, so this can't fail");
53 if bit_storage.data.len() != data.len() {
54 warn!(
55 "Ignoring heightmap data, size does not match; expected: {}, got: {}",
56 bit_storage.data.len(),
57 data.len()
58 );
59 } else {
60 bit_storage.data.copy_from_slice(&data);
61 }
62 Self {
63 kind,
64 data: bit_storage,
65 min_y,
66 }
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 is_heightmap_opaque(self.kind, 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 is_heightmap_opaque(
111 self.kind,
112 get_block_state_from_sections(
113 sections,
114 &ChunkBlockPos::new(pos.x, y, pos.z),
115 self.min_y,
116 )
117 .unwrap_or_default(),
118 ) {
119 self.set_height(pos.x, pos.z, y + 1);
120 return true;
121 }
122 }
123
124 self.set_height(pos.x, pos.z, self.min_y);
125 return true;
126 }
127
128 false
129 }
130
131 pub fn iter_first_available(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
134 self.data.iter().enumerate().map(move |(index, height)| {
135 let x = (index % 16) as u8;
136 let z = (index / 16) as u8;
137 ChunkBlockPos::new(x, height as i32 + self.min_y, z)
138 })
139 }
140
141 pub fn iter_highest_taken(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
143 self.data.iter().enumerate().map(move |(index, height)| {
144 let x = (index % 16) as u8;
145 let z = (index / 16) as u8;
146 ChunkBlockPos::new(x, height as i32 + self.min_y - 1, z)
147 })
148 }
149}