Unity中的四元數類詳解【詳解】

一、簡介

Quaternion又稱四元數,由x,y,z和w這四個分量組成,是由愛爾蘭數學家威廉·盧雲·哈密頓在1843年發現的數學概念。四元數的乘法不符合交換律。從明確地角度而言,四元數是複數的不可交換延伸。如把四元數的集合考慮成多維實數空間的話,四元數就代表着一個四維空間,相對於複數爲二維空間。

四元數

關於四元數的性質、與旋轉的關係、球型線性插值的介紹,請閱讀3D遊戲與計算機圖形學中的數學方法-四元數,在此不多做介紹。下面主要介紹的是Unity中的四元數-Quaternion。

在Unity中,用Quaternion來存儲和表示對象的旋轉角度。Quaternion的變換比較複雜,對於GameObject一般的旋轉及移動,可以用Transform中的相關方法實現。

二、Quaternion類屬性

eulerAngles-歐拉角

定義

public Vector3 eulerAngles{get;set;}

如何改變一個遊戲對象旋的轉狀態,我們可以通過改變其Transform進行歐拉角的變換次序,例如假設p(x,y,z)是遊戲對象上的一個點,繞x軸旋轉a角,繞y軸旋轉b角,繞z軸旋轉c角,這樣就可以得到旋轉之後的狀態p'(x',y',z')。Unity的實現過程是很簡單的,一句代碼就可以搞定。但是具體的實現過程確實很複雜的,詳情請閱讀3D遊戲與計算機圖形學中的數學方法-變換

下面給出一個例子,演示一下如何使用歐拉角。

 

using UnityEngine;
using System.Collections;

public class EulerAngler_ts : MonoBehaviour {
    public Transform A, B;
    Quaternion rotations = Quaternion.identity;
    Vector3 eulerAngle = Vector3.zero;
    float speed = 10.0f;
    float tSpeed = 0.0f;
    // Use this for initialization
    void Start () {

    }
    
    // Update is called once per frame
    void Update () {
        tSpeed += speed * Time.deltaTime;
        //第一種方式:將Quaternion實例對象賦值給transform的rotation
        rotations.eulerAngles = new Vector3(0.0f, tSpeed, 0.0f);
        A.rotation = rotations;
        //第二種方式:將三位向量代表的歐拉角直接賦值給transform的eulerAngle
        B.eulerAngles = new Vector3(0.0f, tSpeed, 0.0f);
    }
}

 

三、Quaternion類實例方法

1、SetFromToRotation方法-創建rotation實例

1.1 函數原型

public void SetFromToRotion(Vector3 fromDirection,Vector3 toDirection);

可以創建一個從formDirection到toDirection的Quaternion實例。

Quaternion q = Quaternion.identity;
q.SetFromToRotation(v1,v2);
transform.rotation = q;

可以將GameObject對象進行如下變換:首先將GameObject對象自身座標系的x,y,z軸方向和世界座標系的x,y,z軸方向一致,然後將GameObject對象自身座標系中向量V1指向的方向旋轉到V2方向。

1.2 PS:不可以直接使用transform.rotation.SetFromToRotation(v1,v2)方式進行設置,只能將實例化的Quaternion複製給transform.rotation。

1.3 實例演示

 

using UnityEngine;
using System.Collections;

public class SetFromToDirection_ts : MonoBehaviour {
    public Transform A, B, C;
    Quaternion q = Quaternion.identity;
    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {

        q.SetFromToRotation(A.position, B.position);
        C.rotation = q;
        Debug.DrawLine(Vector3.zero, A.position, Color.red);
        Debug.DrawLine(Vector3.zero, B.position, Color.green);
        Debug.DrawLine(C.position, C.position + new Vector3(0.0f, 1.0f, 0.0f), Color.black);
        Debug.DrawLine(C.position, C.TransformPoint(Vector3.up * 1.5f), Color.yellow);
    }
}

 

