需求背景
工作中需要使用 threejs
制作一个第一人称的 viewer
,在使用 threejs
的 flyControls
时发现一些和需求不一样的情景。
主要的问题就是拖拽查看时相机在拖拽后会导致上方向的变化从而导致视角变化很奇怪,所以需要改造一下原本的 FlyControl
。
既然要改造那就肯定要先解析 FlyControls
的源码了
FlyControls的功能
FlyControls
实现了一个类似第一人称视角通过 WASD
来调整相机前进的基本控制器。
可以看到上图 WASD
控制移动,RF
控制上下移动,QE
旋转相机,方向键上下左右可以控制视角。
也可以用鼠标偏移位置自动旋转视角
源码分析属性声明
地址 :
首先 FlyControls
继承自 EventDispatcher
,EventDispatcher
是事件调度器。
主要是用于 FlyControls
的事件信息传递用。
构造函数中一个是 object
也就是控制的摄像机对象从 TS
定位文件中可以看出是一个Camera
或者继承自Camera
的对象。
domElement
则是当前 threejs renderer
绘制的 dom
元素
接下来就是一系列变量的定义
其中 movmentSpeed
,rollSpeed
是控制相机移动和旋转的速度的
dragToLook
则是我们需求中需要的拖拽使相机视角发生变化,可以看到这里默认是 false
。
autoForward
是自动向前
EPS
应该是最小误差修正用的
可以看到这里还用 lastQuaternion
和 lastPosition
记录了最后相机的四元组和位置信息
tmpQuaternion
就是临时保存用的一个四元组
剩下就是一些状态值
源码分析事件监听
在代码最底部可以看到 FlyControls
监听了7个事件分别对应一系列操作。
先从第一个keydown分析
1、一开始判断是否按下了 alt
键和是否启用了控制器
2、然后根据按键的键码分别设置 moveState
的状态,其中 shift
对移动速度进行了修改
3、然后就是最关键的 updateMovementVector
和 updateRotationVector
,这两个之后进行分析。
这里 keyup
和 keydown
类似就不过多做分析
然后是鼠标操作三兄弟
从 pointerdown
可以看出来是根据 dragToLook
来进行不同的操作。
如果是拖拽调整视角就修改 status
的值
如果不是则根据按下的鼠标左键还是又键修改 moveState
的状态。
紧接着pointermove事件中:
依旧如果不是使用拖拽查看或者 status大于0
(这里的 status>0
是由向前的 pointerdown
修改的,也就是可以看做是否按下了鼠标)
然后就是这个 getContainerDimensions
,其实就是查看这个当前的 viewer
是不是在 docment
对象,返回相应的大小和偏移值
而 halfWidth
,halfHeight
就是当前 viewer
可视区域一半的宽高。
-( event.pageX - container.offset[ 0 ] )
和 ( ( event.pageY - container.offset[ 1 ] )
是鼠标在当前可视区域的相对坐标,
其值减去一半的宽度后,其实就可以视作鼠标相对于可视区域中心点的相对位置了。
以x轴
来举例可以看出来 ( event.pageX - container.offset[ 0 ] ) - halfWidth
则是红线部分则是鼠标到中心点的距离。
然后除以 halfWidth
则是一个归一化处理,相当于把鼠标距离中心点位置的值域控制在0到1之间了,最后在修改 moveState
。
最后pointerup事件则是将状态还原
在此之后 可以看到 updateMovementVector
和 updateRotationVector
实质上是修改了 moveVector
和 rotationVector
最后
再根据之前设置的速度和旋转值以及 rotationVector
和 moveVector
来控制相机的位置以及旋转。
最后的判断则是看移动和旋转的值是否太小了。
太小就进行忽略。
分析
那么这个控制器为什么会造成拖拽后上方向发生变化呢,其实从上面代码的分析不难看出。
先将鼠标向上旋转一个角度
再将鼠标朝右旋转
最后再朝下拖动鼠标时
此时的相机左方向轴已经不在x轴与y轴构成的平面上了。
所以相机上方向发生了变化。
其根本原因是相机左右旋转时旋转轴是以自身上方向为旋转轴造成的。
如果要保持相机上方向一致的情况下旋转视角应该以z轴作为相机旋转的旋转轴。
这样相机怎么旋转都能保持上方向一致了。