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
7use rustc_hash::FxHashMap;
8use std::cell::{Cell, RefCell};
9
10use crate::block_entity::SharedBlockEntity;
11use crate::world::ScheduledTickAccess;
12
13mod decorators;
14mod fallen;
15mod foliage;
16mod leaves;
17mod root_system;
18mod roots;
19mod trunk;
20
21/// Places a nested configured feature during tree decoration.
22pub(crate) type NestedFeaturePlacer<L> =
23    fn(&mut L, &Registry, &mut WorldgenRandom, &ConfiguredFeatureKind, BlockPos, i64) -> bool;
24
25pub(crate) const fn no_nested_features<L>(
26    _level: &mut L,
27    _registry: &Registry,
28    _random: &mut WorldgenRandom,
29    _kind: &ConfiguredFeatureKind,
30    _origin: BlockPos,
31    _biome_zoom_seed: i64,
32) -> bool {
33    false
34}
35
36impl FeatureDecorationRunner {
37    pub(crate) fn place_tree_feature<L: LevelAccessor>(
38        region: &mut L,
39        registry: &Registry,
40        random: &mut WorldgenRandom,
41        config: &TreeConfiguration,
42        origin: BlockPos,
43        biome_zoom_seed: i64,
44        place_nested: NestedFeaturePlacer<L>,
45    ) -> bool {
46        if region.requires_live_write_preflight() {
47            let mut preflight_random = random.clone();
48            let mut preflight_region = TreeWritePreflight::new(region);
49            let mut preflight_placement = TreePlacement::default();
50            let preflight_placed = Self::do_place_tree(
51                &mut preflight_region,
52                registry,
53                &mut preflight_random,
54                config,
55                origin,
56                &mut preflight_placement,
57            );
58            if !preflight_placed || preflight_region.failed() {
59                return false;
60            }
61        }
62
63        let mut placement = TreePlacement::default();
64        let placed = Self::do_place_tree(region, registry, random, config, origin, &mut placement);
65        if !placed || (placement.trunks.is_empty() && placement.foliage.is_empty()) {
66            return false;
67        }
68
69        if !config.decorators.is_empty() {
70            Self::place_tree_decorators(
71                region,
72                registry,
73                random,
74                &config.decorators,
75                &mut placement,
76                biome_zoom_seed,
77                place_nested,
78            );
79        }
80
81        let Some(bounds) = TreeBounds::from_placement(&placement) else {
82            return false;
83        };
84        Self::update_tree_leaves(region, bounds, &placement);
85        true
86    }
87
88    fn do_place_tree(
89        region: &mut impl LevelAccessor,
90        registry: &Registry,
91        random: &mut WorldgenRandom,
92        config: &TreeConfiguration,
93        origin: BlockPos,
94        placement: &mut TreePlacement,
95    ) -> bool {
96        let tree_height = Self::tree_height(random, &config.trunk_placer);
97        let foliage_height = Self::tree_foliage_height(random, tree_height, config);
98        let trunk_height = tree_height - foliage_height;
99        let leaf_radius = Self::tree_foliage_radius(random, &config.foliage_placer, trunk_height);
100        let trunk_origin = Self::tree_root_origin(random, origin, config.root_placer.as_ref());
101        let min_y = origin.y().min(trunk_origin.y());
102        let max_y = origin.y().max(trunk_origin.y()) + tree_height + 1;
103
104        if min_y < region.min_y() + 1 || max_y > region.max_y_exclusive() {
105            return false;
106        }
107
108        let clipped_tree_height =
109            Self::max_free_tree_height(region, tree_height, trunk_origin, config);
110        let min_clipped_height = Self::tree_min_clipped_height(&config.minimum_size);
111        if clipped_tree_height < tree_height
112            && min_clipped_height.is_none_or(|height| clipped_tree_height < height)
113        {
114            return false;
115        }
116
117        if config.root_placer.is_some()
118            && !Self::place_tree_roots(
119                region,
120                registry,
121                random,
122                origin,
123                trunk_origin,
124                config,
125                placement,
126            )
127        {
128            return false;
129        }
130
131        let foliage_attachments = Self::place_tree_trunk(
132            region,
133            registry,
134            random,
135            clipped_tree_height,
136            trunk_origin,
137            config,
138            placement,
139        );
140        for foliage_attachment in foliage_attachments {
141            Self::create_tree_foliage(
142                region,
143                registry,
144                random,
145                config,
146                clipped_tree_height,
147                foliage_attachment,
148                foliage_height,
149                leaf_radius,
150                placement,
151            );
152        }
153
154        true
155    }
156
157    const fn tree_min_clipped_height(feature_size: &FeatureSize) -> Option<i32> {
158        match feature_size {
159            FeatureSize::TwoLayers(size) => size.min_clipped_height,
160            FeatureSize::ThreeLayers(size) => size.min_clipped_height,
161        }
162    }
163
164    const fn tree_size_at_height(feature_size: &FeatureSize, tree_height: i32, y: i32) -> i32 {
165        match feature_size {
166            FeatureSize::TwoLayers(size) => {
167                if y < size.limit {
168                    size.lower_size
169                } else {
170                    size.upper_size
171                }
172            }
173            FeatureSize::ThreeLayers(size) => {
174                if y < size.limit {
175                    size.lower_size
176                } else if y >= tree_height - size.upper_limit {
177                    size.upper_size
178                } else {
179                    size.middle_size
180                }
181            }
182        }
183    }
184
185    fn max_free_tree_height(
186        region: &impl LevelReader,
187        max_tree_height: i32,
188        tree_pos: BlockPos,
189        config: &TreeConfiguration,
190    ) -> i32 {
191        for y in 0..=max_tree_height + 1 {
192            let radius = Self::tree_size_at_height(&config.minimum_size, max_tree_height, y);
193            for x in -radius..=radius {
194                for z in -radius..=radius {
195                    let pos = tree_pos.offset(x, y, z);
196                    if !Self::tree_trunk_placer_is_free(region, pos, &config.trunk_placer)
197                        || (!config.ignore_vines && Self::tree_is_vine(region, pos))
198                    {
199                        return y - 2;
200                    }
201                }
202            }
203        }
204
205        max_tree_height
206    }
207
208    fn tree_valid_pos(region: &impl LevelReader, pos: BlockPos) -> bool {
209        let state = region.get_block_state(pos);
210        state.is_air() || state.get_block().has_tag(&BlockTag::REPLACEABLE_BY_TREES)
211    }
212
213    fn tree_trunk_placer_is_free(
214        region: &impl LevelReader,
215        pos: BlockPos,
216        trunk_placer: &TrunkPlacer,
217    ) -> bool {
218        let state = region.get_block_state(pos);
219        Self::tree_valid_pos_for_trunk_placer(region, pos, trunk_placer)
220            || state.get_block().has_tag(&BlockTag::LOGS)
221    }
222
223    fn tree_valid_pos_for_trunk_placer(
224        region: &impl LevelReader,
225        pos: BlockPos,
226        trunk_placer: &TrunkPlacer,
227    ) -> bool {
228        match trunk_placer {
229            TrunkPlacer::UpwardsBranching(placer) => {
230                Self::tree_valid_pos_or_tag(region, pos, &placer.can_grow_through)
231            }
232            TrunkPlacer::Straight(_)
233            | TrunkPlacer::Forking(_)
234            | TrunkPlacer::Giant(_)
235            | TrunkPlacer::Fancy(_)
236            | TrunkPlacer::DarkOak(_)
237            | TrunkPlacer::MegaJungle(_)
238            | TrunkPlacer::Bending(_)
239            | TrunkPlacer::Cherry(_) => Self::tree_valid_pos(region, pos),
240        }
241    }
242
243    fn tree_valid_pos_or_tag(region: &impl LevelReader, pos: BlockPos, tag: &Identifier) -> bool {
244        let state = region.get_block_state(pos);
245        let block = state.get_block();
246        state.is_air() || block.has_tag(&BlockTag::REPLACEABLE_BY_TREES) || block.has_tag(tag)
247    }
248
249    fn tree_is_air_or_leaves(region: &impl LevelReader, pos: BlockPos) -> bool {
250        let state = region.get_block_state(pos);
251        state.is_air() || state.get_block().has_tag(&BlockTag::LEAVES)
252    }
253
254    fn tree_is_vine(region: &impl LevelReader, pos: BlockPos) -> bool {
255        region.get_block_state(pos).get_block() == &vanilla_blocks::VINE
256    }
257
258    fn set_tree_block(region: &mut impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
259        let flags = UpdateFlags::UPDATE_NEIGHBORS
260            | UpdateFlags::UPDATE_CLIENTS
261            | UpdateFlags::UPDATE_KNOWN_SHAPE;
262        let _ = region.set_block_state(pos, state, flags);
263    }
264}
265
266struct TreeWritePreflight<'a, L: LevelAccessor + ?Sized> {
267    level: &'a mut L,
268    writes: RefCell<FxHashMap<BlockPos, BlockStateId>>,
269    failed: Cell<bool>,
270}
271
272impl<'a, L: LevelAccessor + ?Sized> TreeWritePreflight<'a, L> {
273    fn new(level: &'a mut L) -> Self {
274        Self {
275            level,
276            writes: RefCell::new(FxHashMap::default()),
277            failed: Cell::new(false),
278        }
279    }
280
281    const fn failed(&self) -> bool {
282        self.failed.get()
283    }
284}
285
286impl<L: LevelAccessor + ?Sized> LevelReader for TreeWritePreflight<'_, L> {
287    fn height_at(&self, heightmap_type: HeightmapType, x: i32, z: i32) -> i32 {
288        self.level.height_at(heightmap_type, x, z)
289    }
290
291    fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
292        self.writes
293            .borrow()
294            .get(&pos)
295            .copied()
296            .unwrap_or_else(|| self.level.get_block_state(pos))
297    }
298
299    fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
300        self.level.get_block_entity(pos)
301    }
302
303    fn is_face_sturdy_for(
304        &self,
305        state: BlockStateId,
306        pos: BlockPos,
307        direction: Direction,
308        support_type: shapes::SupportType,
309    ) -> bool {
310        self.level
311            .is_face_sturdy_for(state, pos, direction, support_type)
312    }
313
314    fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
315        self.level.raw_brightness(pos, sky_darkening)
316    }
317
318    fn can_see_sky(&self, pos: BlockPos) -> bool {
319        self.level.can_see_sky(pos)
320    }
321
322    fn ambient_light(&self) -> f32 {
323        self.level.ambient_light()
324    }
325
326    fn min_y(&self) -> i32 {
327        self.level.min_y()
328    }
329
330    fn height(&self) -> i32 {
331        self.level.height()
332    }
333}
334
335impl<L: LevelAccessor + ?Sized> ScheduledTickAccess for TreeWritePreflight<'_, L> {
336    fn fluid_tick_delay(&self, fluid: FluidRef) -> i32 {
337        self.level.fluid_tick_delay(fluid)
338    }
339
340    fn schedule_block_tick_default(&self, _pos: BlockPos, _block: BlockRef, _delay: i32) -> bool {
341        false
342    }
343
344    fn schedule_fluid_tick_default(&self, _pos: BlockPos, _fluid: FluidRef, _delay: i32) -> bool {
345        false
346    }
347}
348
349impl<L: LevelAccessor + ?Sized> LevelAccessor for TreeWritePreflight<'_, L> {
350    fn can_write_to_chunk(&self, chunk_x: i32, chunk_z: i32) -> bool {
351        self.level.can_write_to_chunk(chunk_x, chunk_z)
352    }
353
354    fn set_block_state(&self, pos: BlockPos, state: BlockStateId, _flags: UpdateFlags) -> bool {
355        let chunk_x = SectionPos::block_to_section_coord(pos.x());
356        let chunk_z = SectionPos::block_to_section_coord(pos.z());
357        if !self.level.can_write_to_chunk(chunk_x, chunk_z) {
358            self.failed.set(true);
359            return false;
360        }
361
362        self.writes.borrow_mut().insert(pos, state);
363        true
364    }
365
366    fn destroy_block(&self, pos: BlockPos, _drop_items: bool) -> bool {
367        if self.get_block_state(pos).is_air() {
368            return false;
369        }
370
371        self.set_block_state(
372            pos,
373            vanilla_blocks::AIR.default_state(),
374            UpdateFlags::UPDATE_ALL,
375        )
376    }
377}
378
379#[derive(Clone, Copy)]
380struct FoliageAttachment {
381    pos: BlockPos,
382    radius_offset: i32,
383    double_trunk: bool,
384}
385
386#[derive(Default)]
387struct TreePlacement {
388    roots: JavaBlockPosSet,
389    trunks: JavaBlockPosSet,
390    foliage: JavaBlockPosSet,
391    decorations: JavaBlockPosSet,
392}
393
394impl TreePlacement {
395    fn set_root(&mut self, region: &mut impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
396        self.roots.insert(pos);
397        FeatureDecorationRunner::set_tree_block(region, pos, state);
398    }
399
400    fn set_trunk(&mut self, region: &mut impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
401        self.trunks.insert(pos);
402        FeatureDecorationRunner::set_tree_block(region, pos, state);
403    }
404
405    fn set_foliage(&mut self, region: &mut impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
406        self.foliage.insert(pos);
407        FeatureDecorationRunner::set_tree_block(region, pos, state);
408    }
409
410    fn set_decoration(
411        &mut self,
412        region: &mut impl LevelAccessor,
413        pos: BlockPos,
414        state: BlockStateId,
415    ) {
416        self.decorations.insert(pos);
417        FeatureDecorationRunner::set_tree_block(region, pos, state);
418    }
419}
420
421#[derive(Clone, Copy)]
422struct TreeBounds {
423    min_x: i32,
424    min_y: i32,
425    min_z: i32,
426    max_x: i32,
427    max_y: i32,
428    max_z: i32,
429}
430
431impl TreeBounds {
432    fn from_placement(placement: &TreePlacement) -> Option<Self> {
433        let mut bounds: Option<Self> = None;
434        for &pos in placement
435            .roots
436            .insertion_order()
437            .chain(placement.trunks.insertion_order())
438            .chain(placement.foliage.insertion_order())
439            .chain(placement.decorations.insertion_order())
440        {
441            match &mut bounds {
442                Some(bounds) => bounds.include(pos),
443                None => bounds = Some(Self::new(pos)),
444            }
445        }
446        bounds
447    }
448
449    const fn new(pos: BlockPos) -> Self {
450        Self {
451            min_x: pos.x(),
452            min_y: pos.y(),
453            min_z: pos.z(),
454            max_x: pos.x(),
455            max_y: pos.y(),
456            max_z: pos.z(),
457        }
458    }
459
460    fn include(&mut self, pos: BlockPos) {
461        self.min_x = self.min_x.min(pos.x());
462        self.min_y = self.min_y.min(pos.y());
463        self.min_z = self.min_z.min(pos.z());
464        self.max_x = self.max_x.max(pos.x());
465        self.max_y = self.max_y.max(pos.y());
466        self.max_z = self.max_z.max(pos.z());
467    }
468
469    const fn contains(self, pos: BlockPos) -> bool {
470        pos.x() >= self.min_x
471            && pos.x() <= self.max_x
472            && pos.y() >= self.min_y
473            && pos.y() <= self.max_y
474            && pos.z() >= self.min_z
475            && pos.z() <= self.max_z
476    }
477}
478
479const fn abs_i32(value: i32) -> i32 {
480    if value < 0 { -value } else { value }
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486    use steel_registry::{init_vanilla_registry, vanilla_blocks};
487
488    struct WriteTestLevel {
489        can_write: bool,
490    }
491
492    impl LevelReader for WriteTestLevel {
493        fn get_block_state(&self, _pos: BlockPos) -> BlockStateId {
494            vanilla_blocks::AIR.default_state()
495        }
496
497        fn raw_brightness(&self, _pos: BlockPos, _sky_darkening: u8) -> u8 {
498            0
499        }
500
501        fn min_y(&self) -> i32 {
502            -64
503        }
504
505        fn height(&self) -> i32 {
506            384
507        }
508    }
509
510    impl ScheduledTickAccess for WriteTestLevel {
511        fn fluid_tick_delay(&self, _fluid: FluidRef) -> i32 {
512            0
513        }
514
515        fn schedule_block_tick_default(
516            &self,
517            _pos: BlockPos,
518            _block: BlockRef,
519            _delay: i32,
520        ) -> bool {
521            false
522        }
523
524        fn schedule_fluid_tick_default(
525            &self,
526            _pos: BlockPos,
527            _fluid: FluidRef,
528            _delay: i32,
529        ) -> bool {
530            false
531        }
532    }
533
534    impl LevelAccessor for WriteTestLevel {
535        fn can_write_to_chunk(&self, _chunk_x: i32, _chunk_z: i32) -> bool {
536            self.can_write
537        }
538
539        fn set_block_state(
540            &self,
541            _pos: BlockPos,
542            _state: BlockStateId,
543            _flags: UpdateFlags,
544        ) -> bool {
545            self.can_write
546        }
547
548        fn destroy_block(&self, _pos: BlockPos, _drop_items: bool) -> bool {
549            self.can_write
550        }
551    }
552
553    #[test]
554    fn tree_write_preflight_rejects_unwritable_chunks() {
555        init_vanilla_registry();
556        let mut level = WriteTestLevel { can_write: false };
557        let preflight_state = vanilla_blocks::OAK_LOG.default_state();
558        let preflight = TreeWritePreflight::new(&mut level);
559
560        assert!(!preflight.set_block_state(
561            BlockPos::ZERO,
562            preflight_state,
563            UpdateFlags::UPDATE_ALL,
564        ));
565        assert!(preflight.failed());
566        assert_eq!(
567            preflight.get_block_state(BlockPos::ZERO),
568            vanilla_blocks::AIR.default_state()
569        );
570    }
571
572    #[test]
573    fn tree_write_preflight_overlays_accepted_writes() {
574        init_vanilla_registry();
575        let mut level = WriteTestLevel { can_write: true };
576        let preflight_state = vanilla_blocks::OAK_LOG.default_state();
577        let preflight = TreeWritePreflight::new(&mut level);
578
579        assert!(preflight.set_block_state(
580            BlockPos::ZERO,
581            preflight_state,
582            UpdateFlags::UPDATE_ALL,
583        ));
584        assert!(!preflight.failed());
585        assert_eq!(preflight.get_block_state(BlockPos::ZERO), preflight_state);
586    }
587}