第36.5节 动画-跟踪运动中物体的操作器

Source

本节功能

第36.4节 动画-路径动画中的角度控制问题中,我们在场景绘制了一个按固定路径飞行的飞机,在这一节中,我们将使用osg自带的osgGA::NodeTrackerManipulator 对飞机进行跟踪,在跟踪过程中可以使用鼠标拖动来任意的调整其观察角度,类似针对飞机的一个trackball操作器。
在这里插入图片描述
请使用浏览器打开,平时遇到问题或加群也可以加我微信:13324598743:
链接:https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw
提取码:xrf5

具体实现

按理说,我们只需要设置跟踪节点,并将当前的操作器设置为NodeTrackerManipulator就可以了,如下:

//跟踪操作器
osgGA::NodeTrackerManipulator* g_ntm = new osgGA::NodeTrackerManipulator;
viewer.setCameraManipulator(g_ntm);
g_ntm->setTrackNode(mt);

但是往往事情没有这么简单,这要从NodeTrackerManipulator的设计目标说起,导致了它不能适应有些场景。NodeTrackerManipulator的设计初衷是假如我们在一个地球上飞行,那么对飞机的跟踪就有很多的约束,比如说最好是能够绕飞机自带的Z轴旋转,而不是任意角度,这很符合常理。NodeTrackerManipulator出来的时候,还没有osgEarth,因此它判断是不是在地球上飞行则主要依赖其跟踪的物体的父结点有没有一个CoordinateSystemNode,所以本身跟踪一个运动物体是和地球没有关系的,但是其设计的目标却导致了其的有些操作的有效性依赖了CoordinateSystemNode。

我们可以在NodeTrackerManipulator的对于TrackerMode的注释中看到很多关于CoordinateSystemNode(csn)的描述。

好了,那么我们要实现跟踪物体,不管csn不csn的,那么就要注意了。

实现要点

1、 要在运动结点构造完毕后设置跟踪结点
比如我们要跟踪一个飞机,我们一般会这样:

//跟踪器
ntm = new NodeTrackerManipulator
//飞机模型
glider = osgDB::readNodeFile(glider.osg)
//调整飞机模型的姿态和大小
mt = new osg::MatrixTransform;
mt->addChild(glider);
//这个setMatrix是用来调整姿态和大小,并未运动
mt->setMatrix
//设置ntm跟踪这个结点
ntm->setTrackNode(mt)

//运动结点 
animateMt = new osg::MatrixTransform;
animateMt->addChild(mt);
//设置运动
animateMt->setUpdateCallback(animate)

结果不好使,为什么不好使呢,因为ntm->setTrackNode(mt)的时候会确定一个nodePath,以来确定这个结点是否在运动。这个时候的运动结点还没有加入。而这个nodePath不会更新,就设置这一回。

我们要修改的话,就需要在运动结点也加入完毕后,再来设置跟踪结点:

//添加运动的飞机
osg::Node* loadFly(osg::AnimationPath* animation)
{
    
      
    //用于旋转飞机,使其朝向合适
    osg::MatrixTransform* mt = new osg::MatrixTransform;

    osg::Node* glider = osgDB::readNodeFile("glider.osg");
    glider->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL, osg::StateAttribute::ON);
    //飞机默认朝X轴负方向,我们绕Z轴转-90度,使其朝Y轴正方向
    mt->setMatrix(osg::Matrix::scale(5.0, 5.0, 5.0) * osg::Matrix::rotate(osg::inDegrees(-90.0), osg::Z_AXIS));
    mt->addChild(glider);

    //实际的路径控制
    osg::MatrixTransform* persionMT = new osg::MatrixTransform;
    persionMT->setUpdateCallback(new osg::AnimationPathCallback(animation));
    persionMT->addChild(mt);

    //跟踪操作器设置跟踪节点
    g_ntm->setTrackNode(mt);
    g_ntm->setTrackerMode(osgGA::NodeTrackerManipulator::NODE_CENTER_AND_ROTATION);
    g_ntm->setRotationMode(osgGA::NodeTrackerManipulator::TRACKBALL);
    g_ntm->setAutoComputeHomePosition(false);
    g_ntm->setDistance(40.0);

    return persionMT;
}

