Skeletal animation source

Can somebody provide a link to an example of doing skeletal animation using jME?

For example, the source for Collada image demo that shows a walking man would be perfect.

Read this thread: More documentation for Bone, BoneAnimation, BoneTransform and BoneDebugger.



Mojomonkey answered me putting an example in the test package of jME.

that example in that thread doesnt quite work and has no comments.



Here's a few questions i have:


  1. With skin.addBoneInfluence()  how are the vertices referenced for each tri/quad ? counter/clockwise? also, the vertices do not seem to be shared between adjacent polygons?!  in the example below, i define a cube box. so to move 2 facets, why do i need to reference 8 vertices, even though 2 vertices are common between them?



    In other words, if i have a complex mesh as the "skin", how do i know which vertices belong where in the skin texture?



    The combined(?) bone weight should add up to 1. Combination of what? vertices in one bone, or one polygon?



    2.        theBone.updateGeometricState(0, true);  what units is 0 ??



    3.  If i do bone transformation in SimpleUpdate() how come i do not see it reflected on the screen?


  2. Any code snippets for using BoneTransform & BOneAnimation?





    PS. The goal of this sample program is to animate  one (or two cube faces) slide off the main model.



import com.jme.animation.Bone;
import com.jme.animation.SkinNode;
import com.jme.animation.*;
import com.jme.app.AbstractGame;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.NodeHandler;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.util.BoneDebugger;
import com.jme.util.TextureManager;

// Created from TestSampleBoneAnimation

public class Stickman extends SimpleGame {

    SkinNode mySkin;
    Bone theBone, theBone2;
   int counter=0;


    public static void main(String[] args) {
        Stickman game = new Stickman();
        game.start();
    }

    protected void simpleRender() {
        BoneDebugger.drawBones(rootNode, display.getRenderer(), true);
    }

    protected void simpleUpdate() {
/*
   long time=System.currentTimeMillis();
//   while (System.currentTimeMillis()-time < 10000) { }

        theBone.setLocalTranslation(new Vector3f(-20, 0, 0));
        theBone.updateGeometricState(1, true);
        mySkin.normalizeWeights();
        mySkin.regenInfluenceOffsets();
dx=dx+1.0f;
   if (++counter > 1000) counter=0;
   System.err.println("hi");
*/
    }

    protected void simpleInitGame() {
        Node modelNode = new Node("model");

   // create a box object, centered at (0,0,0) and its extent from center (vector  2,0.5,0.5)
        Box b = new Box("test", new Vector3f(0, 0, 0), 5f, 5f, 5f);

   // set bounding box for box ;)
        b.setModelBound(new BoundingBox());
        b.updateModelBound();

        mySkin = new SkinNode("test skin");
        mySkin.setSkin(b);
        modelNode.attachChild(mySkin);

   // Give Box object blue color
        MaterialState ms = display.getRenderer().createMaterialState();
        ms.setSpecular(new ColorRGBA(0.0f, 0.0f, 0.9f, 1));
        ms.setShininess(10);
        b.setRenderState(ms);

        theBone = new Bone("Bone01");

        int[] verts = new int[] { 0, 1, 2, 3,       // first face of box
            4, 5, 6, 7      // second face
                };
        float[] weights = new float[] { 0.1f, 0.1f, 0.1f, 0.1f,    // weights for 1st face
               0.1f, 0.1f, 0.1f, 0.1f    // weight for 2nd face
                 };


        for (int x = 0; x < verts.length; x++) {
            mySkin.addBoneInfluence(0, verts[x], theBone, weights[x]);
        }

        /// WHAT DOES THIS DO ??
   // i think this attaches bone to "global" model
   // so that bone structure (skeleton) can be displayed
   // by BoneDebugger
   modelNode.attachChild(theBone);

        theBone2 = new Bone("Bone02");
        int[] verts2 = new int[] { 0, 13, 20, 3, 14, 19, 9, 12, 21, 10, 15, 18 };
        float[] weights2 = new float[] { 0.536468f, 0.536468f, 0.536468f,
                0.600621f, 0.600621f, 0.600621f, 0.960102f, 0.960102f,
                0.960102f, 0.961855f, 0.961855f, 0.961855f };

        Quaternion b1Q1 = new Quaternion().fromAngleAxis(-0.00123886f,
                new Vector3f(0, 0, 1));
        Quaternion b1Q2 = new Quaternion().fromAngleAxis(-86.3343f,
                new Vector3f(0, 1, 0));
        Quaternion b1Q3 = new Quaternion().fromAngleAxis(90.0012f,
                new Vector3f(1, 0, 0));

   // i think this should be a unit vector?? NO
        theBone.setLocalTranslation(new Vector3f(-20.0f, 0, 0));

   // what 0 means?
   // what units?
        theBone.updateGeometricState(0, true);
        mySkin.normalizeWeights();
        mySkin.regenInfluenceOffsets();

        rootNode.attachChild(modelNode);

int nFrames=5;  // 5 animation frames
BoneTransform bt = new BoneTransform(theBone, nFrames);
for (int i=0; i<nFrames; i++) {
   bt.setTranslation(i, new Vector3f(i,0,0));
}

BoneAnimation ba = new BoneAnimation("ani1", theBone, nFrames);
ba.addBoneTransforms(bt);


        this.input = new NodeHandler(modelNode, 10, 10);
    }

}

