第04节 操作器和Trackball

文章发布之后,大家有很多疑问,我发现有些很基础。要求我再聊细一些,我就再重头来再比较细致的写一写。虽然不需要大家看代码,但是可能在讲代码的时候大家还是希望能有对照,作者使用的是3.6.5版本,我已经上传到了百度网盘里。大家可以通过如下方式下载:

注意:务必使用浏览器打开:
链接:https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw
提取码:xrf5

对于只有三行的最简单的osg程序:

    osgViewer::Viewer viewer;
    viewer.setSceneData(osgDB::readNodeFile("glider.osg"));
    return viewer.run();

我们重新回到Viewer::run()这个函数里,这个函数的调用是如下这样子的:

int Viewer::run()
{
    if (!getCameraManipulator() && getCamera()->getAllowEventFocus())
    {
        setCameraManipulator(new osgGA::TrackballManipulator());
    }

    setReleaseContextAtEndOfFrameHint(false);

    return ViewerBase::run();
}

从字面上我们很容易理解,首先判断当前viewer是不是设置了操作器,然后再看相机是不是允许获取焦点,假如不允许获取焦点,那鼠标键盘都点不上去,是不需要操作器的。

那么getCameraManipulator()我们知道我们没有调用过setCameraManipulator所以这个得到的是null。但是getCamera()不会返回空吗?答案是不会的,这个camera的申请阶段是在osg::View的构造函数里osgViewer::Viewer派生自osg::View因此自然构造。初始化的代码在osg/view.cpp的第27~40行,如下:

View::View():Object(true)
{
...
    _camera = new osg::Camera;
    _camera->setView(this);

    double height = osg::DisplaySettings::instance()->getScreenHeight();
    double width = osg::DisplaySettings::instance()->getScreenWidth();
    double distance = osg::DisplaySettings::instance()->getScreenDistance();
    double vfov = osg::RadiansToDegrees(atan2(height/2.0f,distance)*2.0);

    _camera->setProjectionMatrixAsPerspective( vfov, width/height, 1.0f,10000.0f);

    _camera->setClearColor(osg::Vec4f(0.2f, 0.2f, 0.4f, 1.0f));

    osg::StateSet* stateset = _camera->getOrCreateStateSet();
    stateset->setGlobalDefaults();
...
}

没有操作器的话就会调用setCameraManipulator(new osgGA::TrackballManipulator());设置Trackball了,那么操作器是如何在场景中起到拖拖拽拽起作用的呢?以及Trackball是怎么回事儿呢?怎么就场景上来就会在屏幕的中间,一拖就会走呢?下面来一一解答。

操作器怎么起作用的?

所有的操作器都派生自osgGA::CameraManipulator,而这个CameraManipulator又派生自osgGA::GUIEventHandler,可见其本质上是个事件处理的类。因此它首先会接收事件,比如鼠标一拖,场景就动。场景动与不动是受视点的位置、朝向来决定的,也就是观察矩阵,因此CameraManipulator必有处理事件的接口和输出矩阵的接口,处理事件的接口是函数bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter &aa);所以每个操作器必须要对这个函数大书特书来达到按自己的操作来达成接口的目的。这个handle的被调用的地方是在一帧绘制的事件遍历阶段,是在void Viewer::eventTraversal()中,viewer.cpp的第1126行:_cameraManipulator->handle( event, 0, _eventVisitor.get());虽然入参不一样,但是跟踪一下会发现会调用过去的。