2、设置跟踪距离
在默认的情况下,视点的位置会在整个场景的包围盒外在的某一个位置,这不是我们想要的,我们只想要离我们跟踪的物体的包盒近一点,这个时候要进行如下设置,不再自动的计算场景的包围盒来计算视点:

g_ntm->setAutoComputeHomePosition(false);

到此设置就结束了,但是一个好的跟踪操作器其实还有很多的操作,比如跟踪不能拉的太近,初始的角度可以控制,也不能让视点可以拉的太远,旋转不能是任意的等等,这些都是NodeTrackerManipulator所不能的,尽管有setDistance, setRotate, setMinimumDistance, 但是这些都不起作用。这也给我们留下了新的课题。

本节所有代码

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/NodeVisitor>
#include <osg/Transform>
#include <osg/AnimationPath>
#include <osgGA/GUIEventHandler>
#include <osgUtil/LineSegmentIntersector>
#include <osg/ShapeDrawable>
#include <osg/LineWidth>
#include <osgAnimation/BasicAnimationManager>
#include <osg/MatrixTransform>
#include <osg/io_utils>
#include <osgGA/NodeTrackerManipulator>

//跟踪操作器
osgGA::NodeTrackerManipulator* g_ntm = new osgGA::NodeTrackerManipulator;

//给定顶点,和行进的速度,创建一个路径
osg::AnimationPath* CreateAnimate(osg::Vec3Array* keyPoint, float speed)
{
    
      
    //只有1个点,是形成不了路径的
    if (keyPoint->size() <= 1)
    {
    
      
        return NULL;
    }

    osg::AnimationPath* animationPath = new osg::AnimationPath;
    animationPath->setLoopMode(osg::AnimationPath::LOOP);

    //关键点对应的时间
    float t = 0.0;
    //朝向
    osg::Quat rotate;
    for (int i = 0; i < keyPoint->size(); i++)
    {
    
      
        //最后一个点,就把第一个点也压进去,形成环路
        if ((keyPoint->size() - 1) == i)
        {
    
      
            //最后一个点的朝向不计算了,就用上一个点的朝向
            animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));

            //计算最后一个点的时间
            t += ((keyPoint->at(0) - keyPoint->at(i)).length() / speed);

            //第一个点的朝向还是指向第二个点
            rotate.makeRotate(osg::Y_AXIS, keyPoint->at(1) - keyPoint->at(0));
            animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(0), rotate));
            //循环结束
            break;
        }
        else
        {
    
      
            //其它点
            //如果是第一个点,时间上是t=0.0, 朝向上由第一个点和第二个点的朝向决定
            //计算当前点的朝向,因为我们的朝向只在一个轴上水平的变化,所以可以用简单的方法
            //视口默认朝Y轴,我们要将其转到由第一个点看向第二个点的向量
            rotate.makeRotate(osg::Y_AXIS, keyPoint->at(i + 1) - keyPoint->at(i));
            animationPath->insert(t, osg::AnimationPath::ControlPoint(keyPoint->at(i), rotate));
        }

        //两点间的距离除以速度
        t += ((keyPoint->at(i + 1) - keyPoint->at(i)).length() / speed);

    }

    return animationPath;
}

//创建一个红球做标志,给定圆心和半径
osg::Node* CreateSphere(osg::Vec3 center, float r)
{
    
      
    osg::Geode* gnode = new osg::Geode;
    osg::ShapeDrawable* sd = new osg::ShapeDrawable(new osg::Sphere(center, r));
    sd->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0));//红色
    gnode->addDrawable(sd);
    return gnode;
}

//创建线,给定顶点和红宽,
osg::Node* CreateLine(osg::Vec3Array* vertex, float lineWidth)
{
    
      
    osg::Geode* gnode = new osg::Geode;
    osg::Geometry* geom = new osg::Geometry;
    gnode->addDrawable(geom);

    //设置顶点
    geom->setVertexArray(vertex);

    //设置颜色
    osg::Vec4Array* color = new osg::Vec4Array();
    color->push_back(osg::Vec4(1.0, 0.0, 0.0, 1.0));
    geom->setColorArray(color, osg::Array::BIND_OVERALL);

    //设置线宽
    osg::LineWidth* lw = new osg::LineWidth(lineWidth);
    geom->getOrCreateStateSet()->setAttribute(lw, osg::StateAttribute::ON | osg::StateAttribute::PROTECTED);
    //关闭灯光显得亮一点
    geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED);

    //设置添加为线
    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_LOOP, 0, vertex->size()));

    return gnode;
}

