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.