From 6e76fddc44cba282f7d90a8cbb193e20090d2472 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 18 Jul 2021 23:15:56 +0100 Subject: [PATCH] Added caverns, fixed chunk load performance issue --- common/net/src/msg/compression.rs | 5 + common/src/terrain/block.rs | 3 +- voxygen/Cargo.toml | 1 + voxygen/src/scene/terrain/watcher.rs | 21 ++-- world/src/layer/mod.rs | 173 ++++++++++++++++++++++++++- world/src/lib.rs | 1 + 6 files changed, 192 insertions(+), 12 deletions(-) diff --git a/common/net/src/msg/compression.rs b/common/net/src/msg/compression.rs index db2bd0c31d..fc7f9aaea0 100644 --- a/common/net/src/msg/compression.rs +++ b/common/net/src/msg/compression.rs @@ -644,6 +644,11 @@ impl VoxelImageDecoding for TriPngEncoding Rgb { + r: 50, + g: 250, + b: 250, + }, Misc => Rgb { r: 255, g: 0, diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index d6420ec348..9615a46ce5 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -48,7 +48,8 @@ make_case_elim!( // 0x32 <= x < 0x40 is reserved for future earths/muds/gravels/sands/etc. Wood = 0x40, Leaves = 0x41, - // 0x42 <= x < 0x50 is reserved for future tree parts + GlowingMushroom = 0x42, + // 0x43 <= x < 0x50 is reserved for future tree parts // Covers all other cases (we sometimes have bizarrely coloured misc blocks, and also we // often want to experiment with new kinds of block without allocating them a // dedicated block kind. diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index f34934e846..789ab89ecd 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -106,6 +106,7 @@ native-dialog = { version = "0.5.2", optional = true } num = "0.4" ordered-float = { version = "2.0.1", default-features = false } rand = "0.8" +rand_chacha = "0.3" rayon = "1.5" rodio = {version = "0.14", default-features = false, features = ["vorbis"]} ron = {version = "0.6", default-features = false} diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs index 8becc3e3e4..e811c715fb 100644 --- a/voxygen/src/scene/terrain/watcher.rs +++ b/voxygen/src/scene/terrain/watcher.rs @@ -5,6 +5,7 @@ use common::{ }; use common_base::span; use rand::prelude::*; +use rand_chacha::ChaCha8Rng; use vek::*; #[derive(Copy, Clone, Debug)] @@ -60,6 +61,8 @@ impl BlocksOfInterest { let mut cricket3 = Vec::new(); let mut frogs = Vec::new(); + let mut rng = ChaCha8Rng::from_seed(thread_rng().gen()); + chunk .vol_iter( Vec3::new(0, 0, chunk.get_min_z()), @@ -71,13 +74,13 @@ impl BlocksOfInterest { ) .for_each(|(pos, block)| { match block.kind() { - BlockKind::Leaves if thread_rng().gen_range(0..16) == 0 => leaves.push(pos), - BlockKind::WeakRock if thread_rng().gen_range(0..6) == 0 => drip.push(pos), + BlockKind::Leaves if rng.gen_range(0..16) == 0 => leaves.push(pos), + BlockKind::WeakRock if rng.gen_range(0..6) == 0 => drip.push(pos), BlockKind::Grass => { - if thread_rng().gen_range(0..16) == 0 { + if rng.gen_range(0..16) == 0 { grass.push(pos); } - match thread_rng().gen_range(0..8192) { + match rng.gen_range(0..8192) { 1 => cricket1.push(pos), 2 => cricket2.push(pos), 3 => cricket3.push(pos), @@ -85,14 +88,12 @@ impl BlocksOfInterest { } }, BlockKind::Water - if chunk.meta().contains_river() && thread_rng().gen_range(0..16) == 0 => + if chunk.meta().contains_river() && rng.gen_range(0..16) == 0 => { river.push(pos) }, - BlockKind::Lava if thread_rng().gen_range(0..5) == 0 => { - fires.push(pos + Vec3::unit_z()) - }, - BlockKind::Snow if thread_rng().gen_range(0..16) == 0 => snow.push(pos), + BlockKind::Lava if rng.gen_range(0..5) == 0 => fires.push(pos + Vec3::unit_z()), + BlockKind::Snow if rng.gen_range(0..16) == 0 => snow.push(pos), _ => match block.get_sprite() { Some(SpriteKind::Ember) => { fires.push(pos); @@ -114,7 +115,7 @@ impl BlocksOfInterest { Some(SpriteKind::Reed) => { reeds.push(pos); fireflies.push(pos); - if thread_rng().gen_range(0..12) == 0 { + if rng.gen_range(0..12) == 0 { frogs.push(pos); } }, diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 9d3148eb80..02f67bde05 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -10,7 +10,7 @@ pub use self::{ use crate::{ column::ColumnSample, - util::{FastNoise, RandomField, Sampler}, + util::{FastNoise, RandomField, Sampler, RandomPerm}, Canvas, IndexRef, }; use common::{ @@ -27,6 +27,7 @@ use std::{ f32, ops::{Mul, Range, Sub}, }; +use hashbrown::HashMap; use vek::*; #[derive(Deserialize)] @@ -570,3 +571,173 @@ pub fn apply_coral_to(canvas: &mut Canvas) { } }); } + +pub fn apply_caverns_to(canvas: &mut Canvas, dynamic_rng: &mut R) { + let info = canvas.info(); + + // Get cavern attributes at a position + let cavern_at = |wpos2d| { + let alt = info.land().get_alt_approx(wpos2d); + + // Horizontal average scale of caverns + let scale = 2048.0; + // How common should they be? (0.0 - 1.0) + let common = 0.15; + // Range of heights for the caverns + let height_range = 48.0..300.0; + // Minimum distance below the surface + let surface_clearance = 64.0; + + let cavern_avg_height = Lerp::lerp( + height_range.start, + height_range.end, + info.index().noise.cave_nz.get((wpos2d.map(|e| e as f64) / 128.0).into_array()) as f32 * 0.5 + 0.5, + ); + + let cavern_avg_alt = alt * 0.25 - height_range.end - surface_clearance; + + let cavern_nz = info.index().noise.cave_nz.get((wpos2d.map(|e| e as f64) / scale).into_array()) as f32; + let cavern_height = ((cavern_nz * 0.5 + 0.5 - (1.0 - common)).max(0.0) / common).powf(common * 2.0) * cavern_avg_height; + + // Stalagtites + let stalagtite = info.index().noise.cave_nz + .get(wpos2d.map(|e| e as f64 * 0.015).into_array()) + .sub(0.5) + .max(0.0) + .mul((cavern_height as f64 - 5.0).mul(0.15).clamped(0.0, 1.0)) + .mul(32.0 + cavern_avg_height as f64 * 0.85); + + let lake = info.index().noise.cave_nz + .get(wpos2d.map(|e| e as f64 * 0.01).into_array()) + .sub(0.5) + .max(0.0) + .mul(2.0) + .mul(80.0); + let lake = 0.0; + + let rugged = 0.25; // How bumpy should the floor be relative to the ceiling? + let cavern_bottom = (cavern_avg_alt - cavern_height * rugged) as i32; + let cavern_avg_bottom = (cavern_avg_alt - ((height_range.start + height_range.end) * 0.5) * rugged) as i32; + let cavern_top = (cavern_avg_alt + cavern_height) as i32; + let cavern_avg_top = (cavern_avg_alt + cavern_avg_height) as i32; + + // Stalagmites rise up to meet stalagtites + let stalagmite = stalagtite * 0.3; + + let floor = stalagmite as i32; + + (cavern_bottom, cavern_top, cavern_avg_bottom, cavern_avg_top, floor, lake, stalagtite) + }; + + let mut mushroom_cache = HashMap::new(); + + struct Mushroom { + pos: Vec3, + stalk: f32, + head_color: Rgb, + } + + // Get mushroom block, if any, at a position + let mut get_mushroom = |wpos: Vec3, dynamic_rng: &mut R| { + for (wpos2d, seed) in info.chunks().gen_ctx.structure_gen.get(wpos.xy()) { + let mushroom = if let Some(mushroom) = mushroom_cache + .entry(wpos2d) + .or_insert_with(|| { + let mut rng = RandomPerm::new(seed); + let (cavern_bottom, _, _, _, floor, _, _) = cavern_at(wpos2d); + if rng.gen_bool(0.1) { + Some(Mushroom { + pos: wpos2d.with_z(cavern_bottom + floor), + stalk: rng.gen_range(8.0..26.0), + head_color: Rgb::new(50, rng.gen_range(70..110), rng.gen_range(100..200)), + }) + } else { + None + } + }) + { + mushroom + } else { + continue + }; + + let wposf = wpos.map(|e| e as f64); + let warp_freq = 1.0 / 32.0; + let warp_amp = Vec3::new(8.0, 8.0, 12.0); + let wposf_warped = wposf.map(|e| e as f32) + Vec3::new( + FastNoise::new(seed + 0).get(wposf * warp_freq) as f32, + FastNoise::new(seed + 1).get(wposf * warp_freq) as f32, + FastNoise::new(seed + 2).get(wposf * warp_freq) as f32, + ) * warp_amp; + + let rpos = wposf_warped - mushroom.pos.map(|e| e as f32).map(|e| e as f32); + + let stalk_radius = 2.0f32; + let head_radius = 12.0f32; + let head_height = 10.0; + + let dist_sq = rpos.xy().magnitude_squared(); + if dist_sq < head_radius.powi(2) { + let dist = dist_sq.sqrt(); + // Head + if rpos.z > mushroom.stalk && rpos.z < mushroom.stalk + head_height && dist < head_radius * (1.0 - (rpos.z - mushroom.stalk) / head_height).powf(0.125) { + return Some(Block::new(BlockKind::GlowingMushroom, mushroom.head_color)); + } else if rpos.z <= mushroom.stalk && rpos.xy().magnitude_squared() < stalk_radius.powi(2) { // Stalk + return Some(Block::new(BlockKind::Wood, Rgb::new(50, 120, 180))); + } else if ((mushroom.stalk - 1.0)..mushroom.stalk).contains(&rpos.z) // Hanging orbs + && ((head_radius * 0.5)..(head_radius * 0.8)).contains(&dist) + && dynamic_rng.gen_bool(0.025) + { + return Some(Block::air(SpriteKind::Orb)); + } + } + } + + None + }; + + canvas.foreach_col(|canvas, wpos2d, _col| { + let (cavern_bottom, cavern_top, cavern_avg_bottom, cavern_avg_top, floor, lake, stalagtite) = cavern_at(wpos2d); + + let mini_stalagtite = info.index().noise.cave_nz + .get(wpos2d.map(|e| e as f64 * 0.08).into_array()) + .sub(0.5) + .max(0.0) + .mul(((cavern_top - cavern_bottom) as f64 - 5.0).mul(0.15).clamped(0.0, 1.0)) + .mul(24.0 + (cavern_avg_top - cavern_avg_bottom) as f64 * 0.2); + let stalagtite_height = (stalagtite + mini_stalagtite) as i32; + + let cavern_top = cavern_top as i32; + let lower_bound = cavern_bottom - lake as i32; + let mut on_ground = true; + for z in lower_bound..cavern_top { + use SpriteKind::*; + + let wpos = wpos2d.with_z(z); + + let block = if z < lower_bound + floor { + Block::new(BlockKind::WeakRock, Rgb::new(110, 120, 150)) + } else if z > cavern_top - stalagtite_height { + if dynamic_rng.gen_bool(0.0035) { // Glowing rock in stalagtites + Block::new(BlockKind::GlowingRock, Rgb::new(30, 150, 120)) + } else { + Block::new(BlockKind::WeakRock, Rgb::new(110, 120, 150)) + } + } else if let Some(mushroom_block) = get_mushroom(wpos, dynamic_rng) { + mushroom_block + } else if z < cavern_avg_bottom as i32 + 16 { + Block::water(SpriteKind::Empty) + } else if z == cavern_bottom + floor && dynamic_rng.gen_bool(0.005) && on_ground { + Block::air(*[CrystalLow, CaveMushroom].choose(dynamic_rng).unwrap()) + } else if z == cavern_top - 1 && dynamic_rng.gen_bool(0.01) { + Block::air(*[CrystalHigh, CeilingMushroom, Orb].choose(dynamic_rng).unwrap()) + } else { + Block::empty() + }; + + on_ground |= block.is_solid(); + + let _ = canvas.set(wpos, block); + } + }); +} diff --git a/world/src/lib.rs b/world/src/lib.rs index 72edb90318..57ebdcf10e 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -346,6 +346,7 @@ impl World { entities: Vec::new(), }; + layer::apply_caverns_to(&mut canvas, &mut dynamic_rng); layer::apply_caves_to(&mut canvas, &mut dynamic_rng); layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng); layer::apply_trees_to(&mut canvas, &mut dynamic_rng);