Added time of day, tick time, basic skybox shader and client to voxygen

This commit is contained in:
Joshua Barretto 2019-01-12 15:57:19 +00:00
parent 96d01a9293
commit b37a6b67f9
14 changed files with 212 additions and 30 deletions

View File

@ -5,6 +5,6 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
common = { path = "../common" }
common = { package = "veloren-common", path = "../common" }
specs = "0.14"

View File

@ -4,7 +4,8 @@ use std::time::Duration;
// Internal
use common::state::State;
pub enum ClientErr {
#[derive(Debug)]
pub enum Error {
ServerShutdown,
Other(String),
}
@ -20,14 +21,21 @@ pub struct Client {
}
impl Client {
/// Create a new `Client`.
pub fn new() -> Self {
Self {
state: State::new(),
}
}
/// Get a reference to the client's game state.
pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the client's game state.
pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Execute a single client tick, handle input and update the game state by the given duration
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), ClientErr> {
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are
// managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the

View File

@ -1,5 +1,5 @@
[package]
name = "common"
name = "veloren-common"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"

50
common/src/clock.rs Normal file
View File

@ -0,0 +1,50 @@
// Standard
use std::{
thread,
time::{Duration, SystemTime},
};
const CLOCK_SMOOTHING: f64 = 0.9;
pub struct Clock {
last_sys_time: SystemTime,
last_delta: Option<Duration>,
running_tps_average: f64,
}
impl Clock {
pub fn new() -> Self {
Self {
last_sys_time: SystemTime::now(),
last_delta: None,
running_tps_average: 0.0,
}
}
pub fn get_tps(&self) -> f64 { self.running_tps_average }
pub fn get_last_delta(&self) -> Duration { self.last_delta.unwrap_or(Duration::new(0, 0)) }
pub fn get_avg_delta(&self) -> Duration { Duration::from_float_secs(self.running_tps_average) }
pub fn tick(&mut self, tgt: Duration) {
let delta = SystemTime::now()
.duration_since(self.last_sys_time)
.expect("Time went backwards!");
// Attempt to sleep to fill the gap
if let Some(sleep_dur) = tgt.checked_sub(delta) {
thread::sleep(sleep_dur);
}
let delta = SystemTime::now()
.duration_since(self.last_sys_time)
.expect("Time went backwards!");
self.last_sys_time = SystemTime::now();
self.last_delta = Some(delta);
self.running_tps_average =
CLOCK_SMOOTHING * self.running_tps_average +
(1.0 - CLOCK_SMOOTHING) * delta.as_float_secs();
}
}

View File

@ -1,5 +1,6 @@
#![feature(euclidean_division)]
#![feature(euclidean_division, duration_float)]
pub mod clock;
pub mod comp;
pub mod state;
pub mod terrain;

View File

@ -11,8 +11,18 @@ use crate::{
vol::VolSize,
};
// A type used to represent game state stored on both the client and the server. This includes
// things like entity components, terrain data, and global state like weather, time of day, etc.
/// How much faster should an in-game day be compared to a real day?
// TODO: Don't hard-code this
const DAY_CYCLE_FACTOR: f64 = 24.0 * 60.0;
/// A resource to store the time of day
struct TimeOfDay(f64);
/// A resource to store the tick (i.e: physics) time
struct Tick(f64);
/// A type used to represent game state stored on both the client and the server. This includes
/// things like entity components, terrain data, and global state like weather, time of day, etc.
pub struct State {
ecs_world: EcsWorld,
terrain_map: TerrainMap,
@ -20,9 +30,15 @@ pub struct State {
}
impl State {
/// Create a new `State`.
pub fn new() -> Self {
let mut ecs_world = EcsWorld::new();
// Register resources used by the ECS
ecs_world.add_resource(TimeOfDay(0.0));
ecs_world.add_resource(Tick(0.0));
// Register common components with the state
comp::register_local_components(&mut ecs_world);
Self {
@ -32,8 +48,24 @@ impl State {
}
}
// Execute a single tick, simulating the game state by the given duration
/// Get the current in-game time of day.
///
/// Note that this should not be used for physics, animations or other such localised timings.
pub fn get_time_of_day(&self) -> f64 {
self.ecs_world.read_resource::<TimeOfDay>().0
}
/// Get the current in-game tick time.
///
/// Note that this does not correspond to the time of day.
pub fn get_tick(&self) -> f64 {
self.ecs_world.read_resource::<Tick>().0
}
/// Execute a single tick, simulating the game state by the given duration.
pub fn tick(&mut self, dt: Duration) {
println!("Ticked!");
// Change the time accordingly
self.ecs_world.write_resource::<TimeOfDay>().0 += dt.as_float_secs() * DAY_CYCLE_FACTOR;
self.ecs_world.write_resource::<Tick>().0 += dt.as_float_secs();
}
}

View File

@ -1,10 +1,10 @@
[package]
name = "server"
name = "veloren-server"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
common = { path = "../common" }
common = { package = "veloren-common", path = "../common" }
specs = "0.14"

View File

@ -4,8 +4,8 @@ use std::time::Duration;
// Internal
use common::state::State;
pub enum ClientErr {
ServerShutdown,
#[derive(Debug)]
pub enum Error {
Other(String),
}
@ -20,14 +20,21 @@ pub struct Server {
}
impl Server {
/// Create a new `Server`.
pub fn new() -> Self {
Self {
state: State::new(),
}
}
/// Get a reference to the client's game state.
pub fn state(&self) -> &State { &self.state }
/// Get a mutable reference to the client's game state.
pub fn state_mut(&mut self) -> &mut State { &mut self.state }
/// Execute a single server tick, handle input and update the game state by the given duration
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), ClientErr> {
pub fn tick(&mut self, input: Input, dt: Duration) -> Result<(), Error> {
// This tick function is the centre of the Veloren universe. Most server-side things are
// managed from here, and as such it's important that it stays organised. Please consult
// the core developers before making significant changes to this code. Here is the

View File

@ -10,7 +10,8 @@ gl = ["gfx_device_gl"]
default = []
[dependencies]
common = { path = "../common" }
common = { package = "veloren-common", path = "../common" }
client = { package = "veloren-client", path = "../client" }
# Graphics
gfx = "0.17"

View File

@ -20,6 +20,27 @@ uniform u_globals {
out vec4 tgt_color;
void main() {
tgt_color = vec4(f_pos, 1.0);
const float PI = 3.141592;
vec3 get_sky_color(vec3 dir, float time_of_day) {
const float TIME_FACTOR = (PI * 2.0) / (3600.0 * 24.0);
const vec3 SKY_TOP = vec3(0.0, 0.3, 1.0);
const vec3 SKY_BOTTOM = vec3(0.0, 0.05, 0.2);
const vec3 SUN_HALO_COLOR = vec3(1.0, 0.8, 0.5);
const vec3 SUN_SURF_COLOR = vec3(1.0, 0.8, 0.5);
float sun_angle_rad = time_of_day * TIME_FACTOR;
vec3 sun_dir = vec3(sin(sun_angle_rad), 0.0, cos(sun_angle_rad));
vec3 sun_halo = pow(max(dot(dir, sun_dir), 0.0), 8.0) * SUN_HALO_COLOR;
vec3 sun_surf = min(pow(max(dot(dir, sun_dir), 0.0) + 0.01, 16.0), 1.0) * SUN_SURF_COLOR;
vec3 sun_light = sun_halo + sun_surf;
return mix(SKY_BOTTOM, SKY_TOP, (dir.z + 1.0) / 2.0) + sun_light * 0.5;
}
void main() {
tgt_color = vec4(get_sky_color(normalize(f_pos), time_of_day.x), 1.0);
}

View File

@ -1,12 +1,17 @@
// Standard
use std::any;
// Project
use client;
// Crate
use crate::render::RenderError;
/// Represents any error that may be triggered by Voxygen
#[derive(Debug)]
pub enum Error {
/// An error relating to the internal client
ClientError(client::Error),
/// A miscellaneous error relating to a backend dependency
BackendError(Box<any::Any>),
/// An error relating the rendering subsystem
@ -20,3 +25,9 @@ impl From<RenderError> for Error {
Error::RenderError(err)
}
}
impl From<client::Error> for Error {
fn from(err: client::Error) -> Self {
Error::ClientError(err)
}
}

View File

@ -20,8 +20,9 @@ gfx_defines! {
proj_mat: [[f32; 4]; 4] = "proj_mat",
cam_pos: [f32; 4] = "cam_pos",
focus_pos: [f32; 4] = "focus_pos",
// TODO: Fix whatever alignment issue requires these uniforms to be aligned
view_distance: [f32; 4] = "view_distance",
time_of_day: [f32; 4] = "time_of_day",
time_of_day: [f32; 4] = "time_of_day", // TODO: Make this f64
time: [f32; 4] = "time",
}
}
@ -47,7 +48,7 @@ impl Globals {
cam_pos: Vec3<f32>,
focus_pos: Vec3<f32>,
view_distance: f32,
time_of_day: f32,
time_of_day: f64,
time: f32,
) -> Self {
Self {
@ -56,7 +57,7 @@ impl Globals {
cam_pos: Vec4::from(cam_pos).into_array(),
focus_pos: Vec4::from(focus_pos).into_array(),
view_distance: [view_distance; 4],
time_of_day: [time_of_day; 4],
time_of_day: [time_of_day as f32; 4],
time: [time; 4],
}
}

View File

@ -1,10 +1,20 @@
pub mod camera;
// Standard
use std::time::Duration;
// Library
use vek::*;
// Project
use client::{
self,
Client,
};
// Crate
use crate::{
Error,
render::{
Consts,
Globals,
@ -32,6 +42,8 @@ pub struct Scene {
camera: Camera,
globals: Consts<Globals>,
skybox: Skybox,
client: Client,
}
impl Scene {
@ -50,9 +62,17 @@ impl Scene {
.create_consts_with(SkyboxLocals::default())
.unwrap(),
},
client: Client::new(),
}
}
/// Tick the scene (and the client attached to it)
pub fn tick(&mut self, dt: Duration) -> Result<(), Error> {
self.client.tick(client::Input {}, dt)?;
Ok(())
}
/// Handle an incoming user input event (i.e: cursor moved, key pressed, window closed, etc.).
pub fn handle_input_event(&mut self, event: Event) -> bool {
match event {
@ -78,7 +98,7 @@ impl Scene {
cam_pos,
self.camera.get_focus_pos(),
10.0,
0.0,
self.client.state().get_time_of_day(),
0.0,
))
.expect("Failed to update global constants");

View File

@ -1,6 +1,12 @@
// Standard
use std::time::Duration;
// Library
use vek::*;
// Project
use common::clock::Clock;
// Crate
use crate::{
PlayState,
@ -11,6 +17,8 @@ use crate::{
scene::Scene,
};
const FPS: u64 = 60;
pub struct SessionState {
scene: Scene,
}
@ -29,11 +37,33 @@ impl SessionState {
// The background colour
const BG_COLOR: Rgba<f32> = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 };
impl SessionState {
/// Render the session to the screen.
///
/// This method should be called once per frame.
pub fn render(&mut self, renderer: &mut Renderer) {
// Maintain scene GPU data
self.scene.maintain_gpu_data(renderer);
// Clear the screen
renderer.clear(BG_COLOR);
// Render the screen using the global renderer
self.scene.render_to(renderer);
// Finish the frame
renderer.flush();
}
}
impl PlayState for SessionState {
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult {
// Trap the cursor
global_state.window.trap_cursor();
// Set up an fps clock
let mut clock = Clock::new();
// Game loop
loop {
// Handle window events
@ -48,20 +78,20 @@ impl PlayState for SessionState {
// TODO: Do something if the event wasn't handled?
}
// Maintain scene GPU data
self.scene.maintain_gpu_data(global_state.window.renderer_mut());
// Perform an in-game tick
self.scene.tick(clock.get_last_delta())
.expect("Failed to tick the scene");
// Clear the screen
global_state.window.renderer_mut().clear(BG_COLOR);
// Render the session
self.render(global_state.window.renderer_mut());
// Render the screen using the global renderer
self.scene.render_to(global_state.window.renderer_mut());
// Finish the frame
global_state.window.renderer_mut().flush();
// Display the frame on the window
global_state.window
.swap_buffers()
.expect("Failed to swap window buffers");
// Wait for the next tick
clock.tick(Duration::from_millis(1000 / FPS));
}
}