Quaternion.js
单位四元数(Unit quaternion)可以用于表示三维空间里的旋转[1]。它与常用的另外两种表示方式(三维正交矩阵和欧拉角)是等价的,但是避免了欧拉角表示法中的万向锁问题。比起三维正交矩阵表示,四元数表示能够更方便地给出旋转的转轴与旋转角。 ——维基百科(https://zh.wikipedia.org/wiki/%E5%9B%9B%E5%85%83%E6%95%B0%E4%B8%8E%E7%A9%BA%E9%97%B4%E6%97%8B%E8%BD%AC)
四元数的维基百科:https://zh.wikipedia.org/wiki/%E5%9B%9B%E5%85%83%E6%95%B8
三个虚数单位:i, j, k. ,张成四维空间。
算是对虚数对一种拓展,虚数是有一个虚数单位:i,,张成二维空间。
表示旋转的话,分别用i,j,k表示绕x,y,z轴旋转,比较优雅。
setFromEuler( euler, update )
欧拉角转四元数,这个好像比较复杂,Three里是直接用了Matlab里的某个实现,具体的推导需要有时间看看。
setFromAxisAngle( axis, angle )
从轴和角度转化为四元数,这个还是可以看一下的,可以了解下四元数最直接的是怎么表示旋转的。
var halfAngle = angle / 2, s = Math.sin( halfAngle );this._x = axis.x * s;this._y = axis.y * s;this._z = axis.z * s;this._w = Math.cos( halfAngle );
这其实算是四元数表示旋转的一种具体定义吧:
其中a是旋转的角度。
具体一些四元数的知识,可以参考Three源码中给的链接:http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm
至于为啥是sin(a/2), cos(a/2),网上查了不少知识,发现这应当是个比较复杂的数学问题,略微看不懂,就姑且先记下,毕竟,这个形式是非常优雅的,数学的确是一个奇妙的东西!
setFromRotationMatrix( m )
又是关于四种旋转方式的转变,突然发现,Three中好多关于数学的知识,都参考了http://www.euclideanspace.com/ 上的知识,这个网站值得好好看看。
其中,有总结四种旋转表示方法的相互转换:http://www.euclideanspace.com/maths/geometry/rotations/conversions/index.htm
setFromUnitVectors( vFrom, vTo )
两个法向量也可以用来表示旋转,这么看,应该算是有五种旋转的表达方式了。
这个的实现还是有点小trick:https://stackoverflow.com/questions/1171849/finding-quaternion-representing-the-rotation-from-one-vector-to-another
一般情况下,直接叉乘两个向量,即得到了x,y,z分量,当然源码中没有调用叉乘的函数,而是直接算的:
this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;this._w = r;
这里的r,本意是,因为是单位向量,第一项是1。
但是,如果两个向量方向平行(相同或相反),叉乘会得到0向量,就不对了,因此需要特殊考虑。
这时候,其实两个向量平行,保证垂直于其中一个向量就OK了,代码里的实现也比较直接:
if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {// Q> 不用归一化?this._x = - vFrom.y;this._y = vFrom.x;this._z = 0;this._w = r;} else {this._x = 0;this._y = - vFrom.z;this._z = vFrom.y;this._w = r;}
不过这里有个小疑问,这个四元数的x,y,z分量不用做归一化么?
angleTo(q)
计算两个四元数之间的夹角,这算是一种定义吧。这里有个是stackexchange上的回答:https://math.stackexchange.com/questions/90081/quaternion-distance
inverse() conjugate()
两个的作用都是翻转一下,反方向转。其实就是把轴反一下就可以了。
multiplyQuaternions( a, b )
四元数的乘法,并不是简单的对应相乘,更像是一种矩阵相乘,这里说了两者的相似之处:http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/geometric/orthogonal/index.htm
啊,数学真是个奇妙的东西!!!
slerp( qb, t )
做基于四元数的旋转的插值。这个讲得的确很好了:http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
噗,找到了好的资料就假装自己看了并假装自己会了呵呵呵
static method
在 Quaternion 中还有两个static的method,这里,其static method是这样实现的:
Object.assign( Quaternion, {slerp: function ( qa, qb, qm, t ) {...}slerpFlat: ...}
也算是一个很好的解决方案,当然,在ES6中,有 static 的关键字,就很棒了
其实就实现来看,static的method相当于可以把插值的结果直接赋值把:
slerp: function ( qa, qb, qm, t ) {return qm.copy( qa ).slerp( qb, t );}
而对于 slerpFlat 的话,就是直接操作数组,没有什么特别的实现,可能会减少后续的一些实现时反复取元素的麻烦和开销吧。
Euler.js
欧拉角的介绍:https://www.matongxue.com/madocs/442.html
其实,欧拉角是相对于四元数以及旋转矩阵而言,比较直观和易于理解的一种旋转表示方式。
旋转步骤如下:
- 物体绕全局的
轴旋转
角
- 继续绕自己的
轴(也就是图中的
轴)旋转
角
- 最后绕自己的
轴旋转
角
关于万向轴死锁的问题,上边那篇博客里讲得很好。
接下来看看Three.js中的欧拉角实现。
RotationOrders
Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];Euler.DefaultOrder = 'XYZ';
欧拉角的旋转结果与顺序相关,绕3个轴旋转有6种顺序。
setFromQuaternion( q, order, update )
_matrix.makeRotationFromQuaternion( q );return this.setFromRotationMatrix( _matrix, order, update );
四元数到欧拉角的转换。其实是用旋转矩阵过渡,先生成旋转矩阵,再转换为欧拉角。
不知道这里有没有更高效的转换方式,总感觉会有点效率低。
setFromRotationMatrix( m, order, update )
首先说明这里的 update 参数,bool类型,用来设定是否调用更新的回调函数,但目前的源码中没有涉及到这个回调函数的用处,因此暂时不谈。
旋转矩阵转欧拉角并不惟一,而且如果欧拉角的方向不同,计算方式也不同。这里关于具体的计算公式找了很久的资料,也没有一个描述很清晰的资料。姑且就先记下吧,具体的推倒暂且不管了。
