Remove Client dependency from Scene types and audio managers, add an

example for using voxygen as a library to renderer images of characters
This commit is contained in:
Imbris 2020-02-28 22:59:11 -05:00
parent 511bbd3899
commit 4a0c474be1
18 changed files with 460 additions and 275 deletions

1
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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();
}

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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::<Pos>()
.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

View File

@ -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::<Stats>()
.get(client.entity())
.get(player_entity)
.map_or(self.state.clone(), |stats| ProgressionState {
level: stats.level.level(),
exp: stats.exp.current(),

View File

@ -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::<Pos>()
.get(client.entity())
.get(player_entity)
.map_or(Vec3::zero(), |pos| pos.0);
let player_ori = ecs
.read_storage::<Ori>()
.get(client.entity())
.get(player_entity)
.map_or(Vec3::zero(), |pos| pos.0);
audio.set_listener_pos(&player_position, &player_ori);

View File

@ -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);

View File

@ -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<String>,
pub singleplayer: Option<Singleplayer>,
}
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<dyn PlayState>),
/// Switch the current play state with a new play state.
Switch(Box<dyn PlayState>),
}
/// 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;
}

View File

@ -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<String>,
singleplayer: Option<Singleplayer>,
}
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<dyn PlayState>),
/// Switch the current play state with a new play state.
Switch(Box<dyn PlayState>),
}
/// 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")

View File

@ -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::<DeltaTime>().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

View File

@ -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<f32>,
pub proj_mat: Mat4<f32>,
pub cam_pos: Vec3<f32>,
}
pub struct Camera {
tgt_focus: Vec3<f32>,
focus: Vec3<f32>,
@ -33,6 +39,8 @@ pub struct Camera {
mode: CameraMode,
last_time: Option<f64>,
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<f32>, Mat4<f32>, Vec3<f32>) {
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::<f32>::identity()
self.dependents.view_mat = Mat4::<f32>::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<f32> {
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<f32> {
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<f32>) {

View File

@ -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::<Pos>()
.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<Globals>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
camera: &Camera,
) {
let tick = client.get_tick();
let ecs = client.state().ecs();
let ecs = state.ecs();
let character_state_storage = client
.state()
.read_storage::<common::comp::CharacterState>();
let character_state = character_state_storage.get(client.entity());
let character_state_storage = state.read_storage::<common::comp::CharacterState>();
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 {

View File

@ -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::<comp::Pos>()
.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::<comp::CharacterState>()
.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::<comp::Body>()
.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::<comp::Pos>(),
client.state().ecs().read_storage::<comp::Ori>().maybe(),
client
.state()
&scene_data.state.ecs().read_storage::<comp::Pos>(),
scene_data.state.ecs().read_storage::<comp::Ori>().maybe(),
scene_data
.state
.ecs()
.read_storage::<crate::ecs::comp::Interpolated>()
.maybe(),
&client.state().ecs().read_storage::<comp::LightEmitter>(),
&scene_data.state.ecs().read_storage::<comp::LightEmitter>(),
)
.join()
.filter(|(pos, _, _, _)| {
@ -247,15 +261,15 @@ impl Scene {
// Update shadow constants
let mut shadows = (
&client.state().ecs().read_storage::<comp::Pos>(),
client
.state()
&scene_data.state.ecs().read_storage::<comp::Pos>(),
scene_data
.state
.ecs()
.read_storage::<crate::ecs::comp::Interpolated>()
.maybe(),
client.state().ecs().read_storage::<comp::Scale>().maybe(),
&client.state().ecs().read_storage::<comp::Body>(),
&client.state().ecs().read_storage::<comp::Stats>(),
scene_data.state.ecs().read_storage::<comp::Scale>().maybe(),
&scene_data.state.ecs().read_storage::<comp::Body>(),
&scene_data.state.ecs().read_storage::<comp::Stats>(),
)
.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,

View File

@ -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<i32>) -> Result<&'a Self::Vox, Self::Error> { Ok(&VoidVox) }
}
struct Skybox {
model: Model<SkyboxPipeline>,
locals: Consts<SkyboxLocals>,
@ -42,8 +58,7 @@ pub struct Scene {
skybox: Skybox,
postprocess: PostProcess,
backdrop_model: Model<FigurePipeline>,
backdrop_state: FigureState<FixtureSkeleton>,
backdrop: Option<(Model<FigurePipeline>, FigureState<FixtureSkeleton>)>,
figure_model_cache: FigureModelCache,
figure_state: FigureState<CharacterSkeleton>,
@ -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<humanoid::Body>,
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<Globals> { &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,20 +145,18 @@ impl Scene {
}
}
pub fn maintain(
&mut self,
renderer: &mut Renderer,
client: &Client,
body: Option<humanoid::Body>,
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);
//self.camera
// .set_orientation(Vec3::new(scene_data.time as f32 * 0.0, 0.0, 0.0));
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; // hours*3600 seconds
if let Err(err) = renderer.update_consts(&mut self.globals, &[Globals::new(
@ -137,31 +166,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::<DeltaTime>().0,
);
self.figure_state
.skeleton_mut()
.interpolate(&tgt_skeleton, scene_data.delta_time);
}
self.figure_state.update(
@ -182,7 +210,7 @@ impl Scene {
pub fn render(
&mut self,
renderer: &mut Renderer,
client: &Client,
tick: u64,
body: Option<humanoid::Body>,
equipment: &Equipment,
) {
@ -195,7 +223,7 @@ impl Scene {
renderer,
Body::Humanoid(body),
Some(equipment),
client.get_tick(),
tick,
CameraMode::default(),
None,
)
@ -211,14 +239,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,

View File

@ -6,7 +6,7 @@ use crate::{
},
};
use client::Client;
use super::SceneData;
use common::{
assets,
figure::Segment,
@ -1083,26 +1083,26 @@ impl<V: RectRasterableVol> Terrain<V> {
pub fn maintain(
&mut self,
renderer: &mut Renderer,
client: &Client,
scene_data: &SceneData,
focus_pos: Vec3<f32>,
loaded_distance: f32,
view_mat: Mat4<f32>,
proj_mat: Mat4<f32>,
) {
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<V: RectRasterableVol> Terrain<V> {
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<V: RectRasterableVol> Terrain<V> {
// 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<V: RectRasterableVol> Terrain<V> {
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<V: RectRasterableVol> Terrain<V> {
}
// 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<V: RectRasterableVol> Terrain<V> {
})
.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<V: RectRasterableVol> Terrain<V> {
// 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<V: RectRasterableVol> Terrain<V> {
// 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),

View File

@ -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<f32> = 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,
);
}