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 WorldGenRegion<'_>,
13 bounds: TreeBounds,
14 placement: &TreePlacement,
15 ) {
16 let mut shape = FxHashSet::default();
17 for pos in placement
18 .decorations
19 .java_ordered_positions()
20 .into_iter()
21 .chain(placement.roots.java_ordered_positions())
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.java_ordered_positions() {
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.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.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: &WorldGenRegion<'_>,
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: &WorldGenRegion<'_>,
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(region: &WorldGenRegion<'_>, pos: BlockPos, direction: Direction) {
163 let neighbor_pos = pos.relative(direction);
164 let state = region.block_state(pos);
165 let neighbor_state = region.block_state(neighbor_pos);
166
167 Self::update_leaf_shape_at_edge(region, pos, state, neighbor_state);
168 Self::update_leaf_shape_at_edge(region, neighbor_pos, neighbor_state, state);
169
170 let new_state = BLOCK_BEHAVIORS
171 .get_behavior(state.get_block())
172 .update_shape(state, region, pos, direction, neighbor_pos, neighbor_state);
173 if state != new_state {
174 let _ = region.set_block_state(pos, new_state, UpdateFlags::UPDATE_CLIENTS);
175 }
176
177 let new_neighbor_state = BLOCK_BEHAVIORS
178 .get_behavior(neighbor_state.get_block())
179 .update_shape(
180 neighbor_state,
181 region,
182 neighbor_pos,
183 direction.opposite(),
184 pos,
185 new_state,
186 );
187 if neighbor_state != new_neighbor_state {
188 let _ = region.set_block_state(
189 neighbor_pos,
190 new_neighbor_state,
191 UpdateFlags::UPDATE_CLIENTS,
192 );
193 }
194 }
195
196 fn update_leaf_shape_at_edge(
197 region: &WorldGenRegion<'_>,
198 pos: BlockPos,
199 state: BlockStateId,
200 neighbor_state: BlockStateId,
201 ) {
202 if !state.get_block().has_tag(&BlockTag::LEAVES) {
203 return;
204 }
205
206 let Some(distance) = state.try_get_value(&BlockStateProperties::DISTANCE) else {
207 return;
208 };
209
210 if !Self::tree_can_schedule_tick_at(region, pos) {
211 return;
212 }
213
214 if state.try_get_value(&BlockStateProperties::WATERLOGGED) == Some(true) {
215 let _ = region.schedule_fluid_tick_default(pos, &vanilla_fluids::WATER, 5);
216 }
217
218 let distance_from_neighbor = Self::tree_leaf_distance_at(neighbor_state) + 1;
219 if distance_from_neighbor != 1 || distance != distance_from_neighbor {
220 let _ = region.schedule_block_tick_default(pos, state.get_block(), 1);
221 }
222 }
223
224 fn tree_optional_leaf_distance_at(state: BlockStateId) -> Option<u8> {
225 if state
226 .get_block()
227 .has_tag(&BlockTag::PREVENTS_NEARBY_LEAF_DECAY)
228 {
229 return Some(0);
230 }
231
232 state.try_get_value(&BlockStateProperties::DISTANCE)
233 }
234
235 fn tree_leaf_distance_at(state: BlockStateId) -> u8 {
236 Self::tree_optional_leaf_distance_at(state).unwrap_or(7)
237 }
238
239 const fn tree_can_schedule_tick_at(region: &WorldGenRegion<'_>, pos: BlockPos) -> bool {
240 region.can_write_to_chunk(
241 SectionPos::block_to_section_coord(pos.x()),
242 SectionPos::block_to_section_coord(pos.z()),
243 )
244 }
245}
246
247fn take_frontier_position(frontier: &mut JavaBlockPosSet) -> Option<BlockPos> {
248 frontier.pop_java_ordered_position()
249}