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