diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 47fcf2499..9d9ae4f31 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -43,7 +43,7 @@ add_component_dir (resource ) add_component_dir (sceneutil - clone + clone attach ) add_component_dir (nif diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp new file mode 100644 index 000000000..7cfb80b5c --- /dev/null +++ b/components/sceneutil/attach.cpp @@ -0,0 +1,185 @@ +#include "attach.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace SceneUtil +{ + + class FindByNameVisitor : public osg::NodeVisitor + { + public: + FindByNameVisitor(const std::string& nameToFind) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mNameToFind(nameToFind) + , mFoundNode(NULL) + { + } + + virtual void apply(osg::Node &node) + { + osg::Group* group = node.asGroup(); + if (group && node.getName() == mNameToFind) + { + mFoundNode = group; + return; + } + traverse(node); + } + + const std::string& mNameToFind; + osg::Group* mFoundNode; + }; + + /// Copy the skeleton-space matrix of a "source" bone to a "dest" bone (the bone that the callback is attached to). + /// Must be set on a Bone. + class CopyController : public osg::NodeCallback + { + public: + CopyController(osgAnimation::Bone* copyFrom) + : mCopyFrom(copyFrom) + { + } + CopyController(const CopyController& copy, const osg::CopyOp& copyop) + : osg::NodeCallback(copy, copyop) + , mCopyFrom(copy.mCopyFrom) + { + } + CopyController() + : mCopyFrom(NULL) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgAnimation::Bone* bone = static_cast(node); + + if (mCopyFrom) + { + bone->setMatrix(mCopyFrom->getMatrix()); + bone->setMatrixInSkeletonSpace(mCopyFrom->getMatrixInSkeletonSpace()); + } + + traverse(node, nv); + } + + private: + const osgAnimation::Bone* mCopyFrom; + }; + + class AddCopyControllerVisitor : public osg::NodeVisitor + { + public: + AddCopyControllerVisitor(const osgAnimation::BoneMap& boneMap) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mBoneMap(boneMap) + { + } + + virtual void apply(osg::MatrixTransform &node) + { + if (osgAnimation::Bone* bone = dynamic_cast(&node)) + { + osgAnimation::BoneMap::const_iterator found = mBoneMap.find(bone->getName()); + if (found != mBoneMap.end()) + { + bone->setUpdateCallback(new CopyController(found->second.get())); + } + } + } + + private: + const osgAnimation::BoneMap& mBoneMap; + }; + + // FIXME: would be more efficient to copy only the wanted nodes instead of deleting unwanted ones later + class FilterVisitor : public osg::NodeVisitor + { + public: + FilterVisitor(const std::string& filter) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mFilter(filter) + { + } + + virtual void apply(osg::Geode &node) + { + if (node.getName().find(mFilter) == std::string::npos) + { + mToRemove.push_back(&node); + } + } + + void removeFilteredParts() + { + for (std::vector::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) + { + osg::Geode* geode = *it; + geode->getParent(0)->removeChild(geode); + } + } + + private: + std::vector mToRemove; + const std::string& mFilter; + }; + + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node *master, const std::string &filter, const std::string &attachNode) + { + if (osgAnimation::Skeleton* skel = dynamic_cast(toAttach.get())) + { + osgAnimation::Skeleton* masterSkel = dynamic_cast(master); + osgAnimation::BoneMapVisitor boneMapVisitor; + masterSkel->accept(boneMapVisitor); + + AddCopyControllerVisitor visitor(boneMapVisitor.getBoneMap()); + toAttach->accept(visitor); + + FilterVisitor filterVisitor(filter); + toAttach->accept(filterVisitor); + filterVisitor.removeFilteredParts(); + + master->asGroup()->addChild(skel); + return skel; + } + else + { + FindByNameVisitor find(attachNode); + master->accept(find); + if (!find.mFoundNode) + throw std::runtime_error(std::string("Can't find attachment node ") + attachNode); + + if (attachNode.find("Left") != std::string::npos) + { + osg::ref_ptr trans = new osg::PositionAttitudeTransform; + trans->setScale(osg::Vec3f(-1.f, 1.f, 1.f)); + + // Need to invert culling because of the negative scale + // Note: for absolute correctness we would need to check the current front face for every mesh then invert it + // However MW isn't doing this either, so don't. Assuming all meshes are using backface culling is more efficient. + osg::FrontFace* frontFace = new osg::FrontFace; + frontFace->setMode(osg::FrontFace::CLOCKWISE); + toAttach->getOrCreateStateSet()->setAttributeAndModes(frontFace, osg::StateAttribute::ON); + + find.mFoundNode->addChild(trans); + trans->addChild(toAttach); + return trans; + } + else + { + find.mFoundNode->addChild(toAttach); + return toAttach; + } + } + } + +} diff --git a/components/sceneutil/attach.hpp b/components/sceneutil/attach.hpp new file mode 100644 index 000000000..72f7809e9 --- /dev/null +++ b/components/sceneutil/attach.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_ATTACH_H +#define OPENMW_COMPONENTS_SCENEUTIL_ATTACH_H + +#include + +#include + +namespace osg +{ + class Node; +} + +namespace SceneUtil +{ + + /// Attach parts of the \a toAttach scenegraph to the \a master scenegraph, using the specified filter and attachment node. + /// If the \a toAttach scene graph contains skinned objects, we will attach only those (filtered by the \a filter). + /// Otherwise, just attach all of the toAttach scenegraph to the attachment node on the master scenegraph, with no filtering. + /// @note The master scene graph is expected to include a skeleton. + /// @return A newly created node that is directly attached to the master scene graph + osg::ref_ptr attach(osg::ref_ptr toAttach, osg::Node* master, const std::string& filter, const std::string& attachNode); + +} + +#endif