mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Began work on CSG-based primitive tree site structure generation system
This commit is contained in:
parent
48cbb4bed1
commit
aedfd65721
@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
cmp::{Eq, PartialEq},
|
||||
cmp::{Eq, PartialEq, Ord, PartialOrd, Ordering},
|
||||
fmt, hash,
|
||||
marker::PhantomData,
|
||||
ops::{Index, IndexMut},
|
||||
@ -29,6 +29,16 @@ impl<T> Eq for Id<T> {}
|
||||
impl<T> PartialEq for Id<T> {
|
||||
fn eq(&self, other: &Self) -> bool { self.idx == other.idx && self.gen == other.gen }
|
||||
}
|
||||
impl<T> Ord for Id<T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
(self.idx, self.gen).cmp(&(other.idx, other.gen))
|
||||
}
|
||||
}
|
||||
impl<T> PartialOrd for Id<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl<T> fmt::Debug for Id<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
|
@ -2,8 +2,9 @@ use crate::{
|
||||
block::ZCache,
|
||||
column::ColumnSample,
|
||||
index::IndexRef,
|
||||
sim::{SimChunk, WorldSim as Land},
|
||||
sim::{SimChunk, WorldSim},
|
||||
util::Grid,
|
||||
land::Land,
|
||||
};
|
||||
use common::{
|
||||
terrain::{Block, TerrainChunk, TerrainChunkSize},
|
||||
@ -17,7 +18,7 @@ pub struct CanvasInfo<'a> {
|
||||
pub(crate) wpos: Vec2<i32>,
|
||||
pub(crate) column_grid: &'a Grid<Option<ZCache<'a>>>,
|
||||
pub(crate) column_grid_border: i32,
|
||||
pub(crate) land: &'a Land,
|
||||
pub(crate) chunks: &'a WorldSim,
|
||||
pub(crate) index: IndexRef<'a>,
|
||||
pub(crate) chunk: &'a SimChunk,
|
||||
}
|
||||
@ -45,7 +46,11 @@ impl<'a> CanvasInfo<'a> {
|
||||
|
||||
pub fn chunk(&self) -> &'a SimChunk { self.chunk }
|
||||
|
||||
pub fn land(&self) -> &'a Land { self.land }
|
||||
pub fn chunks(&self) -> &'a WorldSim { self.chunks }
|
||||
|
||||
pub fn land(&self) -> Land<'_> {
|
||||
Land::from_sim(self.chunks)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Canvas<'a> {
|
||||
|
@ -58,11 +58,11 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
|
||||
|
||||
let info = canvas.info();
|
||||
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||
let trees = info.land().get_near_trees(wpos2d);
|
||||
let trees = info.chunks().get_near_trees(wpos2d);
|
||||
|
||||
for TreeAttr { pos, seed, scale, forest_kind, inhabited } in trees {
|
||||
let tree = if let Some(tree) = tree_cache.entry(pos).or_insert_with(|| {
|
||||
let col = ColumnGen::new(info.land()).get((pos, info.index()))?;
|
||||
let col = ColumnGen::new(info.chunks()).get((pos, info.index()))?;
|
||||
|
||||
let is_quirky = QUIRKY_RAND.chance(seed, 1.0 / 500.0);
|
||||
|
||||
|
@ -9,7 +9,8 @@
|
||||
const_panic,
|
||||
label_break_value,
|
||||
or_patterns,
|
||||
array_value_iter
|
||||
array_value_iter,
|
||||
array_map,
|
||||
)]
|
||||
|
||||
mod all;
|
||||
@ -282,7 +283,7 @@ impl World {
|
||||
wpos: chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||
column_grid: &zcache_grid,
|
||||
column_grid_border: grid_border,
|
||||
land: &self.sim,
|
||||
chunks: &self.sim,
|
||||
index,
|
||||
chunk: sim_chunk,
|
||||
},
|
||||
|
63
world/src/site2/gen.rs
Normal file
63
world/src/site2/gen.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use common::{
|
||||
terrain::Block,
|
||||
store::{Id, Store},
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
pub enum Primitive {
|
||||
Empty, // Placeholder
|
||||
Aabb(Aabb<i32>),
|
||||
And(Id<Primitive>, Id<Primitive>),
|
||||
Or(Id<Primitive>, Id<Primitive>),
|
||||
Xor(Id<Primitive>, Id<Primitive>),
|
||||
}
|
||||
|
||||
pub struct Fill {
|
||||
pub prim: Id<Primitive>,
|
||||
pub block: Block,
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
fn contains_at(&self, tree: &Store<Primitive>, prim: Id<Primitive>, pos: Vec3<i32>) -> bool {
|
||||
match &tree[prim] {
|
||||
Primitive::Empty => false,
|
||||
Primitive::Aabb(aabb) => (aabb.min.x..aabb.max.x).contains(&pos.x) && (aabb.min.y..aabb.max.y).contains(&pos.y),
|
||||
Primitive::And(a, b) => self.contains_at(tree, *a, pos) & self.contains_at(tree, *b, pos),
|
||||
Primitive::Or(a, b) => self.contains_at(tree, *a, pos) | self.contains_at(tree, *b, pos),
|
||||
Primitive::Xor(a, b) => self.contains_at(tree, *a, pos) ^ self.contains_at(tree, *b, pos),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_at(&self, tree: &Store<Primitive>, pos: Vec3<i32>) -> Option<Block> {
|
||||
Some(self.block).filter(|_| self.contains_at(tree, self.prim, pos))
|
||||
}
|
||||
|
||||
fn get_bounds_inner(&self, tree: &Store<Primitive>, prim: Id<Primitive>) -> Aabb<i32> {
|
||||
match &tree[prim] {
|
||||
Primitive::Empty => Aabb::new_empty(Vec3::zero()),
|
||||
Primitive::Aabb(aabb) => *aabb,
|
||||
Primitive::And(a, b) => self.get_bounds_inner(tree, *a).intersection(self.get_bounds_inner(tree, *b)),
|
||||
Primitive::Or(a, b) | Primitive::Xor(a, b) => self.get_bounds_inner(tree, *a).union(self.get_bounds_inner(tree, *b)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bounds(&self, tree: &Store<Primitive>) -> Aabb<i32> {
|
||||
self.get_bounds_inner(tree, self.prim)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Structure {
|
||||
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Fill)>(
|
||||
&self,
|
||||
emit_prim: F,
|
||||
emit_fill: G,
|
||||
) {}
|
||||
|
||||
// Generate a primitive tree and fills for this structure
|
||||
fn render_collect(&self) -> (Store<Primitive>, Vec<Fill>) {
|
||||
let mut tree = Store::default();
|
||||
let mut fills = Vec::new();
|
||||
let root = self.render(|p| tree.insert(p), |f| fills.push(f));
|
||||
(tree, fills)
|
||||
}
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
mod gen;
|
||||
mod plot;
|
||||
mod tile;
|
||||
|
||||
use self::{
|
||||
plot::{Plot, PlotKind},
|
||||
tile::{TileGrid, Tile, TileKind, HazardKind, TILE_SIZE},
|
||||
gen::{Primitive, Fill, Structure},
|
||||
};
|
||||
use crate::{
|
||||
site::SpawnRules,
|
||||
util::{Grid, attempt, CARDINALS, SQUARE_4, SQUARE_9},
|
||||
util::{Grid, attempt, DHashSet, CARDINALS, SQUARE_4, SQUARE_9, LOCALITY},
|
||||
Canvas,
|
||||
Land,
|
||||
};
|
||||
@ -86,7 +88,11 @@ impl Site {
|
||||
MAX_ITERS,
|
||||
&heuristic,
|
||||
|tile| { let tile = *tile; CARDINALS.iter().map(move |dir| tile + *dir) },
|
||||
|a, b| rng.gen_range(1.0..1.5),
|
||||
|a, b| {
|
||||
let alt_a = land.get_alt_approx(self.tile_center_wpos(*a));
|
||||
let alt_b = land.get_alt_approx(self.tile_center_wpos(*b));
|
||||
(alt_a - alt_b).abs() / TILE_SIZE as f32
|
||||
},
|
||||
|tile| *tile == b,
|
||||
).into_path()?;
|
||||
|
||||
@ -95,7 +101,6 @@ impl Site {
|
||||
root_tile: a,
|
||||
tiles: path.clone().into_iter().collect(),
|
||||
seed: rng.gen(),
|
||||
base_alt: 0,
|
||||
});
|
||||
|
||||
self.roads.push(plot);
|
||||
@ -163,7 +168,6 @@ impl Site {
|
||||
root_tile: pos,
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
base_alt: land.get_alt_approx(self.tile_center_wpos(aabr.center())) as i32,
|
||||
});
|
||||
self.plazas.push(plaza);
|
||||
self.blit_aabr(aabr, Tile {
|
||||
@ -239,11 +243,10 @@ impl Site {
|
||||
let size = (2.0 + rng.gen::<f32>().powf(8.0) * 3.0).round() as u32;
|
||||
if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(rng, 4..(size + 1).pow(2), Extent2::broadcast(size))) {
|
||||
let plot = site.create_plot(Plot {
|
||||
kind: PlotKind::House,
|
||||
kind: PlotKind::House(plot::House::generate(land, rng, &site, aabr)),
|
||||
root_tile: aabr.center(),
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
base_alt: land.get_alt_approx(site.tile_center_wpos(aabr.center())) as i32,
|
||||
});
|
||||
|
||||
site.blit_aabr(aabr, Tile {
|
||||
@ -260,7 +263,6 @@ impl Site {
|
||||
root_tile: aabr.center(),
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
base_alt: land.get_alt_approx(site.tile_center_wpos(aabr.center())) as i32,
|
||||
});
|
||||
|
||||
site.blit_aabr(aabr, Tile {
|
||||
@ -309,7 +311,6 @@ impl Site {
|
||||
root_tile: aabr.center(),
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
base_alt: land.get_alt_approx(site.tile_center_wpos(aabr.center())) as i32,
|
||||
});
|
||||
|
||||
// Walls
|
||||
@ -367,57 +368,47 @@ impl Site {
|
||||
|
||||
pub fn render_tile(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng, tpos: Vec2<i32>) {
|
||||
let tile = self.tiles.get(tpos);
|
||||
let twpos = self.tile_wpos(tpos);
|
||||
let twpos = self.tile_center_wpos(tpos);
|
||||
let cols = (-(TILE_SIZE as i32)..TILE_SIZE as i32 * 2).map(|y| (-(TILE_SIZE as i32)..TILE_SIZE as i32 * 2).map(move |x| (twpos + Vec2::new(x, y), Vec2::new(x, y)))).flatten();
|
||||
|
||||
match &tile.kind {
|
||||
TileKind::Empty | TileKind::Hazard(_) => {},
|
||||
TileKind::Road => cols.for_each(|(wpos2d, offs)| {
|
||||
let tpos = self.tile_wpos(wpos2d);
|
||||
TileKind::Road => {
|
||||
let near_roads = CARDINALS
|
||||
.map(|rpos| if self.tiles.get(tpos + rpos) == tile {
|
||||
Some(LineSegment2 {
|
||||
start: self.tile_center_wpos(tpos).map(|e| e as f32),
|
||||
end: self.tile_center_wpos(tpos + rpos).map(|e| e as f32),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
});
|
||||
|
||||
let is_x = [
|
||||
self.tiles.get(tpos - Vec2::unit_x()) == tile,
|
||||
self.tiles.get(tpos) == tile,
|
||||
self.tiles.get(tpos + Vec2::unit_x()) == tile,
|
||||
];
|
||||
cols.for_each(|(wpos2d, offs)| {
|
||||
let wpos2df = wpos2d.map(|e| e as f32);
|
||||
let nearest_road = near_roads
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(|line| Some(line?.projected_point(wpos2df)))
|
||||
.min_by_key(|p| p.distance_squared(wpos2df) as i32);
|
||||
|
||||
let dist_x = [
|
||||
if is_x[0] ^ is_x[1] { Some((offs.x % tile::TILE_SIZE as i32) * if is_x[1] { -1 } else { 1 }) } else { None },
|
||||
if is_x[1] ^ is_x[2] { Some((tile::TILE_SIZE as i32 - offs.x % tile::TILE_SIZE as i32) * if is_x[1] { -1 } else { 1 }) } else { None },
|
||||
].iter().filter_map(|x| *x).min();
|
||||
let is_near_road = nearest_road.map_or(false, |r| r.distance_squared(wpos2df) < 3.0f32.powi(2));
|
||||
|
||||
let is_y = [
|
||||
self.tiles.get(tpos - Vec2::unit_y()) == tile,
|
||||
self.tiles.get(tpos) == tile,
|
||||
self.tiles.get(tpos + Vec2::unit_y()) == tile,
|
||||
];
|
||||
|
||||
let dist_y = [
|
||||
if is_y[0] ^ is_y[1] { Some((offs.y % tile::TILE_SIZE as i32) * if is_y[1] { -1 } else { 1 }) } else { None },
|
||||
if is_y[1] ^ is_y[2] { Some((tile::TILE_SIZE as i32 - offs.y % tile::TILE_SIZE as i32) * if is_y[1] { -1 } else { 1 }) } else { None },
|
||||
].iter().filter_map(|x| *x).min();
|
||||
|
||||
let dist = dist_x.unwrap_or(-(tile::TILE_SIZE as i32)).min(dist_y.unwrap_or(-(tile::TILE_SIZE as i32)));
|
||||
|
||||
if dist > 4 {
|
||||
let alt = canvas.col(wpos2d).map_or(0, |c| c.alt as i32);
|
||||
(-4..5).for_each(|z| canvas.map(
|
||||
Vec3::new(wpos2d.x, wpos2d.y, alt + z),
|
||||
|b| if [
|
||||
BlockKind::Grass,
|
||||
BlockKind::Earth,
|
||||
BlockKind::Sand,
|
||||
BlockKind::Snow,
|
||||
BlockKind::Rock,
|
||||
]
|
||||
.contains(&b.kind()) {
|
||||
Block::new(BlockKind::Rock, Rgb::new(55, 45, 65))
|
||||
} else {
|
||||
b.with_sprite(SpriteKind::Empty)
|
||||
},
|
||||
));
|
||||
}
|
||||
}),
|
||||
if let Some(nearest_road) = nearest_road
|
||||
.filter(|r| r.distance_squared(wpos2df) < 4.0f32.powi(2))
|
||||
{
|
||||
let road_alt = canvas.col(nearest_road.map(|e| e.floor() as i32)).map_or(0, |col| col.alt as i32);
|
||||
(-4..5).for_each(|z| canvas.map(
|
||||
Vec3::new(wpos2d.x, wpos2d.y, road_alt + z),
|
||||
|b| if z > 0 {
|
||||
Block::air(SpriteKind::Empty)
|
||||
} else {
|
||||
Block::new(BlockKind::Rock, Rgb::new(55, 45, 65))
|
||||
},
|
||||
));
|
||||
}
|
||||
});
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
@ -428,9 +419,42 @@ impl Site {
|
||||
max: self.wpos_tile_pos(canvas.wpos() + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + 2) + 3, // Round up, uninclusive, border
|
||||
};
|
||||
|
||||
// Don't double-generate the same plot per chunk!
|
||||
let mut plots = DHashSet::default();
|
||||
|
||||
for y in tile_aabr.min.y..tile_aabr.max.y {
|
||||
for x in tile_aabr.min.x..tile_aabr.max.x {
|
||||
self.render_tile(canvas, dynamic_rng, Vec2::new(x, y));
|
||||
|
||||
if let Some(plot) = self.tiles.get(Vec2::new(x, y)).plot {
|
||||
plots.insert(plot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut plots_to_render = plots.into_iter().collect::<Vec<_>>();
|
||||
plots_to_render.sort_unstable();
|
||||
|
||||
for plot in plots_to_render {
|
||||
let (prim_tree, fills) = match &self.plots[plot].kind {
|
||||
PlotKind::House(house) => house.render_collect(),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
for fill in fills {
|
||||
let aabb = fill.get_bounds(&prim_tree);
|
||||
|
||||
for x in aabb.min.x..aabb.max.x + 1 {
|
||||
for y in aabb.min.y..aabb.max.y + 1 {
|
||||
for z in aabb.min.z..aabb.max.z + 1 {
|
||||
let pos = Vec3::new(x, y, z);
|
||||
|
||||
if let Some(block) = fill.sample_at(&prim_tree, pos) {
|
||||
canvas.set(pos, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,7 +462,7 @@ impl Site {
|
||||
// let tile = self.wpos_tile(wpos2d);
|
||||
// let seed = tile.plot.map_or(0, |p| self.plot(p).seed);
|
||||
// match tile.kind {
|
||||
// TileKind::Field | TileKind::Road => (-4..5).for_each(|z| canvas.map(
|
||||
// TileKind::Field /*| TileKind::Road*/ => (-4..5).for_each(|z| canvas.map(
|
||||
// Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32 + z),
|
||||
// |b| if [
|
||||
// BlockKind::Grass,
|
||||
|
@ -1,3 +1,10 @@
|
||||
mod house;
|
||||
|
||||
pub use self::{
|
||||
house::House,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::util::DHashSet;
|
||||
use common::path::Path;
|
||||
use vek::*;
|
||||
@ -7,7 +14,6 @@ pub struct Plot {
|
||||
pub(crate) root_tile: Vec2<i32>,
|
||||
pub(crate) tiles: DHashSet<Vec2<i32>>,
|
||||
pub(crate) seed: u32,
|
||||
pub(crate) base_alt: i32,
|
||||
}
|
||||
|
||||
impl Plot {
|
||||
@ -22,7 +28,7 @@ impl Plot {
|
||||
|
||||
pub enum PlotKind {
|
||||
Field,
|
||||
House,
|
||||
House(House),
|
||||
Plaza,
|
||||
Castle,
|
||||
Road(Path<Vec2<i32>>),
|
||||
|
43
world/src/site2/plot/house.rs
Normal file
43
world/src/site2/plot/house.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use super::*;
|
||||
use crate::Land;
|
||||
use common::terrain::{Block, BlockKind};
|
||||
use vek::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
pub struct House {
|
||||
bounds: Aabr<i32>,
|
||||
alt: i32,
|
||||
}
|
||||
|
||||
impl House {
|
||||
pub fn generate(land: &Land, rng: &mut impl Rng, site: &Site, tile_aabr: Aabr<i32>) -> Self {
|
||||
Self {
|
||||
bounds: Aabr {
|
||||
min: site.tile_wpos(tile_aabr.min),
|
||||
max: site.tile_wpos(tile_aabr.max),
|
||||
},
|
||||
alt: land.get_alt_approx(site.tile_center_wpos(tile_aabr.center())) as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Structure for House {
|
||||
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Fill)>(
|
||||
&self,
|
||||
mut emit_prim: F,
|
||||
mut emit_fill: G,
|
||||
) {
|
||||
let wall = emit_prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(self.bounds.min.x, self.bounds.min.y, self.alt - 8),
|
||||
max: Vec3::new(self.bounds.max.x, self.bounds.max.y, self.alt + 16),
|
||||
}));
|
||||
let inner = emit_prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(self.bounds.min.x + 1, self.bounds.min.y + 1, self.alt - 8),
|
||||
max: Vec3::new(self.bounds.max.x - 1, self.bounds.max.y - 1, self.alt + 16),
|
||||
}));
|
||||
emit_fill(Fill {
|
||||
prim: emit_prim(Primitive::Xor(wall, inner)),
|
||||
block: Block::new(BlockKind::Rock, Rgb::new(150, 50, 10)),
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user