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.45))
640 * MUSH_FACT
641 * 7.5,
642 None,
643 )
644 },
645 },
646 ScatterConfig {
647 kind: Pyrebloom,
648 water_mode: Ground,
649 permit: |b| matches!(b, BlockKind::Grass),
650 f: |_, col| {
651 (
652 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
653 * MUSH_FACT
654 * 0.1,
655 None,
656 )
657 },
658 },
659 ScatterConfig {
660 kind: LargeCactus,
661 water_mode: Ground,
662 permit: |b| matches!(b, BlockKind::Grass),
663 f: |_, col| {
664 (
665 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
666 * MUSH_FACT
667 * 1.5,
668 None,
669 )
670 },
671 },
672 ScatterConfig {
673 kind: BarrelCactus,
674 water_mode: Ground,
675 permit: |b| matches!(b, BlockKind::Grass),
676 f: |_, col| {
677 (
678 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
679 * MUSH_FACT
680 * 2.0,
681 None,
682 )
683 },
684 },
685 ScatterConfig {
686 kind: TallCactus,
687 water_mode: Ground,
688 permit: |b| matches!(b, BlockKind::Grass),
689 f: |_, col| {
690 (
691 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
692 * MUSH_FACT
693 * 1.5,
694 None,
695 )
696 },
697 },
698 ScatterConfig {
699 kind: RoundCactus,
700 water_mode: Ground,
701 permit: |b| matches!(b, BlockKind::Grass),
702 f: |_, col| {
703 (
704 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
705 * MUSH_FACT
706 * 2.0,
707 None,
708 )
709 },
710 },
711 ScatterConfig {
712 kind: ShortCactus,
713 water_mode: Ground,
714 permit: |b| matches!(b, BlockKind::Grass),
715 f: |_, col| {
716 (
717 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
718 * MUSH_FACT
719 * 2.0,
720 None,
721 )
722 },
723 },
724 ScatterConfig {
725 kind: MedFlatCactus,
726 water_mode: Ground,
727 permit: |b| matches!(b, BlockKind::Grass),
728 f: |_, col| {
729 (
730 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
731 * MUSH_FACT
732 * 2.0,
733 None,
734 )
735 },
736 },
737 ScatterConfig {
738 kind: ShortFlatCactus,
739 water_mode: Ground,
740 permit: |b| matches!(b, BlockKind::Grass),
741 f: |_, col| {
742 (
743 close(col.temp, CONFIG.desert_temp, 0.25).min(close(col.humidity, 0.0, 0.2))
744 * MUSH_FACT
745 * 2.0,
746 None,
747 )
748 },
749 },
750 ScatterConfig {
752 kind: ChestBuried,
753 water_mode: Underwater,
754 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
755 f: |_, col| {
756 (
757 MUSH_FACT
758 * 1.0e-6
759 * if col.alt < col.water_level - DEPTH_WATER_NORM + 30.0 {
760 1.0
761 } else {
762 0.0
763 },
764 None,
765 )
766 },
767 },
768 ScatterConfig {
770 kind: Mud,
771 water_mode: Underwater,
772 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
773 f: |_, col| {
774 (
775 MUSH_FACT
776 * 1.0e-3
777 * if col.alt < col.water_level - DEPTH_WATER_NORM {
778 1.0
779 } else {
780 0.0
781 },
782 None,
783 )
784 },
785 },
786 ScatterConfig {
788 kind: GrassBlue,
789 water_mode: Underwater,
790 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
791 f: |_, col| {
792 (
793 MUSH_FACT
794 * 250.0
795 * if col.alt < col.water_level - DEPTH_WATER_NORM {
796 1.0
797 } else {
798 0.0
799 },
800 Some((0.0, 100.0, 0.15)),
801 )
802 },
803 },
804 ScatterConfig {
806 kind: Seagrass,
807 water_mode: Underwater,
808 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
809 f: |_, col| {
810 (
811 close(col.temp, CONFIG.temperate_temp, 0.8)
812 * MUSH_FACT
813 * 300.0
814 * if col.water_level <= CONFIG.sea_level
815 && col.alt < col.water_level - DEPTH_WATER_NORM + 18.0
816 {
817 1.0
818 } else {
819 0.0
820 },
821 Some((0.0, 150.0, 0.3)),
822 )
823 },
824 },
825 ScatterConfig {
827 kind: Seagrass,
828 water_mode: Underwater,
829 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
830 f: |_, col| {
831 (
832 MUSH_FACT
833 * 600.0
834 * if col.water_level <= CONFIG.sea_level
835 && (col.water_level - col.alt) < 3.0
836 {
837 1.0
838 } else {
839 0.0
840 },
841 Some((0.0, 150.0, 0.4)),
842 )
843 },
844 },
845 ScatterConfig {
847 kind: SeaweedTemperate,
848 water_mode: Underwater,
849 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
850 f: |_, col| {
851 (
852 close(col.temp, CONFIG.temperate_temp, 0.8)
853 * MUSH_FACT
854 * 50.0
855 * if col.water_level <= CONFIG.sea_level
856 && col.alt < col.water_level - DEPTH_WATER_NORM + 11.0
857 {
858 1.0
859 } else {
860 0.0
861 },
862 Some((0.0, 500.0, 0.75)),
863 )
864 },
865 },
866 ScatterConfig {
868 kind: SeaweedTropical,
869 water_mode: Underwater,
870 permit: |b| matches!(b, BlockKind::Grass | BlockKind::Sand),
871 f: |_, col| {
872 (
873 close(col.temp, 1.0, 0.95)
874 * MUSH_FACT
875 * 50.0
876 * if col.water_level <= CONFIG.sea_level
877 && col.alt < col.water_level - DEPTH_WATER_NORM + 11.0
878 {
879 1.0
880 } else {
881 0.0
882 },
883 Some((0.0, 500.0, 0.75)),
884 )
885 },
886 },
887 ScatterConfig {
889 kind: SeaGrapes,
890 water_mode: Underwater,
891 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
892 f: |_, col| {
893 (
894 MUSH_FACT
895 * 250.0
896 * if col.water_level <= CONFIG.sea_level
897 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
898 {
899 1.0
900 } else {
901 0.0
902 },
903 Some((0.0, 100.0, 0.15)),
904 )
905 },
906 },
907 ScatterConfig {
909 kind: WavyAlgae,
910 water_mode: Underwater,
911 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
912 f: |_, col| {
913 (
914 MUSH_FACT
915 * 250.0
916 * if col.water_level <= CONFIG.sea_level
917 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
918 {
919 1.0
920 } else {
921 0.0
922 },
923 Some((0.0, 100.0, 0.15)),
924 )
925 },
926 },
927 ScatterConfig {
929 kind: MermaidsFan,
930 water_mode: Underwater,
931 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
932 f: |_, col| {
933 (
934 close(col.temp, 1.0, 0.95)
935 * MUSH_FACT
936 * 500.0
937 * if col.water_level <= CONFIG.sea_level
938 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
939 {
940 1.0
941 } else {
942 0.0
943 },
944 Some((0.0, 50.0, 0.10)),
945 )
946 },
947 },
948 ScatterConfig {
950 kind: SeaAnemone,
951 water_mode: Underwater,
952 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
953 f: |_, col| {
954 (
955 close(col.temp, CONFIG.temperate_temp, 0.8)
956 * MUSH_FACT
957 * 125.0
958 * if col.water_level <= CONFIG.sea_level
959 && col.alt < col.water_level - DEPTH_WATER_NORM - 9.0
960 {
961 1.0
962 } else {
963 0.0
964 },
965 Some((0.0, 100.0, 0.3)),
966 )
967 },
968 },
969 ScatterConfig {
971 kind: GiantKelp,
972 water_mode: Underwater,
973 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
974 f: |_, col| {
975 (
976 close(col.temp, CONFIG.temperate_temp, 0.8)
977 * MUSH_FACT
978 * 220.0
979 * if col.water_level <= CONFIG.sea_level
980 && col.alt < col.water_level - DEPTH_WATER_NORM - 9.0
981 {
982 1.0
983 } else {
984 0.0
985 },
986 Some((0.0, 200.0, 0.4)),
987 )
988 },
989 },
990 ScatterConfig {
992 kind: BullKelp,
993 water_mode: Underwater,
994 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
995 f: |_, col| {
996 (
997 close(col.temp, CONFIG.temperate_temp, 0.7)
998 * MUSH_FACT
999 * 300.0
1000 * if col.water_level <= CONFIG.sea_level
1001 && col.alt < col.water_level - DEPTH_WATER_NORM + 3.0
1002 {
1003 1.0
1004 } else {
1005 0.0
1006 },
1007 Some((0.0, 75.0, 0.3)),
1008 )
1009 },
1010 },
1011 ScatterConfig {
1013 kind: StonyCoral,
1014 water_mode: Underwater,
1015 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
1016 f: |_, col| {
1017 (
1018 close(col.temp, 1.0, 0.9)
1019 * MUSH_FACT
1020 * 160.0
1021 * if col.water_level <= CONFIG.sea_level
1022 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
1023 {
1024 1.0
1025 } else {
1026 0.0
1027 },
1028 Some((0.0, 120.0, 0.4)),
1029 )
1030 },
1031 },
1032 ScatterConfig {
1034 kind: SoftCoral,
1035 water_mode: Underwater,
1036 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
1037 f: |_, col| {
1038 (
1039 close(col.temp, 1.0, 0.9)
1040 * MUSH_FACT
1041 * 120.0
1042 * if col.water_level <= CONFIG.sea_level
1043 && col.alt < col.water_level - DEPTH_WATER_NORM + 10.0
1044 {
1045 1.0
1046 } else {
1047 0.0
1048 },
1049 Some((0.0, 120.0, 0.4)),
1050 )
1051 },
1052 },
1053 ScatterConfig {
1055 kind: Seashells,
1056 water_mode: Underwater,
1057 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
1058 f: |c, col| {
1059 (
1060 (c.rockiness - 0.5).max(0.0)
1061 * 1.0e-3
1062 * if col.water_level <= CONFIG.sea_level
1063 && col.alt < col.water_level - DEPTH_WATER_NORM + 20.0
1064 {
1065 1.0
1066 } else {
1067 0.0
1068 },
1069 None,
1070 )
1071 },
1072 },
1073 ScatterConfig {
1074 kind: Stones,
1075 water_mode: Underwater,
1076 permit: |b| matches!(b, BlockKind::Earth | BlockKind::Sand),
1077 f: |c, col| {
1078 (
1079 (c.rockiness - 0.5).max(0.0)
1080 * 1.0e-3
1081 * if col.alt < col.water_level - DEPTH_WATER_NORM {
1082 1.0
1083 } else {
1084 0.0
1085 },
1086 None,
1087 )
1088 },
1089 },
1090 ScatterConfig {
1092 kind: LillyPads,
1093 water_mode: Floating,
1094 permit: |_| true,
1095 f: |_, col| {
1096 (
1097 close(col.temp, 0.2, 0.6).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
1098 * GRASS_FACT
1099 * 100.0
1100 * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0)
1101 * col
1102 .water_dist
1103 .map_or(0.0, |d| 1.0 / (1.0 + (d.abs() * 0.4).powi(2))),
1104 Some((0.0, 128.0, 0.35)),
1105 )
1106 },
1107 },
1108 ScatterConfig {
1109 kind: Reed,
1110 water_mode: Underwater,
1111 permit: |b| matches!(b, BlockKind::Grass),
1112 f: |_, col| {
1113 (
1114 close(col.temp, 0.2, 0.6).min(close(col.humidity, CONFIG.jungle_hum, 0.4))
1115 * GRASS_FACT
1116 * 100.0
1117 * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0)
1118 * col
1119 .water_dist
1120 .map_or(0.0, |d| 1.0 / (1.0 + (d.abs() * 0.40).powi(2))),
1121 Some((0.2, 128.0, 0.5)),
1122 )
1123 },
1124 },
1125 ScatterConfig {
1126 kind: Reed,
1127 water_mode: Ground,
1128 permit: |b| matches!(b, BlockKind::Grass),
1129 f: |_, col| {
1130 (
1131 close(col.humidity, CONFIG.jungle_hum, 0.9)
1132 * col
1133 .water_dist
1134 .map(|wd| Lerp::lerp(0.2, 0.0, (wd / 8.0).clamped(0.0, 1.0)))
1135 .unwrap_or(0.0)
1136 * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0),
1137 Some((0.2, 128.0, 0.5)),
1138 )
1139 },
1140 },
1141 ScatterConfig {
1142 kind: Bamboo,
1143 water_mode: Ground,
1144 permit: |b| matches!(b, BlockKind::Grass),
1145 f: |_, col| {
1146 (
1147 0.014
1148 * close(col.humidity, CONFIG.jungle_hum, 0.9)
1149 * col
1150 .water_dist
1151 .map(|wd| Lerp::lerp(0.2, 0.0, (wd / 8.0).clamped(0.0, 1.0)))
1152 .unwrap_or(0.0)
1153 * ((col.alt - CONFIG.sea_level) / 12.0).clamped(0.0, 1.0),
1154 Some((0.2, 128.0, 0.5)),
1155 )
1156 },
1157 },
1158 ];
1159
1160 canvas.foreach_col(|canvas, wpos2d, col| {
1161 let underwater = col.water_level.floor() > col.alt;
1162
1163 let kind = scatter.iter().enumerate().find_map(
1164 |(
1165 i,
1166 ScatterConfig {
1167 kind,
1168 water_mode,
1169 permit,
1170 f,
1171 },
1172 )| {
1173 let block_kind = canvas
1174 .get(Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32))
1175 .kind();
1176 if !permit(block_kind) {
1177 return None;
1178 }
1179 let snow_covered = matches!(block_kind, BlockKind::Snow | BlockKind::Ice);
1180 let (density, patch) = f(canvas.chunk(), col);
1181 let density = patch
1182 .map(|(base_density_prop, wavelen, threshold)| {
1183 if canvas
1184 .index()
1185 .noise
1186 .scatter_nz
1187 .get(
1188 wpos2d
1189 .map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0)
1190 .into_array(),
1191 )
1192 .abs()
1193 > 1.0 - threshold as f64
1194 {
1195 density
1196 } else {
1197 density * base_density_prop
1198 }
1199 })
1200 .unwrap_or(density);
1201 if density > 0.0
1202 && RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
1204 && matches!(&water_mode, Underwater | Floating) == underwater
1205 {
1206 Some((*kind, snow_covered, water_mode))
1207 } else {
1208 None
1209 }
1210 },
1211 );
1212
1213 if let Some((kind, snow_covered, water_mode)) = kind {
1214 let (alt, is_under): (_, fn(Block) -> bool) = match water_mode {
1215 Ground | Underwater => (col.alt as i32, |block| block.is_solid()),
1216 Floating => (col.water_level as i32, |block| !block.is_air()),
1217 };
1218
1219 if let Some(solid_end) = (-4..8)
1222 .find(|z| is_under(canvas.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z))))
1223 .and_then(|solid_start| {
1224 (1..8)
1225 .map(|z| solid_start + z)
1226 .find(|z| !is_under(canvas.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z))))
1227 })
1228 {
1229 canvas.map_resource(Vec3::new(wpos2d.x, wpos2d.y, alt + solid_end), |block| {
1230 let mut block = block.with_sprite(kind);
1231 if block.sprite_category().is_some_and(|category| category.has_attr::<SnowCovered>()) {
1232 block = block.with_attr(SnowCovered(snow_covered)).expect("`Category::has_attr` should have ensured setting the attribute will succeed");
1233 }
1234
1235 block
1236 });
1237 }
1238 }
1239 });
1240}