From 4dcec2f2aaf54fe899d8bf07980ffeeff9a38917 Mon Sep 17 00:00:00 2001 From: danij Date: Fri, 29 Aug 2014 16:52:19 +0100 Subject: [PATCH] Refactor|Renderer|Sky: Introduced an animator abstraction for the sky Began splitting up the renderer's Sky class into subcomponents. The first of which will be an animator for separation of these duties from the GL drawable/rendering logic. --- doomsday/client/include/render/sky.h | 87 ++++- doomsday/client/src/render/sky.cpp | 383 +++++++++++++--------- doomsday/client/src/world/map.cpp | 19 +- doomsday/client/src/world/p_ticker.cpp | 20 +- doomsday/client/src/world/worldsystem.cpp | 46 ++- 5 files changed, 350 insertions(+), 205 deletions(-) diff --git a/doomsday/client/include/render/sky.h b/doomsday/client/include/render/sky.h index d141bc5c1f..b564a08deb 100644 --- a/doomsday/client/include/render/sky.h +++ b/doomsday/client/include/render/sky.h @@ -22,13 +22,14 @@ #define DENG_CLIENT_RENDER_SKY_H #include "Material" +#include #include #include #include #include #include #include -#include +#include "ModelDef" #define MAX_SKY_LAYERS ( 2 ) #define MAX_SKY_MODELS ( 32 ) @@ -46,9 +47,15 @@ class Sky { public: + /// No animator is presently configured. @ingroup errors + DENG2_ERROR(MissingAnimatorError); + /// Required layer is missing. @ingroup errors DENG2_ERROR(MissingLayerError); + /// Required model is missing. @ingroup errors + DENG2_ERROR(MissingModelError); + /** * Multiple layers can be used for parallax effects. */ @@ -156,6 +163,48 @@ class Sky float _fadeoutLimit; }; + struct ModelInfo + { + de::Record const *def; // Sky model def + ModelDef *model; + int frame; + int timer; + int maxTimer; + float yaw; + }; + + /** + * Sky sphere and model animator. + * + * Animates a sky according to the configured definition. + */ + class Animator + { + public: + Animator(); + Animator(Sky &sky); + virtual ~Animator(); + + void setSky(Sky &sky); + Sky &sky() const; + + /** + * Reconfigure according to the specified @a definition if not @c NULL, + * otherwise, reconfigure using the default values. + */ + void configure(defn::Sky *definition); + + /** + * Advances the animation state. + * + * @param elapsed Duration of elapsed time. + */ + void advanceTime(timespan_t elapsed); + + public: + DENG2_PRIVATE(d) + }; + public: Sky(); @@ -164,16 +213,14 @@ class Sky */ void configureDefault(); - /** - * Reconfigure the sky according the specified @a definition if not @c NULL, - * otherwise, setup using suitable defaults. - */ - void configure(defn::Sky *sky); + bool hasAnimator() const; + void setAnimator(Animator *newAnimator); + Animator &animator(); /** - * Animate the sky. + * Models are set up according to the given @a skyDef. */ - void runTick(); + void setupModels(defn::Sky const &skyDef); #ifdef __CLIENT__ @@ -220,6 +267,30 @@ class Sky */ int firstActiveLayer() const; + /** + * Determines whether the specified sky model @a index is valid. + * + * @see model(), modelPtr() + */ + bool hasModel(int index) const; + + /** + * Lookup a sky model by it's unique @a index. + * + * @see hasModel() + */ + ModelInfo &model(int index); + + /// @copydoc model() + ModelInfo const &model(int index) const; + + /** + * Returns a pointer to the referenced sky model; otherwise @c 0. + * + * @see hasModel(), model() + */ + inline ModelInfo *modelPtr(int index) { return hasModel(index)? &model(index) : 0; } + /** * Returns the horizon offset for the sky. * diff --git a/doomsday/client/src/render/sky.cpp b/doomsday/client/src/render/sky.cpp index 827ddf76d8..e3eb064501 100644 --- a/doomsday/client/src/render/sky.cpp +++ b/doomsday/client/src/render/sky.cpp @@ -21,6 +21,13 @@ #include "de_base.h" #include "render/sky.h" +#include +#include +#include +#include +#include +#include + #include "clientapp.h" #include "client/cl_def.h" @@ -35,15 +42,129 @@ #include "render/rend_model.h" #include "render/vissprite.h" -#include -#include -#include -#include -#include -#include - using namespace de; +DENG2_PIMPL_NOREF(Sky::Animator) +{ + Sky *sky; + Instance(Sky *sky = 0) : sky(sky) {} +}; + +Sky::Animator::Animator() : d(new Instance) +{} + +Sky::Animator::Animator(Sky &sky) : d(new Instance(&sky)) +{} + +Sky::Animator::~Animator() +{} + +void Sky::Animator::setSky(Sky &sky) +{ + d->sky = &sky; +} + +Sky &Sky::Animator::sky() const +{ + DENG2_ASSERT(d->sky != 0); + return *d->sky; +} + +void Sky::Animator::configure(defn::Sky *_def) +{ + LOG_AS("Sky::Animator"); + + // The default configuration is used as a starting point. + sky().configureDefault(); + + if(!_def) return; // Go with the defaults, then. + defn::Sky const &def = *_def; + + sky().setHeight(def.getf("height")); + sky().setHorizonOffset(def.getf("horizonOffset")); + + for(int i = 0; i < MAX_SKY_LAYERS; ++i) + { + Record const &lyrDef = def.layer(i); + Layer &lyr = sky().layer(i); + + if(!(lyrDef.geti("flags") & Layer::Active)) + { + lyr.disable(); + continue; + } + + lyr.setMasked((lyrDef.geti("flags") & Layer::Masked) != 0) + .setOffset(lyrDef.getf("offset")) + .setFadeoutLimit(lyrDef.getf("colorLimit")) + .enable(); + + de::Uri const matUri(lyrDef.gets("material")); + if(!matUri.isEmpty()) + { + try + { + lyr.setMaterial(ClientApp::resourceSystem().materialPtr(matUri)); + } + catch(ResourceSystem::MissingManifestError const &er) + { + // Log but otherwise ignore this error. + LOG_RES_WARNING(er.asText() + ". Unknown material \"%s\" in definition layer %i, using default.") + << matUri << i; + } + } + } + + Vector3f ambientColor = Vector3f(def.get("color")).max(Vector3f(0, 0, 0)); + if(ambientColor != Vector3f(0, 0, 0)) + { + sky().setAmbientColor(ambientColor); + } + + // Models are set up using the data in the definition (will override the sphere by default). + sky().setupModels(def); +} + +void Sky::Animator::advanceTime(timespan_t /*elapsed*/) +{ + LOG_AS("Sky::Animator"); + + if(!d->sky) return; + + if(clientPaused) return; + if(!DD_IsSharpTick()) return; + + // Animate layers. + /*for(int i = 0; i < MAX_SKY_LAYERS; ++i) + { + Sky::Layer &lyr = sky().layer(i); + }*/ + + // Animate models. + for(int i = 0; i < MAX_SKY_MODELS; ++i) + { + Sky::ModelInfo &minfo = sky().model(i); + if(!minfo.def) continue; + + // Rotate the model. + minfo.yaw += minfo.def->getf("yawSpeed") / TICSPERSEC; + + // Is it time to advance to the next frame? + if(minfo.maxTimer > 0 && ++minfo.timer >= minfo.maxTimer) + { + minfo.frame++; + minfo.timer = 0; + + // Execute a console command? + String const execute = minfo.def->gets("execute"); + if(!execute.isEmpty()) + { + Con_Execute(CMDS_SCRIPT, execute.toUtf8().constData(), true, false); + } + } + } +} + enum SphereComponentFlag { UpperHemisphere = 0x1, LowerHemisphere = 0x2 @@ -183,40 +304,24 @@ DENG2_PIMPL(Sky) , DENG2_OBSERVES(Layer, ActiveChange) , DENG2_OBSERVES(Layer, MaskedChange) { + Animator *animator = nullptr; + Layer layers[MAX_SKY_LAYERS]; - int firstActiveLayer; - bool needUpdateFirstActiveLayer; + int firstActiveLayer = -1; /// @c -1= 'no active layers'. + bool needUpdateFirstActiveLayer = true; - float horizonOffset; - float height; - bool ambientColorDefined; /// @c true= pre-defined in a MapInfo def. - bool needUpdateAmbientColor; /// @c true= update if not pre-defined. + float horizonOffset = 0; + float height = 0; + bool ambientColorDefined = false; /// @c true= pre-defined in a MapInfo def. + bool needUpdateAmbientColor = true; /// @c true= update if not pre-defined. Vector3f ambientColor; - bool alwaysDrawSphere; + bool alwaysDrawSphere = false; - struct ModelInfo - { - Record const *def; // Sky model def - ModelDef *model; - int frame; - int timer; - int maxTimer; - float yaw; - }; ModelInfo models[MAX_SKY_MODELS]; - bool haveModels; - - Instance(Public *i) - : Base(i) - , firstActiveLayer(-1) /// @c -1 denotes 'no active layers'. - , needUpdateFirstActiveLayer(true) - , horizonOffset(0) - , height(0) - , ambientColorDefined(false) - , needUpdateAmbientColor(true) - , alwaysDrawSphere(false) - , haveModels(false) + bool haveModels = false; + + Instance(Public *i) : Base(i) { for(int i = 0; i < MAX_SKY_LAYERS; ++i) { @@ -225,11 +330,10 @@ DENG2_PIMPL(Sky) layers[i].audienceForMaskedChange += this; } - zap(models); + de::zap(models); } - inline ResourceSystem &resSys() - { + inline ResourceSystem &resSys() { return App_ResourceSystem(); } @@ -316,46 +420,6 @@ DENG2_PIMPL(Sky) } } - /** - * Models are set up using the data in the definition. - */ - void setupModels(defn::Sky const &def) - { - zap(models); - - // Normally the sky sphere is not drawn if models are in use. - alwaysDrawSphere = (def.geti("flags") & SIF_DRAW_SPHERE) != 0; - - // The normal sphere is used if no models will be set up. - haveModels = false; - - for(int i = 0; i < def.modelCount(); ++i) - { - Record const &modef = def.model(i); - ModelInfo *minfo = &models[i]; - - // Is the model ID set? - try - { - minfo->model = &resSys().modelDef(modef.gets("id")); - if(!minfo->model->subCount()) - { - continue; - } - - // There is a model here. - haveModels = true; - - minfo->def = modef.accessedRecordPtr(); - minfo->maxTimer = int(TICSPERSEC * modef.getf("frameInterval")); - minfo->yaw = modef.getf("yaw"); - minfo->frame = minfo->model->subModelDef(0).frame; - } - catch(ResourceSystem::MissingModelDefError const &) - {} // Ignore this error. - } - } - void drawModels() { DENG_ASSERT_IN_MAIN_THREAD(); @@ -461,7 +525,7 @@ Sky::Layer &Sky::layer(int index) return d->layers[index - 1]; // 1-based index. } /// @throw MissingLayerError An invalid layer index was specified. - throw MissingLayerError("Sky::Layer", "Invalid layer index #" + String::number(index) + "."); + throw MissingLayerError("Sky::layer", "Invalid layer index #" + String::number(index) + "."); } Sky::Layer const &Sky::layer(int index) const @@ -479,6 +543,26 @@ int Sky::firstActiveLayer() const return d->firstActiveLayer + 1; // 1-based index. } +bool Sky::hasModel(int index) const +{ + return (index > 0 && index <= MAX_SKY_MODELS); +} + +Sky::ModelInfo &Sky::model(int index) +{ + if(hasModel(index)) + { + return d->models[index - 1]; // 1-based index. + } + /// @throw MissingModelError An invalid model index was specified. + throw MissingModelError("Sky::model", "Invalid model index #" + String::number(index) + "."); +} + +Sky::ModelInfo const &Sky::model(int index) const +{ + return const_cast(const_cast(this)->model(index)); +} + void Sky::configureDefault() { d->height = DEFAULT_SKY_HEIGHT; @@ -504,6 +588,45 @@ void Sky::configureDefault() catch(MaterialManifest::MissingMaterialError const &) {} // Ignore this error. } + + d->haveModels = false; + de::zap(d->models); +} + +void Sky::setupModels(defn::Sky const &def) +{ + // Normally the sky sphere is not drawn if models are in use. + d->alwaysDrawSphere = (def.geti("flags") & SIF_DRAW_SPHERE) != 0; + + // The normal sphere is used if no models will be set up. + d->haveModels = false; + de::zap(d->models); + + for(int i = 0; i < def.modelCount(); ++i) + { + Record const &modef = def.model(i); + ModelInfo *minfo = &d->models[i]; + + // Is the model ID set? + try + { + minfo->model = &App_ResourceSystem().modelDef(modef.gets("id")); + if(!minfo->model->subCount()) + { + continue; + } + + // There is a model here. + d->haveModels = true; + + minfo->def = modef.accessedRecordPtr(); + minfo->maxTimer = int(TICSPERSEC * modef.getf("frameInterval")); + minfo->yaw = modef.getf("yaw"); + minfo->frame = minfo->model->subModelDef(0).frame; + } + catch(ResourceSystem::MissingModelDefError const &) + {} // Ignore this error. + } } float Sky::horizonOffset() const @@ -551,89 +674,6 @@ void Sky::setAmbientColor(Vector3f const &newColor) d->ambientColorDefined = true; } -void Sky::configure(defn::Sky *_def) -{ - LOG_AS("Sky"); - - // The default configuration is used as a starting point. - configureDefault(); - - if(!_def) return; // Go with the defaults, then. - defn::Sky const &def = *_def; - - setHeight(def.getf("height")); - setHorizonOffset(def.getf("horizonOffset")); - - for(int i = 0; i < MAX_SKY_LAYERS; ++i) - { - Record const &lyrDef = def.layer(i); - Layer &lyr = d->layers[i]; - - if(!(lyrDef.geti("flags") & Layer::Active)) - { - lyr.disable(); - continue; - } - - lyr.setMasked((lyrDef.geti("flags") & Layer::Masked) != 0) - .setOffset(lyrDef.getf("offset")) - .setFadeoutLimit(lyrDef.getf("colorLimit")) - .enable(); - - de::Uri const matUri(lyrDef.gets("material")); - if(!matUri.isEmpty()) - { - try - { - lyr.setMaterial(ClientApp::resourceSystem().materialPtr(matUri)); - } - catch(ResourceSystem::MissingManifestError const &er) - { - // Log but otherwise ignore this error. - LOG_RES_WARNING(er.asText() + ". Unknown material \"%s\" in definition layer %i, using default.") - << matUri << i; - } - } - } - - Vector3f ambientColor = Vector3f(def.get("color")).max(Vector3f(0, 0, 0)); - if(ambientColor != Vector3f(0, 0, 0)) - { - setAmbientColor(ambientColor); - } - - // Any sky models to setup? Models will override the normal sphere by default. - d->setupModels(def); -} - -void Sky::runTick() -{ - if(clientPaused || !d->haveModels) return; - - for(int i = 0; i < MAX_SKY_MODELS; ++i) - { - Instance::ModelInfo &minfo = d->models[i]; - if(!minfo.def) continue; - - // Rotate the model. - minfo.yaw += minfo.def->getf("yawSpeed") / TICSPERSEC; - - // Is it time to advance to the next frame? - if(minfo.maxTimer > 0 && ++minfo.timer >= minfo.maxTimer) - { - minfo.frame++; - minfo.timer = 0; - - // Execute a console command? - String const execute = minfo.def->gets("execute"); - if(!execute.isEmpty()) - { - Con_Execute(CMDS_SCRIPT, execute.toUtf8().constData(), true, false); - } - } - } -} - // Look up the precalculated vertex. static inline Vector3f &skyVertex(int r, int c) { @@ -921,7 +961,7 @@ void Sky::cacheDrawableAssets() { for(int i = 0; i < MAX_SKY_MODELS; ++i) { - Instance::ModelInfo &minfo = d->models[i]; + ModelInfo &minfo = d->models[i]; if(!minfo.def) continue; d->resSys().cache(minfo.model); @@ -983,6 +1023,23 @@ void Sky::draw() if(usingFog) glDisable(GL_FOG); } +bool Sky::hasAnimator() const +{ + return d->animator != 0; +} + +void Sky::setAnimator(Animator *newAnimator) +{ + d->animator = newAnimator; +} + +Sky::Animator &Sky::animator() +{ + if(d->animator) return *d->animator; + /// @throw MissingAnimatorError No animator is presently configured. + throw MissingAnimatorError("Sky::animator", "Missing animator"); +} + static Sky sky; Sky *theSky = &sky; diff --git a/doomsday/client/src/world/map.cpp b/doomsday/client/src/world/map.cpp index 4ac1f007ec..1d5c88a41e 100644 --- a/doomsday/client/src/world/map.cpp +++ b/doomsday/client/src/world/map.cpp @@ -3237,18 +3237,19 @@ void Map::update() /// @todo Sky needs breaking up into multiple components. There should be /// a representation on server side and a logical entity which the renderer /// visualizes. We also need multiple concurrent skies for BOOM support. + defn::Sky skyDef; if(mapInfo) { - defn::Sky skyDef; - int skyIdx = defs.getSkyNum(mapInfo.gets("skyId").toUtf8().constData()); - if(skyIdx >= 0) skyDef = defs.skies[skyIdx]; - else skyDef = mapInfo.subrecord("sky"); - theSky->configure(&skyDef); - } - else - { - theSky->configureDefault(); + if(Record const *def = defs.skies.tryFind("id", mapInfo.gets("skyId"))) + { + skyDef = *def; + } + else + { + skyDef = mapInfo.subrecord("sky"); + } } + theSky->animator().configure(&skyDef); #endif } diff --git a/doomsday/client/src/world/p_ticker.cpp b/doomsday/client/src/world/p_ticker.cpp index de5912f180..36062021e6 100644 --- a/doomsday/client/src/world/p_ticker.cpp +++ b/doomsday/client/src/world/p_ticker.cpp @@ -120,19 +120,19 @@ void P_Ticker(timespan_t elapsed) materialsTicker(elapsed); #endif - if(!App_WorldSystem().hasMap()) return; - - Map &map = App_WorldSystem().map(); - if(!map.thinkers().isInited()) return; // Not initialized yet. - - if(DD_IsSharpTick()) + if(App_WorldSystem().hasMap()) { + Map &map = App_WorldSystem().map(); + #ifdef __CLIENT__ - theSky->runTick(); + theSky->animator().advanceTime(elapsed); #endif - // Check all mobjs (always public). - map.thinkers().iterate(reinterpret_cast(gx.MobjThinker), 0x1, - P_MobjTicker); + if(DD_IsSharpTick()) + { + // Check all mobjs (always public). + map.thinkers().iterate(reinterpret_cast(gx.MobjThinker), 0x1, + P_MobjTicker); + } } } diff --git a/doomsday/client/src/world/worldsystem.cpp b/doomsday/client/src/world/worldsystem.cpp index da6ce26017..45ddfa5f6a 100644 --- a/doomsday/client/src/world/worldsystem.cpp +++ b/doomsday/client/src/world/worldsystem.cpp @@ -283,13 +283,28 @@ namespace de { DENG2_PIMPL(WorldSystem) { - Map *map; ///< Current map. - timespan_t time; ///< World-wide time. + Map *map = nullptr; ///< Current map. + timespan_t time = 0; ///< World-wide time. #ifdef __CLIENT__ - QScopedPointer hand; ///< For map editing/manipulation. + std::unique_ptr hand; ///< For map editing/manipulation. + std::unique_ptr skyAnimator; #endif - Instance(Public *i) : Base(i), map(0), time(0) {} + Instance(Public *i) : Base(i) + { +#ifdef __CLIENT__ + // Initialize an animator for the sky. + skyAnimator.reset(new Sky::Animator(*::theSky)); + ::theSky->setAnimator(skyAnimator.get()); +#endif + } + + ~Instance() + { +#ifdef __CLIENT__ + ::theSky->setAnimator(nullptr); +#endif + } void notifyMapChange() { @@ -493,18 +508,19 @@ DENG2_PIMPL(WorldSystem) #ifdef __CLIENT__ // Reconfigure the sky. + defn::Sky skyDef; if(mapInfo) { - defn::Sky skyDef; - int skyIdx = defs.getSkyNum(mapInfo.gets("skyId").toUtf8().constData()); - if(skyIdx >= 0) skyDef = defs.skies[skyIdx]; - else skyDef = mapInfo.subrecord("sky"); - theSky->configure(&skyDef); - } - else - { - theSky->configureDefault(); + if(Record const *def = defs.skies.tryFind("id", mapInfo.gets("skyId"))) + { + skyDef = *def; + } + else + { + skyDef = mapInfo.subrecord("sky"); + } } + theSky->animator().configure(&skyDef); #endif // Init the thinker lists (public and private). @@ -880,7 +896,7 @@ timespan_t WorldSystem::time() const Hand &WorldSystem::hand(coord_t *distance) const { // Time to create the hand? - if(d->hand.isNull()) + if(!d->hand) { d->hand.reset(new Hand()); audienceForFrameEnd += *d->hand; @@ -904,7 +920,7 @@ void WorldSystem::beginFrame(bool resetNextViewer) void WorldSystem::endFrame() { - if(d->map && !d->hand.isNull()) + if(d->map && d->hand) { d->updateHandOrigin();