Added test terrain loading and meshing

This commit is contained in:
Joshua Barretto 2019-01-23 20:01:58 +00:00
parent 41b6672743
commit 248577bdef
16 changed files with 508 additions and 60 deletions

View File

@ -10,3 +10,4 @@ world = { package = "veloren-world", path = "../world" }
specs = "0.14"
vek = "0.9"
threadpool = "1.7"

View File

@ -4,6 +4,7 @@ use std::time::Duration;
// Library
use specs::Entity as EcsEntity;
use vek::*;
use threadpool;
// Project
use common::{
@ -23,8 +24,10 @@ pub struct Input {
}
pub struct Client {
state: State,
thread_pool: threadpool::ThreadPool,
tick: u64,
state: State,
player: Option<EcsEntity>,
// Testing
@ -36,8 +39,12 @@ impl Client {
/// Create a new `Client`.
pub fn new() -> Self {
Self {
state: State::new(),
thread_pool: threadpool::Builder::new()
.thread_name("veloren-worker".into())
.build(),
tick: 0,
state: State::new(),
player: None,
// Testing
@ -46,18 +53,34 @@ impl Client {
}
}
/// TODO: Get rid of this
/// Get a reference to the client's worker thread pool. This pool should be used for any
/// computationally expensive operations that run outside of the main thread (i.e: threads that
/// block on I/O operations are exempt).
pub fn thread_pool(&self) -> &threadpool::ThreadPool { &self.thread_pool }
// TODO: Get rid of this
pub fn with_test_state(mut self) -> Self {
self.chunk = Some(self.world.generate_chunk(Vec3::zero()));
self
}
// TODO: Get rid of this
pub fn load_chunk(&mut self, pos: Vec3<i32>) {
self.state.terrain_mut().insert(pos, self.world.generate_chunk(pos));
self.state.changes_mut().new_chunks.push(pos);
}
/// Get a reference to the client's game state.
pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the client's game state.
pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Get the current tick number.
pub fn get_tick(&self) -> u64 {
self.tick
}
/// Execute a single client tick, handle input and update the game state by the given duration
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are
@ -76,6 +99,13 @@ impl Client {
self.state.tick(dt);
// Finish the tick, pass control back to the frontend (step 6)
self.tick += 1;
Ok(())
}
/// Clean up the client after a tick
pub fn cleanup(&mut self) {
// Cleanup the local state
self.state.cleanup();
}
}

View File

@ -1,9 +1,10 @@
// Standard
use std::time::Duration;
// External
// Library
use specs::World as EcsWorld;
use shred::Fetch;
use shred::{Fetch, FetchMut};
use vek::*;
// Crate
use crate::{
@ -19,12 +20,35 @@ const DAY_CYCLE_FACTOR: f64 = 24.0 * 60.0;
struct TimeOfDay(f64);
/// A resource to store the tick (i.e: physics) time
struct Tick(f64);
struct Time(f64);
pub struct Changes {
pub new_chunks: Vec<Vec3<i32>>,
pub changed_chunks: Vec<Vec3<i32>>,
pub removed_chunks: Vec<Vec3<i32>>,
}
impl Changes {
pub fn default() -> Self {
Self {
new_chunks: vec![],
changed_chunks: vec![],
removed_chunks: vec![],
}
}
pub fn cleanup(&mut self) {
self.new_chunks.clear();
self.changed_chunks.clear();
self.removed_chunks.clear();
}
}
/// A type used to represent game state stored on both the client and the server. This includes
/// things like entity components, terrain data, and global state like weather, time of day, etc.
pub struct State {
ecs_world: EcsWorld,
changes: Changes,
}
impl State {
@ -34,7 +58,7 @@ impl State {
// Register resources used by the ECS
ecs_world.add_resource(TimeOfDay(0.0));
ecs_world.add_resource(Tick(0.0));
ecs_world.add_resource(Time(0.0));
ecs_world.add_resource(TerrainMap::new());
// Register common components with the state
@ -42,9 +66,20 @@ impl State {
Self {
ecs_world,
changes: Changes::default(),
}
}
/// Get a reference to the internal ECS world
pub fn ecs_world(&self) -> &EcsWorld { &self.ecs_world }
/// Get a reference to the `Changes` structure of the state. This contains
/// information about state that has changed since the last game tick.
pub fn changes(&self) -> &Changes { &self.changes }
// TODO: Get rid of this since it shouldn't be needed
pub fn changes_mut(&mut self) -> &mut Changes { &mut self.changes }
/// Get the current in-game time of day.
///
/// Note that this should not be used for physics, animations or other such localised timings.
@ -52,11 +87,11 @@ impl State {
self.ecs_world.read_resource::<TimeOfDay>().0
}
/// Get the current in-game tick time.
/// Get the current in-game time.
///
/// Note that this does not correspond to the time of day.
pub fn get_tick(&self) -> f64 {
self.ecs_world.read_resource::<Tick>().0
pub fn get_time(&self) -> f64 {
self.ecs_world.read_resource::<Time>().0
}
/// Get a reference to this state's terrain.
@ -64,10 +99,21 @@ impl State {
self.ecs_world.read_resource::<TerrainMap>()
}
// TODO: Get rid of this since it shouldn't be needed
pub fn terrain_mut<'a>(&'a mut self) -> FetchMut<'a, TerrainMap> {
self.ecs_world.write_resource::<TerrainMap>()
}
/// Execute a single tick, simulating the game state by the given duration.
pub fn tick(&mut self, dt: Duration) {
// Change the time accordingly
self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_float_secs() * DAY_CYCLE_FACTOR;
self.ecs_world.write_resource::<Tick>().0 += dt.as_float_secs();
self.ecs_world.write_resource::<Time>().0 += dt.as_float_secs();
}
/// Clean up the state after a tick
pub fn cleanup(&mut self) {
// Clean up data structures from the last tick
self.changes.cleanup();
}
}

View File

@ -1,3 +1,6 @@
// Library
use vek::*;
// Crate
use crate::vol::Vox;
@ -7,6 +10,23 @@ pub struct Block {
color: [u8; 3],
}
impl Block {
pub fn new(kind: u8, color: Rgb<u8>) -> Self {
Self {
kind,
color: color.into_array(),
}
}
pub fn get_color(&self) -> Option<Rgb<u8>> {
if self.is_empty() {
None
} else {
Some(self.color.into())
}
}
}
impl Vox for Block {
fn empty() -> Self {
Self {

View File

@ -71,6 +71,19 @@ impl<V: Vox + Clone, S: VolSize, M> SampleVol for VolMap<V, S, M> {
///
/// Note that the resultant volume does not carry forward metadata from the original chunks.
fn sample(&self, range: Aabb<i32>) -> Result<Self::Sample, VolMapErr> {
// Return early if we don't have all the needed chunks that we need!
let min_chunk = Self::chunk_key(range.min);
let max_chunk = Self::chunk_key(range.max - Vec3::one());
for x in min_chunk.x..=max_chunk.x {
for y in min_chunk.y..=max_chunk.y {
for z in min_chunk.z..=max_chunk.z {
if self.chunks.get(&Vec3::new(x, y, z)).is_none() {
return Err(VolMapErr::NoSuchChunk);
}
}
}
}
let mut sample = Dyna::filled(
range.size().map(|e| e as u32).into(),
V::empty(),
@ -106,6 +119,8 @@ impl<V: Vox, S: VolSize, M> VolMap<V, S, M> {
}
}
pub fn chunk_size() -> Vec3<u32> { S::SIZE }
pub fn insert(&mut self, key: Vec3<i32>, chunk: Chunk<V, S, M>) -> Option<Chunk<V, S, M>> {
self.chunks.insert(key, chunk)
}

View File

@ -1,3 +1,6 @@
// Standard
use std::f32::consts::PI;
// Library
use vek::*;
@ -18,8 +21,8 @@ impl Animation for RunAnimation {
time: f64,
) {
let wave = (time as f32 * 12.0).sin();
let wave_fast = (time as f32 * 6.0).sin();
let wave_dip = (wave_fast.abs() - 0.5).abs();
let wave_slow = (time as f32 * 6.0 + PI).sin();
let wave_dip = (wave_slow.abs() - 0.5).abs();
skeleton.head.offset = Vec3::unit_z() * 13.0;
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3);

View File

@ -1,3 +1,5 @@
#![feature(drain_filter)]
pub mod anim;
pub mod error;
pub mod menu;

View File

@ -1,4 +1,5 @@
pub mod segment;
pub mod terrain;
// Library
use vek::*;
@ -11,10 +12,7 @@ use crate::render::{
pub trait Meshable {
type Pipeline: render::Pipeline;
type Supplement;
fn generate_mesh(&self) -> Mesh<Self::Pipeline> {
self.generate_mesh_with_offset(Vec3::zero())
}
fn generate_mesh_with_offset(&self, offs: Vec3<f32>) -> Mesh<Self::Pipeline>;
fn generate_mesh(&self, supp: Self::Supplement) -> Mesh<Self::Pipeline>;
}

View File

@ -1,14 +1,14 @@
// Project
use common::figure::Segment;
// Library
use vek::*;
// Project
use common::vol::{
Vox,
SizedVol,
ReadVol,
use common::{
vol::{
Vox,
SizedVol,
ReadVol,
},
figure::Segment,
};
// Crate
@ -44,8 +44,9 @@ fn create_quad(
impl Meshable for Segment {
type Pipeline = FigurePipeline;
type Supplement = Vec3<f32>;
fn generate_mesh_with_offset(&self, offs: Vec3<f32>) -> Mesh<FigurePipeline> {
fn generate_mesh(&self, offs: Self::Supplement) -> Mesh<Self::Pipeline> {
let mut mesh = Mesh::new();
for pos in self.iter_positions() {

147
voxygen/src/mesh/terrain.rs Normal file
View File

@ -0,0 +1,147 @@
// Library
use vek::*;
// Project
use common::{
vol::{
Vox,
SizedVol,
ReadVol,
},
volumes::dyna::Dyna,
terrain::{Block, TerrainChunk},
};
// Crate
use crate::{
mesh::Meshable,
render::{
self,
Mesh,
Quad,
TerrainPipeline,
},
};
type TerrainVertex = <TerrainPipeline as render::Pipeline>::Vertex;
// Utility function
// TODO: Evaluate how useful this is
fn create_quad(
origin: Vec3<f32>,
unit_x: Vec3<f32>,
unit_y: Vec3<f32>,
norm: Vec3<f32>,
col: Rgb<f32>,
) -> Quad<TerrainPipeline> {
Quad::new(
TerrainVertex::new(origin, norm, col),
TerrainVertex::new(origin + unit_x, norm, col),
TerrainVertex::new(origin + unit_x + unit_y, norm, col),
TerrainVertex::new(origin + unit_y, norm, col),
)
}
impl<M> Meshable for Dyna<Block, M> {
type Pipeline = TerrainPipeline;
type Supplement = ();
fn generate_mesh(&self, _: Self::Supplement) -> Mesh<Self::Pipeline> {
let mut mesh = Mesh::new();
for pos in self
.iter_positions()
.filter(|pos| pos.map(|e| e >= 1).reduce_and())
.filter(|pos| pos.map2(self.get_size(), |e, sz| e < sz as i32 - 1).reduce_and())
{
if let Some(col) = self
.get(pos)
.ok()
.and_then(|vox| vox.get_color())
{
let col = col.map(|e| e as f32 / 255.0);
// -x
if self.get(pos - Vec3::unit_x())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_y(),
-Vec3::unit_y(),
Vec3::unit_z(),
-Vec3::unit_x(),
col,
));
}
// +x
if self.get(pos + Vec3::unit_x())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_x(),
Vec3::unit_y(),
Vec3::unit_z(),
Vec3::unit_x(),
col,
));
}
// -y
if self.get(pos - Vec3::unit_y())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32),
Vec3::unit_x(),
Vec3::unit_z(),
-Vec3::unit_y(),
col,
));
}
// +y
if self.get(pos + Vec3::unit_y())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_y(),
Vec3::unit_z(),
Vec3::unit_x(),
Vec3::unit_y(),
col,
));
}
// -z
if self.get(pos - Vec3::unit_z())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32),
Vec3::unit_y(),
Vec3::unit_x(),
-Vec3::unit_z(),
col,
));
}
// +z
if self.get(pos + Vec3::unit_z())
.map(|v| v.is_empty())
.unwrap_or(true)
{
mesh.push_quad(create_quad(
Vec3::one() + pos.map(|e| e as f32) + Vec3::unit_z(),
Vec3::unit_x(),
Vec3::unit_y(),
Vec3::unit_z(),
col,
));
}
}
}
mesh
}
}