sust said:
that example in that thread doesnt quite work and has no comments.


If you mean the code I posted, I agree: it does not work. Though the example I meant was not mine but is the one placed in the CVS source code of jME, inside the test package. That example works for me.

I only wanted to mention that using the value 10 for the mouse speed parameter in the NodeHandler constructor was way too much on my machine. Changing it to 1 seemed to make it more sane.

I am extremely interested to find the same answers you are looking for. Hope someone who knows can reply soon.

Stan

Ender, what's the name of the file in test package that you're talking about?



I did not find anything there. The collada test simply reads the animation from file, which is kinda useless to me.

My bone & animation data comes from a proprietary format, so i need to understand how to build bone mesh  & its animation "manually", so to speak.



The most puzzling thing about the sample program i posted earlier is that the animation transform works during intial setup, but seems to be ignored on subsequent redraws. I need to call some update method, but without any documentation, this is a mystery :wink:

jmetest.renderer.TestSimpleBoneAnimation



Of course, I did not mean the Collada import test.

Ender said:

jmetest.renderer.TestSimpleBoneAnimation

Of course, I did not mean the Collada import test.


that's what i was working with, however lots of ???s.

I simplied this test file to have a normal 8-sided cube as object, no texture. One bone is one of its sides.  But, i cant get the bone animation to work.

Do you have any working code snippets for using BoneTranformation, BoneAnimation and AnimationController?

sust said:
that's what i was working with, however lots of ???s.


A lot of  :?  :?  :?  :? here too.

You was talking about collada test, as you said in the quote below.

sust said:
I did not find anything there. The collada test simply reads the animation from file, which is kinda useless to me.


So, I guessed that you have not found the the TestSimpleBoneAnimation test, that has no import code in it self.

sust said:
Do you have any working code snippets for using BoneTranformation, BoneAnimation and AnimationController?


My code is basically the same of TestSimpleBoneAnimation. I am trying to understand how to move correctly the bones. Because everything seems messy... but bones rotate and work even if my code does not.

Ok I just fixed the test I made and now it is working. This would be part of the SkinNode and BoneAnimation support of the next and refactored MD5 Reader 2. After you will have launched the application you will be able to notice, if you use W key to approach the bones, axis rods rotating around Y axis. I used axis rods to debug rotations. They are children of bone1. So, they are allways rotated like their parent.

Now if you follow the TestSimpleBoneAnimation example you should be able to add a SkinNode too.



/

*

*/

package org.md5reader2.test;



import com.jme.animation.AnimationController;

import com.jme.animation.Bone;

import com.jme.animation.BoneAnimation;

import com.jme.animation.BoneTransform;

import com.jme.animation.SkinNode;

import com.jme.app.AbstractGame;

import com.jme.app.SimpleGame;

import com.jme.bounding.BoundingBox;

import com.jme.math.Matrix3f;

import com.jme.math.Matrix4f;

import com.jme.math.Quaternion;

import com.jme.math.TransformMatrix;

import com.jme.math.Vector3f;

import com.jme.renderer.ColorRGBA;

import com.jme.scene.Controller;

import com.jme.scene.Node;

import com.jme.scene.shape.AxisRods;

import com.jme.scene.shape.Box;

import com.jme.scene.shape.Capsule;

import com.jme.scene.state.MaterialState;

import com.jme.util.BoneDebugger;



