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