Skip to content

Commit bacfeb7

Browse files
committed
Initial implementation: tree finisher
1 parent 8ec312c commit bacfeb7

5 files changed

Lines changed: 250 additions & 11 deletions

File tree

server/src/worldgen/finishers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ mod tree;
88
pub use clumped::ClumpedFoliageFinisher;
99
pub use single::SingleFoliageFinisher;
1010
pub use snow::SnowFinisher;
11+
pub use tree::TreeFinisher;

server/src/worldgen/finishers/tree/mod.rs

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@
33
mod schematic;
44
mod ty;
55

6+
use crate::worldgen::presence::PresenceGrid;
7+
use crate::worldgen::util::shuffle_seed_for_column;
68
use crate::worldgen::{FinishingGenerator, NearbyBiomes, TopBlocks};
7-
use feather_core::Chunk;
9+
use feather_core::{Biome, Chunk, ChunkPosition};
10+
use rand::distributions::{Distribution, WeightedIndex};
11+
use rand::{Rng, SeedableRng};
12+
use rand_xorshift::XorShiftRng;
13+
use std::cmp;
14+
use std::convert::TryFrom;
815
pub use ty::TreeType;
916

1017
/// Finisher for generating trees, depending on biome.
@@ -19,6 +26,143 @@ impl FinishingGenerator for TreeFinisher {
1926
top_blocks: &TopBlocks,
2027
seed: u64,
2128
) {
22-
unimplemented!()
29+
// Presence grid for trees.
30+
// We set values to `true` for
31+
// any column in which a tree will be generated.
32+
let mut tree_presences = PresenceGrid::new();
33+
34+
// Compute presence of trees for each column within this chunk
35+
// and the 6 columns bordering it on each side. For columns with
36+
// trees generated, do the following:
37+
// * First, check if a tree is already generated within params.spread.
38+
// If so, remove the tree.
39+
// * Generate a tree schematic for the tree and apply it to the chunk.
40+
for x in -6..16 + 6 {
41+
for z in -6..16 + 6 {
42+
let biome = biomes.biome_at(x, z);
43+
let params = params_for_biome(biome);
44+
45+
if compute_presence_for_column(chunk.position(), &params, x, z, seed) {
46+
if tree_presences.is_presence_within(x, z, params.spread) {
47+
continue;
48+
}
49+
50+
tree_presences.set_presence_at(x, z, true);
51+
52+
let column_seed = column_seed(seed, chunk.position(), x, z);
53+
let mut rng = XorShiftRng::seed_from_u64(column_seed);
54+
55+
// Choose tree type based on weights.
56+
let possible_trees = params.possible_trees;
57+
let index = possible_trees.weights.sample(&mut rng);
58+
let tree = possible_trees.trees[index];
59+
60+
// Generate tree schematic and write it to the chunk.
61+
let schematic = schematic::generate_tree(tree, seed);
62+
let y = top_blocks.top_block_at(
63+
cmp::min(15, usize::try_from(x).unwrap_or(0)),
64+
cmp::min(15, usize::try_from(z).unwrap_or(0)),
65+
); // TODO
66+
schematic.write_to_chunk(
67+
chunk,
68+
(chunk.position().x * 16) as isize + x,
69+
y as isize,
70+
(chunk.position().z * 16) as isize + z,
71+
);
72+
}
73+
}
74+
}
75+
}
76+
}
77+
78+
fn compute_presence_for_column(
79+
center_chunk: ChunkPosition,
80+
params: &TreeParams,
81+
column_x: isize,
82+
column_z: isize,
83+
seed: u64,
84+
) -> bool {
85+
let seed = column_seed(seed, center_chunk, column_x, column_z);
86+
let mut rng = XorShiftRng::seed_from_u64(seed);
87+
88+
rng.gen_bool(params.frequency / 256.0)
89+
}
90+
91+
fn column_seed(seed: u64, center_chunk: ChunkPosition, column_x: isize, column_z: isize) -> u64 {
92+
let chunk = ChunkPosition::new(
93+
center_chunk.x + column_x as i32 / 16,
94+
center_chunk.z + column_z as i32 / 16,
95+
);
96+
97+
let mut local_x = column_x % 16;
98+
let mut local_z = column_z % 16;
99+
if column_x < 0 {
100+
local_x = 16 - local_x;
101+
}
102+
if column_z < 0 {
103+
local_z = 16 - column_z;
104+
}
105+
106+
shuffle_seed_for_column(seed, chunk, local_x as usize, local_z as usize)
107+
}
108+
109+
/// WeightedIndex for a biome's tree weights, with
110+
/// the array of possible tree types corresponding
111+
/// to samples from the weighted index.
112+
#[derive(Debug)]
113+
struct TreeWeights {
114+
weights: WeightedIndex<f64>,
115+
trees: &'static [TreeType],
116+
}
117+
118+
impl TreeWeights {
119+
fn new(weights: &[f64], trees: &'static [TreeType]) -> Self {
120+
Self {
121+
weights: WeightedIndex::new(weights).unwrap(),
122+
trees,
123+
}
124+
}
125+
}
126+
127+
/// Settings of a biome for tree generation.
128+
#[derive(Debug)]
129+
struct TreeParams {
130+
/// Frequency of trees. Higher values mean more trees.
131+
frequency: f64,
132+
/// Minimum distance between two trees.
133+
spread: u32,
134+
/// Table of tree types possible for this biome
135+
/// and their corresponding weights. The table
136+
/// for each tree is in a `lazy_static` variable,
137+
/// since initializing it requires an expensive
138+
/// allocation.
139+
possible_trees: &'static TreeWeights,
140+
}
141+
142+
lazy_static! {
143+
static ref WEIGHTS_DEFAULT: TreeWeights = TreeWeights::new(&[1.0], &[TreeType::Oak]);
144+
static ref WEIGHTS_FOREST: TreeWeights = TreeWeights::new(&[1.0], &[TreeType::Oak]);
145+
}
146+
147+
impl Default for TreeParams {
148+
/// Tree parameters which generate zero trees.
149+
fn default() -> Self {
150+
Self {
151+
frequency: 0.0,
152+
spread: 0,
153+
possible_trees: &WEIGHTS_DEFAULT,
154+
}
155+
}
156+
}
157+
158+
/// Returns the tree parameters for the given biome.
159+
fn params_for_biome(biome: Biome) -> TreeParams {
160+
match biome {
161+
Biome::Forest | Biome::DarkForest => TreeParams {
162+
frequency: 4.0,
163+
spread: 4,
164+
possible_trees: &WEIGHTS_FOREST,
165+
},
166+
_ => TreeParams::default(), // TODO
23167
}
24168
}