運行結果如下圖所示:

2、SetLookRotation方法-設置Quaternion實例的朝向

2.1 函數原型

public void SetLookRotation(Vector3 view);
public void SetLookRotation(Vector3 view,Vector3 up);

例如:

Quaternion q = Quaternion.identity;
q.SetLookRotation(v1,v2);
transform.rotation = q;

transform.forward方向與V1方向相同。

transform.right垂直於由Vector3.zer0、V1和V2這3點構成的平面。

V2決定了transform.up的朝向,因爲當transform.forward和transform.right方向確定後,transform.up的方向總會與V2的方向的夾角小於或等於90度。

當V1爲Vector3.zero時,方法失效。

2.2 PS:同上,不要直接使用transform.rotation.SetLookRotation(v1,v2)的方式來實例化Quaternion對象。

2.3 實例演示

 

using UnityEngine;
using System.Collections;

public class SetLookRotation_ts : MonoBehaviour {

    public Transform A, B, C;
    Quaternion q = Quaternion.identity;
    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

        q.SetLookRotation(A.position, B.position);
        C.rotation = q;
        Debug.DrawLine(Vector3.zero, A.position, Color.red);
        Debug.DrawLine(Vector3.zero, B.position, Color.green);
        Debug.DrawLine(C.position, C.TransformPoint(Vector3.right * 1.5f), Color.black);
        Debug.DrawLine(C.position, C.TransformPoint(Vector3.forward * 1.5f), Color.yellow);

        Debug.Log("C.right與A的夾角: " + Vector3.Angle(C.right, A.position));
        Debug.Log("C.right與B的夾角: " + Vector3.Angle(C.right, B.position));
        Debug.Log("C.up與B的夾角: " + Vector3.Angle(C.up, B.position));
    }
}

 

運行結果

    

3、ToAngleAxis方法

3.1 函數原型

public void ToAngleAxis(out float angle,out Vector3 axis);

參數angle爲旋轉角,參數axis爲軸向量。

該函數可以實現將GameObject對象的rotation從Quaternion.identity狀態變換到當前狀態,只需要將GameObject對象繞着axis軸(世界座標系)旋轉angle角度即可。

3.2 實例演示

 

using UnityEngine;
using System.Collections;

public class ToAngleAxis_ts : MonoBehaviour {
    public Transform A, B;
    float angle;
    Vector3 axis = Vector3.zero;
    float xSpeed = 0.0f, ySpeed = 0.0f, zSpeed = 0.0f;
    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
        xSpeed += 0.5f * Time.deltaTime;
        ySpeed += 1.0f * Time.deltaTime;
        zSpeed += 2.5f * Time.deltaTime;
        A.eulerAngles = new Vector3(xSpeed, ySpeed, zSpeed);
        //獲取A的rotation的旋轉軸和角度
        A.rotation.ToAngleAxis(out angle, out axis);
        //設置B的rotation,使得B的rotation和A相同
        B.rotation = Quaternion.AngleAxis(angle, axis);
    }
}

四、Quaternion類靜態方法

Quaternion中的靜態方法有9個即:Angle方法、Dot方法、Euler方法、FromToRotation方法、Inverse方法、Lerp方法、LookRotation方法、RotateToWards方法和Slerp方法。關於靜態的方法的使用就是直接用類名調用其靜態方法,例如Quaternion.Angle(q1,q2);下面對這些靜態方法做下分析。

1、Angle方法

1.1 函數原型

public static float Angle(Quaternion a,Quaternion b);

該方法可以計算兩個旋轉狀態a達到b時需要旋轉的最小夾角。

1.2 實例演示

 

using UnityEngine;
using System.Collections;

public class Angle_ts : MonoBehaviour {
   

