osg漫遊器的原理和執行流程

關鍵詞:manipulator; 漫遊器; 第一人稱漫遊器; osg; osg漫遊器的原理; osg漫遊器的流程
CameraManipulator講起吧,CameraManipulator是漫遊器的基類,它繼承於GUIEventHandler

class OSGGA_EXPORT CameraManipulator : public GUIEventHandler
{
    ...
};

繼承於GUIEventHandler的原因就是爲了獲取用戶的操作事件。這表明了CameraManipulator實際上也是個GUIEventHandler的本質。對於GUIEventHandler而言,對它比較熟悉的朋友來說就會知道,它的最重要成員函數就是handle()函數了,所以在CameraManipulator裏面也對 handle() 函數進行了重載。只不過它什麼也沒做:

bool CameraManipulator::handle(const GUIEventAdapter&,GUIActionAdapter&)
{
    return false;
}

CameraManipulator這個類因爲是個純虛類,比較簡單,只真正的實現了幾個函數:

osg::Vec3d getSideVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(0,0),cf(0,1),cf(0,2)); }
osg::Vec3d getFrontVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(1,0),cf(1,1),cf(1,2)); }
osg::Vec3d getUpVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(2,0),cf(2,1),cf(2,2)); }
virtual void updateCamera(osg::Camera& camera) { camera.setViewMatrix(getInverseMatrix()); }

virtual void setHomePosition(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up, bool autoComputeHomePosition=false)
{
            setAutoComputeHomePosition(autoComputeHomePosition);
            _homeEye = eye;
            _homeCenter = center;
            _homeUp = up;
}

virtual void getHomePosition(osg::Vec3d& eye, osg::Vec3d& center, osg::Vec3d& up) const
{
            eye = _homeEye;
            center = _homeCenter;
            up = _homeUp;
}
//。。。。等

值得注意的是,CameraManipulator裏面定義了四個純虛函數

virtual void setByMatrix(const osg::Matrixd& matrix) = 0;
virtual void setByInverseMatrix(const osg::Matrixd& matrix) = 0;
virtual osg::Matrixd getMatrix() const = 0;
virtual osg::Matrixd getInverseMatrix() const = 0;

因此,在繼承這個類時,這四個函數必須要在子類中實現他們。那麼這幾個函數是用來幹嘛的呢,非逼得它的子類一定要實現它呢?

大家不妨往上翻一下,在已經實現的函數中有一個updateCamera函數,這個函數的功能是更新相機的視圖矩陣的,更新用到的矩陣就是由 getInverseMatrix() 返回的矩陣。從這幾個純虛函數的名字來看,都是跟矩陣有關的,因此他們都是和相機的視圖矩陣有千絲萬縷的聯繫的,所以可以預測他們的重要作用了吧(<_<大家闊以先歪歪它們的功能)。

在講用CameraManipulator生孩子之前,先說一說CameraManipulator類是在什麼時候更新相機的吧,畢竟控制相機去拍攝我們想要拍攝的部位<_<,額不,是拍攝我們想看的場景,額。。。想看的地方。。。。這纔是它的最終使命呀。那它的updateCamera函數是在哪裏被調用的呢?好了不賣關子了

打開osg源碼,定位到osgViewer\Viewer.cpp文件,再定位至updateTraversal函數,在這個函數裏頭,有一句你絕對灰常喜翻的命令:

_cameraManipulator->updateCamera(*_camera);

看到了吧,就是在這裏把相機移過去的呢<_<

好了,知道相機對準的時機之後,我們再說說CameraManipulator生孩子的事吧,畢竟這纔是你們找了這麼久的資料的最終目的嘛。

打開源碼定位到osgGA工程,在這裏你可以找到CameraManipulator的6個子類。

class OSGGA_EXPORT AnimationPathManipulator : public CameraManipulator{};
class OSGGA_EXPORT CameraViewSwitchManipulator : public CameraManipulator{};
class OSGGA_EXPORT DriveManipulator : public CameraManipulator{};
class OSGGA_EXPORT KeySwitchMatrixManipulator : public CameraManipulator{};
class OSGGA_EXPORT SphericalManipulator : public CameraManipulator{};
class OSGGA_EXPORT StandardManipulator : public CameraManipulator{};

