【FirstPersonControls】2021-r126版three.js中FirstPersonControls类的源码学习

【FirstPersonControls】2021-r126版three.js中FirstPersonControls类的源码学习

1、构造函数

看下构造函数,传入参数有两个,第一个代表该控制器控制的相机,第二个代表渲染到的目标canvas元素。(源码里不止这点属性,先贴这一小块后面再分析属性,这里主要为了看一下console.warn,从这里可以看到,现在第一人称控制器强制要求传入canvas元素了!因此后面事件函数里判断未传入的代码暂时不用看了)

FirstPersonControls( object : Camera, domElement : HTMLDOMElement ){
	if ( domElement === undefined ) {
				console.warn( 'THREE.FirstPersonControls: The second parameter "domElement" is now mandatory.' );
				domElement = document;
				
	}
	this.object = object;
	this.domElement = domElement;
}

2、属性

2.1 布尔类型

属性名含义
.activeLook : Boolean是否能够环视四周。默认为true。
.autoForward : Boolean摄像机是否自动向前移动。默认为false。
.constrainVertical : Boolean垂直环视是否约束在[.verticalMin, .verticalMax]之间。默认值为false。
.enabled : Boolean是否启用控制器。默认为true。
.heightSpeed : Boolean摄像机的高度是否影响向前移动的速度。默认值为false。 使用属性 .heightCoef、 .heightMin 和 .heightMax 来进行配置。
.lookVertical : Boolean是否能够垂直环视。默认为true。
.mouseDragOn : Boolean鼠标是否被按下。只读属性。

2.2 浮点型

属性名含义
.heightCoef : Number确定当相机的y分量接近.heightMax时相机移动的速度。 默认值为1。
.heightMax : Number用于移动速度调整的相机高度上限。 默认值为1。
.heightMin : Number用于移动速度调整的相机高度下限。 默认值为0。
.lookSpeed : Number环视速度。默认为0.005。
.movementSpeed : Number移动速度。默认为1。
.verticalMax : Number你能够垂直环视角度的上限。范围在 0 到 Math.PI 弧度之间。默认为Math.PI。
.verticalMin : Number你能够垂直环视角度的下限。范围在 0 到 Math.PI 弧度之间。默认为0。

2.3 其他

属性名含义
.object : Camera被控制的摄像机。
.domElement : HTMLDOMElement该 HTMLDOMElement 用于监听鼠标/触摸事件,该属性必须在构造函数中传入。在此处改变它将不会设置新的事件监听。

2.4 内部与私有属性

3、方法

先看鼠标在canvas上移动的事件,看下源码,其中mouseX和mouseY是控制器储存的属性,用来记录鼠标在此canvas内的坐标,先只看else里的内容(if为没传入canvas元素的情况),event.pageX是鼠标基于页面的坐标,鼠标事件内x朝右是正,y朝下是正,减去的this.domElement.offsetLeft和offsetTop是canvas元素基于浏览器边缘的偏移量,此意为求出鼠标在canvas内的坐标,然后减去的this.viewHalfX是canvas元素宽度的一半,viewHalfY是高度的一半。

上面所述运算的用意是,将canvas正中心定为原点(0,0),x向右为正,y向下为正。
该事件函数仅用于修改控制器储存的鼠标位置。

this.onMouseMove = function ( event ) {
		if ( this.domElement === document ) {
			this.mouseX = event.pageX - this.viewHalfX;
			this.mouseY = event.pageY - this.viewHalfY;		
		} else {
		this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX;
		this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY;	
	}
};	

接下来看下update的源码,因为这个函数很长,而且判断了很多属性的状态并作出了调整,因此我们可以根据“if”来拆开一点点看。

首先update 是返回的那个update( delta )函数的句柄,因此我们这里主要看update( delta )。(这种写法应该是可以提高安全性吧)

其中delta是传入的一段时间,单位为秒,这个时间可以用clock类来获取,【Clock】2021-r126版three.js中Clock类的源码学习,其余分析写在了代码注释里。

