3DS frame animation HELP!

Started by nnyerges, October 07, 2014, 04:36:33 PM

Previous topic - Next topic

nnyerges

Hello,
In my example, I have a <flag object> in MD2 format, placed in my animation code and works perfectly. However, since MD2 is an old single byte and obsolete format, it's preferable to use 3DS for animation.  I am learning to use 3DS for animation and follow all jPCT documentation, examples and forum topics about.  Using Blender 2.72, I managed to create all the 3DS keyframes files of the <flag object> and add them to my modified animation code.  Everything loads ok, but the FLAG doesn't MOVE.

I have been hours treating to see why. Don't see where it is the problem. PLEASE HELP!

The following code,  is to check the mesh's, preloading and post animation:

Object3D flag = new Object3D(load3DSModel("xflag/flag_000001.3ds", 40 * h / 552));
flag.build();
Mesh M1 = flag.getMesh();

Object3D flag2 = new Object3D(load3DSModel("xflag/flag_000002.3ds", 40 * h / 552));
flag2.strip();
flag2.build();
Mesh M2 = flag2.getMesh();

System.out.println("------------   CHECK INITIAL MESH's -------------");
if (M1 == M2) {
System.out.println("M1 = M2");
} else {
System.out.println("M1 << >> M2");  // <<<< PERFECT >>>>
}

- Load frame "flag" and store the corresponding  mesh in "M1", for future verification
- Load the second frame "flag2" and store the mesh in "M2".
- Verify that M1 is different than M2. Please note that I check both 3DS files, and have the same number of vertex, but in different positions.

flaganim = new Animation(2);
flaganim.setInterpolationMethod(Animation.LINEAR);
flaganim.createSubSequence("example");
flaganim.addKeyFrame(M1);
flaganim.addKeyFrame(M2);

flag.setAnimationSequence(flaganim);

world.addObject(flag);

world.buildAllObjects();

- Add M1 and M2 to the animation sequence "flaganim"
- Assign the animation to "flag"
- Add "flag" to WORLD

flag.animate(0, 0);
Mesh posM1 = flag.getMesh();

flag.animate(1, 0);
Mesh posM2 = flag.getMesh();

System.out.println("------------   CHECK MESH BASED ON ANIMATION -------------");
if (posM1 == posM2) {
System.out.println("posM1 = posM2");  //  <<<<< THIS IS MY PROBLEM  >>>>
} else {
System.out.println("posM1 << >> posM2");
}

- Calculate a new mesh for object "flag", based on the first keyframe of its animation sequence "flag.animate (0, 0)" and save the new mesh to "posM1" for future verification.
- Re/Calculate a new mesh for object "flag", based on the LAST (second) keyframe of its animation sequence "flag.animate (1, 0)" and save the new mesh to "posM2".
- Compare posM1 with posM2 and they ARE EQUAL ????. For that reason the flag doesn't move.

jPCT is calculating two same mesh, but WHY? WHAT I AM MAKING WRONG?

EgonOlsen

