From 11bb878e345c0197fef3a1f2246e84da4532b619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= Date: Wed, 1 Jun 2016 21:37:52 +0300 Subject: [PATCH] Refactor|Model Renderer: Moved shader variables to another source file Shader variables are not specific to models in any way. --- .../apps/client/include/render/shadervar.h | 81 +++++ doomsday/apps/client/src/render/model.cpp | 6 +- .../apps/client/src/render/modelrenderer.cpp | 94 +++--- .../src/render/playerweaponanimator.cpp | 12 +- doomsday/apps/client/src/render/shadervar.cpp | 172 +++++++++++ .../apps/client/src/render/stateanimator.cpp | 280 ++++-------------- 6 files changed, 372 insertions(+), 273 deletions(-) create mode 100644 doomsday/apps/client/include/render/shadervar.h create mode 100644 doomsday/apps/client/src/render/shadervar.cpp diff --git a/doomsday/apps/client/include/render/shadervar.h b/doomsday/apps/client/include/render/shadervar.h new file mode 100644 index 0000000000..404784727c --- /dev/null +++ b/doomsday/apps/client/include/render/shadervar.h @@ -0,0 +1,81 @@ +/** @file render/shadervar.h + * + * @authors Copyright (c) 2016 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef DENG_CLIENT_RENDER_SHADERVAR_H +#define DENG_CLIENT_RENDER_SHADERVAR_H + +#include +#include +#include +#include + +/** + * Animatable variable bound to a GL uniform. The value can have 1...4 float + * components. + */ +struct ShaderVar +{ + struct Value { + de::Animation anim; + de::Rangef wrap; + + Value(de::Animation const &a) : anim(a) {} + }; + QList values; + de::GLUniform *uniform = nullptr; // owned + +public: + virtual ~ShaderVar(); + + void init(float value); + + 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)); + } + } + + float currentValue(int index) const; + + /** + * Copies the current values to the uniform. + */ + void updateUniform(); +}; + +struct ShaderVars +{ + QHash members; + + DENG2_ERROR(DefinitionError); + +public: + virtual ~ShaderVars(); + + void initVariableFromDefinition(de::String const &variableName, + de::Record const &valueDef, + de::Record &bindingNames); + + void addBinding(de::Record &names, de::String const &varName, de::Animation &anim); +}; + +#endif // DENG_CLIENT_RENDER_SHADERVAR_H diff --git a/doomsday/apps/client/src/render/model.cpp b/doomsday/apps/client/src/render/model.cpp index 98d8f34cef..dca9fbfba0 100644 --- a/doomsday/apps/client/src/render/model.cpp +++ b/doomsday/apps/client/src/render/model.cpp @@ -29,12 +29,12 @@ Model::AnimSequence::AnimSequence(String const &name, Record const &def) , def(&def) { // Parse timeline events. - if(def.hasSubrecord(DEF_TIMELINE)) + if (def.hasSubrecord(DEF_TIMELINE)) { timeline = new Scheduler; timeline->addFromInfo(def.subrecord(DEF_TIMELINE)); } - else if(def.hasMember(DEF_TIMELINE)) + else if (def.hasMember(DEF_TIMELINE)) { // Uses a shared timeline in the definition. This will be looked up when // the animation starts. @@ -46,7 +46,7 @@ Model::~Model() { // The commit group will be deleted now. unsetAtlas(); - + qDeleteAll(timelines.values()); } diff --git a/doomsday/apps/client/src/render/modelrenderer.cpp b/doomsday/apps/client/src/render/modelrenderer.cpp index 07bf2ea2cb..e7c3e579be 100644 --- a/doomsday/apps/client/src/render/modelrenderer.cpp +++ b/doomsday/apps/client/src/render/modelrenderer.cpp @@ -91,7 +91,7 @@ DENG2_PIMPL(ModelRenderer) ~Programs() { #ifdef DENG2_DEBUG - for(auto i = constBegin(); i != constEnd(); ++i) + for (auto i = constBegin(); i != constEnd(); ++i) { qDebug() << i.key() << i.value(); DENG2_ASSERT(i.value()); @@ -171,7 +171,7 @@ DENG2_PIMPL(ModelRenderer) << (event == filesys::AssetObserver::Added? "available" : "unavailable"); - if(event == filesys::AssetObserver::Added) + if (event == filesys::AssetObserver::Added) { bank.add(identifier, App::asset(identifier).absolutePath("path")); @@ -183,16 +183,16 @@ DENG2_PIMPL(ModelRenderer) auto const &model = bank.model(identifier); // Unload programs used by the various rendering passes. - for(auto const &pass : model.passes) + for (auto const &pass : model.passes) { DENG2_ASSERT(pass.program); unloadProgram(*static_cast(pass.program)); } // Alternatively, the entire model may be using a single program. - if(model.passes.isEmpty()) + if (model.passes.isEmpty()) { - if(model.program()) + if (model.program()) { unloadProgram(*static_cast(model.program())); } @@ -216,7 +216,7 @@ DENG2_PIMPL(ModelRenderer) */ Program *loadProgram(String const &name) { - if(programs.contains(name)) + if (programs.contains(name)) { programs[name]->useCount++; return programs[name]; @@ -242,7 +242,7 @@ DENG2_PIMPL(ModelRenderer) << uFogColor; // Built-in special uniforms. - if(prog->def->hasMember(VAR_U_MAP_TIME)) + if (prog->def->hasMember(VAR_U_MAP_TIME)) { *prog << ClientApp::renderSystem().uMapTime(); } @@ -259,7 +259,7 @@ DENG2_PIMPL(ModelRenderer) */ void unloadProgram(Program &program) { - if(--program.useCount == 0) + if (--program.useCount == 0) { String name = program.shaderName; LOG_RES_VERBOSE("Model shader \"%s\" unloaded (no more users)") << name; @@ -288,9 +288,9 @@ DENG2_PIMPL(ModelRenderer) { "LessOrEqual", gl::LessOrEqual }, { "GreaterOrEqual", gl::GreaterOrEqual } }; - for(auto const &p : cs) + for (auto const &p : cs) { - if(text == p.txt) + if (text == p.txt) { return p.comp; } @@ -313,9 +313,9 @@ DENG2_PIMPL(ModelRenderer) { "DestAlpha", gl::DestAlpha }, { "OneMinusDestAlpha", gl::OneMinusDestAlpha } }; - for(auto const &p : bs) + for (auto const &p : bs) { - if(text == p.txt) + if (text == p.txt) { return p.blend; } @@ -326,9 +326,9 @@ DENG2_PIMPL(ModelRenderer) static gl::BlendOp textToBlendOp(String const &text) { - if(text == "Add") return gl::Add; - if(text == "Subtract") return gl::Subtract; - if(text == "ReverseSubtract") return gl::ReverseSubtract; + if (text == "Add") return gl::Add; + if (text == "Subtract") return gl::Subtract; + if (text == "ReverseSubtract") return gl::ReverseSubtract; throw DefinitionError("ModelRenderer::textToBlendOp", QString("Invalid blending operation \"%1\"").arg(text)); } @@ -346,21 +346,21 @@ DENG2_PIMPL(ModelRenderer) void composeTextureMappings(ModelDrawable::Mapping &mapping, Record const &shaderDef) { - if(shaderDef.has(DEF_TEXTURE_MAPPING)) + if (shaderDef.has(DEF_TEXTURE_MAPPING)) { ArrayValue const &array = shaderDef.geta(DEF_TEXTURE_MAPPING); - for(int i = 0; i < int(array.size()); ++i) + for (int i = 0; i < int(array.size()); ++i) { ModelDrawable::TextureMap map = ModelDrawable::textToTextureMap(array.element(i).asText()); - if(i == mapping.size()) + if (i == mapping.size()) { mapping << map; } - else if(mapping.at(i) != map) + else if (mapping.at(i) != map) { // Must match what the shader expects to receive. QStringList list; - for(auto map : mapping) list << ModelDrawable::textureMapToText(map); + for (auto map : mapping) list << ModelDrawable::textureMapToText(map); throw TextureMappingError("ModelRenderer::composeTextureMappings", QString("Texture mapping <%1> is incompatible with shader %2") .arg(list.join(", ")) @@ -388,11 +388,11 @@ DENG2_PIMPL(ModelRenderer) // Determine the coordinate system of the model. Vector3f front(0, 0, 1); Vector3f up (0, 1, 0); - if(asset.has(DEF_FRONT_VECTOR)) + if (asset.has(DEF_FRONT_VECTOR)) { front = Vector3f(asset.geta(DEF_FRONT_VECTOR)); } - if(asset.has(DEF_UP_VECTOR)) + if (asset.has(DEF_UP_VECTOR)) { up = Vector3f(asset.geta(DEF_UP_VECTOR)); } @@ -401,7 +401,7 @@ DENG2_PIMPL(ModelRenderer) // Assimp's coordinate system uses different handedness than Doomsday, // so mirroring is needed. model.transformation = Matrix4f::frame(front, up, !mirror); - if(asset.has(DEF_OFFSET)) + if (asset.has(DEF_OFFSET)) { model.offset = vectorFromValue(asset.get(DEF_OFFSET)); } @@ -409,15 +409,15 @@ DENG2_PIMPL(ModelRenderer) // Custom texture maps and additional materials. model.materialIndexForName.insert(MATERIAL_DEFAULT, 0); - if(asset.has(DEF_MATERIAL)) + if (asset.has(DEF_MATERIAL)) { asset.subrecord(DEF_MATERIAL).forSubrecords( [this, &model] (String const &blockName, Record const &block) { - if(ScriptedInfo::blockType(block) == DEF_VARIANT) + if (ScriptedInfo::blockType(block) == DEF_VARIANT) { String const materialName = blockName; - if(!model.materialIndexForName.contains(materialName)) + if (!model.materialIndexForName.contains(materialName)) { // Add a new material. model.materialIndexForName.insert(materialName, model.addMaterial()); @@ -439,14 +439,14 @@ DENG2_PIMPL(ModelRenderer) } // Set up the animation sequences for states. - if(asset.has(DEF_ANIMATION)) + if (asset.has(DEF_ANIMATION)) { auto states = ScriptedInfo::subrecordsOfType(DEF_STATE, asset.subrecord(DEF_ANIMATION)); DENG2_FOR_EACH_CONST(Record::Subrecords, state, states) { // Sequences are added in source order. auto seqs = ScriptedInfo::subrecordsOfType(DEF_SEQUENCE, *state.value()); - for(String key : ScriptedInfo::sortRecordsBySource(seqs)) + for (String key : ScriptedInfo::sortRecordsBySource(seqs)) { model.animations[state.key()] << render::Model::AnimSequence(key, *seqs[key]); } @@ -466,13 +466,13 @@ DENG2_PIMPL(ModelRenderer) String modelShader = SHADER_DEFAULT; // Rendering passes. - if(asset.has(DEF_RENDER)) + if (asset.has(DEF_RENDER)) { Record const &renderBlock = asset.subrecord(DEF_RENDER); modelShader = renderBlock.gets(DEF_SHADER, modelShader); auto passes = ScriptedInfo::subrecordsOfType(DEF_PASS, renderBlock); - for(String key : ScriptedInfo::sortRecordsBySource(passes)) + for (String key : ScriptedInfo::sortRecordsBySource(passes)) { try { @@ -482,7 +482,7 @@ DENG2_PIMPL(ModelRenderer) pass.name = key; pass.meshes.resize(model.meshCount()); - for(Value const *value : def.geta(DEF_MESHES).elements()) + for (Value const *value : def.geta(DEF_MESHES).elements()) { int meshId = identifierFromText(value->asText(), [&model] (String const &text) { return model.meshId(text); @@ -491,7 +491,7 @@ DENG2_PIMPL(ModelRenderer) } // GL state parameters. - if(def.has(DEF_BLENDFUNC)) + if (def.has(DEF_BLENDFUNC)) { ArrayValue const &blendDef = def.geta(DEF_BLENDFUNC); pass.blendFunc.first = textToBlendFunc(blendDef.at(0).asText()); @@ -508,7 +508,7 @@ DENG2_PIMPL(ModelRenderer) model.passes.append(pass); } - catch(Error const &er) + catch (Error const &er) { LOG_RES_ERROR("Rendering pass \"%s\" in asset \"%s\" is invalid: %s") << key << path << er.asText(); @@ -519,7 +519,7 @@ DENG2_PIMPL(ModelRenderer) // Rendering passes will always have programs associated with them. // However, if there are no passes, we need to set up the default // shader for the entire model. - if(model.passes.isEmpty()) + if (model.passes.isEmpty()) { try { @@ -527,7 +527,7 @@ DENG2_PIMPL(ModelRenderer) composeTextureMappings(textureMapping, ClientApp::shaders()[modelShader]); } - catch(Error const &er) + catch (Error const &er) { LOG_RES_ERROR("Asset \"%s\" cannot use shader \"%s\": %s") << path << modelShader << er.asText(); @@ -568,7 +568,7 @@ DENG2_PIMPL(ModelRenderer) String const &textureName, ModelDrawable::TextureMap map) { - if(matDef.has(textureName)) + if (matDef.has(textureName)) { String const path = ScriptedInfo::absolutePathInContext(matDef, matDef.gets(textureName)); model.setTexturePath(mesh, map, path); @@ -585,7 +585,7 @@ DENG2_PIMPL(ModelRenderer) [this, excludeSourceMobj] (VectorLightData const &vlight) { - if(excludeSourceMobj && vlight.sourceMobj == excludeSourceMobj) + if (excludeSourceMobj && vlight.sourceMobj == excludeSourceMobj) { // This source should not be included. return LoopContinue; @@ -605,7 +605,7 @@ DENG2_PIMPL(ModelRenderer) { lightCount = 0; - for(int i = 0; i < MAX_LIGHTS; ++i) + for (int i = 0; i < MAX_LIGHTS; ++i) { uLightDirs .set(i, Vector3f()); uLightIntensities.set(i, Vector4f()); @@ -614,7 +614,7 @@ DENG2_PIMPL(ModelRenderer) void addLight(Vector3f const &direction, Vector3f const &intensity) { - if(lightCount == MAX_LIGHTS) return; + if (lightCount == MAX_LIGHTS) return; int idx = lightCount; uLightDirs .set(idx, (inverseLocal * direction).normalize()); @@ -625,7 +625,7 @@ DENG2_PIMPL(ModelRenderer) void setupFog() { - if(fogParams.usingFog) + if (fogParams.usingFog) { uFogColor = Vector4f(fogParams.fogColor[0], fogParams.fogColor[1], @@ -663,7 +663,7 @@ DENG2_PIMPL(ModelRenderer) uReflectionMatrix = Matrix4f::rotate(-yawAngle, Vector3f(0, 1, 0)) * Matrix4f::rotate(pitchAngle, Vector3f(0, 0, 1)); - if(preModelToLocal) + if (preModelToLocal) { modelToLocal = modelToLocal * (*preModelToLocal); } @@ -702,7 +702,7 @@ DENG2_PIMPL(ModelRenderer) void setReflectionForObject(mobj_t const *object) { - if(object && Mobj_HasSubspace(*object)) + if (object && Mobj_HasSubspace(*object)) { setReflectionForCluster(&Mobj_Cluster(*object)); } @@ -744,7 +744,7 @@ ModelBank &ModelRenderer::bank() render::Model::StateAnims const *ModelRenderer::animations(DotPath const &modelId) const { auto const &model = d->bank.model(modelId); - if(!model.animations.isEmpty()) + if (!model.animations.isEmpty()) { return &model.animations; } @@ -828,7 +828,7 @@ int ModelRenderer::identifierFromText(String const &text, /// @todo This might be useful on a more general level, outside ModelRenderer. -jk int id = 0; - if(text.beginsWith('@')) + if (text.beginsWith('@')) { id = text.mid(1).toInt(); } @@ -843,9 +843,9 @@ int ModelRenderer::identifierFromText(String const &text, static render::StateAnimator &animatorInstance(Context &ctx) { - if(auto *self = ctx.selfInstance().get(Record::VAR_NATIVE_SELF).maybeAs()) + if (auto *self = ctx.selfInstance().get(Record::VAR_NATIVE_SELF).maybeAs()) { - if(auto *obj = self->nativeObject()) + if (auto *obj = self->nativeObject()) { return *obj; } @@ -858,7 +858,7 @@ static Value *Function_StateAnimator_PlayingSequences(Context &ctx, Function::Ar { render::StateAnimator &anim = animatorInstance(ctx); std::unique_ptr playing(new ArrayValue); - for(int i = 0; i < anim.count(); ++i) + for (int i = 0; i < anim.count(); ++i) { playing->add(new NumberValue(anim.at(i).animId)); } diff --git a/doomsday/apps/client/src/render/playerweaponanimator.cpp b/doomsday/apps/client/src/render/playerweaponanimator.cpp index 1bbe9be41c..7e3d9d54bf 100644 --- a/doomsday/apps/client/src/render/playerweaponanimator.cpp +++ b/doomsday/apps/client/src/render/playerweaponanimator.cpp @@ -48,13 +48,13 @@ DENG2_PIMPL_NOREF(PlayerWeaponAnimator) { angleOffset = Vector2f(); - if(animator) + if (animator) { animator->model().audienceForDeletion() -= this; } // Is there a model for the weapon? - if(modelBank().has(identifier)) + if (modelBank().has(identifier)) { // Prepare the animation state of the model. auto &model = modelBank().model(identifier); @@ -90,7 +90,7 @@ void PlayerWeaponAnimator::setAsset(String const &identifier) void PlayerWeaponAnimator::stateChanged(state_s const *state) { - if(d->animator) + if (d->animator) { d->animator->triggerByState(Def_GetStateName(state)); } @@ -127,9 +127,9 @@ void PlayerWeaponAnimator::setupVisPSprite(vispsprite_t &spr) const void PlayerWeaponAnimator::advanceTime(TimeDelta const &elapsed) { - if(clientPaused) return; + if (clientPaused) return; - if(d->animator) + if (d->animator) { d->animator->advanceTime(elapsed); } @@ -142,7 +142,7 @@ bool PlayerWeaponAnimator::hasModel() const Model const *PlayerWeaponAnimator::model() const { - if(!hasModel()) return nullptr; + if (!hasModel()) return nullptr; return &d->animator->model(); } diff --git a/doomsday/apps/client/src/render/shadervar.cpp b/doomsday/apps/client/src/render/shadervar.cpp new file mode 100644 index 0000000000..b56a7e7900 --- /dev/null +++ b/doomsday/apps/client/src/render/shadervar.cpp @@ -0,0 +1,172 @@ +/** @file render/shadervar.cpp Shader variable. + * + * @authors Copyright (c) 2016 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "render/shadervar.h" + +#include +#include +#include + +using namespace de; + +static String const DEF_WRAP("wrap"); + +/** + * Animatable variable bound to a GL uniform. The value can have 1...4 float + * components. + */ +void ShaderVar::init(float value) +{ + values.clear(); + values.append(Animation(value, Animation::Linear)); +} + +ShaderVar::~ShaderVar() +{ + delete uniform; +} + +float ShaderVar::currentValue(int index) const +{ + auto const &val = values.at(index); + float v = val.anim.value(); + if (val.wrap.isEmpty()) + { + return v; + } + return val.wrap.wrap(v); +} + +void ShaderVar::updateUniform() +{ + switch (values.size()) + { + case 1: + *uniform = currentValue(0); + break; + + case 2: + *uniform = Vector2f(currentValue(0), + currentValue(1)); + break; + + case 3: + *uniform = Vector3f(currentValue(0), + currentValue(1), + currentValue(2)); + break; + + case 4: + *uniform = Vector4f(currentValue(0), + currentValue(1), + currentValue(2), + currentValue(3)); + break; + } +} + +void ShaderVars::initVariableFromDefinition(String const &variableName, + Record const &valueDef, + Record &bindingNames) +{ + static char const *componentNames[] = { "x", "y", "z", "w" }; + + GLUniform::Type uniformType = GLUniform::Float; + std::unique_ptr var(new ShaderVar); + + // Initialize the appropriate type of value animation and uniform, + // depending on the "value" key in the definition. + Value const &initialValue = valueDef.get("value"); + if (auto const *array = initialValue.maybeAs()) + { + switch (array->size()) + { + default: + throw DefinitionError("StateAnimator::initVariables", + QString("%1: Invalid initial value size (%2) for render.variable") + .arg(ScriptedInfo::sourceLocation(valueDef)) + .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. + for (int k = 0; k < var->values.size(); ++k) + { + addBinding(bindingNames, + variableName.concatenateMember(componentNames[k]), + var->values[k].anim); + } + } + else + { + var->init(float(initialValue.asNumber())); + + // Expose in the namespace for scripts. + addBinding(bindingNames, variableName, var->values[0].anim); + } + + // Optional range wrapping. + if (valueDef.hasSubrecord(DEF_WRAP)) + { + for (int k = 0; k < 4; ++k) + { + String const varName = QString("%1.%2").arg(DEF_WRAP).arg(componentNames[k]); + if (valueDef.has(varName)) + { + var->values[k].wrap = rangeFromValue(valueDef.geta(varName)); + } + } + } + else if (valueDef.has(DEF_WRAP)) + { + var->values[0].wrap = rangeFromValue(valueDef.geta(DEF_WRAP)); + } + + // Uniform to be passed to the shader. + var->uniform = new GLUniform(variableName.toLatin1(), uniformType); + + // Compose a lookup for quickly finding the variables of each pass + // (by pass name). + members[variableName] = var.release(); +} + +ShaderVars::~ShaderVars() +{ + qDeleteAll(members.values()); +} + +void ShaderVars::addBinding(Record &names, String const &varName, Animation &anim) +{ + names.add(varName) + .set(new NativeValue(&anim, &ScriptSystem::builtInClass(QStringLiteral("Animation")))) + .setReadOnly(); +} diff --git a/doomsday/apps/client/src/render/stateanimator.cpp b/doomsday/apps/client/src/render/stateanimator.cpp index 26ddb7dd3c..f56e68ff77 100644 --- a/doomsday/apps/client/src/render/stateanimator.cpp +++ b/doomsday/apps/client/src/render/stateanimator.cpp @@ -18,6 +18,7 @@ #include "render/stateanimator.h" #include "render/rendersystem.h" +#include "render/shadervar.h" #include "clientapp.h" #include "dd_loop.h" @@ -42,7 +43,6 @@ static String const DEF_ALWAYS_TRIGGER("alwaysTrigger"); static String const DEF_RENDER ("render"); static String const DEF_PASS ("pass"); static String const DEF_VARIABLE ("variable"); -static String const DEF_WRAP ("wrap"); static String const DEF_ENABLED ("enabled"); static String const DEF_MATERIAL ("material"); static String const DEF_ANIMATION ("animation"); @@ -116,7 +116,7 @@ DENG2_PIMPL(StateAnimator) bool isRunning() const { - if(looping == Looping) + if (looping == Looping) { // Looping animations are always running. return true; @@ -137,87 +137,7 @@ DENG2_PIMPL(StateAnimator) QHash indexForPassName; QHash passForMaterialVariable; - /** - * Animatable variable bound to a GL uniform. The value can have 1...4 float - * components. - */ - struct RenderVar - { - struct Value { - Animation anim; - Rangef wrap; - - Value(Animation const &a) : anim(a) {} - }; - QList values; - GLUniform *uniform = nullptr; - - void init(float value) - { - values.clear(); - values.append(Animation(value, Animation::Linear)); - } - - template - void init(VecType const &vec) - { - values.clear(); - for(int i = 0; i < vec.size(); ++i) - { - values.append(Animation(vec[i], Animation::Linear)); - } - } - - ~RenderVar() - { - delete uniform; - } - - float currentValue(int index) const - { - auto const &val = values.at(index); - float v = val.anim.value(); - if(val.wrap.isEmpty()) - { - return v; - } - return val.wrap.wrap(v); - } - - /** - * Copies the current values to the uniform. - */ - void updateUniform() - { - switch(values.size()) - { - case 1: - *uniform = currentValue(0); - break; - - case 2: - *uniform = Vector2f(currentValue(0), - currentValue(1)); - break; - - case 3: - *uniform = Vector3f(currentValue(0), - currentValue(1), - currentValue(2)); - break; - - case 4: - *uniform = Vector4f(currentValue(0), - currentValue(1), - currentValue(2), - currentValue(3)); - break; - } - } - }; - - typedef QHash RenderVars; - QHash passVars; + QHash passVars; struct AnimVar { @@ -240,7 +160,7 @@ DENG2_PIMPL(StateAnimator) initVariables(); // Set up the model drawing parameters. - if(!self.model().passes.isEmpty()) + if (!self.model().passes.isEmpty()) { appearance.drawPasses = &self.model().passes; } @@ -269,14 +189,14 @@ DENG2_PIMPL(StateAnimator) // Clear lookups affected by the variables. indexForPassName.clear(); appearance.passMaterial.clear(); - if(!passCount) + if (!passCount) { // Material to be used with the default pass. appearance.passMaterial << 0; } else { - for(int i = 0; i < passCount; ++i) + for (int i = 0; i < passCount; ++i) { appearance.passMaterial << 0; } @@ -289,7 +209,7 @@ DENG2_PIMPL(StateAnimator) int passIndex = 0; auto const &def = names[VAR_ASSET].valueAsRecord(); - if(def.has(DEF_RENDER)) + if (def.has(DEF_RENDER)) { Record const &renderBlock = def.subrecord(DEF_RENDER); @@ -298,7 +218,7 @@ DENG2_PIMPL(StateAnimator) // Each rendering pass is represented by a subrecord, named // according the to the pass names. auto passes = ScriptedInfo::subrecordsOfType(DEF_PASS, renderBlock); - for(String passName : ScriptedInfo::sortRecordsBySource(passes)) + for (String passName : ScriptedInfo::sortRecordsBySource(passes)) { Record const &passDef = *passes[passName]; @@ -313,10 +233,10 @@ DENG2_PIMPL(StateAnimator) } } - if(def.has(DEF_ANIMATION)) + if (def.has(DEF_ANIMATION)) { auto varDefs = ScriptedInfo::subrecordsOfType(DEF_VARIABLE, def.subrecord(DEF_ANIMATION)); - for(String varName : varDefs.keys()) + for (String varName : varDefs.keys()) { initAnimationVariable(varName, *varDefs[varName]); } @@ -341,12 +261,21 @@ DENG2_PIMPL(StateAnimator) /// @todo Should observe if the variable above is deleted unexpectedly. -jk + ShaderVars *vars = passVars[passName]; + if (!vars) + { + vars = new ShaderVars; + passVars.insert(passName, vars); + } + // Create the animated variables to be used with the shader based // on the pass definitions. - auto vars = ScriptedInfo::subrecordsOfType(DEF_VARIABLE, block); - for(auto i = vars.constBegin(); i != vars.constEnd(); ++i) + auto varDefs = ScriptedInfo::subrecordsOfType(DEF_VARIABLE, block); + for (auto i = varDefs.constBegin(); i != varDefs.constEnd(); ++i) { - initRenderVariable(i.key(), *i.value(), passName); + vars->initVariableFromDefinition( + i.key(), *i.value(), + names.addSubrecord(passName, Record::KeepExisting)); } } @@ -370,93 +299,13 @@ DENG2_PIMPL(StateAnimator) // animation sequences so that the variables are applied. self.setFlags(AlwaysTransformNodes); } - catch(Error const &er) + catch (Error const &er) { LOG_GL_WARNING("%s: %s") << ScriptedInfo::sourceLocation(variableDef) << er.asText(); } } - void initRenderVariable(String const &variableName, - Record const &valueDef, - String const &passName) - { - static char const *componentNames[] = { "x", "y", "z", "w" }; - - GLUniform::Type uniformType = GLUniform::Float; - std::unique_ptr var(new RenderVar); - - // Initialize the appropriate type of value animation and uniform, - // depending on the "value" key in the definition. - Value const &initialValue = valueDef.get("value"); - if(auto const *array = initialValue.maybeAs()) - { - switch(array->size()) - { - default: - throw DefinitionError("StateAnimator::initVariables", - QString("%1: Invalid initial value size (%2) for render.variable") - .arg(ScriptedInfo::sourceLocation(valueDef)) - .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. - for(int k = 0; k < var->values.size(); ++k) - { - addBinding(passName.concatenateMember(String(variableName) - .concatenateMember(componentNames[k])), - var->values[k].anim); - } - } - else - { - var->init(float(initialValue.asNumber())); - - // Expose in the namespace for scripts. - addBinding(passName.concatenateMember(variableName), - var->values[0].anim); - } - - // Optional range wrapping. - if(valueDef.hasSubrecord(DEF_WRAP)) - { - for(int k = 0; k < 4; ++k) - { - String const varName = QString("%1.%2").arg(DEF_WRAP).arg(componentNames[k]); - if(valueDef.has(varName)) - { - var->values[k].wrap = rangeFromValue(valueDef.geta(varName)); - } - } - } - else if(valueDef.has(DEF_WRAP)) - { - var->values[0].wrap = rangeFromValue(valueDef.geta(DEF_WRAP)); - } - - // Uniform to be passed to the shader. - var->uniform = new GLUniform(variableName.toLatin1(), uniformType); - - // Compose a lookup for quickly finding the variables of each pass - // (by pass name). - passVars[passName][variableName] = var.release(); - } - void addBinding(String const &varName, Animation &anim) { names.add(varName) @@ -469,10 +318,7 @@ DENG2_PIMPL(StateAnimator) appearance.passMaterial.clear(); // Shader variables. - for(RenderVars const &vars : passVars.values()) - { - qDeleteAll(vars.values()); - } + qDeleteAll(passVars.values()); passVars.clear(); // Animator variables. @@ -483,10 +329,10 @@ DENG2_PIMPL(StateAnimator) Variable const &materialVariableForPass(duint passIndex) const { auto const &model = self.model(); - if(!model.passes.isEmpty()) + if (!model.passes.isEmpty()) { String const varName = model.passes.at(passIndex).name.concatenateMember(VAR_MATERIAL); - if(names.has(varName)) + if (names.has(varName)) { return names[varName]; } @@ -496,7 +342,7 @@ DENG2_PIMPL(StateAnimator) void updatePassMaterials() { - for(int i = 0; i < appearance.passMaterial.size(); ++i) + for (int i = 0; i < appearance.passMaterial.size(); ++i) { appearance.passMaterial[i] = materialForUserProvidedName( materialVariableForPass(i).value().asText()); @@ -505,11 +351,11 @@ DENG2_PIMPL(StateAnimator) void variableValueChanged(Variable &var, Value const &newValue) { - if(var.name() == VAR_MATERIAL) + if (var.name() == VAR_MATERIAL) { // Update the corresponding pass material. int passIndex = passForMaterialVariable[&var]; - if(passIndex < 0) + if (passIndex < 0) { updatePassMaterials(); } @@ -539,7 +385,7 @@ DENG2_PIMPL(StateAnimator) { auto const &model = self.model(); auto const matIndex = model.materialIndexForName.constFind(materialName); - if(matIndex != model.materialIndexForName.constEnd()) + if (matIndex != model.materialIndexForName.constEnd()) { return matIndex.value(); } @@ -555,7 +401,7 @@ DENG2_PIMPL(StateAnimator) }); appearance.passMask.fill(false); - for(String name : enabledPasses.keys()) + for (String name : enabledPasses.keys()) { DENG2_ASSERT(indexForPassName.contains(name)); appearance.passMask.setBit(indexForPassName[name], true); @@ -600,15 +446,15 @@ DENG2_PIMPL(StateAnimator) auto const &modelRenderer = ClientApp::renderSystem().modelRenderer(); auto const vars = passVars.constFind(passName); - if(vars != passVars.constEnd()) + if (vars != passVars.constEnd()) { - for(auto i : vars.value()) + for (auto i : vars.value()->members) { - if(!hasDeclaredVariable(modelRenderer.shaderDefinition(program), + if (!hasDeclaredVariable(modelRenderer.shaderDefinition(program), *i->uniform)) continue; - if(operation == Bind) + if (operation == Bind) { i->updateUniform(); program.bind(*i->uniform); @@ -632,7 +478,7 @@ DENG2_PIMPL(StateAnimator) { Sequence &anim = self.start(spec.animId, spec.node).as(); anim.apply(spec); - if(anim.timeline) + if (anim.timeline) { anim.clock.reset(new Scheduler::Clock(*anim.timeline, &names)); } @@ -657,7 +503,7 @@ void StateAnimator::setOwnerNamespace(Record &names, String const &varName) d->names.add(varName).set(new RecordValue(names)); // Call the onInit() function if there is one. - if(d->names.has(QStringLiteral("ASSET.onInit"))) + if (d->names.has(QStringLiteral("ASSET.onInit"))) { Record ns; ns.add(QStringLiteral("self")).set(new RecordValue(d->names)); @@ -673,23 +519,23 @@ void StateAnimator::triggerByState(String const &stateName) // No animations can be triggered if none are available. auto const *stateAnims = &model().animations; - if(!stateAnims) return; + if (!stateAnims) return; auto found = stateAnims->constFind(stateName); - if(found == stateAnims->constEnd()) return; + if (found == stateAnims->constEnd()) return; LOG_AS("StateAnimator"); //LOGDEV_GL_XVERBOSE("triggerByState: ") << stateName; d->currentStateName = stateName; - foreach(Model::AnimSequence const &seq, found.value()) + foreach (Model::AnimSequence const &seq, found.value()) { try { // Test for the probability of this animation. float chance = seq.def->getf(DEF_PROBABILITY, 1.f); - if(frand() > chance) continue; + if (frand() > chance) continue; // Start the animation on the specified node (defaults to root), // unless it is already running. @@ -697,22 +543,22 @@ void StateAnimator::triggerByState(String const &stateName) int animId = d->animationId(seq.name); bool const alwaysTrigger = ScriptedInfo::isTrue(*seq.def, DEF_ALWAYS_TRIGGER, false); - if(!alwaysTrigger) + if (!alwaysTrigger) { // Do not restart running sequences. /// @todo Only restart the animation if the current state is not the expected /// one (checking the state cycle). - if(isRunning(animId, node)) continue; + if (isRunning(animId, node)) continue; } int const priority = seq.def->geti(DEF_PRIORITY, ANIM_DEFAULT_PRIORITY); // Look up the timeline. Scheduler *timeline = seq.timeline; - if(!seq.sharedTimeline.isEmpty()) + if (!seq.sharedTimeline.isEmpty()) { auto tl = model().timelines.constFind(seq.sharedTimeline); - if(tl != model().timelines.constEnd()) + if (tl != model().timelines.constEnd()) { timeline = tl.value(); } @@ -726,9 +572,9 @@ void StateAnimator::triggerByState(String const &stateName) timeline); // Do not override higher-priority animations. - if(auto *existing = find(node)->maybeAs()) + if (auto *existing = find(node)->maybeAs()) { - if(priority < existing->priority) + if (priority < existing->priority) { // This will be started once the higher-priority animation // has finished. @@ -742,7 +588,7 @@ void StateAnimator::triggerByState(String const &stateName) // Start a new sequence. d->start(anim); } - catch(ModelDrawable::Animator::InvalidError const &er) + catch (ModelDrawable::Animator::InvalidError const &er) { LOGDEV_GL_WARNING("Failed to start animation \"%s\": %s") << seq.name << er.asText(); @@ -762,7 +608,7 @@ void StateAnimator::triggerDamage(int points, struct mobj_s const *inflictor) * variable holds a direct pointer to the asset definition, where the * function is defined. */ - if(d->names.has(QStringLiteral("ASSET.onDamage"))) + if (d->names.has(QStringLiteral("ASSET.onDamage"))) { /* * We need to provide the StateAnimator instance to the script as an @@ -795,17 +641,17 @@ void StateAnimator::advanceTime(TimeDelta const &elapsed) bool retrigger = false; // Update animation variables values. - for(auto *var : d->animVars.values()) + for (auto *var : d->animVars.values()) { var->angle.shift(var->speed * elapsed); // Keep the angle in the 0..360 range. float varAngle = var->angle; - if(varAngle > 360) var->angle.shift(-360); - else if(varAngle < 0) var->angle.shift(+360); + if (varAngle > 360) var->angle.shift(-360); + else if (varAngle < 0) var->angle.shift(+360); } - for(int i = 0; i < count(); ++i) + for (int i = 0; i < count(); ++i) { auto &anim = at(i).as(); ddouble factor = 1.0; @@ -815,21 +661,21 @@ void StateAnimator::advanceTime(TimeDelta const &elapsed) TimeDelta animElapsed = factor * elapsed; anim.time += animElapsed; - if(anim.looping == Sequence::NotLooping) + if (anim.looping == Sequence::NotLooping) { // Clamp at the end. anim.time = min(anim.time, anim.duration); } - if(anim.looping == Sequence::Looping) + if (anim.looping == Sequence::Looping) { // When a looping animation has completed a loop, it may still // trigger a variant. - if(anim.atEnd()) + if (anim.atEnd()) { retrigger = true; anim.time -= anim.duration; // Trigger only once per loop. - if(anim.clock) + if (anim.clock) { anim.clock->rewind(anim.time); } @@ -837,27 +683,27 @@ void StateAnimator::advanceTime(TimeDelta const &elapsed) } // Scheduled events. - if(anim.clock) + if (anim.clock) { anim.clock->advanceTime(animElapsed); } // Stop finished animations. - if(!anim.isRunning()) + if (!anim.isRunning()) { String const node = anim.node; // Keep the last animation intact so there's something to update the // model state with (ModelDrawable being shared with multiple objects, // so each animator needs to retain its bone transformations). - if(count() > 1) + if (count() > 1) { stop(i--); // `anim` gets deleted } // Start a previously triggered pending animation. auto &pending = d->pendingAnimForNode; - if(pending.contains(node)) + if (pending.contains(node)) { LOG_GL_VERBOSE("Starting pending animation %i") << pending[node].animId; d->start(pending[node]); @@ -866,7 +712,7 @@ void StateAnimator::advanceTime(TimeDelta const &elapsed) } } - if(retrigger && !d->currentStateName.isEmpty()) + if (retrigger && !d->currentStateName.isEmpty()) { triggerByState(d->currentStateName); } @@ -882,7 +728,7 @@ ddouble StateAnimator::currentTime(int index) const Vector4f StateAnimator::extraRotationForNode(String const &nodeName) const { auto found = d->animVars.constFind(nodeName); - if(found != d->animVars.constEnd()) + if (found != d->animVars.constEnd()) { Instance::AnimVar const &var = *found.value(); return Vector4f(var.axis, var.angle);