处理:使用四元数旋转 3D 对象适用于 x 轴,但不适用于 y 或 z 轴?
Processing: Rotating a 3D object using Quaternions works for x-axis, but not for y or z axis?
我正在根据串行设备的输入创建四元数。在 Processing 中,我在下面的代码中绕 x 轴旋转。我的四元数对象接受输入并使用 set 函数来设置值、欧拉角和归一化。数学有问题吗?
我注释掉了 z 和 y 的旋转,但基本上对象不能很好地旋转,或者与 x 轴相比旋转不稳,但效果很好。我在下面的代码中做错了什么?
作为参考,下面的 shape(model) 行是从使用 loadShape 加载的 .obj 文件中加载的 3d 对象,形状函数在绘制循环中显示它。
Quaternion q = new Quaternion(s);
q.set(x, y, z, w);
q = q.Euler(q.eulerAngles);
translate(x, y);
rotateX(q.eulerAngles.x);
//rotateY(q.eulerAngles.y);
//rotateZ(q.eulerAngles.z);
shape(model);
rotateX(-q.eulerAngles.x);
translate(-x, -y);
这是四元数的一部分class:
public class Quaternion {
PApplet s;
public float w,x,y,z;
public PVector eulerAngles;
public Quaternion(PApplet s, float x, float y, float z, float w){
this.s = s;
this.x = x;
this.y = y;
this.z = z;
this.w = w;
normalize();
}
public Quaternion(Quaternion q){
this.s = q.s;
this.w = q.w;
this.x = q.x;
this.y = q.y;
this.z = q.z;
}
public Quaternion normalize() {
float magnitude = w*w + x*x + y*y + z*z;
if(magnitude != 0.0 && magnitude != 1.0){
magnitude = 1.0f / s.sqrt(magnitude);
w *= magnitude;
x *= magnitude;
y *= magnitude;
z *= magnitude;
}
eulerAngles = setEulerAngles();
return this;
}
public Quaternion set(float x, float y, float z, float w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
return normalize();
}
// Returns a rotation that rotates z degrees around
// the z axis, x degrees around the x axis, and y
// degrees around the y axis.
public Quaternion Euler(){
float roll = eulerAngles.x;
float pitch = eulerAngles.y;
float yaw = eulerAngles.z;
float cr = (float)Math.cos(roll * 0.5);
float sr = (float)Math.sin(roll * 0.5);
float cp = (float)Math.cos(pitch * 0.5);
float sp = (float)Math.sin(pitch * 0.5);
float cy = (float)Math.cos(yaw * 0.5);
float sy = (float)Math.sin(yaw * 0.5);
w = cy*cr*cp + sy*sr*sp;
x = cy*sr*cp - sy*cr*sp;
y = cy*cr*sp + sy*sr*cp;
z = sy*cr*cp - cy*sr*sp;
return normalize();
}
// set euler angle representation of
// the rotation in 3-dim PVector
private PVector setEulerAngles(){
// roll: x-axis rotation
float sinr = 2.0f * (w*x + y*z);
float cosr = 1.0f - 2.0f * (x*x + y*y);
float roll = (float)Math.atan2(sinr, cosr);
// pitch: y-axis rotation
float sinp = 2.0f * (w*y - z*x);
float pitch = 0.0f;
if(Math.abs(sinp) >= 1){
pitch = (float)Math.copySign(Math.PI/2, sinp);
} else {
pitch = (float)Math.asin(sinp);
}
// yaw: z-axis rotation
float siny = 2.0f * (w*z + x*y);
float cosy = 1.0f - 2.0f * (y*y + z*z);
float yaw = (float)Math.atan2(siny, cosy);
return new PVector(roll, pitch, yaw);
}
}
据我所知,您从您的方法中获得的欧拉角应该按 ZYX 顺序而不是 XYZ 顺序应用。但无论如何,除非万不得已,否则不要乱用欧拉角。在这种情况下,你不需要。
相反,convert the quaternion to a rotation matrix and apply this transform using applyMatrix()
。这里不会有歧义。
要还原转换,请不要应用逆变换(就像您对 rotateX(-q.eulerAngles.x)
和 translate(-x, -y)
所做的那样)。在开发过程中很容易混淆顺序或忘记转换。相反,使用 pushMatrix()
/ popMatrix()
or resetMatrix
.
顺便说一句,我发现您的四元数 class 的定义非常混乱。有些方法 return 值我不希望 return 任何东西(例如 normalize()
)。此外,我不认为将欧拉角表示与四元数一起存储是个好主意。即使你认为是,我也不明白方法 Euler()
的目的,因为它既没有参数,也不能从外部设置欧拉角。
我正在根据串行设备的输入创建四元数。在 Processing 中,我在下面的代码中绕 x 轴旋转。我的四元数对象接受输入并使用 set 函数来设置值、欧拉角和归一化。数学有问题吗?
我注释掉了 z 和 y 的旋转,但基本上对象不能很好地旋转,或者与 x 轴相比旋转不稳,但效果很好。我在下面的代码中做错了什么?
作为参考,下面的 shape(model) 行是从使用 loadShape 加载的 .obj 文件中加载的 3d 对象,形状函数在绘制循环中显示它。
Quaternion q = new Quaternion(s);
q.set(x, y, z, w);
q = q.Euler(q.eulerAngles);
translate(x, y);
rotateX(q.eulerAngles.x);
//rotateY(q.eulerAngles.y);
//rotateZ(q.eulerAngles.z);
shape(model);
rotateX(-q.eulerAngles.x);
translate(-x, -y);
这是四元数的一部分class:
public class Quaternion {
PApplet s;
public float w,x,y,z;
public PVector eulerAngles;
public Quaternion(PApplet s, float x, float y, float z, float w){
this.s = s;
this.x = x;
this.y = y;
this.z = z;
this.w = w;
normalize();
}
public Quaternion(Quaternion q){
this.s = q.s;
this.w = q.w;
this.x = q.x;
this.y = q.y;
this.z = q.z;
}
public Quaternion normalize() {
float magnitude = w*w + x*x + y*y + z*z;
if(magnitude != 0.0 && magnitude != 1.0){
magnitude = 1.0f / s.sqrt(magnitude);
w *= magnitude;
x *= magnitude;
y *= magnitude;
z *= magnitude;
}
eulerAngles = setEulerAngles();
return this;
}
public Quaternion set(float x, float y, float z, float w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
return normalize();
}
// Returns a rotation that rotates z degrees around
// the z axis, x degrees around the x axis, and y
// degrees around the y axis.
public Quaternion Euler(){
float roll = eulerAngles.x;
float pitch = eulerAngles.y;
float yaw = eulerAngles.z;
float cr = (float)Math.cos(roll * 0.5);
float sr = (float)Math.sin(roll * 0.5);
float cp = (float)Math.cos(pitch * 0.5);
float sp = (float)Math.sin(pitch * 0.5);
float cy = (float)Math.cos(yaw * 0.5);
float sy = (float)Math.sin(yaw * 0.5);
w = cy*cr*cp + sy*sr*sp;
x = cy*sr*cp - sy*cr*sp;
y = cy*cr*sp + sy*sr*cp;
z = sy*cr*cp - cy*sr*sp;
return normalize();
}
// set euler angle representation of
// the rotation in 3-dim PVector
private PVector setEulerAngles(){
// roll: x-axis rotation
float sinr = 2.0f * (w*x + y*z);
float cosr = 1.0f - 2.0f * (x*x + y*y);
float roll = (float)Math.atan2(sinr, cosr);
// pitch: y-axis rotation
float sinp = 2.0f * (w*y - z*x);
float pitch = 0.0f;
if(Math.abs(sinp) >= 1){
pitch = (float)Math.copySign(Math.PI/2, sinp);
} else {
pitch = (float)Math.asin(sinp);
}
// yaw: z-axis rotation
float siny = 2.0f * (w*z + x*y);
float cosy = 1.0f - 2.0f * (y*y + z*z);
float yaw = (float)Math.atan2(siny, cosy);
return new PVector(roll, pitch, yaw);
}
}
据我所知,您从您的方法中获得的欧拉角应该按 ZYX 顺序而不是 XYZ 顺序应用。但无论如何,除非万不得已,否则不要乱用欧拉角。在这种情况下,你不需要。
相反,convert the quaternion to a rotation matrix and apply this transform using applyMatrix()
。这里不会有歧义。
要还原转换,请不要应用逆变换(就像您对 rotateX(-q.eulerAngles.x)
和 translate(-x, -y)
所做的那样)。在开发过程中很容易混淆顺序或忘记转换。相反,使用 pushMatrix()
/ popMatrix()
or resetMatrix
.
顺便说一句,我发现您的四元数 class 的定义非常混乱。有些方法 return 值我不希望 return 任何东西(例如 normalize()
)。此外,我不认为将欧拉角表示与四元数一起存储是个好主意。即使你认为是,我也不明白方法 Euler()
的目的,因为它既没有参数,也不能从外部设置欧拉角。