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