Trying to understand Quaternions

Well after I'm currently trying to understand what exactly a Quaternion is, I have some questions:



the Quaternion from getWorldRotation is relative to wich vector?

How can a Quaternion define the roll of an object when it only stores the information to transfom one vector into another and a vector has no roll?

Do jme even need the w in the Quaternion as far as I understood it's only the length difference betwenn the two mentiond Vectors how is this relevant to the Rotation in our case?

chuckle



Well, Empire, I've read up a bit on Quaternions.



the best explanation I ever saw on how to understand quaternions was only one word long:



"don't"



They're odd beasts.  I don't really understand the math behind them, but the long and short is that all four of those numbers (yes, including the w) are necessary to define the rotation of the node from its original X,Y,Z orientation.  In short, you can take any vector, run it through the 'mult()' method of a quaternion, and come out with the correct resulting vector after that rotation is applied.



so in other words, one SINGLE vector doesn't store roll, but the combination of 3 vectors does.  Take your Unit X, Unit Y, and Unit Z vectors . . . run them EACH through that quaternion, and the outcome will be the Unit X, Unit Y, and Unit Z vectors in your object's frame of reference.



Pretty slick when all that defines the shape of your object is a whole ton of vertices, which are all represented as Vectors.  :slight_smile:



And it's all represented with four itty-bitty little floating point numbers, making storage more efficient, though the calculation is much uglier than rotation matrices.



But the math behind them . . . I couldn't HOPE to explain it.  :slight_smile:



-Falken

Well first thank you for your help, but I really need to understand them, because in order to build a space game I need a real 3d approach for the rotation, so even the mult() with a Vector si quite helpfull for some stuff, it won't work for anything in my case :confused:

The Quaterninon is not really designed for understanding it  :wink:



That is why there are a bunch of convenience methods like .fromAngleAxis(), and so on.



You do not need to understand how it works to work with it.

Hehe, once I also decided not to try to understand the math behind but just to know how to use it.

For me there a two very useful and important method-calls:



q1.lookAt(position,up-Vector): using this you let q1 lookAt a given position (up_vector is usually (0,1,0))

q1.fromAngleAxis(angle,axis): to rotate a quaternion a certain about of degree (radians) about a given axis. Caution: That is always using the startposition! So calling the same call a second time would have the same result.

Well but the problem with the angle stuff is the gimbal lock, wich is why i have to do it with quaternions

Well, I guess that begs the question . . . what is it you're trying to do?  Maybe someone else has run into a similar problem, or could offer a different perspective for problem-solving purposes.



If you're trying to do a real 3d approach to a space game, the best thing to remember is "the enemy's gate is down".



Or, if you're not an Orson Scott Card fan . . . there is no correct orientation but the one you choose.  Everything's relative, so if you're trying to turn a ship . . . you know what all the controls do in terms of pitch, yaw and roll, and it's all from the perspective of that ship's frame of reference.  Come up with the ship's applied rotation based on the applied forces, and simply do 'getLocalRotation().multLocal(appliedRotation)' and you're done.  You don't run into gimbal lock 'cause you're not calculating all the angles yourself.



If you're trying to 'look' in a particular direction, that's easily solved with simple vector math and a nifty Quaternion setter method:



start.cross(end) = axis

start.angle(end) = angle

Quaternion.fromAngleAxis(angle,axis) = rotationFromCurrentFrameOfReference



If you're wanting to force a particular orientation (i.e., your space stations must always be right-side up) then you'll have some other problems to solve, and that's where things really do start to get complicated.



So that bit of soapboxy wackiness out of the way, I'm curious what you're running into.  :)  Sounds like interesting problems to solve.

Here's code from a very old spacegame of mine. It has 6 degrees of freedom rotation of the spacecraft/camera. You can see how with a combination of toAxes, fromAxes, fromAngleAxis and mults you can create and combine rotations to achieve advanced effects.