/

  • @author ender

    *

    /

    public class TestBoneAnimation extends SimpleGame {

    /

  • (non-Javadoc)

    *
  • @see com.jme.app.BaseSimpleGame#simpleInitGame()

    */

    @Override

    protected void simpleInitGame() {

    // Create 3 vectors rapresenting local axis.

    Vector3f axisX = new Vector3f(1f, 0f, 0f);

    Vector3f axisY = new Vector3f(0f, 1f, 0f);

    Vector3f axisZ = new Vector3f(0f, 0f, 1f);



    // Set up the scenegraph

    Node model = new Node("Model");



    Bone bone1 = new Bone("Bone");

    model.attachChild(bone1);



    Bone bone2 = new Bone("Bone 2");

    bone1.attachChild(bone2);



    // Setup axis rods

    AxisRods ar = new AxisRods("Bone's Axis", true, 0.05f);

    ar.setModelBound(new BoundingBox());

    ar.updateModelBound();

    bone1.attachChild(ar);



    // Update

    //bone1.updateGeometricState(0, true);



    // Prepare bones beginning transformations.

    Quaternion b1Q1 = new Quaternion().fromAngleAxis(0f, axisZ);

    Quaternion b1Q2 = new Quaternion().fromAngleAxis(0f, axisY);

    Quaternion b1Q3 = new Quaternion().fromAngleAxis(0f, axisX);



    bone1.setLocalRotation(b1Q1.mult(b1Q2).mult(b1Q3));

    bone1.setLocalTranslation(new Vector3f(0.0f, 0.0f,

    0.0f));



    Quaternion b2Q1 = new Quaternion().fromAngleAxis(0f, axisZ);

    Quaternion b2Q2 = new Quaternion().fromAngleAxis(0f, axisY);

    Quaternion b2Q3 = new Quaternion().fromAngleAxis(0f, axisX);



    bone2.setLocalRotation(b2Q1.mult(b2Q2).mult(b2Q3));

    bone2.setLocalTranslation(new Vector3f(0.0f, 2.0f,

    0.0f));



    // Update again

    //bone1.updateGeometricState(0, true);



    // Add a material state for axis rods

    MaterialState arMs = display.getRenderer().createMaterialState();

    arMs.setDiffuse(ColorRGBA.cyan);

    ar.setRenderState(arMs);

    ar.updateRenderState();



    ar.setLocalTranslation(0f, 0f, 0f);

    ar.setLocalScale(10f);



    // Update again

    bone1.updateGeometricState(0, true);



    rootNode.attachChild(model);



    // Animation setup



    // Prepare transformations

    Quaternion[] rotations = new Quaternion[5];

    rotations[0] = new Quaternion().fromAngleAxis((float) Math

    .toRadians(3.5), axisY);

    rotations[1] = new Quaternion().fromAngleAxis((float) Math

    .toRadians(11.38), axisY);

    rotations[2] = new Quaternion().fromAngleAxis((float) Math

    .toRadians(34.53), axisY);

    rotations[3] = new Quaternion().fromAngleAxis((float) Math

    .toRadians(41.1), axisY);

    rotations[4] = new Quaternion().fromAngleAxis((float) Math

    .toRadians(57.13), axisY);



    Vector3f[] translations = new Vector3f[5];

    translations[0] = new Vector3f(0f, 0f, 0f);

    translations[1] = new Vector3f(0f, 0f, 0f);

    translations[2] = new Vector3f(0f, 0f, 0f);

    translations[3] = new Vector3f(0f, 0f, 0f);

    translations[4] = new Vector3f(0f, 0f, 0f);



    // Setup BoneTransform.

    BoneTransform boneTrans = new BoneTransform();

    boneTrans.setBone(bone1);

    boneTrans.setRotations(rotations);

    boneTrans.setTranslations(translations);



    // Add boneTransform to boneAnim.

    BoneAnimation boneAnim = new BoneAnimation("Bone Animimation");

    boneAnim.addBoneTransforms(boneTrans);



    // Prepare times.

    float[] times = new float[5];

    times[0] = 0.0f;

    times[1] = 1.0f;

    times[2] = 2.0f;

    times[3] = 3.0f;

    times[4] = 4.0f;



    boneAnim.setTimes(times);

    boneAnim.setEndFrame(4);



    // Setup AnimationController.

    AnimationController ac = new AnimationController();

    ac.addAnimation(boneAnim);

    ac.setActiveAnimation(boneAnim);

    ac.setRepeatType(Controller.RT_WRAP);



    bone1.addController(ac);

    }



    @Override

    protected void simpleRender() {

    BoneDebugger.drawBones(rootNode, display.getRenderer(), true);

    }



    @Override

    protected void simpleUpdate() {

    // Debug.

    // System.out.println("boneAnim's Frame: " +

    // boneAnim.getCurrentFrame());

    // System.out.println("boneAnim's Time: " + boneAnim.getCurrentTime());

    // System.out.println("boneAnim's Interpolation: "

    // + boneAnim.getInterpolationRate());

    //

    // float[] angles = new float[3];

    // bone.getLocalRotation().toAngles(angles);

    // System.out.println("bone's LocalRot: " + angles[0] + ", " + angles[1]

    // + ", " + angles[2]);

    }



    /**
  • @param args

    */

    public static void main(String[] args) {

    TestBoneAnimation app = new TestBoneAnimation();

    app

    .setDialogBehaviour(AbstractGame.FIRSTRUN_OR_NOCONFIGFILE_SHOW_PROPS_DIALOG);

    app.start();

    }



    }

