Animate rotation using quaternions

I’m trying to store rotation and rotational velocity as quaternions to avoid gimble lock. Of course the rotational velocity needs to be scaled for the particular tpf in the update loop. I initially tried quat.mult(float tpf) but this seems to corrupt the quaternion (nothing good happens anyway). I then tried converting the rotational velocity quaternion to angles and scaled each angle for tpf before putting back into a quaternion (see code). This actually works nicely without gimble lock but it feels think a hack. Is this the right way to do this, and if not what is?

You can see what i’m trying to do in the below code, slowly rotate a cube.

[java]package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication {

Quaternion rotation=(new Quaternion()).fromAngles(0,0, 0);
Quaternion rotationVelocity=(new Quaternion()).fromAngles(0.25f,0.5f, 0.25f);
Geometry geom;

public static void main(String[] args) {
    Main app = new Main();
    app.start();
}

@Override
public void simpleInitApp() {
    Box b = new Box(Vector3f.ZERO, 1, 1, 1);
    geom = new Geometry("Box", b);

    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Blue);
    mat.getAdditionalRenderState().setWireframe(true);
    
    geom.setMaterial(mat);
    
    rootNode.attachChild(geom);
}

@Override
public void simpleUpdate(float tpf) {
    
    //taking the velocity quaternion into angles, scaling for tpf
    //and putting back into quaternion
    
    float angles[]=new float[3];
    rotationVelocity.toAngles(angles);
    for(int i=0;i<angles.length;i++){
        angles[i]*=tpf;
    }
    
    Quaternion rotationVelocityFPS=(new Quaternion()).fromAngles(angles);
 
    rotation.multLocal(rotationVelocityFPS);

    geom.setLocalRotation(rotation);
}

@Override
public void simpleRender(RenderManager rm) {
    //TODO: add render code
}

}

[/java]

Maybe use Quaternion.slerp between unit quaternion and your rotation target, with tt being slerp factor between 0-1, then multiply local rotation with resulting quat?

1 Like

Thank you, I hadn’t ever though of using slerp this way.

For completeness, the code including slerp

[java]package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication {

Quaternion rotation=(new Quaternion()).fromAngles(0,0, 0);
Quaternion rotationVelocity=(new Quaternion()).fromAngles(0.25f,0.5f, 0.25f);
Geometry geom;

public static void main(String[] args) {
    Main app = new Main();
    app.start();
}

@Override
public void simpleInitApp() {
    Box b = new Box(Vector3f.ZERO, 1, 1, 1);
    geom = new Geometry("Box", b);

    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Blue);
    mat.getAdditionalRenderState().setWireframe(true);
    
    geom.setMaterial(mat);
    
    rootNode.attachChild(geom);
}

@Override
public void simpleUpdate(float tpf) {
    
    Quaternion rotationVelocityFPS=new Quaternion();
    rotationVelocityFPS.slerp(Quaternion.IDENTITY, rotationVelocity, tpf);
    
    rotation.multLocal(rotationVelocityFPS);

    geom.setLocalRotation(rotation);
}

@Override
public void simpleRender(RenderManager rm) {
    //TODO: add render code
}

}
[/java]

2 Likes