three.js 尤拉角和四元數

Vadim發表於2020-07-26

這篇郭先生就來說說尤拉角和四元數,尤拉角和四元數的優缺點是老生常談的話題了,使用條件我就不多說了,我只說一下使用方法。

1. 尤拉角(Euler)

尤拉角描述一個旋轉變換,通過指定軸順序和其各個軸向上的指定旋轉角度來旋轉一個物體。下面我們開看看它的方法

1. set( x: number, y: number, z: number, order?: string ): Euler

x - 用弧度表示x軸旋轉量。y - 用弧度表示y軸旋轉量。z - 用弧度表示z軸旋轉量。order - (optional) 表示旋轉順序的字串。設定該尤拉變換的角度和旋轉順序 order。

2. clone(): this

返回一個與當前引數相同的新尤拉角。

3. copy( euler: Euler ): this

將 euler 的屬性拷貝到當前物件。

4. setFromRotationMatrix( m: Matrix4, order?: string ): Euler

m - Matrix4 矩陣上面的3x3部分是一個純旋轉矩陣rotation matrix (也就是不發生縮放)order - (可選引數) 表示旋轉順序的字串。使用基於 order 順序的純旋轉矩陣來設定當前尤拉角。

var vector = new THREE.Vector3(0,0,1);
var matrix = new THREE.Matrix4().makeRotationAxis(vector, Math.PI/6)
var euler = new THREE.Euler().setFromRotationMatrix(matrix); // 返回Euler {_x: -0, _y: 0, _z: 0.5235987755982987, _order: "XYZ"}

5. setFromQuaternion( q: Quaternion, order?: string ): Euler

根據 order 指定的方向,使用歸一化四元數設定這個尤拉變換的角度。

var vector = new THREE.Vector3(0,0,1);
var quaternion = new THREE.Quaternion().setFromAxisAngle(vector, Math.PI/6)
var euler = new THREE.Euler().setFromQuaternion(quaternion);// 返回Euler {_x: -0, _y: 0, _z: 0.5235987755982987, _order: "XYZ"}結果同上

6. setFromVector3( v: Vector3, order?: string ): Euler

設定 x, y and z 並且選擇性更新 order。

var vector = new THREE.Vector3(0,0,Math.PI/6);
var euler = new THREE.Euler().setFromVector3(vector);/ 返回Euler {_x: -0, _y: 0, _z: 0.5235987755982987, _order: "XYZ"}結果同上

7. reorder( newOrder: string ): Euler

通過這個尤拉角建立一個四元數,然後用這個四元數和新順序設定這個尤拉角。

8. equals( euler: Euler ): boolean

檢查 euler 是否與當前物件相同。

9. fromArray( xyzo: any[] ): Euler

長度為3或4的一個 array 。array[3] 是一個可選的 order 引數。將尤拉角的x分量設定為 array[0]。將尤拉角的x分量設定為 array[1]。將尤拉角的x分量設定為 array[2]。將array[3]設定給尤拉角的 order 。可選。

10. toArray( array?: number[], offset?: number ): number[]

返回一個陣列:[x, y, z, order ]。

11. toVector3( optionalResult?: Vector3 ): Vector3

以 Vector3 的形式返回尤拉角的 x, y 和 z。

var vector = new THREE.Vector3(0,0,Math.PI/6);
var euler = new THREE.Euler().setFromVector3(vector);
euler.toVector3(); //返回Vector3 {x: 0, y: 0, z: 0.5235987755982988}

2. 四元數

四元數物件Quaternion使用x、y、z和w四個分量表示。在三維空間中一個旋轉由一個旋轉軸、一個旋轉角度和旋轉方向來唯一確定。

假設我們預設為右手法則的旋轉,則旋轉方向為逆時針,旋轉軸向量為v = (vx, vy, vz), 角度為旋轉角度,那麼該旋轉就應該類似如下圖所示:

其對應的四元數就是:

