Newbie problem: combining rotations [Solved]

Hey,



I'm quite unfamiliar with all of this, so please forgive me if this is a very stupid problem.



I'm trying to make a simple game with a space ship that can pitch, yaw and roll. To the ship is attached a vector for its facing direction and a vector for its normal, and the ship is supposed to turn to face the direction of the chase camera gradually. What I have now seems to work for yaw and pitch individually (ignore roll for now, it's a bit iffy), but when I try to combine the two it goes crazy. Here's what I have so far:


/**
    * Called every time the game updates - updates basic movement of the ship.
    */
   public void update(float time)
   {      
      Quaternion facingQuat = new Quaternion(facingDirection.x, facingDirection.y, facingDirection.z, 0);
      
      Quaternion facingNorm = new Quaternion(facingNormal.x, facingNormal.y, facingNormal.z, 0);
      Quaternion rotateShip = doYaw(time);
      rotateShip.multLocal(doPitch(time));
      //rotateShip.multLocal(doRoll(time));
      
      shipModel.getLocalRotation().multLocal(rotateShip);
      facingQuat = rotateShip.mult(facingQuat.mult(rotateShip.inverse()));
      facingDirection = new Vector3f(facingQuat.x, facingQuat.y, facingQuat.z);
      facingNorm = rotateShip.mult(facingNorm.mult(rotateShip.inverse()));
      facingNormal = new Vector3f(facingNorm.x, facingNorm.y, facingNorm.z);
      
      System.out.println("Normal: " + facingNormal);
      
      doAcceleration(time);
      doMotion(time);
      
      //System.out.println("Yaw velocity: " + yawVelocity);
   }
   


   private Quaternion doYaw(float time)
   {
      return new Quaternion().fromAngleAxis(-yawVelocity, facingNormal);
   }
   
   
   private Quaternion doPitch(float time)
   {
      return new Quaternion().fromAngleAxis(pitchVelocity, facingDirection.cross(facingNormal));
   }   
   
   private Quaternion doRoll(float time)
   {
      return new Quaternion().fromAngleAxis(rollVelocity, facingDirection);
   }



If I comment out the line so that it only gets one of the "doYaw" and "doPitch" quaternions, then it seems to work okay.

And for the turning towards the camera:

   private void doTurning(float time)
   {   
      
      if (targetDirection != null &&
         targetNormal != null)
      {
         if (!targetDirection.equals(facingDirection) ||
            !targetNormal.equals(facingNormal))
         {
            // Work out what angles we need to turn in each direction to meet the target.

            Plane yawPlane = new Plane(facingDirection.cross(facingNormal), facingNormal.z);
            Plane pitchPlane = new Plane(facingNormal, facingDirection.z);
            
            float yawDist = yawPlane.pseudoDistance(targetDirection);
            float pitchDist = pitchPlane.pseudoDistance(targetDirection);
            float rollDist = yawPlane.pseudoDistance(targetNormal);
            //System.out.println(rollDist);
            
            if (yawDist < -Globals.MIN_TURN_DIST)
            {
               setYaw(false);
            }
            if (yawDist >= -Globals.MIN_TURN_DIST && yawDist <= Globals.MIN_TURN_DIST)
            {
               zeroYaw();
            }
            if (yawDist > Globals.MIN_TURN_DIST)
            {
               setYaw(true);
            }
            
            if (yawDist < -Globals.MIN_TURN_DIST)
            {
               setPitch(false);
            }
            if (pitchDist >= -Globals.MIN_TURN_DIST && pitchDist <= Globals.MIN_TURN_DIST)
            {
               zeroPitch();
            }
            if (pitchDist > Globals.MIN_TURN_DIST)
            {
               setPitch(true);
            }
            
            if (rollDist < -Globals.MIN_TURN_DIST)
            {
               //System.out.println("roll left");
               setRoll(false);
            }
            if (rollDist >= -Globals.MIN_TURN_DIST && rollDist <= Globals.MIN_TURN_DIST)
            {
               //System.out.println("stop rolling");
               zeroRoll();
            }
            if (rollDist > Globals.MIN_TURN_DIST)
            {
               //System.out.println("roll right");
               setRoll(true);
            }
                        
         }
         else
         {
            zeroYaw();
            zeroPitch();
            zeroRoll();
         }
      }
   }




