Bug in AnimationTrack and rotation

@nehon I found another bug while rotating a box in AnimationTrack. Seems that I can’t rotate negatively from a negative angle to another negative angle w/o something weird happening with the animation. Look at the testCase below:



[java]

package mygame;





import com.jme3.animation.AnimControl;

import com.jme3.animation.AnimationFactory;

import com.jme3.animation.LoopMode;

import com.jme3.cinematic.Cinematic;

import com.jme3.cinematic.events.CinematicEvent;

import com.jme3.app.SimpleApplication;

import com.jme3.cinematic.PlayState;

import com.jme3.cinematic.events.AnimationTrack;

import com.jme3.cinematic.events.CinematicEventListener;

import com.jme3.cinematic.events.PositionTrack;

import com.jme3.input.ChaseCamera;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Box;

import java.io.File;

import java.util.concurrent.Callable;

import java.util.concurrent.ScheduledThreadPoolExecutor;

import javax.swing.JOptionPane;



public class RotationBug extends SimpleApplication {



private Spatial model;

private Spatial podModel;



private Cinematic cinematic;

private ChaseCamera chaseCam;

private ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(2);





public static void main(String[] args) {

RotationBug app = new RotationBug();

app.start();



}



@Override

public void simpleInitApp() {



createScene();



cinematic = new Cinematic(rootNode, 1000);

stateManager.attach(cinematic);



AnimationFactory factory = new AnimationFactory(10, “animation”);

factory.addTimeTranslation(0, new Vector3f(0, 0, 0));

factory.addTimeRotationAngles(2, 0, 0, -3.14f); // - 90 degrees

factory.addTimeTranslation(10, new Vector3f(5, 0, 0));



AnimControl control = model.getControl(AnimControl.class);

if( control == null){

control = new AnimControl();

model.addControl(control);

}



control.addAnim(factory.buildAnimation());



cinematic.addCinematicEvent(0, new AnimationTrack(model, “animation”));





Quaternion rotQuat = new Quaternion().fromAngleAxis( -3.14f, Vector3f.UNIT_Z);



AnimationFactory factory2 = new AnimationFactory(10, “animation2”);

factory2.addTimeTranslation(0, new Vector3f(5, 0, 0));

factory2.addTimeRotation(0, rotQuat);

factory2.addTimeRotationAngles(10, 0, 0, -1.57f); // - 90 degrees



AnimControl control2 = model.getControl(AnimControl.class);

if( control2 == null){

control2 = new AnimControl();

model.addControl(control2);

}



control2.addAnim(factory2.buildAnimation());



cinematic.addCinematicEvent(10.1f, new AnimationTrack(model, “animation2”));





AnimationFactory factory3 = new AnimationFactory(3, “animation3”);

factory3.addTimeTranslation(0, new Vector3f(0, 0, 1));

factory3.addTimeTranslation(3, new Vector3f(5, 0, 1));



AnimControl control3 = podModel.getControl(AnimControl.class);

if( control3 == null){

control3 = new AnimControl();

podModel.addControl(control3);

}



control3.addAnim(factory3.buildAnimation());



cinematic.addCinematicEvent(8.1f, new AnimationTrack(podModel, “animation3”));



cinematic.addListener(new CinematicEventListener() {



public void onPlay(CinematicEvent s) {

chaseCam.setEnabled(false);

System.out.println(“play”);

System.out.println("Animation Speed: " + cinematic.getSpeed());

System.out.println("Animation State: " + cinematic.getPlayState());

System.out.println("Animation Time: " + cinematic.getTime());

}



public void onPause(CinematicEvent cinematic) {

chaseCam.setEnabled(false);

System.out.println(“pause”);

System.out.println("Animation Speed: " + cinematic.getSpeed());

System.out.println("Animation State: " + cinematic.getPlayState());

System.out.println("Animation Time: " + cinematic.getTime());

}



public void onStop(CinematicEvent cinematic) {

chaseCam.setEnabled(false);

// fade.setValue(1);

System.out.println(“stop”);

}

});



flyCam.setEnabled(false);

chaseCam = new ChaseCamera(cam, model, inputManager);



initInputs();

cinematic.setSpeed(1);



}





Callable jumpBackwards = new Callable() {



public Object call() throws Exception {

try {

final float value = Float.parseFloat(JOptionPane.showInputDialog(null,

“Enter Time”,

“Choose Desired Jump Time”,

JOptionPane.QUESTION_MESSAGE));



RotationBug.this.enqueue(new Callable<Void>() {



public Void call() throws Exception {

cinematic.setTime(value);

return null;

}

});



} catch (Exception nFE) {

// do nothing just cancel command

}

return null;

}

};







private void createScene() {



Box pod = new Box(new Vector3f(0, 0, 1f), 1, .25f, .5f);

podModel = new Geometry(“pod #”, pod);

Material mat2 = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

mat2.setColor(“Color”, ColorRGBA.Blue);

podModel.setMaterial(mat2);



Box drive_unit = new Box(new Vector3f(0, 0, 0), 1, .25f, .5f); // must take from config xml file (TODO)

model = new Geometry(“Drive #”, drive_unit);

Material mat1 = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

mat1.setColor(“Color”, ColorRGBA.Orange);

model.setMaterial(mat1);



model.center();

//podModel.center();



rootNode.attachChild(model);

rootNode.attachChild(podModel);



//jumpBackwards();



Material matSoil = new Material(assetManager, “Common/MatDefs/Light/Lighting.j3md”);

matSoil.setBoolean(“UseMaterialColors”, true);

matSoil.setColor(“Ambient”, ColorRGBA.Gray);

matSoil.setColor(“Diffuse”, ColorRGBA.Green);

matSoil.setColor(“Specular”, ColorRGBA.Black);



}



private void initInputs() {

inputManager.addMapping(“togglePause”, new KeyTrigger(keyInput.KEY_RETURN));

inputManager.addMapping(“jump”, new KeyTrigger(keyInput.KEY_J));

inputManager.addMapping(“increaseSpeed”, new KeyTrigger(keyInput.KEY_EQUALS));

ActionListener acl = new ActionListener() {



public void onAction(String name, boolean keyPressed, float tpf) {

if (name.equals(“togglePause”) && keyPressed) {

if (cinematic.getPlayState() == PlayState.Playing) {

cinematic.pause();

} else {

cinematic.play();

}

}



if (name.equals(“jump”) && keyPressed) {

System.out.println("Time skipped to t = "

  • cinematic.getTime());



    exec.submit(jumpBackwards);



    }



    else if (name.equals("increaseSpeed") && keyPressed) {



    //pause to maintain animation accuracy

    //cinematic.pause();

    exec.submit(increaseSpeed);

    // exec.submit(increaseSpeed);

    // increase animation speed by 1x

    //cinematic.setSpeed(cinematic.getSpeed() + 1);

    }





    }

    };

    inputManager.addListener(acl, "togglePause");

    inputManager.addListener(acl, "jump");

    inputManager.addListener(acl, "increaseSpeed");

    }



    /** Jump time function. Allows user to jump
  • in time during the animation (forward/backward)

    /

    Callable jumpTime = new Callable() {



    public Object call() throws Exception {

    try {



    //setPauseOnLostFocus(false);

    final float value = Float.parseFloat(JOptionPane.showInputDialog(null,

    "Enter Time",

    "Choose Desired Jump Time",

    JOptionPane.QUESTION_MESSAGE));





    RotationBug.this.enqueue(new Callable<Void>() {



    public Void call() throws Exception {



    cinematic.setTime(value);

    System.out.println("Jumping to time: "+cinematic.getTime());

    return null;

    }

    });



    } catch (Exception nFE) {

    // do nothing just cancel command

    System.out.println("Exception: Jumping in time failed");

    }

    return null;

    }

    };







    /
    * Jump time function. Allows user to jump
  • in time during the animation (forward/backward)

    */

    Callable increaseSpeed = new Callable() {



    public Object call() throws Exception {

    try {



    RotationBug.this.enqueue(new Callable<Void>() {



    public Void call() throws Exception {

    cinematic.pause();

    cinematic.setSpeed(cinematic.getSpeed() + 1);

    return null;

    }

    });



    } catch (Exception nFE) {

    // do nothing just cancel command

    }

    return null;

    }

    };

    }

    [/java]



    notice how the orange box is tilting to the side while it should only rotate. I am noticing this in many angles, but mainly noticed it whenever the final angle is negative.

define “something weird”

while rotating the box would go up and down suddenly as if a car hit a speed bump. Depending on which axis you are rotating about there is an additional TILT occuring.

I don’t get the “speed bump hit” with your test case.



I got the twist though, but it’s because you are first passing a quaternion and then passing euler angles

[java]

Quaternion rotQuat = new Quaternion().fromAngleAxis(-3.14f, Vector3f.UNIT_Z);

factory2.addTimeRotation(0,rotQuat);

factory2.addTimeRotationAngles(10, 0, 0, -1.57f); // - 90 degrees

[/java]

This is resolved by the AnimationFactory by transforming the first quaternion to euler angles and then interpolating through the second rotation.

The problem is that a quaternion can have several euler angles representation. It’s not a problem when you’re applying a rotation, but it’s a huge porblem when interpolating.

so basically, your code will just end up in an unexpected result.



If you want to use Euler angles, use them for ALL rotations in the animation, but don’t mix.

[java]

factory2.addTimeRotationAngles(0, 0, 0, -3.14f);

factory2.addTimeRotationAngles(10, 0, 0, -1.57f); // - 90 degrees

[/java]



but first update to this http://code.google.com/p/jmonkeyengine/source/detail?r=9246

or you’ll have an arrayIndexOutOfBound.

1 Like

Noted, that makes sense. I tried it now after the update and it works flawlessly.



thanks!!!