1. set( x: number, y: number, z: number, w: number ): Quaternion

設定該四元數的值。

2. clone(): this

克隆此四元數。

3. copy( q: Quaternion ): this

將q的值複製到這個四元數。

4. setFromEuler( euler: Euler ): Quaternion

用尤拉角指定的旋轉來設定此四元數。

var euler = new THREE.Euler(0,0,Math.PI/6);
var quaternion = new THREE.Quaternion().setFromEuler(euler) //返回Quaternion {_x: 0, _y: 0, _z: 0.25881904510252074, _w: 0.9659258262890683}

5. setFromAxisAngle( axis: Vector3, angle: number ): Quaternion

使用由軸和角度指定的旋轉來設定此四元數。axis 應該是歸一化的,angle 的單位是弧度。

var vector1 = new THREE.Vector3(0,0,1);
var vector2 = new THREE.Vector3(0,0,2);
var quaternion1 = new THREE.Quaternion().setFromAxisAngle(vector1, Math.PI/6); //返回Quaternion {_x: 0, _y: 0, _z: 0.25881904510252074, _w: 0.9659258262890683}
var quaternion2 = new THREE.Quaternion().setFromAxisAngle(vector2, Math.PI/6); //返回Quaternion {_x: 0, _y: 0, _z: 0.5176380902050415, _w: 0.9659258262890683}

可見axis是否歸一化對四元數的x、y和z值的影響是線性的。

6. setFromRotationMatrix( m: Matrix4 ): Quaternion

從m的旋轉分量來設定該四元數。使用很簡單就不多說了。

7. setFromUnitVectors( vFrom: Vector3, vTo: Vector3 ): Quaternion

通過從向量vFrom到vTo所需的旋轉來設定這四元數。vFrom 和 vTo 應該是歸一化的。我們來看一下

var vector1 = new THREE.Vector3(1,1,0);
var vector2 = new THREE.Vector3(0,1,0);
var quaternion = new THREE.Quaternion().setFromUnitVectors(vector1, vector2); //相當於繞z軸旋轉了Math.PI/4

8. angleTo( q: Quaternion ): number

返回這個四元數到q的角度

var quaternion1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,Math.PI/3));
var quaternion2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,Math.PI/6));
quaternion1.angleTo(quaternion2); // 返回0.5235987755982987

9. rotateTowards( q: Quaternion, step: number ): Quaternion

將此四元數按給定的step旋轉到定義的四元數q。該方法確保最終四元數不會超出q。那麼是什麼意思呢?

var quaternion1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,Math.PI/3)); //{_x: 0, _y: 0, _z: 0.49999999999999994, _w: 0.8660254037844387}
var quaternion2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,Math.PI/6)); //{_x: 0, _y: 0, _z: 0.25881904510252074, _w: 0.9659258262890683}
quaternion1.rotateTowards( quaternion2, 0); //{_x: 0, _y: 0, _z: 0.49999999999999994, _w: 0.8660254037844387}
quaternion1.rotateTowards( quaternion2, 0.5); //{_x: 0, _y: 0, _z: 0.2701980971440553, _w: 0.9628047508709812}
quaternion1.rotateTowards( quaternion2, 1); //{_x: 0, _y: 0, _z: 0.25881904510252074, _w: 0.9659258262890683}

可以看出其內部使用了quaternion.slerp()方法。當step為0時,rotateTowards方法返回就是當前四元數。當step為1時,rotateTowards方法返回就是引數q的四元數。當step為0~1之間時,rotateTowards方法返回就是當前四元數和引數q的四元數之間的插值。

10. inverse(): Quaternion

轉置此四元數-計算共軛。假設四元數具有單位長度。

var quaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(Math.PI/6,Math.PI/6,Math.PI/6)); //初始四元數Quaternion {_x: 0.30618621784789724, _y: 0.17677669529663687, _z: 0.30618621784789724, _w: 0.8838834764831845}
quaternion.inverse(); //返回Quaternion {_x: -0.30618621784789724, _y: -0.17677669529663687, _z: -0.30618621784789724, _w: 0.8838834764831845}