View File

@ -21,7 +21,7 @@ impl Camera {
Self {
focus: Vec3::unit_z() * 10.0,
ori: Vec3::zero(),
dist: 40.0,
dist: 100.0,
fov: 1.3,
aspect: 1.618,
}

View File

@ -82,15 +82,15 @@ impl Scene {
test_figure: Figure::new(
renderer,
[
Some(load_segment("head.vox").generate_mesh_with_offset(Vec3::new(-7.0, -5.5, -1.0))),
Some(load_segment("chest.vox").generate_mesh_with_offset(Vec3::new(-6.0, -3.0, 0.0))),
Some(load_segment("belt.vox").generate_mesh_with_offset(Vec3::new(-5.0, -3.0, 0.0))),
Some(load_segment("pants.vox").generate_mesh_with_offset(Vec3::new(-5.0, -3.0, 0.0))),
Some(load_segment("hand.vox").generate_mesh_with_offset(Vec3::new(-2.0, -2.0, -1.0))),
Some(load_segment("hand.vox").generate_mesh_with_offset(Vec3::new(-2.0, -2.0, -1.0))),
Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, -2.0))),
Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, -2.0))),
Some(load_segment("sword.vox").generate_mesh_with_offset(Vec3::new(-6.5, -1.0, 0.0))),
Some(load_segment("head.vox").generate_mesh(Vec3::new(-7.0, -5.5, -1.0))),
Some(load_segment("chest.vox").generate_mesh(Vec3::new(-6.0, -3.0, 0.0))),
Some(load_segment("belt.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))),
Some(load_segment("pants.vox").generate_mesh(Vec3::new(-5.0, -3.0, 0.0))),
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))),
Some(load_segment("hand.vox").generate_mesh(Vec3::new(-2.0, -2.0, -1.0))),
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))),
Some(load_segment("foot.vox").generate_mesh(Vec3::new(-2.5, -3.0, -2.0))),
Some(load_segment("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))),
None,
None,
None,
@ -118,8 +118,8 @@ impl Scene {
}
}
/// Maintain and update GPU data such as constant buffers, models, etc.
pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer, client: &Client) {
/// Maintain data such as GPU constant buffers, models, etc. To be called once per tick.
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
// Compute camera matrices
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
@ -131,17 +131,17 @@ impl Scene {
self.camera.get_focus_pos(),
10.0,
client.state().get_time_of_day(),
client.state().get_tick(),
client.state().get_time(),
)])
.expect("Failed to update global constants");
// Maintain terrain GPU data
self.terrain.maintain_gpu_data(renderer);
// Maintain the terrain
self.terrain.maintain(renderer, client);
// TODO: Don't do this here
RunAnimation::update_skeleton(
&mut self.test_figure.skeleton,
client.state().get_tick(),
client.state().get_time(),
);
self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap();
self.test_figure.update_skeleton(renderer).unwrap();

