From 2e938b548bb5572da45336a09d94a2c671d93044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= Date: Mon, 3 Aug 2015 22:26:29 +0300 Subject: [PATCH] Model Renderer: Animation priorities and loop re-triggering 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. --- .../apps/client/include/render/mobjanimator.h | 4 +- .../apps/client/src/render/mobjanimator.cpp | 111 +++++++++++++++--- 2 files changed, 94 insertions(+), 21 deletions(-) diff --git a/doomsday/apps/client/include/render/mobjanimator.h b/doomsday/apps/client/include/render/mobjanimator.h index 83a82056e4..07f31fc2c2 100644 --- a/doomsday/apps/client/include/render/mobjanimator.h +++ b/doomsday/apps/client/include/render/mobjanimator.h @@ -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 diff --git a/doomsday/apps/client/src/render/mobjanimator.cpp b/doomsday/apps/client/src/render/mobjanimator.cpp index a0864a2fb1..6e06cca2dd 100644 --- a/doomsday/apps/client/src/render/mobjanimator.cpp +++ b/doomsday/apps/client/src/render/mobjanimator.cpp @@ -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 { @@ -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 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 ¶ms = anim.data.value(); @@ -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()) { @@ -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().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) { @@ -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); @@ -130,12 +179,40 @@ 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 @@ -143,6 +220,4 @@ 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? }