The mesh instance returned by flag.getMesh() will always be the same, no matter which animation sequence is called with which index. In your case, you should have three mesh instances. One belongs to the Object3D of flag, which is what getMesh() returns. That's the one that's supposed to be modified by the animate()-calls. Then you should have one for each key frame. In your case (and the code in the wiki is misleading here, i've modified it some minutes ago to be more clear on this), your first keyframe and the animation share the same instance of Mesh. That's not a good idea.

Try to change this


flaganim.addKeyFrame(M1);


to this:


flaganim.addKeyFrame(M1.cloneMesh(true));


For proper animation testing, you might want to iterate your index from 0..1 to see what happens. If you only use 0 and 1, you might not see much of a change depending on your animation.

nnyerges

Dear Egon

First of all, thank you very much for your quick response and sorry for not having answered before, since I was on a work trip. This weekend I will try what you are telling me. I'll keep you informed.

Chears

nnyerges

Dear Egon,

I did what you suggest without any changes.  I really don't know what is going on. Maybe I'm doing something wrong with the .3ds files.  Attached you will find the original .md2 flag animation file. I use Blender 2.49b to import the md2 file and export the first three keyframes into the three .3ds files (attached) that I use in my jPCT example. If you can take your time and see where the problem is is, will be a lot of help. Note: I also attach the .blender file where all the import export job was done.

Here is the onSurfaceChanged code:
@Override
public void onSurfaceChanged(GL10 gl, int w, int h) {

if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(gl, w, h);

if (master == null) {

world = new World();
world.setAmbientLight(50, 50, 50);

cam = world.getCamera();
float yfov = (float) (2 * Math.atan(cam.getFOV() / 2 * h / w)); // <<---OK
cam.setYFovAngle(yfov);
float z = (float) (0.5 * h / (10 * Math.tan(yfov / 2)));
cam.setPosition(0, -10 / scaleY, -z);
cam.lookAt(SimpleVector.ORIGIN);


// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

flag = new Object3D(load3DSModel("xflag/flag1.3ds", 4 * h / 552));
flag.build();
flaganim = new Animation(3);
flaganim.createSubSequence("example");
flaganim.setInterpolationMethod(Animation.LINEAR);

Object3D flagframes = new Object3D(load3DSModel("xflag/flag2.3ds", 4 * h / 552));
flagframes.calcBoundingBox();
flagframes.calcNormals();
flaganim.addKeyFrame(flagframes.getMesh());

flagframes = new Object3D(load3DSModel("xflag/flag3.3ds", 4 * h / 552));
flagframes.calcBoundingBox();
flagframes.calcNormals();
flaganim.addKeyFrame(flagframes.getMesh());

flag.setAnimationSequence(flaganim);
world.addObject(flag);

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

world.buildAllObjects();

MemoryHelper.compact();
if (master == null) {
master = CopyOfActivityT.this;
}
}
}


The onDrawFrame code:
@Override
public void onDrawFrame(GL10 arg0) {


// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

flagkey += 0.018f;
if (flagkey > 1f) {
flagkey -= 1f;
}
flag.animate(flagkey, 1);

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

fb.clear(transp);
world.renderScene(fb);
world.draw(fb);
fb.display();

}


(Note: for test simplicity, I'm not using the texture file of the object, but is attached anyway).

flagtest1.zip = flag.md2 + flag.png + flag248.blender
flagtest2.zip = the three .3ds files ( becouse upload size limitations, I will attach this zip in the next post)
Thanks in advance.

nnyerges


EgonOlsen

Actually, jPCT-AE is nice to you and tells you what's wrong in the log... ;)


10-11 19:43:56.729: W/jPCT-AE(1362): [ 1413056636730 ] - WARNING: You are adding an Animation to an Object3D that has already been build in static mode. Consider to use { calcNormals(); calcBoundingBox(); } instead of build() and call build() only after the animation has been set.


What this tries to express is: When you call build() on an object, the engine has to decide the mode in which this object should be compiled. By default and if you haven't done an explicit call to compile(...) before, this depends on the fact if an animation and/or vertex controller has been assigned to it or not. In your case, you are calling build() before adding the animation. Don't do that but do (just like the log message says) replace it with calls to calcNormals(); calcBoundingBox(); instead.

So...instead of


flag = new Object3D(load3DSModel("flag1.3ds", 4 * h / 552));
flag.build();
flaganim = new Animation(3);
flaganim.createSubSequence("example");
flaganim.setInterpolationMethod(Animation.LINEAR);


do


flag = new Object3D(load3DSModel("flag1.3ds", 4 * h / 552));
flag.calcBoundingBox();
flag.calcNormals();
flaganim = new Animation(3);
flaganim.createSubSequence("example");
flaganim.setInterpolationMethod(Animation.LINEAR);


And add the flag.build() to after assigning the animation.

The wiki entry doesn't really make this clear, because it has been written for desktop jPCT, which is a bit different in this regard. For the reference, here's a working example:

package com.example.flagtest;

import java.io.IOException;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

import com.threed.jpct.Animation;
import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Loader;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;
import com.threed.jpct.util.MemoryHelper;

/**
* @author EgonOlsen
*
*/
public class Flaggy extends Activity {

private GLSurfaceView mGLView;
private MyRenderer renderer;
private FrameBuffer fb;
private World world;
private RGBColor back = new RGBColor(50, 50, 100);

private Object3D flag;
private Animation flaganim;
private Camera cam;
private int fps = 0;
private float flagkey = 0;
private float scaleY = 1;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(getApplication());
mGLView.setEGLContextClientVersion(2);
renderer = new MyRenderer();
mGLView.setRenderer(renderer);
setContentView(mGLView);
}

@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}

@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}

