forked from teamnwah/openmw-tes3coop
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.
This commit is contained in:
parent
167ae600c5
commit
4ea6d4aa01
6 changed files with 520 additions and 290 deletions
|
@ -47,7 +47,7 @@ add_component_dir (nif
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (nifosg
|
add_component_dir (nifosg
|
||||||
nifloader controller particle userdata
|
nifloader controller particle userdata skeleton riggeometry
|
||||||
)
|
)
|
||||||
|
|
||||||
#add_component_dir (nifcache
|
#add_component_dir (nifcache
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
#include <osg/Geometry>
|
#include <osg/Geometry>
|
||||||
#include <osg/Array>
|
#include <osg/Array>
|
||||||
|
|
||||||
|
#include <osg/ShapeDrawable>
|
||||||
|
#include <osg/ComputeBoundsVisitor>
|
||||||
|
|
||||||
#include <osg/io_utils>
|
#include <osg/io_utils>
|
||||||
|
|
||||||
// resource
|
// resource
|
||||||
|
@ -14,11 +17,7 @@
|
||||||
#include <components/resource/texturemanager.hpp>
|
#include <components/resource/texturemanager.hpp>
|
||||||
|
|
||||||
// skel
|
// skel
|
||||||
#include <osgAnimation/Skeleton>
|
|
||||||
#include <osgAnimation/Bone>
|
|
||||||
#include <osgAnimation/RigGeometry>
|
|
||||||
#include <osgAnimation/MorphGeometry>
|
#include <osgAnimation/MorphGeometry>
|
||||||
#include <osgAnimation/BoneMapVisitor>
|
|
||||||
|
|
||||||
// particle
|
// particle
|
||||||
#include <osgParticle/ParticleSystem>
|
#include <osgParticle/ParticleSystem>
|
||||||
|
@ -45,6 +44,8 @@
|
||||||
|
|
||||||
#include "particle.hpp"
|
#include "particle.hpp"
|
||||||
#include "userdata.hpp"
|
#include "userdata.hpp"
|
||||||
|
#include "skeleton.hpp"
|
||||||
|
#include "riggeometry.hpp"
|
||||||
|
|
||||||
namespace
|
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<osgAnimation::Bone*>(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
|
// 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.
|
// set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera.
|
||||||
class BillboardCallback : public osg::NodeCallback
|
class BillboardCallback : public osg::NodeCallback
|
||||||
|
@ -304,7 +279,7 @@ namespace
|
||||||
|
|
||||||
for (osg::NodePath::iterator it = path.begin(); it != path.end(); ++it)
|
for (osg::NodePath::iterator it = path.begin(); it != path.end(); ++it)
|
||||||
{
|
{
|
||||||
if (dynamic_cast<osgAnimation::Skeleton*>(*it))
|
if (dynamic_cast<NifOsg::Skeleton*>(*it))
|
||||||
{
|
{
|
||||||
path.erase(path.begin(), it+1);
|
path.erase(path.begin(), it+1);
|
||||||
// the bone's transform in skeleton space
|
// 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<osgAnimation::Skeleton> _root;
|
|
||||||
FindNearestParentSkeleton() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_PARENTS) {}
|
|
||||||
void apply(osg::Transform& node)
|
|
||||||
{
|
|
||||||
if (_root.valid())
|
|
||||||
return;
|
|
||||||
_root = dynamic_cast<osgAnimation::Skeleton*>(&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<osgAnimation::RigGeometry*>(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
|
struct UpdateMorphGeometry : public osg::Drawable::CullCallback
|
||||||
{
|
{
|
||||||
UpdateMorphGeometry()
|
UpdateMorphGeometry()
|
||||||
|
@ -500,88 +386,6 @@ namespace
|
||||||
osg::BoundingBox mBoundingBox;
|
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<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;
|
|
||||||
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<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)
|
void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys)
|
||||||
{
|
{
|
||||||
for(size_t i = 0;i < tk->list.size();i++)
|
for(size_t i = 0;i < tk->list.size();i++)
|
||||||
|
@ -731,8 +535,7 @@ namespace NifOsg
|
||||||
|
|
||||||
osg::ref_ptr<TextKeyMapHolder> textkeys (new TextKeyMapHolder);
|
osg::ref_ptr<TextKeyMapHolder> textkeys (new TextKeyMapHolder);
|
||||||
|
|
||||||
osg::ref_ptr<osgAnimation::Skeleton> skel = new osgAnimation::Skeleton;
|
osg::ref_ptr<Skeleton> skel = new Skeleton;
|
||||||
skel->setDefaultUpdateCallback(); // validates the skeleton hierarchy
|
|
||||||
handleNode(nifNode, skel, textureManager, true, std::map<int, int>(), 0, 0, false, &textkeys->mTextKeys);
|
handleNode(nifNode, skel, textureManager, true, std::map<int, int>(), 0, 0, false, &textkeys->mTextKeys);
|
||||||
|
|
||||||
skel->getOrCreateUserDataContainer()->addUserObject(textkeys);
|
skel->getOrCreateUserDataContainer()->addUserObject(textkeys);
|
||||||
|
@ -762,18 +565,8 @@ namespace NifOsg
|
||||||
static osg::ref_ptr<osg::Node> handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::TextureManager* textureManager, bool createSkeleton,
|
static osg::ref_ptr<osg::Node> handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::TextureManager* textureManager, bool createSkeleton,
|
||||||
std::map<int, int> boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL)
|
std::map<int, int> boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::MatrixTransform> transformNode;
|
osg::ref_ptr<osg::MatrixTransform> transformNode = new osg::MatrixTransform(toMatrix(nifNode->trafo));
|
||||||
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));
|
|
||||||
}
|
|
||||||
if (nifNode->recType == Nif::RC_NiBillboardNode)
|
if (nifNode->recType == Nif::RC_NiBillboardNode)
|
||||||
{
|
{
|
||||||
transformNode->addCullCallback(new BillboardCallback);
|
transformNode->addCullCallback(new BillboardCallback);
|
||||||
|
@ -870,10 +663,6 @@ namespace NifOsg
|
||||||
if (!nifNode->controller.empty())
|
if (!nifNode->controller.empty())
|
||||||
handleNodeControllers(nifNode, transformNode, animflags);
|
handleNodeControllers(nifNode, transformNode, animflags);
|
||||||
|
|
||||||
// Added last so the changes from KeyframeControllers are taken into account
|
|
||||||
if (osgAnimation::Bone* bone = dynamic_cast<osgAnimation::Bone*>(transformNode.get()))
|
|
||||||
bone->addUpdateCallback(new UpdateBone);
|
|
||||||
|
|
||||||
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode);
|
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode);
|
||||||
if(ninode)
|
if(ninode)
|
||||||
{
|
{
|
||||||
|
@ -1219,49 +1008,6 @@ namespace NifOsg
|
||||||
{
|
{
|
||||||
const Nif::NiTriShapeData* data = triShape->data.getPtr();
|
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<osg::Vec3Array> newVerts (new osg::Vec3Array(data->vertices.size()));
|
|
||||||
osg::ref_ptr<osg::Vec3Array> 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<Nif::NiSkinData::VertWeight> &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;i<newNormals->size();++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]));
|
geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), &data->vertices[0]));
|
||||||
if (!data->normals.empty())
|
if (!data->normals.empty())
|
||||||
|
@ -1397,6 +1143,24 @@ namespace NifOsg
|
||||||
return morphGeom;
|
return morphGeom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BoundingBoxCallback : public osg::NodeCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void operator()( osg::Node* node, osg::NodeVisitor* nv )
|
||||||
|
{
|
||||||
|
osg::BoundingBox bb = mDrawable->getBound();
|
||||||
|
|
||||||
|
static_cast<osg::MatrixTransform*>(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<int, int>& boundTextures, int animflags)
|
static void handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, const std::map<int, int>& boundTextures, int animflags)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
|
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
|
||||||
|
@ -1404,21 +1168,13 @@ namespace NifOsg
|
||||||
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||||
triShapeToGeometry(triShape, geometry, geode, boundTextures, animflags);
|
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.
|
osg::ref_ptr<RigGeometry> rig(new RigGeometry);
|
||||||
// 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<osgAnimation::RigGeometry> rig(new osgAnimation::RigGeometry);
|
|
||||||
rig->setSourceGeometry(geometry);
|
rig->setSourceGeometry(geometry);
|
||||||
|
|
||||||
const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
|
const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
|
||||||
|
|
||||||
RigBoundingBoxCallback* callback = new RigBoundingBoxCallback;
|
|
||||||
rig->setComputeBoundingBoxCallback(callback);
|
|
||||||
|
|
||||||
// Assign bone weights
|
// Assign bone weights
|
||||||
osg::ref_ptr<osgAnimation::VertexInfluenceMap> map (new osgAnimation::VertexInfluenceMap);
|
osg::ref_ptr<RigGeometry::InfluenceMap> map (new RigGeometry::InfluenceMap);
|
||||||
|
|
||||||
const Nif::NiSkinData *data = skin->data.getPtr();
|
const Nif::NiSkinData *data = skin->data.getPtr();
|
||||||
const Nif::NodeList &bones = skin->bones;
|
const Nif::NodeList &bones = skin->bones;
|
||||||
|
@ -1426,34 +1182,42 @@ namespace NifOsg
|
||||||
{
|
{
|
||||||
std::string boneName = bones[i].getPtr()->name;
|
std::string boneName = bones[i].getPtr()->name;
|
||||||
|
|
||||||
callback->addBoundSphere(boneName, osg::BoundingSphere(data->bones[i].boundSphereCenter, data->bones[i].boundSphereRadius));
|
RigGeometry::BoneInfluence influence;
|
||||||
|
|
||||||
osgAnimation::VertexInfluence influence;
|
|
||||||
influence.setName(boneName);
|
|
||||||
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
|
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
|
||||||
influence.reserve(weights.size());
|
//influence.mWeights.reserve(weights.size());
|
||||||
for(size_t j = 0;j < weights.size();j++)
|
for(size_t j = 0;j < weights.size();j++)
|
||||||
{
|
{
|
||||||
osgAnimation::VertexIndexWeight indexWeight = std::make_pair(weights[j].vertex, weights[j].weight);
|
std::pair<short, float> indexWeight = std::make_pair(weights[j].vertex, weights[j].weight);
|
||||||
influence.push_back(indexWeight);
|
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);
|
rig->setInfluenceMap(map);
|
||||||
|
|
||||||
// Override default update using cull callback instead for efficiency.
|
rig->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(geometry->getBound()));
|
||||||
rig->setUpdateCallback(NULL);
|
|
||||||
rig->setCullCallback(new UpdateRigGeometry);
|
|
||||||
|
|
||||||
osg::ref_ptr<osg::MatrixTransform> trans(new osg::MatrixTransform);
|
|
||||||
trans->setUpdateCallback(new InvertBoneMatrix());
|
|
||||||
|
|
||||||
geode->addDrawable(rig);
|
geode->addDrawable(rig);
|
||||||
geode->addUpdateCallback(new DirtyBoundCallback);
|
|
||||||
|
// World bounding box callback & node
|
||||||
|
osg::ref_ptr<BoundingBoxCallback> bbcb = new BoundingBoxCallback;
|
||||||
|
bbcb->mDrawable = rig;
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Geode> geode2 = new osg::Geode;
|
||||||
|
geode2->addDrawable( new osg::ShapeDrawable(new osg::Box) );
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::MatrixTransform> 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
|
// 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
|
// This is so we can set the DataVariance as STATIC, giving a huge performance boost
|
||||||
|
/*
|
||||||
rig->setDataVariance(osg::Object::STATIC);
|
rig->setDataVariance(osg::Object::STATIC);
|
||||||
osg::Geode* geode2 = static_cast<osg::Geode*>(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|
|
osg::Geode* geode2 = static_cast<osg::Geode*>(osg::clone(geode.get(), osg::CopyOp::DEEP_COPY_NODES|
|
||||||
osg::CopyOp::DEEP_COPY_DRAWABLES));
|
osg::CopyOp::DEEP_COPY_DRAWABLES));
|
||||||
|
@ -1463,7 +1227,10 @@ namespace NifOsg
|
||||||
frameswitch->addChild(geode2);
|
frameswitch->addChild(geode2);
|
||||||
|
|
||||||
trans->addChild(frameswitch);
|
trans->addChild(frameswitch);
|
||||||
parentNode->addChild(trans);
|
*/
|
||||||
|
|
||||||
|
parentNode->addChild(geode);
|
||||||
|
parentNode->addChild(boundingBoxNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
201
components/nifosg/riggeometry.cpp
Normal file
201
components/nifosg/riggeometry.cpp
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
#include "riggeometry.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <osg/MatrixTransform>
|
||||||
|
|
||||||
|
#include "skeleton.hpp"
|
||||||
|
|
||||||
|
#include <osg/io_utils>
|
||||||
|
|
||||||
|
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<RigGeometry*>(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<osg::Array*>(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)));
|
||||||
|
//setNormalArray(dynamic_cast<osg::Array*>(from.getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::Array::BIND_PER_VERTEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> 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.getNumTexCoordArrays();++ti)
|
||||||
|
{
|
||||||
|
if (from.getTexCoordArray(ti))
|
||||||
|
setTexCoordArray(ti,from.getTexCoordArray(ti));
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Geometry::ArrayList& arrayList = from.getVertexAttribArrayList();
|
||||||
|
for(unsigned int vi=0;vi< arrayList.size();++vi)
|
||||||
|
{
|
||||||
|
osg::Array* array = arrayList[vi].get();
|
||||||
|
if (array)
|
||||||
|
setVertexAttribArray(vi,array);
|
||||||
|
}
|
||||||
|
|
||||||
|
setVertexArray(dynamic_cast<osg::Array*>(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)));
|
||||||
|
setNormalArray(dynamic_cast<osg::Array*>(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<Skeleton*>(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<std::string, BoneInfluence>::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<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
||||||
|
osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
|
||||||
|
|
||||||
|
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(getVertexArray());
|
||||||
|
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(getNormalArray());
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<positionDst->size(); ++i)
|
||||||
|
(*positionDst)[i] = osg::Vec3f(0,0,0);
|
||||||
|
for (unsigned int i=0; i<positionDst->size(); ++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<short, float>::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> influenceMap)
|
||||||
|
{
|
||||||
|
mInfluenceMap = influenceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
56
components/nifosg/riggeometry.hpp
Normal file
56
components/nifosg/riggeometry.hpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H
|
||||||
|
#define OPENMW_COMPONENTS_NIFOSG_RIGGEOMETRY_H
|
||||||
|
|
||||||
|
#include <osg/Geometry>
|
||||||
|
#include <osg/Matrixf>
|
||||||
|
|
||||||
|
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;
|
||||||
|
// <vertex index, weight>
|
||||||
|
std::map<short, float> mWeights;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct InfluenceMap : public osg::Referenced
|
||||||
|
{
|
||||||
|
std::map<std::string, BoneInfluence> mMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
void setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap);
|
||||||
|
|
||||||
|
void setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom);
|
||||||
|
|
||||||
|
// Called automatically by our CullCallback
|
||||||
|
void update(osg::NodeVisitor* nv);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
osg::ref_ptr<osg::Geometry> mSourceGeometry;
|
||||||
|
osg::ref_ptr<Skeleton> mSkeleton;
|
||||||
|
|
||||||
|
osg::ref_ptr<InfluenceMap> mInfluenceMap;
|
||||||
|
|
||||||
|
typedef std::map<Bone*, BoneInfluence> ResolvedInfluenceMap;
|
||||||
|
ResolvedInfluenceMap mResolvedInfluenceMap;
|
||||||
|
|
||||||
|
bool initFromParentSkeleton(osg::NodeVisitor* nv);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
148
components/nifosg/skeleton.cpp
Normal file
148
components/nifosg/skeleton.cpp
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
#include "skeleton.hpp"
|
||||||
|
|
||||||
|
#include <osg/Transform>
|
||||||
|
#include <osg/MatrixTransform>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace NifOsg
|
||||||
|
{
|
||||||
|
|
||||||
|
class InitBoneCacheVisitor : public osg::NodeVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InitBoneCacheVisitor(std::map<std::string, std::pair<osg::NodePath, osg::MatrixTransform*> >& 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<std::string, std::pair<osg::NodePath, osg::MatrixTransform*> >& 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<osg::MatrixTransform*>(*it);
|
||||||
|
if (!matrixTransform)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Bone* child = NULL;
|
||||||
|
for (unsigned int i=0; i<bone->mChildren.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; i<mRootBone->mChildren.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; i<mChildren.size(); ++i)
|
||||||
|
delete mChildren[i];
|
||||||
|
mChildren.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bone::update(const osg::Matrixf* parentMatrixInSkeletonSpace)
|
||||||
|
{
|
||||||
|
if (!mNode)
|
||||||
|
{
|
||||||
|
std::cerr << "Bone without node " << std::endl;
|
||||||
|
}
|
||||||
|
if (parentMatrixInSkeletonSpace)
|
||||||
|
mMatrixInSkeletonSpace = mNode->getMatrix() * (*parentMatrixInSkeletonSpace);
|
||||||
|
else
|
||||||
|
mMatrixInSkeletonSpace = mNode->getMatrix();
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<mChildren.size(); ++i)
|
||||||
|
{
|
||||||
|
mChildren[i]->update(&mMatrixInSkeletonSpace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
components/nifosg/skeleton.hpp
Normal file
58
components/nifosg/skeleton.hpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_NIFOSG_SKELETON_H
|
||||||
|
#define OPENMW_COMPONENTS_NIFOSG_SKELETON_H
|
||||||
|
|
||||||
|
#include <osg/Group>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
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<Bone*> 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<Bone> mRootBone;
|
||||||
|
|
||||||
|
typedef std::map<std::string, std::pair<osg::NodePath, osg::MatrixTransform*> > BoneCache;
|
||||||
|
BoneCache mBoneCache;
|
||||||
|
bool mBoneCacheInit;
|
||||||
|
|
||||||
|
bool mNeedToUpdateBoneMatrices;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue