From 7a292860536d439bc7a8508fc6010c2c0dffef3a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 2 Jan 2019 21:25:01 +0000 Subject: [PATCH 01/21] Started adding voxygen and windowing code --- Cargo.toml | 1 + voxygen/.gitignore | 3 ++ voxygen/Cargo.toml | 14 +++++++ voxygen/src/main.rs | 50 ++++++++++++++++++++++ voxygen/src/menu/mod.rs | 1 + voxygen/src/menu/title.rs | 30 ++++++++++++++ voxygen/src/window.rs | 87 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 186 insertions(+) create mode 100644 voxygen/.gitignore create mode 100644 voxygen/Cargo.toml create mode 100644 voxygen/src/main.rs create mode 100644 voxygen/src/menu/mod.rs create mode 100644 voxygen/src/menu/title.rs create mode 100644 voxygen/src/window.rs diff --git a/Cargo.toml b/Cargo.toml index ddf12b4e37..70930e72e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "common", "client", "server", + "voxygen", ] [profile.dev] diff --git a/voxygen/.gitignore b/voxygen/.gitignore new file mode 100644 index 0000000000..693699042b --- /dev/null +++ b/voxygen/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml new file mode 100644 index 0000000000..feb943fe65 --- /dev/null +++ b/voxygen/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "voxygen" +version = "0.1.0" +authors = ["Joshua Barretto "] +edition = "2018" + +[dependencies] +common = { path = "../common" } + +# Graphics +gfx = "0.17.1" +gfx_device_gl = "0.15.0" +gfx_window_glutin = "0.25.0" +glutin = "0.17.0" diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs new file mode 100644 index 0000000000..eca06ce4d1 --- /dev/null +++ b/voxygen/src/main.rs @@ -0,0 +1,50 @@ +mod menu; +mod window; + +// Standard +use std::mem; + +// Crate +use crate::{ + menu::title::TitleState, + window::Window, +}; + +// A type used to store state that is shared between all play states +pub struct GlobalState { + window: Window, +} + +// States can either close (and revert to a previous state), push a new state on top of themselves, +// or switch to a totally different state +pub enum StateResult { + Close, + Push(Box), + Switch(Box), +} + +pub trait PlayState { + fn play(&mut self, global_state: &mut GlobalState) -> StateResult; +} + +fn main() { + let mut states: Vec> = vec![Box::new(TitleState::new())]; + + let mut global_state = GlobalState { + window: Window::new(), + }; + + loop { + // Implement state transfer logic + match states.last_mut().map(|last| last.play(&mut global_state)) { + Some(StateResult::Close) => { states.pop(); }, + Some(StateResult::Push(new_state)) => { states.push(new_state); }, + Some(StateResult::Switch(mut new_state)) => if let Some(old_state) = states.last_mut() { + mem::swap(old_state, &mut new_state); + } else { + break; + }, + None => break, + } + } +} diff --git a/voxygen/src/menu/mod.rs b/voxygen/src/menu/mod.rs new file mode 100644 index 0000000000..5cec26ea76 --- /dev/null +++ b/voxygen/src/menu/mod.rs @@ -0,0 +1 @@ +pub mod title; diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs new file mode 100644 index 0000000000..ccb2354ec1 --- /dev/null +++ b/voxygen/src/menu/title.rs @@ -0,0 +1,30 @@ +// Crate +use crate::{ + PlayState, + StateResult, + GlobalState, + window::Event, +}; + +pub struct TitleState; + +impl TitleState { + pub fn new() -> Self { + Self + } +} + +impl PlayState for TitleState { + fn play(&mut self, global_state: &mut GlobalState) -> StateResult { + let mut running = true; + while running { + global_state.window.poll_events(|event| match event { + Event::Close => running = false, + }); + + global_state.window.swap_buffers(); + } + + StateResult::Close + } +} diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs new file mode 100644 index 0000000000..17ba361bb7 --- /dev/null +++ b/voxygen/src/window.rs @@ -0,0 +1,87 @@ +// External +use gfx::handle::{RenderTargetView, DepthStencilView}; +use gfx_device_gl::{Device, Resources, Factory}; +use gfx_window_glutin; +use glutin::{ + self, + Api::OpenGl, + dpi::LogicalSize, + ContextBuilder, + EventsLoop, + GlContext, + GlRequest, + GlWindow, + WindowBuilder, +}; + +type TgtColorView = RenderTargetView; +type TgtDepthView = DepthStencilView; + +pub struct RenderCtx { + device: Device, + factory: Factory, + tgt_color_view: TgtColorView, + tgt_depth_view: TgtDepthView, +} + +pub struct Window { + events_loop: EventsLoop, + gl_window: GlWindow, + render_ctx: RenderCtx, +} + + +impl Window { + pub fn new() -> Window { + let events_loop = EventsLoop::new(); + + let ( + gl_window, + device, + factory, + tgt_color_view, + tgt_depth_view, + ) = gfx_window_glutin::init( + WindowBuilder::new() + .with_title("Veloren (Voxygen)") + .with_dimensions(LogicalSize::new(800.0, 500.0)) + .with_maximized(false), + ContextBuilder::new() + .with_gl(GlRequest::Specific(OpenGl, (3, 2))) + .with_multisampling(2) + .with_vsync(true), + &events_loop, + ); + + Self { + events_loop, + gl_window, + render_ctx: RenderCtx { + device, + factory, + tgt_color_view, + tgt_depth_view, + }, + } + } + + pub fn poll_events(&mut self, mut f: F) { + self.events_loop.poll_events(|event| match event { + glutin::Event::WindowEvent { event, .. } => match event { + glutin::WindowEvent::CloseRequested => f(Event::Close), + _ => {}, + }, + _ => {}, + }); + } + + pub fn swap_buffers(&self) { + self.gl_window + .swap_buffers() + .expect("Failed to swap window buffers"); + } +} + +pub enum Event { + Close, +} From 6f358fc762ec8a5f139c67670d791aa5f9de0948 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 2 Jan 2019 22:08:13 +0000 Subject: [PATCH 02/21] Added RenderCtx --- voxygen/Cargo.toml | 3 +++ voxygen/src/main.rs | 20 +++++++++-------- voxygen/src/menu/title.rs | 10 +++++++++ voxygen/src/render_ctx.rs | 45 +++++++++++++++++++++++++++++++++++++++ voxygen/src/window.rs | 21 ++++++------------ 5 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 voxygen/src/render_ctx.rs diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index feb943fe65..4744287815 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -12,3 +12,6 @@ gfx = "0.17.1" gfx_device_gl = "0.15.0" gfx_window_glutin = "0.25.0" glutin = "0.17.0" + +# Mathematics +vek = "0.9" diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index eca06ce4d1..8e2cbab9a4 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -1,4 +1,5 @@ mod menu; +mod render_ctx; mod window; // Standard @@ -34,17 +35,18 @@ fn main() { window: Window::new(), }; - loop { + while let Some(state_result) = states.last_mut().map(|last| last.play(&mut global_state)){ // Implement state transfer logic - match states.last_mut().map(|last| last.play(&mut global_state)) { - Some(StateResult::Close) => { states.pop(); }, - Some(StateResult::Push(new_state)) => { states.push(new_state); }, - Some(StateResult::Switch(mut new_state)) => if let Some(old_state) = states.last_mut() { - mem::swap(old_state, &mut new_state); - } else { - break; + match state_result { + StateResult::Close => { + states.pop(); + }, + StateResult::Push(new_state) => { + states.push(new_state); + }, + StateResult::Switch(mut new_state) => { + states.last_mut().map(|old_state| mem::swap(old_state, &mut new_state)); }, - None => break, } } } diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index ccb2354ec1..bab4b3b183 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -1,3 +1,6 @@ +// Library +use vek::*; + // Crate use crate::{ PlayState, @@ -22,6 +25,13 @@ impl PlayState for TitleState { Event::Close => running = false, }); + global_state.window.render_ctx_mut().clear(Rgba::new( + 0.0, + 0.3, + 1.0, + 1.0, + )); + global_state.window.render_ctx_mut().flush_and_cleanup(); global_state.window.swap_buffers(); } diff --git a/voxygen/src/render_ctx.rs b/voxygen/src/render_ctx.rs new file mode 100644 index 0000000000..b4a1efe459 --- /dev/null +++ b/voxygen/src/render_ctx.rs @@ -0,0 +1,45 @@ +// Library +use gfx_device_gl::{Device, Resources, Factory, CommandBuffer}; +use gfx::{ + handle::{RenderTargetView, DepthStencilView}, + Device as DeviceTrait, + Encoder, +}; +use vek::*; + +type TgtColorView = RenderTargetView; +type TgtDepthView = DepthStencilView; + +pub struct RenderCtx { + device: Device, + encoder: Encoder, + factory: Factory, + tgt_color_view: TgtColorView, + tgt_depth_view: TgtDepthView, +} + +impl RenderCtx { + pub fn new( + device: Device, + mut factory: Factory, + tgt_color_view: TgtColorView, + tgt_depth_view: TgtDepthView, + ) -> Self { + Self { + device, + encoder: Encoder::from(factory.create_command_buffer()), + factory, + tgt_color_view, + tgt_depth_view, + } + } + + pub fn clear(&mut self, col: Rgba) { + self.encoder.clear(&self.tgt_color_view, col.into_array()); + } + + pub fn flush_and_cleanup(&mut self) { + self.encoder.flush(&mut self.device); + self.device.cleanup(); + } +} diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 17ba361bb7..365960c409 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -1,9 +1,6 @@ // External -use gfx::handle::{RenderTargetView, DepthStencilView}; -use gfx_device_gl::{Device, Resources, Factory}; use gfx_window_glutin; use glutin::{ - self, Api::OpenGl, dpi::LogicalSize, ContextBuilder, @@ -14,15 +11,8 @@ use glutin::{ WindowBuilder, }; -type TgtColorView = RenderTargetView; -type TgtDepthView = DepthStencilView; - -pub struct RenderCtx { - device: Device, - factory: Factory, - tgt_color_view: TgtColorView, - tgt_depth_view: TgtDepthView, -} +// Crate +use crate::render_ctx::RenderCtx; pub struct Window { events_loop: EventsLoop, @@ -56,15 +46,18 @@ impl Window { Self { events_loop, gl_window, - render_ctx: RenderCtx { + render_ctx: RenderCtx::new( device, factory, tgt_color_view, tgt_depth_view, - }, + ), } } + pub fn render_ctx(&self) -> &RenderCtx { &self.render_ctx } + pub fn render_ctx_mut(&mut self) -> &mut RenderCtx { &mut self.render_ctx } + pub fn poll_events(&mut self, mut f: F) { self.events_loop.poll_events(|event| match event { glutin::Event::WindowEvent { event, .. } => match event { From 5d6ec363a5f89dbd0b5c0241c724ff53ea582742 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 7 Jan 2019 21:10:31 +0000 Subject: [PATCH 03/21] Added triangle test --- voxygen/Cargo.toml | 19 ++- voxygen/shaders/test/shader.frag | 11 ++ voxygen/shaders/test/shader.vert | 11 ++ voxygen/src/consts.rs | 34 +++++ voxygen/src/global_consts.rs | 18 +++ voxygen/src/main.rs | 33 ++++- voxygen/src/menu/title.rs | 24 ++-- voxygen/src/render/mesh.rs | 21 +++ voxygen/src/render/mod.rs | 36 +++++ voxygen/src/render/model.rs | 18 +++ voxygen/src/render/renderer.rs | 218 +++++++++++++++++++++++++++++++ voxygen/src/render/shader_set.rs | 23 ++++ voxygen/src/render_ctx.rs | 45 ------- voxygen/src/window.rs | 82 ++++-------- 14 files changed, 472 insertions(+), 121 deletions(-) create mode 100644 voxygen/shaders/test/shader.frag create mode 100644 voxygen/shaders/test/shader.vert create mode 100644 voxygen/src/consts.rs create mode 100644 voxygen/src/global_consts.rs create mode 100644 voxygen/src/render/mesh.rs create mode 100644 voxygen/src/render/mod.rs create mode 100644 voxygen/src/render/model.rs create mode 100644 voxygen/src/render/renderer.rs create mode 100644 voxygen/src/render/shader_set.rs delete mode 100644 voxygen/src/render_ctx.rs diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 4744287815..a31f5ee603 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -4,14 +4,25 @@ version = "0.1.0" authors = ["Joshua Barretto "] edition = "2018" +[features] +empty = ["rendy/empty"] +dx12 = ["rendy/dx12"] +metal = ["rendy/metal"] +vulkan = ["rendy/vulkan"] + +default = ["empty"] + [dependencies] common = { path = "../common" } # Graphics -gfx = "0.17.1" -gfx_device_gl = "0.15.0" -gfx_window_glutin = "0.25.0" -glutin = "0.17.0" +winit = "0.18" +rendy = { git = "https://github.com/omni-viral/rendy" } # Mathematics vek = "0.9" + +# Utility +glsl-include = "0.2" +failure = "0.1" +lazy_static = "1.1" diff --git a/voxygen/shaders/test/shader.frag b/voxygen/shaders/test/shader.frag new file mode 100644 index 0000000000..c1ceb77ce0 --- /dev/null +++ b/voxygen/shaders/test/shader.frag @@ -0,0 +1,11 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(early_fragment_tests) in; + +layout(location = 0) in vec4 frag_color; +layout(location = 0) out vec4 color; + +void main() { + color = frag_color; +} diff --git a/voxygen/shaders/test/shader.vert b/voxygen/shaders/test/shader.vert new file mode 100644 index 0000000000..1562324ae1 --- /dev/null +++ b/voxygen/shaders/test/shader.vert @@ -0,0 +1,11 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 pos; +layout(location = 1) in vec4 color; +layout(location = 0) out vec4 frag_color; + +void main() { + frag_color = color; + gl_Position = vec4(pos, 1.0); +} diff --git a/voxygen/src/consts.rs b/voxygen/src/consts.rs new file mode 100644 index 0000000000..95ec0a2564 --- /dev/null +++ b/voxygen/src/consts.rs @@ -0,0 +1,34 @@ +// Library +use gfx::{ + handle::Buffer, + traits::{FactoryExt, Pod}, +}; + +// Crate +use crate::render_ctx::{RenderCtx, Resources}; + +type ConstBuffer = Buffer; + +#[derive(Clone)] +pub struct ConstHandle { + buffer: ConstBuffer, +} + +impl ConstHandle { + pub fn new(render_ctx: &mut RenderCtx) -> Self { + Self { + buffer: render_ctx + .factory_mut() + .create_constant_buffer(1), + } + } + + pub fn update(&self, render_ctx: &mut RenderCtx, consts: T) { + render_ctx + .encoder_mut() + .update_buffer(&self.buffer, &[consts], 0) + .unwrap(); + } + + pub fn buffer(&self) -> &ConstBuffer { &self.buffer } +} diff --git a/voxygen/src/global_consts.rs b/voxygen/src/global_consts.rs new file mode 100644 index 0000000000..2045d35ef9 --- /dev/null +++ b/voxygen/src/global_consts.rs @@ -0,0 +1,18 @@ +// Library +use gfx::{ + // Urgh + gfx_defines, + gfx_constant_struct_meta, + gfx_impl_struct_meta, +}; + +gfx_defines! { + constant GlobalConsts { + view_mat: [[f32; 4]; 4] = "view_mat", + proj_mat: [[f32; 4]; 4] = "proj_mat", + cam_origin: [f32; 4] = "cam_origin", + play_origin: [f32; 4] = "play_origin", + view_distance: [f32; 4] = "view_distance", + time: [f32; 4] = "time", + } +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 8e2cbab9a4..209bfbe69e 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -1,16 +1,26 @@ mod menu; -mod render_ctx; +mod render; mod window; // Standard use std::mem; +// Library +use winit; +use failure; + // Crate use crate::{ menu::title::TitleState, window::Window, }; +#[derive(Debug)] +pub enum VoxygenErr { + WinitCreationErr(winit::CreationError), + Other(failure::Error), +} + // A type used to store state that is shared between all play states pub struct GlobalState { window: Window, @@ -18,33 +28,42 @@ pub struct GlobalState { // States can either close (and revert to a previous state), push a new state on top of themselves, // or switch to a totally different state -pub enum StateResult { +pub enum PlayStateResult { + /// Pop all play states in reverse order and shut down the program + Shutdown, + /// Close the current play state Close, + /// Push a new play state onto the play state stack Push(Box), + /// Switch the current play state with a new play state Switch(Box), } pub trait PlayState { - fn play(&mut self, global_state: &mut GlobalState) -> StateResult; + fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult; } fn main() { let mut states: Vec> = vec![Box::new(TitleState::new())]; let mut global_state = GlobalState { - window: Window::new(), + window: Window::new() + .expect("Failed to create window"), }; while let Some(state_result) = states.last_mut().map(|last| last.play(&mut global_state)){ // Implement state transfer logic match state_result { - StateResult::Close => { + PlayStateResult::Shutdown => while states.last().is_some() { states.pop(); }, - StateResult::Push(new_state) => { + PlayStateResult::Close => { + states.pop(); + }, + PlayStateResult::Push(new_state) => { states.push(new_state); }, - StateResult::Switch(mut new_state) => { + PlayStateResult::Switch(mut new_state) => { states.last_mut().map(|old_state| mem::swap(old_state, &mut new_state)); }, } diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index bab4b3b183..ac4eb506ec 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -4,7 +4,7 @@ use vek::*; // Crate use crate::{ PlayState, - StateResult, + PlayStateResult, GlobalState, window::Event, }; @@ -18,23 +18,23 @@ impl TitleState { } impl PlayState for TitleState { - fn play(&mut self, global_state: &mut GlobalState) -> StateResult { - let mut running = true; - while running { - global_state.window.poll_events(|event| match event { - Event::Close => running = false, - }); + fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult { + 'eventloop: loop { + // Process window events + for event in global_state.window.fetch_events() { + match event { + Event::Close => break 'eventloop PlayStateResult::Shutdown, + } + } - global_state.window.render_ctx_mut().clear(Rgba::new( + global_state.window.renderer_mut().clear(Rgba::new( 0.0, 0.3, 1.0, 1.0, )); - global_state.window.render_ctx_mut().flush_and_cleanup(); - global_state.window.swap_buffers(); + global_state.window.renderer_mut().flush(); + global_state.window.display(); } - - StateResult::Close } } diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs new file mode 100644 index 0000000000..352e1e92df --- /dev/null +++ b/voxygen/src/render/mesh.rs @@ -0,0 +1,21 @@ +// Local +use super::Pipeline; + +/// Used to store vertex data on the CPU +pub struct Mesh { + verts: Vec, +} + +impl Mesh

{ + pub fn empty() -> Self { + Self { verts: vec![] } + } + + pub fn verts(&self) -> &[P::Vertex] { + &self.verts + } + + pub fn push(&mut self, vert: P::Vertex) { + self.verts.push(vert); + } +} diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs new file mode 100644 index 0000000000..db1f83f747 --- /dev/null +++ b/voxygen/src/render/mod.rs @@ -0,0 +1,36 @@ +mod mesh; +mod model; +mod renderer; +mod shader_set; + +// Reexports +pub use self::{ + mesh::Mesh, + model::Model, + shader_set::ShaderSet, + renderer::Renderer, +}; + +// Library +use rendy; + +#[cfg(not(any(feature = "dx12", feature = "metal", feature = "vulkan")))] +type Backend = rendy::empty::Backend; + +#[cfg(feature = "dx12")] +type Backend = rendy::dx12::Backend; + +#[cfg(feature = "metal")] +type Backend = rendy::metal::Backend; + +#[cfg(feature = "vulkan")] +type Backend = rendy::vulkan::Backend; + +/// Used to represent one of many possible errors that may be omitted by the rendering code +#[derive(Debug)] +pub enum RenderErr {} + +/// Used to represent a specific rendering configuration +pub trait Pipeline { + type Vertex; +} diff --git a/voxygen/src/render/model.rs b/voxygen/src/render/model.rs new file mode 100644 index 0000000000..b7aaf5d6f7 --- /dev/null +++ b/voxygen/src/render/model.rs @@ -0,0 +1,18 @@ +// Standard +use std::marker::PhantomData; + +// Local +use super::Pipeline; + +/// Represents a mesh that has been sent to the CPU +pub struct Model { + phantom: PhantomData

, +} + +impl Model

{ + pub fn new() -> Self { + Self { + phantom: PhantomData, + } + } +} diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs new file mode 100644 index 0000000000..9c8b6cade5 --- /dev/null +++ b/voxygen/src/render/renderer.rs @@ -0,0 +1,218 @@ +// Library +use vek::*; +use winit; +use lazy_static::lazy_static; +use rendy::{ + hal, + command::RenderPassInlineEncoder, + wsi::Surface, + graph::{ + Graph, + GraphBuilder, + NodeBuffer, + NodeImage, + present::PresentNode, + render::RenderPass, + }, + factory::{Config, Factory}, + memory::MemoryUsageValue, + resource::buffer::Buffer, + mesh::AsVertex, + shader::{Shader, ShaderKind, SourceLanguage, StaticShaderInfo}, +}; + +// Crate +use crate::VoxygenErr; + +// Local +use super::{ + model::Model, + mesh::Mesh, + shader_set::ShaderSet, + Pipeline, + RenderErr, + Backend, +}; + +lazy_static! { + static ref VS: StaticShaderInfo = StaticShaderInfo::new( + concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/test/shader.vert"), + ShaderKind::Vertex, + SourceLanguage::GLSL, + "main", + ); + + static ref FS: StaticShaderInfo = StaticShaderInfo::new( + concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/test/shader.frag"), + ShaderKind::Fragment, + SourceLanguage::GLSL, + "main", + ); +} + +#[derive(Debug)] +struct TriangleRenderPass { + vertex: Option>, +} + +impl RenderPass for TriangleRenderPass { + fn name() -> &'static str { "triangle" } + + fn vertices() -> Vec<( + Vec>, + hal::pso::ElemStride, + )> { vec![rendy::mesh::PosColor::VERTEX.gfx_vertex_input_desc()] } + + fn load_shader_sets<'a>( + storage: &'a mut Vec, + factory: &mut Factory, + _aux: &mut T, + ) -> Vec> { + storage.clear(); + storage.push(VS.module(factory).unwrap()); + storage.push(FS.module(factory).unwrap()); + + vec![hal::pso::GraphicsShaderSet { + vertex: hal::pso::EntryPoint { + entry: "main", + module: &storage[0], + specialization: hal::pso::Specialization::default(), + }, + fragment: Some(hal::pso::EntryPoint { + entry: "main", + module: &storage[1], + specialization: hal::pso::Specialization::default(), + }), + hull: None, + domain: None, + geometry: None, + }] + } + + fn build<'a>( + _factory: &mut Factory, + _aux: &mut T, + buffers: &mut [NodeBuffer<'a, B>], + images: &mut [NodeImage<'a, B>], + sets: &[impl AsRef<[B::DescriptorSetLayout]>], + ) -> Self { + assert!(buffers.is_empty()); + assert!(images.is_empty()); + assert_eq!(sets.len(), 1); + assert!(sets[0].as_ref().is_empty()); + + Self { + vertex: None, + } + } + + fn prepare(&mut self, factory: &mut Factory, _aux: &T) -> bool { + if self.vertex.is_some() { + return false; + } else { + let mut vbuf = factory.create_buffer( + 512, + rendy::mesh::PosColor::VERTEX.stride as u64 * 3, + (hal::buffer::Usage::VERTEX, MemoryUsageValue::Dynamic) + ).unwrap(); + + unsafe { + factory.upload_visible_buffer(&mut vbuf, 0, &[ + rendy::mesh::PosColor { + position: [0.0, -1.0, 0.0].into(), + color: [1.0, 0.0, 0.0, 1.0].into(), + }, + rendy::mesh::PosColor { + position: [1.0, 1.0, 0.0].into(), + color: [0.0, 1.0, 0.0, 1.0].into(), + }, + rendy::mesh::PosColor { + position: [-1.0, 1.0, 0.0].into(), + color: [0.0, 0.0, 1.0, 1.0].into(), + }, + ]).unwrap(); + } + + self.vertex = Some(vbuf); + + true + } + } + + fn draw( + &mut self, + _layouts: &[B::PipelineLayout], + pipelines: &[B::GraphicsPipeline], + mut encoder: RenderPassInlineEncoder<'_, B>, + _index: usize, + _aux: &T, + ) { + let vbuf = self.vertex.as_ref().unwrap(); + encoder.bind_graphics_pipeline(&pipelines[0]); + encoder.bind_vertex_buffers(0, Some((vbuf.raw(), 0))); + encoder.draw(0..3, 0..1); + } + + fn dispose(self, _factory: &mut Factory, _aux: &mut T) {} +} + +pub struct Renderer { + //surface: Surface, + graph: Graph, + factory: Factory, +} + +impl Renderer { + pub fn new( + window: winit::Window, + ) -> Result { + let config: Config = Config::default(); + + let mut factory = Factory::::new(config) + .map_err(|err| VoxygenErr::Other(err))?; + + let surface = factory.create_surface(window); + + let mut graph_builder = GraphBuilder::::new(); + + let color_img = graph_builder.create_image( + surface.kind(), + 1, + hal::format::Format::Rgba8Unorm, + MemoryUsageValue::Data, + Some(hal::command::ClearValue::Color([1.0; 4].into())), + ); + + graph_builder.add_node( + TriangleRenderPass::builder() + .with_image(color_img) + ); + + graph_builder.add_node( + PresentNode::builder(surface) + .with_image(color_img) + ); + + let graph = graph_builder.build(&mut factory, &mut ()) + .map_err(|err| VoxygenErr::Other(err))?; + + Ok(Self { + graph, + factory, + }) + } + + pub fn clear(&mut self, col: Rgba) { + } + + pub fn flush(&mut self) { + self.graph.run(&mut self.factory, &mut ()); + } +} + +impl Drop for Renderer { + fn drop(&mut self) { + //self.graph.dispose(&mut self.factory, &mut ()); + //self.factory.dispose(); + } +} diff --git a/voxygen/src/render/shader_set.rs b/voxygen/src/render/shader_set.rs new file mode 100644 index 0000000000..d1e70d4cc4 --- /dev/null +++ b/voxygen/src/render/shader_set.rs @@ -0,0 +1,23 @@ +// Standard +use std::marker::PhantomData; + +// Local +use super::{ + Pipeline, + RenderErr +}; + +pub struct ShaderSet { + phantom: PhantomData

, +} + +impl ShaderSet

