1 #include "animation.hpp"
2 
3 #include <iomanip>
4 #include <limits>
5 
6 #include <osg/MatrixTransform>
7 #include <osg/BlendFunc>
8 #include <osg/Material>
9 #include <osg/PositionAttitudeTransform>
10 #include <osg/Switch>
11 
12 #include <osgParticle/ParticleSystem>
13 #include <osgParticle/ParticleProcessor>
14 
15 #include <components/debug/debuglog.hpp>
16 
17 #include <components/resource/scenemanager.hpp>
18 #include <components/resource/keyframemanager.hpp>
19 
20 #include <components/misc/constants.hpp>
21 #include <components/misc/resourcehelpers.hpp>
22 
23 #include <components/sceneutil/keyframe.hpp>
24 
25 #include <components/vfs/manager.hpp>
26 
27 #include <components/sceneutil/actorutil.hpp>
28 #include <components/sceneutil/statesetupdater.hpp>
29 #include <components/sceneutil/visitor.hpp>
30 #include <components/sceneutil/lightmanager.hpp>
31 #include <components/sceneutil/lightutil.hpp>
32 #include <components/sceneutil/skeleton.hpp>
33 #include <components/sceneutil/positionattitudetransform.hpp>
34 #include <components/sceneutil/util.hpp>
35 
36 #include <components/settings/settings.hpp>
37 
38 #include "../mwbase/environment.hpp"
39 #include "../mwbase/world.hpp"
40 #include "../mwworld/esmstore.hpp"
41 #include "../mwworld/class.hpp"
42 #include "../mwworld/cellstore.hpp"
43 
44 #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority
45 
46 #include "vismask.hpp"
47 #include "util.hpp"
48 #include "rotatecontroller.hpp"
49 
50 namespace
51 {
52 
53     /// Removes all particle systems and related nodes in a subgraph.
54     class RemoveParticlesVisitor : public osg::NodeVisitor
55     {
56     public:
RemoveParticlesVisitor()57         RemoveParticlesVisitor()
58             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
59         { }
60 
apply(osg::Node & node)61         void apply(osg::Node &node) override
62         {
63             if (dynamic_cast<osgParticle::ParticleProcessor*>(&node))
64                 mToRemove.emplace_back(&node);
65 
66             traverse(node);
67         }
68 
apply(osg::Drawable & drw)69         void apply(osg::Drawable& drw) override
70         {
71             if (osgParticle::ParticleSystem* partsys = dynamic_cast<osgParticle::ParticleSystem*>(&drw))
72                 mToRemove.emplace_back(partsys);
73         }
74 
remove()75         void remove()
76         {
77             for (osg::Node* node : mToRemove)
78             {
79                 // FIXME: a Drawable might have more than one parent
80                 if (node->getNumParents())
81                     node->getParent(0)->removeChild(node);
82             }
83             mToRemove.clear();
84         }
85 
86     private:
87         std::vector<osg::ref_ptr<osg::Node> > mToRemove;
88     };
89 
90     class DayNightCallback : public osg::NodeCallback
91     {
92     public:
DayNightCallback()93         DayNightCallback() : mCurrentState(0)
94         {
95         }
96 
operator ()(osg::Node * node,osg::NodeVisitor * nv)97         void operator()(osg::Node* node, osg::NodeVisitor* nv) override
98         {
99             unsigned int state = MWBase::Environment::get().getWorld()->getNightDayMode();
100             const unsigned int newState = node->asGroup()->getNumChildren() > state ? state : 0;
101 
102             if (newState != mCurrentState)
103             {
104                 mCurrentState = newState;
105                 node->asSwitch()->setSingleChildOn(mCurrentState);
106             }
107 
108             traverse(node, nv);
109         }
110 
111     private:
112         unsigned int mCurrentState;
113     };
114 
115     class AddSwitchCallbacksVisitor : public osg::NodeVisitor
116     {
117     public:
AddSwitchCallbacksVisitor()118         AddSwitchCallbacksVisitor()
119             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
120         { }
121 
apply(osg::Switch & switchNode)122         void apply(osg::Switch &switchNode) override
123         {
124             if (switchNode.getName() == Constants::NightDayLabel)
125                 switchNode.addUpdateCallback(new DayNightCallback());
126 
127             traverse(switchNode);
128         }
129     };
130 
131     class HarvestVisitor : public osg::NodeVisitor
132     {
133     public:
HarvestVisitor()134         HarvestVisitor()
135             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
136         {
137         }
138 
apply(osg::Switch & node)139         void apply(osg::Switch& node) override
140         {
141             if (node.getName() == Constants::HerbalismLabel)
142             {
143                 node.setSingleChildOn(1);
144             }
145 
146             traverse(node);
147         }
148     };
149 
calcAnimVelocity(const SceneUtil::TextKeyMap & keys,SceneUtil::KeyframeController * nonaccumctrl,const osg::Vec3f & accum,const std::string & groupname)150     float calcAnimVelocity(const SceneUtil::TextKeyMap& keys, SceneUtil::KeyframeController *nonaccumctrl,
151                            const osg::Vec3f& accum, const std::string &groupname)
152     {
153         const std::string start = groupname+": start";
154         const std::string loopstart = groupname+": loop start";
155         const std::string loopstop = groupname+": loop stop";
156         const std::string stop = groupname+": stop";
157         float starttime = std::numeric_limits<float>::max();
158         float stoptime = 0.0f;
159 
160         // Pick the last Loop Stop key and the last Loop Start key.
161         // This is required because of broken text keys in AshVampire.nif.
162         // It has *two* WalkForward: Loop Stop keys at different times, the first one is used for stopping playback
163         // but the animation velocity calculation uses the second one.
164         // As result the animation velocity calculation is not correct, and this incorrect velocity must be replicated,
165         // because otherwise the Creature's Speed (dagoth uthol) would not be sufficient to move fast enough.
166         auto keyiter = keys.rbegin();
167         while(keyiter != keys.rend())
168         {
169             if(keyiter->second == start || keyiter->second == loopstart)
170             {
171                 starttime = keyiter->first;
172                 break;
173             }
174             ++keyiter;
175         }
176         keyiter = keys.rbegin();
177         while(keyiter != keys.rend())
178         {
179             if (keyiter->second == stop)
180                 stoptime = keyiter->first;
181             else if (keyiter->second == loopstop)
182             {
183                 stoptime = keyiter->first;
184                 break;
185             }
186             ++keyiter;
187         }
188 
189         if(stoptime > starttime)
190         {
191             osg::Vec3f startpos = osg::componentMultiply(nonaccumctrl->getTranslation(starttime), accum);
192             osg::Vec3f endpos = osg::componentMultiply(nonaccumctrl->getTranslation(stoptime), accum);
193 
194             return (startpos-endpos).length() / (stoptime - starttime);
195         }
196 
197         return 0.0f;
198     }
199 
200     /// @brief Base class for visitors that remove nodes from a scene graph.
201     /// Subclasses need to fill the mToRemove vector.
202     /// To use, node->accept(removeVisitor); removeVisitor.remove();
203     class RemoveVisitor : public osg::NodeVisitor
204     {
205     public:
RemoveVisitor()206         RemoveVisitor()
207             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
208         {
209         }
210 
remove()211         void remove()
212         {
213             for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it)
214             {
215                 if (!it->second->removeChild(it->first))
216                     Log(Debug::Error) << "Error removing " << it->first->getName();
217             }
218         }
219 
220     protected:
221         // <node to remove, parent node to remove it from>
222         typedef std::vector<std::pair<osg::Node*, osg::Group*> > RemoveVec;
223         std::vector<std::pair<osg::Node*, osg::Group*> > mToRemove;
224     };
225 
226     class GetExtendedBonesVisitor : public osg::NodeVisitor
227     {
228     public:
GetExtendedBonesVisitor()229         GetExtendedBonesVisitor()
230             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
231         {
232         }
233 
apply(osg::Node & node)234         void apply(osg::Node& node) override
235         {
236             if (SceneUtil::hasUserDescription(&node, "CustomBone"))
237             {
238                 mFoundBones.emplace_back(&node, node.getParent(0));
239                 return;
240             }
241 
242             traverse(node);
243         }
244 
245         std::vector<std::pair<osg::Node*, osg::Group*> > mFoundBones;
246     };
247 
248     class RemoveFinishedCallbackVisitor : public RemoveVisitor
249     {
250     public:
251         bool mHasMagicEffects;
252 
RemoveFinishedCallbackVisitor()253         RemoveFinishedCallbackVisitor()
254             : RemoveVisitor()
255             , mHasMagicEffects(false)
256         {
257         }
258 
apply(osg::Node & node)259         void apply(osg::Node &node) override
260         {
261             traverse(node);
262         }
263 
apply(osg::Group & group)264         void apply(osg::Group &group) override
265         {
266             traverse(group);
267 
268             osg::Callback* callback = group.getUpdateCallback();
269             if (callback)
270             {
271                 // We should remove empty transformation nodes and finished callbacks here
272                 MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast<MWRender::UpdateVfxCallback*>(callback);
273                 if (vfxCallback)
274                 {
275                     if (vfxCallback->mFinished)
276                         mToRemove.emplace_back(group.asNode(), group.getParent(0));
277                     else
278                         mHasMagicEffects = true;
279                 }
280             }
281         }
282 
apply(osg::MatrixTransform & node)283         void apply(osg::MatrixTransform &node) override
284         {
285             traverse(node);
286         }
287 
apply(osg::Geometry &)288         void apply(osg::Geometry&) override
289         {
290         }
291     };
292 
293     class RemoveCallbackVisitor : public RemoveVisitor
294     {
295     public:
296         bool mHasMagicEffects;
297 
RemoveCallbackVisitor()298         RemoveCallbackVisitor()
299             : RemoveVisitor()
300             , mHasMagicEffects(false)
301             , mEffectId(-1)
302         {
303         }
304 
RemoveCallbackVisitor(int effectId)305         RemoveCallbackVisitor(int effectId)
306             : RemoveVisitor()
307             , mHasMagicEffects(false)
308             , mEffectId(effectId)
309         {
310         }
311 
apply(osg::Node & node)312         void apply(osg::Node &node) override
313         {
314             traverse(node);
315         }
316 
apply(osg::Group & group)317         void apply(osg::Group &group) override
318         {
319             traverse(group);
320 
321             osg::Callback* callback = group.getUpdateCallback();
322             if (callback)
323             {
324                 MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast<MWRender::UpdateVfxCallback*>(callback);
325                 if (vfxCallback)
326                 {
327                     bool toRemove = mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId;
328                     if (toRemove)
329                         mToRemove.emplace_back(group.asNode(), group.getParent(0));
330                     else
331                         mHasMagicEffects = true;
332                 }
333             }
334         }
335 
apply(osg::MatrixTransform & node)336         void apply(osg::MatrixTransform &node) override
337         {
338             traverse(node);
339         }
340 
apply(osg::Geometry &)341         void apply(osg::Geometry&) override
342         {
343         }
344 
345     private:
346         int mEffectId;
347     };
348 
349     class FindVfxCallbacksVisitor : public osg::NodeVisitor
350     {
351     public:
352 
353         std::vector<MWRender::UpdateVfxCallback*> mCallbacks;
354 
FindVfxCallbacksVisitor()355         FindVfxCallbacksVisitor()
356             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
357             , mEffectId(-1)
358         {
359         }
360 
FindVfxCallbacksVisitor(int effectId)361         FindVfxCallbacksVisitor(int effectId)
362             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
363             , mEffectId(effectId)
364         {
365         }
366 
apply(osg::Node & node)367         void apply(osg::Node &node) override
368         {
369             traverse(node);
370         }
371 
apply(osg::Group & group)372         void apply(osg::Group &group) override
373         {
374             osg::Callback* callback = group.getUpdateCallback();
375             if (callback)
376             {
377                 MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast<MWRender::UpdateVfxCallback*>(callback);
378                 if (vfxCallback)
379                 {
380                     if (mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId)
381                     {
382                         mCallbacks.push_back(vfxCallback);
383                     }
384                 }
385             }
386             traverse(group);
387         }
388 
apply(osg::MatrixTransform & node)389         void apply(osg::MatrixTransform &node) override
390         {
391             traverse(node);
392         }
393 
apply(osg::Geometry &)394         void apply(osg::Geometry&) override
395         {
396         }
397 
398     private:
399         int mEffectId;
400     };
401 
402     // Removes all drawables from a graph.
403     class CleanObjectRootVisitor : public RemoveVisitor
404     {
405     public:
apply(osg::Drawable & drw)406         void apply(osg::Drawable& drw) override
407         {
408             applyDrawable(drw);
409         }
410 
apply(osg::Group & node)411         void apply(osg::Group& node) override
412         {
413             applyNode(node);
414         }
apply(osg::MatrixTransform & node)415         void apply(osg::MatrixTransform& node) override
416         {
417             applyNode(node);
418         }
apply(osg::Node & node)419         void apply(osg::Node& node) override
420         {
421             applyNode(node);
422         }
423 
applyNode(osg::Node & node)424         void applyNode(osg::Node& node)
425         {
426             if (node.getStateSet())
427                 node.setStateSet(nullptr);
428 
429             if (node.getNodeMask() == 0x1 && node.getNumParents() == 1)
430                 mToRemove.emplace_back(&node, node.getParent(0));
431             else
432                 traverse(node);
433         }
applyDrawable(osg::Node & node)434         void applyDrawable(osg::Node& node)
435         {
436             osg::NodePath::iterator parent = getNodePath().end()-2;
437             // We know that the parent is a Group because only Groups can have children.
438             osg::Group* parentGroup = static_cast<osg::Group*>(*parent);
439 
440             // Try to prune nodes that would be empty after the removal
441             if (parent != getNodePath().begin())
442             {
443                 // This could be extended to remove the parent's parent, and so on if they are empty as well.
444                 // But for NIF files, there won't be a benefit since only TriShapes can be set to STATIC dataVariance.
445                 osg::Group* parentParent = static_cast<osg::Group*>(*(parent - 1));
446                 if (parentGroup->getNumChildren() == 1 && parentGroup->getDataVariance() == osg::Object::STATIC)
447                 {
448                     mToRemove.emplace_back(parentGroup, parentParent);
449                     return;
450                 }
451             }
452 
453             mToRemove.emplace_back(&node, parentGroup);
454         }
455     };
456 
457     class RemoveTriBipVisitor : public RemoveVisitor
458     {
459     public:
apply(osg::Drawable & drw)460         void apply(osg::Drawable& drw) override
461         {
462             applyImpl(drw);
463         }
464 
apply(osg::Group & node)465         void apply(osg::Group& node) override
466         {
467             traverse(node);
468         }
apply(osg::MatrixTransform & node)469         void apply(osg::MatrixTransform& node) override
470         {
471             traverse(node);
472         }
473 
applyImpl(osg::Node & node)474         void applyImpl(osg::Node& node)
475         {
476             const std::string toFind = "tri bip";
477             if (Misc::StringUtils::ciCompareLen(node.getName(), toFind, toFind.size()) == 0)
478             {
479                 osg::Group* parent = static_cast<osg::Group*>(*(getNodePath().end()-2));
480                 // Not safe to remove in apply(), since the visitor is still iterating the child list
481                 mToRemove.emplace_back(&node, parent);
482             }
483         }
484     };
485 }
486 
487 namespace MWRender
488 {
489     class TransparencyUpdater : public SceneUtil::StateSetUpdater
490     {
491     public:
TransparencyUpdater(const float alpha)492         TransparencyUpdater(const float alpha)
493             : mAlpha(alpha)
494         {
495         }
496 
setAlpha(const float alpha)497         void setAlpha(const float alpha)
498         {
499             mAlpha = alpha;
500         }
501 
setLightSource(const osg::ref_ptr<SceneUtil::LightSource> & lightSource)502         void setLightSource(const osg::ref_ptr<SceneUtil::LightSource>& lightSource)
503         {
504             mLightSource = lightSource;
505         }
506 
507     protected:
setDefaults(osg::StateSet * stateset)508         void setDefaults(osg::StateSet* stateset) override
509         {
510             osg::BlendFunc* blendfunc (new osg::BlendFunc);
511             stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
512 
513             stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
514             stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
515 
516             // FIXME: overriding diffuse/ambient/emissive colors
517             osg::Material* material = new osg::Material;
518             material->setColorMode(osg::Material::OFF);
519             material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,mAlpha));
520             material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
521             stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
522             stateset->addUniform(new osg::Uniform("colorMode", 0), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
523         }
524 
apply(osg::StateSet * stateset,osg::NodeVisitor *)525         void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override
526         {
527             osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
528             material->setAlpha(osg::Material::FRONT_AND_BACK, mAlpha);
529             if (mLightSource)
530                 mLightSource->setActorFade(mAlpha);
531         }
532 
533     private:
534         float mAlpha;
535         osg::ref_ptr<SceneUtil::LightSource> mLightSource;
536     };
537 
538     struct Animation::AnimSource
539     {
540         osg::ref_ptr<const SceneUtil::KeyframeHolder> mKeyframes;
541 
542         typedef std::map<std::string, osg::ref_ptr<SceneUtil::KeyframeController> > ControllerMap;
543 
544         ControllerMap mControllerMap[Animation::sNumBlendMasks];
545 
546         const SceneUtil::TextKeyMap& getTextKeys() const;
547     };
548 
operator ()(osg::Node * node,osg::NodeVisitor * nv)549     void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
550     {
551         traverse(node, nv);
552 
553         if (mFinished)
554             return;
555 
556         double newTime = nv->getFrameStamp()->getSimulationTime();
557         if (mStartingTime == 0)
558         {
559             mStartingTime = newTime;
560             return;
561         }
562 
563         double duration = newTime - mStartingTime;
564         mStartingTime = newTime;
565 
566         mParams.mAnimTime->addTime(duration);
567         if (mParams.mAnimTime->getTime() >= mParams.mMaxControllerLength)
568         {
569             if (mParams.mLoop)
570             {
571                 // Start from the beginning again; carry over the remainder
572                 // Not sure if this is actually needed, the controller function might already handle loops
573                 float remainder = mParams.mAnimTime->getTime() - mParams.mMaxControllerLength;
574                 mParams.mAnimTime->resetTime(remainder);
575             }
576             else
577             {
578                 // Hide effect immediately
579                 node->setNodeMask(0);
580                 mFinished = true;
581             }
582         }
583     }
584 
585     class ResetAccumRootCallback : public osg::NodeCallback
586     {
587     public:
operator ()(osg::Node * node,osg::NodeVisitor * nv)588         void operator()(osg::Node* node, osg::NodeVisitor* nv) override
589         {
590             osg::MatrixTransform* transform = static_cast<osg::MatrixTransform*>(node);
591 
592             osg::Matrix mat = transform->getMatrix();
593             osg::Vec3f position = mat.getTrans();
594             position = osg::componentMultiply(mResetAxes, position);
595             mat.setTrans(position);
596             transform->setMatrix(mat);
597 
598             traverse(node, nv);
599         }
600 
setAccumulate(const osg::Vec3f & accumulate)601         void setAccumulate(const osg::Vec3f& accumulate)
602         {
603             // anything that accumulates (1.f) should be reset in the callback to (0.f)
604             mResetAxes.x() = accumulate.x() != 0.f ? 0.f : 1.f;
605             mResetAxes.y() = accumulate.y() != 0.f ? 0.f : 1.f;
606             mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f;
607         }
608 
609     private:
610         osg::Vec3f mResetAxes;
611     };
612 
Animation(const MWWorld::Ptr & ptr,osg::ref_ptr<osg::Group> parentNode,Resource::ResourceSystem * resourceSystem)613     Animation::Animation(const MWWorld::Ptr &ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem)
614         : mInsert(parentNode)
615         , mSkeleton(nullptr)
616         , mNodeMapCreated(false)
617         , mPtr(ptr)
618         , mResourceSystem(resourceSystem)
619         , mAccumulate(1.f, 1.f, 0.f)
620         , mTextKeyListener(nullptr)
621         , mHeadYawRadians(0.f)
622         , mHeadPitchRadians(0.f)
623         , mUpperBodyYawRadians(0.f)
624         , mLegsYawRadians(0.f)
625         , mBodyPitchRadians(0.f)
626         , mHasMagicEffects(false)
627         , mAlpha(1.f)
628     {
629         for(size_t i = 0;i < sNumBlendMasks;i++)
630             mAnimationTimePtr[i].reset(new AnimationTime);
631 
632         mLightListCallback = new SceneUtil::LightListCallback;
633     }
634 
~Animation()635     Animation::~Animation()
636     {
637         Animation::setLightEffect(0.f);
638 
639         if (mObjectRoot)
640             mInsert->removeChild(mObjectRoot);
641     }
642 
getPtr() const643     MWWorld::ConstPtr Animation::getPtr() const
644     {
645         return mPtr;
646     }
647 
getPtr()648     MWWorld::Ptr Animation::getPtr()
649     {
650         return mPtr;
651     }
652 
setActive(int active)653     void Animation::setActive(int active)
654     {
655         if (mSkeleton)
656             mSkeleton->setActive(static_cast<SceneUtil::Skeleton::ActiveType>(active));
657     }
658 
updatePtr(const MWWorld::Ptr & ptr)659     void Animation::updatePtr(const MWWorld::Ptr &ptr)
660     {
661         mPtr = ptr;
662     }
663 
setAccumulation(const osg::Vec3f & accum)664     void Animation::setAccumulation(const osg::Vec3f& accum)
665     {
666         mAccumulate = accum;
667 
668         if (mResetAccumRootCallback)
669             mResetAccumRootCallback->setAccumulate(mAccumulate);
670     }
671 
detectBlendMask(const osg::Node * node) const672     size_t Animation::detectBlendMask(const osg::Node* node) const
673     {
674         static const char sBlendMaskRoots[sNumBlendMasks][32] = {
675             "", /* Lower body / character root */
676             "Bip01 Spine1", /* Torso */
677             "Bip01 L Clavicle", /* Left arm */
678             "Bip01 R Clavicle", /* Right arm */
679         };
680 
681         while(node != mObjectRoot)
682         {
683             const std::string &name = node->getName();
684             for(size_t i = 1;i < sNumBlendMasks;i++)
685             {
686                 if(name == sBlendMaskRoots[i])
687                     return i;
688             }
689 
690             assert(node->getNumParents() > 0);
691 
692             node = node->getParent(0);
693         }
694 
695         return 0;
696     }
697 
getTextKeys() const698     const SceneUtil::TextKeyMap &Animation::AnimSource::getTextKeys() const
699     {
700         return mKeyframes->mTextKeys;
701     }
702 
loadAllAnimationsInFolder(const std::string & model,const std::string & baseModel)703     void Animation::loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel)
704     {
705         const std::map<std::string, VFS::File*>& index = mResourceSystem->getVFS()->getIndex();
706 
707         std::string animationPath = model;
708         if (animationPath.find("meshes") == 0)
709         {
710             animationPath.replace(0, 6, "animations");
711         }
712         animationPath.replace(animationPath.size()-3, 3, "/");
713 
714         mResourceSystem->getVFS()->normalizeFilename(animationPath);
715 
716         std::map<std::string, VFS::File*>::const_iterator found = index.lower_bound(animationPath);
717         while (found != index.end())
718         {
719             const std::string& name = found->first;
720             if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath)
721             {
722                 size_t pos = name.find_last_of('.');
723                 if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".kf") == 0)
724                     addSingleAnimSource(name, baseModel);
725             }
726             else
727                 break;
728             ++found;
729         }
730     }
731 
addAnimSource(const std::string & model,const std::string & baseModel)732     void Animation::addAnimSource(const std::string &model, const std::string& baseModel)
733     {
734         std::string kfname = model;
735         Misc::StringUtils::lowerCaseInPlace(kfname);
736 
737         if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
738             kfname.replace(kfname.size()-4, 4, ".kf");
739 
740         addSingleAnimSource(kfname, baseModel);
741 
742         static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game");
743         if (useAdditionalSources)
744             loadAllAnimationsInFolder(kfname, baseModel);
745     }
746 
addSingleAnimSource(const std::string & kfname,const std::string & baseModel)747     void Animation::addSingleAnimSource(const std::string &kfname, const std::string& baseModel)
748     {
749         if(!mResourceSystem->getVFS()->exists(kfname))
750             return;
751 
752         std::shared_ptr<AnimSource> animsrc;
753         animsrc.reset(new AnimSource);
754         animsrc->mKeyframes = mResourceSystem->getKeyframeManager()->get(kfname);
755 
756         if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty())
757             return;
758 
759         const NodeMap& nodeMap = getNodeMap();
760 
761         for (SceneUtil::KeyframeHolder::KeyframeControllerMap::const_iterator it = animsrc->mKeyframes->mKeyframeControllers.begin();
762              it != animsrc->mKeyframes->mKeyframeControllers.end(); ++it)
763         {
764             std::string bonename = Misc::StringUtils::lowerCase(it->first);
765             NodeMap::const_iterator found = nodeMap.find(bonename);
766             if (found == nodeMap.end())
767             {
768                 Log(Debug::Warning) << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << baseModel << " (referenced by " << kfname << ")";
769                 continue;
770             }
771 
772             osg::Node* node = found->second;
773 
774             size_t blendMask = detectBlendMask(node);
775 
776             // clone the controller, because each Animation needs its own ControllerSource
777             osg::ref_ptr<SceneUtil::KeyframeController> cloned = osg::clone(it->second.get(), osg::CopyOp::SHALLOW_COPY);
778             cloned->setSource(mAnimationTimePtr[blendMask]);
779 
780             animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned));
781         }
782 
783         mAnimSources.push_back(animsrc);
784 
785         SceneUtil::AssignControllerSourcesVisitor assignVisitor(mAnimationTimePtr[0]);
786         mObjectRoot->accept(assignVisitor);
787 
788         if (!mAccumRoot)
789         {
790             NodeMap::const_iterator found = nodeMap.find("bip01");
791             if (found == nodeMap.end())
792                 found = nodeMap.find("root bone");
793 
794             if (found != nodeMap.end())
795                 mAccumRoot = found->second;
796         }
797     }
798 
clearAnimSources()799     void Animation::clearAnimSources()
800     {
801         mStates.clear();
802 
803         for(size_t i = 0;i < sNumBlendMasks;i++)
804             mAnimationTimePtr[i]->setTimePtr(std::shared_ptr<float>());
805 
806         mAccumCtrl = nullptr;
807 
808         mAnimSources.clear();
809 
810         mAnimVelocities.clear();
811     }
812 
hasAnimation(const std::string & anim) const813     bool Animation::hasAnimation(const std::string &anim) const
814     {
815         AnimSourceList::const_iterator iter(mAnimSources.begin());
816         for(;iter != mAnimSources.end();++iter)
817         {
818             const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys();
819             if (keys.hasGroupStart(anim))
820                 return true;
821         }
822 
823         return false;
824     }
825 
getStartTime(const std::string & groupname) const826     float Animation::getStartTime(const std::string &groupname) const
827     {
828         for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter)
829         {
830             const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys();
831 
832             const auto found = keys.findGroupStart(groupname);
833             if(found != keys.end())
834                 return found->first;
835         }
836         return -1.f;
837     }
838 
getTextKeyTime(const std::string & textKey) const839     float Animation::getTextKeyTime(const std::string &textKey) const
840     {
841         for(AnimSourceList::const_reverse_iterator iter(mAnimSources.rbegin()); iter != mAnimSources.rend(); ++iter)
842         {
843             const SceneUtil::TextKeyMap &keys = (*iter)->getTextKeys();
844 
845             for(auto iterKey = keys.begin(); iterKey != keys.end(); ++iterKey)
846             {
847                 if(iterKey->second.compare(0, textKey.size(), textKey) == 0)
848                     return iterKey->first;
849             }
850         }
851 
852         return -1.f;
853     }
854 
handleTextKey(AnimState & state,const std::string & groupname,SceneUtil::TextKeyMap::ConstIterator key,const SceneUtil::TextKeyMap & map)855     void Animation::handleTextKey(AnimState &state, const std::string &groupname, SceneUtil::TextKeyMap::ConstIterator key,
856                        const SceneUtil::TextKeyMap& map)
857     {
858         const std::string &evt = key->second;
859 
860         size_t off = groupname.size()+2;
861         size_t len = evt.size() - off;
862 
863         if(evt.compare(0, groupname.size(), groupname) == 0 &&
864            evt.compare(groupname.size(), 2, ": ") == 0)
865         {
866             if(evt.compare(off, len, "loop start") == 0)
867                 state.mLoopStartTime = key->first;
868             else if(evt.compare(off, len, "loop stop") == 0)
869                 state.mLoopStopTime = key->first;
870         }
871 
872         if (mTextKeyListener)
873         {
874             try
875             {
876                 mTextKeyListener->handleTextKey(groupname, key, map);
877             }
878             catch (std::exception& e)
879             {
880                 Log(Debug::Error) << "Error handling text key " << evt << ": " << e.what();
881             }
882         }
883     }
884 
play(const std::string & groupname,const AnimPriority & priority,int blendMask,bool autodisable,float speedmult,const std::string & start,const std::string & stop,float startpoint,size_t loops,bool loopfallback)885     void Animation::play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
886                          const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback)
887     {
888         if(!mObjectRoot || mAnimSources.empty())
889             return;
890 
891         if(groupname.empty())
892         {
893             resetActiveGroups();
894             return;
895         }
896 
897         AnimStateMap::iterator stateiter = mStates.begin();
898         while(stateiter != mStates.end())
899         {
900             if(stateiter->second.mPriority == priority)
901                 mStates.erase(stateiter++);
902             else
903                 ++stateiter;
904         }
905 
906         stateiter = mStates.find(groupname);
907         if(stateiter != mStates.end())
908         {
909             stateiter->second.mPriority = priority;
910             resetActiveGroups();
911             return;
912         }
913 
914         /* Look in reverse; last-inserted source has priority. */
915         AnimState state;
916         AnimSourceList::reverse_iterator iter(mAnimSources.rbegin());
917         for(;iter != mAnimSources.rend();++iter)
918         {
919             const SceneUtil::TextKeyMap &textkeys = (*iter)->getTextKeys();
920             if(reset(state, textkeys, groupname, start, stop, startpoint, loopfallback))
921             {
922                 state.mSource = *iter;
923                 state.mSpeedMult = speedmult;
924                 state.mLoopCount = loops;
925                 state.mPlaying = (state.getTime() < state.mStopTime);
926                 state.mPriority = priority;
927                 state.mBlendMask = blendMask;
928                 state.mAutoDisable = autodisable;
929                 mStates[groupname] = state;
930 
931                 if (state.mPlaying)
932                 {
933                     auto textkey = textkeys.lowerBound(state.getTime());
934                     while(textkey != textkeys.end() && textkey->first <= state.getTime())
935                     {
936                         handleTextKey(state, groupname, textkey, textkeys);
937                         ++textkey;
938                     }
939                 }
940 
941                 if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0)
942                 {
943                     state.mLoopCount--;
944                     state.setTime(state.mLoopStartTime);
945                     state.mPlaying = true;
946                     if(state.getTime() >= state.mLoopStopTime)
947                         break;
948 
949                     auto textkey = textkeys.lowerBound(state.getTime());
950                     while(textkey != textkeys.end() && textkey->first <= state.getTime())
951                     {
952                         handleTextKey(state, groupname, textkey, textkeys);
953                         ++textkey;
954                     }
955                 }
956 
957                 break;
958             }
959         }
960 
961         resetActiveGroups();
962     }
963 
reset(AnimState & state,const SceneUtil::TextKeyMap & keys,const std::string & groupname,const std::string & start,const std::string & stop,float startpoint,bool loopfallback)964     bool Animation::reset(AnimState &state, const SceneUtil::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback)
965     {
966         // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two
967         // separate walkforward keys, and the last one is supposed to be used.
968         auto groupend = keys.rbegin();
969         for(;groupend != keys.rend();++groupend)
970         {
971             if(groupend->second.compare(0, groupname.size(), groupname) == 0 &&
972                groupend->second.compare(groupname.size(), 2, ": ") == 0)
973                 break;
974         }
975 
976         std::string starttag = groupname+": "+start;
977         auto startkey = groupend;
978         while(startkey != keys.rend() && startkey->second != starttag)
979             ++startkey;
980         if(startkey == keys.rend() && start == "loop start")
981         {
982             starttag = groupname+": start";
983             startkey = groupend;
984             while(startkey != keys.rend() && startkey->second != starttag)
985                 ++startkey;
986         }
987         if(startkey == keys.rend())
988             return false;
989 
990         const std::string stoptag = groupname+": "+stop;
991         auto stopkey = groupend;
992         while(stopkey != keys.rend()
993               // We have to ignore extra garbage at the end.
994               // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop".
995               // Why, just why? :(
996               && (stopkey->second.size() < stoptag.size() || stopkey->second.compare(0,stoptag.size(), stoptag) != 0))
997             ++stopkey;
998         if(stopkey == keys.rend())
999             return false;
1000 
1001         if(startkey->first > stopkey->first)
1002             return false;
1003 
1004         state.mStartTime = startkey->first;
1005         if (loopfallback)
1006         {
1007             state.mLoopStartTime = startkey->first;
1008             state.mLoopStopTime = stopkey->first;
1009         }
1010         else
1011         {
1012             state.mLoopStartTime = startkey->first;
1013             state.mLoopStopTime = std::numeric_limits<float>::max();
1014         }
1015         state.mStopTime = stopkey->first;
1016 
1017         state.setTime(state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint));
1018 
1019         // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation
1020         // (see handleTextKey). But if startpoint is already past these keys, or start time is == stop time, we need to assign them now.
1021         const std::string loopstarttag = groupname+": loop start";
1022         const std::string loopstoptag = groupname+": loop stop";
1023 
1024         auto key = groupend;
1025         for (; key != startkey && key != keys.rend(); ++key)
1026         {
1027             if (key->first > state.getTime())
1028                 continue;
1029 
1030             if (key->second == loopstarttag)
1031                 state.mLoopStartTime = key->first;
1032             else if (key->second == loopstoptag)
1033                 state.mLoopStopTime = key->first;
1034         }
1035 
1036         return true;
1037     }
1038 
setTextKeyListener(Animation::TextKeyListener * listener)1039     void Animation::setTextKeyListener(Animation::TextKeyListener *listener)
1040     {
1041         mTextKeyListener = listener;
1042     }
1043 
getNodeMap() const1044     const Animation::NodeMap &Animation::getNodeMap() const
1045     {
1046         if (!mNodeMapCreated && mObjectRoot)
1047         {
1048             SceneUtil::NodeMapVisitor visitor(mNodeMap);
1049             mObjectRoot->accept(visitor);
1050             mNodeMapCreated = true;
1051         }
1052         return mNodeMap;
1053     }
1054 
resetActiveGroups()1055     void Animation::resetActiveGroups()
1056     {
1057         // remove all previous external controllers from the scene graph
1058         for (auto it = mActiveControllers.begin(); it != mActiveControllers.end(); ++it)
1059         {
1060             osg::Node* node = it->first;
1061             node->removeUpdateCallback(it->second);
1062 
1063             // Should be no longer needed with OSG 3.4
1064             it->second->setNestedCallback(nullptr);
1065         }
1066 
1067         mActiveControllers.clear();
1068 
1069         mAccumCtrl = nullptr;
1070 
1071         for(size_t blendMask = 0;blendMask < sNumBlendMasks;blendMask++)
1072         {
1073             AnimStateMap::const_iterator active = mStates.end();
1074 
1075             AnimStateMap::const_iterator state = mStates.begin();
1076             for(;state != mStates.end();++state)
1077             {
1078                 if(!(state->second.mBlendMask&(1<<blendMask)))
1079                     continue;
1080 
1081                 if(active == mStates.end() || active->second.mPriority[(BoneGroup)blendMask] < state->second.mPriority[(BoneGroup)blendMask])
1082                     active = state;
1083             }
1084 
1085             mAnimationTimePtr[blendMask]->setTimePtr(active == mStates.end() ? std::shared_ptr<float>() : active->second.mTime);
1086 
1087             // add external controllers for the AnimSource active in this blend mask
1088             if (active != mStates.end())
1089             {
1090                 std::shared_ptr<AnimSource> animsrc = active->second.mSource;
1091 
1092                 for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it)
1093                 {
1094                     osg::ref_ptr<osg::Node> node = getNodeMap().at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
1095 
1096                     node->addUpdateCallback(it->second);
1097                     mActiveControllers.emplace_back(node, it->second);
1098 
1099                     if (blendMask == 0 && node == mAccumRoot)
1100                     {
1101                         mAccumCtrl = it->second;
1102 
1103                         // make sure reset is last in the chain of callbacks
1104                         if (!mResetAccumRootCallback)
1105                         {
1106                             mResetAccumRootCallback = new ResetAccumRootCallback;
1107                             mResetAccumRootCallback->setAccumulate(mAccumulate);
1108                         }
1109                         mAccumRoot->addUpdateCallback(mResetAccumRootCallback);
1110                         mActiveControllers.emplace_back(mAccumRoot, mResetAccumRootCallback);
1111                     }
1112                 }
1113             }
1114         }
1115         addControllers();
1116     }
1117 
adjustSpeedMult(const std::string & groupname,float speedmult)1118     void Animation::adjustSpeedMult(const std::string &groupname, float speedmult)
1119     {
1120         AnimStateMap::iterator state(mStates.find(groupname));
1121         if(state != mStates.end())
1122             state->second.mSpeedMult = speedmult;
1123     }
1124 
isPlaying(const std::string & groupname) const1125     bool Animation::isPlaying(const std::string &groupname) const
1126     {
1127         AnimStateMap::const_iterator state(mStates.find(groupname));
1128         if(state != mStates.end())
1129             return state->second.mPlaying;
1130         return false;
1131     }
1132 
getInfo(const std::string & groupname,float * complete,float * speedmult) const1133     bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult) const
1134     {
1135         AnimStateMap::const_iterator iter = mStates.find(groupname);
1136         if(iter == mStates.end())
1137         {
1138             if(complete) *complete = 0.0f;
1139             if(speedmult) *speedmult = 0.0f;
1140             return false;
1141         }
1142 
1143         if(complete)
1144         {
1145             if(iter->second.mStopTime > iter->second.mStartTime)
1146                 *complete = (iter->second.getTime() - iter->second.mStartTime) /
1147                             (iter->second.mStopTime - iter->second.mStartTime);
1148             else
1149                 *complete = (iter->second.mPlaying ? 0.0f : 1.0f);
1150         }
1151         if(speedmult) *speedmult = iter->second.mSpeedMult;
1152         return true;
1153     }
1154 
getCurrentTime(const std::string & groupname) const1155     float Animation::getCurrentTime(const std::string &groupname) const
1156     {
1157         AnimStateMap::const_iterator iter = mStates.find(groupname);
1158         if(iter == mStates.end())
1159             return -1.f;
1160 
1161         return iter->second.getTime();
1162     }
1163 
getCurrentLoopCount(const std::string & groupname) const1164     size_t Animation::getCurrentLoopCount(const std::string& groupname) const
1165     {
1166         AnimStateMap::const_iterator iter = mStates.find(groupname);
1167         if(iter == mStates.end())
1168             return 0;
1169 
1170         return iter->second.mLoopCount;
1171     }
1172 
disable(const std::string & groupname)1173     void Animation::disable(const std::string &groupname)
1174     {
1175         AnimStateMap::iterator iter = mStates.find(groupname);
1176         if(iter != mStates.end())
1177             mStates.erase(iter);
1178         resetActiveGroups();
1179     }
1180 
getVelocity(const std::string & groupname) const1181     float Animation::getVelocity(const std::string &groupname) const
1182     {
1183         if (!mAccumRoot)
1184             return 0.0f;
1185 
1186         std::map<std::string, float>::const_iterator found = mAnimVelocities.find(groupname);
1187         if (found != mAnimVelocities.end())
1188             return found->second;
1189 
1190         // Look in reverse; last-inserted source has priority.
1191         AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin());
1192         for(;animsrc != mAnimSources.rend();++animsrc)
1193         {
1194             const SceneUtil::TextKeyMap &keys = (*animsrc)->getTextKeys();
1195             if (keys.hasGroupStart(groupname))
1196                 break;
1197         }
1198         if(animsrc == mAnimSources.rend())
1199             return 0.0f;
1200 
1201         float velocity = 0.0f;
1202         const SceneUtil::TextKeyMap &keys = (*animsrc)->getTextKeys();
1203 
1204         const AnimSource::ControllerMap& ctrls = (*animsrc)->mControllerMap[0];
1205         for (AnimSource::ControllerMap::const_iterator it = ctrls.begin(); it != ctrls.end(); ++it)
1206         {
1207             if (Misc::StringUtils::ciEqual(it->first, mAccumRoot->getName()))
1208             {
1209                 velocity = calcAnimVelocity(keys, it->second, mAccumulate, groupname);
1210                 break;
1211             }
1212         }
1213 
1214         // If there's no velocity, keep looking
1215         if(!(velocity > 1.0f))
1216         {
1217             AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin();
1218             while(*animiter != *animsrc)
1219                 ++animiter;
1220 
1221             while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend())
1222             {
1223                 const SceneUtil::TextKeyMap &keys2 = (*animiter)->getTextKeys();
1224 
1225                 const AnimSource::ControllerMap& ctrls2 = (*animiter)->mControllerMap[0];
1226                 for (AnimSource::ControllerMap::const_iterator it = ctrls2.begin(); it != ctrls2.end(); ++it)
1227                 {
1228                     if (Misc::StringUtils::ciEqual(it->first, mAccumRoot->getName()))
1229                     {
1230                         velocity = calcAnimVelocity(keys2, it->second, mAccumulate, groupname);
1231                         break;
1232                     }
1233                 }
1234             }
1235         }
1236 
1237         mAnimVelocities.insert(std::make_pair(groupname, velocity));
1238 
1239         return velocity;
1240     }
1241 
updatePosition(float oldtime,float newtime,osg::Vec3f & position)1242     void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position)
1243     {
1244         // Get the difference from the last update, and move the position
1245         osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate);
1246         position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate);
1247     }
1248 
runAnimation(float duration)1249     osg::Vec3f Animation::runAnimation(float duration)
1250     {
1251         // If we have scripted animations, play only them
1252         bool hasScriptedAnims = false;
1253         for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++)
1254         {
1255             if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) && stateiter->second.mPlaying)
1256             {
1257                 hasScriptedAnims = true;
1258                 break;
1259             }
1260         }
1261 
1262         osg::Vec3f movement(0.f, 0.f, 0.f);
1263         AnimStateMap::iterator stateiter = mStates.begin();
1264         while(stateiter != mStates.end())
1265         {
1266             AnimState &state = stateiter->second;
1267             if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent)))
1268             {
1269                 ++stateiter;
1270                 continue;
1271             }
1272 
1273             const SceneUtil::TextKeyMap &textkeys = state.mSource->getTextKeys();
1274             auto textkey = textkeys.upperBound(state.getTime());
1275 
1276             float timepassed = duration * state.mSpeedMult;
1277             while(state.mPlaying)
1278             {
1279                 if (!state.shouldLoop())
1280                 {
1281                     float targetTime = state.getTime() + timepassed;
1282                     if(textkey == textkeys.end() || textkey->first > targetTime)
1283                     {
1284                         if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr())
1285                             updatePosition(state.getTime(), targetTime, movement);
1286                         state.setTime(std::min(targetTime, state.mStopTime));
1287                     }
1288                     else
1289                     {
1290                         if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr())
1291                             updatePosition(state.getTime(), textkey->first, movement);
1292                         state.setTime(textkey->first);
1293                     }
1294 
1295                     state.mPlaying = (state.getTime() < state.mStopTime);
1296                     timepassed = targetTime - state.getTime();
1297 
1298                     while(textkey != textkeys.end() && textkey->first <= state.getTime())
1299                     {
1300                         handleTextKey(state, stateiter->first, textkey, textkeys);
1301                         ++textkey;
1302                     }
1303                 }
1304                 if(state.shouldLoop())
1305                 {
1306                     state.mLoopCount--;
1307                     state.setTime(state.mLoopStartTime);
1308                     state.mPlaying = true;
1309 
1310                     textkey = textkeys.lowerBound(state.getTime());
1311                     while(textkey != textkeys.end() && textkey->first <= state.getTime())
1312                     {
1313                         handleTextKey(state, stateiter->first, textkey, textkeys);
1314                         ++textkey;
1315                     }
1316 
1317                     if(state.getTime() >= state.mLoopStopTime)
1318                         break;
1319                 }
1320 
1321                 if(timepassed <= 0.0f)
1322                     break;
1323             }
1324 
1325             if(!state.mPlaying && state.mAutoDisable)
1326             {
1327                 mStates.erase(stateiter++);
1328 
1329                 resetActiveGroups();
1330             }
1331             else
1332                 ++stateiter;
1333         }
1334 
1335         updateEffects();
1336 
1337         const float epsilon = 0.001f;
1338         float yawOffset = 0;
1339         if (mRootController)
1340         {
1341             bool enable = std::abs(mLegsYawRadians) > epsilon || std::abs(mBodyPitchRadians) > epsilon;
1342             mRootController->setEnabled(enable);
1343             if (enable)
1344             {
1345                 mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0,0,1)) * osg::Quat(mBodyPitchRadians, osg::Vec3f(1,0,0)));
1346                 yawOffset = mLegsYawRadians;
1347             }
1348         }
1349         if (mSpineController)
1350         {
1351             float yaw = mUpperBodyYawRadians - yawOffset;
1352             bool enable = std::abs(yaw) > epsilon;
1353             mSpineController->setEnabled(enable);
1354             if (enable)
1355             {
1356                 mSpineController->setRotate(osg::Quat(yaw, osg::Vec3f(0,0,1)));
1357                 yawOffset = mUpperBodyYawRadians;
1358             }
1359         }
1360         if (mHeadController)
1361         {
1362             float yaw = mHeadYawRadians - yawOffset;
1363             bool enable = (std::abs(mHeadPitchRadians) > epsilon || std::abs(yaw) > epsilon);
1364             mHeadController->setEnabled(enable);
1365             if (enable)
1366                 mHeadController->setRotate(osg::Quat(mHeadPitchRadians, osg::Vec3f(1,0,0)) * osg::Quat(yaw, osg::Vec3f(0,0,1)));
1367         }
1368 
1369         // Scripted animations should not cause movement
1370         if (hasScriptedAnims)
1371             return osg::Vec3f(0, 0, 0);
1372 
1373         return movement;
1374     }
1375 
setLoopingEnabled(const std::string & groupname,bool enabled)1376     void Animation::setLoopingEnabled(const std::string &groupname, bool enabled)
1377     {
1378         AnimStateMap::iterator state(mStates.find(groupname));
1379         if(state != mStates.end())
1380             state->second.mLoopingEnabled = enabled;
1381     }
1382 
loadBonesFromFile(osg::ref_ptr<osg::Node> & baseNode,const std::string & model,Resource::ResourceSystem * resourceSystem)1383     void loadBonesFromFile(osg::ref_ptr<osg::Node>& baseNode, const std::string &model, Resource::ResourceSystem* resourceSystem)
1384     {
1385         const osg::Node* node = resourceSystem->getSceneManager()->getTemplate(model).get();
1386         osg::ref_ptr<osg::Node> sheathSkeleton (const_cast<osg::Node*>(node)); // const-trickery required because there is no const version of NodeVisitor
1387 
1388         GetExtendedBonesVisitor getBonesVisitor;
1389         sheathSkeleton->accept(getBonesVisitor);
1390         for (auto& nodePair : getBonesVisitor.mFoundBones)
1391         {
1392             SceneUtil::FindByNameVisitor findVisitor (nodePair.second->getName());
1393             baseNode->accept(findVisitor);
1394 
1395             osg::Group* sheathParent = findVisitor.mFoundNode;
1396             if (sheathParent)
1397             {
1398                 osg::Node* copy = static_cast<osg::Node*>(nodePair.first->clone(osg::CopyOp::DEEP_COPY_NODES));
1399                 sheathParent->addChild(copy);
1400             }
1401         }
1402     }
1403 
injectCustomBones(osg::ref_ptr<osg::Node> & node,const std::string & model,Resource::ResourceSystem * resourceSystem)1404     void injectCustomBones(osg::ref_ptr<osg::Node>& node, const std::string& model, Resource::ResourceSystem* resourceSystem)
1405     {
1406         if (model.empty())
1407             return;
1408 
1409         const std::map<std::string, VFS::File*>& index = resourceSystem->getVFS()->getIndex();
1410 
1411         std::string animationPath = model;
1412         if (animationPath.find("meshes") == 0)
1413         {
1414             animationPath.replace(0, 6, "animations");
1415         }
1416         animationPath.replace(animationPath.size()-4, 4, "/");
1417 
1418         resourceSystem->getVFS()->normalizeFilename(animationPath);
1419 
1420         std::map<std::string, VFS::File*>::const_iterator found = index.lower_bound(animationPath);
1421         while (found != index.end())
1422         {
1423             const std::string& name = found->first;
1424             if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath)
1425             {
1426                 size_t pos = name.find_last_of('.');
1427                 if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".nif") == 0)
1428                     loadBonesFromFile(node, name, resourceSystem);
1429             }
1430             else
1431                 break;
1432             ++found;
1433         }
1434     }
1435 
getModelInstance(Resource::ResourceSystem * resourceSystem,const std::string & model,bool baseonly,bool inject,const std::string & defaultSkeleton)1436     osg::ref_ptr<osg::Node> getModelInstance(Resource::ResourceSystem* resourceSystem, const std::string& model, bool baseonly, bool inject, const std::string& defaultSkeleton)
1437     {
1438         Resource::SceneManager* sceneMgr = resourceSystem->getSceneManager();
1439         if (baseonly)
1440         {
1441             typedef std::map<std::string, osg::ref_ptr<osg::Node> > Cache;
1442             static Cache cache;
1443             Cache::iterator found = cache.find(model);
1444             if (found == cache.end())
1445             {
1446                 osg::ref_ptr<osg::Node> created = sceneMgr->getInstance(model);
1447 
1448                 if (inject)
1449                 {
1450                     injectCustomBones(created, defaultSkeleton, resourceSystem);
1451                     injectCustomBones(created, model, resourceSystem);
1452                 }
1453 
1454                 SceneUtil::CleanObjectRootVisitor removeDrawableVisitor;
1455                 created->accept(removeDrawableVisitor);
1456                 removeDrawableVisitor.remove();
1457 
1458                 cache.insert(std::make_pair(model, created));
1459 
1460                 return sceneMgr->createInstance(created);
1461             }
1462             else
1463                 return sceneMgr->createInstance(found->second);
1464         }
1465         else
1466         {
1467             osg::ref_ptr<osg::Node> created = sceneMgr->getInstance(model);
1468 
1469             if (inject)
1470             {
1471                 injectCustomBones(created, defaultSkeleton, resourceSystem);
1472                 injectCustomBones(created, model, resourceSystem);
1473             }
1474 
1475             return created;
1476         }
1477     }
1478 
setObjectRoot(const std::string & model,bool forceskeleton,bool baseonly,bool isCreature)1479     void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature)
1480     {
1481         osg::ref_ptr<osg::StateSet> previousStateset;
1482         if (mObjectRoot)
1483         {
1484             if (mLightListCallback)
1485                 mObjectRoot->removeCullCallback(mLightListCallback);
1486             if (mTransparencyUpdater)
1487                 mObjectRoot->removeCullCallback(mTransparencyUpdater);
1488             previousStateset = mObjectRoot->getStateSet();
1489             mObjectRoot->getParent(0)->removeChild(mObjectRoot);
1490         }
1491         mObjectRoot = nullptr;
1492         mSkeleton = nullptr;
1493 
1494         mNodeMap.clear();
1495         mNodeMapCreated = false;
1496         mActiveControllers.clear();
1497         mAccumRoot = nullptr;
1498         mAccumCtrl = nullptr;
1499 
1500         static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game");
1501         std::string defaultSkeleton;
1502         bool inject = false;
1503 
1504         if (useAdditionalSources && mPtr.getClass().isActor())
1505         {
1506             if (isCreature)
1507             {
1508                 MWWorld::LiveCellRef<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
1509                 if(ref->mBase->mFlags & ESM::Creature::Bipedal)
1510                 {
1511                     defaultSkeleton = Settings::Manager::getString("xbaseanim", "Models");
1512                     inject = true;
1513                 }
1514             }
1515             else
1516             {
1517                 inject = true;
1518                 MWWorld::LiveCellRef<ESM::NPC> *ref = mPtr.get<ESM::NPC>();
1519                 if (!ref->mBase->mModel.empty())
1520                 {
1521                     // If NPC has a custom animation model attached, we should inject bones from default skeleton for given race and gender as well
1522                     // Since it is a quite rare case, there should not be a noticable performance loss
1523                     // Note: consider that player and werewolves have no custom animation files attached for now
1524                     const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
1525                     const ESM::Race *race = store.get<ESM::Race>().find(ref->mBase->mRace);
1526 
1527                     bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0;
1528                     bool isFemale = !ref->mBase->isMale();
1529 
1530                     defaultSkeleton = SceneUtil::getActorSkeleton(false, isFemale, isBeast, false);
1531                     defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS());
1532                 }
1533             }
1534         }
1535 
1536         if (!forceskeleton)
1537         {
1538             osg::ref_ptr<osg::Node> created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton);
1539             mInsert->addChild(created);
1540             mObjectRoot = created->asGroup();
1541             if (!mObjectRoot)
1542             {
1543                 mInsert->removeChild(created);
1544                 mObjectRoot = new osg::Group;
1545                 mObjectRoot->addChild(created);
1546                 mInsert->addChild(mObjectRoot);
1547             }
1548             osg::ref_ptr<SceneUtil::Skeleton> skel = dynamic_cast<SceneUtil::Skeleton*>(mObjectRoot.get());
1549             if (skel)
1550                 mSkeleton = skel.get();
1551         }
1552         else
1553         {
1554             osg::ref_ptr<osg::Node> created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton);
1555             osg::ref_ptr<SceneUtil::Skeleton> skel = dynamic_cast<SceneUtil::Skeleton*>(created.get());
1556             if (!skel)
1557             {
1558                 skel = new SceneUtil::Skeleton;
1559                 skel->addChild(created);
1560             }
1561             mSkeleton = skel.get();
1562             mObjectRoot = skel;
1563             mInsert->addChild(mObjectRoot);
1564         }
1565 
1566         if (previousStateset)
1567             mObjectRoot->setStateSet(previousStateset);
1568 
1569         if (isCreature)
1570         {
1571             SceneUtil::RemoveTriBipVisitor removeTriBipVisitor;
1572             mObjectRoot->accept(removeTriBipVisitor);
1573             removeTriBipVisitor.remove();
1574         }
1575 
1576         if (!mLightListCallback)
1577             mLightListCallback = new SceneUtil::LightListCallback;
1578         mObjectRoot->addCullCallback(mLightListCallback);
1579         if (mTransparencyUpdater)
1580             mObjectRoot->addCullCallback(mTransparencyUpdater);
1581     }
1582 
getObjectRoot()1583     osg::Group* Animation::getObjectRoot()
1584     {
1585         return mObjectRoot.get();
1586     }
1587 
getOrCreateObjectRoot()1588     osg::Group* Animation::getOrCreateObjectRoot()
1589     {
1590         if (mObjectRoot)
1591             return mObjectRoot.get();
1592 
1593         mObjectRoot = new osg::Group;
1594         mInsert->addChild(mObjectRoot);
1595         return mObjectRoot.get();
1596     }
1597 
addSpellCastGlow(const ESM::MagicEffect * effect,float glowDuration)1598     void Animation::addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration)
1599     {
1600         osg::Vec4f glowColor(1,1,1,1);
1601         glowColor.x() = effect->mData.mRed / 255.f;
1602         glowColor.y() = effect->mData.mGreen / 255.f;
1603         glowColor.z() = effect->mData.mBlue / 255.f;
1604 
1605         if (!mGlowUpdater || (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true)))
1606         {
1607             if (mGlowUpdater && mGlowUpdater->isDone())
1608                 mObjectRoot->removeUpdateCallback(mGlowUpdater);
1609 
1610             if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater())
1611             {
1612                 mGlowUpdater->setColor(glowColor);
1613                 mGlowUpdater->setDuration(glowDuration);
1614             }
1615             else
1616                 mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, glowColor, glowDuration);
1617         }
1618     }
1619 
addExtraLight(osg::ref_ptr<osg::Group> parent,const ESM::Light * esmLight)1620     void Animation::addExtraLight(osg::ref_ptr<osg::Group> parent, const ESM::Light *esmLight)
1621     {
1622         bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
1623 
1624         mExtraLightSource = SceneUtil::addLight(parent, esmLight, Mask_ParticleSystem, Mask_Lighting, exterior);
1625     }
1626 
addEffect(const std::string & model,int effectId,bool loop,const std::string & bonename,const std::string & texture)1627     void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture)
1628     {
1629         if (!mObjectRoot.get())
1630             return;
1631 
1632         // Early out if we already have this effect
1633         FindVfxCallbacksVisitor visitor(effectId);
1634         mInsert->accept(visitor);
1635 
1636         for (std::vector<UpdateVfxCallback*>::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it)
1637         {
1638             UpdateVfxCallback* callback = *it;
1639 
1640             if (loop && !callback->mFinished && callback->mParams.mLoop && callback->mParams.mBoneName == bonename)
1641                 return;
1642         }
1643 
1644         EffectParams params;
1645         params.mModelName = model;
1646         osg::ref_ptr<osg::Group> parentNode;
1647         if (bonename.empty())
1648             parentNode = mInsert;
1649         else
1650         {
1651             NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename));
1652             if (found == getNodeMap().end())
1653                 throw std::runtime_error("Can't find bone " + bonename);
1654 
1655             parentNode = found->second;
1656         }
1657 
1658         osg::ref_ptr<osg::PositionAttitudeTransform> trans = new osg::PositionAttitudeTransform;
1659         if (!mPtr.getClass().isNpc())
1660         {
1661             osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mPtr) * 2.f / Constants::UnitsPerFoot);
1662             float scale = std::max({ bounds.x()/3.f, bounds.y()/3.f, bounds.z()/6.f });
1663             trans->setScale(osg::Vec3f(scale, scale, scale));
1664         }
1665         parentNode->addChild(trans);
1666 
1667         osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(model, trans);
1668         node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
1669 
1670         SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor;
1671         node->accept(findMaxLengthVisitor);
1672 
1673         // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters
1674         SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor;
1675         node->accept(disableFreezeOnCullVisitor);
1676         node->setNodeMask(Mask_Effect);
1677 
1678         params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength();
1679         params.mLoop = loop;
1680         params.mEffectId = effectId;
1681         params.mBoneName = bonename;
1682         params.mAnimTime = std::shared_ptr<EffectAnimationTime>(new EffectAnimationTime);
1683         trans->addUpdateCallback(new UpdateVfxCallback(params));
1684 
1685         SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr<SceneUtil::ControllerSource>(params.mAnimTime));
1686         node->accept(assignVisitor);
1687 
1688         // Notify that this animation has attached magic effects
1689         mHasMagicEffects = true;
1690 
1691         overrideFirstRootTexture(texture, mResourceSystem, node);
1692     }
1693 
removeEffect(int effectId)1694     void Animation::removeEffect(int effectId)
1695     {
1696         RemoveCallbackVisitor visitor(effectId);
1697         mInsert->accept(visitor);
1698         visitor.remove();
1699         mHasMagicEffects = visitor.mHasMagicEffects;
1700     }
1701 
removeEffects()1702     void Animation::removeEffects()
1703     {
1704         removeEffect(-1);
1705     }
1706 
getLoopingEffects(std::vector<int> & out) const1707     void Animation::getLoopingEffects(std::vector<int> &out) const
1708     {
1709         if (!mHasMagicEffects)
1710             return;
1711 
1712         FindVfxCallbacksVisitor visitor;
1713         mInsert->accept(visitor);
1714 
1715         for (std::vector<UpdateVfxCallback*>::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it)
1716         {
1717             UpdateVfxCallback* callback = *it;
1718 
1719             if (callback->mParams.mLoop && !callback->mFinished)
1720                 out.push_back(callback->mParams.mEffectId);
1721         }
1722     }
1723 
updateEffects()1724     void Animation::updateEffects()
1725     {
1726         // We do not need to visit scene every frame.
1727         // We can use a bool flag to check in spellcasting effect found.
1728         if (!mHasMagicEffects)
1729             return;
1730 
1731         // TODO: objects without animation still will have
1732         // transformation nodes with finished callbacks
1733         RemoveFinishedCallbackVisitor visitor;
1734         mInsert->accept(visitor);
1735         visitor.remove();
1736         mHasMagicEffects = visitor.mHasMagicEffects;
1737     }
1738 
upperBodyReady() const1739     bool Animation::upperBodyReady() const
1740     {
1741         for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter)
1742         {
1743             if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Hit))
1744                     || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Weapon))
1745                     || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Knockdown))
1746                     || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Death)))
1747                 return false;
1748         }
1749         return true;
1750     }
1751 
getNode(const std::string & name) const1752     const osg::Node* Animation::getNode(const std::string &name) const
1753     {
1754         std::string lowerName = Misc::StringUtils::lowerCase(name);
1755         NodeMap::const_iterator found = getNodeMap().find(lowerName);
1756         if (found == getNodeMap().end())
1757             return nullptr;
1758         else
1759             return found->second;
1760     }
1761 
setAlpha(float alpha)1762     void Animation::setAlpha(float alpha)
1763     {
1764         if (alpha == mAlpha)
1765             return;
1766         mAlpha = alpha;
1767 
1768         // TODO: we use it to fade actors away too, but it would be nice to have a dithering shader instead.
1769         if (alpha != 1.f)
1770         {
1771             if (mTransparencyUpdater == nullptr)
1772             {
1773                 mTransparencyUpdater = new TransparencyUpdater(alpha);
1774                 mTransparencyUpdater->setLightSource(mExtraLightSource);
1775                 mObjectRoot->addCullCallback(mTransparencyUpdater);
1776             }
1777             else
1778                 mTransparencyUpdater->setAlpha(alpha);
1779         }
1780         else
1781         {
1782             mObjectRoot->removeCullCallback(mTransparencyUpdater);
1783             mTransparencyUpdater = nullptr;
1784         }
1785     }
1786 
setLightEffect(float effect)1787     void Animation::setLightEffect(float effect)
1788     {
1789         if (effect == 0)
1790         {
1791             if (mGlowLight)
1792             {
1793                 mInsert->removeChild(mGlowLight);
1794                 mGlowLight = nullptr;
1795             }
1796         }
1797         else
1798         {
1799             // 1 pt of Light magnitude corresponds to 1 foot of radius
1800             float radius = effect * std::ceil(Constants::UnitsPerFoot);
1801             // Arbitrary multiplier used to make the obvious cut-off less obvious
1802             float cutoffMult = 3;
1803 
1804             if (!mGlowLight || (radius * cutoffMult) != mGlowLight->getRadius())
1805             {
1806                 if (mGlowLight)
1807                 {
1808                     mInsert->removeChild(mGlowLight);
1809                     mGlowLight = nullptr;
1810                 }
1811 
1812                 osg::ref_ptr<osg::Light> light (new osg::Light);
1813                 light->setDiffuse(osg::Vec4f(0,0,0,0));
1814                 light->setSpecular(osg::Vec4f(0,0,0,0));
1815                 light->setAmbient(osg::Vec4f(1.5f,1.5f,1.5f,1.f));
1816 
1817                 bool isExterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
1818                 SceneUtil::configureLight(light, radius, isExterior);
1819 
1820                 mGlowLight = new SceneUtil::LightSource;
1821                 mGlowLight->setNodeMask(Mask_Lighting);
1822                 mInsert->addChild(mGlowLight);
1823                 mGlowLight->setLight(light);
1824             }
1825 
1826             mGlowLight->setRadius(radius * cutoffMult);
1827         }
1828     }
1829 
addControllers()1830     void Animation::addControllers()
1831     {
1832         mHeadController = addRotateController("bip01 head");
1833         mSpineController = addRotateController("bip01 spine1");
1834         mRootController = addRotateController("bip01");
1835     }
1836 
addRotateController(std::string bone)1837     RotateController* Animation::addRotateController(std::string bone)
1838     {
1839         auto iter = getNodeMap().find(bone);
1840         if (iter == getNodeMap().end())
1841             return nullptr;
1842         osg::MatrixTransform* node = iter->second;
1843 
1844         bool foundKeyframeCtrl = false;
1845         osg::Callback* cb = node->getUpdateCallback();
1846         while (cb)
1847         {
1848             if (dynamic_cast<SceneUtil::KeyframeController*>(cb))
1849             {
1850                 foundKeyframeCtrl = true;
1851                 break;
1852             }
1853             cb = cb->getNestedCallback();
1854         }
1855         // Without KeyframeController the orientation will not be reseted each frame, so
1856         // RotateController shouldn't be used for such nodes.
1857         if (!foundKeyframeCtrl)
1858             return nullptr;
1859 
1860         RotateController* controller = new RotateController(mObjectRoot.get());
1861         node->addUpdateCallback(controller);
1862         mActiveControllers.emplace_back(node, controller);
1863         return controller;
1864     }
1865 
setHeadPitch(float pitchRadians)1866     void Animation::setHeadPitch(float pitchRadians)
1867     {
1868         mHeadPitchRadians = pitchRadians;
1869     }
1870 
setHeadYaw(float yawRadians)1871     void Animation::setHeadYaw(float yawRadians)
1872     {
1873         mHeadYawRadians = yawRadians;
1874     }
1875 
getHeadPitch() const1876     float Animation::getHeadPitch() const
1877     {
1878         return mHeadPitchRadians;
1879     }
1880 
getHeadYaw() const1881     float Animation::getHeadYaw() const
1882     {
1883         return mHeadYawRadians;
1884     }
1885 
1886     // ------------------------------------------------------
1887 
getValue(osg::NodeVisitor *)1888     float Animation::AnimationTime::getValue(osg::NodeVisitor*)
1889     {
1890         if (mTimePtr)
1891             return *mTimePtr;
1892         return 0.f;
1893     }
1894 
getValue(osg::NodeVisitor *)1895     float EffectAnimationTime::getValue(osg::NodeVisitor*)
1896     {
1897         return mTime;
1898     }
1899 
addTime(float duration)1900     void EffectAnimationTime::addTime(float duration)
1901     {
1902         mTime += duration;
1903     }
1904 
resetTime(float time)1905     void EffectAnimationTime::resetTime(float time)
1906     {
1907         mTime = time;
1908     }
1909 
getTime() const1910     float EffectAnimationTime::getTime() const
1911     {
1912         return mTime;
1913     }
1914 
1915     // --------------------------------------------------------------------------------
1916 
ObjectAnimation(const MWWorld::Ptr & ptr,const std::string & model,Resource::ResourceSystem * resourceSystem,bool animated,bool allowLight)1917     ObjectAnimation::ObjectAnimation(const MWWorld::Ptr &ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight)
1918         : Animation(ptr, osg::ref_ptr<osg::Group>(ptr.getRefData().getBaseNode()), resourceSystem)
1919     {
1920         if (!model.empty())
1921         {
1922             setObjectRoot(model, false, false, false);
1923             if (animated)
1924                 addAnimSource(model, model);
1925 
1926             if (!ptr.getClass().getEnchantment(ptr).empty())
1927                 mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
1928         }
1929         if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight)
1930             addExtraLight(getOrCreateObjectRoot(), ptr.get<ESM::Light>()->mBase);
1931 
1932         if (!allowLight && mObjectRoot)
1933         {
1934             RemoveParticlesVisitor visitor;
1935             mObjectRoot->accept(visitor);
1936             visitor.remove();
1937         }
1938 
1939         if (SceneUtil::hasUserDescription(mObjectRoot, Constants::NightDayLabel))
1940         {
1941             AddSwitchCallbacksVisitor visitor;
1942             mObjectRoot->accept(visitor);
1943         }
1944 
1945         if (ptr.getRefData().getCustomData() != nullptr && canBeHarvested())
1946         {
1947             const MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
1948             if (!store.hasVisibleItems())
1949             {
1950                 HarvestVisitor visitor;
1951                 mObjectRoot->accept(visitor);
1952             }
1953         }
1954     }
1955 
canBeHarvested() const1956     bool ObjectAnimation::canBeHarvested() const
1957     {
1958         if (mPtr.getTypeName() != typeid(ESM::Container).name())
1959             return false;
1960 
1961         const MWWorld::LiveCellRef<ESM::Container>* ref = mPtr.get<ESM::Container>();
1962         if (!(ref->mBase->mFlags & ESM::Container::Organic))
1963             return false;
1964 
1965         return SceneUtil::hasUserDescription(mObjectRoot, Constants::HerbalismLabel);
1966     }
1967 
~AnimState()1968     Animation::AnimState::~AnimState()
1969     {
1970 
1971     }
1972 
1973     // ------------------------------
1974 
PartHolder(osg::ref_ptr<osg::Node> node)1975     PartHolder::PartHolder(osg::ref_ptr<osg::Node> node)
1976         : mNode(node)
1977     {
1978     }
1979 
~PartHolder()1980     PartHolder::~PartHolder()
1981     {
1982         if (mNode.get() && !mNode->getNumParents())
1983             Log(Debug::Verbose) << "Part \"" << mNode->getName() << "\" has no parents" ;
1984 
1985         if (mNode.get() && mNode->getNumParents())
1986         {
1987             if (mNode->getNumParents() > 1)
1988                 Log(Debug::Verbose) << "Part \"" << mNode->getName() << "\" has multiple (" << mNode->getNumParents() << ") parents";
1989             mNode->getParent(0)->removeChild(mNode);
1990         }
1991     }
1992 }
1993