mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 01:45:33 +00:00
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
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
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