mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added initial civ generation
This commit is contained in:
parent
41b77a9b10
commit
c1514fc37b
@ -27,21 +27,21 @@ fn main() {
|
||||
for j in 0..H {
|
||||
let pos = focus + Vec2::new(i as i32, j as i32) * scale;
|
||||
|
||||
let (alt, location) = sampler
|
||||
let (alt, place) = sampler
|
||||
.get(pos)
|
||||
.map(|sample| {
|
||||
(
|
||||
sample.alt.sub(64.0).add(gain).mul(0.7).max(0.0).min(255.0) as u8,
|
||||
sample.location,
|
||||
sample.chunk.place,
|
||||
)
|
||||
})
|
||||
.unwrap_or((0, None));
|
||||
|
||||
let loc_color = location
|
||||
.map(|l| (l.loc_idx as u8 * 17, l.loc_idx as u8 * 13))
|
||||
let place_color = place
|
||||
.map(|p| ((p.id() % 256) as u8 * 17, (p.id() % 256) as u8 * 13))
|
||||
.unwrap_or((0, 0));
|
||||
|
||||
buf[j * W + i] = u32::from_le_bytes([loc_color.0, loc_color.1, alt, alt]);
|
||||
buf[j * W + i] = u32::from_le_bytes([place_color.0, place_color.1, alt, alt]);
|
||||
}
|
||||
}
|
||||
|
||||
|
248
world/src/civ/mod.rs
Normal file
248
world/src/civ/mod.rs
Normal file
@ -0,0 +1,248 @@
|
||||
use std::ops::Range;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use vek::*;
|
||||
use rand::prelude::*;
|
||||
use common::{
|
||||
terrain::TerrainChunkSize,
|
||||
vol::RectVolSize,
|
||||
store::{Id, Store},
|
||||
path::Path,
|
||||
astar::Astar,
|
||||
};
|
||||
use crate::sim::WorldSim;
|
||||
|
||||
const CARDINALS: [Vec2<i32>; 4] = [
|
||||
Vec2::new(1, 0),
|
||||
Vec2::new(-1, 0),
|
||||
Vec2::new(0, 1),
|
||||
Vec2::new(0, -1),
|
||||
];
|
||||
|
||||
const DIAGONALS: [Vec2<i32>; 8] = [
|
||||
Vec2::new(1, 0),
|
||||
Vec2::new(1, 1),
|
||||
Vec2::new(-1, 0),
|
||||
Vec2::new(-1, 1),
|
||||
Vec2::new(0, 1),
|
||||
Vec2::new(1, -1),
|
||||
Vec2::new(0, -1),
|
||||
Vec2::new(-1, -1),
|
||||
];
|
||||
|
||||
fn attempt<T>(max_iters: usize, mut f: impl FnMut() -> Option<T>) -> Option<T> {
|
||||
(0..max_iters).find_map(|_| f())
|
||||
}
|
||||
|
||||
const INITIAL_CIV_COUNT: usize = 20;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Civs {
|
||||
civs: Store<Civ>,
|
||||
places: Store<Place>,
|
||||
routes: HashMap<(Id<Place>, Id<Place>), Route>,
|
||||
}
|
||||
|
||||
struct GenCtx<'a, R: Rng> {
|
||||
sim: &'a mut WorldSim,
|
||||
rng: &'a mut R,
|
||||
}
|
||||
|
||||
impl Civs {
|
||||
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
|
||||
let mut this = Self::default();
|
||||
let mut rng = sim.rng.clone();
|
||||
let mut ctx = GenCtx { sim, rng: &mut rng };
|
||||
|
||||
for _ in 0..INITIAL_CIV_COUNT {
|
||||
if let Some(civ) = this.birth_civ(&mut ctx) {
|
||||
println!("Initial civilisation: {:#?}", this.civs.get(civ));
|
||||
} else {
|
||||
println!("Failed to find starting site");
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary!
|
||||
for route in this.routes.values() {
|
||||
for loc in route.path.iter() {
|
||||
sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland);
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
|
||||
const CIV_BIRTHPLACE_AREA: Range<usize> = 64..256;
|
||||
let place = attempt(5, || {
|
||||
let loc = find_site_loc(ctx, None)?;
|
||||
self.establish_place(ctx, loc, CIV_BIRTHPLACE_AREA)
|
||||
})?;
|
||||
|
||||
let civ = self.civs.insert(Civ {
|
||||
homeland: place,
|
||||
});
|
||||
|
||||
Some(civ)
|
||||
}
|
||||
|
||||
fn establish_place(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>, area: Range<usize>) -> Option<Id<Place>> {
|
||||
let mut dead = HashSet::new();
|
||||
let mut alive = HashSet::new();
|
||||
alive.insert(loc);
|
||||
|
||||
// Fill the surrounding area
|
||||
while let Some(cloc) = alive.iter().choose(ctx.rng).copied() {
|
||||
for dir in CARDINALS.iter() {
|
||||
if site_in_dir(&ctx.sim, cloc, *dir) {
|
||||
let rloc = cloc + *dir;
|
||||
if !dead.contains(&rloc) && ctx.sim.get(rloc).map(|c| c.place.is_none()).unwrap_or(false) {
|
||||
alive.insert(rloc);
|
||||
}
|
||||
}
|
||||
}
|
||||
alive.remove(&cloc);
|
||||
dead.insert(cloc);
|
||||
|
||||
if dead.len() + alive.len() >= area.end {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Make sure the place is large enough
|
||||
if dead.len() + alive.len() <= area.start {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Find neighbors
|
||||
const MAX_NEIGHBOR_DISTANCE: f32 = 100.0;
|
||||
let mut nearby = self.places
|
||||
.iter_ids()
|
||||
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
|
||||
.filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
|
||||
.collect::<Vec<_>>();
|
||||
nearby.sort_by_key(|(_, dist)| -*dist as i32);
|
||||
let route_count = ctx.rng.gen_range(1, 3);
|
||||
let neighbors = nearby
|
||||
.into_iter()
|
||||
.map(|(p, _)| p)
|
||||
.filter_map(|p| if let Some(path) = find_path(ctx, loc, self.places.get(p).center) {
|
||||
Some((p, path))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.take(route_count)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let place = self.places.insert(Place {
|
||||
center: loc,
|
||||
neighbors: neighbors.iter().map(|(p, _)| *p).collect(),
|
||||
});
|
||||
|
||||
// Insert routes to neighbours into route list
|
||||
for (p, path) in neighbors {
|
||||
self.routes.insert((place, p), Route { path });
|
||||
}
|
||||
|
||||
// Write place to map
|
||||
for cell in dead.union(&alive) {
|
||||
if let Some(chunk) = ctx.sim.get_mut(*cell) {
|
||||
chunk.place = Some(place);
|
||||
}
|
||||
}
|
||||
|
||||
Some(place)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to find a path between two locations
|
||||
fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<Path<Vec2<i32>>> {
|
||||
let sim = &ctx.sim;
|
||||
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
|
||||
let neighbors = |l: &Vec2<i32>| {
|
||||
let l = *l;
|
||||
DIAGONALS.iter().filter(move |dir| walk_in_dir(sim, l, **dir).is_some()).map(move |dir| l + *dir)
|
||||
};
|
||||
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
|
||||
let satisfied = |l: &Vec2<i32>| *l == b;
|
||||
Astar::new(5000, a, heuristic)
|
||||
.poll(5000, heuristic, neighbors, transition, satisfied)
|
||||
.into_path()
|
||||
}
|
||||
|
||||
/// Return true if travel between a location and a chunk next to it is permitted (TODO: by whom?)
|
||||
fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
||||
if loc_suitable_for_walking(sim, a) &&
|
||||
loc_suitable_for_walking(sim, a + dir)
|
||||
{
|
||||
let a_alt = sim.get(a)?.alt;
|
||||
let b_alt = sim.get(a + dir)?.alt;
|
||||
Some((b_alt - a_alt).max(0.0).powf(2.0).abs() / 50.0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if a position is suitable for walking on
|
||||
fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
||||
if let Some(chunk) = sim.get(loc) {
|
||||
!chunk.river.is_ocean() && !chunk.river.is_lake()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if a site could be constructed between a location and a chunk next to it is permitted (TODO: by whom?)
|
||||
fn site_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> bool {
|
||||
loc_suitable_for_site(sim, a) &&
|
||||
loc_suitable_for_site(sim, a + dir)
|
||||
}
|
||||
|
||||
/// Return true if a position is suitable for site construction (TODO: criteria?)
|
||||
fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
||||
if let Some(chunk) = sim.get(loc) {
|
||||
!chunk.is_underwater() &&
|
||||
sim.get_gradient_approx(loc).map(|grad| grad < 1.0).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to search for a location that's suitable for site construction
|
||||
fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) -> Option<Vec2<i32>> {
|
||||
const MAX_ATTEMPTS: usize = 100;
|
||||
let mut loc = None;
|
||||
for _ in 0..MAX_ATTEMPTS {
|
||||
let test_loc = loc.unwrap_or_else(|| match near {
|
||||
Some((origin, dist)) => origin + (Vec2::new(
|
||||
ctx.rng.gen_range(-1.0, 1.0),
|
||||
ctx.rng.gen_range(-1.0, 1.0),
|
||||
).try_normalized().unwrap_or(Vec2::zero()) * ctx.rng.gen::<f32>() * dist).map(|e| e as i32),
|
||||
None => Vec2::new(
|
||||
ctx.rng.gen_range(0, ctx.sim.get_size().x as i32),
|
||||
ctx.rng.gen_range(0, ctx.sim.get_size().y as i32),
|
||||
),
|
||||
});
|
||||
|
||||
if loc_suitable_for_site(&ctx.sim, test_loc) {
|
||||
return Some(test_loc);
|
||||
}
|
||||
|
||||
loc = ctx.sim.get(test_loc).and_then(|c| Some(c.downhill?.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
|
||||
e / (sz as i32)
|
||||
})));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Civ {
|
||||
homeland: Id<Place>,
|
||||
}
|
||||
|
||||
pub struct Place {
|
||||
center: Vec2<i32>,
|
||||
neighbors: Vec<Id<Place>>,
|
||||
}
|
||||
|
||||
pub struct Route {
|
||||
path: Path<Vec2<i32>>,
|
||||
}
|
@ -9,6 +9,7 @@ pub mod config;
|
||||
pub mod sim;
|
||||
pub mod site;
|
||||
pub mod util;
|
||||
pub mod civ;
|
||||
|
||||
// Reexports
|
||||
pub use crate::config::CONFIG;
|
||||
@ -34,13 +35,14 @@ pub enum Error {
|
||||
|
||||
pub struct World {
|
||||
sim: sim::WorldSim,
|
||||
civs: civ::Civs,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
|
||||
Self {
|
||||
sim: sim::WorldSim::generate(seed, opts),
|
||||
}
|
||||
let mut sim = sim::WorldSim::generate(seed, opts);
|
||||
let civs = civ::Civs::generate(seed, &mut sim);
|
||||
Self { sim, civs }
|
||||
}
|
||||
|
||||
pub fn sim(&self) -> &sim::WorldSim { &self.sim }
|
||||
|
@ -110,6 +110,14 @@ pub enum RiverKind {
|
||||
}
|
||||
|
||||
impl RiverKind {
|
||||
pub fn is_ocean(&self) -> bool {
|
||||
if let RiverKind::Ocean = *self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_river(&self) -> bool {
|
||||
if let RiverKind::River { .. } = *self {
|
||||
true
|
||||
@ -187,6 +195,13 @@ pub struct RiverData {
|
||||
}
|
||||
|
||||
impl RiverData {
|
||||
pub fn is_ocean(&self) -> bool {
|
||||
self.river_kind
|
||||
.as_ref()
|
||||
.map(RiverKind::is_ocean)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn is_river(&self) -> bool {
|
||||
self.river_kind
|
||||
.as_ref()
|
||||
|
@ -147,7 +147,7 @@ impl MapConfig {
|
||||
let pos =
|
||||
(focus_rect + Vec2::new(i as f64, j as f64) * scale).map(|e: f64| e as i32);
|
||||
|
||||
let (alt, basement, water_alt, humidity, temperature, downhill, river_kind) =
|
||||
let (alt, basement, water_alt, humidity, temperature, downhill, river_kind, place) =
|
||||
sampler
|
||||
.get(pos)
|
||||
.map(|sample| {
|
||||
@ -159,6 +159,7 @@ impl MapConfig {
|
||||
sample.temp,
|
||||
sample.downhill,
|
||||
sample.river.river_kind,
|
||||
sample.place,
|
||||
)
|
||||
})
|
||||
.unwrap_or((
|
||||
@ -169,6 +170,7 @@ impl MapConfig {
|
||||
0.0,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
));
|
||||
let humidity = humidity.min(1.0).max(0.0);
|
||||
let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5;
|
||||
@ -295,6 +297,12 @@ impl MapConfig {
|
||||
),
|
||||
};
|
||||
|
||||
let rgba = if let Some(place) = place {
|
||||
(((place.id() * 64) % 256) as u8, 0, 0, 0)
|
||||
} else {
|
||||
rgba
|
||||
};
|
||||
|
||||
write_pixel(Vec2::new(i, j), rgba);
|
||||
});
|
||||
|
||||
|
@ -26,6 +26,7 @@ use crate::{
|
||||
block::BlockGen,
|
||||
column::ColumnGen,
|
||||
site::{Settlement, Site},
|
||||
civ::Place,
|
||||
util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d},
|
||||
CONFIG,
|
||||
};
|
||||
@ -33,6 +34,7 @@ use common::{
|
||||
assets,
|
||||
terrain::{BiomeKind, TerrainChunkSize},
|
||||
vol::RectVolSize,
|
||||
store::Id,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use noise::{
|
||||
@ -1303,6 +1305,10 @@ impl WorldSim {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> Vec2<u32> {
|
||||
WORLD_SIZE.map(|e| e as u32)
|
||||
}
|
||||
|
||||
/// Draw a map of the world based on chunk information. Returns a buffer of
|
||||
/// u32s.
|
||||
pub fn get_map(&self) -> Vec<u32> {
|
||||
@ -1548,6 +1554,18 @@ impl WorldSim {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_gradient_approx(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
|
||||
let a = self.get(chunk_pos)?;
|
||||
if let Some(downhill) = a.downhill {
|
||||
let b = self.get(downhill.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
|
||||
e / (sz as i32)
|
||||
}))?;
|
||||
Some((a.alt - b.alt).abs() / TerrainChunkSize::RECT_SIZE.x as f32)
|
||||
} else {
|
||||
Some(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_wpos(&self, wpos: Vec2<i32>) -> Option<&SimChunk> {
|
||||
self.get(
|
||||
wpos.map2(Vec2::from(TerrainChunkSize::RECT_SIZE), |e, sz: u32| {
|
||||
@ -1768,6 +1786,7 @@ pub struct SimChunk {
|
||||
pub river: RiverData,
|
||||
|
||||
pub sites: Vec<Site>,
|
||||
pub place: Option<Id<Place>>,
|
||||
pub contains_waypoint: bool,
|
||||
}
|
||||
|
||||
@ -2006,12 +2025,14 @@ impl SimChunk {
|
||||
spawn_rate: 1.0,
|
||||
location: None,
|
||||
river,
|
||||
|
||||
sites: Vec::new(),
|
||||
place: None,
|
||||
contains_waypoint: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_underwater(&self) -> bool { self.river.river_kind.is_some() }
|
||||
pub fn is_underwater(&self) -> bool { self.water_alt > self.alt || self.river.river_kind.is_some() }
|
||||
|
||||
pub fn get_base_z(&self) -> f32 { self.alt - self.chaos * 50.0 - 16.0 }
|
||||
|
||||
|
@ -9,10 +9,11 @@ use common::{
|
||||
spiral::Spiral2d,
|
||||
terrain::{Block, BlockKind},
|
||||
vol::{BaseVol, RectSizedVol, WriteVol},
|
||||
store::{Id, Store},
|
||||
};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rand::prelude::*;
|
||||
use std::{collections::VecDeque, f32, marker::PhantomData};
|
||||
use std::{collections::VecDeque, f32};
|
||||
use vek::*;
|
||||
|
||||
pub fn gradient(line: [Vec2<f32>; 2]) -> f32 {
|
||||
@ -723,37 +724,3 @@ impl Land {
|
||||
|
||||
pub fn new_plot(&mut self, plot: Plot) -> Id<Plot> { self.plots.insert(plot) }
|
||||
}
|
||||
|
||||
#[derive(Hash)]
|
||||
pub struct Id<T>(usize, PhantomData<T>);
|
||||
|
||||
impl<T> Copy for Id<T> {}
|
||||
impl<T> Clone for Id<T> {
|
||||
fn clone(&self) -> Self { Self(self.0, PhantomData) }
|
||||
}
|
||||
impl<T> Eq for Id<T> {}
|
||||
impl<T> PartialEq for Id<T> {
|
||||
fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
|
||||
}
|
||||
|
||||
pub struct Store<T> {
|
||||
items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for Store<T> {
|
||||
fn default() -> Self { Self { items: Vec::new() } }
|
||||
}
|
||||
|
||||
impl<T> Store<T> {
|
||||
pub fn get(&self, id: Id<T>) -> &T { self.items.get(id.0).unwrap() }
|
||||
|
||||
pub fn get_mut(&mut self, id: Id<T>) -> &mut T { self.items.get_mut(id.0).unwrap() }
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> { self.items.iter() }
|
||||
|
||||
pub fn insert(&mut self, item: T) -> Id<T> {
|
||||
let id = Id(self.items.len(), PhantomData);
|
||||
self.items.push(item);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user