    // Use this for initialization
    void Start () {

        Quaternion q1 = Quaternion.identity;
        Quaternion q2 = Quaternion.identity;
        q1.eulerAngles = new Vector3(30.0f, 40.0f, 50.0f);
        float a1 = Quaternion.Angle(q1, q2);
        float a2 = 0.0f;
        Vector3 v = Vector3.zero;
        q1.ToAngleAxis(out a2,out v);

        Debug.Log("a1: " + a1);
        Debug.Log("a2: " + a2);
        Debug.Log("q1的歐拉角: " + q1.eulerAngles + " q1的rotation: " + q1);
        Debug.Log("q2的歐拉角: " + q2.eulerAngles + " q2的rotation: " + q2);
    }
    
    // Update is called once per frame
    void Update () {
    
    }
}

 

運行結果

從輸出結果可以看出a1和a2的值相等,即Angle的返回值是兩個Quaternion實例轉換的最小夾角。

2、Dot方法-點乘

2.1 函數原型

public static float Dot(Quaternion a,Quaternion b);

該方法可以根據點乘的結果,判斷a和b對應歐拉角的關係。

例如有兩個Quaternion實例q1(x1,y1,z1,w1)和q2(x2,y2,z2,w2),則float f = Quaternion.Dot(q1,q2);即f = x1*x2+y1*y2+z1*z2+w1*w2,結果值f的範圍爲[-1,1]。

當f=+(-)1時,q1和q2對應的歐拉角是相等的,即旋轉狀態是一致的。特別地,當f = -1時,說明其中一個rotation比另外一個rotation多旋轉了360°。

2.2 實例演示

 

using UnityEngine;
using System.Collections;

public class Dot_ts : MonoBehaviour {
   
    public Transform A, B;
    Quaternion q1 = Quaternion.identity;
    Quaternion q2 = Quaternion.identity;
    // Use this for initialization
    void Start () {

        A.eulerAngles = new Vector3(0.0f, 40.0f, 0.0f);
        B.eulerAngles = new Vector3(0.0f, 360.0f + 40.0f, 0.0f);
        q1 = A.rotation;
        q2 = B.rotation;
        float f = Quaternion.Dot(q1, q2);
        
        Debug.Log("q1的歐拉角: " + q1.eulerAngles + " q1的rotation: " + q1);
        Debug.Log("q2的歐拉角: " + q2.eulerAngles + " q2的rotation: " + q2);
        Debug.Log("Dot(q1,q2): " + f);
    }
    
    // Update is called once per frame
    void Update () {
    
    }
}

 

運行輸出

從輸出結果可以證明q1和q2的歐拉角相等,但是rotation值卻是相反的,也說明當Dot的返回值爲-1時,兩個參數的角度相差360°。

3、Euler方法

3.1 函數原型

public static Quaternion Euler(Vector3 euler);
public static Quaternion Euler(float x,float y,float z);

該方法用於返回歐拉角Vector3(ex,ey,ez)對應的四元數Quaternion q(qx,qy,qz,qw)。其對應關係如下:

已知PIover180 = 3.141592/180 = 0.0174532925f是計算機圖形學中的一個常亮,其變換過程如下:

 

ex = ex * PIover180 / 2.0f;
ey = ey * PIover180 / 2.0f;
ez = ez * PIover180 / 2.0f;

qx = Mathf.Sin(ex) * Mathf.Cos(ey) * Mathf.Cos(ez) + Mathf.Cos(ex) * Mathf.Sin(ey) * Mathf.Sin(ez);
qy = Mathf.Cos(ex) * Mathf.Sin(ey) * Mathf.Cos(ez) - Mathf.Sin(ex) * Mathf.Cos(ey) * Mathf.Sin(ez);
qz = Mathf.Cos(ex) * Mathf.Cos(ey) * Mathf.Sin(ez) - Mathf.Sin(ex) * Mathf.Sin(ey) * Mathf.Cos(ez);
qw = Mathf.Cos(ex) * Mathf.Cos(ey) * Mathf.Cos(ez) + Mathf.Sin(ex) * Mathf.Sin(ey) * Mathf.Sin(ez);

 

