Several tweaks to biome distribution

This commit is contained in:
Syniis 2024-03-05 00:12:35 +01:00
parent 87c847b108
commit 81c2b7e969
3 changed files with 176 additions and 42 deletions

View File

@ -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 rayon::ThreadPoolBuilder;
use veloren_world::{ use veloren_world::{
layer, layer,
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP}, sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP},
Land, World, CanvasInfo, Land, World,
}; };
fn cave(c: &mut Criterion) { fn cave(c: &mut Criterion) {
@ -19,12 +21,13 @@ fn cave(c: &mut Criterion) {
&|_| {}, &|_| {},
); );
let land = Land::from_sim(world.sim()); let land = Land::from_sim(world.sim());
let mut group = c.benchmark_group("cave");
c.bench_function("generate", |b| { group.sample_size(25);
group.bench_function("generate_entrances", |b| {
b.iter(|| { b.iter(|| {
let entrances = black_box(layer::cave::surface_entrances(&land)) let entrances = black_box(layer::cave::surface_entrances(&land))
.step_by(5) .step_by(10)
.map(|e| e / 32); .map(|e| e.wpos_to_cpos());
for entrance in entrances { for entrance in entrances {
_ = black_box(world.generate_chunk( _ = black_box(world.generate_chunk(
index.as_index_ref(), 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); criterion_group!(benches, cave);

View File

@ -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");
}
}

View File

@ -40,7 +40,7 @@ fn to_wpos(cell: Vec2<i32>, level: u32) -> Vec2<i32> {
} }
const AVG_LEVEL_DEPTH: i32 = 120; const AVG_LEVEL_DEPTH: i32 = 120;
const LAYERS: u32 = 4; pub const LAYERS: u32 = 5;
const MIN_RADIUS: f32 = 8.0; const MIN_RADIUS: f32 = 8.0;
const MAX_RADIUS: f32 = 64.0; const MAX_RADIUS: f32 = 64.0;
@ -174,13 +174,14 @@ impl Tunnel {
} }
} }
fn biome_at(&self, wpos: Vec3<i32>, info: &CanvasInfo) -> Biome { pub fn biome_at(&self, wpos: Vec3<i32>, info: &CanvasInfo) -> Biome {
let Some(col) = info.col_or_gen(wpos.xy()) else { let Some(col) = info.col_or_gen(wpos.xy()) else {
return Biome::default(); return Biome::default();
}; };
// Below the ground // 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 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); 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( let temp = Lerp::lerp_unclamped(
col.temp, col.temp * 2.0,
FastNoise2d::new(42) FastNoise2d::new(42)
.get(wpos.xy().map(|e| e as f64 / 1536.0)) .get(wpos.xy().map(|e| e as f64 / 1536.0))
.mul(1.15) .mul(1.15)
@ -229,42 +230,44 @@ impl Tunnel {
sandy, sandy,
] = { ] = {
// Default biome, no other conditions apply // 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 // Mushrooms grow underground and thrive in a humid environment with moderate
// temperatures // temperatures
let mushroom = underground let mushroom = underground
* close(humidity, 1.0, 0.6, 4) * close(humidity, 1.0, 0.7, 4)
* close(temp, 1.5, 0.9, 4) * close(temp, 1.5, 1.2, 4)
* close(depth, 1.0, 0.55, 4); * close(depth, 0.9, 0.65, 4);
// Extremely hot and dry areas deep underground // Extremely hot and dry areas deep underground
let fire = underground let fire = underground
* close(humidity, 0.0, 0.6, 4) * close(humidity, 0.0, 0.6, 4)
* close(temp, 2.0, 1.3, 4) * close(temp, 2.0, 1.4, 4)
* close(depth, 1.0, 0.5, 4); * close(depth, 1.0, 0.45, 4);
// Overgrown with plants that need a moderate climate to survive // Overgrown with plants that need a moderate climate to survive
let leafy = underground let leafy = underground
* close(humidity, 0.9, 0.65, 4) * close(humidity, 0.8, 0.9, 4)
* close(temp, 1.15, 0.85, 4) * close(temp, 0.8, 1.0, 4)
* close(depth, 0.0, 0.65, 4); * close(depth, 0.1, 0.7, 4);
// Cool temperature, dry and devoid of value // 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 // Deep underground and freezing cold
let icy = underground let icy = underground
* close(temp, -1.5, 1.0, 4) * close(temp, -1.5, 2.0, 4)
* close(depth, 1.0, 0.6, 4) * close(depth, 0.9, 0.55, 4)
* close(humidity, 1.0, 0.7, 4); * close(humidity, 1.0, 0.75, 4);
// Rocky cold cave that appear near the surface // 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 // Crystals grow deep underground in areas rich with minerals. They are present
// in areas with colder temperatures and low humidity // in areas with colder temperatures and low humidity
let crystal = underground let crystal = underground
* close(humidity, 0.0, 0.5, 4) * 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(depth, 1.0, 0.55, 4)
* close(mineral, 2.0, 1.25, 4); * close(mineral, 2.0, 1.25, 4);
// Hot, dry and shallow // Hot, dry and shallow
let sandy = 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 = [ let biomes = [
barren, mushroom, fire, leafy, dusty, icy, snowy, crystal, sandy, 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 { for (level, z_range, horizontal, vertical, dist, tunnel) in tunnel_bounds {
write_column( write_column(
canvas, canvas,
@ -470,10 +479,12 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
level, level,
wpos2d, wpos2d,
z_range.clone(), z_range.clone(),
z_ranges,
tunnel, tunnel,
(horizontal, vertical, dist), (horizontal, vertical, dist),
giant_tree_dist, giant_tree_dist,
&mut structure_cache, &mut structure_cache,
&structure_seeds,
rng, rng,
); );
} }
@ -482,19 +493,19 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
} }
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Default)] #[derive(Default, Clone)]
struct Biome { pub struct Biome {
humidity: f32, humidity: f32,
barren: f32, pub barren: f32,
mineral: f32, mineral: f32,
mushroom: f32, pub mushroom: f32,
fire: f32, pub fire: f32,
leafy: f32, pub leafy: f32,
dusty: f32, pub dusty: f32,
icy: f32, pub icy: f32,
snowy: f32, pub snowy: f32,
crystal: f32, pub crystal: f32,
sandy: f32, pub sandy: f32,
depth: f32, depth: f32,
} }
@ -547,10 +558,12 @@ fn write_column<R: Rng>(
level: u32, level: u32,
wpos2d: Vec2<i32>, wpos2d: Vec2<i32>,
z_range: Range<i32>, z_range: Range<i32>,
z_ranges: &[Range<i32>],
tunnel: Tunnel, tunnel: Tunnel,
dimensions: (f32, f32, f32), dimensions: (f32, f32, f32),
giant_tree_dist: f32, giant_tree_dist: f32,
structure_cache: &mut SmallCache<Vec3<i32>, Option<CaveStructure>>, structure_cache: &mut SmallCache<Vec3<i32>, Option<CaveStructure>>,
structure_seeds: &[(Vec2<i32>, u32); 9],
rng: &mut R, rng: &mut R,
) { ) {
let info = canvas.info(); let info = canvas.info();
@ -564,6 +577,19 @@ fn write_column<R: Rng>(
let (cave_width, max_height, dist_cave_center) = dimensions; let (cave_width, max_height, dist_cave_center) = dimensions;
let biome = tunnel.biome_at(wpos2d.with_z(z_range.start), &info); 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 = { let stalactite = {
FastNoise2d::new(35) FastNoise2d::new(35)
.get(wpos2d.map(|e| e as f64 / 8.0)) .get(wpos2d.map(|e| e as f64 / 8.0))
@ -631,7 +657,7 @@ fn write_column<R: Rng>(
.sub(0.2) .sub(0.2)
.min(0.0) .min(0.0)
// .mul((biome.temp as f64 - 1.5).mul(30.0).clamped(0.0, 1.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((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((biome.fire - 0.5).mul(30.0).clamped(0.0, 1.0))
.mul(64.0) .mul(64.0)
@ -743,9 +769,7 @@ fn write_column<R: Rng>(
0 0
}; };
let structures = StructureGen2d::new(34537, 24, 8) let structures = structure_seeds
.get(wpos2d)
.as_slice()
.iter() .iter()
.filter_map(|(wpos2d, seed)| { .filter_map(|(wpos2d, seed)| {
let structure = structure_cache.get(wpos2d.with_z(tunnel.a.depth), |_| { let structure = structure_cache.get(wpos2d.with_z(tunnel.a.depth), |_| {
@ -758,6 +782,7 @@ fn write_column<R: Rng>(
tunnel_bounds_at(pos.xy(), &info, &info.land()) tunnel_bounds_at(pos.xy(), &info, &info.land())
.any(|(_, z_range, _, _, _, _)| z_range.contains(&(z_range.start - 1))) .any(|(_, z_range, _, _, _, _)| z_range.contains(&(z_range.start - 1)))
}; };
if biome.mushroom > 0.7 if biome.mushroom > 0.7
&& vertical > 16.0 && vertical > 16.0
&& rng.gen_bool( && rng.gen_bool(
@ -1164,7 +1189,10 @@ fn write_column<R: Rng>(
&& !void_below && !void_below
{ {
Block::new(BlockKind::Rock, Rgb::new(50, 35, 75)) 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<i16> = Lerp::lerp_unclamped( let stalactite: Rgb<i16> = Lerp::lerp_unclamped(
Lerp::lerp_unclamped( Lerp::lerp_unclamped(
Lerp::lerp_unclamped( Lerp::lerp_unclamped(