Began work on caves2

This commit is contained in:
Joshua Barretto 2022-06-27 22:32:57 +01:00
parent 61573a7800
commit 2cce44fc36
8 changed files with 483 additions and 8 deletions

View File

@ -153,7 +153,7 @@ float lights_at(vec3 wpos, vec3 wnorm, vec3 /*cam_to_frag*/view_dir, vec3 mu, ve
// float strength = attenuation_strength(difference);// pow(attenuation_strength(difference), 0.6);
// NOTE: This normalizes strength to 0.25 at the center of the point source.
float strength = 1.0 / (4 + distance_2);
float strength = 3.0 / (5 + distance_2);
// Multiply the vec3 only once
const float PI = 3.1415926535897932384626433832795;

View File

@ -0,0 +1,85 @@
use rand::thread_rng;
use vek::*;
use veloren_world::{index::Index, site::Settlement, IndexRef};
const W: usize = 640;
const H: usize = 480;
fn main() {
let seed = 1337;
let index = &Index::new(seed);
let mut win =
minifb::Window::new("Cave Viewer", W, H, minifb::WindowOptions::default()).unwrap();
let settlement = Settlement::generate(Vec2::zero(), None, &mut thread_rng());
let mut focus = Vec2::<f32>::zero();
let mut zoom = 1.0;
let mut is_t = false;
let colors = &*index.colors();
let features = &*index.features();
let index = IndexRef {
colors,
features,
index,
};
while win.is_open() {
let mut buf = vec![0; W * H];
let win_to_pos =
|wp: Vec2<usize>| (wp.map(|e| e as f32) - Vec2::new(W as f32, H as f32) * 0.5) * zoom;
for i in 0..W {
for j in 0..H {
use common::terrain::{quadratic_nearest_point, river_spline_coeffs};
let pos = focus + win_to_pos(Vec2::new(i, j)) * zoom;
let a = Vec2::new(1000.0, 0.0);
let b = Vec2::new(1100.0, 30.0);
let d = Vec2::new(0.0, 0.0);
let closest = quadratic_nearest_point(
&river_spline_coeffs(a, d, b),
pos.map(|e| e as f64),
Vec2::new(a, b),
)
.unwrap();
let color = Lerp::lerp(
Rgb::new(1.0, 0.0, 0.0),
Rgb::new(0.0, 1.0, 0.0),
1.0 / (1.0 + if is_t { closest.0 } else { closest.2 }),
);
let color = Rgba::new(color.r, color.g, color.b, 1.0);
buf[j * W + i] = u32::from_le_bytes(color.map(|e| (e * 255.0) as u8).into_array());
}
}
let spd = 4.0;
if win.is_key_down(minifb::Key::W) {
focus.y -= spd * zoom;
}
if win.is_key_down(minifb::Key::A) {
focus.x -= spd * zoom;
}
if win.is_key_down(minifb::Key::S) {
focus.y += spd * zoom;
}
if win.is_key_down(minifb::Key::D) {
focus.x += spd * zoom;
}
if win.is_key_down(minifb::Key::Q) {
zoom *= 1.015;
}
if win.is_key_down(minifb::Key::E) {
zoom /= 1.015;
}
if win.is_key_down(minifb::Key::Tab) {
is_t ^= true;
}
win.update_with_buffer(&buf, W, H).unwrap();
}
}

View File

@ -94,8 +94,8 @@ impl Civs {
let initial_civ_count = initial_civ_count(sim.map_size_lg());
let mut ctx = GenCtx { sim, rng };
info!("starting cave generation");
this.generate_caves(&mut ctx);
// info!("starting cave generation");
// this.generate_caves(&mut ctx);
info!("starting civilisation creation");
let mut start_locations: Vec<Vec2<i32>> = Vec::new();
@ -438,7 +438,7 @@ impl Civs {
&& chunk.alt < cave_max_alt
&& cave_min_alt < chunk.water_alt
&& chunk.river.near_water()
// Only do this for caves at the sea level for now.
// Only do this for caves at the sea level for now.
// The reason being that floodfilling from a water alt to an alt lower than the water alt causes problems.
&& chunk.water_alt <= CONFIG.sea_level;
if submerged {

View File

@ -11,6 +11,8 @@ pub struct Land<'a> {
impl<'a> Land<'a> {
pub fn empty() -> Self { Self { sim: None } }
pub fn size(&self) -> Vec2<u32> { self.sim.map_or(Vec2::one(), |s| s.get_size()) }
pub fn from_sim(sim: &'a sim::WorldSim) -> Self { Self { sim: Some(sim) } }
pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> f32 {

377
world/src/layer/cave.rs Normal file
View File

@ -0,0 +1,377 @@
use super::scatter::close;
use crate::{
util::{sampler::Sampler, RandomField, LOCALITY},
Canvas, ColumnSample, Land,
};
use common::{
terrain::{
quadratic_nearest_point, river_spline_coeffs, Block, BlockKind, SpriteKind,
TerrainChunkSize,
},
vol::RectVolSize,
};
use noise::{Fbm, NoiseFn};
use rand::prelude::*;
use std::{
f64::consts::PI,
ops::{Add, Mul, Range, Sub},
};
use vek::*;
const CELL_SIZE: i32 = 1024;
#[derive(Copy, Clone)]
pub struct Node {
pub wpos: Vec3<i32>,
}
fn to_cell(wpos: Vec2<i32>, level: u32) -> Vec2<i32> {
(wpos + (level & 1) as i32 * CELL_SIZE / 2).map(|e| e.div_euclid(CELL_SIZE))
}
fn to_wpos(cell: Vec2<i32>, level: u32) -> Vec2<i32> {
(cell * CELL_SIZE) - (level & 1) as i32 * CELL_SIZE / 2
}
const AVG_LEVEL_DEPTH: i32 = 120;
fn node_at(cell: Vec2<i32>, level: u32, land: &Land) -> Option<Node> {
let rand = RandomField::new(37 + level);
if rand.chance(cell.with_z(0), 0.5) || level == 0 {
let dx = RandomField::new(38 + level);
let dy = RandomField::new(39 + level);
let wpos = to_wpos(cell, level)
+ CELL_SIZE as i32 / 4
+ (Vec2::new(dx.get(cell.with_z(0)), dy.get(cell.with_z(0))) % CELL_SIZE as u32 / 2)
.map(|e| e as i32);
land.get_chunk_wpos(wpos).and_then(|chunk| {
let alt = chunk.alt as i32 + 8 - AVG_LEVEL_DEPTH * level as i32;
if level > 0
|| (!chunk.near_cliffs()
&& !chunk.river.near_water()
&& chunk.sites.is_empty()
&& land.get_gradient_approx(wpos) < 0.75)
{
Some(Node {
wpos: wpos.with_z(alt),
})
} else {
None
}
})
} else {
None
}
}
pub fn surface_entrances<'a>(land: &'a Land) -> impl Iterator<Item = Vec2<i32>> + 'a {
let sz_cells = to_cell(
land.size()
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| (e * sz) as i32),
0,
);
(0..sz_cells.x + 1)
.flat_map(move |x| (0..sz_cells.y + 1).map(move |y| Vec2::new(x, y)))
.filter_map(|cell| {
let tunnel = tunnels_below_from_cell(cell, 0, land)?;
// Hacky, moves the entrance position closer to the actual entrance
Some(Lerp::lerp(tunnel.a.wpos.xy(), tunnel.b.wpos.xy(), 0.25))
})
}
struct Tunnel {
a: Node,
b: Node,
}
fn tunnels_at<'a>(
wpos: Vec2<i32>,
level: u32,
land: &'a Land,
) -> impl Iterator<Item = Tunnel> + 'a {
let rand = RandomField::new(37 + level);
let col_cell = to_cell(wpos, level);
LOCALITY
.into_iter()
.filter_map(move |rpos| {
let current_cell_pos = col_cell + rpos;
Some(current_cell_pos).zip(node_at(current_cell_pos, level, land))
})
.flat_map(move |(current_cell_pos, current_cell)| {
[Vec2::new(1, 1), Vec2::new(1, -1)]
.into_iter()
.filter(move |rpos| {
let mid = (current_cell_pos * 2 + rpos) / 2;
rand.chance(mid.with_z(0), 0.5) ^ (rpos.y == -1)
})
.chain([Vec2::new(1, 0), Vec2::new(0, 1)])
.filter_map(move |rpos| {
let other_cell_pos = current_cell_pos + rpos;
Some(other_cell_pos).zip(node_at(other_cell_pos, level, land))
})
.map(move |(other_cell_pos, other_cell)| Tunnel {
a: current_cell,
b: other_cell,
})
})
}
fn tunnels_below_from_cell(cell: Vec2<i32>, level: u32, land: &Land) -> Option<Tunnel> {
let wpos = to_wpos(cell, level);
Some(Tunnel {
a: node_at(to_cell(wpos, level), level, land)?,
b: node_at(
to_cell(wpos + CELL_SIZE as i32 / 2, level + 1),
level + 1,
land,
)?,
})
}
fn tunnels_down_from<'a>(
wpos: Vec2<i32>,
level: u32,
land: &'a Land,
) -> impl Iterator<Item = Tunnel> + 'a {
let col_cell = to_cell(wpos, level);
LOCALITY
.into_iter()
.filter_map(move |rpos| tunnels_below_from_cell(col_cell + rpos, level, land))
}
pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
let nz = Fbm::new();
let info = canvas.info();
canvas.foreach_col(|canvas, wpos2d, col| {
let wposf = wpos2d.map(|e| e as f64 + 0.5);
let land = info.land();
for level in 1..4 {
let rand = RandomField::new(37 + level);
let tunnel_bounds = tunnels_at(wpos2d, level, &land)
.chain(tunnels_down_from(wpos2d, level, &land))
.filter_map(|tunnel| {
let start = tunnel.a.wpos.xy().map(|e| e as f64 + 0.5);
let end = tunnel.b.wpos.xy().map(|e| e as f64 + 0.5);
let dist = LineSegment2 { start, end }
.distance_to_point(wpos2d.map(|e| e as f64 + 0.5));
let curve = (
RandomField::new(13)
.get_f32(tunnel.a.wpos.xy().with_z(0))
.powf(0.25) as f64,
(RandomField::new(14).get_f32(tunnel.a.wpos.xy().with_z(0)) as f64 - 0.5)
.signum(),
);
if let Some((t, closest, _)) = quadratic_nearest_point(
&river_spline_coeffs(
start,
((end - start) * 0.5
+ ((end - start) * 0.5).rotated_z(PI / 2.0)
* 4.0
* curve.0
* curve.1)
.map(|e| e as f32),
end,
),
wposf,
Vec2::new(start, end),
) {
let dist = closest.distance(wposf);
if dist < 64.0 {
let tunnel_len = tunnel
.a
.wpos
.map(|e| e as f64)
.distance(tunnel.b.wpos.map(|e| e as f64));
let radius = Lerp::lerp(
6.0,
32.0,
(nz.get((wposf / 200.0).into_array()) * 2.0 * 0.5 + 0.5)
.clamped(0.0, 1.0),
); // Lerp::lerp(8.0, 24.0, (t * 0.075 * tunnel_len).sin() * 0.5 + 0.5);
let height_here = (1.0 - dist / radius).max(0.0).powf(0.3) * radius;
if height_here > 0.0 {
let z_offs = nz.get((wposf / 512.0).into_array())
* 48.0
* ((1.0 - (t - 0.5).abs() * 2.0) * 8.0).min(1.0);
let depth =
Lerp::lerp(tunnel.a.wpos.z as f64, tunnel.b.wpos.z as f64, t)
+ z_offs;
Some((
(depth - height_here * 0.3) as i32,
(depth + height_here * 1.35) as i32,
z_offs,
))
} else {
None
}
} else {
None
}
} else {
None
}
});
for (min, max, z_offs) in tunnel_bounds {
// Avoid cave entrances intersecting water
let z_range = Lerp::lerp(
max,
min,
1.0 - (1.0 - ((col.alt - col.water_level) / 4.0).clamped(0.0, 1.0))
* (1.0 - ((col.alt - max as f32) / 8.0).clamped(0.0, 1.0)),
)..max;
write_column(canvas, col, level, wpos2d, z_range, z_offs, rng);
}
}
});
}
struct Biome {
humidity: f32,
temp: f32,
mineral: f32,
fungal: f32,
}
fn write_column(
canvas: &mut Canvas,
col: &ColumnSample,
level: u32,
wpos2d: Vec2<i32>,
z_range: Range<i32>,
z_offs: f64,
rng: &mut impl Rng,
) {
let info = canvas.info();
let below = ((col.alt - z_range.start as f32) / 50.0).clamped(0.0, 1.0);
let biome = Biome {
humidity: Lerp::lerp(
col.humidity,
info.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 / 1024.0).into_array())
.mul(0.5)
.add(0.5) as f32,
below,
),
temp: Lerp::lerp(
col.temp,
info.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 / 2048.0).into_array())
.add(
((col.alt as f64 - z_range.start as f64) / (AVG_LEVEL_DEPTH as f64 * 2.0))
.clamped(0.0, 2.0),
) as f32,
below,
),
mineral: info
.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 / 256.0).into_array())
.mul(0.5)
.add(0.5) as f32,
fungal: info
.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 / 512.0).into_array())
.mul(0.5)
.add(0.5) as f32,
};
// Exposed to the sky
let exposed = z_range.end as f32 > col.alt;
let rand = RandomField::new(37 + level);
let stalactite = {
let cavern_height = (z_range.end - z_range.start) as f64;
info
.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 / 16.0).into_array())
.sub(0.5)
.max(0.0)
.mul(2.0)
// No stalactites near entrances
.mul(((col.alt as f64 - z_range.end as f64) / 8.0).clamped(0.0, 1.0))
.mul(8.0 + cavern_height * 0.4)
};
let lava = {
info.index()
.noise
.cave_nz
.get(wpos2d.map(|e| e as f64 / 64.0).into_array())
.sub(0.5)
.abs()
.sub(0.2)
.min(0.0)
.mul((biome.temp as f64 - 1.5).mul(30.0).clamped(0.0, 1.0))
.mul(64.0)
.max(-32.0)
};
let dirt = if exposed { 0 } else { 1 };
let bedrock = z_range.start + lava as i32;
let base = bedrock + (stalactite * 0.4) as i32;
let floor = base + dirt;
let ceiling = z_range.end - stalactite as i32;
for z in bedrock..z_range.end {
canvas.map(wpos2d.with_z(z), |block| {
if !block.is_filled() {
block.into_vacant()
} else if z < z_range.start - 4 {
Block::new(BlockKind::Lava, Rgb::new(255, 100, 0))
} else if z < base || z >= ceiling {
Block::new(BlockKind::WeakRock, Rgb::new(80, 100, 150))
} else if z >= base && z < floor {
let surf_color: Rgb<i16> = Lerp::lerp(
Lerp::lerp(Rgb::new(40, 20, 0), Rgb::new(80, 80, 30), col.marble_small),
Lerp::lerp(Rgb::new(100, 20, 50), Rgb::new(80, 80, 100), col.marble_mid),
((col.alt - z as f32) / 300.0).clamped(0.0, 1.0),
);
Block::new(BlockKind::Sand, surf_color.map(|e| e as u8))
} else if let Some(sprite) = (z == floor && !exposed)
.then(|| {
if rand.chance(
wpos2d.with_z(1),
close(biome.humidity, 1.0, 0.5) * close(biome.temp, 0.0, 0.75) * 0.02,
) {
Some(SpriteKind::CaveMushroom)
} else if rand.chance(
wpos2d.with_z(1),
close(biome.humidity, 0.0, 0.5) * biome.mineral * 0.02,
) {
Some(SpriteKind::CrystalLow)
} else {
None
}
})
.flatten()
{
Block::air(sprite)
} else if z == ceiling - 1 && rand.chance(wpos2d.with_z(0), 0.0075) {
use SpriteKind::*;
Block::air(
*[Orb, CavernMycelBlue, CrystalHigh, CeilingMushroom]
.choose(rng)
.unwrap(),
)
} else {
Block::air(SpriteKind::Empty)
}
});
}
}

