1pub mod cave;
2pub mod rock;
3pub mod scatter;
4pub mod shrub;
5pub mod spot;
6pub mod tree;
7pub mod wildlife;
8
9pub use self::{
10 cave::apply_caves_to, rock::apply_rocks_to, scatter::apply_scatter_to, shrub::apply_shrubs_to,
11 spot::apply_spots_to, tree::apply_trees_to,
12};
13
14use crate::{
15 Canvas, CanvasInfo,
16 column::ColumnSample,
17 config::CONFIG,
18 sim,
19 util::{FastNoise, RandomPerm, Sampler},
20};
21use common::terrain::{Block, BlockKind, SpriteKind};
22use hashbrown::HashMap;
23use noise::NoiseFn;
24use rand::prelude::*;
25use serde::Deserialize;
26use std::{
27 f32,
28 ops::{Add, Mul, Range, Sub},
29};
30use vek::*;
31
32#[derive(Deserialize)]
33pub struct Colors {
34 pub bridge: (u8, u8, u8),
35}
36
37const EMPTY_AIR: Block = Block::empty();
38
39pub struct PathLocals {
40 pub riverless_alt: f32,
41 pub alt: f32,
42 pub water_dist: f32,
43 pub bridge_offset: f32,
44 pub depth: i32,
45}
46
47impl PathLocals {
48 pub fn new(info: &CanvasInfo, col: &ColumnSample, path_nearest: Vec2<f32>) -> PathLocals {
49 let col_pos = -info.wpos().map(|e| e as f32) + path_nearest;
52 let col00 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 0));
53 let col10 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 0));
54 let col01 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 1));
55 let col11 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 1));
56 let col_attr = |col: &ColumnSample| {
57 Vec3::new(col.riverless_alt, col.alt, col.water_dist.unwrap_or(1000.0))
58 };
59 let [riverless_alt, alt, water_dist] = match (col00, col10, col01, col11) {
60 (Some(col00), Some(col10), Some(col01), Some(col11)) => Lerp::lerp(
61 Lerp::lerp(col_attr(col00), col_attr(col10), path_nearest.x.fract()),
62 Lerp::lerp(col_attr(col01), col_attr(col11), path_nearest.x.fract()),
63 path_nearest.y.fract(),
64 ),
65 _ => col_attr(col),
66 }
67 .into_array();
68 let (bridge_offset, depth) = (
69 ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0,
70 ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs())
71 * (riverless_alt + 5.0 - alt).max(0.0)
72 * 1.75
73 + 3.0) as i32,
74 );
75 PathLocals {
76 riverless_alt,
77 alt,
78 water_dist,
79 bridge_offset,
80 depth,
81 }
82 }
83}
84
85pub fn apply_paths_to(canvas: &mut Canvas) {
86 canvas.foreach_col(|canvas, wpos2d, col| {
87 if let Some((path_dist, path_nearest, path, _)) =
88 col.path.filter(|(dist, _, path, _)| *dist < path.width)
89 {
90 let inset = 0;
91
92 let PathLocals {
93 riverless_alt,
94 alt: _,
95 water_dist: _,
96 bridge_offset: _,
97 depth: _,
98 } = PathLocals::new(&canvas.info(), col, path_nearest);
99
100 let depth = 4;
101 let surface_z = riverless_alt.floor() as i32;
102
103 for z in inset - depth..inset {
104 let wpos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z);
105 let path_color =
106 path.surface_color(col.sub_surface_color.map(|e| (e * 255.0) as u8), wpos);
107 canvas.set(wpos, Block::new(BlockKind::Earth, path_color));
108 }
109 let head_space = path.head_space(path_dist);
110 for z in inset..inset + head_space {
111 let pos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z);
112 if canvas.get(pos).kind() != BlockKind::Water {
113 canvas.set(pos, EMPTY_AIR);
114 }
115 }
116 }
117 });
118}
119
120pub fn apply_trains_to(
121 canvas: &mut Canvas,
122 sim: &sim::WorldSim,
123 sim_chunk: &sim::SimChunk,
124 chunk_center_wpos2d: Vec2<i32>,
125) {
126 let mut splines = Vec::new();
127 let g = |v: Vec2<f32>| -> Vec3<f32> {
128 let path_nearest = sim
129 .get_nearest_path(v.as_::<i32>())
130 .map(|x| x.1)
131 .unwrap_or(v.as_::<f32>());
132 let alt = if let Some(c) = canvas.col_or_gen(v.as_::<i32>()) {
133 let pl = PathLocals::new(canvas, &c, path_nearest);
134 pl.riverless_alt + pl.bridge_offset + 0.75
135 } else {
136 sim_chunk.alt
137 };
138 v.with_z(alt)
139 };
140 fn hermite_to_bezier(
141 p0: Vec3<f32>,
142 m0: Vec3<f32>,
143 p3: Vec3<f32>,
144 m3: Vec3<f32>,
145 ) -> CubicBezier3<f32> {
146 let hermite = Vec4::new(p0, p3, m0, m3);
147 let hermite = hermite.map(|v| v.with_w(0.0));
148 let hermite: [[f32; 4]; 4] = hermite.map(|v: Vec4<f32>| v.into_array()).into_array();
149 let mut m = Mat4::from_row_arrays([
151 [1.0, 0.0, 0.0, 0.0],
152 [0.0, 0.0, 0.0, 1.0],
153 [-3.0, 3.0, 0.0, 0.0],
154 [0.0, 0.0, -3.0, 3.0],
155 ]);
156 m.invert();
157 let bezier = m * Mat4::from_row_arrays(hermite);
158 let bezier: Vec4<Vec4<f32>> =
159 Vec4::<[f32; 4]>::from(bezier.into_row_arrays()).map(Vec4::from);
160 let bezier = bezier.map(Vec3::from);
161 CubicBezier3::from(bezier)
162 }
163 for sim::NearestWaysData { bezier: bez, .. } in
164 sim.get_nearest_ways(chunk_center_wpos2d, &|chunk| Some(chunk.path))
165 {
166 if bez.length_by_discretization(16) < 0.125 {
167 continue;
168 }
169 let a = 0.0;
170 let b = 1.0;
171 for bez in bez.split((a + b) / 2.0) {
172 let p0 = g(bez.evaluate(a));
173 let p1 = g(bez.evaluate(a + (b - a) / 3.0));
174 let p2 = g(bez.evaluate(a + 2.0 * (b - a) / 3.0));
175 let p3 = g(bez.evaluate(b));
176 splines.push(hermite_to_bezier(p0, 3.0 * (p1 - p0), p3, 3.0 * (p3 - p2)));
177 }
178 }
179 for spline in splines.into_iter() {
180 canvas.chunk.meta_mut().add_track(spline);
181 }
182}
183
184pub fn apply_coral_to(canvas: &mut Canvas) {
185 let info = canvas.info();
186
187 if !info.chunk.river.near_water() {
188 return; }
190
191 canvas.foreach_col(|canvas, wpos2d, col| {
192 const CORAL_DEPTH: Range<f32> = 14.0..32.0;
193 const CORAL_HEIGHT: f32 = 14.0;
194 const CORAL_DEPTH_FADEOUT: f32 = 5.0;
195 const CORAL_SCALE: f32 = 10.0;
196
197 let water_depth = col.water_level - col.alt;
198
199 if !CORAL_DEPTH.contains(&water_depth) {
200 return; }
202
203 for z in col.alt.floor() as i32..(col.alt + CORAL_HEIGHT) as i32 {
204 let wpos = Vec3::new(wpos2d.x, wpos2d.y, z);
205
206 let coral_factor = Lerp::lerp(
207 1.0,
208 0.0,
209 ((water_depth.clamped(CORAL_DEPTH.start, CORAL_DEPTH.end) - water_depth).abs()
211 / CORAL_DEPTH_FADEOUT)
212 .min(1.0),
213 ) * Lerp::lerp(
214 1.0,
215 0.0,
216 ((z as f32 - col.alt) / CORAL_HEIGHT).powi(2),
218 ) * FastNoise::new(info.index.seed + 7)
219 .get(wpos.map(|e| e as f64) / 32.0)
220 .sub(0.2)
221 .mul(100.0)
222 .clamped(0.0, 1.0);
223
224 let nz = Vec3::iota().map(|e: u32| FastNoise::new(info.index.seed + e * 177));
225
226 let wpos_warped = wpos.map(|e| e as f32)
227 + nz.map(|nz| {
228 nz.get(wpos.map(|e| e as f64) / CORAL_SCALE as f64) * CORAL_SCALE * 0.3
229 });
230
231 let is_coral = [
238 FastNoise::new(info.index.seed),
239 FastNoise::new(info.index.seed + 177),
240 ]
241 .iter()
242 .all(|nz| {
243 nz.get(wpos_warped.map(|e| e as f64) / CORAL_SCALE as f64)
244 .abs()
245 < coral_factor * 0.3
246 });
247
248 if is_coral {
249 canvas.set(wpos, Block::new(BlockKind::Rock, Rgb::new(170, 220, 210)));
250 }
251 }
252 });
253}
254
255pub fn apply_caverns_to<R: Rng>(canvas: &mut Canvas, dynamic_rng: &mut R) {
256 let info = canvas.info();
257
258 let canvern_nz_at = |wpos2d: Vec2<i32>| {
259 let scale = 2048.0;
261 let common = 0.15;
263
264 let cavern_nz = info
265 .index()
266 .noise
267 .cave_nz
268 .get((wpos2d.map(|e| e as f64) / scale).into_array()) as f32;
269 ((cavern_nz * 0.5 + 0.5 - (1.0 - common)).max(0.0) / common).powf(common * 2.0)
270 };
271
272 let cavern_at = |wpos2d| {
274 let alt = info.land().get_alt_approx(wpos2d);
275
276 let height_range = 16.0..250.0;
278 let surface_clearance = 64.0;
280
281 let cavern_avg_height = Lerp::lerp(
282 height_range.start,
283 height_range.end,
284 info.index()
285 .noise
286 .cave_nz
287 .get((wpos2d.map(|e| e as f64) / 300.0).into_array()) as f32
288 * 0.5
289 + 0.5,
290 );
291
292 let cavern_avg_alt =
293 CONFIG.sea_level.min(alt * 0.25) - height_range.end - surface_clearance;
294
295 let cavern = canvern_nz_at(wpos2d);
296 let cavern_height = cavern * cavern_avg_height;
297
298 let stalactite = info
300 .index()
301 .noise
302 .cave_nz
303 .get(wpos2d.map(|e| e as f64 * 0.015).into_array())
304 .sub(0.5)
305 .max(0.0)
306 .mul((cavern_height as f64 - 5.0).mul(0.15).clamped(0.0, 1.0))
307 .mul(32.0 + cavern_avg_height as f64);
308
309 let hill = info
310 .index()
311 .noise
312 .cave_nz
313 .get((wpos2d.map(|e| e as f64) / 96.0).into_array()) as f32
314 * cavern
315 * 24.0;
316 let rugged = 0.4; let cavern_bottom = (cavern_avg_alt - cavern_height * rugged + hill) as i32;
318 let cavern_avg_bottom =
319 (cavern_avg_alt - ((height_range.start + height_range.end) * 0.5) * rugged) as i32;
320 let cavern_top = (cavern_avg_alt + cavern_height) as i32;
321 let cavern_avg_top = (cavern_avg_alt + cavern_avg_height) as i32;
322
323 let stalagmite = stalactite;
325
326 let floor = stalagmite as i32;
327
328 (
329 cavern_bottom,
330 cavern_top,
331 cavern_avg_bottom,
332 cavern_avg_top,
333 floor,
334 stalactite,
335 cavern_avg_bottom + 16, )
337 };
338
339 let mut mushroom_cache = HashMap::new();
340
341 struct Mushroom {
342 pos: Vec3<i32>,
343 stalk: f32,
344 head_color: Rgb<u8>,
345 }
346
347 let mut get_mushroom = |wpos: Vec3<i32>, dynamic_rng: &mut R| {
349 for (wpos2d, seed) in info.chunks().gen_ctx.structure_gen.get(wpos.xy()) {
350 let mushroom = if let Some(mushroom) =
351 mushroom_cache.entry(wpos2d).or_insert_with(|| {
352 let mut rng = RandomPerm::new(seed);
353 let (cavern_bottom, cavern_top, _, _, floor, _, water_level) =
354 cavern_at(wpos2d);
355 let pos = wpos2d.with_z(cavern_bottom + floor);
356 if rng.gen_bool(0.15)
357 && cavern_top - cavern_bottom > 32
358 && pos.z > water_level - 2
359 {
360 Some(Mushroom {
361 pos,
362 stalk: 12.0 + rng.gen::<f32>().powf(2.0) * 35.0,
363 head_color: Rgb::new(
364 50,
365 rng.gen_range(70..110),
366 rng.gen_range(100..200),
367 ),
368 })
369 } else {
370 None
371 }
372 }) {
373 mushroom
374 } else {
375 continue;
376 };
377
378 let wposf = wpos.map(|e| e as f64);
379 let warp_freq = 1.0 / 32.0;
380 let warp_amp = Vec3::new(12.0, 12.0, 12.0);
381 let wposf_warped = wposf.map(|e| e as f32)
382 + Vec3::new(
383 FastNoise::new(seed).get(wposf * warp_freq),
384 FastNoise::new(seed + 1).get(wposf * warp_freq),
385 FastNoise::new(seed + 2).get(wposf * warp_freq),
386 ) * warp_amp
387 * (wposf.z as f32 - mushroom.pos.z as f32)
388 .mul(0.1)
389 .clamped(0.0, 1.0);
390
391 let rpos = wposf_warped - mushroom.pos.map(|e| e as f32);
392
393 let stalk_radius = 2.5f32;
394 let head_radius = 18.0f32;
395 let head_height = 16.0;
396
397 let dist_sq = rpos.xy().magnitude_squared();
398 if dist_sq < head_radius.powi(2) {
399 let dist = dist_sq.sqrt();
400 let head_dist = ((rpos - Vec3::unit_z() * mushroom.stalk)
401 / Vec2::broadcast(head_radius).with_z(head_height))
402 .magnitude();
403
404 let stalk = mushroom.stalk + Lerp::lerp(head_height * 0.5, 0.0, dist / head_radius);
405
406 if rpos.z > stalk
408 && rpos.z <= mushroom.stalk + head_height
409 && dist
410 < head_radius * (1.0 - (rpos.z - mushroom.stalk) / head_height).powf(0.125)
411 {
412 if head_dist < 0.85 {
413 let radial = (rpos.x.atan2(rpos.y) * 10.0).sin() * 0.5 + 0.5;
414 return Some(Block::new(
415 BlockKind::GlowingMushroom,
416 Rgb::new(30, 50 + (radial * 100.0) as u8, 100 - (radial * 50.0) as u8),
417 ));
418 } else if head_dist < 1.0 {
419 return Some(Block::new(BlockKind::Wood, mushroom.head_color));
420 }
421 }
422
423 if rpos.z <= mushroom.stalk + head_height - 1.0
424 && dist_sq
425 < (stalk_radius * Lerp::lerp(1.5, 0.75, rpos.z / mushroom.stalk)).powi(2)
426 {
427 return Some(Block::new(BlockKind::Wood, Rgb::new(25, 60, 90)));
429 } else if ((mushroom.stalk - 0.1)..(mushroom.stalk + 0.9)).contains(&rpos.z) && dist > head_radius * 0.85
431 && dynamic_rng.gen_bool(0.1)
432 {
433 use SpriteKind::*;
434 let sprites = if dynamic_rng.gen_bool(0.1) {
435 &[Beehive, Lantern] as &[_]
436 } else {
437 &[Orb, MycelBlue, MycelBlue] as &[_]
438 };
439 return Some(Block::air(*sprites.choose(dynamic_rng).unwrap()));
440 }
441 }
442 }
443
444 None
445 };
446
447 canvas.foreach_col(|canvas, wpos2d, _col| {
448 if canvern_nz_at(wpos2d) <= 0.0 {
449 return;
450 }
451
452 let (
453 cavern_bottom,
454 cavern_top,
455 cavern_avg_bottom,
456 cavern_avg_top,
457 floor,
458 stalactite,
459 water_level,
460 ) = cavern_at(wpos2d);
461
462 let mini_stalactite = info
463 .index()
464 .noise
465 .cave_nz
466 .get(wpos2d.map(|e| e as f64 * 0.08).into_array())
467 .sub(0.5)
468 .max(0.0)
469 .mul(
470 ((cavern_top - cavern_bottom) as f64 - 5.0)
471 .mul(0.15)
472 .clamped(0.0, 1.0),
473 )
474 .mul(24.0 + (cavern_avg_top - cavern_avg_bottom) as f64 * 0.2);
475 let stalactite_height = (stalactite + mini_stalactite) as i32;
476
477 let moss_common = 1.5;
478 let moss = info
479 .index()
480 .noise
481 .cave_nz
482 .get(wpos2d.map(|e| e as f64 * 0.035).into_array())
483 .sub(1.0 - moss_common)
484 .max(0.0)
485 .mul(1.0 / moss_common)
486 .powf(8.0 * moss_common)
487 .mul(
488 ((cavern_top - cavern_bottom) as f64)
489 .mul(0.15)
490 .clamped(0.0, 1.0),
491 )
492 .mul(16.0 + (cavern_avg_top - cavern_avg_bottom) as f64 * 0.35);
493
494 let plant_factor = info
495 .index()
496 .noise
497 .cave_nz
498 .get(wpos2d.map(|e| e as f64 * 0.015).into_array())
499 .add(1.0)
500 .mul(0.5)
501 .powf(2.0);
502
503 let is_vine = |wpos: Vec3<f32>, dynamic_rng: &mut R| {
504 let wpos = wpos + wpos.xy().yx().with_z(0.0) * 0.2; let dims = Vec2::new(7.0, 256.0); let vine_posf = (wpos + Vec2::new(0.0, (wpos.x / dims.x).floor() * 733.0)) / dims; let vine_pos = vine_posf.map(|e| e.floor() as i32);
508 let mut rng = RandomPerm::new(((vine_pos.x << 16) | vine_pos.y) as u32); if rng.gen_bool(0.2) {
510 let vine_height = (cavern_avg_top - cavern_avg_bottom).max(64) as f32;
511 let vine_base = cavern_avg_bottom as f32 + rng.gen_range(48.0..vine_height);
512 let vine_y = (vine_posf.y.fract() - 0.5).abs() * 2.0 * dims.y;
513 let vine_reach = (vine_y * 0.05).powf(2.0).min(1024.0);
514 let vine_z = vine_base + vine_reach;
515 if Vec2::new(vine_posf.x.fract() * 2.0 - 1.0, (wpos.z - vine_z) / 5.0)
516 .magnitude_squared()
517 < 1.0f32
518 {
519 let kind = if dynamic_rng.gen_bool(0.025) {
520 BlockKind::GlowingRock
521 } else {
522 BlockKind::Leaves
523 };
524 Some(Block::new(
525 kind,
526 Rgb::new(
527 85,
528 (vine_y + vine_reach).mul(0.05).sin().mul(35.0).add(85.0) as u8,
529 20,
530 ),
531 ))
532 } else {
533 None
534 }
535 } else {
536 None
537 }
538 };
539
540 let mut last_kind = BlockKind::Rock;
541 for z in cavern_bottom - 1..cavern_top {
542 use SpriteKind::*;
543
544 let wpos = wpos2d.with_z(z);
545 let wposf = wpos.map(|e| e as f32);
546
547 let block = if z < cavern_bottom {
548 if z > water_level + dynamic_rng.gen_range(4..16) {
549 Block::new(BlockKind::Grass, Rgb::new(10, 75, 90))
550 } else {
551 Block::new(BlockKind::Rock, Rgb::new(50, 40, 10))
552 }
553 } else if z < cavern_bottom + floor {
554 Block::new(BlockKind::WeakRock, Rgb::new(110, 120, 150))
555 } else if z > cavern_top - stalactite_height {
556 if dynamic_rng.gen_bool(0.0035) {
557 Block::new(BlockKind::GlowingRock, Rgb::new(30, 150, 120))
559 } else {
560 Block::new(BlockKind::WeakRock, Rgb::new(110, 120, 150))
561 }
562 } else if let Some(mushroom_block) = get_mushroom(wpos, dynamic_rng) {
563 mushroom_block
564 } else if z > cavern_top - moss as i32 {
565 let kind = if dynamic_rng
566 .gen_bool(0.05 / (1.0 + ((cavern_top - z).max(0) as f64).mul(0.1)))
567 {
568 BlockKind::GlowingMushroom
569 } else {
570 BlockKind::Leaves
571 };
572 Block::new(kind, Rgb::new(50, 120, 160))
573 } else if z < water_level {
574 Block::water(Empty).with_sprite(
575 if z == cavern_bottom + floor && dynamic_rng.gen_bool(0.01) {
576 *[Seagrass, SeaGrapes, SeaweedTemperate, StonyCoral]
577 .choose(dynamic_rng)
578 .unwrap()
579 } else {
580 Empty
581 },
582 )
583 } else if z == water_level
584 && dynamic_rng.gen_bool(Lerp::lerp(0.0, 0.05, plant_factor))
585 && last_kind == BlockKind::Water
586 {
587 Block::air(CavernLillypadBlue)
588 } else if z == cavern_bottom + floor
589 && dynamic_rng.gen_bool(Lerp::lerp(0.0, 0.5, plant_factor))
590 && last_kind == BlockKind::Grass
591 {
592 Block::air(
593 *if dynamic_rng.gen_bool(0.9) {
594 &[GrassBlueShort, GrassBlueMedium, GrassBlueLong] as &[_]
596 } else if dynamic_rng.gen_bool(0.5) {
597 &[CaveMushroom] as &[_]
599 } else {
600 &[LeafyPlant, Fern, Pyrebloom, Moonbell, Welwitch, GrassBlue] as &[_]
602 }
603 .choose(dynamic_rng)
604 .unwrap(),
605 )
606 } else if z == cavern_top - 1 && dynamic_rng.gen_bool(0.001) {
607 Block::air(
608 *[CrystalHigh, CeilingMushroom, Orb, MycelBlue]
609 .choose(dynamic_rng)
610 .unwrap(),
611 )
612 } else if let Some(vine) = is_vine(wposf, dynamic_rng)
613 .or_else(|| is_vine(wposf.xy().yx().with_z(wposf.z), dynamic_rng))
614 {
615 vine
616 } else {
617 Block::empty()
618 };
619
620 last_kind = block.kind();
621
622 let block = if block.is_filled() {
623 Block::new(
624 block.kind(),
625 block.get_color().unwrap_or_default().map(|e| {
626 (e as f32 * dynamic_rng.gen_range(0.95..1.05)).clamped(0.0, 255.0) as u8
627 }),
628 )
629 } else {
630 block
631 };
632
633 canvas.set(wpos, block);
634 }
635 });
636}