Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Fixed|Model Renderer: Animation triggering when state changes multipl…
…e times

Sometimes a mobj's state changes more than once before the mobj thinks.
ClientMobjThinkerData is now a bit smarter in determining when to
trigger animations for a new state and when to defer the triggering.
  • Loading branch information
skyjake committed Oct 29, 2015
1 parent b5d4383 commit d0ce736
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 66 deletions.
2 changes: 2 additions & 0 deletions doomsday/apps/client/include/render/model.h
Expand Up @@ -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
{
Expand Down
22 changes: 0 additions & 22 deletions doomsday/apps/client/include/render/stateanimator.h
Expand Up @@ -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)
};
Expand Down
84 changes: 50 additions & 34 deletions doomsday/apps/client/src/render/stateanimator.cpp
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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);
};
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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
43 changes: 33 additions & 10 deletions doomsday/apps/client/src/world/clientmobjthinkerdata.cpp
Expand Up @@ -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()
Expand Down Expand Up @@ -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());
}
}
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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);
}

0 comments on commit d0ce736

Please sign in to comment.