Skip to main content

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

1#![expect(
2    clippy::too_many_arguments,
3    reason = "trunk placement helpers mirror vanilla placement state"
4)]
5
6use std::f32::consts::TAU;
7use std::f64::consts::PI;
8
9use steel_math::trig;
10
11use super::super::super::prelude::*;
12use super::super::super::runner::FeatureDecorationRunner;
13use super::{FoliageAttachment, TreePlacement, abs_i32};
14
15const FANCY_TRUNK_HEIGHT_SCALE: f64 = 0.618;
16const FANCY_CLUSTER_DENSITY_MAGIC: f64 = 1.382;
17const FANCY_BRANCH_SLOPE: f64 = 0.381;
18const FANCY_BRANCH_LENGTH_MAGIC: f64 = 0.328;
19
20impl FeatureDecorationRunner {
21    pub(super) fn tree_height(random: &mut WorldgenRandom, placer: &TrunkPlacer) -> i32 {
22        match placer {
23            TrunkPlacer::Straight(base)
24            | TrunkPlacer::Giant(base)
25            | TrunkPlacer::Fancy(base)
26            | TrunkPlacer::Forking(base)
27            | TrunkPlacer::DarkOak(base)
28            | TrunkPlacer::MegaJungle(base) => Self::sample_tree_height(
29                random,
30                base.base_height,
31                base.height_rand_a,
32                base.height_rand_b,
33            ),
34            TrunkPlacer::Poplar(base) => Self::sample_tree_height(
35                random,
36                base.base_height,
37                base.height_rand_a,
38                base.height_rand_b,
39            ),
40            TrunkPlacer::Bending(placer) => Self::sample_tree_height(
41                random,
42                placer.base_height,
43                placer.height_rand_a,
44                placer.height_rand_b,
45            ),
46            TrunkPlacer::UpwardsBranching(placer) => Self::sample_tree_height(
47                random,
48                placer.base_height,
49                placer.height_rand_a,
50                placer.height_rand_b,
51            ),
52            TrunkPlacer::Cherry(placer) => Self::sample_tree_height(
53                random,
54                placer.base_height,
55                placer.height_rand_a,
56                placer.height_rand_b,
57            ),
58        }
59    }
60
61    fn sample_tree_height(
62        random: &mut WorldgenRandom,
63        base_height: i32,
64        height_rand_a: i32,
65        height_rand_b: i32,
66    ) -> i32 {
67        base_height
68            + random.next_i32_bounded(height_rand_a + 1)
69            + random.next_i32_bounded(height_rand_b + 1)
70    }
71
72    pub(super) fn place_tree_trunk(
73        region: &mut impl LevelAccessor,
74        registry: &Registry,
75        random: &mut WorldgenRandom,
76        tree_height: i32,
77        origin: BlockPos,
78        config: &TreeConfiguration,
79        placement: &mut TreePlacement,
80    ) -> Vec<FoliageAttachment> {
81        match &config.trunk_placer {
82            TrunkPlacer::Straight(_) => Self::place_straight_tree_trunk(
83                region,
84                registry,
85                random,
86                tree_height,
87                origin,
88                config,
89                placement,
90            ),
91            TrunkPlacer::Forking(_) => Self::place_forking_tree_trunk(
92                region,
93                registry,
94                random,
95                tree_height,
96                origin,
97                config,
98                placement,
99            ),
100            TrunkPlacer::Giant(_) => Self::place_giant_tree_trunk(
101                region,
102                registry,
103                random,
104                tree_height,
105                origin,
106                config,
107                placement,
108            ),
109            TrunkPlacer::Fancy(_) => Self::place_fancy_tree_trunk(
110                region,
111                registry,
112                random,
113                tree_height,
114                origin,
115                config,
116                placement,
117            ),
118            TrunkPlacer::DarkOak(_) => Self::place_dark_oak_tree_trunk(
119                region,
120                registry,
121                random,
122                tree_height,
123                origin,
124                config,
125                placement,
126            ),
127            TrunkPlacer::MegaJungle(_) => Self::place_mega_jungle_tree_trunk(
128                region,
129                registry,
130                random,
131                tree_height,
132                origin,
133                config,
134                placement,
135            ),
136            TrunkPlacer::Bending(placer) => Self::place_bending_tree_trunk(
137                region,
138                registry,
139                random,
140                tree_height,
141                origin,
142                config,
143                placer,
144                placement,
145            ),
146            TrunkPlacer::UpwardsBranching(placer) => Self::place_upwards_branching_tree_trunk(
147                region,
148                registry,
149                random,
150                tree_height,
151                origin,
152                config,
153                placer,
154                placement,
155            ),
156            TrunkPlacer::Cherry(placer) => Self::place_cherry_tree_trunk(
157                region,
158                registry,
159                random,
160                tree_height,
161                origin,
162                config,
163                placer,
164                placement,
165            ),
166            TrunkPlacer::Poplar(placer) => Self::place_poplar_tree_trunk(
167                region,
168                registry,
169                random,
170                tree_height,
171                origin,
172                config,
173                placer,
174                placement,
175            ),
176        }
177    }
178
179    fn place_straight_tree_trunk(
180        region: &mut impl LevelAccessor,
181        registry: &Registry,
182        random: &mut WorldgenRandom,
183        tree_height: i32,
184        origin: BlockPos,
185        config: &TreeConfiguration,
186        placement: &mut TreePlacement,
187    ) -> Vec<FoliageAttachment> {
188        Self::place_below_trunk_block(region, registry, random, origin.below(), config, placement);
189
190        for y in 0..tree_height {
191            let pos = origin.above_n(y);
192            let _ = Self::place_tree_log(region, registry, random, pos, config, placement);
193        }
194
195        vec![FoliageAttachment {
196            pos: origin.above_n(tree_height),
197            radius_offset: 0,
198            double_trunk: false,
199        }]
200    }
201
202    fn place_poplar_tree_trunk(
203        region: &mut impl LevelAccessor,
204        registry: &Registry,
205        random: &mut WorldgenRandom,
206        tree_height: i32,
207        origin: BlockPos,
208        config: &TreeConfiguration,
209        placer: &PoplarTrunkPlacer,
210        placement: &mut TreePlacement,
211    ) -> Vec<FoliageAttachment> {
212        Self::place_below_trunk_block(region, registry, random, origin.below(), config, placement);
213        let trunk_height_up_to_foliage_branches =
214            tree_height - placer.trunk_height_above_branches.sample(random);
215
216        for y in 0..tree_height {
217            let log_pos = origin.above_n(y);
218            let _ = Self::place_tree_log(region, registry, random, log_pos, config, placement);
219            let directions = Self::shuffled_directions(random, Self::VANILLA_DIRECTION_VALUES);
220
221            if trunk_height_up_to_foliage_branches - 1 != y {
222                continue;
223            }
224
225            let branches = placer.branch_amount.sample(random);
226            for direction in directions
227                .into_iter()
228                .filter(|direction| direction.axis() != Axis::Y)
229                .take(branches as usize)
230            {
231                let _ = Self::place_tree_log_with_axis(
232                    region,
233                    registry,
234                    random,
235                    log_pos.relative(direction),
236                    direction.axis(),
237                    config,
238                    placement,
239                );
240            }
241        }
242
243        vec![FoliageAttachment {
244            pos: origin.above_n(trunk_height_up_to_foliage_branches),
245            radius_offset: 0,
246            double_trunk: false,
247        }]
248    }
249
250    fn place_forking_tree_trunk(
251        region: &mut impl LevelAccessor,
252        registry: &Registry,
253        random: &mut WorldgenRandom,
254        tree_height: i32,
255        origin: BlockPos,
256        config: &TreeConfiguration,
257        placement: &mut TreePlacement,
258    ) -> Vec<FoliageAttachment> {
259        Self::place_below_trunk_block(region, registry, random, origin.below(), config, placement);
260
261        let mut attachments = Vec::new();
262        let lean_direction = Self::random_horizontal_direction(random);
263        let lean_height = tree_height - random.next_i32_bounded(4) - 1;
264        let mut lean_steps = 3 - random.next_i32_bounded(3);
265        let mut trunk_x = origin.x();
266        let mut trunk_z = origin.z();
267        let mut foliage_y = None;
268
269        for y_offset in 0..tree_height {
270            let y = origin.y() + y_offset;
271            if y_offset >= lean_height && lean_steps > 0 {
272                let (dx, dz) = lean_direction.offset_xz();
273                trunk_x += dx;
274                trunk_z += dz;
275                lean_steps -= 1;
276            }
277
278            let pos = BlockPos::new(trunk_x, y, trunk_z);
279            if Self::place_tree_log(region, registry, random, pos, config, placement) {
280                foliage_y = Some(y + 1);
281            }
282        }
283
284        if let Some(y) = foliage_y {
285            attachments.push(FoliageAttachment {
286                pos: BlockPos::new(trunk_x, y, trunk_z),
287                radius_offset: 1,
288                double_trunk: false,
289            });
290        }
291
292        trunk_x = origin.x();
293        trunk_z = origin.z();
294        let branch_direction = Self::random_horizontal_direction(random);
295        if branch_direction != lean_direction {
296            let mut branch_y_offset = lean_height - random.next_i32_bounded(2) - 1;
297            let mut branch_steps = 1 + random.next_i32_bounded(3);
298            foliage_y = None;
299
300            while branch_y_offset < tree_height && branch_steps > 0 {
301                if branch_y_offset >= 1 {
302                    let y = origin.y() + branch_y_offset;
303                    let (dx, dz) = branch_direction.offset_xz();
304                    trunk_x += dx;
305                    trunk_z += dz;
306                    let pos = BlockPos::new(trunk_x, y, trunk_z);
307                    if Self::place_tree_log(region, registry, random, pos, config, placement) {
308                        foliage_y = Some(y + 1);
309                    }
310                }
311
312                branch_y_offset += 1;
313                branch_steps -= 1;
314            }
315
316            if let Some(y) = foliage_y {
317                attachments.push(FoliageAttachment {
318                    pos: BlockPos::new(trunk_x, y, trunk_z),
319                    radius_offset: 0,
320                    double_trunk: false,
321                });
322            }
323        }
324
325        attachments
326    }
327
328    fn place_giant_tree_trunk(
329        region: &mut impl LevelAccessor,
330        registry: &Registry,
331        random: &mut WorldgenRandom,
332        tree_height: i32,
333        origin: BlockPos,
334        config: &TreeConfiguration,
335        placement: &mut TreePlacement,
336    ) -> Vec<FoliageAttachment> {
337        let below = origin.below();
338        Self::place_below_trunk_block(region, registry, random, below, config, placement);
339        Self::place_below_trunk_block(
340            region,
341            registry,
342            random,
343            below.relative(Direction::East),
344            config,
345            placement,
346        );
347        Self::place_below_trunk_block(
348            region,
349            registry,
350            random,
351            below.relative(Direction::South),
352            config,
353            placement,
354        );
355        Self::place_below_trunk_block(
356            region,
357            registry,
358            random,
359            below.offset(1, 0, 1),
360            config,
361            placement,
362        );
363
364        for y in 0..tree_height {
365            let _ = Self::place_tree_log_if_free(
366                region,
367                registry,
368                random,
369                origin.above_n(y),
370                config,
371                placement,
372            );
373            if y < tree_height - 1 {
374                let _ = Self::place_tree_log_if_free(
375                    region,
376                    registry,
377                    random,
378                    origin.offset(1, y, 0),
379                    config,
380                    placement,
381                );
382                let _ = Self::place_tree_log_if_free(
383                    region,
384                    registry,
385                    random,
386                    origin.offset(1, y, 1),
387                    config,
388                    placement,
389                );
390                let _ = Self::place_tree_log_if_free(
391                    region,
392                    registry,
393                    random,
394                    origin.offset(0, y, 1),
395                    config,
396                    placement,
397                );
398            }
399        }
400
401        vec![FoliageAttachment {
402            pos: origin.above_n(tree_height),
403            radius_offset: 0,
404            double_trunk: true,
405        }]
406    }
407
408    fn place_mega_jungle_tree_trunk(
409        region: &mut impl LevelAccessor,
410        registry: &Registry,
411        random: &mut WorldgenRandom,
412        tree_height: i32,
413        origin: BlockPos,
414        config: &TreeConfiguration,
415        placement: &mut TreePlacement,
416    ) -> Vec<FoliageAttachment> {
417        let mut attachments = Self::place_giant_tree_trunk(
418            region,
419            registry,
420            random,
421            tree_height,
422            origin,
423            config,
424            placement,
425        );
426
427        let mut branch_height = tree_height - 2 - random.next_i32_bounded(4);
428        while branch_height > tree_height / 2 {
429            let angle = random.next_f32() * TAU;
430            let mut branch_x = 0;
431            let mut branch_z = 0;
432
433            for branch_step in 0..5 {
434                branch_x = (1.5_f32 + trig::cos(angle) * branch_step as f32) as i32;
435                branch_z = (1.5_f32 + trig::sin(angle) * branch_step as f32) as i32;
436                let pos = origin.offset(branch_x, branch_height - 3 + branch_step / 2, branch_z);
437                let _ = Self::place_tree_log(region, registry, random, pos, config, placement);
438            }
439
440            attachments.push(FoliageAttachment {
441                pos: origin.offset(branch_x, branch_height, branch_z),
442                radius_offset: -2,
443                double_trunk: false,
444            });
445            branch_height -= 2 + random.next_i32_bounded(4);
446        }
447
448        attachments
449    }
450
451    fn place_bending_tree_trunk(
452        region: &mut impl LevelAccessor,
453        registry: &Registry,
454        random: &mut WorldgenRandom,
455        tree_height: i32,
456        origin: BlockPos,
457        config: &TreeConfiguration,
458        placer: &BendingTrunkPlacer,
459        placement: &mut TreePlacement,
460    ) -> Vec<FoliageAttachment> {
461        let direction = Self::random_horizontal_direction(random);
462        let log_height = tree_height - 1;
463        let mut pos = origin;
464        Self::place_below_trunk_block(region, registry, random, origin.below(), config, placement);
465        let mut foliage_points = Vec::new();
466
467        for y in 0..=log_height {
468            if y + 1 >= log_height + random.next_i32_bounded(2) {
469                pos = pos.relative(direction);
470            }
471
472            let _ = Self::place_tree_log(region, registry, random, pos, config, placement);
473
474            if y >= placer.min_height_for_leaves {
475                foliage_points.push(FoliageAttachment {
476                    pos,
477                    radius_offset: 0,
478                    double_trunk: false,
479                });
480            }
481
482            pos = pos.relative(Direction::Up);
483        }
484
485        let bend_length = placer.bend_length.sample(random);
486        for _ in 0..=bend_length {
487            let _ = Self::place_tree_log(region, registry, random, pos, config, placement);
488            foliage_points.push(FoliageAttachment {
489                pos,
490                radius_offset: 0,
491                double_trunk: false,
492            });
493            pos = pos.relative(direction);
494        }
495
496        foliage_points
497    }
498
499    fn place_upwards_branching_tree_trunk(
500        region: &mut impl LevelAccessor,
501        registry: &Registry,
502        random: &mut WorldgenRandom,
503        tree_height: i32,
504        origin: BlockPos,
505        config: &TreeConfiguration,
506        placer: &UpwardsBranchingTrunkPlacer,
507        placement: &mut TreePlacement,
508    ) -> Vec<FoliageAttachment> {
509        let mut attachments = Vec::new();
510
511        for height_pos in 0..tree_height {
512            let current_height = origin.y() + height_pos;
513            let log_pos = BlockPos::new(origin.x(), current_height, origin.z());
514            if Self::place_tree_log_growing_through(
515                region,
516                registry,
517                random,
518                log_pos,
519                &placer.can_grow_through,
520                config,
521                placement,
522            ) && height_pos < tree_height - 1
523                && random.next_f32() < placer.place_branch_per_log_probability
524            {
525                let branch_dir = Self::random_horizontal_direction(random);
526                let branch_len = placer.extra_branch_length.sample(random);
527                let branch_pos = 0.max(branch_len - placer.extra_branch_length.sample(random) - 1);
528                let branch_steps = placer.extra_branch_steps.sample(random);
529                Self::place_upwards_branching_tree_branch(
530                    region,
531                    registry,
532                    random,
533                    tree_height,
534                    config,
535                    placer,
536                    &mut attachments,
537                    log_pos,
538                    current_height,
539                    branch_dir,
540                    branch_pos,
541                    branch_steps,
542                    placement,
543                );
544            }
545
546            if height_pos == tree_height - 1 {
547                attachments.push(FoliageAttachment {
548                    pos: BlockPos::new(origin.x(), current_height + 1, origin.z()),
549                    radius_offset: 0,
550                    double_trunk: false,
551                });
552            }
553        }
554
555        attachments
556    }
557
558    #[expect(clippy::too_many_arguments, reason = "mirrors vanilla branch state")]
559    fn place_upwards_branching_tree_branch(
560        region: &mut impl LevelAccessor,
561        registry: &Registry,
562        random: &mut WorldgenRandom,
563        tree_height: i32,
564        config: &TreeConfiguration,
565        placer: &UpwardsBranchingTrunkPlacer,
566        attachments: &mut Vec<FoliageAttachment>,
567        log_pos: BlockPos,
568        current_height: i32,
569        branch_dir: Direction,
570        branch_pos: i32,
571        mut branch_steps: i32,
572        placement: &mut TreePlacement,
573    ) {
574        let mut height_along_branch = current_height + branch_pos;
575        let mut log_x = log_pos.x();
576        let mut log_z = log_pos.z();
577        let mut branch_placement_index = branch_pos;
578
579        while branch_placement_index < tree_height && branch_steps > 0 {
580            if branch_placement_index >= 1 {
581                let placement_height = current_height + branch_placement_index;
582                let (dx, dz) = branch_dir.offset_xz();
583                log_x += dx;
584                log_z += dz;
585                height_along_branch = placement_height;
586                let branch_log_pos = BlockPos::new(log_x, placement_height, log_z);
587                if Self::place_tree_log_growing_through(
588                    region,
589                    registry,
590                    random,
591                    branch_log_pos,
592                    &placer.can_grow_through,
593                    config,
594                    placement,
595                ) {
596                    height_along_branch = placement_height + 1;
597                }
598
599                attachments.push(FoliageAttachment {
600                    pos: branch_log_pos,
601                    radius_offset: 0,
602                    double_trunk: false,
603                });
604            }
605
606            branch_placement_index += 1;
607            branch_steps -= 1;
608        }
609
610        if height_along_branch - current_height > 1 {
611            let foliage_pos = BlockPos::new(log_x, height_along_branch, log_z);
612            attachments.push(FoliageAttachment {
613                pos: foliage_pos,
614                radius_offset: 0,
615                double_trunk: false,
616            });
617            attachments.push(FoliageAttachment {
618                pos: foliage_pos.below_n(2),
619                radius_offset: 0,
620                double_trunk: false,
621            });
622        }
623    }
624
625    fn place_cherry_tree_trunk(
626        region: &mut impl LevelAccessor,
627        registry: &Registry,
628        random: &mut WorldgenRandom,
629        tree_height: i32,
630        origin: BlockPos,
631        config: &TreeConfiguration,
632        placer: &CherryTrunkPlacer,
633        placement: &mut TreePlacement,
634    ) -> Vec<FoliageAttachment> {
635        Self::place_below_trunk_block(region, registry, random, origin.below(), config, placement);
636        let first_branch_offset =
637            0.max(tree_height - 1 + placer.branch_start_offset_from_top.sample(random));
638        let second_branch_provider = placer
639            .branch_start_offset_from_top
640            .with_max_inclusive(placer.branch_start_offset_from_top.max_inclusive - 1);
641        let mut second_branch_offset =
642            0.max(tree_height - 1 + second_branch_provider.sample(random));
643        if second_branch_offset >= first_branch_offset {
644            second_branch_offset += 1;
645        }
646
647        let branch_count = placer.branch_count.sample(random);
648        let has_middle_branch = branch_count == 3;
649        let has_both_side_branches = branch_count >= 2;
650        let trunk_height = if has_middle_branch {
651            tree_height
652        } else if has_both_side_branches {
653            first_branch_offset.max(second_branch_offset) + 1
654        } else {
655            first_branch_offset + 1
656        };
657
658        for y in 0..trunk_height {
659            let _ = Self::place_tree_log(
660                region,
661                registry,
662                random,
663                origin.above_n(y),
664                config,
665                placement,
666            );
667        }
668
669        let mut attachments = Vec::new();
670        if has_middle_branch {
671            attachments.push(FoliageAttachment {
672                pos: origin.above_n(trunk_height),
673                radius_offset: 0,
674                double_trunk: false,
675            });
676        }
677
678        let tree_direction = Self::random_horizontal_direction(random);
679        let sideways_axis = tree_direction.get_axis();
680        attachments.push(Self::generate_cherry_tree_branch(
681            region,
682            registry,
683            random,
684            tree_height,
685            origin,
686            config,
687            placer,
688            tree_direction,
689            first_branch_offset,
690            first_branch_offset < trunk_height - 1,
691            sideways_axis,
692            placement,
693        ));
694        if has_both_side_branches {
695            attachments.push(Self::generate_cherry_tree_branch(
696                region,
697                registry,
698                random,
699                tree_height,
700                origin,
701                config,
702                placer,
703                tree_direction.opposite(),
704                second_branch_offset,
705                second_branch_offset < trunk_height - 1,
706                sideways_axis,
707                placement,
708            ));
709        }
710
711        attachments
712    }
713
714    #[expect(clippy::too_many_arguments, reason = "mirrors vanilla branch state")]
715    fn generate_cherry_tree_branch(
716        region: &mut impl LevelAccessor,
717        registry: &Registry,
718        random: &mut WorldgenRandom,
719        tree_height: i32,
720        origin: BlockPos,
721        config: &TreeConfiguration,
722        placer: &CherryTrunkPlacer,
723        branch_direction: Direction,
724        offset_from_origin: i32,
725        middle_continues_upwards: bool,
726        sideways_axis: Axis,
727        placement: &mut TreePlacement,
728    ) -> FoliageAttachment {
729        let mut log_pos = origin.above_n(offset_from_origin);
730        let branch_end_y = tree_height - 1 + placer.branch_end_offset_from_top.sample(random);
731        let extend_branch_away_from_trunk =
732            middle_continues_upwards || branch_end_y < offset_from_origin;
733        let distance_to_trunk = placer.branch_horizontal_length.sample(random)
734            + i32::from(extend_branch_away_from_trunk);
735        let branch_end_pos = origin
736            .relative_n(branch_direction, distance_to_trunk)
737            .above_n(branch_end_y);
738        let steps_horizontally = if extend_branch_away_from_trunk { 2 } else { 1 };
739
740        for _ in 0..steps_horizontally {
741            log_pos = log_pos.relative(branch_direction);
742            let _ = Self::place_tree_log_with_axis(
743                region,
744                registry,
745                random,
746                log_pos,
747                sideways_axis,
748                config,
749                placement,
750            );
751        }
752
753        let vertical_direction = if branch_end_pos.y() > log_pos.y() {
754            Direction::Up
755        } else {
756            Direction::Down
757        };
758
759        loop {
760            let distance = Self::manhattan_distance(log_pos, branch_end_pos);
761            if distance == 0 {
762                return FoliageAttachment {
763                    pos: branch_end_pos.above(),
764                    radius_offset: 0,
765                    double_trunk: false,
766                };
767            }
768
769            let vertical_distance = (branch_end_pos.y() - log_pos.y()).abs();
770            let grow_vertically = random.next_f32() < vertical_distance as f32 / distance as f32;
771            log_pos = if grow_vertically {
772                log_pos.relative(vertical_direction)
773            } else {
774                log_pos.relative(branch_direction)
775            };
776
777            if grow_vertically {
778                let _ = Self::place_tree_log(region, registry, random, log_pos, config, placement);
779            } else {
780                let _ = Self::place_tree_log_with_axis(
781                    region,
782                    registry,
783                    random,
784                    log_pos,
785                    sideways_axis,
786                    config,
787                    placement,
788                );
789            }
790        }
791    }
792
793    fn place_dark_oak_tree_trunk(
794        region: &mut impl LevelAccessor,
795        registry: &Registry,
796        random: &mut WorldgenRandom,
797        tree_height: i32,
798        origin: BlockPos,
799        config: &TreeConfiguration,
800        placement: &mut TreePlacement,
801    ) -> Vec<FoliageAttachment> {
802        let mut attachments = Vec::new();
803        let below = origin.below();
804        Self::place_below_trunk_block(region, registry, random, below, config, placement);
805        Self::place_below_trunk_block(
806            region,
807            registry,
808            random,
809            below.relative(Direction::East),
810            config,
811            placement,
812        );
813        Self::place_below_trunk_block(
814            region,
815            registry,
816            random,
817            below.relative(Direction::South),
818            config,
819            placement,
820        );
821        Self::place_below_trunk_block(
822            region,
823            registry,
824            random,
825            below.offset(1, 0, 1),
826            config,
827            placement,
828        );
829
830        let lean_direction = Self::random_horizontal_direction(random);
831        let lean_height = tree_height - random.next_i32_bounded(4);
832        let mut lean_steps = 2 - random.next_i32_bounded(3);
833        let x = origin.x();
834        let y = origin.y();
835        let z = origin.z();
836        let mut trunk_x = x;
837        let mut trunk_z = z;
838        let foliage_y = y + tree_height - 1;
839
840        for y_offset in 0..tree_height {
841            if y_offset >= lean_height && lean_steps > 0 {
842                let (dx, dz) = lean_direction.offset_xz();
843                trunk_x += dx;
844                trunk_z += dz;
845                lean_steps -= 1;
846            }
847
848            let pos = BlockPos::new(trunk_x, y + y_offset, trunk_z);
849            if Self::tree_is_air_or_leaves(region, pos) {
850                let _ = Self::place_tree_log(region, registry, random, pos, config, placement);
851                let _ = Self::place_tree_log(
852                    region,
853                    registry,
854                    random,
855                    pos.relative(Direction::East),
856                    config,
857                    placement,
858                );
859                let _ = Self::place_tree_log(
860                    region,
861                    registry,
862                    random,
863                    pos.relative(Direction::South),
864                    config,
865                    placement,
866                );
867                let _ = Self::place_tree_log(
868                    region,
869                    registry,
870                    random,
871                    pos.offset(1, 0, 1),
872                    config,
873                    placement,
874                );
875            }
876        }
877
878        attachments.push(FoliageAttachment {
879            pos: BlockPos::new(trunk_x, foliage_y, trunk_z),
880            radius_offset: 0,
881            double_trunk: true,
882        });
883
884        for ox in -1..=2 {
885            for oz in -1..=2 {
886                if (0..=1).contains(&ox) && (0..=1).contains(&oz) {
887                    continue;
888                }
889                if random.next_i32_bounded(3) > 0 {
890                    continue;
891                }
892
893                let branch_length = random.next_i32_bounded(3) + 2;
894                for branch_y in 0..branch_length {
895                    let pos = BlockPos::new(x + ox, foliage_y - branch_y - 1, z + oz);
896                    let _ = Self::place_tree_log(region, registry, random, pos, config, placement);
897                }
898
899                attachments.push(FoliageAttachment {
900                    pos: BlockPos::new(x + ox, foliage_y, z + oz),
901                    radius_offset: 0,
902                    double_trunk: false,
903                });
904            }
905        }
906
907        attachments
908    }
909
910    fn place_fancy_tree_trunk(
911        region: &mut impl LevelAccessor,
912        registry: &Registry,
913        random: &mut WorldgenRandom,
914        tree_height: i32,
915        origin: BlockPos,
916        config: &TreeConfiguration,
917        placement: &mut TreePlacement,
918    ) -> Vec<FoliageAttachment> {
919        let height = tree_height + 2;
920        let trunk_height = fast_floor(f64::from(height) * FANCY_TRUNK_HEIGHT_SCALE);
921        Self::place_below_trunk_block(region, registry, random, origin.below(), config, placement);
922
923        let clusters_per_y = 1.min(fast_floor(
924            FANCY_CLUSTER_DENSITY_MAGIC + (f64::from(height) / 13.0).powi(2),
925        ));
926        let trunk_top = origin.y() + trunk_height;
927        let mut relative_y = height - 5;
928        let mut foliage_coords = vec![FancyFoliageCoords {
929            attachment: FoliageAttachment {
930                pos: origin.above_n(relative_y),
931                radius_offset: 0,
932                double_trunk: false,
933            },
934            branch_base: trunk_top,
935        }];
936
937        while relative_y >= 0 {
938            let tree_shape = Self::fancy_tree_shape(height, relative_y);
939            if tree_shape >= 0.0 {
940                for _ in 0..clusters_per_y {
941                    let radius = f64::from(tree_shape)
942                        * (f64::from(random.next_f32()) + FANCY_BRANCH_LENGTH_MAGIC);
943                    let angle = f64::from(random.next_f32() * 2.0_f32) * PI;
944                    let x = radius * angle.sin() + 0.5;
945                    let z = radius * angle.cos() + 0.5;
946                    let check_start = origin.offset(fast_floor(x), relative_y - 1, fast_floor(z));
947                    let check_end = check_start.above_n(5);
948                    if Self::make_fancy_tree_limb(
949                        region,
950                        registry,
951                        random,
952                        check_start,
953                        check_end,
954                        false,
955                        config,
956                        placement,
957                    ) {
958                        let dx = origin.x() - check_start.x();
959                        let dz = origin.z() - check_start.z();
960                        let branch_height = f64::from(check_start.y())
961                            - f64::from(dx * dx + dz * dz).sqrt() * FANCY_BRANCH_SLOPE;
962                        let branch_top = if branch_height > f64::from(trunk_top) {
963                            trunk_top
964                        } else {
965                            branch_height as i32
966                        };
967                        let check_branch_base = BlockPos::new(origin.x(), branch_top, origin.z());
968                        if Self::make_fancy_tree_limb(
969                            region,
970                            registry,
971                            random,
972                            check_branch_base,
973                            check_start,
974                            false,
975                            config,
976                            placement,
977                        ) {
978                            foliage_coords.push(FancyFoliageCoords {
979                                attachment: FoliageAttachment {
980                                    pos: check_start,
981                                    radius_offset: 0,
982                                    double_trunk: false,
983                                },
984                                branch_base: check_branch_base.y(),
985                            });
986                        }
987                    }
988                }
989            }
990            relative_y -= 1;
991        }
992
993        Self::make_fancy_tree_limb(
994            region,
995            registry,
996            random,
997            origin,
998            origin.above_n(trunk_height),
999            true,
1000            config,
1001            placement,
1002        );
1003        Self::make_fancy_tree_branches(
1004            region,
1005            registry,
1006            random,
1007            height,
1008            origin,
1009            &foliage_coords,
1010            config,
1011            placement,
1012        );
1013
1014        foliage_coords
1015            .into_iter()
1016            .filter(|coord| Self::trim_fancy_tree_branch(height, coord.branch_base - origin.y()))
1017            .map(|coord| coord.attachment)
1018            .collect()
1019    }
1020
1021    fn make_fancy_tree_limb(
1022        region: &mut impl LevelAccessor,
1023        registry: &Registry,
1024        random: &mut WorldgenRandom,
1025        start_pos: BlockPos,
1026        end_pos: BlockPos,
1027        do_place: bool,
1028        config: &TreeConfiguration,
1029        placement: &mut TreePlacement,
1030    ) -> bool {
1031        if !do_place && start_pos == end_pos {
1032            return true;
1033        }
1034
1035        let delta = BlockPos::new(
1036            end_pos.x() - start_pos.x(),
1037            end_pos.y() - start_pos.y(),
1038            end_pos.z() - start_pos.z(),
1039        );
1040        let steps = Self::fancy_tree_limb_steps(delta);
1041        if steps == 0 {
1042            if do_place {
1043                let _ = Self::place_fancy_tree_log(
1044                    region,
1045                    registry,
1046                    random,
1047                    start_pos,
1048                    Axis::Y,
1049                    config,
1050                    placement,
1051                );
1052            }
1053            return true;
1054        }
1055
1056        let dx = delta.x() as f32 / steps as f32;
1057        let dy = delta.y() as f32 / steps as f32;
1058        let dz = delta.z() as f32 / steps as f32;
1059
1060        for step in 0..=steps {
1061            let step = step as f32;
1062            let pos = start_pos.offset(
1063                fast_floor(f64::from(0.5_f32 + step * dx)),
1064                fast_floor(f64::from(0.5_f32 + step * dy)),
1065                fast_floor(f64::from(0.5_f32 + step * dz)),
1066            );
1067            if do_place {
1068                let axis = Self::fancy_tree_log_axis(start_pos, pos);
1069                let _ = Self::place_fancy_tree_log(
1070                    region, registry, random, pos, axis, config, placement,
1071                );
1072            } else if !Self::tree_trunk_placer_is_free(region, pos, &config.trunk_placer) {
1073                return false;
1074            }
1075        }
1076
1077        true
1078    }
1079
1080    fn fancy_tree_limb_steps(pos: BlockPos) -> i32 {
1081        let abs_x = abs_i32(pos.x());
1082        let abs_y = abs_i32(pos.y());
1083        let abs_z = abs_i32(pos.z());
1084        abs_x.max(abs_y).max(abs_z)
1085    }
1086
1087    fn fancy_tree_log_axis(start_pos: BlockPos, block_pos: BlockPos) -> Axis {
1088        let xdiff = abs_i32(block_pos.x() - start_pos.x());
1089        let zdiff = abs_i32(block_pos.z() - start_pos.z());
1090        let maxdiff = xdiff.max(zdiff);
1091        if maxdiff == 0 {
1092            Axis::Y
1093        } else if xdiff == maxdiff {
1094            Axis::X
1095        } else {
1096            Axis::Z
1097        }
1098    }
1099
1100    fn trim_fancy_tree_branch(height: i32, local_y: i32) -> bool {
1101        f64::from(local_y) >= f64::from(height) * 0.2
1102    }
1103
1104    fn make_fancy_tree_branches(
1105        region: &mut impl LevelAccessor,
1106        registry: &Registry,
1107        random: &mut WorldgenRandom,
1108        height: i32,
1109        origin: BlockPos,
1110        foliage_coords: &[FancyFoliageCoords],
1111        config: &TreeConfiguration,
1112        placement: &mut TreePlacement,
1113    ) {
1114        for end_coord in foliage_coords {
1115            let base_coord = BlockPos::new(origin.x(), end_coord.branch_base, origin.z());
1116            if base_coord != end_coord.attachment.pos
1117                && Self::trim_fancy_tree_branch(height, end_coord.branch_base - origin.y())
1118            {
1119                Self::make_fancy_tree_limb(
1120                    region,
1121                    registry,
1122                    random,
1123                    base_coord,
1124                    end_coord.attachment.pos,
1125                    true,
1126                    config,
1127                    placement,
1128                );
1129            }
1130        }
1131    }
1132
1133    fn place_tree_log_with_axis(
1134        region: &mut impl LevelAccessor,
1135        registry: &Registry,
1136        random: &mut WorldgenRandom,
1137        pos: BlockPos,
1138        axis: Axis,
1139        config: &TreeConfiguration,
1140        placement: &mut TreePlacement,
1141    ) -> bool {
1142        if !Self::tree_valid_pos(region, pos) {
1143            return false;
1144        }
1145
1146        let state = BlockStateProviderEvaluator::sample_block_state_provider(
1147            region,
1148            registry,
1149            random,
1150            &config.trunk_provider,
1151            pos,
1152        );
1153        let state = Self::with_axis_if_present(state, axis);
1154        placement.set_trunk(region, pos, state);
1155        true
1156    }
1157
1158    fn place_fancy_tree_log(
1159        region: &mut impl LevelAccessor,
1160        registry: &Registry,
1161        random: &mut WorldgenRandom,
1162        pos: BlockPos,
1163        axis: Axis,
1164        config: &TreeConfiguration,
1165        placement: &mut TreePlacement,
1166    ) -> bool {
1167        Self::place_tree_log_with_axis(region, registry, random, pos, axis, config, placement)
1168    }
1169
1170    pub(super) fn with_axis_if_present(state: BlockStateId, axis: Axis) -> BlockStateId {
1171        if state.try_get_value(&BlockStateProperties::AXIS).is_some() {
1172            state.set_value(&BlockStateProperties::AXIS, axis)
1173        } else {
1174            state
1175        }
1176    }
1177
1178    fn fancy_tree_shape(height: i32, y: i32) -> f32 {
1179        if (y as f32) < height as f32 * 0.3 {
1180            return -1.0;
1181        }
1182
1183        let radius = height as f32 / 2.0;
1184        let adjacent = radius - y as f32;
1185        let mut distance = (radius * radius - adjacent * adjacent).sqrt();
1186        if adjacent == 0.0 {
1187            distance = radius;
1188        } else if adjacent.abs() >= radius {
1189            return 0.0;
1190        }
1191
1192        distance * 0.5
1193    }
1194
1195    fn place_below_trunk_block(
1196        region: &mut impl LevelAccessor,
1197        registry: &Registry,
1198        random: &mut WorldgenRandom,
1199        pos: BlockPos,
1200        config: &TreeConfiguration,
1201        placement: &mut TreePlacement,
1202    ) {
1203        let Some(state) = BlockStateProviderEvaluator::sample_block_state_provider_optional(
1204            region,
1205            registry,
1206            random,
1207            &config.below_trunk_provider,
1208            pos,
1209        ) else {
1210            return;
1211        };
1212        placement.set_trunk(region, pos, state);
1213    }
1214
1215    fn place_tree_log(
1216        region: &mut impl LevelAccessor,
1217        registry: &Registry,
1218        random: &mut WorldgenRandom,
1219        pos: BlockPos,
1220        config: &TreeConfiguration,
1221        placement: &mut TreePlacement,
1222    ) -> bool {
1223        if !Self::tree_valid_pos(region, pos) {
1224            return false;
1225        }
1226
1227        let state = BlockStateProviderEvaluator::sample_block_state_provider(
1228            region,
1229            registry,
1230            random,
1231            &config.trunk_provider,
1232            pos,
1233        );
1234        placement.set_trunk(region, pos, state);
1235        true
1236    }
1237
1238    fn place_tree_log_if_free(
1239        region: &mut impl LevelAccessor,
1240        registry: &Registry,
1241        random: &mut WorldgenRandom,
1242        pos: BlockPos,
1243        config: &TreeConfiguration,
1244        placement: &mut TreePlacement,
1245    ) -> bool {
1246        if !Self::tree_trunk_placer_is_free(region, pos, &config.trunk_placer) {
1247            return false;
1248        }
1249
1250        Self::place_tree_log(region, registry, random, pos, config, placement)
1251    }
1252
1253    fn place_tree_log_growing_through(
1254        region: &mut impl LevelAccessor,
1255        registry: &Registry,
1256        random: &mut WorldgenRandom,
1257        pos: BlockPos,
1258        can_grow_through: &Identifier,
1259        config: &TreeConfiguration,
1260        placement: &mut TreePlacement,
1261    ) -> bool {
1262        if !Self::tree_valid_pos_or_tag(region, pos, can_grow_through) {
1263            return false;
1264        }
1265
1266        let state = BlockStateProviderEvaluator::sample_block_state_provider(
1267            region,
1268            registry,
1269            random,
1270            &config.trunk_provider,
1271            pos,
1272        );
1273        placement.set_trunk(region, pos, state);
1274        true
1275    }
1276}
1277
1278#[derive(Clone, Copy)]
1279struct FancyFoliageCoords {
1280    attachment: FoliageAttachment,
1281    branch_base: i32,
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286    use super::*;
1287
1288    #[test]
1289    fn fancy_tree_shape_rejects_lower_third() {
1290        assert_eq!(FeatureDecorationRunner::fancy_tree_shape(12, 3), -1.0);
1291    }
1292
1293    #[test]
1294    fn fancy_tree_shape_uses_half_circle_crown() {
1295        assert_eq!(FeatureDecorationRunner::fancy_tree_shape(12, 6), 3.0);
1296        assert_eq!(FeatureDecorationRunner::fancy_tree_shape(12, 12), 0.0);
1297    }
1298
1299    #[test]
1300    fn fancy_log_axis_prefers_x_on_horizontal_tie() {
1301        let start = BlockPos::new(0, 0, 0);
1302        assert_eq!(
1303            FeatureDecorationRunner::fancy_tree_log_axis(start, BlockPos::new(0, 3, 0)),
1304            Axis::Y
1305        );
1306        assert_eq!(
1307            FeatureDecorationRunner::fancy_tree_log_axis(start, BlockPos::new(2, 3, 1)),
1308            Axis::X
1309        );
1310        assert_eq!(
1311            FeatureDecorationRunner::fancy_tree_log_axis(start, BlockPos::new(1, 3, 2)),
1312            Axis::Z
1313        );
1314        assert_eq!(
1315            FeatureDecorationRunner::fancy_tree_log_axis(start, BlockPos::new(2, 3, 2)),
1316            Axis::X
1317        );
1318    }
1319}