It seems either I have a fundamental misunderstanding of how to combine the two, or something more subtle is wrong with the way I'm doing this. Can anyone spot anything obvious I've done wrong? If not, I could provide the whole program on request.

Thanks,

-Joe

Your method of creating the quaternions from normal vectors by just passing them with w=0 strikes me as a bit odd. Maybe this can help?



http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q60

It's funny you should mention that FAQ, because that's where I read:


Q63. How do I use quaternions to rotate a vector?
  A rather elegant way to rotate a vector using a quaternion directly
  is the following (qr being the rotation quaternion):

                      -1
      v' = qr * v * qr

  This can easily be realised and is most likely faster then the transformation
  using a rotation matrix.


The thing with this is that the vector can't be multiplied by a quaternion. My passing of x, y, z and 0 was my guess on how to make a pure quaternion. The other one I tried was:

new Quaternion().fromAngleAxis(0, facingDirection)



Is this better?

No, angle 0 means "no rotation", which doesn't help at all.



Maybe you shouldn't try to create the rotation from the direction vectors, but the direction vectors from the quaternions using the function you quoted?

I'm not sure I understand you correctly - this is what I'm trying to do. The actual rotation is rotateShip, which is made by rotating about the relative yaw/pitch/roll axes of the ship. Using this quaternion I am trying to rotate both the direction vectors and the ship's model such that they remain consistent.

What that function solves is the problem "If I have a vector V, and a rotation Qr, then how do I transform the vector V using the quaternion Qr?" That doesn't sound like what you're actually trying to do. Btw: you add a 0 for the w before you do the multiplication, and then you remove the 0 when you're done. It also works with a 1, if I remember correctly; it'll cancel out.



If you want a given orientation to match a target orientation over time, then the easiest way to do that is to lerp or slerp between the quaternions. For example, something like (pseudo code, I don't remember the specific jME math calls):



  update:
    myRotation = lerp(myRotation, targetRotation, 0.05f);  // change constant for different control speed
    myRotation.normalize();



You can also interpolate axis and angle values, or even matrices, if you re-orthonormalize the matrices. However, I prefer quaternions for this. The full loop then becomes:


  update:
    cameraYaw += mouseMovement.x * scale;
    cameraYaw = moduloToInterval(cameraYaw, -Math.PI, Math.PI);
    cameraPitch += mouseMovement.y * scale;
    cameraPitch = modoloToInterval(cameraPitch, -Math.PI, Math.PI);
    targetOrientation.fromYawPitchRoll(cameraYaw, cameraPitch, 0);
    shipOrientation = lerp(shipOrientation, targetOrientation, 0.05f);
    shipOrientation.normalize();


Thanks for the replies. I had the idea of just letting it interpolate for me, but I wanted to separate out the yaw/pitch/roll axes so that I could govern their speeds separately. I found the stock chase camera to be kind of hard to control, so I decided to go with my own custom camera system and found that I was rotating the vectors correctly but not the ship model itself. I was able to keep the ship aligned properly with this code:


public void update(float time)
{
   // Rotate the vectors
   doYaw(time);
   doPitch(time);
   //doRoll(time);      
   
   // Re-align the ship model
   Quaternion newAlign = new Quaternion();
   newAlign.fromAxes(facingDirection, facingNormal, facingRight);
   shipModel.setLocalRotation(newAlign);
   
   doAcceleration(time);
   doMotion(time);
}



not sure that creating a new Quaternion like that in the update is the most efficient - cant you just modify the existing one

Good point, I'll get right on that. I've done a lot of little inefficient things like this, which I guess need fixing - from my background I'm more used to writing the simplest / most readable code possible than the most efficient.