#include #include #include #include #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->setMatrixInSkeletonSpace( osg::Matrix::translate(offset) * parent->getMatrixInSkeletonSpace() ); osgAnimation::UpdateBone* updateBone = static_cast( parent->getUpdateCallback() ); if ( updateBone ) { osgAnimation::StackedTransform& stack = updateBone->getStackedTransforms(); stack.push_back( new osgAnimation::StackedTranslateElement("position", offset) ); stack.push_back( new osgAnimation::StackedQuaternionElement("quaternion", osg::Quat()) ); } 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->setMatrixInSkeletonSpace( osg::Matrix::translate(offsetEndSite) * bone->getMatrixInSkeletonSpace() ); bone->setDataVariance( osg::Object::DYNAMIC ); parent->insertChild( 0, bone.get() ); 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 ); bone->setDefaultUpdateCallback(); parent->insertChild( 0, bone.get() ); _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_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_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_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() ); } } osg::Group* 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 boneroot = new osgAnimation::Bone( "Root" ); boneroot->setDefaultUpdateCallback(); osg::ref_ptr skelroot = new osgAnimation::Skeleton; skelroot->setDefaultUpdateCallback(); skelroot->insertChild( 0, boneroot.get() ); osg::ref_ptr anim = new osgAnimation::Animation; while( !fr.eof() ) { if ( fr.matchSequence("HIERARCHY") ) { ++fr; buildHierarchy( fr, 0, boneroot.get() ); } else if ( fr.matchSequence("MOTION") ) { ++fr; buildMotion( fr, anim.get() ); } else { if ( fr[0].getStr()==NULL ) continue; 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 osg::Group* root = new osg::Group; osgAnimation::BasicAnimationManager* manager = new osgAnimation::BasicAnimationManager; root->addChild( skelroot.get() ); root->setUpdateCallback(manager); manager->registerAnimation( anim.get() ); manager->buildTargetReference(); manager->playAnimation( anim.get() ); _joints.clear(); return root; } 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; osgDB::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 )