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}