1#![expect(
2 clippy::match_same_arms,
3 clippy::too_many_arguments,
4 clippy::too_many_lines,
5 reason = "foliage dispatch keeps vanilla variant behavior explicit"
6)]
7
8use steel_registry::vanilla_block_tags::BlockTag;
9
10use super::super::super::prelude::*;
11use super::super::super::runner::FeatureDecorationRunner;
12use super::{FoliageAttachment, TreePlacement, abs_i32};
13
14impl FeatureDecorationRunner {
15 pub(super) fn tree_foliage_height(
16 random: &mut WorldgenRandom,
17 tree_height: i32,
18 config: &TreeConfiguration,
19 ) -> i32 {
20 match &config.foliage_placer {
21 FoliagePlacer::Blob(placer) => placer.height.sample(random),
22 FoliagePlacer::Bush(placer) => placer.height.sample(random),
23 FoliagePlacer::Fancy(placer) => placer.height.sample(random),
24 FoliagePlacer::Pine(placer) => placer.height.sample(random),
25 FoliagePlacer::Spruce(placer) => {
26 (tree_height - placer.trunk_height.sample(random)).max(4)
27 }
28 FoliagePlacer::MegaPine(placer) => placer.crown_height.sample(random),
29 FoliagePlacer::Acacia(_) => 0,
30 FoliagePlacer::DarkOak(_) => 4,
31 FoliagePlacer::Jungle(placer) => placer.height.sample(random),
32 FoliagePlacer::RandomSpread(placer) => placer.foliage_height,
33 FoliagePlacer::Cherry(placer) => placer.height.sample(random),
34 FoliagePlacer::Poplar(placer) => placer.height.sample(random),
35 }
36 }
37
38 pub(super) fn tree_foliage_radius(
39 random: &mut WorldgenRandom,
40 foliage_placer: &FoliagePlacer,
41 trunk_height: i32,
42 ) -> i32 {
43 match foliage_placer {
44 FoliagePlacer::Blob(placer) => placer.radius.sample(random),
45 FoliagePlacer::Bush(placer) => placer.radius.sample(random),
46 FoliagePlacer::Fancy(placer) => placer.radius.sample(random),
47 FoliagePlacer::Pine(placer) => {
48 placer.radius.sample(random) + random.next_i32_bounded((trunk_height + 1).max(1))
49 }
50 FoliagePlacer::Spruce(placer) => placer.radius.sample(random),
51 FoliagePlacer::MegaPine(placer) => placer.radius.sample(random),
52 FoliagePlacer::Acacia(placer) => placer.radius.sample(random),
53 FoliagePlacer::DarkOak(placer) => placer.radius.sample(random),
54 FoliagePlacer::Jungle(placer) => placer.radius.sample(random),
55 FoliagePlacer::RandomSpread(placer) => placer.radius.sample(random),
56 FoliagePlacer::Cherry(placer) => placer.radius.sample(random),
57 FoliagePlacer::Poplar(placer) => placer.radius.sample(random),
58 }
59 }
60
61 pub(super) fn create_tree_foliage(
62 region: &mut impl LevelAccessor,
63 registry: &Registry,
64 random: &mut WorldgenRandom,
65 config: &TreeConfiguration,
66 _tree_height: i32,
67 attachment: FoliageAttachment,
68 foliage_height: i32,
69 leaf_radius: i32,
70 placement: &mut TreePlacement,
71 ) {
72 match &config.foliage_placer {
73 FoliagePlacer::Blob(placer) => Self::create_blob_tree_foliage(
74 region,
75 registry,
76 random,
77 config,
78 placer,
79 attachment,
80 foliage_height,
81 leaf_radius,
82 placement,
83 ),
84 FoliagePlacer::Bush(_) => Self::create_bush_tree_foliage(
85 region,
86 registry,
87 random,
88 config,
89 attachment,
90 foliage_height,
91 leaf_radius,
92 placement,
93 ),
94 FoliagePlacer::Fancy(_) => Self::create_fancy_tree_foliage(
95 region,
96 registry,
97 random,
98 config,
99 attachment,
100 foliage_height,
101 leaf_radius,
102 placement,
103 ),
104 FoliagePlacer::Pine(_) => Self::create_pine_tree_foliage(
105 region,
106 registry,
107 random,
108 config,
109 attachment,
110 foliage_height,
111 leaf_radius,
112 placement,
113 ),
114 FoliagePlacer::Spruce(_) => Self::create_spruce_tree_foliage(
115 region,
116 registry,
117 random,
118 config,
119 attachment,
120 foliage_height,
121 leaf_radius,
122 placement,
123 ),
124 FoliagePlacer::MegaPine(_) => Self::create_mega_pine_tree_foliage(
125 region,
126 registry,
127 random,
128 config,
129 attachment,
130 foliage_height,
131 leaf_radius,
132 placement,
133 ),
134 FoliagePlacer::Acacia(_) => Self::create_acacia_tree_foliage(
135 region,
136 registry,
137 random,
138 config,
139 attachment,
140 foliage_height,
141 leaf_radius,
142 placement,
143 ),
144 FoliagePlacer::DarkOak(_) => Self::create_dark_oak_tree_foliage(
145 region,
146 registry,
147 random,
148 config,
149 attachment,
150 leaf_radius,
151 placement,
152 ),
153 FoliagePlacer::Jungle(_) => Self::create_jungle_tree_foliage(
154 region,
155 registry,
156 random,
157 config,
158 attachment,
159 foliage_height,
160 leaf_radius,
161 placement,
162 ),
163 FoliagePlacer::RandomSpread(placer) => Self::create_random_spread_tree_foliage(
164 region,
165 registry,
166 random,
167 config,
168 placer,
169 attachment,
170 foliage_height,
171 leaf_radius,
172 placement,
173 ),
174 FoliagePlacer::Cherry(placer) => Self::create_cherry_tree_foliage(
175 region,
176 registry,
177 random,
178 config,
179 placer,
180 attachment,
181 foliage_height,
182 leaf_radius,
183 placement,
184 ),
185 FoliagePlacer::Poplar(placer) => Self::create_poplar_tree_foliage(
186 region,
187 registry,
188 random,
189 config,
190 placer,
191 attachment,
192 foliage_height,
193 leaf_radius,
194 placement,
195 ),
196 }
197 }
198
199 fn create_poplar_tree_foliage(
200 region: &mut impl LevelAccessor,
201 registry: &Registry,
202 random: &mut WorldgenRandom,
203 config: &TreeConfiguration,
204 placer: &PoplarFoliagePlacer,
205 attachment: FoliageAttachment,
206 foliage_height: i32,
207 leaf_radius: i32,
208 placement: &mut TreePlacement,
209 ) {
210 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
211 let foliage_pos = attachment.pos.above_n(offset);
212 let current_radius = leaf_radius + attachment.radius_offset - 1;
213 let flip_rhombus_shape = random.next_bool();
214
215 Self::place_poplar_tree_leaves_row(
216 region,
217 registry,
218 random,
219 config,
220 foliage_pos,
221 current_radius - 2,
222 foliage_height - 1,
223 attachment.double_trunk,
224 foliage_height,
225 flip_rhombus_shape,
226 placer.side_hole_chance,
227 placement,
228 );
229 Self::place_poplar_tree_leaves_row(
230 region,
231 registry,
232 random,
233 config,
234 foliage_pos,
235 current_radius - 1,
236 foliage_height - 2,
237 attachment.double_trunk,
238 foliage_height,
239 flip_rhombus_shape,
240 placer.side_hole_chance,
241 placement,
242 );
243 Self::place_poplar_tree_leaves_row(
244 region,
245 registry,
246 random,
247 config,
248 foliage_pos,
249 current_radius - 1,
250 foliage_height - 3,
251 attachment.double_trunk,
252 foliage_height,
253 flip_rhombus_shape,
254 placer.side_hole_chance,
255 placement,
256 );
257
258 for y in (1..foliage_height - 3).rev() {
259 Self::place_poplar_tree_leaves_row(
260 region,
261 registry,
262 random,
263 config,
264 foliage_pos,
265 current_radius,
266 y,
267 attachment.double_trunk,
268 foliage_height,
269 flip_rhombus_shape,
270 placer.side_hole_chance,
271 placement,
272 );
273 }
274
275 Self::replace_poplar_leaves_with_logs(
276 region,
277 registry,
278 random,
279 config,
280 foliage_pos,
281 current_radius,
282 foliage_height - 4,
283 attachment.double_trunk,
284 foliage_height,
285 flip_rhombus_shape,
286 placement,
287 );
288 Self::place_poplar_tree_leaves_row(
289 region,
290 registry,
291 random,
292 config,
293 foliage_pos,
294 current_radius - 1,
295 0,
296 attachment.double_trunk,
297 foliage_height,
298 flip_rhombus_shape,
299 placer.side_hole_chance,
300 placement,
301 );
302 Self::place_poplar_tree_leaves_row(
303 region,
304 registry,
305 random,
306 config,
307 foliage_pos,
308 (current_radius - 2).clamp(1, 2),
309 -1,
310 attachment.double_trunk,
311 foliage_height,
312 flip_rhombus_shape,
313 placer.side_hole_chance,
314 placement,
315 );
316 }
317
318 fn place_poplar_tree_leaves_row(
319 region: &mut impl LevelAccessor,
320 registry: &Registry,
321 random: &mut WorldgenRandom,
322 config: &TreeConfiguration,
323 origin: BlockPos,
324 current_radius: i32,
325 y: i32,
326 double_trunk: bool,
327 foliage_height: i32,
328 flip_rhombus_shape: bool,
329 side_hole_chance: f32,
330 placement: &mut TreePlacement,
331 ) {
332 let offset = i32::from(double_trunk);
333 for dx in -current_radius..=current_radius + offset {
334 for dz in -current_radius..=current_radius + offset {
335 if !Self::poplar_foliage_should_skip_location(
336 random,
337 dx,
338 y,
339 dz,
340 current_radius,
341 foliage_height,
342 flip_rhombus_shape,
343 side_hole_chance,
344 ) {
345 let pos = origin.offset(dx, y, dz);
346 let _ =
347 Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
348 }
349 }
350 }
351 }
352
353 #[expect(
354 clippy::similar_names,
355 reason = "the names mirror Vanilla's x/z offsets"
356 )]
357 fn replace_poplar_leaves_with_logs(
358 region: &mut impl LevelAccessor,
359 registry: &Registry,
360 random: &mut WorldgenRandom,
361 config: &TreeConfiguration,
362 origin: BlockPos,
363 current_radius: i32,
364 y: i32,
365 double_trunk: bool,
366 foliage_height: i32,
367 flip_rhombus_shape: bool,
368 placement: &mut TreePlacement,
369 ) {
370 let offset = i32::from(double_trunk);
371 for dx in -current_radius..=current_radius + offset {
372 for dz in -current_radius..=current_radius + offset {
373 let abs_dx = abs_i32(dx);
374 let abs_dz = abs_i32(dz);
375 let corner_blocks_to_cut = Self::poplar_corner_blocks_to_cut(
376 dx,
377 dz,
378 current_radius,
379 Self::poplar_row_is_partial(foliage_height, y),
380 flip_rhombus_shape,
381 );
382 if !Self::poplar_is_within_rhombus(
383 current_radius,
384 abs_dx,
385 abs_dz,
386 corner_blocks_to_cut,
387 2,
388 ) || !(abs_dz == 0 && current_radius - abs_dx >= 4
389 || abs_dx == 0 && current_radius - abs_dz >= 4)
390 {
391 continue;
392 }
393
394 let pos = origin.offset(dx, y, dz);
395 let foliage_state = BlockStateProviderEvaluator::sample_block_state_provider(
396 region,
397 registry,
398 random,
399 &config.foliage_provider,
400 pos,
401 );
402 if region.get_block_state(pos) != foliage_state {
403 continue;
404 }
405
406 let trunk_state = BlockStateProviderEvaluator::sample_block_state_provider(
407 region,
408 registry,
409 random,
410 &config.trunk_provider,
411 pos,
412 );
413 let axis = if abs_dz == 0 { Axis::X } else { Axis::Z };
414 let trunk_state = Self::with_axis_if_present(trunk_state, axis);
415 placement.set_foliage(region, pos, trunk_state);
416 }
417 }
418 }
419
420 #[expect(
421 clippy::similar_names,
422 reason = "the names mirror Vanilla's x/z offsets"
423 )]
424 fn poplar_foliage_should_skip_location(
425 random: &mut WorldgenRandom,
426 dx: i32,
427 y: i32,
428 dz: i32,
429 current_radius: i32,
430 foliage_height: i32,
431 flip_rhombus_shape: bool,
432 side_hole_chance: f32,
433 ) -> bool {
434 let partial_rhombus = Self::poplar_row_is_partial(foliage_height, y);
435 let corner_blocks_to_cut = Self::poplar_corner_blocks_to_cut(
436 dx,
437 dz,
438 current_radius,
439 partial_rhombus,
440 flip_rhombus_shape,
441 );
442 let abs_dx = abs_i32(dx);
443 let abs_dz = abs_i32(dz);
444 if partial_rhombus && (abs_dx == current_radius || abs_dz == current_radius) {
445 return true;
446 }
447
448 let additional_side_removal = i32::from(random.next_f32() <= side_hole_chance);
449 !Self::poplar_is_within_rhombus(
450 current_radius,
451 abs_dx,
452 abs_dz,
453 corner_blocks_to_cut,
454 additional_side_removal,
455 )
456 }
457
458 const fn poplar_row_is_partial(foliage_height: i32, y: i32) -> bool {
459 foliage_height - 1 == y || foliage_height - 2 == y
460 }
461
462 const fn poplar_corner_blocks_to_cut(
463 dx: i32,
464 dz: i32,
465 current_radius: i32,
466 partial_rhombus: bool,
467 flip_rhombus_shape: bool,
468 ) -> i32 {
469 let small_corner = if flip_rhombus_shape {
470 (dx > 0 && dz > 0) || (dz < 0 && dx < 0)
471 } else {
472 (dx > 0 && dz < 0) || (dz > 0 && dx < 0)
473 };
474 if small_corner {
475 current_radius - 1
476 } else if partial_rhombus {
477 current_radius + 1
478 } else {
479 current_radius
480 }
481 }
482
483 #[expect(
484 clippy::similar_names,
485 reason = "the names mirror Vanilla's x/z offsets"
486 )]
487 const fn poplar_is_within_rhombus(
488 current_radius: i32,
489 abs_dx: i32,
490 abs_dz: i32,
491 corner_blocks_to_cut: i32,
492 additional_side_removal: i32,
493 ) -> bool {
494 abs_dx + abs_dz <= current_radius * 2 - (corner_blocks_to_cut + additional_side_removal)
495 }
496
497 fn create_fancy_tree_foliage(
498 region: &mut impl LevelAccessor,
499 registry: &Registry,
500 random: &mut WorldgenRandom,
501 config: &TreeConfiguration,
502 attachment: FoliageAttachment,
503 foliage_height: i32,
504 leaf_radius: i32,
505 placement: &mut TreePlacement,
506 ) {
507 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
508 for y in (offset - foliage_height..=offset).rev() {
509 let current_radius = if y != offset && y != offset - foliage_height {
510 leaf_radius + 1
511 } else {
512 leaf_radius
513 };
514 Self::place_tree_leaves_row(
515 region,
516 registry,
517 random,
518 config,
519 attachment.pos,
520 current_radius,
521 y,
522 attachment.double_trunk,
523 placement,
524 );
525 }
526 }
527
528 fn create_jungle_tree_foliage(
529 region: &mut impl LevelAccessor,
530 registry: &Registry,
531 random: &mut WorldgenRandom,
532 config: &TreeConfiguration,
533 attachment: FoliageAttachment,
534 foliage_height: i32,
535 leaf_radius: i32,
536 placement: &mut TreePlacement,
537 ) {
538 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
539 let leaf_height = if attachment.double_trunk {
540 foliage_height
541 } else {
542 1 + random.next_i32_bounded(2)
543 };
544
545 for y in (offset - leaf_height..=offset).rev() {
546 let current_radius = leaf_radius + attachment.radius_offset + 1 - y;
547 Self::place_tree_leaves_row(
548 region,
549 registry,
550 random,
551 config,
552 attachment.pos,
553 current_radius,
554 y,
555 attachment.double_trunk,
556 placement,
557 );
558 }
559 }
560
561 fn create_random_spread_tree_foliage(
562 region: &mut impl LevelAccessor,
563 registry: &Registry,
564 random: &mut WorldgenRandom,
565 config: &TreeConfiguration,
566 placer: &RandomSpreadFoliagePlacer,
567 attachment: FoliageAttachment,
568 foliage_height: i32,
569 leaf_radius: i32,
570 placement: &mut TreePlacement,
571 ) {
572 for _ in 0..placer.leaf_placement_attempts {
573 let pos = attachment.pos.offset(
574 random.next_i32_bounded(leaf_radius) - random.next_i32_bounded(leaf_radius),
575 random.next_i32_bounded(foliage_height) - random.next_i32_bounded(foliage_height),
576 random.next_i32_bounded(leaf_radius) - random.next_i32_bounded(leaf_radius),
577 );
578 let _ = Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
579 }
580 }
581
582 fn create_cherry_tree_foliage(
583 region: &mut impl LevelAccessor,
584 registry: &Registry,
585 random: &mut WorldgenRandom,
586 config: &TreeConfiguration,
587 placer: &CherryFoliagePlacer,
588 attachment: FoliageAttachment,
589 foliage_height: i32,
590 leaf_radius: i32,
591 placement: &mut TreePlacement,
592 ) {
593 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
594 let foliage_pos = attachment.pos.above_n(offset);
595 let current_radius = leaf_radius + attachment.radius_offset - 1;
596 Self::place_tree_leaves_row(
597 region,
598 registry,
599 random,
600 config,
601 foliage_pos,
602 current_radius - 2,
603 foliage_height - 3,
604 attachment.double_trunk,
605 placement,
606 );
607 Self::place_tree_leaves_row(
608 region,
609 registry,
610 random,
611 config,
612 foliage_pos,
613 current_radius - 1,
614 foliage_height - 4,
615 attachment.double_trunk,
616 placement,
617 );
618
619 for y in (0..=foliage_height - 5).rev() {
620 Self::place_tree_leaves_row(
621 region,
622 registry,
623 random,
624 config,
625 foliage_pos,
626 current_radius,
627 y,
628 attachment.double_trunk,
629 placement,
630 );
631 }
632
633 Self::place_tree_leaves_row_with_hanging_leaves_below(
634 region,
635 registry,
636 random,
637 config,
638 foliage_pos,
639 current_radius,
640 -1,
641 attachment.double_trunk,
642 placer.hanging_leaves_chance,
643 placer.hanging_leaves_extension_chance,
644 placement,
645 );
646 Self::place_tree_leaves_row_with_hanging_leaves_below(
647 region,
648 registry,
649 random,
650 config,
651 foliage_pos,
652 current_radius - 1,
653 -2,
654 attachment.double_trunk,
655 placer.hanging_leaves_chance,
656 placer.hanging_leaves_extension_chance,
657 placement,
658 );
659 }
660
661 fn create_dark_oak_tree_foliage(
662 region: &mut impl LevelAccessor,
663 registry: &Registry,
664 random: &mut WorldgenRandom,
665 config: &TreeConfiguration,
666 attachment: FoliageAttachment,
667 leaf_radius: i32,
668 placement: &mut TreePlacement,
669 ) {
670 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
671 let pos = attachment.pos.above_n(offset);
672 if attachment.double_trunk {
673 Self::place_tree_leaves_row(
674 region,
675 registry,
676 random,
677 config,
678 pos,
679 leaf_radius + 2,
680 -1,
681 true,
682 placement,
683 );
684 Self::place_tree_leaves_row(
685 region,
686 registry,
687 random,
688 config,
689 pos,
690 leaf_radius + 3,
691 0,
692 true,
693 placement,
694 );
695 Self::place_tree_leaves_row(
696 region,
697 registry,
698 random,
699 config,
700 pos,
701 leaf_radius + 2,
702 1,
703 true,
704 placement,
705 );
706 if random.next_bool() {
707 Self::place_tree_leaves_row(
708 region,
709 registry,
710 random,
711 config,
712 pos,
713 leaf_radius,
714 2,
715 true,
716 placement,
717 );
718 }
719 } else {
720 Self::place_tree_leaves_row(
721 region,
722 registry,
723 random,
724 config,
725 pos,
726 leaf_radius + 2,
727 -1,
728 false,
729 placement,
730 );
731 Self::place_tree_leaves_row(
732 region,
733 registry,
734 random,
735 config,
736 pos,
737 leaf_radius + 1,
738 0,
739 false,
740 placement,
741 );
742 }
743 }
744
745 fn create_mega_pine_tree_foliage(
746 region: &mut impl LevelAccessor,
747 registry: &Registry,
748 random: &mut WorldgenRandom,
749 config: &TreeConfiguration,
750 attachment: FoliageAttachment,
751 foliage_height: i32,
752 leaf_radius: i32,
753 placement: &mut TreePlacement,
754 ) {
755 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
756 let mut previous_radius = 0;
757 let min_y = attachment.pos.y() - foliage_height + offset;
758 let max_y = attachment.pos.y() + offset;
759
760 for y in min_y..=max_y {
761 let y_offset = attachment.pos.y() - y;
762 let smooth_radius = leaf_radius
763 + attachment.radius_offset
764 + fast_floor(f64::from(y_offset) / f64::from(foliage_height) * 3.5);
765 let jagged_radius = if y_offset > 0 && smooth_radius == previous_radius && (y & 1) == 0
766 {
767 smooth_radius + 1
768 } else {
769 smooth_radius
770 };
771 let row_origin = BlockPos::new(attachment.pos.x(), y, attachment.pos.z());
772 Self::place_tree_leaves_row(
773 region,
774 registry,
775 random,
776 config,
777 row_origin,
778 jagged_radius,
779 0,
780 attachment.double_trunk,
781 placement,
782 );
783 previous_radius = smooth_radius;
784 }
785 }
786
787 fn create_bush_tree_foliage(
788 region: &mut impl LevelAccessor,
789 registry: &Registry,
790 random: &mut WorldgenRandom,
791 config: &TreeConfiguration,
792 attachment: FoliageAttachment,
793 foliage_height: i32,
794 leaf_radius: i32,
795 placement: &mut TreePlacement,
796 ) {
797 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
798 for y in (offset - foliage_height..=offset).rev() {
799 let current_radius = leaf_radius + attachment.radius_offset - 1 - y;
800 Self::place_tree_leaves_row(
801 region,
802 registry,
803 random,
804 config,
805 attachment.pos,
806 current_radius,
807 y,
808 attachment.double_trunk,
809 placement,
810 );
811 }
812 }
813
814 fn create_pine_tree_foliage(
815 region: &mut impl LevelAccessor,
816 registry: &Registry,
817 random: &mut WorldgenRandom,
818 config: &TreeConfiguration,
819 attachment: FoliageAttachment,
820 foliage_height: i32,
821 leaf_radius: i32,
822 placement: &mut TreePlacement,
823 ) {
824 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
825 let mut current_radius = 0;
826 for y in (offset - foliage_height..=offset).rev() {
827 Self::place_tree_leaves_row(
828 region,
829 registry,
830 random,
831 config,
832 attachment.pos,
833 current_radius,
834 y,
835 attachment.double_trunk,
836 placement,
837 );
838 if current_radius >= 1 && y == offset - foliage_height + 1 {
839 current_radius -= 1;
840 } else if current_radius < leaf_radius + attachment.radius_offset {
841 current_radius += 1;
842 }
843 }
844 }
845
846 fn create_spruce_tree_foliage(
847 region: &mut impl LevelAccessor,
848 registry: &Registry,
849 random: &mut WorldgenRandom,
850 config: &TreeConfiguration,
851 attachment: FoliageAttachment,
852 foliage_height: i32,
853 leaf_radius: i32,
854 placement: &mut TreePlacement,
855 ) {
856 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
857 let mut current_radius = random.next_i32_bounded(2);
858 let mut max_radius = 1;
859 let mut min_radius = 0;
860
861 for y in (-foliage_height..=offset).rev() {
862 Self::place_tree_leaves_row(
863 region,
864 registry,
865 random,
866 config,
867 attachment.pos,
868 current_radius,
869 y,
870 attachment.double_trunk,
871 placement,
872 );
873 if current_radius >= max_radius {
874 current_radius = min_radius;
875 min_radius = 1;
876 max_radius = (max_radius + 1).min(leaf_radius + attachment.radius_offset);
877 } else {
878 current_radius += 1;
879 }
880 }
881 }
882
883 fn create_blob_tree_foliage(
884 region: &mut impl LevelAccessor,
885 registry: &Registry,
886 random: &mut WorldgenRandom,
887 config: &TreeConfiguration,
888 _placer: &BlobFoliagePlacer,
889 attachment: FoliageAttachment,
890 foliage_height: i32,
891 leaf_radius: i32,
892 placement: &mut TreePlacement,
893 ) {
894 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
895 for y in (offset - foliage_height..=offset).rev() {
896 let current_radius = (leaf_radius + attachment.radius_offset - 1 - y / 2).max(0);
897 Self::place_tree_leaves_row(
898 region,
899 registry,
900 random,
901 config,
902 attachment.pos,
903 current_radius,
904 y,
905 attachment.double_trunk,
906 placement,
907 );
908 }
909 }
910
911 fn create_acacia_tree_foliage(
912 region: &mut impl LevelAccessor,
913 registry: &Registry,
914 random: &mut WorldgenRandom,
915 config: &TreeConfiguration,
916 attachment: FoliageAttachment,
917 foliage_height: i32,
918 leaf_radius: i32,
919 placement: &mut TreePlacement,
920 ) {
921 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
922 let foliage_pos = attachment.pos.above_n(offset);
923 Self::place_tree_leaves_row(
924 region,
925 registry,
926 random,
927 config,
928 foliage_pos,
929 leaf_radius + attachment.radius_offset,
930 -1 - foliage_height,
931 attachment.double_trunk,
932 placement,
933 );
934 Self::place_tree_leaves_row(
935 region,
936 registry,
937 random,
938 config,
939 foliage_pos,
940 leaf_radius - 1,
941 -foliage_height,
942 attachment.double_trunk,
943 placement,
944 );
945 Self::place_tree_leaves_row(
946 region,
947 registry,
948 random,
949 config,
950 foliage_pos,
951 leaf_radius + attachment.radius_offset - 1,
952 0,
953 attachment.double_trunk,
954 placement,
955 );
956 }
957
958 fn tree_foliage_offset(random: &mut WorldgenRandom, foliage_placer: &FoliagePlacer) -> i32 {
959 match foliage_placer {
960 FoliagePlacer::Blob(placer) => placer.offset.sample(random),
961 FoliagePlacer::Bush(placer) => placer.offset.sample(random),
962 FoliagePlacer::Fancy(placer) => placer.offset.sample(random),
963 FoliagePlacer::Pine(placer) => placer.offset.sample(random),
964 FoliagePlacer::Spruce(placer) => placer.offset.sample(random),
965 FoliagePlacer::MegaPine(placer) => placer.offset.sample(random),
966 FoliagePlacer::Acacia(placer) => placer.offset.sample(random),
967 FoliagePlacer::DarkOak(placer) => placer.offset.sample(random),
968 FoliagePlacer::Jungle(placer) => placer.offset.sample(random),
969 FoliagePlacer::RandomSpread(placer) => placer.offset.sample(random),
970 FoliagePlacer::Cherry(placer) => placer.offset.sample(random),
971 FoliagePlacer::Poplar(placer) => placer.offset.sample(random),
972 }
973 }
974
975 fn place_tree_leaves_row(
976 region: &mut impl LevelAccessor,
977 registry: &Registry,
978 random: &mut WorldgenRandom,
979 config: &TreeConfiguration,
980 origin: BlockPos,
981 current_radius: i32,
982 y: i32,
983 double_trunk: bool,
984 placement: &mut TreePlacement,
985 ) {
986 let offset = i32::from(double_trunk);
987 for dx in -current_radius..=current_radius + offset {
988 for dz in -current_radius..=current_radius + offset {
989 if !Self::tree_foliage_should_skip_location(
990 random,
991 &config.foliage_placer,
992 dx,
993 y,
994 dz,
995 current_radius,
996 double_trunk,
997 ) {
998 let pos = origin.offset(dx, y, dz);
999 let _ =
1000 Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
1001 }
1002 }
1003 }
1004 }
1005
1006 #[expect(
1007 clippy::too_many_arguments,
1008 reason = "mirrors vanilla foliage row helper"
1009 )]
1010 fn place_tree_leaves_row_with_hanging_leaves_below(
1011 region: &mut impl LevelAccessor,
1012 registry: &Registry,
1013 random: &mut WorldgenRandom,
1014 config: &TreeConfiguration,
1015 origin: BlockPos,
1016 current_radius: i32,
1017 y: i32,
1018 double_trunk: bool,
1019 hanging_leaves_chance: f32,
1020 hanging_leaves_extension_chance: f32,
1021 placement: &mut TreePlacement,
1022 ) {
1023 Self::place_tree_leaves_row(
1024 region,
1025 registry,
1026 random,
1027 config,
1028 origin,
1029 current_radius,
1030 y,
1031 double_trunk,
1032 placement,
1033 );
1034
1035 let offset = i32::from(double_trunk);
1036 let log_pos = origin.below();
1037 for along_edge in Self::VANILLA_HORIZONTAL_DIRECTIONS {
1038 let to_edge = along_edge.rotate_y_clockwise();
1039 let offset_to_edge = if Self::direction_is_positive(to_edge) {
1040 current_radius + offset
1041 } else {
1042 current_radius
1043 };
1044 let mut pos = origin
1045 .offset(0, y - 1, 0)
1046 .relative_n(to_edge, offset_to_edge)
1047 .relative_n(along_edge, -current_radius);
1048 let mut offset_along_edge = -current_radius;
1049
1050 while offset_along_edge < current_radius + offset {
1051 let leaves_above = placement.foliage.contains(pos.above());
1052 if leaves_above
1053 && Self::try_place_hanging_leaf_extension(
1054 region,
1055 registry,
1056 random,
1057 config,
1058 hanging_leaves_chance,
1059 log_pos,
1060 pos,
1061 placement,
1062 )
1063 {
1064 let _ = Self::try_place_hanging_leaf_extension(
1065 region,
1066 registry,
1067 random,
1068 config,
1069 hanging_leaves_extension_chance,
1070 log_pos,
1071 pos.below(),
1072 placement,
1073 );
1074 }
1075
1076 offset_along_edge += 1;
1077 pos = pos.relative(along_edge);
1078 }
1079 }
1080 }
1081
1082 fn try_place_hanging_leaf_extension(
1083 region: &mut impl LevelAccessor,
1084 registry: &Registry,
1085 random: &mut WorldgenRandom,
1086 config: &TreeConfiguration,
1087 chance: f32,
1088 log_pos: BlockPos,
1089 pos: BlockPos,
1090 placement: &mut TreePlacement,
1091 ) -> bool {
1092 if Self::manhattan_distance(pos, log_pos) >= 7 || random.next_f32() > chance {
1093 return false;
1094 }
1095
1096 Self::try_place_tree_leaf(region, registry, random, config, pos, placement)
1097 }
1098
1099 const fn direction_is_positive(direction: Direction) -> bool {
1100 matches!(
1101 direction,
1102 Direction::Up | Direction::South | Direction::East
1103 )
1104 }
1105
1106 fn tree_foliage_should_skip_location(
1107 random: &mut WorldgenRandom,
1108 foliage_placer: &FoliagePlacer,
1109 dx: i32,
1110 y: i32,
1111 dz: i32,
1112 current_radius: i32,
1113 double_trunk: bool,
1114 ) -> bool {
1115 if matches!(foliage_placer, FoliagePlacer::DarkOak(_)) {
1116 return Self::dark_oak_foliage_should_skip_location(
1117 dx,
1118 y,
1119 dz,
1120 current_radius,
1121 double_trunk,
1122 );
1123 }
1124
1125 let (dx, dz) = Self::foliage_signed_distances(dx, dz, double_trunk);
1126 match foliage_placer {
1127 FoliagePlacer::Blob(_) => {
1128 Self::blob_foliage_should_skip_location(random, dx, y, dz, current_radius)
1129 }
1130 FoliagePlacer::Bush(_) => {
1131 Self::bush_foliage_should_skip_location(random, dx, dz, current_radius)
1132 }
1133 FoliagePlacer::Fancy(_) => {
1134 Self::fancy_foliage_should_skip_location(dx, dz, current_radius)
1135 }
1136 FoliagePlacer::Pine(_) | FoliagePlacer::Spruce(_) => {
1137 Self::conifer_foliage_should_skip_location(dx, dz, current_radius)
1138 }
1139 FoliagePlacer::MegaPine(_) => {
1140 Self::mega_pine_foliage_should_skip_location(dx, dz, current_radius)
1141 }
1142 FoliagePlacer::Jungle(_) => {
1143 Self::mega_pine_foliage_should_skip_location(dx, dz, current_radius)
1144 }
1145 FoliagePlacer::RandomSpread(_) => false,
1146 FoliagePlacer::Acacia(_) => {
1147 Self::acacia_foliage_should_skip_location(dx, y, dz, current_radius)
1148 }
1149 FoliagePlacer::Cherry(placer) => {
1150 Self::cherry_foliage_should_skip_location(random, placer, dx, y, dz, current_radius)
1151 }
1152 FoliagePlacer::DarkOak(_) => unreachable!(),
1153 FoliagePlacer::Poplar(_) => {
1154 unreachable!("Poplar uses poplar_foliage_should_skip_location")
1155 }
1156 }
1157 }
1158
1159 fn blob_foliage_should_skip_location(
1160 random: &mut WorldgenRandom,
1161 dx: i32,
1162 y: i32,
1163 dz: i32,
1164 current_radius: i32,
1165 ) -> bool {
1166 dx == current_radius && dz == current_radius && (random.next_i32_bounded(2) == 0 || y == 0)
1167 }
1168
1169 fn bush_foliage_should_skip_location(
1170 random: &mut WorldgenRandom,
1171 dx: i32,
1172 dz: i32,
1173 current_radius: i32,
1174 ) -> bool {
1175 dx == current_radius && dz == current_radius && random.next_i32_bounded(2) == 0
1176 }
1177
1178 fn fancy_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
1179 let dx = dx as f32 + 0.5;
1180 let dz = dz as f32 + 0.5;
1181 dx * dx + dz * dz > (current_radius * current_radius) as f32
1182 }
1183
1184 fn dark_oak_foliage_should_skip_location(
1185 dx: i32,
1186 y: i32,
1187 dz: i32,
1188 current_radius: i32,
1189 double_trunk: bool,
1190 ) -> bool {
1191 if y == 0
1192 && double_trunk
1193 && (dx == -current_radius || dx >= current_radius)
1194 && (dz == -current_radius || dz >= current_radius)
1195 {
1196 return true;
1197 }
1198
1199 let (dx, dz) = Self::foliage_signed_distances(dx, dz, double_trunk);
1200 if y == -1 && !double_trunk {
1201 dx == current_radius && dz == current_radius
1202 } else if y == 1 {
1203 dx + dz > current_radius * 2 - 2
1204 } else {
1205 false
1206 }
1207 }
1208
1209 fn cherry_foliage_should_skip_location(
1210 random: &mut WorldgenRandom,
1211 placer: &CherryFoliagePlacer,
1212 dx: i32,
1213 y: i32,
1214 dz: i32,
1215 current_radius: i32,
1216 ) -> bool {
1217 if y == -1
1218 && (dx == current_radius || dz == current_radius)
1219 && random.next_f32() < placer.wide_bottom_layer_hole_chance
1220 {
1221 return true;
1222 }
1223
1224 let corner = dx == current_radius && dz == current_radius;
1225 let wide_layer = current_radius > 2;
1226 if wide_layer {
1227 corner
1228 || dx + dz > current_radius * 2 - 2 && random.next_f32() < placer.corner_hole_chance
1229 } else {
1230 corner && random.next_f32() < placer.corner_hole_chance
1231 }
1232 }
1233
1234 const fn conifer_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
1235 dx == current_radius && dz == current_radius && current_radius > 0
1236 }
1237
1238 const fn mega_pine_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
1239 dx + dz >= 7 || dx * dx + dz * dz > current_radius * current_radius
1240 }
1241
1242 const fn acacia_foliage_should_skip_location(
1243 dx: i32,
1244 y: i32,
1245 dz: i32,
1246 current_radius: i32,
1247 ) -> bool {
1248 if y == 0 {
1249 (dx > 1 || dz > 1) && dx != 0 && dz != 0
1250 } else {
1251 dx == current_radius && dz == current_radius && current_radius > 0
1252 }
1253 }
1254
1255 fn foliage_signed_distances(dx: i32, dz: i32, double_trunk: bool) -> (i32, i32) {
1256 if double_trunk {
1257 (
1258 abs_i32(dx).min(abs_i32(dx - 1)),
1259 abs_i32(dz).min(abs_i32(dz - 1)),
1260 )
1261 } else {
1262 (abs_i32(dx), abs_i32(dz))
1263 }
1264 }
1265
1266 fn try_place_tree_leaf(
1267 region: &mut impl LevelAccessor,
1268 registry: &Registry,
1269 random: &mut WorldgenRandom,
1270 config: &TreeConfiguration,
1271 pos: BlockPos,
1272 placement: &mut TreePlacement,
1273 ) -> bool {
1274 let current_state = region.get_block_state(pos);
1275 let is_persistent = current_state
1276 .try_get_value(&BlockStateProperties::PERSISTENT)
1277 .unwrap_or(false);
1278 let valid_tree_pos = current_state.is_air()
1279 || current_state
1280 .get_block()
1281 .has_tag(&BlockTag::REPLACEABLE_BY_TREES);
1282 if is_persistent || !valid_tree_pos {
1283 return false;
1284 }
1285
1286 let foliage_state = BlockStateProviderEvaluator::sample_block_state_provider(
1287 region,
1288 registry,
1289 random,
1290 &config.foliage_provider,
1291 pos,
1292 );
1293 let foliage_state = Self::copy_waterlogged_from(region, pos, foliage_state);
1294 placement.set_foliage(region, pos, foliage_state);
1295 true
1296 }
1297}
1298
1299#[cfg(test)]
1300mod tests {
1301 use super::*;
1302
1303 #[test]
1304 fn acacia_top_layer_keeps_cross_and_inner_corners() {
1305 assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(2, 0, 2, 2));
1306 assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(1, 0, 2, 2));
1307 assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(0, 0, 2, 2));
1308 assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(1, 0, 1, 2));
1309 }
1310
1311 #[test]
1312 fn acacia_lower_layers_skip_only_outer_corners() {
1313 assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(2, -1, 2, 2));
1314 assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(1, -1, 2, 2));
1315 assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(0, -1, 0, 0));
1316 }
1317
1318 #[test]
1319 fn conifer_layers_skip_only_nonzero_outer_corners() {
1320 assert!(FeatureDecorationRunner::conifer_foliage_should_skip_location(2, 2, 2));
1321 assert!(!FeatureDecorationRunner::conifer_foliage_should_skip_location(1, 2, 2));
1322 assert!(!FeatureDecorationRunner::conifer_foliage_should_skip_location(0, 0, 0));
1323 }
1324
1325 #[test]
1326 fn mega_pine_uses_circular_row_cutoff_with_vanilla_seven_limit() {
1327 assert!(FeatureDecorationRunner::mega_pine_foliage_should_skip_location(4, 3, 5));
1328 assert!(FeatureDecorationRunner::mega_pine_foliage_should_skip_location(4, 4, 5));
1329 assert!(!FeatureDecorationRunner::mega_pine_foliage_should_skip_location(3, 3, 5));
1330 }
1331
1332 #[test]
1333 fn fancy_foliage_uses_shifted_circular_cutoff() {
1334 assert!(!FeatureDecorationRunner::fancy_foliage_should_skip_location(0, 0, 1));
1335 assert!(FeatureDecorationRunner::fancy_foliage_should_skip_location(
1336 1, 0, 1
1337 ));
1338 assert!(FeatureDecorationRunner::fancy_foliage_should_skip_location(
1339 0, 0, 0
1340 ));
1341 }
1342
1343 #[test]
1344 fn dark_oak_double_trunk_skips_outer_corner_extensions_on_center_layer() {
1345 assert!(
1346 FeatureDecorationRunner::dark_oak_foliage_should_skip_location(-3, 0, -3, 3, true,)
1347 );
1348 assert!(FeatureDecorationRunner::dark_oak_foliage_should_skip_location(4, 0, 4, 3, true,));
1349 assert!(
1350 !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(-2, 0, -3, 3, true,)
1351 );
1352 }
1353
1354 #[test]
1355 fn dark_oak_side_crowns_skip_lower_outer_corners() {
1356 assert!(
1357 FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, -1, 2, 2, false,)
1358 );
1359 assert!(
1360 !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, -1, 1, 2, false,)
1361 );
1362 }
1363
1364 #[test]
1365 fn dark_oak_upper_layer_uses_diagonal_cutoff() {
1366 assert!(FeatureDecorationRunner::dark_oak_foliage_should_skip_location(3, 1, 2, 3, false,));
1367 assert!(
1368 !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, 1, 2, 3, false,)
1369 );
1370 }
1371}