OSG动画库Animation解析(二)

在前文中讨论到osgAnimation中的Target对象应该和场景的更新回调有一些关系,本文接着前面继续讨论。

在前面的代码中,使用自己编写的更新回调类RotateCallback,osgAnimation根据动画的内容和方式的不同,提供了几种动画的更新回调类,分别是UpdateMatrixTransform和UpdateMorph(用来描述位置变化的动画回调)、UpdateMaterial(材质变化的动画回调)

1. UpdateMatrixTransform

在之前的RotateCallback中,把它设置为一个场景节点osg::MatrixTransform的更新回调,这个也是UpdateMatrixTransform也是这样一个类,它继承与osg::NodeVisitor,的实现的operator方法如下:

void UpdateMatrixTransform::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
    if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
    {
        osg::MatrixTransform* matrixTransform = dynamic_cast<osg::MatrixTransform*>(node);
        if (matrixTransform)
        {
            // here we would prefer to have a flag inside transform stack in order to avoid update and a dirty state in matrixTransform if it's not require.
            _transforms.update();
            const osg::Matrix& matrix = _transforms.getMatrix();
            matrixTransform->setMatrix(matrix);
        }
    }
    traverse(node,nv);
}

代码的实现和之前写的RotateCallback大同小异,不同之处在于旋转矩阵matrix的计算方式,RotateCallback使用Target去获取,而真实的计算过程在插值器中(通过Animation获取Channel再获取Sampler最后获取到插值器,计算结果)。UpdateMatrixTransform矩阵的计算则是通过一个_transforms的数组得到,它是一个StackedTransformElement的数组。

按照之前的设想,场景需要的关键帧的值从Target获得,在UpdateMatrixTransform应该也遵守这条规则,事实上确实是这样,StackedTransformElement提供了操作Target的一些接口

    class  StackedTransformElement : public osg::Object
    {
    public:
        virtual void applyToMatrix(osg::Matrix& matrix) const = 0;
        virtual Target* getOrCreateTarget() {return 0;}
        virtual Target* getTarget() {return 0;}
        virtual const Target* getTarget() const {return 0;}
    };

根据动画的类型,派生出许多不同类型的StackedTransformElement子类,主要包括:
OSG动画库Animation解析(二)_第1张图片
与RotateCallback对应的应该是这里的StackedRotateAxisElement

    class OSGANIMATION_EXPORT StackedRotateAxisElement : public StackedTransformElement
    {
    public:
        META_Object(osgAnimation, StackedRotateAxisElement);

        StackedRotateAxisElement();
        StackedRotateAxisElement(const StackedRotateAxisElement&, const osg::CopyOp&);
        StackedRotateAxisElement(const std::string& name, const osg::Vec3& axis, double angle);
        StackedRotateAxisElement(const osg::Vec3& axis, double angle);

        void applyToMatrix(osg::Matrix& matrix) const;
        osg::Matrix getAsMatrix() const;
        bool isIdentity() const { return (_angle == 0); }
        void update(float t = 0.0);

        const osg::Vec3& getAxis() const;
        double getAngle() const;
        void setAxis(const osg::Vec3&);
        void setAngle(double);

        virtual Target* getOrCreateTarget();
        virtual Target* getTarget() {return _target.get();}
        virtual const Target* getTarget() const {return _target.get();}

    protected:
        osg::Vec3 _axis;
        double _angle;
        osg::ref_ptr _target;
    };

这些派生类都要实现基类中的抽象函数applyToMatrix,这个函数起到真正的作用,StackedRotateAxisElement的实现如下:

void StackedRotateAxisElement::applyToMatrix(osg::Matrix& matrix) const 
{    
    matrix.preMultRotate(osg::Quat(_angle, _axis)); 
}

传入的matrix会在原来的基础上进行一次旋转。

在引入了UpdateMatrixTransform之后,改造之前的程序,使用这个回调代替RotateCallback,代码如下:


