在开发有关空间主题的项目时,我遇到了一个事实,由于某些原因,Three.js没有适合此类任务的现成且方便的摄像机控制工具。 当然,我承认我看起来很糟糕……但是,搜索结果并没有花很长时间。
传统上,OrbitControls是Three.js示例的最爱,它不知道如何颠倒相机,也不知道还需要多少其他东西。
TrackballControls的卓越之处在于它可以根据需要围绕对象旋转相机,也可以上下颠倒旋转,但是它不知道如何打开视轴,不知道如何在不改变比例的情况下来回移动,无法方便地调整移动和转弯的速度。
FlyControls-相反,它允许您制作“枪管”并更改速度,但是...围绕所讨论对象的摄影机旋转在哪里?
当然,有可能在各种拐杖的帮助下走出去,但是以某种方式这并不容易。 确保没有针对我的目的的现成解决方案后,我决定自己创建它。 谁有兴趣,请照顾下。
我决定以TrackballControls.js为基础,打开它后,我们看到了作者:
这是他们的工作,将作为我们的出发点。 有必要添加一点,如果可能的话不要破坏现有的。 (但是,必须删除一些内容)。
首先,我们拥有的是:相机不受限制地绕物体旋转,不会卡在杆子上,始终保持恒定的角速度。 有变焦,有平底锅。 这很好,但还不够。
令人惊讶的是按钮的奇怪位置。 实际上,这是意外的:
this.keys = [ 65 , 83 , 68 ];
只需三个按钮即可控制摄像机? 也许这是一个好主意,但实际上它不起作用。 在源代码中的keydown事件处理程序中,我们观察到以下几行:
window.removeEventListener( 'keydown', keydown ); _prevState = _state; if ( _state !== STATE.NONE ) { return; } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) { _state = STATE.ROTATE; } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) { _state = STATE.ZOOM; } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) { _state = STATE.PAN; }
看起来有点神秘,但是...不起作用。 即 一般而言。 上面的按钮实际上没有任何作用,相机仅由鼠标或触摸控制。
我评论了这段代码是不必要的,然后将其替换为我的代码,关于该代码的故事会更进一步。 删除用于按钮处理的回调-显然,每次按下按钮仅需工作一次。 但这不是外层空间所需的行为,因此也受到评论。
那么,我们在这里缺少什么呢? 我决定添加的内容:
- 沿视线来回运动。
- 摄像机以轴为视线的旋转。
- 自动旋转。
- 一键改变所有动作和转弯速度的能力。
- 全键盘控制。
我希望读者能原谅我这样一个事实,我谈论编辑过程不是按照完成这些子任务的顺序,而是按照从上至下的源代码的顺序。 我将解释我在做什么和为什么做。
建设者
添加一些变量:
this.rotationZFactor = 0.0; this.RVMovingFactor = 0.0; this.autoRotate = false; this.autoRotateSpeed = 0.001; this.allSpeedsFactor = 1;
需要rotationZFactor来控制摄像机在视轴上的旋转,
RVMovingFactor沿同一轴控制前进和后退,
我认为autoRotate不需要注释,
allSpeedsFactor控制所有组合,移动和转弯的速度。
RotateCamera方法
这样组织:
this.rotateCamera = ( function () { var axis = new THREE.Vector3(), quaternion = new THREE.Quaternion(), eyeDirection = new THREE.Vector3(), objectUpDirection = new THREE.Vector3(), objectSidewaysDirection = new THREE.Vector3(), moveDirection = new THREE.Vector3(), tmpQuaternion = new THREE.Quaternion(), angle; return function rotateCamera() { ...
在这里,几乎所有事情都是如此,我唯一添加的就是变量tmpQuaternion来处理摄像机沿Z轴的旋转。
此外,检查是否有必要进行任何处理非常简单:
moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); angle = moveDirection.length(); if ( angle ) {
它变成了这样:
if (_this.autoRotate) _moveCurr.x += _this.autoRotateSpeed; moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); moveDirection.setLength(moveDirection.length() * _this.allSpeedsFactor); angle = moveDirection.length(); if ( angle || _this.rotationZFactor) {
这是什么,为什么。 首先,如果启用了自动旋转,则将其速度添加到沿X轴的旋转中;其次,将所有旋转的缩放比例(也包括自动旋转)添加到allSpeedsFactor中。 控制所有转换的速度。 第三,我们不仅会在角度角度(相机相对于controls.target的运动)不为零的情况下执行进一步的动作,而且还会负责负责相机沿Z轴旋转的rotationZFactor为非零的情况。
进一步在方法代码中,在计算了旋转四元数之后,进行了少量添加:
quaternion.setFromAxisAngle( axis, angle );
相机围绕对象旋转的四元数乘以单独计算的沿Z轴旋转的四元数,现在相机可以沿所有三个轴旋转。
ZoomCamera方法
我将给出整个代码,它很小:
this.zoomCamera = function () { var factor; if ( _state === STATE.TOUCH_ZOOM_PAN ) {
所有更改归结为在多个位置添加allSpeedsFactor来控制速度(运动,旋转)和缩放。
PanCamera方法
我还将给出其完整代码,如下 它很小:
this.panCamera = ( function () { var mouseChange = new THREE.Vector2(), objectUp = new THREE.Vector3(), rv = new THREE.Vector3() pan = new THREE.Vector3(); return function panCamera() { mouseChange.copy( _panEnd ).sub( _panStart ); mouseChange.setLength(mouseChange.length() * _this.allSpeedsFactor); if ( mouseChange.lengthSq() || _this.RVMovingFactor ) { mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); pan.add( rv.copy( _eye ).setLength(_this.RVMovingFactor * _eye.length()) ); _this.object.position.add( pan ); _this.target.add( pan ); if ( _this.staticMoving ) { _panStart.copy( _panEnd ); } else { _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor * _this.allSpeedsFactor ) ); } } }; }() );
但是还有更多变化。 我会按顺序告诉您所有事情。
添加变量rv-用于处理摄像机径向速度的矢量,即 沿z轴的速度。
在相同的allSpeedsFactor上添加了mouseChange矢量的缩放比例,但是再次在这里,我想使用它将所有变换调整为单个变换,因此将其添加为处理所有运动。
现在,不仅需要移动鼠标,还可以检查是否需要处理,如果RVMovingFactor不为零,则该检查也可以通过-该变量负责摄像机沿视线的移动。
最后,在方法代码中,将pan和rv向量相加,以获取相机沿所有三个轴的完整运动,并将其应用于相机和controls.target。
更新方式
为了消除一些不必要的影响,在该方法的最后添加了以下几行:
_this.RVMovingFactor = 0.0; _this.rotationZFactor = 0.0;
您可能会猜到,不良影响与沿Z轴的旋转和移动有关。
按键方法
我在故事开始时显示的源代码已被完全注释。 除了他之外,该方法的主体几乎没有任何内容,因此可以说它是从头开始重写的。
function keydown( event ) { if ( _this.enabled === false ) return; switch (event.keyCode) {
所以在这里做了什么:
许多按钮模拟鼠标移动所做的更改。
“ A”,“ D”-重复左右旋转摄像机。
“ Z”,“ X”-复制相机向上和向下旋转。
“ +”,“-”-用鼠标滚轮复制缩放。
箭头按钮可复制相机上下左右移动,而不会更改变焦和旋转角度。
并添加了标准鼠标按钮还不够的功能:
“ W”,“ S”-沿视线移动而不更改缩放比例。
“ Q”,“ E”-相机绕Z轴旋转,即 视线周围。
“ R”,“ Shift”-分别降低所有运动,转弯,变换的速度3倍和20倍。
同时,您会注意到拒绝使用keys数组。 就我的目的而言,这不是必需的,老实说,要弄乱我个人不需要的东西,我太懒了。
键入方法
我将全部引用它:
function keyup( event ) { if ( _this.enabled === false ) return; switch (event.keyCode) {
您可能会猜到,所有要做的就是将变量RVMovingFactor,rotationZFactor,allSpeedsFactor的值恢复为其默认值。
另外,还评论了击键处理程序的安装-因为在keydown方法中将其删除。
最后,为了不使修改后的库与标准库混淆,我们更改名称。
重命名构造函数:
THREE.AstroControls = function ( object, domElement ) {
现在是AstroControls,而不是TrackballControls。
在文件末尾,我们写:
THREE.AstroControls.prototype = Object.create( THREE.EventDispatcher.prototype ); THREE.AstroControls.prototype.constructor = THREE.AstroControls;
一切准备就绪,可以使用新控件。
他所有的代码都被破坏了 THREE.AstroControls = function ( object, domElement ) { var _this = this; var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; this.object = object; this.domElement = ( domElement !== undefined ) ? domElement : document; this.rotationZFactor = 0.0;
申请书
将完整的代码保存到文件中,将其命名为AstroControls.js,添加到您的项目中。
创建这样的控件:
var controls; ...
将来,所有事情都是标准的,像往常一样设置controls.target和controls.autoRotate。 ( ,
three.js , OrbitControls . AstroControls ) , , , touch-events.
, - .