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 crate::java_block_pos_set::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(_)
240            | TrunkPlacer::Poplar(_) => Self::tree_valid_pos(region, pos),
241        }
242    }
243
244    fn tree_valid_pos_or_tag(region: &impl LevelReader, pos: BlockPos, tag: &Identifier) -> bool {
245        let state = region.get_block_state(pos);
246        let block = state.get_block();
247        state.is_air() || block.has_tag(&BlockTag::REPLACEABLE_BY_TREES) || block.has_tag(tag)
248    }
249
250    fn tree_is_air_or_leaves(region: &impl LevelReader, pos: BlockPos) -> bool {
251        let state = region.get_block_state(pos);
252        state.is_air() || state.get_block().has_tag(&BlockTag::LEAVES)
253    }
254
255    fn tree_is_vine(region: &impl LevelReader, pos: BlockPos) -> bool {
256        region.get_block_state(pos).get_block() == &vanilla_blocks::VINE
257    }
258
259    fn set_tree_block(region: &mut impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
260        let flags = UpdateFlags::UPDATE_NEIGHBORS
261            | UpdateFlags::UPDATE_CLIENTS
262            | UpdateFlags::UPDATE_KNOWN_SHAPE;
263        let _ = region.set_block_state(pos, state, flags);
264    }
265}
266
267struct TreeWritePreflight<'a, L: LevelAccessor + ?Sized> {
268    level: &'a mut L,
269    writes: RefCell<FxHashMap<BlockPos, BlockStateId>>,
270    failed: Cell<bool>,
271}
272
273impl<'a, L: LevelAccessor + ?Sized> TreeWritePreflight<'a, L> {
274    fn new(level: &'a mut L) -> Self {
275        Self {
276            level,
277            writes: RefCell::new(FxHashMap::default()),
278            failed: Cell::new(false),
279        }
280    }
281
282    const fn failed(&self) -> bool {
283        self.failed.get()
284    }
285}
286
287impl<L: LevelAccessor + ?Sized> LevelReader for TreeWritePreflight<'_, L> {
288    fn height_at(&self, heightmap_type: HeightmapType, x: i32, z: i32) -> i32 {
289        self.level.height_at(heightmap_type, x, z)
290    }
291
292    fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
293        self.writes
294            .borrow()
295            .get(&pos)
296            .copied()
297            .unwrap_or_else(|| self.level.get_block_state(pos))
298    }
299
300    fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
301        self.level.get_block_entity(pos)
302    }
303
304    fn is_face_sturdy_for(
305        &self,
306        state: BlockStateId,
307        pos: BlockPos,
308        direction: Direction,
309        support_type: shapes::SupportType,
310    ) -> bool {
311        self.level
312            .is_face_sturdy_for(state, pos, direction, support_type)
313    }
314
315    fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
316        self.level.raw_brightness(pos, sky_darkening)
317    }
318
319    fn can_see_sky(&self, pos: BlockPos) -> bool {
320        self.level.can_see_sky(pos)
321    }
322
323    fn ambient_light(&self) -> f32 {
324        self.level.ambient_light()
325    }
326
327    fn min_y(&self) -> i32 {
328        self.level.min_y()
329    }
330
331    fn height(&self) -> i32 {
332        self.level.height()
333    }
334
335    fn sea_level(&self) -> i32 {
336        self.level.sea_level()
337    }
338}
339
340impl<L: LevelAccessor + ?Sized> ScheduledTickAccess for TreeWritePreflight<'_, L> {
341    fn fluid_tick_delay(&self, fluid: FluidRef) -> i32 {
342        self.level.fluid_tick_delay(fluid)
343    }
344
345    fn schedule_block_tick_default(&self, _pos: BlockPos, _block: BlockRef, _delay: i32) -> bool {
346        false
347    }
348
349    fn schedule_fluid_tick_default(&self, _pos: BlockPos, _fluid: FluidRef, _delay: i32) -> bool {
350        false
351    }
352}
353
354impl<L: LevelAccessor + ?Sized> LevelAccessor for TreeWritePreflight<'_, L> {
355    fn can_write_to_chunk(&self, chunk_x: i32, chunk_z: i32) -> bool {
356        self.level.can_write_to_chunk(chunk_x, chunk_z)
357    }
358
359    fn set_block_state(&self, pos: BlockPos, state: BlockStateId, _flags: UpdateFlags) -> bool {
360        let chunk_x = SectionPos::block_to_section_coord(pos.x());
361        let chunk_z = SectionPos::block_to_section_coord(pos.z());
362        if !self.level.can_write_to_chunk(chunk_x, chunk_z) {
363            self.failed.set(true);
364            return false;
365        }
366
367        self.writes.borrow_mut().insert(pos, state);
368        true
369    }
370
371    fn destroy_block(&self, pos: BlockPos, _drop_items: bool) -> bool {
372        if self.get_block_state(pos).is_air() {
373            return false;
374        }
375
376        self.set_block_state(
377            pos,
378            vanilla_blocks::AIR.default_state(),
379            UpdateFlags::UPDATE_ALL,
380        )
381    }
382}
383
384#[derive(Clone, Copy)]
385struct FoliageAttachment {
386    pos: BlockPos,
387    radius_offset: i32,
388    double_trunk: bool,
389}
390
391#[derive(Default)]
392struct TreePlacement {
393    roots: JavaBlockPosSet,
394    trunks: JavaBlockPosSet,
395    foliage: JavaBlockPosSet,
396    decorations: JavaBlockPosSet,
397}
398
399impl TreePlacement {
400    fn set_root(&mut self, region: &mut impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
401        self.roots.insert(pos);
402        FeatureDecorationRunner::set_tree_block(region, pos, state);
403    }
404
405    fn set_trunk(&mut self, region: &mut impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
406        self.trunks.insert(pos);
407        FeatureDecorationRunner::set_tree_block(region, pos, state);
408    }
409
410    fn set_foliage(&mut self, region: &mut impl LevelAccessor, pos: BlockPos, state: BlockStateId) {
411        self.foliage.insert(pos);
412        FeatureDecorationRunner::set_tree_block(region, pos, state);
413    }
414
415    fn set_decoration(
416        &mut self,
417        region: &mut impl LevelAccessor,
418        pos: BlockPos,
419        state: BlockStateId,
420    ) {
421        self.decorations.insert(pos);
422        FeatureDecorationRunner::set_tree_block(region, pos, state);
423    }
424}
425
426#[derive(Clone, Copy)]
427struct TreeBounds {
428    min_x: i32,
429    min_y: i32,
430    min_z: i32,
431    max_x: i32,
432    max_y: i32,
433    max_z: i32,
434}
435
436impl TreeBounds {
437    fn from_placement(placement: &TreePlacement) -> Option<Self> {
438        let mut bounds: Option<Self> = None;
439        for &pos in placement
440            .roots
441            .java_order()
442            .chain(placement.trunks.java_order())
443            .chain(placement.foliage.java_order())
444            .chain(placement.decorations.java_order())
445        {
446            match &mut bounds {
447                Some(bounds) => bounds.include(pos),
448                None => bounds = Some(Self::new(pos)),
449            }
450        }
451        bounds
452    }
453
454    const fn new(pos: BlockPos) -> Self {
455        Self {
456            min_x: pos.x(),
457            min_y: pos.y(),
458            min_z: pos.z(),
459            max_x: pos.x(),
460            max_y: pos.y(),
461            max_z: pos.z(),
462        }
463    }
464
465    fn include(&mut self, pos: BlockPos) {
466        self.min_x = self.min_x.min(pos.x());
467        self.min_y = self.min_y.min(pos.y());
468        self.min_z = self.min_z.min(pos.z());
469        self.max_x = self.max_x.max(pos.x());
470        self.max_y = self.max_y.max(pos.y());
471        self.max_z = self.max_z.max(pos.z());
472    }
473
474    const fn contains(self, pos: BlockPos) -> bool {
475        pos.x() >= self.min_x
476            && pos.x() <= self.max_x
477            && pos.y() >= self.min_y
478            && pos.y() <= self.max_y
479            && pos.z() >= self.min_z
480            && pos.z() <= self.max_z
481    }
482}
483
484const fn abs_i32(value: i32) -> i32 {
485    if value < 0 { -value } else { value }
486}
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491    use steel_registry::{init_vanilla_registry, vanilla_blocks};
492    use steel_worldgen::density_functions::overworld::OverworldNoiseSettings;
493
494    struct WriteTestLevel {
495        can_write: bool,
496    }
497
498    impl LevelReader for WriteTestLevel {
499        fn get_block_state(&self, _pos: BlockPos) -> BlockStateId {
500            vanilla_blocks::AIR.default_state()
501        }
502
503        fn raw_brightness(&self, _pos: BlockPos, _sky_darkening: u8) -> u8 {
504            0
505        }
506
507        fn min_y(&self) -> i32 {
508            OverworldNoiseSettings::MIN_Y
509        }
510
511        fn height(&self) -> i32 {
512            OverworldNoiseSettings::HEIGHT
513        }
514
515        fn sea_level(&self) -> i32 {
516            OverworldNoiseSettings::SEA_LEVEL
517        }
518    }
519
520    impl ScheduledTickAccess for WriteTestLevel {
521        fn fluid_tick_delay(&self, _fluid: FluidRef) -> i32 {
522            0
523        }
524
525        fn schedule_block_tick_default(
526            &self,
527            _pos: BlockPos,
528            _block: BlockRef,
529            _delay: i32,
530        ) -> bool {
531            false
532        }
533
534        fn schedule_fluid_tick_default(
535            &self,
536            _pos: BlockPos,
537            _fluid: FluidRef,
538            _delay: i32,
539        ) -> bool {
540            false
541        }
542    }
543
544    impl LevelAccessor for WriteTestLevel {
545        fn can_write_to_chunk(&self, _chunk_x: i32, _chunk_z: i32) -> bool {
546            self.can_write
547        }
548
549        fn set_block_state(
550            &self,
551            _pos: BlockPos,
552            _state: BlockStateId,
553            _flags: UpdateFlags,
554        ) -> bool {
555            self.can_write
556        }
557
558        fn destroy_block(&self, _pos: BlockPos, _drop_items: bool) -> bool {
559            self.can_write
560        }
561    }
562
563    #[test]
564    fn tree_write_preflight_rejects_unwritable_chunks() {
565        init_vanilla_registry();
566        let mut level = WriteTestLevel { can_write: false };
567        let preflight_state = vanilla_blocks::OAK_LOG.default_state();
568        let preflight = TreeWritePreflight::new(&mut level);
569
570        assert!(!preflight.set_block_state(
571            BlockPos::ZERO,
572            preflight_state,
573            UpdateFlags::UPDATE_ALL,
574        ));
575        assert!(preflight.failed());
576        assert_eq!(
577            preflight.get_block_state(BlockPos::ZERO),
578            vanilla_blocks::AIR.default_state()
579        );
580    }
581
582    #[test]
583    fn tree_write_preflight_overlays_accepted_writes() {
584        init_vanilla_registry();
585        let mut level = WriteTestLevel { can_write: true };
586        let preflight_state = vanilla_blocks::OAK_LOG.default_state();
587        let preflight = TreeWritePreflight::new(&mut level);
588
589        assert!(preflight.set_block_state(
590            BlockPos::ZERO,
591            preflight_state,
592            UpdateFlags::UPDATE_ALL,
593        ));
594        assert!(!preflight.failed());
595        assert_eq!(preflight.get_block_state(BlockPos::ZERO), preflight_state);
596    }
597}