int main()
{
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
    osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");

    osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
    root->addChild(cowNode);

    osgAnimation::DoubleKeyframe keyframe1(0.0, 0.0);
    osgAnimation::DoubleKeyframe keyframe2(10.0, osg::PI * 2);

    osg::ref_ptr<osgAnimation::DoubleKeyframeContainer> keyframeContainer = new osgAnimation::DoubleKeyframeContainer();
    keyframeContainer->push_back(keyframe1);
    keyframeContainer->push_back(keyframe2);
    osg::ref_ptr<osgAnimation::DoubleLinearSampler> sampler = new osgAnimation::DoubleLinearSampler();
    sampler->setKeyframeContainer(keyframeContainer.get());
    osg::ref_ptr<osgAnimation::DoubleLinearChannel> channel = new osgAnimation::DoubleLinearChannel();
    channel->setSampler(sampler.get());

    osg::ref_ptr<osgAnimation::Animation> animation = new osgAnimation::Animation();
    animation->addChannel(channel.get());

    osg::ref_ptr<osgAnimation::UpdateMatrixTransform> umt = new osgAnimation::UpdateMatrixTransform();
    osg::ref_ptr<osgAnimation::StackedRotateAxisElement>    srae = new osgAnimation::StackedRotateAxisElement();
    srae->setAxis(osg::Z_AXIS);
    srae->setAngle(0.0);
    umt->getStackedTransforms().push_back(srae);
    root->addUpdateCallback(umt);

    viewer->setUpViewInWindow(200, 200, 800, 600);
    viewer->setSceneData(root);
    return viewer->run();
}

编译运行之后,发现场景中模型并没有变化,这是为什么呢?仔细看一下发现osgAnimation::Animation类和UpdateMatrixTransform之间根本没有任何关联,也就是说真正作插值工作的Animation并没有把中间帧的值传给UpdateMatrixTransform,更具体来说是没有把Animation插值的结果传递给StackedRotateAxisElement中的Target,也就是说需要把StackedRotateAxisElement中的Target和osgAnimation::Animation “链接”起来,这正是UpdateMatrixTransform这个类除了调用operator()之外的另一个作用。

2. 链接Animation与NodeCallback

通过查看UpdateMatrixTransform的继承关系
Inherits

在AnimationUpdateCallbackBase中定义的两个链接纯虚函数:

    class AnimationUpdateCallbackBase : public virtual osg::Object
    {
    public:
        virtual bool link(Channel* channel) = 0;
        virtual int link(Animation* animation) = 0;
    };

在AnimationUpdateCallback中实现了virtual int link(Animation* animation) = 0;,实现方式是将Animation中的每一个频道分别调用link,剩下的工作就是在派生类中完成bool link(Channel* channel),UpdateMatrixTransform的实现:

bool UpdateMatrixTransform::link(osgAnimation::Channel* channel)
{
    const std::string& channelName = channel->getName();

    // check if we can link a StackedTransformElement to the current Channel
    for (StackedTransform::iterator it = _transforms.begin(); it != _transforms.end(); ++it)
    {
        StackedTransformElement* element = it->get();
        if (element && !element->getName().empty() && channelName == element->getName())
        {
            Target* target = element->getOrCreateTarget();
            if (target && channel->setTarget(target))
                return true;
        }
    }

    OSG_INFO << "UpdateMatrixTransform::link Channel " << channel->getName() << " does not contain a symbolic name that can be linked to a StackedTransformElement." << std::endl;

    return false;
}

这段代码非常的关键, 在上一篇文章(OSG动画库Animation解析(一))的结尾提到了,从Channel中强行获取Target并捕获Target存储的中间帧的值,再设置给RotateCallback的方式非常的不自然,这个工作不应该是让开发者去做,osgAnimation内部应该处理了,这段代码就解释了osgAnimation是怎么做的

从代码实现中了解到,Target的产生是由StackedTransformElement来负责的,将产生好的Target传递给Channel,从Channel中捕获关键帧计算的结果,捕获到的结果不需要开发者手动设置到UpdateMatrixTransform这个Callback中,在UpdateMatrixTransform的opeator()函数中通过

  _transforms.update();
  const osg::Matrix& matrix = _transforms.getMatrix();
  matrixTransform->setMatrix(matrix);

