[commited] OgreXML importer Multiple Animations

Here is what I have so far for Multiple Animations on a single Model with the OgreXML importer as I mentioned here http://www.jmonkeyengine.com/forum/index.php?topic=12424  Maybe some more optimization could be done, but nevertheless it works!



Here are the diff’s, but first a test file, like the rest of the ogrexml test files:

jmetest.ogrexml.MultipleAnimations.java

/*
 * Copyright (c) 2003-2009 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package jmetest.ogrexml;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.app.SimpleGame;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.util.resource.ClasspathResourceLocator;
import com.jme.util.resource.RelativeResourceLocator;
import com.jme.util.resource.ResourceLocator;
import com.jme.util.resource.ResourceLocatorTool;
import com.jmex.model.ModelFormatException;
import com.jmex.model.ogrexml.MaterialLoader;
import com.jmex.model.ogrexml.OgreLoader;
import com.jmex.model.ogrexml.anim.AnimationChannel;
import com.jmex.model.ogrexml.anim.MeshAnimationController;

public class TestMultipleAnimations extends SimpleGame {

    private static final Logger logger = Logger.getLogger(
            TestMultipleAnimations.class.getName());

    private Node model;

    public static void main(String[] args){
        TestMultipleAnimations app = new TestMultipleAnimations();
        app.setConfigShowMode(ConfigShowMode.AlwaysShow);
        app.start();
    }

    protected void loadMeshModel(){
        OgreLoader loader = new OgreLoader();
        MaterialLoader matLoader = new MaterialLoader();
        String matUrlString = "/jmetest/data/model/ogrexml/Example.material";
        String ninjaMeshUrlString =
                "/jmetest/data/model/ogrexml/ninja.mesh.xml";

        try {
            URL matURL = ResourceLocatorTool.locateResource(
                    ResourceLocatorTool.TYPE_TEXTURE, matUrlString);
            URL meshURL = ResourceLocatorTool.locateResource(
                    ResourceLocatorTool.TYPE_MODEL, ninjaMeshUrlString);

            if (meshURL == null)
                throw new IllegalStateException(
                        "Required runtime resource missing: "
                        + ninjaMeshUrlString);
            if (matURL == null)
                throw new IllegalStateException(
                        "Required runtime resource missing: " + matUrlString);
            try {
                ResourceLocatorTool.addResourceLocator(
                        ResourceLocatorTool.TYPE_TEXTURE,
                        new RelativeResourceLocator(matURL));
                  // This causes relative references in the .material file to
                  // resolve to the same dir as the material file.
                  // Don't have to set up a relative locator for TYPE_MODEL
                  // here, because OgreLoader.loadModel() takes care of that.
            } catch (URISyntaxException use) {
                // Since we're generating the URI from a URL we know to be
                // good, we won't get here.  This is just to satisfy the
                // compiler.
                throw new RuntimeException(use);
            }
            matLoader.load(matURL.openStream());
            if (matLoader.getMaterials().size() > 0)
                loader.setMaterials(matLoader.getMaterials());

            model = (Node) loader.loadModel(meshURL);
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (ModelFormatException mfe) {
            logger.log(Level.SEVERE, null, mfe);
        }
    }

    @Override
    protected void simpleInitGame() {
        ResourceLocator locator = new ClasspathResourceLocator();
        ResourceLocatorTool.addResourceLocator(
                ResourceLocatorTool.TYPE_MODEL, locator);
          // This is to find our *.mesh.xml file.
        ResourceLocatorTool.addResourceLocator(
                ResourceLocatorTool.TYPE_TEXTURE, locator);
          // This is to find our *.material file.

        loadMeshModel();

        Quaternion q =  new Quaternion();
        q.fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y);
        model.setLocalRotation(q); // make it face forward

        rootNode.attachChild(model);

        if (model.getControllerCount() < 1)
            throw new IllegalStateException(
                    "Ninja's animations are missing");

        MeshAnimationController animControl =
                (MeshAnimationController) model.getController(0);
        AnimationChannel lower = animControl.getAnimationChannel();
        //lower.addFromRootBone("Joint3");
        lower.addFromRootBone("Joint23");
        lower.addFromRootBone("Joint18");
        animControl.setAnimation(lower, "Walk");
        
        AnimationChannel upper = animControl.getAnimationChannel();
        //upper.addFromRootBone("Joint18");
        upper.addFromRootBone("Joint3");
        animControl.setAnimation(upper, "Attack1");

        cam.setLocation(new Vector3f(139.05014f, 206.22263f, 225.55989f));
        cam.lookAt(model.getWorldBound().getCenter(), Vector3f.UNIT_Y);

        rootNode.updateGeometricState(0, true);
        rootNode.updateRenderState();
    }


}

Ugh, multiple posts, but here are the diffs:



com.jmex.model.ogrexml.anim.MeshAnimationController.java

@@ -35,6 +35,8 @@
 
 import com.jme.math.Matrix4f;
 import com.jme.math.Vector3f;
+
+import java.util.ArrayList;
 import java.util.Map;
 import com.jme.scene.Controller;
 import com.jme.scene.state.GLSLShaderObjectsState;
@@ -67,17 +69,23 @@
      * Skeleton object must contain corresponding data for the targets' weight buffers.
      */
     private Skeleton skeleton;
