veloren/world/src/column/mod.rs
Joshua Yanovski c02f2a7f9e Fixes to worldgen and adding a debug command.
Humidity and temperature are now indexed to uniform altitude *over land
chunks* (and water chunks adjacent to land) rather than over the whole
range of altitude.  This is necessary in order to satisfy the uniformity
conditions of the formula for weighted sum CDF.

Additionally, fixes the computation of whether a tree should be
generated or not.  Previously, it was using a source of randomness
scaled to use much less than the full 0-1 range; this has been resolved.
This makes for much nicer and more gradual transitions between densities
and reduces the amount of completely barren landscapes, while also
making forests larger.

Finally, this commit adds a server command, debug_column, which returns
some useful debug information about a column given an x and y
coordinate.  This is useful for debugging worldgen.
2019-08-26 11:52:25 +02:00

545 lines
18 KiB
Rust

use crate::{
all::ForestKind,
block::StructureMeta,
sim::{LocationInfo, SimChunk},
util::{RandomPerm, Sampler, UnitChooser},
World, CONFIG,
};
use common::{
assets,
terrain::{BlockKind, Structure, TerrainChunkSize},
vol::VolSize,
};
use lazy_static::lazy_static;
use noise::NoiseFn;
use std::{
f32,
ops::{Add, Div, Mul, Neg, Sub},
sync::Arc,
};
use vek::*;
pub struct ColumnGen<'a> {
world: &'a World,
}
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
static DUNGEON_RAND: RandomPerm = RandomPerm::new(0x42782335);
lazy_static! {
pub static ref DUNGEONS: Vec<Arc<Structure>> = vec![
assets::load_map("world.structure.dungeon.ruins", |s: Structure| s
.with_center(Vec3::new(57, 58, 61))
.with_default_kind(BlockKind::Dense))
.unwrap(),
assets::load_map("world.structure.dungeon.ruins_2", |s: Structure| s
.with_center(Vec3::new(53, 57, 60))
.with_default_kind(BlockKind::Dense))
.unwrap(),
assets::load_map("world.structure.dungeon.ruins_3", |s: Structure| s
.with_center(Vec3::new(58, 45, 72))
.with_default_kind(BlockKind::Dense))
.unwrap(),
assets::load_map(
"world.structure.dungeon.meso_sewer_temple",
|s: Structure| s
.with_center(Vec3::new(63, 62, 60))
.with_default_kind(BlockKind::Dense)
)
.unwrap(),
assets::load_map("world.structure.dungeon.ruins_maze", |s: Structure| s
.with_center(Vec3::new(60, 60, 116))
.with_default_kind(BlockKind::Dense))
.unwrap(),
];
}
impl<'a> ColumnGen<'a> {
pub fn new(world: &'a World) -> Self {
Self { world }
}
fn get_local_structure(&self, wpos: Vec2<i32>) -> Option<StructureData> {
let (pos, seed) = self
.world
.sim()
.gen_ctx
.region_gen
.get(wpos)
.iter()
.copied()
.min_by_key(|(pos, _)| pos.distance_squared(wpos))
.unwrap();
let chunk_pos = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
e / sz as i32
});
let chunk = self.world.sim().get(chunk_pos)?;
if seed % 5 == 2
&& chunk.temp > CONFIG.desert_temp
&& chunk.alt > CONFIG.sea_level + 5.0
&& chunk.chaos <= 0.35
{
Some(StructureData {
pos,
seed,
meta: Some(StructureMeta::Pyramid { height: 140 }),
})
} else if seed % 17 == 2 && chunk.chaos < 0.2 {
Some(StructureData {
pos,
seed,
meta: Some(StructureMeta::Volume {
units: UNIT_CHOOSER.get(seed),
volume: &DUNGEONS[DUNGEON_RAND.get(seed) as usize % DUNGEONS.len()],
}),
})
} else {
None
}
}
fn gen_close_structures(&self, wpos: Vec2<i32>) -> [Option<StructureData>; 9] {
let mut metas = [None; 9];
self.world
.sim()
.gen_ctx
.structure_gen
.get(wpos)
.into_iter()
.copied()
.enumerate()
.for_each(|(i, (pos, seed))| {
metas[i] = self.get_local_structure(pos).or(Some(StructureData {
pos,
seed,
meta: None,
}));
});
metas
}
}
impl<'a> Sampler for ColumnGen<'a> {
type Index = Vec2<i32>;
type Sample = Option<ColumnSample<'a>>;
fn get(&self, wpos: Vec2<i32>) -> Option<ColumnSample<'a>> {
let wposf = wpos.map(|e| e as f64);
let chunk_pos = wpos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
e / sz as i32
});
let sim = self.world.sim();
let turb = Vec2::new(
sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32,
sim.gen_ctx.turb_y_nz.get((wposf.div(48.0)).into_array()) as f32,
) * 12.0;
let wposf_turb = wposf + turb.map(|e| e as f64);
let alt_base = sim.get_interpolated(wpos, |chunk| chunk.alt_base)?;
let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
let dryness = sim.get_interpolated(wpos, |chunk| chunk.dryness)?;
let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?;
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let sim_chunk = sim.get(chunk_pos)?;
const RIVER_PROPORTION: f32 = 0.025;
/*
let river = dryness
.abs()
.neg()
.add(RIVER_PROPORTION)
.div(RIVER_PROPORTION)
.max(0.0)
.mul((1.0 - (chaos - 0.15) * 20.0).max(0.0).min(1.0));
*/
let river = 0.0;
let cliff_hill = (sim
.gen_ctx
.small_nz
.get((wposf_turb.div(128.0)).into_array()) as f32)
.mul(24.0);
let riverless_alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?
+ (sim
.gen_ctx
.small_nz
.get((wposf_turb.div(150.0)).into_array()) as f32)
.abs()
.mul(chaos.max(0.15))
.mul(64.0);
let is_cliffs = sim_chunk.is_cliffs;
let near_cliffs = sim_chunk.near_cliffs;
let alt = riverless_alt
- (1.0 - river)
.mul(f32::consts::PI)
.cos()
.add(1.0)
.mul(0.5)
.mul(24.0);
let water_level = riverless_alt - 4.0 - 5.0 * chaos;
let rock = (sim.gen_ctx.small_nz.get(
Vec3::new(wposf.x, wposf.y, alt as f64)
.div(100.0)
.into_array(),
) as f32)
.mul(rockiness)
.sub(0.4)
.max(0.0)
.mul(8.0);
let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64);
let marble_small = (sim.gen_ctx.hill_nz.get((wposf3d.div(3.0)).into_array()) as f32)
.add(1.0)
.mul(0.5);
let marble = (sim.gen_ctx.hill_nz.get((wposf3d.div(48.0)).into_array()) as f32)
.mul(0.75)
.add(1.0)
.mul(0.5)
.add(marble_small.sub(0.5).mul(0.25));
let temp = temp.add((marble - 0.5) * 0.25);
let humidity = humidity.add((marble - 0.5) * 0.25);
// Colours
let cold_grass = Rgb::new(0.0, 0.5, 0.25);
let warm_grass = Rgb::new(0.03, 0.8, 0.0);
let dark_grass = Rgb::new(0.01, 0.3, 0.0);
let wet_grass = Rgb::new(0.1, 0.8, 0.2);
let cold_stone = Rgb::new(0.57, 0.67, 0.8);
let warm_stone = Rgb::new(0.77, 0.77, 0.64);
let beach_sand = Rgb::new(0.89, 0.87, 0.64);
let desert_sand = Rgb::new(0.93, 0.80, 0.54);
let snow = Rgb::new(0.8, 0.85, 1.0);
let dirt = Lerp::lerp(
Rgb::new(0.078, 0.078, 0.20),
Rgb::new(0.61, 0.49, 0.0),
marble,
);
let tundra = Lerp::lerp(snow, Rgb::new(0.01, 0.3, 0.0), 0.4 + marble * 0.6);
let dead_tundra = Lerp::lerp(warm_stone, Rgb::new(0.3, 0.12, 0.2), marble);
let cliff = Rgb::lerp(cold_stone, warm_stone, marble);
let grass = Rgb::lerp(
cold_grass,
warm_grass,
marble.sub(0.5).add(1.0.sub(humidity).mul(0.5)).powf(1.5),
);
let snow_moss = Rgb::lerp(snow, cold_grass, 0.4 + marble.powf(1.5) * 0.6);
let moss = Rgb::lerp(dark_grass, cold_grass, marble.powf(1.5));
let rainforest = Rgb::lerp(wet_grass, warm_grass, marble.powf(1.5));
let sand = Rgb::lerp(beach_sand, desert_sand, marble);
let tropical = Rgb::lerp(
Rgb::lerp(
grass,
Rgb::new(0.15, 0.2, 0.15),
marble_small
.sub(0.5)
.mul(0.2)
.add(0.75.mul(1.0.sub(humidity)))
.powf(0.667),
),
Rgb::new(0.87, 0.62, 0.56),
marble.powf(1.5).sub(0.5).mul(4.0),
);
// For below desert humidity, we are always sand or rock, depending on altitude and
// temperature.
let ground = Rgb::lerp(
Rgb::lerp(
dead_tundra,
sand,
temp.sub(CONFIG.snow_temp)
.div(CONFIG.desert_temp.sub(CONFIG.snow_temp))
.mul(0.5),
),
cliff,
alt.sub(CONFIG.mountain_scale * 0.25)
.div(CONFIG.mountain_scale * 0.125),
);
// From desert to forest humidity, we go from tundra to dirt to grass to moss to sand,
// depending on temperature.
let ground = Rgb::lerp(
ground,
Rgb::lerp(
Rgb::lerp(
Rgb::lerp(
Rgb::lerp(
tundra,
// snow_temp to 0
dirt,
temp.sub(CONFIG.snow_temp)
.div(CONFIG.snow_temp.neg())
/*.sub((marble - 0.5) * 0.05)
.mul(256.0)*/
.mul(1.0),
),
// 0 to tropical_temp
grass,
temp.div(CONFIG.tropical_temp).mul(4.0),
),
// tropical_temp to desert_temp
moss,
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(1.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(4.0),
),
humidity
.sub(CONFIG.desert_hum)
.div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
.mul(1.0),
);
// From forest to jungle humidity, we go from snow to dark grass to grass to tropics to sand
// depending on temperature.
let ground = Rgb::lerp(
ground,
Rgb::lerp(
Rgb::lerp(
Rgb::lerp(
snow_moss,
// 0 to tropical_temp
grass,
temp.div(CONFIG.tropical_temp).mul(4.0),
),
// tropical_temp to desert_temp
tropical,
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(1.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(4.0),
),
humidity
.sub(CONFIG.forest_hum)
.div(CONFIG.jungle_hum.sub(CONFIG.forest_hum))
.mul(1.0),
);
// From jungle humidity upwards, we go from snow to grass to rainforest to tropics to sand.
let ground = Rgb::lerp(
ground,
Rgb::lerp(
Rgb::lerp(
Rgb::lerp(
snow_moss,
// 0 to tropical_temp
rainforest,
temp.div(CONFIG.tropical_temp).mul(4.0),
),
// tropical_temp to desert_temp
tropical,
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(4.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(4.0),
),
humidity.sub(CONFIG.jungle_hum).mul(1.0),
);
// Snow covering
let ground = Rgb::lerp(
snow,
ground,
temp.sub(CONFIG.snow_temp)
.max(-humidity.sub(CONFIG.desert_hum))
.mul(16.0)
.add((marble_small - 0.5) * 0.5),
);
// Work out if we're on a path or near a town
let dist_to_path = match &sim_chunk.location {
Some(loc) => {
let this_loc = &sim.locations[loc.loc_idx];
this_loc
.neighbours
.iter()
.map(|j| {
let other_loc = &sim.locations[*j];
// Find the two location centers
let near_0 = this_loc.center.map(|e| e as f32);
let near_1 = other_loc.center.map(|e| e as f32);
// Calculate distance to path between them
(0.0 + (near_1.y - near_0.y) * wposf_turb.x as f32
- (near_1.x - near_0.x) * wposf_turb.y as f32
+ near_1.x * near_0.y
- near_0.x * near_1.y)
.abs()
.div(near_0.distance(near_1))
})
.filter(|x| x.is_finite())
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(f32::INFINITY)
}
None => f32::INFINITY,
};
let on_path = dist_to_path < 5.0 && !sim_chunk.near_cliffs; // || near_0.distance(wposf_turb.map(|e| e as f32)) < 150.0;
let (alt, ground) = if on_path {
(alt - 1.0, dirt)
} else {
(alt, ground)
};
// Cities
// TODO: In a later MR
let building = match &sim_chunk.location {
Some(loc) => {
let loc = &sim.locations[loc.loc_idx];
let rpos = wposf.map2(loc.center, |a, b| a as f32 - b as f32) / 256.0 + 0.5;
if rpos.map(|e| e >= 0.0 && e < 1.0).reduce_and() {
(loc.settlement
.get_at(rpos)
.map(|b| b.seed % 20 + 10)
.unwrap_or(0)) as f32
} else {
0.0
}
}
None => 0.0,
};
let alt = alt + building;
// Caves
let cave_at = |wposf: Vec2<f64>| {
(sim.gen_ctx.cave_0_nz.get(
Vec3::new(wposf.x, wposf.y, alt as f64 * 8.0)
.div(800.0)
.into_array(),
) as f32)
.powf(2.0)
.neg()
.add(1.0)
.mul((1.15 - chaos).min(1.0))
};
let cave_xy = cave_at(wposf);
let cave_alt = alt - 24.0
+ (sim
.gen_ctx
.cave_1_nz
.get(Vec2::new(wposf.x, wposf.y).div(48.0).into_array()) as f32)
* 8.0
+ (sim
.gen_ctx
.cave_1_nz
.get(Vec2::new(wposf.x, wposf.y).div(500.0).into_array()) as f32)
.add(1.0)
.mul(0.5)
.powf(15.0)
.mul(150.0);
Some(ColumnSample {
alt,
chaos,
water_level,
river,
surface_color: Rgb::lerp(
sand,
// Land
Rgb::lerp(
ground,
// Mountain
Rgb::lerp(
cliff,
snow,
(alt - CONFIG.sea_level
- 0.4 * CONFIG.mountain_scale
- alt_base
- temp * 96.0
- marble * 24.0)
/ 12.0,
),
(alt - CONFIG.sea_level - 0.25 * CONFIG.mountain_scale + marble * 128.0)
/ (0.25 * CONFIG.mountain_scale),
),
// Beach
((alt - CONFIG.sea_level - 1.0) / 2.0)
.min(1.0 - river * 2.0)
.max(0.0),
),
sub_surface_color: dirt,
tree_density,
forest_kind: sim_chunk.forest_kind,
close_structures: self.gen_close_structures(wpos),
cave_xy,
cave_alt,
marble,
marble_small,
rock,
is_cliffs,
near_cliffs,
cliff_hill,
close_cliffs: sim.gen_ctx.cliff_gen.get(wpos),
temp,
spawn_rate,
location: sim_chunk.location.as_ref(),
})
}
}
#[derive(Clone)]
pub struct ColumnSample<'a> {
pub alt: f32,
pub chaos: f32,
pub water_level: f32,
pub river: f32,
pub surface_color: Rgb<f32>,
pub sub_surface_color: Rgb<f32>,
pub tree_density: f32,
pub forest_kind: ForestKind,
pub close_structures: [Option<StructureData>; 9],
pub cave_xy: f32,
pub cave_alt: f32,
pub marble: f32,
pub marble_small: f32,
pub rock: f32,
pub is_cliffs: bool,
pub near_cliffs: bool,
pub cliff_hill: f32,
pub close_cliffs: [(Vec2<i32>, u32); 9],
pub temp: f32,
pub spawn_rate: f32,
pub location: Option<&'a LocationInfo>,
}
#[derive(Copy, Clone)]
pub struct StructureData {
pub pos: Vec2<i32>,
pub seed: u32,
pub meta: Option<StructureMeta>,
}