获取到并且设置给调用Callback的节点了(也就是场景中的MatrixTransform),从而改变了模型的位置和姿态。至此整个Animation众多的类如何协作起来共同驱动场景更新形成动画的过程已经解释清楚了。

另外注意设置Target给Channel的过程中,通过_transforms找到对应的StackedTransformElement和Channel关联使用的是channel的名称,也就是说在链接的时候需要设置Channel的名称和StackedRotateAxisElement的名称一样,否则不能正确的将StackedRotateAxisElement产生的Target传递给Channel,从而使得整个数据传递链失败。

了解了上面的原理,修改上面的程序,修改代码如下:

int main()
{
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
    osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");

    osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
    root->addChild(cowNode);

    osgAnimation::DoubleKeyframe keyframe1(0.0, 0.0);
    osgAnimation::DoubleKeyframe keyframe2(10.0, osg::PI * 2);

    osg::ref_ptr<osgAnimation::DoubleKeyframeContainer> keyframeContainer = new osgAnimation::DoubleKeyframeContainer();
    keyframeContainer->push_back(keyframe1);
    keyframeContainer->push_back(keyframe2);
    osg::ref_ptr<osgAnimation::DoubleLinearSampler> sampler = new osgAnimation::DoubleLinearSampler();
    sampler->setKeyframeContainer(keyframeContainer.get());
    osg::ref_ptr<osgAnimation::DoubleLinearChannel> channel = new osgAnimation::DoubleLinearChannel();
    channel->setSampler(sampler.get());
    channel->setName("rotateAroundZAxis");

    osg::ref_ptr<osgAnimation::Animation> animation = new osgAnimation::Animation();
    animation->addChannel(channel.get());

    osg::ref_ptr<osgAnimation::UpdateMatrixTransform> umt = new osgAnimation::UpdateMatrixTransform();
    osg::ref_ptr<osgAnimation::StackedRotateAxisElement>    srae = new osgAnimation::StackedRotateAxisElement();
    srae->setAxis(osg::Z_AXIS);
    srae->setName("rotateAroundZAxis");
    umt->getStackedTransforms().push_back(srae);
    umt->link(channel.get());
    root->addUpdateCallback(umt);


    viewer->setUpViewInWindow(200, 200, 800, 600);
    viewer->setSceneData(root);
    return viewer->run();
}

运行代码,发现仍然没有动画效果。问题出在哪儿呢?通过修改的代码,已经将动画运行需要的整个流程打通了(Animation中的Channel通过Channel内部的Sampler采样得到中间帧的值,由UpdateMatrixTransform中的StackedRotateAxisElement数组产生的Targets传递给Channel接受中间帧的值,并设置给UpdateMatrixTransform的宿主(场景中的树结构MatrixTransform))。与之前的代码对比发现:Animation的update方法并没有被调用,也就是说“万事俱备,只欠东风”,这里没有驱动动画运行的更新时间点,Animation中Channel的update方法没有被调用,根本没有办法产生中间帧的值。

既然没有驱动动画运行的方式,可以“制造”一个驱动方式,可以通过在MatrixTransform的上层节点中添加一个回调,在这个回调中调用Animation的update方法。【需要注意的是,这个节点一定要在MatrixTransform之上,因为osg会从树的顶端一直往下遍历,运行每一个节点的UpdateCallback,只有让先于MatrixTransform进行变化,才能让和MatrixTransform绑定的这个UpdatematrixTransform获得计算的插值结果,从而改变MatrixTransform】,修改的代码如下:

class DummyDriverNodeCallback : public osg::NodeCallback
{
public:
    DummyDriverNodeCallback(osgAnimation::Animation *dls)
    {
        _startTick = osg::Timer::instance()->tick();
        _animation = dls;
    }

    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
    {
        double currentTick = osg::Timer::instance()->tick();
        double elaspedTime = osg::Timer::instance()->delta_s(_startTick, currentTick);

        _animation->resetTargets();
        _animation->setPlayMode(osgAnimation::Animation::LOOP);
        _animation->update(elaspedTime);
        _animation->setWeight(1.0);

        traverse(node, nv);
    }

protected:

