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), }