Implement giant trees in site2

This commit is contained in:
InfRandomness 2022-02-02 02:33:37 +00:00 committed by Justin Shipsey
parent 18f6077321
commit 54b69e37a5
11 changed files with 247 additions and 40 deletions

View File

@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A 'point light glow' effect, making lanterns and other point lights more visually pronounced
- Generate random name for site2 sites
- Shader dithering to remove banding from scenes with large colour gradients
- Convert giant trees to site2
### Changed

View File

@ -44,7 +44,7 @@ fn main() -> Result {
CanvasInfo::with_mock_canvas_info(index.as_index_ref(), world.sim(), |canvas| {
for plot in site.plots() {
if let PlotKind::Dungeon(dungeon) = plot.kind() {
let (prim_tree, fills) = dungeon.render_collect(&site, &canvas.land());
let (prim_tree, fills) = dungeon.render_collect(&site, canvas);
for (prim, fill) in fills {
let aabb = fill.get_bounds(&prim_tree, prim);

View File

@ -103,7 +103,13 @@ impl Civs {
let (kind, size) = match ctx.rng.gen_range(0..64) {
0..=4 => (SiteKind::Castle, 3),
5..=28 if index.features().site2 => (SiteKind::Refactor, 6),
29..=31 => (SiteKind::Tree, 4),
29..=31 => {
if index.features().site2 {
(SiteKind::GiantTree, 4)
} else {
(SiteKind::Tree, 4)
}
},
_ => (SiteKind::Dungeon, 0),
};
let loc = find_site_loc(&mut ctx, None, size, kind)?;
@ -129,6 +135,7 @@ impl Civs {
SiteKind::Castle => (16i32, 5.0),
SiteKind::Refactor => (0i32, 0.0),
SiteKind::Tree => (12i32, 8.0),
SiteKind::GiantTree => (12i32, 8.0),
};
let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind {
@ -207,6 +214,11 @@ impl Civs {
SiteKind::Tree => {
WorldSite::tree(Tree::generate(wpos, &Land::from_sim(ctx.sim), &mut rng))
},
SiteKind::GiantTree => WorldSite::giant_tree(site2::Site::generate_giant_tree(
&Land::from_sim(ctx.sim),
&mut rng,
wpos,
)),
});
sim_site.site_tmp = Some(site);
let site_ref = &index.sites[site];
@ -1029,6 +1041,7 @@ pub enum SiteKind {
Castle,
Refactor,
Tree,
GiantTree,
}
impl SiteKind {

View File

@ -275,8 +275,8 @@ pub fn apply_trees_to(
&& dynamic_rng.gen_range(0..256) == 0
{
canvas.set(wpos + Vec3::unit_z(), Block::air(SpriteKind::Lantern));
// Add a snow covering to the block above under certain
// circumstances
// Add a snow covering to the block above under certain
// circumstances
} else if col.snow_cover
&& ((block.kind() == BlockKind::Leaves && is_leaf_top)
|| (is_top && block.is_filled()))
@ -803,47 +803,54 @@ impl ProceduralTree {
}
// Recursively search for branches or leaves by walking the tree's branch graph.
fn is_branch_or_leaves_at_inner(
fn walk_inner(
&self,
pos: Vec3<f32>,
descend: &mut impl FnMut(&Branch, &Branch) -> bool,
parent: &Branch,
branch_idx: usize,
) -> (bool, bool, bool, bool) {
) {
let branch = &self.branches[branch_idx];
// Always probe the sibling branch, since our AABB doesn't include its bounds
// (it's not one of our children)
let branch_or_leaves = branch
// Always probe the sibling branch, since it's not a child of the current
// branch.
let _branch_or_leaves = branch
.sibling_idx
.map(|idx| Vec4::<bool>::from(self.is_branch_or_leaves_at_inner(pos, parent, idx)))
.unwrap_or_default();
// Only continue probing this sub-graph of the tree if the sample position falls
// within its AABB
if branch.aabb.contains_point(pos) {
// Probe this branch
let (this, _d2) = branch.is_branch_or_leaves_at(&self.config, pos, parent);
let siblings = branch_or_leaves | Vec4::from(this);
.map(|idx| self.walk_inner(descend, parent, idx));
// Only continue probing this sub-graph of the tree if the branch maches a
// criteria (usually that it falls within the region we care about
// sampling)
if descend(branch, parent) {
// Probe the children of this branch
let children = branch
let _children = branch
.child_idx
.map(|idx| Vec4::<bool>::from(self.is_branch_or_leaves_at_inner(pos, branch, idx)))
.unwrap_or_default();
// Only allow empties for children if there is no solid at the current depth
(siblings | children).into_tuple()
} else {
branch_or_leaves.into_tuple()
.map(|idx| self.walk_inner(descend, branch, idx));
}
}
/// Recursively walk the tree's branches, calling the current closure with
/// the branch and its parent. If the closure returns `false`, recursion
/// into the child branches is skipped.
pub fn walk<F: FnMut(&Branch, &Branch) -> bool>(&self, mut f: F) {
self.walk_inner(&mut f, &self.branches[self.trunk_idx], self.trunk_idx);
}
/// Determine whether there are either branches or leaves at the given
/// position in the tree.
#[inline(always)]
pub fn is_branch_or_leaves_at(&self, pos: Vec3<f32>) -> (bool, bool, bool, bool) {
let (log, leaf, platform, air) =
self.is_branch_or_leaves_at_inner(pos, &self.branches[self.trunk_idx], self.trunk_idx);
let mut flags = Vec4::broadcast(false);
self.walk(|branch, parent| {
if branch.aabb.contains_point(pos) {
flags |=
Vec4::<bool>::from(branch.is_branch_or_leaves_at(&self.config, pos, parent).0);
true
} else {
false
}
});
let (log, leaf, platform, air) = flags.into_tuple();
let root = if self.root_aabb.contains_point(pos) {
self.roots.iter().any(|root| {
let p = root.line.projected_point(pos);
@ -867,7 +874,7 @@ impl ProceduralTree {
// associated with the parent. This means that the entire tree is laid out in a
// walkable graph where each branch refers only to two other branches. As a
// result, walking the tree is simply a case of performing double recursion.
struct Branch {
pub struct Branch {
line: LineSegment3<f32>,
wood_radius: f32,
leaf_radius: f32,
@ -969,6 +976,16 @@ impl Branch {
(mask, d2)
}
/// This returns an AABB of both the branch and all of the children of that
/// branch
pub fn get_aabb(&self) -> Aabb<f32> { self.aabb }
pub fn get_line(&self) -> LineSegment3<f32> { self.line }
pub fn get_wood_radius(&self) -> f32 { self.wood_radius }
pub fn get_leaf_radius(&self) -> f32 { self.leaf_radius }
}
struct Root {

View File

@ -155,7 +155,7 @@ impl World {
},
civ::SiteKind::Castle => world_msg::SiteKind::Castle,
civ::SiteKind::Refactor => world_msg::SiteKind::Town,
civ::SiteKind::Tree => world_msg::SiteKind::Tree,
civ::SiteKind::Tree | civ::SiteKind::GiantTree => world_msg::SiteKind::Tree,
},
wpos: site.center * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
}

View File

@ -198,6 +198,7 @@ fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::i
let mut castles = EconStatistics::default();
let mut towns = EconStatistics::default();
let mut dungeons = EconStatistics::default();
let giant_trees = EconStatistics::default();
for site in index.sites.ids() {
let site = &index.sites[site];
match site.kind {
@ -206,6 +207,7 @@ fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::i
SiteKind::Castle(_) => castles += site.economy.pop,
SiteKind::Tree(_) => (),
SiteKind::Refactor(_) => towns += site.economy.pop,
SiteKind::GiantTree(_) => (),
}
}
if towns.valid() {
@ -232,6 +234,14 @@ fn simulate_return(index: &mut Index, world: &mut WorldSim) -> Result<(), std::i
dungeons.sum / (dungeons.count as f32)
);
}
if giant_trees.valid() {
info!(
"Giant Trees {:.0}-{:.0} avg {:.0}",
giant_trees.min,
giant_trees.max,
giant_trees.sum / (giant_trees.count as f32)
)
}
check_money(index);
}

View File

@ -62,6 +62,7 @@ pub enum SiteKind {
Castle(Castle),
Refactor(site2::Site),
Tree(tree::Tree),
GiantTree(site2::Site),
}
impl Site {
@ -100,6 +101,13 @@ impl Site {
}
}
pub fn giant_tree(gt: site2::Site) -> Self {
Self {
kind: SiteKind::GiantTree(gt),
economy: Economy::default(),
}
}
pub fn radius(&self) -> f32 {
match &self.kind {
SiteKind::Settlement(s) => s.radius(),
@ -107,6 +115,7 @@ impl Site {
SiteKind::Castle(c) => c.radius(),
SiteKind::Refactor(s) => s.radius(),
SiteKind::Tree(t) => t.radius(),
SiteKind::GiantTree(gt) => gt.radius(),
}
}
@ -117,6 +126,7 @@ impl Site {
SiteKind::Castle(c) => c.get_origin(),
SiteKind::Refactor(s) => s.origin,
SiteKind::Tree(t) => t.origin,
SiteKind::GiantTree(gt) => gt.origin,
}
}
@ -127,6 +137,7 @@ impl Site {
SiteKind::Castle(c) => c.spawn_rules(wpos),
SiteKind::Refactor(s) => s.spawn_rules(wpos),
SiteKind::Tree(t) => t.spawn_rules(wpos),
SiteKind::GiantTree(gt) => gt.spawn_rules(wpos),
}
}
@ -137,6 +148,7 @@ impl Site {
SiteKind::Castle(c) => c.name(),
SiteKind::Refactor(s) => s.name(),
SiteKind::Tree(_) => "Giant Tree",
SiteKind::GiantTree(gt) => gt.name(),
}
}
@ -169,6 +181,7 @@ impl Site {
SiteKind::Castle(c) => c.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk),
SiteKind::Refactor(s) => s.render(canvas, dynamic_rng),
SiteKind::Tree(t) => t.render(canvas, dynamic_rng),
SiteKind::GiantTree(gt) => gt.render(canvas, dynamic_rng),
}
}
@ -192,6 +205,7 @@ impl Site {
SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),
SiteKind::Refactor(_) => {},
SiteKind::Tree(_) => {},
SiteKind::GiantTree(gt) => gt.apply_supplement(dynamic_rng, wpos2d, supplement),
}
}

View File

@ -3,6 +3,7 @@ use crate::{
block::block_from_structure,
site2::util::Dir,
util::{RandomField, Sampler},
CanvasInfo,
};
use common::{
store::{Id, Store},
@ -504,6 +505,7 @@ impl Fill {
pub struct Painter {
prims: RefCell<Store<Primitive>>,
fills: RefCell<Vec<(Id<Primitive>, Fill)>>,
render_area: Aabr<i32>,
}
impl Painter {
@ -789,6 +791,8 @@ impl Painter {
pub fn fill(&self, prim: impl Into<Id<Primitive>>, fill: Fill) {
self.fills.borrow_mut().push((prim.into(), fill));
}
pub fn render_aabr(&self) -> Aabr<i32> { self.render_area }
}
#[derive(Copy, Clone)]
@ -867,14 +871,18 @@ pub trait Structure {
fn render_collect(
&self,
site: &Site,
land: &Land,
canvas: &CanvasInfo,
) -> (Store<Primitive>, Vec<(Id<Primitive>, Fill)>) {
let painter = Painter {
prims: RefCell::new(Store::default()),
fills: RefCell::new(Vec::new()),
render_area: Aabr {
min: canvas.wpos,
max: canvas.wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
},
};
self.render(site, land, &painter);
self.render(site, &canvas.land(), &painter);
(painter.prims.into_inner(), painter.fills.into_inner())
}
}

View File

@ -55,7 +55,19 @@ impl Site {
.map(|e| e.abs())
.reduce_max()
.max(self.tiles.bounds.max.map(|e| e.abs()).reduce_max())
+ 1)
// Temporary solution for giving giant_tree's leaves enough space to be painted correctly
// TODO: This will have to be replaced by a system as described on discord :
// https://discord.com/channels/449602562165833758/450064928720814081/937044837461536808
+ if self
.plots
.values()
.any(|p| matches!(&p.kind, PlotKind::GiantTree(_)))
{
// 25 Seems to be big enough for the current scale of 4.0
25
} else {
1
})
* tile::TILE_SIZE as i32) as f32
}
@ -364,6 +376,40 @@ impl Site {
site
}
pub fn generate_giant_tree(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
let mut rng = reseed(rng);
let mut site = Site {
origin,
..Site::default()
};
site.demarcate_obstacles(land);
let giant_tree = plot::GiantTree::generate(&site, Vec2::zero(), land, &mut rng);
site.name = giant_tree.name().to_string();
let size = (giant_tree.radius() / tile::TILE_SIZE as f32).ceil() as i32;
let aabr = Aabr {
min: Vec2::broadcast(-size),
max: Vec2::broadcast(size) + 1,
};
let plot = site.create_plot(Plot {
kind: PlotKind::GiantTree(giant_tree),
root_tile: aabr.center(),
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
});
site.blit_aabr(aabr, Tile {
kind: TileKind::Building,
plot: Some(plot),
hard_alt: None,
});
site
}
pub fn generate_city(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
let mut rng = reseed(rng);
@ -875,6 +921,13 @@ impl Site {
}
}
// TODO: Solve the 'trees are too big' problem and remove this
for (id, plot) in self.plots.iter() {
if matches!(&plot.kind, PlotKind::GiantTree(_)) {
plots.insert(id);
}
}
let mut plots_to_render = plots.into_iter().collect::<Vec<_>>();
plots_to_render.sort_unstable();
@ -888,10 +941,11 @@ impl Site {
for plot in plots_to_render {
let (prim_tree, fills) = match &self.plots[plot].kind {
PlotKind::House(house) => house.render_collect(self, &canvas.land()),
PlotKind::Workshop(workshop) => workshop.render_collect(self, &canvas.land()),
PlotKind::Castle(castle) => castle.render_collect(self, &canvas.land()),
PlotKind::Dungeon(dungeon) => dungeon.render_collect(self, &canvas.land()),
PlotKind::House(house) => house.render_collect(self, canvas),
PlotKind::Workshop(workshop) => workshop.render_collect(self, canvas),
PlotKind::Castle(castle) => castle.render_collect(self, canvas),
PlotKind::Dungeon(dungeon) => dungeon.render_collect(self, canvas),
PlotKind::GiantTree(giant_tree) => giant_tree.render_collect(self, canvas),
_ => continue,
};

View File

@ -1,9 +1,12 @@
mod castle;
pub mod dungeon;
mod giant_tree;
mod house;
mod workshop;
pub use self::{castle::Castle, dungeon::Dungeon, house::House, workshop::Workshop};
pub use self::{
castle::Castle, dungeon::Dungeon, giant_tree::GiantTree, house::House, workshop::Workshop,
};
use super::*;
use crate::util::DHashSet;
@ -45,4 +48,5 @@ pub enum PlotKind {
Castle(Castle),
Road(Path<Vec2<i32>>),
Dungeon(Dungeon),
GiantTree(GiantTree),
}

View File

@ -0,0 +1,86 @@
use crate::{
layer::tree::{ProceduralTree, TreeConfig},
site::namegen::NameGen,
site2::{Fill, Painter, Site, Structure},
util::FastNoise,
Land, Sampler,
};
use common::terrain::{Block, BlockKind};
use rand::Rng;
use vek::*;
pub struct GiantTree {
name: String,
wpos: Vec3<i32>,
tree: ProceduralTree,
seed: u32,
}
impl GiantTree {
pub fn generate(site: &Site, center_tile: Vec2<i32>, land: &Land, rng: &mut impl Rng) -> Self {
let wpos = site.tile_center_wpos(center_tile);
Self {
name: format!("Tree of {}", NameGen::location(rng).generate()),
// Find the tree's altitude
wpos: wpos.with_z(land.get_alt_approx(wpos) as i32),
tree: {
let config = TreeConfig::giant(rng, 4.0, true);
ProceduralTree::generate(config, rng)
},
seed: rng.gen(),
}
}
pub fn name(&self) -> &str { &self.name }
pub fn radius(&self) -> f32 { 100.0 }
pub fn tree(&self) -> &ProceduralTree { &self.tree }
}
impl Structure for GiantTree {
fn render(&self, _site: &Site, _land: &Land, painter: &Painter) {
let fast_noise = FastNoise::new(self.seed);
let dark = Rgb::new(10, 70, 50).map(|e| e as f32);
let light = Rgb::new(80, 140, 10).map(|e| e as f32);
let leaf_col = Lerp::lerp(
dark,
light,
fast_noise.get((self.wpos.map(|e| e as f64) * 0.05) * 0.5 + 0.5),
);
self.tree.walk(|branch, _| {
let aabr = Aabr {
min: self.wpos.xy() + branch.get_aabb().min.xy().as_(),
max: self.wpos.xy() + branch.get_aabb().max.xy().as_(),
};
if aabr.collides_with_aabr(painter.render_aabr().as_()) {
// TODO : Migrate to using Painter#line() instead
painter
.line(
self.wpos + branch.get_line().start.as_(),
self.wpos + branch.get_line().end.as_(),
branch.get_wood_radius(),
)
.fill(Fill::Block(Block::new(
BlockKind::Wood,
Rgb::new(80, 32, 0),
)));
if branch.get_leaf_radius() > branch.get_wood_radius() {
painter
.line(
self.wpos + branch.get_line().start.as_(),
self.wpos + branch.get_line().end.as_(),
branch.get_leaf_radius(),
)
.fill(Fill::Block(Block::new(
BlockKind::Leaves,
leaf_col.map(|e| e as u8),
)))
}
true
} else {
false
}
});
}
}