Hi all, hi Cedric,
I'm just working on a plugin to read Biovision hierarchical files (.BVH) to
generate character motion animations. BVH format is widely used in Character
Studio of 3dsmax, Motion builder and other software. The plugin is based on
the latest osgAnimation library in the SVN version of OSG and will return a
osgAnimation::AnimationManager pointer if using readNodeFile().
The plugin builds the Skeleton/Bone tree within the HIERARCHY section of
a .bvh file and Animate/Channel within the MOTION section. Each Bone object
has a Vec3LinearChannel and a QuatSphericalLinearChannel linked to perform
translating and rotating. I'm not very familiar with the osgAnimation
nodekit but glad to see this structure works for me.
I was wondering to support a BvhNode in my osgModeling peoject before, but
found it better be a plugin for animation. A problem is, how to bind the
geometry models to the skeleton. Maybe we could have a bindingToNode()
function in the Skeleton class to find geodes matching names of bones and
add them as bones' children?
Attachment are the incomplete source file and a sample motion file (which
acts like the osganimationskinning example), not tested on Linux. The plugin
will draw lines as bones at present and I will make it an optional
functionality later. Just build the osgdb_bvh file and run "osgviewer
example.bvh" to see the result.
My thanks to Cedric and Jememy for their great (especially robust) work, and
I wish this ReaderWriterBVH plugin would be sent to osg-submissions soon
after I review it in a few days. Some of the results seem to have small
problems while reading BVH motions of human characters.
Best regards,
Wang Rui
#include <osg/Notify>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/Registry>
#include <osgAnimation/Bone>
#include <osgAnimation/Skeleton>
#include <osgAnimation/AnimationManager>
class BvhMotionBuilder : public osg::Referenced
{
public:
typedef std::pair<osg::ref_ptr<osgAnimation::Bone>, int> JointNode;
typedef std::vector<JointNode> JointList;
BvhMotionBuilder() {}
virtual ~BvhMotionBuilder() {}
static BvhMotionBuilder* instance()
{
static osg::ref_ptr<BvhMotionBuilder> s_library = new BvhMotionBuilder;
return s_library.get();
}
void buildHierarchy( osgDB::Input& fr, int level, osgAnimation::Bone*
parent )
{
bool isRecognized = false;
if ( !parent ) return;
if ( fr.matchSequence("OFFSET %f %f %f") )
{
isRecognized = true;
++fr;
osg::Vec3 offset;
if ( fr.readSequence(offset) )
{
// Process OFFSET section
parent->setBindMatrixInBoneSpace(
osg::Matrix::translate(offset) );
if ( parent->getNumParents() && level>0 )
parent->getParent(0)->addChild( createRefGeometry(offset,
0.1) );
}
}
if ( fr.matchSequence("CHANNELS %i") )
{
isRecognized = true;
// Process CHANNELS section
int noChannels;
fr[1].getInt( noChannels );
fr += 2;
for ( int i=0; i<noChannels; ++i )
{
// Process each channel
std::string channelName;
fr.readSequence( channelName );
alterChannel( channelName, _joints.back().second );
}
}
if ( fr.matchSequence("End Site {") )
{
isRecognized = true;
fr += 3;
if ( fr.matchSequence("OFFSET %f %f %f") )
{
++fr;
osg::Vec3 offsetEndSite;
if ( fr.readSequence(offsetEndSite) )
{
// Process End Site section
osg::ref_ptr<osgAnimation::Bone> bone = new
osgAnimation::Bone( parent->getName()+"End" );
bone->setBindMatrixInBoneSpace(
osg::Matrix::translate(offsetEndSite) );
bone->setDataVariance( osg::Object::DYNAMIC );
parent->addChild( bone );
parent->addChild( createRefGeometry(offsetEndSite, 0.1) );
}
}
fr.advanceOverCurrentFieldOrBlock();
}
if ( fr.matchSequence("ROOT %w {") || fr.matchSequence("JOINT %w {") )
{
isRecognized = true;
// Process JOINT section
osg::ref_ptr<osgAnimation::Bone> bone = new osgAnimation::Bone(
fr[1].getStr() );
bone->setDataVariance( osg::Object::DYNAMIC );
osgAnimation::AnimationUpdateCallback* cb =
dynamic_cast<osgAnimation::AnimationUpdateCallback*>(
bone->getUpdateCallback() );
if ( cb ) cb->setName( bone->getName() );
parent->addChild( bone );
_joints.push_back( JointNode(bone, 0) );
int entry = fr[1].getNoNestedBrackets();
fr += 3;
while ( !fr.eof() && fr[0].getNoNestedBrackets()>entry )
buildHierarchy( fr, entry, bone.get() );
fr.advanceOverCurrentFieldOrBlock();
}
if ( !isRecognized )
{
osg::notify(osg::WARN) << "BVH Reader: Unrecognized symbol " <<
fr[0].getStr()
<< ". Ignore current field or block." << std::endl;
fr.advanceOverCurrentFieldOrBlock();
}
}
void buildMotion( osgDB::Input& fr, osgAnimation::Animation* anim )
{
int i=0, frames=0;
float frameTime=0.033f;
if ( !fr.readSequence("Frames:", frames) )
{
osg::notify(osg::WARN) << "BVH Reader: Frame number setting not
found, but an unexpected "
<< fr[0].getStr() << ". Set to 1." << std::endl;
}
++fr;
if ( !fr.readSequence("Time:", frameTime) )
{
osg::notify(osg::WARN) << "BVH Reader: Frame time setting not
found, but an unexpected "
<< fr[0].getStr() << ". Set to 0.033 (30FPS)." << std::endl;
}
// Each joint has a position animating channel and an euler animating
channel
std::vector< osg::ref_ptr<osgAnimation::Vec3LinearChannel> >
posChannels;
std::vector< osg::ref_ptr<osgAnimation::QuatSphericalLinearChannel> >
rotChannels;
for ( JointList::iterator itr=_joints.begin(); itr!=_joints.end();
++itr )
{
std::string name = itr->first->getName();
osg::ref_ptr<osgAnimation::Vec3KeyframeContainer> posKey = new
osgAnimation::Vec3KeyframeContainer;
osg::ref_ptr<osgAnimation::Vec3LinearSampler> posSampler = new
osgAnimation::Vec3LinearSampler;
osg::ref_ptr<osgAnimation::Vec3LinearChannel> posChannel = new
osgAnimation::Vec3LinearChannel( posSampler.get() );
posSampler->setKeyframeContainer( posKey.get() );
posChannel->setName( "position" );
posChannel->setTargetName( name );
osg::ref_ptr<osgAnimation::QuatKeyframeContainer> rotKey = new
osgAnimation::QuatKeyframeContainer;
osg::ref_ptr<osgAnimation::QuatSphericalLinearSampler> rotSampler =
new osgAnimation::QuatSphericalLinearSampler;
osg::ref_ptr<osgAnimation::QuatSphericalLinearChannel> rotChannel =
new osgAnimation::QuatSphericalLinearChannel( rotSampler.get() );
rotSampler->setKeyframeContainer( rotKey.get() );
rotChannel->setName( "quaternion" );
rotChannel->setTargetName( name );
posChannels.push_back( posChannel );
rotChannels.push_back( rotChannel );
}
// Obtain motion data from the stream
while ( !fr.eof() && i<frames )
{
for ( unsigned int n=0; n<_joints.size(); ++n )
{
osgAnimation::Vec3LinearChannel* posChannel =
posChannels[n].get();
osgAnimation::QuatSphericalLinearChannel* rotChannel =
rotChannels[n].get();
setKeyframe( fr, _joints[n].second, frameTime*(double)i,
dynamic_cast<osgAnimation::Vec3KeyframeContainer*>(posChannel->getSampler()->getKeyframeContainer()),
dynamic_cast<osgAnimation::QuatKeyframeContainer*>(rotChannel->getSampler()->getKeyframeContainer())
);
}
i++;
}
// Add valid channels to the animate object
for ( unsigned int n=0; n<_joints.size(); ++n )
{
if (
posChannels[n]->getOrCreateSampler()->getOrCreateKeyframeContainer()->size()>0 )
anim->addChannel( posChannels[n].get() );
if (
rotChannels[n]->getOrCreateSampler()->getOrCreateKeyframeContainer()->size()>0 )
anim->addChannel( rotChannels[n].get() );
}
}
osgAnimation::AnimationManager* buildBVH( std::istream& stream )
{
osgDB::Input fr;
fr.attach( &stream );
osg::ref_ptr<osgAnimation::Skeleton> skelroot = new
osgAnimation::Skeleton;
osg::ref_ptr<osgAnimation::Animation> anim = new
osgAnimation::Animation;
while( !fr.eof() )
{
if ( fr.matchSequence("HIERARCHY") )
{
++fr;
buildHierarchy( fr, 0, skelroot.get() );
}
else if ( fr.matchSequence("MOTION") )
{
++fr;
buildMotion( fr, anim.get() );
}
else
{
if ( fr[0].getStr()==NULL ) continue;
osg::notify(osg::WARN) << "BVH Reader: Unexpected beginning "
<< fr[0].getStr()
<< ", neither HIERARCHY nor MOTION. Stopped." << std::endl;
break;
}
}
#if 0
std::cout << "Bone number: " << _joints.size() << "\n";
for ( unsigned int i=0; i<_joints.size(); ++i )
{
JointNode node = _joints[i];
std::cout << "Bone" << i << " " << node.first->getName() << ": ";
std::cout << std::hex << node.second << std::dec << "; ";
if ( node.first->getNumParents() )
std::cout << "Parent: " << node.first->getParent(0)->getName();
std::cout << "\n";
}
#endif
osgAnimation::AnimationManager* manager = new
osgAnimation::AnimationManager;
manager->addChild( skelroot.get() );
manager->registerAnimation( anim.get() );
manager->buildTargetReference();
manager->playAnimation( anim.get() );
_joints.clear();
return manager;
}
protected:
void alterChannel( std::string name, int& value )
{
if ( name=="Xposition" ) value |= 0x01;
else if ( name=="Yposition" ) value |= 0x02;
else if ( name=="Zposition" ) value |= 0x04;
else if ( name=="Zrotation" ) value |= 0x08;
else if ( name=="Xrotation" ) value |= 0x10;
else if ( name=="Yrotation" ) value |= 0x20;
}
void setKeyframe( osgDB::Input& fr, int ch, double time,
osgAnimation::Vec3KeyframeContainer* posKey,
osgAnimation::QuatKeyframeContainer* rotKey )
{
if ( ch&0x07 && posKey ) // Position keyframe
{
osg::Vec3f keyValue( 0.0, 0.0, 0.0 );
if ( ch&0x01 ) fr.readSequence( keyValue.x() );
if ( ch&0x02 ) fr.readSequence( keyValue.y() );
if ( ch&0x04 ) fr.readSequence( keyValue.z() );
posKey->push_back( osgAnimation::Vec3Keyframe(time, keyValue) );
}
if ( ch&0x38 && rotKey ) // Rotation keyframe
{
float keyValue[3] = { 0.0f };
if ( ch&0x08 ) fr.readSequence( keyValue[2] );
if ( ch&0x10 ) fr.readSequence( keyValue[0] );
if ( ch&0x20 ) fr.readSequence( keyValue[1] );
osg::Quat quat(
osg::DegreesToRadians(keyValue[0]), osg::Vec3(1.0,0.0,0.0),
osg::DegreesToRadians(keyValue[1]), osg::Vec3(0.0,1.0,0.0),
osg::DegreesToRadians(keyValue[2]), osg::Vec3(0.0,0.0,1.0) );
rotKey->push_back( osgAnimation::QuatKeyframe(time, quat) );
}
}
osg::Geode* createRefGeometry( osg::Vec3 p, double len )
{
osg::Geode* geode = new osg::Geode;
osg::Geometry* geometry = new osg::Geometry;
osg::Vec3Array* vertices = new osg::Vec3Array;
// Joint
vertices->push_back( osg::Vec3(-len, 0.0, 0.0) );
vertices->push_back( osg::Vec3( len, 0.0, 0.0) );
vertices->push_back( osg::Vec3( 0.0,-len, 0.0) );
vertices->push_back( osg::Vec3( 0.0, len, 0.0) );
vertices->push_back( osg::Vec3( 0.0, 0.0,-len) );
vertices->push_back( osg::Vec3( 0.0, 0.0, len) );
// Bone
vertices->push_back( osg::Vec3( 0.0, 0.0, 0.0) );
vertices->push_back( p );
geometry->setVertexArray( vertices );
geometry->addPrimitiveSet( new
osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 8) );
geode->addDrawable( geometry );
return geode;
}
JointList _joints;
};
class ReaderWriterBVH : public osgDB::ReaderWriter
{
public:
ReaderWriterBVH()
{
supportsExtension( "bvh", "Biovision motion hierarchical file" );
}
virtual const char* className() const
{ return "BVH Motion Reader"; }
virtual ReadResult readNode(std::istream& stream, const
osgDB::ReaderWriter::Options* options) const
{
ReadResult rr = BvhMotionBuilder::instance()->buildBVH( stream );
return rr;
}
virtual ReadResult readNode(const std::string& file, const
osgDB::ReaderWriter::Options* options) const
{
std::string ext = osgDB::getLowerCaseFileExtension( file );
if ( !acceptsExtension(ext) ) return ReadResult::FILE_NOT_HANDLED;
std::string fileName = osgDB::findDataFile( file, options );
if ( fileName.empty() ) return ReadResult::FILE_NOT_FOUND;
std::ifstream stream( fileName.c_str(), std::ios::in|std::ios::binary );
if( !stream ) return ReadResult::FILE_NOT_HANDLED;
return readNode( stream, options );
}
};
// Now register with Registry to instantiate the above reader/writer.
REGISTER_OSGPLUGIN( bvh, ReaderWriterBVH )
HIERARCHY
ROOT BoneRoot
{
OFFSET -1.0000 0.0000 0.0000
CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
JOINT BoneRight0
{
OFFSET 1.0000 0.0000 0.0000
CHANNELS 3 Zrotation Xrotation Yrotation
JOINT BoneRight1
{
OFFSET 1.0000 0.0000 0.0000
CHANNELS 3 Zrotation Xrotation Yrotation
End Site
{
OFFSET 1.0000 0.0000 -0.0000
}
}
}
}
MOTION
Frames: 3
Frame Time: 3.0
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
0.0000
1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 -90.00 0.0000 0.0000
0.0000
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 -90.00 0.0000 0.0000
-90.00_______________________________________________
osg-users mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org