万向节死锁产生的根本原因是,旋转矩阵是依次进行的,假设先围绕x轴旋转,再围绕y轴旋转,最后围绕z轴旋转,这就导致物体其实是围绕自己的X轴旋转,而不是世界坐标的X轴旋转。
然后,进行一次旋转时,物体是先围绕x轴进行旋转,这时候物体的局部坐标系和世界坐标系是重合的,虽然是围绕世界坐标的x轴旋转,但也是围绕自己的x轴进行旋转。最后得到的旋转结果,也变成了物体是绕自己的x轴转的结果了。
表现就是,在一个欧拉角(x,y,z)下,改变x的值,物体会围绕物体自己的x轴进行旋转,而不是世界坐标系的x轴进行旋转。
下图所示,改变x值,都是在绕自己的红色的轴(x轴)转。RGB三个颜色分别对应XYZ正半轴:
首先,旋转3D模型时,实际是对原始模型的顶点进行旋转。比如旋转(x1,y1,z1),就是把原始模型旋转到这个角度,然后渲染显示。当旋转变成(x2,y2,z2),会把原始模型旋转到这个角度,然后渲染显示。
然后,进行一次旋转时,物体是先围绕x轴进行旋转,这时候物体的局部坐标系和世界坐标系是重合的,虽然是围绕世界坐标的x轴旋转,但也是围绕自己的x轴进行旋转。最后得到的旋转结果,也变成了物体是绕自己的x轴转的结果了。
最后,当把物体的x轴旋转到与世界的z轴重合时,欧垃角的x和z旋转结果就都一样了,也就丢失了一个维度。另一方面,比如在(30, 30, 30)的欧垃角下,把y从30调到60会发现并不是绕自己的y轴在转,也不是绕世界坐标的y旋转。
可以自己通过欧拉角计算出顶点旋转后的坐标来验证这个。
参考资料:
附上自己的Unity测试代码:
using UnityEngine;
[ExecuteInEditMode]
public class TestEuler : MonoBehaviour
{
public Vector3 Euler;
public Vector3 OrgPosOfA;
public Vector3 OrgPosOfB;
public Vector3 OrgPosOfC;
public Transform TransA;
public Transform TransB;
public Transform TransC;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update ()
{
TransA.position = Rotate(OrgPosOfA, Euler) + transform.position;
TransB.position = Rotate(OrgPosOfB, Euler) + transform.position;
TransC.position = Rotate(OrgPosOfC, Euler) + transform.position;
}
Vector3 Rotate(Vector3 pos, Vector3 euler)
{
pos = RotateX(pos, euler.x * Mathf.Deg2Rad);
pos = RotateY(pos, euler.y * Mathf.Deg2Rad);
pos = RotateZ(pos, euler.z * Mathf.Deg2Rad);
return pos;
}
Vector3 RotateX(Vector3 pos, float r)
{
Vector3 newPos = pos;
float cos = Mathf.Cos(r);
float sin = Mathf.Sin(r);
newPos.y = pos.y * cos + pos.z * sin;
newPos.z = -pos.y * sin + pos.z * cos;
return newPos;
}
Vector3 RotateY(Vector3 pos, float r)
{
Vector3 newPos = pos;
float cos = Mathf.Cos(r);
float sin = Mathf.Sin(r);
newPos.x = pos.x * cos - pos.z * sin;
newPos.z = pos.x * sin + pos.z * cos;
return newPos;
}
Vector3 RotateZ(Vector3 pos, float r)
{
Vector3 newPos = pos;
float cos = Mathf.Cos(r);
float sin = Mathf.Sin(r);
newPos.x = pos.x * cos + pos.y * sin;
newPos.y = -pos.x * sin + pos.y * cos;
return newPos;
}
void OnDrawGizmos()
{
//坐标轴
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, transform.position + Vector3.right);
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, transform.position + Vector3.up);
Gizmos.color = Color.blue;
Gizmos.DrawLine(transform.position, transform.position + Vector3.forward);
//旋转后的点
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, TransA.position);
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, TransB.position);
Gizmos.color = Color.blue;
Gizmos.DrawLine(transform.position, TransC.position);
}
}
下图所示,改变x值,都是在绕红色的轴(x轴)转。RGB三个颜色分别对应XYZ正半轴: