-
Notifications
You must be signed in to change notification settings - Fork 37
Temp. plant renderer and treegen fixes/improvements #52
Changes from 5 commits
ae0f449
c21a350
7a334ff
ad52fed
5e91259
becdf95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,4 @@ cgmath = "0.10.0" | |
log = "0.3.6" | ||
num-traits = "0.1.33" | ||
rand = "0.3.14" | ||
fnv = "1.0.3" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,44 @@ | ||
//! Functionality about procedurally generating content. | ||
//! | ||
|
||
extern crate fnv; | ||
|
||
mod plant; | ||
mod world; | ||
|
||
pub use self::world::WorldGenerator; | ||
pub use self::plant::PlantGenerator; | ||
|
||
use rand::{SeedableRng, XorShiftRng}; | ||
use self::fnv::FnvHasher; | ||
use std::hash::{Hash, Hasher}; | ||
|
||
type Random = XorShiftRng; | ||
|
||
/// Creates a seeded RNG for use in world gen. | ||
/// | ||
/// This function takes 3 seed parameters which are hashed and mixed together. | ||
/// | ||
/// # Parameters | ||
/// | ||
/// * `world_seed`: The constant world seed as set in the config | ||
/// * `feat_seed`: Feature-specific constant seed | ||
/// * `loc_seed`: Location-seed, for example, X/Y coordinates of a feature | ||
fn seeded_rng<T: Hash, U: Hash>(world_seed: u64, feat_seed: T, loc_seed: U) -> Random { | ||
// Hash everything, even `world_seed`, since XorShift really doesn't like seeds | ||
// with many 0s in it | ||
let mut fnv = FnvHasher::default(); | ||
world_seed.hash(&mut fnv); | ||
feat_seed.hash(&mut fnv); | ||
loc_seed.hash(&mut fnv); | ||
let rng_seed = fnv.finish(); | ||
let seed0 = (rng_seed >> 32) as u32; | ||
let seed1 = rng_seed as u32; | ||
// Apply our patented cryptoperturbation mask to magically extend the 64-bit | ||
// hash to 128 bits used by xorshift: | ||
// (To be honest, I just didn't want to pass the same 2 word twice) | ||
let seed2 = seed0 ^ 0xdeadbeef; | ||
let seed3 = seed1 ^ 0xcafebabe; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this. This obviously effects the world we are generating for a given seed, so changing this hacky solution is not backwards compatible. Furthermore we might want to use another RNG in the future, which is seeded by a different number of bytes. Masking our hashed seed like that probably doesn't increase the quality of our random numbers at all 😛 So I guess we should just repeat our hashed seed as many times as necessary to completely seed the RNG (without masking). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
...which would be incompatible, too :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh... right ^_^ I created #66 to discuss that someday in the future. |
||
|
||
XorShiftRng::from_seed([seed0, seed1, seed2, seed3]) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ use std::ops::Range; | |
/// Parameters for the tree generator. | ||
#[derive(Debug)] | ||
struct Preset { | ||
name: &'static str, | ||
/// Diameter of the first branch we create (the trunk). | ||
trunk_diameter: Range<f32>, | ||
/// Trunk height. Note that branches going upward can increase plant height | ||
|
@@ -43,6 +44,7 @@ struct Preset { | |
} | ||
|
||
static PRESETS: &'static [Preset] = &[Preset { | ||
name: "'Regular' Tree", | ||
trunk_diameter: 0.3..0.5, | ||
trunk_height: 3.0..6.0, | ||
trunk_diameter_top: 0.2..0.4, | ||
|
@@ -147,16 +149,22 @@ impl TreeGen { | |
points: points, | ||
// FIXME Fixed color for now, we should use a configurable random color (or at least | ||
// make it brown). | ||
color: Vector3f::new(0.0, 0.0, 1.0), | ||
color: Vector3f::new(0.0, 1.0, 0.0), | ||
}); | ||
} | ||
|
||
/// Given the growing direction of the parent branch, calculates a growing | ||
/// direction to use for a new child branch. | ||
fn gen_branch_direction<R: Rng>(&self, rng: &mut R, parent_dir: Vector3f) -> Vector3f { | ||
let x_angle = range_sample(&self.preset.branch_angle_deg, rng); | ||
let y_angle = range_sample(&self.preset.branch_angle_deg, rng); | ||
// `branch_angle_deg` specifies the angle range in degrees | ||
let mut x_angle = range_sample(&self.preset.branch_angle_deg, rng); | ||
let mut y_angle = range_sample(&self.preset.branch_angle_deg, rng); | ||
|
||
// Invert sign with 50%, to mirror the specified range to the other side | ||
x_angle = if rng.gen() { -x_angle } else { x_angle }; | ||
y_angle = if rng.gen() { -y_angle } else { y_angle }; | ||
|
||
// Rotate the growing direction of the parent branch | ||
let rotation = Basis3::from(Euler { | ||
x: Deg::new(x_angle), | ||
y: Deg::new(y_angle), | ||
|
@@ -169,17 +177,23 @@ impl TreeGen { | |
let trunk_diameter = range_sample(&self.preset.trunk_diameter, rng); | ||
let trunk_height = range_sample(&self.preset.trunk_height, rng); | ||
let trunk_diameter_top = range_sample(&self.preset.trunk_diameter_top, rng); | ||
let min_branch_height = range_sample(&self.preset.min_branch_height, rng); | ||
let min_branch_height = range_sample(&self.preset.min_branch_height, rng) * trunk_height; | ||
|
||
info!("trunk diam {} to {}, height {}, branch start at {}", | ||
trunk_diameter, | ||
trunk_diameter_top, | ||
trunk_height, | ||
min_branch_height); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this rather be |
||
|
||
let mut points = Vec::new(); | ||
|
||
{ | ||
let mut add_point = |height, diam| { | ||
let point = Point3f::new(0.0, height, 0.0); | ||
let point = Point3f::new(0.0, 0.0, height); | ||
if height >= min_branch_height { | ||
// FIXME Make branch spawn chance configurable | ||
// 1/5 chance to spawn a branch at any point | ||
if rng.gen_weighted_bool(5) { | ||
// 1/3 chance to spawn a branch at any point | ||
if rng.gen_weighted_bool(3) { | ||
// Build a vector for the branch direction (Z is up) | ||
let dir = self.gen_branch_direction(rng, Vector3f::new(0.0, 0.0, 1.0)); | ||
self.create_branch(rng, point, dir, 1, diam); | ||
|
@@ -194,19 +208,18 @@ impl TreeGen { | |
|
||
let diam_start = Vector1::new(trunk_diameter); | ||
let diam_end = Vector1::new(trunk_diameter_top); | ||
let mut height = 0.0; | ||
while height < trunk_height { | ||
// Current height as a fraction of the total height | ||
let height_frac = if height == 0.0 { 0.0 } else { trunk_height / height }; | ||
|
||
// Split trunk in segments | ||
// FIXME Vary the segment direction like we do for normal branches | ||
// FIXME Make segment count depend on the trunk height | ||
const SEGMENT_COUNT: u32 = 10; | ||
for i in 0..SEGMENT_COUNT + 1 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C'mon rust-lang/rust#28237 ____ |
||
let height = i as f32 * trunk_height / SEGMENT_COUNT as f32; | ||
let height_frac = height / trunk_height; | ||
let diam = diam_start.lerp(diam_end, height_frac); | ||
|
||
add_point(height, diam.x); | ||
|
||
let segment_len = segment_dist(diam.x); | ||
height += segment_len; | ||
} | ||
|
||
// FIXME Do we need to create another point here? | ||
} | ||
|
||
assert!(points.len() >= 2, | ||
|
@@ -215,7 +228,7 @@ impl TreeGen { | |
points: points, | ||
// FIXME Fixed color for now, we should use a configurable random color (or at least | ||
// make it brown). | ||
color: Vector3f::new(0.0, 0.0, 1.0), | ||
color: Vector3f::new(0.0, 1.0, 0.0), | ||
}); | ||
|
||
debug!("generated tree with {} branches", self.branches.len()); | ||
|
@@ -225,7 +238,6 @@ impl TreeGen { | |
/// | ||
/// The tree is returned as a list of branches for now. | ||
pub fn generate<R: Rng>(mut self, rng: &mut R) -> Vec<Branch> { | ||
info!("treegen activated!"); // deleteme | ||
// Recursively create the tree and put all branches in a buffer. | ||
self.create_trunk(rng); | ||
self.branches | ||
|
@@ -234,8 +246,7 @@ impl TreeGen { | |
|
||
impl Rand for TreeGen { | ||
fn rand<R: Rng>(rng: &mut R) -> Self { | ||
// Create a tree generator with random parameters. | ||
// First, select a random preset: | ||
// Select a random preset that we'll use | ||
let preset = rng.choose(PRESETS).unwrap().clone(); | ||
|
||
TreeGen { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,9 @@ | |
//! | ||
|
||
use world::{CHUNK_SIZE, Chunk, ChunkIndex, ChunkProvider, HeightType, HexPillar}; | ||
use world::{GroundMaterial, PillarSection, Prop, PropType}; | ||
use rand::Rand; | ||
use gen::PlantGenerator; | ||
|
||
/// Main type to generate the game world. Implements the `ChunkProvider` trait | ||
/// (TODO, see #8). | ||
|
@@ -26,12 +29,29 @@ impl ChunkProvider for WorldGenerator { | |
let mut pillars = Vec::new(); | ||
let q = index.0.q * CHUNK_SIZE as i32; | ||
let r = index.0.r * CHUNK_SIZE as i32; | ||
let mut height; | ||
|
||
for i in q..q + CHUNK_SIZE as i32 { | ||
for j in r..r + CHUNK_SIZE as i32 { | ||
height = (((i as f32) * 0.25).sin() * 10.0 + | ||
((j as f32) * 0.25).sin() * 10.0 + 100.0) as u16; | ||
pillars.push(HexPillar::from_height(HeightType(height))); | ||
let height = (((i as f32) * 0.25).sin() * 10.0 + ((j as f32) * 0.25).sin() * 10.0 + | ||
100.0) as u16; | ||
|
||
let ground_section = | ||
PillarSection::new(GroundMaterial::Dirt, HeightType(0), HeightType(height)); | ||
let mut props = Vec::new(); | ||
|
||
// Place a test plant every few blocks | ||
const TREE_SPACING: i32 = 8; | ||
if i % TREE_SPACING == 0 && j % TREE_SPACING == 0 { | ||
let mut rng = super::seeded_rng(self.seed, "TREE", (i, j)); | ||
let gen = PlantGenerator::rand(&mut rng); | ||
|
||
props.push(Prop { | ||
baseline: HeightType(height), | ||
prop: PropType::Plant(gen.generate(&mut rng)), | ||
}); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe if-else to avoid mutable vector? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My intention was that other world features could be added after trees. But we'll probably use a completely different and more flexible system then. |
||
|
||
pillars.push(HexPillar::new(vec![ground_section], props)); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hate this
as
cast more every day. It would be way nicer to express your intend clearly in the code, likelet seed1: u32 = rng_seed.truncate();
. Ok but I guess this can't be improved right now 😞