Added settlement generation

This commit is contained in:
Joshua Barretto 2019-06-25 16:59:09 +01:00
parent f781aca8e9
commit b40d19ad0e
9 changed files with 292 additions and 78 deletions

30
world/examples/city.rs Normal file
View File

@ -0,0 +1,30 @@
use std::ops::{Add, Mul, Sub};
use vek::*;
use veloren_world::sim::Settlement;
use rand::thread_rng;
const W: usize = 640;
const H: usize = 480;
fn main() {
let mut win =
minifb::Window::new("City Viewer", W, H, minifb::WindowOptions::default()).unwrap();
let settlement = Settlement::generate(&mut thread_rng());
while win.is_open() {
let mut buf = vec![0; W * H];
for i in 0..W {
for j in 0..H {
let pos = Vec2::new(i as f32, j as f32) * 0.002;
let seed = settlement.get_at(pos).map(|b| b.seed).unwrap_or(0);
buf[j * W + i] = u32::from_le_bytes([(seed >> 0) as u8, (seed >> 8) as u8, (seed >> 16) as u8, (seed >> 24) as u8]);
}
}
win.update_with_buffer(&buf).unwrap();
}
}

47
world/examples/turb.rs Normal file
View File

@ -0,0 +1,47 @@
use std::ops::{Add, Mul, Sub};
use vek::*;
use veloren_world::sim::Settlement;
use rand::thread_rng;
use noise::{Seedable, NoiseFn, SuperSimplex};
const W: usize = 640;
const H: usize = 640;
fn main() {
let mut win =
minifb::Window::new("Turb", W, H, minifb::WindowOptions::default()).unwrap();
let nz_x = SuperSimplex::new().set_seed(0);
let nz_y = SuperSimplex::new().set_seed(1);
let mut time = 0.0f64;
while win.is_open() {
let mut buf = vec![0; W * H];
for i in 0..W {
for j in 0..H {
let pos = Vec2::new(i as f64 / W as f64, j as f64 / H as f64) * 0.5 - 0.25;
let pos = pos * 10.0;
let pos = (0..10)
.fold(pos, |pos, _| pos.map(|e| e.powf(3.0) - 1.0));
let val = if pos
.map(|e| e.abs() < 0.5)
.reduce_and()
{
1.0f32
} else {
0.0
};
buf[j * W + i] = u32::from_le_bytes([(val.max(0.0).min(1.0) * 255.0) as u8; 4]);
}
}
win.update_with_buffer(&buf).unwrap();
time += 1.0 / 60.0;
}
}

View File