    osg::ref_ptr<osgAnimation::Animation>    _animation;
    double                                   _startTick;
};

int main()
{
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
    osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");

    osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
    root->addChild(cowNode);

    osgAnimation::DoubleKeyframe keyframe1(0.0, 0.0);
    osgAnimation::DoubleKeyframe keyframe2(10.0, osg::PI * 2);

    osg::ref_ptr<osgAnimation::DoubleKeyframeContainer> keyframeContainer = new osgAnimation::DoubleKeyframeContainer();
    keyframeContainer->push_back(keyframe1);
    keyframeContainer->push_back(keyframe2);
    osg::ref_ptr<osgAnimation::DoubleLinearSampler> sampler = new osgAnimation::DoubleLinearSampler();
    sampler->setKeyframeContainer(keyframeContainer.get());
    osg::ref_ptr<osgAnimation::DoubleLinearChannel> channel = new osgAnimation::DoubleLinearChannel();
    channel->setSampler(sampler.get());
    channel->setName("rotateAroundZAxis");
    channel->setTargetName("updateMatrixCallback");

    osg::ref_ptr<osgAnimation::Animation> animation = new osgAnimation::Animation();
    animation->addChannel(channel);

    osg::ref_ptr<osgAnimation::UpdateMatrixTransform> umt = new osgAnimation::UpdateMatrixTransform();
    umt->setName("updateMatrixCallback");
    osgAnimation::StackedRotateAxisElement* srae = new osgAnimation::StackedRotateAxisElement("rotateAroundZAxis", osg::Z_AXIS, 0.0);
    umt->getStackedTransforms().push_back(srae);
    osg::ref_ptr<osgAnimation::AnimationUpdateCallbackBase> base = umt;
    base->link(animation);

    root->addUpdateCallback(umt);

    osg::ref_ptr<osg::Group> dummyDriverGroup = new osg::Group;
    dummyDriverGroup->addUpdateCallback(new DummyDriverNodeCallback(animation));
    dummyDriverGroup->addChild(root);

    viewer->setUpViewInWindow(200, 200, 800, 600);
    viewer->setSceneData(dummyDriverGroup);
    return viewer->run();
}

运行之后发现会出现崩溃的情况,调试之后发现在StackedRotateAxisElement中使用的是Target的Target变量,在链接到Channel时(Channel的setTarget函数),Channel使用的是DoubleTarget,强制转换之后会失败,导致Channel的Target是空值。修改办法要么我们写一个StackedRotateAxisElement的派生类使用DoubleTarget作为成员变量,或者为了简便,把之前的Double类型都修改成float类型,修改之后代码如下:

class DummyDriverNodeCallback : public osg::NodeCallback
{
public:
    DummyDriverNodeCallback(osgAnimation::Animation *dls)
    {
        _startTick = osg::Timer::instance()->tick();
        _animation = dls;
    }

    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
    {
        double currentTick = osg::Timer::instance()->tick();
        double elaspedTime = osg::Timer::instance()->delta_s(_startTick, currentTick);

        _animation->resetTargets();
        _animation->setPlayMode(osgAnimation::Animation::LOOP);
        _animation->update(elaspedTime);
        _animation->setWeight(1.0);

        traverse(node, nv);
    }

protected:

    osg::ref_ptr<osgAnimation::Animation>    _animation;
    double                                   _startTick;
};



