steel_core/worldgen/feature/features/tree/
leaves.rs1use steel_registry::vanilla_block_tags::BlockTag;
2
3use super::super::super::prelude::*;
4use super::super::super::runner::FeatureDecorationRunner;
5use super::super::super::vanilla_collections::JavaBlockPosSet;
6use super::{TreeBounds, TreePlacement};
7
8const LEAF_DISTANCE_LIMIT: usize = 7;
9
10impl FeatureDecorationRunner {
11 pub(super) fn update_tree_leaves(
12 region: &mut impl LevelAccessor,
13 bounds: TreeBounds,
14 placement: &TreePlacement,
15 ) {
16 let mut shape = FxHashSet::default();
17 for pos in placement
18 .decorations
19 .insertion_order()
20 .copied()
21 .chain(placement.roots.insertion_order().copied())
22 {
23 if bounds.contains(pos) {
24 shape.insert(pos);
25 }
26 }
27
28 let mut frontiers = (0..LEAF_DISTANCE_LIMIT)
29 .map(|_| JavaBlockPosSet::default())
30 .collect::<Vec<_>>();
31 for pos in placement.trunks.insertion_order().copied() {
32 frontiers[0].insert(pos);
33 }
34 let mut smallest_distance = 0;
35
36 loop {
37 while smallest_distance < LEAF_DISTANCE_LIMIT && frontiers[smallest_distance].is_empty()
38 {
39 smallest_distance += 1;
40 }
41 if smallest_distance >= LEAF_DISTANCE_LIMIT {
42 break;
43 }
44
45 let Some(pos) = take_frontier_position(&mut frontiers[smallest_distance]) else {
46 continue;
47 };
48 if !bounds.contains(pos) {
49 continue;
50 }
51
52 if smallest_distance != 0 {
53 let state = region.get_block_state(pos);
54 if state
55 .try_get_value(&BlockStateProperties::DISTANCE)
56 .is_some()
57 {
58 let distance = smallest_distance as u8;
59 Self::set_tree_block(
60 region,
61 pos,
62 state.set_value(&BlockStateProperties::DISTANCE, distance),
63 );
64 }
65 }
66
67 shape.insert(pos);
68
69 for direction in Self::VANILLA_DIRECTION_VALUES {
70 let neighbor_pos = pos.relative(direction);
71 if !bounds.contains(neighbor_pos) || shape.contains(&neighbor_pos) {
72 continue;
73 }
74
75 let state = region.get_block_state(neighbor_pos);
76 let Some(distance) = Self::tree_optional_leaf_distance_at(state) else {
77 continue;
78 };
79 let new_distance = distance.min((smallest_distance + 1) as u8);
80 if new_distance < LEAF_DISTANCE_LIMIT as u8 {
81 frontiers[usize::from(new_distance)].insert(neighbor_pos);
82 smallest_distance = smallest_distance.min(usize::from(new_distance));
83 }
84 }
85 }
86
87 Self::update_tree_shape_at_edge(region, bounds, &shape);
88 }
89
90 fn update_tree_shape_at_edge(
91 region: &mut impl LevelAccessor,
92 bounds: TreeBounds,
93 shape: &FxHashSet<BlockPos>,
94 ) {
95 for x in bounds.min_x..=bounds.max_x {
96 for y in bounds.min_y..=bounds.max_y {
97 Self::scan_tree_shape_line(
98 region,
99 shape,
100 bounds.min_z,
101 bounds.max_z,
102 |z| BlockPos::new(x, y, z),
103 Direction::North,
104 Direction::South,
105 );
106 }
107 }
108
109 for z in bounds.min_z..=bounds.max_z {
110 for x in bounds.min_x..=bounds.max_x {
111 Self::scan_tree_shape_line(
112 region,
113 shape,
114 bounds.min_y,
115 bounds.max_y,
116 |y| BlockPos::new(x, y, z),
117 Direction::Down,
118 Direction::Up,
119 );
120 }
121 }
122
123 for y in bounds.min_y..=bounds.max_y {
124 for z in bounds.min_z..=bounds.max_z {
125 Self::scan_tree_shape_line(
126 region,
127 shape,
128 bounds.min_x,
129 bounds.max_x,
130 |x| BlockPos::new(x, y, z),
131 Direction::West,
132 Direction::East,
133 );
134 }
135 }
136 }
137
138 fn scan_tree_shape_line(
139 region: &mut impl LevelAccessor,
140 shape: &FxHashSet<BlockPos>,
141 start: i32,
142 end: i32,
143 mut pos_at: impl FnMut(i32) -> BlockPos,
144 negative: Direction,
145 positive: Direction,
146 ) {
147 let mut last_full = false;
148 for cursor in start..=end + 1 {
149 let full = cursor != end + 1 && shape.contains(&pos_at(cursor));
150 if !last_full && full {
151 Self::update_tree_shape_face(region, pos_at(cursor), negative);
152 }
153
154 if last_full && !full {
155 Self::update_tree_shape_face(region, pos_at(cursor - 1), positive);
156 }
157
158 last_full = full;
159 }
160 }
161
162 fn update_tree_shape_face(
163 region: &mut impl LevelAccessor,
164 pos: BlockPos,
165 direction: Direction,
166 ) {
167 let neighbor_pos = pos.relative(direction);
168 let state = region.get_block_state(pos);
169 let neighbor_state = region.get_block_state(neighbor_pos);
170
171 Self::update_leaf_shape_at_edge(region, pos, state, neighbor_state);
172 Self::update_leaf_shape_at_edge(region, neighbor_pos, neighbor_state, state);
173
174 let new_state = BLOCK_BEHAVIORS
175 .get_behavior(state.get_block())
176 .update_shape(state, region, pos, direction, neighbor_pos, neighbor_state);
177 if state != new_state {
178 let _ = region.set_block_state(pos, new_state, UpdateFlags::UPDATE_CLIENTS);
179 }
180
181 let new_neighbor_state = BLOCK_BEHAVIORS
182 .get_behavior(neighbor_state.get_block())
183 .update_shape(
184 neighbor_state,
185 region,
186 neighbor_pos,
187 direction.opposite(),
188 pos,
189 new_state,
190 );
191 if neighbor_state != new_neighbor_state {
192 let _ = region.set_block_state(
193 neighbor_pos,
194 new_neighbor_state,
195 UpdateFlags::UPDATE_CLIENTS,
196 );
197 }
198 }
199
200 fn update_leaf_shape_at_edge(
201 region: &mut impl LevelAccessor,
202 pos: BlockPos,
203 state: BlockStateId,
204 neighbor_state: BlockStateId,
205 ) {
206 if !state.get_block().has_tag(&BlockTag::LEAVES) {
207 return;
208 }
209
210 let Some(distance) = state.try_get_value(&BlockStateProperties::DISTANCE) else {
211 return;
212 };
213
214 if !Self::tree_can_schedule_tick_at(region, pos) {
215 return;
216 }
217
218 if state.try_get_value(&BlockStateProperties::WATERLOGGED) == Some(true) {
219 let _ = region.schedule_fluid_tick_default(pos, &vanilla_fluids::WATER, 5);
220 }
221
222 let distance_from_neighbor = Self::tree_leaf_distance_at(neighbor_state) + 1;
223 if distance_from_neighbor != 1 || distance != distance_from_neighbor {
224 let _ = region.schedule_block_tick_default(pos, state.get_block(), 1);
225 }
226 }
227
228 fn tree_optional_leaf_distance_at(state: BlockStateId) -> Option<u8> {
229 if state
230 .get_block()
231 .has_tag(&BlockTag::PREVENTS_NEARBY_LEAF_DECAY)
232 {
233 return Some(0);
234 }
235
236 state.try_get_value(&BlockStateProperties::DISTANCE)
237 }
238
239 fn tree_leaf_distance_at(state: BlockStateId) -> u8 {
240 Self::tree_optional_leaf_distance_at(state).unwrap_or(7)
241 }
242
243 fn tree_can_schedule_tick_at(region: &mut impl LevelAccessor, pos: BlockPos) -> bool {
244 region.can_write_to_chunk(
245 SectionPos::block_to_section_coord(pos.x()),
246 SectionPos::block_to_section_coord(pos.z()),
247 )
248 }
249}
250
251fn take_frontier_position(frontier: &mut JavaBlockPosSet) -> Option<BlockPos> {
252 frontier.pop_java_ordered_position()
253}