Skeleton based bounding box callback for RigGeometry (Bug #455)

c++11
scrawl 10 years ago
parent 99e1720980
commit c10c146ad1

@ -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());

@ -151,7 +151,8 @@ public:
struct BoneInfo
{
Transformation trafo;
osg::Vec4f unknown;
osg::Vec3f boundSphereCenter;
float boundSphereRadius;
std::vector<VertWeight> weights;
};

@ -18,6 +18,7 @@
#include <osgAnimation/Bone>
#include <osgAnimation/RigGeometry>
#include <osgAnimation/MorphGeometry>
#include <osgAnimation/BoneMapVisitor>
// particle
#include <osgParticle/ParticleSystem>
@ -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<len_ydash) bsphere._radius = len_ydash;
if (bsphere._radius<len_zdash) bsphere._radius = len_zdash;
}
virtual osg::BoundingBox computeBound(const osg::Drawable& drawable) const
{
osg::BoundingBox box;
const osgAnimation::RigGeometry* rig = dynamic_cast<const osgAnimation::RigGeometry*>(&drawable);
if (!rig)
{
std::cerr << "Warning: RigBoundingBoxCallback set on non-rig" << std::endl;
return box;
}
if (!mBoneMapInit)
{
initBoneMap(rig);
}
for (std::map<osgAnimation::Bone*, osg::BoundingSphere>::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<osgAnimation::Skeleton*>(rig->getSkeleton());
skel->accept(mapVisitor);
}
for (osgAnimation::BoneMap::const_iterator it = mapVisitor.getBoneMap().begin(); it != mapVisitor.getBoneMap().end(); ++it)
{
std::map<std::string, osg::BoundingSphere>::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<osgAnimation::Bone*, osg::BoundingSphere> mBoneMap;
std::map<std::string, osg::BoundingSphere> 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<osg::Node> created = handleNode(nifNode, false, std::map<int, int>(), 0, 0, false, textKeys);
osg::ref_ptr<osg::Node> created = handleNode(nifNode, NULL, false, std::map<int, int>(), 0, 0, false, textKeys);
return created;
}
@ -469,7 +608,7 @@ namespace NifOsg
osg::ref_ptr<osgAnimation::Skeleton> skel = new osgAnimation::Skeleton;
skel->setDefaultUpdateCallback(); // validates the skeleton hierarchy
skel->addChild(handleNode(nifNode, true, std::map<int, int>(), 0, 0, false, textKeys));
handleNode(nifNode, skel, true, std::map<int, int>(), 0, 0, false, textKeys);
return skel;
}
@ -494,7 +633,7 @@ namespace NifOsg
toSetup->mFunction = boost::shared_ptr<ControllerFunction>(new ControllerFunction(ctrl));
}
osg::ref_ptr<osg::Node> handleNode(const Nif::Node* nifNode, bool createSkeleton,
osg::ref_ptr<osg::Node> handleNode(const Nif::Node* nifNode, osg::Group* parentNode, bool createSkeleton,
std::map<int, int> boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL)
{
osg::ref_ptr<osg::MatrixTransform> 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<osgAnimation::RigGeometry> 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<osgAnimation::VertexInfluenceMap> 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<Nif::NiSkinData::VertWeight> &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);

Loading…
Cancel
Save