Processing: Rotating a 3D object using Quaternions

2019-08-26 15:59发布

问题:

I'm creating a Quaternion from input from a serial device. In Processing I rotate around the x-axis in the code below. My Quaternion object takes the input and uses the set function to set the values, euler angles, and normalize. Is there something wrong with the math?

I commented out rotation for z and y, but basically the object doesn't rotate around very well or is jerky compared to the x-axis, which works perfectly. What am I doing wrong in the code below?

For reference, the shape(model) line below is the loaded 3d object from a .obj file loaded in using loadShape and the shape function displays it in the draw loop.

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);

This is part of the Quaternion 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);
   }
}

回答1:

As far as I can tell, the Euler angles that you get from your method should be applied in ZYX order rather than XYZ. But anyway, do not mess around with Euler angles unless you really have to. And in this case you don't.

Instead, convert the quaternion to a rotation matrix and apply this transform using applyMatrix(). There will be no ambiguity here.

To revert a transform, do not apply the inverse transform (like you did with rotateX(-q.eulerAngles.x) and translate(-x, -y)). It is very easy to confuse the order or forget a transform during development. Instead, use pushMatrix() / popMatrix() or resetMatrix.

Btw, I find the definition of your quaternion class very confusing. Some methods return values that I would not expect to return anything (e.g. normalize()). Furthermore, I do not think that having an Euler angle representation stored with the quaternion is a good idea. And even if you think it is, I don't understand the purpose of the method Euler() since it neither has parameters, nor can you set the Euler angles from outside.