View File

@ -1,12 +1,26 @@
// Standard
use std::collections::HashMap;
use std::{
collections::{HashMap, LinkedList},
sync::mpsc,
time::Duration,
};
// Library
use vek::*;
// Project
use client::Client;
use common::{
terrain::TerrainMap,
volumes::vol_map::{
VolMap,
VolMapErr,
},
vol::SampleVol,
};
// Crate
use crate::{
Error,
render::{
Consts,
Globals,
@ -16,6 +30,7 @@ use crate::{
TerrainPipeline,
TerrainLocals,
},
mesh::Meshable,
};
struct TerrainChunk {
@ -24,29 +39,152 @@ struct TerrainChunk {
locals: Consts<TerrainLocals>,
}
struct ChunkMeshState {
pos: Vec3<i32>,
started_tick: u64,
active_worker: bool,
}
/// A type produced by mesh worker threads corresponding to the position and mesh of a chunk
struct MeshWorkerResponse {
pos: Vec3<i32>,
mesh: Mesh<TerrainPipeline>,
started_tick: u64,
}
/// Function executed by worker threads dedicated to chunk meshing
fn mesh_worker(
pos: Vec3<i32>,
started_tick: u64,
volume: <TerrainMap as SampleVol>::Sample,
) -> MeshWorkerResponse {
MeshWorkerResponse {
pos,
mesh: volume.generate_mesh(()),
started_tick,
}
}
pub struct Terrain {
chunks: HashMap<Vec3<i32>, TerrainChunk>,
// The mpsc sender and receiver used for talking to meshing worker threads.
// We keep the sender component for no reason othe than to clone it and send it to new workers.
mesh_send_tmp: mpsc::Sender<MeshWorkerResponse>,
mesh_recv: mpsc::Receiver<MeshWorkerResponse>,
mesh_todo: LinkedList<ChunkMeshState>,
}
impl Terrain {
pub fn new() -> Self {
// Create a new mpsc (Multiple Produced, Single Consumer) pair for communicating with
// worker threads that are meshing chunks.
let (send, recv) = mpsc::channel();
Self {
chunks: HashMap::new(),
mesh_send_tmp: send,
mesh_recv: recv,
mesh_todo: LinkedList::new(),
}
}
pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer) {
// TODO
/// Maintain terrain data. To be called once per tick.
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
let current_tick = client.get_tick();
// Add any recently created or changed chunks to the list of chunks to be meshed
for pos in client.state().changes().new_chunks.iter()
.chain(client.state().changes().changed_chunks.iter())
{
// TODO: ANOTHER PROBLEM HERE!
// What happens if the block on the edge of a chunk gets modified? We need to spawn
// a mesh worker to remesh its neighbour(s) too since their ambient occlusion and face
// elision information changes too!
match self.mesh_todo.iter_mut().find(|todo| todo.pos == *pos) {
Some(todo) => todo.started_tick = current_tick,
// The chunk it's queued yet, add it to the queue
None => self.mesh_todo.push_back(ChunkMeshState {
pos: *pos,
started_tick: current_tick,
active_worker: false,
}),
}
}
// Remove any models for chunks that have been recently removed
for pos in &client.state().changes().removed_chunks {
self.chunks.remove(pos);
self.mesh_todo.drain_filter(|todo| todo.pos == *pos);
}
// Clone the sender to the thread can send us the chunk data back
// TODO: It's a bit hacky cloning it here and then cloning it again below. Fix this.
let send = self.mesh_send_tmp.clone();
self.mesh_todo
.iter_mut()
// Only spawn workers for meshing jobs without an active worker already
.filter(|todo| !todo.active_worker)
.for_each(|todo| {
// Find the area of the terrain we want. Because meshing needs to compute things like
// ambient occlusion and edge elision, we also need to borders of the chunk's
// neighbours too (hence the `- 1` and `+ 1`).
let aabb = Aabb {
min: todo.pos.map2(TerrainMap::chunk_size(), |e, sz| e * sz as i32 - 1),
max: todo.pos.map2(TerrainMap::chunk_size(), |e, sz| (e + 1) * sz as i32 + 1),
};
// Copy out the chunk data we need to perform the meshing. We do this by taking a
// sample of the terrain that includes both the chunk we want and
let volume = match client.state().terrain().sample(aabb) {
Ok(sample) => sample,
// If either this chunk or its neighbours doesn't yet exist, so we keep it in the
// todo queue to be processed at a later date when we have its neighbours.
Err(VolMapErr::NoSuchChunk) => return,
_ => panic!("Unhandled edge case"),
};
// Clone various things to that they can be moved into the thread
let send = send.clone();
let pos = todo.pos;
// Queue the worker thread
client.thread_pool().execute(move || {
send.send(mesh_worker(pos, current_tick, volume))
.expect("Failed to send chunk mesh to main thread");
});
todo.active_worker = true;
});
// Receive chunk meshes from worker threads, upload them to the GPU and then store them
while let Ok(response) = self.mesh_recv.recv_timeout(Duration::new(0, 0)) {
match self.mesh_todo.iter().find(|todo| todo.pos == response.pos) {
// It's the mesh we want, insert the newly finished model into the terrain model
// data structure (convert the mesh to a model first of course)
Some(todo) if response.started_tick == todo.started_tick => {
self.chunks.insert(response.pos, TerrainChunk {
model: renderer.create_model(&response.mesh).expect("Failed to upload chunk mesh to the GPU"),
locals: renderer.create_consts(&[TerrainLocals {
model_offs: response.pos.map2(TerrainMap::chunk_size(), |e, sz| e as f32 * sz as f32).into_array(),
}]).expect("Failed to upload chunk locals to the GPU"),
});
},
// Chunk must have been removed, or it was spawned on an old tick. Drop the mesh
// since it's either out of date or no longer needed
_ => continue,
}
}
}
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
/*
renderer.render_terrain_chunk(
&self.model,
globals,
&self.locals,
&self.bone_consts,
);
*/
for (pos, chunk) in &self.chunks {
renderer.render_terrain_chunk(
&chunk.model,
globals,
&chunk.locals,
);
}
}
}