Note that it's quite old and I cut out a lot of irrelevant pieces of code before posting so some things might not make much sense. The important thing is to look at how the Quaternions are used for the spacegame.

    protected boolean up, down, left, right;
   
    protected float shipZoom = 3.2f;
   
    protected Vector3f[] temp = new Vector3f[3];
   

    protected Camera cam;
   
    {
        temp[0] = new Vector3f(); temp[1] = new Vector3f(); temp[2] = new Vector3f();
    }

    protected Quaternion camRot = new Quaternion();
    protected Quaternion rotTemp = new Quaternion();
   
    @Override
    public void update(float time) {
        if (!isActive())
           return;
       
        // get the rotation of the player spacecraft as 3 axes
        player.getLocalRotation().toAxes(temp);
        // temp[0] = left
        // temp[1] = up
        // temp[2] = direction
  
        // if currently pressing left or right key
        if (left || right){
            // rotation around up vector
            Quaternion q = new Quaternion();
            q.fromAngleNormalAxis(time * FastMath.HALF_PI * (right ? -1f : 1f), temp[1]);
           
            // rotation around direction vector
            Quaternion q2 = new Quaternion();
            q2.fromAngleNormalAxis(time * FastMath.HALF_PI * (right ? -1f : 1f), temp[2]);
          
            // apply the rotations to the camera vectors
            q.multLocal(cam.getDirection());
            q.multLocal(cam.getLeft());
            q.multLocal(cam.getUp());
           
            q2.multLocal(cam.getDirection());
            q2.multLocal(cam.getLeft());
            q2.multLocal(cam.getUp());
           
            cam.normalize();
            cam.update();
        }
       
        // update camera location based on spacecraft position
        // puts the camera above the spacecraft and a little bit behind
        Vector3f op = player.getLocalTranslation().clone();
        op.addLocal(temp[2].mult(-shipZoom * 2.5f));
        op.addLocal(temp[1].mult(shipZoom));
        cam.setLocation(op);
       
        // handles up/down keys
        if (up){
            // 100 is the ship's speed.
            // acceleration per second = current direction * 100
            temp[2].multLocal(time).multLocal(100.0f);
            player.getVelocity().addLocal(temp[2]);
        }else if (down){
            // slow down the ship or stop it completely
            temp[2].multLocal(time).multLocal(50.0f);
            if (player.getVelocity().length() > 50.0f * time)
                player.getVelocity().subtractLocal(temp[2]);
            else
                player.getVelocity().set(Vector3f.ZERO);
        }else if (player.getVelocity().length() > FastMath.ZERO_TOLERANCE){
            // i forgot what this does
            player.getVelocity().multLocal(1f - (0.7f * time));
        }
       
        // update camRot quaternion with the camera axes
        camRot.fromAxes(cam.getLeft(), cam.getUp(), cam.getDirection());
        // update the spacecraft's rotation based on the camera's
        player.getLocalRotation().slerp(camRot, time * 5.0f);
    }

    public void onMove(int xDelta, int yDelta, int newX, int newY) {
        rotTemp.fromAngleNormalAxis(-0.01f * xDelta, cam.getUp());
        rotTemp.multLocal(cam.getDirection());
        rotTemp.multLocal(cam.getLeft());
        rotTemp.multLocal(cam.getUp());
       
        rotTemp.fromAngleNormalAxis(-0.01f * yDelta, cam.getLeft());
        rotTemp.multLocal(cam.getDirection());
        rotTemp.multLocal(cam.getLeft());
        rotTemp.multLocal(cam.getUp());
    }

Wella fter a day of screaming and testing ^^ I fially wa able to create a Physic based steering.



It steers kinda like Freelancer now, just witha bit more realistic steering;



I bet there are many operations that can be optimized, so I would appreciate a little help.



Here is my TestFlyer


package Client;

import Client.MouseManager.MouseKey;
import Client.SolarSystem.SolarSystem;

import com.jme.input.KeyInput;
import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.shape.Box;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.material.Material;

public class TestFlyer {
   private static final float dampeningrotation = 0.89f;
   private static final float dampeningmove = 0.99f;
   private static final int forwardforce = 500;
   private static final int strafeforce = 200;
   private static final float rollchange = 0.1f;
   private static final float udforce = 1f;
   private static final float rlforce = 1f;
   
   private SolarSystem system;
   private CameraController cam;
   private DynamicPhysicsNode flyobject;
   private Quaternion NullToShouldBe = new Quaternion();
   
   
   
   public TestFlyer(SolarSystem mysystem){
      this.system = mysystem;
      this.cam = CameraController.Get();
      this.flyobject = this.system.getPhysicspace().createDynamicNode();
      Box mycoldata = new Box("TestFly",new Vector3f(0,0,0), 1,1,1);
      this.flyobject.attachChild(mycoldata);
      this.flyobject.setMaterial(Material.CONCRETE);
      this.flyobject.generatePhysicsGeometry(true);
      this.system.getSystemnode().attachChild(this.flyobject);
      
      NullToShouldBe.normalize();
   }
   
   public void Update(float interpolate){
      ApplyForce(interpolate);

      this.flyobject.setLinearVelocity(this.flyobject.getLinearVelocity(null).mult(dampeningmove));
      this.flyobject.SetAngularVel(this.flyobject.getAngularVel().mult(dampeningrotation));
      
      this.cam.setLocalRotation(this.flyobject.getLocalRotation());
      this.cam.setLocalTranslation(this.flyobject.getLocalTranslation().add(GetForward(this.flyobject.getLocalRotation()).mult(-10)));
   }