吼吼。。。這麼多,一個個講會累死我滴,爲了我的小命考慮,大家就自己對這些子類的名稱去歪歪它們的功能吧。我們這裏只講標準漫遊器類StandardManipulator。(有種瞬間五殺的趕腳,只剩一個StandardManipulator了,但也不能太得瑟了,等我講完看看面對這個子類,我的小命還能不能保住吧)

咳。咳。。敲黑板啦,大家翻到osgGA\StandardManipulator文件。。。。。。

看看StandardManipulator內部都有些啥?我的媽耶,這麼一大坨的咚咚
。。。
。。。
吐血身亡
還沒去翻源碼的你實在太懶了,你說你,欸,我會被氣死。算了把那一大坨函數扔上來吧(這不是全部。。)

public:
        /** Sets manipulator by eye position and eye orientation.*/
        virtual void setTransformation( const osg::Vec3d& eye, const osg::Quat& rotation ) = 0;

        /** Sets manipulator by eye position, center of rotation, and up vector.*/
        virtual void setTransformation( const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up ) = 0;

        /** Gets manipulator's eye position and eye orientation.*/
        virtual void getTransformation( osg::Vec3d& eye, osg::Quat& rotation ) const = 0;

        /** Gets manipulator's focal center, eye position, and up vector.*/
        virtual void getTransformation( osg::Vec3d& eye, osg::Vec3d& center, osg::Vec3d& up ) const = 0;

        virtual void setNode( osg::Node* );
        virtual const osg::Node* getNode() const;
        virtual osg::Node* getNode();
        virtual void setVerticalAxisFixed( bool value );
        inline bool getVerticalAxisFixed() const;
        inline bool getAllowThrow() const;
        virtual void setAllowThrow( bool allowThrow );
        virtual void setAnimationTime( const double t );
        double getAnimationTime() const;
        bool isAnimating() const;
        virtual void finishAnimation();
        virtual void home( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual void home( double );
        virtual void init( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual void getUsage( osg::ApplicationUsage& usage ) const;
    protected:
        virtual bool handleFrame( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleResize( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleMouseMove( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleMouseDrag( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleMousePush( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleMouseRelease( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleKeyDown( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleKeyUp( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleMouseWheel( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool handleMouseDeltaMovement( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool performMovement();
        virtual bool performMovementLeftMouseButton( const double eventTimeDelta, const double dx, const double dy );
        virtual bool performMovementMiddleMouseButton( const double eventTimeDelta, const double dx, const double dy );
        virtual bool performMovementRightMouseButton( const double eventTimeDelta, const double dx, const double dy );
        virtual bool performMouseDeltaMovement( const float dx, const float dy );
        virtual bool performAnimationMovement( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual void applyAnimationStep( const double currentProgress, const double prevProgress );
        
        void addMouseEvent( const osgGA::GUIEventAdapter& ea );
        void flushMouseEventStack();
        virtual bool isMouseMoving() const;
        float getThrowScale( const double eventTimeDelta ) const;
        virtual void centerMousePointer( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        static void rotateYawPitch( osg::Quat& rotation, const double yaw, const double pitch,
                                    const osg::Vec3d& localUp = osg::Vec3d( 0.,0.,0.) );
        static void fixVerticalAxis( osg::Quat& rotation, const osg::Vec3d& localUp, bool disallowFlipOver );
        void fixVerticalAxis( osg::Vec3d& eye, osg::Quat& rotation, bool disallowFlipOver );
        static void fixVerticalAxis( const osg::Vec3d& forward, const osg::Vec3d& up, osg::Vec3d& newUp,
                                     const osg::Vec3d& localUp, bool disallowFlipOver );
        virtual bool setCenterByMousePointerIntersection( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
        virtual bool startAnimationByMousePointerIntersection( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );

就問你怕不怕,反正我是挺怕的。不過作爲一心想要把你搞懂,額不,把osg搞懂,的我還是拼了這條小命吧,幫你理一理。

不過,我先去上個廁所先。

好了,言歸正傳。話說前面我們講到了CameraManipulator實際上是一個GUIEventHandler,是接受用戶事件,控制相機拍這拍那的一個老司機,而且還有那麼多小孩子,其中一個就是擁有上面一大坨成員函數的StandardManipulator。也就是說StandardManipulator實際上也是繼承了它祖先GUIEventHandler的事件處理能力,只不過他爸爸很懶,在handle()函數中啥都沒做,那我們就去看看它把它爹的那個功能擴展到了什麼地步吧。

定位到 StandardManipulator::handle() 函數,算了,你們這些懶鬼肯定不會去看源碼的,我還是貼出來吧

bool StandardManipulator::handle( const GUIEventAdapter& ea, GUIActionAdapter& us )
{
    switch( ea.getEventType() )
    {
        case GUIEventAdapter::FRAME:
            return handleFrame( ea, us );
        case GUIEventAdapter::RESIZE:
            return handleResize( ea, us );
        default:
            break;
   }
    if( ea.getHandled() )
        return false;
    switch( ea.getEventType() )
    {
        case GUIEventAdapter::MOVE:
            return handleMouseMove( ea, us );
        case GUIEventAdapter::DRAG:
            return handleMouseDrag( ea, us );
        case GUIEventAdapter::PUSH:
            return handleMousePush( ea, us );
        case GUIEventAdapter::RELEASE:
            return handleMouseRelease( ea, us );
        case GUIEventAdapter::KEYDOWN:
            return handleKeyDown( ea, us );
        case GUIEventAdapter::KEYUP:
            return handleKeyUp( ea, us );
        case GUIEventAdapter::SCROLL:
            if( _flags & PROCESS_MOUSE_WHEEL )
            return handleMouseWheel( ea, us );
            else
            return false;
        default:
            return false;
    }
}

還是很簡單滴嘛,要是上面代碼的邏輯都看不懂,那你還是放棄osg吧,額不,放棄IT吧,這不適合你。

StandardManipulator::handle() 函數實際上就只做了一個事————事件分發,判斷事件的類型,然後調用對應的事件處理函數,OK搞定,就是這麼滴簡單,看來也不會累屎我嘛。

比如GUIEventAdapter::DRAG事件,則調用handleMouseDrag()函數;GUIEventAdapter::SCROLL事件則調用handleMouseWheel();GUIEventAdapter::FRAME事件則調用handleFrame()函數。。。爲了方便解說,就統稱這些函數爲“事件函數”吧。

在定義這些事件函數的時候,osg作者全都把他們用 virtual 貼了標籤,你是不是又想到了點啥<_<(這是又要搞事生孩子呀,看來老外也很能生的嘛。。。)

的確,這些事件函數都是有預謀的,有的完全是給孩子定的一個字輩讓它的孩子老老實實的叫(call)這個咚咚,有的還是幹了點事,幫它的孩子做了點事。

我們先看看StandardManipulator慈愛的一面吧,看看它爲孩子們都做了啥事。看註釋理解就OK

bool StandardManipulator::handleFrame( const GUIEventAdapter& ea, GUIActionAdapter& us )
{
    double current_frame_time = ea.getTime();
    _delta_frame_time = current_frame_time - _last_frame_time;//計算幀間時差
    _last_frame_time = current_frame_time;
    if( _thrown && performMovement() ) //實現拋丟動畫
    {
        us.requestRedraw(); //請求重繪
    }
    if( _animationData && _animationData->_isAnimating )
    {
        performAnimationMovement( ea, us );//動畫效果
    }
   return false;
}

bool StandardManipulator::handleResize( const GUIEventAdapter& ea, GUIActionAdapter& us ) // resize事件
{
    init( ea, us );//清空事件棧
    us.requestRedraw(); //請求重繪
    return true;
}

bool StandardManipulator::handleMouseDrag( const GUIEventAdapter& ea, GUIActionAdapter& us )
{
    addMouseEvent( ea );//當前事件壓棧
    if( performMovement() )//變化場景
        us.requestRedraw();
    us.requestContinuousUpdate( false );
    _thrown = false;//停止拋丟
    return true;
}
bool StandardManipulator::handleMousePush( const GUIEventAdapter& ea, GUIActionAdapter& us )
{
    flushMouseEventStack();//清棧
    addMouseEvent( ea );//壓棧
    if( performMovement() )//變化場景
        us.requestRedraw();
    us.requestContinuousUpdate( false );
    _thrown = false;
    return true;
}

bool StandardManipulator::handleMouseRelease( const GUIEventAdapter& ea, GUIActionAdapter& us )
{
    if( ea.getButtonMask() == 0 )
    {
        double timeSinceLastRecordEvent = _ga_t0.valid() ? (ea.getTime() - _ga_t0->getTime()) : DBL_MAX;
        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;
}
bool StandardManipulator::handleKeyDown( const GUIEventAdapter& ea, GUIActionAdapter& us )
{
    if( ea.getKey() == GUIEventAdapter::KEY_Space )//空格回家(相機歸位)
    {
        flushMouseEventStack();
        _thrown = false;
        home(ea,us);
        return true;
    }
    return false;
}

事件函數中,StandardManipulator就爲它的孩子做了這些事,說多也不多,說少也不少,還是挺有愛心的嘛。

上面大家可以看到,StandardManipulator主要做的事就幾件而已,拋丟事件棧處理場景變更

拋丟大家可能會比較陌生,就是那種你拖一下場景,然後模型就一直在轉轉轉的那種效果,好吧,這個描述有點抽象,但還是就當你已經get了。

事件棧又是什麼鬼?事件棧其實就是兩個變量啦,喏,就是下面這兩個咚咚:

osg::ref_ptr< const osgGA::GUIEventAdapter > _ga_t1;
osg::ref_ptr< const osgGA::GUIEventAdapter > _ga_t0;

void StandardManipulator::addMouseEvent( const GUIEventAdapter& ea )
{//壓棧
    _ga_t1 = _ga_t0;
    _ga_t0 = &ea;//新事件是_ga_t0,上一個事件是_ga_t1
}
void StandardManipulator::flushMouseEventStack()
{//清棧
    _ga_t1 = NULL;
    _ga_t0 = NULL;
}

然後就是場景變更了performMovement(),我們看看它幹了些啥:

bool StandardManipulator::performMovement()
{//(咳咳,事件棧的作用體現的淋漓盡致)
    if( _ga_t0.get() == NULL || _ga_t1.get() == NULL )
        return false;
    //計算兩次事件的間隔時間
    double eventTimeDelta = _ga_t0->getTime() - _ga_t1->getTime();
    if( eventTimeDelta < 0. )
    {
        OSG_WARN << "Manipulator warning: eventTimeDelta = " << eventTimeDelta << std::endl;
        eventTimeDelta = 0.;
    }
    //計算與上次事件的鼠標位移量(歸一化的屏幕座標差)
    float dx = _ga_t0->getXnormalized() - _ga_t1->getXnormalized();
    float dy = _ga_t0->getYnormalized() - _ga_t1->getYnormalized();
    if( dx == 0. && dy == 0. )
        return false;
    //根據鼠標的按鈕進行具體事件分發
    unsigned int buttonMask = _ga_t1->getButtonMask();
    if( buttonMask == GUIEventAdapter::LEFT_MOUSE_BUTTON )
    {
        return performMovementLeftMouseButton( eventTimeDelta, dx, dy );
    }
    else if( buttonMask == GUIEventAdapter::MIDDLE_MOUSE_BUTTON ||
            buttonMask == (GUIEventAdapter::LEFT_MOUSE_BUTTON | GUIEventAdapter::RIGHT_MOUSE_BUTTON) )
    {
        return performMovementMiddleMouseButton( eventTimeDelta, dx, dy );
    }
    else if( buttonMask == GUIEventAdapter::RIGHT_MOUSE_BUTTON )
    {
        return performMovementRightMouseButton( eventTimeDelta, dx, dy );
    }
    return false;
}

可以看到,performMovement()函數實際上是在計算本次事件和上次事件的時間差以及鼠標的偏移量,然後根據按下鼠標的按鍵進行相應的處理。我們把這裏的

performMovementLeftMouseButton()

performMovementMiddleMouseButton()

performMovementRightMouseButton()
三個函數統稱爲“具體事件函數”。從函數名字可以大致猜測他們的功能分別是:根據鼠標左鍵變更場景、根據鼠標中鍵變更場景、根據鼠標右鍵變更場景。

那它們到底是怎麼變更場景的呢?這是不是你最最希望的從這篇又臭又長的文章裏找到的keypoint呢?好了,讓我們立馬揭曉。

敲黑板啦。。。。

定位到。。。。等等。。

這麼大一個烏龍

bool StandardManipulator::performMovementLeftMouseButton( const double /*eventTimeDelta*/, const double /*dx*/, const double /*dy*/ )
{
    return false;
}

bool StandardManipulator::performMovementMiddleMouseButton( const double /*eventTimeDelta*/, const double /*dx*/, const double /*dy*/ )
{
    return false;
}

bool StandardManipulator::performMovementRightMouseButton( const double /*eventTimeDelta*/, const double /*dx*/, const double /*dy*/ )
{
    return false;
}

他們啥都沒幹。坑定又是要它的孩子去做了,這不得不逼我們去看StandardManipulator的孩子了。

在離開StandardManipulator看他的孩子之前,我們再回顧一下StandardManipulator裏面的內容看看還有什麼沒有講到的咚咚。。。咦,差點忘了,StandardManipulator的父親CameraManipulator裏面有四個純虛函數呢,在StandardManipulator裏面並沒有實現它們。看來StandardManipulator也就是空架子罷了,坑爹又坑娃的類,我們並不能直接用它創建對象。走吧走吧,看它的孩子去。

好吧,通過FBI搜索,就只發現了標準漫遊器的兩個孩子,感覺有點少啊。廢話不多說,看看這兩個苦逼的孩子吧,還有很多事等着他們做呢。

//第一人稱漫遊器
class OSGGA_EXPORT FirstPersonManipulator : public StandardManipulator{...}

//軌跡漫遊器
class OSGGA_EXPORT OrbitManipulator : public StandardManipulator{...}

對比了一下,FirstPersonManipulator這個類要簡單一點,所以獅子要挑軟的捏。
定位到osgGA\FirstPersonManipulator,蒽,首先抓住我氪金狗眼的就是那四個純虛函數了,嘿嘿,看來歷經了這部漫長的家族史,這就是我們的first guy了,額,男豬腳。。。我把代碼貼出來給你瞄一眼,別不以爲意哦,這裏有最重要的咚咚呢!!!

void FirstPersonManipulator::setByMatrix( const Matrixd& matrix )
{
   _eye = matrix.getTrans();
   _rotation = matrix.getRotate();
   if( getVerticalAxisFixed() ) 
      fixVerticalAxis( _eye, _rotation, true );//縱軸鎖定
}
void FirstPersonManipulator::setByInverseMatrix( const Matrixd& matrix )
{
   setByMatrix( Matrixd::inverse( matrix ) );
}
Matrixd FirstPersonManipulator::getMatrix() const//獲取相機位置姿態的變換矩陣
{
   return Matrixd::rotate( _rotation ) * Matrixd::translate( _eye );
}
Matrixd FirstPersonManipulator::getInverseMatrix() const//獲取視圖矩陣
{
   return Matrixd::translate( -_eye ) * Matrixd::rotate( _rotation.inverse() );
}

別跳過這段代碼(小樣兒,知道你不想瞄這些代碼,但是瞄一眼又不會懷孕)。我只寫了三句註釋,但是卻蘊含了宇宙星辰的所有能量。從這幾個函數中我們闊以知道類中必有兩個成員變量:_eye和_rotation。而根據前面所說,我們漫遊器的作用是用來更新相機視圖矩陣的,在更新相機時用到的函數就是getInverseMatrix()這個傢伙。也就是說,這個函數是利用了_eye和_rotation(兩個被保存在漫遊器裏的成員)計算出的視圖矩陣。由此我們可以大致歪歪一下_eye和_rotation的作用有多重要了(直接影響視圖矩陣!!!)。

那我們就把目光轉移到_eye和_rotation吧。從名字來看,根據望文知義的潛意識告訴我,_eye保存的應該是相機的位置座標(相機在世界座標裏的位置,即相機從(0,0,0)到當前位置的平移向量),而_rotation是相機旋轉變換的矩陣。由於osg的變換是右乘的(參照https://www.cnblogs.com/indif/archive/2011/05/13/2045106.html理解osg的變換操作),所以獲取相機的位置姿態矩陣的函數getMatrix()裏的公式意義是:相機旋轉後再平移。所以公式的意義和我們歪歪的_eye和_rotation的意義是一樣的,即_eye保存了相機的位置,_rotation保存了相機的旋轉量。由此可見,我們的FirstPersonManipulator只需要調整好_eye和_rotation就可以指揮相機這拍拍那拍拍了,hiahia。

第一人稱的漫遊器操作起來很簡單,就是滾動鼠標前進或後退,拖拽鼠標扭脖子,來看看他們是怎麼做到的吧:

void FirstPersonManipulator::moveForward( const Quat& rotation, const double distance )
{
   _eye += rotation * Vec3d( 0., 0., -distance ); //前進或後退 (distance可正可負,下同)
}
void FirstPersonManipulator::moveRight( const double distance )
{
   _eye += _rotation * Vec3d( distance, 0., 0. );//向左或右側移
}
void FirstPersonManipulator::moveUp( const double distance )
{
   _eye += _rotation * Vec3d( 0., distance, 0. );//上移下移(視線方向不變,只是單純垂直的上下移)
}
//注:前後左右上下都是相對於相機而言的,即向那個方向平移的量參照的是相機座標系,因此在改變相機位置(世界座標)時,
要先把平移量轉換成世界座標系下的量,然後再和_eye做運算(平移)。把相機座標下的平移量轉成世界座標系下的平移量的操
作就是上面幾個 += 後面的那坨咚咚,自己好好領悟一下(嗯~ 真香)。

//扭脖子函數
void StandardManipulator::rotateYawPitch( Quat& rotation, const double yaw, const double pitch,
                                           const Vec3d& localUp )//使用yaw和pitch設置旋轉矩陣
{
    bool verticalAxisFixed = (localUp != Vec3d( 0.,0.,0. ));
    if( verticalAxisFixed )
        fixVerticalAxis( rotation, localUp, true );//縱軸鎖定
    Quat rotateYaw( -yaw, verticalAxisFixed ? localUp : rotation * Vec3d( 0.,1.,0. ) );//左右扭脖子
    Quat rotatePitch;
    Quat newRotation;
    Vec3d cameraRight( rotation * Vec3d( 1.,0.,0. ) );
    double my_dy = pitch;
    int i = 0;
    do {
        rotatePitch.makeRotate( my_dy, cameraRight );//仰頭低頭
        newRotation = rotation * rotateYaw * rotatePitch;//計算新的旋轉矩陣(先扭脖子再 仰頭或低頭)
        if( verticalAxisFixed )
            fixVerticalAxis( newRotation, localUp, false );
        Vec3d newCameraUp = newRotation * Vec3d( 0.,1.,0. );
        if( newCameraUp * localUp > 0. )
        {
            rotation = newRotation;
            return;
        }
        my_dy /= 2.;
        if( ++i == 20 )
        {
            rotation = rotation * rotateYaw;
            return;
        }
    } while( true );
}

至此呢,第一人稱的漫遊器就講的差不多了,源碼中還有很多處理是對動畫的處理,很有意思的呢,你闊以自己去啃一啃,我就不再多講了。對了,上面已經講了好幾次的縱軸鎖定了,爲了理解這個操作是怎麼實現的可費了我好大勁呢,你們也可以試試,到時有空我再講講這個縱軸鎖定的實現原理。

對了,插播一條注意事項,由於修改_eye和_rotation的操作都是在事件處理裏面進行的,所以這些參數的變化都是在frame()函數裏的 eventTraversal() 裏面進行的,而真正呈現變化的時候是在 updateTraversal() 裏面(獲取視圖矩陣之後)。

這篇就到這結了吧,寫了三天才寫完,平時沒時間,只能抽空寫,原創不易,希望大家轉載時附上本文鏈接,在此謝謝啦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章