diff --git a/CMakeLists.txt b/CMakeLists.txt index 37696f44c..76764fbd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,7 +259,7 @@ if(NOT HAVE_STDINT_H) endif() -find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow) +find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX osgShadow osgAnimation) include_directories(SYSTEM ${OPENSCENEGRAPH_INCLUDE_DIRS}) set(USED_OSG_PLUGINS diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f8ff3780d..8c9f5f493 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -782,6 +782,8 @@ namespace MWRender NodeMap::const_iterator found = nodeMap.find("bip01"); if (found == nodeMap.end()) found = nodeMap.find("root bone"); + if (found == nodeMap.end()) + found = nodeMap.find("root"); if (found != nodeMap.end()) mAccumRoot = found->second; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 08f183f5e..3c1899037 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,7 +41,8 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager stats + scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem + resourcemanager stats animation ) add_component_dir (shader @@ -51,7 +52,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer - actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin + actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller ) add_component_dir (nif diff --git a/components/resource/animation.cpp b/components/resource/animation.cpp new file mode 100644 index 000000000..34ae162ee --- /dev/null +++ b/components/resource/animation.cpp @@ -0,0 +1,40 @@ +#include + +#include +#include + +namespace Resource +{ + Animation::Animation(const Animation& anim, const osg::CopyOp& copyop): osg::Object(anim, copyop), + mDuration(0.0f), + mStartTime(0.0f) + { + const osgAnimation::ChannelList& channels = anim.getChannels(); + for (const osg::ref_ptr channel: channels) + addChannel(channel.get()->clone()); + } + + void Animation::addChannel(osg::ref_ptr pChannel) + { + mChannels.push_back(pChannel); + } + + std::vector>& Animation::getChannels() + { + return mChannels; + } + + const std::vector>& Animation::getChannels() const + { + return mChannels; + } + + bool Animation::update (double time) + { + for (const osg::ref_ptr channel: mChannels) + { + channel->update(time, 1.0f, 0); + } + return true; + } +} diff --git a/components/resource/animation.hpp b/components/resource/animation.hpp new file mode 100644 index 000000000..885394747 --- /dev/null +++ b/components/resource/animation.hpp @@ -0,0 +1,39 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_ANIMATION_HPP +#define OPENMW_COMPONENTS_RESOURCE_ANIMATION_HPP + +#include + +#include +#include +#include + +namespace Resource +{ + /// Stripped down class of osgAnimation::Animation, only needed for OSG's plugin formats like dae + class Animation : public osg::Object + { + public: + META_Object(Resource, Animation) + + Animation() : + mDuration(0.0), mStartTime(0) {} + + Animation(const Animation&, const osg::CopyOp&); + ~Animation() {} + + void addChannel (osg::ref_ptr pChannel); + + std::vector>& getChannels(); + + const std::vector>& getChannels() const; + + bool update (double time); + + protected: + double mDuration; + double mStartTime; + std::vector> mChannels; + }; +} + +#endif diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index ef6339adb..6cda9728c 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -2,29 +2,86 @@ #include +#include +#include +#include + #include +#include +#include +#include "animation.hpp" #include "objectcache.hpp" #include "scenemanager.hpp" -namespace +namespace OsgAOpenMW { - class RetrieveAnimationsVisitor : public osg::NodeVisitor - { - public: - RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target) {} + RetrieveAnimationsVisitor::RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mTarget(target), mAnimationManager(animationManager) {} - virtual void apply(osg::Node& node) - { - if (node.libraryName() == std::string("osgAnimation")) - std::cout << "found an " << node.className() << std::endl; - traverse(node); - } + void RetrieveAnimationsVisitor::apply(osg::Node& node) + { + if (node.libraryName() == std::string("osgAnimation") && node.className() == std::string("Bone") && node.getName() == std::string("root")) + { + if (!mAnimationManager) + { + traverse(node); + return; + } + + osg::ref_ptr callback = new OsgaController::KeyframeController(); + + std::vector emulatedAnimations; + + for (auto animation : mAnimationManager->getAnimationList()) + { + if (animation) + { + if (animation->getName() == "Default") //"Default" is osg dae plugin's default naming scheme for unnamed animations + { + animation->setName(std::string("idle")); // animation naming scheme "idle: start" and "idle: stop" is the default idle animation that OpenMW seems to want to play + } + + osg::ref_ptr mergedAnimationTrack = new Resource::Animation; + std::string animationName = animation->getName(); + std::string start = animationName + std::string(": start"); + std::string stop = animationName + std::string(": stop"); + std::string loopstart = animationName + std::string(": loop start"); + std::string loopstop = animationName + std::string(": loop stop"); + + const osgAnimation::ChannelList& channels = animation->getChannels(); + for (const osg::ref_ptr channel: channels) + { + mergedAnimationTrack->addChannel(channel.get()->clone()); // is ->clone needed? + } + mergedAnimationTrack->setName(animation->getName()); + callback->addMergedAnimationTrack(mergedAnimationTrack); + + float startTime = animation->getStartTime(); + float stopTime = startTime + animation->getDuration(); + + // mTextKeys is a nif-thing, used by OpenMW's animation system + // Format is likely "AnimationName: [Keyword_optional] [Start OR Stop]" + // AnimationNames are keywords like idle2, idle3... AiPackages and various mechanics control which animations are played + // Keywords can be stuff like Loop, Equip, Unequip, Block, InventoryHandtoHand, InventoryWeaponOneHand, PickProbe, Slash, Thrust, Chop... even "Slash Small Follow" + mTarget.mTextKeys.emplace(startTime, std::move(start)); + mTarget.mTextKeys.emplace(stopTime, std::move(stop)); + mTarget.mTextKeys.emplace(startTime, std::move(loopstart)); + mTarget.mTextKeys.emplace(stopTime, std::move(loopstop)); + + SceneUtil::EmulatedAnimation emulatedAnimation; + emulatedAnimation.mStartTime = startTime; + emulatedAnimation.mStopTime = stopTime; + emulatedAnimation.mName = animationName; + emulatedAnimations.emplace_back(emulatedAnimation); + } + } + callback->setEmulatedAnimations(emulatedAnimations); + mTarget.mKeyframeControllers.emplace(node.getName(), callback); + } - private: - SceneUtil::KeyframeHolder& mTarget; - }; + traverse(node); + } std::string getFileExtension(const std::string& file) { @@ -59,16 +116,17 @@ namespace Resource else { osg::ref_ptr loaded (new SceneUtil::KeyframeHolder); - std::string ext = getFileExtension(normalized); + std::string ext = OsgAOpenMW::getFileExtension(normalized); if (ext == "kf") { NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); } else { - osg::ref_ptr scene = mSceneManager->getTemplate(normalized); - RetrieveAnimationsVisitor rav(*loaded.get()); - const_cast(scene.get())->accept(rav); // const_cast required because there is no const version of osg::NodeVisitor + osg::ref_ptr scene = const_cast ( mSceneManager->getTemplate(normalized).get() ); + osg::ref_ptr bam = dynamic_cast (scene->getUpdateCallback()); + OsgAOpenMW::RetrieveAnimationsVisitor rav(*loaded.get(), bam); + scene->accept(rav); } mCache->addEntryToObjectCache(normalized, loaded); return loaded; diff --git a/components/resource/keyframemanager.hpp b/components/resource/keyframemanager.hpp index e186e2783..4f83196f3 100644 --- a/components/resource/keyframemanager.hpp +++ b/components/resource/keyframemanager.hpp @@ -2,12 +2,32 @@ #define OPENMW_COMPONENTS_KEYFRAMEMANAGER_H #include +#include #include #include #include "resourcemanager.hpp" +namespace OsgAOpenMW +{ + /// @brief extract animations to OpenMW's animation system + class RetrieveAnimationsVisitor : public osg::NodeVisitor + { + public: + RetrieveAnimationsVisitor(SceneUtil::KeyframeHolder& target, osg::ref_ptr animationManager); + + virtual void apply(osg::Node& node); + + private: + SceneUtil::KeyframeHolder& mTarget; + osg::ref_ptr mAnimationManager; + + }; + + std::string getFileExtension(const std::string& file); +} + namespace Resource { diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 44ba7e687..e99003b74 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -492,7 +492,7 @@ namespace Resource } catch (std::exception& e) { - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2" }; + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }; for (unsigned int i=0; i +#include +#include +#include +#include + #include #include #include @@ -30,6 +35,11 @@ namespace SceneUtil mUpdaterToOldPs[cloned] = updater->getParticleSystem(0); return cloned; } + + if (dynamic_cast(node) || dynamic_cast(node)) + { + return osg::clone(node, *this); + } return osg::CopyOp::operator()(node); } @@ -38,7 +48,7 @@ namespace SceneUtil if (const osgParticle::ParticleSystem* partsys = dynamic_cast(drawable)) return operator()(partsys); - if (dynamic_cast(drawable) || dynamic_cast(drawable)) + if (dynamic_cast(drawable) || dynamic_cast(drawable) || dynamic_cast(drawable) || dynamic_cast(drawable)) { return static_cast(drawable->clone(*this)); } diff --git a/components/sceneutil/keyframe.hpp b/components/sceneutil/keyframe.hpp index c993c7b7c..5a0435469 100644 --- a/components/sceneutil/keyframe.hpp +++ b/components/sceneutil/keyframe.hpp @@ -7,9 +7,16 @@ #include #include +#include namespace SceneUtil { + struct EmulatedAnimation + { + float mStartTime; + float mStopTime; + std::string mName; + }; class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller { @@ -19,12 +26,18 @@ namespace SceneUtil KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop) : osg::NodeCallback(copy, copyop) , SceneUtil::Controller(copy) + , mMergedAnimationTracks(copy.mMergedAnimationTracks) + , mEmulatedAnimations(copy.mEmulatedAnimations) {} META_Object(SceneUtil, KeyframeController) virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } virtual void operator() (osg::Node* node, osg::NodeVisitor* nodeVisitor) { traverse(node, nodeVisitor); } + + protected: + std::vector> mMergedAnimationTracks; // Used only by osgAnimation-based formats (e.g. dae) + std::vector mEmulatedAnimations; }; /// Wrapper object containing an animation track as a ref-countable osg::Object. diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp new file mode 100644 index 000000000..6b635d4ea --- /dev/null +++ b/components/sceneutil/osgacontroller.cpp @@ -0,0 +1,195 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace OsgaController +{ + LinkVisitor::LinkVisitor() : osg::NodeVisitor( TRAVERSE_ALL_CHILDREN ) + { + mAnimation = nullptr; + } + + void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) + { + const osgAnimation::ChannelList& channels = mAnimation->getChannels(); + for (const osg::ref_ptr channel: channels) + { + const std::string& channelName = channel->getName(); + const std::string& channelTargetName = channel->getTargetName(); + + if (channelTargetName != umt->getName()) continue; + + // check if we can link a StackedTransformElement to the current Channel + for (auto stackedTransform : umt->getStackedTransforms()) + { + osgAnimation::StackedTransformElement* element = stackedTransform.get(); + if (element && !element->getName().empty() && channelName == element->getName()) + { + osgAnimation::Target* target = element->getOrCreateTarget(); + if (target) + { + channel->setTarget(target); + } + } + } + } + } + + void LinkVisitor::handle_stateset(osg::StateSet* stateset) + { + if (!stateset) + return; + const osg::StateSet::AttributeList& attributeList = stateset->getAttributeList(); + for (auto attribute : attributeList) + { + osg::StateAttribute* sattr = attribute.second.first.get(); + osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(sattr->getUpdateCallback()); //Can this even be in sa? + if (umt) link(umt); + } + } + + void LinkVisitor::setAnimation(Resource::Animation* animation) + { + mAnimation = animation; + } + + void LinkVisitor::apply(osg::Node& node) + { + osg::StateSet* st = node.getStateSet(); + if (st) + handle_stateset(st); + + osg::Callback* cb = node.getUpdateCallback(); + while (cb) + { + osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(cb); + if (umt) + if (node.getName() != "root") link(umt); + cb = cb->getNestedCallback(); + } + + traverse( node ); + } + + void LinkVisitor::apply(osg::Geode& node) + { + for (unsigned int i = 0; i < node.getNumDrawables(); i++) + { + osg::Drawable* drawable = node.getDrawable(i); + if (drawable && drawable->getStateSet()) + handle_stateset(drawable->getStateSet()); + } + apply(static_cast(node)); + } + + KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) + { + mLinker = nullptr; + } + + osg::Vec3f KeyframeController::getTranslation(float time) const + { + osg::Vec3f translationValue; + std::string animationName; + float newTime = time; + + //Find the correct animation based on time + for (auto emulatedAnimation : mEmulatedAnimations) + { + if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) + { + newTime = time - emulatedAnimation.mStartTime; + animationName = emulatedAnimation.mName; + } + } + + //Find the root transform track in animation + for (auto mergedAnimationTrack : mMergedAnimationTracks) + { + if (mergedAnimationTrack->getName() != animationName) continue; + + const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); + + for (const osg::ref_ptr channel: channels) + { + if (channel->getTargetName() != "root" || channel->getName() != "transform") continue; + + if ( osgAnimation::MatrixLinearSampler* templateSampler = dynamic_cast (channel->getSampler()) ) + { + osg::Matrixf matrix; + templateSampler->getValueAt(newTime, matrix); + translationValue = matrix.getTrans(); + return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]); + } + } + } + + return osg::Vec3f(); + } + + void KeyframeController::update(float time, std::string animationName) + { + for (auto mergedAnimationTrack : mMergedAnimationTracks) + { + if (mergedAnimationTrack->getName() == animationName) mergedAnimationTrack->update(time); + } + } + + void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) + { + if (hasInput()) + { + if (mNeedToLink) + { + for (auto mergedAnimationTrack : mMergedAnimationTracks) + { + if (!mLinker.valid()) mLinker = new LinkVisitor(); + mLinker->setAnimation(mergedAnimationTrack); + node->accept(*mLinker); + } + mNeedToLink = false; + } + + float time = getInputValue(nv); + + for (auto emulatedAnimation : mEmulatedAnimations) + { + if (time > emulatedAnimation.mStartTime && time < emulatedAnimation.mStopTime) + { + update(time - emulatedAnimation.mStartTime, emulatedAnimation.mName); + } + } + } + + traverse(node, nv); + } + + void KeyframeController::setEmulatedAnimations(std::vector emulatedAnimations) + { + mEmulatedAnimations = emulatedAnimations; + } + + void KeyframeController::addMergedAnimationTrack(osg::ref_ptr animationTrack) + { + mMergedAnimationTracks.emplace_back(animationTrack); + } +} diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp new file mode 100644 index 000000000..4ae122199 --- /dev/null +++ b/components/sceneutil/osgacontroller.hpp @@ -0,0 +1,70 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_OSGACONTROLLER_HPP +#define OPENMW_COMPONENTS_SCENEUTIL_OSGACONTROLLER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace OsgaController +{ + class LinkVisitor : public osg::NodeVisitor + { + public: + LinkVisitor(); + + virtual void link(osgAnimation::UpdateMatrixTransform* umt); + + virtual void handle_stateset(osg::StateSet* stateset); + + virtual void setAnimation(Resource::Animation* animation); + + virtual void apply(osg::Node& node); + + virtual void apply(osg::Geode& node); + + protected: + Resource::Animation* mAnimation; + }; + + class KeyframeController : public SceneUtil::KeyframeController + { + public: + /// @brief Handles the animation for osgAnimation formats + KeyframeController() {}; + + KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop); + + META_Object(OsgaController, KeyframeController) + + /// @brief Handles the location of the instance + osg::Vec3f getTranslation(float time) const override; + + /// @brief Calls animation track update() + void update(float time, std::string animationName); + + /// @brief Called every frame for osgAnimation + void operator() (osg::Node*, osg::NodeVisitor*) override; + + /// @brief Sets details of the animations + void setEmulatedAnimations(std::vector emulatedAnimations); + + /// @brief Adds an animation track to a model + void addMergedAnimationTrack(osg::ref_ptr animationTrack); + private: + bool mNeedToLink = true; + osg::ref_ptr mLinker; + }; +} + +#endif