Skip to main content

steel_core/worldgen/feature/features/tree/
leaves.rs

1use 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}