From 91d3a0352088611d3b78d3344b7a2bf2d4955a0a Mon Sep 17 00:00:00 2001 From: Stijn Buys Date: Tue, 6 May 2008 21:07:11 +0000 Subject: client-side frame interpolation: frames and timers --- src/client/camera.cc | 4 +-- src/client/input.cc | 8 +++-- src/client/view.cc | 23 +++++++------ src/core/Makefile.am | 4 +-- src/core/entity.cc | 14 +++----- src/core/entity.h | 30 ++++++++--------- src/core/gameconnection.cc | 4 +++ src/core/gameinterface.cc | 48 ++++++++++++++++++++++++++- src/core/gameinterface.h | 28 ++++++++++++++++ src/core/gameserver.cc | 19 ++++++++++- src/core/gameserver.h | 1 + src/core/netconnection.cc | 26 ++++++++++----- src/core/stats.h | 1 - src/game/game.cc | 1 + src/math/functions.h | 6 ++++ src/render/draw.cc | 82 +++++++++++++++++++++++++--------------------- 16 files changed, 207 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/client/camera.cc b/src/client/camera.cc index 8144781..808ff7e 100644 --- a/src/client/camera.cc +++ b/src/client/camera.cc @@ -179,8 +179,8 @@ void draw(float seconds) target_direction = core::localcontrol()->target_direction; pitch_target = core::localcontrol()->target_pitch; - yaw_target = - 45 * target_direction; - pitch_target = pitch_track -45 * target_pitch; + yaw_target = - 25 * target_direction; + pitch_target = pitch_track - 25 * target_pitch; } else if (mode == Free) { diff --git a/src/client/input.cc b/src/client/input.cc index 17ca724..fa23c1a 100644 --- a/src/client/input.cc +++ b/src/client/input.cc @@ -12,6 +12,7 @@ #include "client/camera.h" #include "client/keyboard.h" #include "client/video.h" +#include "math/functions.h" #include "render/text.h" #include "SDL/SDL.h" @@ -231,7 +232,7 @@ void frame(float seconds) break; case SDL_KEYDOWN: if (event.key.keysym.sym == '`' || event.key.keysym.sym == '~') { - last_control = 0; + //last_control = 0; console::toggle(); if (console::visible() && chat::visible()) @@ -298,8 +299,9 @@ void frame(float seconds) local_direction = mouse_direction; local_pitch = mouse_pitch; } else if (camera::mode == camera::Free) { - camera::set_direction(-mouse_direction); - camera::set_pitch(-mouse_pitch); + // squared values to smoothen camera movement + camera::set_direction( -mouse_direction * math::absf(mouse_direction)); + camera::set_pitch(-mouse_pitch * math::absf(mouse_pitch)); } } else { diff --git a/src/client/view.cc b/src/client/view.cc index 660930b..488a1cb 100644 --- a/src/client/view.cc +++ b/src/client/view.cc @@ -132,19 +132,18 @@ void draw_status() gl::color(1.0f, 1.0f, 1.0f, 1.0f); std::stringstream status; - int hours = (int) sys::time() / 3600; - int minutes = (int)(sys::time() - 3600*hours) / 60; - int seconds = (int)(sys::time() - 3600*hours - 60 *minutes); - status << "clock " << std::setfill('0') << std::setw(2) << hours << ":" - << std::setfill('0') << std::setw(2) << minutes << ":" - << std::setfill('0') << std::setw(2) << seconds; - - minutes = (int) floorf(core::application()->time() / 60.0f); - seconds = (int) floorf(core::application()->time() - (float) minutes* 60.0f); - - status << " time " << std::setfill('0') << std::setw(2) << minutes << ":" << std::setfill('0') << std::setw(2) << seconds; + /* + int minutes = (int) floorf(core::application()->time() / 60.0f); + int seconds = (int) floorf(core::application()->time() - (float) minutes* 60.0f); + */ + + if (core::game()) { + int minutes = (int) floorf(core::game()->clientframetime() / 60.0f); + int seconds = (int) floorf( core::game()->clientframetime() - (float) minutes* 60.0f); - draw_text(CHARWIDTH, 4, status); + status << " time " << std::setfill('0') << std::setw(2) << minutes << ":" << std::setfill('0') << std::setw(2) << seconds; + draw_text(CHARWIDTH, 4, status); + } // print stats if desired if (draw_stats && draw_stats->value()) { diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 7c18362..efa4e7b 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -1,7 +1,7 @@ METASOURCES = AUTO INCLUDES = -I$(top_srcdir)/src -libcore_la_SOURCES = application.cc commandbuffer.cc core.cc cvar.cc entity.cc \ +libcore_la_SOURCES = application.cc commandbuffer.cc clientstate.cc core.cc cvar.cc entity.cc \ func.cc gameconnection.cc gameinterface.cc gameserver.cc module.cc netclient.cc \ netconnection.cc netserver.cc player.cc stats.cc libcore_la_LDFLAGS = -avoid-version -no-undefined @@ -9,7 +9,7 @@ libcore_la_LIBADD = $(top_builddir)/src/filesystem/libfilesystem.la \ $(top_builddir)/src/math/libmath.la $(top_builddir)/src/sys/libsys.la $(top_builddir)/src/model/libmodel.la noinst_LTLIBRARIES = libcore.la -noinst_HEADERS = application.h commandbuffer.h core.h cvar.h entity.h func.h \ +noinst_HEADERS = application.h commandbuffer.h clientstate.h core.h cvar.h entity.h func.h \ gameconnection.h gameinterface.h gameserver.h module.h net.h \ netclient.h netconnection.h netserver.cc player.h stats.h diff --git a/src/core/entity.cc b/src/core/entity.cc index d2fef43..cc956b0 100644 --- a/src/core/entity.cc +++ b/src/core/entity.cc @@ -7,13 +7,9 @@ #include #include -namespace core -{ - class Entity; -} - #include "sys/sys.h" #include "core/entity.h" +#include "core/cvar.h" namespace core { @@ -101,8 +97,7 @@ Entity::Entity(unsigned int flags) : entity_modelname.clear(); entity_name.clear(); - entity_renderstate = 0; - entity_renderfuzz = math::randomf(); + entity_clientstate = 0; add(this); } @@ -140,14 +135,15 @@ Entity::Entity(std::istream & is) entity_destroyed = false; entity_dirty = false; - entity_renderstate = 0; - entity_renderfuzz = math::randomf(); + entity_clientstate = 0; add(this, entity_id); } Entity::~Entity() { + if (entity_clientstate) + delete entity_clientstate; } void Entity::serialize(std::ostream & os) const diff --git a/src/core/entity.h b/src/core/entity.h index 1e80435..287157b 100644 --- a/src/core/entity.h +++ b/src/core/entity.h @@ -7,6 +7,10 @@ #ifndef __INCLUDED_CORE_ENTITY_H__ #define __INCLUDED_CORE_ENTITY_H__ +#include +#include +#include + #include "model/model.h" #include "math/axis.h" #include "math/mathlib.h" @@ -19,12 +23,9 @@ class EntityControlable; } +#include "core/clientstate.h" #include "core/player.h" -#include -#include -#include - namespace core { @@ -33,11 +34,11 @@ class Entity { public: /// Entity flags + /** + * entities with the Static flag set will not get client-side interpolation + */ enum Flags {Static=1, Solid=2, Bright=4}; - /// Entity render state flags - enum State {InRange=1, InCloseRange=2}; - /// Entity type constants enum Type {Default=0, Dynamic=1, Controlable=2, Globe=3}; @@ -64,12 +65,6 @@ public: /// core type id virtual inline unsigned int type() const { return Default; } - /// client state - inline unsigned int state() const { return entity_renderstate; } - - /// client render fuzz factor - inline float fuzz() const { return entity_renderfuzz; }; - /// entity flags inline unsigned int flags() const { return entity_flags; } @@ -79,6 +74,9 @@ public: /// entity model name inline std::string const & modelname() { return entity_modelname; } + /// entity client render state + inline ClientState * state() { return entity_clientstate; } + /// pointer to the model, is used client-side inline model::Model * model() { return entity_model; } @@ -158,8 +156,10 @@ public: bool entity_created; bool entity_destroyed; - unsigned int entity_renderstate; - float entity_renderfuzz; + /// timestamp when entity data was received from the server + float entity_servertimestamp; + + ClientState *entity_clientstate; private: /// add an entity to the registry diff --git a/src/core/gameconnection.cc b/src/core/gameconnection.cc index 4f410d5..baf0967 100644 --- a/src/core/gameconnection.cc +++ b/src/core/gameconnection.cc @@ -88,6 +88,10 @@ void GameConnection::frame(float seconds) return; } + if (!Cvar::sv_dedicated->value()) { + update_clientstate(); + } + connection_frametime += seconds; float f = 0; diff --git a/src/core/gameinterface.cc b/src/core/gameinterface.cc index 5789592..8d351a0 100644 --- a/src/core/gameinterface.cc +++ b/src/core/gameinterface.cc @@ -91,7 +91,53 @@ void GameInterface::clear() // remove all models model::Model::clear(); + + game_previousframetime = 0; + game_serverframetime = 0; + game_clientframetime = 0; + game_timestep = 0; + game_frames = 0; } +void GameInterface::reset_clientstate(float servertime) +{ + game_timestep = (servertime - game_serverframetime); + + if (game_timestep < 0) + game_timestep = 0; + + game_previousframetime = game_serverframetime; + game_serverframetime = servertime; + game_clientframetime = game_previousframetime; + + std::map::iterator it; + for (it=core::Entity::registry.begin(); it != core::Entity::registry.end(); it++) { + + core::Entity *entity = (*it).second; + + if (entity->state() && !(entity->flags() & Entity::Static)) + entity->state()->assign(entity); + } + + game_frames = 0; +} -} // namespace core +void GameInterface::update_clientstate() +{ + game_frames++; + game_clientframetime += game_timestep; + + if (game_clientframetime > game_serverframetime) + game_clientframetime = game_serverframetime; +} + +float GameInterface::timeoffset() { + float d = game_serverframetime - game_previousframetime; + if (d < 0) + d = 1; + float t = game_serverframetime - game_clientframetime; + + return t/d; +} + +} diff --git a/src/core/gameinterface.h b/src/core/gameinterface.h index e38c061..2324bb5 100644 --- a/src/core/gameinterface.h +++ b/src/core/gameinterface.h @@ -27,6 +27,20 @@ public: /// return the local player inline Player *localplayer() { return &game_localplayer; } + /// return the server time of the last received server frame + inline float serverframetime() const { return game_serverframetime; } + + /// return the server time of the previous received server frame + inline float previousframetime() const { return game_serverframetime; } + + /// return the server time of the previous received server frame + inline float clientframetime() const { return game_clientframetime; } + + /// client frame time between previousframetime and serverframetime, from 0 - 1 + float timeoffset(); + + inline float timestep() const { return game_timestep; } + /*----- virtual inspectors --------------------------------------- */ /// returns true if the game server can run a time frime @@ -37,6 +51,12 @@ public: /// clear all game variables, game functions and entities void clear(); + /// reset the client state + void reset_clientstate(float servertime); + + /// update the client state timers + void update_clientstate(); + /*----- virtual mutators ------------------------------------------ */ /// run one game time frame @@ -46,6 +66,14 @@ public: protected: /// the local player static Player game_localplayer; + + float game_serverframetime; + float game_previousframetime; + + float game_timestep; + float game_clientframetime; + + unsigned int game_frames; }; /// global local player instance diff --git a/src/core/gameserver.cc b/src/core/gameserver.cc index ae2b3a9..3c126fc 100644 --- a/src/core/gameserver.cc +++ b/src/core/gameserver.cc @@ -32,6 +32,7 @@ GameServer::GameServer() : GameInterface() con_print << "Initializing game server...\n"; server_instance = this; server_network = 0; + server_time = 0; server_frametime = 0.0f; server_maxplayerid = 1; @@ -243,6 +244,8 @@ void GameServer::frame(float seconds) if (error()) return; + server_time += seconds; + // process incoming network messages if (server_network) { server_network->receive(); @@ -256,6 +259,10 @@ void GameServer::frame(float seconds) if (localplayer()->dirty()) localplayer()->update_info(); + if (!Cvar::sv_dedicated->value()) { + update_clientstate(); + } + server_frametime += seconds; if ((Cvar::sv_dedicated->value() || Cvar::sv_private->value())) { @@ -266,7 +273,12 @@ void GameServer::frame(float seconds) } } } - + + // copy the previous entity state to the client state + if (!Cvar::sv_dedicated->value()) { + reset_clientstate(server_time); + } + // run a time frame on each entity std::map::iterator it; for (it=Entity::registry.begin(); it != Entity::registry.end(); it++) { @@ -292,6 +304,11 @@ void GameServer::frame(float seconds) server_network->transmit(); // TODO - start server frame + std::ostringstream framehdr; + framehdr.str(""); + framehdr << "frame " << server_time << "\n"; + server_network->broadcast(framehdr.str()); + std::map::iterator it; for (it=Entity::registry.begin(); it != Entity::registry.end(); it++) { Entity *entity = (*it).second; diff --git a/src/core/gameserver.h b/src/core/gameserver.h index 907d69e..4fb9c47 100644 --- a/src/core/gameserver.h +++ b/src/core/gameserver.h @@ -75,6 +75,7 @@ private: unsigned int server_maxplayerid; float server_frametime; + float server_time; }; inline GameServer *server() { return GameServer::instance(); } diff --git a/src/core/netconnection.cc b/src/core/netconnection.cc index dda339c..55480d2 100644 --- a/src/core/netconnection.cc +++ b/src/core/netconnection.cc @@ -267,6 +267,7 @@ void NetConnection::transmit() * msg public * die * ent + * frame * sup * pif */ @@ -305,6 +306,12 @@ void NetConnection::parse_incoming_message(const std::string & message) } else if (command == "ping") { + } else if (command == "frame") { + float timestamp; + if (msgstream >> timestamp) { + game()->reset_clientstate(timestamp); + } + } else if (command == "die") { unsigned int id; if (msgstream >> id) { @@ -339,14 +346,17 @@ void NetConnection::parse_incoming_message(const std::string & message) } } } else if (command == "sup") { - unsigned int id; - if (msgstream >> id) { - //con_debug << "Received update entity id " << id << std::endl; - Entity *entity = Entity::find(id); - if (!entity) { - con_warn << "Update for unknown entity " << id << std::endl; - } else - entity->recieve_server_update(msgstream); + if (connection_state == Connected) + { + unsigned int id; + if (msgstream >> id) { + //con_debug << "Received update entity id " << id << std::endl; + Entity *entity = Entity::find(id); + if (!entity) { + con_warn << "Update for unknown entity " << id << std::endl; + } else + entity->recieve_server_update(msgstream); + } } } else if (command == "pif") { diff --git a/src/core/stats.h b/src/core/stats.h index 44153ec..9d90fdc 100644 --- a/src/core/stats.h +++ b/src/core/stats.h @@ -7,7 +7,6 @@ #ifndef __INCLUDED_CORE_STATS_H__ #define __INCLUDED_CORE_STATS_H__ -/// this namespace contains the network subsystem namespace core { diff --git a/src/game/game.cc b/src/game/game.cc index 31b99c2..3bdcb8e 100644 --- a/src/game/game.cc +++ b/src/game/game.cc @@ -190,6 +190,7 @@ void Game::init() } else if (worldini.got_section("entity")) { entity = new core::Entity(); + entity->entity_flags += core::Entity::Static; } else if (worldini.got_section()) { con_warn << worldini.name() << " unknown section '" << worldini.section() << "' at line " << worldini.line() << std::endl; diff --git a/src/math/functions.h b/src/math/functions.h index b44a4e5..16de778 100644 --- a/src/math/functions.h +++ b/src/math/functions.h @@ -55,6 +55,12 @@ inline void clamp(float &value, float min=0.0f, float max=1.0f) if (value < min) value = min; else if (value > max) value = max; } +/// return the absolute value of a float +inline float absf(float f) +{ + if (f >0) return f; else return -f; +} + } // namespace math #endif // __INCLUDED_MATH_FUNCTIONS_H__ diff --git a/src/render/draw.cc b/src/render/draw.cc index 1017746..b0671de 100644 --- a/src/render/draw.cc +++ b/src/render/draw.cc @@ -44,19 +44,6 @@ math::Axis camera_axis; float angle = 0; - -/* ----- Distance test functions ----------------------------------- */ - -inline bool test_draw_distance(core::Entity *entity) -{ - return (entity->entity_renderstate > 0); -} - -inline bool test_drawfx_distance(core::Entity *entity) -{ - return ((entity->entity_renderstate & core::Entity::InCloseRange) == core::Entity::InCloseRange); -} - // function to test flags inline bool flag_is_set(unsigned int spawnflags, unsigned int flag) { return ((spawnflags & flag) == flag); @@ -162,7 +149,7 @@ void draw_entity_axis(core::Entity *entity) void draw_model_vertex(core::Entity *entity) { size_t count = entity->model()->vertex_structural(); - if (test_drawfx_distance(entity)) + if (entity->state()->detailvisible()) count += entity->model()->vertex_detail(); // draw model vertices @@ -176,7 +163,7 @@ void draw_model_vertex(core::Entity *entity) void draw_model_evertex(core::Entity *entity) { size_t count = entity->model()->evertex_structural(); - if (test_drawfx_distance(entity)) + if (entity->state()->detailvisible()) count += entity->model()->evertex_detail(); // draw model evertices @@ -245,15 +232,19 @@ void draw_model_shield(core::EntityControlable *entity) /* ----- Render passes --------------------------------------------- */ /* calculate entity visibility */ -void pass_visibility() +void pass_prepare() { std::map::iterator it; for (it=core::Entity::registry.begin(); it != core::Entity::registry.end(); it++) { core::Entity *entity = (*it).second; - entity->entity_renderstate = 0; + if (!entity->state()) { + entity->entity_clientstate = new core::ClientState(); + } + entity->state()->state_visible = false; + entity->state()->state_detailvisible = false; - // load entity models if necessary + // load entity models and light flare textures if (!entity->model() && entity->modelname().size()) { entity->entity_model = model::Model::load(entity->modelname()); @@ -271,25 +262,40 @@ void pass_visibility() } } - if (entity->model()) { + // update client state + if (entity->state() && flag_is_set(entity->flags(), core::Entity::Static)) { + entity->state()->state_location = entity->state()->previouslocation() + + (entity->location() - entity->state()->previouslocation()) * core::game()->timeoffset(); + } + + // calculate visibility for entities with models + if (entity->model()) { float dq = math::distancesquared(camera_eye, entity->location()); if (dq <= drawfxdistance*drawfxdistance*entity->model()->radius()) { - // entites withint drawfxdistance - entity->entity_renderstate = core::Entity::InCloseRange; + // entites within drawing distance + entity->state()->state_visible = true; + entity->state()->state_detailvisible = true; } else if (dq <= drawdistance*drawdistance*entity->model()->radius()) { // entities within drawdistance - entity->entity_renderstate = core::Entity::InRange; + entity->state()->state_visible = true; + entity->state()->state_detailvisible = false; } - } else if ((entity->type() == core::Entity::Globe) && flag_is_set(entity->flags(), core::Entity::Bright)) { - // bright globes set level light - GLfloat light_position[4]; - for (size_t i=0; i <3; i++) - light_position[i] = entity->location()[i]; - light_position[3] = 1.0f; + } else { - glLightfv(GL_LIGHT0, GL_POSITION, light_position); + entity->state()->state_visible = true; + + if ((entity->type() == core::Entity::Globe) && flag_is_set(entity->flags(), core::Entity::Bright)) { + + // bright globes set level light + GLfloat light_position[4]; + for (size_t i=0; i <3; i++) + light_position[i] = entity->location()[i]; + light_position[3] = 1.0f; + + glLightfv(GL_LIGHT0, GL_POSITION, light_position); + } } } } @@ -345,7 +351,7 @@ void draw_pass_model_vertex() for (it=core::Entity::registry.begin(); it != core::Entity::registry.end(); it++) { core::Entity *entity = (*it).second; - if (test_draw_distance(entity)) { + if (entity->model() && entity->state()->visible()) { gl::push(); gl::translate(entity->location()); gl::multmatrix(entity->axis()); @@ -365,7 +371,7 @@ void draw_pass_model_evertex() core::Entity *entity = (*it).second; - if (test_draw_distance(entity)) { + if (entity->model() && entity->state()->visible()) { gl::push(); gl::translate(entity->location()); gl::multmatrix(entity->axis()); @@ -384,7 +390,7 @@ void draw_pass_model_shields() { core::Entity *entity = (*it).second; - if (test_drawfx_distance(entity)) { + if (entity->model() && entity->state()->detailvisible()) { if (entity->type() == core::Entity::Controlable) { @@ -415,7 +421,7 @@ void draw_pass_model_fx() for (std::map::iterator it=core::Entity::registry.begin(); it != core::Entity::registry.end(); it++) { core::Entity *entity = (*it).second; - if (test_drawfx_distance(entity)) { + if (entity->model() && entity->state()->detailvisible()) { // draw model lights for (std::list::iterator lit = entity->model()->model_light.begin(); lit != entity->model()->model_light.end(); lit++) { @@ -423,7 +429,7 @@ void draw_pass_model_fx() // strobe frequency t = 1.0f; if ((*lit)->strobe()) - t = (core::application()->time() + entity->fuzz() + (*lit)->offset()) * (*lit)->frequency(); + t = (core::application()->time() + entity->state()->fuzz() + (*lit)->offset()) * (*lit)->frequency(); if (!(*lit)->strobe() || (( t - floorf(t)) <= (*lit)->time())) { math::Vector3f location = entity->location() + (entity->axis() * (*lit)->location()); @@ -469,7 +475,7 @@ void draw_pass_model_fx() float u = static_cast(entity)->thrust(); - t = entity->fuzz() + core::application()->time() * 4; + t = entity->state()->fuzz() + core::application()->time() * 4; t = t - floorf(t); if (t > 0.5) @@ -510,7 +516,7 @@ void draw_pass_model_corona() for (std::map::iterator it=core::Entity::registry.begin(); it != core::Entity::registry.end(); it++) { core::Entity *entity = (*it).second; - if (test_draw_distance(entity)) { + if (entity->state()->visible() && (entity->shape() != core::Entity::Sphere)) { gl::push(); gl::translate(entity->location()); math::Color color = entity->color(); @@ -574,7 +580,7 @@ void draw(math::Axis const &axis, math::Vector3f const &eye, math::Vector3f cons camera_eye.assign(eye); camera_axis.assign(axis); - pass_visibility(); + pass_prepare(); gl::enable(GL_DEPTH_TEST); // enable depth buffer writing gl::enable(GL_CULL_FACE); // enable culling @@ -618,7 +624,7 @@ void draw(math::Axis const &axis, math::Vector3f const &eye, math::Vector3f cons gl::enable(GL_LIGHTING); gl::enable(GL_RESCALE_NORMAL); - draw_pass_model_corona(); // draw entity radius and star corona + draw_pass_model_corona(); // draw entity radius glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); -- cgit v1.2.3