首先我们来参考一下四元数在Unity中的应用:
unity3D 详解Quaternion类(二)
四元数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,就可以实现比较顺滑的旋转效果,真机实现的效果就不展示了。