-
+    
     /**
      * List of animations, bone or vertex based.
      */
     private Map<String, Animation> animationMap;
-
+    
     /**
-     * The currently playing animation.
+     * The currently playing animations.
      */
-    private Animation animation;
-    private float time = 0f;
+    private ArrayList<AnimationChannel> animations = new ArrayList<AnimationChannel>();

+    /**
+     * Sets whether the current animations actually have Bone Animation on them.
+     */
+    private boolean boneAnimation = false;
+    
 
     /**
      * True if the mesh data should be reset to bind pose every frame.
@@ -85,40 +93,40 @@
      */
     private boolean resetToBindEveryFrame = false;
 
-    /**
-     * Frameskip LOD option
-     */
-    private int framesToSkip = 0;
-    private int curFrame = 0;

 
-    /**
-     * Animation to blend from, e.g the animation
-     * that was set before setAnimation() was called.
-     */
-    private transient Animation blendFrom = null;
 
-    /**
-     * How much to blend the new animation,
-     * a value of 0 indicates apply only the previous animation
-     * while a value of 1 means apply only the current animation.
-     */
-    private transient float blendScale = 0f;
 
-    /**
-     * Multiply this by TPF to get an addition value for blendScale,
-     * makes sure blendScale only becomes 1 when the blend time has been
-     * reached.
-     */
-    private transient float blendMultiply = 0f;
-
-    /**
-     * Same as the <code>time</code> variable but for
-     * the blendFrom animation.
-     */
-    private transient float blendFromTime = 0f;
-
-
     public MeshAnimationController(OgreMesh[] meshes,
                                    Skeleton skeleton,
                                    Map<String, Animation> anims){
@@ -208,22 +216,32 @@
             reset();
             return true;
         }
+        
+        AnimationChannel animationChannel = new AnimationChannel(this);
+        animationChannel.addAllBones();
 
-        if (blendTime > 0f){
-            blendFrom = animation;
-            blendMultiply = 1f / blendTime;
-            blendScale = 0f;
-        }

 
-        animation = animationMap.get(name);
+        Animation animation = animationMap.get(name);
 
         if (animation == null)
             return false;
+        
+        // Sets whether the current running animation has bone animation
+        boneAnimation = animation.hasBoneAnimation();
 
+        animationChannel.setAnimation(animation, blendTime);
+        
         resetToBind();
         resetToBindEveryFrame = animation.hasMeshAnimation() || !isHardwareSkinning();
 
-        time = 0;

+        animations.clear();
+        animations.add(animationChannel);
 
         return true;
     }
@@ -237,8 +255,65 @@
     public boolean setAnimation(String name){
         return setAnimation(name, 0f);
     }
+    
+    /**
+     * Sets the currently active animation.
+     * Use the animation name "<bind>" to set the model into bind pose.
+     *
+     * @returns true if the animation has been successfuly set. False if no such animation exists.
+     */
+    public boolean setAnimation(AnimationChannel animationChannel, String name, float blendTime){
+        if (name.equals("<bind>")){
+            reset();
+            return true;
+        }
 

+
+        Animation animation = animationMap.get(name);
+
+        if (animation == null)
+            return false;
+        
+        // Sets whether the current running animation has bone animation
+        boneAnimation |= animation.hasBoneAnimation();
+
+        animationChannel.setAnimation(animation, blendTime);
+        
+        resetToBind();
+        resetToBindEveryFrame = animation.hasMeshAnimation() || !isHardwareSkinning();
+
+//        time = 0;
+        animations.add(animationChannel);
+
+        return true;
+    }
+
     /**
+     * Sets the currently active animation.
+     * Use the animation name "<bind>" to set the model into bind pose.
+     *
+     * @returns true if the animation has been successfuly set. False if no such animation exists.
+     */
+    public boolean setAnimation(AnimationChannel animationChannel, String name){
+        return setAnimation(animationChannel, name, 0f);
+    }
+    
+    /**
+     * Makes and returns a new animation channel constructed with this controller.
+     *
+     * @returns a new animation channel constructed with this controller.
+     */
+    public AnimationChannel getAnimationChannel(){
+       return new AnimationChannel(this);
+    }
+    
+    /**
      * Returns the length of the animation in seconds. Returns -1 if the animation is not defined.
      */
     public float getAnimationLength(String name){
@@ -251,13 +326,21 @@
     }

+    /**
+     * @return The animation defined with the given String name
+     */
+   public Animation getAnimation(String name){
+        return animationMap.get(name);
+    }
 
     /**
-     * @return The name of the currently active animation
+     * @return The name of the currently active animations
      */
-    public String getActiveAnimation(){
-        if (animation == null)
-            return "<bind>";
+    public String[] getActiveAnimations(){
+       String[] activeAnimations;
+        if (animations.size() == 0) {
+           activeAnimations = new String[1];
+           activeAnimations[0] = "<bind>";
+        } else {        
+           activeAnimations = new String[animations.size()];
+           for (int i = 0; i < animations.size(); i++) {
+              activeAnimations[i] = animations.get(i).getAnimation().getName();
+         }
+        }
 
-        return animation.getName();
+        return activeAnimations;
     }
 
     /**
@@ -274,24 +357,29 @@
      * @param framesToSkip One frame will be played out of the framesToSkip number.
      */
     public void setFrameSkip(int framesToSkip){
-        if (this.framesToSkip != framesToSkip)
-            this.curFrame = 0;
-
-        this.framesToSkip = framesToSkip;
+       for (AnimationChannel animation : animations) {
+         animation.setFrameSkip(framesToSkip);
+      }
     }
 
-    public float getCurTime() {
-        return time;
-    }
-

     /**
      * Sets the time of the animation.
      * If it's greater than getAnimationLength(getActiveAnimation()),
      * the time will be appropriately clamped/wraped depending on the repeatMode.
      */
     public void setCurTime(float time){
-        this.time = time;
+       for (AnimationChannel animation : animations) {
+         animation.setCurTime(time);
+      }
     }
+    
+    public OgreMesh[] getTargets() {
+      return targets;
+   }
 
     Skeleton getSkeleton(){
         return skeleton;
@@ -307,8 +395,9 @@
             skeleton.resetAndUpdate();
         }
         resetToBindEveryFrame = false;
-        animation = null;
-        time = 0;
+        animations.clear();
     }
 
     void resetToBind(){
@@ -405,9 +494,9 @@
         mesh.updateModelBound();
     }
 
