From c3b493d95adfdaebbd7cf21abb1fc3c5a43b3129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= Date: Mon, 17 Aug 2015 18:20:14 +0300 Subject: [PATCH] Model Renderer|Scripting: Animated uniform variables for model shaders Each MobjAnimator now has its own set of uniform variables that are passed to the model shader. The uniform variables are declared in the asset metadata, and can be animated using Doomsday Script's native Core.Animation methods. Also cleaned up MobjAnimator a little internally. --- .../apps/client/include/render/mobjanimator.h | 8 + .../apps/client/include/render/rend_model.h | 3 +- .../apps/client/include/render/vissprite.h | 2 +- .../include/world/clientmobjthinkerdata.h | 6 +- .../apps/client/src/render/mobjanimator.cpp | 242 +++++++++++++++--- .../apps/client/src/render/modelrenderer.cpp | 19 +- doomsday/apps/client/src/render/r_things.cpp | 2 +- .../src/world/clientmobjthinkerdata.cpp | 4 +- 8 files changed, 244 insertions(+), 42 deletions(-) diff --git a/doomsday/apps/client/include/render/mobjanimator.h b/doomsday/apps/client/include/render/mobjanimator.h index fb3c5c9edb..dda762763e 100644 --- a/doomsday/apps/client/include/render/mobjanimator.h +++ b/doomsday/apps/client/include/render/mobjanimator.h @@ -21,6 +21,7 @@ #include "render/modelrenderer.h" #include +#include /** * Mobj-specific model animator. @@ -29,6 +30,9 @@ */ class MobjAnimator : public de::ModelDrawable::Animator { +public: + DENG2_ERROR(DefinitionError); + public: MobjAnimator(de::DotPath const &id, de::ModelDrawable const &model); @@ -38,6 +42,10 @@ class MobjAnimator : public de::ModelDrawable::Animator de::ddouble currentTime(int index) const; + void bindUniforms(de::GLProgram &program) const; + + void unbindUniforms(de::GLProgram &program) const; + private: DENG2_PRIVATE(d) }; diff --git a/doomsday/apps/client/include/render/rend_model.h b/doomsday/apps/client/include/render/rend_model.h index e99d9745e7..3d2fa81544 100644 --- a/doomsday/apps/client/include/render/rend_model.h +++ b/doomsday/apps/client/include/render/rend_model.h @@ -29,6 +29,7 @@ #include class TextureVariantSpec; +class MobjAnimator; struct vissprite_t; /// Absolute maximum number of vertices per submodel supported by this module. @@ -65,7 +66,7 @@ struct drawmodel2params_t struct mobj_s const *object; de::ModelDrawable const *model; ModelRenderer::AuxiliaryData const *auxData; - de::ModelDrawable::Animator const *animator; + MobjAnimator const *animator; }; DENG_EXTERN_C de::dbyte useModels; diff --git a/doomsday/apps/client/include/render/vissprite.h b/doomsday/apps/client/include/render/vissprite.h index b81354ebbd..69b04ea595 100644 --- a/doomsday/apps/client/include/render/vissprite.h +++ b/doomsday/apps/client/include/render/vissprite.h @@ -194,7 +194,7 @@ struct vispsprite_t struct vispsprite_model2_s { de::ModelDrawable const *model; ModelAuxiliaryData const *auxData; - de::ModelDrawable::Animator const *animator; + MobjAnimator const *animator; } model2; } data; }; diff --git a/doomsday/apps/client/include/world/clientmobjthinkerdata.h b/doomsday/apps/client/include/world/clientmobjthinkerdata.h index 98e6b15c71..6505022e62 100644 --- a/doomsday/apps/client/include/world/clientmobjthinkerdata.h +++ b/doomsday/apps/client/include/world/clientmobjthinkerdata.h @@ -52,6 +52,8 @@ //#define CLM_MAGIC1 0xdecafed1 //#define CLM_MAGIC2 0xcafedeb8 +class MobjAnimator; + /** * Private client-side data for mobjs. This includes any per-object state for rendering * and client-side network state. @@ -102,9 +104,9 @@ class ClientMobjThinkerData : public MobjThinkerData * * @return Animation state, or @c NULL if not drawn as a model. */ - de::ModelDrawable::Animator *animator(); + MobjAnimator *animator(); - de::ModelDrawable::Animator const *animator() const; + MobjAnimator const *animator() const; de::Matrix4f const &modelTransformation() const; diff --git a/doomsday/apps/client/src/render/mobjanimator.cpp b/doomsday/apps/client/src/render/mobjanimator.cpp index 316b2510e9..1336370a85 100644 --- a/doomsday/apps/client/src/render/mobjanimator.cpp +++ b/doomsday/apps/client/src/render/mobjanimator.cpp @@ -22,6 +22,9 @@ #include "dd_loop.h" #include +#include +#include +#include using namespace de; @@ -29,6 +32,10 @@ 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 String const DEF_VARIABLE ("variable"); + +static String const VAR_SELF ("self"); +static String const VAR_ASSET("__asset__"); static int const ANIM_DEFAULT_PRIORITY = 1; @@ -37,7 +44,7 @@ DENG2_PIMPL(MobjAnimator) /** * Specialized animation sequence state for a running animation. */ - struct Animation : public ModelDrawable::Animator::Animation + struct Sequence : public ModelDrawable::Animator::Animation { enum LoopMode { NotLooping = 0, @@ -49,10 +56,10 @@ DENG2_PIMPL(MobjAnimator) Scheduler const *timeline = nullptr; // owned by ModelRenderer::AnimSequence std::unique_ptr clock; - Animation() {} + Sequence() {} - Animation(int animationId, String const &rootNode, LoopMode looping, int priority, - Scheduler const *timeline = nullptr) + Sequence(int animationId, String const &rootNode, LoopMode looping, int priority, + Scheduler const *timeline = nullptr) : looping(looping) , priority(priority) , timeline(timeline) @@ -61,18 +68,18 @@ DENG2_PIMPL(MobjAnimator) node = rootNode; } - Animation(Animation const &other) : ModelDrawable::Animator::Animation(other) + Sequence(Sequence const &other) : Animation(other) { apply(other); } - Animation &operator = (Animation const &other) + Sequence &operator = (Sequence const &other) { apply(other); return *this; } - void apply(Animation const &other) + void apply(Sequence const &other) { animId = other.animId; node = other.node; @@ -85,7 +92,7 @@ DENG2_PIMPL(MobjAnimator) bool isRunning() const { - if(looping == Animation::Looping) + if(looping == Looping) { // Looping animations are always running. return true; @@ -93,17 +100,171 @@ DENG2_PIMPL(MobjAnimator) return !isAtEnd(); } - static Animation *make() { return new Animation; } + static Sequence *make() { return new Sequence; } }; ModelRenderer::AuxiliaryData const *auxData; - QHash pendingAnimForNode; + QHash pendingAnimForNode; String currentStateName; + Record names; ///< Local context for scripts. + + /** + * Animatable variable bound to a GL uniform. The value can have 1...4 float + * components. + */ + struct RenderVar + { + QList values; + GLUniform *uniform = nullptr; + + void init(float value) + { + values.clear(); + values.append(de::Animation(value, de::Animation::Linear)); + } + + template + void init(VecType const &vec) + { + values.clear(); + for(int i = 0; i < vec.size(); ++i) + { + values.append(de::Animation(vec[i], de::Animation::Linear)); + } + } + + ~RenderVar() + { + delete uniform; + } + + /** + * Copies the current values to the uniform. + */ + void updateUniform() + { + switch(values.size()) + { + case 1: + *uniform = values.at(0).value(); + break; + + case 2: + *uniform = Vector2f(values.at(0).value(), + values.at(1).value()); + break; + + case 3: + *uniform = Vector3f(values.at(0).value(), + values.at(1).value(), + values.at(2).value()); + break; + + case 4: + *uniform = Vector4f(values.at(0).value(), + values.at(1).value(), + values.at(2).value(), + values.at(3).value()); + break; + } + } + }; + + QMap renderVars; Instance(Public *i, DotPath const &id) : Base(i) , auxData(ClientApp::renderSystem().modelRenderer().auxiliaryData(id)) - {} + { + names.addText(VAR_ASSET, id).setReadOnly(); + names.add(VAR_SELF).set(new RecordValue(App::asset(id).accessedRecord())).setReadOnly(); + + initVariables(); + } + + ~Instance() + { + deinitVariables(); + } + + void initVariables() + { + auto const &def = names[VAR_SELF].valueAsRecord(); + if(def.has("render")) + { + // Look up the variable declarations. + auto vars = ScriptedInfo::subrecordsOfType(DEF_VARIABLE, def.subrecord("render")); + DENG2_FOR_EACH_CONST(Record::Subrecords, i, vars) + { + std::unique_ptr var(new RenderVar); + + GLUniform::Type uniformType = GLUniform::Float; + + // Initialize the appropriate type of value animation and uniform, + // depending on the "value" key in the definition. + auto const &valueDef = *i.value(); + Value const &initialValue = valueDef["value"].value(); + if(auto const *array = initialValue.maybeAs()) + { + switch(array->size()) + { + default: + throw DefinitionError("MobjAnimator::initVariables", + QString("%1: Invalid initial value size (%2) for render.variable") + .arg(valueDef.gets(ScriptedInfo::VAR_SOURCE)) + .arg(array->size())); + + case 2: + var->init(vectorFromValue(*array)); + uniformType = GLUniform::Vec2; + break; + + case 3: + var->init(vectorFromValue(*array)); + uniformType = GLUniform::Vec3; + break; + + case 4: + var->init(vectorFromValue(*array)); + uniformType = GLUniform::Vec4; + break; + } + + // Expose the components individually in the namespace for scripts. + static char const *componentNames[] = { "x", "y", "z", "w" }; + for(int k = 0; k < var->values.size(); ++k) + { + addBinding(String(i.key()).concatenateMember(componentNames[k]), + var->values[k]); + } + } + else + { + var->init(float(initialValue.asNumber())); + + // Expose in the namespace for scripts. + addBinding(i.key(), var->values[0]); + } + + // Uniform to be passed to the shader. + var->uniform = new GLUniform(i.key().toLatin1(), uniformType); + + renderVars[i.key()] = var.release(); + } + } + } + + void addBinding(String const &varName, de::Animation &anim) + { + names.add(varName) + .set(new NativeValue(&anim, &ScriptSystem::builtInClass("Animation"))) + .setReadOnly(); + } + + void deinitVariables() + { + qDeleteAll(renderVars.values()); + } int animationId(String const &name) const { @@ -112,25 +273,27 @@ DENG2_PIMPL(MobjAnimator) }); } - Animation &start(Animation const &spec) + Sequence &start(Sequence const &spec) { - Animation &anim = self.start(spec.animId, spec.node).as(); + Sequence &anim = self.start(spec.animId, spec.node).as(); anim.apply(spec); if(anim.timeline) { - anim.clock.reset(new Scheduler::Clock(*anim.timeline)); + anim.clock.reset(new Scheduler::Clock(*anim.timeline, &names)); } return anim; } }; MobjAnimator::MobjAnimator(DotPath const &id, ModelDrawable const &model) - : ModelDrawable::Animator(model, Instance::Animation::make) + : ModelDrawable::Animator(model, Instance::Sequence::make) , d(new Instance(this, id)) {} void MobjAnimator::triggerByState(String const &stateName) { + using Sequence = Instance::Sequence; + // No animations can be triggered if none are available. auto const *stateAnims = (d->auxData? &d->auxData->animations : nullptr); if(!stateAnims) return; @@ -145,8 +308,6 @@ void MobjAnimator::triggerByState(String const &stateName) foreach(ModelRenderer::AnimSequence const &seq, found.value()) { - using Animation = Instance::Animation; - try { // Test for the probability of this animation. @@ -159,7 +320,8 @@ void MobjAnimator::triggerByState(String const &stateName) int animId = d->animationId(seq.name); // Do not restart running sequences. - // TODO: Only restart if the current state is not the expected one. + /// @todo Only restart the animation if the current state is not the expected + /// one (checking the state cycle). if(isRunning(animId, node)) continue; int const priority = seq.def->geti(DEF_PRIORITY, ANIM_DEFAULT_PRIORITY); @@ -176,14 +338,14 @@ void MobjAnimator::triggerByState(String const &stateName) } // Parameters for the new sequence. - Animation anim(animId, node, - ScriptedInfo::isTrue(*seq.def, DEF_LOOPING)? Animation::Looping : - Animation::NotLooping, - priority, - timeline); + Sequence anim(animId, node, + ScriptedInfo::isTrue(*seq.def, DEF_LOOPING)? Sequence::Looping : + Sequence::NotLooping, + priority, + timeline); // Do not override higher-priority animations. - if(auto *existing = find(node)->maybeAs()) + if(auto *existing = find(node)->maybeAs()) { if(priority < existing->priority) { @@ -215,13 +377,12 @@ void MobjAnimator::advanceTime(TimeDelta const &elapsed) { ModelDrawable::Animator::advanceTime(elapsed); + using Sequence = Instance::Sequence; bool retrigger = false; for(int i = 0; i < count(); ++i) { - using Animation = Instance::Animation; - - auto &anim = at(i).as(); + auto &anim = at(i).as(); ddouble factor = 1.0; // TODO: Determine actual time factor. @@ -229,13 +390,13 @@ void MobjAnimator::advanceTime(TimeDelta const &elapsed) TimeDelta animElapsed = factor * elapsed; anim.time += animElapsed; - if(anim.looping == Animation::NotLooping) + if(anim.looping == Sequence::NotLooping) { // Clamp at the end. anim.time = min(anim.time, anim.duration); } - if(anim.looping == Animation::Looping) + if(anim.looping == Sequence::Looping) { // When a looping animation has completed a loop, it may still trigger // a variant. @@ -243,6 +404,10 @@ void MobjAnimator::advanceTime(TimeDelta const &elapsed) { retrigger = true; anim.time -= anim.duration; // Trigger only once per loop. + if(anim.clock) + { + anim.clock->rewind(anim.time); + } } } @@ -280,5 +445,22 @@ 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; + return ModelDrawable::Animator::currentTime(index); // + frameTimePos; +} + +void MobjAnimator::bindUniforms(GLProgram &program) const +{ + for(auto i : d->renderVars.values()) + { + i->updateUniform(); + program.bind(*i->uniform); + } +} + +void MobjAnimator::unbindUniforms(GLProgram &program) const +{ + for(auto i : d->renderVars.values()) + { + program.unbind(*i->uniform); + } } diff --git a/doomsday/apps/client/src/render/modelrenderer.cpp b/doomsday/apps/client/src/render/modelrenderer.cpp index 44f66dcaf5..7e52c01cd6 100644 --- a/doomsday/apps/client/src/render/modelrenderer.cpp +++ b/doomsday/apps/client/src/render/modelrenderer.cpp @@ -17,9 +17,10 @@ */ #include "render/modelrenderer.h" -#include "gl/gl_main.h" +#include "render/mobjanimator.h" #include "render/rend_main.h" #include "render/vissprite.h" +#include "gl/gl_main.h" #include "world/p_players.h" #include "world/clientmobjthinkerdata.h" #include "clientapp.h" @@ -424,6 +425,16 @@ DENG2_PIMPL(ModelRenderer) lightCount++; } + + template + void draw(Params const &p) + { + p.animator->bindUniforms(program); /// @todo Constant buffers? + p.model->draw(p.animator, + !p.auxData->passes.isEmpty()? &p.auxData->passes : + nullptr); + p.animator->unbindUniforms(program); + } }; ModelRenderer::ModelRenderer() : d(new Instance(this)) @@ -502,8 +513,7 @@ void ModelRenderer::render(vissprite_t const &spr) d->setupLighting(spr.light); // Draw the model using the current animation state. - p.model->draw(p.animator, - !p.auxData->passes.isEmpty()? &p.auxData->passes : nullptr); + d->draw(p); GLState::pop(); /// @todo Something is interfering with the cull setting elsewhere (remove this). @@ -528,8 +538,7 @@ void ModelRenderer::render(vispsprite_t const &pspr) d->setupLighting(pspr.light); GLState::push().setCull(p.auxData->cull); - p.model->draw(p.animator, - !p.auxData->passes.isEmpty()? &p.auxData->passes : nullptr); + d->draw(p); GLState::pop(); /// @todo Something is interfering with the cull setting elsewhere (remove this). diff --git a/doomsday/apps/client/src/render/r_things.cpp b/doomsday/apps/client/src/render/r_things.cpp index 15546a1d91..311af10b26 100644 --- a/doomsday/apps/client/src/render/r_things.cpp +++ b/doomsday/apps/client/src/render/r_things.cpp @@ -191,7 +191,7 @@ void R_ProjectSprite(mobj_t &mob) ModelDef *mf = nullptr, *nextmf = nullptr; dfloat interp = 0; - ModelDrawable::Animator const *animator = nullptr; // GL2 model present? + MobjAnimator const *animator = nullptr; // GL2 model present? if(useModels) { diff --git a/doomsday/apps/client/src/world/clientmobjthinkerdata.cpp b/doomsday/apps/client/src/world/clientmobjthinkerdata.cpp index 3a3bed16b5..80a6428f87 100644 --- a/doomsday/apps/client/src/world/clientmobjthinkerdata.cpp +++ b/doomsday/apps/client/src/world/clientmobjthinkerdata.cpp @@ -204,12 +204,12 @@ ClientMobjThinkerData::RemoteSync &ClientMobjThinkerData::remoteSync() return *d->sync; } -ModelDrawable::Animator *ClientMobjThinkerData::animator() +MobjAnimator *ClientMobjThinkerData::animator() { return d->animator.get(); } -ModelDrawable::Animator const *ClientMobjThinkerData::animator() const +MobjAnimator const *ClientMobjThinkerData::animator() const { return d->animator.get(); }