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 }
35 }
36
37 pub(super) fn tree_foliage_radius(
38 random: &mut WorldgenRandom,
39 foliage_placer: &FoliagePlacer,
40 trunk_height: i32,
41 ) -> i32 {
42 match foliage_placer {
43 FoliagePlacer::Blob(placer) => placer.radius.sample(random),
44 FoliagePlacer::Bush(placer) => placer.radius.sample(random),
45 FoliagePlacer::Fancy(placer) => placer.radius.sample(random),
46 FoliagePlacer::Pine(placer) => {
47 placer.radius.sample(random) + random.next_i32_bounded((trunk_height + 1).max(1))
48 }
49 FoliagePlacer::Spruce(placer) => placer.radius.sample(random),
50 FoliagePlacer::MegaPine(placer) => placer.radius.sample(random),
51 FoliagePlacer::Acacia(placer) => placer.radius.sample(random),
52 FoliagePlacer::DarkOak(placer) => placer.radius.sample(random),
53 FoliagePlacer::Jungle(placer) => placer.radius.sample(random),
54 FoliagePlacer::RandomSpread(placer) => placer.radius.sample(random),
55 FoliagePlacer::Cherry(placer) => placer.radius.sample(random),
56 }
57 }
58
59 pub(super) fn create_tree_foliage(
60 region: &mut WorldGenRegion<'_>,
61 registry: &Registry,
62 random: &mut WorldgenRandom,
63 config: &TreeConfiguration,
64 _tree_height: i32,
65 attachment: FoliageAttachment,
66 foliage_height: i32,
67 leaf_radius: i32,
68 placement: &mut TreePlacement,
69 ) {
70 match &config.foliage_placer {
71 FoliagePlacer::Blob(placer) => Self::create_blob_tree_foliage(
72 region,
73 registry,
74 random,
75 config,
76 placer,
77 attachment,
78 foliage_height,
79 leaf_radius,
80 placement,
81 ),
82 FoliagePlacer::Bush(_) => Self::create_bush_tree_foliage(
83 region,
84 registry,
85 random,
86 config,
87 attachment,
88 foliage_height,
89 leaf_radius,
90 placement,
91 ),
92 FoliagePlacer::Fancy(_) => Self::create_fancy_tree_foliage(
93 region,
94 registry,
95 random,
96 config,
97 attachment,
98 foliage_height,
99 leaf_radius,
100 placement,
101 ),
102 FoliagePlacer::Pine(_) => Self::create_pine_tree_foliage(
103 region,
104 registry,
105 random,
106 config,
107 attachment,
108 foliage_height,
109 leaf_radius,
110 placement,
111 ),
112 FoliagePlacer::Spruce(_) => Self::create_spruce_tree_foliage(
113 region,
114 registry,
115 random,
116 config,
117 attachment,
118 foliage_height,
119 leaf_radius,
120 placement,
121 ),
122 FoliagePlacer::MegaPine(_) => Self::create_mega_pine_tree_foliage(
123 region,
124 registry,
125 random,
126 config,
127 attachment,
128 foliage_height,
129 leaf_radius,
130 placement,
131 ),
132 FoliagePlacer::Acacia(_) => Self::create_acacia_tree_foliage(
133 region,
134 registry,
135 random,
136 config,
137 attachment,
138 foliage_height,
139 leaf_radius,
140 placement,
141 ),
142 FoliagePlacer::DarkOak(_) => Self::create_dark_oak_tree_foliage(
143 region,
144 registry,
145 random,
146 config,
147 attachment,
148 leaf_radius,
149 placement,
150 ),
151 FoliagePlacer::Jungle(_) => Self::create_jungle_tree_foliage(
152 region,
153 registry,
154 random,
155 config,
156 attachment,
157 foliage_height,
158 leaf_radius,
159 placement,
160 ),
161 FoliagePlacer::RandomSpread(placer) => Self::create_random_spread_tree_foliage(
162 region,
163 registry,
164 random,
165 config,
166 placer,
167 attachment,
168 foliage_height,
169 leaf_radius,
170 placement,
171 ),
172 FoliagePlacer::Cherry(placer) => Self::create_cherry_tree_foliage(
173 region,
174 registry,
175 random,
176 config,
177 placer,
178 attachment,
179 foliage_height,
180 leaf_radius,
181 placement,
182 ),
183 }
184 }
185
186 fn create_fancy_tree_foliage(
187 region: &mut WorldGenRegion<'_>,
188 registry: &Registry,
189 random: &mut WorldgenRandom,
190 config: &TreeConfiguration,
191 attachment: FoliageAttachment,
192 foliage_height: i32,
193 leaf_radius: i32,
194 placement: &mut TreePlacement,
195 ) {
196 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
197 for y in (offset - foliage_height..=offset).rev() {
198 let current_radius = if y != offset && y != offset - foliage_height {
199 leaf_radius + 1
200 } else {
201 leaf_radius
202 };
203 Self::place_tree_leaves_row(
204 region,
205 registry,
206 random,
207 config,
208 attachment.pos,
209 current_radius,
210 y,
211 attachment.double_trunk,
212 placement,
213 );
214 }
215 }
216
217 fn create_jungle_tree_foliage(
218 region: &mut WorldGenRegion<'_>,
219 registry: &Registry,
220 random: &mut WorldgenRandom,
221 config: &TreeConfiguration,
222 attachment: FoliageAttachment,
223 foliage_height: i32,
224 leaf_radius: i32,
225 placement: &mut TreePlacement,
226 ) {
227 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
228 let leaf_height = if attachment.double_trunk {
229 foliage_height
230 } else {
231 1 + random.next_i32_bounded(2)
232 };
233
234 for y in (offset - leaf_height..=offset).rev() {
235 let current_radius = leaf_radius + attachment.radius_offset + 1 - y;
236 Self::place_tree_leaves_row(
237 region,
238 registry,
239 random,
240 config,
241 attachment.pos,
242 current_radius,
243 y,
244 attachment.double_trunk,
245 placement,
246 );
247 }
248 }
249
250 fn create_random_spread_tree_foliage(
251 region: &mut WorldGenRegion<'_>,
252 registry: &Registry,
253 random: &mut WorldgenRandom,
254 config: &TreeConfiguration,
255 placer: &RandomSpreadFoliagePlacer,
256 attachment: FoliageAttachment,
257 foliage_height: i32,
258 leaf_radius: i32,
259 placement: &mut TreePlacement,
260 ) {
261 for _ in 0..placer.leaf_placement_attempts {
262 let pos = attachment.pos.offset(
263 random.next_i32_bounded(leaf_radius) - random.next_i32_bounded(leaf_radius),
264 random.next_i32_bounded(foliage_height) - random.next_i32_bounded(foliage_height),
265 random.next_i32_bounded(leaf_radius) - random.next_i32_bounded(leaf_radius),
266 );
267 let _ = Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
268 }
269 }
270
271 fn create_cherry_tree_foliage(
272 region: &mut WorldGenRegion<'_>,
273 registry: &Registry,
274 random: &mut WorldgenRandom,
275 config: &TreeConfiguration,
276 placer: &CherryFoliagePlacer,
277 attachment: FoliageAttachment,
278 foliage_height: i32,
279 leaf_radius: i32,
280 placement: &mut TreePlacement,
281 ) {
282 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
283 let foliage_pos = attachment.pos.above_n(offset);
284 let current_radius = leaf_radius + attachment.radius_offset - 1;
285 Self::place_tree_leaves_row(
286 region,
287 registry,
288 random,
289 config,
290 foliage_pos,
291 current_radius - 2,
292 foliage_height - 3,
293 attachment.double_trunk,
294 placement,
295 );
296 Self::place_tree_leaves_row(
297 region,
298 registry,
299 random,
300 config,
301 foliage_pos,
302 current_radius - 1,
303 foliage_height - 4,
304 attachment.double_trunk,
305 placement,
306 );
307
308 for y in (0..=foliage_height - 5).rev() {
309 Self::place_tree_leaves_row(
310 region,
311 registry,
312 random,
313 config,
314 foliage_pos,
315 current_radius,
316 y,
317 attachment.double_trunk,
318 placement,
319 );
320 }
321
322 Self::place_tree_leaves_row_with_hanging_leaves_below(
323 region,
324 registry,
325 random,
326 config,
327 foliage_pos,
328 current_radius,
329 -1,
330 attachment.double_trunk,
331 placer.hanging_leaves_chance,
332 placer.hanging_leaves_extension_chance,
333 placement,
334 );
335 Self::place_tree_leaves_row_with_hanging_leaves_below(
336 region,
337 registry,
338 random,
339 config,
340 foliage_pos,
341 current_radius - 1,
342 -2,
343 attachment.double_trunk,
344 placer.hanging_leaves_chance,
345 placer.hanging_leaves_extension_chance,
346 placement,
347 );
348 }
349
350 fn create_dark_oak_tree_foliage(
351 region: &mut WorldGenRegion<'_>,
352 registry: &Registry,
353 random: &mut WorldgenRandom,
354 config: &TreeConfiguration,
355 attachment: FoliageAttachment,
356 leaf_radius: i32,
357 placement: &mut TreePlacement,
358 ) {
359 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
360 let pos = attachment.pos.above_n(offset);
361 if attachment.double_trunk {
362 Self::place_tree_leaves_row(
363 region,
364 registry,
365 random,
366 config,
367 pos,
368 leaf_radius + 2,
369 -1,
370 true,
371 placement,
372 );
373 Self::place_tree_leaves_row(
374 region,
375 registry,
376 random,
377 config,
378 pos,
379 leaf_radius + 3,
380 0,
381 true,
382 placement,
383 );
384 Self::place_tree_leaves_row(
385 region,
386 registry,
387 random,
388 config,
389 pos,
390 leaf_radius + 2,
391 1,
392 true,
393 placement,
394 );
395 if random.next_bool() {
396 Self::place_tree_leaves_row(
397 region,
398 registry,
399 random,
400 config,
401 pos,
402 leaf_radius,
403 2,
404 true,
405 placement,
406 );
407 }
408 } else {
409 Self::place_tree_leaves_row(
410 region,
411 registry,
412 random,
413 config,
414 pos,
415 leaf_radius + 2,
416 -1,
417 false,
418 placement,
419 );
420 Self::place_tree_leaves_row(
421 region,
422 registry,
423 random,
424 config,
425 pos,
426 leaf_radius + 1,
427 0,
428 false,
429 placement,
430 );
431 }
432 }
433
434 fn create_mega_pine_tree_foliage(
435 region: &mut WorldGenRegion<'_>,
436 registry: &Registry,
437 random: &mut WorldgenRandom,
438 config: &TreeConfiguration,
439 attachment: FoliageAttachment,
440 foliage_height: i32,
441 leaf_radius: i32,
442 placement: &mut TreePlacement,
443 ) {
444 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
445 let mut previous_radius = 0;
446 let min_y = attachment.pos.y() - foliage_height + offset;
447 let max_y = attachment.pos.y() + offset;
448
449 for y in min_y..=max_y {
450 let y_offset = attachment.pos.y() - y;
451 let smooth_radius = leaf_radius
452 + attachment.radius_offset
453 + fast_floor(f64::from(y_offset) / f64::from(foliage_height) * 3.5);
454 let jagged_radius = if y_offset > 0 && smooth_radius == previous_radius && (y & 1) == 0
455 {
456 smooth_radius + 1
457 } else {
458 smooth_radius
459 };
460 let row_origin = BlockPos::new(attachment.pos.x(), y, attachment.pos.z());
461 Self::place_tree_leaves_row(
462 region,
463 registry,
464 random,
465 config,
466 row_origin,
467 jagged_radius,
468 0,
469 attachment.double_trunk,
470 placement,
471 );
472 previous_radius = smooth_radius;
473 }
474 }
475
476 fn create_bush_tree_foliage(
477 region: &mut WorldGenRegion<'_>,
478 registry: &Registry,
479 random: &mut WorldgenRandom,
480 config: &TreeConfiguration,
481 attachment: FoliageAttachment,
482 foliage_height: i32,
483 leaf_radius: i32,
484 placement: &mut TreePlacement,
485 ) {
486 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
487 for y in (offset - foliage_height..=offset).rev() {
488 let current_radius = leaf_radius + attachment.radius_offset - 1 - y;
489 Self::place_tree_leaves_row(
490 region,
491 registry,
492 random,
493 config,
494 attachment.pos,
495 current_radius,
496 y,
497 attachment.double_trunk,
498 placement,
499 );
500 }
501 }
502
503 fn create_pine_tree_foliage(
504 region: &mut WorldGenRegion<'_>,
505 registry: &Registry,
506 random: &mut WorldgenRandom,
507 config: &TreeConfiguration,
508 attachment: FoliageAttachment,
509 foliage_height: i32,
510 leaf_radius: i32,
511 placement: &mut TreePlacement,
512 ) {
513 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
514 let mut current_radius = 0;
515 for y in (offset - foliage_height..=offset).rev() {
516 Self::place_tree_leaves_row(
517 region,
518 registry,
519 random,
520 config,
521 attachment.pos,
522 current_radius,
523 y,
524 attachment.double_trunk,
525 placement,
526 );
527 if current_radius >= 1 && y == offset - foliage_height + 1 {
528 current_radius -= 1;
529 } else if current_radius < leaf_radius + attachment.radius_offset {
530 current_radius += 1;
531 }
532 }
533 }
534
535 fn create_spruce_tree_foliage(
536 region: &mut WorldGenRegion<'_>,
537 registry: &Registry,
538 random: &mut WorldgenRandom,
539 config: &TreeConfiguration,
540 attachment: FoliageAttachment,
541 foliage_height: i32,
542 leaf_radius: i32,
543 placement: &mut TreePlacement,
544 ) {
545 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
546 let mut current_radius = random.next_i32_bounded(2);
547 let mut max_radius = 1;
548 let mut min_radius = 0;
549
550 for y in (-foliage_height..=offset).rev() {
551 Self::place_tree_leaves_row(
552 region,
553 registry,
554 random,
555 config,
556 attachment.pos,
557 current_radius,
558 y,
559 attachment.double_trunk,
560 placement,
561 );
562 if current_radius >= max_radius {
563 current_radius = min_radius;
564 min_radius = 1;
565 max_radius = (max_radius + 1).min(leaf_radius + attachment.radius_offset);
566 } else {
567 current_radius += 1;
568 }
569 }
570 }
571
572 fn create_blob_tree_foliage(
573 region: &mut WorldGenRegion<'_>,
574 registry: &Registry,
575 random: &mut WorldgenRandom,
576 config: &TreeConfiguration,
577 _placer: &BlobFoliagePlacer,
578 attachment: FoliageAttachment,
579 foliage_height: i32,
580 leaf_radius: i32,
581 placement: &mut TreePlacement,
582 ) {
583 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
584 for y in (offset - foliage_height..=offset).rev() {
585 let current_radius = (leaf_radius + attachment.radius_offset - 1 - y / 2).max(0);
586 Self::place_tree_leaves_row(
587 region,
588 registry,
589 random,
590 config,
591 attachment.pos,
592 current_radius,
593 y,
594 attachment.double_trunk,
595 placement,
596 );
597 }
598 }
599
600 fn create_acacia_tree_foliage(
601 region: &mut WorldGenRegion<'_>,
602 registry: &Registry,
603 random: &mut WorldgenRandom,
604 config: &TreeConfiguration,
605 attachment: FoliageAttachment,
606 foliage_height: i32,
607 leaf_radius: i32,
608 placement: &mut TreePlacement,
609 ) {
610 let offset = Self::tree_foliage_offset(random, &config.foliage_placer);
611 let foliage_pos = attachment.pos.above_n(offset);
612 Self::place_tree_leaves_row(
613 region,
614 registry,
615 random,
616 config,
617 foliage_pos,
618 leaf_radius + attachment.radius_offset,
619 -1 - foliage_height,
620 attachment.double_trunk,
621 placement,
622 );
623 Self::place_tree_leaves_row(
624 region,
625 registry,
626 random,
627 config,
628 foliage_pos,
629 leaf_radius - 1,
630 -foliage_height,
631 attachment.double_trunk,
632 placement,
633 );
634 Self::place_tree_leaves_row(
635 region,
636 registry,
637 random,
638 config,
639 foliage_pos,
640 leaf_radius + attachment.radius_offset - 1,
641 0,
642 attachment.double_trunk,
643 placement,
644 );
645 }
646
647 fn tree_foliage_offset(random: &mut WorldgenRandom, foliage_placer: &FoliagePlacer) -> i32 {
648 match foliage_placer {
649 FoliagePlacer::Blob(placer) => placer.offset.sample(random),
650 FoliagePlacer::Bush(placer) => placer.offset.sample(random),
651 FoliagePlacer::Fancy(placer) => placer.offset.sample(random),
652 FoliagePlacer::Pine(placer) => placer.offset.sample(random),
653 FoliagePlacer::Spruce(placer) => placer.offset.sample(random),
654 FoliagePlacer::MegaPine(placer) => placer.offset.sample(random),
655 FoliagePlacer::Acacia(placer) => placer.offset.sample(random),
656 FoliagePlacer::DarkOak(placer) => placer.offset.sample(random),
657 FoliagePlacer::Jungle(placer) => placer.offset.sample(random),
658 FoliagePlacer::RandomSpread(placer) => placer.offset.sample(random),
659 FoliagePlacer::Cherry(placer) => placer.offset.sample(random),
660 }
661 }
662
663 fn place_tree_leaves_row(
664 region: &mut WorldGenRegion<'_>,
665 registry: &Registry,
666 random: &mut WorldgenRandom,
667 config: &TreeConfiguration,
668 origin: BlockPos,
669 current_radius: i32,
670 y: i32,
671 double_trunk: bool,
672 placement: &mut TreePlacement,
673 ) {
674 let offset = i32::from(double_trunk);
675 for dx in -current_radius..=current_radius + offset {
676 for dz in -current_radius..=current_radius + offset {
677 if !Self::tree_foliage_should_skip_location(
678 random,
679 &config.foliage_placer,
680 dx,
681 y,
682 dz,
683 current_radius,
684 double_trunk,
685 ) {
686 let pos = origin.offset(dx, y, dz);
687 let _ =
688 Self::try_place_tree_leaf(region, registry, random, config, pos, placement);
689 }
690 }
691 }
692 }
693
694 #[expect(
695 clippy::too_many_arguments,
696 reason = "mirrors vanilla foliage row helper"
697 )]
698 fn place_tree_leaves_row_with_hanging_leaves_below(
699 region: &mut WorldGenRegion<'_>,
700 registry: &Registry,
701 random: &mut WorldgenRandom,
702 config: &TreeConfiguration,
703 origin: BlockPos,
704 current_radius: i32,
705 y: i32,
706 double_trunk: bool,
707 hanging_leaves_chance: f32,
708 hanging_leaves_extension_chance: f32,
709 placement: &mut TreePlacement,
710 ) {
711 Self::place_tree_leaves_row(
712 region,
713 registry,
714 random,
715 config,
716 origin,
717 current_radius,
718 y,
719 double_trunk,
720 placement,
721 );
722
723 let offset = i32::from(double_trunk);
724 let log_pos = origin.below();
725 for along_edge in Self::VANILLA_HORIZONTAL_DIRECTIONS {
726 let to_edge = along_edge.rotate_y_clockwise();
727 let offset_to_edge = if Self::direction_is_positive(to_edge) {
728 current_radius + offset
729 } else {
730 current_radius
731 };
732 let mut pos = origin
733 .offset(0, y - 1, 0)
734 .relative_n(to_edge, offset_to_edge)
735 .relative_n(along_edge, -current_radius);
736 let mut offset_along_edge = -current_radius;
737
738 while offset_along_edge < current_radius + offset {
739 let leaves_above = placement.foliage.contains(pos.above());
740 if leaves_above
741 && Self::try_place_hanging_leaf_extension(
742 region,
743 registry,
744 random,
745 config,
746 hanging_leaves_chance,
747 log_pos,
748 pos,
749 placement,
750 )
751 {
752 let _ = Self::try_place_hanging_leaf_extension(
753 region,
754 registry,
755 random,
756 config,
757 hanging_leaves_extension_chance,
758 log_pos,
759 pos.below(),
760 placement,
761 );
762 }
763
764 offset_along_edge += 1;
765 pos = pos.relative(along_edge);
766 }
767 }
768 }
769
770 fn try_place_hanging_leaf_extension(
771 region: &mut WorldGenRegion<'_>,
772 registry: &Registry,
773 random: &mut WorldgenRandom,
774 config: &TreeConfiguration,
775 chance: f32,
776 log_pos: BlockPos,
777 pos: BlockPos,
778 placement: &mut TreePlacement,
779 ) -> bool {
780 if Self::manhattan_distance(pos, log_pos) >= 7 || random.next_f32() > chance {
781 return false;
782 }
783
784 Self::try_place_tree_leaf(region, registry, random, config, pos, placement)
785 }
786
787 const fn direction_is_positive(direction: Direction) -> bool {
788 matches!(
789 direction,
790 Direction::Up | Direction::South | Direction::East
791 )
792 }
793
794 fn tree_foliage_should_skip_location(
795 random: &mut WorldgenRandom,
796 foliage_placer: &FoliagePlacer,
797 dx: i32,
798 y: i32,
799 dz: i32,
800 current_radius: i32,
801 double_trunk: bool,
802 ) -> bool {
803 if matches!(foliage_placer, FoliagePlacer::DarkOak(_)) {
804 return Self::dark_oak_foliage_should_skip_location(
805 dx,
806 y,
807 dz,
808 current_radius,
809 double_trunk,
810 );
811 }
812
813 let (dx, dz) = Self::foliage_signed_distances(dx, dz, double_trunk);
814 match foliage_placer {
815 FoliagePlacer::Blob(_) => {
816 Self::blob_foliage_should_skip_location(random, dx, y, dz, current_radius)
817 }
818 FoliagePlacer::Bush(_) => {
819 Self::bush_foliage_should_skip_location(random, dx, dz, current_radius)
820 }
821 FoliagePlacer::Fancy(_) => {
822 Self::fancy_foliage_should_skip_location(dx, dz, current_radius)
823 }
824 FoliagePlacer::Pine(_) | FoliagePlacer::Spruce(_) => {
825 Self::conifer_foliage_should_skip_location(dx, dz, current_radius)
826 }
827 FoliagePlacer::MegaPine(_) => {
828 Self::mega_pine_foliage_should_skip_location(dx, dz, current_radius)
829 }
830 FoliagePlacer::Jungle(_) => {
831 Self::mega_pine_foliage_should_skip_location(dx, dz, current_radius)
832 }
833 FoliagePlacer::RandomSpread(_) => false,
834 FoliagePlacer::Acacia(_) => {
835 Self::acacia_foliage_should_skip_location(dx, y, dz, current_radius)
836 }
837 FoliagePlacer::Cherry(placer) => {
838 Self::cherry_foliage_should_skip_location(random, placer, dx, y, dz, current_radius)
839 }
840 FoliagePlacer::DarkOak(_) => unreachable!(),
841 }
842 }
843
844 fn blob_foliage_should_skip_location(
845 random: &mut WorldgenRandom,
846 dx: i32,
847 y: i32,
848 dz: i32,
849 current_radius: i32,
850 ) -> bool {
851 dx == current_radius && dz == current_radius && (random.next_i32_bounded(2) == 0 || y == 0)
852 }
853
854 fn bush_foliage_should_skip_location(
855 random: &mut WorldgenRandom,
856 dx: i32,
857 dz: i32,
858 current_radius: i32,
859 ) -> bool {
860 dx == current_radius && dz == current_radius && random.next_i32_bounded(2) == 0
861 }
862
863 fn fancy_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
864 let dx = dx as f32 + 0.5;
865 let dz = dz as f32 + 0.5;
866 dx * dx + dz * dz > (current_radius * current_radius) as f32
867 }
868
869 fn dark_oak_foliage_should_skip_location(
870 dx: i32,
871 y: i32,
872 dz: i32,
873 current_radius: i32,
874 double_trunk: bool,
875 ) -> bool {
876 if y == 0
877 && double_trunk
878 && (dx == -current_radius || dx >= current_radius)
879 && (dz == -current_radius || dz >= current_radius)
880 {
881 return true;
882 }
883
884 let (dx, dz) = Self::foliage_signed_distances(dx, dz, double_trunk);
885 if y == -1 && !double_trunk {
886 dx == current_radius && dz == current_radius
887 } else if y == 1 {
888 dx + dz > current_radius * 2 - 2
889 } else {
890 false
891 }
892 }
893
894 fn cherry_foliage_should_skip_location(
895 random: &mut WorldgenRandom,
896 placer: &CherryFoliagePlacer,
897 dx: i32,
898 y: i32,
899 dz: i32,
900 current_radius: i32,
901 ) -> bool {
902 if y == -1
903 && (dx == current_radius || dz == current_radius)
904 && random.next_f32() < placer.wide_bottom_layer_hole_chance
905 {
906 return true;
907 }
908
909 let corner = dx == current_radius && dz == current_radius;
910 let wide_layer = current_radius > 2;
911 if wide_layer {
912 corner
913 || dx + dz > current_radius * 2 - 2 && random.next_f32() < placer.corner_hole_chance
914 } else {
915 corner && random.next_f32() < placer.corner_hole_chance
916 }
917 }
918
919 const fn conifer_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
920 dx == current_radius && dz == current_radius && current_radius > 0
921 }
922
923 const fn mega_pine_foliage_should_skip_location(dx: i32, dz: i32, current_radius: i32) -> bool {
924 dx + dz >= 7 || dx * dx + dz * dz > current_radius * current_radius
925 }
926
927 const fn acacia_foliage_should_skip_location(
928 dx: i32,
929 y: i32,
930 dz: i32,
931 current_radius: i32,
932 ) -> bool {
933 if y == 0 {
934 (dx > 1 || dz > 1) && dx != 0 && dz != 0
935 } else {
936 dx == current_radius && dz == current_radius && current_radius > 0
937 }
938 }
939
940 fn foliage_signed_distances(dx: i32, dz: i32, double_trunk: bool) -> (i32, i32) {
941 if double_trunk {
942 (
943 abs_i32(dx).min(abs_i32(dx - 1)),
944 abs_i32(dz).min(abs_i32(dz - 1)),
945 )
946 } else {
947 (abs_i32(dx), abs_i32(dz))
948 }
949 }
950
951 fn try_place_tree_leaf(
952 region: &mut WorldGenRegion<'_>,
953 registry: &Registry,
954 random: &mut WorldgenRandom,
955 config: &TreeConfiguration,
956 pos: BlockPos,
957 placement: &mut TreePlacement,
958 ) -> bool {
959 let current_state = region.block_state(pos);
960 let is_persistent = current_state
961 .try_get_value(&BlockStateProperties::PERSISTENT)
962 .unwrap_or(false);
963 let valid_tree_pos = current_state.is_air()
964 || current_state
965 .get_block()
966 .has_tag(&BlockTag::REPLACEABLE_BY_TREES);
967 if is_persistent || !valid_tree_pos {
968 return false;
969 }
970
971 let foliage_state = Self::sample_block_state_provider(
972 region,
973 registry,
974 random,
975 &config.foliage_provider,
976 pos,
977 );
978 let foliage_state = Self::copy_waterlogged_from(region, pos, foliage_state);
979 placement.set_foliage(region, pos, foliage_state);
980 true
981 }
982}
983
984#[cfg(test)]
985mod tests {
986 use super::*;
987
988 #[test]
989 fn acacia_top_layer_keeps_cross_and_inner_corners() {
990 assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(2, 0, 2, 2));
991 assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(1, 0, 2, 2));
992 assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(0, 0, 2, 2));
993 assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(1, 0, 1, 2));
994 }
995
996 #[test]
997 fn acacia_lower_layers_skip_only_outer_corners() {
998 assert!(FeatureDecorationRunner::acacia_foliage_should_skip_location(2, -1, 2, 2));
999 assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(1, -1, 2, 2));
1000 assert!(!FeatureDecorationRunner::acacia_foliage_should_skip_location(0, -1, 0, 0));
1001 }
1002
1003 #[test]
1004 fn conifer_layers_skip_only_nonzero_outer_corners() {
1005 assert!(FeatureDecorationRunner::conifer_foliage_should_skip_location(2, 2, 2));
1006 assert!(!FeatureDecorationRunner::conifer_foliage_should_skip_location(1, 2, 2));
1007 assert!(!FeatureDecorationRunner::conifer_foliage_should_skip_location(0, 0, 0));
1008 }
1009
1010 #[test]
1011 fn mega_pine_uses_circular_row_cutoff_with_vanilla_seven_limit() {
1012 assert!(FeatureDecorationRunner::mega_pine_foliage_should_skip_location(4, 3, 5));
1013 assert!(FeatureDecorationRunner::mega_pine_foliage_should_skip_location(4, 4, 5));
1014 assert!(!FeatureDecorationRunner::mega_pine_foliage_should_skip_location(3, 3, 5));
1015 }
1016
1017 #[test]
1018 fn fancy_foliage_uses_shifted_circular_cutoff() {
1019 assert!(!FeatureDecorationRunner::fancy_foliage_should_skip_location(0, 0, 1));
1020 assert!(FeatureDecorationRunner::fancy_foliage_should_skip_location(
1021 1, 0, 1
1022 ));
1023 assert!(FeatureDecorationRunner::fancy_foliage_should_skip_location(
1024 0, 0, 0
1025 ));
1026 }
1027
1028 #[test]
1029 fn dark_oak_double_trunk_skips_outer_corner_extensions_on_center_layer() {
1030 assert!(
1031 FeatureDecorationRunner::dark_oak_foliage_should_skip_location(-3, 0, -3, 3, true,)
1032 );
1033 assert!(FeatureDecorationRunner::dark_oak_foliage_should_skip_location(4, 0, 4, 3, true,));
1034 assert!(
1035 !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(-2, 0, -3, 3, true,)
1036 );
1037 }
1038
1039 #[test]
1040 fn dark_oak_side_crowns_skip_lower_outer_corners() {
1041 assert!(
1042 FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, -1, 2, 2, false,)
1043 );
1044 assert!(
1045 !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, -1, 1, 2, false,)
1046 );
1047 }
1048
1049 #[test]
1050 fn dark_oak_upper_layer_uses_diagonal_cutoff() {
1051 assert!(FeatureDecorationRunner::dark_oak_foliage_should_skip_location(3, 1, 2, 3, false,));
1052 assert!(
1053 !FeatureDecorationRunner::dark_oak_foliage_should_skip_location(2, 1, 2, 3, false,)
1054 );
1055 }
1056}