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) );
        }
    }
    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<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.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<osgAnimation::Bone> bone = new osgAnimation::Bone( fr[1].getStr() );
            bone->setDefaultUpdateCallback();
            bone->setDataVariance( osg::Object::DYNAMIC );
            parent->addChild( 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::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() );
        }
    }