int main()
{
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
    osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");

    osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
    root->addChild(cowNode);

    osgAnimation::FloatKeyframe keyframe1(0.0, 0.0);
    osgAnimation::FloatKeyframe keyframe2(10.0, osg::PI * 2);

    osg::ref_ptr<osgAnimation::FloatKeyframeContainer> keyframeContainer = new osgAnimation::FloatKeyframeContainer();
    keyframeContainer->push_back(keyframe1);
    keyframeContainer->push_back(keyframe2);
    osg::ref_ptr<osgAnimation::FloatLinearSampler> sampler = new osgAnimation::FloatLinearSampler();
    sampler->setKeyframeContainer(keyframeContainer.get());
    osg::ref_ptr<osgAnimation::FloatLinearChannel> channel = new osgAnimation::FloatLinearChannel();
    channel->setSampler(sampler.get());
    channel->setName("rotateAroundZAxis");
    channel->setTargetName("updateMatrixCallback");

    osg::ref_ptr<osgAnimation::Animation> animation = new osgAnimation::Animation();
    animation->addChannel(channel);

    osg::ref_ptr<osgAnimation::UpdateMatrixTransform> umt = new osgAnimation::UpdateMatrixTransform();
    umt->setName("updateMatrixCallback");
    osgAnimation::StackedRotateAxisElement* srae = new osgAnimation::StackedRotateAxisElement("rotateAroundZAxis", osg::Z_AXIS, 0.0);
    umt->getStackedTransforms().push_back(srae);
    osg::ref_ptr<osgAnimation::AnimationUpdateCallbackBase> base = umt;
    base->link(animation);

    root->addUpdateCallback(umt);

    osg::ref_ptr<osg::Group> dummyDriverGroup = new osg::Group;
    dummyDriverGroup->addUpdateCallback(new DummyDriverNodeCallback(animation));
    dummyDriverGroup->addChild(root);

    viewer->setUpViewInWindow(200, 200, 800, 600);
    viewer->setSceneData(dummyDriverGroup);
    return viewer->run();
}

编译运行程序,一切正常了。在编码中有两个地方需要注意一下:

  1. UpdateMatrixTransform在与Animation进行连接的时候,会和需要设置UpdateMatrixTransform的名称与Animation中Channel的Target的名称一样
  2. UpdateMatrixTransform中的StackedTransform数组中每一个对象的名称(StackedTransformElement)必须和Channel的名称一样,这样才能把StackedTransformElement new出来的Target传给对应的Channel
  3. 还有一个小地方提一下:UpdateMatrixTransform不能直接link一个Animation,需要把它转换到父类的指针才能调用父类中的方法(由于使用了虚继承的原因)

3. 动画的管理

通过前面的代码,基本上把整个Animation的过程都讲到了。上面的代码使用了DummyDriverNodeCallback来驱动动画,同时代码中手动链接了UpdateMatrix和Animation,在OSG中提供了动画的管理类来帮助开发者做这样的事情,原理当然和前面手动实现的是一样的,只不过OSG把这些实现整理的更加结构化一点,方便使用。

3.1 AnimationManagerBase

这个类定义了管理Animation的基类,它是一个NodeCallback,提供了连接动画中Target到Animation的工作。同时提供了添加Animation和删除Animation的功能,管理一个Animation的数组。除此之外它提供的功能和上面写的DummyDriverNodeCallback一样,驱动动画开始,它的operator()实现如下:

void AnimationManagerBase::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
    if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
    {
        if (needToLink())
        {
            /** manager need to link, it means that an animation has been added
                so we need to relink all item animated with all animations.
                We apply the linker visitor on the manager node to affect
                all its children.
                But it should not be done here, it should be done in the
                update of AnimationManager
            */
            link(node);
        }
        const osg::FrameStamp* fs = nv->getFrameStamp();
        update(fs->getSimulationTime());
    }
    traverse(node,nv);
}

可以看到,如果场景需要动画进行链接(说明有新动画被添加),那么连接一次,之后就调用update函数来开始驱动动画了,这个类的update函数是一个纯虚函数,需要子类实现。它的连接是使用LinkVisitor来进行遍历场景的。

3.2 LinkVisitor

遍历场景中所有的节点,如果发现这个节点存在UpdateCallback,并且这个Callback可以转换成AnimationUpdateCallbackBase(比如前面提到的UpdateMatrixTransform),那么就会执行link的操作,将动画和这个UpdateMatrixTransform连接起来。(具体的步骤和上面代码中的一样)。

3.3 BasicAnimationManager

