mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added test terrain loading and meshing
This commit is contained in:
parent
41b6672743
commit
248577bdef
@ -10,3 +10,4 @@ world = { package = "veloren-world", path = "../world" }
|
||||
|
||||
specs = "0.14"
|
||||
vek = "0.9"
|
||||
threadpool = "1.7"
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![feature(drain_filter)]
|
||||
|
||||
pub mod anim;
|
||||
pub mod error;
|
||||
pub mod menu;
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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
147
voxygen/src/mesh/terrain.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,3 +7,4 @@ edition = "2018"
|
||||
[dependencies]
|
||||
common = { package = "veloren-common", path = "../common" }
|
||||
vek = "0.9"
|
||||
noise = "0.5"
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user