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