3.2 驗證變換過程

 

using UnityEngine;
using System.Collections;

public class Euler_ts : MonoBehaviour {

    public float ex, ey, ez;
    float qx, qy, qz,qw;
    float PIover180 = 0.0174532925f;
    Quaternion q = Quaternion.identity;
    Vector3 euler;
  
    void OnGUI()
    {
        if(GUI.Button(new Rect(10.0f,10.0f,100.0f,45.0f),"計算"))
        {
            euler = new Vector3(ex,ey,ez);
            Debug.Log("歐拉角Euler(ex,ey,ez): " + euler);
            q = Quaternion.Euler(ex, ey, ez);
            Debug.Log("對應的四元數爲: " + q);
            Debug.Log("q.x: " + q.x + " q.y: " + q.y + " q.z: " + q.z + " q.w: " + q.w);
            //驗證算法
            ex = ex * PIover180 / 2.0f;
            ey = ey * PIover180 / 2.0f;
            ez = ez * PIover180 / 2.0f;

            qx = Mathf.Sin(ex) * Mathf.Cos(ey) * Mathf.Cos(ez) + Mathf.Cos(ex) * Mathf.Sin(ey) * Mathf.Sin(ez);
            qy = Mathf.Cos(ex) * Mathf.Sin(ey) * Mathf.Cos(ez) - Mathf.Sin(ex) * Mathf.Cos(ey) * Mathf.Sin(ez);
            qz = Mathf.Cos(ex) * Mathf.Cos(ey) * Mathf.Sin(ez) - Mathf.Sin(ex) * Mathf.Sin(ey) * Mathf.Cos(ez);
            qw = Mathf.Cos(ex) * Mathf.Cos(ey) * Mathf.Cos(ez) + Mathf.Sin(ex) * Mathf.Sin(ey) * Mathf.Sin(ez);
            Debug.Log("qx: " + qx + " qy: " + qy + " qz: " + qz + " qw: " + qw);
        }
    }
}

 

運行結果

從輸出結果可以證明該公式是正確,另外轉換後的四元數直接輸出的話,如下:

 q = Quaternion.Euler(ex, ey, ez);
 Debug.Log("對應的四元數爲: " + q);

輸出值是做了四捨五入處理的。

4、FromToRotation方法

函數原型

public static Quaternion FromToRotation(Vector3 fromDirection,Vector3 ToDirection);

在前面介紹了SetFromToRotation實例方法,它們的功能都是一樣的只不過用法稍有不同。使用FromToRotation類靜態方法,需要直接使用類名進行調用,如Quaternion.FromToRotation(v1,v2);

在此就不做演示了!

5、Inverse方法

5.1 函數原型

public static Quaternion Inverse(Quaternion rotation);

該方法可以返回參數rotation的逆向Quaternion值。

例如rotation(x,y,z,w),那麼Quaternion.Inverse(rotation) = (-x,-y,-z,w)。假設rotation的歐拉角爲(a,b,c),則transform.rotation = Quaternion.Inverse(rotation)相當於transform依次繞自身座標系的z軸、x軸和y軸分別旋轉-c°、-a°和-z°。由於是在局部座標系內的變換,最後transform的歐拉角的各個分量值並不一定等於-a、-b或-c。

5.2 實例演示

 

using UnityEngine;
using System.Collections;

public class Invers_ts : MonoBehaviour {
    public Transform A, B;

    // Use this for initialization
    void Start () {

        Quaternion q1 = Quaternion.identity;
        Quaternion q2 = Quaternion.identity;
        q1.eulerAngles = new Vector3(30.0f,40.0f,50.0f);
        q2 = Quaternion.Inverse(q1);
        A.rotation = q1;
        B.rotation = q2;

        Debug.Log("q1的歐拉角: " + q1.eulerAngles + "q1的rotation: " + q1);
        Debug.Log("q2的歐拉角: " + q2.eulerAngles + "q2的rotation: " + q2);
    }
    