-    public float clampWrapTime(float t, float max, int repeatMode){
+    public float clampWrapTime(float t, float max){
         if (t < 0f){
-            switch (repeatMode){
+            switch (getRepeatType()){
                 case RT_CLAMP:
                     return 0;
                 case RT_CYCLE:
@@ -416,7 +505,7 @@
                     return max - t;
             }
         }else if (t > max){
-            switch (repeatMode){
+            switch (getRepeatType()){
                 case RT_CLAMP:
                     return max;
                 case RT_CYCLE:
@@ -431,57 +520,63 @@
 
     @Override
     public void update(float tpf) {
-        if (!isActive() || animation == null)
+       
+        if (!isActive() || animations.isEmpty())
             return;
 
-        // do clamping/wrapping of time
-        time = clampWrapTime(time, animation.getLength(), getRepeatType());
-        if (blendFrom != null){
-            blendFromTime = clampWrapTime(blendFromTime, blendFrom.getLength(), getRepeatType());
-        }
        
-        if (framesToSkip > 0){
-            // check frame skipping
-            curFrame++;

 
-            if (curFrame != framesToSkip){
-                time += tpf * getSpeed();
-                if (blendFrom != null)
-                    blendFromTime += tpf * getSpeed();
-
-                return;
-            }else{
-                curFrame = 0;
-            }
-        }
-
         if (resetToBindEveryFrame)
             resetToBind(); // reset morph meshes to bind pose
 
-        if (animation.hasBoneAnimation()){
-            skeleton.reset(); // reset skeleton to bind pose
-        }
-
-        if (blendFrom == null){
-            animation.setTime(time, targets, skeleton, 1f);
-        }else{
-            blendFrom.setTime(blendFromTime, targets, skeleton, 1f - blendScale);
-            animation.setTime(time, targets, skeleton, blendScale);
-
-            // here update the blending scale
-            blendScale += tpf * blendMultiply;
-            if (blendScale > 1f){
-                blendFrom = null;
-                blendScale = 0f;
-                blendMultiply = 0f;
-            }
-        }
+//      if (animation.hasBoneAnimation())
+        if (boneAnimation)
+           skeleton.reset(); // reset skeleton to bind pose
        
-        if (animation.hasBoneAnimation()){
-            skeleton.updateWorldVectors();
+        for (AnimationChannel animation : animations) {
+         animation.update(tpf);
+      }
+    

+        
+          if (boneAnimation) {
+           skeleton.updateWorldVectors();
 
             if (!isHardwareSkinning()){
-                // here update the targets verticles if no hardware skinning supported
+                // here update the targets vertices if no hardware skinning supported
 
                 Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
 
@@ -493,9 +588,9 @@
             }
         }
 
-        time += tpf * getSpeed();
-        if (blendFrom != null)
-            blendFromTime += tpf * getSpeed();
     }
 
     @Override
@@ -522,4 +617,4 @@
         out.writeStringSavableMap(animationMap, "animations", null);
     }
 
-}
+}
No newline at end of file



com.jmex.model.ogrexml.anim.Animation.java


@@ -39,6 +39,7 @@
 import com.jme.util.export.Savable;
 import java.io.IOException;
 import java.io.Serializable;
+import java.util.ArrayList;
 
 /**
  * Combines mesh and bone animations into one class for easier access
@@ -106,12 +107,12 @@
         return length;
     }
 
-    void setTime(float time, OgreMesh[] targets, Skeleton skeleton, float weight){
+    void setTime(float time, OgreMesh[] targets, Skeleton skeleton, float weight, ArrayList<Integer> affectedBones){
         if (meshAnim != null)
             meshAnim.setTime(time, targets, weight);
 
         if (boneAnim != null){
-            boneAnim.setTime(time, skeleton, weight);
+            boneAnim.setTime(time, skeleton, weight, affectedBones);
         }
     }
 



com.jmex.model.ogrexml.anim.BoneAnimation.java

@@ -39,6 +39,7 @@
 import com.jme.util.export.Savable;
 import java.io.IOException;
 import java.io.Serializable;
+import java.util.ArrayList;
 
 /**
  * Bone animation updates each of it's tracks with the skeleton and time
@@ -77,9 +78,9 @@
         return tracks;
     }
 
-    void setTime(float time, Skeleton skeleton, float weight){
+    void setTime(float time, Skeleton skeleton, float weight, ArrayList<Integer> affectedBones){
         for (int i = 0; i < tracks.length; i++){
-            tracks[i].setTime(time, skeleton, weight);
+            tracks[i].setTime(time, skeleton, weight, affectedBones);
         }
     }
 

Still more…



com.jmex.model.ogrexml.anim.BoneTrack.java

@@ -36,6 +36,7 @@
 import com.jme.util.export.JMEImporter;
 import java.io.IOException;
 import java.io.Serializable;
+import java.util.ArrayList;
 
 import com.jme.math.Quaternion;
 import com.jme.math.Vector3f;
@@ -92,8 +93,12 @@
      * the correct animation transforms for a given time.
      * The transforms can be interpolated in some method from the keyframes.
      */
-    public void setTime(float time, Skeleton skeleton, float weight) {
-        Bone target = skeleton.getBone(targetBoneIndex);
+    public void setTime(float time, Skeleton skeleton, float weight, ArrayList<Integer> affectedBones) {
+        if (affectedBones != null && !affectedBones.contains(targetBoneIndex)) {
+         return;
+      }
+       
+       Bone target = skeleton.getBone(targetBoneIndex);
 
         int lastFrame = times.length - 1;
         if (time < 0 || times.length == 1){



New File!
com.jmex.model.ogrexml.anim.AnimationChannel.java

package com.jmex.model.ogrexml.anim;

import java.util.ArrayList;

import com.jme.math.Matrix4f;
import com.jme.scene.Controller;

public class AnimationChannel {
   
    /**
     * The controller that causes the animation.
     */
   private MeshAnimationController controller;
   
    /**
     * The subset of affected bone indexes affected by the animation.
     */
   private ArrayList<Integer> affectedBones;
   
    /**
     * The currently playing animation.
     */
    private Animation animation;

   private float time = 0f; 
   
    /**
     * Frameskip LOD option
     */
    private int framesToSkip = 0;
    private int curFrame = 0;
   
    /**
     * Animation to blend from, e.g the animation
     * that was set before setAnimation() was called.
     */
    private transient Animation blendFrom = null;
   
    /**
     * How much to blend the new animation,
     * a value of 0 indicates apply only the previous animation
     * while a value of 1 means apply only the current animation.
     */
    private transient float blendScale = 0f;

    /**
     * Multiply this by TPF to get an addition value for blendScale,
     * makes sure blendScale only becomes 1 when the blend time has been
     * reached.
     */
    private transient float blendMultiply = 0f;

    /**
     * Same as the <code>time</code> variable but for
     * the blendFrom animation.
     */
    private transient float blendFromTime = 0f;
   
   public ArrayList<Integer> getAffectedBones() {
      return affectedBones;
   }
   
    /**
     * Sets the currently active animation.
     */
    public void setAnimation(Animation animation, float blendTime) {      
        if (blendTime > 0f){
            blendFrom = this.animation;
            blendMultiply = 1f / blendTime;
            blendScale = 0f;
        }
       
        this.animation = animation;
       
        time = 0;
    }
   
    public Animation getAnimation() {
      return animation;
   }
   
    /**
     * Enables frameskip LOD.
     * This technique is mostly only effective when software skinning is used.
     *
     * @param framesToSkip One frame will be played out of the framesToSkip number.
     */
    public void setFrameSkip(int framesToSkip){
        if (this.framesToSkip != framesToSkip)
            this.curFrame = 0;

        this.framesToSkip = framesToSkip;
    }
   
   public float getCurTime() {
      return time;
   }
   
   /**
   * Sets the time of the animation.
   * If it's greater than getAnimationLength(getActiveAnimation()),
   * the time will be appropriately clamped/wraped depending on the repeatMode.
   */
   public void setCurTime(float time){
      this.time = time;
   }
       
   public AnimationChannel(MeshAnimationController controller) {
      this.controller = controller;
   }
   
   /**
    * Add all the bones to the animation channel.
    */
   public void addAllBones() {
      affectedBones = null;
   }
   
   /**
    * Add a single Bone to the Channel,
    * and don't have multiple instances of the same in the list.
    */
   public void addBone(String name) {
      addBone(controller.getSkeleton().getBone(name));
   }
   
   /**
    * Add a single Bone to the Channel,
    * and don't have multiple instances of the same in the list.
    */
   public void addBone(Bone bone) {
      int boneIndex = controller.getSkeleton().getBoneIndex(bone);
      if(affectedBones == null) {
         affectedBones = new ArrayList<Integer>();
         affectedBones.add(boneIndex);
      }
      else if(!affectedBones.contains(boneIndex)) {
         affectedBones.add(boneIndex);
      }
   }
   
   /**
    * Add Bones to the Channel going toward the root bone. (i.e. parents)
    */
   public void addToRootBone(String name) {      
      addToRootBone(controller.getSkeleton().getBone(name));
   }
   
   /**
    * Add Bones to the Channel going toward the root bone. (i.e. parents)
    */
   public void addToRootBone(Bone bone) {
      addBone(bone);
      while (bone.parent != null) {
         bone = bone.parent;
         addBone(bone);
      }   
   }
      
   /**
    * Add Bones to the Channel going away from the root bone. (i.e. children)
    */
   public void addFromRootBone(String name) {
      addFromRootBone(controller.getSkeleton().getBone(name));
   }
   
   /**
    * Add Bones to the Channel going away from the root bone. (i.e. children)
    */
   public void addFromRootBone(Bone bone) {
      addBone(bone);
      if (bone.children == null)
         return;      
      for (Bone childBone : bone.children) {
         addBone(childBone);
         addFromRootBone(childBone);
      }
      
   }
   
   public void update(float tpf) {
       
        if (animation == null)
            return;

        // do clamping/wrapping of time
        time = controller.clampWrapTime(time, animation.getLength());
        if (blendFrom != null){
            blendFromTime = controller.clampWrapTime(blendFromTime, blendFrom.getLength());
        }
       
        if (framesToSkip > 0){
            // check frame skipping
            curFrame++;

            if (curFrame != framesToSkip){
                time += tpf * controller.getSpeed();
                if (blendFrom != null)
                    blendFromTime += tpf * controller.getSpeed();

                return;
            }else{
                curFrame = 0;
            }
        }

        if (blendFrom == null){
            animation.setTime(time, controller.getTargets(), controller.getSkeleton(), 1f, affectedBones);
        } else {
            blendFrom.setTime(blendFromTime, controller.getTargets(), controller.getSkeleton(), 1f - blendScale, affectedBones);
            animation.setTime(time, controller.getTargets(), controller.getSkeleton(), blendScale, affectedBones);

            // here update the blending scale
            blendScale += tpf * blendMultiply;
            if (blendScale > 1f){
                blendFrom = null;
                blendScale = 0f;
                blendMultiply = 0f;
            }
        }
       

        time += tpf * controller.getSpeed();
        if (blendFrom != null)
            blendFromTime += tpf * controller.getSpeed();
   }
   

}

Very nice! This is exactly the way I envisioned it.  :slight_smile:

Did you test it enough that you think it should go into trunk?

All the ogrexml tests still work, and my new multiple animations tests work.  So yeah I think it could go into the trunk,  the huge commented code sections in MeshAnimationController that were left over from the older version should be gotten rid of though.



EDIT: I deleted the huge commented code sections by hand in the MeshAnimationController diff, tell me if they’re broken or w/e.  :smiley:

wow this looks good, momoko have u given any thought to the cycle_ended listeners, I could start porting to ogre once I figure out how everything works

That wouldn't be too hard to do, just throw an event in AnimationChannel when the cycle ends, which the listener will handle.  Are there events in Java?  I'm too used to C#.

There are events and event listeners in java but they are not integrated in the syntax like in C#.

SomethingNew said:

That wouldn't be too hard to do, just throw an event in AnimationChannel when the cycle ends, which the listener will handle.  Are there events in Java?  I'm too used to C#.


as momoko said they exist and my animation sequencing code depends heavily on the animation listener in the old md5reader  which maybe, until now had better "tooling" for doing what I need. than anything else in jme and its more than 6yrs old :( :|

Here's the patch file for multiple animations instead of trying to use lots diffs.

When is this going to trunk? I tried around with it and it seems to work perfectly… Always feels strange to work with a locally modified jme :smiley:



btw, thanks for the work, SomethingNew. Used OgreXML, tried around with HottBJ/JmeXML but now OgreXML is definitely the way to go again.

normen said:

When is this going to trunk? I tried around with it and it seems to work perfectly.. Always feels strange to work with a locally modified jme :D

Yeah, I don't know, I don't have commit rights.  Maybe if we talk about it enough, someone might commit it. :)

normen said:

btw, thanks for the work, SomethingNew. Used OgreXML, tried around with HottBJ/JmeXML but now OgreXML is definitely the way to go again.

You're welcome.  :D

You're suppposed to email jmonkeyengine@gmail.com afaik to get commit rights. Make sure to include your googlecode username.

Or give erlend or sbook your google-account

Commited!  :smiley:

@Momoko_Fan would you mind if I reimplemented this for JME3?  I might also implement the callback feature as well.  What do you think?  Anybody else have an opinion?

Actually the animation system in jME3 is barely anything at this point. There's a single method and that is for playing an animation. No attachments, no blending, no masking, etc yet.

Yikes, I thought it looked exactly the same, I best look again!  :’(