From c10c146ad1b49e4bec6f914fde82e59f8cda3563 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 26 Mar 2015 23:15:46 +0100 Subject: [PATCH] Skeleton based bounding box callback for RigGeometry (Bug #455) --- components/nif/data.cpp | 3 +- components/nif/data.hpp | 3 +- components/nifosg/nifloader.cpp | 166 +++++++++++++++++++++++++++++--- 3 files changed, 156 insertions(+), 16 deletions(-) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 1fef8d56f..4c9373029 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -200,7 +200,8 @@ void NiSkinData::read(NIFStream *nif) bi.trafo.rotation = nif->getMatrix3(); bi.trafo.pos = nif->getVector3(); bi.trafo.scale = nif->getFloat(); - bi.unknown = nif->getVector4(); + bi.boundSphereCenter = nif->getVector3(); + bi.boundSphereRadius = nif->getFloat(); // Number of vertex weights bi.weights.resize(nif->getUShort()); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 702fbf313..e3f1e2770 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -151,7 +151,8 @@ public: struct BoneInfo { Transformation trafo; - osg::Vec4f unknown; + osg::Vec3f boundSphereCenter; + float boundSphereRadius; std::vector weights; }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index df912728b..6e9de08f8 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -18,6 +18,7 @@ #include #include #include +#include // particle #include @@ -319,6 +320,144 @@ namespace int mSourceIndex; }; + // 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) + { + } + + void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osg::Geode* geode = node->asGeode(); + if (geode && geode->getNumDrawables()) + { + geode->getDrawable(0)->dirtyBound(); + } + traverse(node, nv); + } + }; + + 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; + } + + // based off code in osg::Transform + void transformBoundingSphere (const osg::Matrix& matrix, osg::BoundingSphere& bsphere) const + { + osg::BoundingSphere::vec_type xdash = bsphere._center; + xdash.x() += bsphere._radius; + xdash = xdash*matrix; + + osg::BoundingSphere::vec_type ydash = bsphere._center; + ydash.y() += bsphere._radius; + ydash = ydash*matrix; + + osg::BoundingSphere::vec_type zdash = bsphere._center; + zdash.z() += bsphere._radius; + zdash = zdash*matrix; + + bsphere._center = bsphere._center*matrix; + + xdash -= bsphere._center; + osg::BoundingSphere::value_type len_xdash = xdash.length(); + + ydash -= bsphere._center; + osg::BoundingSphere::value_type len_ydash = ydash.length(); + + zdash -= bsphere._center; + osg::BoundingSphere::value_type len_zdash = zdash.length(); + + bsphere._radius = len_xdash; + if (bsphere._radius(&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; + 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++) @@ -451,7 +590,7 @@ namespace NifOsg if (nifNode == NULL) nif->fail("First root was not a node, but a " + r->recName); - osg::ref_ptr created = handleNode(nifNode, false, std::map(), 0, 0, false, textKeys); + osg::ref_ptr created = handleNode(nifNode, NULL, false, std::map(), 0, 0, false, textKeys); return created; } @@ -469,7 +608,7 @@ namespace NifOsg osg::ref_ptr skel = new osgAnimation::Skeleton; skel->setDefaultUpdateCallback(); // validates the skeleton hierarchy - skel->addChild(handleNode(nifNode, true, std::map(), 0, 0, false, textKeys)); + handleNode(nifNode, skel, true, std::map(), 0, 0, false, textKeys); return skel; } @@ -494,7 +633,7 @@ namespace NifOsg toSetup->mFunction = boost::shared_ptr(new ControllerFunction(ctrl)); } - osg::ref_ptr handleNode(const Nif::Node* nifNode, bool createSkeleton, + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, bool createSkeleton, std::map boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { osg::ref_ptr transformNode; @@ -515,6 +654,9 @@ namespace NifOsg transformNode = new osg::MatrixTransform(toMatrix(nifNode->trafo)); } + if (parentNode) + parentNode->addChild(transformNode); + if (!rootNode) rootNode = transformNode; @@ -610,8 +752,7 @@ namespace NifOsg { if(!children[i].empty()) { - transformNode->addChild( - handleNode(children[i].getPtr(), createSkeleton, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode)); + handleNode(children[i].getPtr(), transformNode, createSkeleton, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode); } } } @@ -1085,17 +1226,11 @@ namespace NifOsg osg::ref_ptr rig(new osgAnimation::RigGeometry); rig->setSourceGeometry(geometry); - // Slightly expand the bounding box to account for movement of the bones - // For more accuracy the skinning should be relative to the parent of the first skinned bone, - // rather than the root bone. - // TODO: calculate a correct bounding box based on the bone positions every frame in a ComputeBoundingBoxCallback - osg::BoundingBox box = geometry->getBound(); - box.expandBy(box._min-(box._max-box._min)/2); - box.expandBy(box._max+(box._max-box._min)/2); - rig->setInitialBound(box); - const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); + RigBoundingBoxCallback* callback = new RigBoundingBoxCallback; + rig->setComputeBoundingBoxCallback(callback); + // Assign bone weights osg::ref_ptr map (new osgAnimation::VertexInfluenceMap); @@ -1105,6 +1240,8 @@ 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); const std::vector &weights = data->bones[i].weights; @@ -1123,6 +1260,7 @@ namespace NifOsg trans->setUpdateCallback(new InvertBoneMatrix()); geode->addDrawable(rig); + geode->addUpdateCallback(new DirtyBoundCallback); trans->addChild(geode); parentNode->addChild(trans);