事件处理之后,就要根据处理的事件改变视点的位置来达到操作场景的目的。这个是通过osgGA::CameraManipulator的函数virtual osg::Matrixd getInverseMatrix()来实现的,它的调用时机是在更新阶段void Viewer::updateTraversal()中,第1213行:_cameraManipulator->updateCamera(*_camera);它会调用至:camera.setViewMatrix(getInverseMatrix());这个时候就有人不明白了,为什么要使用逆矩阵呢?原因这里有点绕,大家认真理解一下,世界坐标下,一个盒子在A点(xa,ya,za),视点在B点(xb,yb,zb),先不管朝向不朝向的。我们要输出此时在B点的人看到A点的图像,做法有很多,实际上图形学界是这么做的,把A点先转换为以B点为局部坐标的坐标系下,然后再进行一系列操作。这个变换的过程其实相当于需要求出把B点移回世界坐标原点的矩阵,A点再乘以这个矩阵就可以,这个移回的矩阵是这样计算的,把人从世界坐标原点移到B点的变换是Matrix,而把B点移回世界坐标原点的变换就是InverseMatrix,现在我们要把A点放在以B点为局部坐标下,则需要变换的就是InverseMatrix。有点绕,但是明白这一点还是很重要的,属于基本功。

Trackball是如何上来就看着物体的
Trackball的第一个特点是上来就会看着场景的物体,这是怎么实现的呢?首先setCameraManipulator其实有两个参数:void setCameraManipulator(osgGA::CameraManipulator* manipulator, bool resetPosition = true);第二个参数默认是true,意思就是初始时要设置到home的位置,其实是调用home函数,看看这个函数的代码:

void View::setCameraManipulator(osgGA::CameraManipulator* manipulator, bool resetPosition)
{
    _cameraManipulator = manipulator;

    if (_cameraManipulator.valid())
    {
        _cameraManipulator->setCoordinateFrameCallback(new ViewerCoordinateFrameCallback(this));

        if (getSceneData()) _cameraManipulator->setNode(getSceneData());

        if (resetPosition)
        {
            osg::ref_ptr<osgGA::GUIEventAdapter> dummyEvent = _eventQueue->createEvent();
            _cameraManipulator->home(*dummyEvent, *this);
        }
    }
}

而home是如何定位到正好看见物体的呢?不大也不小,不近也不远的。在home这个函数中,它调用了virtual void computeHomePosition(const osg::Camera *camera = NULL, bool useBoundingBox = false);来计算了一下home的位置,其计算方法也很一目了然,先计算包围盒,然后再调用setHomePosition根据包围盒的中心点和半径来设置视点的位置和朝向。virtual void setHomePosition(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up, bool autoComputeHomePosition=false)

Trackball是如何实现鼠标惯性操作的?
Trackball有两个特征:
1、鼠标按下,拖动,停下,鼠标弹起。则场景转动,在鼠标弹起时不转了。
2、鼠标按下,甩出来,则场景一直转动。

这两个的差别关键在鼠标的弹起操作,一个是停下弹,一个是甩弹。这个是在这里判断的,bool StandardManipulator::handleMouseRelease( const GUIEventAdapter& ea, GUIActionAdapter& us )是操作器的鼠标弹起事件:这里的关键是鼠标弹起的时候和上一个鼠标移动的时候的时间差>0.02秒,则认为是情况1,小于等于0.02秒认为是甩。

bool StandardManipulator::handleMouseRelease( const GUIEventAdapter& ea, GUIActionAdapter& us )
{
    //判断是DRAG,而不是MOVE,DRAG是有键按下了,Mask=0
    if( ea.getButtonMask() == 0 )
    {

        double timeSinceLastRecordEvent = _ga_t0.valid() ? (ea.getTime() - _ga_t0->getTime()) : DBL_MAX;
        //判断上一个事件和当前事件的时间差,小于0.02秒可认为是甩
        if( timeSinceLastRecordEvent > 0.02 )
            flushMouseEventStack();

        if( isMouseMoving() )
        {

            if( performMovement() && _allowThrow )
            {
                us.requestRedraw();
                us.requestContinuousUpdate( true );
                _thrown = true;
            }

            return true;
        }
    }

    flushMouseEventStack();
    addMouseEvent( ea );
    if( performMovement() )
        us.requestRedraw();
    us.requestContinuousUpdate( false );
    _thrown = false;

    return true;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容