    // Update is called once per frame
    void Update () {
    
    }
}
運行截圖


6、Lerp和Slerp方法

6.1 函數原型

public static Quaternion Lerp(Quaternion form, Quaternion to,float t);
public static Quaternion Slerp(Quaternion form, Quaternion to,float t);

兩種方法的作用都是返回從form到to的插值。當參數t<=0時返回值爲from,當參數t>=1時返回值爲to。其中Lerp是線性差值,而Slerp是球面插值。

6.2 實例演示

 

using UnityEngine;
using System.Collections;

public class LerpAndSlerp_ts : MonoBehaviour
{
    public Transform A, B, C,D;
    float speed = 0.2f;
    float total = 0.0f;
    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
        total += Time.deltaTime * speed;
        if(total >= 1.0f)
            total = 1.0f;
        C.rotation = Quaternion.Lerp(A.rotation, B.rotation, total);
        D.rotation = Quaternion.Lerp(A.rotation, B.rotation, total);
        //C.rotation = Quaternion.Lerp(A.rotation, B.rotation, Time.deltaTime * speed);
        //D.rotation = Quaternion.Lerp(A.rotation, B.rotation, Time.deltaTime * speed);
    }
}

 

7、RotateTowards方法

7.1 函數原型

public static Quaternion RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta);

該方法也是一個插值方法,即從返回參數from到to的插值,且返回值的最大角度不超過maxDegreesDelta。maxDegreesDelta是角度值,不是插值係數,當maxDegreesDelta < 0時,將進行逆向插值即從to到from的方向進行插值計算。

7.2 實例演示

 

using UnityEngine;
using System.Collections;

public class RotateToWards_ts : MonoBehaviour {

    public Transform A, B, C;
    float speed = 10.0f;
    float total = 0.0f;
    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        total += Time.deltaTime * speed;
        if (total >= 1.0f)
            total = 1.0f;
        C.rotation = Quaternion.RotateTowards(A.rotation, B.rotation, Time.time * speed - 40.0f);
        Debug.Log("C與A的歐拉角的插值: " + (C.eulerAngles - A.eulerAngles) + "maxDegreesDelta: " + (Time.time * speed - 40.0f));

    }
}

 

運行截圖

8、LookRotation方法

函數原型

public static Quaternion LookRotation(Vector3 forward);
public static Quaternion LookRotation(Vector3 forward,Vector3 upwards);

參數forward爲返回Quaternion實例的forward朝向。該方法和前面講到的SetLookRotation實例方法的功能是一樣的,故不多做闡述了。

五、Quaternion類運算符

Quaternion類涉及到兩個Quaternion對象相乘和Quaternion對象與Vector3對象相乘,那麼就必須重載"*"運算符。

1、函數原型

public static Quaternion operator *(Quaternion lhs, Quaternion rhs);
public static Vector3 operator *(Quaternion rotation, Vector3 point);

2、兩個Quaternion對象相乘

對於兩個Quaternion對象相乘主要用於自身旋轉變換,例如:

B.rotation *= A.rotation;
  • B會繞着B的局部座標系的z、x、y軸按照先繞z軸再繞x軸最後繞y軸的旋轉次序,分別旋轉A.eulerAngles.z度、A.eulerAngles.x度、和A.eulerAngles.y度。由於是繞着局部座標系進行旋轉,所以當繞着其中一個軸進行旋轉時,可能會影響其餘兩個座標軸方向的歐拉角(除非其餘兩軸的歐拉角都爲0纔不會受到影響)。
  • 假如A的歐拉角爲aEuler(ax,ay,az),則沿着B的初始局部座標系的aEuler方向向下看B在繞着aEuler順時針旋轉。B的旋轉狀況還受到其初始狀態的影響。

2.1 實例演示

 

