diff --git a/apps/nifosgtest/test.cpp b/apps/nifosgtest/test.cpp index 487a91890..6d0586775 100644 --- a/apps/nifosgtest/test.cpp +++ b/apps/nifosgtest/test.cpp @@ -14,6 +14,8 @@ #include +#include + #include #include @@ -129,6 +131,9 @@ int main(int argc, char** argv) Resource::TextureManager texMgr(&resourceMgr); newNode->addChild(loader.load(nif, &texMgr)); + SceneUtil::AssignControllerSourcesVisitor visitor(boost::shared_ptr(new SceneUtil::FrameTimeSource)); + newNode->accept(visitor); + osg::PositionAttitudeTransform* trans = new osg::PositionAttitudeTransform; root->addChild(trans); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 91b1ab1c5..03a4181b5 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -20,7 +20,7 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - actors objects renderingmanager animation sky npcanimation + actors objects renderingmanager animation sky npcanimation vismask # debugging camera creatureanimation activatoranimation # renderinginterface localmap occlusionquery water shadows # characterpreview globalmap ripplesimulation refraction diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3aeaa2ed1..2febf9c9a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -10,13 +10,18 @@ #include #include +#include + #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" +#include "vismask.hpp" + namespace { @@ -62,6 +67,30 @@ namespace std::vector > mTextures; }; + class FindMaxControllerLengthVisitor : public SceneUtil::ControllerVisitor + { + public: + FindMaxControllerLengthVisitor() + : SceneUtil::ControllerVisitor() + , mMaxLength(0) + { + } + + virtual void visit(osg::Node& , SceneUtil::Controller& ctrl) + { + if (ctrl.mFunction) + mMaxLength = std::max(mMaxLength, ctrl.mFunction->getMaximum()); + } + + float getMaxLength() const + { + return mMaxLength; + } + + private: + float mMaxLength; + }; + } namespace MWRender @@ -83,6 +112,8 @@ namespace MWRender osg::Vec3f Animation::runAnimation(float duration) { + updateEffects(duration); + return osg::Vec3f(); } @@ -147,6 +178,133 @@ namespace MWRender return result; } + void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, std::string texture) + { + // Early out if we already have this effect + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) + return; + + EffectParams params; + params.mModelName = model; + osg::ref_ptr parentNode; + if (bonename.empty()) + parentNode = mObjectRoot->asGroup(); + else + { + SceneUtil::FindByNameVisitor visitor(bonename); + mObjectRoot->accept(visitor); + if (!visitor.mFoundNode) + throw std::runtime_error("Can't find bone " + bonename); + parentNode = visitor.mFoundNode; + } + osg::ref_ptr node = mResourceSystem->getSceneManager()->createInstance(model, parentNode); + params.mObjects = PartHolderPtr(new PartHolder(node)); + + FindMaxControllerLengthVisitor findMaxLengthVisitor; + node->accept(findMaxLengthVisitor); + + params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); + + node->setNodeMask(Mask_Effect); + + params.mLoop = loop; + params.mEffectId = effectId; + params.mBoneName = bonename; + + params.mAnimTime = boost::shared_ptr(new EffectAnimationTime); + + SceneUtil::AssignControllerSourcesVisitor assignVisitor(boost::shared_ptr(params.mAnimTime)); + node->accept(assignVisitor); + + if (!texture.empty()) + { + std::string correctedTexture = Misc::ResourceHelpers::correctTexturePath(texture, mResourceSystem->getVFS()); + // Not sure if wrap settings should be pulled from the overridden texture? + osg::ref_ptr tex = mResourceSystem->getTextureManager()->getTexture2D(correctedTexture, osg::Texture2D::CLAMP, + osg::Texture2D::CLAMP); + osg::ref_ptr stateset; + if (node->getStateSet()) + stateset = static_cast(node->getStateSet()->clone(osg::CopyOp::SHALLOW_COPY)); + else + stateset = new osg::StateSet; + + stateset->setTextureAttribute(0, tex, osg::StateAttribute::OVERRIDE); + + node->setStateSet(stateset); + } + + // TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box. + + mEffects.push_back(params); + } + + void Animation::removeEffect(int effectId) + { + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mEffectId == effectId) + { + mEffects.erase(it); + return; + } + } + } + + void Animation::getLoopingEffects(std::vector &out) + { + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mLoop) + out.push_back(it->mEffectId); + } + } + + void Animation::updateEffects(float duration) + { + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) + { + it->mAnimTime->addTime(duration); + + if (it->mAnimTime->getTime() >= it->mMaxControllerLength) + { + if (it->mLoop) + { + // Start from the beginning again; carry over the remainder + // Not sure if this is actually needed, the controller function might already handle loops + float remainder = it->mAnimTime->getTime() - it->mMaxControllerLength; + it->mAnimTime->resetTime(remainder); + } + else + { + it = mEffects.erase(it); + continue; + } + } + ++it; + } + } + + float Animation::EffectAnimationTime::getValue(osg::NodeVisitor*) + { + return mTime; + } + + void Animation::EffectAnimationTime::addTime(float duration) + { + mTime += duration; + } + + void Animation::EffectAnimationTime::resetTime(float time) + { + mTime = time; + } + + float Animation::EffectAnimationTime::getTime() const + { + return mTime; + } + // -------------------------------------------------------------------------------- ObjectAnimation::ObjectAnimation(const MWWorld::Ptr &ptr, const std::string &model, Resource::ResourceSystem* resourceSystem) @@ -159,11 +317,6 @@ namespace MWRender if (!ptr.getClass().getEnchantment(ptr).empty()) addGlow(mObjectRoot, getEnchantmentColor(ptr)); } - else - { - // No model given. Create an object root anyway, so that lights can be added to it if needed. - //mObjectRoot = NifOgre::ObjectScenePtr (new NifOgre::ObjectScene(mInsert->getCreator())); - } } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 1feab7bf3..ccc4bfa23 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -61,17 +61,18 @@ protected: virtual void setValue(Ogre::Real value); }; - class EffectAnimationTime : public Ogre::ControllerValue + class EffectAnimationTime : public SceneUtil::ControllerSource { private: float mTime; public: - EffectAnimationTime() : mTime(0) { } - void addTime(float time) { mTime += time; } - void resetTime(float value) { mTime = value; } + virtual float getValue(osg::NodeVisitor* nv); - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value); + void addTime(float duration); + void resetTime(float time); + float getTime() const; + + EffectAnimationTime() : mTime(0) { } }; class NullAnimationTime : public SceneUtil::ControllerSource @@ -124,6 +125,44 @@ protected: Resource::ResourceSystem* mResourceSystem; + /// @brief Detaches the node from its parent when the object goes out of scope. + class PartHolder + { + public: + PartHolder(osg::ref_ptr node) + : mNode(node) + { + } + + ~PartHolder() + { + if (mNode->getNumParents()) + mNode->getParent(0)->removeChild(mNode); + } + + osg::ref_ptr getNode() + { + return mNode; + } + + private: + osg::ref_ptr mNode; + }; + typedef boost::shared_ptr PartHolderPtr; + + struct EffectParams + { + std::string mModelName; // Just here so we don't add the same effect twice + PartHolderPtr mObjects; + boost::shared_ptr mAnimTime; + float mMaxControllerLength; + int mEffectId; + bool mLoop; + std::string mBoneName; + }; + + std::vector mEffects; + /* Sets the appropriate animations on the bone groups based on priority. */ //void resetActiveGroups(); @@ -192,12 +231,12 @@ public: * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead - * @param texture override the texture specified in the model's materials + * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ - //void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); - //void removeEffect (int effectId); - //void getLoopingEffects (std::vector& out); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); + void removeEffect (int effectId); + void getLoopingEffects (std::vector& out); //void updatePtr(const MWWorld::Ptr &ptr); @@ -273,6 +312,9 @@ public: //float getVelocity(const std::string &groupname) const; virtual osg::Vec3f runAnimation(float duration); + + /// This is typically called as part of runAnimation, but may be called manually if needed. + void updateEffects(float duration); }; class ObjectAnimation : public Animation { diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e30c701fc..c413d9334 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -587,12 +587,15 @@ public: } }; -PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, int group, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) +Animation::PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, int group, const std::string& bonename, const std::string& bonefilter, bool enchantedGlow, osg::Vec4f* glowColor) { osg::ref_ptr instance = mResourceSystem->getSceneManager()->createInstance(model); osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, bonename); if (enchantedGlow) addGlow(attached, *glowColor); + + // TODO: set group userdata for inventory picking + return PartHolderPtr(new PartHolder(attached)); } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 2605d58e3..16bd45cc4 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -50,26 +50,6 @@ public: }; */ -/// @brief Detaches the node from its parent when the object goes out of scope. -class PartHolder -{ -public: - PartHolder(osg::ref_ptr node) - : mNode(node) - { - } - - ~PartHolder() - { - if (mNode->getNumParents()) - mNode->getParent(0)->removeChild(mNode); - } - -private: - osg::ref_ptr mNode; -}; -typedef boost::shared_ptr PartHolderPtr; - class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 21d7c1ab5..4564e3a97 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -246,7 +246,7 @@ void Objects::removeCell(const MWWorld::CellStore* store) } } -void Objects::update(float dt, Ogre::Camera* camera) +void Objects::update(float dt) { PtrAnimationMap::const_iterator it = mObjects.begin(); for(;it != mObjects.end();++it) diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 07328b959..f4d5675aa 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -49,7 +49,7 @@ public: Animation* getAnimation(const MWWorld::Ptr &ptr); - void update (float dt, Ogre::Camera* camera); + void update (float dt); ///< per-frame update //Ogre::AxisAlignedBox getDimensions(MWWorld::CellStore*); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ba34cf303..26b86d1b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -160,4 +160,9 @@ namespace MWRender return mSky.get(); } + void RenderingManager::update(float dt, bool paused) + { + mObjects->update(dt); + } + } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 1db0467ef..b7aec3786 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -63,6 +63,8 @@ namespace MWRender osg::Vec3f getEyePos(); + void update(float dt, bool paused); + private: osgViewer::Viewer& mViewer; osg::ref_ptr mRootNode; diff --git a/apps/openmw/mwrender/vismask.hpp b/apps/openmw/mwrender/vismask.hpp new file mode 100644 index 000000000..4a064b60f --- /dev/null +++ b/apps/openmw/mwrender/vismask.hpp @@ -0,0 +1,17 @@ +#ifndef OPENMW_MWRENDER_VISMASK_H +#define OPENMW_MWRENDER_VISMASK_H + +namespace MWRender +{ + + /// Node masks used for controlling visibility of game objects. + enum VisMask + { + Mask_UpdateVisitor = 0x1, // reserved for separating UpdateVisitors from CullVisitors + + Mask_Effect = 0x2 + }; + +} + +#endif diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 1084ae03a..c9fd84ee3 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -180,7 +180,7 @@ namespace MWWorld } } - //mRendering.update (duration, paused); + mRendering.update (duration, paused); } void Scene::unloadCell (CellStoreCollection::iterator iter) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 20298bd85..c6a1b5477 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1606,13 +1606,13 @@ namespace MWWorld goToJail(); updateWeather(duration, paused); - /* - if (!paused) - doPhysics (duration); + //if (!paused) + // doPhysics (duration); mWorldScene->update (duration, paused); + /* performUpdateSceneQueries (); updateWindowManager (); diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index ce004dc79..499a74d95 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -64,6 +64,11 @@ float ControllerFunction::calculate(float value) } } +float ControllerFunction::getMaximum() const +{ + return mStopTime; +} + KeyframeController::KeyframeController() { } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 3136b3118..e480f4c13 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -95,6 +95,8 @@ namespace NifOsg ControllerFunction(const Nif::Controller *ctrl); float calculate(float value); + + virtual float getMaximum() const; }; class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller, public ValueInterpolator diff --git a/components/sceneutil/controller.cpp b/components/sceneutil/controller.cpp index 565d48672..1f1b95ac8 100644 --- a/components/sceneutil/controller.cpp +++ b/components/sceneutil/controller.cpp @@ -32,31 +32,26 @@ namespace SceneUtil return nv->getFrameStamp()->getSimulationTime(); } - AssignControllerSourcesVisitor::AssignControllerSourcesVisitor() + ControllerVisitor::ControllerVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { - } - AssignControllerSourcesVisitor::AssignControllerSourcesVisitor(boost::shared_ptr toAssign) - : mToAssign(toAssign) - , osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - { } - void AssignControllerSourcesVisitor::apply(osg::Node &node) + void ControllerVisitor::apply(osg::Node &node) { osg::NodeCallback* callback = node.getUpdateCallback(); while (callback) { if (Controller* ctrl = dynamic_cast(callback)) - assign(node, *ctrl); + visit(node, *ctrl); if (CompositeStateSetUpdater* composite = dynamic_cast(callback)) { for (unsigned int i=0; igetNumControllers(); ++i) { StateSetUpdater* statesetcontroller = composite->getController(i); if (Controller* ctrl = dynamic_cast(statesetcontroller)) - assign(node, *ctrl); + visit(node, *ctrl); } } @@ -66,18 +61,29 @@ namespace SceneUtil traverse(node); } - void AssignControllerSourcesVisitor::apply(osg::Geode &geode) + void ControllerVisitor::apply(osg::Geode &geode) { for (unsigned int i=0; igetUpdateCallback(); if (Controller* ctrl = dynamic_cast(callback)) - assign(geode, *ctrl); + visit(geode, *ctrl); } } - void AssignControllerSourcesVisitor::assign(osg::Node&, Controller &ctrl) + AssignControllerSourcesVisitor::AssignControllerSourcesVisitor() + : ControllerVisitor() + { + } + + AssignControllerSourcesVisitor::AssignControllerSourcesVisitor(boost::shared_ptr toAssign) + : ControllerVisitor() + , mToAssign(toAssign) + { + } + + void AssignControllerSourcesVisitor::visit(osg::Node&, Controller &ctrl) { if (!ctrl.mSource.get()) ctrl.mSource = mToAssign; diff --git a/components/sceneutil/controller.hpp b/components/sceneutil/controller.hpp index de73c7e80..655e02164 100644 --- a/components/sceneutil/controller.hpp +++ b/components/sceneutil/controller.hpp @@ -25,6 +25,10 @@ namespace SceneUtil { public: virtual float calculate(float input) = 0; + + /// Get the "stop time" of the controller function, typically the maximum of the calculate() function. + /// May not be meaningful for all types of controller functions. + virtual float getMaximum() const = 0; }; class Controller @@ -42,18 +46,27 @@ namespace SceneUtil boost::shared_ptr mFunction; }; - class AssignControllerSourcesVisitor : public osg::NodeVisitor + /// Pure virtual base class - visit() all controllers that are attached as UpdateCallbacks in a scene graph. + class ControllerVisitor : public osg::NodeVisitor { public: - AssignControllerSourcesVisitor(); - AssignControllerSourcesVisitor(boost::shared_ptr toAssign); + ControllerVisitor(); virtual void apply(osg::Node& node); virtual void apply(osg::Geode& geode); + virtual void visit(osg::Node& node, Controller& ctrl) = 0; + }; + + class AssignControllerSourcesVisitor : public ControllerVisitor + { + public: + AssignControllerSourcesVisitor(); + AssignControllerSourcesVisitor(boost::shared_ptr toAssign); + /// Assign the wanted ControllerSource. May be overriden in derived classes. /// By default assigns the ControllerSource passed to the constructor of this class if no ControllerSource is assigned to that controller yet. - virtual void assign(osg::Node& node, Controller& ctrl); + virtual void visit(osg::Node& node, Controller& ctrl); private: boost::shared_ptr mToAssign;