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.
c++11
scrawl 10 years ago
parent 167ae600c5
commit 4ea6d4aa01

@ -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

@ -6,6 +6,9 @@
#include <osg/Geometry>
#include <osg/Array>
#include <osg/ShapeDrawable>
#include <osg/ComputeBoundsVisitor>
#include <osg/io_utils>
// resource
@ -14,11 +17,7 @@
#include <components/resource/texturemanager.hpp>
// skel
#include <osgAnimation/Skeleton>
#include <osgAnimation/Bone>
#include <osgAnimation/RigGeometry>
#include <osgAnimation/MorphGeometry>
#include <osgAnimation/BoneMapVisitor>
// particle
#include <osgParticle/ParticleSystem>
@ -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<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
// 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<osgAnimation::Skeleton*>(*it))
if (dynamic_cast<NifOsg::Skeleton*>(*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<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
{
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<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)
{
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<osgAnimation::Skeleton> skel = new osgAnimation::Skeleton;
skel->setDefaultUpdateCallback(); // validates the skeleton hierarchy
osg::ref_ptr<Skeleton> skel = new Skeleton;
handleNode(nifNode, skel, textureManager, true, std::map<int, int>(), 0, 0, false, &textkeys->mTextKeys);
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,
std::map<int, int> boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL)
{
osg::ref_ptr<osg::MatrixTransform> 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<osg::MatrixTransform> 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<osgAnimation::Bone*>(transformNode.get()))
bone->addUpdateCallback(new UpdateBone);
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(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<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]));
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<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)
{
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
@ -1404,21 +1168,13 @@ namespace NifOsg
osg::ref_ptr<osg::Geometry> 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<osgAnimation::RigGeometry> rig(new osgAnimation::RigGeometry);
osg::ref_ptr<RigGeometry> 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<osgAnimation::VertexInfluenceMap> map (new osgAnimation::VertexInfluenceMap);
osg::ref_ptr<RigGeometry::InfluenceMap> 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<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++)
{
osgAnimation::VertexIndexWeight indexWeight = std::make_pair(weights[j].vertex, weights[j].weight);
influence.push_back(indexWeight);
std::pair<short, float> 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<osg::MatrixTransform> 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<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
// 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::Geode*>(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);
}

@ -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 &copy, const osg::CopyOp &copyop)
: 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;
}
}

@ -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

@ -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 &copy, const osg::CopyOp &copyop)
: 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);
}
}
}

@ -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…
Cancel
Save