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}