From 767731ecb01bd99cf94702f53e23a3316a9e2ca3 Mon Sep 17 00:00:00 2001 From: Syniis Date: Sat, 2 Mar 2024 23:31:42 +0100 Subject: [PATCH 1/6] Cave performance improvements --- Cargo.lock | 15 ++ world/Cargo.toml | 6 +- world/benches/cave.rs | 67 +++++++ world/src/layer/cave.rs | 373 +++++++++++++++++++----------------- world/src/util/structure.rs | 52 +++++ 5 files changed, 332 insertions(+), 181 deletions(-) create mode 100644 world/benches/cave.rs diff --git a/Cargo.lock b/Cargo.lock index ecc5992933..433f4fbedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3166,8 +3166,22 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6acddbefae08bfba73e27f55513f491f35c365d84bf3002bf85ba9b916c5e5f" dependencies = [ + "inline_tweak_derive", "lazy_static", + "proc-macro2 1.0.78", "rustc-hash", + "syn 2.0.48", +] + +[[package]] +name = "inline_tweak_derive" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d62a0a3b6af04d4eee8e7251cd758ce74b0ed86253d3e4ac8a1b297a75f4a0" +dependencies = [ + "proc-macro2 1.0.78", + "quote 1.0.35", + "syn 2.0.48", ] [[package]] @@ -7442,6 +7456,7 @@ dependencies = [ "hashbrown 0.13.2", "image", "indicatif", + "inline_tweak", "itertools 0.10.5", "kiddo", "lazy_static", diff --git a/world/Cargo.toml b/world/Cargo.toml index 6e90ae3ebe..a392968c1d 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -40,7 +40,7 @@ packed_simd = { version = "0.3.9", optional = true } rayon = { workspace = true } serde = { workspace = true } ron = { workspace = true } -# inline_tweak = { workspace = true, features = ["derive"] } +inline_tweak = { workspace = true, features = ["derive"] } kiddo = "0.2" strum = { workspace = true } @@ -69,6 +69,10 @@ svg_fmt = "0.4" harness = false name = "tree" +[[bench]] +harness = false +name = "cave" + [[example]] name = "chunk_compression_benchmarks" required-features = ["bin_compression"] diff --git a/world/benches/cave.rs b/world/benches/cave.rs new file mode 100644 index 0000000000..b8f0ec2e1e --- /dev/null +++ b/world/benches/cave.rs @@ -0,0 +1,67 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rayon::ThreadPoolBuilder; +use vek::Vec2; +use veloren_world::{ + layer, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + Land, World, +}; + +fn cave(c: &mut Criterion) { + let pool = ThreadPoolBuilder::new().build().unwrap(); + let (world, index) = World::generate( + 123, + WorldOpts { + seed_elements: true, + world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + ..WorldOpts::default() + }, + &pool, + &|_| {}, + ); + let land = Land::from_sim(world.sim()); + + c.bench_function("generate", |b| { + b.iter(|| { + let entrances = black_box(layer::cave::surface_entrances(&land)) + .step_by(5) + .map(|e| e / 32); + for entrance in entrances { + _ = black_box(world.generate_chunk( + index.as_index_ref(), + entrance, + None, + || false, + None, + )); + } + }); + }); + + c.bench_function("generate_specific", |b| { + b.iter(|| { + let base_positions = vec![ + Vec2::new(600, 650), + Vec2::new(630, 300), + Vec2::new(809, 141), + ]; + for base_pos in base_positions { + for i in 0..=4 { + for j in 0..=4 { + let pos = base_pos + Vec2::new(i as i32, j as i32) - 2; + _ = black_box(world.generate_chunk( + index.as_index_ref(), + pos, + None, + || false, + None, + )); + } + } + } + }); + }); +} + +criterion_group!(benches, cave); +criterion_main!(benches); diff --git a/world/src/layer/cave.rs b/world/src/layer/cave.rs index a7ce750ff9..63e41fa1f5 100644 --- a/world/src/layer/cave.rs +++ b/world/src/layer/cave.rs @@ -174,6 +174,7 @@ impl Tunnel { } } + #[inline_tweak::tweak_fn] fn biome_at(&self, wpos: Vec3, info: &CanvasInfo) -> Biome { let Some(col) = info.col_or_gen(wpos.xy()) else { return Biome::default(); @@ -202,7 +203,7 @@ impl Tunnel { .mul(2.0) .sub(1.0) .add( - ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.75)) + ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.6)) .clamped(0.0, 2.5), ), below, @@ -233,38 +234,38 @@ impl Tunnel { // Mushrooms grow underground and thrive in a humid environment with moderate // temperatures let mushroom = underground - * close(humidity, 1.0, 0.7, 3) - * close(temp, 1.5, 0.9, 3) - * close(depth, 1.0, 0.6, 3); + * close(humidity, 1.0, 0.7, 4) + * close(temp, 1.5, 0.9, 4) + * close(depth, 1.0, 0.6, 4); // Extremely hot and dry areas deep underground let fire = underground - * close(humidity, 0.0, 0.6, 3) - * close(temp, 2.0, 1.3, 3) - * close(depth, 1.0, 0.55, 3); + * close(humidity, 0.0, 0.6, 4) + * close(temp, 2.0, 1.3, 4) + * close(depth, 1.0, 0.55, 4); // Overgrown with plants that need a moderate climate to survive let leafy = underground - * close(humidity, 0.8, 0.8, 3) - * close(temp, 0.95, 0.85, 3) - * close(depth, 0.0, 0.6, 3); + * close(humidity, 0.8, 0.8, 4) + * close(temp, 0.75, 1.25, 4) + * close(depth, 0.0, 0.75, 4); // Cool temperature, dry and devoid of value - let dusty = close(humidity, 0.0, 0.5, 3) * close(temp, -0.1, 0.6, 3); + let dusty = close(humidity, 0.0, 0.5, 4) * close(temp, -0.1, 0.6, 4); // Deep underground and freezing cold let icy = underground - * close(temp, -1.5, 1.3, 3) - * close(depth, 1.0, 0.65, 3) - * close(humidity, 1.0, 0.7, 3); + * close(temp, -1.5, 1.3, 4) + * close(depth, 1.0, 0.6, 4) + * close(humidity, 1.0, 0.7, 4); // Rocky cold cave that appear near the surface - let snowy = close(temp, -0.6, 0.5, 3) * close(depth, 0.0, 0.45, 3); + let snowy = close(temp, -0.6, 0.5, 4) * close(depth, 0.0, 0.45, 4); // Crystals grow deep underground in areas rich with minerals. They are present // in areas with colder temperatures and low humidity let crystal = underground - * close(humidity, 0.0, 0.5, 3) - * close(temp, -0.6, 0.75, 3) - * close(depth, 1.0, 0.55, 3) - * close(mineral, 2.0, 1.25, 3); + * close(humidity, 0.0, 0.5, 4) + * close(temp, -0.6, 0.75, 4) + * close(depth, 1.0, 0.55, 4) + * close(mineral, 2.0, 1.25, 4); // Hot, dry and shallow let sandy = - close(humidity, 0.0, 0.3, 3) * close(temp, 0.7, 0.9, 3) * close(depth, 0.0, 0.6, 3); + close(humidity, 0.0, 0.3, 4) * close(temp, 0.7, 0.9, 4) * close(depth, 0.0, 0.6, 4); let biomes = [ barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy, @@ -434,6 +435,7 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { }) }) .collect::>(); + if !tunnels.is_empty() { let giant_tree_dist = info .chunk @@ -497,6 +499,7 @@ struct Biome { depth: f32, } +#[derive(Clone)] enum CaveStructure { Mushroom(Mushroom), Crystal(CrystalCluster), @@ -508,24 +511,28 @@ enum CaveStructure { }, } +#[derive(Clone)] struct Mushroom { pos: Vec3, stalk: f32, head_color: Rgb, } +#[derive(Clone)] struct Crystal { dir: Vec3, length: f32, radius: f32, } +#[derive(Clone)] struct CrystalCluster { pos: Vec3, crystals: Vec, color: Rgb, } +#[derive(Clone)] struct Flower { pos: Vec3, stalk: f32, @@ -535,6 +542,7 @@ struct Flower { // rotation: Mat3, } +#[inline_tweak::tweak_fn] fn write_column( canvas: &mut Canvas, col: &ColumnSample, @@ -543,7 +551,7 @@ fn write_column( z_range: Range, tunnel: Tunnel, dimensions: (f32, f32, f32), - giant_tree_factor: f32, + giant_tree_dist: f32, structure_cache: &mut SmallCache, Option>, rng: &mut R, ) { @@ -741,154 +749,173 @@ fn write_column( 0 }; - let mut get_structure = |wpos: Vec3, dynamic_rng: &mut R| { - for (wpos2d, seed) in StructureGen2d::new(34537, 24, 8).get(wpos.xy()) { - let structure = if let Some(structure) = - structure_cache.get(wpos2d.with_z(tunnel.a.depth), |_| { - let mut rng = RandomPerm::new(seed); - let (z_range, horizontal, vertical, _) = - tunnel.z_range_at(wpos2d.map(|e| e as f64 + 0.5), info)?; - let pos = wpos2d.with_z(z_range.start); - - let biome = tunnel.biome_at(pos, &info); - let ground_below = !tunnel_bounds_at(pos.xy(), &info, &info.land()) - .any(|(_, z_range, _, _, _, _)| z_range.contains(&(z_range.start - 1))); - if !ground_below { + let structures = StructureGen2d::new(34537, 24, 8) + .get(wpos2d) + .as_slice() + .iter() + .filter_map(|(wpos2d, seed)| { + let structure = structure_cache.get(wpos2d.with_z(tunnel.a.depth), |_| { + let mut rng = RandomPerm::new(*seed); + let (z_range, horizontal, vertical, _) = + tunnel.z_range_at(wpos2d.map(|e| e as f64 + 0.5), info)?; + let pos = wpos2d.with_z(z_range.start); + let biome = tunnel.biome_at(pos, &info); + let tunnel_intersection = || { + tunnel_bounds_at(pos.xy(), &info, &info.land()) + .any(|(_, z_range, _, _, _, _)| z_range.contains(&(z_range.start - 1))) + }; + if biome.mushroom > 0.7 + && vertical > 16.0 + && rng.gen_bool( + 0.5 * close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0, 2) as f64 + * close(biome.mushroom, 1.0, 0.7, 1) as f64, + ) + { + if tunnel_intersection() { return None; } - - if biome.mushroom > 0.7 - && vertical > 16.0 - && rng.gen_bool( - 0.5 * close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0, 2) as f64 - * close(biome.mushroom, 1.0, 0.7, 1) as f64, - ) - { - let purp = rng.gen_range(0..50); - Some(CaveStructure::Mushroom(Mushroom { - pos, - stalk: 8.0 - + rng.gen::().powf(2.0) - * (z_range.end - z_range.start - 8) as f32 - * 0.75, - head_color: Rgb::new( - 40 + purp, - rng.gen_range(60..120), - rng.gen_range(80..200) + purp, - ), - })) - } else if biome.crystal > 0.5 - && rng.gen_bool(0.4 * close(biome.crystal, 1.0, 0.7, 2) as f64) - { - let on_ground = rng.gen_bool(0.6); - let pos = wpos2d.with_z(if on_ground { - z_range.start - } else { - z_range.end - }); - - let mut crystals: Vec = Vec::new(); - let max_length = - (48.0 * close(vertical, MAX_RADIUS, MAX_RADIUS, 1)).max(12.0); - let length = rng.gen_range(8.0..max_length); - let radius = - Lerp::lerp(2.0, 4.5, length / max_length + rng.gen_range(-0.1..0.1)); - let dir = Vec3::new( - rng.gen_range(-3.0..3.0), - rng.gen_range(-3.0..3.0), - rng.gen_range(0.5..10.0) * if on_ground { 1.0 } else { -1.0 }, - ) - .normalized(); - - crystals.push(Crystal { - dir, - length, - radius, - }); - - (0..4).for_each(|_| { - crystals.push(Crystal { - dir: Vec3::new( - rng.gen_range(-1.0..1.0), - rng.gen_range(-1.0..1.0), - (dir.z + rng.gen_range(-0.2..0.2)).clamped(0.0, 1.0), - ), - length: length * rng.gen_range(0.3..0.8), - radius: (radius * rng.gen_range(0.5..0.8)).max(1.0), - }); - }); - - let purple = rng.gen_range(25..75); - let blue = (rng.gen_range(45.0..75.0) * biome.icy) as u8; - - Some(CaveStructure::Crystal(CrystalCluster { - pos, - crystals, - color: Rgb::new( - 255 - blue * 2, - 255 - blue - purple, - 200 + rng.gen_range(25..55), - ), - })) - } else if biome.leafy > 0.8 - && vertical > 16.0 - && horizontal > 8.0 - && rng.gen_bool( - 0.25 * (close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0, 2) - * close(horizontal, MAX_RADIUS, MAX_RADIUS - 8.0, 2) - * biome.leafy) as f64, - ) - { - let petal_radius = rng.gen_range(8.0..16.0); - Some(CaveStructure::Flower(Flower { - pos, - stalk: 6.0 - + rng.gen::().powf(2.0) - * (z_range.end - z_range.start - 8) as f32 - * 0.75, - petals: rng.gen_range(1..5) * 2 + 1, - petal_height: 0.4 * petal_radius * (1.0 + rng.gen::().powf(2.0)), - petal_radius, - })) - } else if (biome.leafy > 0.7 || giant_tree_factor > 0.0) - && rng.gen_bool( - (0.5 * close(biome.leafy, 1.0, 0.5, 1).max(1.0 + giant_tree_factor) - as f64) - .clamped(0.0, 1.0), - ) - { - Some(CaveStructure::GiantRoot { - pos, - radius: rng.gen_range( - 1.5..(3.5 - + close(vertical, MAX_RADIUS, MAX_RADIUS / 2.0, 2) * 3.0 - + close(horizontal, MAX_RADIUS, MAX_RADIUS / 2.0, 2) * 3.0), - ), - height: (z_range.end - z_range.start) as f32, - }) - } else { - None + let purp = rng.gen_range(0..50); + Some(CaveStructure::Mushroom(Mushroom { + pos, + stalk: 8.0 + + rng.gen::().powf(2.0) + * (z_range.end - z_range.start - 8) as f32 + * 0.75, + head_color: Rgb::new( + 40 + purp, + rng.gen_range(60..120), + rng.gen_range(80..200) + purp, + ), + })) + } else if biome.crystal > 0.5 + && rng.gen_bool(0.4 * close(biome.crystal, 1.0, 0.7, 2) as f64) + { + if tunnel_intersection() { + return None; } - }) { - structure - } else { - continue; - }; + let on_ground = rng.gen_bool(0.6); + let pos = wpos2d.with_z(if on_ground { + z_range.start + } else { + z_range.end + }); + let mut crystals: Vec = Vec::new(); + let max_length = (48.0 * close(vertical, MAX_RADIUS, MAX_RADIUS, 1)).max(12.0); + let length = rng.gen_range(8.0..max_length); + let radius = + Lerp::lerp(2.0, 4.5, length / max_length + rng.gen_range(-0.1..0.1)); + let dir = Vec3::new( + rng.gen_range(-3.0..3.0), + rng.gen_range(-3.0..3.0), + rng.gen_range(0.5..10.0) * if on_ground { 1.0 } else { -1.0 }, + ) + .normalized(); + + crystals.push(Crystal { + dir, + length, + radius, + }); + + (0..4).for_each(|_| { + crystals.push(Crystal { + dir: Vec3::new( + rng.gen_range(-1.0..1.0), + rng.gen_range(-1.0..1.0), + (dir.z + rng.gen_range(-0.2..0.2)).clamped(0.0, 1.0), + ), + length: length * rng.gen_range(0.3..0.8), + radius: (radius * rng.gen_range(0.5..0.8)).max(1.0), + }); + }); + + let purple = rng.gen_range(25..75); + let blue = (rng.gen_range(45.0..75.0) * biome.icy) as u8; + + Some(CaveStructure::Crystal(CrystalCluster { + pos, + crystals, + color: Rgb::new( + 255 - blue * 2, + 255 - blue - purple, + 200 + rng.gen_range(25..55), + ), + })) + } else if biome.leafy > 0.8 + && vertical > 16.0 + && horizontal > 8.0 + && rng.gen_bool( + 0.25 * (close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0, 2) + * close(horizontal, MAX_RADIUS, MAX_RADIUS - 8.0, 2) + * biome.leafy) as f64, + ) + { + if tunnel_intersection() { + return None; + } + let petal_radius = rng.gen_range(8.0..16.0); + Some(CaveStructure::Flower(Flower { + pos, + stalk: 6.0 + + rng.gen::().powf(2.0) + * (z_range.end - z_range.start - 8) as f32 + * 0.75, + petals: rng.gen_range(1..5) * 2 + 1, + petal_height: 0.4 * petal_radius * (1.0 + rng.gen::().powf(2.0)), + petal_radius, + })) + } else if (biome.leafy > 0.7 || giant_tree_dist > 0.0) + && rng.gen_bool( + (0.5 * close(biome.leafy, 1.0, 0.5, 1).max(1.0 + giant_tree_dist) as f64) + .clamped(0.0, 1.0), + ) + { + if tunnel_intersection() { + return None; + } + Some(CaveStructure::GiantRoot { + pos, + radius: rng.gen_range( + 1.5..(3.5 + + close(vertical, MAX_RADIUS, MAX_RADIUS / 2.0, 2) * 3.0 + + close(horizontal, MAX_RADIUS, MAX_RADIUS / 2.0, 2) * 3.0), + ), + height: (z_range.end - z_range.start) as f32, + }) + } else { + None + } + }); + + structure + .as_ref() + .map(|structure| (*seed, structure.clone())) + }) + .collect_vec(); + let get_structure = |wpos: Vec3, dynamic_rng: &mut R| { + let warp = |wposf: Vec3, freq: f64, amp: Vec3, seed: u32| -> Option> { + let xy = wposf.xy(); + let xz = Vec2::new(wposf.x, wposf.z); + let yz = Vec2::new(wposf.y, wposf.z); + Some( + Vec3::new( + FastNoise2d::new(seed).get(yz * freq), + FastNoise2d::new(seed).get(xz * freq), + FastNoise2d::new(seed).get(xy * freq), + ) * amp, + ) + }; + for (seed, structure) in structures.iter() { + let seed = *seed; match structure { CaveStructure::Mushroom(mushroom) => { let wposf = wpos.map(|e| e as f64); let warp_freq = 1.0 / 32.0; let warp_amp = Vec3::new(12.0, 12.0, 12.0); - let xy = wposf.xy(); - let xz = Vec2::new(wposf.x, wposf.z); - let yz = Vec2::new(wposf.y, wposf.z); + let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?; let wposf_warped = wposf.map(|e| e as f32) - + Vec3::new( - FastNoise2d::new(seed).get(yz * warp_freq), - FastNoise2d::new(seed).get(xz * warp_freq), - FastNoise2d::new(seed).get(xy * warp_freq), - ) * warp_amp + + warp_offset * (wposf.z as f32 - mushroom.pos.z as f32) .mul(0.1) .clamped(0.0, 1.0); @@ -1011,15 +1038,9 @@ fn write_column( let wposf = wpos.map(|e| e as f64); let warp_freq = 1.0 / 16.0; let warp_amp = Vec3::new(8.0, 8.0, 8.0); - let xy = wposf.xy(); - let xz = Vec2::new(wposf.x, wposf.z); - let yz = Vec2::new(wposf.y, wposf.z); + let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?; let wposf_warped = wposf.map(|e| e as f32) - + Vec3::new( - FastNoise2d::new(seed).get(yz * warp_freq), - FastNoise2d::new(seed).get(xz * warp_freq), - FastNoise2d::new(seed).get(xy * warp_freq), - ) * warp_amp + + warp_offset * (wposf.z as f32 - flower.pos.z as f32) .mul(1.0 / flower.stalk) .sub(1.0) @@ -1044,8 +1065,7 @@ fn write_column( let dist_sq = rpos.xy().magnitude_squared(); let petal_radius_sq = flower.petal_radius.powi(2); if dist_sq < petal_radius_sq { - let petal_height_at = - (dist_sq / petal_radius_sq).powf(1.0) * flower.petal_height; + let petal_height_at = (dist_sq / petal_radius_sq) * flower.petal_height; if rpos.z > petal_height_at - 1.0 && rpos.z <= petal_height_at + petal_thickness { @@ -1108,16 +1128,9 @@ fn write_column( } => { let wposf = wpos.map(|e| e as f64); let warp_freq = 1.0 / 32.0; - let warp_amp = Vec3::new(20.0, 20.0, 20.0); - let xy = wposf.xy(); - let xz = Vec2::new(wposf.x, wposf.z); - let yz = Vec2::new(wposf.y, wposf.z); - let wposf_warped = wposf.map(|e| e as f32) - + Vec3::new( - FastNoise2d::new(seed).get(yz * warp_freq), - FastNoise2d::new(seed).get(xz * warp_freq), - FastNoise2d::new(seed).get(xy * warp_freq), - ) * warp_amp; + let warp_amp = Vec3::new(12.0, 12.0, 12.0); + let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?; + let wposf_warped = wposf.map(|e| e as f32) + warp_offset; let rpos = wposf_warped - pos.map(|e| e as f32); let dist_sq = rpos.xy().magnitude_squared(); if dist_sq < radius.powi(2) { @@ -1142,7 +1155,7 @@ fn write_column( for z in bedrock..z_range.end { let wpos = wpos2d.with_z(z); let mut try_spawn_entity = false; - canvas.map_resource(wpos, |_block| { + canvas.set(wpos, { if z < z_range.start - 4 && !void_below { Block::new(BlockKind::Lava, Rgb::new(255, 65, 0)) } else if basalt > 0.0 diff --git a/world/src/util/structure.rs b/world/src/util/structure.rs index ed0fe38ebf..5326e359f1 100644 --- a/world/src/util/structure.rs +++ b/world/src/util/structure.rs @@ -111,6 +111,58 @@ impl StructureGen2d { ) }) } + + /// Note: Generates all possible closest samples for elements in the range + /// of min to max, *exclusive.* + pub fn iter_with_wpos( + &self, + min: Vec2, + max: Vec2, + ) -> impl Iterator, StructureField)> { + let freq = self.freq; + let spread = self.spread; + let spread_mul = Self::spread_mul(spread); + assert!(spread * 2 == spread_mul); + assert!(spread_mul <= freq); + let spread = spread as i32; + let freq = freq as i32; + let freq_offset = Self::freq_offset(freq); + assert!(freq_offset * 2 == freq); + + let min_index = Self::sample_to_index_internal(freq, min) - 1; + let max_index = Self::sample_to_index_internal(freq, max) + 1; + assert!(min_index.x < max_index.x); + // NOTE: xlen > 0 + let xlen = (max_index.x - min_index.x) as u32; + assert!(min_index.y < max_index.y); + // NOTE: ylen > 0 + let ylen = (max_index.y - min_index.y) as u32; + // NOTE: Cannot fail, since every product of u32s fits in a u64. + let len = ylen as u64 * xlen as u64; + // NOTE: since iteration is *exclusive* for the initial range, it's fine that we + // don't go up to the maximum value. + // NOTE: we convert to usize first, and then iterate, because we want to make + // sure we get a properly indexed parallel iterator that can deal with + // the whole range at once. + let x_field = self.x_field; + let y_field = self.y_field; + let seed_field = self.seed_field; + (0..len).map(move |xy| { + let index = min_index + Vec2::new((xy % xlen as u64) as i32, (xy / xlen as u64) as i32); + let index_wpos = min + Vec2::new((xy % xlen as u64) as i32, (xy / len as u64) as i32); + let field = Self::index_to_sample_internal( + freq, + freq_offset, + spread, + spread_mul, + x_field, + y_field, + seed_field, + index, + ); + (index_wpos, field) + }) + } } impl Sampler<'static> for StructureGen2d { From a07e270220e4a633f17e53b47b5f00d0c3ebcee3 Mon Sep 17 00:00:00 2001 From: Syniis Date: Mon, 4 Mar 2024 00:38:18 +0100 Subject: [PATCH 2/6] Address cave feedback --- world/src/layer/cave.rs | 139 +++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 72 deletions(-) diff --git a/world/src/layer/cave.rs b/world/src/layer/cave.rs index 63e41fa1f5..bfab1c15d8 100644 --- a/world/src/layer/cave.rs +++ b/world/src/layer/cave.rs @@ -181,7 +181,7 @@ impl Tunnel { }; // Below the ground - let below = ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * 2.0)).clamped(0.0, 1.0); + let below = ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * 1.5)).clamped(0.0, 1.0); let depth = (col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32); let underground = ((col.alt - wpos.z as f32) / 80.0 - 1.0).clamped(0.0, 1.0); @@ -204,7 +204,7 @@ impl Tunnel { .sub(1.0) .add( ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.6)) - .clamped(0.0, 2.5), + .clamped(0.0, 2.0), ), below, ); @@ -234,38 +234,38 @@ impl Tunnel { // Mushrooms grow underground and thrive in a humid environment with moderate // temperatures let mushroom = underground - * close(humidity, 1.0, 0.7, 4) + * close(humidity, 1.0, 0.6, 4) * close(temp, 1.5, 0.9, 4) - * close(depth, 1.0, 0.6, 4); + * close(depth, 1.0, 0.55, 4); // Extremely hot and dry areas deep underground let fire = underground * close(humidity, 0.0, 0.6, 4) * close(temp, 2.0, 1.3, 4) - * close(depth, 1.0, 0.55, 4); + * close(depth, 1.0, 0.5, 4); // Overgrown with plants that need a moderate climate to survive let leafy = underground - * close(humidity, 0.8, 0.8, 4) - * close(temp, 0.75, 1.25, 4) - * close(depth, 0.0, 0.75, 4); + * close(humidity, 0.9, 0.65, 4) + * close(temp, 1.15, 0.85, 4) + * close(depth, 0.0, 0.65, 4); // Cool temperature, dry and devoid of value - let dusty = close(humidity, 0.0, 0.5, 4) * close(temp, -0.1, 0.6, 4); + let dusty = close(humidity, 0.0, 0.5, 4) * close(temp, -0.3, 0.7, 4); // Deep underground and freezing cold let icy = underground - * close(temp, -1.5, 1.3, 4) + * close(temp, -1.5, 1.0, 4) * close(depth, 1.0, 0.6, 4) * close(humidity, 1.0, 0.7, 4); // Rocky cold cave that appear near the surface - let snowy = close(temp, -0.6, 0.5, 4) * close(depth, 0.0, 0.45, 4); + let snowy = close(temp, -0.8, 0.5, 4) * close(depth, 0.0, 0.45, 4); // Crystals grow deep underground in areas rich with minerals. They are present // in areas with colder temperatures and low humidity let crystal = underground * close(humidity, 0.0, 0.5, 4) - * close(temp, -0.6, 0.75, 4) + * close(temp, -0.9, 0.7, 4) * close(depth, 1.0, 0.55, 4) * close(mineral, 2.0, 1.25, 4); // Hot, dry and shallow let sandy = - close(humidity, 0.0, 0.3, 4) * close(temp, 0.7, 0.9, 4) * close(depth, 0.0, 0.6, 4); + close(humidity, 0.0, 0.4, 4) * close(temp, 1.1, 0.6, 4) * close(depth, 0.0, 0.6, 4); let biomes = [ barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy, @@ -612,20 +612,20 @@ fn write_column( 0.0 }; - let basalt = if biome.fire > 0.0 { + let basalt = if biome.fire > 0.5 { FastNoise2d::new(36) - .get(wpos2d.map(|e| e as f64 / 32.0)) - .mul(1.25) + .get(wpos2d.map(|e| e as f64 / 40.0)) + .mul(1.1) .sub(0.5) .max(0.0) - .mul(((cave_width + max_height) / 32.0).clamped(0.0, 1.0)) - .mul(6.0 + cavern_height * 0.5) - .mul(biome.fire) + .mul(((cave_width + max_height) / 48.0).clamped(0.0, 1.0)) + .mul(6.0 + cavern_height * 0.6) + .mul((biome.fire - 0.5).powi(3) * 8.0) } else { 0.0 }; - let lava = if biome.fire > 0.0 { + let lava = if biome.fire > 0.5 { FastNoise2d::new(37) .get(wpos2d.map(|e| e as f64 / 32.0)) .mul(0.5) @@ -633,6 +633,8 @@ fn write_column( .sub(0.2) .min(0.0) // .mul((biome.temp as f64 - 1.5).mul(30.0).clamped(0.0, 1.0)) + .mul((cave_width / 16.0).clamped(0.0, 1.0)) + .mul((cave_width / (MAX_RADIUS - 16.0)).clamped(1.0, 1.25)) .mul((biome.fire - 0.5).mul(30.0).clamped(0.0, 1.0)) .mul(64.0) .max(-32.0) @@ -640,40 +642,35 @@ fn write_column( 0.0 }; - let height_factor = (max_height / MAX_RADIUS * 0.5).clamped(0.0, 1.0).powf(2.0); - let width_factor = (cave_width / MAX_RADIUS * 0.5).clamped(0.0, 1.0).powf(2.0); - let ridge = FastNoise2d::new(38) - .get(wpos2d.map(|e| e as f64 / 512.0)) - .sub(0.25) - .max(0.0) - .mul(1.3) - .mul(height_factor) - .mul(width_factor) - .mul( - (0.75 * dist_cave_center) - + max_height * (close(dist_cave_center, cave_width, cave_width * 0.7, 3)), - ) - .mul(((col.alt - z_range.end as f32) / 64.0).clamped(0.0, 1.0)); - - let bump = FastNoise2d::new(39) - .get(wpos2d.map(|e| e as f64 / 4.0)) - .mul(1.15) - .add(1.0) - .mul(0.5) - .mul(((col.alt - z_range.end as f32) / 16.0).clamped(0.0, 1.0)) - .mul({ - let (val, total) = [ - (biome.sandy, 0.9), - (biome.dusty, 0.5), - (biome.leafy, 0.6), - (biome.barren, 0.6), - ] - .into_iter() - .fold((0.0, 0.0), |a, x| (a.0 + x.0.max(0.0) * x.1, a.1 + x.1)); - val / total - }) - .mul(cavern_height * 0.2) - .clamped(0.0, 4.0); + let bump = if biome + .sandy + .max(biome.dusty) + .max(biome.leafy) + .max(biome.barren) + > 0.5 + { + FastNoise2d::new(38) + .get(wpos2d.map(|e| e as f64 / 4.0)) + .mul(1.15) + .add(1.0) + .mul(0.5) + .mul(((col.alt - z_range.end as f32) / 16.0).clamped(0.0, 1.0)) + .mul({ + let (val, total) = [ + (biome.sandy, 0.9), + (biome.dusty, 0.5), + (biome.leafy, 0.6), + (biome.barren, 0.6), + ] + .into_iter() + .fold((0.0, 0.0), |a, x| (a.0 + x.0.max(0.0) * x.1, a.1 + x.1)); + val / total + }) + .mul(cavern_height * 0.2) + .clamped(0.0, 4.0) + } else { + 0.0 + }; let rand = RandomField::new(37 + level); @@ -699,9 +696,8 @@ fn write_column( 1 + (!is_ice) as i32 + is_snow as i32 }; let bedrock = z_range.start + lava as i32; - let ridge_bedrock = bedrock + (ridge * 0.7) as i32; - let base = ridge_bedrock + (stalactite * (0.4 + stalagmite_only)) as i32; - let floor = base + dirt + (ridge * 0.3) as i32 + bump as i32; + let base = bedrock + (stalactite * (0.4 + stalagmite_only)) as i32; + let floor = base + dirt + bump as i32; let ceiling = z_range.end - (stalactite * has_stalactite as i32 as f32).max(ceiling_cover) as i32; @@ -844,11 +840,12 @@ fn write_column( })) } else if biome.leafy > 0.8 && vertical > 16.0 - && horizontal > 8.0 + && horizontal > 16.0 && rng.gen_bool( - 0.25 * (close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0, 2) - * close(horizontal, MAX_RADIUS, MAX_RADIUS - 8.0, 2) - * biome.leafy) as f64, + 0.125 + * (close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0, 2) + * close(horizontal, MAX_RADIUS, MAX_RADIUS - 16.0, 2) + * biome.leafy) as f64, ) { if tunnel_intersection() { @@ -877,7 +874,7 @@ fn write_column( Some(CaveStructure::GiantRoot { pos, radius: rng.gen_range( - 1.5..(3.5 + 2.5..(3.5 + close(vertical, MAX_RADIUS, MAX_RADIUS / 2.0, 2) * 3.0 + close(horizontal, MAX_RADIUS, MAX_RADIUS / 2.0, 2) * 3.0), ), @@ -1115,7 +1112,7 @@ fn write_column( .powi(2) { return Some(Block::new( - BlockKind::GlowingMushroom, + BlockKind::GlowingWeakRock, Rgb::new(239, 192, 0), )); } @@ -1127,8 +1124,8 @@ fn write_column( height, } => { let wposf = wpos.map(|e| e as f64); - let warp_freq = 1.0 / 32.0; - let warp_amp = Vec3::new(12.0, 12.0, 12.0); + let warp_freq = 1.0 / 16.0; + let warp_amp = Vec3::new(8.0, 8.0, 8.0); let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?; let wposf_warped = wposf.map(|e| e as f32) + warp_offset; let rpos = wposf_warped - pos.map(|e| e as f32); @@ -1169,8 +1166,6 @@ fn write_column( && !void_below { Block::new(BlockKind::Rock, Rgb::new(50, 35, 75)) - } else if z < ridge_bedrock && !void_below { - Block::new(BlockKind::Rock, col.stone_col) } else if (z < base && !void_below) || (z >= ceiling && !void_above) { let stalactite: Rgb = Lerp::lerp_unclamped( Lerp::lerp_unclamped( @@ -1669,7 +1664,7 @@ fn apply_entity_spawns(canvas: &mut Canvas, wpos: Vec3, biome: &Bio ( Some("common.entity.wild.aggressive.cave_spider"), biome.dusty + 0.0, - 0.4, + 0.05, 0.5, ), ( @@ -1719,19 +1714,19 @@ fn apply_entity_spawns(canvas: &mut Canvas, wpos: Vec3, biome: &Bio ( Some("common.entity.wild.aggressive.lavadrake"), biome.fire + 0.0, - 0.5, + 0.15, 0.5, ), ( Some("common.entity.wild.peaceful.crawler_molten"), biome.fire + 0.0, - 0.5, + 0.2, 0.5, ), ( Some("common.entity.wild.aggressive.cave_salamander"), biome.fire + 0.0, - 0.5, + 0.4, 0.5, ), ( @@ -1776,7 +1771,7 @@ fn apply_entity_spawns(canvas: &mut Canvas, wpos: Vec3, biome: &Bio ( Some("common.entity.wild.aggressive.akhlut"), (biome.snowy.max(biome.icy) + 0.1), - 0.05, + 0.01, 0.5, ), ( From 87c847b108a4fa146d030c8864674f4d873a473e Mon Sep 17 00:00:00 2001 From: Syniis Date: Mon, 4 Mar 2024 00:47:35 +0100 Subject: [PATCH 3/6] Cleanup --- Cargo.lock | 15 ----------- world/Cargo.toml | 2 +- world/benches/cave.rs | 25 ------------------ world/src/layer/cave.rs | 2 -- world/src/util/structure.rs | 52 ------------------------------------- 5 files changed, 1 insertion(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 433f4fbedc..ecc5992933 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3166,22 +3166,8 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6acddbefae08bfba73e27f55513f491f35c365d84bf3002bf85ba9b916c5e5f" dependencies = [ - "inline_tweak_derive", "lazy_static", - "proc-macro2 1.0.78", "rustc-hash", - "syn 2.0.48", -] - -[[package]] -name = "inline_tweak_derive" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46d62a0a3b6af04d4eee8e7251cd758ce74b0ed86253d3e4ac8a1b297a75f4a0" -dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", ] [[package]] @@ -7456,7 +7442,6 @@ dependencies = [ "hashbrown 0.13.2", "image", "indicatif", - "inline_tweak", "itertools 0.10.5", "kiddo", "lazy_static", diff --git a/world/Cargo.toml b/world/Cargo.toml index a392968c1d..92bd288fbf 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -40,7 +40,7 @@ packed_simd = { version = "0.3.9", optional = true } rayon = { workspace = true } serde = { workspace = true } ron = { workspace = true } -inline_tweak = { workspace = true, features = ["derive"] } +# inline_tweak = { workspace = true, features = ["derive"] } kiddo = "0.2" strum = { workspace = true } diff --git a/world/benches/cave.rs b/world/benches/cave.rs index b8f0ec2e1e..795e2ffd30 100644 --- a/world/benches/cave.rs +++ b/world/benches/cave.rs @@ -1,6 +1,5 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use rayon::ThreadPoolBuilder; -use vek::Vec2; use veloren_world::{ layer, sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, @@ -37,30 +36,6 @@ fn cave(c: &mut Criterion) { } }); }); - - c.bench_function("generate_specific", |b| { - b.iter(|| { - let base_positions = vec![ - Vec2::new(600, 650), - Vec2::new(630, 300), - Vec2::new(809, 141), - ]; - for base_pos in base_positions { - for i in 0..=4 { - for j in 0..=4 { - let pos = base_pos + Vec2::new(i as i32, j as i32) - 2; - _ = black_box(world.generate_chunk( - index.as_index_ref(), - pos, - None, - || false, - None, - )); - } - } - } - }); - }); } criterion_group!(benches, cave); diff --git a/world/src/layer/cave.rs b/world/src/layer/cave.rs index bfab1c15d8..29cf56dcc5 100644 --- a/world/src/layer/cave.rs +++ b/world/src/layer/cave.rs @@ -174,7 +174,6 @@ impl Tunnel { } } - #[inline_tweak::tweak_fn] fn biome_at(&self, wpos: Vec3, info: &CanvasInfo) -> Biome { let Some(col) = info.col_or_gen(wpos.xy()) else { return Biome::default(); @@ -542,7 +541,6 @@ struct Flower { // rotation: Mat3, } -#[inline_tweak::tweak_fn] fn write_column( canvas: &mut Canvas, col: &ColumnSample, diff --git a/world/src/util/structure.rs b/world/src/util/structure.rs index 5326e359f1..ed0fe38ebf 100644 --- a/world/src/util/structure.rs +++ b/world/src/util/structure.rs @@ -111,58 +111,6 @@ impl StructureGen2d { ) }) } - - /// Note: Generates all possible closest samples for elements in the range - /// of min to max, *exclusive.* - pub fn iter_with_wpos( - &self, - min: Vec2, - max: Vec2, - ) -> impl Iterator, StructureField)> { - let freq = self.freq; - let spread = self.spread; - let spread_mul = Self::spread_mul(spread); - assert!(spread * 2 == spread_mul); - assert!(spread_mul <= freq); - let spread = spread as i32; - let freq = freq as i32; - let freq_offset = Self::freq_offset(freq); - assert!(freq_offset * 2 == freq); - - let min_index = Self::sample_to_index_internal(freq, min) - 1; - let max_index = Self::sample_to_index_internal(freq, max) + 1; - assert!(min_index.x < max_index.x); - // NOTE: xlen > 0 - let xlen = (max_index.x - min_index.x) as u32; - assert!(min_index.y < max_index.y); - // NOTE: ylen > 0 - let ylen = (max_index.y - min_index.y) as u32; - // NOTE: Cannot fail, since every product of u32s fits in a u64. - let len = ylen as u64 * xlen as u64; - // NOTE: since iteration is *exclusive* for the initial range, it's fine that we - // don't go up to the maximum value. - // NOTE: we convert to usize first, and then iterate, because we want to make - // sure we get a properly indexed parallel iterator that can deal with - // the whole range at once. - let x_field = self.x_field; - let y_field = self.y_field; - let seed_field = self.seed_field; - (0..len).map(move |xy| { - let index = min_index + Vec2::new((xy % xlen as u64) as i32, (xy / xlen as u64) as i32); - let index_wpos = min + Vec2::new((xy % xlen as u64) as i32, (xy / len as u64) as i32); - let field = Self::index_to_sample_internal( - freq, - freq_offset, - spread, - spread_mul, - x_field, - y_field, - seed_field, - index, - ); - (index_wpos, field) - }) - } } impl Sampler<'static> for StructureGen2d { From 81c2b7e96996b53135837b14fbadd7b1f15ed9f4 Mon Sep 17 00:00:00 2001 From: Syniis Date: Tue, 5 Mar 2024 00:12:35 +0100 Subject: [PATCH 4/6] Several tweaks to biome distribution --- world/benches/cave.rs | 51 +++++++++++++++-- world/examples/cave_biomes.rs | 67 +++++++++++++++++++++++ world/src/layer/cave.rs | 100 ++++++++++++++++++++++------------ 3 files changed, 176 insertions(+), 42 deletions(-) create mode 100644 world/examples/cave_biomes.rs diff --git a/world/benches/cave.rs b/world/benches/cave.rs index 795e2ffd30..121c9fe35a 100644 --- a/world/benches/cave.rs +++ b/world/benches/cave.rs @@ -1,9 +1,11 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use common::{spiral::Spiral2d, terrain::CoordinateConversions}; +use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; +use rand::{seq::IteratorRandom, thread_rng}; use rayon::ThreadPoolBuilder; use veloren_world::{ layer, sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, - Land, World, + CanvasInfo, Land, World, }; fn cave(c: &mut Criterion) { @@ -19,12 +21,13 @@ fn cave(c: &mut Criterion) { &|_| {}, ); let land = Land::from_sim(world.sim()); - - c.bench_function("generate", |b| { + let mut group = c.benchmark_group("cave"); + group.sample_size(25); + group.bench_function("generate_entrances", |b| { b.iter(|| { let entrances = black_box(layer::cave::surface_entrances(&land)) - .step_by(5) - .map(|e| e / 32); + .step_by(10) + .map(|e| e.wpos_to_cpos()); for entrance in entrances { _ = black_box(world.generate_chunk( index.as_index_ref(), @@ -36,6 +39,42 @@ fn cave(c: &mut Criterion) { } }); }); + + group.bench_function("generate_hard", |b| { + b.iter_batched( + || { + let entrance = layer::cave::surface_entrances(&land) + .choose(&mut thread_rng()) + .unwrap() + .wpos_to_cpos(); + Spiral2d::new() + .step_by(8) + .find(|p| { + CanvasInfo::with_mock_canvas_info( + index.as_index_ref(), + world.sim(), + |&info| { + let land = &info.land(); + let tunnels = + layer::cave::tunnel_bounds_at(entrance + p, &info, land); + tunnels.count() > 2 + }, + ) + }) + .map_or(entrance, |p| entrance + p) + }, + |chunk| { + _ = black_box(world.generate_chunk( + index.as_index_ref(), + chunk, + None, + || false, + None, + )); + }, + BatchSize::SmallInput, + ); + }); } criterion_group!(benches, cave); diff --git a/world/examples/cave_biomes.rs b/world/examples/cave_biomes.rs new file mode 100644 index 0000000000..6d29a9b659 --- /dev/null +++ b/world/examples/cave_biomes.rs @@ -0,0 +1,67 @@ +use common::terrain::CoordinateConversions; +use rayon::ThreadPoolBuilder; +use vek::Vec2; +use veloren_world::{ + layer::{ + self, + cave::{Biome, LAYERS}, + }, + sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, + CanvasInfo, Land, World, +}; + +fn main() { + let pool = ThreadPoolBuilder::new().build().unwrap(); + let (world, index) = World::generate( + 123, + WorldOpts { + seed_elements: true, + world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), + ..WorldOpts::default() + }, + &pool, + &|_| {}, + ); + let land = Land::from_sim(world.sim()); + + let mut biomes: Vec<(Biome, u32)> = vec![(Biome::default(), 0); LAYERS as usize]; + for x in 0..land.size().x { + for y in 0..land.size().y { + let wpos = Vec2::new(x as i32, y as i32).cpos_to_wpos(); + CanvasInfo::with_mock_canvas_info(index.as_index_ref(), world.sim(), |&info| { + let land = &info.land(); + let tunnels = layer::cave::tunnel_bounds_at(wpos, &info, land); + for (level, z_range, _, _, _, tunnel) in tunnels { + let biome = tunnel.biome_at(wpos.with_z(z_range.start), &info); + let (ref mut current, ref mut total) = &mut biomes[level as usize - 1]; + current.barren += biome.barren; + current.mushroom += biome.mushroom; + current.fire += biome.fire; + current.leafy += biome.leafy; + current.dusty += biome.dusty; + current.icy += biome.icy; + current.snowy += biome.snowy; + current.crystal += biome.crystal; + current.sandy += biome.sandy; + *total += 1; + } + }); + } + } + + for (level, (biome, total)) in biomes.iter().enumerate() { + let total = *total as f32; + println!("--- LEVEL {} ---", level); + println!("TOTAL {}", total); + println!("BARREN {:.3}", biome.barren / total); + println!("MUSHROOM {:.3}", biome.mushroom / total); + println!("FIRE {:.3}", biome.fire / total); + println!("LEAFY {:.3}", biome.leafy / total); + println!("DUSTY {:.3}", biome.dusty / total); + println!("ICY {:.3}", biome.icy / total); + println!("SNOWY {:.3}", biome.snowy / total); + println!("CRYSTAL {:.3}", biome.crystal / total); + println!("SANDY {:.3}", biome.sandy / total); + println!("\n"); + } +} diff --git a/world/src/layer/cave.rs b/world/src/layer/cave.rs index 29cf56dcc5..71d0b47af0 100644 --- a/world/src/layer/cave.rs +++ b/world/src/layer/cave.rs @@ -40,7 +40,7 @@ fn to_wpos(cell: Vec2, level: u32) -> Vec2 { } const AVG_LEVEL_DEPTH: i32 = 120; -const LAYERS: u32 = 4; +pub const LAYERS: u32 = 5; const MIN_RADIUS: f32 = 8.0; const MAX_RADIUS: f32 = 64.0; @@ -174,13 +174,14 @@ impl Tunnel { } } - fn biome_at(&self, wpos: Vec3, info: &CanvasInfo) -> Biome { + pub fn biome_at(&self, wpos: Vec3, info: &CanvasInfo) -> Biome { let Some(col) = info.col_or_gen(wpos.xy()) else { return Biome::default(); }; // Below the ground - let below = ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * 1.5)).clamped(0.0, 1.0); + let below = ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.5)) + .clamped(0.0, 1.0); let depth = (col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32); let underground = ((col.alt - wpos.z as f32) / 80.0 - 1.0).clamped(0.0, 1.0); @@ -195,7 +196,7 @@ impl Tunnel { ); let temp = Lerp::lerp_unclamped( - col.temp, + col.temp * 2.0, FastNoise2d::new(42) .get(wpos.xy().map(|e| e as f64 / 1536.0)) .mul(1.15) @@ -229,42 +230,44 @@ impl Tunnel { sandy, ] = { // Default biome, no other conditions apply - let barren = 0.01; + let barren = 0.005; // Mushrooms grow underground and thrive in a humid environment with moderate // temperatures let mushroom = underground - * close(humidity, 1.0, 0.6, 4) - * close(temp, 1.5, 0.9, 4) - * close(depth, 1.0, 0.55, 4); + * close(humidity, 1.0, 0.7, 4) + * close(temp, 1.5, 1.2, 4) + * close(depth, 0.9, 0.65, 4); // Extremely hot and dry areas deep underground let fire = underground * close(humidity, 0.0, 0.6, 4) - * close(temp, 2.0, 1.3, 4) - * close(depth, 1.0, 0.5, 4); + * close(temp, 2.0, 1.4, 4) + * close(depth, 1.0, 0.45, 4); // Overgrown with plants that need a moderate climate to survive let leafy = underground - * close(humidity, 0.9, 0.65, 4) - * close(temp, 1.15, 0.85, 4) - * close(depth, 0.0, 0.65, 4); + * close(humidity, 0.8, 0.9, 4) + * close(temp, 0.8, 1.0, 4) + * close(depth, 0.1, 0.7, 4); // Cool temperature, dry and devoid of value - let dusty = close(humidity, 0.0, 0.5, 4) * close(temp, -0.3, 0.7, 4); + let dusty = close(humidity, 0.0, 0.5, 4) + * close(temp, -0.3, 0.7, 4) + * close(depth, 0.5, 0.5, 4); // Deep underground and freezing cold let icy = underground - * close(temp, -1.5, 1.0, 4) - * close(depth, 1.0, 0.6, 4) - * close(humidity, 1.0, 0.7, 4); + * close(temp, -1.5, 2.0, 4) + * close(depth, 0.9, 0.55, 4) + * close(humidity, 1.0, 0.75, 4); // Rocky cold cave that appear near the surface - let snowy = close(temp, -0.8, 0.5, 4) * close(depth, 0.0, 0.45, 4); + let snowy = close(temp, -0.7, 0.4, 4) * close(depth, 0.0, 0.4, 4); // Crystals grow deep underground in areas rich with minerals. They are present // in areas with colder temperatures and low humidity let crystal = underground * close(humidity, 0.0, 0.5, 4) - * close(temp, -0.9, 0.7, 4) + * close(temp, -0.9, 0.9, 4) * close(depth, 1.0, 0.55, 4) * close(mineral, 2.0, 1.25, 4); // Hot, dry and shallow let sandy = - close(humidity, 0.0, 0.4, 4) * close(temp, 1.1, 0.6, 4) * close(depth, 0.0, 0.6, 4); + close(humidity, 0.0, 0.5, 4) * close(temp, 1.0, 0.9, 4) * close(depth, 0.0, 0.6, 4); let biomes = [ barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy, @@ -463,6 +466,12 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { } } + let z_ranges = &tunnel_bounds + .iter() + .map(|(_, z_range, _, _, _, _)| z_range.clone()) + .collect_vec(); + + let structure_seeds = StructureGen2d::new(34537, 24, 8).get(wpos2d); for (level, z_range, horizontal, vertical, dist, tunnel) in tunnel_bounds { write_column( canvas, @@ -470,10 +479,12 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { level, wpos2d, z_range.clone(), + z_ranges, tunnel, (horizontal, vertical, dist), giant_tree_dist, &mut structure_cache, + &structure_seeds, rng, ); } @@ -482,19 +493,19 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { } #[allow(dead_code)] -#[derive(Default)] -struct Biome { +#[derive(Default, Clone)] +pub struct Biome { humidity: f32, - barren: f32, + pub barren: f32, mineral: f32, - mushroom: f32, - fire: f32, - leafy: f32, - dusty: f32, - icy: f32, - snowy: f32, - crystal: f32, - sandy: f32, + pub mushroom: f32, + pub fire: f32, + pub leafy: f32, + pub dusty: f32, + pub icy: f32, + pub snowy: f32, + pub crystal: f32, + pub sandy: f32, depth: f32, } @@ -547,10 +558,12 @@ fn write_column( level: u32, wpos2d: Vec2, z_range: Range, + z_ranges: &[Range], tunnel: Tunnel, dimensions: (f32, f32, f32), giant_tree_dist: f32, structure_cache: &mut SmallCache, Option>, + structure_seeds: &[(Vec2, u32); 9], rng: &mut R, ) { let info = canvas.info(); @@ -564,6 +577,19 @@ fn write_column( let (cave_width, max_height, dist_cave_center) = dimensions; let biome = tunnel.biome_at(wpos2d.with_z(z_range.start), &info); + // Get the range, if there is any, where the current cave overlaps with other + // caves. Right now this is only used to prevent ceiling cover from being + // place + let overlap = z_ranges.iter().find_map(|other_z_range| { + if *other_z_range == z_range { + return None; + } + let start = z_range.start.max(other_z_range.start); + let end = z_range.end.min(other_z_range.end); + let min = z_range.start.min(other_z_range.start); + if start < end { Some(min..end) } else { None } + }); + let stalactite = { FastNoise2d::new(35) .get(wpos2d.map(|e| e as f64 / 8.0)) @@ -631,7 +657,7 @@ fn write_column( .sub(0.2) .min(0.0) // .mul((biome.temp as f64 - 1.5).mul(30.0).clamped(0.0, 1.0)) - .mul((cave_width / 16.0).clamped(0.0, 1.0)) + .mul((cave_width / 16.0).clamped(0.5, 1.0)) .mul((cave_width / (MAX_RADIUS - 16.0)).clamped(1.0, 1.25)) .mul((biome.fire - 0.5).mul(30.0).clamped(0.0, 1.0)) .mul(64.0) @@ -743,9 +769,7 @@ fn write_column( 0 }; - let structures = StructureGen2d::new(34537, 24, 8) - .get(wpos2d) - .as_slice() + let structures = structure_seeds .iter() .filter_map(|(wpos2d, seed)| { let structure = structure_cache.get(wpos2d.with_z(tunnel.a.depth), |_| { @@ -758,6 +782,7 @@ fn write_column( tunnel_bounds_at(pos.xy(), &info, &info.land()) .any(|(_, z_range, _, _, _, _)| z_range.contains(&(z_range.start - 1))) }; + if biome.mushroom > 0.7 && vertical > 16.0 && rng.gen_bool( @@ -1164,7 +1189,10 @@ fn write_column( && !void_below { Block::new(BlockKind::Rock, Rgb::new(50, 35, 75)) - } else if (z < base && !void_below) || (z >= ceiling && !void_above) { + } else if (z < base && !void_below) + || ((z >= ceiling && !void_above) + && !(ceiling_cover > 0.0 && overlap.as_ref().map_or(false, |o| o.contains(&z)))) + { let stalactite: Rgb = Lerp::lerp_unclamped( Lerp::lerp_unclamped( Lerp::lerp_unclamped( From 46af9ee489f715c0e453e8c8736f8409543bf87e Mon Sep 17 00:00:00 2001 From: Syniis Date: Mon, 11 Mar 2024 16:36:56 +0100 Subject: [PATCH 5/6] Biome distribution tweaks --- world/benches/cave.rs | 41 ++++++------ world/examples/cave_biomes.rs | 2 +- world/src/layer/cave.rs | 113 +++++++++++++++++----------------- 3 files changed, 77 insertions(+), 79 deletions(-) diff --git a/world/benches/cave.rs b/world/benches/cave.rs index 121c9fe35a..25b9c71e04 100644 --- a/world/benches/cave.rs +++ b/world/benches/cave.rs @@ -1,6 +1,5 @@ use common::{spiral::Spiral2d, terrain::CoordinateConversions}; -use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; -use rand::{seq::IteratorRandom, thread_rng}; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; use rayon::ThreadPoolBuilder; use veloren_world::{ layer, @@ -11,7 +10,7 @@ use veloren_world::{ fn cave(c: &mut Criterion) { let pool = ThreadPoolBuilder::new().build().unwrap(); let (world, index) = World::generate( - 123, + 230, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), @@ -22,12 +21,11 @@ fn cave(c: &mut Criterion) { ); let land = Land::from_sim(world.sim()); let mut group = c.benchmark_group("cave"); - group.sample_size(25); + group.sample_size(10); group.bench_function("generate_entrances", |b| { b.iter(|| { - let entrances = black_box(layer::cave::surface_entrances(&land)) - .step_by(10) - .map(|e| e.wpos_to_cpos()); + let entrances = + black_box(layer::cave::surface_entrances(&land)).map(|e| e.wpos_to_cpos()); for entrance in entrances { _ = black_box(world.generate_chunk( index.as_index_ref(), @@ -40,15 +38,14 @@ fn cave(c: &mut Criterion) { }); }); - group.bench_function("generate_hard", |b| { - b.iter_batched( - || { - let entrance = layer::cave::surface_entrances(&land) - .choose(&mut thread_rng()) - .unwrap() - .wpos_to_cpos(); - Spiral2d::new() - .step_by(8) + group.bench_function("generate_multiple_tunnels", |b| { + b.iter(|| { + let entrances = layer::cave::surface_entrances(&land) + .map(|e| e.wpos_to_cpos()) + .step_by(6); + for entrance in entrances { + let chunk = Spiral2d::new() + .step_by(16) .find(|p| { CanvasInfo::with_mock_canvas_info( index.as_index_ref(), @@ -57,13 +54,12 @@ fn cave(c: &mut Criterion) { let land = &info.land(); let tunnels = layer::cave::tunnel_bounds_at(entrance + p, &info, land); - tunnels.count() > 2 + tunnels.count() > 1 }, ) }) - .map_or(entrance, |p| entrance + p) - }, - |chunk| { + .map_or(entrance, |p| entrance + p); + _ = black_box(world.generate_chunk( index.as_index_ref(), chunk, @@ -71,9 +67,8 @@ fn cave(c: &mut Criterion) { || false, None, )); - }, - BatchSize::SmallInput, - ); + } + }); }); } diff --git a/world/examples/cave_biomes.rs b/world/examples/cave_biomes.rs index 6d29a9b659..26fa889d36 100644 --- a/world/examples/cave_biomes.rs +++ b/world/examples/cave_biomes.rs @@ -13,7 +13,7 @@ use veloren_world::{ fn main() { let pool = ThreadPoolBuilder::new().build().unwrap(); let (world, index) = World::generate( - 123, + 230, WorldOpts { seed_elements: true, world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()), diff --git a/world/src/layer/cave.rs b/world/src/layer/cave.rs index 71d0b47af0..ab3cfbd149 100644 --- a/world/src/layer/cave.rs +++ b/world/src/layer/cave.rs @@ -174,14 +174,14 @@ impl Tunnel { } } + // #[inline_tweak::tweak_fn] pub fn biome_at(&self, wpos: Vec3, info: &CanvasInfo) -> Biome { let Some(col) = info.col_or_gen(wpos.xy()) else { return Biome::default(); }; // Below the ground - let below = ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.5)) - .clamped(0.0, 1.0); + let below = ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * 2.0)).clamped(0.0, 1.0); let depth = (col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32); let underground = ((col.alt - wpos.z as f32) / 80.0 - 1.0).clamped(0.0, 1.0); @@ -198,13 +198,13 @@ impl Tunnel { let temp = Lerp::lerp_unclamped( col.temp * 2.0, FastNoise2d::new(42) - .get(wpos.xy().map(|e| e as f64 / 1536.0)) + .get(wpos.xy().map(|e| e as f64 / 2048.0)) .mul(1.15) .mul(2.0) - .sub(1.0) + .sub(0.5) .add( - ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32 * 0.6)) - .clamped(0.0, 2.0), + ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32)) + .clamped(0.0, 1.0), ), below, ); @@ -215,7 +215,7 @@ impl Tunnel { .mul(0.5) .add( ((col.alt - wpos.z as f32) / (AVG_LEVEL_DEPTH as f32 * LAYERS as f32)) - .clamped(0.0, 1.5), + .clamped(0.0, 1.0), ); let [ @@ -230,44 +230,45 @@ impl Tunnel { sandy, ] = { // Default biome, no other conditions apply - let barren = 0.005; + let barren = 0.01; // Mushrooms grow underground and thrive in a humid environment with moderate // temperatures let mushroom = underground * close(humidity, 1.0, 0.7, 4) - * close(temp, 1.5, 1.2, 4) - * close(depth, 0.9, 0.65, 4); + * close(temp, 2.0, 1.8, 4) + * close(depth, 0.9, 0.7, 4); // Extremely hot and dry areas deep underground let fire = underground * close(humidity, 0.0, 0.6, 4) - * close(temp, 2.0, 1.4, 4) - * close(depth, 1.0, 0.45, 4); + * close(temp, 2.5, 2.2, 4) + * close(depth, 1.0, 0.4, 4); // Overgrown with plants that need a moderate climate to survive let leafy = underground - * close(humidity, 0.8, 0.9, 4) - * close(temp, 0.8, 1.0, 4) + * close(humidity, 0.8, 0.8, 4) + * close(temp, 1.2 + depth, 1.5, 4) * close(depth, 0.1, 0.7, 4); // Cool temperature, dry and devoid of value let dusty = close(humidity, 0.0, 0.5, 4) - * close(temp, -0.3, 0.7, 4) + * close(temp, -0.3, 0.6, 4) * close(depth, 0.5, 0.5, 4); // Deep underground and freezing cold let icy = underground - * close(temp, -1.5, 2.0, 4) - * close(depth, 0.9, 0.55, 4) - * close(humidity, 1.0, 0.75, 4); + * close(temp, -2.3 + (depth - 0.5) * 0.5, 2.0, 4) + * close(depth, 0.8, 0.6, 4) + * close(humidity, 1.0, 0.85, 4); // Rocky cold cave that appear near the surface - let snowy = close(temp, -0.7, 0.4, 4) * close(depth, 0.0, 0.4, 4); + let snowy = close(temp, -1.8, 1.3, 4) * close(depth, 0.0, 0.6, 4); // Crystals grow deep underground in areas rich with minerals. They are present // in areas with colder temperatures and low humidity let crystal = underground - * close(humidity, 0.0, 0.5, 4) - * close(temp, -0.9, 0.9, 4) - * close(depth, 1.0, 0.55, 4) - * close(mineral, 2.0, 1.25, 4); + * close(humidity, 0.0, 0.6, 4) + * close(temp, -1.6, 1.3, 4) + * close(depth, 1.0, 0.5, 4) + * close(mineral, 1.5, 1.0, 4); // Hot, dry and shallow - let sandy = - close(humidity, 0.0, 0.5, 4) * close(temp, 1.0, 0.9, 4) * close(depth, 0.0, 0.6, 4); + let sandy = close(humidity, 0.0, 0.4, 4) + * close(temp, 0.7, 0.8, 4) + * close(depth, 0.0, 0.65, 4); let biomes = [ barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy, @@ -471,6 +472,9 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { .map(|(_, z_range, _, _, _, _)| z_range.clone()) .collect_vec(); + // Compute structure samples only once for each column. + // TODO Use iter function in StructureGen2d to compute samples for the whole + // chunk once let structure_seeds = StructureGen2d::new(34537, 24, 8).get(wpos2d); for (level, z_range, horizontal, vertical, dist, tunnel) in tunnel_bounds { write_column( @@ -492,7 +496,6 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) { } } -#[allow(dead_code)] #[derive(Default, Clone)] pub struct Biome { humidity: f32, @@ -552,6 +555,7 @@ struct Flower { // rotation: Mat3, } +// #[inline_tweak::tweak_fn] fn write_column( canvas: &mut Canvas, col: &ColumnSample, @@ -638,12 +642,12 @@ fn write_column( let basalt = if biome.fire > 0.5 { FastNoise2d::new(36) - .get(wpos2d.map(|e| e as f64 / 40.0)) - .mul(1.1) - .sub(0.5) + .get(wpos2d.map(|e| e as f64 / 16.0)) + .mul(1.25) + .sub(0.75) .max(0.0) - .mul(((cave_width + max_height) / 48.0).clamped(0.0, 1.0)) - .mul(6.0 + cavern_height * 0.6) + .mul(((cave_width + max_height) / 64.0).clamped(0.0, 1.0)) + .mul(6.0 + cavern_height * 0.5) .mul((biome.fire - 0.5).powi(3) * 8.0) } else { 0.0 @@ -658,7 +662,6 @@ fn write_column( .min(0.0) // .mul((biome.temp as f64 - 1.5).mul(30.0).clamped(0.0, 1.0)) .mul((cave_width / 16.0).clamped(0.5, 1.0)) - .mul((cave_width / (MAX_RADIUS - 16.0)).clamped(1.0, 1.25)) .mul((biome.fire - 0.5).mul(30.0).clamped(0.0, 1.0)) .mul(64.0) .max(-32.0) @@ -671,7 +674,7 @@ fn write_column( .max(biome.dusty) .max(biome.leafy) .max(biome.barren) - > 0.5 + > 0.0 { FastNoise2d::new(38) .get(wpos2d.map(|e| e as f64 / 4.0)) @@ -748,7 +751,7 @@ fn write_column( }; let ceiling_drip = ceiling - - if !void_above && !sky_above { + - if !void_above && !sky_above && overlap.is_none() { let c = if biome.mushroom > 0.9 && ceiling_cover > 0.0 { Some((0.07, 7.0)) } else if biome.icy > 0.9 { @@ -865,10 +868,9 @@ fn write_column( && vertical > 16.0 && horizontal > 16.0 && rng.gen_bool( - 0.125 - * (close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0, 2) - * close(horizontal, MAX_RADIUS, MAX_RADIUS - 16.0, 2) - * biome.leafy) as f64, + 0.25 * (close(vertical, MAX_RADIUS, MAX_RADIUS - 16.0, 2) + * close(horizontal, MAX_RADIUS, MAX_RADIUS - 16.0, 2) + * biome.leafy) as f64, ) { if tunnel_intersection() { @@ -908,23 +910,22 @@ fn write_column( } }); + // TODO Some way to not clone here? structure .as_ref() .map(|structure| (*seed, structure.clone())) }) .collect_vec(); let get_structure = |wpos: Vec3, dynamic_rng: &mut R| { - let warp = |wposf: Vec3, freq: f64, amp: Vec3, seed: u32| -> Option> { + let warp = |wposf: Vec3, freq: f64, amp: Vec3, seed: u32| -> Vec3 { let xy = wposf.xy(); let xz = Vec2::new(wposf.x, wposf.z); let yz = Vec2::new(wposf.y, wposf.z); - Some( - Vec3::new( - FastNoise2d::new(seed).get(yz * freq), - FastNoise2d::new(seed).get(xz * freq), - FastNoise2d::new(seed).get(xy * freq), - ) * amp, - ) + Vec3::new( + FastNoise2d::new(seed).get(yz * freq), + FastNoise2d::new(seed).get(xz * freq), + FastNoise2d::new(seed).get(xy * freq), + ) * amp }; for (seed, structure) in structures.iter() { let seed = *seed; @@ -933,7 +934,7 @@ fn write_column( let wposf = wpos.map(|e| e as f64); let warp_freq = 1.0 / 32.0; let warp_amp = Vec3::new(12.0, 12.0, 12.0); - let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?; + let warp_offset = warp(wposf, warp_freq, warp_amp, seed); let wposf_warped = wposf.map(|e| e as f32) + warp_offset * (wposf.z as f32 - mushroom.pos.z as f32) @@ -1058,7 +1059,7 @@ fn write_column( let wposf = wpos.map(|e| e as f64); let warp_freq = 1.0 / 16.0; let warp_amp = Vec3::new(8.0, 8.0, 8.0); - let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?; + let warp_offset = warp(wposf, warp_freq, warp_amp, seed); let wposf_warped = wposf.map(|e| e as f32) + warp_offset * (wposf.z as f32 - flower.pos.z as f32) @@ -1149,7 +1150,7 @@ fn write_column( let wposf = wpos.map(|e| e as f64); let warp_freq = 1.0 / 16.0; let warp_amp = Vec3::new(8.0, 8.0, 8.0); - let warp_offset = warp(wposf, warp_freq, warp_amp, seed)?; + let warp_offset = warp(wposf, warp_freq, warp_amp, seed); let wposf_warped = wposf.map(|e| e as f32) + warp_offset; let rpos = wposf_warped - pos.map(|e| e as f32); let dist_sq = rpos.xy().magnitude_squared(); @@ -1381,7 +1382,6 @@ fn write_column( [ (SpriteKind::GlowMushroom, 0.5), (SpriteKind::Mushroom, 0.25), - (SpriteKind::GrassBlue, 0.0), (SpriteKind::GrassBlueMedium, 1.5), (SpriteKind::GrassBlueLong, 2.0), (SpriteKind::Moonbell, 0.01), @@ -1394,7 +1394,7 @@ fn write_column( && biome.leafy > 0.4 && rand.chance( wpos2d.with_z(15), - biome.leafy.powi(2) * 0.25 * col.marble_mid, + biome.leafy.powi(2) * col.marble_mid * (biome.humidity * 1.3) * 0.25, ) { let mixed = col.marble.add(col.marble_small.sub(0.5).mul(0.25)); @@ -1402,7 +1402,6 @@ fn write_column( return [ (SpriteKind::LongGrass, 1.0), (SpriteKind::MediumGrass, 2.0), - (SpriteKind::ShortGrass, 0.0), (SpriteKind::JungleFern, 0.5), (SpriteKind::JungleRedGrass, 0.35), (SpriteKind::Fern, 0.75), @@ -1429,7 +1428,6 @@ fn write_column( return [ (SpriteKind::LongGrass, 1.0), (SpriteKind::MediumGrass, 2.0), - (SpriteKind::ShortGrass, 0.0), (SpriteKind::JungleFern, 0.5), (SpriteKind::JungleLeafyPlant, 0.5), (SpriteKind::JungleRedGrass, 0.35), @@ -1551,14 +1549,18 @@ fn write_column( Block::air(sprite) } else if let Some(sprite) = (z == ceiling - 1 && !void_above) .then(|| { - if biome.mushroom > 0.5 && rand.chance(wpos2d.with_z(3), biome.mushroom * 0.01) + if biome.mushroom > 0.5 + && rand.chance(wpos2d.with_z(3), biome.mushroom.powi(2) * 0.01) { [(SpriteKind::MycelBlue, 0.75), (SpriteKind::Mold, 1.0)] .choose_weighted(rng, |(_, w)| *w) .ok() .map(|s| s.0) } else if biome.leafy > 0.4 - && rand.chance(wpos2d.with_z(4), biome.leafy * 0.015) + && rand.chance( + wpos2d.with_z(4), + biome.leafy * (biome.humidity * 1.3) * 0.015, + ) { [ (SpriteKind::Liana, 1.5), @@ -1593,6 +1595,7 @@ fn write_column( } } +// #[inline_tweak::tweak_fn] fn apply_entity_spawns(canvas: &mut Canvas, wpos: Vec3, biome: &Biome, rng: &mut R) { if RandomField::new(canvas.info().index().seed).chance(wpos, 0.035) { if let Some(entity_asset) = [ From 361fb5b080d8bdd3977b5f93371a3197867eb1f7 Mon Sep 17 00:00:00 2001 From: Syniis Date: Sat, 23 Mar 2024 18:11:42 +0100 Subject: [PATCH 6/6] Use fixed size array for crystals --- world/src/layer/cave.rs | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/world/src/layer/cave.rs b/world/src/layer/cave.rs index ab3cfbd149..96db840e3a 100644 --- a/world/src/layer/cave.rs +++ b/world/src/layer/cave.rs @@ -541,7 +541,7 @@ struct Crystal { #[derive(Clone)] struct CrystalCluster { pos: Vec3, - crystals: Vec, + crystals: [Crystal; 5], color: Rgb, } @@ -822,7 +822,6 @@ fn write_column( z_range.end }); - let mut crystals: Vec = Vec::new(); let max_length = (48.0 * close(vertical, MAX_RADIUS, MAX_RADIUS, 1)).max(12.0); let length = rng.gen_range(8.0..max_length); let radius = @@ -834,27 +833,30 @@ fn write_column( ) .normalized(); - crystals.push(Crystal { - dir, - length, - radius, - }); + let mut gen_crystal = || Crystal { + dir: Vec3::new( + rng.gen_range(-1.0..1.0), + rng.gen_range(-1.0..1.0), + (dir.z + rng.gen_range(-0.2..0.2)).clamped(0.0, 1.0), + ), + length: length * rng.gen_range(0.3..0.8), + radius: (radius * rng.gen_range(0.5..0.8)).max(1.0), + }; - (0..4).for_each(|_| { - crystals.push(Crystal { - dir: Vec3::new( - rng.gen_range(-1.0..1.0), - rng.gen_range(-1.0..1.0), - (dir.z + rng.gen_range(-0.2..0.2)).clamped(0.0, 1.0), - ), - length: length * rng.gen_range(0.3..0.8), - radius: (radius * rng.gen_range(0.5..0.8)).max(1.0), - }); - }); + let crystals = [ + Crystal { + dir, + length, + radius, + }, + gen_crystal(), + gen_crystal(), + gen_crystal(), + gen_crystal(), + ]; let purple = rng.gen_range(25..75); let blue = (rng.gen_range(45.0..75.0) * biome.icy) as u8; - Some(CaveStructure::Crystal(CrystalCluster { pos, crystals,