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"
|
specs = "0.14"
|
||||||
vek = "0.9"
|
vek = "0.9"
|
||||||
|
threadpool = "1.7"
|
||||||
|
@ -4,6 +4,7 @@ use std::time::Duration;
|
|||||||
// Library
|
// Library
|
||||||
use specs::Entity as EcsEntity;
|
use specs::Entity as EcsEntity;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
use threadpool;
|
||||||
|
|
||||||
// Project
|
// Project
|
||||||
use common::{
|
use common::{
|
||||||
@ -23,8 +24,10 @@ pub struct Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
state: State,
|
thread_pool: threadpool::ThreadPool,
|
||||||
|
|
||||||
|
tick: u64,
|
||||||
|
state: State,
|
||||||
player: Option<EcsEntity>,
|
player: Option<EcsEntity>,
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
@ -36,8 +39,12 @@ impl Client {
|
|||||||
/// Create a new `Client`.
|
/// Create a new `Client`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: State::new(),
|
thread_pool: threadpool::Builder::new()
|
||||||
|
.thread_name("veloren-worker".into())
|
||||||
|
.build(),
|
||||||
|
|
||||||
|
tick: 0,
|
||||||
|
state: State::new(),
|
||||||
player: None,
|
player: None,
|
||||||
|
|
||||||
// Testing
|
// 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 {
|
pub fn with_test_state(mut self) -> Self {
|
||||||
self.chunk = Some(self.world.generate_chunk(Vec3::zero()));
|
self.chunk = Some(self.world.generate_chunk(Vec3::zero()));
|
||||||
self
|
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.
|
/// Get a reference to the client's game state.
|
||||||
pub fn state(&self) -> &State { &self.state }
|
pub fn state(&self) -> &State { &self.state }
|
||||||
|
|
||||||
/// Get a mutable reference to the client's game state.
|
/// Get a mutable reference to the client's game state.
|
||||||
pub fn state_mut(&mut self) -> &mut State { &mut self.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
|
/// 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> {
|
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
|
// 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);
|
self.state.tick(dt);
|
||||||
|
|
||||||
// Finish the tick, pass control back to the frontend (step 6)
|
// Finish the tick, pass control back to the frontend (step 6)
|
||||||
|
self.tick += 1;
|
||||||
Ok(())
|
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
|
// Standard
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// External
|
// Library
|
||||||
use specs::World as EcsWorld;
|
use specs::World as EcsWorld;
|
||||||
use shred::Fetch;
|
use shred::{Fetch, FetchMut};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
// Crate
|
// Crate
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -19,12 +20,35 @@ const DAY_CYCLE_FACTOR: f64 = 24.0 * 60.0;
|
|||||||
struct TimeOfDay(f64);
|
struct TimeOfDay(f64);
|
||||||
|
|
||||||
/// A resource to store the tick (i.e: physics) time
|
/// 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
|
/// 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.
|
/// things like entity components, terrain data, and global state like weather, time of day, etc.
|
||||||
pub struct State {
|
pub struct State {
|
||||||
ecs_world: EcsWorld,
|
ecs_world: EcsWorld,
|
||||||
|
changes: Changes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
@ -34,7 +58,7 @@ impl State {
|
|||||||
|
|
||||||
// Register resources used by the ECS
|
// Register resources used by the ECS
|
||||||
ecs_world.add_resource(TimeOfDay(0.0));
|
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());
|
ecs_world.add_resource(TerrainMap::new());
|
||||||
|
|
||||||
// Register common components with the state
|
// Register common components with the state
|
||||||
@ -42,9 +66,20 @@ impl State {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
ecs_world,
|
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.
|
/// Get the current in-game time of day.
|
||||||
///
|
///
|
||||||
/// Note that this should not be used for physics, animations or other such localised timings.
|
/// 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
|
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.
|
/// Note that this does not correspond to the time of day.
|
||||||
pub fn get_tick(&self) -> f64 {
|
pub fn get_time(&self) -> f64 {
|
||||||
self.ecs_world.read_resource::<Tick>().0
|
self.ecs_world.read_resource::<Time>().0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to this state's terrain.
|
/// Get a reference to this state's terrain.
|
||||||
@ -64,10 +99,21 @@ impl State {
|
|||||||
self.ecs_world.read_resource::<TerrainMap>()
|
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.
|
/// Execute a single tick, simulating the game state by the given duration.
|
||||||
pub fn tick(&mut self, dt: Duration) {
|
pub fn tick(&mut self, dt: Duration) {
|
||||||
// Change the time accordingly
|
// Change the time accordingly
|
||||||
self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_float_secs() * DAY_CYCLE_FACTOR;
|
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
|
// Crate
|
||||||
use crate::vol::Vox;
|
use crate::vol::Vox;
|
||||||
|
|
||||||
@ -7,6 +10,23 @@ pub struct Block {
|
|||||||
color: [u8; 3],
|
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 {
|
impl Vox for Block {
|
||||||
fn empty() -> Self {
|
fn empty() -> Self {
|
||||||
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.
|
/// Note that the resultant volume does not carry forward metadata from the original chunks.
|
||||||
fn sample(&self, range: Aabb<i32>) -> Result<Self::Sample, VolMapErr> {
|
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(
|
let mut sample = Dyna::filled(
|
||||||
range.size().map(|e| e as u32).into(),
|
range.size().map(|e| e as u32).into(),
|
||||||
V::empty(),
|
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>> {
|
pub fn insert(&mut self, key: Vec3<i32>, chunk: Chunk<V, S, M>) -> Option<Chunk<V, S, M>> {
|
||||||
self.chunks.insert(key, chunk)
|
self.chunks.insert(key, chunk)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
// Standard
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
// Library
|
// Library
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -18,8 +21,8 @@ impl Animation for RunAnimation {
|
|||||||
time: f64,
|
time: f64,
|
||||||
) {
|
) {
|
||||||
let wave = (time as f32 * 12.0).sin();
|
let wave = (time as f32 * 12.0).sin();
|
||||||
let wave_fast = (time as f32 * 6.0).sin();
|
let wave_slow = (time as f32 * 6.0 + PI).sin();
|
||||||
let wave_dip = (wave_fast.abs() - 0.5).abs();
|
let wave_dip = (wave_slow.abs() - 0.5).abs();
|
||||||
|
|
||||||
skeleton.head.offset = Vec3::unit_z() * 13.0;
|
skeleton.head.offset = Vec3::unit_z() * 13.0;
|
||||||
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3);
|
skeleton.head.ori = Quaternion::rotation_z(wave * 0.3);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#![feature(drain_filter)]
|
||||||
|
|
||||||
pub mod anim;
|
pub mod anim;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub mod segment;
|
pub mod segment;
|
||||||
|
pub mod terrain;
|
||||||
|
|
||||||
// Library
|
// Library
|
||||||
use vek::*;
|
use vek::*;
|
||||||
@ -11,10 +12,7 @@ use crate::render::{
|
|||||||
|
|
||||||
pub trait Meshable {
|
pub trait Meshable {
|
||||||
type Pipeline: render::Pipeline;
|
type Pipeline: render::Pipeline;
|
||||||
|
type Supplement;
|
||||||
|
|
||||||
fn generate_mesh(&self) -> Mesh<Self::Pipeline> {
|
fn generate_mesh(&self, supp: Self::Supplement) -> Mesh<Self::Pipeline>;
|
||||||
self.generate_mesh_with_offset(Vec3::zero())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_mesh_with_offset(&self, offs: Vec3<f32>) -> Mesh<Self::Pipeline>;
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
// Project
|
|
||||||
use common::figure::Segment;
|
|
||||||
|
|
||||||
// Library
|
// Library
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
// Project
|
// Project
|
||||||
use common::vol::{
|
use common::{
|
||||||
Vox,
|
vol::{
|
||||||
SizedVol,
|
Vox,
|
||||||
ReadVol,
|
SizedVol,
|
||||||
|
ReadVol,
|
||||||
|
},
|
||||||
|
figure::Segment,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Crate
|
// Crate
|
||||||
@ -44,8 +44,9 @@ fn create_quad(
|
|||||||
|
|
||||||
impl Meshable for Segment {
|
impl Meshable for Segment {
|
||||||
type Pipeline = FigurePipeline;
|
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();
|
let mut mesh = Mesh::new();
|
||||||
|
|
||||||
for pos in self.iter_positions() {
|
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 {
|
Self {
|
||||||
focus: Vec3::unit_z() * 10.0,
|
focus: Vec3::unit_z() * 10.0,
|
||||||
ori: Vec3::zero(),
|
ori: Vec3::zero(),
|
||||||
dist: 40.0,
|
dist: 100.0,
|
||||||
fov: 1.3,
|
fov: 1.3,
|
||||||
aspect: 1.618,
|
aspect: 1.618,
|
||||||
}
|
}
|
||||||
|
@ -82,15 +82,15 @@ impl Scene {
|
|||||||
test_figure: Figure::new(
|
test_figure: Figure::new(
|
||||||
renderer,
|
renderer,
|
||||||
[
|
[
|
||||||
Some(load_segment("head.vox").generate_mesh_with_offset(Vec3::new(-7.0, -5.5, -1.0))),
|
Some(load_segment("head.vox").generate_mesh(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("chest.vox").generate_mesh(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("belt.vox").generate_mesh(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("pants.vox").generate_mesh(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(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("hand.vox").generate_mesh(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(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("foot.vox").generate_mesh(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("sword.vox").generate_mesh(Vec3::new(-6.5, -1.0, 0.0))),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@ -118,8 +118,8 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maintain and update GPU data such as constant buffers, models, etc.
|
/// Maintain data such as GPU constant buffers, models, etc. To be called once per tick.
|
||||||
pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer, client: &Client) {
|
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
|
||||||
// Compute camera matrices
|
// Compute camera matrices
|
||||||
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
|
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
|
||||||
|
|
||||||
@ -131,17 +131,17 @@ impl Scene {
|
|||||||
self.camera.get_focus_pos(),
|
self.camera.get_focus_pos(),
|
||||||
10.0,
|
10.0,
|
||||||
client.state().get_time_of_day(),
|
client.state().get_time_of_day(),
|
||||||
client.state().get_tick(),
|
client.state().get_time(),
|
||||||
)])
|
)])
|
||||||
.expect("Failed to update global constants");
|
.expect("Failed to update global constants");
|
||||||
|
|
||||||
// Maintain terrain GPU data
|
// Maintain the terrain
|
||||||
self.terrain.maintain_gpu_data(renderer);
|
self.terrain.maintain(renderer, client);
|
||||||
|
|
||||||
// TODO: Don't do this here
|
// TODO: Don't do this here
|
||||||
RunAnimation::update_skeleton(
|
RunAnimation::update_skeleton(
|
||||||
&mut self.test_figure.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_locals(renderer, FigureLocals::default()).unwrap();
|
||||||
self.test_figure.update_skeleton(renderer).unwrap();
|
self.test_figure.update_skeleton(renderer).unwrap();
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
// Standard
|
// Standard
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
|
collections::{HashMap, LinkedList},
|
||||||
|
sync::mpsc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
// Library
|
// Library
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
// Project
|
||||||
|
use client::Client;
|
||||||
|
use common::{
|
||||||
|
terrain::TerrainMap,
|
||||||
|
volumes::vol_map::{
|
||||||
|
VolMap,
|
||||||
|
VolMapErr,
|
||||||
|
},
|
||||||
|
vol::SampleVol,
|
||||||
|
};
|
||||||
|
|
||||||
// Crate
|
// Crate
|
||||||
use crate::{
|
use crate::{
|
||||||
Error,
|
|
||||||
render::{
|
render::{
|
||||||
Consts,
|
Consts,
|
||||||
Globals,
|
Globals,
|
||||||
@ -16,6 +30,7 @@ use crate::{
|
|||||||
TerrainPipeline,
|
TerrainPipeline,
|
||||||
TerrainLocals,
|
TerrainLocals,
|
||||||
},
|
},
|
||||||
|
mesh::Meshable,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TerrainChunk {
|
struct TerrainChunk {
|
||||||
@ -24,29 +39,152 @@ struct TerrainChunk {
|
|||||||
locals: Consts<TerrainLocals>,
|
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 {
|
pub struct Terrain {
|
||||||
chunks: HashMap<Vec3<i32>, TerrainChunk>,
|
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 {
|
impl Terrain {
|
||||||
pub fn new() -> Self {
|
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 {
|
Self {
|
||||||
chunks: HashMap::new(),
|
chunks: HashMap::new(),
|
||||||
|
|
||||||
|
mesh_send_tmp: send,
|
||||||
|
mesh_recv: recv,
|
||||||
|
mesh_todo: LinkedList::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer) {
|
/// Maintain terrain data. To be called once per tick.
|
||||||
// TODO
|
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>) {
|
pub fn render(&self, renderer: &mut Renderer, globals: &Consts<Globals>) {
|
||||||
/*
|
for (pos, chunk) in &self.chunks {
|
||||||
renderer.render_terrain_chunk(
|
renderer.render_terrain_chunk(
|
||||||
&self.model,
|
&chunk.model,
|
||||||
globals,
|
globals,
|
||||||
&self.locals,
|
&chunk.locals,
|
||||||
&self.bone_consts,
|
);
|
||||||
);
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,13 +52,15 @@ impl SessionState {
|
|||||||
Ok(())
|
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.
|
/// Render the session to the screen.
|
||||||
///
|
///
|
||||||
/// This method should be called once per frame.
|
/// This method should be called once per frame.
|
||||||
pub fn render(&mut self, renderer: &mut Renderer) {
|
pub fn render(&mut self, renderer: &mut Renderer) {
|
||||||
// Maintain scene GPU data
|
|
||||||
self.scene.maintain_gpu_data(renderer, &self.client);
|
|
||||||
|
|
||||||
// Clear the screen
|
// Clear the screen
|
||||||
renderer.clear(BG_COLOR);
|
renderer.clear(BG_COLOR);
|
||||||
|
|
||||||
@ -78,6 +80,15 @@ impl PlayState for SessionState {
|
|||||||
// Set up an fps clock
|
// Set up an fps clock
|
||||||
let mut clock = Clock::new();
|
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
|
// Game loop
|
||||||
loop {
|
loop {
|
||||||
// Handle window events
|
// Handle window events
|
||||||
@ -96,6 +107,9 @@ impl PlayState for SessionState {
|
|||||||
self.tick(clock.get_last_delta())
|
self.tick(clock.get_last_delta())
|
||||||
.expect("Failed to tick the scene");
|
.expect("Failed to tick the scene");
|
||||||
|
|
||||||
|
// Maintain the scene
|
||||||
|
self.scene.maintain(global_state.window.renderer_mut(), &self.client);
|
||||||
|
|
||||||
// Render the session
|
// Render the session
|
||||||
self.render(global_state.window.renderer_mut());
|
self.render(global_state.window.renderer_mut());
|
||||||
|
|
||||||
@ -106,6 +120,9 @@ impl PlayState for SessionState {
|
|||||||
|
|
||||||
// Wait for the next tick
|
// Wait for the next tick
|
||||||
clock.tick(Duration::from_millis(1000 / FPS));
|
clock.tick(Duration::from_millis(1000 / FPS));
|
||||||
|
|
||||||
|
// Clean things up after the tick
|
||||||
|
self.cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,3 +7,4 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
common = { package = "veloren-common", path = "../common" }
|
common = { package = "veloren-common", path = "../common" }
|
||||||
vek = "0.9"
|
vek = "0.9"
|
||||||
|
noise = "0.5"
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
// Library
|
// Library
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
use noise::{NoiseFn, Perlin};
|
||||||
|
|
||||||
// Project
|
// Project
|
||||||
use common::{
|
use common::{
|
||||||
vol::Vox,
|
vol::{Vox, SizedVol, WriteVol},
|
||||||
terrain::{
|
terrain::{
|
||||||
Block,
|
Block,
|
||||||
TerrainChunk,
|
TerrainChunk,
|
||||||
@ -23,7 +24,35 @@ impl World {
|
|||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_chunk(&self, pos: Vec3<i32>) -> TerrainChunk {
|
pub fn generate_chunk(&self, chunk_pos: Vec3<i32>) -> TerrainChunk {
|
||||||
TerrainChunk::filled(Block::empty(), TerrainChunkMeta::void())
|
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