Animating .obj files

There are two basic ways of animating, stop frame and interpolated over key frames. Obj files are basically stop frames (albeit interpolated by Blender over the duration of the timeline). Apparently there is no code for loading Blender produced .obj animations, so I wrote some. First, setup. I used jme3. From Blender 2.49b I exported an .obj file of a simple painted animation into a folder @ D:javamyGameassetsModelsboxFigure. myGame is the name of a jme3 project. The following code loads the .obj files, ignoring the .mtl files, and the .jpg file which has been UV’d onto the animated character. Keys are mapped:

numpad 8 moves animation to next pose

numpad 4 rotates character counterclockwise

numpad 6 rotates character clockwise

p key plays the animation

l key stops the animation



package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.light.DirectionalLight;

import com.jme3.math.Vector3f;

import com.jme3.scene.Spatial;

import com.jme3.scene.Node;

import com.jme3.font.BitmapText;

import java.io.File;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;





/**

  • objAnimation
  • @author stephenjones

    */

    public class Main extends SimpleApplication{

    private File folder=new File(“D:/java/myGame/assets/Models/boxFigure”);

    protected Node placeHolder;

    private Spatial anims[];

    private int idx, animsLen=0;

    private BitmapText say;

    private float tim=0.0f;

    private boolean doAnimate=false;





    public static void main(String[] args) {

    Main app = new Main();

    app.start();

    }



    @Override

    public void simpleInitApp() {

    placeHolder=new Node();

    loadAnims();

    initKeys();

    rootNode.attachChild(placeHolder);

    placeHolder.attachChild(anims[0]);



    DirectionalLight sun=new DirectionalLight();

    sun.setDirection(new Vector3f(-0.1f,-0.7f,-1.0f));

    rootNode.addLight(sun);



    //For debugging

    guiNode.detachAllChildren();

    guiFont=assetManager.loadFont(“Interface/Fonts/Default.fnt”);

    say=new BitmapText(guiFont, false);

    say.setSize(guiFont.getCharSet().getRenderedSize());

    say.setLocalTranslation(300, say.getLineHeight(), 0);

    guiNode.attachChild(say);

    }



    @Override

    public void simpleUpdate(float tpf) {

    //set animation frame rate

    tim+=tpf;

    if(doAnimate){

    if(tim>0.03){

    animate();

    tim=0;

    }

    }

    }



    private void loadAnims(){

    //load .obj files from named folder

    File[] fileList=folder.listFiles();

    int i, j=0;

    int len=fileList.length;

    anims=new Spatial[len];



    //fill the array with Spatials of obj files

    for(i=0;i<len;i++){

    if(fileList.getName().endsWith(".obj")){

    anims[j]=assetManager.loadModel(“Models/boxFigure/” + fileList.getName());

    j++;

    }

    }

    animsLen=j-1;

    }



    private void animate(){

    //replace the Spatial hung off placeholder with the next Spatial

    placeHolder.detachAllChildren();

    placeHolder.attachChild(anims[++idx]);

    if(idx==animsLen)

    idx=-1;

    }



    private void initKeys(){

    inputManager.addMapping(“counterClockwise”, new KeyTrigger(KeyInput.KEY_NUMPAD4));

    inputManager.addMapping(“clockwise”, new KeyTrigger(KeyInput.KEY_NUMPAD6));

    inputManager.addMapping(“next”, new KeyTrigger(KeyInput.KEY_NUMPAD8));

    inputManager.addMapping(“play”, new KeyTrigger(KeyInput.KEY_P));

    inputManager.addMapping(“stop”, new KeyTrigger(KeyInput.KEY_L));



    inputManager.addListener(actionListener, new String[]{“counterClockwise”});

    inputManager.addListener(actionListener, new String[]{“clockwise”});

    inputManager.addListener(actionListener, new String[]{“next”});

    inputManager.addListener(actionListener, new String[]{“play”});

    inputManager.addListener(actionListener, new String[]{“stop”});

    }



    private ActionListener actionListener=new ActionListener(){

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

    if(name.equals(“counterClockwise”)&&!keyPressed){

    placeHolder.rotate(0.0f,-0.5f,0.0f);

    }

    if(name.equals(“clockwise”)&&!keyPressed){

    placeHolder.rotate(0.0f,0.5f,0.0f);

    }

    if(name.equals(“next”)&&!keyPressed && !doAnimate){

    placeHolder.detachAllChildren();

    placeHolder.attachChild(anims[++idx]);

    if(idx==animsLen)

    idx=-1;

    }

    if(name.equals(“play”)&&!keyPressed){

    doAnimate=true;

    }

    if(name.equals(“stop”)&&!keyPressed){

    doAnimate=false;

    }

    }

    };



    private void debug(String str){

    say.setText(str);

    }

    }





    Points of note:

    placeHolder is a Node object and is child of RootNode. The rotation keys act upon the placeHolder, not upon the animation. This means that the rotations will not be affected by replacing the character as happens every animation frame.



    Because placeHolder is the only Node attached to rootNode the performance penalty to jme3 of running the animation is probably less than running a skeletal animation via ogre. Basically, once jme3 has loaded the sequence of .obj files and placed them into non-rendered Spatial objects (all of which can be accomplished on a separate thread), all that is left to do is swap which Spatial object is attached to the placeHolder. With bones, I think, jme3 has to calculate vertex transformations from the original mesh data as dictated by alterations of the skeleton.



    Animation of textures should be simple to implement because each .obj Spatial links to its .mtl file which links to its jpg file. One need only link in a different jpg file using the same UV layout.



    Multiple use of animations should also be easy to implement. Different characters only need to scale the mesh and wear different textures. Add to this normal maps and a single set of .obj files, loaded once can parade about as a variety of characters.



    There might be a problem with memory use, but the bonus of jme3 and the scene graph is the ability to trim the scene. The real problem Java faces, as I understand it, is speed.

That’s interesting!

Could be good to compare speed using the same animation based on bones, and with your method.

I think there could be cases where your approach is faster, but the main problem as you mentioned would be memory.

That’s a huge amount of data to store, especially for character animation.

By the way, the obj file must be huge, isn’t it?



EDIT : Tour post was posted twice I don’t know why, i deleted the other.

Every .obj file should be about the same size as a ogre.mesh.xml. Only you need as many .obj files as you want poses in any animation. But here too is another wrinkle, many if not most interpolated frames between key frames using a skeleton are unnecessary to give the illusion of animation… using a skeleton Jme3 would be calculating the position of every vertex in the animation for every game loop, I think that is right… thousands of of calculations for every vertex for every second.