diff --git a/CHANGELOG.md b/CHANGELOG.md index dc512f1cdb..697f71dfc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/world/examples/dungeon_voxel_export.rs b/world/examples/dungeon_voxel_export.rs index b9308454b1..1a83274038 100644 --- a/world/examples/dungeon_voxel_export.rs +++ b/world/examples/dungeon_voxel_export.rs @@ -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); diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 3319834a4b..a6949a5a97 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -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 { diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index 1968191c9a..b6c9eb5870 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -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, + 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::::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::::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 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) -> (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::::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, 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 { self.aabb } + + pub fn get_line(&self) -> LineSegment3 { self.line } + + pub fn get_wood_radius(&self) -> f32 { self.wood_radius } + + pub fn get_leaf_radius(&self) -> f32 { self.leaf_radius } } struct Root { diff --git a/world/src/lib.rs b/world/src/lib.rs index c46d838f94..8d6f3252c2 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -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), } diff --git a/world/src/sim2/mod.rs b/world/src/sim2/mod.rs index b7091d0468..bf98236c76 100644 --- a/world/src/sim2/mod.rs +++ b/world/src/sim2/mod.rs @@ -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); } diff --git a/world/src/site/mod.rs b/world/src/site/mod.rs index 81888397dd..09a4b09b1d 100644 --- a/world/src/site/mod.rs +++ b/world/src/site/mod.rs @@ -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), } } diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index 711167edb2..0f6787e76b 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -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>, fills: RefCell, Fill)>>, + render_area: Aabr, } impl Painter { @@ -789,6 +791,8 @@ impl Painter { pub fn fill(&self, prim: impl Into>, fill: Fill) { self.fills.borrow_mut().push((prim.into(), fill)); } + + pub fn render_aabr(&self) -> Aabr { self.render_area } } #[derive(Copy, Clone)] @@ -867,14 +871,18 @@ pub trait Structure { fn render_collect( &self, site: &Site, - land: &Land, + canvas: &CanvasInfo, ) -> (Store, Vec<(Id, 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()) } } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index acd3d5dde2..c3dc9d8358 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -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) -> 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) -> 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::>(); 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, }; diff --git a/world/src/site2/plot.rs b/world/src/site2/plot.rs index 900d6d7c99..a7bacbc79d 100644 --- a/world/src/site2/plot.rs +++ b/world/src/site2/plot.rs @@ -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>), Dungeon(Dungeon), + GiantTree(GiantTree), } diff --git a/world/src/site2/plot/giant_tree.rs b/world/src/site2/plot/giant_tree.rs new file mode 100644 index 0000000000..3130b59ce8 --- /dev/null +++ b/world/src/site2/plot/giant_tree.rs @@ -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, + tree: ProceduralTree, + seed: u32, +} + +impl GiantTree { + pub fn generate(site: &Site, center_tile: Vec2, 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 + } + }); + } +}