   private void ApplyForce(float interpolate) {
      KeyInput keysystem = MassiveSpaceEngine.Get().getKeySystem();
      if(keysystem.isKeyDown(KeyInput.KEY_W)){
         Vector3f forwardvec = GetForward(this.flyobject.getWorldRotation());
         this.flyobject.addForce(forwardvec.mult((forwardforce*interpolate)));
      }
      if(keysystem.isKeyDown(KeyInput.KEY_S)){
         Vector3f forwardvec = GetForward(this.flyobject.getWorldRotation());
         this.flyobject.addForce(forwardvec.mult(-(forwardforce*interpolate)));
      }
      if(keysystem.isKeyDown(KeyInput.KEY_A)){
         Vector3f forwardvec = GetRight(this.flyobject.getWorldRotation());
         this.flyobject.addForce(forwardvec.mult((strafeforce*interpolate)));
      }
      if(keysystem.isKeyDown(KeyInput.KEY_D)){
         Vector3f forwardvec = GetRight(this.flyobject.getWorldRotation());
         this.flyobject.addForce(forwardvec.mult(-(strafeforce*interpolate)));
      }
      if(keysystem.isKeyDown(KeyInput.KEY_SPACE)){
         Vector3f forwardvec = GetUp(this.flyobject.getWorldRotation());
         this.flyobject.addForce(forwardvec.mult(strafeforce*interpolate));
      }
      if(keysystem.isKeyDown(KeyInput.KEY_LSHIFT)){
         Vector3f forwardvec = GetUp(this.flyobject.getWorldRotation());
         this.flyobject.addForce(forwardvec.mult(-strafeforce*interpolate));
      }
      
      if(keysystem.isKeyDown(KeyInput.KEY_Q)){
         Vector3f forwardvec = GetUp(this.flyobject.getLocalRotation().mult(rollchange));
         this.flyobject.addForce(forwardvec,new Vector3f(-10,0,0));
      }
      if(keysystem.isKeyDown(KeyInput.KEY_E)){
         Vector3f forwardvec = GetUp(this.flyobject.getLocalRotation().mult(rollchange));
         this.flyobject.addForce(forwardvec,new Vector3f(10,0,0));
      }
      
      float deltay = MouseManager.Get().RelativeGetY()*udforce;
      float deltax = MouseManager.Get().RelativeGetX()*rlforce;
      
      if(MouseManager.Get().MousePressed(MouseKey.Left)){
         Vector3f forwardvec = GetUp(this.flyobject.getLocalRotation()).mult(deltay);
         this.flyobject.addForce(forwardvec,new Vector3f(0,0,10));   
         
         Vector3f forwardvec2 = GetForward(this.flyobject.getLocalRotation()).mult(deltax);
         this.flyobject.addForce(forwardvec2,new Vector3f(10,0,0));   
      }
   }

   public Vector3f GetForward(Quaternion quat){
      Matrix3f matrix = quat.toRotationMatrix();
      Vector3f front = matrix.mult(Vector3f.UNIT_Z);
      return front;
   }
   
   public Vector3f GetUp(Quaternion quat){
      Matrix3f matrix = quat.toRotationMatrix();
      Vector3f front = matrix.mult(Vector3f.UNIT_Y);
      return front;
   }
   
   public Vector3f GetRight(Quaternion quat){
      Matrix3f matrix = quat.toRotationMatrix();
      Vector3f front = matrix.mult(Vector3f.UNIT_X);
      return front;
   }
}

Matrix3f matrix = quat.toRotationMatrix();
      Vector3f front = matrix.mult(Vector3f.UNIT_X);
      return front;



Why do you do this?
Just:

return quat.mult(Vector3f.UNIT_X);



Else, you would loose some time in generating the matrix and secondly, you will create a Matrix3f in each update cycle, wich then have to be garbage collected.... ;)

Quaternions are actually quite easy to understand,



Probably the best way to understand them is to go back to Ken Shoemake's paper from SIGGRAPH 1985.  His explanations are quite succinct and very accessable.



http://portal.acm.org/citation.cfm?id=325242



I happened to be at the session where Shoemake presented his paper and it was really a hoot to watch him twist his body under his hand in order to explain half rotations.



As a side note, I came back from SIGGRAPH and was telling the director of the lab where I worked about this brand new mathematical technique that I was all hot about.  He sort of smiled, turned to his bookshelf and handed me his copy of Hamilton's On Quaternions.  As computer graphics pioneer James Kajiya put it, computer graphics is mostly mathematical archeology.