diff --git a/assets/voxygen/element/skill_bar/xp_bar2.png b/assets/voxygen/element/skill_bar/xp_bar2.png deleted file mode 100644 index 15ddbb5c8a..0000000000 Binary files a/assets/voxygen/element/skill_bar/xp_bar2.png and /dev/null differ diff --git a/assets/voxygen/element/skill_bar/xp_bar_l.png b/assets/voxygen/element/skill_bar/xp_bar_l.png deleted file mode 100644 index d2218dd26d..0000000000 Binary files a/assets/voxygen/element/skill_bar/xp_bar_l.png and /dev/null differ diff --git a/assets/voxygen/element/skill_bar/xp_bar_l_filled.png b/assets/voxygen/element/skill_bar/xp_bar_l_filled.png deleted file mode 100644 index 71f8e1ae74..0000000000 Binary files a/assets/voxygen/element/skill_bar/xp_bar_l_filled.png and /dev/null differ diff --git a/assets/voxygen/element/skill_bar/xp_bar_r.png b/assets/voxygen/element/skill_bar/xp_bar_r.png deleted file mode 100644 index 81e3f11b9f..0000000000 Binary files a/assets/voxygen/element/skill_bar/xp_bar_r.png and /dev/null differ diff --git a/voxygen/shaders/ui.frag b/voxygen/shaders/ui.frag index 7ef02c68b5..2d77dd3ef7 100644 --- a/voxygen/shaders/ui.frag +++ b/voxygen/shaders/ui.frag @@ -1,9 +1,16 @@ #version 330 core +#include + in vec2 f_uv; in vec4 f_color; flat in uint f_mode; +layout (std140) +uniform u_locals { + vec4 w_pos; +}; + uniform sampler2D u_tex; out vec4 tgt_color; diff --git a/voxygen/shaders/ui.vert b/voxygen/shaders/ui.vert index ab90588fc3..2411525756 100644 --- a/voxygen/shaders/ui.vert +++ b/voxygen/shaders/ui.vert @@ -1,10 +1,17 @@ #version 330 core +#include + in vec2 v_pos; in vec2 v_uv; in vec4 v_color; in uint v_mode; +layout (std140) +uniform u_locals { + vec4 w_pos; +}; + uniform sampler2D u_tex; out vec2 f_uv; @@ -14,6 +21,13 @@ out vec4 f_color; void main() { f_uv = v_uv; f_color = v_color; - gl_Position = vec4(v_pos, 0.0, 1.0); + + if (w_pos.w == 1.0) { + // In-game element + gl_Position = proj_mat * (view_mat * w_pos + vec4(v_pos, 0.0, 0.0)); + } else { + // Interface element + gl_Position = vec4(v_pos, 0.0, 1.0); + } f_mode = v_mode; } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 9949bae1ae..fe727ddd60 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -23,18 +23,23 @@ use skillbar::Skillbar; use small_window::{SmallWindow, SmallWindowType}; use crate::{ - render::Renderer, + render::{Consts, Globals, Renderer}, + scene::camera::Camera, settings::{ControlSettings, Settings}, - ui::{ScaleMode, Ui}, + ui::{Ingame, Ingameable, ScaleMode, Ui}, window::{Event as WinEvent, Key, Window}, GlobalState, }; +use client::Client; +use common::comp; use conrod_core::{ color, graph, widget::{self, Button, Image, Rectangle, Text}, widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, }; +use specs::Join; use std::collections::VecDeque; +use vek::*; const XP_COLOR: Color = Color::Rgba(0.59, 0.41, 0.67, 1.0); const TEXT_COLOR: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); @@ -43,6 +48,12 @@ const MANA_COLOR: Color = Color::Rgba(0.42, 0.41, 0.66, 1.0); widget_ids! { struct Ids { + // Character Names + name_tags[], + // Health Bars + health_bars[], + health_bar_backs[], + // Test bag_space_add, // Debug @@ -124,6 +135,7 @@ pub struct Show { map: bool, inventory_test_button: bool, mini_map: bool, + ingame: bool, want_grab: bool, } @@ -247,13 +259,19 @@ impl Hud { inventory_test_button: false, mini_map: false, want_grab: true, + ingame: true, }, to_focus: None, force_ungrab: false, } } - fn update_layout(&mut self, global_state: &GlobalState, debug_info: DebugInfo) -> Vec { + fn update_layout( + &mut self, + client: &Client, + global_state: &GlobalState, + debug_info: DebugInfo, + ) -> Vec { let mut events = Vec::new(); let ref mut ui_widgets = self.ui.set_widgets(); let version = env!("CARGO_PKG_VERSION"); @@ -263,6 +281,76 @@ impl Hud { return events; } + // Nametags and healthbars + if self.show.ingame { + let ecs = client.state().ecs(); + let actor = ecs.read_storage::(); + let pos = ecs.read_storage::(); + let stats = ecs.read_storage::(); + let entities = ecs.entities(); + let player = client.entity(); + let mut name_id_walker = self.ids.name_tags.walk(); + let mut health_id_walker = self.ids.health_bars.walk(); + let mut health_back_id_walker = self.ids.health_bar_backs.walk(); + for (pos, name) in + (&entities, &pos, &actor) + .join() + .filter_map(|(entity, pos, actor)| match actor { + comp::Actor::Character { name, .. } if entity != player => { + Some((pos.0, name)) + } + _ => None, + }) + { + let id = name_id_walker.next( + &mut self.ids.name_tags, + &mut ui_widgets.widget_id_generator(), + ); + Text::new(&name) + .font_size(20) + .color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) + .x_y(0.0, 0.0) + .position_ingame(pos + Vec3::new(0.0, 0.0, 3.0)) + .resolution(100.0) + .set(id, ui_widgets); + } + for (pos, hp) in (&entities, &pos, &stats) + .join() + .filter_map(|(entity, pos, stats)| { + if entity != player { + Some((pos.0, stats.hp)) + } else { + None + } + }) + { + let back_id = health_back_id_walker.next( + &mut self.ids.health_bar_backs, + &mut ui_widgets.widget_id_generator(), + ); + let bar_id = health_id_walker.next( + &mut self.ids.health_bars, + &mut ui_widgets.widget_id_generator(), + ); + // Healh Bar + Rectangle::fill_with([120.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5)) + .x_y(0.0, -25.0) + .position_ingame(pos + Vec3::new(0.0, 0.0, 3.0)) + .resolution(100.0) + .set(back_id, ui_widgets); + + // Filling + Rectangle::fill_with( + [120.0 * (hp.current as f64 / hp.maximum as f64), 8.0], + HP_COLOR, + ) + .x_y(0.0, -25.0) + .position_ingame(pos + Vec3::new(0.0, 0.0, 3.0)) + .resolution(100.0) + .set(bar_id, ui_widgets); + } + } + // Display debug window. if self.show.debug { // Alpha Version @@ -362,7 +450,15 @@ impl Hud { } // Skillbar - Skillbar::new(&self.imgs, &self.fonts).set(self.ids.skillbar, ui_widgets); + // Get player stats + let stats = client + .state() + .ecs() + .read_storage::() + .get(client.entity()) + .map(|&s| s) + .unwrap_or_default(); + Skillbar::new(&self.imgs, &self.fonts, stats).set(self.ids.skillbar, ui_widgets); // Chat box match Chat::new(&mut self.new_messages, &self.imgs, &self.fonts) @@ -474,7 +570,9 @@ impl Hud { self.ui .widget_graph() .widget(id) - .and_then(graph::Container::unique_widget_state::) + .filter(|c| { + c.type_id == std::any::TypeId::of::<::State>() + }) .is_some() } else { false @@ -559,6 +657,10 @@ impl Hud { self.show.debug = !self.show.debug; true } + Key::ToggleIngameUi => { + self.show.ingame = !self.show.ingame; + true + } _ => false, }, WinEvent::KeyDown(key) | WinEvent::KeyUp(key) => match key { @@ -580,19 +682,26 @@ impl Hud { pub fn maintain( &mut self, + client: &Client, global_state: &mut GlobalState, debug_info: DebugInfo, + camera: &Camera, ) -> Vec { if let Some(maybe_id) = self.to_focus.take() { self.ui.focus_widget(maybe_id); } - let events = self.update_layout(&global_state, debug_info); - self.ui.maintain(&mut global_state.window.renderer_mut()); + let events = self.update_layout(client, global_state, debug_info); + let (view_mat, _, _) = camera.compute_dependents(client); + let fov = camera.get_fov(); + self.ui.maintain( + &mut global_state.window.renderer_mut(), + Some((view_mat, fov)), + ); events } - pub fn render(&self, renderer: &mut Renderer) { - self.ui.render(renderer); + pub fn render(&self, renderer: &mut Renderer, globals: &Consts) { + self.ui.render(renderer, Some(globals)); } } diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 2bfc8a307d..7e2fc45207 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -1,4 +1,5 @@ use super::{img_ids::Imgs, Fonts, HP_COLOR, MANA_COLOR, TEXT_COLOR, XP_COLOR}; +use common::comp::Stats; use conrod_core::{ widget::{self, Image, Rectangle, Text}, widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, @@ -30,15 +31,18 @@ pub struct Skillbar<'a> { imgs: &'a Imgs, fonts: &'a Fonts, + stats: Stats, + #[conrod(common_builder)] common: widget::CommonBuilder, } impl<'a> Skillbar<'a> { - pub fn new(imgs: &'a Imgs, fonts: &'a Fonts) -> Self { + pub fn new(imgs: &'a Imgs, fonts: &'a Fonts, stats: Stats) -> Self { Self { imgs, fonts, + stats, common: widget::CommonBuilder::default(), } } @@ -68,9 +72,13 @@ impl<'a> Widget for Skillbar<'a> { fn update(self, args: widget::UpdateArgs) -> Self::Event { let widget::UpdateArgs { state, ui, .. } = args; - // TODO: Read from parameter/character struct - let xp_percentage = 0.4; - let hp_percentage = 1.0; + // TODO: remove this + let level = (self.stats.xp as f64).log(4.0).trunc() as u32 + 1; + let start_level_xp = ((level - 1) as f64).powi(4); + let next_level_xp = (level as f64).powi(4) - start_level_xp; + // TODO: We need a max xp value + let xp_percentage = (self.stats.xp as f64 - start_level_xp) / next_level_xp; + let hp_percentage = self.stats.hp.current as f64 / self.stats.hp.maximum as f64; let mana_percentage = 1.0; // TODO: Only show while aiming with a bow or when casting a spell. @@ -82,7 +90,7 @@ impl<'a> Widget for Skillbar<'a> { // Experience-Bar Image::new(self.imgs.xp_bar) - .w_h(2688.0 / 6.0, 116.0 / 6.0) + .w_h(672.0 / 1.5, 29.0 / 1.5) .mid_bottom_of(ui.window) .set(state.ids.xp_bar, ui); @@ -92,37 +100,37 @@ impl<'a> Widget for Skillbar<'a> { // Left Grid Image::new(self.imgs.sb_grid) - .w_h(2240.0 / 12.0, 448.0 / 12.0) + .w_h(280.0 / 1.5, 56.0 / 1.5) .up_from(state.ids.xp_bar, 0.0) .align_left_of(state.ids.xp_bar) .set(state.ids.sb_grid_l, ui); Image::new(self.imgs.sb_grid_bg) - .w_h(2240.0 / 12.0, 448.0 / 12.0) + .w_h(280.0 / 1.5, 56.0 / 1.5) .middle_of(state.ids.sb_grid_l) .set(state.ids.sb_grid_bg_l, ui); // Right Grid Image::new(self.imgs.sb_grid) - .w_h(2240.0 / 12.0, 448.0 / 12.0) + .w_h(280.0 / 1.5, 56.0 / 1.5) .up_from(state.ids.xp_bar, 0.0) .align_right_of(state.ids.xp_bar) .set(state.ids.sb_grid_r, ui); Image::new(self.imgs.sb_grid_bg) - .w_h(2240.0 / 12.0, 448.0 / 12.0) + .w_h(280.0 / 1.5, 56.0 / 1.5) .middle_of(state.ids.sb_grid_r) .set(state.ids.sb_grid_bg_r, ui); // Right and Left Click Image::new(self.imgs.l_click) - .w_h(224.0 / 6.0, 320.0 / 6.0) + .w_h(56.0 / 1.5, 80.0 / 1.5) .right_from(state.ids.sb_grid_bg_l, 0.0) .align_bottom_of(state.ids.sb_grid_bg_l) .set(state.ids.l_click, ui); Image::new(self.imgs.r_click) - .w_h(224.0 / 6.0, 320.0 / 6.0) + .w_h(56.0 / 1.5, 80.0 / 1.5) .left_from(state.ids.sb_grid_bg_r, 0.0) .align_bottom_of(state.ids.sb_grid_bg_r) .set(state.ids.r_click, ui); @@ -135,7 +143,7 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.health_bar, ui); // Filling - Rectangle::fill_with([182.0 * (hp_percentage), 6.0], HP_COLOR) // "W=182.0 * [Health. %]" + Rectangle::fill_with([182.0 * hp_percentage, 6.0], HP_COLOR) // "W=182.0 * [Health. %]" .top_right_with_margins_on(state.ids.health_bar, 5.0, 0.0) .set(state.ids.health_bar_color, ui); @@ -147,7 +155,7 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.mana_bar, ui); // Filling - Rectangle::fill_with([182.0 * (mana_percentage), 6.0], MANA_COLOR) // "W=182.0 * [Mana. %]" + Rectangle::fill_with([182.0 * mana_percentage, 6.0], MANA_COLOR) // "W=182.0 * [Mana. %]" .top_left_with_margins_on(state.ids.mana_bar, 5.0, 0.0) .set(state.ids.mana_bar_color, ui); @@ -159,15 +167,16 @@ impl<'a> Widget for Skillbar<'a> { // Level Display + // TODO: don't construct a new string here // TODO: Insert actual Level here. - Text::new("1") + Text::new(&level.to_string()) .left_from(state.ids.xp_bar, -15.0) .font_size(10) .color(TEXT_COLOR) .set(state.ids.level_text, ui); // TODO: Insert next Level here. - Text::new("2") + Text::new(&(level + 1).to_string()) .right_from(state.ids.xp_bar, -15.0) .font_size(10) .color(TEXT_COLOR) diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 85c93fce63..4a75a4b3ba 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -106,7 +106,7 @@ impl PlayState for CharSelectionState { // Draw the UI to the screen. self.char_selection_ui - .render(global_state.window.renderer_mut()); + .render(global_state.window.renderer_mut(), self.scene.globals()); // Tick the client (currently only to keep the connection alive). self.client diff --git a/voxygen/src/menu/char_selection/scene.rs b/voxygen/src/menu/char_selection/scene.rs index 7c9af72ac6..66e0d1ba1c 100644 --- a/voxygen/src/menu/char_selection/scene.rs +++ b/voxygen/src/menu/char_selection/scene.rs @@ -72,6 +72,10 @@ impl Scene { } } + pub fn globals(&self) -> &Consts { + &self.globals + } + pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { self.camera.set_focus_pos(Vec3::unit_z() * 2.0); self.camera.update(client.state().get_time()); diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/ui.rs index 0dc08a092e..007a669f56 100644 --- a/voxygen/src/menu/char_selection/ui.rs +++ b/voxygen/src/menu/char_selection/ui.rs @@ -1,5 +1,5 @@ use crate::{ - render::Renderer, + render::{Consts, Globals, Renderer}, ui::{ self, img_ids::{ImageGraphic, VoxelGraphic}, @@ -1081,11 +1081,11 @@ impl CharSelectionUi { pub fn maintain(&mut self, renderer: &mut Renderer) -> Vec { let events = self.update_layout(); - self.ui.maintain(renderer); + self.ui.maintain(renderer, None); events } - pub fn render(&self, renderer: &mut Renderer) { - self.ui.render(renderer); + pub fn render(&self, renderer: &mut Renderer, globals: &Consts) { + self.ui.render(renderer, Some(globals)); } } diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 6f59a54c8d..2bd084f4b8 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -508,11 +508,11 @@ impl MainMenuUi { pub fn maintain(&mut self, global_state: &mut GlobalState) -> Vec { let events = self.update_layout(global_state); - self.ui.maintain(global_state.window.renderer_mut()); + self.ui.maintain(global_state.window.renderer_mut(), None); events } pub fn render(&self, renderer: &mut Renderer) { - self.ui.render(renderer); + self.ui.render(renderer, None); } } diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 9159b8d0aa..84379ac1c3 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -19,7 +19,8 @@ pub use self::{ skybox::{create_mesh as create_skybox_mesh, Locals as SkyboxLocals, SkyboxPipeline}, terrain::{Locals as TerrainLocals, TerrainPipeline}, ui::{ - create_quad as create_ui_quad, create_tri as create_ui_tri, Mode as UiMode, UiPipeline, + create_quad as create_ui_quad, create_tri as create_ui_tri, Locals as UiLocals, + Mode as UiMode, UiPipeline, }, Globals, }, diff --git a/voxygen/src/render/pipelines/ui.rs b/voxygen/src/render/pipelines/ui.rs index 2ebbcc3bcf..4e781c88dd 100644 --- a/voxygen/src/render/pipelines/ui.rs +++ b/voxygen/src/render/pipelines/ui.rs @@ -1,6 +1,7 @@ -use super::super::{Pipeline, Quad, Tri, WinColorFmt, WinDepthFmt}; +use super::super::{Globals, Pipeline, Quad, Tri, WinColorFmt, WinDepthFmt}; use gfx::{ self, + gfx_constant_struct_meta, // Macros gfx_defines, gfx_impl_struct_meta, @@ -18,15 +19,21 @@ gfx_defines! { mode: u32 = "v_mode", } + constant Locals { + pos: [f32; 4] = "w_pos", + } + pipeline pipe { vbuf: gfx::VertexBuffer = (), + locals: gfx::ConstantBuffer = "u_locals", + globals: gfx::ConstantBuffer = "u_globals", tex: gfx::TextureSampler<[f32; 4]> = "u_tex", scissor: gfx::Scissor = (), tgt_color: gfx::BlendTarget = ("tgt_color", gfx::state::ColorMask::all(), gfx::preset::blend::ALPHA), - tgt_depth: gfx::DepthTarget = gfx::preset::depth::PASS_TEST, + tgt_depth: gfx::DepthTarget = gfx::preset::depth::LESS_EQUAL_TEST, } } @@ -36,6 +43,20 @@ impl Pipeline for UiPipeline { type Vertex = Vertex; } +impl From> for Locals { + fn from(pos: Vec3) -> Self { + Self { + pos: [pos[0], pos[1], pos[2], 1.0], + } + } +} + +impl Default for Locals { + fn default() -> Self { + Self { pos: [0.0; 4] } + } +} + /// Draw text from the text cache texture `tex` in the fragment shader. pub const MODE_TEXT: u32 = 0; /// Draw an image from the texture at `tex` in the fragment shader. diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index fbd0397968..db4a64d3b0 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -314,7 +314,7 @@ impl Renderer { memory::Typed, }; type WinSurfaceData = <::Surface as SurfaceTyped>::DataType; - let mut download = self + let download = self .factory .create_download_buffer::(width as usize * height as usize) .map_err(|err| RenderError::BufferCreationError(err))?; @@ -423,6 +423,8 @@ impl Renderer { model: &Model, tex: &Texture, scissor: Aabr, + globals: &Consts, + locals: &Consts, ) { let Aabr { min, max } = scissor; self.encoder.draw( @@ -437,6 +439,8 @@ impl Renderer { h: max.y - min.y, }, tex: (tex.srv.clone(), tex.sampler.clone()), + locals: locals.buf.clone(), + globals: globals.buf.clone(), tgt_color: self.win_color_view.clone(), tgt_depth: self.win_depth_view.clone(), }, diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index d23638fdff..ef747ab3df 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -150,4 +150,9 @@ impl Camera { pub fn get_orientation(&self) -> Vec3 { self.ori } + + /// Get the field of view of the camera in radians. + pub fn get_fov(&self) -> f32 { + self.fov + } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index dad88a7c03..8fcb143ada 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -68,6 +68,11 @@ impl Scene { } } + /// Get a reference to the scene's globals + pub fn globals(&self) -> &Consts { + &self.globals + } + /// Get a reference to the scene's camera. pub fn camera(&self) -> &Camera { &self.camera diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 7457f84448..a74c01fd0f 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -100,7 +100,7 @@ impl SessionState { // Render the screen using the global renderer self.scene.render(renderer, &mut self.client.borrow_mut()); // Draw the UI to the screen - self.hud.render(renderer); + self.hud.render(renderer, self.scene.globals()); // Finish the frame renderer.flush(); @@ -171,18 +171,18 @@ impl PlayState for SessionState { global_state.maintain(); // Maintain the scene. - self.scene.maintain( - global_state.window.renderer_mut(), - &self.client.borrow_mut(), - ); + self.scene + .maintain(global_state.window.renderer_mut(), &self.client.borrow()); // extract HUD events ensuring the client borrow gets dropped let hud_events = self.hud.maintain( + &self.client.borrow(), global_state, DebugInfo { tps: clock.get_tps(), ping_ms: self.client.borrow().get_ping_ms(), }, + &self.scene.camera(), ); // Maintain the UI. for event in hud_events { @@ -215,6 +215,7 @@ impl PlayState for SessionState { } } } + {} // Render the session. self.render(global_state.window.renderer_mut()); diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 8986f08cac..f504fd43e3 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -40,6 +40,7 @@ pub struct ControlSettings { pub toggle_debug: VirtualKeyCode, pub fullscreen: VirtualKeyCode, pub screenshot: VirtualKeyCode, + pub toggle_ingame_ui: VirtualKeyCode, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -95,6 +96,7 @@ impl Default for Settings { toggle_debug: VirtualKeyCode::F3, fullscreen: VirtualKeyCode::F11, screenshot: VirtualKeyCode::F4, + toggle_ingame_ui: VirtualKeyCode::F6, }, networking: NetworkingSettings { username: "Username".to_string(), diff --git a/voxygen/src/ui/event.rs b/voxygen/src/ui/event.rs index 1a6dd709ae..82203a8807 100644 --- a/voxygen/src/ui/event.rs +++ b/voxygen/src/ui/event.rs @@ -6,7 +6,6 @@ pub struct Event(pub Input); impl Event { pub fn try_from(event: glutin::Event, window: &glutin::GlWindow) -> Option { use conrod_winit::*; - use winit; // A wrapper around the winit window that allows us to implement the trait necessary for // enabling the winit <-> conrod conversion functions. struct WindowRef<'a>(&'a winit::Window); diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 0ad8129663..29934e0fb7 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -12,13 +12,18 @@ mod font_ids; pub use event::Event; pub use graphic::Graphic; pub use scale::ScaleMode; -pub use widgets::{image_slider::ImageSlider, toggle_button::ToggleButton}; +pub use widgets::{ + image_slider::ImageSlider, + ingame::{Ingame, IngameAnchor, Ingameable}, + toggle_button::ToggleButton, +}; use crate::{ render::{ - create_ui_quad, create_ui_tri, DynamicModel, Mesh, RenderError, Renderer, UiMode, - UiPipeline, + create_ui_quad, create_ui_tri, Consts, DynamicModel, Globals, Mesh, RenderError, Renderer, + UiLocals, UiMode, UiPipeline, }, + scene::camera::Camera, window::Window, Error, }; @@ -27,12 +32,12 @@ use common::assets; use conrod_core::{ event::Input, graph::Graph, - image::{Id as ImgId, Map}, + image::{self, Map}, input::{touch::Touch, Motion, Widget}, - render::Primitive, + render::{Primitive, PrimitiveKind}, text::{self, font}, - widget::{id::Generator, Id as WidgId}, - Ui as CrUi, UiBuilder, UiCell, + widget::{self, id::Generator}, + Rect, UiBuilder, UiCell, }; use graphic::Id as GraphicId; use scale::Scale; @@ -55,6 +60,7 @@ enum DrawKind { enum DrawCommand { Draw { kind: DrawKind, verts: Range }, Scissor(Aabr), + WorldPos(Option>), } impl DrawCommand { fn image(verts: Range) -> DrawCommand { @@ -81,13 +87,16 @@ impl assets::Asset for Font { } pub struct Ui { - ui: CrUi, + ui: conrod_core::Ui, image_map: Map, cache: Cache, // Draw commands for the next render draw_commands: Vec, // Model for drawing the ui model: DynamicModel, + // Consts for default ui drawing position (ie the interface) + interface_locals: Consts, + default_globals: Consts, // Window size for updating scaling window_resized: Option>, // Scaling of the ui @@ -99,12 +108,16 @@ impl Ui { let scale = Scale::new(window, ScaleMode::Absolute(1.0)); let win_dims = scale.scaled_window_size().into_array(); + let mut renderer = window.renderer_mut(); + Ok(Self { ui: UiBuilder::new(win_dims).build(), image_map: Map::new(), - cache: Cache::new(window.renderer_mut())?, + cache: Cache::new(renderer)?, draw_commands: vec![], - model: window.renderer_mut().create_dynamic_model(100)?, + model: renderer.create_dynamic_model(100)?, + interface_locals: renderer.create_consts(&[UiLocals::default()])?, + default_globals: renderer.create_consts(&[Globals::default()])?, window_resized: None, scale, }) @@ -118,7 +131,7 @@ impl Ui { self.ui.handle_event(Input::Resize(w, h)); } - pub fn add_graphic(&mut self, graphic: Graphic) -> ImgId { + pub fn add_graphic(&mut self, graphic: Graphic) -> image::Id { self.image_map.insert(self.cache.add_graphic(graphic)) } @@ -135,7 +148,7 @@ impl Ui { } // Accepts Option so widget can be unfocused. - pub fn focus_widget(&mut self, id: Option) { + pub fn focus_widget(&mut self, id: Option) { self.ui.keyboard_capture(match id { Some(id) => id, None => self.ui.window, @@ -143,7 +156,7 @@ impl Ui { } // Get id of current widget capturing keyboard. - pub fn widget_capturing_keyboard(&self) -> Option { + pub fn widget_capturing_keyboard(&self) -> Option { self.ui.global_input().current.widget_capturing_keyboard } @@ -189,11 +202,11 @@ impl Ui { } } - pub fn widget_input(&self, id: WidgId) -> Widget { + pub fn widget_input(&self, id: widget::Id) -> Widget { self.ui.widget_input(id) } - pub fn maintain(&mut self, renderer: &mut Renderer) { + pub fn maintain(&mut self, renderer: &mut Renderer, cam_params: Option<(Mat4, f32)>) { // Regenerate draw commands and associated models only if the ui changed let mut primitives = match self.ui.draw_if_changed() { Some(primitives) => primitives, @@ -216,6 +229,16 @@ impl Ui { let window_scissor = default_scissor(renderer); let mut current_scissor = window_scissor; + enum Placement { + Interface, + // Number and resolution + InWorld(usize, Option), + }; + + let mut placement = Placement::Interface; + // TODO: maybe mutate an ingame scale factor instead of this, depends on if we want them to scale with other ui scaling or not + let mut p_scale_factor = self.scale.scale_factor_physical(); + // Switches to the `Plain` state and completes the previous `Command` if not already in the // `Plain` state. macro_rules! switch_to_plain_state { @@ -223,14 +246,12 @@ impl Ui { if let State::Image = current_state { self.draw_commands .push(DrawCommand::image(start..mesh.vertices().len())); - current_state = State::Plain; start = mesh.vertices().len(); + current_state = State::Plain; } }; } - let p_scale_factor = self.scale.scale_factor_physical(); - while let Some(prim) = primitives.next() { let Primitive { kind, @@ -242,6 +263,7 @@ impl Ui { // Check for a change in the scissor. let new_scissor = { let (l, b, w, h) = scizzor.l_b_w_h(); + let scale_factor = self.scale.scale_factor_physical(); // Calculate minimum x and y coordinates while // flipping y axis (from +up to +down) and // moving origin to top-left corner (from middle). @@ -249,12 +271,12 @@ impl Ui { let min_y = self.ui.win_h / 2.0 - b - h; Aabr { min: Vec2 { - x: (min_x * p_scale_factor) as u16, - y: (min_y * p_scale_factor) as u16, + x: (min_x * scale_factor) as u16, + y: (min_y * scale_factor) as u16, }, max: Vec2 { - x: ((min_x + w) * p_scale_factor) as u16, - y: ((min_y + h) * p_scale_factor) as u16, + x: ((min_x + w) * scale_factor) as u16, + y: ((min_y + h) * scale_factor) as u16, }, } .intersection(window_scissor) @@ -272,12 +294,41 @@ impl Ui { self.draw_commands.push(DrawCommand::Scissor(new_scissor)); } + match placement { + Placement::InWorld(0, _) => { + placement = Placement::Interface; + p_scale_factor = self.scale.scale_factor_physical(); + // Finish current state + self.draw_commands.push(match current_state { + State::Plain => DrawCommand::plain(start..mesh.vertices().len()), + State::Image => DrawCommand::image(start..mesh.vertices().len()), + }); + start = mesh.vertices().len(); + // Push new position command + self.draw_commands.push(DrawCommand::WorldPos(None)); + } + Placement::InWorld(n, res) => match kind { + // Other types don't need to be drawn in the game + PrimitiveKind::Other(_) => {} + _ => { + placement = Placement::InWorld(n - 1, res); + if res.is_none() { + continue; + } + } + }, + Placement::Interface => {} + } + // Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0). - let ui_win_w = self.ui.win_w; - let ui_win_h = self.ui.win_h; + let (ui_win_w, ui_win_h) = if let Placement::InWorld(_, Some(res)) = placement { + (res as f64, res as f64) + } else { + (self.ui.win_w, self.ui.win_h) + }; let vx = |x: f64| (x / ui_win_w * 2.0) as f32; let vy = |y: f64| (y / ui_win_h * 2.0) as f32; - let gl_aabr = |rect: conrod_core::Rect| { + let gl_aabr = |rect: Rect| { let (l, r, b, t) = rect.l_r_b_t(); Aabr { min: Vec2::new(vx(l), vy(b)), @@ -285,7 +336,6 @@ impl Ui { } }; - use conrod_core::render::PrimitiveKind; match kind { PrimitiveKind::Image { image_id, @@ -321,16 +371,17 @@ impl Ui { // Transform the source rectangle into uv coordinate. // TODO: Make sure this is right. let source_aabr = { - let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0); /*match source_rect { - Some(src_rect) => { - let (l, r, b, t) = src_rect.l_r_b_t(); - ((l / image_w) as f32, - (r / image_w) as f32, - (b / image_h) as f32, - (t / image_h) as f32) - } - None => (0.0, 1.0, 0.0, 1.0), - };*/ + let (uv_l, uv_r, uv_b, uv_t) = (0.0, 1.0, 0.0, 1.0); + /*match source_rect { + Some(src_rect) => { + let (l, r, b, t) = src_rect.l_r_b_t(); + ((l / image_w) as f32, + (r / image_w) as f32, + (b / image_h) as f32, + (t / image_h) as f32) + } + None => (0.0, 1.0, 0.0, 1.0), + };*/ Aabr { min: Vec2::new(uv_l, uv_b), max: Vec2::new(uv_r, uv_t), @@ -371,13 +422,8 @@ impl Ui { font_id, } => { switch_to_plain_state!(); - // Get screen width and height. - let (screen_w, screen_h) = - renderer.get_resolution().map(|e| e as f32).into_tuple(); - // Calculate dpi factor. - let dpi_factor = screen_w / ui_win_w as f32; - let positioned_glyphs = text.positioned_glyphs(dpi_factor); + let positioned_glyphs = text.positioned_glyphs(p_scale_factor as f32); let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex(); // Queue the glyphs to be cached. for glyph in positioned_glyphs { @@ -410,12 +456,16 @@ impl Ui { }; let rect = Aabr { min: Vec2::new( - (screen_rect.min.x as f32 / screen_w - 0.5) * 2.0, - (screen_rect.max.y as f32 / screen_h - 0.5) * -2.0, + vx(screen_rect.min.x as f64 / p_scale_factor + - self.ui.win_w / 2.0), + vy(self.ui.win_h / 2.0 + - screen_rect.max.y as f64 / p_scale_factor), ), max: Vec2::new( - (screen_rect.max.x as f32 / screen_w - 0.5) * 2.0, - (screen_rect.min.y as f32 / screen_h - 0.5) * -2.0, + vx(screen_rect.max.x as f64 / p_scale_factor + - self.ui.win_w / 2.0), + vy(self.ui.win_h / 2.0 + - screen_rect.min.y as f64 / p_scale_factor), ), }; mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text)); @@ -469,10 +519,48 @@ impl Ui { )); } } + PrimitiveKind::Other(container) => { + if container.type_id == std::any::TypeId::of::() { + // Calculate the scale factor to pixels at this 3d point using the camera. + if let Some((view_mat, fov)) = cam_params { + // Retrieve world position + let parameters = container + .state_and_style::() + .unwrap() + .state + .parameters; + // Finish current state + self.draw_commands.push(match current_state { + State::Plain => DrawCommand::plain(start..mesh.vertices().len()), + State::Image => DrawCommand::image(start..mesh.vertices().len()), + }); + start = mesh.vertices().len(); + // Push new position command + self.draw_commands.push(DrawCommand::WorldPos(Some( + renderer.create_consts(&[parameters.pos.into()]).unwrap(), + ))); + + let pos_in_view = view_mat * Vec4::from_point(parameters.pos); + let scale_factor = self.ui.win_w as f64 + / (-2.0 + * pos_in_view.z as f64 + * (0.5 * fov as f64).tan() + * parameters.res as f64); + // Don't process ingame elements behind the camera or very far away + placement = if scale_factor > 0.1 { + p_scale_factor = ((scale_factor * 10.0).log2().round().powi(2) + / 10.0) + .min(1.6) + .max(0.2); + Placement::InWorld(parameters.num, Some(parameters.res)) + } else { + Placement::InWorld(parameters.num, None) + }; + } + } + } _ => {} // TODO: Add this. //PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind multicolor with id {:?}", id);} - // Other unneeded for now. - //PrimitiveKind::Other {..} => {println!("primitive kind other with id {:?}", id);} } } // Enter the final command. @@ -481,6 +569,25 @@ impl Ui { State::Image => DrawCommand::image(start..mesh.vertices().len()), }); + // Draw glyph cache (use for debugging). + /*self.draw_commands + .push(DrawCommand::Scissor(default_scissor(renderer))); + start = mesh.vertices().len(); + mesh.push_quad(create_ui_quad( + Aabr { + min: (-1.0, -1.0).into(), + max: (1.0, 1.0).into(), + }, + Aabr { + min: (0.0, 1.0).into(), + max: (1.0, 0.0).into(), + }, + Rgba::new(1.0, 1.0, 1.0, 0.8), + UiMode::Text, + )); + self.draw_commands + .push(DrawCommand::plain(start..mesh.vertices().len()));*/ + // Create a larger dynamic model if the mesh is larger than the current model size. if self.model.vbuf.len() < mesh.vertices().len() { self.model = renderer @@ -506,12 +613,17 @@ impl Ui { } } - pub fn render(&self, renderer: &mut Renderer) { - let mut scissor_to_render = default_scissor(renderer); + pub fn render(&self, renderer: &mut Renderer, maybe_globals: Option<&Consts>) { + let mut scissor = default_scissor(renderer); + let globals = maybe_globals.unwrap_or(&self.default_globals); + let mut locals = &self.interface_locals; for draw_command in self.draw_commands.iter() { match draw_command { - DrawCommand::Scissor(scissor) => { - scissor_to_render = *scissor; + DrawCommand::Scissor(new_scissor) => { + scissor = *new_scissor; + } + DrawCommand::WorldPos(ref pos) => { + locals = pos.as_ref().unwrap_or(&self.interface_locals); } DrawCommand::Draw { kind, verts } => { let tex = match kind { @@ -519,14 +631,14 @@ impl Ui { DrawKind::Plain => self.cache.glyph_cache_tex(), }; let model = self.model.submodel(verts.clone()); - renderer.render_ui_element(&model, &tex, scissor_to_render); + renderer.render_ui_element(&model, &tex, scissor, globals, locals); } } } } } -fn default_scissor(renderer: &mut Renderer) -> Aabr { +fn default_scissor(renderer: &Renderer) -> Aabr { let (screen_w, screen_h) = renderer.get_resolution().map(|e| e as u16).into_tuple(); Aabr { min: Vec2 { x: 0, y: 0 }, diff --git a/voxygen/src/ui/scale.rs b/voxygen/src/ui/scale.rs index 46c945fb52..9c096cf366 100644 --- a/voxygen/src/ui/scale.rs +++ b/voxygen/src/ui/scale.rs @@ -48,6 +48,10 @@ impl Scale { pub fn scale_factor_physical(&self) -> f64 { self.scale_factor_logical() * self.dpi_factor } + // Get the dpi factor (ratio between physical and logical coordinates) + pub fn dpi_factor(&self) -> f64 { + self.dpi_factor + } // Updates internal window size (and/or dpi_factor). pub fn window_resized(&mut self, new_dims: Vec2, renderer: &Renderer) { self.dpi_factor = renderer.get_resolution().x as f64 / new_dims.x; diff --git a/voxygen/src/ui/widgets/ingame.rs b/voxygen/src/ui/widgets/ingame.rs new file mode 100644 index 0000000000..426912eddc --- /dev/null +++ b/voxygen/src/ui/widgets/ingame.rs @@ -0,0 +1,199 @@ +use conrod_core::{ + builder_methods, image, + position::Dimension, + widget::{self, button, Id}, + widget_ids, Color, Position, Positionable, Rect, Sizeable, Ui, UiCell, Widget, WidgetCommon, +}; +use std::slice; +use vek::*; + +#[derive(Clone, WidgetCommon)] +pub struct Ingame { + #[conrod(common_builder)] + common: widget::CommonBuilder, + widget: W, + parameters: IngameParameters, +} + +pub trait Ingameable: Widget + Sized { + fn prim_count(&self) -> usize; + // Note this is not responsible for the 3d positioning + // Only call this directly if using IngameAnchor + fn set_ingame(self, id: widget::Id, parent_id: Id, ui: &mut UiCell) -> Self::Event { + self + // should pass focus to the window if these are clicked + // (they are not displayed where conrod thinks they are) + .graphics_for(ui.window) + //.parent(parent_id) // is this needed + .set(id, ui) + } + fn position_ingame(self, pos: Vec3) -> Ingame { + Ingame::new(pos, self) + } +} + +pub trait PrimitiveMarker {} +impl PrimitiveMarker for widget::Line {} +impl PrimitiveMarker for widget::Image {} +impl PrimitiveMarker for widget::PointPath {} +impl PrimitiveMarker for widget::Circle {} +impl PrimitiveMarker for widget::Oval {} +impl PrimitiveMarker for widget::Polygon {} +impl PrimitiveMarker for widget::Rectangle {} +impl PrimitiveMarker for widget::Triangles {} +impl<'a> PrimitiveMarker for widget::Text<'a> {} + +impl

