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