由此可知計算共軛之後,x、y和z分別取複製,而w值不變。

11. conjugate(): Quaternion

返回此四元數的旋轉共軛。四元數的共軛。表示旋轉軸在相反方向上的同一個旋轉。經過我的測試這個方法和inverse()方法是一樣的,來看看inverse的原始碼

inverse: function () {
        // quaternion is assumed to have unit length
        return this.conjugate();
},

12. dot( v: Quaternion ): number

計算四元數v和當前四元數的點積。眾所周知點積得到的是一個數字。很簡單

13. lengthSq(): number

計算四元數的平方長度。就是各個值平方求和。

14 length(): number

計算此四元數的長度。也就是各個值平方求和,然後在開根號。

15. normalize(): Quaternion

歸一化該四元數。開看下原始碼

normalize: function () {
        var l = this.length();
        if ( l === 0 ) { //如果四元數參length為0,那麼this._x、this._y和this._z都設定為0,this._w設定為1
            this._x = 0;
            this._y = 0;
            this._z = 0;
            this._w = 1;
        } else { //如果四元數參length為l,那麼四元數的各個引數乘以l的倒數。
            l = 1 / l;
            this._x = this._x * l;
            this._y = this._y * l;
            this._z = this._z * l;
            this._w = this._w * l;
        }
        return this;
    },

16. multiply( q: Quaternion ): Quaternion

把該四元數和q相乘。具體怎麼相乘。稍後再說。

17. premultiply( q: Quaternion ): Quaternion;

使用q左乘以(pre-multiply)該四元數。同樣稍後再說。

18. multiplyQuaternions( a: Quaternion, b: Quaternion ): Quaternion

四元數a乘以四元數b,我們說一下四元數的乘法。

multiplyQuaternions: function ( a, b ) {
        var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
        var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
        this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
        this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
        this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
        this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
        return this;
},

19. equals( v: Quaternion ): boolean;

比較v和這個四元數的各個分量,以確定兩者是否代表同樣的旋轉。不多說。

20. slerp( qb: Quaternion, t: number ): Quaternion

處理四元數之間的球面線性插值。t 代表quaternionA(這裡t為0)和quaternionB(這裡t為1)這兩個四元數之間的旋轉量。quaternion 被設定為結果。rotateTowards的底層同樣使用了slerp方法。

var quaternion1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,Math.PI/6));
var quaternion2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,Math.PI/2));
quaternion1; //quaternion1的值為{_x: 0, _y: 0, _z: 0.25881904510252074, _w: 0.9659258262890683}
quaternion2; //quaternion2的值為{_x: 0, _y: 0, _z: 0.7071067811865475, _w: 0.7071067811865476}
quaternion1.slerp(quaternion2, 0) //返回的結果和quaternion1相同
quaternion1.slerp(quaternion2, 1) //返回的結果和quaternion2相同
quaternion1.slerp(quaternion2, 其他值) //返回quaternion1到quaternion2的插值,當然這個t也是可以大於1的
//看一下rotateTowards的部分原始碼
rotateTowards: function ( q, step ) {
    var angle = this.angleTo( q );
    if ( angle === 0 ) return this;
    var t = Math.min( 1, step / angle );
    this.slerp( q, t );
    return this;
}

21. static slerp: functistatic slerp(qa: Quaternion, qb: Quaternion, qm: Quaternion, t: number): Quaternionon

這是slerp的靜態方法,無需動態設定。同樣使用了slerp方法。

slerp: function ( qa, qb, qm, t ) {
    return qm.copy( qa ).slerp( qb, t );
}

關於尤拉角四元數要說的差不多就這些,還需要平時多多應用才能記熟。

 

轉載請註明地址:郭先生的部落格

 

相關文章