View File

@ -1,3 +1,4 @@
pub mod cave;
pub mod rock;
pub mod scatter;
pub mod shrub;
@ -6,8 +7,8 @@ pub mod tree;
pub mod wildlife;
pub use self::{
rock::apply_rocks_to, scatter::apply_scatter_to, shrub::apply_shrubs_to, spot::apply_spots_to,
tree::apply_trees_to,
cave::apply_caves_to as apply_caves2_to, rock::apply_rocks_to, scatter::apply_scatter_to,
shrub::apply_shrubs_to, spot::apply_spots_to, tree::apply_trees_to,
};
use crate::{

View File

@ -5,7 +5,7 @@ use rand::prelude::*;
use std::f32;
use vek::*;
fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
pub fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
(1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.125)
}

View File

@ -11,7 +11,8 @@
bool_to_option,
label_break_value,
option_zip,
arbitrary_enum_discriminant
arbitrary_enum_discriminant,
let_else
)]
mod all;
@ -180,6 +181,14 @@ impl World {
wpos: pos,
}),
)
.chain(layer::cave::surface_entrances(&Land::from_sim(self.sim()))
.enumerate()
.map(|(i, wpos)| world_msg::SiteInfo {
id: 65536 + i as u64, // Generate a fake ID, TODO: don't do this
name: None,
kind: world_msg::SiteKind::Cave,
wpos,
}))
.collect(),
..self.sim.get_map(index, self.sim().calendar.as_ref())
}
@ -387,6 +396,7 @@ impl World {
if index.features.spots {
layer::apply_spots_to(&mut canvas, &mut dynamic_rng);
}
layer::apply_caves2_to(&mut canvas, &mut dynamic_rng);
// layer::apply_coral_to(&mut canvas);
// Apply site generation