这个类在AnimationManagerBase的基础上添加了动画管理的功能,可以播放动画和暂定动画的播放,其实很简单。把当前激活动画的对象存储到一个数组就可以了,因为它是驱动动画播放的,只让激活的动画的update函数调用,就可以起到这样的效果,需要停止某个动画时,把它从列表中删掉就可以了。它继承自AnimationManagerBase,重写了纯虚的方法update
,实现如下:

void BasicAnimationManager::update (double time)
{
    _lastUpdate = time;

    for (TargetSet::iterator it = _targets.begin(); it != _targets.end(); ++it)
        (*it).get()->reset();

    for( AnimationLayers::reverse_iterator iterAnim = _animationsPlaying.rbegin(); iterAnim != _animationsPlaying.rend(); ++iterAnim )
    {
        std::vector<int> toremove;
        int priority = iterAnim->first;
        AnimationList& list = iterAnim->second;
        for (unsigned int i = 0; i < list.size(); i++)
        {
            if (! list[i]->update(time, priority))
            {
                toremove.push_back(i);
            } else
            {
            }
        }

        while (!toremove.empty())
        {
            list.erase(list.begin() + toremove.back());
            toremove.pop_back();
        }
    }
}

这个函数每一帧把所有的动画运行一遍,会把运行动画收集起来,在运行过后删除掉这个临时的数组【这里我个人认为是否是调试的代码没有删掉,收集运行的动画,之后删掉好像没有什么意义,不知道大家怎么看?
】,动画是按priority进行排序播放的(动画在添加播放的时候给了一个优先级,按优先级存储在一个map中,这些都非常好理解)。既然引入了osgAnimation的动画管理,那么继续调整上面的程序:


int main()
{
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
    osg::ref_ptr<osg::Node> cowNode = osgDB::readNodeFile("cow.osg");

    osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform;
    root->addChild(cowNode);

    osgAnimation::FloatKeyframe keyframe1(0.0, 0.0);
    osgAnimation::FloatKeyframe keyframe2(10.0, osg::PI * 2);

    osg::ref_ptr<osgAnimation::FloatKeyframeContainer> keyframeContainer = new osgAnimation::FloatKeyframeContainer();
    keyframeContainer->push_back(keyframe1);
    keyframeContainer->push_back(keyframe2);
    osg::ref_ptr<osgAnimation::FloatLinearSampler> sampler = new osgAnimation::FloatLinearSampler();
    sampler->setKeyframeContainer(keyframeContainer.get());
    osg::ref_ptr<osgAnimation::FloatLinearChannel> channel = new osgAnimation::FloatLinearChannel();
    channel->setSampler(sampler.get());
    channel->setName("rotateAroundZAxis");
    channel->setTargetName("updateMatrixCallback");

    osg::ref_ptr<osgAnimation::Animation> animation = new osgAnimation::Animation();
    animation->addChannel(channel);

    osg::ref_ptr<osgAnimation::UpdateMatrixTransform> umt = new osgAnimation::UpdateMatrixTransform();
    umt->setName("updateMatrixCallback");
    osgAnimation::StackedRotateAxisElement* srae = new osgAnimation::StackedRotateAxisElement("rotateAroundZAxis", osg::Z_AXIS, 0.0);
    umt->getStackedTransforms().push_back(srae);
    root->addUpdateCallback(umt);

    osg::ref_ptr<osgAnimation::BasicAnimationManager>   bam = new osgAnimation::BasicAnimationManager;
    bam->registerAnimation(animation.get());
    bam->playAnimation(animation.get());

    osg::ref_ptr<osg::Group> dummyDriverGroup = new osg::Group;
    dummyDriverGroup->addUpdateCallback(bam.get());
    dummyDriverGroup->addChild(root);

    viewer->setUpViewInWindow(200, 200, 800, 600);
    viewer->setSceneData(dummyDriverGroup);
    return viewer->run();
}

上面这个实现类似于osgAnimationsolid这个例子的实现方式,基本上是osgAnimation推荐的一种动画的做法。

本文和上一篇文章(OSG动画库Animation解析(一))对osgAnimation的库做了一个完整的剖析,关于动画的权(weight)优先级等内容,参考后续文章。

你可能感兴趣的:(OSG动画库Animation解析(二))