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        }
35    }
36
37    pub(super) fn tree_foliage_radius(
38        random: &mut WorldgenRandom,
39        foliage_placer: &FoliagePlacer,
40        trunk_height: i32,
41    ) -> i32 {
42        match foliage_placer {
43            FoliagePlacer::Blob(placer) => placer.radius.sample(random),
44            FoliagePlacer::Bush(placer) => placer.radius.sample(random),
45            FoliagePlacer::Fancy(placer) => placer.radius.sample(random),
46            FoliagePlacer::Pine(placer) => {
47                placer.radius.sample(random) + random.next_i32_bounded((trunk_height + 1).max(1))
48            }
49            FoliagePlacer::Spruce(placer) => placer.radius.sample(random),
50            FoliagePlacer::MegaPine(placer) => placer.radius.sample(random),
51            FoliagePlacer::Acacia(placer) => placer.radius.sample(random),
52            FoliagePlacer::DarkOak(placer) => placer.radius.sample(random),
53            FoliagePlacer::Jungle(placer) => placer.radius.sample(random),
54            FoliagePlacer::RandomSpread(placer) => placer.radius.sample(random),
55            FoliagePlacer::Cherry(placer) => placer.radius.sample(random),
56        }
57    }
58
59    pub(super) fn create_tree_foliage(
60        region: &mut WorldGenRegion<'_>,
61        registry: &Registry,
62        random: &mut WorldgenRandom,
63        config: &TreeConfiguration,
64        _tree_height: i32,
65        attachment: FoliageAttachment,
66        foliage_height: i32,
67        leaf_radius: i32,
68        placement: &mut TreePlacement,
69    ) {
70        match &config.foliage_placer {
71            FoliagePlacer::Blob(placer) => Self::create_blob_tree_foliage(
72                region,
73                registry,
74                random,
75                config,
76                placer,
77                attachment,
78                foliage_height,
79                leaf_radius,
80                placement,
81            ),
82            FoliagePlacer::Bush(_) => Self::create_bush_tree_foliage(
83                region,
84                registry,
85                random,
86                config,
87                attachment,
88                foliage_height,
89                leaf_radius,
90                placement,
91            ),
92            FoliagePlacer::Fancy(_) => Self::create_fancy_tree_foliage(
93                region,
94                registry,
95                random,
96                config,
97                attachment,
98                foliage_height,
99                leaf_radius,
100                placement,
101            ),
102            FoliagePlacer::Pine(_) => Self::create_pine_tree_foliage(
103                region,
104                registry,
105                random,
106                config,
107                attachment,
108                foliage_height,
109                leaf_radius,
110                placement,
111            ),
112            FoliagePlacer::Spruce(_) => Self::create_spruce_tree_foliage(
113                region,
114                registry,
115                random,
116                config,
117                attachment,
118                foliage_height,
119                leaf_radius,
120                placement,
121            ),
122            FoliagePlacer::MegaPine(_) => Self::create_mega_pine_tree_foliage(
123                region,
124                registry,
125                random,
126                config,
127                attachment,
128                foliage_height,
129                leaf_radius,
130                placement,
131            ),
132            FoliagePlacer::Acacia(_) => Self::create_acacia_tree_foliage(
133                region,
134                registry,
135                random,
136                config,
137                attachment,
138                foliage_height,
139                leaf_radius,
140                placement,
141            ),
142            FoliagePlacer::DarkOak(_) => Self::create_dark_oak_tree_foliage(
143                region,
144                registry,
145                random,
146                config,
147                attachment,
148                leaf_radius,
149                placement,
150            ),
151            FoliagePlacer::Jungle(_) => Self::create_jungle_tree_foliage(
152                region,
153                registry,
154                random,
155                config,
156                attachment,
157                foliage_height,
158                leaf_radius,
159                placement,
160            ),
161            FoliagePlacer::RandomSpread(placer) => Self::create_random_spread_tree_foliage(
162                region,
163                registry,
164                random,
165                config,
166                placer,
167                attachment,
168                foliage_height,
169                leaf_radius,
170                placement,
171            ),
172            FoliagePlacer::Cherry(placer) => Self::create_cherry_tree_foliage(
173                region,
174                registry,
175                random,
176                config,
177                placer,
178                attachment,
179                foliage_height,
180                leaf_radius,
181                placement,
182            ),
183        }
184    }
185
186    fn create_fancy_tree_foliage(
187        region: &mut WorldGenRegion<'_>,
188        registry: &Registry,
189        random: &mut WorldgenRandom,
190        config: &TreeConfiguration,
191        attachment: FoliageAttachment,
192        foliage_height: i32,
193        leaf_radius: i32,
194        placement: &mut TreePlacement,
195    ) {
196        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
197        for y in (offset - foliage_height..=offset).rev() {
198            let current_radius = if y != offset && y != offset - foliage_height {
199                leaf_radius + 1
200            } else {
201                leaf_radius
202            };
203            Self::place_tree_leaves_row(
204                region,
205                registry,
206                random,
207                config,
208                attachment.pos,
209                current_radius,
210                y,
211                attachment.double_trunk,
212                placement,
213            );
214        }
215    }
216
217    fn create_jungle_tree_foliage(
218        region: &mut WorldGenRegion<'_>,
219        registry: &Registry,
220        random: &mut WorldgenRandom,
221        config: &TreeConfiguration,
222        attachment: FoliageAttachment,
223        foliage_height: i32,
224        leaf_radius: i32,
225        placement: &mut TreePlacement,
226    ) {
227        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
228        let leaf_height = if attachment.double_trunk {
229            foliage_height
230        } else {
231            1 + random.next_i32_bounded(2)
232        };
233
234        for y in (offset - leaf_height..=offset).rev() {
235            let current_radius = leaf_radius + attachment.radius_offset + 1 - y;
236            Self::place_tree_leaves_row(
237                region,
238                registry,
239                random,
240                config,
241                attachment.pos,
242                current_radius,
243                y,
244                attachment.double_trunk,
245                placement,
246            );
247        }
248    }
249
250    fn create_random_spread_tree_foliage(
251        region: &mut WorldGenRegion<'_>,
252        registry: &Registry,
253        random: &mut WorldgenRandom,
254        config: &TreeConfiguration,
255        placer: &RandomSpreadFoliagePlacer,
256        attachment: FoliageAttachment,
257        foliage_height: i32,
258        leaf_radius: i32,
259        placement: &mut TreePlacement,
260    ) {
261        for _ in 0..placer.leaf_placement_attempts {
262            let pos = attachment.pos.offset(
263                random.next_i32_bounded(leaf_radius) - random.next_i32_bounded(leaf_radius),
264                random.next_i32_bounded(foliage_height) - random.next_i32_bounded(foliage_height),
265                random.next_i32_bounded(leaf_radius) - random.next_i32_bounded(leaf_radius),
266            );
267            let _ = Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
268        }
269    }
270
271    fn create_cherry_tree_foliage(
272        region: &mut WorldGenRegion<'_>,
273        registry: &Registry,
274        random: &mut WorldgenRandom,
275        config: &TreeConfiguration,
276        placer: &CherryFoliagePlacer,
277        attachment: FoliageAttachment,
278        foliage_height: i32,
279        leaf_radius: i32,
280        placement: &mut TreePlacement,
281    ) {
282        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
283        let foliage_pos = attachment.pos.above_n(offset);
284        let current_radius = leaf_radius + attachment.radius_offset - 1;
285        Self::place_tree_leaves_row(
286            region,
287            registry,
288            random,
289            config,
290            foliage_pos,
291            current_radius - 2,
292            foliage_height - 3,
293            attachment.double_trunk,
294            placement,
295        );
296        Self::place_tree_leaves_row(
297            region,
298            registry,
299            random,
300            config,
301            foliage_pos,
302            current_radius - 1,
303            foliage_height - 4,
304            attachment.double_trunk,
305            placement,
306        );
307
308        for y in (0..=foliage_height - 5).rev() {
309            Self::place_tree_leaves_row(
310                region,
311                registry,
312                random,
313                config,
314                foliage_pos,
315                current_radius,
316                y,
317                attachment.double_trunk,
318                placement,
319            );
320        }
321
322        Self::place_tree_leaves_row_with_hanging_leaves_below(
323            region,
324            registry,
325            random,
326            config,
327            foliage_pos,
328            current_radius,
329            -1,
330            attachment.double_trunk,
331            placer.hanging_leaves_chance,
332            placer.hanging_leaves_extension_chance,
333            placement,
334        );
335        Self::place_tree_leaves_row_with_hanging_leaves_below(
336            region,
337            registry,
338            random,
339            config,
340            foliage_pos,
341            current_radius - 1,
342            -2,
343            attachment.double_trunk,
344            placer.hanging_leaves_chance,
345            placer.hanging_leaves_extension_chance,
346            placement,
347        );
348    }
349
350    fn create_dark_oak_tree_foliage(
351        region: &mut WorldGenRegion<'_>,
352        registry: &Registry,
353        random: &mut WorldgenRandom,
354        config: &TreeConfiguration,
355        attachment: FoliageAttachment,
356        leaf_radius: i32,
357        placement: &mut TreePlacement,
358    ) {
359        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
360        let pos = attachment.pos.above_n(offset);
361        if attachment.double_trunk {
362            Self::place_tree_leaves_row(
363                region,
364                registry,
365                random,
366                config,
367                pos,
368                leaf_radius + 2,
369                -1,
370                true,
371                placement,
372            );
373            Self::place_tree_leaves_row(
374                region,
375                registry,
376                random,
377                config,
378                pos,
379                leaf_radius + 3,
380                0,
381                true,
382                placement,
383            );
384            Self::place_tree_leaves_row(
385                region,
386                registry,
387                random,
388                config,
389                pos,
390                leaf_radius + 2,
391                1,
392                true,
393                placement,
394            );
395            if random.next_bool() {
396                Self::place_tree_leaves_row(
397                    region,
398                    registry,
399                    random,
400                    config,
401                    pos,
402                    leaf_radius,
403                    2,
404                    true,
405                    placement,
406                );
407            }
408        } else {
409            Self::place_tree_leaves_row(
410                region,
411                registry,
412                random,
413                config,
414                pos,
415                leaf_radius + 2,
416                -1,
417                false,
418                placement,
419            );
420            Self::place_tree_leaves_row(
421                region,
422                registry,
423                random,
424                config,
425                pos,
426                leaf_radius + 1,
427                0,
428                false,
429                placement,
430            );
431        }
432    }
433
434    fn create_mega_pine_tree_foliage(
435        region: &mut WorldGenRegion<'_>,
436        registry: &Registry,
437        random: &mut WorldgenRandom,
438        config: &TreeConfiguration,
439        attachment: FoliageAttachment,
440        foliage_height: i32,
441        leaf_radius: i32,
442        placement: &mut TreePlacement,
443    ) {
444        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
445        let mut previous_radius = 0;
446        let min_y = attachment.pos.y() - foliage_height + offset;
447        let max_y = attachment.pos.y() + offset;
448
449        for y in min_y..=max_y {
450            let y_offset = attachment.pos.y() - y;
451            let smooth_radius = leaf_radius
452                + attachment.radius_offset
453                + fast_floor(f64::from(y_offset) / f64::from(foliage_height) * 3.5);
454            let jagged_radius = if y_offset > 0 && smooth_radius == previous_radius && (y & 1) == 0
455            {
456                smooth_radius + 1
457            } else {
458                smooth_radius
459            };
460            let row_origin = BlockPos::new(attachment.pos.x(), y, attachment.pos.z());
461            Self::place_tree_leaves_row(
462                region,
463                registry,
464                random,
465                config,
466                row_origin,
467                jagged_radius,
468                0,
469                attachment.double_trunk,
470                placement,
471            );
472            previous_radius = smooth_radius;
473        }
474    }
475
476    fn create_bush_tree_foliage(
477        region: &mut WorldGenRegion<'_>,
478        registry: &Registry,
479        random: &mut WorldgenRandom,
480        config: &TreeConfiguration,
481        attachment: FoliageAttachment,
482        foliage_height: i32,
483        leaf_radius: i32,
484        placement: &mut TreePlacement,
485    ) {
486        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
487        for y in (offset - foliage_height..=offset).rev() {
488            let current_radius = leaf_radius + attachment.radius_offset - 1 - y;
489            Self::place_tree_leaves_row(
490                region,
491                registry,
492                random,
493                config,
494                attachment.pos,
495                current_radius,
496                y,
497                attachment.double_trunk,
498                placement,
499            );
500        }
501    }
502
503    fn create_pine_tree_foliage(
504        region: &mut WorldGenRegion<'_>,
505        registry: &Registry,
506        random: &mut WorldgenRandom,
507        config: &TreeConfiguration,
508        attachment: FoliageAttachment,
509        foliage_height: i32,
510        leaf_radius: i32,
511        placement: &mut TreePlacement,
512    ) {
513        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
514        let mut current_radius = 0;
515        for y in (offset - foliage_height..=offset).rev() {
516            Self::place_tree_leaves_row(
517                region,
518                registry,
519                random,
520                config,
521                attachment.pos,
522                current_radius,
523                y,
524                attachment.double_trunk,
525                placement,
526            );
527            if current_radius >= 1 && y == offset - foliage_height + 1 {
528                current_radius -= 1;
529            } else if current_radius < leaf_radius + attachment.radius_offset {
530                current_radius += 1;
531            }
532        }
533    }
534
535    fn create_spruce_tree_foliage(
536        region: &mut WorldGenRegion<'_>,
537        registry: &Registry,
538        random: &mut WorldgenRandom,
539        config: &TreeConfiguration,
540        attachment: FoliageAttachment,
541        foliage_height: i32,
542        leaf_radius: i32,
543        placement: &mut TreePlacement,
544    ) {
545        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
546        let mut current_radius = random.next_i32_bounded(2);
547        let mut max_radius = 1;
548        let mut min_radius = 0;
549
550        for y in (-foliage_height..=offset).rev() {
551            Self::place_tree_leaves_row(
552                region,
553                registry,
554                random,
555                config,
556                attachment.pos,
557                current_radius,
558                y,
559                attachment.double_trunk,
560                placement,
561            );
562            if current_radius >= max_radius {
563                current_radius = min_radius;
564                min_radius = 1;
565                max_radius = (max_radius + 1).min(leaf_radius + attachment.radius_offset);
566            } else {
567                current_radius += 1;
568            }
569        }
570    }
571
572    fn create_blob_tree_foliage(
573        region: &mut WorldGenRegion<'_>,
574        registry: &Registry,
575        random: &mut WorldgenRandom,
576        config: &TreeConfiguration,
577        _placer: &BlobFoliagePlacer,
578        attachment: FoliageAttachment,
579        foliage_height: i32,
580        leaf_radius: i32,
581        placement: &mut TreePlacement,
582    ) {
583        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
584        for y in (offset - foliage_height..=offset).rev() {
585            let current_radius = (leaf_radius + attachment.radius_offset - 1 - y / 2).max(0);
586            Self::place_tree_leaves_row(
587                region,
588                registry,
589                random,
590                config,
591                attachment.pos,
592                current_radius,
593                y,
594                attachment.double_trunk,
595                placement,
596            );
597        }
598    }
599
600    fn create_acacia_tree_foliage(
601        region: &mut WorldGenRegion<'_>,
602        registry: &Registry,
603        random: &mut WorldgenRandom,
604        config: &TreeConfiguration,
605        attachment: FoliageAttachment,
606        foliage_height: i32,
607        leaf_radius: i32,
608        placement: &mut TreePlacement,
609    ) {
610        let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
611        let foliage_pos = attachment.pos.above_n(offset);
612        Self::place_tree_leaves_row(
613            region,
614            registry,
615            random,
616            config,
617            foliage_pos,
618            leaf_radius + attachment.radius_offset,
619            -1 - foliage_height,
620            attachment.double_trunk,
621            placement,
622        );
623        Self::place_tree_leaves_row(
624            region,
625            registry,
626            random,
627            config,
628            foliage_pos,
629            leaf_radius - 1,
630            -foliage_height,
631            attachment.double_trunk,
632            placement,
633        );
634        Self::place_tree_leaves_row(
635            region,
636            registry,
637            random,
638            config,
639            foliage_pos,
640            leaf_radius + attachment.radius_offset - 1,
641            0,
642            attachment.double_trunk,
643            placement,
644        );
645    }
646
647    fn tree_foliage_offset(random: &mut WorldgenRandom, foliage_placer: &FoliagePlacer) -> i32 {
648        match foliage_placer {
649            FoliagePlacer::Blob(placer) => placer.offset.sample(random),
650            FoliagePlacer::Bush(placer) => placer.offset.sample(random),
651            FoliagePlacer::Fancy(placer) => placer.offset.sample(random),
652            FoliagePlacer::Pine(placer) => placer.offset.sample(random),
653            FoliagePlacer::Spruce(placer) => placer.offset.sample(random),
654            FoliagePlacer::MegaPine(placer) => placer.offset.sample(random),
655            FoliagePlacer::Acacia(placer) => placer.offset.sample(random),
656            FoliagePlacer::DarkOak(placer) => placer.offset.sample(random),
657            FoliagePlacer::Jungle(placer) => placer.offset.sample(random),
658            FoliagePlacer::RandomSpread(placer) => placer.offset.sample(random),
659            FoliagePlacer::Cherry(placer) => placer.offset.sample(random),
660        }
661    }
662
663    fn place_tree_leaves_row(
664        region: &mut WorldGenRegion<'_>,
665        registry: &Registry,
666        random: &mut WorldgenRandom,
667        config: &TreeConfiguration,
668        origin: BlockPos,
669        current_radius: i32,
670        y: i32,
671        double_trunk: bool,
672        placement: &mut TreePlacement,
673    ) {
674        let offset = i32::from(double_trunk);
675        for dx in -current_radius..=current_radius + offset {
676            for dz in -current_radius..=current_radius + offset {
677                if !Self::tree_foliage_should_skip_location(
678                    random,
679                    &config.foliage_placer,
680                    dx,
681                    y,
682                    dz,
683                    current_radius,
684                    double_trunk,
685                ) {
686                    let pos = origin.offset(dx, y, dz);
687                    let _ =
688                        Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
689                }
690            }
691        }
692    }
693
694    #[expect(
695        clippy::too_many_arguments,
696        reason = "mirrors vanilla foliage row helper"
697    )]
698    fn place_tree_leaves_row_with_hanging_leaves_below(
699        region: &mut WorldGenRegion<'_>,
700        registry: &Registry,
701        random: &mut WorldgenRandom,
702        config: &TreeConfiguration,
703        origin: BlockPos,
704        current_radius: i32,
705        y: i32,
706        double_trunk: bool,
707        hanging_leaves_chance: f32,
708        hanging_leaves_extension_chance: f32,
709        placement: &mut TreePlacement,
710    ) {
711        Self::place_tree_leaves_row(
712            region,
713            registry,
714            random,
715            config,
716            origin,
717            current_radius,
718            y,
719            double_trunk,
720            placement,
721        );
722
723        let offset = i32::from(double_trunk);
724        let log_pos = origin.below();
725        for along_edge in Self::VANILLA_HORIZONTAL_DIRECTIONS {
726            let to_edge = along_edge.rotate_y_clockwise();
727            let offset_to_edge = if Self::direction_is_positive(to_edge) {
728                current_radius + offset
729            } else {
730                current_radius
731            };
732            let mut pos = origin
733                .offset(0, y - 1, 0)
734                .relative_n(to_edge, offset_to_edge)
735                .relative_n(along_edge, -current_radius);
736            let mut offset_along_edge = -current_radius;
737
738            while offset_along_edge < current_radius + offset {
739                let leaves_above = placement.foliage.contains(pos.above());
740                if leaves_above
741                    && Self::try_place_hanging_leaf_extension(
742                        region,
743                        registry,
744                        random,
745                        config,
746                        hanging_leaves_chance,
747                        log_pos,
748                        pos,
749                        placement,
750                    )
751                {
752                    let _ = Self::try_place_hanging_leaf_extension(
753                        region,
754                        registry,
755                        random,
756                        config,
757                        hanging_leaves_extension_chance,
758                        log_pos,
759                        pos.below(),
760                        placement,
761                    );
762                }
763
764                offset_along_edge += 1;
765                pos = pos.relative(along_edge);
766            }
767        }
768    }
769
770    fn try_place_hanging_leaf_extension(
771        region: &mut WorldGenRegion<'_>,
772        registry: &Registry,
773        random: &mut WorldgenRandom,
774        config: &TreeConfiguration,
775        chance: f32,
776        log_pos: BlockPos,
777        pos: BlockPos,
778        placement: &mut TreePlacement,
779    ) -> bool {
780        if Self::manhattan_distance(pos, log_pos) >= 7 || random.next_f32() > chance {
781            return false;
782        }
783
784        Self::try_place_tree_leaf(region, registry, random, config, pos, placement)
785    }
786
787    const fn direction_is_positive(direction: Direction) -> bool {
788        matches!(
789            direction,
790            Direction::Up | Direction::South | Direction::East
791        )
792    }
793
794    fn tree_foliage_should_skip_location(
795        random: &mut WorldgenRandom,
796        foliage_placer: &FoliagePlacer,
797        dx: i32,
798        y: i32,
799        dz: i32,
800        current_radius: i32,
801        double_trunk: bool,
802    ) -> bool {
803        if matches!(foliage_placer, FoliagePlacer::DarkOak(_)) {
804            return Self::dark_oak_foliage_should_skip_location(
805                dx,
806                y,
807                dz,
808                current_radius,
809                double_trunk,
810            );
811        }
812
813        let (dx, dz) = Self::foliage_signed_distances(dx, dz, double_trunk);
814        match foliage_placer {
815            FoliagePlacer::Blob(_) => {
816                Self::blob_foliage_should_skip_location(random, dx, y, dz, current_radius)
817            }
818            FoliagePlacer::Bush(_) => {
819                Self::bush_foliage_should_skip_location(random, dx, dz, current_radius)
820            }
821            FoliagePlacer::Fancy(_) => {
822                Self::fancy_foliage_should_skip_location(dx, dz, current_radius)
823            }
824            FoliagePlacer::Pine(_) | FoliagePlacer::Spruce(_) => {
825                Self::conifer_foliage_should_skip_location(dx, dz, current_radius)
826            }
827            FoliagePlacer::MegaPine(_) => {
828                Self::mega_pine_foliage_should_skip_location(dx, dz, current_radius)
829            }
830            FoliagePlacer::Jungle(_) => {
831                Self::mega_pine_foliage_should_skip_location(dx, dz, current_radius)
832            }
833            FoliagePlacer::RandomSpread(_) => false,
834            FoliagePlacer::Acacia(_) => {
835                Self::acacia_foliage_should_skip_location(dx, y, dz, current_radius)
836            }
837            FoliagePlacer::Cherry(placer) => {
838                Self::cherry_foliage_should_skip_location(random, placer, dx, y, dz, current_radius)
839            }
840            FoliagePlacer::DarkOak(_) => unreachable!(),
841        }
842    }
843
844    fn blob_foliage_should_skip_location(
845        random: &mut WorldgenRandom,
846        dx: i32,
847        y: i32,
848        dz: i32,
849        current_radius: i32,
850    ) -> bool {
851        dx == current_radius && dz == current_radius && (random.next_i32_bounded(2) == 0 || y == 0)
852    }
853
854    fn bush_foliage_should_skip_location(
855        random: &mut WorldgenRandom,
856        dx: i32,
857        dz: i32,
858        current_radius: i32,
859    ) -> bool {
860        dx == current_radius && dz == current_radius && random.next_i32_bounded(2) == 0
861    }
862
863    fn fancy_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
864        let dx = dx as f32 + 0.5;
865        let dz = dz as f32 + 0.5;
866        dx * dx + dz * dz > (current_radius * current_radius) as f32
867    }
868
869    fn dark_oak_foliage_should_skip_location(
870        dx: i32,
871        y: i32,
872        dz: i32,
873        current_radius: i32,
874        double_trunk: bool,
875    ) -> bool {
876        if y == 0
877            && double_trunk
878            && (dx == -current_radius || dx >= current_radius)
879            && (dz == -current_radius || dz >= current_radius)
880        {
881            return true;
882        }
883
884        let (dx, dz) = Self::foliage_signed_distances(dx, dz, double_trunk);
885        if y == -1 && !double_trunk {
886            dx == current_radius && dz == current_radius
887        } else if y == 1 {
888            dx + dz > current_radius * 2 - 2
889        } else {
890            false
891        }
892    }
893
894    fn cherry_foliage_should_skip_location(
895        random: &mut WorldgenRandom,
896        placer: &CherryFoliagePlacer,
897        dx: i32,
898        y: i32,
899        dz: i32,
900        current_radius: i32,
901    ) -> bool {
902        if y == -1
903            && (dx == current_radius || dz == current_radius)
904            && random.next_f32() < placer.wide_bottom_layer_hole_chance
905        {
906            return true;
907        }
908
909        let corner = dx == current_radius && dz == current_radius;
910        let wide_layer = current_radius > 2;
911        if wide_layer {
912            corner
913                || dx + dz > current_radius * 2 - 2 && random.next_f32() < placer.corner_hole_chance
914        } else {
915            corner && random.next_f32() < placer.corner_hole_chance
916        }
917    }
918
919    const fn conifer_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
920        dx == current_radius && dz == current_radius && current_radius > 0
921    }
922
923    const fn mega_pine_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
924        dx + dz >= 7 || dx * dx + dz * dz > current_radius * current_radius
925    }
926
927    const fn acacia_foliage_should_skip_location(
928        dx: i32,
929        y: i32,
930        dz: i32,
931        current_radius: i32,
932    ) -> bool {
933        if y == 0 {
934            (dx > 1 || dz > 1) && dx != 0 && dz != 0
935        } else {
936            dx == current_radius && dz == current_radius && current_radius > 0
937        }
938    }
939
940    fn foliage_signed_distances(dx: i32, dz: i32, double_trunk: bool) -> (i32, i32) {
941        if double_trunk {
942            (
943                abs_i32(dx).min(abs_i32(dx - 1)),
944                abs_i32(dz).min(abs_i32(dz - 1)),
945            )
946        } else {
947            (abs_i32(dx), abs_i32(dz))
948        }
949    }
950
951    fn try_place_tree_leaf(
952        region: &mut WorldGenRegion<'_>,
953        registry: &Registry,
954        random: &mut WorldgenRandom,
955        config: &TreeConfiguration,
956        pos: BlockPos,
957        placement: &mut TreePlacement,
958    ) -> bool {
959        let current_state = region.block_state(pos);
960        let is_persistent = current_state
961            .try_get_value(&BlockStateProperties::PERSISTENT)
962            .unwrap_or(false);
963        let valid_tree_pos = current_state.is_air()
964            || current_state
965                .get_block()
966                .has_tag(&BlockTag::REPLACEABLE_BY_TREES);
967        if is_persistent || !valid_tree_pos {
968            return false;
969        }
970
971        let foliage_state = Self::sample_block_state_provider(
972            region,
973            registry,
974            random,
975            &config.foliage_provider,
976            pos,
977        );
978        let foliage_state = Self::copy_waterlogged_from(region, pos, foliage_state);
979        placement.set_foliage(region, pos, foliage_state);
980        true
981    }
982}
983
984#[cfg(test)]
985mod tests {
986    use super::*;
987
988    #[test]
989    fn acacia_top_layer_keeps_cross_and_inner_corners() {
990        assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(2, 0, 2, 2));
991        assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(1, 0, 2, 2));
992        assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(0, 0, 2, 2));
993        assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(1, 0, 1, 2));
994    }
995
996    #[test]
997    fn acacia_lower_layers_skip_only_outer_corners() {
998        assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(2, -1, 2, 2));
999        assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(1, -1, 2, 2));
1000        assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(0, -1, 0, 0));
1001    }
1002
1003    #[test]
1004    fn conifer_layers_skip_only_nonzero_outer_corners() {
1005        assert!(FeatureDecorationRunner::conifer_foliage_should_skip_location(2, 2, 2));
1006        assert!(!FeatureDecorationRunner::conifer_foliage_should_skip_location(1, 2, 2));
1007        assert!(!FeatureDecorationRunner::conifer_foliage_should_skip_location(0, 0, 0));
1008    }
1009
1010    #[test]
1011    fn mega_pine_uses_circular_row_cutoff_with_vanilla_seven_limit() {
1012        assert!(FeatureDecorationRunner::mega_pine_foliage_should_skip_location(4, 3, 5));
1013        assert!(FeatureDecorationRunner::mega_pine_foliage_should_skip_location(4, 4, 5));
1014        assert!(!FeatureDecorationRunner::mega_pine_foliage_should_skip_location(3, 3, 5));
1015    }
1016
1017    #[test]
1018    fn fancy_foliage_uses_shifted_circular_cutoff() {
1019        assert!(!FeatureDecorationRunner::fancy_foliage_should_skip_location(0, 0, 1));
1020        assert!(FeatureDecorationRunner::fancy_foliage_should_skip_location(
1021            1, 0, 1
1022        ));
1023        assert!(FeatureDecorationRunner::fancy_foliage_should_skip_location(
1024            0, 0, 0
1025        ));
1026    }
1027
1028    #[test]
1029    fn dark_oak_double_trunk_skips_outer_corner_extensions_on_center_layer() {
1030        assert!(
1031            FeatureDecorationRunner::dark_oak_foliage_should_skip_location(-3, 0, -3, 3, true,)
1032        );
1033        assert!(FeatureDecorationRunner::dark_oak_foliage_should_skip_location(4, 0, 4, 3, true,));
1034        assert!(
1035            !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(-2, 0, -3, 3, true,)
1036        );
1037    }
1038
1039    #[test]
1040    fn dark_oak_side_crowns_skip_lower_outer_corners() {
1041        assert!(
1042            FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, -1, 2, 2, false,)
1043        );
1044        assert!(
1045            !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, -1, 1, 2, false,)
1046        );
1047    }
1048
1049    #[test]
1050    fn dark_oak_upper_layer_uses_diagonal_cutoff() {
1051        assert!(FeatureDecorationRunner::dark_oak_foliage_should_skip_location(3, 1, 2, 3, false,));
1052        assert!(
1053            !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, 1, 2, 3, false,)
1054        );
1055    }
1056}