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
parent
167ae600c5
commit
4ea6d4aa01
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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 ©, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 New Issue