补充:第一小段是计算前进的增加量,也就是在高度影响下,前进是否要加速,计算这个加速的值。

this.update = function () {	
	var targetPosition = new THREE.Vector3();	
	return function update( delta ) {
		//如果该控制器没有启用(enabled为false),则无法作出更新,直接返回。
		if ( this.enabled === false ) return;
		//下面这个if-else是根据【高度是否影响前进速度】来分别作出不同的计算,计算主要是为了更新autoSpeedFactor属性,这是一个内部属性,在文档里没有,但在源码里能看到。
		if ( this.heightSpeed ) {
			//this.heightSpeed:摄像机的高度是否影响前进速度,如果影响的话进入该部分
			//MathUtils里有一些数学工具,例如角度弧度转换等,其中clamp是判断值是否在min和max之间,
			//小于min则返回min,大于max则返回max,否则返回原值
			//heightMin和max是用于调整速度的,不是说相机就只能在这里运动
			var y = THREE.MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax );
			//摄像机坐标越高,heightDelta 越大,但是也有上限,也就是摄像机高度超过heightMax 或低于heightMin时,heightDelta 这个数就不动了,也可以理解为运行速度只当摄像机高度在heightMax和heightMin之间时有变化。
			var heightDelta = y - this.heightMin;
			//delta 是传入的一段时间,在官方给的第一人称控制器的demo里,是传入上一次调用该函数到这一次调用之间的时间差(单位是秒)
			//autoSpeedFactor 值是,上次到这次调用该函数的时间差,乘以摄像机与heightMin的高度差,再乘以速度影响因子的值,其中乘以delta是因为移动的距离是基于时间计算的。
			//可以这样思考autoSpeedFactor值对平移的加成,movementSpeed是正常移动速度的属性,它是1代表摄像机每秒移动速度是1个单位,而当摄像机高度会影响移动速度时,每秒实际移动单位是movementSpeed加上heightDelta * heightCoef,其中heightCoef可以理解为权重因子。
			//因此,调整heightMax的值,可以让不同高度的摄像机(即不同高度的人类/模型)以不同的速度在地上走路。且超过heightMax的人物,走路速度不会无限增长。
			this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
		} else {
			//当高度不影响前进速度时,autoSpeedFactor为0,即每秒移动距离完全由movementSpeed决定。
			this.autoSpeedFactor = 0.0;
		}

再看下朝6个方向运动的判断,这六个属性也是内部的,不在文档里,这些内部属性主要是用于函数内部做判断的,如果暴露给大家可以随意修改,那可能会造成不太好的控制效果,此外,它们都是布尔类型的,主要用于判断摄像机往哪里移动。(其实在代码层面讲,还是可以改的,只是在设定上它们是内部的)。

详细解析写在代码注释里了。

补充:下面这一小段是在上面那段计算完前进加速值的情况下,根据按下的键进行位移。

//actualMoveSpeed是移动速度,也就是一次update移动几个单位,
//这里是用delta与movementSpeed的乘积,movementSpeed是移动
//速度(默认1像素),delta是上一次调用update到这一次调用的时
//间差,比如相差3秒,那这次位置就要更新3*移动速度,也就是说,如
//果每1s调用一次update,且在这期间一直按着前进键,那么每秒往前
//移动1像素。因此,actualMoveSpeed是基于时间决定移动距离的,
//即使这会导致屏幕刷新不及时而出现瞬移,但至少移动的距离是根据手
//按住键盘或者鼠标的时长来正确计算的。
var actualMoveSpeed = delta * this.movementSpeed;
//autoForward是是否自动前移,threejs的第一人称控制器只给了自动前行这一个属性,你也可以自己加点别的。
//这个判断的意思是,如果开启了前行,或者自动前行(且并不往后运动),则摄像机对象沿着Z轴负方向(垂直屏幕向外是正)移动,移动的距离是计算出的actualMoveSpeed加上autoSpeedFactor,后者是高度影响下的加速操作。【强调下,这里变换都是基于模型坐标轴的】
if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) );
//如果在按住向前键的同时,也按住了向后运动的键,则模型会在上面那个if的基础上,再向后(模型的z轴正向)移动,注意!只有前进才有前进加成(autoSpeedFactor ),所以在开启高度影响速度时,同时按住前后键,还是会前进。但同时按住左右键,会静止不动。
if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed );
//左移右移也是一样的。		 
if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed );
if ( this.moveRight ) this.object.translateX( actualMoveSpeed );
//上升下降也一样。	
if ( this.moveUp ) this.object.translateY( actualMoveSpeed );
if ( this.moveDown ) this.object.translateY( - actualMoveSpeed );