{ + pub fn new( + vs: &[u8], + fs: &[u8], + ) -> Result { + Ok(Self { + phantom: PhantomData, + }) + } +} diff --git a/voxygen/src/render_ctx.rs b/voxygen/src/render_ctx.rs deleted file mode 100644 index b4a1efe459..0000000000 --- a/voxygen/src/render_ctx.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Library -use gfx_device_gl::{Device, Resources, Factory, CommandBuffer}; -use gfx::{ - handle::{RenderTargetView, DepthStencilView}, - Device as DeviceTrait, - Encoder, -}; -use vek::*; - -type TgtColorView = RenderTargetView; -type TgtDepthView = DepthStencilView; - -pub struct RenderCtx { - device: Device, - encoder: Encoder, - factory: Factory, - tgt_color_view: TgtColorView, - tgt_depth_view: TgtDepthView, -} - -impl RenderCtx { - pub fn new( - device: Device, - mut factory: Factory, - tgt_color_view: TgtColorView, - tgt_depth_view: TgtDepthView, - ) -> Self { - Self { - device, - encoder: Encoder::from(factory.create_command_buffer()), - factory, - tgt_color_view, - tgt_depth_view, - } - } - - pub fn clear(&mut self, col: Rgba) { - self.encoder.clear(&self.tgt_color_view, col.into_array()); - } - - pub fn flush_and_cleanup(&mut self) { - self.encoder.flush(&mut self.device); - self.device.cleanup(); - } -} diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 365960c409..fcc36efaab 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -1,77 +1,53 @@ -// External -use gfx_window_glutin; -use glutin::{ - Api::OpenGl, - dpi::LogicalSize, - ContextBuilder, - EventsLoop, - GlContext, - GlRequest, - GlWindow, - WindowBuilder, -}; +// Library +use winit; // Crate -use crate::render_ctx::RenderCtx; +use crate::{ + VoxygenErr, + render::Renderer, +}; pub struct Window { - events_loop: EventsLoop, - gl_window: GlWindow, - render_ctx: RenderCtx, + events_loop: winit::EventsLoop, + renderer: Renderer, } impl Window { - pub fn new() -> Window { - let events_loop = EventsLoop::new(); + pub fn new() -> Result { + let events_loop = winit::EventsLoop::new(); - let ( - gl_window, - device, - factory, - tgt_color_view, - tgt_depth_view, - ) = gfx_window_glutin::init( - WindowBuilder::new() - .with_title("Veloren (Voxygen)") - .with_dimensions(LogicalSize::new(800.0, 500.0)) - .with_maximized(false), - ContextBuilder::new() - .with_gl(GlRequest::Specific(OpenGl, (3, 2))) - .with_multisampling(2) - .with_vsync(true), - &events_loop, - ); + let window = winit::WindowBuilder::new() + .with_title("Veloren (Voxygen)") + .with_dimensions(winit::dpi::LogicalSize::new(800.0, 500.0)) + .with_maximized(false) + .build(&events_loop) + .map_err(|err| VoxygenErr::WinitCreationErr(err))?; - Self { + let tmp = Ok(Self { events_loop, - gl_window, - render_ctx: RenderCtx::new( - device, - factory, - tgt_color_view, - tgt_depth_view, - ), - } + renderer: Renderer::new(window)?, + }); + tmp } - pub fn render_ctx(&self) -> &RenderCtx { &self.render_ctx } - pub fn render_ctx_mut(&mut self) -> &mut RenderCtx { &mut self.render_ctx } + pub fn renderer(&self) -> &Renderer { &self.renderer } + pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer } - pub fn poll_events(&mut self, mut f: F) { + pub fn fetch_events(&mut self) -> Vec { + let mut events = vec![]; self.events_loop.poll_events(|event| match event { - glutin::Event::WindowEvent { event, .. } => match event { - glutin::WindowEvent::CloseRequested => f(Event::Close), + winit::Event::WindowEvent { event, .. } => match event { + winit::WindowEvent::CloseRequested => events.push(Event::Close), _ => {}, }, _ => {}, }); + events } - pub fn swap_buffers(&self) { - self.gl_window - .swap_buffers() - .expect("Failed to swap window buffers"); + pub fn display(&self) { + // TODO } } From d8c87984d54aca5407580e0d33c30e3dcb6af313 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 11 Jan 2019 17:30:13 +0000 Subject: [PATCH 04/21] Switch back to old gfx backend --- voxygen/Cargo.toml | 13 +- voxygen/src/main.rs | 9 +- voxygen/src/menu/title.rs | 3 +- voxygen/src/render/mod.rs | 19 +- voxygen/src/render/pipelines/character.rs | 39 ++++ voxygen/src/render/pipelines/mod.rs | 1 + voxygen/src/render/renderer.rs | 209 +++------------------- voxygen/src/window.rs | 54 ++++-- 8 files changed, 125 insertions(+), 222 deletions(-) create mode 100644 voxygen/src/render/pipelines/character.rs create mode 100644 voxygen/src/render/pipelines/mod.rs diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index a31f5ee603..4b58ce03ff 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -5,19 +5,18 @@ authors = ["Joshua Barretto "] edition = "2018" [features] -empty = ["rendy/empty"] -dx12 = ["rendy/dx12"] -metal = ["rendy/metal"] -vulkan = ["rendy/vulkan"] +gl = ["gfx_device_gl"] -default = ["empty"] +default = [] [dependencies] common = { path = "../common" } # Graphics -winit = "0.18" -rendy = { git = "https://github.com/omni-viral/rendy" } +gfx = "0.17" +gfx_device_gl = { version = "0.15", optional = true } +gfx_window_glutin = "0.28" +glutin = "0.19" # Mathematics vek = "0.9" diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 209bfbe69e..60ae4fe4d3 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -3,10 +3,13 @@ mod render; mod window; // Standard -use std::mem; +use std::{ + any, + mem, +}; // Library -use winit; +use glutin; use failure; // Crate @@ -17,7 +20,7 @@ use crate::{ #[derive(Debug)] pub enum VoxygenErr { - WinitCreationErr(winit::CreationError), + BackendErr(Box), Other(failure::Error), } diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index ac4eb506ec..adbe185975 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -34,7 +34,8 @@ impl PlayState for TitleState { 1.0, )); global_state.window.renderer_mut().flush(); - global_state.window.display(); + global_state.window.display() + .expect("Failed to display window"); } } } diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index db1f83f747..0e4c0badf1 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -1,6 +1,7 @@ mod mesh; mod model; mod renderer; +mod pipelines; mod shader_set; // Reexports @@ -8,23 +9,11 @@ pub use self::{ mesh::Mesh, model::Model, shader_set::ShaderSet, - renderer::Renderer, + renderer::{Renderer, TgtColorFmt, TgtDepthFmt}, }; -// Library -use rendy; - -#[cfg(not(any(feature = "dx12", feature = "metal", feature = "vulkan")))] -type Backend = rendy::empty::Backend; - -#[cfg(feature = "dx12")] -type Backend = rendy::dx12::Backend; - -#[cfg(feature = "metal")] -type Backend = rendy::metal::Backend; - -#[cfg(feature = "vulkan")] -type Backend = rendy::vulkan::Backend; +#[cfg(feature = "gl")] +use gfx_device_gl as gfx_backend; /// Used to represent one of many possible errors that may be omitted by the rendering code #[derive(Debug)] diff --git a/voxygen/src/render/pipelines/character.rs b/voxygen/src/render/pipelines/character.rs new file mode 100644 index 0000000000..72aa4d19a4 --- /dev/null +++ b/voxygen/src/render/pipelines/character.rs @@ -0,0 +1,39 @@ +// Library +use gfx::{ + self, + VertexBuffer, + ConstantBuffer, + RenderTarget, + DepthTarget, + preset::depth::LESS_EQUAL_WRITE, + // Macros + gfx_defines, + gfx_vertex_struct_meta, + gfx_constant_struct_meta, + gfx_impl_struct_meta, + gfx_pipeline, + gfx_pipeline_inner, +}; + +// Local +use super::super::{ + renderer::{TgtColorFmt, TgtDepthFmt}, +}; + +gfx_defines! { + vertex Vertex { + pos: [f32; 3] = "v_pos", + bone: u8 = "v_bone", + } + + constant Locals { + model: [[f32; 4]; 4] = "u_model", + } + + pipeline pipe { + vbuf: VertexBuffer = (), + locals: ConstantBuffer = "locals", + tgt_color: RenderTarget = "tgt", + tgt_depth: DepthTarget = LESS_EQUAL_WRITE, + } +} diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs new file mode 100644 index 0000000000..771e7198e1 --- /dev/null +++ b/voxygen/src/render/pipelines/mod.rs @@ -0,0 +1 @@ +mod character; diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 9c8b6cade5..d3da83f0c3 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -1,24 +1,8 @@ // Library use vek::*; -use winit; -use lazy_static::lazy_static; -use rendy::{ - hal, - command::RenderPassInlineEncoder, - wsi::Surface, - graph::{ - Graph, - GraphBuilder, - NodeBuffer, - NodeImage, - present::PresentNode, - render::RenderPass, - }, - factory::{Config, Factory}, - memory::MemoryUsageValue, - resource::buffer::Buffer, - mesh::AsVertex, - shader::{Shader, ShaderKind, SourceLanguage, StaticShaderInfo}, +use gfx::{ + self, + traits::Device, }; // Crate @@ -31,188 +15,47 @@ use super::{ shader_set::ShaderSet, Pipeline, RenderErr, - Backend, + gfx_backend, }; -lazy_static! { - static ref VS: StaticShaderInfo = StaticShaderInfo::new( - concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/test/shader.vert"), - ShaderKind::Vertex, - SourceLanguage::GLSL, - "main", - ); +pub type TgtColorFmt = gfx::format::Srgba8; +pub type TgtDepthFmt = gfx::format::DepthStencil; - static ref FS: StaticShaderInfo = StaticShaderInfo::new( - concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/test/shader.frag"), - ShaderKind::Fragment, - SourceLanguage::GLSL, - "main", - ); -} - -#[derive(Debug)] -struct TriangleRenderPass { - vertex: Option>, -} - -impl RenderPass for TriangleRenderPass { - fn name() -> &'static str { "triangle" } - - fn vertices() -> Vec<( - Vec>, - hal::pso::ElemStride, - )> { vec![rendy::mesh::PosColor::VERTEX.gfx_vertex_input_desc()] } - - fn load_shader_sets<'a>( - storage: &'a mut Vec, - factory: &mut Factory, - _aux: &mut T, - ) -> Vec> { - storage.clear(); - storage.push(VS.module(factory).unwrap()); - storage.push(FS.module(factory).unwrap()); - - vec![hal::pso::GraphicsShaderSet { - vertex: hal::pso::EntryPoint { - entry: "main", - module: &storage[0], - specialization: hal::pso::Specialization::default(), - }, - fragment: Some(hal::pso::EntryPoint { - entry: "main", - module: &storage[1], - specialization: hal::pso::Specialization::default(), - }), - hull: None, - domain: None, - geometry: None, - }] - } - - fn build<'a>( - _factory: &mut Factory, - _aux: &mut T, - buffers: &mut [NodeBuffer<'a, B>], - images: &mut [NodeImage<'a, B>], - sets: &[impl AsRef<[B::DescriptorSetLayout]>], - ) -> Self { - assert!(buffers.is_empty()); - assert!(images.is_empty()); - assert_eq!(sets.len(), 1); - assert!(sets[0].as_ref().is_empty()); - - Self { - vertex: None, - } - } - - fn prepare(&mut self, factory: &mut Factory, _aux: &T) -> bool { - if self.vertex.is_some() { - return false; - } else { - let mut vbuf = factory.create_buffer( - 512, - rendy::mesh::PosColor::VERTEX.stride as u64 * 3, - (hal::buffer::Usage::VERTEX, MemoryUsageValue::Dynamic) - ).unwrap(); - - unsafe { - factory.upload_visible_buffer(&mut vbuf, 0, &[ - rendy::mesh::PosColor { - position: [0.0, -1.0, 0.0].into(), - color: [1.0, 0.0, 0.0, 1.0].into(), - }, - rendy::mesh::PosColor { - position: [1.0, 1.0, 0.0].into(), - color: [0.0, 1.0, 0.0, 1.0].into(), - }, - rendy::mesh::PosColor { - position: [-1.0, 1.0, 0.0].into(), - color: [0.0, 0.0, 1.0, 1.0].into(), - }, - ]).unwrap(); - } - - self.vertex = Some(vbuf); - - true - } - } - - fn draw( - &mut self, - _layouts: &[B::PipelineLayout], - pipelines: &[B::GraphicsPipeline], - mut encoder: RenderPassInlineEncoder<'_, B>, - _index: usize, - _aux: &T, - ) { - let vbuf = self.vertex.as_ref().unwrap(); - encoder.bind_graphics_pipeline(&pipelines[0]); - encoder.bind_vertex_buffers(0, Some((vbuf.raw(), 0))); - encoder.draw(0..3, 0..1); - } - - fn dispose(self, _factory: &mut Factory, _aux: &mut T) {} -} +pub type TgtColorView = gfx::handle::RenderTargetView; +pub type TgtDepthView = gfx::handle::DepthStencilView; pub struct Renderer { - //surface: Surface, - graph: Graph, - factory: Factory, + device: gfx_backend::Device, + encoder: gfx::Encoder, + factory: gfx_backend::Factory, + + tgt_color_view: TgtColorView, + tgt_depth_view: TgtDepthView, } impl Renderer { pub fn new( - window: winit::Window, + device: gfx_backend::Device, + mut factory: gfx_backend::Factory, + tgt_color_view: TgtColorView, + tgt_depth_view: TgtDepthView, ) -> Result { - let config: Config = Config::default(); - - let mut factory = Factory::::new(config) - .map_err(|err| VoxygenErr::Other(err))?; - - let surface = factory.create_surface(window); - - let mut graph_builder = GraphBuilder::::new(); - - let color_img = graph_builder.create_image( - surface.kind(), - 1, - hal::format::Format::Rgba8Unorm, - MemoryUsageValue::Data, - Some(hal::command::ClearValue::Color([1.0; 4].into())), - ); - - graph_builder.add_node( - TriangleRenderPass::builder() - .with_image(color_img) - ); - - graph_builder.add_node( - PresentNode::builder(surface) - .with_image(color_img) - ); - - let graph = graph_builder.build(&mut factory, &mut ()) - .map_err(|err| VoxygenErr::Other(err))?; - Ok(Self { - graph, + device, + encoder: factory.create_command_buffer().into(), factory, + tgt_color_view, + tgt_depth_view, }) } pub fn clear(&mut self, col: Rgba) { + self.encoder.clear(&self.tgt_color_view, col.into_array()); + self.encoder.clear_depth(&self.tgt_depth_view, 1.0); } pub fn flush(&mut self) { - self.graph.run(&mut self.factory, &mut ()); - } -} - -impl Drop for Renderer { - fn drop(&mut self) { - //self.graph.dispose(&mut self.factory, &mut ()); - //self.factory.dispose(); + self.encoder.flush(&mut self.device); + self.device.cleanup(); } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index fcc36efaab..4698be36c0 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -1,32 +1,59 @@ // Library -use winit; +use glutin; +use gfx_window_glutin; // Crate use crate::{ VoxygenErr, - render::Renderer, + render::{ + Renderer, + TgtColorFmt, + TgtDepthFmt, + }, }; pub struct Window { - events_loop: winit::EventsLoop, + events_loop: glutin::EventsLoop, + window: glutin::GlWindow, renderer: Renderer, } impl Window { pub fn new() -> Result { - let events_loop = winit::EventsLoop::new(); + let events_loop = glutin::EventsLoop::new(); - let window = winit::WindowBuilder::new() + let win_builder = glutin::WindowBuilder::new() .with_title("Veloren (Voxygen)") - .with_dimensions(winit::dpi::LogicalSize::new(800.0, 500.0)) + .with_dimensions(glutin::dpi::LogicalSize::new(800.0, 500.0)) .with_maximized(false) - .build(&events_loop) - .map_err(|err| VoxygenErr::WinitCreationErr(err))?; + ; + + let ctx_builder = glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))) + .with_vsync(true); + + let ( + window, + device, + factory, + tgt_color_view, + tgt_depth_view, + ) = gfx_window_glutin::init::( + win_builder, + ctx_builder, + &events_loop, + ).map_err(|err| VoxygenErr::BackendErr(Box::new(err)))?; let tmp = Ok(Self { events_loop, - renderer: Renderer::new(window)?, + window, + renderer: Renderer::new( + device, + factory, + tgt_color_view, + tgt_depth_view, + )?, }); tmp } @@ -37,8 +64,8 @@ impl Window { pub fn fetch_events(&mut self) -> Vec { let mut events = vec![]; self.events_loop.poll_events(|event| match event { - winit::Event::WindowEvent { event, .. } => match event { - winit::WindowEvent::CloseRequested => events.push(Event::Close), + glutin::Event::WindowEvent { event, .. } => match event { + glutin::WindowEvent::CloseRequested => events.push(Event::Close), _ => {}, }, _ => {}, @@ -46,8 +73,9 @@ impl Window { events } - pub fn display(&self) { - // TODO + pub fn display(&self) -> Result<(), VoxygenErr> { + self.window.swap_buffers() + .map_err(|err| VoxygenErr::BackendErr(Box::new(err))) } } From d559d633ab3858f3f5a758b9658bb25a39c1791a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 11 Jan 2019 20:14:37 +0000 Subject: [PATCH 05/21] Added pipeline components --- voxygen/shaders/skybox.frag | 25 ++++++ voxygen/shaders/skybox.vert | 30 +++++++ voxygen/src/main.rs | 8 ++ voxygen/src/menu/title.rs | 9 +- voxygen/src/render/consts.rs | 33 +++++++ voxygen/src/render/mesh.rs | 35 +++++++- voxygen/src/render/mod.rs | 25 ++++-- voxygen/src/render/model.rs | 30 +++++-- voxygen/src/render/pipelines/character.rs | 31 ++++--- voxygen/src/render/pipelines/mod.rs | 27 +++++- voxygen/src/render/pipelines/skybox.rs | 102 +++++++++++++++++++++ voxygen/src/render/renderer.rs | 104 +++++++++++++++++++++- voxygen/src/render/shader_set.rs | 23 ----- 13 files changed, 423 insertions(+), 59 deletions(-) create mode 100644 voxygen/shaders/skybox.frag create mode 100644 voxygen/shaders/skybox.vert create mode 100644 voxygen/src/render/consts.rs create mode 100644 voxygen/src/render/pipelines/skybox.rs delete mode 100644 voxygen/src/render/shader_set.rs diff --git a/voxygen/shaders/skybox.frag b/voxygen/shaders/skybox.frag new file mode 100644 index 0000000000..198b7a3f0e --- /dev/null +++ b/voxygen/shaders/skybox.frag @@ -0,0 +1,25 @@ +#version 330 core + +in vec3 f_pos; + +layout (std140) +uniform u_locals { + mat4 model_mat; +}; + +layout (std140) +uniform u_globals { + mat4 view_mat; + mat4 proj_mat; + vec4 cam_pos; + vec4 focus_pos; + vec4 view_distance; + vec4 tod; + vec4 time; +}; + +out vec4 tgt_color; + +void main() { + tgt_color = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/voxygen/shaders/skybox.vert b/voxygen/shaders/skybox.vert new file mode 100644 index 0000000000..a3e63adce3 --- /dev/null +++ b/voxygen/shaders/skybox.vert @@ -0,0 +1,30 @@ +#version 330 core + +in vec3 v_pos; + +layout (std140) +uniform u_locals { + mat4 model_mat; +}; + +layout (std140) +uniform u_globals { + mat4 view_mat; + mat4 proj_mat; + vec4 cam_pos; + vec4 focus_pos; + vec4 view_distance; + vec4 tod; + vec4 time; +}; + +out vec3 f_pos; + +void main() { + f_pos = v_pos; + + gl_Position = + proj_mat * + view_mat * + vec4(3000 * v_pos + cam_pos.xyz, 1); +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 60ae4fe4d3..adbeed6198 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -16,14 +16,22 @@ use failure; use crate::{ menu::title::TitleState, window::Window, + render::RenderErr, }; #[derive(Debug)] pub enum VoxygenErr { BackendErr(Box), + RenderErr(RenderErr), Other(failure::Error), } +impl From for VoxygenErr { + fn from(err: RenderErr) -> Self { + VoxygenErr::RenderErr(err) + } +} + // A type used to store state that is shared between all play states pub struct GlobalState { window: Window, diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index adbe185975..883f164693 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -17,6 +17,8 @@ impl TitleState { } } +const BG_COLOR: Rgba = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 }; + impl PlayState for TitleState { fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult { 'eventloop: loop { @@ -27,12 +29,7 @@ impl PlayState for TitleState { } } - global_state.window.renderer_mut().clear(Rgba::new( - 0.0, - 0.3, - 1.0, - 1.0, - )); + global_state.window.renderer_mut().clear(BG_COLOR); global_state.window.renderer_mut().flush(); global_state.window.display() .expect("Failed to display window"); diff --git a/voxygen/src/render/consts.rs b/voxygen/src/render/consts.rs new file mode 100644 index 0000000000..b73f6859e3 --- /dev/null +++ b/voxygen/src/render/consts.rs @@ -0,0 +1,33 @@ +// Library +use gfx::{ + self, + traits::FactoryExt, +}; + +// Local +use super::{ + RenderErr, + gfx_backend, +}; + +#[derive(Clone)] +pub struct Consts { + pub buf: gfx::handle::Buffer, +} + +impl Consts { + pub fn new(factory: &mut gfx_backend::Factory) -> Self { + Self { + buf: factory.create_constant_buffer(1), + } + } + + pub fn update( + &mut self, + encoder: &mut gfx::Encoder, + data: T, + ) -> Result<(), RenderErr> { + encoder.update_buffer(&self.buf, &[data], 0) + .map_err(|err| RenderErr::UpdateErr(err)) + } +} diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index 352e1e92df..83f05037ad 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -7,15 +7,46 @@ pub struct Mesh { } impl Mesh