osg::Group* createMark(osg::Vec3Array* keyPoint)
{
    
      
    osg::Group* markGroup = new osg::Group();

    //添加红球做关键点
    for (int i = 0; i < keyPoint->size(); i++)
    {
    
      
        markGroup->addChild(CreateSphere(keyPoint->at(i), 1.0));
    }

    //添加线
    markGroup->addChild(CreateLine(keyPoint, 1.0));

    return markGroup;
}


//添加运动的飞机
osg::Node* loadFly(osg::AnimationPath* animation)
{
    
      
    //用于旋转飞机,使其朝向合适
    osg::MatrixTransform* mt = new osg::MatrixTransform;

    osg::Node* glider = osgDB::readNodeFile("glider.osg");
    glider->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL, osg::StateAttribute::ON);
    //飞机默认朝X轴负方向,我们绕Z轴转-90度,使其朝Y轴正方向
    mt->setMatrix(osg::Matrix::scale(5.0, 5.0, 5.0) * osg::Matrix::rotate(osg::inDegrees(-90.0), osg::Z_AXIS));
    mt->addChild(glider);

    //实际的路径控制
    osg::MatrixTransform* persionMT = new osg::MatrixTransform;
    persionMT->setUpdateCallback(new osg::AnimationPathCallback(animation));
    persionMT->addChild(mt);

    //跟踪操作器设置跟踪节点
    g_ntm->setTrackNode(mt);
    g_ntm->setTrackerMode(osgGA::NodeTrackerManipulator::NODE_CENTER_AND_ROTATION);
    g_ntm->setRotationMode(osgGA::NodeTrackerManipulator::TRACKBALL);
    g_ntm->setAutoComputeHomePosition(false);
    g_ntm->setDistance(40.0);

    return persionMT;
}

int main()
{
    
      
    osgViewer::Viewer viewer;
    //里面存放的是路径上的关键点
    osg::Vec3Array* keyPoint = new osg::Vec3Array;
    keyPoint->push_back(osg::Vec3(-5.15, -88.89, 8.0));
    keyPoint->push_back(osg::Vec3(50.1688, -62.9621, 39.0));
    keyPoint->push_back(osg::Vec3(79.1318, -46.6088, 15.0));
    keyPoint->push_back(osg::Vec3(80.03, -26.95, 29.0));
    keyPoint->push_back(osg::Vec3(81.41, -6.42, 12.0));
    keyPoint->push_back(osg::Vec3(70.11, 12.47, 29.0));
    keyPoint->push_back(osg::Vec3(57.51, 28.49, 12.0));
    keyPoint->push_back(osg::Vec3(38.83, 45.23, 29.0));
    keyPoint->push_back(osg::Vec3(6.98, 61.87, 12.0));
    keyPoint->push_back(osg::Vec3(-23.46, 43.42, 29.0));
    keyPoint->push_back(osg::Vec3(-36.27, 22.40, 15.0));
    keyPoint->push_back(osg::Vec3(-42.94, -13.22, 29.0));
    keyPoint->push_back(osg::Vec3(-60.78, -41.55, 8.0));
    keyPoint->push_back(osg::Vec3(-41.95, -49.65, 29.0));
    osg::Node* ceep = osgDB::readNodeFile("ceep.ive");

    //场景ceep
    osg::Group* root = new osg::Group;
    root->addChild(ceep);
    //关键点和路径的标记
    root->addChild(createMark(keyPoint));

    //添加运动的飞机
    osg::Node* fly = loadFly(CreateAnimate(keyPoint, 3.0));
    root->addChild(fly);

    viewer.setCameraManipulator(g_ntm);

    viewer.setSceneData(root);

    return viewer.run();
}