server/src/worldgen/finishers/tree/schematic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rand::{Rng, SeedableRng};
66
use rand_xorshift::XorShiftRng;
77

88
/// Generates a tree with the given type and seed.
9-
pub fn generate_tree<R: Rng>(ty: TreeType, seed: u64) -> Schematic {
9+
pub fn generate_tree(ty: TreeType, seed: u64) -> Schematic {
1010
let mut rng = XorShiftRng::seed_from_u64(seed);
1111
match ty {
1212
TreeType::Oak => generate_oak(&mut rng),

server/src/worldgen/mod.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ mod biomes;
99
mod composition;
1010
mod density_map;
1111
mod finishers;
12+
13+
mod superflat;
14+
1215
pub mod noise;
16+
pub mod presence;
1317
pub mod schematic;
14-
mod superflat;
1518
mod util;
1619
pub mod voronoi;
1720

18-
use crate::worldgen::finishers::{ClumpedFoliageFinisher, SingleFoliageFinisher, SnowFinisher};
21+
use crate::worldgen::finishers::{
22+
ClumpedFoliageFinisher, SingleFoliageFinisher, SnowFinisher, TreeFinisher,
23+
};
1924
pub use biomes::{DistortedVoronoiBiomeGenerator, TwoLevelBiomeGenerator};
2025
use bitvec::slice::BitSlice;
2126
use bitvec::vec::BitVec;
@@ -107,6 +112,7 @@ impl ComposableGenerator {
107112
Box::new(SnowFinisher::default()),
108113
Box::new(SingleFoliageFinisher::default()),
109114
Box::new(ClumpedFoliageFinisher::default()),
115+
Box::new(TreeFinisher::default()),
110116
];
111117
Self::new(
112118
TwoLevelBiomeGenerator::default(),
@@ -171,12 +177,7 @@ impl WorldGenerator for ComposableGenerator {
171177

172178
// Finishers.
173179
for finisher in &self.finishers {
174-
finisher.generate_for_chunk(
175-
&mut chunk,
176-
&biomes.biomes[4],
177-
&top_blocks,
178-
seed_shuffler.gen(),
179-
);
180+
finisher.generate_for_chunk(&mut chunk, &biomes, &top_blocks, seed_shuffler.gen());
180181
}
181182

182183
// TODO: correct lighting.

server/src/worldgen/presence.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use bitvec::boxed::BitBox;
2+
3+
/// A presence grid for a 3x3 area of chunks.
4+
///
5+
/// "Presence" simply is used to describe whether
6+
/// something exists at the given column; it is a boolean.
7+
/// For example, the tree generator uses this to
8+
/// indicate whether there is a tree stump at a given
9+
/// column.
10+
///
11+
/// Column positions are the same as `NearbyBiomes`.
12+
#[derive(Debug, Clone)]
13+
pub struct PresenceGrid {
14+
/// Array of booleans with values set to `true` for
15+
/// each coordinate presence has been set.
16+
grid: BitBox,
17+
}
18+
19+
impl PresenceGrid {
20+
/// Creates a new presence grid, with all values
21+
/// initialized to `false`.
22+
pub fn new() -> Self {
23+
Self {
24+
grid: bitbox!(0; 16 * 256 * 16 * 9),
25+
}
26+
}
27+
28+
/// Retrieves whether there is presence in the given column,
29+
/// relative to the center chunk.
30+
pub fn is_presence_at(&self, x: isize, z: isize) -> bool {
31+
let index = index(x, z);
32+
33+
self.grid[index]
34+
}
35+
36+
/// Sets the presence value in the given column,
37+
/// relative to the center chunk.
38+
pub fn set_presence_at(&mut self, x: isize, z: isize, value: bool) {
39+
let index = index(x, z);
40+
41+
self.grid.set(index, value);
42+
}
43+
44+
/// Returns whether there is a presence value set to
45+
/// `true` within the given distance. The distance function
46+
/// is non-standard; it computes a square of length `distance * 2`
47+
/// and checks for each column within this square.
48+
pub fn is_presence_within(&self, x: isize, z: isize, distance: u32) -> bool {
49+
let distance = distance as isize;
50+
for offset_x in -distance..=distance {
51+
for offset_z in -distance..=distance {
52+
if offset_x == 0 && offset_z == 0 {
53+
// Don't test the column itself.
54+
continue;
55+
}
56+
57+
if self.is_presence_at(x + offset_x, z + offset_z) {
58+
return true;
59+
}
60+
}
61+
}
62+
63+
false
64+
}
65+
}
66+
67+
fn index(mut x: isize, mut z: isize) -> usize {
68+
// Account for negatives
69+
x += 16;
70+
z += 16;
71+
72+
(z * 16 * 3 + x) as usize
73+
}
74+
75+
#[cfg(test)]
76+
mod tests {
77+
use super::*;
78+
79+
#[test]
80+
fn test_presence_grid() {
81+
let mut grid = PresenceGrid::new();
82+
83+
assert!(!grid.is_presence_at(0, 0));
84+
grid.set_presence_at(0, 0, true);
85+
assert!(grid.is_presence_at(0, 0));
86+
87+
grid.set_presence_at(-16, -16, true);
88+
assert!(grid.is_presence_at(-16, -16));
89+
90+
assert!(grid.is_presence_within(-10, -10, 6));
91+
assert!(!grid.is_presence_within(5, -5, 2));
92+
}
93+
}

0 commit comments

Comments
 (0)