View File

@ -52,13 +52,15 @@ impl SessionState {
Ok(())
}
/// Clean up the session (and the client attached to it) after a tick
pub fn cleanup(&mut self) {
self.client.cleanup();
}
/// Render the session to the screen.
///
/// This method should be called once per frame.
pub fn render(&mut self, renderer: &mut Renderer) {
// Maintain scene GPU data
self.scene.maintain_gpu_data(renderer, &self.client);
// Clear the screen
renderer.clear(BG_COLOR);
@ -78,6 +80,15 @@ impl PlayState for SessionState {
// Set up an fps clock
let mut clock = Clock::new();
// Load a few chunks TODO: Remove this
for x in -2..3 {
for y in -2..3 {
for z in -1..2 {
self.client.load_chunk(Vec3::new(x, y, z));
}
}
}
// Game loop
loop {
// Handle window events
@ -96,6 +107,9 @@ impl PlayState for SessionState {
self.tick(clock.get_last_delta())
.expect("Failed to tick the scene");
// Maintain the scene
self.scene.maintain(global_state.window.renderer_mut(), &self.client);
// Render the session
self.render(global_state.window.renderer_mut());
@ -106,6 +120,9 @@ impl PlayState for SessionState {
// Wait for the next tick
clock.tick(Duration::from_millis(1000 / FPS));
// Clean things up after the tick
self.cleanup();
}
}

View File

@ -7,3 +7,4 @@ edition = "2018"
[dependencies]
common = { package = "veloren-common", path = "../common" }
vek = "0.9"
noise = "0.5"

View File

@ -1,9 +1,10 @@
// Library
use vek::*;
use noise::{NoiseFn, Perlin};
// Project
use common::{
vol::Vox,
vol::{Vox, SizedVol, WriteVol},
terrain::{
Block,
TerrainChunk,
@ -23,7 +24,35 @@ impl World {
Self
}
pub fn generate_chunk(&self, pos: Vec3<i32>) -> TerrainChunk {
TerrainChunk::filled(Block::empty(), TerrainChunkMeta::void())
pub fn generate_chunk(&self, chunk_pos: Vec3<i32>) -> TerrainChunk {
let mut chunk = TerrainChunk::filled(Block::empty(), TerrainChunkMeta::void());
let air = Block::empty();
let stone = Block::new(1, Rgb::new(200, 220, 255));
let grass = Block::new(2, Rgb::new(50, 255, 0));
let perlin_nz = Perlin::new();
for lpos in chunk.iter_positions() {
let wpos = lpos + chunk_pos * chunk.get_size().map(|e| e as i32);
let wposf = wpos.map(|e| e as f64);
let freq = 1.0 / 32.0;
let ampl = 16.0;
let offs = 16.0;
let height = perlin_nz.get(Vec2::from(wposf * freq).into_array()) * ampl + offs;
chunk.set(lpos, if wposf.z < height {
if wposf.z < height - 1.0 {
stone
} else {
grass
}
} else {
air
});
}
chunk
}
}