处理:使用四元数旋转 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() 的目的,因为它既没有参数,也不能从外部设置欧拉角。