1use crate::{
2 CONFIG, Canvas,
3 column::ColumnSample,
4 sim::SimChunk,
5 util::{RandomField, close},
6};
7use common::{
8 calendar::{Calendar, CalendarEvent},
9 terrain::{Block, BlockKind, SpriteKind, sprite::SnowCovered},
10};
11use noise::NoiseFn;
12use num::traits::Pow;
13use rand::prelude::*;
14use std::f32;
15use vek::*;
16
17pub fn density_factor_by_altitude(lower_limit: f32, altitude: f32, upper_limit: f32) -> f32 {
27 let maximum: f32 = (upper_limit - lower_limit).pow(2) / 4.0f32;
28 (-((altitude - lower_limit) * (altitude - upper_limit)) / maximum).max(0.0)
29}
30
31const MUSH_FACT: f32 = 1.0e-4; const GRASS_FACT: f32 = 1.0e-3; const TREE_FACT: f32 = 0.15e-3; const DEPTH_WATER_NORM: f32 = 15.0; pub fn apply_scatter_to(canvas: &mut Canvas, _rng: &mut impl Rng, calendar: Option<&Calendar>) {
36 enum WaterMode {
37 Underwater,
38 Floating,
39 Ground,
40 }
41 use WaterMode::*;
42
43 use SpriteKind::*;
44
45 struct ScatterConfig {
46 kind: SpriteKind,
47 water_mode: WaterMode,
48 permit: fn(BlockKind) -> bool,
49 f: fn(&SimChunk, &ColumnSample) -> (f32, Option<(f32, f32, f32)>),
50 }
51
52 let scatter: &[ScatterConfig] = &[
54 ScatterConfig {
57 kind: BlueFlower,
58 water_mode: Ground,
59 permit: |b| matches!(b, BlockKind::Grass),
60 f: |_, col| {
61 (
62 close(col.temp, CONFIG.temperate_temp, 0.7).min(close(
63 col.humidity,
64 CONFIG.jungle_hum,
65 0.4,
66 )) * col.tree_density
67 * MUSH_FACT
68 * 256.0,
69 Some((0.0, 256.0, 0.25)),
70 )
71 },
72 },
73 ScatterConfig {
74 kind: PinkFlower,
75 water_mode: Ground,
76 permit: |b| matches!(b, BlockKind::Grass),
77 f: |_, col| {
78 (
79 close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
80 * col.tree_density
81 * MUSH_FACT
82 * 350.0,
83 Some((0.0, 100.0, 0.1)),
84 )
85 },
86 },
87 ScatterConfig {
88 kind: PurpleFlower,
89 water_mode: Ground,
90 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Snow),
91 f: |_, col| {
92 (
93 close(col.temp, CONFIG.temperate_temp, 0.7)
94 .max(close(col.temp, CONFIG.snow_temp, 0.7))
95 .min(close(col.humidity, CONFIG.jungle_hum, 0.4).max(close(
96 col.humidity,
97 CONFIG.forest_hum,
98 0.5,
99 )))
100 * col.tree_density
101 * MUSH_FACT
102 * 350.0,
103 Some((0.0, 100.0, 0.1)),
104 )
105 },
106 },
107 ScatterConfig {
108 kind: RedFlower,
109 water_mode: Ground,
110 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Snow),
111 f: |_, col| {
112 (
113 close(col.temp, CONFIG.tropical_temp, 0.7)
114 .max(close(col.temp, CONFIG.snow_temp, 0.7))
115 .min(close(col.humidity, CONFIG.jungle_hum, 0.4).max(close(
116 col.humidity,
117 CONFIG.forest_hum,
118 0.5,
119 )))
120 * col.tree_density
121 * MUSH_FACT
122 * 350.0,
123 Some((0.0, 100.0, 0.1)),
124 )
125 },
126 },
127 ScatterConfig {
128 kind: WhiteFlower,
129 water_mode: Ground,
130 permit: |b| matches!(b, BlockKind::Grass),
131 f: |_, col| {
132 (
133 close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
134 * col.tree_density
135 * MUSH_FACT
136 * 350.0,
137 Some((0.0, 100.0, 0.1)),
138 )
139 },
140 },
141 ScatterConfig {
142 kind: YellowFlower,
143 water_mode: Ground,
144 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Snow),
145 f: |_, col| {
146 (
147 close(col.temp, 0.0, 0.7)
148 .max(close(col.temp, CONFIG.snow_temp, 0.7))
149 .min(close(col.humidity, CONFIG.jungle_hum, 0.4).max(close(
150 col.humidity,
151 CONFIG.forest_hum,
152 0.5,
153 )))
154 * col.tree_density
155 * MUSH_FACT
156 * 350.0,
157 Some((0.0, 100.0, 0.1)),
158 )
159 },
160 },
161 ScatterConfig {
162 kind: Cotton,
163 water_mode: Ground,
164 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Grass),
165 f: |_, col| {
166 (
167 close(col.temp, CONFIG.tropical_temp, 0.7).min(close(
168 col.humidity,
169 CONFIG.jungle_hum,
170 0.4,
171 )) * col.tree_density
172 * MUSH_FACT
173 * 200.0
174 * (!col.snow_cover) as i32 as f32 * density_factor_by_altitude(-500.0 , col.alt, 500.0), Some((0.0, 128.0, 0.30)),
179 )
180 },
181 },
182 ScatterConfig {
183 kind: Sunflower,
184 water_mode: Ground,
185 permit: |b| matches!(b, BlockKind::Grass),
186 f: |_, col| {
187 (
188 close(col.temp, 0.0, 0.7).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
189 * col.tree_density
190 * MUSH_FACT
191 * 350.0,
192 Some((0.0, 100.0, 0.15)),
193 )
194 },
195 },
196 ScatterConfig {
197 kind: WildFlax,
198 water_mode: Ground,
199 permit: |b| matches!(b, BlockKind::Grass),
200 f: |_, col| {
201 (
202 close(col.temp, CONFIG.temperate_temp, 0.7).min(close(
203 col.humidity,
204 CONFIG.forest_hum,
205 0.4,
206 )) * col.tree_density
207 * MUSH_FACT
208 * 600.0
209 * density_factor_by_altitude(200.0, col.alt, 1000.0), Some((0.0, 100.0, 0.15)),
213 )
214 },
215 },
216 ScatterConfig {
218 kind: LingonBerry,
219 water_mode: Ground,
220 permit: |b| matches!(b, BlockKind::Grass),
221 f: |_, col| {
222 (
223 close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.5))
224 * MUSH_FACT
225 * 2.5,
226 None,
227 )
228 },
229 },
230 ScatterConfig {
231 kind: LeafyPlant,
232 water_mode: Ground,
233 permit: |b| matches!(b, BlockKind::Grass),
234 f: |_, col| {
235 (
236 close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.3))
237 * GRASS_FACT
238 * 4.0,
239 None,
240 )
241 },
242 },
243 ScatterConfig {
244 kind: JungleLeafyPlant,
245 water_mode: Ground,
246 permit: |b| matches!(b, BlockKind::Grass),
247 f: |_, col| {
248 (
249 close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
250 * GRASS_FACT
251 * 32.0,
252 Some((0.15, 64.0, 0.2)),
253 )
254 },
255 },
256 ScatterConfig {
257 kind: Fern,
258 water_mode: Ground,
259 permit: |b| matches!(b, BlockKind::Grass),
260 f: |_, col| {
261 (
262 close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.forest_hum, 0.5))
263 * GRASS_FACT
264 * 0.25,
265 Some((0.0, 64.0, 0.2)),
266 )
267 },
268 },
269 ScatterConfig {
270 kind: JungleFern,
271 water_mode: Ground,
272 permit: |b| matches!(b, BlockKind::Grass),
273 f: |_, col| {
274 (
275 close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
276 * col.tree_density
277 * MUSH_FACT
278 * 200.0,
279 Some((0.0, 84.0, 0.35)),
280 )
281 },
282 },
283 ScatterConfig {
284 kind: Blueberry,
285 water_mode: Ground,
286 permit: |b| matches!(b, BlockKind::Grass),
287 f: |_, col| {
288 (
289 close(col.temp, CONFIG.temperate_temp, 0.5).min(close(
290 col.humidity,
291 CONFIG.forest_hum,
292 0.5,
293 )) * MUSH_FACT
294 * 0.3,
295 None,
296 )
297 },
298 },
299 ScatterConfig {
300 kind: Pumpkin,
301 water_mode: Ground,
302 permit: |b| matches!(b, BlockKind::Grass),
303 f: if calendar.is_some_and(|calendar| calendar.is_event(CalendarEvent::Halloween)) {
304 |_, _| (0.1, Some((0.0003, 128.0, 0.1)))
305 } else {
306 |_, col| {
307 (
308 close(col.temp, CONFIG.temperate_temp, 0.5).min(close(
309 col.humidity,
310 CONFIG.forest_hum,
311 0.5,
312 )) * MUSH_FACT
313 * 500.0,
314 Some((0.0, 512.0, 0.05)),
315 )
316 }
317 },
318 },
319 ScatterConfig {
322 kind: Twigs,
323 water_mode: Ground,
324 permit: |b| matches!(b, BlockKind::Grass),
325 f: |_, col| {
326 (
327 (col.tree_density * 1.25 - 0.25).powf(0.5).max(0.0) * TREE_FACT * 5.0,
328 None,
329 )
330 },
331 },
332 ScatterConfig {
334 kind: Wood,
335 water_mode: Ground,
336 permit: |b| matches!(b, BlockKind::Grass),
337 f: |_, col| {
338 (
339 (col.tree_density * 1.25 - 0.25).powf(0.5).max(0.0) * TREE_FACT,
340 None,
341 )
342 },
343 },
344 ScatterConfig {
345 kind: Hardwood,
346 water_mode: Ground,
347 permit: |b| matches!(b, BlockKind::Grass),
348 f: |_, col| {
349 (
350 ((close(col.temp, CONFIG.tropical_temp + 0.1, 0.3).min(close(
351 col.humidity,
352 CONFIG.jungle_hum,
353 0.4,
354 )) > 0.0) as i32) as f32
355 * (col.tree_density * 1.25 - 0.25).powf(0.5).max(0.0)
356 * TREE_FACT
357 * 0.75,
358 None,
359 )
360 },
361 },
362 ScatterConfig {
372 kind: Frostwood,
373 water_mode: Ground,
374 permit: |b| matches!(b, BlockKind::Snow | BlockKind::Ice),
375 f: |_, col| {
376 (
377 (col.tree_density * 1.25 - 0.25).powf(0.5).max(0.0) * TREE_FACT * 0.5,
378 None,
379 )
380 },
381 },
382 ScatterConfig {
383 kind: Stones,
384 water_mode: Ground,
385 permit: |b| {
386 matches!(
387 b,
388 BlockKind::Earth
389 | BlockKind::Grass
390 | BlockKind::Rock
391 | BlockKind::Sand
392 | BlockKind::Snow
393 | BlockKind::Ice
394 )
395 },
396 f: |chunk, _| ((chunk.rockiness - 0.5).max(0.025) * 1.0e-3, None),
397 },
398 ScatterConfig {
399 kind: Copper,
400 water_mode: Ground,
401 permit: |b| {
402 matches!(
403 b,
404 BlockKind::Earth | BlockKind::Grass | BlockKind::Rock | BlockKind::Sand
405 )
406 },
407 f: |chunk, _| ((chunk.rockiness - 0.5).max(0.0) * 0.85e-3, None),
408 },
409 ScatterConfig {
410 kind: Tin,
411 water_mode: Ground,
412 permit: |b| {
413 matches!(
414 b,
415 BlockKind::Earth | BlockKind::Grass | BlockKind::Rock | BlockKind::Sand
416 )
417 },
418 f: |chunk, _| ((chunk.rockiness - 0.5).max(0.0) * 0.85e-3, None),
419 },
420 ScatterConfig {
422 kind: Mushroom,
423 water_mode: Ground,
424 permit: |b| matches!(b, BlockKind::Grass),
425 f: |_, col| {
426 (
427 close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.forest_hum, 0.35))
428 * MUSH_FACT,
429 None,
430 )
431 },
432 },
433 ScatterConfig {
435 kind: ShortGrass,
436 water_mode: Ground,
437 permit: |b| matches!(b, BlockKind::Grass),
438 f: |_, col| {
439 (
440 close(col.temp, 0.2, 0.75).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
441 * GRASS_FACT
442 * 150.0,
443 Some((0.3, 64.0, 0.3)),
444 )
445 },
446 },
447 ScatterConfig {
448 kind: ShortGrass,
449 water_mode: Ground,
450 permit: |b| matches!(b, BlockKind::Snow),
451 f: |_, col| {
452 (
453 close(col.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
454 col.humidity,
455 CONFIG.forest_hum,
456 0.5,
457 )) * GRASS_FACT
458 * 50.0,
459 Some((0.0, 48.0, 0.2)),
460 )
461 },
462 },
463 ScatterConfig {
464 kind: MediumGrass,
465 water_mode: Ground,
466 permit: |b| matches!(b, BlockKind::Grass),
467 f: |_, col| {
468 (
469 close(col.temp, 0.2, 0.6).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
470 * GRASS_FACT
471 * 120.0,
472 Some((0.3, 64.0, 0.3)),
473 )
474 },
475 },
476 ScatterConfig {
477 kind: LongGrass,
478 water_mode: Ground,
479 permit: |b| matches!(b, BlockKind::Grass),
480 f: |_, col| {
481 (
482 close(col.temp, 0.3, 0.35).min(close(col.humidity, CONFIG.jungle_hum, 0.3))
483 * GRASS_FACT
484 * 150.0,
485 Some((0.1, 48.0, 0.3)),
486 )
487 },
488 },
489 ScatterConfig {
490 kind: LongGrass,
491 water_mode: Ground,
492 permit: |b| matches!(b, BlockKind::Snow),
493 f: |_, col| {
494 (
495 close(col.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
496 col.humidity,
497 CONFIG.forest_hum,
498 0.5,
499 )) * GRASS_FACT
500 * 25.0,
501 Some((0.0, 48.0, 0.2)),
502 )
503 },
504 },
505 ScatterConfig {
506 kind: JungleRedGrass,
507 water_mode: Ground,
508 permit: |b| matches!(b, BlockKind::Grass),
509 f: |_, col| {
510 (
511 close(col.temp, 0.3, 0.4).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
512 * col.tree_density
513 * MUSH_FACT
514 * 350.0,
515 Some((0.0, 128.0, 0.25)),
516 )
517 },
518 },
519 ScatterConfig {
539 kind: TaigaGrass,
540 water_mode: Ground,
541 permit: |b| matches!(b, BlockKind::Grass),
542 f: |_, col| {
543 (
544 close(col.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
545 col.humidity,
546 CONFIG.forest_hum,
547 0.5,
548 )) * GRASS_FACT
549 * 100.0,
550 Some((0.0, 48.0, 0.2)),
551 )
552 },
553 },
554 ScatterConfig {
555 kind: Moonbell,
556 water_mode: Ground,
557 permit: |b| matches!(b, BlockKind::Grass),
558 f: |_, col| {
559 (
560 close(col.temp, CONFIG.snow_temp - 0.2, 0.4).min(close(
561 col.humidity,
562 CONFIG.forest_hum,
563 0.5,
564 )) * 0.003,
565 Some((0.0, 48.0, 0.2)),
566 )
567 },
568 },
569 ScatterConfig {
571 kind: SavannaGrass,
572 water_mode: Ground,
573 permit: |b| matches!(b, BlockKind::Grass),
574 f: |_, col| {
575 (
576 {
577 let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25);
578 let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1);
579 (savanna - desert * 5.0).max(0.0) * GRASS_FACT * 250.0
580 },
581 Some((0.15, 64.0, 0.2)),
582 )
583 },
584 },
585 ScatterConfig {
586 kind: TallSavannaGrass,
587 water_mode: Ground,
588 permit: |b| matches!(b, BlockKind::Grass),
589 f: |_, col| {
590 (
591 {
592 let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25);
593 let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1);
594 (savanna - desert * 5.0).max(0.0) * GRASS_FACT * 150.0
595 },
596 Some((0.1, 48.0, 0.2)),
597 )
598 },
599 },
600 ScatterConfig {
601 kind: RedSavannaGrass,
602 water_mode: Ground,
603 permit: |b| matches!(b, BlockKind::Grass),
604 f: |_, col| {
605 (
606 {
607 let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25);
608 let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1);
609 (savanna - desert * 5.0).max(0.0) * GRASS_FACT * 120.0
610 },
611 Some((0.15, 48.0, 0.25)),
612 )
613 },
614 },
615 ScatterConfig {
616 kind: SavannaBush,
617 water_mode: Ground,
618 permit: |b| matches!(b, BlockKind::Grass),
619 f: |_, col| {
620 (
621 {
622 let savanna = close(col.temp, 1.0, 0.4) * close(col.humidity, 0.2, 0.25);
623 let desert = close(col.temp, 1.0, 0.25) * close(col.humidity, 0.0, 0.1);
624 (savanna - desert * 5.0).max(0.0) * GRASS_FACT * 40.0
625 },
626 Some((0.1, 96.0, 0.15)),
627 )
628 },
629 },
630 ScatterConfig {
632 kind: DeadBush,
633 water_mode: Ground,
634 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Snow),
635 f: |_, col| {
636 (
637 close(col.temp, 1.0, 0.95)
638 .max(close(col.temp, CONFIG.snow_temp, 0.95))
639 .min(close(col.humidity, 0.0, 0.3).max(close(
640 col.humidity,
641 CONFIG.forest_hum,
642 0.3,
643 )))
644 * MUSH_FACT
645 * 7.5,
646 None,
647 )
648 },
649 },
650 ScatterConfig {
651 kind: Pyrebloom,
652 water_mode: Ground,
653 permit: |b| matches!(b, BlockKind::Grass),
654 f: |_, col| {
655 (
656 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
657 * MUSH_FACT
658 * 0.1,
659 None,
660 )
661 },
662 },
663 ScatterConfig {
664 kind: LargeCactus,
665 water_mode: Ground,
666 permit: |b| matches!(b, BlockKind::Grass),
667 f: |_, col| {
668 (
669 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
670 * MUSH_FACT
671 * 1.5,
672 None,
673 )
674 },
675 },
676 ScatterConfig {
677 kind: BarrelCactus,
678 water_mode: Ground,
679 permit: |b| matches!(b, BlockKind::Grass),
680 f: |_, col| {
681 (
682 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
683 * MUSH_FACT
684 * 2.0,
685 None,
686 )
687 },
688 },
689 ScatterConfig {
690 kind: TallCactus,
691 water_mode: Ground,
692 permit: |b| matches!(b, BlockKind::Grass),
693 f: |_, col| {
694 (
695 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
696 * MUSH_FACT
697 * 1.5,
698 None,
699 )
700 },
701 },
702 ScatterConfig {
703 kind: RoundCactus,
704 water_mode: Ground,
705 permit: |b| matches!(b, BlockKind::Grass),
706 f: |_, col| {
707 (
708 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
709 * MUSH_FACT
710 * 2.0,
711 None,
712 )
713 },
714 },
715 ScatterConfig {
716 kind: ShortCactus,
717 water_mode: Ground,
718 permit: |b| matches!(b, BlockKind::Grass),
719 f: |_, col| {
720 (
721 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
722 * MUSH_FACT
723 * 2.0,
724 None,
725 )
726 },
727 },
728 ScatterConfig {
729 kind: MedFlatCactus,
730 water_mode: Ground,
731 permit: |b| matches!(b, BlockKind::Grass),
732 f: |_, col| {
733 (
734 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
735 * MUSH_FACT
736 * 2.0,
737 None,
738 )
739 },
740 },
741 ScatterConfig {
742 kind: ShortFlatCactus,
743 water_mode: Ground,
744 permit: |b| matches!(b, BlockKind::Grass),
745 f: |_, col| {
746 (
747 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
748 * MUSH_FACT
749 * 2.0,
750 None,
751 )
752 },
753 },
754 ScatterConfig {
756 kind: ChestBuried,
757 water_mode: Underwater,
758 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
759 f: |_, col| {
760 (
761 MUSH_FACT
762 * 1.0e-6
763 * if col.alt < col.water_level - DEPTH_WATER_NORM + 30.0 {
764 1.0
765 } else {
766 0.0
767 },
768 None,
769 )
770 },
771 },
772 ScatterConfig {
774 kind: Mud,
775 water_mode: Underwater,
776 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
777 f: |_, col| {
778 (
779 MUSH_FACT
780 * 1.0e-3
781 * if col.alt < col.water_level - DEPTH_WATER_NORM {
782 1.0
783 } else {
784 0.0
785 },
786 None,
787 )
788 },
789 },
790 ScatterConfig {
792 kind: GrassBlue,
793 water_mode: Underwater,
794 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
795 f: |_, col| {
796 (
797 MUSH_FACT
798 * 250.0
799 * if col.alt < col.water_level - DEPTH_WATER_NORM {
800 1.0
801 } else {
802 0.0
803 },
804 Some((0.0, 100.0, 0.15)),
805 )
806 },
807 },
808 ScatterConfig {
810 kind: Seagrass,
811 water_mode: Underwater,
812 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
813 f: |_, col| {
814 (
815 close(col.temp, CONFIG.temperate_temp, 0.8)
816 * MUSH_FACT
817 * 300.0
818 * if col.water_level <= CONFIG.sea_level
819 && col.alt < col.water_level - DEPTH_WATER_NORM + 18.0
820 {
821 1.0
822 } else {
823 0.0
824 },
825 Some((0.0, 150.0, 0.3)),
826 )
827 },
828 },
829 ScatterConfig {
831 kind: Seagrass,
832 water_mode: Underwater,
833 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
834 f: |_, col| {
835 (
836 MUSH_FACT
837 * 600.0
838 * if col.water_level <= CONFIG.sea_level
839 && (col.water_level - col.alt) < 3.0
840 {
841 1.0
842 } else {
843 0.0
844 },
845 Some((0.0, 150.0, 0.4)),
846 )
847 },
848 },
849 ScatterConfig {
851 kind: SeaweedTemperate,
852 water_mode: Underwater,
853 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
854 f: |_, col| {
855 (
856 close(col.temp, CONFIG.temperate_temp, 0.8)
857 * MUSH_FACT
858 * 50.0
859 * if col.water_level <= CONFIG.sea_level
860 && col.alt < col.water_level - DEPTH_WATER_NORM + 11.0
861 {
862 1.0
863 } else {
864 0.0
865 },
866 Some((0.0, 500.0, 0.75)),
867 )
868 },
869 },
870 ScatterConfig {
872 kind: SeaweedTropical,
873 water_mode: Underwater,
874 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
875 f: |_, col| {
876 (
877 close(col.temp, 1.0, 0.95)
878 * MUSH_FACT
879 * 50.0
880 * if col.water_level <= CONFIG.sea_level
881 && col.alt < col.water_level - DEPTH_WATER_NORM + 11.0
882 {
883 1.0
884 } else {
885 0.0
886 },
887 Some((0.0, 500.0, 0.75)),
888 )
889 },
890 },
891 ScatterConfig {
893 kind: SeaGrapes,
894 water_mode: Underwater,
895 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
896 f: |_, col| {
897 (
898 MUSH_FACT
899 * 250.0
900 * if col.water_level <= CONFIG.sea_level
901 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
902 {
903 1.0
904 } else {
905 0.0
906 },
907 Some((0.0, 100.0, 0.15)),
908 )
909 },
910 },
911 ScatterConfig {
913 kind: WavyAlgae,
914 water_mode: Underwater,
915 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
916 f: |_, col| {
917 (
918 MUSH_FACT
919 * 250.0
920 * if col.water_level <= CONFIG.sea_level
921 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
922 {
923 1.0
924 } else {
925 0.0
926 },
927 Some((0.0, 100.0, 0.15)),
928 )
929 },
930 },
931 ScatterConfig {
933 kind: MermaidsFan,
934 water_mode: Underwater,
935 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
936 f: |_, col| {
937 (
938 close(col.temp, 1.0, 0.95)
939 * MUSH_FACT
940 * 500.0
941 * if col.water_level <= CONFIG.sea_level
942 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
943 {
944 1.0
945 } else {
946 0.0
947 },
948 Some((0.0, 50.0, 0.10)),
949 )
950 },
951 },
952 ScatterConfig {
954 kind: SeaAnemone,
955 water_mode: Underwater,
956 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
957 f: |_, col| {
958 (
959 close(col.temp, CONFIG.temperate_temp, 0.8)
960 * MUSH_FACT
961 * 125.0
962 * if col.water_level <= CONFIG.sea_level
963 && col.alt < col.water_level - DEPTH_WATER_NORM - 9.0
964 {
965 1.0
966 } else {
967 0.0
968 },
969 Some((0.0, 100.0, 0.3)),
970 )
971 },
972 },
973 ScatterConfig {
975 kind: GiantKelp,
976 water_mode: Underwater,
977 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
978 f: |_, col| {
979 (
980 close(col.temp, CONFIG.temperate_temp, 0.8)
981 * MUSH_FACT
982 * 220.0
983 * if col.water_level <= CONFIG.sea_level
984 && col.alt < col.water_level - DEPTH_WATER_NORM - 9.0
985 {
986 1.0
987 } else {
988 0.0
989 },
990 Some((0.0, 200.0, 0.4)),
991 )
992 },
993 },
994 ScatterConfig {
996 kind: BullKelp,
997 water_mode: Underwater,
998 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
999 f: |_, col| {
1000 (
1001 close(col.temp, CONFIG.temperate_temp, 0.7)
1002 * MUSH_FACT
1003 * 300.0
1004 * if col.water_level <= CONFIG.sea_level
1005 && col.alt < col.water_level - DEPTH_WATER_NORM + 3.0
1006 {
1007 1.0
1008 } else {
1009 0.0
1010 },
1011 Some((0.0, 75.0, 0.3)),
1012 )
1013 },
1014 },
1015 ScatterConfig {
1017 kind: StonyCoral,
1018 water_mode: Underwater,
1019 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
1020 f: |_, col| {
1021 (
1022 close(col.temp, 1.0, 0.9)
1023 * MUSH_FACT
1024 * 160.0
1025 * if col.water_level <= CONFIG.sea_level
1026 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
1027 {
1028 1.0
1029 } else {
1030 0.0
1031 },
1032 Some((0.0, 120.0, 0.4)),
1033 )
1034 },
1035 },
1036 ScatterConfig {
1038 kind: SoftCoral,
1039 water_mode: Underwater,
1040 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
1041 f: |_, col| {
1042 (
1043 close(col.temp, 1.0, 0.9)
1044 * MUSH_FACT
1045 * 120.0
1046 * if col.water_level <= CONFIG.sea_level
1047 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
1048 {
1049 1.0
1050 } else {
1051 0.0
1052 },
1053 Some((0.0, 120.0, 0.4)),
1054 )
1055 },
1056 },
1057 ScatterConfig {
1059 kind: Seashells,
1060 water_mode: Underwater,
1061 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
1062 f: |c, col| {
1063 (
1064 (c.rockiness - 0.5).max(0.0)
1065 * 1.0e-3
1066 * if col.water_level <= CONFIG.sea_level
1067 && col.alt < col.water_level - DEPTH_WATER_NORM + 20.0
1068 {
1069 1.0
1070 } else {
1071 0.0
1072 },
1073 None,
1074 )
1075 },
1076 },
1077 ScatterConfig {
1078 kind: Stones,
1079 water_mode: Underwater,
1080 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
1081 f: |c, col| {
1082 (
1083 (c.rockiness - 0.5).max(0.0)
1084 * 1.0e-3
1085 * if col.alt < col.water_level - DEPTH_WATER_NORM {
1086 1.0
1087 } else {
1088 0.0
1089 },
1090 None,
1091 )
1092 },
1093 },
1094 ScatterConfig {
1096 kind: LillyPads,
1097 water_mode: Floating,
1098 permit: |_| true,
1099 f: |_, col| {
1100 (
1101 close(col.temp, 0.2, 0.6).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
1102 * GRASS_FACT
1103 * 100.0
1104 * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0)
1105 * col
1106 .water_dist
1107 .map_or(0.0, |d| 1.0 / (1.0 + (d.abs() * 0.4).powi(2))),
1108 Some((0.0, 128.0, 0.35)),
1109 )
1110 },
1111 },
1112 ScatterConfig {
1113 kind: Reed,
1114 water_mode: Underwater,
1115 permit: |b| matches!(b, BlockKind::Grass),
1116 f: |_, col| {
1117 (
1118 close(col.temp, 0.2, 0.6).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
1119 * GRASS_FACT
1120 * 100.0
1121 * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0)
1122 * col
1123 .water_dist
1124 .map_or(0.0, |d| 1.0 / (1.0 + (d.abs() * 0.40).powi(2))),
1125 Some((0.2, 128.0, 0.5)),
1126 )
1127 },
1128 },
1129 ScatterConfig {
1130 kind: Reed,
1131 water_mode: Ground,
1132 permit: |b| matches!(b, BlockKind::Grass),
1133 f: |_, col| {
1134 (
1135 close(col.humidity, CONFIG.jungle_hum, 0.9)
1136 * col
1137 .water_dist
1138 .map(|wd| Lerp::lerp(0.2, 0.0, (wd / 8.0).clamped(0.0, 1.0)))
1139 .unwrap_or(0.0)
1140 * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0),
1141 Some((0.2, 128.0, 0.5)),
1142 )
1143 },
1144 },
1145 ScatterConfig {
1146 kind: Bamboo,
1147 water_mode: Ground,
1148 permit: |b| matches!(b, BlockKind::Grass),
1149 f: |_, col| {
1150 (
1151 0.014
1152 * close(col.humidity, CONFIG.jungle_hum, 0.9)
1153 * col
1154 .water_dist
1155 .map(|wd| Lerp::lerp(0.2, 0.0, (wd / 8.0).clamped(0.0, 1.0)))
1156 .unwrap_or(0.0)
1157 * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0),
1158 Some((0.2, 128.0, 0.5)),
1159 )
1160 },
1161 },
1162 ];
1163
1164 canvas.foreach_col(|canvas, wpos2d, col| {
1165 let underwater = col.water_level.floor() > col.alt;
1166
1167 let kind = scatter.iter().enumerate().find_map(
1168 |(
1169 i,
1170 ScatterConfig {
1171 kind,
1172 water_mode,
1173 permit,
1174 f,
1175 },
1176 )| {
1177 let block_kind = canvas
1178 .get(Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32))
1179 .kind();
1180 if !permit(block_kind) {
1181 return None;
1182 }
1183 let snow_covered = matches!(block_kind, BlockKind::Snow | BlockKind::Ice);
1184 let (density, patch) = f(canvas.chunk(), col);
1185 let density = patch
1186 .map(|(base_density_prop, wavelen, threshold)| {
1187 if canvas
1188 .index()
1189 .noise
1190 .scatter_nz
1191 .get(
1192 wpos2d
1193 .map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0)
1194 .into_array(),
1195 )
1196 .abs()
1197 > 1.0 - threshold as f64
1198 {
1199 density
1200 } else {
1201 density * base_density_prop
1202 }
1203 })
1204 .unwrap_or(density);
1205 if density > 0.0
1206 && RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
1208 && matches!(&water_mode, Underwater | Floating) == underwater
1209 {
1210 Some((*kind, snow_covered, water_mode))
1211 } else {
1212 None
1213 }
1214 },
1215 );
1216
1217 if let Some((kind, snow_covered, water_mode)) = kind {
1218 let (alt, is_under): (_, fn(Block) -> bool) = match water_mode {
1219 Ground | Underwater => (col.alt as i32, |block| block.is_solid()),
1220 Floating => (col.water_level as i32, |block| !block.is_air()),
1221 };
1222
1223 if let Some(solid_end) = (-4..8)
1226 .find(|z| is_under(canvas.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z))))
1227 .and_then(|solid_start| {
1228 (1..8)
1229 .map(|z| solid_start + z)
1230 .find(|z| !is_under(canvas.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z))))
1231 })
1232 {
1233 canvas.map_resource(Vec3::new(wpos2d.x, wpos2d.y, alt + solid_end), |block| {
1234 let mut block = block.with_sprite(kind);
1235 if block.sprite_category().is_some_and(|category| category.has_attr::<SnowCovered>()) {
1236 block = block.with_attr(SnowCovered(snow_covered)).expect("`Category::has_attr` should have ensured setting the attribute will succeed");
1237 }
1238
1239 block
1240 });
1241 }
1242 }
1243 });
1244}