More tree variety, denser forests

This commit is contained in:
Joshua Barretto 2021-02-06 23:53:25 +00:00
parent 81206d5e13
commit e30c625d81
7 changed files with 199 additions and 146 deletions

View File

@ -31,7 +31,13 @@ impl<T> PartialEq for Id<T> {
}
impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Id<{}>({}, {})", std::any::type_name::<T>(), self.idx, self.gen)
write!(
f,
"Id<{}>({}, {})",
std::any::type_name::<T>(),
self.idx,
self.gen
)
}
}
impl<T> hash::Hash for Id<T> {
@ -69,9 +75,7 @@ impl<T> Store<T> {
}
pub fn get(&self, id: Id<T>) -> &T {
let entry = self.entries
.get(id.idx as usize)
.unwrap();
let entry = self.entries.get(id.idx as usize).unwrap();
if entry.gen == id.gen {
entry.item.as_ref().unwrap()
} else {
@ -80,9 +84,7 @@ impl<T> Store<T> {
}
pub fn get_mut(&mut self, id: Id<T>) -> &mut T {
let entry = self.entries
.get_mut(id.idx as usize)
.unwrap();
let entry = self.entries.get_mut(id.idx as usize).unwrap();
if entry.gen == id.gen {
entry.item.as_mut().unwrap()
} else {
@ -90,13 +92,9 @@ impl<T> Store<T> {
}
}
pub fn ids(&self) -> impl Iterator<Item = Id<T>> + '_ {
self.iter().map(|(id, _)| id)
}
pub fn ids(&self) -> impl Iterator<Item = Id<T>> + '_ { self.iter().map(|(id, _)| id) }
pub fn values(&self) -> impl Iterator<Item = &T> + '_ {
self.iter().map(|(_, item)| item)
}
pub fn values(&self) -> impl Iterator<Item = &T> + '_ { self.iter().map(|(_, item)| item) }
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> + '_ {
self.iter_mut().map(|(_, item)| item)
@ -111,7 +109,8 @@ impl<T> Store<T> {
idx: idx as u32,
gen: entry.gen,
phantom: PhantomData,
}).zip(entry.item.as_ref())
})
.zip(entry.item.as_ref())
})
}
@ -124,14 +123,16 @@ impl<T> Store<T> {
idx: idx as u32,
gen: entry.gen,
phantom: PhantomData,
}).zip(entry.item.as_mut())
})
.zip(entry.item.as_mut())
})
}
pub fn insert(&mut self, item: T) -> Id<T> {
if self.len < self.entries.len() {
// TODO: Make this more efficient with a lookahead system
let (idx, entry) = self.entries
let (idx, entry) = self
.entries
.iter_mut()
.enumerate()
.find(|(_, e)| e.item.is_none())
@ -161,13 +162,10 @@ impl<T> Store<T> {
}
pub fn remove(&mut self, id: Id<T>) -> Option<T> {
if let Some(item) = self.entries
if let Some(item) = self
.entries
.get_mut(id.idx as usize)
.and_then(|e| if e.gen == id.gen {
e.item.take()
} else {
None
})
.and_then(|e| if e.gen == id.gen { e.item.take() } else { None })
{
self.len -= 1;
Some(item)

View File

@ -5,7 +5,10 @@ use veloren_world::site2::test_site;
fn main() {
let site = test_site();
let size = site.bounds().size();
println!("{}", BeginSvg { w: size.w as f32, h: size.h as f32 });
println!("{}", BeginSvg {
w: size.w as f32,
h: size.h as f32
});
for plot in site.plots() {
let bounds = plot.find_bounds();
@ -15,7 +18,11 @@ fn main() {
w: bounds.size().w as f32,
h: bounds.size().h as f32,
style: Style {
fill: Fill::Color(Color { r: 50, g: 50, b: 50 }),
fill: Fill::Color(Color {
r: 50,
g: 50,
b: 50
}),
stroke: Stroke::Color(Color { r: 0, g: 0, b: 0 }, 1.0),
opacity: 1.0,
stroke_opacity: 1.0,

View File

@ -7,14 +7,17 @@ use crate::{
};
use common::{
assets::AssetHandle,
terrain::{Block, BlockKind, structure::{Structure, StructureBlock, StructuresGroup}},
terrain::{
structure::{Structure, StructureBlock, StructuresGroup},
Block, BlockKind,
},
vol::ReadVol,
};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use rand::prelude::*;
use std::{f32, ops::Range};
use vek::*;
use rand::prelude::*;
lazy_static! {
static ref OAKS: AssetHandle<StructuresGroup> = Structure::load_group("oaks");
@ -99,15 +102,25 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
ForestKind::Acacia => *ACACIAS,
ForestKind::Baobab => *BAOBABS,
// ForestKind::Oak => *OAKS,
ForestKind::Oak => break 'model TreeModel::Procedural(
ProceduralTree::generate(TreeConfig::OAK, seed),
StructureBlock::TemperateLeaves,
),
ForestKind::Oak => {
break 'model TreeModel::Procedural(
ProceduralTree::generate(
TreeConfig::oak(&mut RandomPerm::new(seed)),
&mut RandomPerm::new(seed),
),
StructureBlock::TemperateLeaves,
);
},
//ForestKind::Pine => *PINES,
ForestKind::Pine => break 'model TreeModel::Procedural(
ProceduralTree::generate(TreeConfig::PINE, seed),
StructureBlock::PineLeaves,
),
ForestKind::Pine => {
break 'model TreeModel::Procedural(
ProceduralTree::generate(
TreeConfig::pine(&mut RandomPerm::new(seed)),
&mut RandomPerm::new(seed),
),
StructureBlock::PineLeaves,
);
},
ForestKind::Birch => *BIRCHES,
ForestKind::Mangrove => *MANGROVE_TREES,
ForestKind::Swamp => *SWAMP_TREES,
@ -115,8 +128,11 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
};
let models = models.read();
TreeModel::Structure(models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize % models.len()]
.clone())
TreeModel::Structure(
models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize
% models.len()]
.clone(),
)
},
seed,
units: UNIT_CHOOSER.get(seed),
@ -133,9 +149,7 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
};
let rpos2d = (wpos2d - tree.pos.xy())
.map2(Vec2::new(tree.units.0, tree.units.1), |p, unit| {
unit * p
})
.map2(Vec2::new(tree.units.0, tree.units.1), |p, unit| unit * p)
.sum();
if !Aabr::from(bounds).contains_point(rpos2d) {
// Skip this column
@ -158,15 +172,17 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
info.index(),
if let Some(block) = match &tree.model {
TreeModel::Structure(s) => s.get(model_pos).ok().copied(),
TreeModel::Procedural(t, leaf_block) => Some(match t.is_branch_or_leaves_at(model_pos.map(|e| e as f32 + 0.5)) {
(true, _) => StructureBlock::Normal(Rgb::new(60, 30, 0)),
(_, true) => *leaf_block,
(_, _) => StructureBlock::None,
}),
TreeModel::Procedural(t, leaf_block) => Some(
match t.is_branch_or_leaves_at(model_pos.map(|e| e as f32 + 0.5)) {
(true, _) => StructureBlock::Normal(Rgb::new(60, 30, 0)),
(_, true) => *leaf_block,
(_, _) => StructureBlock::None,
},
),
} {
block
} else {
break
break;
},
wpos,
tree.pos.xy(),
@ -215,50 +231,62 @@ pub struct TreeConfig {
pub max_depth: usize,
/// The number of branches that form from each branch.
pub splits: usize,
/// The range of proportions along a branch at which a split into another branch might occur.
/// This value is clamped between 0 and 1, but a wider range may bias the results towards branch ends.
/// The range of proportions along a branch at which a split into another
/// branch might occur. This value is clamped between 0 and 1, but a
/// wider range may bias the results towards branch ends.
pub split_range: Range<f32>,
/// The bias applied to the length of branches based on the proportion along their parent that they eminate from.
/// -1.0 = negative bias (branches at ends are longer, branches at the start are shorter)
/// 0.0 = no bias (branches do not change their length with regard to parent branch proportion)
/// 1.0 = positive bias (branches at ends are shorter, branches at the start are longer)
/// The bias applied to the length of branches based on the proportion along
/// their parent that they eminate from. -1.0 = negative bias (branches
/// at ends are longer, branches at the start are shorter) 0.0 = no bias
/// (branches do not change their length with regard to parent branch
/// proportion) 1.0 = positive bias (branches at ends are shorter,
/// branches at the start are longer)
pub branch_len_bias: f32,
/// The scale of leaves in the vertical plane. Less than 1.0 implies a flattening of the leaves.
/// The scale of leaves in the vertical plane. Less than 1.0 implies a
/// flattening of the leaves.
pub leaf_vertical_scale: f32,
/// How evenly spaced (vs random) sub-branches are along their parent.
pub proportionality: f32,
}
impl TreeConfig {
pub const OAK: Self = Self {
trunk_len: 12.0,
trunk_radius: 3.0,
branch_child_len: 0.8,
branch_child_radius: 0.6,
leaf_radius: 3.0..5.0,
straightness: 0.5,
max_depth: 4,
splits: 3,
split_range: 0.5..1.5,
branch_len_bias: 0.0,
leaf_vertical_scale: 1.0,
proportionality: 0.0,
};
pub fn oak(rng: &mut impl Rng) -> Self {
let scale = Lerp::lerp(0.8, 1.5, rng.gen::<f32>().powi(4));
pub const PINE: Self = Self {
trunk_len: 32.0,
trunk_radius: 1.5,
branch_child_len: 0.3,
branch_child_radius: 0.0,
leaf_radius: 2.0..2.5,
straightness: 0.0,
max_depth: 1,
splits: 56,
split_range: 0.2..1.2,
branch_len_bias: 0.75,
leaf_vertical_scale: 0.3,
proportionality: 1.0,
};
Self {
trunk_len: 12.0 * scale,
trunk_radius: 3.0 * scale,
branch_child_len: 0.8,
branch_child_radius: 0.6,
leaf_radius: 3.0 * scale..4.0 * scale,
straightness: 0.5,
max_depth: 4,
splits: 3,
split_range: 0.5..1.5,
branch_len_bias: 0.0,
leaf_vertical_scale: 1.0,
proportionality: 0.0,
}
}
pub fn pine(rng: &mut impl Rng) -> Self {
let scale = Lerp::lerp(1.0, 2.0, rng.gen::<f32>().powi(4));
Self {
trunk_len: 32.0 * scale,
trunk_radius: 1.5 * scale,
branch_child_len: 0.3,
branch_child_radius: 0.0,
leaf_radius: 2.0 * scale..2.5 * scale,
straightness: 0.0,
max_depth: 1,
splits: 56,
split_range: 0.2..1.2,
branch_len_bias: 0.75,
leaf_vertical_scale: 0.3,
proportionality: 1.0,
}
}
}
// TODO: Rename this to `Tree` when the name conflict is gone
@ -269,9 +297,7 @@ pub struct ProceduralTree {
impl ProceduralTree {
/// Generate a new tree using the given configuration and seed.
pub fn generate(config: TreeConfig, seed: u32) -> Self {
let mut rng = RandomPerm::new(seed);
pub fn generate(config: TreeConfig, rng: &mut impl Rng) -> Self {
let mut this = Self {
branches: Vec::new(),
trunk_idx: 0, // Gets replaced later
@ -283,20 +309,21 @@ impl ProceduralTree {
// Our trunk starts at the origin...
Vec3::zero(),
// ...and has a roughly upward direction
Vec3::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0), 5.0).normalized(),
Vec3::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0), 10.0).normalized(),
config.trunk_len,
config.trunk_radius,
0,
None,
&mut rng,
rng,
);
this.trunk_idx = trunk_idx;
this
}
// Recursively add a branch (with sub-branches) to the tree's branch graph, returning the index and AABB of the
// branch. This AABB gets propagated down to the parent and is used later during sampling to cull the branches to
// Recursively add a branch (with sub-branches) to the tree's branch graph,
// returning the index and AABB of the branch. This AABB gets propagated
// down to the parent and is used later during sampling to cull the branches to
// be sampled.
fn add_branch(
&mut self,
@ -318,7 +345,8 @@ impl ProceduralTree {
0.0
};
// The AABB that covers this branch, along with wood and leaves that eminate from it
// The AABB that covers this branch, along with wood and leaves that eminate
// from it
let mut aabb = Aabb {
min: Vec3::partial_min(start, end) - wood_radius.max(leaf_radius),
max: Vec3::partial_max(start, end) + wood_radius.max(leaf_radius),
@ -327,31 +355,43 @@ impl ProceduralTree {
let mut child_idx = None;
// Don't add child branches if we're already enough layers into the tree
if depth < config.max_depth {
let x_axis = dir.cross(Vec3::<f32>::zero().map(|_| rng.gen_range(-1.0..1.0))).normalized();
let x_axis = dir
.cross(Vec3::<f32>::zero().map(|_| rng.gen_range(-1.0..1.0)))
.normalized();
let y_axis = dir.cross(x_axis).normalized();
let screw_shift = rng.gen_range(0.0..f32::consts::TAU);
for i in 0..config.splits {
let dist = Lerp::lerp(i as f32 / (config.splits - 1) as f32, rng.gen_range(0.0..1.0), config.proportionality);
let dist = Lerp::lerp(
i as f32 / (config.splits - 1) as f32,
rng.gen_range(0.0..1.0),
config.proportionality,
);
const PHI: f32 = 0.618;
const RAD_PER_BRANCH: f32 = f32::consts::TAU * PHI;
let screw =
(screw_shift + dist * config.splits as f32 * RAD_PER_BRANCH).sin() * x_axis +
(screw_shift + dist * config.splits as f32 * RAD_PER_BRANCH).cos() * y_axis;
let screw = (screw_shift + dist * config.splits as f32 * RAD_PER_BRANCH).sin()
* x_axis
+ (screw_shift + dist * config.splits as f32 * RAD_PER_BRANCH).cos() * y_axis;
// Choose a point close to the branch to act as the target direction for the branch to grow in
// let split_factor = rng.gen_range(config.split_range.start, config.split_range.end).clamped(0.0, 1.0);
let split_factor = Lerp::lerp(config.split_range.start, config.split_range.end, dist);
let tgt = Lerp::lerp_unclamped(
start,
end,
split_factor,
) + Lerp::lerp(Vec3::<f32>::zero().map(|_| rng.gen_range(-1.0..1.0)), screw, config.proportionality);
// Choose a point close to the branch to act as the target direction for the
// branch to grow in let split_factor =
// rng.gen_range(config.split_range.start, config.split_range.end).clamped(0.0,
// 1.0);
let split_factor =
Lerp::lerp(config.split_range.start, config.split_range.end, dist);
let tgt = Lerp::lerp_unclamped(start, end, split_factor)
+ Lerp::lerp(
Vec3::<f32>::zero().map(|_| rng.gen_range(-1.0..1.0)),
screw,
config.proportionality,
);
// Start the branch at the closest point to the target
let branch_start = line.projected_point(tgt);
// Now, interpolate between the target direction and the parent branch's direction to find a direction
let branch_dir = Lerp::lerp(tgt - branch_start, dir, config.straightness).normalized();
// Now, interpolate between the target direction and the parent branch's
// direction to find a direction
let branch_dir =
Lerp::lerp(tgt - branch_start, dir, config.straightness).normalized();
let (branch_idx, branch_aabb) = self.add_branch(
config,
@ -359,14 +399,18 @@ impl ProceduralTree {
branch_dir,
branch_len
* config.branch_child_len
* (1.0 - (split_factor - 0.5) * 2.0 * config.branch_len_bias.clamped(-1.0, 1.0)),
* (1.0
- (split_factor - 0.5)
* 2.0
* config.branch_len_bias.clamped(-1.0, 1.0)),
branch_radius * config.branch_child_radius,
depth + 1,
child_idx,
rng,
);
child_idx = Some(branch_idx);
// Parent branches AABBs include the AABBs of child branches to allow for culling during sampling
// Parent branches AABBs include the AABBs of child branches to allow for
// culling during sampling
aabb.expand_to_contain(branch_aabb);
}
}
@ -386,19 +430,20 @@ impl ProceduralTree {
}
/// Get the bounding box that covers the tree (all branches and leaves)
pub fn get_bounds(&self) -> Aabb<f32> {
self.branches[self.trunk_idx].aabb
}
pub fn get_bounds(&self) -> Aabb<f32> { self.branches[self.trunk_idx].aabb }
// Recursively search for branches or leaves by walking the tree's branch graph.
fn is_branch_or_leaves_at_inner(&self, pos: Vec3<f32>, branch_idx: usize) -> (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.sibling_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
.sibling_idx
.map(|idx| Vec2::from(self.is_branch_or_leaves_at_inner(pos, idx)))
.unwrap_or_default();
// Only continue probing this sub-graph of the tree if the sample position falls within its AABB
// Only continue probing this sub-graph of the tree if the sample position falls
// within its AABB
if branch.aabb.contains_point(pos) {
(branch_or_leaves
// Probe this branch
@ -407,23 +452,25 @@ impl ProceduralTree {
| branch.child_idx
.map(|idx| Vec2::from(self.is_branch_or_leaves_at_inner(pos, idx)))
.unwrap_or_default())
.into_tuple()
.into_tuple()
} else {
branch_or_leaves.into_tuple()
}
}
/// Determine whether there are either branches or leaves at the given position in the tree.
/// 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) {
self.is_branch_or_leaves_at_inner(pos, self.trunk_idx)
}
}
// Branches are arranged in a graph shape. Each branch points to both its first child (if any) and also to the next
// branch in the list of child branches 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.
// Branches are arranged in a graph shape. Each branch points to both its first
// child (if any) and also to the next branch in the list of child branches
// 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 {
line: LineSegment3<f32>,
wood_radius: f32,
@ -436,7 +483,8 @@ struct Branch {
}
impl Branch {
/// Determine whether there are either branches or leaves at the given position in the branch.
/// Determine whether there are either branches or leaves at the given
/// position in the branch.
pub fn is_branch_or_leaves_at(&self, pos: Vec3<f32>) -> (bool, bool) {
// fn finvsqrt(x: f32) -> f32 {
// let y = f32::from_bits(0x5f375a86 - (x.to_bits() >> 1));

View File

@ -516,7 +516,7 @@ impl WorldSim {
cave_0_nz: SuperSimplex::new().set_seed(rng.gen()),
cave_1_nz: SuperSimplex::new().set_seed(rng.gen()),
structure_gen: StructureGen2d::new(rng.gen(), 32, 16),
structure_gen: StructureGen2d::new(rng.gen(), 24, 8),
region_gen: StructureGen2d::new(rng.gen(), 400, 96),
humid_nz: Billow::new()
.set_octaves(9)

View File

@ -1,14 +1,14 @@
mod tile;
mod plot;
mod tile;
use vek::*;
use common::store::{Store, Id};
use crate::util::Grid;
use self::{
tile::TileGrid,
plot::{Plot, PlotKind},
tile::TileGrid,
};
use crate::util::Grid;
use common::store::{Id, Store};
use rand::prelude::*;
use vek::*;
#[derive(Default)]
pub struct Site {
@ -25,19 +25,17 @@ impl Site {
}
}
pub fn plots(&self) -> impl Iterator<Item=&Plot> + '_ {
self.plots.values()
}
pub fn plots(&self) -> impl Iterator<Item = &Plot> + '_ { self.plots.values() }
pub fn create_plot(&mut self, plot: Plot) -> Id<Plot> {
self.plots.insert(plot)
}
pub fn create_plot(&mut self, plot: Plot) -> Id<Plot> { self.plots.insert(plot) }
pub fn generate(rng: &mut impl Rng) -> Self {
let mut site = Site::default();
for i in 0..10 {
let dir = Vec2::<f32>::zero().map(|_| rng.gen_range(-1.0..1.0)).normalized();
let dir = Vec2::<f32>::zero()
.map(|_| rng.gen_range(-1.0..1.0))
.normalized();
let search_pos = (dir * 32.0).map(|e| e as i32);
site.tiles
@ -51,6 +49,4 @@ impl Site {
}
}
pub fn test_site() -> Site {
Site::generate(&mut thread_rng())
}
pub fn test_site() -> Site { Site::generate(&mut thread_rng()) }

View File

@ -1,5 +1,5 @@
use vek::*;
use crate::util::DHashSet;
use vek::*;
pub struct Plot {
kind: PlotKind,
@ -11,7 +11,9 @@ impl Plot {
pub fn find_bounds(&self) -> Aabr<i32> {
self.tiles
.iter()
.fold(Aabr::new_empty(self.root_tile), |b, t| b.expanded_to_contain_point(*t))
.fold(Aabr::new_empty(self.root_tile), |b, t| {
b.expanded_to_contain_point(*t)
})
}
}

View File

@ -23,18 +23,22 @@ impl TileGrid {
let tpos = tpos + TILE_RADIUS as i32;
self.zones
.get(tpos)
.and_then(|zone| zone.as_ref()?.get(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32))))
.and_then(|zone| {
zone.as_ref()?
.get(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
})
.and_then(|tile| tile.as_ref())
}
pub fn get_mut(&mut self, tpos: Vec2<i32>) -> Option<&mut Tile> {
let tpos = tpos + TILE_RADIUS as i32;
self.zones
.get_mut(tpos)
.and_then(|zone| zone
.get_or_insert_with(|| Grid::populate_from(Vec2::broadcast(ZONE_RADIUS as i32 * 2 + 1), |_| None))
.get_mut(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
.map(|tile| tile.get_or_insert_with(|| Tile::empty())))
self.zones.get_mut(tpos).and_then(|zone| {
zone.get_or_insert_with(|| {
Grid::populate_from(Vec2::broadcast(ZONE_RADIUS as i32 * 2 + 1), |_| None)
})
.get_mut(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
.map(|tile| tile.get_or_insert_with(|| Tile::empty()))
})
}
pub fn find_near(&self, tpos: Vec2<i32>, f: impl Fn(&Tile) -> bool) -> Option<Vec2<i32>> {
@ -62,7 +66,5 @@ impl Tile {
}
}
pub fn is_empty(&self) -> bool {
self.kind == TileKind::Empty
}
pub fn is_empty(&self) -> bool { self.kind == TileKind::Empty }
}