diff --git a/doomsday/client/include/render/r_main.h b/doomsday/client/include/render/r_main.h index 3530721532..88352e98df 100644 --- a/doomsday/client/include/render/r_main.h +++ b/doomsday/client/include/render/r_main.h @@ -105,16 +105,6 @@ void R_Shutdown(void); void R_Ticker(timespan_t time); -/** - * Prepare for rendering view(s) of the world. - */ -void R_BeginWorldFrame(void); - -/** - * Wrap up after drawing view(s) of the world. - */ -void R_EndWorldFrame(void); - /** * Render all view ports in the viewport grid. */ @@ -144,6 +134,8 @@ void R_UpdateViewer(int consoleNum); void R_ResetViewer(void); +int R_NextViewer(void); + /** * Update the sharp world data by rotating the stored values of plane * heights and sharp camera positions. diff --git a/doomsday/client/include/world/map.h b/doomsday/client/include/world/map.h index 4fb7ebc25d..29b57fa5dd 100644 --- a/doomsday/client/include/world/map.h +++ b/doomsday/client/include/world/map.h @@ -30,6 +30,10 @@ #include "p_particle.h" #include "Polyobj" +#ifdef __CLIENT__ +# include "world/world.h" +#endif + class BspLeaf; class BspNode; class Line; @@ -85,6 +89,9 @@ class Thinkers; * @ingroup world */ class Map +#ifdef __CLIENT__ +: DENG2_OBSERVES(World, FrameBegin) +#endif { DENG2_NO_COPY (Map) DENG2_NO_ASSIGN(Map) @@ -108,13 +115,18 @@ class Map #endif /* - * Observers to be notified when a one-way window construct is first found. + * Notified when the map is about to be deleted. + */ + DENG2_DEFINE_AUDIENCE(Deletion, void mapBeingDeleted(Map const &map)) + + /* + * Notified when a one-way window construct is first found. */ DENG2_DEFINE_AUDIENCE(OneWayWindowFound, void oneWayWindowFound(Line &line, Sector &backFacingSector)) /* - * Observers to be notified when an unclosed sector is first found. + * Notified when an unclosed sector is first found. */ DENG2_DEFINE_AUDIENCE(UnclosedSectorFound, void unclosedSectorFound(Sector §or, Vector2d const &nearPoint)) @@ -677,12 +689,11 @@ class Map */ void initLightGrid(); -#endif // __CLIENT__ +protected: + /// Observes World FrameBegin + void worldFrameBegins(World &world, bool resetNextViewer); - /** - * To be called following an engine reset to update the map state. - */ - void update(); +#endif // __CLIENT__ public: /// @todo Make private: diff --git a/doomsday/client/include/world/world.h b/doomsday/client/include/world/world.h index ee0d11d778..9f102e56aa 100644 --- a/doomsday/client/include/world/world.h +++ b/doomsday/client/include/world/world.h @@ -37,10 +37,14 @@ #include "uri.hh" -#include "world/map.h" +#ifdef __CLIENT__ +class Hand; +#endif namespace de { +class Map; + /** * @ingroup world */ @@ -54,9 +58,21 @@ class World DENG2_ERROR(MapError); /** - * Audience that will be notified when the "current" map changes. + * Notified when the "current" map changes. + */ + DENG2_DEFINE_AUDIENCE(MapChange, void worldMapChanged(World &world)) + +#ifdef __CLIENT__ + /** + * Notified when the "current" frame begins. */ - DENG2_DEFINE_AUDIENCE(MapChange, void currentMapChanged()) + DENG2_DEFINE_AUDIENCE(FrameBegin, void worldFrameBegins(World &world, bool resetNextViewer)) + + /** + * Notified when the "current" frame ends. + */ + DENG2_DEFINE_AUDIENCE(FrameEnd, void worldFrameEnds(World &world)) +#endif public: /** @@ -69,6 +85,19 @@ class World */ static void consoleRegister(); + /** + * To be called to reset the world back to the initial state. Any currently + * loaded map will be unloaded and player states are re-initialized. + * + * @todo World should observe GameChange. + */ + void reset(); + + /** + * To be called following an engine reset to update the world state. + */ + void update(); + /** * Returns @c true iff a map is currently loaded. */ @@ -96,18 +125,29 @@ class World */ inline void unloadMap() { changeMap(Uri()); } +#ifdef __CLIENT__ /** - * To be called to reset the world back to the initial state. Any currently - * loaded map will be unloaded and player states are re-initialized. - * - * @todo World should observe GameChange. + * To be called at the beginning of a render frame, so that we can prepare for + * drawing view(s) of the current map. */ - void reset(); + void beginFrame(bool resetNextViewer = false); /** - * To be called following an engine reset to update the world state. + * To be called at the end of a render frame, so that we can finish up any tasks + * that must be completed after view(s) have been drawn. */ - void update(); + void endFrame(); + + /** + * Returns the hand of the "user" in the world. Used for manipulating elements + * for the purposes of runtime map editing. + * + * @param distance The current distance of the hand from the viewer will be + * written here if not @c 0. + */ + Hand &hand(coord_t *distance = 0) const; + +#endif // __CLIENT__ private: DENG2_PRIVATE(d) diff --git a/doomsday/client/src/render/r_main.cpp b/doomsday/client/src/render/r_main.cpp index 4e1f0ad58e..05e96671d4 100644 --- a/doomsday/client/src/render/r_main.cpp +++ b/doomsday/client/src/render/r_main.cpp @@ -49,10 +49,6 @@ float devCameraMovementStartTime = 0; // sysTime float devCameraMovementStartTimeRealSecs = 0; #endif -BEGIN_PROF_TIMERS() - PROF_MOBJ_INIT_ADD -END_PROF_TIMERS() - D_CMD(ViewGrid); int validCount = 1; // Increment every time a check is made. @@ -75,7 +71,7 @@ byte texGammaLut[256]; fontid_t fontFixed, fontVariable[FONTSTYLE_COUNT]; -static boolean resetNextViewer = true; +static int resetNextViewer = true; static viewdata_t viewDataOfConsole[DDMAXPLAYERS]; // Indexed by console number. @@ -565,6 +561,11 @@ void R_ResetViewer() resetNextViewer = 1; } +int R_NextViewer() +{ + return resetNextViewer; +} + void R_InterpolateViewer(viewer_t *start, viewer_t *end, float pos, viewer_t *out) { float inv = 1 - pos; @@ -733,64 +734,6 @@ END_PROF( PROF_MOBJ_INIT_ADD ); #endif } -void R_BeginWorldFrame() -{ -#ifdef __CLIENT__ - - // Clear all flags that can be cleared before each frame. - foreach(Sector *sector, App_World().map().sectors()) - { - sector->_frameFlags &= ~SIF_FRAME_CLEAR; - } - - App_World().map().lerpTrackedPlanes(resetNextViewer); - App_World().map().lerpScrollingSurfaces(resetNextViewer); - - if(!freezeRLs) - { - // Initialize and/or update the LightGrid. - App_World().map().initLightGrid(); - - SB_BeginFrame(); - LO_BeginWorldFrame(); - R_ClearObjlinksForFrame(); // Zeroes the links. - - // Clear the objlinks. - R_InitForNewFrame(); - - // Generate surface decorations for the frame. - Rend_DecorBeginFrame(); - - // Spawn omnilights for decorations. - Rend_DecorAddLuminous(); - - // Spawn omnilights for mobjs. - LO_AddLuminousMobjs(); - - // Create objlinks for mobjs. - R_CreateMobjLinks(); - - // Link all active particle generators into the world. - P_CreatePtcGenLinks(); - - // Link objs to all contacted surfaces. - R_LinkObjs(); - } - -#endif -} - -void R_EndWorldFrame() -{ -#ifdef __CLIENT__ - if(!freezeRLs) - { - // Wrap up with Source, Bias lights. - SB_EndFrame(); - } -#endif -} - void R_UpdateViewer(int consoleNum) { DENG_ASSERT(consoleNum >= 0 && consoleNum < DDMAXPLAYERS); diff --git a/doomsday/client/src/ui/widgets/legacywidget.cpp b/doomsday/client/src/ui/widgets/legacywidget.cpp index d00c804266..3cddffb5c9 100644 --- a/doomsday/client/src/ui/widgets/legacywidget.cpp +++ b/doomsday/client/src/ui/widgets/legacywidget.cpp @@ -29,7 +29,9 @@ #include "dd_main.h" #include "dd_loop.h" #include "sys_system.h" -#include "edit_bias.h" +#ifdef __CLIENT__ +# include "edit_bias.h" +#endif #include "world/map.h" #include "network/net_main.h" #include "render/r_main.h" @@ -75,11 +77,9 @@ DENG2_PIMPL(LegacyWidget) { if(App_GameLoaded()) { - // Interpolate the world ready for drawing view(s) of it. - if(App_World().hasMap()) - { - R_BeginWorldFrame(); - } + // Notify the world that a new render frame has begun. + App_World().beginFrame(CPP_BOOL(R_NextViewer())); + R_RenderViewPorts(); } else if(titleFinale == 0) @@ -114,8 +114,8 @@ DENG2_PIMPL(LegacyWidget) if(drawGame) { - // Shadow Bias Editor HUD (if it is active). - SBE_DrawHUD(); + // Draw the widgets of the Shadow Bias Editor (if active). + SBE_DrawGui(); /* * Draw debug information. @@ -127,8 +127,8 @@ DENG2_PIMPL(LegacyWidget) Net_Drawer(); S_Drawer(); - // Finish up any tasks that must be completed after view(s) have been drawn. - R_EndWorldFrame(); + // Notify the world that we've finished rendering the frame. + App_World().endFrame(); } if(UI_IsActive()) diff --git a/doomsday/client/src/world/map.cpp b/doomsday/client/src/world/map.cpp index 02435601d2..a4accc3e50 100644 --- a/doomsday/client/src/world/map.cpp +++ b/doomsday/client/src/world/map.cpp @@ -55,11 +55,16 @@ #include "world/maputil.h" #include "world/p_intercept.h" #include "world/p_object.h" +#include "world/p_objlink.h" #include "world/thinkers.h" #include "world/world.h" // ddMapSetup #include "render/r_main.h" // validCount #ifdef __CLIENT__ +# include "render/lumobj.h" +# include "render/rend_bias.h" +# include "render/rend_decor.h" +# include "render/rend_main.h" # include "render/sky.h" #endif @@ -2695,6 +2700,56 @@ void Map::update() #endif } +#ifdef __CLIENT__ +void Map::worldFrameBegins(World &world, bool resetNextViewer) +{ + DENG2_ASSERT(&world.map() == this); // Sanity check. + + // Clear all flags that can be cleared before each frame. + foreach(Sector *sector, d->sectors) + { + sector->_frameFlags &= ~SIF_FRAME_CLEAR; + } + + // Interpolate the map ready for drawing view(s) of it. + d->lerpTrackedPlanes(resetNextViewer); + d->lerpScrollingSurfaces(resetNextViewer); + + if(!freezeRLs) + { + // Initialize and/or update the LightGrid. + initLightGrid(); + + d->biasBeginFrame(); + + LO_BeginWorldFrame(); + R_ClearObjlinksForFrame(); // Zeroes the links. + + // Clear the objlinks. + R_InitForNewFrame(); + + // Generate surface decorations for the frame. + Rend_DecorBeginFrame(); + + // Spawn omnilights for decorations. + Rend_DecorAddLuminous(); + + // Spawn omnilights for mobjs. + LO_AddLuminousMobjs(); + + // Create objlinks for mobjs. + d->createMobjLinks(); + + // Link all active particle generators into the world. + P_CreatePtcGenLinks(); + + // Link objs to all contacted surfaces. + R_LinkObjs(); + } +} + +#endif // __CLIENT__ + /// Runtime map editing ----------------------------------------------------- /// Used when sorting vertex line owners. diff --git a/doomsday/client/src/world/world.cpp b/doomsday/client/src/world/world.cpp index 53e1595e70..1bfbeb80d7 100644 --- a/doomsday/client/src/world/world.cpp +++ b/doomsday/client/src/world/world.cpp @@ -50,6 +50,9 @@ #ifdef __CLIENT__ # include "client/cl_frame.h" # include "client/cl_player.h" +# include "edit_bias.h" +# include "Hand" +# include "HueCircle" # include "render/lumobj.h" # include "render/r_shadow.h" @@ -72,6 +75,10 @@ using namespace de; +#ifdef __CLIENT__ +static float handDistance = 300; //cvar +#endif + /** * Observes the progress of a map conversion and records any issues/problems that * are encountered in the process. When asked, compiles a human-readable report @@ -211,11 +218,16 @@ DENG2_PIMPL(World) typedef QMap Records; + /// Map cache records. + Records records; + /// Current map. Map *map; - /// Map cache records. - Records records; +#ifdef __CLIENT__ + /// Hand for runtime map manipulation/editing. + QScopedPointer hand; +#endif Instance(Public *i) : Base(i), map(0) {} @@ -224,7 +236,7 @@ DENG2_PIMPL(World) { DENG2_FOR_PUBLIC_AUDIENCE(MapChange, i) { - i->currentMapChanged(); + i->worldMapChanged(self); } } @@ -434,6 +446,15 @@ DENG2_PIMPL(World) DAM_MapWrite(map, rec.cachePath); }*/ +#ifdef __CLIENT__ + // Connect the map to world audiences: + /// @todo The map should instead be notified when it is made current + /// so that it may perform the connection itself. Such notification + /// would also afford the map the opportunity to prepare various data + /// which is only needed when made current (e.g., caches for render). + self.audienceForFrameBegin += map; +#endif + // Print summary information about this map. #define TABBED(count, label) String(_E(Ta) " %1 " _E(Tb) "%2\n").arg(count).arg(label) @@ -644,6 +665,18 @@ DENG2_PIMPL(World) // Inform interested parties that the "current" map has changed. notifyMapChange(); } + +#ifdef __CLIENT__ + void updateHandOrigin() + { + DENG2_ASSERT(hand != 0 && map != 0); + + viewdata_t const *viewData = R_ViewData(viewPlayer - ddPlayers); + hand->setOrigin(Vector3d(viewData->current.origin[VX] + viewData->frontVec[VX] * handDistance, + viewData->current.origin[VY] + viewData->frontVec[VZ] * handDistance, + viewData->current.origin[VZ] + viewData->frontVec[VY] * handDistance)); + } +#endif }; World::World() : d(new Instance(this)) @@ -651,7 +684,10 @@ World::World() : d(new Instance(this)) void World::consoleRegister() // static { - //C_VAR_BYTE("map-cache", &mapCache, 0, 0, 1); + //C_VAR_BYTE ("map-cache", &mapCache, 0, 0, 1); +#ifdef __CLIENT__ + C_VAR_FLOAT("edit-bias-grab-distance", &handDistance, 0, 10, 1000); +#endif Map::consoleRegister(); } @@ -672,6 +708,15 @@ Map &World::map() const bool World::changeMap(de::Uri const &uri) { +#ifdef __CLIENT__ + if(d->map) + { + // Remove the current map from our audiences. + /// @todo Map should handle this. + audienceForFrameBegin -= d->map; + } +#endif + // As the memory zone does not provide the mechanisms to prepare another // map in parallel we must free the current map first. /// @todo The memory zone would still be useful if the purge and tagging @@ -783,4 +828,50 @@ void World::update() } } +#ifdef __CLIENT__ +Hand &World::hand(coord_t *distance) const +{ + // Time to create the hand? + if(d->hand.isNull()) + { + d->hand.reset(new Hand()); + const_cast(this)->audienceForFrameEnd += *d->hand; + if(d->map) + { + d->updateHandOrigin(); + } + } + if(distance) + { + *distance = handDistance; + } + return *d->hand; +} + +void World::beginFrame(bool resetNextViewer) +{ + // Notify interested parties that a new frame has begun. + DENG2_FOR_AUDIENCE(FrameBegin, i) i->worldFrameBegins(*this, resetNextViewer); +} + +void World::endFrame() +{ + if(d->map && !d->hand.isNull()) + { + d->updateHandOrigin(); + + // If the HueCircle is active update the current edit color. + if(HueCircle *hueCircle = SBE_HueCircle()) + { + viewdata_t const *viewData = R_ViewData(viewPlayer - ddPlayers); + d->hand->setEditColor(hueCircle->colorAt(viewData->frontVec)); + } + } + + // Notify interested parties that the current frame has ended. + DENG2_FOR_AUDIENCE(FrameEnd, i) i->worldFrameEnds(*this); +} + +#endif // __CLIENT__ + } // namespace de diff --git a/doomsday/server/include/shellusers.h b/doomsday/server/include/shellusers.h index 5c6e101d69..aead403463 100644 --- a/doomsday/server/include/shellusers.h +++ b/doomsday/server/include/shellusers.h @@ -29,7 +29,7 @@ * All remote shell users. */ class ShellUsers : public QObject, - DENG2_OBSERVES(de::World, MapChange) +DENG2_OBSERVES(de::World, MapChange) { Q_OBJECT @@ -49,7 +49,8 @@ class ShellUsers : public QObject, int count() const; - void currentMapChanged(); + /// Observes World MapChange. + void worldMapChanged(de::World &world); public slots: void sendPlayerInfoToAll(); diff --git a/doomsday/server/src/shellusers.cpp b/doomsday/server/src/shellusers.cpp index ab4381f5f8..6bf5ed0461 100644 --- a/doomsday/server/src/shellusers.cpp +++ b/doomsday/server/src/shellusers.cpp @@ -21,6 +21,8 @@ #include "dd_main.h" #include +using namespace de; + static int const PLAYER_INFO_INTERVAL = 2500; // ms DENG2_PIMPL_NOREF(ShellUsers) @@ -76,8 +78,10 @@ int ShellUsers::count() const return d->users.size(); } -void ShellUsers::currentMapChanged() +void ShellUsers::worldMapChanged(World &world) { + DENG2_UNUSED(world); + foreach(ShellUser *user, d->users) { user->sendGameState();