azalea_world/
heightmap.rs1use azalea_block::{BlockState, BlockTrait};
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_storage::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 block = Box::<dyn BlockTrait>::from(block_state);
35 let registry_block = block.as_registry_block();
36 type K = HeightmapKind_;
37 match heightmap {
38 K::WorldSurfaceWg => !block_state.is_air(),
39 K::WorldSurface => !block_state.is_air(),
40 K::OceanFloorWg => blocks_motion(block_state),
41 K::OceanFloor => blocks_motion(block_state),
42 K::MotionBlocking => motion_blocking(block_state),
43 K::MotionBlockingNoLeaves => {
44 motion_blocking(block_state) && !LEAVES.contains(®istry_block)
45 }
46 }
47}
48
49impl Heightmap {
50 pub fn new(kind: HeightmapKind_, dimension_height: u32, min_y: i32, data: Box<[u64]>) -> Self {
51 let bits = math::ceil_log2(dimension_height + 1);
52 let mut bit_storage = BitStorage::new(bits as usize, 16 * 16, None)
53 .expect("data is empty, so this can't fail");
54 if bit_storage.data.len() != data.len() {
55 warn!(
56 "Ignoring heightmap data, size does not match; expected: {}, got: {}",
57 bit_storage.data.len(),
58 data.len()
59 );
60 } else {
61 bit_storage.data.copy_from_slice(&data);
62 }
63 Self {
64 kind,
65 data: bit_storage,
66 min_y,
67 }
68 }
69
70 pub fn get_index(x: u8, z: u8) -> usize {
71 (x as usize) + (z as usize) * 16
72 }
73
74 pub fn get_first_available_at_index(&self, index: usize) -> i32 {
75 self.data.get(index) as i32 + self.min_y
76 }
77
78 pub fn get_first_available(&self, x: u8, z: u8) -> i32 {
79 self.get_first_available_at_index(Self::get_index(x, z))
80 }
81
82 pub fn get_highest_taken(&self, x: u8, z: u8) -> i32 {
83 self.get_first_available(x, z) - 1
84 }
85
86 pub fn set_height(&mut self, x: u8, z: u8, height: i32) {
87 self.data
88 .set(Self::get_index(x, z), (height - self.min_y) as u64);
89 }
90
91 pub fn update(
93 &mut self,
94 pos: &ChunkBlockPos,
95 block_state: BlockState,
96 sections: &[Section],
97 ) -> bool {
98 let first_available_y = self.get_first_available(pos.x, pos.z);
99 if pos.y <= first_available_y - 2 {
100 return false;
101 }
102 if is_heightmap_opaque(self.kind, block_state) {
103 if pos.y >= first_available_y {
105 self.set_height(pos.x, pos.z, pos.y + 1);
106 return true;
107 }
108 } else if first_available_y - 1 == pos.y {
109 for y in (self.min_y..pos.y).rev() {
111 if is_heightmap_opaque(
112 self.kind,
113 get_block_state_from_sections(
114 sections,
115 &ChunkBlockPos::new(pos.x, y, pos.z),
116 self.min_y,
117 )
118 .unwrap_or_default(),
119 ) {
120 self.set_height(pos.x, pos.z, y + 1);
121 return true;
122 }
123 }
124
125 self.set_height(pos.x, pos.z, self.min_y);
126 return true;
127 }
128
129 false
130 }
131
132 pub fn iter_first_available(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
135 self.data.iter().enumerate().map(move |(index, height)| {
136 let x = (index % 16) as u8;
137 let z = (index / 16) as u8;
138 ChunkBlockPos::new(x, height as i32 + self.min_y, z)
139 })
140 }
141
142 pub fn iter_highest_taken(&self) -> impl Iterator<Item = ChunkBlockPos> + '_ {
144 self.data.iter().enumerate().map(move |(index, height)| {
145 let x = (index % 16) as u8;
146 let z = (index / 16) as u8;
147 ChunkBlockPos::new(x, height as i32 + self.min_y - 1, z)
148 })
149 }
150}