using UnityEngine;
using System.Collections;

public class Multiply1_ts : MonoBehaviour {

    public Transform A, B;

    // Use this for initialization
    void Start () {

        A.eulerAngles = new Vector3(1.0f, 1.5f, 2.0f);
    }
    
    // Update is called once per frame
    void Update () {

        B.rotation *= A.rotation;
        Debug.Log(B.eulerAngles);
    }
}

 

運行截圖

2.2 分析

B繞着其自身座標系的Vector3(1.0f,1.5f,2.0f)方向旋轉。雖然每次都繞着這個軸向旋轉的角度相同,但角度的旋轉在3個座標軸的值都不爲零,三個軸的旋轉會相互影響,所以B的歐拉角的各個分量的每次遞增是不固定的。

3、Quaternion對象與Vector3對象

對於Quaternion對象與Vector3對象相乘主要用於自身移動變換,例如

transform.position += tansform.rotation * A;

其中A爲Vector3的對象。transform對應的對象會沿着自身座標系中向量A的方向移動A的模長的距離。transform.rotation與A相乘可以確定移動的方向和距離。

3.1 實例演示

 

using UnityEngine;
using System.Collections;

public class Multiply2_ts : MonoBehaviour {
    public Transform A;
    float speed = 0.1f;

    // Use this for initialization
    void Start () {

        A.position = Vector3.zero;
        A.eulerAngles = new Vector3(0.0f, 45.0f, 0.0f);
    }
    
    // Update is called once per frame
    void Update () {

        A.position += A.rotation * (Vector3.forward * speed);
        Debug.Log(A.position);
    }
}

 

運行截圖

4、兩個Quaternion對象相乘與Quaternion對象與Vector3對象相乘的異同

  • 設A爲兩個兩個Quaternion對象相乘的結果。B爲Quaternion對象與Vector3對象相乘的結果。其中A爲Quaternion類型,B爲Vector3類型。
  • A與B的相同之處是它們都通過自身座標系的“相乘”方式來實現在世界座標系中的變換。
  • A主要用來實現transform繞自身座標系的某個軸旋轉,B主要用來實現transform沿着自身座標系的某個方向移動。
  • 必須遵守Quaternion對象*Vector3對象的形式,不可以顛倒順序。

