From 91f8dbb583e1af3b33f99eb62507ace8de0c891b Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Mon, 1 Dec 2008 14:07:20 +0000 Subject: [PATCH] From Wang Rui, "Attachment is a plugin reading Biovision hierarchical files (.BVH) to generate character motion animations. BVH format is widely used by Character Studio of 3dsmax, MotionBuilder and other softwares, also supported by most motion capture devices. The plugin is based on the latest osgAnimation library of OSG 2.7.6 and will return a osgAnimation::AnimationManager pointer if using readNodeFile() to load it. Source and CMake files are: CMakeLists.txt ReaderWriterBVH.cpp Also there are 3 example BVH files. The first two are captured from motions of human beings - maybe a kung-fu master here. PLEASE use command below to see the results: # osgviewer example1.bvh -O solids This will demonstrate the animating of a skeleton and render bones as solid boxes. Note that the motion assumes XOZ is the ground and has an offset from the center, so we should adjust our view to get best effects. You may also use "-O contours" to render bones as lines. The viewer shows nothing if without any options because osgAnimation::Bone does not render itself. User may add customized models to each named bones as osganimationskinning does to make uses of this plugin in their own applications. I was wondering to support a BvhNode in my osgModeling peoject before, but soon found it better be a plugin for animation. A problem is, how to bind real geometry models to the skeleton. Maybe we could have a bindingToNode() visitor in future to find geodes matching names of bones and add them as bones' children." --- src/osgPlugins/CMakeLists.txt | 19 +- src/osgPlugins/bvh/CMakeLists.txt | 8 + src/osgPlugins/bvh/ReaderWriterBVH.cpp | 368 +++++++++++++++++++++++++ 3 files changed, 386 insertions(+), 9 deletions(-) create mode 100644 src/osgPlugins/bvh/CMakeLists.txt create mode 100644 src/osgPlugins/bvh/ReaderWriterBVH.cpp diff --git a/src/osgPlugins/CMakeLists.txt b/src/osgPlugins/CMakeLists.txt index 8142d16bc..86dd99fc2 100644 --- a/src/osgPlugins/CMakeLists.txt +++ b/src/osgPlugins/CMakeLists.txt @@ -103,14 +103,6 @@ IF(GDAL_FOUND) ADD_SUBDIRECTORY(ogr) ENDIF(GDAL_FOUND) -IF(CURL_FOUND) - ADD_SUBDIRECTORY(curl) -ENDIF(CURL_FOUND) - -IF(ZLIB_FOUND) - ADD_SUBDIRECTORY(gz) -ENDIF(ZLIB_FOUND) - IF(XUL_FOUND) IF (WIN32 OR APPLE OR GTK_FOUND) ADD_SUBDIRECTORY(gecko) @@ -119,7 +111,6 @@ ENDIF(XUL_FOUND) - ############################################################ # # 3D Image plugins @@ -136,6 +127,15 @@ ENDIF(ITK_FOUND OR DCMTK_FOUND) ADD_SUBDIRECTORY(3dc) +IF(CURL_FOUND) + ADD_SUBDIRECTORY(curl) +ENDIF(CURL_FOUND) + +IF(ZLIB_FOUND) + ADD_SUBDIRECTORY(gz) +ENDIF(ZLIB_FOUND) + + IF(INVENTOR_FOUND) ADD_SUBDIRECTORY(Inventor) ENDIF(INVENTOR_FOUND) @@ -149,6 +149,7 @@ IF(COLLADA_FOUND) ENDIF(COLLADA_FOUND) ADD_SUBDIRECTORY(lwo) +ADD_SUBDIRECTORY(bvh) ADD_SUBDIRECTORY(x) ADD_SUBDIRECTORY(dw) ADD_SUBDIRECTORY(dxf) diff --git a/src/osgPlugins/bvh/CMakeLists.txt b/src/osgPlugins/bvh/CMakeLists.txt new file mode 100644 index 000000000..a00c8c5c4 --- /dev/null +++ b/src/osgPlugins/bvh/CMakeLists.txt @@ -0,0 +1,8 @@ +#this file is automatically generated + + +SET(TARGET_SRC ReaderWriterBVH.cpp ) + +SET(TARGET_ADDED_LIBRARIES osgAnimation ) +#### end var setup ### +SETUP_PLUGIN(bvh) diff --git a/src/osgPlugins/bvh/ReaderWriterBVH.cpp b/src/osgPlugins/bvh/ReaderWriterBVH.cpp new file mode 100644 index 000000000..54b57883e --- /dev/null +++ b/src/osgPlugins/bvh/ReaderWriterBVH.cpp @@ -0,0 +1,368 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +class BvhMotionBuilder : public osg::Referenced +{ +public: + typedef std::pair, int> JointNode; + typedef std::vector JointList; + + BvhMotionBuilder() : _drawingFlag(0) {} + virtual ~BvhMotionBuilder() {} + + static BvhMotionBuilder* instance() + { + static osg::ref_ptr 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 ( _drawingFlag && parent->getNumParents() && level>0 ) + parent->getParent(0)->addChild( createRefGeometry(offset, 0.5).get() ); + } + } + + if ( fr.matchSequence("CHANNELS %i") ) + { + isRecognized = true; + + // Process CHANNELS section + int noChannels; + fr[1].getInt( noChannels ); + fr += 2; + + for ( int i=0; i bone = new osgAnimation::Bone( parent->getName()+"End" ); + bone->setBindMatrixInBoneSpace( osg::Matrix::translate(offsetEndSite) ); + bone->setDataVariance( osg::Object::DYNAMIC ); + parent->addChild( bone ); + + if ( _drawingFlag ) + parent->addChild( createRefGeometry(offsetEndSite, 0.5).get() ); + } + } + fr.advanceOverCurrentFieldOrBlock(); + } + + if ( fr.matchSequence("ROOT %w {") || fr.matchSequence("JOINT %w {") ) + { + isRecognized = true; + + // Process JOINT section + osg::ref_ptr bone = new osgAnimation::Bone( fr[1].getStr() ); + bone->setDataVariance( osg::Object::DYNAMIC ); + osgAnimation::AnimationUpdateCallback* cb = + dynamic_cast( 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 > posChannels; + std::vector< osg::ref_ptr > rotChannels; + for ( JointList::iterator itr=_joints.begin(); itr!=_joints.end(); ++itr ) + { + std::string name = itr->first->getName(); + + osg::ref_ptr posKey = new osgAnimation::Vec3KeyframeContainer; + osg::ref_ptr posSampler = new osgAnimation::Vec3LinearSampler; + osg::ref_ptr posChannel = new osgAnimation::Vec3LinearChannel( posSampler.get() ); + posSampler->setKeyframeContainer( posKey.get() ); + posChannel->setName( "position" ); + posChannel->setTargetName( name ); + + osg::ref_ptr rotKey = new osgAnimation::QuatKeyframeContainer; + osg::ref_ptr rotSampler = new osgAnimation::QuatSphericalLinearSampler; + osg::ref_ptr 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(posChannel->getSampler()->getKeyframeContainer()), + dynamic_cast(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, const osgDB::ReaderWriter::Options* options ) + { + if ( options ) + { + if ( options->getOptionString().find("contours")!=std::string::npos ) _drawingFlag = 1; + else if ( options->getOptionString().find("solids")!=std::string::npos ) _drawingFlag = 2; + } + + osgDB::Input fr; + fr.attach( &stream ); + + osg::ref_ptr skelroot = new osgAnimation::Skeleton; + osg::ref_ptr 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 + { + float keyValue[3] = { 0.0f }; + if ( ch&0x01 ) fr.readSequence( keyValue[0] ); + if ( ch&0x02 ) fr.readSequence( keyValue[1] ); + if ( ch&0x04 ) fr.readSequence( keyValue[2] ); + + osg::Vec3 vec( keyValue[0], keyValue[1], keyValue[2] ); + posKey->push_back( osgAnimation::Vec3Keyframe(time, vec) ); + } + + 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] ); + + // Note that BVH data need to concatenate the rotating matrices as Y*X*Z + // So we should not create Quat directly from input values, which makes a wrong X*Y*Z + osg::Matrix rotMat = osg::Matrix::rotate(osg::DegreesToRadians(keyValue[1]), osg::Vec3(0.0,1.0,0.0)) + * osg::Matrix::rotate(osg::DegreesToRadians(keyValue[0]), osg::Vec3(1.0,0.0,0.0)) + * osg::Matrix::rotate(osg::DegreesToRadians(keyValue[2]), osg::Vec3(0.0,0.0,1.0)); + osg::Quat quat = rotMat.getRotate(); + rotKey->push_back( osgAnimation::QuatKeyframe(time, quat) ); + } + } + + osg::ref_ptr createRefGeometry( osg::Vec3 p, double len ) + { + osg::ref_ptr geode = new osg::Geode; + + if ( _drawingFlag==1 ) + { + osg::ref_ptr geometry = new osg::Geometry; + osg::ref_ptr 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->addPrimitiveSet( new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 8) ); + geometry->setVertexArray( vertices.get() ); + + geode->addDrawable( geometry.get() ); + } + else if ( _drawingFlag==2 ) + { + osg::Quat quat; + osg::ref_ptr box = new osg::Box( p*0.5, p.length(), len, len ); + quat.makeRotate( osg::Vec3(1.0,0.0,0.0), p ); + box->setRotation( quat ); + + geode->addDrawable( new osg::ShapeDrawable(box.get()) ); + } + + return geode; + } + + int _drawingFlag; + JointList _joints; +}; + +class ReaderWriterBVH : public osgDB::ReaderWriter +{ +public: + ReaderWriterBVH() + { + supportsExtension( "bvh", "Biovision motion hierarchical file" ); + + supportsOption( "contours","Show the skeleton with lines." ); + supportsOption( "solids","Show the skeleton with solid boxes." ); + } + + 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, options ); + 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::ERROR_IN_READING_FILE; + return readNode( stream, options ); + } +}; + +// Now register with Registry to instantiate the above reader/writer. +REGISTER_OSGPLUGIN( bvh, ReaderWriterBVH )