Bones Frustration

Hello,



I'm writing an M2 importer, so that I can use World of Warcraft models with JME for a small AR project here at the University.



The problem is however, that there is currently no documentation whatsoever on bones, animation, controllers and basically anything generally related to animation. I've been trying to get anything to move with only the TestSimpleBoneAnimation.java to go by, but I can't get any of it to work.



Basically, M2 models have a weighted bones system. Bones are defined as a tree, and have seperate many different animations (walk, run, dance, attack etc), each split up in seperate blocks for scaling, translation and rotation.



These blocks have the same timestamp range, but not neccesairly the same number of keyframes. This means I need different BoneAnimation objects for each part of the transformation, or create my own thing altogether.



I want to try to get it to work with the standard JME stuff because at some point maybe other people might want to use M2 models and it would be nice if it integrates well with JME.



I've attached my code, maybe you guys can spot the obvious mistake?



from M2Viewer.java:


      
         RandomAccessFile file = new RandomAccessFile("Gronn.m2", "r");
         Model model = Model.read(file);

         Node node = new Node();
         node.setName(model.getHeader().getName());

         // set cull mode
         CullState CS = DisplaySystem.getDisplaySystem().getRenderer()
               .createCullState();
         CS.setCullMode(CullState.CS_NONE);
         CS.setEnabled(true);
         node.setRenderState(CS);

         // create mesh
         TriMesh mesh = new TriMesh();
         View view = model.getViews()[0];
         int numTris = view.getTriangles().length;

         Vector3f[] meshVerts = new Vector3f[numTris * 3];
         Vector3f[] meshNormals = new Vector3f[numTris * 3];
         Vector2f[] meshTexCoords = new Vector2f[numTris * 3];
         int[] meshIndex = new int[numTris * 3];

         for (int i = 0; i < numTris; i++) {
            Triangle tri = view.getTriangles()[i];
            for (int j = 0; j < 3; j++) {
               int index = i * 3 + j;

               int vertexNr = view.getIndices()[tri.getIndices()[j]];
               meshIndex[index] = index;

               Vertex v = model.getVertices()[vertexNr];
               meshVerts[index] = v.getPosition().getVector3f();
               meshNormals[index] = v.getNormal().getVector3f();
               meshTexCoords[index] = v.getTextureCoordinate()
                     .getVector2f();

            }
         }

         mesh.reconstruct(BufferUtils.createFloatBuffer(meshVerts),
               BufferUtils.createFloatBuffer(meshNormals), null,
               BufferUtils.createFloatBuffer(meshTexCoords), BufferUtils
                     .createIntBuffer(meshIndex));
         mesh.setName(model.getHeader().getName() + "_mesh_0");

         mesh.setModelBound(new BoundingBox());
         mesh.updateModelBound();
         node.attachChild(mesh);

         // create skin for the mesh
         skin = new SkinNode();
         node.attachChild(skin);
         skin.setSkin(mesh);

         // build bone tree
         jmeBones = new com.jme.animation.Bone[model.getBones().length];

         for (int i = 0; i < model.getBones().length; i++) {
            Bone bone = model.getBones()[i];
            jmeBones[i] = bone.getJMEBone();

            if (bone.getParentId() == -1) {
               // bone is a root bone, attach it to the node
               node.attachChild(jmeBones[i]);
            } else {
               // get the JME Bone object corresponding to the parent bone of this
               // M2 bone, and attach the JME Bone corresponding to the M2 Bone
               // to it. Got that? :P
               model.getBones()[bone.getParentId()].getJMEBone()
                     .attachChild(jmeBones[i]);
            }

         }

         // set bone influences
         for (int i = 0; i < numTris; i++) {
            Triangle tri = view.getTriangles()[i];
            for (int j = 0; j < 3; j++) {
               int index = i * 3 + j;
               int vertexNr = view.getIndices()[tri.getIndices()[j]];
               Vertex v = model.getVertices()[vertexNr];

               for (int k = 0; k < 4; k++)
                  skin.addBoneInfluence(0, index, jmeBones[v
                        .getBoneIndices()[k]],
                        v.getBoneWeights()[k] / 255.0f);

            }
         }

         skin.normalizeWeights();
         skin.regenInfluenceOffsets();

         rootNode.attachChild(node);

         display.getRenderer().setBackgroundColor(ColorRGBA.white);



From Bone.java:


   public com.jme.animation.Bone getJMEBone()
   {
      
      if (jmeBone==null)
      {
         jmeBone = new com.jme.animation.Bone("Bone");
         
         int numFrames = translationAnimation.getValues().length;
         
         if (numFrames>0)
         {
         
            // add animations
            BoneAnimation boneAnim = new BoneAnimation();
            
            BoneTransform bt = new BoneTransform(jmeBone,numFrames);
            
            for (int i=0; i<numFrames; i++)
               bt.setTranslation(i, translationAnimation.getValues()[i].getVector3f());

            boneAnim.addBoneTransforms(bt);
            boneAnim.setTimes(translationAnimation.getTimeStampsAsFloat());
            boneAnim.setEndFrame(numFrames-1);
            
            AnimationController animController = new AnimationController();
            animController.addAnimation(boneAnim);
            animController.setActiveAnimation(boneAnim);   
            animController.setRepeatType(Controller.RT_WRAP);
            
            jmeBone.addController(animController);
         }
         
      }
      
      return jmeBone;
      
   }




Could you describe what exactly it is that you see when using your code?

I have some problems with the animation classes as well, you can find the thread about it here: http://www.jmonkeyengine.com/jmeforum/index.php?topic=6277



Oh and I'd love to see your full code, any chance I could get a copy?

After some more hacking, I found the bug(s). I was having the problem where my model would simply not animate at all.



It turns out that to get the animation to interpolate, you have to use setInterpolate(true) because it's set to false by default.


boneAnim.setInterpolate(true);



Also for some reason the skin wasn't updating by itself, so I added


protected void simpleUpdate()
{
   skin.updateSkin();
}



The third issue I had was a bug in the BoneAnimation class. Someone has written a constructor that takes no parameters (maybe for deserialization?) that leaves the thing in an invalid state because it does not allocate the keyframeTime and interpolationType members. The constructor that only takes a string has the same problem. The result was that when I enabled interpolation, the thing promptly threw a NullpointerException.

It might be better to either document that your constructor leaves an object in an invalid state, or make sure it doesn't.

I have something that distorts my Mesh now, but not remotely in the way I intended. I'm not sure if that's because of me or because of JME, but I suspect the fault is mine.

When the code is finished, I'll post it here.