由於它們都是相對於自身座標系進行的旋轉或移動,所以當自身座標系的軸向和世界座標系的軸向不一致時,它們繞着自身座標系中的某個軸向的變動都會影響到物體在世界座標系中各個座標軸的變動。

 四元數quaternion的變換比較複雜,但是在unity中已經給我們寫好了相應的函數實現對transform的操作。

  在最近的一個項目中,遇到了一個單手指滑動手機屏幕實現對模型的一個旋轉操作,在嘗試了各種unity中的旋轉函數之後都沒能夠達到想要的效果之後,我選擇了用Quaternion.AngleAxis的函數來實現旋轉的操作效果。

  首先我們來分析一下Quaternion.AngleAxis(angle,axis),參數angle和axis代表了物體的旋轉角度和旋轉軸心。如下圖:紅色箭頭方向代表物體所圍繞的旋轉軸,旋轉角度可以是自定義的。

  

  

  接下來,我們就要做兩件事情,確定axis和計算angle。在這個項目中,我們是根據單個手指在手機屏幕上滑動,我們通過記錄滑動的距離,X方向的增量,以及Y軸方向的增量來爲後面計算axis和angle打下基礎。unity的Input函數有GetTouch這個函數,我們只需要調用這個函數的相關方法就可以實現需求。

  現在,我們在unity中新建一個場景,在場景中新建一個立方塊。

  

  注意立方塊的世界座標軸,Z軸的朝向應該是朝着攝像機的。根據之前對四元數腳本的分析,立方體的旋轉腳本爲:

  Gesture.cs:

 

 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class gesture : MonoBehaviour {
 5     public Transform Cube;
 6     private float radius = 1080;
 7     private Vector3 originalDir = new Vector3(0f,0f,1080f);
 8     private Vector3 CenterPos = new Vector3(0, 0, 0);
 9     private Vector2 startPos;
10     private Vector2 tempPos;
11     private Vector3 tempVec;
12     private Vector3 normalAxis;
13     private float angle;
14     // Use this for initialization
15     void Start () {
16         Cube = GameObject.Find("Cube").transform;
17     }
18     
19     // Update is called once per frame
20     void Update () {
21         if (Input.touchCount == 1)
22         {
23             //Vector2 startPos = Input.compositionCursorPos;
24             if (Input.GetTouch(0).phase == TouchPhase.Began)
25             {
26                 startPos = Input.GetTouch(0).position;
27                 //tempPos = startPos;
28             }
29             //if (Input.GetTouch(0).phase == TouchPhase.Ended)
30             //{
31             //    tempPos = startPos;
32             //}
33             if (Input.GetTouch(0).phase == TouchPhase.Moved)
34             {
35                 tempPos = Event.current.mousePosition;
36 
37                 float tempX = tempPos.x - startPos.x;
38                     
39                 float tempY = tempPos.y - startPos.y;
40 
41                 //tempPos = Input.GetTouch(0).deltaPosition;
42                 //float tempX = tempPos.x;
43 
44                 //float tempY = tempPos.y;
45 
46                 float tempZ = Mathf.Sqrt(radius * radius - tempX * tempX - tempY * tempY);
47 
48                 tempVec = new Vector3(tempX, tempY, tempZ);
49 
50                 angle = Mathf.Acos(Vector3.Dot(originalDir.normalized, tempVec.normalized)) * Mathf.Rad2Deg;
51 
52                 normalAxis = getNormal(CenterPos, originalDir, tempVec);
53 
54                 Cube.rotation = Quaternion.AngleAxis(2 *angle, normalAxis);
55 
56             }
57         }
58     }
59 
60     void OnGUI()
61     {
62         GUILayout.Label("StartPos 的座標值爲: "+startPos);
63         GUILayout.Label("tempPos 的座標值爲: " + tempPos);
64         GUILayout.Label("tempVec 的座標值爲: " + tempVec);
65         GUILayout.Label("normalAxis 的座標值爲: " + normalAxis);
66         GUILayout.Label("旋轉角度的值爲: " + 2*angle);
67         GUILayout.Label("Cube的四元數角度: " + Cube.rotation);
68         GUILayout.Label("Cube de rotation.x: " + Cube.rotation.eulerAngles.x);
69         GUILayout.Label("Cube de rotation.y: " + Cube.rotation.eulerAngles.y);
70         GUILayout.Label("Cube de rotation.z: " + Cube.rotation.eulerAngles.z);
71     }
72 
73     private Vector3 getNormal(Vector3 p1,Vector3 p2,Vector3 p3)
74     {
75         float a = ((p2.y - p1.y) * (p3.z - p1.z) - (p2.z - p1.z) * (p3.y - p1.y));
76 
77         float b = ((p2.z - p1.z) * (p3.x - p1.x) - (p2.x - p1.x) * (p3.z - p1.z));
78 
79         float c = ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x));
80         //a對應的屏幕的垂直方向,b對應的屏幕的水平方向。
81         return new Vector3(a, -b, c);
82     }
83 }

 

  如果我們將這個在新機上運行,會發現在第一次手指滑動旋轉是正常的,但是第二次就會有個跳動的過程。在這裏我們需要注意一個問題,四元數函數Quaternion.AngleAxis是將立方體以初始的旋轉角度來進行圍繞着軸Axis旋轉Angle角度,不是在上一個狀態下的增量。如果需要延續上一次的旋轉狀態,就需要將這個物體的rotation恢復到初始的狀態。按照這個思路,我在Cube添加了一個父對象,我們在操作的時候對這個父對象進行操作,然後手指在離開屏幕的時候,將Cube脫離父對象,然後將父對象的rotation進行還原,再將Cube綁定爲父物體的子對象,在一下次手指旋轉之後就會接着上一次的旋轉狀態進行旋轉,實現了旋轉的延續。

  

  實現的代碼爲:

 

 1 using UnityEngine;
 2 using System.Collections;
 3 
 4 public class gesture : MonoBehaviour {
 5     public Transform Cube;
 6     public Transform RotObj;
 7     private float radius = 1080;
 8     private Vector3 originalDir = new Vector3(0f,0f,1080f);
 9     private Vector3 CenterPos = new Vector3(0, 0, 0);
10     private Vector2 startPos;
11     private Vector2 tempPos;
12     private Vector3 tempVec;
13     private Vector3 normalAxis;
14     private float angle;
15     // Use this for initialization
16     void Start () {
17         Cube = GameObject.Find("Cube").transform;
18     }
19     
20     // Update is called once per frame
21     void Update () {
22         if (Input.touchCount == 1)
23         {
24             //Vector2 startPos = Input.compositionCursorPos;
25             if (Input.GetTouch(0).phase == TouchPhase.Began)
26             {
27                 startPos = Input.GetTouch(0).position;
28             }
29             if (Input.GetTouch(0).phase == TouchPhase.Moved)
30             {
31                 tempPos = Event.current.mousePosition;
32 
33                 float tempX = tempPos.x - startPos.x;
34                     
35                 float tempY = tempPos.y - startPos.y;
36 
37                 float tempZ = Mathf.Sqrt(radius * radius - tempX * tempX - tempY * tempY);
38 
39                 tempVec = new Vector3(tempX, tempY, tempZ);
40 
41                 angle = Mathf.Acos(Vector3.Dot(originalDir.normalized, tempVec.normalized)) * Mathf.Rad2Deg;
42 
43                 normalAxis = getNormal(CenterPos, originalDir, tempVec);
44 
45                 RotObj.rotation = Quaternion.AngleAxis(2 *angle, normalAxis);
46 
47             }
48             if (Input.GetTouch(0).phase == TouchPhase.Ended)
49             {
50                 Cube.transform.parent = null;
51                 RotObj.rotation = Quaternion.identity;
52                 Cube.parent = RotObj;
53             }
54         }
55     }
56 
57     void OnGUI()
58     {
59         GUILayout.Label("StartPos 的座標值爲: "+startPos);
60         GUILayout.Label("tempPos 的座標值爲: " + tempPos);
61         GUILayout.Label("tempVec 的座標值爲: " + tempVec);
62         GUILayout.Label("normalAxis 的座標值爲: " + normalAxis);
63         GUILayout.Label("旋轉角度的值爲: " + 2*angle);
64         GUILayout.Label("Cube的四元數角度: " + Cube.rotation);
65         GUILayout.Label("Cube de rotation.x: " + Cube.rotation.eulerAngles.x);
66         GUILayout.Label("Cube de rotation.y: " + Cube.rotation.eulerAngles.y);
67         GUILayout.Label("Cube de rotation.z: " + Cube.rotation.eulerAngles.z);
68     }
69 
70     private Vector3 getNormal(Vector3 p1,Vector3 p2,Vector3 p3)
71     {
72         float a = ((p2.y - p1.y) * (p3.z - p1.z) - (p2.z - p1.z) * (p3.y - p1.y));
73 
74         float b = ((p2.z - p1.z) * (p3.x - p1.x) - (p2.x - p1.x) * (p3.z - p1.z));
75 
76         float c = ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x));
77         //a對應的屏幕的垂直方向,b對應的屏幕的水平方向。
78         return new Vector3(a, -b, c);
79     }
80 }

 

  現在對應着手指的滑動距離,然後調整參數radius,就可以實現比較順滑的旋轉效果,真機實現的效果就不展示了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章