@Override
protected void onStop() {
super.onStop();
System.exit(0);
}

protected boolean isFullscreenOpaque() {
return true;
}

class MyRenderer implements GLSurfaceView.Renderer {

private long time = System.currentTimeMillis();

public MyRenderer() {
}

public void onSurfaceChanged(GL10 gl, int w, int h) {
if (fb != null) {
fb.dispose();
}
fb = new FrameBuffer(w, h);

world = new World();
world.setAmbientLight(50, 50, 50);

cam = world.getCamera();
float yfov = (float) (2 * Math.atan(cam.getFOV() / 2 * h / w));
cam.setYFovAngle(yfov);
float z = (float) (0.5 * h / (10 * Math.tan(yfov / 2)));
cam.setPosition(0, -10 / scaleY, -z);
cam.lookAt(SimpleVector.ORIGIN);

flag = new Object3D(load3DSModel("flag1.3ds", 4 * h / 552));
flag.calcBoundingBox();
flag.calcNormals();
flaganim = new Animation(3);
flaganim.createSubSequence("example");
flaganim.setInterpolationMethod(Animation.LINEAR);

Object3D flagframes = new Object3D(load3DSModel("flag2.3ds", 4 * h / 552));
flagframes.calcBoundingBox();
flagframes.calcNormals();
flaganim.addKeyFrame(flagframes.getMesh());

flagframes = new Object3D(load3DSModel("flag3.3ds", 4 * h / 552));
flagframes.calcBoundingBox();
flagframes.calcNormals();
flaganim.addKeyFrame(flagframes.getMesh());

flag.setAnimationSequence(flaganim);
flag.build();
world.addObject(flag);

MemoryHelper.compact();
}

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}

private Object3D load3DSModel(String name, float scale) {
try {
Object3D[] objs = Loader.load3DS(Flaggy.this.getBaseContext().getAssets().open(name), scale);
return objs[0];
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void onDrawFrame(GL10 gl) {
flagkey += 0.018f;
if (flagkey > 1f) {
flagkey -= 1f;
}
flag.animate(flagkey, 1);

fb.clear(back);
world.renderScene(fb);
world.draw(fb);
fb.display();

if (System.currentTimeMillis() - time >= 1000) {
Logger.log(fps + "fps");
fps = 0;
time = System.currentTimeMillis();
}
fps++;
}
}
}

nnyerges

Dear Egon

1.
Again, thank you for jPCT and this excellent response and effort. That was all; simple changes in the instructions sequence LOL !!! / IT WORKS!.
T  h a n k   y o u !

2. One last question about built() method. I read that the built() it's a heavy method and cost a little performance. Inside jPCT, applying built() for each object 3D being created at the time, cost the same that applying one "buildAllObjects()"? or is there any benefic using "buildAllObjects()"?

Regards,
Nicolás Nyerges Miklossy

EgonOlsen

No, buildAllObjects() just builds everything at once. The costs are the same as individual calls for each object. I once added it on request, personally i don't use it in most cases.
If build() turns out to be slow for you (it's usually not that bad but can be noticable on large objects), yon can always switch to serialized objects, which will build (and load) faster: http://www.jpct.net/wiki/index.php/Differences_between_jPCT_and_jPCT-AE#Performance_and_memory_issues.2C_serialized_objects