From aedfd657216278774149b5409594ca2afe1095ac Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 28 Feb 2021 23:34:36 +0000 Subject: [PATCH] Began work on CSG-based primitive tree site structure generation system --- common/src/store.rs | 12 +++- world/src/canvas.rs | 11 ++- world/src/layer/tree.rs | 4 +- world/src/lib.rs | 5 +- world/src/site2/gen.rs | 63 ++++++++++++++++ world/src/site2/mod.rs | 130 ++++++++++++++++++++-------------- world/src/site2/plot.rs | 10 ++- world/src/site2/plot/house.rs | 43 +++++++++++ 8 files changed, 215 insertions(+), 63 deletions(-) create mode 100644 world/src/site2/gen.rs create mode 100644 world/src/site2/plot/house.rs diff --git a/common/src/store.rs b/common/src/store.rs index 0b61090350..6666ccadd4 100644 --- a/common/src/store.rs +++ b/common/src/store.rs @@ -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 Eq for Id {} impl PartialEq for Id { fn eq(&self, other: &Self) -> bool { self.idx == other.idx && self.gen == other.gen } } +impl Ord for Id { + fn cmp(&self, other: &Self) -> Ordering { + (self.idx, self.gen).cmp(&(other.idx, other.gen)) + } +} +impl PartialOrd for Id { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} impl fmt::Debug for Id { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( diff --git a/world/src/canvas.rs b/world/src/canvas.rs index d6ed1acd14..02de530bba 100644 --- a/world/src/canvas.rs +++ b/world/src/canvas.rs @@ -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, pub(crate) column_grid: &'a Grid>>, 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> { diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index ab7bd7d6ca..9198f08bd6 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -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); diff --git a/world/src/lib.rs b/world/src/lib.rs index 78e755a5a5..6a90f4c34a 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -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, }, diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs new file mode 100644 index 0000000000..c6f94d34e8 --- /dev/null +++ b/world/src/site2/gen.rs @@ -0,0 +1,63 @@ +use common::{ + terrain::Block, + store::{Id, Store}, +}; +use vek::*; + +pub enum Primitive { + Empty, // Placeholder + Aabb(Aabb), + And(Id, Id), + Or(Id, Id), + Xor(Id, Id), +} + +pub struct Fill { + pub prim: Id, + pub block: Block, +} + +impl Fill { + fn contains_at(&self, tree: &Store, prim: Id, pos: Vec3) -> 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, pos: Vec3) -> Option { + Some(self.block).filter(|_| self.contains_at(tree, self.prim, pos)) + } + + fn get_bounds_inner(&self, tree: &Store, prim: Id) -> Aabb { + 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) -> Aabb { + self.get_bounds_inner(tree, self.prim) + } +} + +pub trait Structure { + fn render Id, G: FnMut(Fill)>( + &self, + emit_prim: F, + emit_fill: G, + ) {} + + // Generate a primitive tree and fills for this structure + fn render_collect(&self) -> (Store, Vec) { + let mut tree = Store::default(); + let mut fills = Vec::new(); + let root = self.render(|p| tree.insert(p), |f| fills.push(f)); + (tree, fills) + } +} diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 3be06ebf8f..d154c48ca2 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -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::().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) { 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::>(); + 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, diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 4d8a771c71..cb50abbc19 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -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, pub(crate) tiles: DHashSet>, 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>), diff --git a/world/src/site2/plot/house.rs b/world/src/site2/plot/house.rs new file mode 100644 index 0000000000..2eb7cb37ea --- /dev/null +++ b/world/src/site2/plot/house.rs @@ -0,0 +1,43 @@ +use super::*; +use crate::Land; +use common::terrain::{Block, BlockKind}; +use vek::*; +use rand::prelude::*; + +pub struct House { + bounds: Aabr, + alt: i32, +} + +impl House { + pub fn generate(land: &Land, rng: &mut impl Rng, site: &Site, tile_aabr: Aabr) -> 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 Id, 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)), + }); + } +}