diff --git a/CHANGELOG.md b/CHANGELOG.md index da0aaa762..73fc0d53a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,6 +157,7 @@ Feature #5051: Provide a separate textures for scrollbars Feature #5094: Unix like console hotkeys Feature #5098: Allow user controller bindings + Feature #5122: Use magic glow for enchanted arrows Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption Task #4789: Optimize cell transitions diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index a99fd7242..df817113a 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -79,7 +79,7 @@ PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std: return PartHolderPtr(); if (enchantedGlow) - addGlow(instance, *glowColor); + mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); return PartHolderPtr(new PartHolder(instance)); } @@ -272,7 +272,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) if (isEnchanted) { osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); - addGlow(weaponNode, glowColor); + mGlowUpdater = SceneUtil::addEnchantedGlow(weaponNode, mResourceSystem, glowColor); } } } @@ -354,7 +354,7 @@ void ActorAnimation::updateQuiver() osg::ref_ptr arrowNode = ammoNode->getChild(i)->asGroup(); osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); if (!ammo->getClass().getEnchantment(*ammo).empty()) - addGlow(arrow, glowColor); + mGlowUpdater = SceneUtil::addEnchantedGlow(arrow, mResourceSystem, glowColor); } } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index a349f4c86..8f8d7e245 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include #include #include @@ -16,10 +14,8 @@ #include -#include #include #include -#include #include @@ -519,135 +515,6 @@ namespace MWRender float mAlpha; }; - class GlowUpdater : public SceneUtil::StateSetUpdater - { - public: - GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector >& textures, - osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) - : mTexUnit(texUnit) - , mColor(color) - , mOriginalColor(color) - , mTextures(textures) - , mNode(node) - , mDuration(duration) - , mOriginalDuration(duration) - , mStartingTime(0) - , mResourceSystem(resourcesystem) - , mColorChanged(false) - , mDone(false) - { - } - - virtual void setDefaults(osg::StateSet *stateset) - { - if (mDone) - removeTexture(stateset); - else - { - stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); - osg::TexGen* texGen = new osg::TexGen; - texGen->setMode(osg::TexGen::SPHERE_MAP); - - stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); - texEnv->setConstantColor(mColor); - texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); - texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); - - stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("envMapColor", mColor)); - } - } - - void removeTexture(osg::StateSet* stateset) - { - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXENV); - stateset->removeTextureMode(mTexUnit, GL_TEXTURE_2D); - stateset->removeUniform("envMapColor"); - - osg::StateSet::TextureAttributeList& list = stateset->getTextureAttributeList(); - while (list.size() && list.rbegin()->empty()) - list.pop_back(); - } - - virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) - { - if (mColorChanged){ - this->reset(); - setDefaults(stateset); - mColorChanged = false; - } - if (mDone) - return; - - // Set the starting time to measure glow duration from if this is a temporary glow - if ((mDuration >= 0) && mStartingTime == 0) - mStartingTime = nv->getFrameStamp()->getSimulationTime(); - - float time = nv->getFrameStamp()->getSimulationTime(); - int index = (int)(time*16) % mTextures.size(); - stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - if ((mDuration >= 0) && (time - mStartingTime > mDuration)) // If this is a temporary glow and it has finished its duration - { - if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation - { - removeTexture(stateset); - this->reset(); - mDone = true; - mResourceSystem->getSceneManager()->recreateShaders(mNode); - } - if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow - { - mDuration = mOriginalDuration; - mStartingTime = 0; - mColor = mOriginalColor; - this->reset(); - setDefaults(stateset); - } - } - } - - bool isPermanentGlowUpdater() - { - return (mDuration < 0); - } - - bool isDone() - { - return mDone; - } - - void setColor(const osg::Vec4f& color) - { - mColor = color; - mColorChanged = true; - } - - void setDuration(float duration) - { - mDuration = duration; - } - - private: - int mTexUnit; - osg::Vec4f mColor; - osg::Vec4f mOriginalColor; // for restoring the color of a permanent glow after a temporary glow on the object finishes - std::vector > mTextures; - osg::Node* mNode; - float mDuration; - float mOriginalDuration; // for recording that this is originally a permanent glow if it is changed to a temporary one - float mStartingTime; - Resource::ResourceSystem* mResourceSystem; - bool mColorChanged; - bool mDone; - }; - struct Animation::AnimSource { osg::ref_ptr mKeyframes; @@ -1574,25 +1441,6 @@ namespace MWRender return mObjectRoot.get(); } - class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor - { - public: - FindLowestUnusedTexUnitVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mLowestUnusedTexUnit(0) - { - } - - virtual void apply(osg::Node& node) - { - if (osg::StateSet* stateset = node.getStateSet()) - mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); - - traverse(node); - } - int mLowestUnusedTexUnit; - }; - void Animation::addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration) { osg::Vec4f glowColor(1,1,1,1); @@ -1611,51 +1459,8 @@ namespace MWRender mGlowUpdater->setDuration(glowDuration); } else - addGlow(mObjectRoot, glowColor, glowDuration); - } - } - - void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration) - { - std::vector > textures; - for (int i=0; i<32; ++i) - { - std::stringstream stream; - stream << "textures/magicitem/caust"; - stream << std::setw(2); - stream << std::setfill('0'); - stream << i; - stream << ".dds"; - - osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); - osg::ref_ptr tex (new osg::Texture2D(image)); - tex->setName("envMap"); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); - mResourceSystem->getSceneManager()->applyFilterSettings(tex); - textures.push_back(tex); - } - - FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; - node->accept(findLowestUnusedTexUnitVisitor); - int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - - osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); - mGlowUpdater = glowUpdater; - node->addUpdateCallback(glowUpdater); - - // set a texture now so that the ShaderVisitor can find it - osg::ref_ptr writableStateSet = nullptr; - if (!node->getStateSet()) - writableStateSet = node->getOrCreateStateSet(); - else - { - writableStateSet = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); - node->setStateSet(writableStateSet); + mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, glowColor, glowDuration); } - writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); - writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); - mResourceSystem->getSceneManager()->recreateShaders(node); } void Animation::addExtraLight(osg::ref_ptr parent, const ESM::Light *esmLight) @@ -1976,7 +1781,7 @@ namespace MWRender addAnimSource(model, model); if (!ptr.getClass().getEnchantment(ptr).empty()) - addGlow(mObjectRoot, ptr.getClass().getEnchantmentColor(ptr)); + mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); } if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight) addExtraLight(getOrCreateObjectRoot(), ptr.get()->mBase); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index bf142689e..763c7a917 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -4,6 +4,7 @@ #include "../mwworld/ptr.hpp" #include +#include namespace ESM { @@ -34,7 +35,6 @@ namespace MWRender class ResetAccumRootCallback; class RotateController; -class GlowUpdater; class TransparencyUpdater; class EffectAnimationTime : public SceneUtil::ControllerSource @@ -266,7 +266,7 @@ protected: bool mHasMagicEffects; osg::ref_ptr mGlowLight; - osg::ref_ptr mGlowUpdater; + osg::ref_ptr mGlowUpdater; osg::ref_ptr mTransparencyUpdater; float mAlpha; @@ -330,8 +330,6 @@ protected: */ virtual void addControllers(); - void addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration = -1); - /// Set the render bin for this animation's object root. May be customized by subclasses. virtual void setRenderBin(); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 5115e8cf3..db88c3487 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -132,7 +132,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) scene.reset(new PartHolder(attached)); if (!item.getClass().getEnchantment(item).empty()) - addGlow(attached, item.getClass().getEnchantmentColor(item)); + mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, item.getClass().getEnchantmentColor(item)); // Crossbows start out with a bolt attached // FIXME: code duplicated from NpcAnimation @@ -180,7 +180,7 @@ void CreatureWeaponAnimation::attachArrow() { osg::Group* bone = getArrowBone(); if (bone != nullptr && bone->getNumChildren()) - addGlow(bone->getChild(0), ammo->getClass().getEnchantmentColor(*ammo)); + SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); } updateQuiver(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 378cdc038..86fc54f68 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -664,7 +664,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second); if (enchantedGlow) - addGlow(attached, *glowColor); + mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); return PartHolderPtr(new PartHolder(attached)); } @@ -954,7 +954,7 @@ void NpcAnimation::attachArrow() { osg::Group* bone = getArrowBone(); if (bone != nullptr && bone->getNumChildren()) - addGlow(bone->getChild(0), ammo->getClass().getEnchantmentColor(*ammo)); + SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); } updateQuiver(); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 19a255d8d..e7a3e8af1 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -323,6 +323,8 @@ namespace MWWorld MWWorld::Ptr ptr = ref.getPtr(); createModel(state, ptr.getClass().getModel(ptr), pos, orient, false, false, osg::Vec4(0,0,0,0)); + if (!ptr.getClass().getEnchantment(ptr).empty()) + SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); mProjectiles.push_back(state); } diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index a9857bed3..ff46329e5 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -1,10 +1,151 @@ #include "util.hpp" +#include +#include +#include + #include +#include +#include +#include + +#include +#include namespace SceneUtil { +class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor +{ +public: + FindLowestUnusedTexUnitVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mLowestUnusedTexUnit(0) + { + } + + virtual void apply(osg::Node& node) + { + if (osg::StateSet* stateset = node.getStateSet()) + mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); + + traverse(node); + } + int mLowestUnusedTexUnit; +}; + +GlowUpdater::GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector >& textures, + osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) + : mTexUnit(texUnit) + , mColor(color) + , mOriginalColor(color) + , mTextures(textures) + , mNode(node) + , mDuration(duration) + , mOriginalDuration(duration) + , mStartingTime(0) + , mResourceSystem(resourcesystem) + , mColorChanged(false) + , mDone(false) +{ +} + +void GlowUpdater::setDefaults(osg::StateSet *stateset) +{ + if (mDone) + removeTexture(stateset); + else + { + stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); + osg::TexGen* texGen = new osg::TexGen; + texGen->setMode(osg::TexGen::SPHERE_MAP); + + stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setConstantColor(mColor); + texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); + texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); + + stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("envMapColor", mColor)); + } +} + +void GlowUpdater::removeTexture(osg::StateSet* stateset) +{ + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXENV); + stateset->removeTextureMode(mTexUnit, GL_TEXTURE_2D); + stateset->removeUniform("envMapColor"); + + osg::StateSet::TextureAttributeList& list = stateset->getTextureAttributeList(); + while (list.size() && list.rbegin()->empty()) + list.pop_back(); +} + +void GlowUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) +{ + if (mColorChanged){ + this->reset(); + setDefaults(stateset); + mColorChanged = false; + } + if (mDone) + return; + + // Set the starting time to measure glow duration from if this is a temporary glow + if ((mDuration >= 0) && mStartingTime == 0) + mStartingTime = nv->getFrameStamp()->getSimulationTime(); + + float time = nv->getFrameStamp()->getSimulationTime(); + int index = (int)(time*16) % mTextures.size(); + stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + if ((mDuration >= 0) && (time - mStartingTime > mDuration)) // If this is a temporary glow and it has finished its duration + { + if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation + { + removeTexture(stateset); + this->reset(); + mDone = true; + mResourceSystem->getSceneManager()->recreateShaders(mNode); + } + if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow + { + mDuration = mOriginalDuration; + mStartingTime = 0; + mColor = mOriginalColor; + this->reset(); + setDefaults(stateset); + } + } +} + +bool GlowUpdater::isPermanentGlowUpdater() +{ + return (mDuration < 0); +} + +bool GlowUpdater::isDone() +{ + return mDone; +} + +void GlowUpdater::setColor(const osg::Vec4f& color) +{ + mColor = color; + mColorChanged = true; +} + +void GlowUpdater::setDuration(float duration) +{ + mDuration = duration; +} + void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere) { osg::BoundingSphere::vec_type xdash = bsphere._center; @@ -73,4 +214,48 @@ bool hasUserDescription(const osg::Node* node, const std::string pattern) return false; } +osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration) +{ + std::vector > textures; + for (int i=0; i<32; ++i) + { + std::stringstream stream; + stream << "textures/magicitem/caust"; + stream << std::setw(2); + stream << std::setfill('0'); + stream << i; + stream << ".dds"; + + osg::ref_ptr image = resourceSystem->getImageManager()->getImage(stream.str()); + osg::ref_ptr tex (new osg::Texture2D(image)); + tex->setName("envMap"); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); + resourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); + } + + FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; + node->accept(findLowestUnusedTexUnitVisitor); + int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; + + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, resourceSystem); + node->addUpdateCallback(glowUpdater); + + // set a texture now so that the ShaderVisitor can find it + osg::ref_ptr writableStateSet = nullptr; + if (!node->getStateSet()) + writableStateSet = node->getOrCreateStateSet(); + else + { + writableStateSet = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + node->setStateSet(writableStateSet); + } + writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + resourceSystem->getSceneManager()->recreateShaders(node); + + return glowUpdater; +} + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index f293b9975..5cc8e3a9d 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -3,10 +3,48 @@ #include #include +#include +#include #include +#include + +#include "statesetupdater.hpp" + namespace SceneUtil { + class GlowUpdater : public SceneUtil::StateSetUpdater + { + public: + GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector >& textures, + osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem); + + virtual void setDefaults(osg::StateSet *stateset); + + void removeTexture(osg::StateSet* stateset); + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv); + + bool isPermanentGlowUpdater(); + + bool isDone(); + + void setColor(const osg::Vec4f& color); + + void setDuration(float duration); + + private: + int mTexUnit; + osg::Vec4f mColor; + osg::Vec4f mOriginalColor; // for restoring the color of a permanent glow after a temporary glow on the object finishes + std::vector > mTextures; + osg::Node* mNode; + float mDuration; + float mOriginalDuration; // for recording that this is originally a permanent glow if it is changed to a temporary one + float mStartingTime; + Resource::ResourceSystem* mResourceSystem; + bool mColorChanged; + bool mDone; + }; // Transform a bounding sphere by a matrix // based off private code in osg::Transform @@ -20,6 +58,8 @@ namespace SceneUtil float makeOsgColorComponent (unsigned int value, unsigned int shift); bool hasUserDescription(const osg::Node* node, const std::string pattern); + + osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration=-1); } #endif