From 4ea6d4aa0107b95a0ec0655ba1c3e88458baea9e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 21 Apr 2015 16:02:40 +0200 Subject: [PATCH] Rewrite of skinning code Goals: - get rid of the mesh pre-transform (this requires supporting different bind matrices for each mesh) - bounding box should be relative to the bone the mesh is attached to, ideally we can then get rid of the expensive skeleton-based bounding boxes - update bone matrices in CullCallback instead of UpdateCallback Works OK, though the bounding boxes are not correct yet. --- components/CMakeLists.txt | 2 +- components/nifosg/nifloader.cpp | 345 +++++------------------------- components/nifosg/riggeometry.cpp | 201 +++++++++++++++++ components/nifosg/riggeometry.hpp | 56 +++++ components/nifosg/skeleton.cpp | 148 +++++++++++++ components/nifosg/skeleton.hpp | 58 +++++ 6 files changed, 520 insertions(+), 290 deletions(-) create mode 100644 components/nifosg/riggeometry.cpp create mode 100644 components/nifosg/riggeometry.hpp create mode 100644 components/nifosg/skeleton.cpp create mode 100644 components/nifosg/skeleton.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f61724eeb..b49beea0b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -47,7 +47,7 @@ add_component_dir (nif ) add_component_dir (nifosg - nifloader controller particle userdata + nifloader controller particle userdata skeleton riggeometry ) #add_component_dir (nifcache diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 1f0c36839..f0bb538f2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include + #include // resource @@ -14,11 +17,7 @@ #include // skel -#include -#include -#include #include -#include // particle #include @@ -45,6 +44,8 @@ #include "particle.hpp" #include "userdata.hpp" +#include "skeleton.hpp" +#include "riggeometry.hpp" namespace { @@ -206,32 +207,6 @@ namespace } }; - // NodeCallback used to update the bone matrices in skeleton space as needed for skinning. - // Must be set on a Bone. - class UpdateBone : public osg::NodeCallback - { - public: - UpdateBone() {} - UpdateBone(const UpdateBone& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) - : osg::Object(copy, copyop), osg::NodeCallback(copy, copyop) - { - } - - META_Object(NifOsg, UpdateBone) - - // Callback method called by the NodeVisitor when visiting a node. - void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - osgAnimation::Bone* b = static_cast(node); - osgAnimation::Bone* parent = b->getBoneParent(); - if (parent) - b->setMatrixInSkeletonSpace(b->getMatrixInBoneSpace() * parent->getMatrixInSkeletonSpace()); - else - b->setMatrixInSkeletonSpace(b->getMatrixInBoneSpace()); - traverse(node,nv); - } - }; - // NodeCallback used to have a transform always oriented towards the camera. Can have translation and scale // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. class BillboardCallback : public osg::NodeCallback @@ -304,7 +279,7 @@ namespace for (osg::NodePath::iterator it = path.begin(); it != path.end(); ++it) { - if (dynamic_cast(*it)) + if (dynamic_cast(*it)) { path.erase(path.begin(), it+1); // the bone's transform in skeleton space @@ -357,95 +332,6 @@ namespace }; */ - // Node callback used to dirty a RigGeometry's bounding box every frame, so that RigBoundingBoxCallback updates. - // This has to be attached to the geode, because the RigGeometry's Drawable::UpdateCallback is already used internally and not extensible. - // Kind of awful, not sure of a better way to do this. - class DirtyBoundCallback : public osg::NodeCallback - { - public: - DirtyBoundCallback() - { - } - DirtyBoundCallback(const DirtyBoundCallback& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) - { - } - - META_Object(NifOsg, DirtyBoundCallback) - - void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - osg::Geode* geode = node->asGeode(); - if (geode && geode->getNumDrawables()) - { - geode->getDrawable(0)->dirtyBound(); - } - traverse(node, nv); - } - }; - - struct FindNearestParentSkeleton : public osg::NodeVisitor - { - osg::ref_ptr _root; - FindNearestParentSkeleton() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_PARENTS) {} - void apply(osg::Transform& node) - { - if (_root.valid()) - return; - _root = dynamic_cast(&node); - traverse(node); - } - }; - - // RigGeometry is skinned from a CullCallback to prevent unnecessary updates of culled rig geometries. - // Note: this assumes only one cull thread is using the given RigGeometry, there would be a race condition otherwise. - struct UpdateRigGeometry : public osg::Drawable::CullCallback - { - UpdateRigGeometry() - { - } - - UpdateRigGeometry(const UpdateRigGeometry& copy, const osg::CopyOp& copyop) - : osg::Drawable::CullCallback(copy, copyop) - { - } - - META_Object(NifOsg, UpdateRigGeometry) - - virtual bool cull(osg::NodeVisitor *, osg::Drawable * drw, osg::State *) const - { - osgAnimation::RigGeometry* geom = static_cast(drw); - if (!geom) - return false; - if (!geom->getSkeleton() && !geom->getParents().empty()) - { - FindNearestParentSkeleton finder; - if (geom->getParents().size() > 1) - osg::notify(osg::WARN) << "A RigGeometry should not have multi parent ( " << geom->getName() << " )" << std::endl; - geom->getParents()[0]->accept(finder); - - if (!finder._root.valid()) - { - osg::notify(osg::WARN) << "A RigGeometry did not find a parent skeleton for RigGeometry ( " << geom->getName() << " )" << std::endl; - return false; - } - geom->buildVertexInfluenceSet(); - geom->setSkeleton(finder._root.get()); - } - - if (!geom->getSkeleton()) - return false; - - if (geom->getNeedToComputeMatrix()) - geom->computeMatrixFromRootSkeleton(); - - geom->update(); - - return false; - } - }; - - // Same for MorphGeometry struct UpdateMorphGeometry : public osg::Drawable::CullCallback { UpdateMorphGeometry() @@ -500,88 +386,6 @@ namespace osg::BoundingBox mBoundingBox; }; - class RigBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback - { - public: - RigBoundingBoxCallback() - : mBoneMapInit(false) - { - } - RigBoundingBoxCallback(const RigBoundingBoxCallback& copy, const osg::CopyOp& copyop) - : osg::Drawable::ComputeBoundingBoxCallback(copy, copyop) - , mBoneMapInit(false) - , mBoundSpheres(copy.mBoundSpheres) - { - } - - META_Object(NifOsg, RigBoundingBoxCallback) - - void addBoundSphere(const std::string& bonename, const osg::BoundingSphere& sphere) - { - mBoundSpheres[bonename] = sphere; - } - - virtual osg::BoundingBox computeBound(const osg::Drawable& drawable) const - { - osg::BoundingBox box; - - const osgAnimation::RigGeometry* rig = dynamic_cast(&drawable); - if (!rig) - { - std::cerr << "Warning: RigBoundingBoxCallback set on non-rig" << std::endl; - return box; - } - - if (!mBoneMapInit) - { - initBoneMap(rig); - } - - for (std::map::const_iterator it = mBoneMap.begin(); - it != mBoneMap.end(); ++it) - { - osgAnimation::Bone* bone = it->first; - osg::BoundingSphere bs = it->second; - SceneUtil::transformBoundingSphere(bone->getMatrixInSkeletonSpace(), bs); - box.expandBy(bs); - } - - return box; - } - - void initBoneMap(const osgAnimation::RigGeometry* rig) const - { - if (!rig->getSkeleton()) - { - // may happen before the first frame update, but we're not animating yet, so no need for a bounding box - return; - } - - osgAnimation::BoneMapVisitor mapVisitor; - { - // const_cast necessary because there does not seem to be a const variant of NodeVisitor. - // Don't worry, we're not actually changing the skeleton. - osgAnimation::Skeleton* skel = const_cast(rig->getSkeleton()); - skel->accept(mapVisitor); - } - - for (osgAnimation::BoneMap::const_iterator it = mapVisitor.getBoneMap().begin(); it != mapVisitor.getBoneMap().end(); ++it) - { - std::map::const_iterator found = mBoundSpheres.find(it->first); - if (found != mBoundSpheres.end()) // not all bones have to be used for skinning - mBoneMap[it->second.get()] = found->second; - } - - mBoneMapInit = true; - } - - private: - mutable bool mBoneMapInit; - mutable std::map mBoneMap; - - std::map mBoundSpheres; - }; - void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys) { for(size_t i = 0;i < tk->list.size();i++) @@ -731,8 +535,7 @@ namespace NifOsg osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr skel = new osgAnimation::Skeleton; - skel->setDefaultUpdateCallback(); // validates the skeleton hierarchy + osg::ref_ptr skel = new Skeleton; handleNode(nifNode, skel, textureManager, true, std::map(), 0, 0, false, &textkeys->mTextKeys); skel->getOrCreateUserDataContainer()->addUserObject(textkeys); @@ -762,18 +565,8 @@ namespace NifOsg static osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::TextureManager* textureManager, bool createSkeleton, std::map boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { - osg::ref_ptr transformNode; - if (createSkeleton) - { - osgAnimation::Bone* bone = new osgAnimation::Bone; - transformNode = bone; - bone->setMatrix(toMatrix(nifNode->trafo)); - bone->setInvBindMatrixInSkeletonSpace(osg::Matrixf::inverse(getWorldTransform(nifNode))); - } - else - { - transformNode = new osg::MatrixTransform(toMatrix(nifNode->trafo)); - } + osg::ref_ptr transformNode = new osg::MatrixTransform(toMatrix(nifNode->trafo)); + if (nifNode->recType == Nif::RC_NiBillboardNode) { transformNode->addCullCallback(new BillboardCallback); @@ -870,10 +663,6 @@ namespace NifOsg if (!nifNode->controller.empty()) handleNodeControllers(nifNode, transformNode, animflags); - // Added last so the changes from KeyframeControllers are taken into account - if (osgAnimation::Bone* bone = dynamic_cast(transformNode.get())) - bone->addUpdateCallback(new UpdateBone); - const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { @@ -1219,49 +1008,6 @@ namespace NifOsg { const Nif::NiTriShapeData* data = triShape->data.getPtr(); - const Nif::NiSkinInstance *skin = (triShape->skin.empty() ? NULL : triShape->skin.getPtr()); - if (skin) - { - // Convert vertices and normals to bone space from bind position. It would be - // better to transform the bones into bind position, but there doesn't seem to - // be a reliable way to do that. - osg::ref_ptr newVerts (new osg::Vec3Array(data->vertices.size())); - osg::ref_ptr newNormals (new osg::Vec3Array(data->normals.size())); - - const Nif::NiSkinData *skinData = skin->data.getPtr(); - const Nif::NodeList &bones = skin->bones; - for(size_t b = 0;b < bones.length();b++) - { - osg::Matrixf mat = toMatrix(skinData->bones[b].trafo); - - mat = mat * getWorldTransform(bones[b].getPtr()); - - const std::vector &weights = skinData->bones[b].weights; - for(size_t i = 0;i < weights.size();i++) - { - size_t index = weights[i].vertex; - float weight = weights[i].weight; - - osg::Vec4f mult = (osg::Vec4f(data->vertices.at(index),1.f) * mat) * weight; - (*newVerts)[index] += osg::Vec3f(mult.x(),mult.y(),mult.z()); - if(newNormals->size() > index) - { - osg::Vec4 normal(data->normals[index].x(), data->normals[index].y(), data->normals[index].z(), 0.f); - normal = (normal * mat) * weight; - (*newNormals)[index] += osg::Vec3f(normal.x(),normal.y(),normal.z()); - } - } - } - // Interpolating normalized normals doesn't necessarily give you a normalized result - // Currently we're using GL_NORMALIZE, so this isn't needed - //for (unsigned int i=0;isize();++i) - // (*newNormals)[i].normalize(); - - geometry->setVertexArray(newVerts); - if (!data->normals.empty()) - geometry->setNormalArray(newNormals, osg::Array::BIND_PER_VERTEX); - } - else { geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), &data->vertices[0])); if (!data->normals.empty()) @@ -1397,6 +1143,24 @@ namespace NifOsg return morphGeom; } + class BoundingBoxCallback : public osg::NodeCallback + { + public: + virtual void operator()( osg::Node* node, osg::NodeVisitor* nv ) + { + osg::BoundingBox bb = mDrawable->getBound(); + + static_cast(node)->setMatrix( + osg::Matrix::scale(bb.xMax()-bb.xMin(), bb.yMax()-bb.yMin(), bb.zMax()-bb.zMin()) * + osg::Matrix::translate(bb.center()) ); + + traverse(node, nv); + } + + osg::Drawable* mDrawable; + }; + + static void handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, const std::map& boundTextures, int animflags) { osg::ref_ptr geode (new osg::Geode); @@ -1404,21 +1168,13 @@ namespace NifOsg osg::ref_ptr geometry (new osg::Geometry); triShapeToGeometry(triShape, geometry, geode, boundTextures, animflags); - // Note the RigGeometry's UpdateCallback uses the skeleton space bone matrix, so the bone UpdateCallback has to be fired first. - // For this to work properly, all bones used for skinning a RigGeometry need to be created before that RigGeometry. - // All NIFs I've checked seem to conform to this restriction, perhaps Gamebryo update method works similarly. - // If a file violates this assumption, the worst that could happen is the bone position being a frame late. - // If this happens, we should get a warning from the Skeleton's validation update callback on the error log. - osg::ref_ptr rig(new osgAnimation::RigGeometry); + osg::ref_ptr rig(new RigGeometry); rig->setSourceGeometry(geometry); const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); - RigBoundingBoxCallback* callback = new RigBoundingBoxCallback; - rig->setComputeBoundingBoxCallback(callback); - // Assign bone weights - osg::ref_ptr map (new osgAnimation::VertexInfluenceMap); + osg::ref_ptr map (new RigGeometry::InfluenceMap); const Nif::NiSkinData *data = skin->data.getPtr(); const Nif::NodeList &bones = skin->bones; @@ -1426,34 +1182,42 @@ namespace NifOsg { std::string boneName = bones[i].getPtr()->name; - callback->addBoundSphere(boneName, osg::BoundingSphere(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius)); - - osgAnimation::VertexInfluence influence; - influence.setName(boneName); + RigGeometry::BoneInfluence influence; const std::vector &weights = data->bones[i].weights; - influence.reserve(weights.size()); + //influence.mWeights.reserve(weights.size()); for(size_t j = 0;j < weights.size();j++) { - osgAnimation::VertexIndexWeight indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); - influence.push_back(indexWeight); + std::pair indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); + influence.mWeights.insert(indexWeight); } + influence.mInvBindMatrix = toMatrix(data->bones[i].trafo); - map->insert(std::make_pair(boneName, influence)); + map->mMap.insert(std::make_pair(boneName, influence)); } rig->setInfluenceMap(map); - // Override default update using cull callback instead for efficiency. - rig->setUpdateCallback(NULL); - rig->setCullCallback(new UpdateRigGeometry); - - osg::ref_ptr trans(new osg::MatrixTransform); - trans->setUpdateCallback(new InvertBoneMatrix()); + rig->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(geometry->getBound())); geode->addDrawable(rig); - geode->addUpdateCallback(new DirtyBoundCallback); + + // World bounding box callback & node + osg::ref_ptr bbcb = new BoundingBoxCallback; + bbcb->mDrawable = rig; + + osg::ref_ptr geode2 = new osg::Geode; + geode2->addDrawable( new osg::ShapeDrawable(new osg::Box) ); + + osg::ref_ptr boundingBoxNode = new osg::MatrixTransform; + boundingBoxNode->addChild( geode2.get() ); + boundingBoxNode->addUpdateCallback( bbcb.get() ); + boundingBoxNode->getOrCreateStateSet()->setAttributeAndModes( + new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE) ); + boundingBoxNode->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF ); + // Add a copy, we will alternate between the two copies every other frame using the FrameSwitch // This is so we can set the DataVariance as STATIC, giving a huge performance boost + /* rig->setDataVariance(osg::Object::STATIC); osg::Geode* geode2 = static_cast(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES| osg::CopyOp::DEEP_COPY_DRAWABLES)); @@ -1463,7 +1227,10 @@ namespace NifOsg frameswitch->addChild(geode2); trans->addChild(frameswitch); - parentNode->addChild(trans); + */ + + parentNode->addChild(geode); + parentNode->addChild(boundingBoxNode); } diff --git a/components/nifosg/riggeometry.cpp b/components/nifosg/riggeometry.cpp new file mode 100644 index 000000000..8283caa23 --- /dev/null +++ b/components/nifosg/riggeometry.cpp @@ -0,0 +1,201 @@ +#include "riggeometry.hpp" + +#include +#include + +#include + +#include + +#include "skeleton.hpp" + +#include + +namespace NifOsg +{ + +// TODO: make threadsafe for multiple cull threads +class UpdateRigGeometry : public osg::Drawable::CullCallback +{ +public: + UpdateRigGeometry() + { + } + + UpdateRigGeometry(const UpdateRigGeometry& copy, const osg::CopyOp& copyop) + : osg::Drawable::CullCallback(copy, copyop) + { + } + + META_Object(NifOsg, UpdateRigGeometry) + + virtual bool cull(osg::NodeVisitor* nv, osg::Drawable* drw, osg::State*) const + { + RigGeometry* geom = static_cast(drw); + geom->update(nv); + return false; + } +}; + +RigGeometry::RigGeometry() +{ + setCullCallback(new UpdateRigGeometry); + setSupportsDisplayList(false); +} + +RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) + : osg::Geometry(copy, copyop) + , mInfluenceMap(copy.mInfluenceMap) + , mSourceGeometry(copy.mSourceGeometry) +{ + //setVertexArray(dynamic_cast(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); + //setNormalArray(dynamic_cast(from.getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::Array::BIND_PER_VERTEX); +} + +void RigGeometry::setSourceGeometry(osg::ref_ptr sourceGeometry) +{ + mSourceGeometry = sourceGeometry; + + osg::Geometry& from = *sourceGeometry; + + if (from.getStateSet()) + setStateSet(from.getStateSet()); + + // copy over primitive sets. + getPrimitiveSetList() = from.getPrimitiveSetList(); + + if (from.getColorArray()) + setColorArray(from.getColorArray()); + + if (from.getSecondaryColorArray()) + setSecondaryColorArray(from.getSecondaryColorArray()); + + if (from.getFogCoordArray()) + setFogCoordArray(from.getFogCoordArray()); + + for(unsigned int ti=0;ti(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); + setNormalArray(dynamic_cast(from.getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::Array::BIND_PER_VERTEX); +} + +bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) +{ + const osg::NodePath& path = nv->getNodePath(); + for (osg::NodePath::const_reverse_iterator it = path.rbegin(); it != path.rend(); ++it) + { + osg::Node* node = *it; + if (Skeleton* skel = dynamic_cast(node)) + { + mSkeleton = skel; + break; + } + } + + if (!mSkeleton) + { + std::cerr << "A RigGeometry did not find its parent skeleton" << std::endl; + return false; + } + + // geometryToSkel = nv->getNodePath()... + + if (!mInfluenceMap) + { + std::cerr << "No InfluenceMap set on RigGeometry" << std::endl; + return false; + } + + for (std::map::const_iterator it = mInfluenceMap->mMap.begin(); it != mInfluenceMap->mMap.end(); ++it) + { + Bone* b = mSkeleton->getBone(it->first); + if (!b) + { + std::cerr << "RigGeometry did not find bone " << it->first << std::endl; + } + + mResolvedInfluenceMap[b] = it->second; + } + return true; +} + +void RigGeometry::update(osg::NodeVisitor* nv) +{ + if (!mSkeleton) + { + if (!initFromParentSkeleton(nv)) + return; + } + + mSkeleton->updateBoneMatrices(); + + osg::NodePath path; + bool foundSkel = false; + for (osg::NodePath::const_iterator it = nv->getNodePath().begin(); it != nv->getNodePath().end(); ++it) + { + if (!foundSkel) + { + if (*it == mSkeleton) + foundSkel = true; + } + else + path.push_back(*it); + } + osg::Matrix geomToSkel = osg::computeWorldToLocal(path); + + // skinning + osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); + osg::Vec3Array* normalSrc = static_cast(mSourceGeometry->getNormalArray()); + + osg::Vec3Array* positionDst = static_cast(getVertexArray()); + osg::Vec3Array* normalDst = static_cast(getNormalArray()); + + for (unsigned int i=0; isize(); ++i) + (*positionDst)[i] = osg::Vec3f(0,0,0); + for (unsigned int i=0; isize(); ++i) + (*normalDst)[i] = osg::Vec3f(0,0,0); + + for (ResolvedInfluenceMap::const_iterator it = mResolvedInfluenceMap.begin(); it != mResolvedInfluenceMap.end(); ++it) + { + const BoneInfluence& bi = it->second; + Bone* bone = it->first; + + // Here we could cache the (weighted) matrix for each combination of bone weights + + osg::Matrixf finalMatrix = bi.mInvBindMatrix * bone->mMatrixInSkeletonSpace * geomToSkel; + + for (std::map::const_iterator weightIt = bi.mWeights.begin(); weightIt != bi.mWeights.end(); ++weightIt) + { + short vertex = weightIt->first; + float weight = weightIt->second; + + osg::Vec3f a = (*positionSrc)[vertex]; + + (*positionDst)[vertex] += finalMatrix.preMult(a) * weight; + (*normalDst)[vertex] += osg::Matrix::transform3x3((*normalSrc)[vertex], finalMatrix) * weight; + } + } + + positionDst->dirty(); + normalDst->dirty(); +} + +void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) +{ + mInfluenceMap = influenceMap; +} + + +} diff --git a/components/nifosg/riggeometry.hpp b/components/nifosg/riggeometry.hpp new file mode 100644 index 000000000..7c8ea83bc --- /dev/null +++ b/components/nifosg/riggeometry.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H +#define OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H + +#include +#include + +namespace NifOsg +{ + + class Skeleton; + class Bone; + + class RigGeometry : public osg::Geometry + { + public: + RigGeometry(); + RigGeometry(const RigGeometry& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, RigGeometry) + + struct BoneInfluence + { + osg::Matrixf mInvBindMatrix; + // + std::map mWeights; + }; + + + struct InfluenceMap : public osg::Referenced + { + std::map mMap; + }; + + void setInfluenceMap(osg::ref_ptr influenceMap); + + void setSourceGeometry(osg::ref_ptr sourceGeom); + + // Called automatically by our CullCallback + void update(osg::NodeVisitor* nv); + + + private: + osg::ref_ptr mSourceGeometry; + osg::ref_ptr mSkeleton; + + osg::ref_ptr mInfluenceMap; + + typedef std::map ResolvedInfluenceMap; + ResolvedInfluenceMap mResolvedInfluenceMap; + + bool initFromParentSkeleton(osg::NodeVisitor* nv); + }; + +} + +#endif diff --git a/components/nifosg/skeleton.cpp b/components/nifosg/skeleton.cpp new file mode 100644 index 000000000..26c6e80b3 --- /dev/null +++ b/components/nifosg/skeleton.cpp @@ -0,0 +1,148 @@ +#include "skeleton.hpp" + +#include +#include + +#include + +namespace NifOsg +{ + +class InitBoneCacheVisitor : public osg::NodeVisitor +{ +public: + InitBoneCacheVisitor(std::map >& cache) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mCache(cache) + { + } + + void apply(osg::Transform &node) + { + osg::MatrixTransform* bone = node.asMatrixTransform(); + if (!bone) + return; + + mCache[bone->getName()] = std::make_pair(getNodePath(), bone); + + traverse(node); + } +private: + std::map >& mCache; +}; + +Skeleton::Skeleton() + : mBoneCacheInit(false) + , mNeedToUpdateBoneMatrices(true) +{ + +} + +Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) + : mBoneCacheInit(false) + , mNeedToUpdateBoneMatrices(true) +{ + +} + + + +Bone* Skeleton::getBone(const std::string &name) +{ + if (!mBoneCacheInit) + { + InitBoneCacheVisitor visitor(mBoneCache); + accept(visitor); + mBoneCacheInit = true; + } + + BoneCache::iterator found = mBoneCache.find(name); + if (found == mBoneCache.end()) + return NULL; + + // find or insert in the bone hierarchy + + if (!mRootBone.get()) + { + mRootBone.reset(new Bone); + } + + const osg::NodePath& path = found->second.first; + Bone* bone = mRootBone.get(); + for (osg::NodePath::const_iterator it = path.begin(); it != path.end(); ++it) + { + osg::MatrixTransform* matrixTransform = dynamic_cast(*it); + if (!matrixTransform) + continue; + + Bone* child = NULL; + for (unsigned int i=0; imChildren.size(); ++i) + { + if (bone->mChildren[i]->mNode == *it) + { + child = bone->mChildren[i]; + break; + } + } + + if (!child) + { + child = new Bone; + bone->mChildren.push_back(child); + mNeedToUpdateBoneMatrices = true; + } + bone = child; + + bone->mNode = matrixTransform; + } + + return bone; +} + +void Skeleton::updateBoneMatrices() +{ + //if (mNeedToUpdateBoneMatrices) + { + + if (mRootBone.get()) + { + for (unsigned int i=0; imChildren.size(); ++i) + mRootBone->mChildren[i]->update(NULL); + } + else + std::cerr << "no root bone" << std::endl; + + mNeedToUpdateBoneMatrices = false; + } +} + +Bone::Bone() + : mNode(NULL) +{ +} + +Bone::~Bone() +{ + for (unsigned int i=0; igetMatrix() * (*parentMatrixInSkeletonSpace); + else + mMatrixInSkeletonSpace = mNode->getMatrix(); + + for (unsigned int i=0; iupdate(&mMatrixInSkeletonSpace); + } +} + +} diff --git a/components/nifosg/skeleton.hpp b/components/nifosg/skeleton.hpp new file mode 100644 index 000000000..f006ca84b --- /dev/null +++ b/components/nifosg/skeleton.hpp @@ -0,0 +1,58 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_SKELETON_H +#define OPENMW_COMPONENTS_NIFOSG_SKELETON_H + +#include + +#include + +namespace NifOsg +{ + + // Defines a Bone hierarchy, used for updating of skeleton-space bone matrices. + // To prevent unnecessary updates, only bones that are used for skinning will be added to this hierarchy. + class Bone + { + public: + Bone(); + ~Bone(); + + osg::Matrix mMatrixInSkeletonSpace; + + osg::MatrixTransform* mNode; + + std::vector mChildren; + + void update(const osg::Matrixf* parentMatrixInSkeletonSpace); + + private: + Bone(const Bone&); + void operator=(const Bone&); + }; + + class Skeleton : public osg::Group + { + public: + Skeleton(); + Skeleton(const Skeleton& copy, const osg::CopyOp& copyop); + + Bone* getBone(const std::string& name); + + META_Node(NifOsg, Skeleton) + + void updateBoneMatrices(); + + private: + // The root bone is not a "real" bone, it has no corresponding node in the scene graph. + // As far as the scene graph goes we support multiple root bones. + std::auto_ptr mRootBone; + + typedef std::map > BoneCache; + BoneCache mBoneCache; + bool mBoneCacheInit; + + bool mNeedToUpdateBoneMatrices; + }; + +} + +#endif