接下来一小节是旋转变换,lookSpeed是环视速度(包括水平和垂直环视),乘以delta的原因跟上面是一样的,actualLookSpeed是计算下基于时间的实际旋转弧度,此外,lookSpeed默认的弧度0.005大约是角度的0.3度。并且,当无法旋转(即环视)时(activeLook为false),实际旋转弧度改为0。

var actualLookSpeed = delta * this.lookSpeed;
	if ( ! this.activeLook ) {
		actualLookSpeed = 0;
	}

constrainVertical是是否限制垂直环视的范围(也就是俯仰),如果限制,那么就计算一个比例(用pi除因为范围最大就是pi)。

var verticalLookRatio = 1;
	if ( this.constrainVertical ) {
		verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin );
	}
//lon是经度,lat是维度,这俩是私有变量	
//横向环视lon的角度不仅与速度有关,还与鼠标此时所在的x点有关,x的绝对值越大,环视速度越快。(这里-=意为,往左看是正,还有下面的-=意为往上看是正)
	lon -= this.mouseX * actualLookSpeed;
	//控制器默认只有垂直环视的开关,所以这里要判断下是否垂直环视,此外,注意if不加花括号的时候只会作用到最近一行。
	if ( this.lookVertical ) 
		//每次update俯仰的速度,不仅跟y值,时间,环视速度有关,还与verticalLookRatio有关,也就是环视限定的范围越小,环视速度越快。
		lat -= this.mouseY * actualLookSpeed * verticalLookRatio;
	//这里是默认的垂直约束,也就是即使你设定的范围超过这个值,也要被拉回这个范围内。
	lat = Math.max( - 85, Math.min( 85, lat ) );
	//degToRad是角度转弧度,加90是为了把-90到90的范围变成0-180,lat要变成负的,大体可以这样理解,lat是-80差不多是朝着脚底下看,加上90的话是10,但实际天空是0,脚底是180度,所以看向脚底应该是170度,而负的-80再加上90,刚好170度。
	var phi = THREE.MathUtils.degToRad( 90 - lat );
	var theta = THREE.MathUtils.degToRad( lon );
	if ( this.constrainVertical ) {
	//mapLinear是[0,Math.PI]范围内的phi到范围[this.verticalMin,this.verticalMax]的线性映射,就像[0,2]内的1在[3,5]内的线性映射是4。
		phi = THREE.MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax );
	}

上面那一块主要是做了一些环视与俯仰角度的计算,但还没有开始旋转变换,下面这一小块是做旋转变换。其中targetPosition是在最顶上定义的一个三维向量,setFromSphericalCoords是基于球坐标系定义点的坐标值,add是加法运算,可以看【Vector3】2021-r126版three.js中Vector3类的源码学习了解更多,这里只要知道是为了计算看向的方向即可,其中传入的第一个参数1是球坐标系的半径,因为我们只是想要一个方向,因此这个值传1即可,lookAt是摄像机看向一个点的方法。

var position = this.object.position;
targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position );	
this.object.lookAt( targetPosition );

以上就是update更新的全部内容,总的来说,如果在动画里加上对该函数的调用,则会不断的根据鼠标当前所在的位置更新摄像机的坐标与朝向,这里就出现了一个大问题,位移是按照键盘是否按下来决定的,但旋转是一直在计算的!除非鼠标正好在0,0上,否则都会不停改变视角(除了垂直环顾有范围),那么这里就希望,鼠标不动的时候,不要环顾。

【待续】

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值