@ -35,12 +35,7 @@ fn main() {
.unwrap_or((0, None));
let loc_color = location
.map(|l| {
(
l.loc.name().bytes().nth(0).unwrap() * 17,
l.loc.name().bytes().nth(1).unwrap() * 17,
)
})
.map(|l| (l.loc_idx as u8 * 17, l.loc_idx as u8 * 13))
.unwrap_or((0, 0));
buf[j * W + i] = u32::from_le_bytes([loc_color.0, loc_color.1, alt, alt]);

View File

@ -52,7 +52,7 @@ impl<'a> BlockGen<'a> {
cache,
Vec2::from(*cliff_pos),
) {
Some(cliff_sample) if cliff_sample.cliffs => {
Some(cliff_sample) if cliff_sample.cliffs && cliff_sample.spawn_rate > 0.5 => {
let cliff_pos3d = Vec3::from(*cliff_pos);
let height = RandomField::new(seed + 1).get(cliff_pos3d) % 48;
@ -299,7 +299,8 @@ impl<'a> SamplerMut for BlockGen<'a> {
Some(tree_sample)
if tree_sample.tree_density
> 0.5 + (*tree_seed as f32 / 1000.0).fract() * 0.2
&& tree_sample.alt > tree_sample.water_level =>
&& tree_sample.alt > tree_sample.water_level
&& tree_sample.spawn_rate > 0.5 =>
{
let cliff_height = Self::get_cliff_height(
column_gen,

View File

@ -50,6 +50,7 @@ impl<'a> Sampler for ColumnGen<'a> {
let dryness = sim.get_interpolated(wpos, |chunk| chunk.dryness)?;
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)?;
@ -135,25 +136,32 @@ impl<'a> Sampler for ColumnGen<'a> {
);
// Work out if we're on a path or near a town
let near_0 = sim_chunk
.location
.as_ref()
.map(|l| l.near[0].block_pos)
.unwrap_or(Vec2::zero())
.map(|e| e as f32);
let near_1 = sim_chunk
.location
.as_ref()
.map(|l| l.near[1].block_pos)
.unwrap_or(Vec2::zero())
.map(|e| e as f32);
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];
let dist_to_path = (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));
// 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; // || near_0.distance(wposf_turb.map(|e| e as f32)) < 150.0;
@ -163,6 +171,23 @@ impl<'a> Sampler for ColumnGen<'a> {
(alt, ground)
};
// Cities
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(
@ -228,6 +253,7 @@ impl<'a> Sampler for ColumnGen<'a> {
cliff_hill,
close_cliffs: sim.gen_ctx.cliff_gen.get(wpos),
temp,
spawn_rate,
location: sim_chunk.location.as_ref(),
})
}
@ -251,5 +277,6 @@ pub struct ColumnSample<'a> {
pub cliff_hill: f32,
pub close_cliffs: [(Vec2<i32>, u32); 9],
pub temp: f32,
pub spawn_rate: f32,
pub location: Option<&'a LocationInfo>,
}

View File

@ -74,7 +74,7 @@ impl World {
None => return TerrainChunk::new(0, water, air, TerrainChunkMeta::void()),
};
let meta = TerrainChunkMeta::new(sim_chunk.get_name(), sim_chunk.get_biome());
let meta = TerrainChunkMeta::new(sim_chunk.get_name(&self.sim), sim_chunk.get_biome());
let mut chunk = TerrainChunk::new(base_z - 8, stone, air, meta);

View File

@ -1,27 +1,25 @@
use rand::Rng;
use vek::*;
#[derive(Copy, Clone, Debug)]
pub enum LocationKind {
Settlement,
Wildnerness,
}
use fxhash::FxHashSet;
use super::Settlement;
#[derive(Clone, Debug)]
pub struct Location {
name: String,
center: Vec2<i32>,
kind: LocationKind,
kingdom: Option<Kingdom>,
pub(crate) name: String,
pub(crate) center: Vec2<i32>,
pub(crate) kingdom: Option<Kingdom>,
pub(crate) neighbours: FxHashSet<usize>,
pub(crate) settlement: Settlement,
}
impl Location {
pub fn generate<R: Rng>(center: Vec2<i32>, rng: &mut R) -> Self {
pub fn generate(center: Vec2<i32>, rng: &mut impl Rng) -> Self {
Self {
name: generate_name(rng),
center,
kind: LocationKind::Wildnerness,
kingdom: None,
neighbours: FxHashSet::default(),
settlement: Settlement::generate(rng),
}
}

View File

@ -1,7 +1,9 @@
mod location;
mod settlement;
// Reexports
pub use self::location::Location;
pub use self::settlement::Settlement;
use crate::{
all::ForestKind,
@ -49,6 +51,8 @@ pub(crate) struct GenCtx {
pub struct WorldSim {
pub seed: u32,
pub(crate) chunks: Vec<SimChunk>,
pub(crate) locations: Vec<Location>,
pub(crate) gen_ctx: GenCtx,
pub rng: XorShiftRng,
}
@ -91,6 +95,7 @@ impl WorldSim {
let mut this = Self {
seed,
chunks,
locations: Vec::new(),
gen_ctx,
rng: XorShiftRng::from_seed([
(seed >> 0) as u8,
@ -113,7 +118,6 @@ impl WorldSim {
};
this.seed_elements();
this.simulate(0);
this
}
@ -126,7 +130,8 @@ impl WorldSim {
let grid_size = WORLD_SIZE / cell_size;
let loc_count = 250;
let mut locations = vec![None; grid_size.product()];
let mut loc_grid = vec![None; grid_size.product()];
let mut locations = Vec::new();
// Seed the world with some locations
for _ in 0..loc_count {
@ -134,13 +139,35 @@ impl WorldSim {
self.rng.gen::<usize>() % grid_size.x,
self.rng.gen::<usize>() % grid_size.y,
);
let wpos = (cell_pos * cell_size)
let wpos = (cell_pos * cell_size + cell_size / 2)
.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
e as i32 * sz as i32
e as i32 * sz as i32 + sz as i32 / 2
});
locations[cell_pos.y * grid_size.x + cell_pos.x] =
Some(Arc::new(Location::generate(wpos, &mut rng)));
locations.push(Location::generate(wpos, &mut rng));
loc_grid[cell_pos.y * grid_size.x + cell_pos.x] = Some(locations.len() - 1);
}
// Find neighbours
let mut loc_clone = locations
.iter()
.map(|l| l.center)
.enumerate()
.collect::<Vec<_>>();
for i in 0..locations.len() {
let pos = locations[i].center;
loc_clone.sort_by_key(|(_, l)| l.distance_squared(pos));
loc_clone
.iter()
.skip(1)
.take(2)
.for_each(|(j, _)| {
locations[i].neighbours.insert(*j);
locations[*j].neighbours.insert(i);
});
}
// Simulate invasion!
@ -148,13 +175,13 @@ impl WorldSim {
for _ in 0..invasion_cycles {
for i in 0..grid_size.x {
for j in 0..grid_size.y {
if locations[j * grid_size.x + i].is_none() {
if loc_grid[j * grid_size.x + i].is_none() {
const R_COORDS: [i32; 5] = [-1, 0, 1, 0, -1];
let idx = self.rng.gen::<usize>() % 4;
let loc = Vec2::new(i as i32 + R_COORDS[idx], j as i32 + R_COORDS[idx + 1])
.map(|e| e as usize);
locations[j * grid_size.x + i] = locations
loc_grid[j * grid_size.x + i] = loc_grid
.get(loc.y * grid_size.x + loc.x)
.cloned()
.flatten();
@ -168,6 +195,7 @@ impl WorldSim {
for i in 0..WORLD_SIZE.x {
for j in 0..WORLD_SIZE.y {
let chunk_pos = Vec2::new(i as i32, j as i32);
let block_pos = Vec2::new(chunk_pos.x * TerrainChunkSize::SIZE.x as i32, chunk_pos.y * TerrainChunkSize::SIZE.y as i32);
let cell_pos = Vec2::new(i / cell_size, j / cell_size);
// Find the distance to each region
@ -188,43 +216,28 @@ impl WorldSim {
near.sort_by(|a, b| a.dist.partial_cmp(&b.dist).unwrap());
let nearest_cell_pos = near[0].chunk_pos.map(|e| e as usize) / cell_size;
self.get_mut(chunk_pos).unwrap().location = locations
self.get_mut(chunk_pos).unwrap().location = loc_grid
.get(nearest_cell_pos.y * grid_size.x + nearest_cell_pos.x)
.cloned()
.unwrap_or(None)
.map(|loc| LocationInfo { loc, near });
}
}
.map(|loc_idx| LocationInfo { loc_idx, near });
self.rng = rng;
}
pub fn simulate(&mut self, cycles: usize) {
let mut rng = self.rng.clone();
for _ in 0..cycles {
for i in 0..WORLD_SIZE.x as i32 {
for j in 0..WORLD_SIZE.y as i32 {
let pos = Vec2::new(i, j);
let location = self.get(pos).unwrap().location.clone();
let rpos =
Vec2::new(rng.gen::<i32>(), rng.gen::<i32>()).map(|e| e.abs() % 3 - 1);
if let Some(other) = &mut self.get_mut(pos + rpos) {
if other.location.is_none()
&& rng.gen::<f32>() > other.chaos * 1.5
&& other.alt > CONFIG.sea_level
{
other.location = location;
}
}
let town_size = 200;
let in_town = self
.get(chunk_pos)
.unwrap()
.location
.as_ref()
.map(|l| locations[l.loc_idx].center.distance_squared(block_pos) < town_size * town_size)
.unwrap_or(false);
if in_town {
self.get_mut(chunk_pos).unwrap().spawn_rate = 0.0;
}
}
}
self.rng = rng;
self.locations = locations;
}
pub fn get(&self, chunk_pos: Vec2<i32>) -> Option<&SimChunk> {
@ -312,6 +325,7 @@ pub struct SimChunk {
pub near_cliffs: bool,
pub tree_density: f32,
pub forest_kind: ForestKind,
pub spawn_rate: f32,
pub location: Option<LocationInfo>,
}
@ -325,7 +339,7 @@ pub struct RegionInfo {
#[derive(Clone)]
pub struct LocationInfo {
pub loc: Arc<Location>,
pub loc_idx: usize,
pub near: Vec<RegionInfo>,
}
@ -437,6 +451,7 @@ impl SimChunk {
ForestKind::SnowPine
}
},
spawn_rate: 1.0,
location: None,
}
}
@ -454,8 +469,14 @@ impl SimChunk {
.max(CONFIG.sea_level + 2.0)
}
pub fn get_name(&self) -> Option<String> {
self.location.as_ref().map(|l| l.loc.name().to_string())
pub fn get_name(&self, world: &WorldSim) -> Option<String> {
if let Some(loc) = &self.location {
Some(world.locations[loc.loc_idx]
.name()
.to_string())
} else {
None
}
}
pub fn get_biome(&self) -> BiomeKind {

View File

@ -0,0 +1,95 @@
use rand::Rng;
use vek::*;
#[derive(Clone, Debug)]
pub struct Settlement {
lot: Lot,
}
impl Settlement {
pub fn generate(rng: &mut impl Rng) -> Self {
Self {
lot: Lot::generate(0, 32.0, 1.0, rng),
}
}
pub fn get_at(&self, pos: Vec2<f32>) -> Option<&Building> {
self.lot.get_at(pos)
}
}
#[derive(Clone, Debug)]
pub struct Building {
pub seed: u32,
}
#[derive(Clone, Debug)]
enum Lot {
None,
One(Building),
Many {
split_x: bool,
lots: Vec<Lot>,
},
}
impl Lot {
pub fn generate(deep: usize, depth: f32, aspect: f32, rng: &mut impl Rng) -> Self {
let depth = if deep < 3 {
8.0
} else {
depth
};
if (depth < 1.0 || deep > 6) && !(deep < 3 || deep % 2 == 1) {
if rng.gen::<f32>() < 0.5 {
Lot::One(Building {
seed: rng.gen(),
})
} else {
Lot::None
}
} else {
Lot::Many {
split_x: aspect > 1.0,
lots: {
let pow2 = 1 + rng.gen::<usize>() % 1;
let n = 1 << pow2;
let new_aspect = if aspect > 1.0 {
aspect / n as f32
} else {
aspect * n as f32
};
let vari = (rng.gen::<f32>() - 0.35) * 2.8;
let new_depth = depth * 0.5 * (1.0 + vari);
(0..n)
.map(|_| Lot::generate(deep + 1, new_depth, new_aspect, rng))
.collect()
},
}
}
}
pub fn get_at(&self, pos: Vec2<f32>) -> Option<&Building> {
match self {
Lot::None => None,
Lot::One(building) => if pos.map(|e| e > 0.1 && e < 0.9).reduce_and() {
Some(building)
} else {
None
},
Lot::Many { split_x, lots } => {
let split_dim = if *split_x { pos.x } else { pos.y };
let idx = (split_dim * lots.len() as f32).floor() as usize;
lots[idx.min(lots.len() - 1)].get_at(if *split_x {
Vec2::new((pos.x * lots.len() as f32).fract(), pos.y)
} else {
Vec2::new(pos.x, (pos.y * lots.len() as f32).fract())
})
},
}
}
}