diff --git a/Cargo.lock b/Cargo.lock index 399d5b6a2f..083ae3808f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3377,6 +3377,7 @@ dependencies = [ "specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "specs-idvs 0.1.0 (git+https://gitlab.com/veloren/specs-idvs.git)", "treeculler 0.1.0 (git+https://gitlab.com/yusdacra/treeculler.git)", + "uvth 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "vek 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "veloren-client 0.5.0", "veloren-common 0.5.0", diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 7c3e6debb0..9ae1608213 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -63,6 +63,7 @@ chrono = "0.4.9" rust-argon2 = "0.5" bincode = "1.2" deunicode = "1.0" +uvth = "3.1.1" [target.'cfg(target_os = "macos")'.dependencies] dispatch = "0.1.4" @@ -74,6 +75,7 @@ winres = "0.1" criterion = "0.3" git2 = "0.10" world = { package = "veloren-world", path = "../world" } +gfx_window_glutin = { version = "0.31.0", features = ["headless"] } [[bench]] name = "meshing_benchmark" diff --git a/voxygen/examples/character_renderer.rs b/voxygen/examples/character_renderer.rs new file mode 100644 index 0000000000..ae07183f5e --- /dev/null +++ b/voxygen/examples/character_renderer.rs @@ -0,0 +1,58 @@ +use common::{assets, comp}; +use gfx_window_glutin::init_headless; +use vek::*; +use veloren_voxygen::{render, scene::simple as scene}; + +fn main() { + // Setup renderer + let dim = (200u16, 300u16, 1, gfx::texture::AaMode::Single); + let events_loop = glutin::EventsLoop::new(); + let context = glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))) + .build_headless(&events_loop, (dim.0 as u32, dim.1 as u32).into()) + .expect("Failed to build headless context"); + + let (_context, device, factory, color_view, depth_view) = init_headless(context, dim); + + let mut renderer = render::Renderer::new( + device, + factory, + color_view, + depth_view, + render::AaMode::SsaaX4, + render::CloudMode::Regular, + render::FluidMode::Shiny, + ) + .unwrap(); + + // Create character + let body = comp::humanoid::Body::random(); + const STARTER_BOW: &str = "common.items.weapons.starter_bow"; + let equipment = comp::Equipment { + main: assets::load_cloned(STARTER_BOW).ok(), + alt: None, + }; + + // Setup scene (using the character selection screen `Scene`) + let mut scene = scene::Scene::new(&mut renderer, None); + let scene_data = scene::SceneData { + time: 1.0, + delta_time: 1.0, + tick: 0, + body: Some(body.clone()), + gamma: 1.0, + }; + scene.camera_mut().set_focus_pos(Vec3::unit_z() * 0.8); + scene.camera_mut().set_distance(1.5); + scene.camera_mut().update(0.0); + scene.maintain(&mut renderer, scene_data); + + // Render + renderer.clear(); + scene.render(&mut renderer, 0, Some(body), &equipment); + + renderer.flush(); + // Get image + let img = renderer.create_screenshot().unwrap(); + img.save("character.png").unwrap(); +} diff --git a/voxygen/src/audio/music.rs b/voxygen/src/audio/music.rs index 7b46abd016..397472472f 100644 --- a/voxygen/src/audio/music.rs +++ b/voxygen/src/audio/music.rs @@ -1,6 +1,5 @@ use crate::audio::AudioFrontend; -use client::Client; -use common::assets; +use common::{assets, state::State}; use rand::{seq::IteratorRandom, thread_rng}; use serde::Deserialize; use std::time::Instant; @@ -44,18 +43,18 @@ impl MusicMgr { } } - pub fn maintain(&mut self, audio: &mut AudioFrontend, client: &Client) { + pub fn maintain(&mut self, audio: &mut AudioFrontend, state: &State) { if audio.music_enabled() && self.began_playing.elapsed().as_secs_f64() > self.next_track_change { - self.play_random_track(audio, client); + self.play_random_track(audio, state); } } - fn play_random_track(&mut self, audio: &mut AudioFrontend, client: &Client) { + fn play_random_track(&mut self, audio: &mut AudioFrontend, state: &State) { const SILENCE_BETWEEN_TRACKS_SECONDS: f64 = 45.0; - let game_time = (client.state().get_time_of_day() as u64 % 86400) as u32; + let game_time = (state.get_time_of_day() as u64 % 86400) as u32; let current_period_of_day = Self::get_current_day_period(game_time); let mut rng = thread_rng(); diff --git a/voxygen/src/audio/sfx/event_mapper/mod.rs b/voxygen/src/audio/sfx/event_mapper/mod.rs index 94905cb802..a745b4de67 100644 --- a/voxygen/src/audio/sfx/event_mapper/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/mod.rs @@ -1,11 +1,12 @@ mod movement; mod progression; +use common::state::State; + use movement::MovementEventMapper; use progression::ProgressionEventMapper; use super::SfxTriggers; -use client::Client; pub struct SfxEventMapper { progression_event_mapper: ProgressionEventMapper, @@ -20,8 +21,15 @@ impl SfxEventMapper { } } - pub fn maintain(&mut self, client: &Client, triggers: &SfxTriggers) { - self.progression_event_mapper.maintain(client, triggers); - self.movement_event_mapper.maintain(client, triggers); + pub fn maintain( + &mut self, + state: &State, + player_entity: specs::Entity, + triggers: &SfxTriggers, + ) { + self.progression_event_mapper + .maintain(state, player_entity, triggers); + self.movement_event_mapper + .maintain(state, player_entity, triggers); } } diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index 617092e8d6..d222042a64 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -3,10 +3,10 @@ /// from use crate::audio::sfx::{SfxTriggerItem, SfxTriggers}; -use client::Client; use common::{ comp::{ActionState, Body, CharacterState, Item, ItemKind, MovementState, Pos, Stats, Vel}, event::{EventBus, SfxEvent, SfxEventItem}, + state::State, }; use hashbrown::HashMap; use specs::{Entity as EcsEntity, Join, WorldExt}; @@ -31,13 +31,13 @@ impl MovementEventMapper { } } - pub fn maintain(&mut self, client: &Client, triggers: &SfxTriggers) { + pub fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) { const SFX_DIST_LIMIT_SQR: f32 = 20000.0; - let ecs = client.state().ecs(); + let ecs = state.ecs(); let player_position = ecs .read_storage::() - .get(client.entity()) + .get(player_entity) .map_or(Vec3::zero(), |pos| pos.0); for (entity, pos, vel, body, stats, character) in ( @@ -97,7 +97,7 @@ impl MovementEventMapper { } } - self.cleanup(client.entity()); + self.cleanup(player_entity); } /// As the player explores the world, we track the last event of the nearby diff --git a/voxygen/src/audio/sfx/event_mapper/progression.rs b/voxygen/src/audio/sfx/event_mapper/progression.rs index 3edfcf1f7b..7c05db0360 100644 --- a/voxygen/src/audio/sfx/event_mapper/progression.rs +++ b/voxygen/src/audio/sfx/event_mapper/progression.rs @@ -2,10 +2,10 @@ /// and experience and emits associated SFX use crate::audio::sfx::SfxTriggers; -use client::Client; use common::{ comp::Stats, event::{EventBus, SfxEvent, SfxEventItem}, + state::State, }; use specs::WorldExt; @@ -30,13 +30,18 @@ impl ProgressionEventMapper { } } - pub fn maintain(&mut self, client: &Client, triggers: &SfxTriggers) { - let ecs = client.state().ecs(); + pub fn maintain( + &mut self, + state: &State, + player_entity: specs::Entity, + triggers: &SfxTriggers, + ) { + let ecs = state.ecs(); // level and exp changes let next_state = ecs.read_storage::() - .get(client.entity()) + .get(player_entity) .map_or(self.state.clone(), |stats| ProgressionState { level: stats.level.level(), exp: stats.exp.current(), diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 2af778a0ef..86771e86c2 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -4,11 +4,11 @@ mod event_mapper; use crate::audio::AudioFrontend; -use client::Client; use common::{ assets, comp::{Ori, Pos}, event::{EventBus, SfxEvent, SfxEventItem}, + state::State, }; use event_mapper::SfxEventMapper; use hashbrown::HashMap; @@ -50,23 +50,29 @@ impl SfxMgr { } } - pub fn maintain(&mut self, audio: &mut AudioFrontend, client: &Client) { + pub fn maintain( + &mut self, + audio: &mut AudioFrontend, + state: &State, + player_entity: specs::Entity, + ) { if !audio.sfx_enabled() { return; } - self.event_mapper.maintain(client, &self.triggers); + self.event_mapper + .maintain(state, player_entity, &self.triggers); - let ecs = client.state().ecs(); + let ecs = state.ecs(); let player_position = ecs .read_storage::() - .get(client.entity()) + .get(player_entity) .map_or(Vec3::zero(), |pos| pos.0); let player_ori = ecs .read_storage::() - .get(client.entity()) + .get(player_entity) .map_or(Vec3::zero(), |pos| pos.0); audio.set_listener_pos(&player_position, &player_ori); diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index d3818494d2..f5a0a89ad8 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -38,7 +38,7 @@ use crate::{ ecs::comp as vcomp, i18n::{i18n_asset_key, LanguageMetadata, VoxygenLocalization}, render::{AaMode, CloudMode, Consts, FluidMode, Globals, Renderer}, - scene::camera::Camera, + scene::camera::{self, Camera}, ui::{fonts::ConrodVoxygenFonts, Graphic, Ingameable, ScaleMode, Ui}, window::{Event as WinEvent, GameInput}, GlobalState, @@ -2112,9 +2112,13 @@ impl Hud { self.ui.focus_widget(maybe_id); } let events = self.update_layout(client, global_state, debug_info, dt); - let (v_mat, p_mat, _) = camera.compute_dependents(client); - self.ui - .maintain(&mut global_state.window.renderer_mut(), Some(p_mat * v_mat)); + let camera::Dependents { + view_mat, proj_mat, .. + } = camera.dependents(); + self.ui.maintain( + &mut global_state.window.renderer_mut(), + Some(proj_mat * view_mat), + ); // Check if item images need to be reloaded self.item_imgs.reload_if_changed(&mut self.ui); diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index 301aab5d19..56877b2131 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -1,6 +1,82 @@ -/// Used by benchmarks -pub mod mesh; -pub mod render; +#![deny(unsafe_code)] +#![feature(drain_filter)] +#![recursion_limit = "2048"] -// Used by tests +#[macro_use] +pub mod ui; +pub mod anim; +pub mod audio; +mod ecs; +pub mod error; +pub mod hud; pub mod i18n; +pub mod key_state; +pub mod logging; +pub mod menu; +pub mod mesh; +pub mod meta; +pub mod render; +pub mod scene; +pub mod session; +pub mod settings; +#[cfg(feature = "singleplayer")] +pub mod singleplayer; +pub mod window; + +// Reexports +pub use crate::error::Error; + +use crate::{ + audio::AudioFrontend, meta::Meta, settings::Settings, singleplayer::Singleplayer, + window::Window, +}; + +/// A type used to store state that is shared between all play states. +pub struct GlobalState { + pub settings: Settings, + pub meta: Meta, + pub window: Window, + pub audio: AudioFrontend, + pub info_message: Option, + pub singleplayer: Option, +} + +impl GlobalState { + /// Called after a change in play state has occurred (usually used to + /// reverse any temporary effects a state may have made). + pub fn on_play_state_changed(&mut self) { + self.window.grab_cursor(false); + self.window.needs_refresh_resize(); + } + + pub fn maintain(&mut self, dt: f32) { self.audio.maintain(dt); } +} + +pub enum Direction { + Forwards, + Backwards, +} + +/// 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 { + /// Pop all play states in reverse order and shut down the program. + Shutdown, + /// 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, direction: Direction, global_state: &mut GlobalState) -> PlayStateResult; + + /// Get a descriptive name for this state type. + fn name(&self) -> &'static str; +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 6cb1ec4e62..f2a091daf0 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -1,94 +1,21 @@ #![deny(unsafe_code)] -#![feature(drain_filter)] #![recursion_limit = "2048"] -#[macro_use] -pub mod ui; -pub mod anim; -pub mod audio; -mod ecs; -pub mod error; -pub mod hud; -pub mod i18n; -pub mod key_state; -mod logging; -pub mod menu; -pub mod mesh; -pub mod meta; -pub mod render; -pub mod scene; -pub mod session; -pub mod settings; -#[cfg(feature = "singleplayer")] -pub mod singleplayer; -pub mod window; - -// Reexports -pub use crate::error::Error; - -use crate::{ - audio::AudioFrontend, - i18n::{i18n_asset_key, VoxygenLocalization}, +use veloren_voxygen::{ + audio::{self, AudioFrontend}, + i18n::{self, i18n_asset_key, VoxygenLocalization}, + logging, menu::main::MainMenuState, meta::Meta, settings::Settings, - singleplayer::Singleplayer, window::Window, + Direction, GlobalState, PlayState, PlayStateResult, }; + use common::assets::{load, load_expect}; use log::{debug, error}; use std::{mem, panic, str::FromStr}; -/// A type used to store state that is shared between all play states. -pub struct GlobalState { - settings: Settings, - meta: Meta, - window: Window, - audio: AudioFrontend, - info_message: Option, - singleplayer: Option, -} - -impl GlobalState { - /// Called after a change in play state has occurred (usually used to - /// reverse any temporary effects a state may have made). - pub fn on_play_state_changed(&mut self) { - self.window.grab_cursor(false); - self.window.needs_refresh_resize(); - } - - pub fn maintain(&mut self, dt: f32) { self.audio.maintain(dt); } -} - -pub enum Direction { - Forwards, - Backwards, -} - -/// 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 { - /// Pop all play states in reverse order and shut down the program. - Shutdown, - /// 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, direction: Direction, global_state: &mut GlobalState) -> PlayStateResult; - - /// Get a descriptive name for this state type. - fn name(&self) -> &'static str; -} - fn main() { // Initialize logging. let term_log_level = std::env::var_os("VOXYGEN_LOG") diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 6dc28fb820..f7eaae27c2 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -1,16 +1,16 @@ -mod scene; mod ui; use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, + scene::simple::{self as scene, Scene}, session::SessionState, window::Event as WinEvent, Direction, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; -use common::{assets, clock::Clock, comp, msg::ClientState}; +use common::{assets, clock::Clock, comp, msg::ClientState, state::DeltaTime}; use log::error; -use scene::Scene; +use specs::WorldExt; use std::{cell::RefCell, rc::Rc, time::Duration}; use ui::CharSelectionUi; @@ -26,7 +26,10 @@ impl CharSelectionState { Self { char_selection_ui: CharSelectionUi::new(global_state), client, - scene: Scene::new(global_state.window.renderer_mut()), + scene: Scene::new( + global_state.window.renderer_mut(), + Some("fixture.selection_bg"), + ), } } } @@ -95,17 +98,23 @@ impl PlayState for CharSelectionState { }); // Maintain the scene. - self.scene.maintain( - global_state.window.renderer_mut(), - &self.client.borrow(), - humanoid_body.clone(), - global_state.settings.graphics.gamma, - ); + { + let client = self.client.borrow(); + let scene_data = scene::SceneData { + time: client.state().get_time(), + delta_time: client.state().ecs().read_resource::().0, + tick: client.get_tick(), + body: humanoid_body.clone(), + gamma: global_state.settings.graphics.gamma, + }; + self.scene + .maintain(global_state.window.renderer_mut(), scene_data); + } // Render the scene. self.scene.render( global_state.window.renderer_mut(), - &self.client.borrow(), + self.client.borrow().get_tick(), humanoid_body.clone(), &comp::Equipment { main: self diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index a0d0d59e54..9eb279d3c5 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -1,4 +1,3 @@ -use client::Client; use common::vol::{ReadVol, Vox}; use std::f32::consts::PI; use treeculler::Frustum; @@ -22,6 +21,13 @@ impl Default for CameraMode { fn default() -> Self { Self::ThirdPerson } } +#[derive(Clone)] +pub struct Dependents { + pub view_mat: Mat4, + pub proj_mat: Mat4, + pub cam_pos: Vec3, +} + pub struct Camera { tgt_focus: Vec3, focus: Vec3, @@ -33,6 +39,8 @@ pub struct Camera { mode: CameraMode, last_time: Option, + + dependents: Dependents, } impl Camera { @@ -49,12 +57,18 @@ impl Camera { mode, last_time: None, + + dependents: Dependents { + view_mat: Mat4::identity(), + proj_mat: Mat4::identity(), + cam_pos: Vec3::zero(), + }, } } /// Compute the transformation matrices (view matrix and projection matrix) /// and position of the camera. - pub fn compute_dependents(&self, client: &Client) -> (Mat4, Mat4, Vec3) { + pub fn compute_dependents(&mut self, terrain: &impl ReadVol) { let dist = { let (start, end) = ( self.focus @@ -66,9 +80,7 @@ impl Camera { self.focus, ); - match client - .state() - .terrain() + match terrain .ray(start, end) .ignore_error() .max_iter(500) @@ -82,7 +94,7 @@ impl Camera { .max(0.0) }; - let view_mat = Mat4::::identity() + self.dependents.view_mat = Mat4::::identity() * Mat4::translation_3d(-Vec3::unit_z() * dist) * Mat4::rotation_z(self.ori.z) * Mat4::rotation_x(self.ori.y) @@ -90,20 +102,21 @@ impl Camera { * Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x()) * Mat4::translation_3d(-self.focus); - let proj_mat = Mat4::perspective_rh_no(self.fov, self.aspect, NEAR_PLANE, FAR_PLANE); + self.dependents.proj_mat = + Mat4::perspective_rh_no(self.fov, self.aspect, NEAR_PLANE, FAR_PLANE); // TODO: Make this more efficient. - let cam_pos = Vec3::from(view_mat.inverted() * Vec4::unit_w()); - - (view_mat, proj_mat, cam_pos) + self.dependents.cam_pos = Vec3::from(self.dependents.view_mat.inverted() * Vec4::unit_w()); } - pub fn frustum(&self, client: &Client) -> Frustum { - let (view_mat, proj_mat, _) = self.compute_dependents(client); - - Frustum::from_modelview_projection((proj_mat * view_mat).into_col_arrays()) + pub fn frustum(&self) -> Frustum { + Frustum::from_modelview_projection( + (self.dependents.proj_mat * self.dependents.view_mat).into_col_arrays(), + ) } + pub fn dependents(&self) -> Dependents { self.dependents.clone() } + /// Rotate the camera about its focus by the given delta, limiting the input /// accordingly. pub fn rotate_by(&mut self, delta: Vec3) { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 9feaf4f7f1..47a0674246 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1,5 +1,5 @@ mod cache; -mod load; +pub mod load; pub use cache::FigureModelCache; pub use load::load_mesh; // TODO: Don't make this public. @@ -13,14 +13,17 @@ use crate::{ quadruped_small::QuadrupedSmallSkeleton, Animation, Skeleton, }, render::{Consts, FigureBoneData, FigureLocals, Globals, Light, Renderer, Shadow}, - scene::camera::{Camera, CameraMode}, + scene::{ + camera::{Camera, CameraMode}, + SceneData, + }, }; -use client::Client; use common::{ comp::{ ActionState::*, Body, CharacterState, ItemKind, Last, MovementState::*, Ori, Pos, Scale, Stats, Vel, }, + state::State, terrain::TerrainChunk, vol::RectRasterableVol, }; @@ -96,17 +99,18 @@ impl FigureMgr { self.biped_large_model_cache.clean(tick); } - pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client, camera: &Camera) { - let time = client.state().get_time(); - let tick = client.get_tick(); - let ecs = client.state().ecs(); - let view_distance = client.view_distance().unwrap_or(1); - let dt = client.state().get_delta_time(); - let frustum = camera.frustum(client); + pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData, camera: &Camera) { + let state = scene_data.state; + let time = state.get_time(); + let tick = scene_data.tick; + let ecs = state.ecs(); + let view_distance = scene_data.view_distance; + let dt = state.get_delta_time(); + let frustum = camera.frustum(); // Get player position. let player_pos = ecs .read_storage::() - .get(client.entity()) + .get(scene_data.player_entity) .map_or(Vec3::zero(), |pos| pos.0); for (entity, pos, ori, scale, body, character, last_character, stats) in ( @@ -1181,7 +1185,7 @@ impl FigureMgr { } } - // Clear states that have dead entities. + // Clear states that have deleted entities. self.character_states .retain(|entity, _| ecs.entities().is_alive(*entity)); self.quadruped_small_states @@ -1209,19 +1213,18 @@ impl FigureMgr { pub fn render( &mut self, renderer: &mut Renderer, - client: &mut Client, + state: &State, + player_entity: EcsEntity, + tick: u64, globals: &Consts, lights: &Consts, shadows: &Consts, camera: &Camera, ) { - let tick = client.get_tick(); - let ecs = client.state().ecs(); + let ecs = state.ecs(); - let character_state_storage = client - .state() - .read_storage::(); - let character_state = character_state_storage.get(client.entity()); + let character_state_storage = state.read_storage::(); + let character_state = character_state_storage.get(player_entity); for (entity, _, _, body, stats, _) in ( &ecs.entities(), @@ -1235,7 +1238,7 @@ impl FigureMgr { // Don't render dead entities .filter(|(_, _, _, _, stats, _)| stats.map_or(true, |s| !s.is_dead)) { - let is_player = entity == client.entity(); + let is_player = entity == player_entity; let player_camera_mode = if is_player { camera.get_mode() } else { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 6653a75e8e..84c6aa2f3e 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,29 +1,29 @@ pub mod camera; pub mod figure; +pub mod simple; pub mod terrain; use self::{ camera::{Camera, CameraMode}, figure::FigureMgr, - music::MusicMgr, terrain::Terrain, }; use crate::{ anim::character::SkeletonAttr, - audio::{music, sfx::SfxMgr, AudioFrontend}, + audio::{music::MusicMgr, sfx::SfxMgr, AudioFrontend}, render::{ create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals, PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline, }, window::Event, }; -use client::Client; use common::{ comp, + state::State, terrain::{BlockKind, TerrainChunk}, vol::ReadVol, }; -use specs::{Join, WorldExt}; +use specs::{Entity as EcsEntity, Join, WorldExt}; use vek::*; // TODO: Don't hard-code this. @@ -62,6 +62,15 @@ pub struct Scene { music_mgr: MusicMgr, } +pub struct SceneData<'a> { + pub state: &'a State, + pub player_entity: specs::Entity, + pub loaded_distance: f32, + pub view_distance: u32, + pub tick: u64, + pub thread_pool: &'a uvth::ThreadPool, +} + impl Scene { /// Create a new `Scene` with default parameters. pub fn new(renderer: &mut Renderer) -> Self { @@ -148,29 +157,29 @@ impl Scene { &mut self, renderer: &mut Renderer, audio: &mut AudioFrontend, - client: &Client, + scene_data: &SceneData, gamma: f32, ) { // Get player position. - let player_pos = client - .state() + let player_pos = scene_data + .state .ecs() .read_storage::() - .get(client.entity()) + .get(scene_data.player_entity) .map_or(Vec3::zero(), |pos| pos.0); - let player_rolling = client - .state() + let player_rolling = scene_data + .state .ecs() .read_storage::() - .get(client.entity()) + .get(scene_data.player_entity) .map_or(false, |cs| cs.action.is_roll()); - let player_scale = match client - .state() + let player_scale = match scene_data + .state .ecs() .read_storage::() - .get(client.entity()) + .get(scene_data.player_entity) { Some(comp::Body::Humanoid(body)) => SkeletonAttr::calculate_scale(body), _ => 1_f32, @@ -196,25 +205,30 @@ impl Scene { ); // Tick camera for interpolation. - self.camera.update(client.state().get_time()); + self.camera.update(scene_data.state.get_time()); // Compute camera matrices. - let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client); + self.camera.compute_dependents(&*scene_data.state.terrain()); + let camera::Dependents { + view_mat, + proj_mat, + cam_pos, + } = self.camera.dependents(); // Update chunk loaded distance smoothly for nice shader fog - let loaded_distance = client.loaded_distance(); - self.loaded_distance = (0.98 * self.loaded_distance + 0.02 * loaded_distance).max(0.01); + self.loaded_distance = + (0.98 * self.loaded_distance + 0.02 * scene_data.loaded_distance).max(0.01); // Update light constants let mut lights = ( - &client.state().ecs().read_storage::(), - client.state().ecs().read_storage::().maybe(), - client - .state() + &scene_data.state.ecs().read_storage::(), + scene_data.state.ecs().read_storage::().maybe(), + scene_data + .state .ecs() .read_storage::() .maybe(), - &client.state().ecs().read_storage::(), + &scene_data.state.ecs().read_storage::(), ) .join() .filter(|(pos, _, _, _)| { @@ -247,15 +261,15 @@ impl Scene { // Update shadow constants let mut shadows = ( - &client.state().ecs().read_storage::(), - client - .state() + &scene_data.state.ecs().read_storage::(), + scene_data + .state .ecs() .read_storage::() .maybe(), - client.state().ecs().read_storage::().maybe(), - &client.state().ecs().read_storage::(), - &client.state().ecs().read_storage::(), + scene_data.state.ecs().read_storage::().maybe(), + &scene_data.state.ecs().read_storage::(), + &scene_data.state.ecs().read_storage::(), ) .join() .filter(|(_, _, _, _, stats)| !stats.is_dead) @@ -285,13 +299,13 @@ impl Scene { cam_pos, self.camera.get_focus_pos(), self.loaded_distance, - client.state().get_time_of_day(), - client.state().get_time(), + scene_data.state.get_time_of_day(), + scene_data.state.get_time(), renderer.get_resolution(), lights.len(), shadows.len(), - client - .state() + scene_data + .state .terrain() .get(cam_pos.map(|e| e.floor() as i32)) .map(|b| b.kind()) @@ -304,7 +318,7 @@ impl Scene { // Maintain the terrain. self.terrain.maintain( renderer, - client, + &scene_data, self.camera.get_focus_pos(), self.loaded_distance, view_mat, @@ -312,22 +326,31 @@ impl Scene { ); // Maintain the figures. - self.figure_mgr.maintain(renderer, client, &self.camera); + self.figure_mgr.maintain(renderer, scene_data, &self.camera); // Remove unused figures. - self.figure_mgr.clean(client.get_tick()); + self.figure_mgr.clean(scene_data.tick); // Maintain audio - self.sfx_mgr.maintain(audio, client); - self.music_mgr.maintain(audio, client); + self.sfx_mgr + .maintain(audio, scene_data.state, scene_data.player_entity); + self.music_mgr.maintain(audio, scene_data.state); } /// Render the scene using the provided `Renderer`. - pub fn render(&mut self, renderer: &mut Renderer, client: &mut Client) { + pub fn render( + &mut self, + renderer: &mut Renderer, + state: &State, + player_entity: EcsEntity, + tick: u64, + ) { // Render terrain and figures. self.figure_mgr.render( renderer, - client, + state, + player_entity, + tick, &self.globals, &self.lights, &self.shadows, diff --git a/voxygen/src/menu/char_selection/scene.rs b/voxygen/src/scene/simple.rs similarity index 66% rename from voxygen/src/menu/char_selection/scene.rs rename to voxygen/src/scene/simple.rs index c09f794013..c12a6f9641 100644 --- a/voxygen/src/menu/char_selection/scene.rs +++ b/voxygen/src/scene/simple.rs @@ -9,21 +9,37 @@ use crate::{ PostProcessLocals, PostProcessPipeline, Renderer, Shadow, SkyboxLocals, SkyboxPipeline, }, scene::{ - camera::{Camera, CameraMode}, + camera::{self, Camera, CameraMode}, figure::{load_mesh, FigureModelCache, FigureState}, }, window::{Event, PressState}, }; -use client::Client; use common::{ comp::{humanoid, Body, Equipment}, - state::DeltaTime, terrain::BlockKind, + vol::{BaseVol, ReadVol, Vox}, }; use log::error; -use specs::WorldExt; use vek::*; +#[derive(PartialEq, Eq, Copy, Clone)] +struct VoidVox; +impl Vox for VoidVox { + fn empty() -> Self { VoidVox } + + fn is_empty(&self) -> bool { true } + + fn or(self, _other: Self) -> Self { VoidVox } +} +struct VoidVol; +impl BaseVol for VoidVol { + type Error = (); + type Vox = VoidVox; +} +impl ReadVol for VoidVol { + fn get<'a>(&'a self, _pos: Vec3) -> Result<&'a Self::Vox, Self::Error> { Ok(&VoidVox) } +} + struct Skybox { model: Model, locals: Consts, @@ -42,8 +58,7 @@ pub struct Scene { skybox: Skybox, postprocess: PostProcess, - backdrop_model: Model, - backdrop_state: FigureState, + backdrop: Option<(Model, FigureState)>, figure_model_cache: FigureModelCache, figure_state: FigureState, @@ -52,15 +67,28 @@ pub struct Scene { char_ori: f32, } +pub struct SceneData { + pub time: f64, + pub delta_time: f32, + pub tick: u64, + pub body: Option, + pub gamma: f32, +} + impl Scene { - pub fn new(renderer: &mut Renderer) -> Self { + pub fn new(renderer: &mut Renderer, backdrop: Option<&str>) -> Self { let resolution = renderer.get_resolution().map(|e| e as f32); + let mut camera = Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson); + camera.set_focus_pos(Vec3::unit_z() * 1.5); + camera.set_distance(3.0); // 4.2 + camera.set_orientation(Vec3::new(0.0, 0.0, 0.0)); + Self { globals: renderer.create_consts(&[Globals::default()]).unwrap(), lights: renderer.create_consts(&[Light::default(); 32]).unwrap(), shadows: renderer.create_consts(&[Shadow::default(); 32]).unwrap(), - camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson), + camera, skybox: Skybox { model: renderer.create_model(&create_skybox_mesh()).unwrap(), @@ -75,13 +103,14 @@ impl Scene { figure_model_cache: FigureModelCache::new(), figure_state: FigureState::new(renderer, CharacterSkeleton::new()), - backdrop_model: renderer - .create_model(&load_mesh( - "fixture.selection_bg", - Vec3::new(-55.0, -49.5, -2.0), - )) - .unwrap(), - backdrop_state: FigureState::new(renderer, FixtureSkeleton::new()), + backdrop: backdrop.map(|specifier| { + ( + renderer + .create_model(&load_mesh(specifier, Vec3::new(-55.0, -49.5, -2.0))) + .unwrap(), + FigureState::new(renderer, FixtureSkeleton::new()), + ) + }), turning: false, char_ori: 0.0, @@ -90,6 +119,8 @@ impl Scene { pub fn globals(&self) -> &Consts { &self.globals } + pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera } + /// Handle an incoming user input event (e.g.: cursor moved, key pressed, /// window closed). /// @@ -114,22 +145,17 @@ impl Scene { } } - pub fn maintain( - &mut self, - renderer: &mut Renderer, - client: &Client, - body: Option, - gamma: f32, - ) { - self.camera.set_focus_pos(Vec3::unit_z() * 1.5); - self.camera.update(client.state().get_time()); - self.camera.set_distance(3.0); // 4.2 - self.camera - .set_orientation(Vec3::new(client.state().get_time() as f32 * 0.0, 0.0, 0.0)); + pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: SceneData) { + self.camera.update(scene_data.time); - let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents(client); - const VD: f32 = 115.0; //View Distance - const TIME: f64 = 43200.0; // hours*3600 seconds + self.camera.compute_dependents(&VoidVol); + let camera::Dependents { + view_mat, + proj_mat, + cam_pos, + } = self.camera.dependents(); + const VD: f32 = 115.0; // View Distance + const TIME: f64 = 43200.0; // 12 hours*3600 seconds if let Err(err) = renderer.update_consts(&mut self.globals, &[Globals::new( view_mat, proj_mat, @@ -137,31 +163,30 @@ impl Scene { self.camera.get_focus_pos(), VD, TIME, - client.state().get_time(), + scene_data.time, renderer.get_resolution(), 0, 0, BlockKind::Air, None, - gamma, + scene_data.gamma, )]) { error!("Renderer failed to update: {:?}", err); } - self.figure_model_cache.clean(client.get_tick()); + self.figure_model_cache.clean(scene_data.tick); - if let Some(body) = body { + if let Some(body) = scene_data.body { let tgt_skeleton = IdleAnimation::update_skeleton( self.figure_state.skeleton_mut(), - client.state().get_time(), - client.state().get_time(), + scene_data.time, + scene_data.time, &mut 0.0, &SkeletonAttr::from(&body), ); - self.figure_state.skeleton_mut().interpolate( - &tgt_skeleton, - client.state().ecs().read_resource::().0, - ); + self.figure_state + .skeleton_mut() + .interpolate(&tgt_skeleton, scene_data.delta_time); } self.figure_state.update( @@ -182,7 +207,7 @@ impl Scene { pub fn render( &mut self, renderer: &mut Renderer, - client: &Client, + tick: u64, body: Option, equipment: &Equipment, ) { @@ -195,7 +220,7 @@ impl Scene { renderer, Body::Humanoid(body), Some(equipment), - client.get_tick(), + tick, CameraMode::default(), None, ) @@ -211,14 +236,16 @@ impl Scene { ); } - renderer.render_figure( - &self.backdrop_model, - &self.globals, - self.backdrop_state.locals(), - self.backdrop_state.bone_consts(), - &self.lights, - &self.shadows, - ); + if let Some((model, state)) = &self.backdrop { + renderer.render_figure( + model, + &self.globals, + state.locals(), + state.bone_consts(), + &self.lights, + &self.shadows, + ); + } renderer.render_post_process( &self.postprocess.model, diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index c6c41f4928..be5f7c40c3 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -6,7 +6,7 @@ use crate::{ }, }; -use client::Client; +use super::SceneData; use common::{ assets, figure::Segment, @@ -1083,26 +1083,26 @@ impl Terrain { pub fn maintain( &mut self, renderer: &mut Renderer, - client: &Client, + scene_data: &SceneData, focus_pos: Vec3, loaded_distance: f32, view_mat: Mat4, proj_mat: Mat4, ) { - let current_tick = client.get_tick(); - let current_time = client.state().get_time(); + let current_tick = scene_data.tick; + let current_time = scene_data.state.get_time(); // Add any recently created or changed chunks to the list of chunks to be // meshed. - for (modified, pos) in client - .state() + for (modified, pos) in scene_data + .state .terrain_changes() .modified_chunks .iter() .map(|c| (true, c)) .chain( - client - .state() + scene_data + .state .terrain_changes() .new_chunks .iter() @@ -1121,8 +1121,8 @@ impl Terrain { let mut neighbours = true; for i in -1..2 { for j in -1..2 { - neighbours &= client - .state() + neighbours &= scene_data + .state .terrain() .get_key(pos + Vec2::new(i, j)) .is_some(); @@ -1143,20 +1143,20 @@ impl Terrain { // Add the chunks belonging to recently changed blocks to the list of chunks to // be meshed - for pos in client - .state() + for pos in scene_data + .state .terrain_changes() .modified_blocks .iter() .map(|(p, _)| *p) { - let chunk_pos = client.state().terrain().pos_key(pos); + let chunk_pos = scene_data.state.terrain().pos_key(pos); // Only mesh if this chunk has all its neighbors let mut neighbours = true; for i in -1..2 { for j in -1..2 { - neighbours &= client - .state() + neighbours &= scene_data + .state .terrain() .get_key(chunk_pos + Vec2::new(i, j)) .is_some(); @@ -1178,15 +1178,15 @@ impl Terrain { for x in -1..2 { for y in -1..2 { let neighbour_pos = pos + Vec3::new(x, y, 0); - let neighbour_chunk_pos = client.state().terrain().pos_key(neighbour_pos); + let neighbour_chunk_pos = scene_data.state.terrain().pos_key(neighbour_pos); if neighbour_chunk_pos != chunk_pos { // Only remesh if this chunk has all its neighbors let mut neighbours = true; for i in -1..2 { for j in -1..2 { - neighbours &= client - .state() + neighbours &= scene_data + .state .terrain() .get_key(neighbour_chunk_pos + Vec2::new(i, j)) .is_some(); @@ -1205,7 +1205,7 @@ impl Terrain { } // Remove any models for chunks that have been recently removed. - for pos in &client.state().terrain_changes().removed_chunks { + for pos in &scene_data.state.terrain_changes().removed_chunks { self.chunks.remove(pos); self.mesh_todo.remove(pos); } @@ -1220,7 +1220,7 @@ impl Terrain { }) .min_by_key(|todo| todo.active_worker.unwrap_or(todo.started_tick)) { - if client.thread_pool().queued_jobs() > 0 { + if scene_data.thread_pool.queued_jobs() > 0 { break; } @@ -1239,7 +1239,7 @@ impl Terrain { // Copy out the chunk data we need to perform the meshing. We do this by taking // a sample of the terrain that includes both the chunk we want and // its neighbours. - let volume = match client.state().terrain().sample(aabr) { + let volume = match scene_data.state.terrain().sample(aabr) { Ok(sample) => sample, // Either this chunk or its neighbours doesn't yet exist, so we keep it in the // queue to be processed at a later date when we have its neighbours. @@ -1266,7 +1266,7 @@ impl Terrain { // Queue the worker thread. let started_tick = todo.started_tick; - client.thread_pool().execute(move || { + scene_data.thread_pool.execute(move || { let _ = send.send(mesh_worker( pos, (min_z as f32, max_z as f32), diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 7670eb1b1e..cf47210669 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -4,7 +4,7 @@ use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, key_state::KeyState, render::Renderer, - scene::Scene, + scene::{camera, Scene, SceneData}, window::{Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; @@ -108,7 +108,11 @@ impl SessionState { renderer.clear(); // Render the screen using the global renderer - self.scene.render(renderer, &mut self.client.borrow_mut()); + { + let client = self.client.borrow(); + self.scene + .render(renderer, client.state(), client.entity(), client.get_tick()); + } // Draw the UI to the screen self.hud.render(renderer, self.scene.globals()); @@ -145,10 +149,12 @@ impl PlayState for SessionState { let mut current_client_state = self.client.borrow().get_client_state(); while let ClientState::Pending | ClientState::Character = current_client_state { // Compute camera data - let (view_mat, _, cam_pos) = self - .scene - .camera() - .compute_dependents(&self.client.borrow()); + self.scene + .camera_mut() + .compute_dependents(&*self.client.borrow().state().terrain()); + let camera::Dependents { + view_mat, cam_pos, .. + } = self.scene.camera().dependents(); let cam_dir: Vec3 = Vec3::from(view_mat.inverted() * -Vec4::unit_z()); // Check to see whether we're aiming at anything @@ -396,6 +402,10 @@ impl PlayState for SessionState { // Maintain global state. global_state.maintain(clock.get_last_delta().as_secs_f32()); + // Recompute dependents just in case some input modified the camera + self.scene + .camera_mut() + .compute_dependents(&*self.client.borrow().state().terrain()); // Extract HUD events ensuring the client borrow gets dropped. let mut hud_events = self.hud.maintain( &self.client.borrow(), @@ -555,6 +565,9 @@ impl PlayState for SessionState { global_state.settings.graphics.fov = new_fov; global_state.settings.save_to_file_warn(); self.scene.camera_mut().set_fov_deg(new_fov); + self.scene + .camera_mut() + .compute_dependents(&*self.client.borrow().state().terrain()); }, HudEvent::ChangeGamma(new_gamma) => { global_state.settings.graphics.gamma = new_gamma; @@ -618,11 +631,19 @@ impl PlayState for SessionState { if global_state.singleplayer.is_none() || !global_state.singleplayer.as_ref().unwrap().is_paused() { - // Maintain the scene. + let client = self.client.borrow(); + let scene_data = SceneData { + state: client.state(), + player_entity: client.entity(), + loaded_distance: client.loaded_distance(), + view_distance: client.view_distance().unwrap_or(1), + tick: client.get_tick(), + thread_pool: client.thread_pool(), + }; self.scene.maintain( global_state.window.renderer_mut(), &mut global_state.audio, - &self.client.borrow(), + &scene_data, global_state.settings.graphics.gamma, ); } diff --git a/voxygen/src/ui/img_ids.rs b/voxygen/src/ui/img_ids.rs index f3cfc0f65f..ea5a8ac294 100644 --- a/voxygen/src/ui/img_ids.rs +++ b/voxygen/src/ui/img_ids.rs @@ -114,7 +114,12 @@ pub struct Rotations { /// corresponding ImgIds and create a struct with all of them. /// /// Example usage: -/// ``` +/// ```ignore +/// use veloren_voxygen::{ +/// image_ids, +/// ui::img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic}, +/// }; +/// /// image_ids! { /// pub struct Imgs { ///