Began work on CSG-based primitive tree site structure generation system

This commit is contained in:
Joshua Barretto 2021-02-28 23:34:36 +00:00
parent 48cbb4bed1
commit aedfd65721
8 changed files with 215 additions and 63 deletions

View File

@ -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!(

View File

@ -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> {

View File

@ -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);

View File

@ -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
View 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)
}
}

View File

@ -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,

View File

@ -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>>),

View 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)),
});
}
}