Skip to content

Commit

Permalink
Model Renderer: Animation priorities and loop re-triggering
Browse files Browse the repository at this point in the history
It is now possible to set a priority value for each animation sequence
(default: 1). Lower priority animations cannot start when a higher
priority animation is running. Instead, they get queued for starting
when the higher priority animation has finished.

When a looping animation has finished its loop, it will retrigger
animations according to its state, which may cause a different variant
of the animation to be started.
  • Loading branch information
skyjake committed Aug 3, 2015
1 parent 1321e7e commit 2e938b5
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 21 deletions.
4 changes: 1 addition & 3 deletions doomsday/apps/client/include/render/mobjanimator.h
Expand Up @@ -41,9 +41,7 @@ class MobjAnimator : public de::ModelDrawable::Animator
struct Parameters;

private:
ModelRenderer::StateAnims const *_stateAnims;

struct Private;
DENG2_PRIVATE(d)
};

#endif // DENG_CLIENT_RENDER_MOBJANIMATOR_H
111 changes: 93 additions & 18 deletions doomsday/apps/client/src/render/mobjanimator.cpp
Expand Up @@ -28,6 +28,9 @@ using namespace de;
static String const DEF_PROBABILITY("prob");
static String const DEF_ROOT_NODE ("node");
static String const DEF_LOOPING ("looping");
static String const DEF_PRIORITY ("priority");

static int const ANIM_DEFAULT_PRIORITY = 1;

struct MobjAnimator::Parameters
{
Expand All @@ -37,14 +40,35 @@ struct MobjAnimator::Parameters
};

LoopMode looping;
int priority;

Parameters(LoopMode loop = NotLooping) : looping(loop) {}
Parameters(LoopMode loop = NotLooping,
int prio = ANIM_DEFAULT_PRIORITY)
: looping(loop)
, priority(prio)
{}
};

Q_DECLARE_METATYPE(MobjAnimator::Parameters)

struct MobjAnimator::Private
DENG2_PIMPL(MobjAnimator)
{
ModelRenderer::StateAnims const *stateAnims;
struct Pending {
int animId;
Parameters params;
Pending() : animId(-1) {}
Pending(int id, Parameters const &p)
: animId(id), params(p) {}
};
QHash<String, Pending> pendingAnimForNode;
String currentStateName;

Instance(Public *i, DotPath const &id)
: Base(i)
, stateAnims(ClientApp::renderSystem().modelRenderer().animations(id))
{}

static bool isRunning(Animation const &anim)
{
Parameters const &params = anim.data.value<Parameters>();
Expand All @@ -55,22 +79,30 @@ struct MobjAnimator::Private
}
return !anim.isAtEnd();
}

int animationId(String const &name) const
{
return ModelRenderer::identifierFromText(name, [this] (String const &name) {
return self.model().animationIdForName(name);
});
}
};

MobjAnimator::MobjAnimator(DotPath const &id, ModelDrawable const &model)
: ModelDrawable::Animator(model)
, _stateAnims(ClientApp::renderSystem().modelRenderer().animations(id))
, d(new Instance(this, id))
{}

void MobjAnimator::triggerByState(String const &stateName)
{
// No animations can be triggered if none are available.
if(!_stateAnims) return;
if(!d->stateAnims) return;

auto found = _stateAnims->constFind(stateName);
if(found == _stateAnims->constEnd()) return;
auto found = d->stateAnims->constFind(stateName);
if(found == d->stateAnims->constEnd()) return;

//LOG_WIP("triggerByState: ") << stateName;
d->currentStateName = stateName;

foreach(ModelRenderer::AnimSequence const &seq, found.value())
{
Expand All @@ -83,20 +115,35 @@ void MobjAnimator::triggerByState(String const &stateName)
// Start the animation on the specified node (defaults to root),
// unless it is already running.
String const node = seq.def->gets(DEF_ROOT_NODE, "");
int animId = ModelRenderer::identifierFromText(seq.name, [this] (String const &name) {
return model().animationIdForName(name);
});
int animId = d->animationId(seq.name);

// Do not restart running sequences.
// TODO: Only restart if the current state is not the expected one.
if(isRunning(animId, node)) continue;

// Start a new sequence.
Animation &anim = start(animId, node);
int const priority = seq.def->geti(DEF_PRIORITY, ANIM_DEFAULT_PRIORITY);

// Parameters for the new sequence.
Parameters params(ScriptedInfo::isTrue(*seq.def, DEF_LOOPING)? Parameters::Looping :
Parameters::NotLooping,
priority);

// Do not override higher-priority animations.
if(Animation *existing = find(node))
{
if(priority < existing->data.value<Parameters>().priority)
{
// This will be started once the higher-priority animation
// has finished.
d->pendingAnimForNode[node] = Instance::Pending(animId, params);
continue;
}
}

d->pendingAnimForNode.remove(node);

Parameters params(ScriptedInfo::isTrue(*seq.def, DEF_LOOPING)?
Parameters::Looping : Parameters::NotLooping);
anim.data.setValue(params);
// Start a new sequence.
start(animId, node).data.setValue(params);
}
catch(ModelDrawable::Animator::InvalidError const &er)
{
Expand All @@ -114,6 +161,8 @@ void MobjAnimator::advanceTime(TimeDelta const &elapsed)
{
ModelDrawable::Animator::advanceTime(elapsed);

bool retrigger = false;

for(int i = 0; i < count(); ++i)
{
Animation &anim = at(i);
Expand All @@ -130,19 +179,45 @@ void MobjAnimator::advanceTime(TimeDelta const &elapsed)
anim.time = min(anim.time, anim.duration);
}

if(params.looping == Parameters::Looping)
{
// When a looping animation has completed a loop, it may still trigger
// a variant.
if(anim.isAtEnd())
{
retrigger = true;
}
}

// Stop finished animations.
if(!Private::isRunning(anim))
if(!Instance::isRunning(anim))
{
stop(i--);
String const node = anim.node;

stop(i--); // `anim` gets deleted

// Start a previously triggered pending animation.
auto &pending = d->pendingAnimForNode;
if(pending.contains(node))
{
LOG_WIP("Starting pending anim %i") << pending[node].animId;
start(pending[node].animId, node)
.data.setValue(pending[node].params);
pending.remove(node);
}
}
}

if(retrigger && !d->currentStateName.isEmpty())
{
LOG_WIP("retrigger: " _E(C)) << d->currentStateName;
triggerByState(d->currentStateName);
}
}

ddouble MobjAnimator::currentTime(int index) const
{
// Mobjs think on sharp ticks only, however we need to ensure time advances on
// every frame for smooth animation.
return ModelDrawable::Animator::currentTime(index);// + frameTimePos;

/// @todo Should prevent time from passing the end of the sequence?
}

0 comments on commit 2e938b5

Please sign in to comment.