Ender, THX A LOT!



i've finally made some progress.



have u noticed that not all frames are drawn? for example, i modified your code to have 20 frames. With each frame X, the bone is supposed to be translated by +X units. So, at time 0, bone is at 0,0,0, at time 1 @ 1,0,0 and so on. BUT, animation seems to ALWAYS start at frame 1, and very quickly goes thru frame 19 (compared to other frames).



Is this a bug in JME?





  protected void simpleUpdate() {
      // Debug.
       System.out.println("boneAnim's Frame: " +       _boneAnim.getCurrentFrame());
       System.out.println("boneAnim's Time: " + _boneAnim.getCurrentTime());
      
       Vector3f loc = _bone.getLocalTranslation();
       System.out.println("bone's translation: " + loc);       
   }




    // Animation setup
int nFrames = 20;

      // Prepare transformations
      Quaternion[] rotations = new Quaternion[nFrames];

for (int qq=0;qq<nFrames;qq++){
 rotations[qq] = new Quaternion();
}

      Vector3f[] translations = new Vector3f[nFrames];
      for (int i=0; i<nFrames;i++) {
         translations[i] = new Vector3f((float)i, 0f, 0f);
   }

      // Setup BoneTransform.
      BoneTransform boneTrans = new BoneTransform();
      boneTrans.setBone(bone1);
//BUG: Must set rotations to something or null ptr exception!
      boneTrans.setRotations(rotations);
      boneTrans.setTranslations(translations);

      // Add boneTransform to boneAnim.
      BoneAnimation boneAnim = new BoneAnimation("Bone Animimation");
      boneAnim.addBoneTransforms(boneTrans);
     
      // Prepare times.
      float[] times = new float[nFrames];
      for (int i=0; i<nFrames;i++) {
        times[i] = (float)i;
   }

      boneAnim.setTimes(times);
      boneAnim.setStartFrame(0);         
      boneAnim.setEndFrame(nFrames-1);




boneAnim's Frame: 18
boneAnim's Time: 17.99911
bone's translation: com.jme.math.Vector3f [X=18.0, Y=0.0, Z=0.0]
boneAnim's Frame: 18
boneAnim's Time: 17.999891
bone's translation: com.jme.math.Vector3f [X=18.0, Y=0.0, Z=0.0]
boneAnim's Frame: 19
boneAnim's Time: 18.00064
bone's translation: com.jme.math.Vector3f [X=19.0, Y=0.0, Z=0.0]
boneAnim's Frame: 1
boneAnim's Time: 7.5E-4
bone's translation: com.jme.math.Vector3f [X=1.0, Y=0.0, Z=0.0]
boneAnim's Frame: 1
boneAnim's Time: 0.0015
bone's translation: com.jme.math.Vector3f [X=1.0, Y=0.0, Z=0.0]
boneAnim's Frame: 1
boneAnim's Time: 0.00221875
bone's translation: com.jme.math.Vector3f [X=1.0, Y=0.0, Z=0.0]

Ihave looked at the source for BoneAnimation class and they deliberately skip the 0th frame and start animation from 1 to N-1 frame.



Looks, they display 0th frame (or something very "close" to it) when interpolation between frames is ON, otherwise , like i said, 0th frame is never displayed.

sust said:
have u noticed that not all frames are drawn? [...]

Is this a bug in JME?


sust said:
I have looked at the source for BoneAnimation class and they deliberately skip the 0th frame and start animation from 1 to N-1 frame.

Looks, they display 0th frame (or something very "close" to it) when interpolation between frames is ON, otherwise , like i said, 0th frame is never displayed.


Uhmmmmm! What a weird thing!

Anyway, I already noticed it running my test. I simply passed over it because I considered it a minor problem. Our goal was to code a working bone animation.

But now I am really interested in resolving this problem.

I make a supposition: maybe the 0th frame is skiped to save it as a base frame. Animation formats usually have a base frame transformation to save a rest pose of the character model skeleton. It would be possible that jME implementation uses 0th frame to achieve this goal.