Ingameable for P +where + P: Widget + PrimitiveMarker, +{ + fn prim_count(&self) -> usize { + 1 + } +} + +#[derive(Copy, Clone, PartialEq)] +pub struct IngameParameters { + // Number of primitive widgets to position in the game at the specified position + // Note this could be more than the number of widgets in the widgets field since widgets can contain widgets + pub num: usize, + pub pos: Vec3, + // Number of pixels per 1 unit in world coordinates (ie a voxel) + // Used for widgets that are rasterized before being sent to the gpu (text & images) + // Potentially make this autmatic based on distance to camera? + pub res: f32, +} + +pub struct State { + id: Option, + pub parameters: IngameParameters, +} + +pub type Style = (); + +impl Ingame { + pub fn new(pos: Vec3, widget: W) -> Self { + Self { + common: widget::CommonBuilder::default(), + parameters: IngameParameters { + num: widget.prim_count(), + pos, + res: 1.0, + }, + widget, + } + } + builder_methods! { + pub resolution { parameters.res = f32 } + } +} + +impl Widget for Ingame { + type State = State; + type Style = Style; + type Event = W::Event; + + fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State { + State { + id: Some(id_gen.next()), + parameters: self.parameters, + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { id, state, ui, .. } = args; + let Ingame { + widget, parameters, .. + } = self; + + // Update pos if it has changed + if state.parameters != parameters { + state.update(|s| { + s.parameters = parameters; + }); + } + + widget.set_ingame(state.id.unwrap(), id, ui) + } + + fn default_x_position(&self, ui: &Ui) -> Position { + Position::Absolute(0.0) + } + fn default_y_position(&self, ui: &Ui) -> Position { + Position::Absolute(0.0) + } + fn default_x_dimension(&self, ui: &Ui) -> Dimension { + Dimension::Absolute(1.0) + } + fn default_y_dimension(&self, ui: &Ui) -> Dimension { + Dimension::Absolute(1.0) + } +} + +// Use this if you have multiple widgets that you want to place at the same spot in-game +// but don't want to create a new custom widget to contain them both +// Note: widgets must be set immediately after settings this +// Note: remove this if it ends up unused +#[derive(Clone, WidgetCommon)] +pub struct IngameAnchor { + #[conrod(common_builder)] + common: widget::CommonBuilder, + parameters: IngameParameters, +} +impl IngameAnchor { + pub fn new(pos: Vec3) -> Self { + IngameAnchor { + common: widget::CommonBuilder::default(), + parameters: IngameParameters { + num: 0, + pos, + res: 1.0, + }, + } + } + pub fn for_widget(mut self, widget: impl Ingameable) -> Self { + self.parameters.num += widget.prim_count(); + self + } + pub fn for_widgets(mut self, widget: impl Ingameable, n: usize) -> Self { + self.parameters.num += n * widget.prim_count(); + self + } + pub fn for_prims(mut self, num: usize) -> Self { + self.parameters.num += num; + self + } +} + +impl Widget for IngameAnchor { + type State = State; + type Style = Style; + type Event = (); + + fn init_state(&self, _: widget::id::Generator) -> Self::State { + State { + id: None, + parameters: self.parameters, + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { id, state, ui, .. } = args; + let IngameAnchor { parameters, .. } = self; + + // Update pos if it has changed + if state.parameters != parameters { + state.update(|s| { + s.parameters = parameters; + }); + } + } +} diff --git a/voxygen/src/ui/widgets/mod.rs b/voxygen/src/ui/widgets/mod.rs index 21f1c2656d..26420fc870 100644 --- a/voxygen/src/ui/widgets/mod.rs +++ b/voxygen/src/ui/widgets/mod.rs @@ -1,2 +1,3 @@ pub mod image_slider; +pub mod ingame; pub mod toggle_button; diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 90e7d1aeb4..c36ad3675b 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -60,6 +60,7 @@ impl Window { key_map.insert(settings.controls.toggle_debug, Key::ToggleDebug); key_map.insert(settings.controls.fullscreen, Key::Fullscreen); key_map.insert(settings.controls.screenshot, Key::Screenshot); + key_map.insert(settings.controls.toggle_ingame_ui, Key::ToggleIngameUi); let tmp = Ok(Self { events_loop, @@ -219,7 +220,7 @@ impl Window { std::thread::spawn(move || { use std::{path::PathBuf, time::SystemTime}; // Check if folder exists and create it if it does not - let mut path = std::path::PathBuf::from("./screenshots"); + let mut path = PathBuf::from("./screenshots"); if !path.exists() { if let Err(err) = std::fs::create_dir(&path) { log::error!("Coudn't create folder for screenshot: {:?}", err); @@ -266,6 +267,7 @@ pub enum Key { ToggleDebug, Fullscreen, Screenshot, + ToggleIngameUi, } /// Represents an incoming event from the window.