Skip to content
This repository has been archived by the owner on Jun 19, 2021. It is now read-only.

Temp. plant renderer and treegen fixes/improvements #52

Merged
merged 6 commits into from
Jul 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
31 changes: 31 additions & 0 deletions base/src/gen/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
//! 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;
Copy link
Member

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, like let seed1: u32 = rng_seed.truncate();. Ok but I guess this can't be improved right now 😞


XorShiftRng::from_seed([seed0, seed1, seed0, seed1])
}
9 changes: 9 additions & 0 deletions base/src/gen/plant/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod tree;

use self::tree::TreeGen;
use prop::Plant;

use rand::{Rand, Rng};

Expand All @@ -12,6 +13,14 @@ pub enum PlantGenerator {
Tree(TreeGen),
}

impl PlantGenerator {
pub fn generate<R: Rng>(self, rng: &mut R) -> Plant {
match self {
PlantGenerator::Tree(treegen) => Plant::Tree { branches: treegen.generate(rng) },
}
}
}

impl Rand for PlantGenerator {
fn rand<R: Rng>(rng: &mut R) -> Self {
PlantGenerator::Tree(TreeGen::rand(rng))
Expand Down
51 changes: 31 additions & 20 deletions base/src/gen/plant/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand All @@ -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;

debug!("trunk diam {} to {}, height {}, branch start at {}",
trunk_diameter,
trunk_diameter_top,
trunk_height,
min_branch_height);

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);
Expand All @@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Expand All @@ -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());
Expand All @@ -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
Expand All @@ -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 {
Expand Down
28 changes: 24 additions & 4 deletions base/src/gen/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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)),
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe if-else to avoid mutable vector?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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));
}
}

Expand Down
11 changes: 1 addition & 10 deletions base/src/prop/plant.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
use math::{Point3f, Vector3f};

/// Represents a plant in the game world.
///
/// Currently, the fields are temporary and just for testing
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Plant {
pub height: f32,
pub stem_width: f32,
}

/// Contains all types of plants we can generate.
#[derive(Clone, Debug)]
pub enum NewPlant {
pub enum Plant {
/// A parameterized tree-like structure.
Tree {
/// The list of branches representing this tree.
Expand Down
9 changes: 0 additions & 9 deletions base/src/world/chunk.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::{CHUNK_SIZE, HexPillar, PillarIndexComponent};
use std::ops;
use std::iter;
use math::*;

/// Represents one part of the game world.
Expand All @@ -20,14 +19,6 @@ pub struct Chunk {
}

impl Chunk {
/// Creates a dummy chunk for early testing. FIXME: remove
pub fn dummy() -> Self {
let pillars = iter::repeat(HexPillar::dummy())
.take(CHUNK_SIZE.pow(2) as usize)
.collect();
Chunk { pillars: pillars }
}

/// Creates a chunk from a `Vec<HexPillar>`
pub fn from_pillars(pillars: Vec<HexPillar>) -> Self {
assert_eq!(pillars.len() as usize, CHUNK_SIZE.pow(2) as usize);
Expand Down
27 changes: 3 additions & 24 deletions base/src/world/hex_pillar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,10 @@ pub struct HexPillar {
}

impl HexPillar {
/// Creates a dummy pillar for early testing. FIXME: remove
pub fn dummy() -> Self {
pub fn new(sections: Vec<PillarSection>, props: Vec<Prop>) -> Self {
HexPillar {
sections: vec![PillarSection::new(GroundMaterial::Dirt, HeightType(0), HeightType(50))],
props: vec![Prop {
baseline: HeightType(50),
prop: PropType::Plant(prop::Plant {
height: 5.0,
stem_width: 0.5,
}),
}],
}
}

/// Creates the dummy pillar but with the given height.
pub fn from_height(height: HeightType) -> Self {
HexPillar {
sections: vec![PillarSection::new(GroundMaterial::Dirt, HeightType(0), height)],
props: vec![Prop {
baseline: HeightType(50),
prop: PropType::Plant(prop::Plant {
height: 5.0,
stem_width: 0.5,
}),
}],
sections: sections,
props: props,
}
}

Expand Down
8 changes: 0 additions & 8 deletions base/src/world/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ impl World {
World { chunks: HashMap::new() }
}

/// Returns a dummy world with one dummy chunk for early testing.
/// FIXME: remove
pub fn dummy() -> Self {
let mut chunks = HashMap::new();
chunks.insert(ChunkIndex(AxialPoint::new(0, 0)), Chunk::dummy());
World { chunks: chunks }
}

pub fn replace_chunk(&mut self, index: ChunkIndex, provider: &ChunkProvider) {
match provider.load_chunk(index) {
Some(x) => {
Expand Down
16 changes: 16 additions & 0 deletions client/src/render/to_arr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,19 @@ impl<T: BaseFloat> ToArr for Vector2<T> {
(*self).into()
}
}

impl<T: BaseNum> ToArr for Point2<T> {
type Output = [T; 2];

fn to_arr(&self) -> Self::Output {
(*self).into()
}
}

impl<T: BaseNum> ToArr for Point3<T> {
type Output = [T; 3];

fn to_arr(&self) -> Self::Output {
(*self).into()
}
}