Skip to main content

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

1use steel_registry::vanilla_block_tags::BlockTag;
2
3use super::super::prelude::*;
4use super::super::runner::FeatureDecorationRunner;
5use super::super::vanilla_collections::JavaBlockPosSet;
6
7mod decorators;
8mod fallen;
9mod foliage;
10mod leaves;
11mod root_system;
12mod roots;
13mod trunk;
14
15impl FeatureDecorationRunner {
16    pub(crate) fn place_tree_feature(
17        region: &mut WorldGenRegion<'_>,
18        registry: &Registry,
19        random: &mut WorldgenRandom,
20        config: &TreeConfiguration,
21        origin: BlockPos,
22        biome_zoom_seed: i64,
23    ) -> bool {
24        let mut placement = TreePlacement::default();
25        let placed = Self::do_place_tree(region, registry, random, config, origin, &mut placement);
26        if !placed || (placement.trunks.is_empty() && placement.foliage.is_empty()) {
27            return false;
28        }
29
30        if !config.decorators.is_empty() {
31            Self::place_tree_decorators(
32                region,
33                registry,
34                random,
35                &config.decorators,
36                &mut placement,
37                biome_zoom_seed,
38            );
39        }
40
41        let Some(bounds) = TreeBounds::from_placement(&placement) else {
42            return false;
43        };
44        Self::update_tree_leaves(region, bounds, &placement);
45        true
46    }
47
48    fn do_place_tree(
49        region: &mut WorldGenRegion<'_>,
50        registry: &Registry,
51        random: &mut WorldgenRandom,
52        config: &TreeConfiguration,
53        origin: BlockPos,
54        placement: &mut TreePlacement,
55    ) -> bool {
56        let tree_height = Self::tree_height(random, &config.trunk_placer);
57        let foliage_height = Self::tree_foliage_height(random, tree_height, config);
58        let trunk_height = tree_height - foliage_height;
59        let leaf_radius = Self::tree_foliage_radius(random, &config.foliage_placer, trunk_height);
60        let trunk_origin = Self::tree_root_origin(random, origin, config.root_placer.as_ref());
61        let min_y = origin.y().min(trunk_origin.y());
62        let max_y = origin.y().max(trunk_origin.y()) + tree_height + 1;
63
64        if min_y < region.min_y() + 1 || max_y > region.max_y_exclusive() {
65            return false;
66        }
67
68        let clipped_tree_height =
69            Self::max_free_tree_height(region, tree_height, trunk_origin, config);
70        let min_clipped_height = Self::tree_min_clipped_height(&config.minimum_size);
71        if clipped_tree_height < tree_height
72            && min_clipped_height.is_none_or(|height| clipped_tree_height < height)
73        {
74            return false;
75        }
76
77        if config.root_placer.is_some()
78            && !Self::place_tree_roots(
79                region,
80                registry,
81                random,
82                origin,
83                trunk_origin,
84                config,
85                placement,
86            )
87        {
88            return false;
89        }
90
91        let foliage_attachments = Self::place_tree_trunk(
92            region,
93            registry,
94            random,
95            clipped_tree_height,
96            trunk_origin,
97            config,
98            placement,
99        );
100        for foliage_attachment in foliage_attachments {
101            Self::create_tree_foliage(
102                region,
103                registry,
104                random,
105                config,
106                clipped_tree_height,
107                foliage_attachment,
108                foliage_height,
109                leaf_radius,
110                placement,
111            );
112        }
113
114        true
115    }
116
117    const fn tree_min_clipped_height(feature_size: &FeatureSize) -> Option<i32> {
118        match feature_size {
119            FeatureSize::TwoLayers(size) => size.min_clipped_height,
120            FeatureSize::ThreeLayers(size) => size.min_clipped_height,
121        }
122    }
123
124    const fn tree_size_at_height(feature_size: &FeatureSize, tree_height: i32, y: i32) -> i32 {
125        match feature_size {
126            FeatureSize::TwoLayers(size) => {
127                if y < size.limit {
128                    size.lower_size
129                } else {
130                    size.upper_size
131                }
132            }
133            FeatureSize::ThreeLayers(size) => {
134                if y < size.limit {
135                    size.lower_size
136                } else if y >= tree_height - size.upper_limit {
137                    size.upper_size
138                } else {
139                    size.middle_size
140                }
141            }
142        }
143    }
144
145    fn max_free_tree_height(
146        region: &WorldGenRegion<'_>,
147        max_tree_height: i32,
148        tree_pos: BlockPos,
149        config: &TreeConfiguration,
150    ) -> i32 {
151        for y in 0..=max_tree_height + 1 {
152            let radius = Self::tree_size_at_height(&config.minimum_size, max_tree_height, y);
153            for x in -radius..=radius {
154                for z in -radius..=radius {
155                    let pos = tree_pos.offset(x, y, z);
156                    if !Self::tree_trunk_placer_is_free(region, pos, &config.trunk_placer)
157                        || (!config.ignore_vines && Self::tree_is_vine(region, pos))
158                    {
159                        return y - 2;
160                    }
161                }
162            }
163        }
164
165        max_tree_height
166    }
167
168    fn tree_valid_pos(region: &WorldGenRegion<'_>, pos: BlockPos) -> bool {
169        let state = region.block_state(pos);
170        state.is_air() || state.get_block().has_tag(&BlockTag::REPLACEABLE_BY_TREES)
171    }
172
173    fn tree_trunk_placer_is_free(
174        region: &WorldGenRegion<'_>,
175        pos: BlockPos,
176        trunk_placer: &TrunkPlacer,
177    ) -> bool {
178        let state = region.block_state(pos);
179        Self::tree_valid_pos_for_trunk_placer(region, pos, trunk_placer)
180            || state.get_block().has_tag(&BlockTag::LOGS)
181    }
182
183    fn tree_valid_pos_for_trunk_placer(
184        region: &WorldGenRegion<'_>,
185        pos: BlockPos,
186        trunk_placer: &TrunkPlacer,
187    ) -> bool {
188        match trunk_placer {
189            TrunkPlacer::UpwardsBranching(placer) => {
190                Self::tree_valid_pos_or_tag(region, pos, &placer.can_grow_through)
191            }
192            TrunkPlacer::Straight(_)
193            | TrunkPlacer::Forking(_)
194            | TrunkPlacer::Giant(_)
195            | TrunkPlacer::Fancy(_)
196            | TrunkPlacer::DarkOak(_)
197            | TrunkPlacer::MegaJungle(_)
198            | TrunkPlacer::Bending(_)
199            | TrunkPlacer::Cherry(_) => Self::tree_valid_pos(region, pos),
200        }
201    }
202
203    fn tree_valid_pos_or_tag(region: &WorldGenRegion<'_>, pos: BlockPos, tag: &Identifier) -> bool {
204        let state = region.block_state(pos);
205        let block = state.get_block();
206        state.is_air() || block.has_tag(&BlockTag::REPLACEABLE_BY_TREES) || block.has_tag(tag)
207    }
208
209    fn tree_is_air_or_leaves(region: &WorldGenRegion<'_>, pos: BlockPos) -> bool {
210        let state = region.block_state(pos);
211        state.is_air() || state.get_block().has_tag(&BlockTag::LEAVES)
212    }
213
214    fn tree_is_vine(region: &WorldGenRegion<'_>, pos: BlockPos) -> bool {
215        region.block_state(pos).get_block() == &vanilla_blocks::VINE
216    }
217
218    fn set_tree_block(region: &mut WorldGenRegion<'_>, pos: BlockPos, state: BlockStateId) {
219        let flags = UpdateFlags::UPDATE_NEIGHBORS
220            | UpdateFlags::UPDATE_CLIENTS
221            | UpdateFlags::UPDATE_KNOWN_SHAPE;
222        let _ = region.set_block_state(pos, state, flags);
223    }
224}
225
226#[derive(Clone, Copy)]
227struct FoliageAttachment {
228    pos: BlockPos,
229    radius_offset: i32,
230    double_trunk: bool,
231}
232
233#[derive(Default)]
234struct TreePlacement {
235    roots: JavaBlockPosSet,
236    trunks: JavaBlockPosSet,
237    foliage: JavaBlockPosSet,
238    decorations: JavaBlockPosSet,
239}
240
241impl TreePlacement {
242    fn set_root(&mut self, region: &mut WorldGenRegion<'_>, pos: BlockPos, state: BlockStateId) {
243        self.roots.insert(pos);
244        FeatureDecorationRunner::set_tree_block(region, pos, state);
245    }
246
247    fn set_trunk(&mut self, region: &mut WorldGenRegion<'_>, pos: BlockPos, state: BlockStateId) {
248        self.trunks.insert(pos);
249        FeatureDecorationRunner::set_tree_block(region, pos, state);
250    }
251
252    fn set_foliage(&mut self, region: &mut WorldGenRegion<'_>, pos: BlockPos, state: BlockStateId) {
253        self.foliage.insert(pos);
254        FeatureDecorationRunner::set_tree_block(region, pos, state);
255    }
256
257    fn set_decoration(
258        &mut self,
259        region: &mut WorldGenRegion<'_>,
260        pos: BlockPos,
261        state: BlockStateId,
262    ) {
263        self.decorations.insert(pos);
264        FeatureDecorationRunner::set_tree_block(region, pos, state);
265    }
266}
267
268#[derive(Clone, Copy)]
269struct TreeBounds {
270    min_x: i32,
271    min_y: i32,
272    min_z: i32,
273    max_x: i32,
274    max_y: i32,
275    max_z: i32,
276}
277
278impl TreeBounds {
279    fn from_placement(placement: &TreePlacement) -> Option<Self> {
280        let mut bounds: Option<Self> = None;
281        for &pos in placement
282            .roots
283            .insertion_order()
284            .chain(placement.trunks.insertion_order())
285            .chain(placement.foliage.insertion_order())
286            .chain(placement.decorations.insertion_order())
287        {
288            match &mut bounds {
289                Some(bounds) => bounds.include(pos),
290                None => bounds = Some(Self::new(pos)),
291            }
292        }
293        bounds
294    }
295
296    const fn new(pos: BlockPos) -> Self {
297        Self {
298            min_x: pos.x(),
299            min_y: pos.y(),
300            min_z: pos.z(),
301            max_x: pos.x(),
302            max_y: pos.y(),
303            max_z: pos.z(),
304        }
305    }
306
307    fn include(&mut self, pos: BlockPos) {
308        self.min_x = self.min_x.min(pos.x());
309        self.min_y = self.min_y.min(pos.y());
310        self.min_z = self.min_z.min(pos.z());
311        self.max_x = self.max_x.max(pos.x());
312        self.max_y = self.max_y.max(pos.y());
313        self.max_z = self.max_z.max(pos.z());
314    }
315
316    const fn contains(self, pos: BlockPos) -> bool {
317        pos.x() >= self.min_x
318            && pos.x() <= self.max_x
319            && pos.y() >= self.min_y
320            && pos.y() <= self.max_y
321            && pos.z() >= self.min_z
322            && pos.z() <= self.max_z
323    }
324}
325
326const fn abs_i32(value: i32) -> i32 {
327    if value < 0 { -value } else { value }
328}