diff --git a/doomsday/apps/client/include/render/model.h b/doomsday/apps/client/include/render/model.h index ffbf6b8685..a4346909a2 100644 --- a/doomsday/apps/client/include/render/model.h +++ b/doomsday/apps/client/include/render/model.h @@ -31,6 +31,8 @@ namespace render { /** * Drawable model with client-specific extra information, e.g., animation * sequences. + * + * @todo Refactor: This could look more like a proper class, yes? -jk */ struct Model : public de::ModelDrawable { diff --git a/doomsday/apps/client/include/render/stateanimator.h b/doomsday/apps/client/include/render/stateanimator.h index 6d2a19cdfe..83ad4b8d26 100644 --- a/doomsday/apps/client/include/render/stateanimator.h +++ b/doomsday/apps/client/include/render/stateanimator.h @@ -57,28 +57,6 @@ class StateAnimator : public de::ModelDrawable::Animator de::ModelDrawable::Appearance const &appearance() const; - enum BindOperation { Bind, Unbind }; - - /** - * Binds or unbinds uniforms that apply to all rendering passes. - * - * @param program Program where bindings are made. - * @param operation Bind or unbind. - */ - void bindUniforms(de::GLProgram &program, BindOperation operation) const; - - /** - * Binds or unbinds uniforms that apply to a single rendering pass. - * - * @param program Program where bindings are made. - * @param passName Name of the rendering pass. The render variables are - * named, e.g., "render.(passName).uName". - * @param operation Bind or unbind. - */ - void bindPassUniforms(de::GLProgram &program, - de::String const &passName, - BindOperation operation) const; - private: DENG2_PRIVATE(d) }; diff --git a/doomsday/apps/client/src/render/stateanimator.cpp b/doomsday/apps/client/src/render/stateanimator.cpp index fb2c6015cb..59aee44477 100644 --- a/doomsday/apps/client/src/render/stateanimator.cpp +++ b/doomsday/apps/client/src/render/stateanimator.cpp @@ -55,6 +55,8 @@ static int const ANIM_DEFAULT_PRIORITY = 1; DENG2_PIMPL(StateAnimator) , DENG2_OBSERVES(Variable, Change) { + enum BindOperation { Bind, Unbind }; + /** * Specialized animation sequence state for a running animation. */ @@ -221,16 +223,14 @@ DENG2_PIMPL(StateAnimator) } appearance.programCallback = [this] (GLProgram &program, ModelDrawable::ProgramBinding binding) { - self.bindUniforms(program, - binding == ModelDrawable::AboutToBind? StateAnimator::Bind : - StateAnimator::Unbind); + bindUniforms(program, + binding == ModelDrawable::AboutToBind? Bind : Unbind); }; appearance.passCallback = [this] (ModelDrawable::Pass const &pass, ModelDrawable::PassState state) { - self.bindPassUniforms(*self.model().currentProgram(), + bindPassUniforms(*self.model().currentProgram(), pass.name, - state == ModelDrawable::PassBegun? StateAnimator::Bind : - StateAnimator::Unbind); + state == ModelDrawable::PassBegun? Bind : Unbind); }; } @@ -494,6 +494,47 @@ DENG2_PIMPL(StateAnimator) } } + /** + * Binds or unbinds uniforms that apply to all rendering passes. + * + * @param program Program where bindings are made. + * @param operation Bind or unbind. + */ + void bindUniforms(de::GLProgram &program, BindOperation operation) const + { + bindPassUniforms(program, PASS_GLOBAL, operation); + } + + /** + * Binds or unbinds uniforms that apply to a single rendering pass. + * + * @param program Program where bindings are made. + * @param passName Name of the rendering pass. The render variables are + * named, e.g., "render.(passName).uName". + * @param operation Bind or unbind. + */ + void bindPassUniforms(de::GLProgram &program, + de::String const &passName, + BindOperation operation) const + { + auto const vars = passVars.constFind(passName); + if(vars != passVars.constEnd()) + { + for(auto i : vars.value()) + { + if(operation == Bind) + { + i->updateUniform(); + program.bind(*i->uniform); + } + else + { + program.unbind(*i->uniform); + } + } + } + } + int animationId(String const &name) const { return ModelRenderer::identifierFromText(name, [this] (String const &name) { @@ -542,7 +583,7 @@ void StateAnimator::triggerByState(String const &stateName) if(found == stateAnims->constEnd()) return; LOG_AS("StateAnimator"); - LOGDEV_GL_XVERBOSE("triggerByState: ") << stateName; + //LOGDEV_GL_XVERBOSE("triggerByState: ") << stateName; d->currentStateName = stateName; @@ -608,7 +649,8 @@ void StateAnimator::triggerByState(String const &stateName) continue; } - LOG_GL_VERBOSE("Starting animation: " _E(b)) << seq.name; + /*LOG_GL_XVERBOSE("Mobj %i starting animation: " _E(b)) + << d->names.geti("self.__id__") << seq.name;*/ break; } } @@ -699,30 +741,4 @@ ModelDrawable::Appearance const &StateAnimator::appearance() const return d->appearance; } -void StateAnimator::bindUniforms(GLProgram &program, BindOperation operation) const -{ - bindPassUniforms(program, PASS_GLOBAL, operation); -} - -void StateAnimator::bindPassUniforms(GLProgram &program, String const &passName, - BindOperation operation) const -{ - auto const vars = d->passVars.constFind(passName); - if(vars != d->passVars.constEnd()) - { - for(auto i : vars.value()) - { - if(operation == Bind) - { - i->updateUniform(); - program.bind(*i->uniform); - } - else - { - program.unbind(*i->uniform); - } - } - } -} - } // namespace render diff --git a/doomsday/apps/client/src/world/clientmobjthinkerdata.cpp b/doomsday/apps/client/src/world/clientmobjthinkerdata.cpp index 9e2c2f6913..51526f90c3 100644 --- a/doomsday/apps/client/src/world/clientmobjthinkerdata.cpp +++ b/doomsday/apps/client/src/world/clientmobjthinkerdata.cpp @@ -76,9 +76,15 @@ DENG2_PIMPL(ClientMobjThinkerData) return Def_GetStateName(self.mobj()->state); } + bool isStateInCurrentSequence(state_t const *previous) + { + if(!previous) return false; + return Def_GetState(previous->nextState) == self.mobj()->state; + } + String modelId() const { - return String("model.thing.%1").arg(thingName().toLower()); + return QStringLiteral("model.thing.%1").arg(thingName().toLower()); } static ModelBank &modelBank() @@ -145,11 +151,16 @@ DENG2_PIMPL(ClientMobjThinkerData) * a less than 1.0 probability for starting. The sequence may be identified either by * name ("walk") or index (for example, "#3"). */ - void triggerStateAnimations() + void triggerStateAnimations(state_t const *state = nullptr) { - if(animator) + if(flags.testFlag(StateChanged)) { - animator->triggerByState(stateName()); + flags &= ~StateChanged; + if(animator) + { + animator->triggerByState(state? Def_GetStateName(state) : + stateName()); + } } } @@ -202,11 +213,7 @@ void ClientMobjThinkerData::think() MobjThinkerData::think(); d->initOnce(); - if(d->flags.testFlag(StateChanged)) - { - d->flags &= ~StateChanged; - d->triggerStateAnimations(); - } + d->triggerStateAnimations(); // with current state d->triggerMovementAnimations(); d->advanceAnimations(SECONDSPERTIC); // mobjs think only on sharp ticks } @@ -257,6 +264,22 @@ void ClientMobjThinkerData::stateChanged(state_t const *previousState) bool const justSpawned = !previousState; d->initOnce(); - d->flags |= StateChanged; // Will trigger animations during think. + if(d->flags.testFlag(StateChanged) && d->isStateInCurrentSequence(previousState)) + { + /* + * Mobj state has already been flagged as changed, but triggers for the + * previous state haven't fired yet. Because it's the same sequence, it + * might be the one that triggers an animation, so let's not miss the + * trigger. + */ + d->triggerStateAnimations(previousState); + } + /* + * Trigger animations later during think(). This is done to avoid multiple + * state changes during a single tick from interrupting longer sequences, + * for instance allowing consecutive attack sequences to play as a single, + * long sequence (e.g., Hexen's Ettin). + */ + d->flags |= StateChanged; d->triggerParticleGenerators(justSpawned); }