mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added settlement generation
This commit is contained in:
parent
f781aca8e9
commit
b40d19ad0e
30
world/examples/city.rs
Normal file
30
world/examples/city.rs
Normal 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
47
world/examples/turb.rs
Normal 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;
|
||||
}
|
||||
}
|
@ -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]);
|
||||
|
@ -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,
|
||||
|
@ -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>,
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
95
world/src/sim/settlement.rs
Normal file
95
world/src/sim/settlement.rs
Normal 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())
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user