关键词: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() 里面(获取视图矩阵之后)。
这篇就到这结了吧,写了三天才写完,平时没时间,只能抽空写,原创不易,希望大家转载时附上本文链接,在此谢谢啦!