Skip to main content

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

1#![expect(
2    clippy::match_same_arms,
3    clippy::too_many_arguments,
4    clippy::too_many_lines,
5    reason = "foliage dispatch keeps vanilla variant behavior explicit"
6)]
7
8use steel_registry::vanilla_block_tags::BlockTag;
9
10use super::super::super::prelude::*;
11use super::super::super::runner::FeatureDecorationRunner;
12use super::{FoliageAttachment, TreePlacement, abs_i32};
13
14impl FeatureDecorationRunner {
15    pub(super) fn tree_foliage_height(
16        random: &mut WorldgenRandom,
17        tree_height: i32,
18        config: &TreeConfiguration,
19    ) -> i32 {
20        match &config.foliage_placer {
21            FoliagePlacer::Blob(placer) => placer.height.sample(random),
22            FoliagePlacer::Bush(placer) => placer.height.sample(random),
23            FoliagePlacer::Fancy(placer) => placer.height.sample(random),
24            FoliagePlacer::Pine(placer) => placer.height.sample(random),
25            FoliagePlacer::Spruce(placer) => {
26                (tree_height - placer.trunk_height.sample(random)).max(4)
27            }
28            FoliagePlacer::MegaPine(placer) => placer.crown_height.sample(random),
29            FoliagePlacer::Acacia(_) => 0,
30            FoliagePlacer::DarkOak(_) => 4,
31            FoliagePlacer::Jungle(placer) => placer.height.sample(random),
32            FoliagePlacer::RandomSpread(placer) => placer.foliage_height,
33            FoliagePlacer::Cherry(placer) => placer.height.sample(random),
34            FoliagePlacer::Poplar(placer) => placer.height.sample(random),
35        }
36    }
37
38    pub(super) fn tree_foliage_radius(
39        random: &mut WorldgenRandom,
40        foliage_placer: &FoliagePlacer,
41        trunk_height: i32,
42    ) -> i32 {
43        match foliage_placer {
44            FoliagePlacer::Blob(placer) => placer.radius.sample(random),
45            FoliagePlacer::Bush(placer) => placer.radius.sample(random),
46            FoliagePlacer::Fancy(placer) => placer.radius.sample(random),
47            FoliagePlacer::Pine(placer) => {
48                placer.radius.sample(random) + random.next_i32_bounded((trunk_height + 1).max(1))
49            }
50            FoliagePlacer::Spruce(placer) => placer.radius.sample(random),
51            FoliagePlacer::MegaPine(placer) => placer.radius.sample(random),
52            FoliagePlacer::Acacia(placer) => placer.radius.sample(random),
53            FoliagePlacer::DarkOak(placer) => placer.radius.sample(random),
54            FoliagePlacer::Jungle(placer) => placer.radius.sample(random),
55            FoliagePlacer::RandomSpread(placer) => placer.radius.sample(random),
56            FoliagePlacer::Cherry(placer) => placer.radius.sample(random),
57            FoliagePlacer::Poplar(placer) => placer.radius.sample(random),
58        }
59    }
60
61    pub(super) fn create_tree_foliage(
62        region: &mut impl LevelAccessor,
63        registry: &Registry,
64        random: &mut WorldgenRandom,
65        config: &TreeConfiguration,
66        _tree_height: i32,
67        attachment: FoliageAttachment,
68        foliage_height: i32,
69        leaf_radius: i32,
70        placement: &mut TreePlacement,
71    ) {
72        match &config.foliage_placer {
73            FoliagePlacer::Blob(placer) => Self::create_blob_tree_foliage(
74                region,
75                registry,
76                random,
77                config,
78                placer,
79                attachment,
80                foliage_height,
81                leaf_radius,
82                placement,
83            ),
84            FoliagePlacer::Bush(_) => Self::create_bush_tree_foliage(
85                region,
86                registry,
87                random,
88                config,
89                attachment,
90                foliage_height,
91                leaf_radius,
92                placement,
93            ),
94            FoliagePlacer::Fancy(_) => Self::create_fancy_tree_foliage(
95                region,
96                registry,
97                random,
98                config,
99                attachment,
100                foliage_height,
101                leaf_radius,
102                placement,
103            ),
104            FoliagePlacer::Pine(_) => Self::create_pine_tree_foliage(
105                region,
106                registry,
107                random,
108                config,
109                attachment,
110                foliage_height,
111                leaf_radius,
112                placement,
113            ),
114            FoliagePlacer::Spruce(_) => Self::create_spruce_tree_foliage(
115                region,
116                registry,
117                random,
118                config,
119                attachment,
120                foliage_height,
121                leaf_radius,
122                placement,
123            ),
124            FoliagePlacer::MegaPine(_) => Self::create_mega_pine_tree_foliage(
125                region,
126                registry,
127                random,
128                config,
129                attachment,
130                foliage_height,
131                leaf_radius,
132                placement,
133            ),
134            FoliagePlacer::Acacia(_) => Self::create_acacia_tree_foliage(
135                region,
136                registry,
137                random,
138                config,
139                attachment,
140                foliage_height,
141                leaf_radius,
142                placement,
143            ),
144            FoliagePlacer::DarkOak(_) => Self::create_dark_oak_tree_foliage(
145                region,
146                registry,
147                random,
148                config,
149                attachment,
150                leaf_radius,
151                placement,
152            ),
153            FoliagePlacer::Jungle(_) => Self::create_jungle_tree_foliage(
154                region,
155                registry,
156                random,
157                config,
158                attachment,
159                foliage_height,
160                leaf_radius,
161                placement,
162            ),
163            FoliagePlacer::RandomSpread(placer) => Self::create_random_spread_tree_foliage(
164                region,
165                registry,
166                random,
167                config,
168                placer,
169                attachment,
170                foliage_height,
171                leaf_radius,
172                placement,
173            ),
174            FoliagePlacer::Cherry(placer) => Self::create_cherry_tree_foliage(
175                region,
176                registry,
177                random,
178                config,
179                placer,
180                attachment,
181                foliage_height,
182                leaf_radius,
183                placement,
184            ),
185            FoliagePlacer::Poplar(placer) => Self::create_poplar_tree_foliage(
186                region,
187                registry,
188                random,
189                config,
190                placer,
191                attachment,
192                foliage_height,
193                leaf_radius,
194                placement,
195            ),
196        }
197    }
198
199    fn create_poplar_tree_foliage(
200        region: &mut impl LevelAccessor,
201        registry: &Registry,
202        random: &mut WorldgenRandom,
203        config: &TreeConfiguration,
204        placer: &PoplarFoliagePlacer,
205        attachment: FoliageAttachment,
206        foliage_height: i32,
207        leaf_radius: i32,
208        placement: &mut TreePlacement,
209    ) {
210        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
211        let foliage_pos = attachment.pos.above_n(offset);
212        let current_radius = leaf_radius + attachment.radius_offset - 1;
213        let flip_rhombus_shape = random.next_bool();
214
215        Self::place_poplar_tree_leaves_row(
216            region,
217            registry,
218            random,
219            config,
220            foliage_pos,
221            current_radius - 2,
222            foliage_height - 1,
223            attachment.double_trunk,
224            foliage_height,
225            flip_rhombus_shape,
226            placer.side_hole_chance,
227            placement,
228        );
229        Self::place_poplar_tree_leaves_row(
230            region,
231            registry,
232            random,
233            config,
234            foliage_pos,
235            current_radius - 1,
236            foliage_height - 2,
237            attachment.double_trunk,
238            foliage_height,
239            flip_rhombus_shape,
240            placer.side_hole_chance,
241            placement,
242        );
243        Self::place_poplar_tree_leaves_row(
244            region,
245            registry,
246            random,
247            config,
248            foliage_pos,
249            current_radius - 1,
250            foliage_height - 3,
251            attachment.double_trunk,
252            foliage_height,
253            flip_rhombus_shape,
254            placer.side_hole_chance,
255            placement,
256        );
257
258        for y in (1..foliage_height - 3).rev() {
259            Self::place_poplar_tree_leaves_row(
260                region,
261                registry,
262                random,
263                config,
264                foliage_pos,
265                current_radius,
266                y,
267                attachment.double_trunk,
268                foliage_height,
269                flip_rhombus_shape,
270                placer.side_hole_chance,
271                placement,
272            );
273        }
274
275        Self::replace_poplar_leaves_with_logs(
276            region,
277            registry,
278            random,
279            config,
280            foliage_pos,
281            current_radius,
282            foliage_height - 4,
283            attachment.double_trunk,
284            foliage_height,
285            flip_rhombus_shape,
286            placement,
287        );
288        Self::place_poplar_tree_leaves_row(
289            region,
290            registry,
291            random,
292            config,
293            foliage_pos,
294            current_radius - 1,
295            0,
296            attachment.double_trunk,
297            foliage_height,
298            flip_rhombus_shape,
299            placer.side_hole_chance,
300            placement,
301        );
302        Self::place_poplar_tree_leaves_row(
303            region,
304            registry,
305            random,
306            config,
307            foliage_pos,
308            (current_radius - 2).clamp(1, 2),
309            -1,
310            attachment.double_trunk,
311            foliage_height,
312            flip_rhombus_shape,
313            placer.side_hole_chance,
314            placement,
315        );
316    }
317
318    fn place_poplar_tree_leaves_row(
319        region: &mut impl LevelAccessor,
320        registry: &Registry,
321        random: &mut WorldgenRandom,
322        config: &TreeConfiguration,
323        origin: BlockPos,
324        current_radius: i32,
325        y: i32,
326        double_trunk: bool,
327        foliage_height: i32,
328        flip_rhombus_shape: bool,
329        side_hole_chance: f32,
330        placement: &mut TreePlacement,
331    ) {
332        let offset = i32::from(double_trunk);
333        for dx in -current_radius..=current_radius + offset {
334            for dz in -current_radius..=current_radius + offset {
335                if !Self::poplar_foliage_should_skip_location(
336                    random,
337                    dx,
338                    y,
339                    dz,
340                    current_radius,
341                    foliage_height,
342                    flip_rhombus_shape,
343                    side_hole_chance,
344                ) {
345                    let pos = origin.offset(dx, y, dz);
346                    let _ =
347                        Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
348                }
349            }
350        }
351    }
352
353    #[expect(
354        clippy::similar_names,
355        reason = "the names mirror Vanilla's x/z offsets"
356    )]
357    fn replace_poplar_leaves_with_logs(
358        region: &mut impl LevelAccessor,
359        registry: &Registry,
360        random: &mut WorldgenRandom,
361        config: &TreeConfiguration,
362        origin: BlockPos,
363        current_radius: i32,
364        y: i32,
365        double_trunk: bool,
366        foliage_height: i32,
367        flip_rhombus_shape: bool,
368        placement: &mut TreePlacement,
369    ) {
370        let offset = i32::from(double_trunk);
371        for dx in -current_radius..=current_radius + offset {
372            for dz in -current_radius..=current_radius + offset {
373                let abs_dx = abs_i32(dx);
374                let abs_dz = abs_i32(dz);
375                let corner_blocks_to_cut = Self::poplar_corner_blocks_to_cut(
376                    dx,
377                    dz,
378                    current_radius,
379                    Self::poplar_row_is_partial(foliage_height, y),
380                    flip_rhombus_shape,
381                );
382                if !Self::poplar_is_within_rhombus(
383                    current_radius,
384                    abs_dx,
385                    abs_dz,
386                    corner_blocks_to_cut,
387                    2,
388                ) || !(abs_dz == 0 && current_radius - abs_dx >= 4
389                    || abs_dx == 0 && current_radius - abs_dz >= 4)
390                {
391                    continue;
392                }
393
394                let pos = origin.offset(dx, y, dz);
395                let foliage_state = BlockStateProviderEvaluator::sample_block_state_provider(
396                    region,
397                    registry,
398                    random,
399                    &config.foliage_provider,
400                    pos,
401                );
402                if region.get_block_state(pos) != foliage_state {
403                    continue;
404                }
405
406                let trunk_state = BlockStateProviderEvaluator::sample_block_state_provider(
407                    region,
408                    registry,
409                    random,
410                    &config.trunk_provider,
411                    pos,
412                );
413                let axis = if abs_dz == 0 { Axis::X } else { Axis::Z };
414                let trunk_state = Self::with_axis_if_present(trunk_state, axis);
415                placement.set_foliage(region, pos, trunk_state);
416            }
417        }
418    }
419
420    #[expect(
421        clippy::similar_names,
422        reason = "the names mirror Vanilla's x/z offsets"
423    )]
424    fn poplar_foliage_should_skip_location(
425        random: &mut WorldgenRandom,
426        dx: i32,
427        y: i32,
428        dz: i32,
429        current_radius: i32,
430        foliage_height: i32,
431        flip_rhombus_shape: bool,
432        side_hole_chance: f32,
433    ) -> bool {
434        let partial_rhombus = Self::poplar_row_is_partial(foliage_height, y);
435        let corner_blocks_to_cut = Self::poplar_corner_blocks_to_cut(
436            dx,
437            dz,
438            current_radius,
439            partial_rhombus,
440            flip_rhombus_shape,
441        );
442        let abs_dx = abs_i32(dx);
443        let abs_dz = abs_i32(dz);
444        if partial_rhombus && (abs_dx == current_radius || abs_dz == current_radius) {
445            return true;
446        }
447
448        let additional_side_removal = i32::from(random.next_f32() <= side_hole_chance);
449        !Self::poplar_is_within_rhombus(
450            current_radius,
451            abs_dx,
452            abs_dz,
453            corner_blocks_to_cut,
454            additional_side_removal,
455        )
456    }
457
458    const fn poplar_row_is_partial(foliage_height: i32, y: i32) -> bool {
459        foliage_height - 1 == y || foliage_height - 2 == y
460    }
461
462    const fn poplar_corner_blocks_to_cut(
463        dx: i32,
464        dz: i32,
465        current_radius: i32,
466        partial_rhombus: bool,
467        flip_rhombus_shape: bool,
468    ) -> i32 {
469        let small_corner = if flip_rhombus_shape {
470            (dx > 0 && dz > 0) || (dz < 0 && dx < 0)
471        } else {
472            (dx > 0 && dz < 0) || (dz > 0 && dx < 0)
473        };
474        if small_corner {
475            current_radius - 1
476        } else if partial_rhombus {
477            current_radius + 1
478        } else {
479            current_radius
480        }
481    }
482
483    #[expect(
484        clippy::similar_names,
485        reason = "the names mirror Vanilla's x/z offsets"
486    )]
487    const fn poplar_is_within_rhombus(
488        current_radius: i32,
489        abs_dx: i32,
490        abs_dz: i32,
491        corner_blocks_to_cut: i32,
492        additional_side_removal: i32,
493    ) -> bool {
494        abs_dx + abs_dz <= current_radius * 2 - (corner_blocks_to_cut + additional_side_removal)
495    }
496
497    fn create_fancy_tree_foliage(
498        region: &mut impl LevelAccessor,
499        registry: &Registry,
500        random: &mut WorldgenRandom,
501        config: &TreeConfiguration,
502        attachment: FoliageAttachment,
503        foliage_height: i32,
504        leaf_radius: i32,
505        placement: &mut TreePlacement,
506    ) {
507        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
508        for y in (offset - foliage_height..=offset).rev() {
509            let current_radius = if y != offset && y != offset - foliage_height {
510                leaf_radius + 1
511            } else {
512                leaf_radius
513            };
514            Self::place_tree_leaves_row(
515                region,
516                registry,
517                random,
518                config,
519                attachment.pos,
520                current_radius,
521                y,
522                attachment.double_trunk,
523                placement,
524            );
525        }
526    }
527
528    fn create_jungle_tree_foliage(
529        region: &mut impl LevelAccessor,
530        registry: &Registry,
531        random: &mut WorldgenRandom,
532        config: &TreeConfiguration,
533        attachment: FoliageAttachment,
534        foliage_height: i32,
535        leaf_radius: i32,
536        placement: &mut TreePlacement,
537    ) {
538        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
539        let leaf_height = if attachment.double_trunk {
540            foliage_height
541        } else {
542            1 + random.next_i32_bounded(2)
543        };
544
545        for y in (offset - leaf_height..=offset).rev() {
546            let current_radius = leaf_radius + attachment.radius_offset + 1 - y;
547            Self::place_tree_leaves_row(
548                region,
549                registry,
550                random,
551                config,
552                attachment.pos,
553                current_radius,
554                y,
555                attachment.double_trunk,
556                placement,
557            );
558        }
559    }
560
561    fn create_random_spread_tree_foliage(
562        region: &mut impl LevelAccessor,
563        registry: &Registry,
564        random: &mut WorldgenRandom,
565        config: &TreeConfiguration,
566        placer: &RandomSpreadFoliagePlacer,
567        attachment: FoliageAttachment,
568        foliage_height: i32,
569        leaf_radius: i32,
570        placement: &mut TreePlacement,
571    ) {
572        for _ in 0..placer.leaf_placement_attempts {
573            let pos = attachment.pos.offset(
574                random.next_i32_bounded(leaf_radius) - random.next_i32_bounded(leaf_radius),
575                random.next_i32_bounded(foliage_height) - random.next_i32_bounded(foliage_height),
576                random.next_i32_bounded(leaf_radius) - random.next_i32_bounded(leaf_radius),
577            );
578            let _ = Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
579        }
580    }
581
582    fn create_cherry_tree_foliage(
583        region: &mut impl LevelAccessor,
584        registry: &Registry,
585        random: &mut WorldgenRandom,
586        config: &TreeConfiguration,
587        placer: &CherryFoliagePlacer,
588        attachment: FoliageAttachment,
589        foliage_height: i32,
590        leaf_radius: i32,
591        placement: &mut TreePlacement,
592    ) {
593        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
594        let foliage_pos = attachment.pos.above_n(offset);
595        let current_radius = leaf_radius + attachment.radius_offset - 1;
596        Self::place_tree_leaves_row(
597            region,
598            registry,
599            random,
600            config,
601            foliage_pos,
602            current_radius - 2,
603            foliage_height - 3,
604            attachment.double_trunk,
605            placement,
606        );
607        Self::place_tree_leaves_row(
608            region,
609            registry,
610            random,
611            config,
612            foliage_pos,
613            current_radius - 1,
614            foliage_height - 4,
615            attachment.double_trunk,
616            placement,
617        );
618
619        for y in (0..=foliage_height - 5).rev() {
620            Self::place_tree_leaves_row(
621                region,
622                registry,
623                random,
624                config,
625                foliage_pos,
626                current_radius,
627                y,
628                attachment.double_trunk,
629                placement,
630            );
631        }
632
633        Self::place_tree_leaves_row_with_hanging_leaves_below(
634            region,
635            registry,
636            random,
637            config,
638            foliage_pos,
639            current_radius,
640            -1,
641            attachment.double_trunk,
642            placer.hanging_leaves_chance,
643            placer.hanging_leaves_extension_chance,
644            placement,
645        );
646        Self::place_tree_leaves_row_with_hanging_leaves_below(
647            region,
648            registry,
649            random,
650            config,
651            foliage_pos,
652            current_radius - 1,
653            -2,
654            attachment.double_trunk,
655            placer.hanging_leaves_chance,
656            placer.hanging_leaves_extension_chance,
657            placement,
658        );
659    }
660
661    fn create_dark_oak_tree_foliage(
662        region: &mut impl LevelAccessor,
663        registry: &Registry,
664        random: &mut WorldgenRandom,
665        config: &TreeConfiguration,
666        attachment: FoliageAttachment,
667        leaf_radius: i32,
668        placement: &mut TreePlacement,
669    ) {
670        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
671        let pos = attachment.pos.above_n(offset);
672        if attachment.double_trunk {
673            Self::place_tree_leaves_row(
674                region,
675                registry,
676                random,
677                config,
678                pos,
679                leaf_radius + 2,
680                -1,
681                true,
682                placement,
683            );
684            Self::place_tree_leaves_row(
685                region,
686                registry,
687                random,
688                config,
689                pos,
690                leaf_radius + 3,
691                0,
692                true,
693                placement,
694            );
695            Self::place_tree_leaves_row(
696                region,
697                registry,
698                random,
699                config,
700                pos,
701                leaf_radius + 2,
702                1,
703                true,
704                placement,
705            );
706            if random.next_bool() {
707                Self::place_tree_leaves_row(
708                    region,
709                    registry,
710                    random,
711                    config,
712                    pos,
713                    leaf_radius,
714                    2,
715                    true,
716                    placement,
717                );
718            }
719        } else {
720            Self::place_tree_leaves_row(
721                region,
722                registry,
723                random,
724                config,
725                pos,
726                leaf_radius + 2,
727                -1,
728                false,
729                placement,
730            );
731            Self::place_tree_leaves_row(
732                region,
733                registry,
734                random,
735                config,
736                pos,
737                leaf_radius + 1,
738                0,
739                false,
740                placement,
741            );
742        }
743    }
744
745    fn create_mega_pine_tree_foliage(
746        region: &mut impl LevelAccessor,
747        registry: &Registry,
748        random: &mut WorldgenRandom,
749        config: &TreeConfiguration,
750        attachment: FoliageAttachment,
751        foliage_height: i32,
752        leaf_radius: i32,
753        placement: &mut TreePlacement,
754    ) {
755        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
756        let mut previous_radius = 0;
757        let min_y = attachment.pos.y() - foliage_height + offset;
758        let max_y = attachment.pos.y() + offset;
759
760        for y in min_y..=max_y {
761            let y_offset = attachment.pos.y() - y;
762            let smooth_radius = leaf_radius
763                + attachment.radius_offset
764                + fast_floor(f64::from(y_offset) / f64::from(foliage_height) * 3.5);
765            let jagged_radius = if y_offset > 0 && smooth_radius == previous_radius && (y & 1) == 0
766            {
767                smooth_radius + 1
768            } else {
769                smooth_radius
770            };
771            let row_origin = BlockPos::new(attachment.pos.x(), y, attachment.pos.z());
772            Self::place_tree_leaves_row(
773                region,
774                registry,
775                random,
776                config,
777                row_origin,
778                jagged_radius,
779                0,
780                attachment.double_trunk,
781                placement,
782            );
783            previous_radius = smooth_radius;
784        }
785    }
786
787    fn create_bush_tree_foliage(
788        region: &mut impl LevelAccessor,
789        registry: &Registry,
790        random: &mut WorldgenRandom,
791        config: &TreeConfiguration,
792        attachment: FoliageAttachment,
793        foliage_height: i32,
794        leaf_radius: i32,
795        placement: &mut TreePlacement,
796    ) {
797        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
798        for y in (offset - foliage_height..=offset).rev() {
799            let current_radius = leaf_radius + attachment.radius_offset - 1 - y;
800            Self::place_tree_leaves_row(
801                region,
802                registry,
803                random,
804                config,
805                attachment.pos,
806                current_radius,
807                y,
808                attachment.double_trunk,
809                placement,
810            );
811        }
812    }
813
814    fn create_pine_tree_foliage(
815        region: &mut impl LevelAccessor,
816        registry: &Registry,
817        random: &mut WorldgenRandom,
818        config: &TreeConfiguration,
819        attachment: FoliageAttachment,
820        foliage_height: i32,
821        leaf_radius: i32,
822        placement: &mut TreePlacement,
823    ) {
824        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
825        let mut current_radius = 0;
826        for y in (offset - foliage_height..=offset).rev() {
827            Self::place_tree_leaves_row(
828                region,
829                registry,
830                random,
831                config,
832                attachment.pos,
833                current_radius,
834                y,
835                attachment.double_trunk,
836                placement,
837            );
838            if current_radius >= 1 && y == offset - foliage_height + 1 {
839                current_radius -= 1;
840            } else if current_radius < leaf_radius + attachment.radius_offset {
841                current_radius += 1;
842            }
843        }
844    }
845
846    fn create_spruce_tree_foliage(
847        region: &mut impl LevelAccessor,
848        registry: &Registry,
849        random: &mut WorldgenRandom,
850        config: &TreeConfiguration,
851        attachment: FoliageAttachment,
852        foliage_height: i32,
853        leaf_radius: i32,
854        placement: &mut TreePlacement,
855    ) {
856        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
857        let mut current_radius = random.next_i32_bounded(2);
858        let mut max_radius = 1;
859        let mut min_radius = 0;
860
861        for y in (-foliage_height..=offset).rev() {
862            Self::place_tree_leaves_row(
863                region,
864                registry,
865                random,
866                config,
867                attachment.pos,
868                current_radius,
869                y,
870                attachment.double_trunk,
871                placement,
872            );
873            if current_radius >= max_radius {
874                current_radius = min_radius;
875                min_radius = 1;
876                max_radius = (max_radius + 1).min(leaf_radius + attachment.radius_offset);
877            } else {
878                current_radius += 1;
879            }
880        }
881    }
882
883    fn create_blob_tree_foliage(
884        region: &mut impl LevelAccessor,
885        registry: &Registry,
886        random: &mut WorldgenRandom,
887        config: &TreeConfiguration,
888        _placer: &BlobFoliagePlacer,
889        attachment: FoliageAttachment,
890        foliage_height: i32,
891        leaf_radius: i32,
892        placement: &mut TreePlacement,
893    ) {
894        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
895        for y in (offset - foliage_height..=offset).rev() {
896            let current_radius = (leaf_radius + attachment.radius_offset - 1 - y / 2).max(0);
897            Self::place_tree_leaves_row(
898                region,
899                registry,
900                random,
901                config,
902                attachment.pos,
903                current_radius,
904                y,
905                attachment.double_trunk,
906                placement,
907            );
908        }
909    }
910
911    fn create_acacia_tree_foliage(
912        region: &mut impl LevelAccessor,
913        registry: &Registry,
914        random: &mut WorldgenRandom,
915        config: &TreeConfiguration,
916        attachment: FoliageAttachment,
917        foliage_height: i32,
918        leaf_radius: i32,
919        placement: &mut TreePlacement,
920    ) {
921        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
922        let foliage_pos = attachment.pos.above_n(offset);
923        Self::place_tree_leaves_row(
924            region,
925            registry,
926            random,
927            config,
928            foliage_pos,
929            leaf_radius + attachment.radius_offset,
930            -1 - foliage_height,
931            attachment.double_trunk,
932            placement,
933        );
934        Self::place_tree_leaves_row(
935            region,
936            registry,
937            random,
938            config,
939            foliage_pos,
940            leaf_radius - 1,
941            -foliage_height,
942            attachment.double_trunk,
943            placement,
944        );
945        Self::place_tree_leaves_row(
946            region,
947            registry,
948            random,
949            config,
950            foliage_pos,
951            leaf_radius + attachment.radius_offset - 1,
952            0,
953            attachment.double_trunk,
954            placement,
955        );
956    }
957
958    fn tree_foliage_offset(random: &mut WorldgenRandom, foliage_placer: &FoliagePlacer) -> i32 {
959        match foliage_placer {
960            FoliagePlacer::Blob(placer) => placer.offset.sample(random),
961            FoliagePlacer::Bush(placer) => placer.offset.sample(random),
962            FoliagePlacer::Fancy(placer) => placer.offset.sample(random),
963            FoliagePlacer::Pine(placer) => placer.offset.sample(random),
964            FoliagePlacer::Spruce(placer) => placer.offset.sample(random),
965            FoliagePlacer::MegaPine(placer) => placer.offset.sample(random),
966            FoliagePlacer::Acacia(placer) => placer.offset.sample(random),
967            FoliagePlacer::DarkOak(placer) => placer.offset.sample(random),
968            FoliagePlacer::Jungle(placer) => placer.offset.sample(random),
969            FoliagePlacer::RandomSpread(placer) => placer.offset.sample(random),
970            FoliagePlacer::Cherry(placer) => placer.offset.sample(random),
971            FoliagePlacer::Poplar(placer) => placer.offset.sample(random),
972        }
973    }
974
975    fn place_tree_leaves_row(
976        region: &mut impl LevelAccessor,
977        registry: &Registry,
978        random: &mut WorldgenRandom,
979        config: &TreeConfiguration,
980        origin: BlockPos,
981        current_radius: i32,
982        y: i32,
983        double_trunk: bool,
984        placement: &mut TreePlacement,
985    ) {
986        let offset = i32::from(double_trunk);
987        for dx in -current_radius..=current_radius + offset {
988            for dz in -current_radius..=current_radius + offset {
989                if !Self::tree_foliage_should_skip_location(
990                    random,
991                    &config.foliage_placer,
992                    dx,
993                    y,
994                    dz,
995                    current_radius,
996                    double_trunk,
997                ) {
998                    let pos = origin.offset(dx, y, dz);
999                    let _ =
1000                        Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
1001                }
1002            }
1003        }
1004    }
1005
1006    #[expect(
1007        clippy::too_many_arguments,
1008        reason = "mirrors vanilla foliage row helper"
1009    )]
1010    fn place_tree_leaves_row_with_hanging_leaves_below(
1011        region: &mut impl LevelAccessor,
1012        registry: &Registry,
1013        random: &mut WorldgenRandom,
1014        config: &TreeConfiguration,
1015        origin: BlockPos,
1016        current_radius: i32,
1017        y: i32,
1018        double_trunk: bool,
1019        hanging_leaves_chance: f32,
1020        hanging_leaves_extension_chance: f32,
1021        placement: &mut TreePlacement,
1022    ) {
1023        Self::place_tree_leaves_row(
1024            region,
1025            registry,
1026            random,
1027            config,
1028            origin,
1029            current_radius,
1030            y,
1031            double_trunk,
1032            placement,
1033        );
1034
1035        let offset = i32::from(double_trunk);
1036        let log_pos = origin.below();
1037        for along_edge in Self::VANILLA_HORIZONTAL_DIRECTIONS {
1038            let to_edge = along_edge.rotate_y_clockwise();
1039            let offset_to_edge = if Self::direction_is_positive(to_edge) {
1040                current_radius + offset
1041            } else {
1042                current_radius
1043            };
1044            let mut pos = origin
1045                .offset(0, y - 1, 0)
1046                .relative_n(to_edge, offset_to_edge)
1047                .relative_n(along_edge, -current_radius);
1048            let mut offset_along_edge = -current_radius;
1049
1050            while offset_along_edge < current_radius + offset {
1051                let leaves_above = placement.foliage.contains(pos.above());
1052                if leaves_above
1053                    && Self::try_place_hanging_leaf_extension(
1054                        region,
1055                        registry,
1056                        random,
1057                        config,
1058                        hanging_leaves_chance,
1059                        log_pos,
1060                        pos,
1061                        placement,
1062                    )
1063                {
1064                    let _ = Self::try_place_hanging_leaf_extension(
1065                        region,
1066                        registry,
1067                        random,
1068                        config,
1069                        hanging_leaves_extension_chance,
1070                        log_pos,
1071                        pos.below(),
1072                        placement,
1073                    );
1074                }
1075
1076                offset_along_edge += 1;
1077                pos = pos.relative(along_edge);
1078            }
1079        }
1080    }
1081
1082    fn try_place_hanging_leaf_extension(
1083        region: &mut impl LevelAccessor,
1084        registry: &Registry,
1085        random: &mut WorldgenRandom,
1086        config: &TreeConfiguration,
1087        chance: f32,
1088        log_pos: BlockPos,
1089        pos: BlockPos,
1090        placement: &mut TreePlacement,
1091    ) -> bool {
1092        if Self::manhattan_distance(pos, log_pos) >= 7 || random.next_f32() > chance {
1093            return false;
1094        }
1095
1096        Self::try_place_tree_leaf(region, registry, random, config, pos, placement)
1097    }
1098
1099    const fn direction_is_positive(direction: Direction) -> bool {
1100        matches!(
1101            direction,
1102            Direction::Up | Direction::South | Direction::East
1103        )
1104    }
1105
1106    fn tree_foliage_should_skip_location(
1107        random: &mut WorldgenRandom,
1108        foliage_placer: &FoliagePlacer,
1109        dx: i32,
1110        y: i32,
1111        dz: i32,
1112        current_radius: i32,
1113        double_trunk: bool,
1114    ) -> bool {
1115        if matches!(foliage_placer, FoliagePlacer::DarkOak(_)) {
1116            return Self::dark_oak_foliage_should_skip_location(
1117                dx,
1118                y,
1119                dz,
1120                current_radius,
1121                double_trunk,
1122            );
1123        }
1124
1125        let (dx, dz) = Self::foliage_signed_distances(dx, dz, double_trunk);
1126        match foliage_placer {
1127            FoliagePlacer::Blob(_) => {
1128                Self::blob_foliage_should_skip_location(random, dx, y, dz, current_radius)
1129            }
1130            FoliagePlacer::Bush(_) => {
1131                Self::bush_foliage_should_skip_location(random, dx, dz, current_radius)
1132            }
1133            FoliagePlacer::Fancy(_) => {
1134                Self::fancy_foliage_should_skip_location(dx, dz, current_radius)
1135            }
1136            FoliagePlacer::Pine(_) | FoliagePlacer::Spruce(_) => {
1137                Self::conifer_foliage_should_skip_location(dx, dz, current_radius)
1138            }
1139            FoliagePlacer::MegaPine(_) => {
1140                Self::mega_pine_foliage_should_skip_location(dx, dz, current_radius)
1141            }
1142            FoliagePlacer::Jungle(_) => {
1143                Self::mega_pine_foliage_should_skip_location(dx, dz, current_radius)
1144            }
1145            FoliagePlacer::RandomSpread(_) => false,
1146            FoliagePlacer::Acacia(_) => {
1147                Self::acacia_foliage_should_skip_location(dx, y, dz, current_radius)
1148            }
1149            FoliagePlacer::Cherry(placer) => {
1150                Self::cherry_foliage_should_skip_location(random, placer, dx, y, dz, current_radius)
1151            }
1152            FoliagePlacer::DarkOak(_) => unreachable!(),
1153            FoliagePlacer::Poplar(_) => {
1154                unreachable!("Poplar uses poplar_foliage_should_skip_location")
1155            }
1156        }
1157    }
1158
1159    fn blob_foliage_should_skip_location(
1160        random: &mut WorldgenRandom,
1161        dx: i32,
1162        y: i32,
1163        dz: i32,
1164        current_radius: i32,
1165    ) -> bool {
1166        dx == current_radius && dz == current_radius && (random.next_i32_bounded(2) == 0 || y == 0)
1167    }
1168
1169    fn bush_foliage_should_skip_location(
1170        random: &mut WorldgenRandom,
1171        dx: i32,
1172        dz: i32,
1173        current_radius: i32,
1174    ) -> bool {
1175        dx == current_radius && dz == current_radius && random.next_i32_bounded(2) == 0
1176    }
1177
1178    fn fancy_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
1179        let dx = dx as f32 + 0.5;
1180        let dz = dz as f32 + 0.5;
1181        dx * dx + dz * dz > (current_radius * current_radius) as f32
1182    }
1183
1184    fn dark_oak_foliage_should_skip_location(
1185        dx: i32,
1186        y: i32,
1187        dz: i32,
1188        current_radius: i32,
1189        double_trunk: bool,
1190    ) -> bool {
1191        if y == 0
1192            && double_trunk
1193            && (dx == -current_radius || dx >= current_radius)
1194            && (dz == -current_radius || dz >= current_radius)
1195        {
1196            return true;
1197        }
1198
1199        let (dx, dz) = Self::foliage_signed_distances(dx, dz, double_trunk);
1200        if y == -1 && !double_trunk {
1201            dx == current_radius && dz == current_radius
1202        } else if y == 1 {
1203            dx + dz > current_radius * 2 - 2
1204        } else {
1205            false
1206        }
1207    }
1208
1209    fn cherry_foliage_should_skip_location(
1210        random: &mut WorldgenRandom,
1211        placer: &CherryFoliagePlacer,
1212        dx: i32,
1213        y: i32,
1214        dz: i32,
1215        current_radius: i32,
1216    ) -> bool {
1217        if y == -1
1218            && (dx == current_radius || dz == current_radius)
1219            && random.next_f32() < placer.wide_bottom_layer_hole_chance
1220        {
1221            return true;
1222        }
1223
1224        let corner = dx == current_radius && dz == current_radius;
1225        let wide_layer = current_radius > 2;
1226        if wide_layer {
1227            corner
1228                || dx + dz > current_radius * 2 - 2 && random.next_f32() < placer.corner_hole_chance
1229        } else {
1230            corner && random.next_f32() < placer.corner_hole_chance
1231        }
1232    }
1233
1234    const fn conifer_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
1235        dx == current_radius && dz == current_radius && current_radius > 0
1236    }
1237
1238    const fn mega_pine_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
1239        dx + dz >= 7 || dx * dx + dz * dz > current_radius * current_radius
1240    }
1241
1242    const fn acacia_foliage_should_skip_location(
1243        dx: i32,
1244        y: i32,
1245        dz: i32,
1246        current_radius: i32,
1247    ) -> bool {
1248        if y == 0 {
1249            (dx > 1 || dz > 1) && dx != 0 && dz != 0
1250        } else {
1251            dx == current_radius && dz == current_radius && current_radius > 0
1252        }
1253    }
1254
1255    fn foliage_signed_distances(dx: i32, dz: i32, double_trunk: bool) -> (i32, i32) {
1256        if double_trunk {
1257            (
1258                abs_i32(dx).min(abs_i32(dx - 1)),
1259                abs_i32(dz).min(abs_i32(dz - 1)),
1260            )
1261        } else {
1262            (abs_i32(dx), abs_i32(dz))
1263        }
1264    }
1265
1266    fn try_place_tree_leaf(
1267        region: &mut impl LevelAccessor,
1268        registry: &Registry,
1269        random: &mut WorldgenRandom,
1270        config: &TreeConfiguration,
1271        pos: BlockPos,
1272        placement: &mut TreePlacement,
1273    ) -> bool {
1274        let current_state = region.get_block_state(pos);
1275        let is_persistent = current_state
1276            .try_get_value(&BlockStateProperties::PERSISTENT)
1277            .unwrap_or(false);
1278        let valid_tree_pos = current_state.is_air()
1279            || current_state
1280                .get_block()
1281                .has_tag(&BlockTag::REPLACEABLE_BY_TREES);
1282        if is_persistent || !valid_tree_pos {
1283            return false;
1284        }
1285
1286        let foliage_state = BlockStateProviderEvaluator::sample_block_state_provider(
1287            region,
1288            registry,
1289            random,
1290            &config.foliage_provider,
1291            pos,
1292        );
1293        let foliage_state = Self::copy_waterlogged_from(region, pos, foliage_state);
1294        placement.set_foliage(region, pos, foliage_state);
1295        true
1296    }
1297}
1298
1299#[cfg(test)]
1300mod tests {
1301    use super::*;
1302
1303    #[test]
1304    fn acacia_top_layer_keeps_cross_and_inner_corners() {
1305        assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(2, 0, 2, 2));
1306        assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(1, 0, 2, 2));
1307        assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(0, 0, 2, 2));
1308        assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(1, 0, 1, 2));
1309    }
1310
1311    #[test]
1312    fn acacia_lower_layers_skip_only_outer_corners() {
1313        assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(2, -1, 2, 2));
1314        assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(1, -1, 2, 2));
1315        assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(0, -1, 0, 0));
1316    }
1317
1318    #[test]
1319    fn conifer_layers_skip_only_nonzero_outer_corners() {
1320        assert!(FeatureDecorationRunner::conifer_foliage_should_skip_location(2, 2, 2));
1321        assert!(!FeatureDecorationRunner::conifer_foliage_should_skip_location(1, 2, 2));
1322        assert!(!FeatureDecorationRunner::conifer_foliage_should_skip_location(0, 0, 0));
1323    }
1324
1325    #[test]
1326    fn mega_pine_uses_circular_row_cutoff_with_vanilla_seven_limit() {
1327        assert!(FeatureDecorationRunner::mega_pine_foliage_should_skip_location(4, 3, 5));
1328        assert!(FeatureDecorationRunner::mega_pine_foliage_should_skip_location(4, 4, 5));
1329        assert!(!FeatureDecorationRunner::mega_pine_foliage_should_skip_location(3, 3, 5));
1330    }
1331
1332    #[test]
1333    fn fancy_foliage_uses_shifted_circular_cutoff() {
1334        assert!(!FeatureDecorationRunner::fancy_foliage_should_skip_location(0, 0, 1));
1335        assert!(FeatureDecorationRunner::fancy_foliage_should_skip_location(
1336            1, 0, 1
1337        ));
1338        assert!(FeatureDecorationRunner::fancy_foliage_should_skip_location(
1339            0, 0, 0
1340        ));
1341    }
1342
1343    #[test]
1344    fn dark_oak_double_trunk_skips_outer_corner_extensions_on_center_layer() {
1345        assert!(
1346            FeatureDecorationRunner::dark_oak_foliage_should_skip_location(-3, 0, -3, 3, true,)
1347        );
1348        assert!(FeatureDecorationRunner::dark_oak_foliage_should_skip_location(4, 0, 4, 3, true,));
1349        assert!(
1350            !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(-2, 0, -3, 3, true,)
1351        );
1352    }
1353
1354    #[test]
1355    fn dark_oak_side_crowns_skip_lower_outer_corners() {
1356        assert!(
1357            FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, -1, 2, 2, false,)
1358        );
1359        assert!(
1360            !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, -1, 1, 2, false,)
1361        );
1362    }
1363
1364    #[test]
1365    fn dark_oak_upper_layer_uses_diagonal_cutoff() {
1366        assert!(FeatureDecorationRunner::dark_oak_foliage_should_skip_location(3, 1, 2, 3, false,));
1367        assert!(
1368            !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, 1, 2, 3, false,)
1369        );
1370    }
1371}