当前正在做AR云渲染服务,用到了需要把从AREngine中获取得到的旋转四元组配置到Unity上,怎么配置也没有配置正确。查了一些资料,把角度与四元组之间的转换公式记录一下。
四元组与欧拉角的转换具体的原理我在这就不讲了,大家可以自行查询资料。我们知道不同的XYZ相乘顺序,得到的四元组是不一样的。注意,在AREngine中,使用XYZ顺序,在Unity中,使用的是YXZ方式。
/**
* Sets the quaternion to the given euler angles.
*
* @param order X Y Z的顺序
* @param x the x in degrees,Angle of rotation around the x-axis
* @param y the y in degress, Angle of rotation around the y-axis
* @param z the z in degess, Angle of rotation around the z-axi
* @return this quaternion
*/
public static Quaternion EulerAngleToQuaternion(String order, double x, double y, double z) {
Quaternion q = new Quaternion();
//角度转换成弧度
float xRotation = (float) Math.toRadians(x);
float yRotation = (float) Math.toRadians(y);
float zRotation = (float) Math.toRadians(z);
double c1 = Math.cos(xRotation / 2);
double c2 = Math.cos(yRotation / 2);
double c3 = Math.cos(zRotation / 2);
double s1 = Math.sin(xRotation / 2);
double s2 = Math.sin(yRotation / 2);
double s3 = Math.sin(zRotation / 2);
switch (order) {
case "XYZ":
q.x = s1 * c2 * c3 + c1 * s2 * s3;
q.y = c1 * s2 * c3 - s1 * c2 * s3;
q.z = c1 * c2 * s3 + s1 * s2 * c3;
q.w = c1 * c2 * c3 - s1 * s2 * s3;
break;
case "YXZ":
q.x = s1 * c2 * c3 + c1 * s2 * s3;
q.y = c1 * s2 * c3 - s1 * c2 * s3;
q.z = c1 * c2 * s3 - s1 * s2 * c3;
q.w = c1 * c2 * c3 + s1 * s2 * s3;
break;
case "ZXY":
q.x = s1 * c2 * c3 - c1 * s2 * s3;
q.y = c1 * s2 * c3 + s1 * c2 * s3;
q.z = c1 * c2 * s3 + s1 * s2 * c3;
q.w = c1 * c2 * c3 - s1 * s2 * s3;
break;
case "ZYX":
q.x = s1 * c2 * c3 - c1 * s2 * s3;
q.y = c1 * s2 * c3 + s1 * c2 * s3;
q.z = c1 * c2 * s3 - s1 * s2 * c3;
q.w = c1 * c2 * c3 + s1 * s2 * s3;
break;
case "YZX":
q.x = s1 * c2 * c3 + c1 * s2 * s3;
q.y = c1 * s2 * c3 + s1 * c2 * s3;
q.z = c1 * c2 * s3 - s1 * s2 * c3;
q.w = c1 * c2 * c3 - s1 * s2 * s3;
break;
case "XZY":
q.x = s1 * c2 * c3 - c1 * s2 * s3;
q.y = c1 * s2 * c3 - s1 * c2 * s3;
q.z = c1 * c2 * s3 + s1 * s2 * c3;
q.w = c1 * c2 * c3 + s1 * s2 * s3;
break;
default:
Log.d("RemoteRender", "EulerAngleToQuaternion encountered an unknown order: " + order);
}
return q;
}
public static float[] ConvertToMatrix(float[] position, Quaternion quaternion, float[] scale) {
float[] matrix = new float[16];
float x = (float) quaternion.x;
float y = (float) quaternion.y;
float z = (float) quaternion.z;
float w = (float) quaternion.w;
float x2 = x + x,
y2 = y + y,
z2 = z + z;
float xx = x * x2,
xy = x * y2,
xz = x * z2;
float yy = y * y2,
yz = y * z2,
zz = z * z2;
float wx = w * x2,
wy = w * y2,
wz = w * z2;
float sx = scale[0], //scale
sy = scale[1],
sz = scale[2];
matrix[0] = (1 - (yy + zz)) * sx;
matrix[1] = (xy + wz) * sx;
matrix[2] = (xz - wy) * sx;
matrix[3] = 0;
matrix[4] = (xy - wz) * sy;
matrix[5] = (1 - (xx + zz)) * sy;
matrix[6] = (yz + wx) * sy;
matrix[7] = 0;
matrix[8] = (xz + wy) * sz;
matrix[9] = (yz - wx) * sz;
matrix[10] = (1 - (xx + yy)) * sz;
matrix[11] = 0;
matrix[12] = position[0];
matrix[13] = position[1];
matrix[14] = position[2];
matrix[15] = 1;
return matrix;
}
public static float clamp(float value, float min, float max) {
return Math.max(min, Math.min(max, value));
}
public static float[] QuaternionToEulerAngle(String order, Quaternion quaternion) {
float[] euler = new float[3];
float[] matrix = ConvertToMatrix(new float[]{0.f, 0.f, 0.f}, quaternion, new float[]{1.f, 1.f, 1.f});
float m11 = matrix[0],
m12 = matrix[4],
m13 = matrix[8];
float m21 = matrix[1],
m22 = matrix[5],
m23 = matrix[9];
float m31 = matrix[2],
m32 = matrix[6],
m33 = matrix[10];
switch (order) {
case "XYZ":
euler[1] = (float) Math.asin(clamp(m13, -1, 1));
if (Math.abs(m13) < 0.9999999) {
euler[0] = (float) Math.atan2(-m23, m33);
euler[2] = (float) Math.atan2(-m12, m11);
} else {
euler[0] = (float) Math.atan2(m32, m22);
euler[2] = (float) 0;
}
break;
case "YXZ":
euler[0] = (float) Math.asin(-clamp(m23, -1, 1));
if (Math.abs(m23) < 0.9999999) {
euler[1] = (float) Math.atan2(m13, m33);
euler[2] = (float) Math.atan2(m21, m22);
} else {
euler[1] = (float) Math.atan2(-m31, m11);
euler[2] = (float) 0;
}
break;
case "ZXY":
euler[0] = (float) Math.asin(clamp(m32, -1, 1));
if (Math.abs(m32) < 0.9999999) {
euler[1] = (float) Math.atan2(-m31, m33);
euler[2] = (float) Math.atan2(-m12, m22);
} else {
euler[1] = (float) 0;
euler[2] = (float) Math.atan2(m21, m11);
}
break;
case "ZYX":
euler[1] = (float) Math.asin(-clamp(m31, -1, 1));
if (Math.abs(m31) < 0.9999999) {
euler[0] = (float) Math.atan2(m32, m33);
euler[2] = (float) Math.atan2(m21, m11);
} else {
euler[0] = (float) 0;
euler[2] = (float) Math.atan2(-m12, m22);
}
break;
case "YZX":
euler[2] = (float) Math.asin(clamp(m21, -1, 1));
if (Math.abs(m21) < 0.9999999) {
euler[0] = (float) Math.atan2(-m23, m22);
euler[1] = (float) Math.atan2(-m31, m11);
} else {
euler[0] = (float) 0;
euler[1] = (float) Math.atan2(m13, m33);
}
break;
case "XZY":
euler[2] = (float) Math.asin(-clamp(m12, -1, 1));
if (Math.abs(m12) < 0.9999999) {
euler[0] = (float) Math.atan2(m32, m22);
euler[1] = (float) Math.atan2(m13, m11);
} else {
euler[0] = (float) Math.atan2(-m23, m33);
euler[1] = (float) 0;
}
break;
default:
Log.e("RemoteRender", "QuaternionToEulerAngle() encountered an unknown order: " + order);
}
//弧度转换成角度
euler[0] = (float) Math.toDegrees(euler[0]);
euler[1] = (float) Math.toDegrees(euler[1]);
euler[2] = (float) Math.toDegrees(euler[2]);
return euler;
}
你写完代码之后,可以在https://quaternions.online/ 或者 https://www.andre-gaschler.com/rotationconverter/ 2个在线测试工具进行测试。
参考文献:
https://blog.csdn.net/weixin_40599145/article/details/90345547