{ - pub fn empty() -> Self { + pub fn new() -> Self { Self { verts: vec![] } } - pub fn verts(&self) -> &[P::Vertex] { + pub fn vertices(&self) -> &[P::Vertex] { &self.verts } pub fn push(&mut self, vert: P::Vertex) { self.verts.push(vert); } + + pub fn push_quad(&mut self, quad: Quad

) { + // Tri 1 + self.verts.push(quad.a.clone()); + self.verts.push(quad.b); + self.verts.push(quad.c.clone()); + + // Tri 2 + self.verts.push(quad.c); + self.verts.push(quad.d); + self.verts.push(quad.a); + } +} + +/// Represents a quad +pub struct Quad { + a: P::Vertex, + b: P::Vertex, + c: P::Vertex, + d: P::Vertex, +} + +impl Quad

{ + pub fn new( + a: P::Vertex, + b: P::Vertex, + c: P::Vertex, + d: P::Vertex, + ) -> Self { + Self { a, b, c, d } + } } diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 0e4c0badf1..ddac823f31 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -1,25 +1,38 @@ +mod consts; mod mesh; mod model; -mod renderer; mod pipelines; -mod shader_set; +mod renderer; // Reexports pub use self::{ - mesh::Mesh, + consts::Consts, + mesh::{Mesh, Quad}, model::Model, - shader_set::ShaderSet, renderer::{Renderer, TgtColorFmt, TgtDepthFmt}, + pipelines::{ + character::CharacterPipeline, + skybox::SkyboxPipeline, + }, }; #[cfg(feature = "gl")] use gfx_device_gl as gfx_backend; +// Library +use gfx; + /// Used to represent one of many possible errors that may be omitted by the rendering code #[derive(Debug)] -pub enum RenderErr {} +pub enum RenderErr { + PipelineErr(gfx::PipelineStateError), + UpdateErr(gfx::UpdateError), +} /// Used to represent a specific rendering configuration pub trait Pipeline { - type Vertex; + type Vertex: + Clone + + gfx::traits::Pod + + gfx::pso::buffer::Structure; } diff --git a/voxygen/src/render/model.rs b/voxygen/src/render/model.rs index b7aaf5d6f7..34394ea561 100644 --- a/voxygen/src/render/model.rs +++ b/voxygen/src/render/model.rs @@ -1,18 +1,36 @@ -// Standard -use std::marker::PhantomData; +// Library +use gfx::{ + self, + traits::FactoryExt, +}; // Local -use super::Pipeline; +use super::{ + mesh::Mesh, + Pipeline, + gfx_backend, +}; /// Represents a mesh that has been sent to the CPU pub struct Model { - phantom: PhantomData

, + pub vbuf: gfx::handle::Buffer, + pub slice: gfx::Slice, } impl Model

{ - pub fn new() -> Self { + pub fn new( + factory: &mut gfx_backend::Factory, + mesh: &Mesh

, + ) -> Self { Self { - phantom: PhantomData, + vbuf: factory.create_vertex_buffer(mesh.vertices()), + slice: gfx::Slice { + start: 0, + end: mesh.vertices().len() as u32, + base_vertex: 0, + instances: None, + buffer: gfx::IndexBuffer::Auto, + }, } } } diff --git a/voxygen/src/render/pipelines/character.rs b/voxygen/src/render/pipelines/character.rs index 72aa4d19a4..48c4edb3f5 100644 --- a/voxygen/src/render/pipelines/character.rs +++ b/voxygen/src/render/pipelines/character.rs @@ -1,11 +1,6 @@ // Library use gfx::{ self, - VertexBuffer, - ConstantBuffer, - RenderTarget, - DepthTarget, - preset::depth::LESS_EQUAL_WRITE, // Macros gfx_defines, gfx_vertex_struct_meta, @@ -16,8 +11,13 @@ use gfx::{ }; // Local -use super::super::{ - renderer::{TgtColorFmt, TgtDepthFmt}, +use super::{ + Globals, + super::{ + Pipeline, + TgtColorFmt, + TgtDepthFmt, + }, }; gfx_defines! { @@ -27,13 +27,20 @@ gfx_defines! { } constant Locals { - model: [[f32; 4]; 4] = "u_model", + model_mat: [[f32; 4]; 4] = "model_mat", } pipeline pipe { - vbuf: VertexBuffer = (), - locals: ConstantBuffer = "locals", - tgt_color: RenderTarget = "tgt", - tgt_depth: DepthTarget = LESS_EQUAL_WRITE, + vbuf: gfx::VertexBuffer = (), + locals: gfx::ConstantBuffer = "u_locals", + globals: gfx::ConstantBuffer = "u_globals", + tgt_color: gfx::RenderTarget = "tgt_color", + tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, } } + +pub struct CharacterPipeline; + +impl Pipeline for CharacterPipeline { + type Vertex = Vertex; +} diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 771e7198e1..6cf5a9f803 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -1 +1,26 @@ -mod character; +pub mod character; +pub mod skybox; + +// Library +use gfx::{ + self, + // Macros + gfx_defines, + gfx_vertex_struct_meta, + gfx_constant_struct_meta, + gfx_impl_struct_meta, + gfx_pipeline, + gfx_pipeline_inner, +}; + +gfx_defines! { + constant Globals { + view_mat: [[f32; 4]; 4] = "view_mat", + proj_mat: [[f32; 4]; 4] = "proj_mat", + cam_pos: [f32; 4] = "cam_pos", + focus_pos: [f32; 4] = "focus_pos", + view_distance: [f32; 4] = "view_distance", + tod: [f32; 4] = "tod", + time: [f32; 4] = "time", + } +} diff --git a/voxygen/src/render/pipelines/skybox.rs b/voxygen/src/render/pipelines/skybox.rs new file mode 100644 index 0000000000..888595316f --- /dev/null +++ b/voxygen/src/render/pipelines/skybox.rs @@ -0,0 +1,102 @@ +// Library +use gfx::{ + self, + // Macros + gfx_defines, + gfx_vertex_struct_meta, + gfx_constant_struct_meta, + gfx_impl_struct_meta, + gfx_pipeline, + gfx_pipeline_inner, +}; + +// Local +use super::{ + Globals, + super::{ + Pipeline, + TgtColorFmt, + TgtDepthFmt, + Mesh, + Quad, + }, +}; + +gfx_defines! { + vertex Vertex { + pos: [f32; 3] = "v_pos", + } + + constant Locals { + model_mat: [[f32; 4]; 4] = "model_mat", + } + + pipeline pipe { + vbuf: gfx::VertexBuffer = (), + locals: gfx::ConstantBuffer = "u_locals", + globals: gfx::ConstantBuffer = "u_globals", + tgt_color: gfx::RenderTarget = "tgt_color", + tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + } +} + +pub struct SkyboxPipeline; + +impl Pipeline for SkyboxPipeline { + type Vertex = Vertex; +} + +pub fn create_mesh() -> Mesh { + let mut mesh = Mesh::new(); + + // -x + #[rustfmt::skip] + mesh.push_quad(Quad::new( + Vertex { pos: [-1.0, -1.0, -1.0] }, + Vertex { pos: [-1.0, 1.0, -1.0] }, + Vertex { pos: [-1.0, 1.0, 1.0] }, + Vertex { pos: [-1.0, -1.0, 1.0] }, + )); + // +x + #[rustfmt::skip] + mesh.push_quad(Quad::new( + Vertex { pos: [ 1.0, -1.0, -1.0] }, + Vertex { pos: [ 1.0, 1.0, 1.0] }, + Vertex { pos: [ 1.0, 1.0, -1.0] }, + Vertex { pos: [ 1.0, -1.0, 1.0] }, + )); + // -y + #[rustfmt::skip] + mesh.push_quad(Quad::new( + Vertex { pos: [ 1.0, -1.0, -1.0] }, + Vertex { pos: [-1.0, -1.0, -1.0] }, + Vertex { pos: [-1.0, -1.0, 1.0] }, + Vertex { pos: [ 1.0, -1.0, 1.0] }, + )); + // +y + #[rustfmt::skip] + mesh.push_quad(Quad::new( + Vertex { pos: [ 1.0, 1.0, -1.0] }, + Vertex { pos: [-1.0, 1.0, 1.0] }, + Vertex { pos: [-1.0, 1.0, -1.0] }, + Vertex { pos: [ 1.0, 1.0, 1.0] }, + )); + // -z + #[rustfmt::skip] + mesh.push_quad(Quad::new( + Vertex { pos: [-1.0, -1.0, -1.0] }, + Vertex { pos: [ 1.0, -1.0, -1.0] }, + Vertex { pos: [ 1.0, 1.0, -1.0] }, + Vertex { pos: [-1.0, 1.0, -1.0] }, + )); + // +z + #[rustfmt::skip] + mesh.push_quad(Quad::new( + Vertex { pos: [-1.0, -1.0, 1.0] }, + Vertex { pos: [ 1.0, 1.0, 1.0] }, + Vertex { pos: [ 1.0, -1.0, 1.0] }, + Vertex { pos: [-1.0, 1.0, 1.0] }, + )); + + mesh +} diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index d3da83f0c3..3e0111a911 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -2,7 +2,7 @@ use vek::*; use gfx::{ self, - traits::Device, + traits::{Device, FactoryExt}, }; // Crate @@ -10,12 +10,17 @@ use crate::VoxygenErr; // Local use super::{ + consts::Consts, model::Model, mesh::Mesh, - shader_set::ShaderSet, Pipeline, RenderErr, gfx_backend, + pipelines::{ + Globals, + character, + skybox, + }, }; pub type TgtColorFmt = gfx::format::Srgba8; @@ -31,6 +36,9 @@ pub struct Renderer { tgt_color_view: TgtColorView, tgt_depth_view: TgtDepthView, + + skybox_pipeline: GfxPipeline>, + //character_pipeline: GfxPipeline>, } impl Renderer { @@ -39,13 +47,35 @@ impl Renderer { mut factory: gfx_backend::Factory, tgt_color_view: TgtColorView, tgt_depth_view: TgtDepthView, - ) -> Result { + ) -> Result { + // Construct a pipeline for rendering skyboxes + let skybox_pipeline = Self::create_pipeline( + &mut factory, + skybox::pipe::new(), + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/skybox.vert")), + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/skybox.frag")), + )?; + + // Construct a pipeline for rendering characters + /* + let character_pipeline = Self::new_pipeline( + &mut factory, + character::pipe::new(), + include_bytes!("shaders/character.vert"), + include_bytes!("shaders/character.frag"), + )?; + */ + Ok(Self { device, encoder: factory.create_command_buffer().into(), factory, + tgt_color_view, tgt_depth_view, + + skybox_pipeline, + //character_pipeline, }) } @@ -54,8 +84,76 @@ impl Renderer { self.encoder.clear_depth(&self.tgt_depth_view, 1.0); } + /// Perform all queued draw calls for this frame and clean up discarded items pub fn flush(&mut self) { self.encoder.flush(&mut self.device); self.device.cleanup(); } + + /// Create a new pipeline from the provided vertex shader and fragment shader + fn create_pipeline<'a, P: gfx::pso::PipelineInit>( + factory: &mut gfx_backend::Factory, + pipe: P, + vs: &[u8], + fs: &[u8], + ) -> Result, RenderErr> { + let program = factory + .link_program(vs, fs) + .map_err(|err| RenderErr::PipelineErr(gfx::PipelineStateError::Program(err)))?; + + Ok(GfxPipeline { + pso: factory.create_pipeline_from_program( + &program, + gfx::Primitive::TriangleList, + gfx::state::Rasterizer { + front_face: gfx::state::FrontFace::CounterClockwise, + cull_face: gfx::state::CullFace::Back, + method: gfx::state::RasterMethod::Fill, + offset: None, + samples: Some(gfx::state::MultiSample), + }, + pipe, + ) + // Do some funky things to work around an oddity in gfx's error ownership rules + .map_err(|err| RenderErr::PipelineErr(match err { + gfx::PipelineStateError::Program(err) => gfx::PipelineStateError::Program(err), + gfx::PipelineStateError::DescriptorInit(err) => gfx::PipelineStateError::DescriptorInit(err.into()), + gfx::PipelineStateError::DeviceCreate(err) => gfx::PipelineStateError::DeviceCreate(err), + }))?, + program, + }) + } + + /// Create a new model from the provided mesh + pub fn create_model(&mut self, mesh: &Mesh

) -> Result, RenderErr> { + Ok(Model::new( + &mut self.factory, + mesh, + )) + } + + /// Queue the rendering of the provided skybox model in the upcoming frame + pub fn render_skybox( + &mut self, + model: &Model, + locals: &Consts, + globals: &Consts, + ) { + self.encoder.draw( + &model.slice, + &self.skybox_pipeline.pso, + &skybox::pipe::Data { + vbuf: model.vbuf.clone(), + locals: locals.buf.clone(), + globals: globals.buf.clone(), + tgt_color: self.tgt_color_view.clone(), + tgt_depth: self.tgt_depth_view.clone(), + }, + ); + } +} + +pub struct GfxPipeline { + program: gfx::handle::Program, + pso: gfx::pso::PipelineState, } diff --git a/voxygen/src/render/shader_set.rs b/voxygen/src/render/shader_set.rs deleted file mode 100644 index d1e70d4cc4..0000000000 --- a/voxygen/src/render/shader_set.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Standard -use std::marker::PhantomData; - -// Local -use super::{ - Pipeline, - RenderErr -}; - -pub struct ShaderSet { - phantom: PhantomData

, -} - -impl ShaderSet

{ - pub fn new( - vs: &[u8], - fs: &[u8], - ) -> Result { - Ok(Self { - phantom: PhantomData, - }) - } -} From c9d877de7a4d814214f7d63364db193d2a46d58c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 11 Jan 2019 23:18:34 +0000 Subject: [PATCH 06/21] Added session, documented, improved structure --- voxygen/Cargo.toml | 2 + voxygen/shaders/character.frag | 25 +++++ voxygen/shaders/character.vert | 32 ++++++ voxygen/shaders/skybox.frag | 4 +- voxygen/shaders/skybox.vert | 5 +- voxygen/src/consts.rs | 34 ------- voxygen/src/error.rs | 22 +++++ voxygen/src/global_consts.rs | 18 ---- voxygen/src/main.rs | 82 ++++++++++------ voxygen/src/menu/title.rs | 23 ++++- voxygen/src/render/consts.rs | 14 ++- voxygen/src/render/mesh.rs | 34 ++++++- voxygen/src/render/mod.rs | 28 ++++-- voxygen/src/render/model.rs | 2 +- voxygen/src/render/pipelines/character.rs | 1 + voxygen/src/render/pipelines/mod.rs | 29 +++++- voxygen/src/render/pipelines/skybox.rs | 10 +- voxygen/src/render/renderer.rs | 114 ++++++++++++---------- voxygen/src/scene/camera.rs | 49 ++++++++++ voxygen/src/scene/mod.rs | 56 +++++++++++ voxygen/src/session.rs | 61 ++++++++++++ voxygen/src/window.rs | 15 +-- 22 files changed, 492 insertions(+), 168 deletions(-) create mode 100644 voxygen/shaders/character.frag create mode 100644 voxygen/shaders/character.vert delete mode 100644 voxygen/src/consts.rs create mode 100644 voxygen/src/error.rs delete mode 100644 voxygen/src/global_consts.rs create mode 100644 voxygen/src/scene/camera.rs create mode 100644 voxygen/src/scene/mod.rs create mode 100644 voxygen/src/session.rs diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 4b58ce03ff..5ae7ee5573 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -25,3 +25,5 @@ vek = "0.9" glsl-include = "0.2" failure = "0.1" lazy_static = "1.1" +log = "0.4" +pretty_env_logger = "0.3" diff --git a/voxygen/shaders/character.frag b/voxygen/shaders/character.frag new file mode 100644 index 0000000000..12b2bbe12a --- /dev/null +++ b/voxygen/shaders/character.frag @@ -0,0 +1,25 @@ +#version 330 core + +in vec3 f_pos; + +layout (std140) +uniform u_locals { + mat4 model_mat; +}; + +layout (std140) +uniform u_globals { + mat4 view_mat; + mat4 proj_mat; + vec4 cam_pos; + vec4 focus_pos; + vec4 view_distance; + vec4 tod; + vec4 time; +}; + +out vec4 tgt_color; + +void main() { + tgt_color = vec4(f_pos, 1.0); +} diff --git a/voxygen/shaders/character.vert b/voxygen/shaders/character.vert new file mode 100644 index 0000000000..885d6b40ba --- /dev/null +++ b/voxygen/shaders/character.vert @@ -0,0 +1,32 @@ +#version 330 core + +in vec3 v_pos; +in vec3 v_col; +in uint v_bone; + +layout (std140) +uniform u_locals { + mat4 model_mat; +}; + +layout (std140) +uniform u_globals { + mat4 view_mat; + mat4 proj_mat; + vec4 cam_pos; + vec4 focus_pos; + vec4 view_distance; + vec4 tod; + vec4 time; +}; + +out vec3 f_pos; + +void main() { + f_pos = v_pos; + + gl_Position = + proj_mat * + view_mat * + vec4(0.5 * v_pos + cam_pos.xyz, 1); +} diff --git a/voxygen/shaders/skybox.frag b/voxygen/shaders/skybox.frag index 198b7a3f0e..fbca11d0ea 100644 --- a/voxygen/shaders/skybox.frag +++ b/voxygen/shaders/skybox.frag @@ -4,7 +4,7 @@ in vec3 f_pos; layout (std140) uniform u_locals { - mat4 model_mat; + vec4 nul; }; layout (std140) @@ -21,5 +21,5 @@ uniform u_globals { out vec4 tgt_color; void main() { - tgt_color = vec4(1.0, 0.0, 0.0, 1.0); + tgt_color = vec4(f_pos, 1.0); } diff --git a/voxygen/shaders/skybox.vert b/voxygen/shaders/skybox.vert index a3e63adce3..f3243ebadb 100644 --- a/voxygen/shaders/skybox.vert +++ b/voxygen/shaders/skybox.vert @@ -4,7 +4,7 @@ in vec3 v_pos; layout (std140) uniform u_locals { - mat4 model_mat; + vec4 nul; }; layout (std140) @@ -26,5 +26,6 @@ void main() { gl_Position = proj_mat * view_mat * - vec4(3000 * v_pos + cam_pos.xyz, 1); + vec4(v_pos + cam_pos.xyz, 1); + gl_Position.z = 0.0; } diff --git a/voxygen/src/consts.rs b/voxygen/src/consts.rs deleted file mode 100644 index 95ec0a2564..0000000000 --- a/voxygen/src/consts.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Library -use gfx::{ - handle::Buffer, - traits::{FactoryExt, Pod}, -}; - -// Crate -use crate::render_ctx::{RenderCtx, Resources}; - -type ConstBuffer = Buffer; - -#[derive(Clone)] -pub struct ConstHandle { - buffer: ConstBuffer, -} - -impl ConstHandle { - pub fn new(render_ctx: &mut RenderCtx) -> Self { - Self { - buffer: render_ctx - .factory_mut() - .create_constant_buffer(1), - } - } - - pub fn update(&self, render_ctx: &mut RenderCtx, consts: T) { - render_ctx - .encoder_mut() - .update_buffer(&self.buffer, &[consts], 0) - .unwrap(); - } - - pub fn buffer(&self) -> &ConstBuffer { &self.buffer } -} diff --git a/voxygen/src/error.rs b/voxygen/src/error.rs new file mode 100644 index 0000000000..6bb4263508 --- /dev/null +++ b/voxygen/src/error.rs @@ -0,0 +1,22 @@ +// Standard +use std::any; + +// Crate +use crate::render::RenderError; + +/// Represents any error that may be triggered by Voxygen +#[derive(Debug)] +pub enum Error { + /// A miscellaneous error relating to a backend dependency + BackendError(Box), + /// An error relating the rendering subsystem + RenderError(RenderError), + // A miscellaneous error with an unknown or unspecified source + Other(failure::Error), +} + +impl From for Error { + fn from(err: RenderError) -> Self { + Error::RenderError(err) + } +} diff --git a/voxygen/src/global_consts.rs b/voxygen/src/global_consts.rs deleted file mode 100644 index 2045d35ef9..0000000000 --- a/voxygen/src/global_consts.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Library -use gfx::{ - // Urgh - gfx_defines, - gfx_constant_struct_meta, - gfx_impl_struct_meta, -}; - -gfx_defines! { - constant GlobalConsts { - view_mat: [[f32; 4]; 4] = "view_mat", - proj_mat: [[f32; 4]; 4] = "proj_mat", - cam_origin: [f32; 4] = "cam_origin", - play_origin: [f32; 4] = "play_origin", - view_distance: [f32; 4] = "view_distance", - time: [f32; 4] = "time", - } -} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index adbeed6198..38d2005d37 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -1,37 +1,28 @@ -mod menu; -mod render; -mod window; +pub mod error; +pub mod menu; +pub mod render; +pub mod scene; +pub mod session; +pub mod window; + +// Reexports +pub use crate::error::Error; // Standard -use std::{ - any, - mem, -}; +use std::mem; // Library use glutin; use failure; +use log; +use pretty_env_logger; // Crate use crate::{ menu::title::TitleState, window::Window, - render::RenderErr, }; -#[derive(Debug)] -pub enum VoxygenErr { - BackendErr(Box), - RenderErr(RenderErr), - Other(failure::Error), -} - -impl From for VoxygenErr { - fn from(err: RenderErr) -> Self { - VoxygenErr::RenderErr(err) - } -} - // A type used to store state that is shared between all play states pub struct GlobalState { window: Window, @@ -42,40 +33,73 @@ pub struct GlobalState { pub enum PlayStateResult { /// Pop all play states in reverse order and shut down the program Shutdown, - /// Close the current play state - Close, + /// Close the current play state and pop it from the play state stack + Pop, /// Push a new play state onto the play state stack Push(Box), /// Switch the current play state with a new play state Switch(Box), } +/// A trait representing a playable game state. This may be a menu, a game session, the title +/// screen, etc. pub trait PlayState { + /// Play the state until some change of state is required (i.e: a menu is opened or the game + /// is closed). fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult; + + /// Get a descriptive name for this state type + fn name(&self) -> &'static str; } fn main() { - let mut states: Vec> = vec![Box::new(TitleState::new())]; + // Init logging + pretty_env_logger::init(); + // Set up the initial play state + let mut states: Vec> = vec![Box::new(TitleState::new())]; + states.last().map(|current_state| { + log::info!("Started game with state '{}'", current_state.name()) + }); + + // Set up the global state let mut global_state = GlobalState { window: Window::new() .expect("Failed to create window"), }; + // What's going on here? + // --------------------- + // The state system used by Voxygen allows for the easy development of stack-based menus. + // For example, you may want a "title" state that can push a "main menu" state on top of it, + // which can in turn push a "settings" state or a "game session" state on top of it. + // The code below manages the state transfer logic automatically so that we don't have to + // re-engineer it for each menu we decide to add to the game. while let Some(state_result) = states.last_mut().map(|last| last.play(&mut global_state)){ // Implement state transfer logic match state_result { - PlayStateResult::Shutdown => while states.last().is_some() { - states.pop(); + PlayStateResult::Shutdown => { + log::info!("Shutting down all states..."); + while states.last().is_some() { + states.pop().map(|old_state| { + log::info!("Popped state '{}'", old_state.name()) + }); + } }, - PlayStateResult::Close => { - states.pop(); + PlayStateResult::Pop => { + states.pop().map(|old_state| { + log::info!("Popped state '{}'", old_state.name()) + }); }, PlayStateResult::Push(new_state) => { + log::info!("Pushed state '{}'", new_state.name()); states.push(new_state); }, PlayStateResult::Switch(mut new_state) => { - states.last_mut().map(|old_state| mem::swap(old_state, &mut new_state)); + states.last_mut().map(|old_state| { + log::info!("Switching to state '{}' from state '{}'", new_state.name(), old_state.name()); + mem::swap(old_state, &mut new_state); + }); }, } } diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index 883f164693..1586d882aa 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -7,32 +7,47 @@ use crate::{ PlayStateResult, GlobalState, window::Event, + render, + session::SessionState, }; pub struct TitleState; impl TitleState { + /// Create a new `TitleState` pub fn new() -> Self { Self } } -const BG_COLOR: Rgba = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 }; +// The background colour +const BG_COLOR: Rgba = Rgba { r: 0.8, g: 1.0, b: 0.8, a: 1.0 }; impl PlayState for TitleState { fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult { - 'eventloop: loop { - // Process window events + loop { + // Handle window events for event in global_state.window.fetch_events() { match event { - Event::Close => break 'eventloop PlayStateResult::Shutdown, + Event::Close => return PlayStateResult::Shutdown, + // When space is pressed, start a session + Event::Char(' ') => return PlayStateResult::Push( + Box::new(SessionState::from_renderer(global_state.window.renderer_mut())), + ), + // Ignore all other events + _ => {}, } } + // Clear the screen global_state.window.renderer_mut().clear(BG_COLOR); + + // Finish the frame global_state.window.renderer_mut().flush(); global_state.window.display() .expect("Failed to display window"); } } + + fn name(&self) -> &'static str { "Title" } } diff --git a/voxygen/src/render/consts.rs b/voxygen/src/render/consts.rs index b73f6859e3..1c15b0362a 100644 --- a/voxygen/src/render/consts.rs +++ b/voxygen/src/render/consts.rs @@ -6,28 +6,32 @@ use gfx::{ // Local use super::{ - RenderErr, + RenderError, gfx_backend, }; +/// A handle to a series of constants sitting on the GPU. This is used to hold information used in +/// the rendering process that does not change throughout a single render pass. #[derive(Clone)] pub struct Consts { pub buf: gfx::handle::Buffer, } impl Consts { + /// Create a new `Const` pub fn new(factory: &mut gfx_backend::Factory) -> Self { Self { buf: factory.create_constant_buffer(1), } } + /// Update the GPU-side value represented by this constant handle. pub fn update( &mut self, encoder: &mut gfx::Encoder, - data: T, - ) -> Result<(), RenderErr> { - encoder.update_buffer(&self.buf, &[data], 0) - .map_err(|err| RenderErr::UpdateErr(err)) + val: T, + ) -> Result<(), RenderError> { + encoder.update_buffer(&self.buf, &[val], 0) + .map_err(|err| RenderError::UpdateError(err)) } } diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index 83f05037ad..75e317845c 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -1,25 +1,38 @@ // Local use super::Pipeline; -/// Used to store vertex data on the CPU +/// A `Vec`-based mesh structure used to store mesh data on the CPU. pub struct Mesh { verts: Vec, } impl Mesh

{ + /// Create a new `Mesh` pub fn new() -> Self { Self { verts: vec![] } } + /// Get a slice referencing the vertices of this mesh. pub fn vertices(&self) -> &[P::Vertex] { &self.verts } + /// Push a new vertex onto the end of this mesh. pub fn push(&mut self, vert: P::Vertex) { self.verts.push(vert); } + /// Push a new polygon onto the end of this mesh. + pub fn push_tri(&mut self, tri: Tri

) { + self.verts.push(tri.a); + self.verts.push(tri.b); + self.verts.push(tri.c); + } + + /// Push a new quad onto the end of this mesh. pub fn push_quad(&mut self, quad: Quad

) { + // A quad is composed of two triangles. The code below converts the former to the latter. + // Tri 1 self.verts.push(quad.a.clone()); self.verts.push(quad.b); @@ -32,7 +45,24 @@ impl Mesh

{ } } -/// Represents a quad +/// Represents a triangle stored on the CPU. +pub struct Tri { + a: P::Vertex, + b: P::Vertex, + c: P::Vertex, +} + +impl Tri

{ + pub fn new( + a: P::Vertex, + b: P::Vertex, + c: P::Vertex, + ) -> Self { + Self { a, b, c } + } +} + +/// Represents a quad stored on the CPU. pub struct Quad { a: P::Vertex, b: P::Vertex, diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index ddac823f31..e8ad2f52ac 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -1,8 +1,8 @@ -mod consts; -mod mesh; -mod model; -mod pipelines; -mod renderer; +pub mod consts; +pub mod mesh; +pub mod model; +pub mod pipelines; +pub mod renderer; // Reexports pub use self::{ @@ -11,8 +11,16 @@ pub use self::{ model::Model, renderer::{Renderer, TgtColorFmt, TgtDepthFmt}, pipelines::{ - character::CharacterPipeline, - skybox::SkyboxPipeline, + Globals, + character::{ + CharacterPipeline, + Locals as CharacterLocals, + }, + skybox::{ + create_mesh as create_skybox_mesh, + SkyboxPipeline, + Locals as SkyboxLocals, + }, }, }; @@ -24,9 +32,9 @@ use gfx; /// Used to represent one of many possible errors that may be omitted by the rendering code #[derive(Debug)] -pub enum RenderErr { - PipelineErr(gfx::PipelineStateError), - UpdateErr(gfx::UpdateError), +pub enum RenderError { + PipelineError(gfx::PipelineStateError), + UpdateError(gfx::UpdateError), } /// Used to represent a specific rendering configuration diff --git a/voxygen/src/render/model.rs b/voxygen/src/render/model.rs index 34394ea561..057abd3e0a 100644 --- a/voxygen/src/render/model.rs +++ b/voxygen/src/render/model.rs @@ -11,7 +11,7 @@ use super::{ gfx_backend, }; -/// Represents a mesh that has been sent to the CPU +/// Represents a mesh that has been sent to the GPU. pub struct Model { pub vbuf: gfx::handle::Buffer, pub slice: gfx::Slice, diff --git a/voxygen/src/render/pipelines/character.rs b/voxygen/src/render/pipelines/character.rs index 48c4edb3f5..8b95b86f91 100644 --- a/voxygen/src/render/pipelines/character.rs +++ b/voxygen/src/render/pipelines/character.rs @@ -23,6 +23,7 @@ use super::{ gfx_defines! { vertex Vertex { pos: [f32; 3] = "v_pos", + col: [f32; 3] = "v_col", bone: u8 = "v_bone", } diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 6cf5a9f803..ba8d1581c9 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -6,12 +6,10 @@ use gfx::{ self, // Macros gfx_defines, - gfx_vertex_struct_meta, gfx_constant_struct_meta, gfx_impl_struct_meta, - gfx_pipeline, - gfx_pipeline_inner, }; +use vek::*; gfx_defines! { constant Globals { @@ -24,3 +22,28 @@ gfx_defines! { time: [f32; 4] = "time", } } + +impl Globals { + pub fn new() -> Self { + // TODO: Get rid of this ugliness + #[rustfmt::skip] + fn f32_arr_to_mat(arr: [f32; 16]) -> [[f32; 4]; 4] { + [ + [arr[ 0], arr[ 1], arr[ 2], arr[ 3]], + [arr[ 4], arr[ 5], arr[ 6], arr[ 7]], + [arr[ 8], arr[ 9], arr[10], arr[11]], + [arr[12], arr[13], arr[14], arr[15]], + ] + } + + Self { + view_mat: f32_arr_to_mat(Mat4::identity().into_col_array()), + proj_mat: f32_arr_to_mat(Mat4::identity().into_col_array()), + cam_pos: [0.0; 4], + focus_pos: [0.0; 4], + view_distance: [0.0; 4], + tod: [0.0; 4], + time: [0.0; 4], + } + } +} diff --git a/voxygen/src/render/pipelines/skybox.rs b/voxygen/src/render/pipelines/skybox.rs index 888595316f..c6d0bd3205 100644 --- a/voxygen/src/render/pipelines/skybox.rs +++ b/voxygen/src/render/pipelines/skybox.rs @@ -28,7 +28,7 @@ gfx_defines! { } constant Locals { - model_mat: [[f32; 4]; 4] = "model_mat", + nul: [f32; 4] = "nul", } pipeline pipe { @@ -36,7 +36,13 @@ gfx_defines! { locals: gfx::ConstantBuffer = "u_locals", globals: gfx::ConstantBuffer = "u_globals", tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + tgt_depth: gfx::DepthTarget = gfx::preset::depth::PASS_TEST, + } +} + +impl Locals { + pub fn new() -> Self { + Self { nul: [0.0; 4] } } } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 3e0111a911..7410a2072c 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -5,16 +5,13 @@ use gfx::{ traits::{Device, FactoryExt}, }; -// Crate -use crate::VoxygenErr; - // Local use super::{ consts::Consts, model::Model, mesh::Mesh, Pipeline, - RenderErr, + RenderError, gfx_backend, pipelines::{ Globals, @@ -23,12 +20,19 @@ use super::{ }, }; +/// Represents the format of the window's color target. pub type TgtColorFmt = gfx::format::Srgba8; +/// Represents the format of the window's depth target. pub type TgtDepthFmt = gfx::format::DepthStencil; +/// A handle to a window color target. pub type TgtColorView = gfx::handle::RenderTargetView; +/// A handle to a window depth target. pub type TgtDepthView = gfx::handle::DepthStencilView; +/// A type that encapsulates rendering state. `Renderer` is central to Voxygen's rendering +/// subsystem and contains any state necessary to interact with the GPU, along with pipeline state +/// objects (PSOs) needed to renderer different kinds of model to the screen. pub struct Renderer { device: gfx_backend::Device, encoder: gfx::Encoder, @@ -38,18 +42,19 @@ pub struct Renderer { tgt_depth_view: TgtDepthView, skybox_pipeline: GfxPipeline>, - //character_pipeline: GfxPipeline>, + character_pipeline: GfxPipeline>, } impl Renderer { + /// Create a new `Renderer` from a variety of backend-specific components and the window targets. pub fn new( device: gfx_backend::Device, mut factory: gfx_backend::Factory, tgt_color_view: TgtColorView, tgt_depth_view: TgtDepthView, - ) -> Result { + ) -> Result { // Construct a pipeline for rendering skyboxes - let skybox_pipeline = Self::create_pipeline( + let skybox_pipeline = create_pipeline( &mut factory, skybox::pipe::new(), include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/skybox.vert")), @@ -57,14 +62,12 @@ impl Renderer { )?; // Construct a pipeline for rendering characters - /* - let character_pipeline = Self::new_pipeline( + let character_pipeline = create_pipeline( &mut factory, character::pipe::new(), - include_bytes!("shaders/character.vert"), - include_bytes!("shaders/character.frag"), + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/character.vert")), + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/character.frag")), )?; - */ Ok(Self { device, @@ -75,57 +78,36 @@ impl Renderer { tgt_depth_view, skybox_pipeline, - //character_pipeline, + character_pipeline, }) } + /// Queue the clearing of the color and depth targets ready for a new frame to be rendered. pub fn clear(&mut self, col: Rgba) { self.encoder.clear(&self.tgt_color_view, col.into_array()); self.encoder.clear_depth(&self.tgt_depth_view, 1.0); } - /// Perform all queued draw calls for this frame and clean up discarded items + /// Perform all queued draw calls for this frame and clean up discarded items. pub fn flush(&mut self) { self.encoder.flush(&mut self.device); self.device.cleanup(); } - /// Create a new pipeline from the provided vertex shader and fragment shader - fn create_pipeline<'a, P: gfx::pso::PipelineInit>( - factory: &mut gfx_backend::Factory, - pipe: P, - vs: &[u8], - fs: &[u8], - ) -> Result, RenderErr> { - let program = factory - .link_program(vs, fs) - .map_err(|err| RenderErr::PipelineErr(gfx::PipelineStateError::Program(err)))?; - - Ok(GfxPipeline { - pso: factory.create_pipeline_from_program( - &program, - gfx::Primitive::TriangleList, - gfx::state::Rasterizer { - front_face: gfx::state::FrontFace::CounterClockwise, - cull_face: gfx::state::CullFace::Back, - method: gfx::state::RasterMethod::Fill, - offset: None, - samples: Some(gfx::state::MultiSample), - }, - pipe, - ) - // Do some funky things to work around an oddity in gfx's error ownership rules - .map_err(|err| RenderErr::PipelineErr(match err { - gfx::PipelineStateError::Program(err) => gfx::PipelineStateError::Program(err), - gfx::PipelineStateError::DescriptorInit(err) => gfx::PipelineStateError::DescriptorInit(err.into()), - gfx::PipelineStateError::DeviceCreate(err) => gfx::PipelineStateError::DeviceCreate(err), - }))?, - program, - }) + /// Create a new set of constants. + pub fn create_consts(&mut self) -> Result, RenderError> { + Ok(Consts::new(&mut self.factory)) } - /// Create a new model from the provided mesh - pub fn create_model(&mut self, mesh: &Mesh

) -> Result, RenderErr> { + /// Create a new set of constants and update then with a value. + pub fn create_consts_with(&mut self, val: T) -> Result, RenderError> { + let mut consts = self.create_consts()?; + consts.update(&mut self.encoder, val)?; + Ok(consts) + } + + /// Create a new model from the provided mesh. + pub fn create_model(&mut self, mesh: &Mesh

) -> Result, RenderError> { Ok(Model::new( &mut self.factory, mesh, @@ -153,7 +135,41 @@ impl Renderer { } } -pub struct GfxPipeline { +struct GfxPipeline { program: gfx::handle::Program, pso: gfx::pso::PipelineState, } + +/// Create a new pipeline from the provided vertex shader and fragment shader +fn create_pipeline<'a, P: gfx::pso::PipelineInit>( + factory: &mut gfx_backend::Factory, + pipe: P, + vs: &[u8], + fs: &[u8], +) -> Result, RenderError> { + let program = factory + .link_program(vs, fs) + .map_err(|err| RenderError::PipelineError(gfx::PipelineStateError::Program(err)))?; + + Ok(GfxPipeline { + pso: factory.create_pipeline_from_program( + &program, + gfx::Primitive::TriangleList, + gfx::state::Rasterizer { + front_face: gfx::state::FrontFace::CounterClockwise, + cull_face: gfx::state::CullFace::Back, + method: gfx::state::RasterMethod::Fill, + offset: None, + samples: Some(gfx::state::MultiSample), + }, + pipe, + ) + // Do some funky things to work around an oddity in gfx's error ownership rules + .map_err(|err| RenderError::PipelineError(match err { + gfx::PipelineStateError::Program(err) => gfx::PipelineStateError::Program(err), + gfx::PipelineStateError::DescriptorInit(err) => gfx::PipelineStateError::DescriptorInit(err.into()), + gfx::PipelineStateError::DeviceCreate(err) => gfx::PipelineStateError::DeviceCreate(err), + }))?, + program, + }) +} diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs new file mode 100644 index 0000000000..503b51bd01 --- /dev/null +++ b/voxygen/src/scene/camera.rs @@ -0,0 +1,49 @@ +// Standard +use std::f32::consts::PI; + +// Library +use vek::*; + +const NEAR_PLANE: f32 = 0.1; +const FAR_PLANE: f32 = 10000.0; + +pub struct Camera { + focus: Vec3, + ori: Vec3, + dist: f32, + fov: f32, + aspect: f32, +} + +impl Camera { + /// Create a new `Camera` with default parameters. + pub fn new() -> Self { + Self { + focus: Vec3::zero(), + ori: Vec3::zero(), + dist: 10.0, + fov: 1.3, + aspect: 1.618, + } + } + + /// Compute the transformation matrices (view matrix and projection matrix) for the camera. + pub fn compute_matrices(&self) -> (Mat4, Mat4) { + let view = Mat4::::identity() + * Mat4::translation_3d(-Vec3::unit_z() * self.dist) + * Mat4::rotation_z(self.ori.z) + * Mat4::rotation_x(self.ori.y) + * Mat4::rotation_y(self.ori.x) + * Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x()) + * Mat4::translation_3d(-self.focus); + + let proj = Mat4::perspective_rh_no( + self.fov, + self.aspect, + NEAR_PLANE, + FAR_PLANE, + ); + + (view, proj) + } +} diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs new file mode 100644 index 0000000000..27d2a6f0f0 --- /dev/null +++ b/voxygen/src/scene/mod.rs @@ -0,0 +1,56 @@ +pub mod camera; + +// Crate +use crate::render::{ + Consts, + Globals, + Model, + Renderer, + SkyboxPipeline, + SkyboxLocals, + create_skybox_mesh, +}; + +// Local +use self::camera::Camera; + +struct Skybox { + model: Model, + locals: Consts, +} + +pub struct Scene { + camera: Camera, + globals: Consts, + skybox: Skybox, +} + +impl Scene { + /// Create a new `Scene` with default parameters. + pub fn new(renderer: &mut Renderer) -> Self { + Self { + camera: Camera::new(), + globals: renderer + .create_consts_with(Globals::new()) + .unwrap(), + skybox: Skybox { + model: renderer + .create_model(&create_skybox_mesh()) + .unwrap(), + locals: renderer + .create_consts_with(SkyboxLocals::new()) + .unwrap(), + }, + } + } + + /// Render the scene using the provided `Renderer` + pub fn render_to(&self, renderer: &mut Renderer) { + // Render the skybox first (it appears over everything else so must be rendered first) + renderer.render_skybox( + &self.skybox.model, + &self.skybox.locals, + &self.globals, + ); + } +} diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs new file mode 100644 index 0000000000..b02a7d0469 --- /dev/null +++ b/voxygen/src/session.rs @@ -0,0 +1,61 @@ +// Library +use vek::*; + +// Crate +use crate::{ + PlayState, + PlayStateResult, + GlobalState, + window::Event, + render::Renderer, + scene::Scene, +}; + +pub struct SessionState { + scene: Scene, +} + +/// Represents an active game session (i.e: one that is being played) +impl SessionState { + /// Create a new `SessionState` + pub fn from_renderer(renderer: &mut Renderer) -> Self { + Self { + // Create a scene for this session. The scene handles visible elements of the game world + scene: Scene::new(renderer), + } + } +} + +// The background colour +const BG_COLOR: Rgba = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 }; + +impl PlayState for SessionState { + fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult { + // Game loop + loop { + // Handle window events + for event in global_state.window.fetch_events() { + match event { + Event::Close => return PlayStateResult::Shutdown, + // When 'q' is pressed, exit the session + Event::Char('q') => return PlayStateResult::Pop, + // Ignore all other events + _ => {}, + } + } + + // Clear the screen + global_state.window.renderer_mut().clear(BG_COLOR); + + // Render the screen using the global renderer + self.scene.render_to(global_state.window.renderer_mut()); + + // Finish the frame + global_state.window.renderer_mut().flush(); + global_state.window.display() + .expect("Failed to display window"); + } + } + + fn name(&self) -> &'static str { "Session" } +} diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 4698be36c0..fc2eb059f7 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -4,7 +4,7 @@ use gfx_window_glutin; // Crate use crate::{ - VoxygenErr, + Error, render::{ Renderer, TgtColorFmt, @@ -20,14 +20,13 @@ pub struct Window { impl Window { - pub fn new() -> Result { + pub fn new() -> Result { let events_loop = glutin::EventsLoop::new(); let win_builder = glutin::WindowBuilder::new() .with_title("Veloren (Voxygen)") .with_dimensions(glutin::dpi::LogicalSize::new(800.0, 500.0)) - .with_maximized(false) - ; + .with_maximized(false); let ctx_builder = glutin::ContextBuilder::new() .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))) @@ -43,7 +42,7 @@ impl Window { win_builder, ctx_builder, &events_loop, - ).map_err(|err| VoxygenErr::BackendErr(Box::new(err)))?; + ).map_err(|err| Error::BackendError(Box::new(err)))?; let tmp = Ok(Self { events_loop, @@ -66,6 +65,7 @@ impl Window { self.events_loop.poll_events(|event| match event { glutin::Event::WindowEvent { event, .. } => match event { glutin::WindowEvent::CloseRequested => events.push(Event::Close), + glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), _ => {}, }, _ => {}, @@ -73,12 +73,13 @@ impl Window { events } - pub fn display(&self) -> Result<(), VoxygenErr> { + pub fn display(&self) -> Result<(), Error> { self.window.swap_buffers() - .map_err(|err| VoxygenErr::BackendErr(Box::new(err))) + .map_err(|err| Error::BackendError(Box::new(err))) } } pub enum Event { Close, + Char(char), } From 45d5a0a39615c42eda48bb40dce6a8c65bda2488 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 12 Jan 2019 01:14:58 +0000 Subject: [PATCH 07/21] Added cursor trapping, more documentation --- voxygen/shaders/character.frag | 2 +- voxygen/shaders/character.vert | 2 +- voxygen/shaders/skybox.frag | 2 +- voxygen/shaders/skybox.vert | 2 +- voxygen/src/main.rs | 16 ++++++--- voxygen/src/menu/title.rs | 6 ++-- voxygen/src/render/mod.rs | 1 + voxygen/src/render/pipelines/mod.rs | 46 +++++++++++++++++--------- voxygen/src/render/pipelines/skybox.rs | 2 +- voxygen/src/render/renderer.rs | 30 ++++++++++++----- voxygen/src/render/util.rs | 10 ++++++ voxygen/src/scene/camera.rs | 18 +++++++--- voxygen/src/scene/mod.rs | 21 ++++++++++-- voxygen/src/session.rs | 11 ++++-- voxygen/src/window.rs | 20 ++++++++++- 15 files changed, 143 insertions(+), 46 deletions(-) create mode 100644 voxygen/src/render/util.rs diff --git a/voxygen/shaders/character.frag b/voxygen/shaders/character.frag index 12b2bbe12a..6caf3768ba 100644 --- a/voxygen/shaders/character.frag +++ b/voxygen/shaders/character.frag @@ -14,7 +14,7 @@ uniform u_globals { vec4 cam_pos; vec4 focus_pos; vec4 view_distance; - vec4 tod; + vec4 time_of_day; vec4 time; }; diff --git a/voxygen/shaders/character.vert b/voxygen/shaders/character.vert index 885d6b40ba..0fa6bc639b 100644 --- a/voxygen/shaders/character.vert +++ b/voxygen/shaders/character.vert @@ -16,7 +16,7 @@ uniform u_globals { vec4 cam_pos; vec4 focus_pos; vec4 view_distance; - vec4 tod; + vec4 time_of_day; vec4 time; }; diff --git a/voxygen/shaders/skybox.frag b/voxygen/shaders/skybox.frag index fbca11d0ea..b39330e15e 100644 --- a/voxygen/shaders/skybox.frag +++ b/voxygen/shaders/skybox.frag @@ -14,7 +14,7 @@ uniform u_globals { vec4 cam_pos; vec4 focus_pos; vec4 view_distance; - vec4 tod; + vec4 time_of_day; vec4 time; }; diff --git a/voxygen/shaders/skybox.vert b/voxygen/shaders/skybox.vert index f3243ebadb..3b58fcfd18 100644 --- a/voxygen/shaders/skybox.vert +++ b/voxygen/shaders/skybox.vert @@ -14,7 +14,7 @@ uniform u_globals { vec4 cam_pos; vec4 focus_pos; vec4 view_distance; - vec4 tod; + vec4 time_of_day; vec4 time; }; diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 38d2005d37..0cc40a4c79 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -12,8 +12,6 @@ pub use crate::error::Error; use std::mem; // Library -use glutin; -use failure; use log; use pretty_env_logger; @@ -28,6 +26,12 @@ pub struct GlobalState { window: Window, } +impl GlobalState { + pub fn on_play_state_changed(&mut self) { + self.window.untrap_cursor(); + } +} + // States can either close (and revert to a previous state), push a new state on top of themselves, // or switch to a totally different state pub enum PlayStateResult { @@ -82,23 +86,27 @@ fn main() { log::info!("Shutting down all states..."); while states.last().is_some() { states.pop().map(|old_state| { - log::info!("Popped state '{}'", old_state.name()) + log::info!("Popped state '{}'", old_state.name()); + global_state.on_play_state_changed(); }); } }, PlayStateResult::Pop => { states.pop().map(|old_state| { - log::info!("Popped state '{}'", old_state.name()) + log::info!("Popped state '{}'", old_state.name()); + global_state.on_play_state_changed(); }); }, PlayStateResult::Push(new_state) => { log::info!("Pushed state '{}'", new_state.name()); states.push(new_state); + global_state.on_play_state_changed(); }, PlayStateResult::Switch(mut new_state) => { states.last_mut().map(|old_state| { log::info!("Switching to state '{}' from state '{}'", new_state.name(), old_state.name()); mem::swap(old_state, &mut new_state); + global_state.on_play_state_changed(); }); }, } diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index 1586d882aa..91c647f993 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -7,7 +7,6 @@ use crate::{ PlayStateResult, GlobalState, window::Event, - render, session::SessionState, }; @@ -44,8 +43,9 @@ impl PlayState for TitleState { // Finish the frame global_state.window.renderer_mut().flush(); - global_state.window.display() - .expect("Failed to display window"); + global_state.window + .swap_buffers() + .expect("Failed to swap window buffers"); } } diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index e8ad2f52ac..459be2ac0e 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -3,6 +3,7 @@ pub mod mesh; pub mod model; pub mod pipelines; pub mod renderer; +mod util; // Reexports pub use self::{ diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index ba8d1581c9..1283c0bab6 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -11,6 +11,9 @@ use gfx::{ }; use vek::*; +// Local +use super::util::arr_to_mat; + gfx_defines! { constant Globals { view_mat: [[f32; 4]; 4] = "view_mat", @@ -18,32 +21,43 @@ gfx_defines! { cam_pos: [f32; 4] = "cam_pos", focus_pos: [f32; 4] = "focus_pos", view_distance: [f32; 4] = "view_distance", - tod: [f32; 4] = "tod", + time_of_day: [f32; 4] = "time_of_day", time: [f32; 4] = "time", } } impl Globals { - pub fn new() -> Self { - // TODO: Get rid of this ugliness - #[rustfmt::skip] - fn f32_arr_to_mat(arr: [f32; 16]) -> [[f32; 4]; 4] { - [ - [arr[ 0], arr[ 1], arr[ 2], arr[ 3]], - [arr[ 4], arr[ 5], arr[ 6], arr[ 7]], - [arr[ 8], arr[ 9], arr[10], arr[11]], - [arr[12], arr[13], arr[14], arr[15]], - ] - } - + /// Create global consts with default values. + pub fn default() -> Self { Self { - view_mat: f32_arr_to_mat(Mat4::identity().into_col_array()), - proj_mat: f32_arr_to_mat(Mat4::identity().into_col_array()), + view_mat: arr_to_mat(Mat4::identity().into_col_array()), + proj_mat: arr_to_mat(Mat4::identity().into_col_array()), cam_pos: [0.0; 4], focus_pos: [0.0; 4], view_distance: [0.0; 4], - tod: [0.0; 4], + time_of_day: [0.0; 4], time: [0.0; 4], } } + + /// Create global consts from the provided parameters. + pub fn new( + view_mat: Mat4, + proj_mat: Mat4, + cam_pos: Vec3, + focus_pos: Vec3, + view_distance: f32, + time_of_day: f32, + time: f32, + ) -> Self { + Self { + view_mat: arr_to_mat(view_mat.into_col_array()), + proj_mat: arr_to_mat(proj_mat.into_col_array()), + cam_pos: Vec4::from(cam_pos).into_array(), + focus_pos: Vec4::from(focus_pos).into_array(), + view_distance: [view_distance; 4], + time_of_day: [time_of_day; 4], + time: [time; 4], + } + } } diff --git a/voxygen/src/render/pipelines/skybox.rs b/voxygen/src/render/pipelines/skybox.rs index c6d0bd3205..2e6c4c1856 100644 --- a/voxygen/src/render/pipelines/skybox.rs +++ b/voxygen/src/render/pipelines/skybox.rs @@ -41,7 +41,7 @@ gfx_defines! { } impl Locals { - pub fn new() -> Self { + pub fn default() -> Self { Self { nul: [0.0; 4] } } } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 7410a2072c..7388bdfc61 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -46,7 +46,8 @@ pub struct Renderer { } impl Renderer { - /// Create a new `Renderer` from a variety of backend-specific components and the window targets. + /// Create a new `Renderer` from a variety of backend-specific components and the window + /// targets. pub fn new( device: gfx_backend::Device, mut factory: gfx_backend::Factory, @@ -99,13 +100,25 @@ impl Renderer { Ok(Consts::new(&mut self.factory)) } - /// Create a new set of constants and update then with a value. - pub fn create_consts_with(&mut self, val: T) -> Result, RenderError> { + /// Create a new set of constants with a value. + pub fn create_consts_with( + &mut self, + val: T + ) -> Result, RenderError> { let mut consts = self.create_consts()?; consts.update(&mut self.encoder, val)?; Ok(consts) } + /// Update a set of constants with a new value. + pub fn update_consts( + &mut self, + consts: &mut Consts, + val: T + ) -> Result<(), RenderError> { + consts.update(&mut self.encoder, val) + } + /// Create a new model from the provided mesh. pub fn create_model(&mut self, mesh: &Mesh

) -> Result, RenderError> { Ok(Model::new( @@ -136,7 +149,6 @@ impl Renderer { } struct GfxPipeline { - program: gfx::handle::Program, pso: gfx::pso::PipelineState, } @@ -166,10 +178,12 @@ fn create_pipeline<'a, P: gfx::pso::PipelineInit>( ) // Do some funky things to work around an oddity in gfx's error ownership rules .map_err(|err| RenderError::PipelineError(match err { - gfx::PipelineStateError::Program(err) => gfx::PipelineStateError::Program(err), - gfx::PipelineStateError::DescriptorInit(err) => gfx::PipelineStateError::DescriptorInit(err.into()), - gfx::PipelineStateError::DeviceCreate(err) => gfx::PipelineStateError::DeviceCreate(err), + gfx::PipelineStateError::Program(err) => + gfx::PipelineStateError::Program(err), + gfx::PipelineStateError::DescriptorInit(err) => + gfx::PipelineStateError::DescriptorInit(err.into()), + gfx::PipelineStateError::DeviceCreate(err) => + gfx::PipelineStateError::DeviceCreate(err), }))?, - program, }) } diff --git a/voxygen/src/render/util.rs b/voxygen/src/render/util.rs new file mode 100644 index 0000000000..a1e64ecdb7 --- /dev/null +++ b/voxygen/src/render/util.rs @@ -0,0 +1,10 @@ +// TODO: Get rid of this ugliness +#[rustfmt::skip] +pub fn arr_to_mat(arr: [f32; 16]) -> [[f32; 4]; 4] { + [ + [arr[ 0], arr[ 1], arr[ 2], arr[ 3]], + [arr[ 4], arr[ 5], arr[ 6], arr[ 7]], + [arr[ 8], arr[ 9], arr[10], arr[11]], + [arr[12], arr[13], arr[14], arr[15]], + ] +} diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 503b51bd01..8aca51c840 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -21,15 +21,15 @@ impl Camera { Self { focus: Vec3::zero(), ori: Vec3::zero(), - dist: 10.0, + dist: 5.0, fov: 1.3, aspect: 1.618, } } /// Compute the transformation matrices (view matrix and projection matrix) for the camera. - pub fn compute_matrices(&self) -> (Mat4, Mat4) { - let view = Mat4::::identity() + pub fn compute_dependents(&self) -> (Mat4, Mat4, Vec3) { + let view_mat = Mat4::::identity() * Mat4::translation_3d(-Vec3::unit_z() * self.dist) * Mat4::rotation_z(self.ori.z) * Mat4::rotation_x(self.ori.y) @@ -37,13 +37,21 @@ impl Camera { * Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x()) * Mat4::translation_3d(-self.focus); - let proj = Mat4::perspective_rh_no( + let proj_mat = Mat4::perspective_rh_no( self.fov, self.aspect, NEAR_PLANE, FAR_PLANE, ); - (view, proj) + // TODO: Make this more efficient + let cam_pos = Vec3::from(view_mat.inverted() * Vec4::unit_w()); + + (view_mat, proj_mat, cam_pos) } + + /// Get the focus position of the camera. + pub fn get_focus_pos(&self) -> Vec3 { self.focus } + /// Set the focus position of the camera. + pub fn set_focus_pos(&mut self, focus: Vec3) { self.focus = focus; } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 27d2a6f0f0..a5cc55783f 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -31,19 +31,36 @@ impl Scene { Self { camera: Camera::new(), globals: renderer - .create_consts_with(Globals::new()) + .create_consts_with(Globals::default()) .unwrap(), skybox: Skybox { model: renderer .create_model(&create_skybox_mesh()) .unwrap(), locals: renderer - .create_consts_with(SkyboxLocals::new()) + .create_consts_with(SkyboxLocals::default()) .unwrap(), }, } } + pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer) { + // Compute camera matrices + let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(); + + // Update global constants + renderer.update_consts(&mut self.globals, Globals::new( + view_mat, + proj_mat, + cam_pos, + self.camera.get_focus_pos(), + 10.0, + 0.0, + 0.0, + )) + .expect("Failed to update global constants"); + } + /// Render the scene using the provided `Renderer` pub fn render_to(&self, renderer: &mut Renderer) { // Render the skybox first (it appears over everything else so must be rendered first) diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index b02a7d0469..4aef16944a 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -31,6 +31,9 @@ const BG_COLOR: Rgba = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 }; impl PlayState for SessionState { fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult { + // Trap the cursor + global_state.window.trap_cursor(); + // Game loop loop { // Handle window events @@ -44,6 +47,9 @@ impl PlayState for SessionState { } } + // Maintain scene GPU data + self.scene.maintain_gpu_data(global_state.window.renderer_mut()); + // Clear the screen global_state.window.renderer_mut().clear(BG_COLOR); @@ -52,8 +58,9 @@ impl PlayState for SessionState { // Finish the frame global_state.window.renderer_mut().flush(); - global_state.window.display() - .expect("Failed to display window"); + global_state.window + .swap_buffers() + .expect("Failed to swap window buffers"); } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index fc2eb059f7..8d70ce2a77 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -1,6 +1,7 @@ // Library use glutin; use gfx_window_glutin; +use vek::*; // Crate use crate::{ @@ -73,13 +74,30 @@ impl Window { events } - pub fn display(&self) -> Result<(), Error> { + pub fn swap_buffers(&self) -> Result<(), Error> { self.window.swap_buffers() .map_err(|err| Error::BackendError(Box::new(err))) } + + pub fn trap_cursor(&mut self) { + self.window.hide_cursor(true); + self.window.grab_cursor(true) + .expect("Failed to grab cursor"); + } + + pub fn untrap_cursor(&mut self) { + self.window.hide_cursor(false); + self.window.grab_cursor(false) + .expect("Failed to ungrab cursor"); + } } +/// Represents an incoming event from the window pub enum Event { + /// The window has been requested to close. Close, + /// A key has been typed that corresponds to a specific character. Char(char), + /// The cursor has been panned across the screen while trapped. + CursorPan(Vec2), } From 979f47420db36e10324ce8867925a3efea58b625 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 12 Jan 2019 13:56:34 +0000 Subject: [PATCH 08/21] Added camera panning and cursor trapping --- voxygen/src/main.rs | 4 ++- voxygen/src/render/mod.rs | 13 +++++++-- voxygen/src/render/pipelines/skybox.rs | 12 ++++---- voxygen/src/render/renderer.rs | 6 ++-- voxygen/src/scene/camera.rs | 9 ++++++ voxygen/src/scene/mod.rs | 39 ++++++++++++++++++++------ voxygen/src/session.rs | 9 +++--- voxygen/src/window.rs | 5 ++++ 8 files changed, 73 insertions(+), 24 deletions(-) diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 0cc40a4c79..223534fd77 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -21,12 +21,14 @@ use crate::{ window::Window, }; -// A type used to store state that is shared between all play states +/// A type used to store state that is shared between all play states pub struct GlobalState { window: Window, } impl GlobalState { + /// Called after a change in play state has occured (usually used to reverse any temporary + /// effects a state may have made). pub fn on_play_state_changed(&mut self) { self.window.untrap_cursor(); } diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 459be2ac0e..0faf4dc7b0 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -31,14 +31,23 @@ use gfx_device_gl as gfx_backend; // Library use gfx; -/// Used to represent one of many possible errors that may be omitted by the rendering code +/// Used to represent one of many possible errors that may be omitted by the rendering subsystem #[derive(Debug)] pub enum RenderError { PipelineError(gfx::PipelineStateError), UpdateError(gfx::UpdateError), } -/// Used to represent a specific rendering configuration +/// Used to represent a specific rendering configuration. +/// +/// Note that pipelines are tied to the +/// rendering backend, and as such it is necessary to modify the rendering subsystem when adding +/// new pipelines - custom pipelines are not currently an objective of the rendering subsystem. +/// +/// # Examples +/// +/// - `SkyboxPipeline` +/// - `CharacterPipeline` pub trait Pipeline { type Vertex: Clone + diff --git a/voxygen/src/render/pipelines/skybox.rs b/voxygen/src/render/pipelines/skybox.rs index 2e6c4c1856..c39a2b3ce6 100644 --- a/voxygen/src/render/pipelines/skybox.rs +++ b/voxygen/src/render/pipelines/skybox.rs @@ -66,10 +66,10 @@ pub fn create_mesh() -> Mesh { // +x #[rustfmt::skip] mesh.push_quad(Quad::new( - Vertex { pos: [ 1.0, -1.0, -1.0] }, + Vertex { pos: [ 1.0, -1.0, 1.0] }, Vertex { pos: [ 1.0, 1.0, 1.0] }, Vertex { pos: [ 1.0, 1.0, -1.0] }, - Vertex { pos: [ 1.0, -1.0, 1.0] }, + Vertex { pos: [ 1.0, -1.0, -1.0] }, )); // -y #[rustfmt::skip] @@ -82,10 +82,10 @@ pub fn create_mesh() -> Mesh { // +y #[rustfmt::skip] mesh.push_quad(Quad::new( - Vertex { pos: [ 1.0, 1.0, -1.0] }, + Vertex { pos: [ 1.0, 1.0, 1.0] }, Vertex { pos: [-1.0, 1.0, 1.0] }, Vertex { pos: [-1.0, 1.0, -1.0] }, - Vertex { pos: [ 1.0, 1.0, 1.0] }, + Vertex { pos: [ 1.0, 1.0, -1.0] }, )); // -z #[rustfmt::skip] @@ -98,10 +98,10 @@ pub fn create_mesh() -> Mesh { // +z #[rustfmt::skip] mesh.push_quad(Quad::new( - Vertex { pos: [-1.0, -1.0, 1.0] }, + Vertex { pos: [-1.0, 1.0, 1.0] }, Vertex { pos: [ 1.0, 1.0, 1.0] }, Vertex { pos: [ 1.0, -1.0, 1.0] }, - Vertex { pos: [-1.0, 1.0, 1.0] }, + Vertex { pos: [-1.0, -1.0, 1.0] }, )); mesh diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 7388bdfc61..dc9ed54109 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -32,7 +32,7 @@ pub type TgtDepthView = gfx::handle::DepthStencilView, @@ -127,7 +127,7 @@ impl Renderer { )) } - /// Queue the rendering of the provided skybox model in the upcoming frame + /// Queue the rendering of the provided skybox model in the upcoming frame. pub fn render_skybox( &mut self, model: &Model, @@ -152,7 +152,7 @@ struct GfxPipeline { pso: gfx::pso::PipelineState, } -/// Create a new pipeline from the provided vertex shader and fragment shader +/// Create a new pipeline from the provided vertex shader and fragment shader. fn create_pipeline<'a, P: gfx::pso::PipelineInit>( factory: &mut gfx_backend::Factory, pipe: P, diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 8aca51c840..5eca5669ce 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -50,6 +50,15 @@ impl Camera { (view_mat, proj_mat, cam_pos) } + /// Rotate the camera about its focus by the given delta, limiting the input accordingly. + pub fn rotate_by(&mut self, delta: Vec3) { + self.ori += delta; + // Clamp camera pitch to the vertical limits + self.ori.y = self.ori.y + .min(PI / 2.0) + .max(-PI / 2.0); + } + /// Get the focus position of the camera. pub fn get_focus_pos(&self) -> Vec3 { self.focus } /// Set the focus position of the camera. diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index a5cc55783f..86f9d62129 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,14 +1,20 @@ pub mod camera; +// Library +use vek::*; + // Crate -use crate::render::{ - Consts, - Globals, - Model, - Renderer, - SkyboxPipeline, - SkyboxLocals, - create_skybox_mesh, +use crate::{ + render::{ + Consts, + Globals, + Model, + Renderer, + SkyboxPipeline, + SkyboxLocals, + create_skybox_mesh, + }, + window::Event, }; // Local @@ -19,6 +25,9 @@ struct Skybox { locals: Consts, } +// TODO: Don't hard-code this +const CURSOR_PAN_SCALE: f32 = 0.005; + pub struct Scene { camera: Camera, globals: Consts, @@ -44,6 +53,20 @@ impl Scene { } } + /// Handle an incoming user input event (i.e: cursor moved, key pressed, window closed, etc.). + pub fn handle_input_event(&mut self, event: Event) -> bool { + match event { + // Panning the cursor makes the camera rotate + Event::CursorPan(delta) => { + self.camera.rotate_by(Vec3::from(delta) * CURSOR_PAN_SCALE); + true + }, + // All other events are unhandled + _ => false, + } + } + + /// Maintain and update GPU data such as constant buffers, models, etc. pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer) { // Compute camera matrices let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 4aef16944a..e5f8968e17 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -38,13 +38,14 @@ impl PlayState for SessionState { loop { // Handle window events for event in global_state.window.fetch_events() { - match event { + let _handled = match event { Event::Close => return PlayStateResult::Shutdown, // When 'q' is pressed, exit the session Event::Char('q') => return PlayStateResult::Pop, - // Ignore all other events - _ => {}, - } + // Pass all other events to the scene + event => self.scene.handle_input_event(event), + }; + // TODO: Do something if the event wasn't handled? } // Maintain scene GPU data diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 8d70ce2a77..d2a88be1b5 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -69,6 +69,11 @@ impl Window { glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), _ => {}, }, + glutin::Event::DeviceEvent { event, .. } => match event { + glutin::DeviceEvent::MouseMotion { delta: (dx, dy), .. } => + events.push(Event::CursorPan(Vec2::new(dx as f32, dy as f32))), + _ => {}, + }, _ => {}, }); events From 96d01a9293ed12fa7f4f5014aad4aeba3a89a4bf Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 12 Jan 2019 14:01:01 +0000 Subject: [PATCH 09/21] Made it bloo --- voxygen/src/menu/title.rs | 2 +- voxygen/src/render/renderer.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index 91c647f993..c38533fe7c 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -20,7 +20,7 @@ impl TitleState { } // The background colour -const BG_COLOR: Rgba = Rgba { r: 0.8, g: 1.0, b: 0.8, a: 1.0 }; +const BG_COLOR: Rgba = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 }; impl PlayState for TitleState { fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult { diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index dc9ed54109..a46da68e5f 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -84,6 +84,7 @@ impl Renderer { } /// Queue the clearing of the color and depth targets ready for a new frame to be rendered. + /// TODO: Make a version of this that doesn't clear the colour target for speed pub fn clear(&mut self, col: Rgba) { self.encoder.clear(&self.tgt_color_view, col.into_array()); self.encoder.clear_depth(&self.tgt_depth_view, 1.0); From b37a6b67f918a2b64f57e85f7a69e37df7e2e59f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 12 Jan 2019 15:57:19 +0000 Subject: [PATCH 10/21] Added time of day, tick time, basic skybox shader and client to voxygen --- client/Cargo.toml | 2 +- client/src/lib.rs | 12 +++++-- common/Cargo.toml | 2 +- common/src/clock.rs | 50 +++++++++++++++++++++++++++++ common/src/lib.rs | 3 +- common/src/state.rs | 40 ++++++++++++++++++++--- server/Cargo.toml | 4 +-- server/src/lib.rs | 13 ++++++-- voxygen/Cargo.toml | 3 +- voxygen/shaders/skybox.frag | 25 +++++++++++++-- voxygen/src/error.rs | 11 +++++++ voxygen/src/render/pipelines/mod.rs | 7 ++-- voxygen/src/scene/mod.rs | 22 ++++++++++++- voxygen/src/session.rs | 48 +++++++++++++++++++++------ 14 files changed, 212 insertions(+), 30 deletions(-) create mode 100644 common/src/clock.rs diff --git a/client/Cargo.toml b/client/Cargo.toml index 97b351b96f..dd73327af8 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,6 +5,6 @@ authors = ["Joshua Barretto "] edition = "2018" [dependencies] -common = { path = "../common" } +common = { package = "veloren-common", path = "../common" } specs = "0.14" diff --git a/client/src/lib.rs b/client/src/lib.rs index 627d7cc1ac..ebcc79fe15 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -4,7 +4,8 @@ use std::time::Duration; // Internal use common::state::State; -pub enum ClientErr { +#[derive(Debug)] +pub enum Error { ServerShutdown, Other(String), } @@ -20,14 +21,21 @@ pub struct Client { } impl Client { + /// Create a new `Client`. pub fn new() -> Self { Self { state: State::new(), } } + /// 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 } + /// 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<(), ClientErr> { + 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 // managed from here, and as such it's important that it stays organised. Please consult // the core developers before making significant changes to this code. Here is the diff --git a/common/Cargo.toml b/common/Cargo.toml index 65a6d13a8d..f5d2368a13 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "common" +name = "veloren-common" version = "0.1.0" authors = ["Joshua Barretto "] edition = "2018" diff --git a/common/src/clock.rs b/common/src/clock.rs new file mode 100644 index 0000000000..437fe77c57 --- /dev/null +++ b/common/src/clock.rs @@ -0,0 +1,50 @@ +// Standard +use std::{ + thread, + time::{Duration, SystemTime}, +}; + +const CLOCK_SMOOTHING: f64 = 0.9; + +pub struct Clock { + last_sys_time: SystemTime, + last_delta: Option, + running_tps_average: f64, +} + +impl Clock { + pub fn new() -> Self { + Self { + last_sys_time: SystemTime::now(), + last_delta: None, + running_tps_average: 0.0, + } + } + + pub fn get_tps(&self) -> f64 { self.running_tps_average } + + pub fn get_last_delta(&self) -> Duration { self.last_delta.unwrap_or(Duration::new(0, 0)) } + + pub fn get_avg_delta(&self) -> Duration { Duration::from_float_secs(self.running_tps_average) } + + pub fn tick(&mut self, tgt: Duration) { + let delta = SystemTime::now() + .duration_since(self.last_sys_time) + .expect("Time went backwards!"); + + // Attempt to sleep to fill the gap + if let Some(sleep_dur) = tgt.checked_sub(delta) { + thread::sleep(sleep_dur); + } + + let delta = SystemTime::now() + .duration_since(self.last_sys_time) + .expect("Time went backwards!"); + + self.last_sys_time = SystemTime::now(); + self.last_delta = Some(delta); + self.running_tps_average = + CLOCK_SMOOTHING * self.running_tps_average + + (1.0 - CLOCK_SMOOTHING) * delta.as_float_secs(); + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index e1ec001dc1..398c59cc87 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,5 +1,6 @@ -#![feature(euclidean_division)] +#![feature(euclidean_division, duration_float)] +pub mod clock; pub mod comp; pub mod state; pub mod terrain; diff --git a/common/src/state.rs b/common/src/state.rs index 5842498ec3..90f46b845f 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -11,8 +11,18 @@ use crate::{ vol::VolSize, }; -// 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. +/// How much faster should an in-game day be compared to a real day? +// TODO: Don't hard-code this +const DAY_CYCLE_FACTOR: f64 = 24.0 * 60.0; + +/// A resource to store the time of day +struct TimeOfDay(f64); + +/// A resource to store the tick (i.e: physics) time +struct Tick(f64); + +/// 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, terrain_map: TerrainMap, @@ -20,9 +30,15 @@ pub struct State { } impl State { + /// Create a new `State`. pub fn new() -> Self { let mut ecs_world = EcsWorld::new(); + // Register resources used by the ECS + ecs_world.add_resource(TimeOfDay(0.0)); + ecs_world.add_resource(Tick(0.0)); + + // Register common components with the state comp::register_local_components(&mut ecs_world); Self { @@ -32,8 +48,24 @@ impl State { } } - // Execute a single tick, simulating the game state by the given duration + /// Get the current in-game time of day. + /// + /// Note that this should not be used for physics, animations or other such localised timings. + pub fn get_time_of_day(&self) -> f64 { + self.ecs_world.read_resource::().0 + } + + /// Get the current in-game tick time. + /// + /// Note that this does not correspond to the time of day. + pub fn get_tick(&self) -> f64 { + self.ecs_world.read_resource::().0 + } + + /// Execute a single tick, simulating the game state by the given duration. pub fn tick(&mut self, dt: Duration) { - println!("Ticked!"); + // Change the time accordingly + self.ecs_world.write_resource::().0 += dt.as_float_secs() * DAY_CYCLE_FACTOR; + self.ecs_world.write_resource::().0 += dt.as_float_secs(); } } diff --git a/server/Cargo.toml b/server/Cargo.toml index 3dbb6308ad..9c48fce6ae 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "server" +name = "veloren-server" version = "0.1.0" authors = ["Joshua Barretto "] edition = "2018" [dependencies] -common = { path = "../common" } +common = { package = "veloren-common", path = "../common" } specs = "0.14" diff --git a/server/src/lib.rs b/server/src/lib.rs index b03b3b61c3..9c6302efc4 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -4,8 +4,8 @@ use std::time::Duration; // Internal use common::state::State; -pub enum ClientErr { - ServerShutdown, +#[derive(Debug)] +pub enum Error { Other(String), } @@ -20,14 +20,21 @@ pub struct Server { } impl Server { + /// Create a new `Server`. pub fn new() -> Self { Self { state: State::new(), } } + /// 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 } + /// Execute a single server tick, handle input and update the game state by the given duration - pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), ClientErr> { + pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> { // This tick function is the centre of the Veloren universe. Most server-side things are // managed from here, and as such it's important that it stays organised. Please consult // the core developers before making significant changes to this code. Here is the diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 5ae7ee5573..29c220175d 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -10,7 +10,8 @@ gl = ["gfx_device_gl"] default = [] [dependencies] -common = { path = "../common" } +common = { package = "veloren-common", path = "../common" } +client = { package = "veloren-client", path = "../client" } # Graphics gfx = "0.17" diff --git a/voxygen/shaders/skybox.frag b/voxygen/shaders/skybox.frag index b39330e15e..f7387bd430 100644 --- a/voxygen/shaders/skybox.frag +++ b/voxygen/shaders/skybox.frag @@ -20,6 +20,27 @@ uniform u_globals { out vec4 tgt_color; -void main() { - tgt_color = vec4(f_pos, 1.0); +const float PI = 3.141592; + +vec3 get_sky_color(vec3 dir, float time_of_day) { + const float TIME_FACTOR = (PI * 2.0) / (3600.0 * 24.0); + + const vec3 SKY_TOP = vec3(0.0, 0.3, 1.0); + const vec3 SKY_BOTTOM = vec3(0.0, 0.05, 0.2); + + const vec3 SUN_HALO_COLOR = vec3(1.0, 0.8, 0.5); + const vec3 SUN_SURF_COLOR = vec3(1.0, 0.8, 0.5); + + float sun_angle_rad = time_of_day * TIME_FACTOR; + vec3 sun_dir = vec3(sin(sun_angle_rad), 0.0, cos(sun_angle_rad)); + + vec3 sun_halo = pow(max(dot(dir, sun_dir), 0.0), 8.0) * SUN_HALO_COLOR; + vec3 sun_surf = min(pow(max(dot(dir, sun_dir), 0.0) + 0.01, 16.0), 1.0) * SUN_SURF_COLOR; + vec3 sun_light = sun_halo + sun_surf; + + return mix(SKY_BOTTOM, SKY_TOP, (dir.z + 1.0) / 2.0) + sun_light * 0.5; +} + +void main() { + tgt_color = vec4(get_sky_color(normalize(f_pos), time_of_day.x), 1.0); } diff --git a/voxygen/src/error.rs b/voxygen/src/error.rs index 6bb4263508..846d84b25b 100644 --- a/voxygen/src/error.rs +++ b/voxygen/src/error.rs @@ -1,12 +1,17 @@ // Standard use std::any; +// Project +use client; + // Crate use crate::render::RenderError; /// Represents any error that may be triggered by Voxygen #[derive(Debug)] pub enum Error { + /// An error relating to the internal client + ClientError(client::Error), /// A miscellaneous error relating to a backend dependency BackendError(Box), /// An error relating the rendering subsystem @@ -20,3 +25,9 @@ impl From for Error { Error::RenderError(err) } } + +impl From for Error { + fn from(err: client::Error) -> Self { + Error::ClientError(err) + } +} diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 1283c0bab6..e1bd0193c6 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -20,8 +20,9 @@ gfx_defines! { proj_mat: [[f32; 4]; 4] = "proj_mat", cam_pos: [f32; 4] = "cam_pos", focus_pos: [f32; 4] = "focus_pos", + // TODO: Fix whatever alignment issue requires these uniforms to be aligned view_distance: [f32; 4] = "view_distance", - time_of_day: [f32; 4] = "time_of_day", + time_of_day: [f32; 4] = "time_of_day", // TODO: Make this f64 time: [f32; 4] = "time", } } @@ -47,7 +48,7 @@ impl Globals { cam_pos: Vec3, focus_pos: Vec3, view_distance: f32, - time_of_day: f32, + time_of_day: f64, time: f32, ) -> Self { Self { @@ -56,7 +57,7 @@ impl Globals { cam_pos: Vec4::from(cam_pos).into_array(), focus_pos: Vec4::from(focus_pos).into_array(), view_distance: [view_distance; 4], - time_of_day: [time_of_day; 4], + time_of_day: [time_of_day as f32; 4], time: [time; 4], } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 86f9d62129..2ff790d464 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,10 +1,20 @@ pub mod camera; +// Standard +use std::time::Duration; + // Library use vek::*; +// Project +use client::{ + self, + Client, +}; + // Crate use crate::{ + Error, render::{ Consts, Globals, @@ -32,6 +42,8 @@ pub struct Scene { camera: Camera, globals: Consts, skybox: Skybox, + + client: Client, } impl Scene { @@ -50,9 +62,17 @@ impl Scene { .create_consts_with(SkyboxLocals::default()) .unwrap(), }, + + client: Client::new(), } } + /// Tick the scene (and the client attached to it) + pub fn tick(&mut self, dt: Duration) -> Result<(), Error> { + self.client.tick(client::Input {}, dt)?; + Ok(()) + } + /// Handle an incoming user input event (i.e: cursor moved, key pressed, window closed, etc.). pub fn handle_input_event(&mut self, event: Event) -> bool { match event { @@ -78,7 +98,7 @@ impl Scene { cam_pos, self.camera.get_focus_pos(), 10.0, - 0.0, + self.client.state().get_time_of_day(), 0.0, )) .expect("Failed to update global constants"); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index e5f8968e17..a9b97b0af3 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1,6 +1,12 @@ +// Standard +use std::time::Duration; + // Library use vek::*; +// Project +use common::clock::Clock; + // Crate use crate::{ PlayState, @@ -11,6 +17,8 @@ use crate::{ scene::Scene, }; +const FPS: u64 = 60; + pub struct SessionState { scene: Scene, } @@ -29,11 +37,33 @@ impl SessionState { // The background colour const BG_COLOR: Rgba = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 }; +impl SessionState { + /// 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); + + // Clear the screen + renderer.clear(BG_COLOR); + + // Render the screen using the global renderer + self.scene.render_to(renderer); + + // Finish the frame + renderer.flush(); + } +} + impl PlayState for SessionState { fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult { // Trap the cursor global_state.window.trap_cursor(); + // Set up an fps clock + let mut clock = Clock::new(); + // Game loop loop { // Handle window events @@ -48,20 +78,20 @@ impl PlayState for SessionState { // TODO: Do something if the event wasn't handled? } - // Maintain scene GPU data - self.scene.maintain_gpu_data(global_state.window.renderer_mut()); + // Perform an in-game tick + self.scene.tick(clock.get_last_delta()) + .expect("Failed to tick the scene"); - // Clear the screen - global_state.window.renderer_mut().clear(BG_COLOR); + // Render the session + self.render(global_state.window.renderer_mut()); - // Render the screen using the global renderer - self.scene.render_to(global_state.window.renderer_mut()); - - // Finish the frame - global_state.window.renderer_mut().flush(); + // Display the frame on the window global_state.window .swap_buffers() .expect("Failed to swap window buffers"); + + // Wait for the next tick + clock.tick(Duration::from_millis(1000 / FPS)); } } From 8184bc6fc0ac3b86926cb979d28cbdb436b226c0 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 13 Jan 2019 20:53:55 +0000 Subject: [PATCH 11/21] Added figures, segments, test .vox files, basic animation test --- common/Cargo.toml | 1 + common/src/comp/mod.rs | 2 +- common/src/figure/cell.rs | 33 ++++++ common/src/figure/mod.rs | 63 ++++++++++ common/src/lib.rs | 1 + common/src/state.rs | 1 - common/src/terrain/block.rs | 1 + common/src/vol.rs | 53 ++++++++- common/src/volumes/chunk.rs | 10 +- common/src/volumes/dyna.rs | 95 +++++++++++++++ common/src/volumes/mod.rs | 1 + voxygen/Cargo.toml | 3 +- .../shaders/{character.frag => figure.frag} | 3 +- .../shaders/{character.vert => figure.vert} | 17 ++- voxygen/src/anim/mod.rs | 63 ++++++++++ voxygen/src/main.rs | 2 + voxygen/src/mesh/mod.rs | 20 ++++ voxygen/src/mesh/segment.rs | 112 ++++++++++++++++++ voxygen/src/render/consts.rs | 8 +- voxygen/src/render/mesh.rs | 17 +++ voxygen/src/render/mod.rs | 11 +- voxygen/src/render/pipelines/character.rs | 47 -------- voxygen/src/render/pipelines/figure.rs | 93 +++++++++++++++ voxygen/src/render/pipelines/mod.rs | 2 +- voxygen/src/render/pipelines/skybox.rs | 2 + voxygen/src/render/renderer.rs | 63 ++++++---- voxygen/src/scene/camera.rs | 4 +- voxygen/src/scene/figure.rs | 79 ++++++++++++ voxygen/src/scene/mod.rs | 83 +++++++++++-- voxygen/test_assets/belt.vox | Bin 0 -> 1544 bytes voxygen/test_assets/chest.vox | Bin 0 -> 2396 bytes voxygen/test_assets/foot.vox | Bin 0 -> 1404 bytes voxygen/test_assets/hand.vox | Bin 0 -> 1272 bytes voxygen/test_assets/head.vox | Bin 0 -> 5232 bytes voxygen/test_assets/knight.vox | Bin 0 -> 9716 bytes voxygen/test_assets/pants.vox | Bin 0 -> 1784 bytes voxygen/test_assets/sword.vox | Bin 0 -> 1568 bytes 37 files changed, 790 insertions(+), 100 deletions(-) create mode 100644 common/src/figure/cell.rs create mode 100644 common/src/figure/mod.rs create mode 100644 common/src/volumes/dyna.rs rename voxygen/shaders/{character.frag => figure.frag} (86%) rename voxygen/shaders/{character.vert => figure.vert} (62%) create mode 100644 voxygen/src/anim/mod.rs create mode 100644 voxygen/src/mesh/mod.rs create mode 100644 voxygen/src/mesh/segment.rs delete mode 100644 voxygen/src/render/pipelines/character.rs create mode 100644 voxygen/src/render/pipelines/figure.rs create mode 100644 voxygen/src/scene/figure.rs create mode 100644 voxygen/test_assets/belt.vox create mode 100644 voxygen/test_assets/chest.vox create mode 100644 voxygen/test_assets/foot.vox create mode 100644 voxygen/test_assets/hand.vox create mode 100644 voxygen/test_assets/head.vox create mode 100644 voxygen/test_assets/knight.vox create mode 100644 voxygen/test_assets/pants.vox create mode 100644 voxygen/test_assets/sword.vox diff --git a/common/Cargo.toml b/common/Cargo.toml index f5d2368a13..d35d5f094a 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" [dependencies] specs = "0.14" vek = "0.9" +dot_vox = "1.0" diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index cc7bec16b9..c7bb5a311f 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -1,7 +1,7 @@ pub mod phys; // External -use specs::{World as EcsWorld, Builder}; +use specs::World as EcsWorld; pub fn register_local_components(ecs_world: &mut EcsWorld) { ecs_world.register::(); diff --git a/common/src/figure/cell.rs b/common/src/figure/cell.rs new file mode 100644 index 0000000000..c6e1d79cc4 --- /dev/null +++ b/common/src/figure/cell.rs @@ -0,0 +1,33 @@ +// Library +use vek::*; + +/// A type representing a single voxel in a figure +#[derive(Copy, Clone, Debug)] +pub enum Cell { + Filled([u8; 3]), + Empty, +} + +impl Cell { + pub fn empty() -> Self { + Cell::Empty + } + + pub fn new(rgb: Rgb) -> Self { + Cell::Filled(rgb.into_array()) + } + + pub fn is_empty(&self) -> bool { + match self { + Cell::Filled(_) => false, + Cell::Empty => true, + } + } + + pub fn get_color(&self) -> Option> { + match self { + Cell::Filled(col) => Some(Rgb::from(*col)), + Cell::Empty => None, + } + } +} diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs new file mode 100644 index 0000000000..cd39161bcf --- /dev/null +++ b/common/src/figure/mod.rs @@ -0,0 +1,63 @@ +pub mod cell; + +// Library +use vek::*; +use dot_vox::DotVoxData; + +// Crate +use crate::{ + vol::WriteVol, + volumes::dyna::Dyna, +}; + +// Local +use self::cell::Cell; + +/// A type representing a single figure bone (e.g: the limb of a character). +#[derive(Copy, Clone)] +pub struct Bone { + origin: Vec3, + offset: Vec3, + ori: Vec3, +} + +/// A type representing a volume that may be part of an animated figure. +/// +/// Figures are used to represent things like characters, NPCs, mobs, etc. +pub type Segment = Dyna; + +impl From for Segment { + fn from(dot_vox_data: DotVoxData) -> Self { + if let Some(model) = dot_vox_data.models.get(0) { + let palette = dot_vox_data + .palette + .iter() + .map(|col| Rgba::from(col.to_ne_bytes()).into()) + .collect::>(); + + let mut segment = Segment::filled( + Vec3::new( + model.size.x, + model.size.y, + model.size.z, + ), + Cell::empty(), + (), + ); + + for voxel in &model.voxels { + if let Some(&color) = palette.get(voxel.i as usize) { + // TODO: Maybe don't ignore this error? + let _ = segment.set( + Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| e as i32), + Cell::new(color), + ); + } + } + + segment + } else { + Segment::filled(Vec3::zero(), Cell::empty(), ()) + } + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 398c59cc87..1aee96ce50 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2,6 +2,7 @@ pub mod clock; pub mod comp; +pub mod figure; pub mod state; pub mod terrain; pub mod volumes; diff --git a/common/src/state.rs b/common/src/state.rs index 90f46b845f..084b701da6 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -8,7 +8,6 @@ use specs::World as EcsWorld; use crate::{ comp, terrain::TerrainMap, - vol::VolSize, }; /// How much faster should an in-game day be compared to a real day? diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 776c2f7a48..0b45fdcf8a 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -1,3 +1,4 @@ +#[derive(Copy, Clone, Debug)] pub struct Block { kind: u8, color: [u8; 3], diff --git a/common/src/vol.rs b/common/src/vol.rs index 025a3adbf5..bb49a86423 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -1,27 +1,76 @@ // Library use vek::*; +/// A volume that contains voxel data. pub trait BaseVol { type Vox; type Err; } -pub trait SizedVol: BaseVol { - const SIZE: Vec3; +// Utility types + +pub struct VoxPosIter { + pos: Vec3, + sz: Vec3, } +impl Iterator for VoxPosIter { + type Item = Vec3; + + fn next(&mut self) -> Option { + let mut old_pos = self.pos; + + if old_pos.z == self.sz.z { + old_pos.z = 0; + old_pos.y += 1; + if old_pos.y == self.sz.y { + old_pos.y = 0; + old_pos.x += 1; + if old_pos.x == self.sz.x { + return None; + } + } + } + + self.pos = old_pos + Vec3::unit_z(); + + Some(old_pos.map(|e| e as i32)) + } +} + +/// A volume that has a finite size. +pub trait SizedVol: BaseVol { + /// Get the size of the volume. + #[inline(always)] + fn get_size(&self) -> Vec3; + + /// Iterate through all potential voxel positions in this volume + fn iter_positions(&self) -> VoxPosIter { + VoxPosIter { + pos: Vec3::zero(), + sz: self.get_size(), + } + } +} + +/// A volume that provided read access to its voxel data. pub trait ReadVol: BaseVol { + /// Get a reference to the voxel at the provided position in the volume. #[inline(always)] fn get(&self, pos: Vec3) -> Result<&Self::Vox, Self::Err>; } +/// A volume that provides write access to its voxel data. pub trait WriteVol: BaseVol { + /// Set the voxel at the provided position in the volume to the provided value. #[inline(always)] fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), Self::Err>; } // Utility traits +/// Used to specify a volume's compile-time size. This exists as a substitute until const generics +/// are implemented. pub trait VolSize { const SIZE: Vec3; } diff --git a/common/src/volumes/chunk.rs b/common/src/volumes/chunk.rs index 1acec192e8..fcd0912873 100644 --- a/common/src/volumes/chunk.rs +++ b/common/src/volumes/chunk.rs @@ -17,6 +17,7 @@ pub enum ChunkErr { OutOfBounds, } +/// A volume with dimensions known at compile-time. // V = Voxel // S = Size (replace when const generics are a thing) // M = Metadata @@ -27,6 +28,8 @@ pub struct Chunk { } impl Chunk { + /// Used to transform a voxel position in the volume into its corresponding index in the voxel + // array. #[inline(always)] fn idx_for(pos: Vec3) -> Option { if @@ -50,7 +53,8 @@ impl BaseVol for Chunk { } impl SizedVol for Chunk { - const SIZE: Vec3 = Vec3 { x: 32, y: 32, z: 32 }; + #[inline(always)] + fn get_size(&self) -> Vec3 { S::SIZE } } impl ReadVol for Chunk { @@ -73,6 +77,8 @@ impl WriteVol for Chunk { } impl Chunk { + /// Create a new `Chunk` with the provided dimensions and all voxels filled with duplicates of + /// the provided voxel. pub fn filled(vox: V, meta: M) -> Self { Self { vox: vec![vox; S::SIZE.product() as usize], @@ -81,10 +87,12 @@ impl Chunk { } } + /// Get a reference to the internal metadata. pub fn metadata(&self) -> &M { &self.meta } + /// Get a mutable reference to the internal metadata. pub fn metadata_mut(&mut self) -> &mut M { &mut self.meta } diff --git a/common/src/volumes/dyna.rs b/common/src/volumes/dyna.rs new file mode 100644 index 0000000000..f975b7e02a --- /dev/null +++ b/common/src/volumes/dyna.rs @@ -0,0 +1,95 @@ +// Library +use vek::*; + +// Local +use crate::vol::{ + BaseVol, + SizedVol, + ReadVol, + WriteVol, +}; + +pub enum DynaErr { + OutOfBounds, +} + +/// A volume with dimensions known only at the creation of the object. +// V = Voxel +// S = Size (replace when const generics are a thing) +// M = Metadata +pub struct Dyna { + vox: Vec, + meta: M, + sz: Vec3, +} + +impl Dyna { + /// Used to transform a voxel position in the volume into its corresponding index in the voxel + // array. + #[inline(always)] + fn idx_for(sz: Vec3, pos: Vec3) -> Option { + if + pos.map(|e| e >= 0).reduce_and() && + pos.map2(sz, |e, lim| e < lim as i32).reduce_and() + { + Some(( + pos.x * sz.y as i32 * sz.z as i32 + + pos.y * sz.z as i32 + + pos.z + ) as usize) + } else { + None + } + } +} + +impl BaseVol for Dyna { + type Vox = V; + type Err = DynaErr; +} + +impl SizedVol for Dyna { + #[inline(always)] + fn get_size(&self) -> Vec3 { self.sz } +} + +impl ReadVol for Dyna { + #[inline(always)] + fn get(&self, pos: Vec3) -> Result<&V, DynaErr> { + Self::idx_for(self.sz, pos) + .and_then(|idx| self.vox.get(idx)) + .ok_or(DynaErr::OutOfBounds) + } +} + +impl WriteVol for Dyna { + #[inline(always)] + fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), DynaErr> { + Self::idx_for(self.sz, pos) + .and_then(|idx| self.vox.get_mut(idx)) + .map(|old_vox| *old_vox = vox) + .ok_or(DynaErr::OutOfBounds) + } +} + +impl Dyna { + /// Create a new `Dyna` with the provided dimensions and all voxels filled with duplicates of + /// the provided voxel. + pub fn filled(sz: Vec3, vox: V, meta: M) -> Self { + Self { + vox: vec![vox; sz.product() as usize], + meta, + sz, + } + } + + /// Get a reference to the internal metadata. + pub fn metadata(&self) -> &M { + &self.meta + } + + /// Get a mutable reference to the internal metadata. + pub fn metadata_mut(&mut self) -> &mut M { + &mut self.meta + } +} diff --git a/common/src/volumes/mod.rs b/common/src/volumes/mod.rs index d1f4568502..8980a5a1ff 100644 --- a/common/src/volumes/mod.rs +++ b/common/src/volumes/mod.rs @@ -1,2 +1,3 @@ +pub mod dyna; pub mod chunk; pub mod vol_map; diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 29c220175d..9535b62ecf 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [features] gl = ["gfx_device_gl"] -default = [] +default = ["gl"] [dependencies] common = { package = "veloren-common", path = "../common" } @@ -28,3 +28,4 @@ failure = "0.1" lazy_static = "1.1" log = "0.4" pretty_env_logger = "0.3" +dot_vox = "1.0" diff --git a/voxygen/shaders/character.frag b/voxygen/shaders/figure.frag similarity index 86% rename from voxygen/shaders/character.frag rename to voxygen/shaders/figure.frag index 6caf3768ba..60b1454ce3 100644 --- a/voxygen/shaders/character.frag +++ b/voxygen/shaders/figure.frag @@ -1,6 +1,7 @@ #version 330 core in vec3 f_pos; +in vec3 f_col; layout (std140) uniform u_locals { @@ -21,5 +22,5 @@ uniform u_globals { out vec4 tgt_color; void main() { - tgt_color = vec4(f_pos, 1.0); + tgt_color = vec4(f_col, 1.0); } diff --git a/voxygen/shaders/character.vert b/voxygen/shaders/figure.vert similarity index 62% rename from voxygen/shaders/character.vert rename to voxygen/shaders/figure.vert index 0fa6bc639b..2a27b94a49 100644 --- a/voxygen/shaders/character.vert +++ b/voxygen/shaders/figure.vert @@ -2,7 +2,7 @@ in vec3 v_pos; in vec3 v_col; -in uint v_bone; +in uint v_bone_idx; layout (std140) uniform u_locals { @@ -20,13 +20,26 @@ uniform u_globals { vec4 time; }; +struct BoneData { + mat4 bone_mat; +}; + +layout (std140) +uniform u_bones { + BoneData bones[16]; +}; + out vec3 f_pos; +out vec3 f_col; void main() { f_pos = v_pos; + f_col = v_col; gl_Position = proj_mat * view_mat * - vec4(0.5 * v_pos + cam_pos.xyz, 1); + model_mat * + bones[v_bone_idx].bone_mat * + vec4(v_pos, 1); } diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs new file mode 100644 index 0000000000..afe754cba3 --- /dev/null +++ b/voxygen/src/anim/mod.rs @@ -0,0 +1,63 @@ +// Library +use vek::*; + +// Crate +use crate::render::FigureBoneData; + +#[derive(Copy, Clone)] +pub struct Bone { + parent: Option, // MUST be less than the current bone index + pub offset: Vec3, + pub ori: Quaternion, +} + +impl Bone { + pub fn default() -> Self { + Self { + parent: None, + offset: Vec3::zero(), + ori: Quaternion::identity(), + } + } + + pub fn compute_base_matrix(&self) -> Mat4 { + Mat4::::translation_3d(self.offset) * Mat4::from(self.ori) + } +} + +#[derive(Copy, Clone)] +pub struct Skeleton { + bones: [Bone; 16], +} + +impl Skeleton { + pub fn default() -> Self { + Self { + bones: [Bone::default(); 16], + } + } + + pub fn with_bone(mut self, bone_idx: u8, bone: Bone) -> Self { + self.bones[bone_idx as usize] = bone; + self + } + + pub fn bone(&self, bone_idx: u8) -> &Bone { &self.bones[bone_idx as usize] } + pub fn bone_mut(&mut self, bone_idx: u8) -> &mut Bone { &mut self.bones[bone_idx as usize] } + + pub fn compute_matrices(&self) -> [FigureBoneData; 16] { + let mut bone_data = [FigureBoneData::default(); 16]; + for i in 0..16 { + bone_data[i] = FigureBoneData::new( + self.bones[i].compute_base_matrix() + // * + //if let Some(parent_idx) = self.bones[i].parent { + // bone_data[parent_idx as usize] + //} else { + // Mat4::identity() + //} + ); + } + bone_data + } +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 223534fd77..b0aac99913 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -1,5 +1,7 @@ +pub mod anim; pub mod error; pub mod menu; +pub mod mesh; pub mod render; pub mod scene; pub mod session; diff --git a/voxygen/src/mesh/mod.rs b/voxygen/src/mesh/mod.rs new file mode 100644 index 0000000000..0f432076ae --- /dev/null +++ b/voxygen/src/mesh/mod.rs @@ -0,0 +1,20 @@ +pub mod segment; + +// Library +use vek::*; + +// Crate +use crate::render::{ + self, + Mesh, +}; + +pub trait Meshable { + type Pipeline: render::Pipeline; + + fn generate_mesh(&self) -> Mesh { + self.generate_mesh_with_offset(Vec3::zero()) + } + + fn generate_mesh_with_offset(&self, offs: Vec3) -> Mesh; +} diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs new file mode 100644 index 0000000000..06a6048e05 --- /dev/null +++ b/voxygen/src/mesh/segment.rs @@ -0,0 +1,112 @@ +// Project +use common::figure::Segment; + +// Library +use vek::*; + +// Project +use common::vol::{ + SizedVol, + ReadVol, +}; + +// Crate +use crate::{ + mesh::Meshable, + render::{ + self, + Mesh, + Quad, + FigurePipeline, + }, +}; + +type FigureVertex = ::Vertex; + +// Utility function +// TODO: Evaluate how useful this is +fn create_quad( + origin: Vec3, + unit_x: Vec3, + unit_y: Vec3, + col: Rgb, + bone: u8, +) -> Quad { + Quad::new( + FigureVertex::new(origin, col, bone), + FigureVertex::new(origin + unit_x, col, bone), + FigureVertex::new(origin + unit_x + unit_y, col, bone), + FigureVertex::new(origin + unit_y, col, bone), + ) +} + +impl Meshable for Segment { + type Pipeline = FigurePipeline; + + fn generate_mesh_with_offset(&self, offs: Vec3) -> Mesh { + let mut mesh = Mesh::new(); + + for pos in self.iter_positions() { + if let Some(col) = self + .get(pos) + .ok() + .and_then(|vox| vox.get_color()) + { + let col = col.map(|e| e as f32 / 255.0); + + // TODO: Face occlusion + + // -x + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_y(), + -Vec3::unit_y(), + Vec3::unit_z(), + col, + 0, + )); + // +x + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_x(), + Vec3::unit_y(), + Vec3::unit_z(), + col, + 0, + )); + // -y + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32), + Vec3::unit_x(), + Vec3::unit_z(), + col, + 0, + )); + // +y + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_y(), + Vec3::unit_z(), + Vec3::unit_x(), + col, + 0, + )); + // -z + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32), + Vec3::unit_y(), + Vec3::unit_x(), + col, + 0, + )); + // +z + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_z(), + Vec3::unit_x(), + Vec3::unit_y(), + col, + 0, + )); + } + } + + mesh + } +} diff --git a/voxygen/src/render/consts.rs b/voxygen/src/render/consts.rs index 1c15b0362a..08a23824e6 100644 --- a/voxygen/src/render/consts.rs +++ b/voxygen/src/render/consts.rs @@ -19,9 +19,9 @@ pub struct Consts { impl Consts { /// Create a new `Const` - pub fn new(factory: &mut gfx_backend::Factory) -> Self { + pub fn new(factory: &mut gfx_backend::Factory, len: usize) -> Self { Self { - buf: factory.create_constant_buffer(1), + buf: factory.create_constant_buffer(len), } } @@ -29,9 +29,9 @@ impl Consts { pub fn update( &mut self, encoder: &mut gfx::Encoder, - val: T, + vals: &[T], ) -> Result<(), RenderError> { - encoder.update_buffer(&self.buf, &[val], 0) + encoder.update_buffer(&self.buf, vals, 0) .map_err(|err| RenderError::UpdateError(err)) } } diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index 75e317845c..7a91cee1c6 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -2,6 +2,7 @@ use super::Pipeline; /// A `Vec`-based mesh structure used to store mesh data on the CPU. +#[derive(Clone)] pub struct Mesh { verts: Vec, } @@ -43,6 +44,22 @@ impl Mesh

{ self.verts.push(quad.d); self.verts.push(quad.a); } + + /// Push the vertices of another mesh onto the end of this mesh + pub fn push_mesh(&mut self, other: &Mesh

) { + self.verts.extend_from_slice(other.vertices()); + } + + /// Push the vertices of another mesh onto the end of this mesh + pub fn push_mesh_map P::Vertex>(&mut self, other: &Mesh

, mut f: F) { + // Reserve enough space in our Vec. This isn't necessary, but it tends to reduce the number + // of required (re)allocations. + self.verts.reserve(other.vertices().len()); + + for vert in other.vertices() { + self.verts.push(f(vert.clone())); + } + } } /// Represents a triangle stored on the CPU. diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 0faf4dc7b0..a61e8ce7fd 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -8,14 +8,15 @@ mod util; // Reexports pub use self::{ consts::Consts, - mesh::{Mesh, Quad}, + mesh::{Mesh, Tri, Quad}, model::Model, renderer::{Renderer, TgtColorFmt, TgtDepthFmt}, pipelines::{ Globals, - character::{ - CharacterPipeline, - Locals as CharacterLocals, + figure::{ + FigurePipeline, + Locals as FigureLocals, + BoneData as FigureBoneData, }, skybox::{ create_mesh as create_skybox_mesh, @@ -47,7 +48,7 @@ pub enum RenderError { /// # Examples /// /// - `SkyboxPipeline` -/// - `CharacterPipeline` +/// - `FigurePipeline` pub trait Pipeline { type Vertex: Clone + diff --git a/voxygen/src/render/pipelines/character.rs b/voxygen/src/render/pipelines/character.rs deleted file mode 100644 index 8b95b86f91..0000000000 --- a/voxygen/src/render/pipelines/character.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Library -use gfx::{ - self, - // Macros - gfx_defines, - gfx_vertex_struct_meta, - gfx_constant_struct_meta, - gfx_impl_struct_meta, - gfx_pipeline, - gfx_pipeline_inner, -}; - -// Local -use super::{ - Globals, - super::{ - Pipeline, - TgtColorFmt, - TgtDepthFmt, - }, -}; - -gfx_defines! { - vertex Vertex { - pos: [f32; 3] = "v_pos", - col: [f32; 3] = "v_col", - bone: u8 = "v_bone", - } - - constant Locals { - model_mat: [[f32; 4]; 4] = "model_mat", - } - - pipeline pipe { - vbuf: gfx::VertexBuffer = (), - locals: gfx::ConstantBuffer = "u_locals", - globals: gfx::ConstantBuffer = "u_globals", - tgt_color: gfx::RenderTarget = "tgt_color", - tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, - } -} - -pub struct CharacterPipeline; - -impl Pipeline for CharacterPipeline { - type Vertex = Vertex; -} diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs new file mode 100644 index 0000000000..e7d14f272b --- /dev/null +++ b/voxygen/src/render/pipelines/figure.rs @@ -0,0 +1,93 @@ +// Library +use gfx::{ + self, + // Macros + gfx_defines, + gfx_vertex_struct_meta, + gfx_constant_struct_meta, + gfx_impl_struct_meta, + gfx_pipeline, + gfx_pipeline_inner, +}; +use vek::*; + +// Local +use super::{ + Globals, + super::{ + Pipeline, + TgtColorFmt, + TgtDepthFmt, + util::arr_to_mat, + }, +}; + +gfx_defines! { + vertex Vertex { + pos: [f32; 3] = "v_pos", + col: [f32; 3] = "v_col", + bone_idx: u8 = "v_bone_idx", + } + + constant Locals { + model_mat: [[f32; 4]; 4] = "model_mat", + } + + constant BoneData { + bone_mat: [[f32; 4]; 4] = "bone_mat", + } + + pipeline pipe { + vbuf: gfx::VertexBuffer = (), + + locals: gfx::ConstantBuffer = "u_locals", + globals: gfx::ConstantBuffer = "u_globals", + bones: gfx::ConstantBuffer = "u_bones", + + tgt_color: gfx::RenderTarget = "tgt_color", + tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + } +} + +impl Vertex { + pub fn new(pos: Vec3, col: Rgb, bone_idx: u8) -> Self { + Self { + pos: pos.into_array(), + col: col.into_array(), + bone_idx, + } + } + + pub fn with_bone_idx(mut self, bone_idx: u8) -> Self { + self.bone_idx = bone_idx; + self + } +} + +impl Locals { + pub fn default() -> Self { + Self { + model_mat: arr_to_mat(Mat4::identity().into_col_array()), + } + } +} + +impl BoneData { + pub fn new(bone_mat: Mat4) -> Self { + Self { + bone_mat: arr_to_mat(bone_mat.into_col_array()), + } + } + + pub fn default() -> Self { + Self { + bone_mat: arr_to_mat(Mat4::identity().into_col_array()), + } + } +} + +pub struct FigurePipeline; + +impl Pipeline for FigurePipeline { + type Vertex = Vertex; +} diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index e1bd0193c6..663174b70d 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -1,4 +1,4 @@ -pub mod character; +pub mod figure; pub mod skybox; // Library diff --git a/voxygen/src/render/pipelines/skybox.rs b/voxygen/src/render/pipelines/skybox.rs index c39a2b3ce6..25e94dab0b 100644 --- a/voxygen/src/render/pipelines/skybox.rs +++ b/voxygen/src/render/pipelines/skybox.rs @@ -33,8 +33,10 @@ gfx_defines! { pipeline pipe { vbuf: gfx::VertexBuffer = (), + locals: gfx::ConstantBuffer = "u_locals", globals: gfx::ConstantBuffer = "u_globals", + tgt_color: gfx::RenderTarget = "tgt_color", tgt_depth: gfx::DepthTarget = gfx::preset::depth::PASS_TEST, } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index a46da68e5f..2e23f70f69 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -15,13 +15,13 @@ use super::{ gfx_backend, pipelines::{ Globals, - character, + figure, skybox, }, }; /// Represents the format of the window's color target. -pub type TgtColorFmt = gfx::format::Srgba8; +pub type TgtColorFmt = gfx::format::Rgba8; /// Represents the format of the window's depth target. pub type TgtDepthFmt = gfx::format::DepthStencil; @@ -42,7 +42,7 @@ pub struct Renderer { tgt_depth_view: TgtDepthView, skybox_pipeline: GfxPipeline>, - character_pipeline: GfxPipeline>, + figure_pipeline: GfxPipeline>, } impl Renderer { @@ -62,12 +62,12 @@ impl Renderer { include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/skybox.frag")), )?; - // Construct a pipeline for rendering characters - let character_pipeline = create_pipeline( + // Construct a pipeline for rendering figures + let figure_pipeline = create_pipeline( &mut factory, - character::pipe::new(), - include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/character.vert")), - include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/character.frag")), + figure::pipe::new(), + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/figure.vert")), + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/figure.frag")), )?; Ok(Self { @@ -79,7 +79,7 @@ impl Renderer { tgt_depth_view, skybox_pipeline, - character_pipeline, + figure_pipeline, }) } @@ -96,28 +96,23 @@ impl Renderer { self.device.cleanup(); } - /// Create a new set of constants. - pub fn create_consts(&mut self) -> Result, RenderError> { - Ok(Consts::new(&mut self.factory)) - } - - /// Create a new set of constants with a value. - pub fn create_consts_with( + /// Create a new set of constants with the provided values. + pub fn create_consts( &mut self, - val: T + vals: &[T], ) -> Result, RenderError> { - let mut consts = self.create_consts()?; - consts.update(&mut self.encoder, val)?; + let mut consts = Consts::new(&mut self.factory, vals.len()); + consts.update(&mut self.encoder, vals)?; Ok(consts) } - /// Update a set of constants with a new value. + /// Update a set of constants with the provided values. pub fn update_consts( &mut self, consts: &mut Consts, - val: T + vals: &[T] ) -> Result<(), RenderError> { - consts.update(&mut self.encoder, val) + consts.update(&mut self.encoder, vals) } /// Create a new model from the provided mesh. @@ -132,8 +127,8 @@ impl Renderer { pub fn render_skybox( &mut self, model: &Model, - locals: &Consts, globals: &Consts, + locals: &Consts, ) { self.encoder.draw( &model.slice, @@ -147,6 +142,28 @@ impl Renderer { }, ); } + + /// Queue the rendering of the provided figure model in the upcoming frame. + pub fn render_figure( + &mut self, + model: &Model, + globals: &Consts, + locals: &Consts, + bones: &Consts, + ) { + self.encoder.draw( + &model.slice, + &self.figure_pipeline.pso, + &figure::pipe::Data { + vbuf: model.vbuf.clone(), + locals: locals.buf.clone(), + globals: globals.buf.clone(), + bones: bones.buf.clone(), + tgt_color: self.tgt_color_view.clone(), + tgt_depth: self.tgt_depth_view.clone(), + }, + ); + } } struct GfxPipeline { diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 5eca5669ce..aad5e5591d 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -19,9 +19,9 @@ impl Camera { /// Create a new `Camera` with default parameters. pub fn new() -> Self { Self { - focus: Vec3::zero(), + focus: Vec3::unit_z() * 10.0, ori: Vec3::zero(), - dist: 5.0, + dist: 40.0, fov: 1.3, aspect: 1.618, } diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs new file mode 100644 index 0000000000..92bc610016 --- /dev/null +++ b/voxygen/src/scene/figure.rs @@ -0,0 +1,79 @@ +// Crate +use crate::{ + Error, + render::{ + Consts, + Globals, + Mesh, + Model, + Renderer, + FigurePipeline, + FigureBoneData, + FigureLocals, + }, + anim::Skeleton, +}; + +pub struct Figure { + // GPU data + model: Model, + bone_consts: Consts, + locals: Consts, + + // CPU data + bone_meshes: [Option>; 16], + pub skeleton: Skeleton, +} + +impl Figure { + pub fn new( + renderer: &mut Renderer, + bone_meshes: [Option>; 16] + ) -> Result { + let skeleton = Skeleton::default(); + let mut this = Self { + model: renderer.create_model(&Mesh::new())?, + bone_consts: renderer.create_consts(&skeleton.compute_matrices())?, + locals: renderer.create_consts(&[FigureLocals::default()])?, + + bone_meshes, + skeleton, + }; + this.update_model(renderer)?; + Ok(this) + } + + pub fn update_model(&mut self, renderer: &mut Renderer) -> Result<(), Error> { + let mut mesh = Mesh::new(); + + self.bone_meshes + .iter() + .enumerate() + .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm))) + .for_each(|(i, bone_mesh)| { + mesh.push_mesh_map(bone_mesh, |vert| vert.with_bone_idx(i as u8)) + }); + + self.model = renderer.create_model(&mesh)?; + Ok(()) + } + + pub fn update_skeleton(&mut self, renderer: &mut Renderer) -> Result<(), Error> { + renderer.update_consts(&mut self.bone_consts, &self.skeleton.compute_matrices())?; + Ok(()) + } + + pub fn update_locals(&mut self, renderer: &mut Renderer, locals: FigureLocals) -> Result<(), Error> { + renderer.update_consts(&mut self.locals, &[locals])?; + Ok(()) + } + + pub fn render(&self, renderer: &mut Renderer, globals: &Consts) { + renderer.render_figure( + &self.model, + globals, + &self.locals, + &self.bone_consts, + ); + } +} diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 2ff790d464..127aff17b2 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,16 +1,19 @@ pub mod camera; +pub mod figure; // Standard use std::time::Duration; // Library use vek::*; +use dot_vox; // Project use client::{ self, Client, }; +use common::figure::Segment; // Crate use crate::{ @@ -22,47 +25,82 @@ use crate::{ Renderer, SkyboxPipeline, SkyboxLocals, + FigureLocals, create_skybox_mesh, }, window::Event, + mesh::Meshable, }; // Local -use self::camera::Camera; +use self::{ + camera::Camera, + figure::Figure, +}; + +// TODO: Don't hard-code this +const CURSOR_PAN_SCALE: f32 = 0.005; struct Skybox { model: Model, locals: Consts, } -// TODO: Don't hard-code this -const CURSOR_PAN_SCALE: f32 = 0.005; - pub struct Scene { camera: Camera, globals: Consts, skybox: Skybox, + test_figure: Figure, + client: Client, } +// TODO: Make a proper asset loading system +fn load_segment(filename: &'static str) -> Segment { + Segment::from(dot_vox::load(&(concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/").to_string() + filename)).unwrap()) +} + impl Scene { /// Create a new `Scene` with default parameters. pub fn new(renderer: &mut Renderer) -> Self { Self { camera: Camera::new(), globals: renderer - .create_consts_with(Globals::default()) + .create_consts(&[Globals::default()]) .unwrap(), skybox: Skybox { model: renderer .create_model(&create_skybox_mesh()) .unwrap(), locals: renderer - .create_consts_with(SkyboxLocals::default()) + .create_consts(&[SkyboxLocals::default()]) .unwrap(), }, + 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("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, 0.0))), + Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -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("sword.vox").generate_mesh_with_offset(Vec3::new(-6.5, -1.0, 0.0))), + None, + None, + None, + None, + None, + None, + None, + ], + ) + .unwrap(), + client: Client::new(), } } @@ -92,7 +130,7 @@ impl Scene { let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(); // Update global constants - renderer.update_consts(&mut self.globals, Globals::new( + renderer.update_consts(&mut self.globals, &[Globals::new( view_mat, proj_mat, cam_pos, @@ -100,8 +138,32 @@ impl Scene { 10.0, self.client.state().get_time_of_day(), 0.0, - )) + )]) .expect("Failed to update global constants"); + + // TODO: Don't do this here + let offs = (self.client.state().get_tick() as f32 * 10.0).sin(); + self.test_figure.skeleton.bone_mut(0).offset = Vec3::new(0.0, 0.0, 13.0); + self.test_figure.skeleton.bone_mut(0).ori = Quaternion::rotation_z(offs * 0.3); + // Chest + self.test_figure.skeleton.bone_mut(1).offset = Vec3::new(0.0, 0.0, 9.0); + self.test_figure.skeleton.bone_mut(2).offset = Vec3::new(0.0, 0.0, 7.0); + self.test_figure.skeleton.bone_mut(3).offset = Vec3::new(0.0, 0.0, 4.0); + self.test_figure.skeleton.bone_mut(1).ori = Quaternion::rotation_z(offs * 0.15); + self.test_figure.skeleton.bone_mut(2).ori = Quaternion::rotation_z(offs * 0.15); + self.test_figure.skeleton.bone_mut(3).ori = Quaternion::rotation_z(offs * 0.15); + //Feet + self.test_figure.skeleton.bone_mut(4).offset = Vec3::new(-3.0, -offs * 4.0, 0.0); + self.test_figure.skeleton.bone_mut(5).offset = Vec3::new(3.0, offs * 4.0, 0.0); + // Hands + self.test_figure.skeleton.bone_mut(6).offset = Vec3::new(-8.0, offs * 4.0, 9.0); + self.test_figure.skeleton.bone_mut(7).offset = Vec3::new(8.0, -offs * 4.0, 9.0); + // Sword + self.test_figure.skeleton.bone_mut(8).offset = Vec3::new(-8.0, 5.0, 24.0); + self.test_figure.skeleton.bone_mut(8).ori = Quaternion::rotation_y(2.5); + + self.test_figure.update_locals(renderer, FigureLocals::default()); + self.test_figure.update_skeleton(renderer); } /// Render the scene using the provided `Renderer` @@ -109,8 +171,11 @@ impl Scene { // Render the skybox first (it appears over everything else so must be rendered first) renderer.render_skybox( &self.skybox.model, - &self.skybox.locals, &self.globals, + &self.skybox.locals, ); + + // Render the test figure + self.test_figure.render(renderer, &self.globals); } } diff --git a/voxygen/test_assets/belt.vox b/voxygen/test_assets/belt.vox new file mode 100644 index 0000000000000000000000000000000000000000..6604240bc9b9a68b466bbac328848914bc5df6db GIT binary patch literal 1544 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 literal 0 HcmV?d00001 diff --git a/voxygen/test_assets/foot.vox b/voxygen/test_assets/foot.vox new file mode 100644 index 0000000000000000000000000000000000000000..6cbbf067b25e882559c1938ed9e084c1ce6e696f GIT binary patch literal 1404 zcmc)JZ%CbG7{~GJ`E!&ctJ|Wfo#FW}BR(=!MCUF&Jamh#+K)h~D-hqIchZ$Gr1uedWXz zM@;;Ha^i|3mWnqh7DqhC)n2alaU;K()k^{kxuR)cLHny1F`Iu^4y8jNRv#DGL70pTQtYOYNlE#nx}u zl@ZAz=TL~yiE_eI^;k;)>%SkHei&QMBBR~NK5A?kH6FiYr1cvQ#*IwHSr?4z_H^2g zW)MA@$p;IM&>S;5#*NqJjN>=8wj-DPzCwy)Wz-}igz^S@v$yr^sw@SMpM}6sWS$fjY;J@s&naUDP*Xll!+si#9pdpqPK~q_h07s znLf&kvS_F*V9zt99OjuR4#BT_q%Y zw$sv3!M^9}iN4TC|DnAMy%}Zny?&Cjr&+mjp5(c1mcD$0^z?K#XKo}E3Q-X*q-NV= zJXINH^xZAYeOAcN*UDJEQO-H-nVd1Me{cNyqcPN1%4EEX<%>=H@k=KIXEGT1D#+!V zIjmCfzdwIfRTXDzr`TRG&5q$y!vLdEwMGVC> zRBejPh+&x#)6Qw%D)h~$s9L7RO*3k!ieee+6+;obFWOhkB17vKI@i!TM$}Lh#WK~K z>QzOt4E2hkNI^xt=zdfc!&0>o9i!~u`9f#mK)Uy~wl>n~H1{WjUFVmmas0&}4hQ*s zFLt{azOpK%K_{*Q9z4hD@lUlQp*2WiEi(NCvgD++SEx8FY#bN1ToQ^C!ebesOuh4h z(A-->&*4&H$IJL=;YqsFLSI&RV@^1FN9Xprsfv54O$TVn1*uFr@Q%3&%v95GV>1fho z^+91eBwXDuth_12)E^lT#t&+IMCkGhLv6xHw{TYDea&v-Aup*uACrd~Nx#y{-B)i)g2snv1S2DJ< zj@-~TLLCk4eX*U`zD^Pc_K6*D2;nsJ;gCB%c+{f8W6H6Dn`2FX8lBY@;`^LfL zJ1$nJ{@>5v)YQbMty64km}dLv30xT$H@+0^z9wAo`SAPw4A+eC(ynVaXOtZ$rr7=YBK?<^2?m4Q7m#9wKe3jDd)FTB@1EYT|H|tVEWEQvshxaO O&W^8^D->RRzWfb~B{1{= literal 0 HcmV?d00001 diff --git a/voxygen/test_assets/head.vox b/voxygen/test_assets/head.vox new file mode 100644 index 0000000000000000000000000000000000000000..3aaee9730caa7b84407c61d872fc5d03beaaecb3 GIT binary patch literal 5232 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 literal 0 HcmV?d00001 diff --git a/voxygen/test_assets/sword.vox b/voxygen/test_assets/sword.vox new file mode 100644 index 0000000000000000000000000000000000000000..16fa7b2d177f2239fa7628814974c750922faaf0 GIT binary patch literal 1568 zcmc&zTTEP46y0aw-a9Zn1{j8!0fv!>XuLX^Nc&$cP_63~5Rx4Z+4plr)Voev$ZLjDO2@r++5p*ORR5v-e(m z?S0OzdkVBtaMHX!18Em=0GW5kvTht`q*IUlli81B9mH=5%EX7|J$h zC`(wQ)l@uVaRFV$6WK&EkrB|8cS9Got*MNNsQ7JN*%{B-FqAzMtKt~fR}JNg7%C;C zDUDio+O&{CEzJ_QZ74mgD<^Bx*)KJuD;G6Qu9G#%JU!xgwf!P{%?}DmfVuT`BI})J>*_o4QWMJdC9<@1dX0c)>>h% z+f9pTYdMJ1CuDUa4QbRMeesa#RLHs$ z_Etg0IYHK_;JG^jYo8!-NRUR~c}q~d+AGAWolOT5;7~YqH*@NkL z_);d&{0zoLutfYs{7#Ht6wH4iSeq5x57Dm`Og9R`#|1m@3jFjhb_zz%Fg_rtFB0@r z2?iPj*BNgw_aM-ii^29nOq?x4@Xbn0^wwkTleh54OaLW$P8=%BLCed9INMo?zGlJD zNx{_nf|bt%6(F$mIi=Pw=Tzh=kCoCBdvH+GQozn_0*W(J>EPT@f5 zG-`&gz&+%~y{`oi-xk~|EX4f$JbLp6@Y>P4kfA}O_oriN^2r+pT`qXNUVK9!$ zICJePTs;|h@NEH>{BDf#uKs)fCePG5(Gfv^FK}sc7Mq)!(RWy0Ud9U-hH?1H6k5Jq zLC2jf)YjJGu>fKv@Hb+%1dqOZGT$S9UtL{Yd*=BUM*p*?Su^>xoEhIM7Zu*SUj7AN CVt>2< literal 0 HcmV?d00001 From 86e70751e0a343b913f589e5d5a7827c5e503665 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 14 Jan 2019 14:18:58 +0000 Subject: [PATCH 12/21] Moved to Skeleton and Animation traits --- voxygen/shaders/figure.frag | 11 +- voxygen/shaders/figure.vert | 3 + voxygen/src/anim/mod.rs | 136 +++++++++++++++++++------ voxygen/src/mesh/segment.rs | 15 ++- voxygen/src/render/pipelines/figure.rs | 4 +- voxygen/src/scene/figure.rs | 10 +- voxygen/src/scene/mod.rs | 36 +++---- 7 files changed, 149 insertions(+), 66 deletions(-) diff --git a/voxygen/shaders/figure.frag b/voxygen/shaders/figure.frag index 60b1454ce3..a51a540a39 100644 --- a/voxygen/shaders/figure.frag +++ b/voxygen/shaders/figure.frag @@ -1,6 +1,7 @@ #version 330 core in vec3 f_pos; +in vec3 f_norm; in vec3 f_col; layout (std140) @@ -22,5 +23,13 @@ uniform u_globals { out vec4 tgt_color; void main() { - tgt_color = vec4(f_col, 1.0); + vec3 world_norm = (model_mat * vec4(f_norm, 0.0)).xyz; + + float ambient = 0.5; + + vec3 sun_dir = normalize(vec3(1.3, 1.7, 1.1)); + + float sun_diffuse = dot(sun_dir, world_norm) * 0.5; + + tgt_color = vec4(f_col * (ambient + sun_diffuse), 1.0); } diff --git a/voxygen/shaders/figure.vert b/voxygen/shaders/figure.vert index 2a27b94a49..52d8d385df 100644 --- a/voxygen/shaders/figure.vert +++ b/voxygen/shaders/figure.vert @@ -1,6 +1,7 @@ #version 330 core in vec3 v_pos; +in vec3 v_norm; in vec3 v_col; in uint v_bone_idx; @@ -30,10 +31,12 @@ uniform u_bones { }; out vec3 f_pos; +out vec3 f_norm; out vec3 f_col; void main() { f_pos = v_pos; + f_norm = v_norm; f_col = v_col; gl_Position = diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index afe754cba3..5d4ba84268 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -6,7 +6,7 @@ use crate::render::FigureBoneData; #[derive(Copy, Clone)] pub struct Bone { - parent: Option, // MUST be less than the current bone index + parent_idx: Option, // MUST be less than the current bone index pub offset: Vec3, pub ori: Quaternion, } @@ -14,50 +14,122 @@ pub struct Bone { impl Bone { pub fn default() -> Self { Self { - parent: None, + parent_idx: None, offset: Vec3::zero(), ori: Quaternion::identity(), } } + pub fn get_parent_idx(&self) -> Option { self.parent_idx } + + pub fn set_parent_idx(&mut self, parent_idx: u8) { + self.parent_idx = Some(parent_idx); + } + pub fn compute_base_matrix(&self) -> Mat4 { Mat4::::translation_3d(self.offset) * Mat4::from(self.ori) } } -#[derive(Copy, Clone)] -pub struct Skeleton { - bones: [Bone; 16], +pub trait Skeleton { + fn compute_matrices(&self) -> [FigureBoneData; 16]; } -impl Skeleton { - pub fn default() -> Self { +pub struct CharacterSkeleton { + head: Bone, + chest: Bone, + belt: Bone, + leggings: Bone, + l_hand: Bone, + r_hand: Bone, + l_foot: Bone, + r_foot: Bone, + back: Bone, +} + +impl CharacterSkeleton { + pub fn new() -> Self { Self { - bones: [Bone::default(); 16], + head: Bone::default(), + chest: Bone::default(), + belt: Bone::default(), + leggings: Bone::default(), + l_hand: Bone::default(), + r_hand: Bone::default(), + l_foot: Bone::default(), + r_foot: Bone::default(), + back: Bone::default(), } } - - pub fn with_bone(mut self, bone_idx: u8, bone: Bone) -> Self { - self.bones[bone_idx as usize] = bone; - self - } - - pub fn bone(&self, bone_idx: u8) -> &Bone { &self.bones[bone_idx as usize] } - pub fn bone_mut(&mut self, bone_idx: u8) -> &mut Bone { &mut self.bones[bone_idx as usize] } - - pub fn compute_matrices(&self) -> [FigureBoneData; 16] { - let mut bone_data = [FigureBoneData::default(); 16]; - for i in 0..16 { - bone_data[i] = FigureBoneData::new( - self.bones[i].compute_base_matrix() - // * - //if let Some(parent_idx) = self.bones[i].parent { - // bone_data[parent_idx as usize] - //} else { - // Mat4::identity() - //} - ); - } - bone_data - } +} + +impl Skeleton for CharacterSkeleton { + fn compute_matrices(&self) -> [FigureBoneData; 16] { + let chest_mat = self.chest.compute_base_matrix(); + + [ + FigureBoneData::new(self.head.compute_base_matrix()), + FigureBoneData::new(chest_mat), + FigureBoneData::new(self.belt.compute_base_matrix()), + FigureBoneData::new(self.leggings.compute_base_matrix()), + FigureBoneData::new(self.l_hand.compute_base_matrix()), + FigureBoneData::new(self.r_hand.compute_base_matrix()), + FigureBoneData::new(self.l_foot.compute_base_matrix()), + FigureBoneData::new(self.r_foot.compute_base_matrix()), + FigureBoneData::new(chest_mat * self.back.compute_base_matrix()), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + ] + } +} + +pub trait Animation { + type Skeleton; + type Dependency; + + fn update_skeleton( + skeleton: &mut Self::Skeleton, + dependency: Self::Dependency, + ); +} + +pub struct RunAnimation; + +impl Animation for RunAnimation { + type Skeleton = CharacterSkeleton; + type Dependency = f64; + + fn update_skeleton( + skeleton: &mut Self::Skeleton, + time: f64, + ) { + let wave = (time as f32 * 10.0).sin(); + let wave_fast = (time as f32 * 5.0).sin(); + + skeleton.head.offset = Vec3::unit_z() * 13.0; + skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); + + skeleton.chest.offset = Vec3::unit_z() * 9.0; + skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3); + + skeleton.belt.offset = Vec3::unit_z() * 7.0; + skeleton.belt.ori = Quaternion::rotation_z(wave * 0.3); + + skeleton.leggings.offset = Vec3::unit_z() * 4.0; + skeleton.leggings.ori = Quaternion::rotation_z(wave * 0.3); + + skeleton.l_hand.offset = Vec3::new(-8.0, wave * 4.0, 9.0); + skeleton.r_hand.offset = Vec3::new(8.0, -wave * 4.0, 9.0); + + skeleton.l_foot.offset = Vec3::new(-3.0, -wave * 4.0, -(wave_fast.abs() - 0.5) * 3.0); + skeleton.r_foot.offset = Vec3::new(3.0, wave * 4.0, (wave_fast.abs() - 0.5) * 3.0); + + skeleton.back.offset = Vec3::new(-8.0, 5.0, 16.0); + skeleton.back.ori = Quaternion::rotation_y(2.5); + } } diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 06a6048e05..3cd86b8b5b 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -29,14 +29,15 @@ fn create_quad( origin: Vec3, unit_x: Vec3, unit_y: Vec3, + norm: Vec3, col: Rgb, bone: u8, ) -> Quad { Quad::new( - FigureVertex::new(origin, col, bone), - FigureVertex::new(origin + unit_x, col, bone), - FigureVertex::new(origin + unit_x + unit_y, col, bone), - FigureVertex::new(origin + unit_y, col, bone), + FigureVertex::new(origin, norm, col, bone), + FigureVertex::new(origin + unit_x, norm, col, bone), + FigureVertex::new(origin + unit_x + unit_y, norm, col, bone), + FigureVertex::new(origin + unit_y, norm, col, bone), ) } @@ -61,6 +62,7 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32) + Vec3::unit_y(), -Vec3::unit_y(), Vec3::unit_z(), + -Vec3::unit_x(), col, 0, )); @@ -69,6 +71,7 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32) + Vec3::unit_x(), Vec3::unit_y(), Vec3::unit_z(), + Vec3::unit_x(), col, 0, )); @@ -77,6 +80,7 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32), Vec3::unit_x(), Vec3::unit_z(), + -Vec3::unit_y(), col, 0, )); @@ -85,6 +89,7 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32) + Vec3::unit_y(), Vec3::unit_z(), Vec3::unit_x(), + Vec3::unit_y(), col, 0, )); @@ -93,6 +98,7 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32), Vec3::unit_y(), Vec3::unit_x(), + -Vec3::unit_z(), col, 0, )); @@ -101,6 +107,7 @@ impl Meshable for Segment { offs + pos.map(|e| e as f32) + Vec3::unit_z(), Vec3::unit_x(), Vec3::unit_y(), + Vec3::unit_z(), col, 0, )); diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs index e7d14f272b..4c4d0de2be 100644 --- a/voxygen/src/render/pipelines/figure.rs +++ b/voxygen/src/render/pipelines/figure.rs @@ -25,6 +25,7 @@ use super::{ gfx_defines! { vertex Vertex { pos: [f32; 3] = "v_pos", + norm: [f32; 3] = "v_norm", col: [f32; 3] = "v_col", bone_idx: u8 = "v_bone_idx", } @@ -50,10 +51,11 @@ gfx_defines! { } impl Vertex { - pub fn new(pos: Vec3, col: Rgb, bone_idx: u8) -> Self { + pub fn new(pos: Vec3, norm: Vec3, col: Rgb, bone_idx: u8) -> Self { Self { pos: pos.into_array(), col: col.into_array(), + norm: norm.into_array(), bone_idx, } } diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index 92bc610016..81b6fec25c 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -14,7 +14,7 @@ use crate::{ anim::Skeleton, }; -pub struct Figure { +pub struct Figure { // GPU data model: Model, bone_consts: Consts, @@ -22,15 +22,15 @@ pub struct Figure { // CPU data bone_meshes: [Option>; 16], - pub skeleton: Skeleton, + pub skeleton: S, } -impl Figure { +impl Figure { pub fn new( renderer: &mut Renderer, - bone_meshes: [Option>; 16] + bone_meshes: [Option>; 16], + skeleton: S, ) -> Result { - let skeleton = Skeleton::default(); let mut this = Self { model: renderer.create_model(&Mesh::new())?, bone_consts: renderer.create_consts(&skeleton.compute_matrices())?, diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 127aff17b2..d529fe9473 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -30,6 +30,11 @@ use crate::{ }, window::Event, mesh::Meshable, + anim::{ + Animation, + CharacterSkeleton, + RunAnimation, + }, }; // Local @@ -51,7 +56,7 @@ pub struct Scene { globals: Consts, skybox: Skybox, - test_figure: Figure, + test_figure: Figure, client: Client, } @@ -85,10 +90,10 @@ impl Scene { 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("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, 0.0))), - Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -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, 0.0))), + Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, 0.0))), Some(load_segment("sword.vox").generate_mesh_with_offset(Vec3::new(-6.5, -1.0, 0.0))), None, None, @@ -98,6 +103,7 @@ impl Scene { None, None, ], + CharacterSkeleton::new(), ) .unwrap(), @@ -142,26 +148,10 @@ impl Scene { .expect("Failed to update global constants"); // TODO: Don't do this here - let offs = (self.client.state().get_tick() as f32 * 10.0).sin(); - self.test_figure.skeleton.bone_mut(0).offset = Vec3::new(0.0, 0.0, 13.0); - self.test_figure.skeleton.bone_mut(0).ori = Quaternion::rotation_z(offs * 0.3); - // Chest - self.test_figure.skeleton.bone_mut(1).offset = Vec3::new(0.0, 0.0, 9.0); - self.test_figure.skeleton.bone_mut(2).offset = Vec3::new(0.0, 0.0, 7.0); - self.test_figure.skeleton.bone_mut(3).offset = Vec3::new(0.0, 0.0, 4.0); - self.test_figure.skeleton.bone_mut(1).ori = Quaternion::rotation_z(offs * 0.15); - self.test_figure.skeleton.bone_mut(2).ori = Quaternion::rotation_z(offs * 0.15); - self.test_figure.skeleton.bone_mut(3).ori = Quaternion::rotation_z(offs * 0.15); - //Feet - self.test_figure.skeleton.bone_mut(4).offset = Vec3::new(-3.0, -offs * 4.0, 0.0); - self.test_figure.skeleton.bone_mut(5).offset = Vec3::new(3.0, offs * 4.0, 0.0); - // Hands - self.test_figure.skeleton.bone_mut(6).offset = Vec3::new(-8.0, offs * 4.0, 9.0); - self.test_figure.skeleton.bone_mut(7).offset = Vec3::new(8.0, -offs * 4.0, 9.0); - // Sword - self.test_figure.skeleton.bone_mut(8).offset = Vec3::new(-8.0, 5.0, 24.0); - self.test_figure.skeleton.bone_mut(8).ori = Quaternion::rotation_y(2.5); - + RunAnimation::update_skeleton( + &mut self.test_figure.skeleton, + self.client.state().get_tick(), + ); self.test_figure.update_locals(renderer, FigureLocals::default()); self.test_figure.update_skeleton(renderer); } From 0a122431db45eb0b0812a209ee62533f493d5d5d Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 14 Jan 2019 14:40:22 +0000 Subject: [PATCH 13/21] Restructured animation code --- voxygen/src/anim/character/mod.rs | 66 +++++++++++++++ voxygen/src/anim/character/run.rs | 44 ++++++++++ voxygen/src/anim/mod.rs | 91 +-------------------- voxygen/src/mesh/segment.rs | 128 ++++++++++++++++++------------ voxygen/src/scene/mod.rs | 7 +- 5 files changed, 193 insertions(+), 143 deletions(-) create mode 100644 voxygen/src/anim/character/mod.rs create mode 100644 voxygen/src/anim/character/run.rs diff --git a/voxygen/src/anim/character/mod.rs b/voxygen/src/anim/character/mod.rs new file mode 100644 index 0000000000..0e63aecf31 --- /dev/null +++ b/voxygen/src/anim/character/mod.rs @@ -0,0 +1,66 @@ +pub mod run; + +// Reexports +pub use self::run::RunAnimation; + +// Crate +use crate::render::FigureBoneData; + +// Local +use super::{ + Skeleton, + Bone, +}; + +pub struct CharacterSkeleton { + head: Bone, + chest: Bone, + belt: Bone, + leggings: Bone, + l_hand: Bone, + r_hand: Bone, + l_foot: Bone, + r_foot: Bone, + back: Bone, +} + +impl CharacterSkeleton { + pub fn new() -> Self { + Self { + head: Bone::default(), + chest: Bone::default(), + belt: Bone::default(), + leggings: Bone::default(), + l_hand: Bone::default(), + r_hand: Bone::default(), + l_foot: Bone::default(), + r_foot: Bone::default(), + back: Bone::default(), + } + } +} + +impl Skeleton for CharacterSkeleton { + fn compute_matrices(&self) -> [FigureBoneData; 16] { + let chest_mat = self.chest.compute_base_matrix(); + + [ + FigureBoneData::new(self.head.compute_base_matrix()), + FigureBoneData::new(chest_mat), + FigureBoneData::new(self.belt.compute_base_matrix()), + FigureBoneData::new(self.leggings.compute_base_matrix()), + FigureBoneData::new(self.l_hand.compute_base_matrix()), + FigureBoneData::new(self.r_hand.compute_base_matrix()), + FigureBoneData::new(self.l_foot.compute_base_matrix()), + FigureBoneData::new(self.r_foot.compute_base_matrix()), + FigureBoneData::new(chest_mat * self.back.compute_base_matrix()), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + FigureBoneData::default(), + ] + } +} diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs new file mode 100644 index 0000000000..8e055a8fd1 --- /dev/null +++ b/voxygen/src/anim/character/run.rs @@ -0,0 +1,44 @@ +// Library +use vek::*; + +// Local +use super::{ + CharacterSkeleton, + super::Animation, +}; + +pub struct RunAnimation; + +impl Animation for RunAnimation { + type Skeleton = CharacterSkeleton; + type Dependency = f64; + + fn update_skeleton( + skeleton: &mut Self::Skeleton, + time: f64, + ) { + let wave = (time as f32 * 10.0).sin(); + let wave_fast = (time as f32 * 5.0).sin(); + + skeleton.head.offset = Vec3::unit_z() * 13.0; + skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); + + skeleton.chest.offset = Vec3::unit_z() * 9.0; + skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3); + + skeleton.belt.offset = Vec3::unit_z() * 7.0; + skeleton.belt.ori = Quaternion::rotation_z(wave * 0.3); + + skeleton.leggings.offset = Vec3::unit_z() * 4.0; + skeleton.leggings.ori = Quaternion::rotation_z(wave * 0.3); + + skeleton.l_hand.offset = Vec3::new(-8.0, wave * 4.0, 9.0); + skeleton.r_hand.offset = Vec3::new(8.0, -wave * 4.0, 9.0); + + skeleton.l_foot.offset = Vec3::new(-3.5, -wave * 4.0, -(wave_fast.abs() - 0.5) * 3.0); + skeleton.r_foot.offset = Vec3::new(3.5, wave * 4.0, (wave_fast.abs() - 0.5) * 3.0); + + skeleton.back.offset = Vec3::new(-8.0, 5.0, 16.0); + skeleton.back.ori = Quaternion::rotation_y(2.5); + } +} diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index 5d4ba84268..6351ed94df 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -1,3 +1,5 @@ +pub mod character; + // Library use vek::*; @@ -35,59 +37,6 @@ pub trait Skeleton { fn compute_matrices(&self) -> [FigureBoneData; 16]; } -pub struct CharacterSkeleton { - head: Bone, - chest: Bone, - belt: Bone, - leggings: Bone, - l_hand: Bone, - r_hand: Bone, - l_foot: Bone, - r_foot: Bone, - back: Bone, -} - -impl CharacterSkeleton { - pub fn new() -> Self { - Self { - head: Bone::default(), - chest: Bone::default(), - belt: Bone::default(), - leggings: Bone::default(), - l_hand: Bone::default(), - r_hand: Bone::default(), - l_foot: Bone::default(), - r_foot: Bone::default(), - back: Bone::default(), - } - } -} - -impl Skeleton for CharacterSkeleton { - fn compute_matrices(&self) -> [FigureBoneData; 16] { - let chest_mat = self.chest.compute_base_matrix(); - - [ - FigureBoneData::new(self.head.compute_base_matrix()), - FigureBoneData::new(chest_mat), - FigureBoneData::new(self.belt.compute_base_matrix()), - FigureBoneData::new(self.leggings.compute_base_matrix()), - FigureBoneData::new(self.l_hand.compute_base_matrix()), - FigureBoneData::new(self.r_hand.compute_base_matrix()), - FigureBoneData::new(self.l_foot.compute_base_matrix()), - FigureBoneData::new(self.r_foot.compute_base_matrix()), - FigureBoneData::new(chest_mat * self.back.compute_base_matrix()), - FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), - FigureBoneData::default(), - ] - } -} - pub trait Animation { type Skeleton; type Dependency; @@ -97,39 +46,3 @@ pub trait Animation { dependency: Self::Dependency, ); } - -pub struct RunAnimation; - -impl Animation for RunAnimation { - type Skeleton = CharacterSkeleton; - type Dependency = f64; - - fn update_skeleton( - skeleton: &mut Self::Skeleton, - time: f64, - ) { - let wave = (time as f32 * 10.0).sin(); - let wave_fast = (time as f32 * 5.0).sin(); - - skeleton.head.offset = Vec3::unit_z() * 13.0; - skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); - - skeleton.chest.offset = Vec3::unit_z() * 9.0; - skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3); - - skeleton.belt.offset = Vec3::unit_z() * 7.0; - skeleton.belt.ori = Quaternion::rotation_z(wave * 0.3); - - skeleton.leggings.offset = Vec3::unit_z() * 4.0; - skeleton.leggings.ori = Quaternion::rotation_z(wave * 0.3); - - skeleton.l_hand.offset = Vec3::new(-8.0, wave * 4.0, 9.0); - skeleton.r_hand.offset = Vec3::new(8.0, -wave * 4.0, 9.0); - - skeleton.l_foot.offset = Vec3::new(-3.0, -wave * 4.0, -(wave_fast.abs() - 0.5) * 3.0); - skeleton.r_foot.offset = Vec3::new(3.0, wave * 4.0, (wave_fast.abs() - 0.5) * 3.0); - - skeleton.back.offset = Vec3::new(-8.0, 5.0, 16.0); - skeleton.back.ori = Quaternion::rotation_y(2.5); - } -} diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 3cd86b8b5b..4bb540afb5 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -55,62 +55,90 @@ impl Meshable for Segment { { let col = col.map(|e| e as f32 / 255.0); - // TODO: Face occlusion - // -x - mesh.push_quad(create_quad( - offs + pos.map(|e| e as f32) + Vec3::unit_y(), - -Vec3::unit_y(), - Vec3::unit_z(), - -Vec3::unit_x(), - col, - 0, - )); + if self.get(pos - Vec3::unit_x()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_y(), + -Vec3::unit_y(), + Vec3::unit_z(), + -Vec3::unit_x(), + col, + 0, + )); + } // +x - mesh.push_quad(create_quad( - offs + pos.map(|e| e as f32) + Vec3::unit_x(), - Vec3::unit_y(), - Vec3::unit_z(), - Vec3::unit_x(), - col, - 0, - )); + if self.get(pos + Vec3::unit_x()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_x(), + Vec3::unit_y(), + Vec3::unit_z(), + Vec3::unit_x(), + col, + 0, + )); + } // -y - mesh.push_quad(create_quad( - offs + pos.map(|e| e as f32), - Vec3::unit_x(), - Vec3::unit_z(), - -Vec3::unit_y(), - col, - 0, - )); + if self.get(pos - Vec3::unit_y()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32), + Vec3::unit_x(), + Vec3::unit_z(), + -Vec3::unit_y(), + col, + 0, + )); + } // +y - mesh.push_quad(create_quad( - offs + pos.map(|e| e as f32) + Vec3::unit_y(), - Vec3::unit_z(), - Vec3::unit_x(), - Vec3::unit_y(), - col, - 0, - )); + if self.get(pos + Vec3::unit_y()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_y(), + Vec3::unit_z(), + Vec3::unit_x(), + Vec3::unit_y(), + col, + 0, + )); + } // -z - mesh.push_quad(create_quad( - offs + pos.map(|e| e as f32), - Vec3::unit_y(), - Vec3::unit_x(), - -Vec3::unit_z(), - col, - 0, - )); + if self.get(pos - Vec3::unit_z()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32), + Vec3::unit_y(), + Vec3::unit_x(), + -Vec3::unit_z(), + col, + 0, + )); + } // +z - mesh.push_quad(create_quad( - offs + pos.map(|e| e as f32) + Vec3::unit_z(), - Vec3::unit_x(), - Vec3::unit_y(), - Vec3::unit_z(), - col, - 0, - )); + if self.get(pos + Vec3::unit_z()) + .map(|v| v.is_empty()) + .unwrap_or(true) + { + mesh.push_quad(create_quad( + offs + pos.map(|e| e as f32) + Vec3::unit_z(), + Vec3::unit_x(), + Vec3::unit_y(), + Vec3::unit_z(), + col, + 0, + )); + } } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index d529fe9473..2fb16e1ad3 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -32,8 +32,7 @@ use crate::{ mesh::Meshable, anim::{ Animation, - CharacterSkeleton, - RunAnimation, + character::{CharacterSkeleton, RunAnimation}, }, }; @@ -152,8 +151,8 @@ impl Scene { &mut self.test_figure.skeleton, self.client.state().get_tick(), ); - self.test_figure.update_locals(renderer, FigureLocals::default()); - self.test_figure.update_skeleton(renderer); + self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap(); + self.test_figure.update_skeleton(renderer).unwrap(); } /// Render the scene using the provided `Renderer` From c5a506dad2024e35674c225676bb3ddac5512023 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 14 Jan 2019 15:47:57 +0000 Subject: [PATCH 14/21] Moved client to session type --- voxygen/shaders/figure.frag | 2 +- voxygen/shaders/figure.vert | 2 +- voxygen/shaders/skybox.frag | 2 +- voxygen/shaders/skybox.vert | 2 +- voxygen/src/anim/character/run.rs | 12 ++++++------ voxygen/src/anim/mod.rs | 8 -------- voxygen/src/menu/title.rs | 2 +- voxygen/src/render/pipelines/mod.rs | 8 ++++---- voxygen/src/scene/mod.rs | 27 +++++---------------------- voxygen/src/session.rs | 19 ++++++++++++++++--- 10 files changed, 36 insertions(+), 48 deletions(-) diff --git a/voxygen/shaders/figure.frag b/voxygen/shaders/figure.frag index a51a540a39..769bf224a8 100644 --- a/voxygen/shaders/figure.frag +++ b/voxygen/shaders/figure.frag @@ -17,7 +17,7 @@ uniform u_globals { vec4 focus_pos; vec4 view_distance; vec4 time_of_day; - vec4 time; + vec4 tick; }; out vec4 tgt_color; diff --git a/voxygen/shaders/figure.vert b/voxygen/shaders/figure.vert index 52d8d385df..1e8d15524c 100644 --- a/voxygen/shaders/figure.vert +++ b/voxygen/shaders/figure.vert @@ -18,7 +18,7 @@ uniform u_globals { vec4 focus_pos; vec4 view_distance; vec4 time_of_day; - vec4 time; + vec4 tick; }; struct BoneData { diff --git a/voxygen/shaders/skybox.frag b/voxygen/shaders/skybox.frag index f7387bd430..caa51fe4d9 100644 --- a/voxygen/shaders/skybox.frag +++ b/voxygen/shaders/skybox.frag @@ -15,7 +15,7 @@ uniform u_globals { vec4 focus_pos; vec4 view_distance; vec4 time_of_day; - vec4 time; + vec4 tick; }; out vec4 tgt_color; diff --git a/voxygen/shaders/skybox.vert b/voxygen/shaders/skybox.vert index 3b58fcfd18..165e747ae9 100644 --- a/voxygen/shaders/skybox.vert +++ b/voxygen/shaders/skybox.vert @@ -15,7 +15,7 @@ uniform u_globals { vec4 focus_pos; vec4 view_distance; vec4 time_of_day; - vec4 time; + vec4 tick; }; out vec3 f_pos; diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 8e055a8fd1..472ae57923 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -17,8 +17,8 @@ impl Animation for RunAnimation { skeleton: &mut Self::Skeleton, time: f64, ) { - let wave = (time as f32 * 10.0).sin(); - let wave_fast = (time as f32 * 5.0).sin(); + let wave = (time as f32 * 8.0).sin(); + let wave_fast = (time as f32 * 4.0).sin(); skeleton.head.offset = Vec3::unit_z() * 13.0; skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); @@ -32,11 +32,11 @@ impl Animation for RunAnimation { skeleton.leggings.offset = Vec3::unit_z() * 4.0; skeleton.leggings.ori = Quaternion::rotation_z(wave * 0.3); - skeleton.l_hand.offset = Vec3::new(-8.0, wave * 4.0, 9.0); - skeleton.r_hand.offset = Vec3::new(8.0, -wave * 4.0, 9.0); + skeleton.l_hand.offset = Vec3::new(-8.0, wave * 5.0, 9.0); + skeleton.r_hand.offset = Vec3::new(8.0, -wave * 5.0, 9.0); - skeleton.l_foot.offset = Vec3::new(-3.5, -wave * 4.0, -(wave_fast.abs() - 0.5) * 3.0); - skeleton.r_foot.offset = Vec3::new(3.5, wave * 4.0, (wave_fast.abs() - 0.5) * 3.0); + skeleton.l_foot.offset = Vec3::new(-3.5, -wave * 4.0, -(wave_fast.abs() - 0.5).abs() * 4.0); + skeleton.r_foot.offset = Vec3::new(3.5, wave * 4.0, -(wave_fast.abs() - 0.5).abs() * 4.0); skeleton.back.offset = Vec3::new(-8.0, 5.0, 16.0); skeleton.back.ori = Quaternion::rotation_y(2.5); diff --git a/voxygen/src/anim/mod.rs b/voxygen/src/anim/mod.rs index 6351ed94df..088f56a4ca 100644 --- a/voxygen/src/anim/mod.rs +++ b/voxygen/src/anim/mod.rs @@ -8,7 +8,6 @@ use crate::render::FigureBoneData; #[derive(Copy, Clone)] pub struct Bone { - parent_idx: Option, // MUST be less than the current bone index pub offset: Vec3, pub ori: Quaternion, } @@ -16,18 +15,11 @@ pub struct Bone { impl Bone { pub fn default() -> Self { Self { - parent_idx: None, offset: Vec3::zero(), ori: Quaternion::identity(), } } - pub fn get_parent_idx(&self) -> Option { self.parent_idx } - - pub fn set_parent_idx(&mut self, parent_idx: u8) { - self.parent_idx = Some(parent_idx); - } - pub fn compute_base_matrix(&self) -> Mat4 { Mat4::::translation_3d(self.offset) * Mat4::from(self.ori) } diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index c38533fe7c..39c48e12a6 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -31,7 +31,7 @@ impl PlayState for TitleState { Event::Close => return PlayStateResult::Shutdown, // When space is pressed, start a session Event::Char(' ') => return PlayStateResult::Push( - Box::new(SessionState::from_renderer(global_state.window.renderer_mut())), + Box::new(SessionState::new(global_state.window.renderer_mut())), ), // Ignore all other events _ => {}, diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 663174b70d..cd5f408181 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -23,7 +23,7 @@ gfx_defines! { // TODO: Fix whatever alignment issue requires these uniforms to be aligned view_distance: [f32; 4] = "view_distance", time_of_day: [f32; 4] = "time_of_day", // TODO: Make this f64 - time: [f32; 4] = "time", + tick: [f32; 4] = "tick", } } @@ -37,7 +37,7 @@ impl Globals { focus_pos: [0.0; 4], view_distance: [0.0; 4], time_of_day: [0.0; 4], - time: [0.0; 4], + tick: [0.0; 4], } } @@ -49,7 +49,7 @@ impl Globals { focus_pos: Vec3, view_distance: f32, time_of_day: f64, - time: f32, + tick: f64, ) -> Self { Self { view_mat: arr_to_mat(view_mat.into_col_array()), @@ -58,7 +58,7 @@ impl Globals { focus_pos: Vec4::from(focus_pos).into_array(), view_distance: [view_distance; 4], time_of_day: [time_of_day as f32; 4], - time: [time; 4], + tick: [tick as f32; 4], } } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 2fb16e1ad3..c3bb5c1585 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,23 +1,16 @@ pub mod camera; pub mod figure; -// Standard -use std::time::Duration; - // Library use vek::*; use dot_vox; // Project -use client::{ - self, - Client, -}; use common::figure::Segment; +use client::Client; // Crate use crate::{ - Error, render::{ Consts, Globals, @@ -56,8 +49,6 @@ pub struct Scene { skybox: Skybox, test_figure: Figure, - - client: Client, } // TODO: Make a proper asset loading system @@ -105,17 +96,9 @@ impl Scene { CharacterSkeleton::new(), ) .unwrap(), - - client: Client::new(), } } - /// Tick the scene (and the client attached to it) - pub fn tick(&mut self, dt: Duration) -> Result<(), Error> { - self.client.tick(client::Input {}, dt)?; - Ok(()) - } - /// Handle an incoming user input event (i.e: cursor moved, key pressed, window closed, etc.). pub fn handle_input_event(&mut self, event: Event) -> bool { match event { @@ -130,7 +113,7 @@ impl Scene { } /// Maintain and update GPU data such as constant buffers, models, etc. - pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer) { + pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer, client: &Client) { // Compute camera matrices let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(); @@ -141,15 +124,15 @@ impl Scene { cam_pos, self.camera.get_focus_pos(), 10.0, - self.client.state().get_time_of_day(), - 0.0, + client.state().get_time_of_day(), + client.state().get_tick(), )]) .expect("Failed to update global constants"); // TODO: Don't do this here RunAnimation::update_skeleton( &mut self.test_figure.skeleton, - self.client.state().get_tick(), + client.state().get_tick(), ); self.test_figure.update_locals(renderer, FigureLocals::default()).unwrap(); self.test_figure.update_skeleton(renderer).unwrap(); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index a9b97b0af3..ac8393664b 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -6,9 +6,14 @@ use vek::*; // Project use common::clock::Clock; +use client::{ + self, + Client, +}; // Crate use crate::{ + Error, PlayState, PlayStateResult, GlobalState, @@ -21,15 +26,17 @@ const FPS: u64 = 60; pub struct SessionState { scene: Scene, + client: Client, } /// Represents an active game session (i.e: one that is being played) impl SessionState { /// Create a new `SessionState` - pub fn from_renderer(renderer: &mut Renderer) -> Self { + pub fn new(renderer: &mut Renderer) -> Self { Self { // Create a scene for this session. The scene handles visible elements of the game world scene: Scene::new(renderer), + client: Client::new(), } } } @@ -38,12 +45,18 @@ impl SessionState { const BG_COLOR: Rgba = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 }; impl SessionState { + /// Tick the session (and the client attached to it) + pub fn tick(&mut self, dt: Duration) -> Result<(), Error> { + self.client.tick(client::Input {}, dt)?; + Ok(()) + } + /// 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.scene.maintain_gpu_data(renderer, &self.client); // Clear the screen renderer.clear(BG_COLOR); @@ -79,7 +92,7 @@ impl PlayState for SessionState { } // Perform an in-game tick - self.scene.tick(clock.get_last_delta()) + self.tick(clock.get_last_delta()) .expect("Failed to tick the scene"); // Render the session From ae4f1ae2ea4cb8f86bdf79ef7cf2370942ea3747 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 14 Jan 2019 18:49:53 +0000 Subject: [PATCH 15/21] Added vox trait --- common/Cargo.toml | 1 + common/src/figure/cell.rs | 27 +++++++++++-------- common/src/figure/mod.rs | 2 +- common/src/state.rs | 11 +++++--- common/src/terrain/block.rs | 16 ++++++++++++ common/src/vol.rs | 23 +++++++++++++++-- common/src/volumes/chunk.rs | 15 ++++++----- common/src/volumes/dyna.rs | 15 ++++++----- common/src/volumes/vol_map.rs | 43 ++++++++++++++++++++++++++----- voxygen/src/anim/character/mod.rs | 6 ++--- voxygen/src/anim/character/run.rs | 4 +-- voxygen/src/mesh/segment.rs | 1 + 12 files changed, 120 insertions(+), 44 deletions(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index d35d5f094a..b07fcfde69 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -6,5 +6,6 @@ edition = "2018" [dependencies] specs = "0.14" +shred = "0.7" vek = "0.9" dot_vox = "1.0" diff --git a/common/src/figure/cell.rs b/common/src/figure/cell.rs index c6e1d79cc4..2d445dcf3d 100644 --- a/common/src/figure/cell.rs +++ b/common/src/figure/cell.rs @@ -1,6 +1,9 @@ // Library use vek::*; +// Crate +use crate::vol::Vox; + /// A type representing a single voxel in a figure #[derive(Copy, Clone, Debug)] pub enum Cell { @@ -9,21 +12,10 @@ pub enum Cell { } impl Cell { - pub fn empty() -> Self { - Cell::Empty - } - pub fn new(rgb: Rgb) -> Self { Cell::Filled(rgb.into_array()) } - pub fn is_empty(&self) -> bool { - match self { - Cell::Filled(_) => false, - Cell::Empty => true, - } - } - pub fn get_color(&self) -> Option> { match self { Cell::Filled(col) => Some(Rgb::from(*col)), @@ -31,3 +23,16 @@ impl Cell { } } } + +impl Vox for Cell { + fn empty() -> Self { + Cell::Empty + } + + fn is_empty(&self) -> bool { + match self { + Cell::Filled(_) => false, + Cell::Empty => true, + } + } +} diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs index cd39161bcf..1d59de63b1 100644 --- a/common/src/figure/mod.rs +++ b/common/src/figure/mod.rs @@ -6,7 +6,7 @@ use dot_vox::DotVoxData; // Crate use crate::{ - vol::WriteVol, + vol::{Vox, WriteVol}, volumes::dyna::Dyna, }; diff --git a/common/src/state.rs b/common/src/state.rs index 084b701da6..71a7e741c2 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -3,6 +3,7 @@ use std::time::Duration; // External use specs::World as EcsWorld; +use shred::Fetch; // Crate use crate::{ @@ -24,8 +25,6 @@ struct Tick(f64); /// things like entity components, terrain data, and global state like weather, time of day, etc. pub struct State { ecs_world: EcsWorld, - terrain_map: TerrainMap, - time: f64, } impl State { @@ -36,14 +35,13 @@ 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(TerrainMap::new()); // Register common components with the state comp::register_local_components(&mut ecs_world); Self { ecs_world, - terrain_map: TerrainMap::new(), - time: 0.0, } } @@ -61,6 +59,11 @@ impl State { self.ecs_world.read_resource::().0 } + /// Get a reference to this state's terrain. + pub fn terrain<'a>(&'a self) -> Fetch<'a, TerrainMap> { + self.ecs_world.read_resource::() + } + /// Execute a single tick, simulating the game state by the given duration. pub fn tick(&mut self, dt: Duration) { // Change the time accordingly diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 0b45fdcf8a..18d8b1d760 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -1,5 +1,21 @@ +// Crate +use crate::vol::Vox; + #[derive(Copy, Clone, Debug)] pub struct Block { kind: u8, color: [u8; 3], } + +impl Vox for Block { + fn empty() -> Self { + Self { + kind: 0, + color: [0; 3], + } + } + + fn is_empty(&self) -> bool { + self.kind == 0 + } +} diff --git a/common/src/vol.rs b/common/src/vol.rs index bb49a86423..5a3afdb314 100644 --- a/common/src/vol.rs +++ b/common/src/vol.rs @@ -1,9 +1,15 @@ // Library use vek::*; +/// A voxel +pub trait Vox { + fn empty() -> Self; + fn is_empty(&self) -> bool; +} + /// A volume that contains voxel data. pub trait BaseVol { - type Vox; + type Vox: Vox; type Err; } @@ -53,13 +59,26 @@ pub trait SizedVol: BaseVol { } } -/// A volume that provided read access to its voxel data. +/// A volume that provides read access to its voxel data. pub trait ReadVol: BaseVol { /// Get a reference to the voxel at the provided position in the volume. #[inline(always)] fn get(&self, pos: Vec3) -> Result<&Self::Vox, Self::Err>; } +/// A volume that provides the ability to sample (i.e: clone a section of) its voxel data. +pub trait SampleVol: BaseVol where Self::Vox: Clone { + type Sample: BaseVol + ReadVol; + /// Take a sample of the volume by cloning voxels within the provided range. + /// + /// Note that value and accessibility of voxels outside the bounds of the sample is + /// implementation-defined and should not be used. + /// + /// Note that the resultant volume has a coordinate space relative to the sample, not the + /// original volume. + fn sample(&self, range: Aabb) -> Result; +} + /// A volume that provides write access to its voxel data. pub trait WriteVol: BaseVol { /// Set the voxel at the provided position in the volume to the provided value. diff --git a/common/src/volumes/chunk.rs b/common/src/volumes/chunk.rs index fcd0912873..97fbdc7368 100644 --- a/common/src/volumes/chunk.rs +++ b/common/src/volumes/chunk.rs @@ -6,6 +6,7 @@ use vek::*; // Local use crate::vol::{ + Vox, BaseVol, SizedVol, ReadVol, @@ -21,13 +22,13 @@ pub enum ChunkErr { // V = Voxel // S = Size (replace when const generics are a thing) // M = Metadata -pub struct Chunk { +pub struct Chunk { vox: Vec, meta: M, phantom: PhantomData, } -impl Chunk { +impl Chunk { /// Used to transform a voxel position in the volume into its corresponding index in the voxel // array. #[inline(always)] @@ -47,17 +48,17 @@ impl Chunk { } } -impl BaseVol for Chunk { +impl BaseVol for Chunk { type Vox = V; type Err = ChunkErr; } -impl SizedVol for Chunk { +impl SizedVol for Chunk { #[inline(always)] fn get_size(&self) -> Vec3 { S::SIZE } } -impl ReadVol for Chunk { +impl ReadVol for Chunk { #[inline(always)] fn get(&self, pos: Vec3) -> Result<&V, ChunkErr> { Self::idx_for(pos) @@ -66,7 +67,7 @@ impl ReadVol for Chunk { } } -impl WriteVol for Chunk { +impl WriteVol for Chunk { #[inline(always)] fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), ChunkErr> { Self::idx_for(pos) @@ -76,7 +77,7 @@ impl WriteVol for Chunk { } } -impl Chunk { +impl Chunk { /// Create a new `Chunk` with the provided dimensions and all voxels filled with duplicates of /// the provided voxel. pub fn filled(vox: V, meta: M) -> Self { diff --git a/common/src/volumes/dyna.rs b/common/src/volumes/dyna.rs index f975b7e02a..ee51bcbb25 100644 --- a/common/src/volumes/dyna.rs +++ b/common/src/volumes/dyna.rs @@ -3,6 +3,7 @@ use vek::*; // Local use crate::vol::{ + Vox, BaseVol, SizedVol, ReadVol, @@ -17,13 +18,13 @@ pub enum DynaErr { // V = Voxel // S = Size (replace when const generics are a thing) // M = Metadata -pub struct Dyna { +pub struct Dyna { vox: Vec, meta: M, sz: Vec3, } -impl Dyna { +impl Dyna { /// Used to transform a voxel position in the volume into its corresponding index in the voxel // array. #[inline(always)] @@ -43,17 +44,17 @@ impl Dyna { } } -impl BaseVol for Dyna { +impl BaseVol for Dyna { type Vox = V; type Err = DynaErr; } -impl SizedVol for Dyna { +impl SizedVol for Dyna { #[inline(always)] fn get_size(&self) -> Vec3 { self.sz } } -impl ReadVol for Dyna { +impl ReadVol for Dyna { #[inline(always)] fn get(&self, pos: Vec3) -> Result<&V, DynaErr> { Self::idx_for(self.sz, pos) @@ -62,7 +63,7 @@ impl ReadVol for Dyna { } } -impl WriteVol for Dyna { +impl WriteVol for Dyna { #[inline(always)] fn set(&mut self, pos: Vec3, vox: Self::Vox) -> Result<(), DynaErr> { Self::idx_for(self.sz, pos) @@ -72,7 +73,7 @@ impl WriteVol for Dyna { } } -impl Dyna { +impl Dyna { /// Create a new `Dyna` with the provided dimensions and all voxels filled with duplicates of /// the provided voxel. pub fn filled(sz: Vec3, vox: V, meta: M) -> Self { diff --git a/common/src/volumes/vol_map.rs b/common/src/volumes/vol_map.rs index c3c2ff0834..33674d4e33 100644 --- a/common/src/volumes/vol_map.rs +++ b/common/src/volumes/vol_map.rs @@ -7,27 +7,34 @@ use vek::*; // Crate use crate::{ vol::{ + Vox, BaseVol, + SizedVol, ReadVol, + SampleVol, WriteVol, VolSize, }, - volumes::chunk::{Chunk, ChunkErr}, + volumes::{ + chunk::{Chunk, ChunkErr}, + dyna::{Dyna, DynaErr}, + }, }; pub enum VolMapErr { NoSuchChunk, ChunkErr(ChunkErr), + DynaErr(DynaErr), } // V = Voxel // S = Size (replace with a const when const generics is a thing) // M = Chunk metadata -pub struct VolMap { +pub struct VolMap { chunks: HashMap, Chunk>, } -impl VolMap { +impl VolMap { #[inline(always)] fn chunk_key(pos: Vec3) -> Vec3 { pos.map2(S::SIZE, |e, sz| e.div_euclid(sz as i32)) @@ -39,12 +46,12 @@ impl VolMap { } } -impl BaseVol for VolMap { +impl BaseVol for VolMap { type Vox = V; type Err = VolMapErr; } -impl ReadVol for VolMap { +impl ReadVol for VolMap { #[inline(always)] fn get(&self, pos: Vec3) -> Result<&V, VolMapErr> { let ck = Self::chunk_key(pos); @@ -57,7 +64,29 @@ impl ReadVol for VolMap { } } -impl WriteVol for VolMap { +impl SampleVol for VolMap { + type Sample = Dyna; + + /// Take a sample of the terrain by cloning the voxels within the provided range. + /// + /// Note that the resultant volume does not carry forward metadata from the original chunks. + fn sample(&self, range: Aabb) -> Result { + let mut sample = Dyna::filled( + range.size().map(|e| e as u32).into(), + V::empty(), + (), + ); + + for pos in sample.iter_positions() { + sample.set(pos, self.get(range.min + pos)?.clone()) + .map_err(|err| VolMapErr::DynaErr(err))?; + } + + Ok(sample) + } +} + +impl WriteVol for VolMap { #[inline(always)] fn set(&mut self, pos: Vec3, vox: V) -> Result<(), VolMapErr> { let ck = Self::chunk_key(pos); @@ -70,7 +99,7 @@ impl WriteVol for VolMap { } } -impl VolMap { +impl VolMap { pub fn new() -> Self { Self { chunks: HashMap::new(), diff --git a/voxygen/src/anim/character/mod.rs b/voxygen/src/anim/character/mod.rs index 0e63aecf31..367cdf32ed 100644 --- a/voxygen/src/anim/character/mod.rs +++ b/voxygen/src/anim/character/mod.rs @@ -16,7 +16,7 @@ pub struct CharacterSkeleton { head: Bone, chest: Bone, belt: Bone, - leggings: Bone, + shorts: Bone, l_hand: Bone, r_hand: Bone, l_foot: Bone, @@ -30,7 +30,7 @@ impl CharacterSkeleton { head: Bone::default(), chest: Bone::default(), belt: Bone::default(), - leggings: Bone::default(), + shorts: Bone::default(), l_hand: Bone::default(), r_hand: Bone::default(), l_foot: Bone::default(), @@ -48,7 +48,7 @@ impl Skeleton for CharacterSkeleton { FigureBoneData::new(self.head.compute_base_matrix()), FigureBoneData::new(chest_mat), FigureBoneData::new(self.belt.compute_base_matrix()), - FigureBoneData::new(self.leggings.compute_base_matrix()), + FigureBoneData::new(self.shorts.compute_base_matrix()), FigureBoneData::new(self.l_hand.compute_base_matrix()), FigureBoneData::new(self.r_hand.compute_base_matrix()), FigureBoneData::new(self.l_foot.compute_base_matrix()), diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 472ae57923..f1bedf8c07 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -29,8 +29,8 @@ impl Animation for RunAnimation { skeleton.belt.offset = Vec3::unit_z() * 7.0; skeleton.belt.ori = Quaternion::rotation_z(wave * 0.3); - skeleton.leggings.offset = Vec3::unit_z() * 4.0; - skeleton.leggings.ori = Quaternion::rotation_z(wave * 0.3); + skeleton.shorts.offset = Vec3::unit_z() * 4.0; + skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.3); skeleton.l_hand.offset = Vec3::new(-8.0, wave * 5.0, 9.0); skeleton.r_hand.offset = Vec3::new(8.0, -wave * 5.0, 9.0); diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 4bb540afb5..0980528190 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -6,6 +6,7 @@ use vek::*; // Project use common::vol::{ + Vox, SizedVol, ReadVol, }; From bd561dd1aa917f13b89a74eba29061c9ad4b9d97 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 14 Jan 2019 23:13:58 +0000 Subject: [PATCH 16/21] Added terrain pipeline --- common/Cargo.toml | 1 + common/src/lib.rs | 1 + common/src/util/mod.rs | 1 + common/src/util/worker_pool.rs | 10 ++++ voxygen/shaders/figure.frag | 16 +++++- voxygen/shaders/figure.vert | 2 + voxygen/shaders/terrain.frag | 33 ++++++++++++ voxygen/shaders/terrain.vert | 36 +++++++++++++ voxygen/src/anim/character/run.rs | 18 ++++--- voxygen/src/render/mod.rs | 4 ++ voxygen/src/render/pipelines/mod.rs | 1 + voxygen/src/render/pipelines/terrain.rs | 68 ++++++++++++++++++++++++ voxygen/src/render/renderer.rs | 31 +++++++++++ voxygen/src/scene/mod.rs | 4 +- voxygen/test_assets/elf/belt.vox | Bin 0 -> 1272 bytes voxygen/test_assets/elf/chest.vox | Bin 0 -> 45228 bytes voxygen/test_assets/elf/foot.vox | Bin 0 -> 1436 bytes voxygen/test_assets/elf/hand.vox | Bin 0 -> 1352 bytes voxygen/test_assets/elf/head.vox | Bin 0 -> 4128 bytes voxygen/test_assets/elf/pants.vox | Bin 0 -> 1936 bytes voxygen/test_assets/elf/sword.vox | Bin 0 -> 1412 bytes 21 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 common/src/util/mod.rs create mode 100644 common/src/util/worker_pool.rs create mode 100644 voxygen/shaders/terrain.frag create mode 100644 voxygen/shaders/terrain.vert create mode 100644 voxygen/src/render/pipelines/terrain.rs create mode 100644 voxygen/test_assets/elf/belt.vox create mode 100644 voxygen/test_assets/elf/chest.vox create mode 100644 voxygen/test_assets/elf/foot.vox create mode 100644 voxygen/test_assets/elf/hand.vox create mode 100644 voxygen/test_assets/elf/head.vox create mode 100644 voxygen/test_assets/elf/pants.vox create mode 100644 voxygen/test_assets/elf/sword.vox diff --git a/common/Cargo.toml b/common/Cargo.toml index b07fcfde69..0975cf220f 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -9,3 +9,4 @@ specs = "0.14" shred = "0.7" vek = "0.9" dot_vox = "1.0" +threadpool = "1.7" diff --git a/common/src/lib.rs b/common/src/lib.rs index 1aee96ce50..4d33fd7988 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -5,5 +5,6 @@ pub mod comp; pub mod figure; pub mod state; pub mod terrain; +pub mod util; pub mod volumes; pub mod vol; diff --git a/common/src/util/mod.rs b/common/src/util/mod.rs new file mode 100644 index 0000000000..05f0169051 --- /dev/null +++ b/common/src/util/mod.rs @@ -0,0 +1 @@ +pub mod worker_pool; diff --git a/common/src/util/worker_pool.rs b/common/src/util/worker_pool.rs new file mode 100644 index 0000000000..a5be780a44 --- /dev/null +++ b/common/src/util/worker_pool.rs @@ -0,0 +1,10 @@ +// Library +use threadpool::ThreadPool; + +pub struct WorkerPool { + thread_pool: ThreadPool, +} + +impl WorkerPool { + +} diff --git a/voxygen/shaders/figure.frag b/voxygen/shaders/figure.frag index 769bf224a8..bf6889d61c 100644 --- a/voxygen/shaders/figure.frag +++ b/voxygen/shaders/figure.frag @@ -3,6 +3,7 @@ in vec3 f_pos; in vec3 f_norm; in vec3 f_col; +flat in uint f_bone_idx; layout (std140) uniform u_locals { @@ -20,10 +21,23 @@ uniform u_globals { vec4 tick; }; +struct BoneData { + mat4 bone_mat; +}; + +layout (std140) +uniform u_bones { + BoneData bones[16]; +}; + out vec4 tgt_color; void main() { - vec3 world_norm = (model_mat * vec4(f_norm, 0.0)).xyz; + vec3 world_norm = ( + model_mat * + bones[f_bone_idx].bone_mat * + vec4(f_norm, 0.0) + ).xyz; float ambient = 0.5; diff --git a/voxygen/shaders/figure.vert b/voxygen/shaders/figure.vert index 1e8d15524c..7880d7876c 100644 --- a/voxygen/shaders/figure.vert +++ b/voxygen/shaders/figure.vert @@ -33,11 +33,13 @@ uniform u_bones { out vec3 f_pos; out vec3 f_norm; out vec3 f_col; +flat out uint f_bone_idx; void main() { f_pos = v_pos; f_norm = v_norm; f_col = v_col; + f_bone_idx = v_bone_idx; gl_Position = proj_mat * diff --git a/voxygen/shaders/terrain.frag b/voxygen/shaders/terrain.frag new file mode 100644 index 0000000000..1b718c3633 --- /dev/null +++ b/voxygen/shaders/terrain.frag @@ -0,0 +1,33 @@ +#version 330 core + +in vec3 f_pos; +in vec3 f_norm; +in vec3 f_col; + +layout (std140) +uniform u_locals { + vec3 model_offs; +}; + +layout (std140) +uniform u_globals { + mat4 view_mat; + mat4 proj_mat; + vec4 cam_pos; + vec4 focus_pos; + vec4 view_distance; + vec4 time_of_day; + vec4 tick; +}; + +out vec4 tgt_color; + +void main() { + float ambient = 0.5; + + vec3 sun_dir = normalize(vec3(1.3, 1.7, 1.1)); + + float sun_diffuse = dot(sun_dir, f_norm) * 0.5; + + tgt_color = vec4(f_col * (ambient + sun_diffuse), 1.0); +} diff --git a/voxygen/shaders/terrain.vert b/voxygen/shaders/terrain.vert new file mode 100644 index 0000000000..e195a1c464 --- /dev/null +++ b/voxygen/shaders/terrain.vert @@ -0,0 +1,36 @@ +#version 330 core + +in vec3 v_pos; +in vec3 v_norm; +in vec3 v_col; + +layout (std140) +uniform u_locals { + vec3 model_offs; +}; + +layout (std140) +uniform u_globals { + mat4 view_mat; + mat4 proj_mat; + vec4 cam_pos; + vec4 focus_pos; + vec4 view_distance; + vec4 time_of_day; + vec4 tick; +}; + +out vec3 f_pos; +out vec3 f_norm; +out vec3 f_col; + +void main() { + f_pos = v_pos; + f_norm = v_norm; + f_col = v_col; + + gl_Position = + proj_mat * + view_mat * + vec4(v_pos + model_offs, 1); +} diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index f1bedf8c07..92411ebc94 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -17,8 +17,8 @@ impl Animation for RunAnimation { skeleton: &mut Self::Skeleton, time: f64, ) { - let wave = (time as f32 * 8.0).sin(); - let wave_fast = (time as f32 * 4.0).sin(); + let wave = (time as f32 * 12.0).sin(); + let wave_fast = (time as f32 * 6.0).sin(); skeleton.head.offset = Vec3::unit_z() * 13.0; skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); @@ -27,16 +27,18 @@ impl Animation for RunAnimation { skeleton.chest.ori = Quaternion::rotation_z(wave * 0.3); skeleton.belt.offset = Vec3::unit_z() * 7.0; - skeleton.belt.ori = Quaternion::rotation_z(wave * 0.3); + skeleton.belt.ori = Quaternion::rotation_z(wave * 0.2); skeleton.shorts.offset = Vec3::unit_z() * 4.0; - skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.3); + skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1); - skeleton.l_hand.offset = Vec3::new(-8.0, wave * 5.0, 9.0); - skeleton.r_hand.offset = Vec3::new(8.0, -wave * 5.0, 9.0); + skeleton.l_hand.offset = Vec3::new(-7.5, wave * 5.0, 9.0); + skeleton.r_hand.offset = Vec3::new(7.5, -wave * 5.0, 9.0); - skeleton.l_foot.offset = Vec3::new(-3.5, -wave * 4.0, -(wave_fast.abs() - 0.5).abs() * 4.0); - skeleton.r_foot.offset = Vec3::new(3.5, wave * 4.0, -(wave_fast.abs() - 0.5).abs() * 4.0); + skeleton.l_foot.offset = Vec3::new(-3.5, 2.0 - wave * 8.0, 3.5 - (wave_fast.abs() - 0.5).abs() * 4.0); + skeleton.l_foot.ori = Quaternion::rotation_x(-wave + 1.0); + skeleton.r_foot.offset = Vec3::new(3.5, 2.0 + wave * 8.0, 3.5 - (wave_fast.abs() - 0.5).abs() * 4.0); + skeleton.r_foot.ori = Quaternion::rotation_x(wave + 1.0); skeleton.back.offset = Vec3::new(-8.0, 5.0, 16.0); skeleton.back.ori = Quaternion::rotation_y(2.5); diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index a61e8ce7fd..7088eb4c19 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -23,6 +23,10 @@ pub use self::{ SkyboxPipeline, Locals as SkyboxLocals, }, + terrain::{ + TerrainPipeline, + Locals as TerrainLocals, + }, }, }; diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index cd5f408181..445259b8ce 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -1,5 +1,6 @@ pub mod figure; pub mod skybox; +pub mod terrain; // Library use gfx::{ diff --git a/voxygen/src/render/pipelines/terrain.rs b/voxygen/src/render/pipelines/terrain.rs new file mode 100644 index 0000000000..dd1e7bcf74 --- /dev/null +++ b/voxygen/src/render/pipelines/terrain.rs @@ -0,0 +1,68 @@ +// Library +use gfx::{ + self, + // Macros + gfx_defines, + gfx_vertex_struct_meta, + gfx_constant_struct_meta, + gfx_impl_struct_meta, + gfx_pipeline, + gfx_pipeline_inner, +}; +use vek::*; + +// Local +use super::{ + Globals, + super::{ + Pipeline, + TgtColorFmt, + TgtDepthFmt, + }, +}; + +gfx_defines! { + vertex Vertex { + pos: [f32; 3] = "v_pos", + norm: [f32; 3] = "v_norm", + col: [f32; 3] = "v_col", + } + + constant Locals { + model_offs: [f32; 3] = "model_offs", + } + + pipeline pipe { + vbuf: gfx::VertexBuffer = (), + + locals: gfx::ConstantBuffer = "u_locals", + globals: gfx::ConstantBuffer = "u_globals", + + tgt_color: gfx::RenderTarget = "tgt_color", + tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_WRITE, + } +} + +impl Vertex { + pub fn new(pos: Vec3, norm: Vec3, col: Rgb) -> Self { + Self { + pos: pos.into_array(), + col: col.into_array(), + norm: norm.into_array(), + } + } +} + +impl Locals { + pub fn default() -> Self { + Self { + model_offs: [0.0; 3], + } + } +} + +pub struct TerrainPipeline; + +impl Pipeline for TerrainPipeline { + type Vertex = Vertex; +} diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 2e23f70f69..dc4e8e053a 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -17,6 +17,7 @@ use super::{ Globals, figure, skybox, + terrain, }, }; @@ -43,6 +44,7 @@ pub struct Renderer { skybox_pipeline: GfxPipeline>, figure_pipeline: GfxPipeline>, + terrain_pipeline: GfxPipeline>, } impl Renderer { @@ -70,6 +72,14 @@ impl Renderer { include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/figure.frag")), )?; + // Construct a pipeline for rendering terrain + let terrain_pipeline = create_pipeline( + &mut factory, + terrain::pipe::new(), + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/terrain.vert")), + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/terrain.frag")), + )?; + Ok(Self { device, encoder: factory.create_command_buffer().into(), @@ -80,6 +90,7 @@ impl Renderer { skybox_pipeline, figure_pipeline, + terrain_pipeline, }) } @@ -164,6 +175,26 @@ impl Renderer { }, ); } + + /// Queue the rendering of the provided terrain chunk model in the upcoming frame. + pub fn render_terrain_chunk( + &mut self, + model: &Model, + globals: &Consts, + locals: &Consts, + ) { + self.encoder.draw( + &model.slice, + &self.terrain_pipeline.pso, + &terrain::pipe::Data { + vbuf: model.vbuf.clone(), + locals: locals.buf.clone(), + globals: globals.buf.clone(), + tgt_color: self.tgt_color_view.clone(), + tgt_depth: self.tgt_depth_view.clone(), + }, + ); + } } struct GfxPipeline { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index c3bb5c1585..beb8de6fa5 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -82,8 +82,8 @@ impl Scene { 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, 0.0))), - Some(load_segment("foot.vox").generate_mesh_with_offset(Vec3::new(-2.5, -3.0, 0.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))), None, None, diff --git a/voxygen/test_assets/elf/belt.vox b/voxygen/test_assets/elf/belt.vox new file mode 100644 index 0000000000000000000000000000000000000000..61c639ec67ee40f7c2fed9b0c397d7352d8989d5 GIT binary patch literal 1272 zcmeH{&udgy6vxk-U(xEy7!yRupk$L660DQJP|QnlYGx!I>+n)DLNYBGX&)&Ok|3Q$ zGKHjzL7YWrgr>-VM2dqYg(gB)vuYP9jv~yWpvHxZl6Ci=en(gS2VL~Qhx6_|_ndRT z=fayxUyI%~v&r$~r*iftWR^*06Gz(%*$1LS1jM!JY;s$^ZD)n{o7xx7lMZnx>^wOf zux8loj>5j@hyhw_I7Z9wOUxCfz|6+wW_ot=KJq!`@01%)aRLc z{I2@DMfgQ$?t5_8h0*}5bitx=@U<$xQtU6qT8ceY>`%r1Q0%eMdSEoF#{6&dx-#O(j}nJ#<_6o0`Z%1M!y>6{1@jL&JHt}86;YMC-cbq zTd7##A8c8AZtm;;(v;uBa?+LE!89AYQo~ZxmndOwk=My2`>xBYkr7@F4)Ws68J?d$ z&0aLh?x|D!dh#Sc_w;bDyPL|16O_8T`0n^|ZgqC@&9P(LE6nrb(hYuC_>$ZH4BzLc zDds%Bz5W@CpHDJ>b%Gm{7s*YyOkezn%-ATC@elcAWC-`%04-m$5PNyeBWLVnjk;&l zt{BynQA!v^*I14le$2=X8{UAC>NDJ^vAw-bL+{r1TU%Sy^?sY1o78GGHa0e>R4SCq zWlE(IYinz)uC7uj6j)kXVqswcUvE4&H^=PkEZJ-p&+|y9)1*=<5{U$3V`IeQabmF; ULqkLK_xID+*GF$}FYO<|zjr(vG5`Po literal 0 HcmV?d00001 diff --git a/voxygen/test_assets/elf/chest.vox b/voxygen/test_assets/elf/chest.vox new file mode 100644 index 0000000000000000000000000000000000000000..a0d9980bb0dc635f6470fc6a2a5d7d6a5f4cf124 GIT binary patch literal 45228 zcmeI)e|*$+9S88w_rCX2`>CQv6cm5da#nPKMAumsC~tzTMUrCF;i1pDpi2mP?$Ki<9GpBsa1_W$dTzWCw$`T71l zKcCO{zKnaQ8|pWP_Zef>En9sBZswNzjcHunT>CsN;5rM*hvY}vxT$%y*Nco^jxip` zGwSggB)$8P+)+RBUgH^cQT8JFkX)4gdPlp9Fp@K1$95*3}~`>xUla*od^38v~kcEgRTXp0M5_RwPUY>nzYXLLml=f zpnGU-+E@EHqq(aZu9#7N8;t*s*MgC;OY?f=RyM4EQ8}xx4SVSw&$!VTuPs;EvS#sl zvN1HT{Z93bm-H6#h4v`yb*@m!)arG0jZ%MGy{y^0Mpo@!CAGV1W!VkO#Lv=gGEn&y~(|&XL=D zvT|#wO>WL_k=-4e<)+jo>1%0{9oJkb#jDpzwqcdDty>{2t7>G^ic6$%X_c(2UL=<< zUMMvc^JJt0dx3N$h5cwTa=6*ZP?M3t^+pEjjP%zU>8ml)U2UWzW~3!zq-ma!x;aK_ z!bT1qIwZq5H@zP`cun^7BbrRo}%B0&%`tFI^wytcV zSjx0&MMU39@j|{k-L7xM$bzU1Q|wB$mdi1HYo|hLfw9Dp&mcCP#;%rp?(^4gjEg$wD05qe>iURXgdTtqLdq!-5Mg^THhRrJCo^umkjh1K-J z*U<|vp%-3CFT9LixRhR4LoZxLFI-M9d_BEz1--DAUU)gZa3#HP6}@mZy|9j6xQ1T1 zmR`7yUa0@UE%$e@_4LB^^ujCYg$?w=Mtb2@^ui{3;Rbr))%3!R^ukT_!e)BmHT1$a z&R=9zJp%aPcM8Yy>K_Z@Llx6chd{^&4gLI!hQ6@_tOh+p%>mtFWgTryp3M?0ea!>^uiC)3kT_ichCzz zL@&IPUie{p;Q@N#UG&0_&#FZ=|( z@RRhy2k3=|=!J*rg%8pTKSeM6G`;X4df{j2g`cGtevV%Fd3xa&=!L`d!Y|SbzeF$m zGQIFC^un*w3m>KzK0+`28oluA^ulk@3y;tXAEg(5lV12Odf~U}g^$q-AEy_7hhF#u zz3{vA!tc=wN9cv$rx*T!Uid?L;gj^jAJGecOfNi2FMNt#c#K|noL=}ez3?aW!k^L$ ze?~9-Ilb@~^uk}#3x7o~e1={)N-sP?FZ?yV@Hh0r-_i?zM=v}{FZ?~d@DKFDKhg{T zL@#`nUifEv;a}*5f29}xjb8Y7df`9lh5w`%{)=AtZ+hW>=!HTlEYvTr%Rr`=AIXOl zFYV|`c{P*CwR!B_#qW^?i)~@+*(g-RV&So*!{UitI-AnLu}C-;u~8C*R6?Uf7DTFS zMKV*|>S%>dT7`^W8hPTR-MP$}lU6iP>56i{eSUtWzaBE*dFsP&8_nd$B@sO<1nN z<8AqL{M3@masKCmueTv4E!fVQP34OA#M<)L>F~;!EhY19Wokv4n%dr#S8CL@UYAK* zx0>R`a^8}*WE06$JZBrvs3_P9-T94U5S`o3uTPBK$Sv7?zSBnU%xo|3e0hh9t%+>P z1_P4kuoI>eo!Ylz zL4^*#^tp!HsndmgcP?qQt@&)epetMw3EO{cp!R&BEVqt}#scZKj;~eQ<6F|FgJ?@6 zj9)r;_1KW9^i(ok-X}Zr_#hi+TP9g*FHdY&aP3$W?KM8J&7=#NGyLOG>`@iF60Mn> zJL(*!(!o dhPEYL@afW)bi8#!$&QOyr+q+lOf-yN{{<3tYDxeA literal 0 HcmV?d00001 diff --git a/voxygen/test_assets/elf/foot.vox b/voxygen/test_assets/elf/foot.vox new file mode 100644 index 0000000000000000000000000000000000000000..4c5d106656fdd6252c71ea79df1837f2cc3c08eb GIT binary patch literal 1436 zcmeH{&ud&&7>2)d?@TJ_s%_d*3K?3mNeBtrNnnuXMmn0A5~p^!F&QBlB~$bgNudeF zSu`W0B10jI&J>K4ff5l1C4!02)vR<885Cg_LTz12Hrcp;eDApOKXBoJhxeTCJKwK! z;m#KpQV-2+ZYKAp#6C@$6?3KROYuQVM|(l|VqvkATX5QFg~Ic^ZX)T96TWp51F^bp z!qYw}60t2-**({Fx@%I?9Va~D$H?yqCrtQ?_k?frCSk>+KB9+a>G3wLeda(*$c(R*%cX6C%@q`n4jIJNs z;~G*gp3mugLV1KQoYPFgWcT8|Nrj%R>zz<9vinl6udywT7yUf$_{JNr&8Uz5;{WyE zc!636q5}xq5VT;l34?X88b`K4&muZjrfnfj%Tu#}$kLd)x1%Gh*TXs;tlh?%O@-C4 zDEjwofA-+1>inV_KPdl`8hoQWk3)DEKz$VM4#I8Y=mTYbCf{%J_2m0mzMtg#QNHhu z-WNuvX{>&?N@cIY($`DOe>G3>agoC31+F!&F?)ZO?7b{AcW1bA=L(ry8K&N!;_|zf znJ7&#RvaU>{m+;$-s_KwRsX^^G|#Q)>R&Te4zaT4Dji^Y8@t}Z@|rJO$ATJv~o;R4^DKhKxv&hhB%SsG`~P#+xRMEbD-{isCd)$wf_@uH(tsL^vJ8yCO?KxI&%yM(?D&^UL#jCGUoStGX^9rv|juX5* zN-xq|h{KBBqih_MjCN?Wt{cs~QO_E+z}UzbQQ9a^7~!aqA2x!NvA@4hN8c^hy}do! z`hL5+yR=#@c6N4XG#YGgZ&R<=+1lD-b90kwwaWVXI%{idMEb@nD=RE7FHGd@1f$jAu8!@~>>4H5qU{szu-K;ZxY literal 0 HcmV?d00001 diff --git a/voxygen/test_assets/elf/hand.vox b/voxygen/test_assets/elf/hand.vox new file mode 100644 index 0000000000000000000000000000000000000000..b4dc12e5c81d0a15d5eadbe68247e7c1e4460bb2 GIT binary patch literal 1352 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 literal 0 HcmV?d00001 diff --git a/voxygen/test_assets/elf/pants.vox b/voxygen/test_assets/elf/pants.vox new file mode 100644 index 0000000000000000000000000000000000000000..d407e0091185f48bf7f2bbf4ae4588a48f4b97dd GIT binary patch literal 1936 zcmeH`&ud&&6vxl~`QE6jwrNW#B(!9c5E8UWV31}a9ZjajsSOjGDI}vLMTbZVO(+i1 zjF7q*3R##b7%2lKA`VIf6QQeF=^|Yyg;@x-?e6&kvvdFT`^J_3feRme=6mlw_uO;O zId|sr*ZS^@$js#QI~4ge5Sg7WW?xAr{9Ynl5=XAh7pLDg9PoRR{1_7}rDHFu6Nv68 zuRG40PD(7trlYiqMXN|@6NgGi0r%3W<2iO5TdJXIYUorPI2CDXXjK$g6?>|oYHDa` zH!AW}FVZm_YmP&nuNl{nfcFS_j}&7BjO}SNiE>SNr;u3Q%MjgF)zRD&DxR%)4pn>R z!n0JOJSQZIe!8pl<5`Ar4Apn{GA7k0ci_D=Z7HK;#ax8BDDG3q+}g-G%ls^Jvyrzp zcF7vmP&G9$HulCwj%ukUnRCBWHrAX6ntqyoR7*99D*AF9I2(uD6DI529}rKQbB1wE zY^b`c+-FT35KkPj#1KtF_B3Q|0^&)?yh7g1(>LH5Ayu@AQmm_D&4e{eSy1gV$sbAt|9}6D3RGJ_ z`2Z+1flLD!tOGqYK&m*Bb+o07mXy$x0_u`ORWc~cD3S)s(GjHGhO}CcW)o7c)2#|A zm;asHpKahNc~}7MXMoxuaHj{jB{=$sCqJX@ciP&t{Yu+o+J2$! zCqesbL8~rU`Edoyd&^k(ZUJ-O&SCc9Eb?FFae4bPrtVE4dpC>8JCnG0`y$de(-{A7 z92efdfRW+|hGvJ*xAjk&FW>E^3aR`BS!X^spY#6AY55RRVqV1q2x}u(8ju|G%hn*7 zDqc)a<9Q~7XJcddV`vCZ&!5MWbLVi_*M~=E&*FzOXYkGG(|B;|6t+*EM6IU>pPe{? z+sBXN<73D0pt6E5*KXj8)ems5ynxS_=TR*c@X7UeaqGPqtX!SKjhRa*O=U2D>2=Ib zjAJJK8r~cmM&{K)w9BjoIb3EvN`ix;pji+!t_bQmK`kq&W(4bLL3vbA8W9u*1-X7f zrcbcHzmFFCmgwHz9-8dm?(QxcjRtmhcCfv@jjgRM)M__~Axb+6_q95-@1zuMrRYoHzT+TXu&L}g zeHY^nQY)eKrqFlVdzE9;Y42ABo7&z(-=P#Gv|XefWBr4At}TbsTUsf~N!z)lRMCz@ z7411y-HuYlw2SwYDj`eAQnHjhOdcl7$TG5=EGH|-3i1efgsdbh$ttpHPg4$6y`xn% z>(|nEleTIYcQal?)$TZ%*ZfZN9Gd+#du#U9?5WvLvzKNc%^ohE)5TgY))+i@x>uYB zS1I_v{*4<*0b0_4I|)bvyE2QsSjb5fSuv58AQEE05-;orl-*rOJ`c&| zAlWP=o#tl=h-Ljf+s{1kg?&D<$6MBalK@_D&tnsKU;xP$VBQVf6zpDS0_l~*Og8UOfE-jdRHHp|-45QCSG4yN*;m2Ww9))l|bsl~9`ryCgNAG+uy63v# zoA#mWS{FL5bf7)bj@EE1JWGGZEV*+qDkT03GRt|Uzw`W@DYgxXa<0e*gm)tY8Ay=x z`I8VMfggbYz8eOB$G)jE-qqWVFB@Y95XXBn3|e`#Wx-w yAII3(7$T7fOw&Xt6hbf Date: Tue, 15 Jan 2019 15:13:11 +0000 Subject: [PATCH 17/21] Added Terrain type to scene --- Cargo.toml | 1 + client/Cargo.toml | 2 ++ client/src/lib.rs | 30 ++++++++++++++++-- common/src/terrain/biome.rs | 1 + common/src/terrain/mod.rs | 40 +++++++++++++++--------- common/src/volumes/vol_map.rs | 8 +++++ server/Cargo.toml | 1 + server/src/lib.rs | 13 ++++++-- voxygen/src/anim/character/run.rs | 11 ++++--- voxygen/src/scene/camera.rs | 3 +- voxygen/src/scene/mod.rs | 18 +++++++++-- voxygen/src/scene/terrain.rs | 52 +++++++++++++++++++++++++++++++ voxygen/src/session.rs | 5 +-- world/.gitignore | 3 ++ world/Cargo.toml | 9 ++++++ world/src/lib.rs | 29 +++++++++++++++++ 16 files changed, 195 insertions(+), 31 deletions(-) create mode 100644 voxygen/src/scene/terrain.rs create mode 100644 world/.gitignore create mode 100644 world/Cargo.toml create mode 100644 world/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 70930e72e1..3730694b99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "client", "server", "voxygen", + "world", ] [profile.dev] diff --git a/client/Cargo.toml b/client/Cargo.toml index dd73327af8..04e8d4ae16 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -6,5 +6,7 @@ edition = "2018" [dependencies] common = { package = "veloren-common", path = "../common" } +world = { package = "veloren-world", path = "../world" } specs = "0.14" +vek = "0.9" diff --git a/client/src/lib.rs b/client/src/lib.rs index ebcc79fe15..eafda2d0b1 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,8 +1,16 @@ // Standard use std::time::Duration; -// Internal -use common::state::State; +// Library +use specs::Entity as EcsEntity; +use vek::*; + +// Project +use common::{ + state::State, + terrain::TerrainChunk, +}; +use world::World; #[derive(Debug)] pub enum Error { @@ -17,7 +25,11 @@ pub struct Input { pub struct Client { state: State, - // TODO: Add "meta" state here + player: Option, + + // Testing + world: World, + pub chunk: Option, } impl Client { @@ -25,9 +37,21 @@ impl Client { pub fn new() -> Self { Self { state: State::new(), + + player: None, + + // Testing + world: World::new(), + chunk: None, } } + /// TODO: Get rid of this + pub fn with_test_state(mut self) -> Self { + self.chunk = Some(self.world.generate_chunk(Vec3::zero())); + self + } + /// Get a reference to the client's game state. pub fn state(&self) -> &State { &self.state } diff --git a/common/src/terrain/biome.rs b/common/src/terrain/biome.rs index 53632d139c..81bd29967d 100644 --- a/common/src/terrain/biome.rs +++ b/common/src/terrain/biome.rs @@ -1,4 +1,5 @@ pub enum BiomeKind { + Void, Grassland, Ocean, Mountain, diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index e54f6d508f..f860e14bcb 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -1,35 +1,47 @@ pub mod block; pub mod biome; +// Reexports +pub use self::{ + block::Block, + biome::BiomeKind, +}; + // Library use vek::*; // Crate use crate::{ vol::VolSize, - volumes::vol_map::VolMap, + volumes::{ + vol_map::VolMap, + chunk::Chunk, + }, }; -// Local -use self::{ - block::Block, - biome::BiomeKind, -}; +// TerrainChunkSize -// ChunkSize +pub struct TerrainChunkSize; -pub struct ChunkSize; - -impl VolSize for ChunkSize { +impl VolSize for TerrainChunkSize { const SIZE: Vec3 = Vec3 { x: 32, y: 32, z: 32 }; } -// ChunkMeta +// TerrainChunkMeta -pub struct ChunkMeta { +pub struct TerrainChunkMeta { biome: BiomeKind, } -// TerrainMap +impl TerrainChunkMeta { + pub fn void() -> Self { + Self { + biome: BiomeKind::Void, + } + } +} -pub type TerrainMap = VolMap; +// Terrain type aliases + +pub type TerrainChunk = Chunk; +pub type TerrainMap = VolMap; diff --git a/common/src/volumes/vol_map.rs b/common/src/volumes/vol_map.rs index 33674d4e33..dd0ab1569b 100644 --- a/common/src/volumes/vol_map.rs +++ b/common/src/volumes/vol_map.rs @@ -105,4 +105,12 @@ impl VolMap { chunks: HashMap::new(), } } + + pub fn insert(&mut self, key: Vec3, chunk: Chunk) -> Option> { + self.chunks.insert(key, chunk) + } + + pub fn remove(&mut self, key: &Vec3) -> Option> { + self.chunks.remove(key) + } } diff --git a/server/Cargo.toml b/server/Cargo.toml index 9c48fce6ae..a5950b0420 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -6,5 +6,6 @@ edition = "2018" [dependencies] common = { package = "veloren-common", path = "../common" } +world = { package = "veloren-world", path = "../world" } specs = "0.14" diff --git a/server/src/lib.rs b/server/src/lib.rs index 9c6302efc4..3e202b9482 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -3,6 +3,7 @@ use std::time::Duration; // Internal use common::state::State; +use world::World; #[derive(Debug)] pub enum Error { @@ -15,6 +16,7 @@ pub struct Input { pub struct Server { state: State, + world: World, // TODO: Add "meta" state here } @@ -24,15 +26,20 @@ impl Server { pub fn new() -> Self { Self { state: State::new(), + world: World::new(), } } - /// Get a reference to the client's game state. + /// Get a reference to the server's game state. pub fn state(&self) -> &State { &self.state } - - /// Get a mutable reference to the client's game state. + /// Get a mutable reference to the server's game state. pub fn state_mut(&mut self) -> &mut State { &mut self.state } + /// Get a reference to the server's world. + pub fn world(&self) -> &World { &self.world } + /// Get a mutable reference to the server's world. + pub fn world_mut(&mut self) -> &mut World { &mut self.world } + /// Execute a single server 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 server-side things are diff --git a/voxygen/src/anim/character/run.rs b/voxygen/src/anim/character/run.rs index 92411ebc94..b5b5639fb9 100644 --- a/voxygen/src/anim/character/run.rs +++ b/voxygen/src/anim/character/run.rs @@ -19,6 +19,7 @@ impl Animation for RunAnimation { ) { 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(); skeleton.head.offset = Vec3::unit_z() * 13.0; skeleton.head.ori = Quaternion::rotation_z(wave * 0.3); @@ -32,15 +33,15 @@ impl Animation for RunAnimation { skeleton.shorts.offset = Vec3::unit_z() * 4.0; skeleton.shorts.ori = Quaternion::rotation_z(wave * 0.1); - skeleton.l_hand.offset = Vec3::new(-7.5, wave * 5.0, 9.0); - skeleton.r_hand.offset = Vec3::new(7.5, -wave * 5.0, 9.0); + skeleton.l_hand.offset = Vec3::new(-6.0 - wave_dip * 6.0, wave * 5.0, 11.0 - wave_dip * 6.0); + skeleton.r_hand.offset = Vec3::new(6.0 + wave_dip * 6.0, -wave * 5.0, 11.0 - wave_dip * 6.0); - skeleton.l_foot.offset = Vec3::new(-3.5, 2.0 - wave * 8.0, 3.5 - (wave_fast.abs() - 0.5).abs() * 4.0); + skeleton.l_foot.offset = Vec3::new(-3.5, 1.0 - wave * 8.0, 3.5 - wave_dip * 4.0); skeleton.l_foot.ori = Quaternion::rotation_x(-wave + 1.0); - skeleton.r_foot.offset = Vec3::new(3.5, 2.0 + wave * 8.0, 3.5 - (wave_fast.abs() - 0.5).abs() * 4.0); + skeleton.r_foot.offset = Vec3::new(3.5, 1.0 + wave * 8.0, 3.5 - wave_dip * 4.0); skeleton.r_foot.ori = Quaternion::rotation_x(wave + 1.0); - skeleton.back.offset = Vec3::new(-8.0, 5.0, 16.0); + skeleton.back.offset = Vec3::new(-9.0, 5.0, 18.0); skeleton.back.ori = Quaternion::rotation_y(2.5); } } diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index aad5e5591d..d17b0aac15 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -27,7 +27,8 @@ impl Camera { } } - /// Compute the transformation matrices (view matrix and projection matrix) for the camera. + /// Compute the transformation matrices (view matrix and projection matrix) and position of the + /// camera. pub fn compute_dependents(&self) -> (Mat4, Mat4, Vec3) { let view_mat = Mat4::::identity() * Mat4::translation_3d(-Vec3::unit_z() * self.dist) diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index beb8de6fa5..cfb1b510f1 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,5 +1,6 @@ pub mod camera; pub mod figure; +pub mod terrain; // Library use vek::*; @@ -33,6 +34,7 @@ use crate::{ use self::{ camera::Camera, figure::Figure, + terrain::Terrain, }; // TODO: Don't hard-code this @@ -44,9 +46,11 @@ struct Skybox { } pub struct Scene { - camera: Camera, globals: Consts, + camera: Camera, + skybox: Skybox, + terrain: Terrain, test_figure: Figure, } @@ -58,12 +62,13 @@ fn load_segment(filename: &'static str) -> Segment { impl Scene { /// Create a new `Scene` with default parameters. - pub fn new(renderer: &mut Renderer) -> Self { + pub fn new(renderer: &mut Renderer, client: &Client) -> Self { Self { - camera: Camera::new(), globals: renderer .create_consts(&[Globals::default()]) .unwrap(), + camera: Camera::new(), + skybox: Skybox { model: renderer .create_model(&create_skybox_mesh()) @@ -72,6 +77,7 @@ impl Scene { .create_consts(&[SkyboxLocals::default()]) .unwrap(), }, + terrain: Terrain::new(), test_figure: Figure::new( renderer, @@ -129,6 +135,9 @@ impl Scene { )]) .expect("Failed to update global constants"); + // Maintain terrain GPU data + self.terrain.maintain_gpu_data(renderer); + // TODO: Don't do this here RunAnimation::update_skeleton( &mut self.test_figure.skeleton, @@ -147,6 +156,9 @@ impl Scene { &self.skybox.locals, ); + // Render terrain + self.terrain.render(renderer, &self.globals); + // Render the test figure self.test_figure.render(renderer, &self.globals); } diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs new file mode 100644 index 0000000000..f7a97703cf --- /dev/null +++ b/voxygen/src/scene/terrain.rs @@ -0,0 +1,52 @@ +// Standard +use std::collections::HashMap; + +// Library +use vek::*; + +// Crate +use crate::{ + Error, + render::{ + Consts, + Globals, + Mesh, + Model, + Renderer, + TerrainPipeline, + TerrainLocals, + }, +}; + +struct TerrainChunk { + // GPU data + model: Model, + locals: Consts, +} + +pub struct Terrain { + chunks: HashMap, TerrainChunk>, +} + +impl Terrain { + pub fn new() -> Self { + Self { + chunks: HashMap::new(), + } + } + + pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer) { + // TODO + } + + pub fn render(&self, renderer: &mut Renderer, globals: &Consts) { + /* + renderer.render_terrain_chunk( + &self.model, + globals, + &self.locals, + &self.bone_consts, + ); + */ + } +} diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index ac8393664b..2dfe231832 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -33,10 +33,11 @@ pub struct SessionState { impl SessionState { /// Create a new `SessionState` pub fn new(renderer: &mut Renderer) -> Self { + let client = Client::new().with_test_state(); // <--- TODO: Remove this Self { // Create a scene for this session. The scene handles visible elements of the game world - scene: Scene::new(renderer), - client: Client::new(), + scene: Scene::new(renderer, &client), + client, } } } diff --git a/world/.gitignore b/world/.gitignore new file mode 100644 index 0000000000..693699042b --- /dev/null +++ b/world/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/world/Cargo.toml b/world/Cargo.toml new file mode 100644 index 0000000000..b7f42a9601 --- /dev/null +++ b/world/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "veloren-world" +version = "0.1.0" +authors = ["Joshua Barretto "] +edition = "2018" + +[dependencies] +common = { package = "veloren-common", path = "../common" } +vek = "0.9" diff --git a/world/src/lib.rs b/world/src/lib.rs new file mode 100644 index 0000000000..2caaff9df4 --- /dev/null +++ b/world/src/lib.rs @@ -0,0 +1,29 @@ +// Library +use vek::*; + +// Project +use common::{ + vol::Vox, + terrain::{ + Block, + TerrainChunk, + TerrainChunkMeta, + }, +}; + +#[derive(Debug)] +pub enum Error { + Other(String), +} + +pub struct World; + +impl World { + pub fn new() -> Self { + Self + } + + pub fn generate_chunk(&self, pos: Vec3) -> TerrainChunk { + TerrainChunk::filled(Block::empty(), TerrainChunkMeta::void()) + } +} From 248577bdefba527f9a1c72f339055f01ad93d4e1 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 23 Jan 2019 20:01:58 +0000 Subject: [PATCH 18/21] Added test terrain loading and meshing --- client/Cargo.toml | 1 + client/src/lib.rs | 36 ++++++- common/src/state.rs | 62 ++++++++++-- common/src/terrain/block.rs | 20 ++++ common/src/volumes/vol_map.rs | 15 +++ voxygen/src/anim/character/run.rs | 7 +- voxygen/src/main.rs | 2 + voxygen/src/mesh/mod.rs | 8 +- voxygen/src/mesh/segment.rs | 17 ++-- voxygen/src/mesh/terrain.rs | 147 +++++++++++++++++++++++++++ voxygen/src/scene/camera.rs | 2 +- voxygen/src/scene/mod.rs | 30 +++--- voxygen/src/scene/terrain.rs | 162 +++++++++++++++++++++++++++--- voxygen/src/session.rs | 23 ++++- world/Cargo.toml | 1 + world/src/lib.rs | 35 ++++++- 16 files changed, 508 insertions(+), 60 deletions(-) create mode 100644 voxygen/src/mesh/terrain.rs diff --git a/client/Cargo.toml b/client/Cargo.toml index 04e8d4ae16..ac82be29e1 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -10,3 +10,4 @@ world = { package = "veloren-world", path = "../world" } specs = "0.14" vek = "0.9" +threadpool = "1.7" diff --git a/client/src/lib.rs b/client/src/lib.rs index eafda2d0b1..5a90e14ff8 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -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, // 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) { + 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(); + } } diff --git a/common/src/state.rs b/common/src/state.rs index 71a7e741c2..a4d974ee57 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -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>, + pub changed_chunks: Vec>, + pub removed_chunks: Vec>, +} + +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::().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::().0 + pub fn get_time(&self) -> f64 { + self.ecs_world.read_resource::

, +} + +impl Texture

{ + pub fn new( + factory: &mut gfx_backend::Factory, + image: &DynamicImage, + ) -> Result { + let (tex, srv) = factory.create_texture_immutable_u8::( + gfx::texture::Kind::D2( + image.width() as u16, + image.height() as u16, + gfx::texture::AaMode::Single, + ), + gfx::texture::Mipmap::Provided, + &[&image.to_rgba().into_raw()], + ) + .map_err(|err| RenderError::CombinedError(err))?; + + Ok(Self { + tex, + srv, + sampler: factory.create_sampler(gfx::texture::SamplerInfo::new( + gfx::texture::FilterMethod::Scale, + gfx::texture::WrapMode::Clamp, + )), + _phantom: PhantomData, + }) + } +} diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index c84072350a..dc5ad7d870 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -60,6 +60,12 @@ impl Camera { .max(-PI / 2.0); } + /// Zoom the camera by the given delta, limiting the input accordingly. + pub fn zoom_by(&mut self, delta: f32) { + // Clamp camera dist to the 0 <= x <= infinity range + self.dist = (self.dist + delta).max(0.0); + } + /// Get the focus position of the camera. pub fn get_focus_pos(&self) -> Vec3 { self.focus } /// Set the focus position of the camera. diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 4e440d97a1..3a8e772c00 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -112,13 +112,25 @@ impl Scene { pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera } /// Handle an incoming user input event (i.e: cursor moved, key pressed, window closed, etc.). + /// + /// If the event is handled, return true pub fn handle_input_event(&mut self, event: Event) -> bool { match event { + // When the window is resized, change the camera's aspect ratio + Event::Resize(dims) => { + self.camera.set_aspect_ratio(dims.x as f32 / dims.y as f32); + true + }, // Panning the cursor makes the camera rotate Event::CursorPan(delta) => { self.camera.rotate_by(Vec3::from(delta) * CURSOR_PAN_SCALE); true }, + // Zoom the camera when a zoom event occurs + Event::Zoom(delta) => { + self.camera.zoom_by(delta); + true + }, // All other events are unhandled _ => false, } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 850094d186..6b51b49ab5 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -95,10 +95,6 @@ impl PlayState for SessionState { for event in global_state.window.fetch_events() { let _handled = match event { Event::Close => return PlayStateResult::Shutdown, - // When the window is resized, change the camera's aspect ratio - Event::Resize(dims) => { - self.scene.camera_mut().set_aspect_ratio(dims.x as f32 / dims.y as f32); - }, // When 'q' is pressed, exit the session Event::Char('q') => return PlayStateResult::Pop, // Toggle cursor grabbing diff --git a/voxygen/src/ui/element/image.rs b/voxygen/src/ui/element/image.rs new file mode 100644 index 0000000000..cc782221f9 --- /dev/null +++ b/voxygen/src/ui/element/image.rs @@ -0,0 +1,80 @@ +// Standard +use std::{ + rc::Rc, + cell::RefCell, +}; + +// Library +use image::DynamicImage; +use vek::*; + +// Crate +use crate::render::{ + Consts, + UiLocals, + Renderer, + Texture, + UiPipeline, +}; + +// Local +use super::{ + super::{ + UiError, + Cache, + }, + Element, + Bounds, + SizeRequest, +}; + +#[derive(Clone)] +pub struct Image { + texture: Rc>, + locals: Consts, +} + +impl Image { + pub fn new(renderer: &mut Renderer, image: &DynamicImage) -> Result { + Ok(Self { + texture: Rc::new( + renderer.create_texture(image) + .map_err(|err| UiError::RenderError(err))? + ), + locals: renderer.create_consts(&[UiLocals::default()]) + .map_err(|err| UiError::RenderError(err))?, + }) + } +} + +impl Element for Image { + fn get_hsize_request(&self) -> SizeRequest { SizeRequest::indifferent() } + fn get_vsize_request(&self) -> SizeRequest { SizeRequest::indifferent() } + + fn maintain( + &mut self, + renderer: &mut Renderer, + cache: &Cache, + bounds: Bounds, + resolution: Vec2, + ) { + renderer.update_consts(&mut self.locals, &[UiLocals::new( + [bounds.x, bounds.y, bounds.w, bounds.h], + )]) + .expect("Could not update UI image consts"); + } + + fn render( + &self, + renderer: &mut Renderer, + cache: &Cache, + bounds: Bounds, + resolution: Vec2, + ) { + renderer.render_ui_element( + cache.model(), + &self.locals, + &self.texture, + ); + } +} diff --git a/voxygen/src/ui/element/mod.rs b/voxygen/src/ui/element/mod.rs new file mode 100644 index 0000000000..1f8d486d1b --- /dev/null +++ b/voxygen/src/ui/element/mod.rs @@ -0,0 +1,181 @@ +pub mod image; + +// Standard +use std::rc::Rc; + +// Library +use vek::*; + +// Crate +use crate::render::{ + Renderer, + Texture, + Consts, + UiLocals, + UiPipeline, +}; + +// Local +use super::{ + UiError, + Cache, + Span, + SizeRequest, +}; + +// Bounds + +pub type Bounds = Rect; + +pub trait BoundsExt { + fn relative_to(self, other: Self) -> Self; +} + +impl BoundsExt for Bounds { + fn relative_to(self, other: Self) -> Self { + Self::new( + other.x + self.x * other.w, + other.y + self.y * other.h, + self.w * other.w, + self.h * other.h, + ) + } +} + +pub trait BoundsSpan { + fn in_resolution(self, resolution: Vec2) -> Bounds; +} + +impl BoundsSpan for Bounds { + fn in_resolution(self, resolution: Vec2) -> Bounds { + Bounds::new( + self.x.to_rel(resolution.x).rel, + self.y.to_rel(resolution.y).rel, + self.w.to_rel(resolution.x).rel, + self.h.to_rel(resolution.y).rel, + ) + } +} + +// Element + +pub trait Element: 'static { + //fn deep_clone(&self) -> Rc; + + fn get_hsize_request(&self) -> SizeRequest; + fn get_vsize_request(&self) -> SizeRequest; + + fn maintain( + &mut self, + renderer: &mut Renderer, + cache: &Cache, + bounds: Bounds, + resolution: Vec2, + ); + + fn render( + &self, + renderer: &mut Renderer, + cache: &Cache, + bounds: Bounds, + resolution: Vec2, + ); +} + +// Surface + +#[derive(Clone)] +pub enum Surface { + Transparent, + Color(Rgba), + Texture(Rc>), + Bevel, +} + +// Widget + +#[derive(Clone)] +pub struct Widget { + inner: Box, + background: Surface, + margin_top: Span, + margin_bottom: Span, + margin_left: Span, + margin_right: Span, + locals: Consts, +} + +impl Widget { + pub fn new(renderer: &mut Renderer, inner: E) -> Result, UiError> { + Ok(Box::new(Self { + inner: Box::new(inner), + background: Surface::Transparent, + margin_top: Span::rel(0.2), + margin_bottom: Span::rel(0.2), + margin_left: Span::rel(0.2), + margin_right: Span::rel(0.2), + locals: renderer.create_consts(&[UiLocals::default()]) + .map_err(|err| UiError::RenderError(err))?, + })) + } + + fn get_inner_bounds(&self) -> Bounds { + Bounds::new( + self.margin_left, + self.margin_top, + Span::full() - self.margin_left - self.margin_right, + Span::full() - self.margin_top - self.margin_bottom, + ) + } +} + +impl Element for Widget { + fn get_hsize_request(&self) -> SizeRequest { + self.inner.get_hsize_request() + self.margin_left + self.margin_right + } + + fn get_vsize_request(&self) -> SizeRequest { + self.inner.get_vsize_request() + self.margin_top + self.margin_bottom + } + + fn maintain( + &mut self, + renderer: &mut Renderer, + cache: &Cache, + bounds: Bounds, + resolution: Vec2, + ) { + renderer.update_consts(&mut self.locals, &[UiLocals::new( + [bounds.x, bounds.y, bounds.w, bounds.h], + )]) + .expect("Could not update UI image consts"); + + let inner_bounds = self + .get_inner_bounds() + .in_resolution(resolution) + .relative_to(bounds); + + self.inner.maintain(renderer, cache, inner_bounds, resolution); + } + + fn render( + &self, + renderer: &mut Renderer, + cache: &Cache, + bounds: Bounds, + resolution: Vec2, + ) { + renderer.render_ui_element( + cache.model(), + &self.locals, + &cache.blank_texture(), + ); + + let inner_bounds = self + .get_inner_bounds() + .in_resolution(resolution) + .relative_to(bounds); + + self.inner.render(renderer, cache, inner_bounds, resolution); + } +} diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs new file mode 100644 index 0000000000..89183c37ff --- /dev/null +++ b/voxygen/src/ui/mod.rs @@ -0,0 +1,85 @@ +pub mod element; +pub mod size_request; +pub mod span; + +// Reexports +pub use self::{ + span::Span, + size_request::SizeRequest, +}; + +// Library +use image::DynamicImage; + +// Crate +use crate::{ + Error, + render::{ + RenderError, + Renderer, + Model, + Texture, + UiPipeline, + create_ui_quad_mesh, + }, +}; + +// Local +use self::element::{ + Element, + Bounds, +}; + +#[derive(Debug)] +pub enum UiError { + RenderError(RenderError), +} + +pub struct Cache { + model: Model, + blank_texture: Texture, +} + +impl Cache { + pub fn new(renderer: &mut Renderer) -> Result { + Ok(Self { + model: renderer.create_model(&create_ui_quad_mesh())?, + blank_texture: renderer.create_texture(&DynamicImage::new_rgba8(1, 1))?, + }) + } + + pub fn model(&self) -> &Model { &self.model } + pub fn blank_texture(&self) -> &Texture { &self.blank_texture } +} + +pub struct Ui { + base: Box, + cache: Cache, +} + +impl Ui { + pub fn new(renderer: &mut Renderer, base: Box) -> Result { + Ok(Self { + base, + cache: Cache::new(renderer)?, + }) + } + + pub fn maintain(&mut self, renderer: &mut Renderer) { + self.base.maintain( + renderer, + &self.cache, + Bounds::new(0.0, 0.0, 1.0, 1.0), + renderer.get_resolution().map(|e| e as f32), + ) + } + + pub fn render(&self, renderer: &mut Renderer) { + self.base.render( + renderer, + &self.cache, + Bounds::new(0.0, 0.0, 1.0, 1.0), + renderer.get_resolution().map(|e| e as f32), + ); + } +} diff --git a/voxygen/src/ui/size_request.rs b/voxygen/src/ui/size_request.rs new file mode 100644 index 0000000000..ad8658e099 --- /dev/null +++ b/voxygen/src/ui/size_request.rs @@ -0,0 +1,30 @@ +// Standard +use std::ops::Add; + +// Local +use super::Span; + +pub struct SizeRequest { + min: Span, + max: Span, +} + +impl SizeRequest { + pub fn indifferent() -> Self { + Self { + min: Span::rel(0.0), + max: Span::rel(std::f32::INFINITY), + } + } +} + +impl Add for SizeRequest { + type Output = Self; + + fn add(self, span: Span) -> Self { + Self { + min: self.min + span, + max: self.max + span, + } + } +} diff --git a/voxygen/src/ui/span.rs b/voxygen/src/ui/span.rs new file mode 100644 index 0000000000..e06674164b --- /dev/null +++ b/voxygen/src/ui/span.rs @@ -0,0 +1,47 @@ +// Standard +use std::ops::{Add, Sub}; + +#[derive(Copy, Clone)] +pub struct Span { + pub rel: f32, + pub abs: f32, +} + +impl Span { + pub fn rel(rel: f32) -> Self { Self { rel, abs: 0.0 } } + pub fn abs(abs: f32) -> Self { Self { rel: 0.0, abs } } + + pub fn full() -> Self { Self { rel: 1.0, abs: 0.0 } } + pub fn half() -> Self { Self { rel: 0.5, abs: 0.0 } } + pub fn none() -> Self { Self { rel: 0.0, abs: 0.0 } } + + pub fn to_abs(self, res: f32) -> Self { + Self { rel: 0.0, abs: self.rel * res + self.abs } + } + + pub fn to_rel(self, res: f32) -> Self { + Self { rel: self.rel + self.abs / res, abs: 0.0 } + } +} + +impl Add for Span { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + rel: self.rel + other.rel, + abs: self.abs + other.abs, + } + } +} + +impl Sub for Span { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self { + rel: self.rel - other.rel, + abs: self.abs - other.abs, + } + } +} diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 5c56310d8a..da6f086813 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -97,6 +97,10 @@ impl Window { glutin::Event::DeviceEvent { event, .. } => match event { glutin::DeviceEvent::MouseMotion { delta: (dx, dy), .. } if cursor_grabbed => events.push(Event::CursorPan(Vec2::new(dx as f32, dy as f32))), + glutin::DeviceEvent::MouseWheel { + delta: glutin::MouseScrollDelta::LineDelta(_x, y), + .. + } if cursor_grabbed => events.push(Event::Zoom(y as f32)), _ => {}, }, _ => {}, @@ -136,6 +140,8 @@ pub enum Event { Char(char), /// The cursor has been panned across the screen while grabbed. CursorPan(Vec2), + /// The camera has been requested to zoom. + Zoom(f32), /// A key that the game recognises has been pressed down KeyDown(Key), /// A key that the game recognises has been released down diff --git a/voxygen/test_assets/.gitattributes b/voxygen/test_assets/.gitattributes new file mode 100644 index 0000000000..c091529f36 --- /dev/null +++ b/voxygen/test_assets/.gitattributes @@ -0,0 +1 @@ +*.png filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/voxygen/test_assets/test.png b/voxygen/test_assets/test.png new file mode 100644 index 0000000000..8c3da8f238 --- /dev/null +++ b/voxygen/test_assets/test.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2918209672b43a1d58994dfa4e1daa477b38af6c5a183ae58bd776838dfb6012 +size 4648282 diff --git a/voxygen/test_assets/wall.png b/voxygen/test_assets/wall.png new file mode 100644 index 0000000000..fd437c4da0 --- /dev/null +++ b/voxygen/test_assets/wall.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:772c1420edbb88d3b47efb2b2cdc781d23ff17de20b36f07a7eb065a43018a08 +size 32152 diff --git a/world/src/lib.rs b/world/src/lib.rs index aa5407b9da..b296ff2e6b 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -32,6 +32,7 @@ impl World { 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 sand = Block::new(3, Rgb::new(180, 150, 50)); let perlin_nz = Perlin::new(); @@ -48,7 +49,7 @@ impl World { if wposf.z < height - 1.0 { stone } else { - grass + sand } } else { air