OSDN Git Service

copy from jme10694
authorkobayasi <kobayasi@pscnet.co.jp>
Sat, 6 Jul 2013 18:10:38 +0000 (03:10 +0900)
committerkobayasi <kobayasi@pscnet.co.jp>
Sat, 6 Jul 2013 18:10:38 +0000 (03:10 +0900)
24 files changed:
engine/src/core/com/jme3/animation/AnimChannel.java
engine/src/core/com/jme3/animation/AnimControl.java
engine/src/core/com/jme3/animation/AnimEventListener.java
engine/src/core/com/jme3/animation/Animation.java
engine/src/core/com/jme3/animation/AnimationFactory.java [new file with mode: 0644]
engine/src/core/com/jme3/animation/AudioTrack.java [new file with mode: 0644]
engine/src/core/com/jme3/animation/Bone.java
engine/src/core/com/jme3/animation/BoneAnimation.java
engine/src/core/com/jme3/animation/BoneTrack.java
engine/src/core/com/jme3/animation/ClonableTrack.java [new file with mode: 0644]
engine/src/core/com/jme3/animation/CompactArray.java
engine/src/core/com/jme3/animation/CompactQuaternionArray.java
engine/src/core/com/jme3/animation/CompactVector3Array.java
engine/src/core/com/jme3/animation/EffectTrack.java [new file with mode: 0644]
engine/src/core/com/jme3/animation/LoopMode.java
engine/src/core/com/jme3/animation/Pose.java
engine/src/core/com/jme3/animation/PoseTrack.java
engine/src/core/com/jme3/animation/Skeleton.java
engine/src/core/com/jme3/animation/SkeletonControl.java
engine/src/core/com/jme3/animation/SpatialAnimation.java
engine/src/core/com/jme3/animation/SpatialTrack.java
engine/src/core/com/jme3/animation/Track.java
engine/src/core/com/jme3/animation/TrackInfo.java [new file with mode: 0644]
engine/src/core/com/jme3/animation/package.html

index a2fae7c..7c79bae 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -29,7 +29,6 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.animation;
 
 import com.jme3.math.FastMath;
@@ -61,38 +60,69 @@ public final class AnimChannel {
     private float speed;
     private float timeBlendFrom;
     private float speedBlendFrom;
+    private boolean notified=false;
 
     private LoopMode loopMode, loopModeBlendFrom;
     
     private float blendAmount = 1f;
     private float blendRate   = 0;
-
+    
     private static float clampWrapTime(float t, float max, LoopMode loopMode){
-        if (max == Float.POSITIVE_INFINITY)
-            return t;
+        if (t == 0) {
+            return 0; // prevent division by 0 errors
+        }
         
-        if (t < 0f){
-            //float tMod = -(-t % max);
-            switch (loopMode){
-                case DontLoop:
-                    return 0;
-                case Cycle:
-                    return t;
-                case Loop:
-                    return max - t;
-            }
-        }else if (t > max){
-            switch (loopMode){
-                case DontLoop:
-                    return max;
-                case Cycle:
-                    return /*-max;*/-(2f * max - t);
-                case Loop:
-                    return t - max;
-            }
+        switch (loopMode) {
+            case Cycle:
+                boolean sign = ((int) (t / max) % 2) != 0;
+                float result;
+                
+//                if (t < 0){
+//                    result = sign ? t % max : -(max + (t % max));
+//                } else {
+                    // NOTE: This algorithm seems stable for both high and low
+                    // tpf so for now its a keeper.
+                    result = sign ? -(max - (t % max)) : t % max;
+//                }
+                    
+//                if (result <= 0 || result >= max) {
+//                    System.out.println("SIGN: " + sign + ", RESULT: " + result + ", T: " + t + ", M: " + max);
+//                }
+                
+                return result;
+            case DontLoop:
+                return t > max ? max : (t < 0 ? 0 : t);
+            case Loop:
+                return t % max;
         }
-
         return t;
+        
+        
+//        if (max == Float.POSITIVE_INFINITY)
+//            return t;
+//        
+//        if (t < 0f){
+//            //float tMod = -(-t % max);
+//            switch (loopMode){
+//                case DontLoop:
+//                    return 0;
+//                case Cycle:
+//                    return t;
+//                case Loop:
+//                    return max - t;
+//            }
+//        }else if (t > max){
+//            switch (loopMode){
+//                case DontLoop:
+//                    return max;
+//                case Cycle:
+//                    return -(2f * max - t) % max;
+//                case Loop:
+//                    return t % max;
+//            }
+//        }
+//
+//        return t;
     }
 
     AnimChannel(AnimControl control){
@@ -206,7 +236,7 @@ public final class AnimChannel {
      */
     public void setAnim(String name, float blendTime){
         if (name == null)
-            throw new NullPointerException();
+            throw new IllegalArgumentException("name cannot be null");
 
         if (blendTime < 0f)
             throw new IllegalArgumentException("blendTime cannot be less than zero");
@@ -225,12 +255,15 @@ public final class AnimChannel {
             loopModeBlendFrom = loopMode;
             blendAmount = 0f;
             blendRate   = 1f / blendTime;
+        }else{
+            blendFrom = null;
         }
 
         animation = anim;
         time = 0;
         speed = 1f;
         loopMode = LoopMode.Loop;
+        notified = false;
     }
 
     /**
@@ -316,14 +349,33 @@ public final class AnimChannel {
     BitSet getAffectedBones(){
         return affectedBones;
     }
+    
+    public void reset(boolean rewind){
+        if(rewind){
+            setTime(0);        
+            if(control.getSkeleton()!=null){
+                control.getSkeleton().resetAndUpdate();
+            }else{
+                TempVars vars = TempVars.get();
+                update(0, vars);
+                vars.release();    
+            }
+        }
+        animation = null;
+       // System.out.println("Setting notified false");
+        notified = false;
+    }
 
     void update(float tpf, TempVars vars) {
         if (animation == null)
             return;
 
-        if (blendFrom != null){
+        if (blendFrom != null && blendAmount != 1.0f){
+            // The blendFrom anim is set, the actual animation
+            // playing will be set 
+//            blendFrom.setTime(timeBlendFrom, 1f, control, this, vars);
             blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
-            //blendFrom.setTime(timeBlendFrom, control.skeleton, 1f - blendAmount, affectedBones);
+            
             timeBlendFrom += tpf * speedBlendFrom;
             timeBlendFrom = clampWrapTime(timeBlendFrom,
                                           blendFrom.getLength(),
@@ -339,25 +391,28 @@ public final class AnimChannel {
                 blendFrom = null;
             }
         }
-
+        
         animation.setTime(time, blendAmount, control, this, vars);
-        //animation.setTime(time, control.skeleton, blendAmount, affectedBones);
         time += tpf * speed;
 
         if (animation.getLength() > 0){
-            if (time >= animation.getLength()) {
-                control.notifyAnimCycleDone(this, animation.getName());
-            } else if (time < 0) {
+            if (!notified && (time >= animation.getLength() || time < 0)) {
+                if (loopMode == LoopMode.DontLoop) {
+                    // Note that this flag has to be set before calling the notify
+                    // since the notify may start a new animation and then unset
+                    // the flag.
+                    notified = true;
+                }
                 control.notifyAnimCycleDone(this, animation.getName());
             } 
         }
 
         time = clampWrapTime(time, animation.getLength(), loopMode);
         if (time < 0){
+            // Negative time indicates that speed should be inverted
+            // (for cycle loop mode only)
             time = -time;
             speed = -speed;
         }
-
-        
     }
 }
index ad56f30..6c25da7 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
  */\r
 package com.jme3.animation;\r
 \r
-import com.jme3.export.JmeExporter;\r
-import com.jme3.export.JmeImporter;\r
-import com.jme3.export.InputCapsule;\r
-import com.jme3.export.OutputCapsule;\r
-import com.jme3.export.Savable;\r
+import com.jme3.export.*;\r
 import com.jme3.renderer.RenderManager;\r
 import com.jme3.renderer.ViewPort;\r
 import com.jme3.scene.Mesh;\r
@@ -47,6 +43,7 @@ import java.io.IOException;
 import java.util.ArrayList;\r
 import java.util.Collection;\r
 import java.util.HashMap;\r
+import java.util.Map.Entry;\r
 \r
 /**\r
  * <code>AnimControl</code> is a Spatial control that allows manipulation\r
@@ -74,21 +71,17 @@ public final class AnimControl extends AbstractControl implements Cloneable {
      * Skeleton object must contain corresponding data for the targets' weight buffers.\r
      */\r
     Skeleton skeleton;\r
-\r
     /** only used for backward compatibility */\r
     @Deprecated\r
     private SkeletonControl skeletonControl;\r
-\r
     /**\r
      * List of animations\r
      */\r
-    HashMap<String, Animation> animationMap;\r
-\r
+    HashMap<String, Animation> animationMap = new HashMap<String, Animation>();\r
     /**\r
      * Animation channels\r
      */\r
     private transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>();\r
-\r
     /**\r
      * Animation event listeners\r
      */\r
@@ -120,13 +113,16 @@ public final class AnimControl extends AbstractControl implements Cloneable {
             AnimControl clone = (AnimControl) super.clone();\r
             clone.spatial = spatial;\r
             clone.channels = new ArrayList<AnimChannel>();\r
-            \r
-            if (skeleton != null){\r
+            clone.listeners = new ArrayList<AnimEventListener>();\r
+\r
+            if (skeleton != null) {\r
                 clone.skeleton = new Skeleton(skeleton);\r
             }\r
-            \r
-            // animationMap is reference-copied, animation data should be shared\r
-            // to reduce memory usage.\r
+\r
+            // animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial\r
+            for (Entry<String, Animation> animEntry : animationMap.entrySet()) {\r
+                clone.animationMap.put(animEntry.getKey(), animEntry.getValue().cloneForSpatial(spatial));\r
+            }\r
             \r
             return clone;\r
         } catch (CloneNotSupportedException ex) {\r
@@ -215,6 +211,11 @@ public final class AnimControl extends AbstractControl implements Cloneable {
      * @see AnimControl#createChannel()\r
      */\r
     public void clearChannels() {\r
+        for (AnimChannel animChannel : channels) {\r
+            for (AnimEventListener list : listeners) {\r
+                list.onAnimCycleDone(this, animChannel, animChannel.getAnimationName());\r
+            }\r
+        }\r
         channels.clear();\r
     }\r
 \r
@@ -276,7 +277,7 @@ public final class AnimControl extends AbstractControl implements Cloneable {
      */\r
     @Override\r
     public void setSpatial(Spatial spatial) {\r
-        if (spatial == null && skeletonControl != null){\r
+        if (spatial == null && skeletonControl != null) {\r
             this.spatial.removeControl(skeletonControl);\r
         }\r
 \r
@@ -316,13 +317,13 @@ public final class AnimControl extends AbstractControl implements Cloneable {
 \r
         return a.getLength();\r
     }\r
-    \r
+\r
     /**\r
      * Internal use only.\r
      */\r
     @Override\r
     protected void controlUpdate(float tpf) {\r
-        if (skeleton != null){\r
+        if (skeleton != null) {\r
             skeleton.reset(); // reset skeleton to bind pose\r
         }\r
 \r
@@ -332,7 +333,7 @@ public final class AnimControl extends AbstractControl implements Cloneable {
         }\r
         vars.release();\r
 \r
-        if (skeleton != null){\r
+        if (skeleton != null) {\r
             skeleton.updateWorldVectors();\r
         }\r
     }\r
@@ -357,12 +358,15 @@ public final class AnimControl extends AbstractControl implements Cloneable {
         super.read(im);\r
         InputCapsule in = im.getCapsule(this);\r
         skeleton = (Skeleton) in.readSavable("skeleton", null);\r
-        animationMap = (HashMap<String, Animation>) in.readStringSavableMap("animations", null);\r
+        HashMap<String, Animation> loadedAnimationMap = (HashMap<String, Animation>) in.readStringSavableMap("animations", null);\r
+        if (loadedAnimationMap != null) {\r
+            animationMap = loadedAnimationMap;\r
+        }\r
 \r
-        if (im.getFormatVersion() == 0){\r
+        if (im.getFormatVersion() == 0) {\r
             // Changed for backward compatibility with j3o files generated \r
             // before the AnimControl/SkeletonControl split.\r
-            \r
+\r
             // If we find a target mesh array the AnimControl creates the \r
             // SkeletonControl for old files and add it to the spatial.        \r
             // When backward compatibility won't be needed anymore this can deleted        \r
index 22a0b98..d9b4845 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -29,7 +29,6 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.animation;
 
 /**
index ef1af12..5ae3462 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2011 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  */
 package com.jme3.animation;
 
-import java.io.IOException;
-
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
+import java.io.IOException;
 
 /**
  * The animation class updates the animation target with the tracks of a given type.
@@ -46,27 +43,26 @@ import com.jme3.util.TempVars;
  * @author Kirill Vainer, Marcin Roguski (Kaelthas)
  */
 public class Animation implements Savable, Cloneable {
-    
+
     /** 
      * The name of the animation. 
      */
     private String name;
-    
     /** 
      * The length of the animation. 
      */
     private float length;
-    
     /** 
      * The tracks of the animation. 
      */
-    private Track[] tracks;
-    
+    private SafeArrayList<Track> tracks = new SafeArrayList<Track>(Track.class);
+
     /**
      * Serialization-only. Do not use.
      */
-    public Animation() {}
-    
+    public Animation() {
+    }
+
     /**
      * Creates a new <code>Animation</code> with the given name and length.
      * 
@@ -77,24 +73,24 @@ public class Animation implements Savable, Cloneable {
         this.name = name;
         this.length = length;
     }
-    
+
     /**
      * The name of the bone animation
      * @return name of the bone animation
      */
     public String getName() {
-       return name;
+        return name;
     }
-    
+
     /**
      * Returns the length in seconds of this animation
      * 
      * @return the length in seconds of this animation
      */
     public float getLength() {
-       return length;
+        return length;
     }
-    
+
     /**
      * This method sets the current time of the animation.
      * This method behaves differently for every known track type.
@@ -106,77 +102,87 @@ public class Animation implements Savable, Cloneable {
      * @param channel the animation channel
      */
     void setTime(float time, float blendAmount, AnimControl control, AnimChannel channel, TempVars vars) {
-        if (tracks == null)
+        if (tracks == null) {
             return;
-        
-        for (int i = 0; i < tracks.length; i++){
-            tracks[i].setTime(time, blendAmount, control, channel, vars);
         }
-        
-        /*
-        if (tracks != null && tracks.length > 0) {
-            Track<?> trackInstance = tracks[0];
-            
-            if (trackInstance instanceof SpatialTrack) {
-                Spatial spatial = control.getSpatial();
-                if (spatial != null) {
-                    ((SpatialTrack) tracks[0]).setTime(time, spatial, blendAmount);
-                }
-            } else if (trackInstance instanceof BoneTrack) {
-                BitSet affectedBones = channel.getAffectedBones();
-                Skeleton skeleton = control.getSkeleton();
-                for (int i = 0; i < tracks.length; ++i) {
-                    if (affectedBones == null || affectedBones.get(((BoneTrack) tracks[i]).getTargetIndex())) {
-                        ((BoneTrack) tracks[i]).setTime(time, skeleton, blendAmount);
-                    }
-                }
-            } else if (trackInstance instanceof PoseTrack) {
-                Spatial spatial = control.getSpatial();
-                List<Mesh> meshes = new ArrayList<Mesh>();
-                this.getMeshes(spatial, meshes);
-                if (meshes.size() > 0) {
-                    Mesh[] targets = meshes.toArray(new Mesh[meshes.size()]);
-                    for (int i = 0; i < tracks.length; ++i) {
-                        ((PoseTrack) tracks[i]).setTime(time, targets, blendAmount);
-                    }
-                }
-            }
+
+        for (Track track : tracks) {
+            track.setTime(time, blendAmount, control, channel, vars);
         }
-        */
     }
-    
+
     /**
      * Set the {@link Track}s to be used by this animation.
-     * <p>
-     * The array should be organized so that the appropriate Track can
-     * be retrieved based on a bone index. 
      * 
-     * @param tracks The tracks to set.
+     * @param tracksArray The tracks to set.
+     */
+    public void setTracks(Track[] tracksArray) {
+        for (Track track : tracksArray) {
+            tracks.add(track);
+        }
+    }
+
+    /**
+     * Adds a track to this animation
+     * @param track the track to add
      */
-    public void setTracks(Track[] tracks){
-        this.tracks = tracks;
+    public void addTrack(Track track) {
+        tracks.add(track);
     }
-    
+
+    /**
+     * removes a track from this animation
+     * @param track the track to remove
+     */
+    public void removeTrack(Track track) {
+        tracks.remove(track);
+        if (track instanceof ClonableTrack) {
+            ((ClonableTrack) track).cleanUp();
+        }
+    }
+
     /**
      * Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }.
      * 
      * @return the tracks set previously
      */
     public Track[] getTracks() {
-       return tracks;
+        return tracks.getArray();
     }
-    
+
     /**
      * This method creates a clone of the current object.
      * @return a clone of the current object
      */
-   @Override
-   public Animation clone() {
+    @Override
+    public Animation clone() {
         try {
             Animation result = (Animation) super.clone();
-            result.tracks = tracks.clone();
-            for (int i = 0; i < tracks.length; ++i) {
-                result.tracks[i] = this.tracks[i].clone();
+            result.tracks = new SafeArrayList<Track>(Track.class);
+            for (Track track : tracks) {
+                result.tracks.add(track.clone());
+            }
+            return result;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * 
+     * @param spat
+     * @return 
+     */
+    public Animation cloneForSpatial(Spatial spat) {
+        try {
+            Animation result = (Animation) super.clone();
+            result.tracks = new SafeArrayList<Track>(Track.class);
+            for (Track track : tracks) {
+                if (track instanceof ClonableTrack) {
+                    result.tracks.add(((ClonableTrack) track).cloneForSpatial(spat));
+                } else {
+                    result.tracks.add(track);
+                }
             }
             return result;
         } catch (CloneNotSupportedException e) {
@@ -188,13 +194,13 @@ public class Animation implements Savable, Cloneable {
     public String toString() {
         return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';
     }
-    
-   @Override
+
+    @Override
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule out = ex.getCapsule(this);
         out.write(name, "name", null);
         out.write(length, "length", 0f);
-        out.write(tracks, "tracks", null);
+        out.write(tracks.getArray(), "tracks", null);
     }
 
     @Override
@@ -202,9 +208,17 @@ public class Animation implements Savable, Cloneable {
         InputCapsule in = im.getCapsule(this);
         name = in.readString("name", null);
         length = in.readFloat("length", 0f);
-        
+
         Savable[] arr = in.readSavableArray("tracks", null);
-        tracks = new Track[arr.length];
-        System.arraycopy(arr, 0, tracks, 0, arr.length);
+        if (arr != null) {
+            // NOTE: Backward compat only .. Some animations have no
+            // tracks set at all even though it makes no sense.
+            // Since there's a null check in setTime(),
+            // its only appropriate that the check is made here as well.
+            tracks = new SafeArrayList<Track>(Track.class);
+            for (Savable savable : arr) {
+                tracks.add((Track) savable);
+            }
+        }
     }
 }
diff --git a/engine/src/core/com/jme3/animation/AnimationFactory.java b/engine/src/core/com/jme3/animation/AnimationFactory.java
new file mode 100644 (file)
index 0000000..38e22ea
--- /dev/null
@@ -0,0 +1,496 @@
+/*
+ * Copyright (c) 2009-2012 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 com.jme3.animation;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+
+/**
+ * A convenience class to easily setup a spatial keyframed animation
+ * you can add some keyFrames for a given time or a given keyFrameIndex, for translation rotation and scale.
+ * The animationHelper will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames.
+ * <br><br>
+ * Usage is : <br>
+ * - Create the AnimationHelper<br>
+ * - add some keyFrames<br>
+ * - call the buildAnimation() method that will retruna new Animation<br>
+ * - add the generated Animation to any existing AnimationControl<br>
+ * <br><br>
+ * Note that the first keyFrame (index 0) is defaulted with the identy transforms.
+ * If you want to change that you have to replace this keyFrame with any transform you want.
+ * 
+ * @author Nehon
+ */
+public class AnimationFactory {
+
+    /**
+     * step for splitting rotation that have a n ange above PI/2
+     */
+    private final static float EULER_STEP = FastMath.QUARTER_PI * 3;
+
+    /**
+     * enum to determine the type of interpolation
+     */
+    private enum Type {
+
+        Translation, Rotation, Scale;
+    }
+
+    /**
+     * Inner Rotation type class to kep track on a rotation Euler angle
+     */
+    protected class Rotation {
+
+        /**
+         * The rotation Quaternion
+         */
+        Quaternion rotation = new Quaternion();
+        /**
+         * This rotation expressed in Euler angles
+         */
+        Vector3f eulerAngles = new Vector3f();
+        /**
+         * the index of the parent key frame is this keyFrame is a splitted rotation
+         */
+        int masterKeyFrame = -1;
+
+        public Rotation() {
+            rotation.loadIdentity();
+        }
+
+        void set(Quaternion rot) {
+            rotation.set(rot);
+            float[] a = new float[3];
+            rotation.toAngles(a);
+            eulerAngles.set(a[0], a[1], a[2]);
+        }
+
+        void set(float x, float y, float z) {
+            float[] a = {x, y, z};
+            rotation.fromAngles(a);
+            eulerAngles.set(x, y, z);
+        }
+    }
+    /**
+     * Name of the animation
+     */
+    protected String name;
+    /**
+     * frames per seconds
+     */
+    protected int fps;
+    /**
+     * Animation duration in seconds
+     */
+    protected float duration;
+    /**
+     * total number of frames
+     */
+    protected int totalFrames;
+    /**
+     * time per frame
+     */
+    protected float tpf;
+    /**
+     * Time array for this animation
+     */
+    protected float[] times;
+    /**
+     * Translation array for this animation
+     */
+    protected Vector3f[] translations;
+    /**
+     * rotation array for this animation
+     */
+    protected Quaternion[] rotations;
+    /**
+     * scales array for this animation
+     */
+    protected Vector3f[] scales;
+    /**
+     * The map of keyFrames to compute the animation. The key is the index of the frame
+     */
+    protected Vector3f[] keyFramesTranslation;
+    protected Vector3f[] keyFramesScale;
+    protected Rotation[] keyFramesRotation;
+
+    /**
+     * Creates and AnimationHelper
+     * @param duration the desired duration for the resulting animation
+     * @param name the name of the resulting animation
+     */
+    public AnimationFactory(float duration, String name) {
+        this(duration, name, 30);
+    }
+
+    /**
+     * Creates and AnimationHelper
+     * @param duration the desired duration for the resulting animation
+     * @param name the name of the resulting animation
+     * @param fps the number of frames per second for this animation (default is 30)
+     */
+    public AnimationFactory(float duration, String name, int fps) {
+        this.name = name;
+        this.duration = duration;
+        this.fps = fps;
+        totalFrames = (int) (fps * duration) + 1;
+        tpf = 1 / (float) fps;
+        times = new float[totalFrames];
+        translations = new Vector3f[totalFrames];
+        rotations = new Quaternion[totalFrames];
+        scales = new Vector3f[totalFrames];
+        keyFramesTranslation = new Vector3f[totalFrames];
+        keyFramesTranslation[0] = new Vector3f();
+        keyFramesScale = new Vector3f[totalFrames];
+        keyFramesScale[0] = new Vector3f(1, 1, 1);
+        keyFramesRotation = new Rotation[totalFrames];
+        keyFramesRotation[0] = new Rotation();
+
+    }
+
+    /**
+     * Adds a key frame for the given Transform at the given time
+     * @param time the time at which the keyFrame must be inserted
+     * @param transform the transforms to use for this keyFrame
+     */
+    public void addTimeTransform(float time, Transform transform) {
+        addKeyFrameTransform((int) (time / tpf), transform);
+    }
+
+    /**
+     * Adds a key frame for the given Transform at the given keyFrame index
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param transform the transforms to use for this keyFrame
+     */
+    public void addKeyFrameTransform(int keyFrameIndex, Transform transform) {
+        addKeyFrameTranslation(keyFrameIndex, transform.getTranslation());
+        addKeyFrameScale(keyFrameIndex, transform.getScale());
+        addKeyFrameRotation(keyFrameIndex, transform.getRotation());
+    }
+
+    /**
+     * Adds a key frame for the given translation at the given time
+     * @param time the time at which the keyFrame must be inserted
+     * @param translation the translation to use for this keyFrame
+     */
+    public void addTimeTranslation(float time, Vector3f translation) {
+        addKeyFrameTranslation((int) (time / tpf), translation);
+    }
+
+    /**
+     * Adds a key frame for the given translation at the given keyFrame index
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param translation the translation to use for this keyFrame
+     */
+    public void addKeyFrameTranslation(int keyFrameIndex, Vector3f translation) {
+        Vector3f t = getTranslationForFrame(keyFrameIndex);
+        t.set(translation);
+    }
+
+    /**
+     * Adds a key frame for the given rotation at the given time<br>
+     * This can't be used if the interpolated angle is higher than PI (180°)<br>
+     * Use {@link #addTimeRotationAngles(float time, float x, float y, float z)}  instead that uses Euler angles rotations.<br>     * 
+     * @param time the time at which the keyFrame must be inserted
+     * @param rotation the rotation Quaternion to use for this keyFrame
+     * @see #addTimeRotationAngles(float time, float x, float y, float z) 
+     */
+    public void addTimeRotation(float time, Quaternion rotation) {
+        addKeyFrameRotation((int) (time / tpf), rotation);
+    }
+
+    /**
+     * Adds a key frame for the given rotation at the given keyFrame index<br>
+     * This can't be used if the interpolated angle is higher than PI (180°)<br>
+     * Use {@link #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angles rotations.
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param rotation the rotation Quaternion to use for this keyFrame
+     * @see #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) 
+     */
+    public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) {
+        Rotation r = getRotationForFrame(keyFrameIndex);
+        r.set(rotation);
+    }
+
+    /**
+     * Adds a key frame for the given rotation at the given time.<br>
+     * Rotation is expressed by Euler angles values in radians.<br>
+     * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br>
+     * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br>
+     * 
+     * @param time the time at which the keyFrame must be inserted
+     * @param x the rotation around the x axis (aka yaw) in radians
+     * @param y the rotation around the y axis (aka roll) in radians
+     * @param z the rotation around the z axis (aka pitch) in radians
+     */
+    public void addTimeRotationAngles(float time, float x, float y, float z) {
+        addKeyFrameRotationAngles((int) (time / tpf), x, y, z);
+    }
+
+    /**
+     * Adds a key frame for the given rotation at the given key frame index.<br>
+     * Rotation is expressed by Euler angles values in radians.<br>
+     * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br>
+     * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br>
+     * 
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param x the rotation around the x axis (aka yaw) in radians
+     * @param y the rotation around the y axis (aka roll) in radians
+     * @param z the rotation around the z axis (aka pitch) in radians
+     */
+    public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) {
+        Rotation r = getRotationForFrame(keyFrameIndex);
+        r.set(x, y, z);
+
+        // if the delta of euler angles is higher than PI, we create intermediate keyframes
+        // since we are using quaternions and slerp for rotation interpolation, we cannot interpolate over an angle higher than PI
+        int prev = getPreviousKeyFrame(keyFrameIndex, keyFramesRotation);
+        if (prev != -1) {
+            //previous rotation keyframe
+            Rotation prevRot = keyFramesRotation[prev];
+            //the maximum delta angle (x,y or z)
+            float delta = Math.max(Math.abs(x - prevRot.eulerAngles.x), Math.abs(y - prevRot.eulerAngles.y));
+            delta = Math.max(delta, Math.abs(z - prevRot.eulerAngles.z));
+            //if delta > PI we have to create intermediates key frames
+            if (delta >= FastMath.PI) {
+                //frames delta
+                int dF = keyFrameIndex - prev;
+                //angle per frame for x,y ,z
+                float dXAngle = (x - prevRot.eulerAngles.x) / (float) dF;
+                float dYAngle = (y - prevRot.eulerAngles.y) / (float) dF;
+                float dZAngle = (z - prevRot.eulerAngles.z) / (float) dF;
+
+                // the keyFrame step
+                int keyStep = (int) (((float) (dF)) / delta * (float) EULER_STEP);
+                // the current keyFrame
+                int cursor = prev + keyStep;
+                while (cursor < keyFrameIndex) {
+                    //for each step we create a new rotation by interpolating the angles
+                    Rotation dr = getRotationForFrame(cursor);
+                    dr.masterKeyFrame = keyFrameIndex;
+                    dr.set(prevRot.eulerAngles.x + cursor * dXAngle, prevRot.eulerAngles.y + cursor * dYAngle, prevRot.eulerAngles.z + cursor * dZAngle);
+                    cursor += keyStep;
+                }
+
+            }
+        }
+
+    }
+
+    /**
+     * Adds a key frame for the given scale at the given time
+     * @param time the time at which the keyFrame must be inserted
+     * @param scale the scale to use for this keyFrame
+     */
+    public void addTimeScale(float time, Vector3f scale) {
+        addKeyFrameScale((int) (time / tpf), scale);
+    }
+
+    /**
+     * Adds a key frame for the given scale at the given keyFrame index
+     * @param keyFrameIndex the index at which the keyFrame must be inserted
+     * @param scale the scale to use for this keyFrame
+     */
+    public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) {
+        Vector3f s = getScaleForFrame(keyFrameIndex);
+        s.set(scale);
+    }
+
+    /**
+     * returns the translation for a given frame index
+     * creates the translation if it doesn't exists
+     * @param keyFrameIndex index
+     * @return the translation
+     */
+    private Vector3f getTranslationForFrame(int keyFrameIndex) {
+        if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+            throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+        }
+        Vector3f v = keyFramesTranslation[keyFrameIndex];
+        if (v == null) {
+            v = new Vector3f();
+            keyFramesTranslation[keyFrameIndex] = v;
+        }
+        return v;
+    }
+
+    /**
+     * returns the scale for a given frame index
+     * creates the scale if it doesn't exists
+     * @param keyFrameIndex index
+     * @return the scale
+     */
+    private Vector3f getScaleForFrame(int keyFrameIndex) {
+        if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+            throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+        }
+        Vector3f v = keyFramesScale[keyFrameIndex];
+        if (v == null) {
+            v = new Vector3f();
+            keyFramesScale[keyFrameIndex] = v;
+        }
+        return v;
+    }
+
+    /**
+     * returns the rotation for a given frame index
+     * creates the rotation if it doesn't exists
+     * @param keyFrameIndex index
+     * @return the rotation
+     */
+    private Rotation getRotationForFrame(int keyFrameIndex) {
+        if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+            throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+        }
+        Rotation v = keyFramesRotation[keyFrameIndex];
+        if (v == null) {
+            v = new Rotation();
+            keyFramesRotation[keyFrameIndex] = v;
+        }
+        return v;
+    }
+
+    /**
+     * Creates an Animation based on the keyFrames previously added to the helper.
+     * @return the generated animation 
+     */
+    public Animation buildAnimation() {
+        interpolateTime();
+        interpolate(keyFramesTranslation, Type.Translation);
+        interpolate(keyFramesRotation, Type.Rotation);
+        interpolate(keyFramesScale, Type.Scale);
+
+        SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales);
+
+        //creating the animation
+        Animation spatialAnimation = new Animation(name, duration);
+        spatialAnimation.setTracks(new SpatialTrack[]{spatialTrack});
+
+        return spatialAnimation;
+    }
+
+    /**
+     * interpolates time values
+     */
+    private void interpolateTime() {
+        for (int i = 0; i < totalFrames; i++) {
+            times[i] = i * tpf;
+        }
+    }
+
+    /**
+     * Interpolates over the key frames for the given keyFrame array and the given type of transform
+     * @param keyFrames the keyFrames array
+     * @param type the type of transforms
+     */
+    private void interpolate(Object[] keyFrames, Type type) {
+        int i = 0;
+        while (i < totalFrames) {
+            //fetching the next keyFrame index transform in the array
+            int key = getNextKeyFrame(i, keyFrames);
+            if (key != -1) {
+                //computing the frame span to interpolate over
+                int span = key - i;
+                //interating over the frames
+                for (int j = i; j <= key; j++) {
+                    // computing interpolation value
+                    float val = (float) (j - i) / (float) span;
+                    //interpolationg depending on the transform type
+                    switch (type) {
+                        case Translation:
+                            translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]);
+                            break;
+                        case Rotation:
+                            Quaternion rot = new Quaternion();
+                            rotations[j] = rot.slerp(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation, val);
+                            break;
+                        case Scale:
+                            scales[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]);
+                            break;
+                    }
+                }
+                //jumping to the next keyFrame
+                i = key;
+            } else {
+                //No more key frame, filling the array witht he last transform computed.
+                for (int j = i; j < totalFrames; j++) {
+
+                    switch (type) {
+                        case Translation:
+                            translations[j] = ((Vector3f) keyFrames[i]).clone();
+                            break;
+                        case Rotation:
+                            rotations[j] = ((Quaternion) ((Rotation) keyFrames[i]).rotation).clone();
+                            break;
+                        case Scale:
+                            scales[j] = ((Vector3f) keyFrames[i]).clone();
+                            break;
+                    }
+                }
+                //we're done
+                i = totalFrames;
+            }
+        }
+    }
+
+    /**
+     * Get the index of the next keyFrame that as a transform
+     * @param index the start index
+     * @param keyFrames the keyFrames array
+     * @return the index of the next keyFrame
+     */
+    private int getNextKeyFrame(int index, Object[] keyFrames) {
+        for (int i = index + 1; i < totalFrames; i++) {
+            if (keyFrames[i] != null) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Get the index of the previous keyFrame that as a transform
+     * @param index the start index
+     * @param keyFrames the keyFrames array
+     * @return the index of the previous keyFrame
+     */
+    private int getPreviousKeyFrame(int index, Object[] keyFrames) {
+        for (int i = index - 1; i >= 0; i--) {
+            if (keyFrames[i] != null) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/engine/src/core/com/jme3/animation/AudioTrack.java b/engine/src/core/com/jme3/animation/AudioTrack.java
new file mode 100644 (file)
index 0000000..1440af1
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2009-2012 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 com.jme3.animation;
+
+import com.jme3.audio.AudioNode;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * AudioTrack is a track to add to an existing animation, to paly a sound during
+ * an animations for example : gun shot, foot step, shout, etc...
+ *
+ * usage is
+ * <pre>
+ * AnimControl control model.getControl(AnimControl.class);
+ * AudioTrack track = new AudioTrack(existionAudioNode, control.getAnim("TheAnim").getLength());
+ * control.getAnim("TheAnim").addTrack(track);
+ * </pre>
+ *
+ * This is mostly intended for short sounds, playInstance will be called on the
+ * AudioNode at time 0 + startOffset.
+ *
+ *
+ * @author Nehon
+ */
+public class AudioTrack implements ClonableTrack {
+
+    private static final Logger logger = Logger.getLogger(AudioTrack.class.getName());
+    private AudioNode audio;
+    private float startOffset = 0;
+    private float length = 0;
+    private boolean initialized = false;
+    private boolean started = false;
+    private boolean played = false;
+
+    //Animation listener to stop the sound when the animation ends or is changed
+    private class OnEndListener implements AnimEventListener {
+
+        public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
+            stop();
+        }
+
+        public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
+        }
+    }
+
+    /**
+     * default constructor for serialization only
+     */
+    public AudioTrack() {
+    }
+
+    /**
+     * Creates an AudioTrack
+     *
+     * @param audio the AudioNode
+     * @param length the length of the track (usually the length of the
+     * animation you want to add the track to)
+     */
+    public AudioTrack(AudioNode audio, float length) {
+        this.audio = audio;
+        this.length = length;
+        setUserData(this);
+    }
+
+    /**
+     * Creates an AudioTrack
+     *
+     * @param audio the AudioNode
+     * @param length the length of the track (usually the length of the
+     * animation you want to add the track to)
+     * @param startOffset the time in second when the sound will be played after
+     * the animation starts (default is 0)
+     */
+    public AudioTrack(AudioNode audio, float length, float startOffset) {
+        this(audio, length);
+        this.startOffset = startOffset;
+    }
+
+    /**
+     * Internal use only
+     *
+     * @see Track#setTime(float, float, com.jme3.animation.AnimControl,
+     * com.jme3.animation.AnimChannel, com.jme3.util.TempVars)
+     */
+    public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
+
+        if (time >= length) {
+            return;
+        }
+        if (!initialized) {
+            control.addListener(new OnEndListener());
+            initialized = true;
+        }
+        if (!started && time >= startOffset) {
+            started = true;
+            audio.playInstance();
+        }
+    }
+
+    //stops the sound
+    private void stop() {
+        audio.stop();
+        started = false;
+    }
+
+    /**
+     * Retruns the length of the track
+     *
+     * @return length of the track
+     */
+    public float getLength() {
+        return length;
+    }
+
+    /**
+     * Clone this track
+     *
+     * @return
+     */
+    @Override
+    public Track clone() {
+        return new AudioTrack(audio, length, startOffset);
+    }
+
+    /**
+     * This method clone the Track and search for the cloned counterpart of the
+     * original audio node in the given cloned spatial. The spatial is assumed
+     * to be the Spatial holding the AnimControl controling the animation using
+     * this Track.
+     *
+     * @param spatial the Spatial holding the AnimControl
+     * @return the cloned Track with proper reference
+     */
+    public Track cloneForSpatial(Spatial spatial) {
+        AudioTrack audioTrack = new AudioTrack();
+        audioTrack.length = this.length;
+        audioTrack.startOffset = this.startOffset;
+
+        //searching for the newly cloned AudioNode
+        audioTrack.audio = findAudio(spatial);
+        if (audioTrack.audio == null) {
+            logger.log(Level.WARNING, "{0} was not found in {1} or is not bound to this track", new Object[]{audio.getName(), spatial.getName()});
+            audioTrack.audio = audio;
+        }
+
+        //setting user data on the new AudioNode and marking it with a reference to the cloned Track.
+        setUserData(audioTrack);
+
+        return audioTrack;
+    }
+
+    /**
+     * recursive function responsible for finding the newly cloned AudioNode
+     *
+     * @param spat
+     * @return
+     */
+    private AudioNode findAudio(Spatial spat) {
+        if (spat instanceof AudioNode) {
+            //spat is an AudioNode
+            AudioNode em = (AudioNode) spat;
+            //getting the UserData TrackInfo so check if it should be attached to this Track
+            TrackInfo t = (TrackInfo) em.getUserData("TrackInfo");
+            if (t != null && t.getTracks().contains(this)) {
+                return em;
+            }
+            return null;
+
+        } else if (spat instanceof Node) {
+            for (Spatial child : ((Node) spat).getChildren()) {
+                AudioNode em = findAudio(child);
+                if (em != null) {
+                    return em;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void setUserData(AudioTrack audioTrack) {
+        //fetching the UserData TrackInfo.
+        TrackInfo data = (TrackInfo) audioTrack.audio.getUserData("TrackInfo");
+
+        //if it does not exist, we create it and attach it to the AudioNode.
+        if (data == null) {
+            data = new TrackInfo();
+            audioTrack.audio.setUserData("TrackInfo", data);
+        }
+
+        //adding the given Track to the TrackInfo.
+        data.addTrack(audioTrack);
+    }
+
+    public void cleanUp() {
+        TrackInfo t = (TrackInfo) audio.getUserData("TrackInfo");
+        t.getTracks().remove(this);
+        if (!t.getTracks().isEmpty()) {
+            audio.setUserData("TrackInfo", null);
+        }
+    }
+
+    /**
+     *
+     * @return the audio node used by this track
+     */
+    public AudioNode getAudio() {
+        return audio;
+    }
+
+    /**
+     * sets the audio node to be used for this track
+     *
+     * @param audio
+     */
+    public void setAudio(AudioNode audio) {
+        if (this.audio != null) {
+            TrackInfo data = (TrackInfo) audio.getUserData("TrackInfo");
+            data.getTracks().remove(this);
+        }
+        this.audio = audio;
+        setUserData(this);
+    }
+
+    /**
+     *
+     * @return the start offset of the track
+     */
+    public float getStartOffset() {
+        return startOffset;
+    }
+
+    /**
+     * set the start offset of the track
+     *
+     * @param startOffset
+     */
+    public void setStartOffset(float startOffset) {
+        this.startOffset = startOffset;
+    }
+
+    /**
+     * Internal use only serialization
+     *
+     * @param ex exporter
+     * @throws IOException exception
+     */
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule out = ex.getCapsule(this);
+        out.write(audio, "audio", null);
+        out.write(length, "length", 0);
+        out.write(startOffset, "startOffset", 0);
+    }
+
+    /**
+     * Internal use only serialization
+     *
+     * @param im importer
+     * @throws IOException Exception
+     */
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule in = im.getCapsule(this);
+        audio = (AudioNode) in.readSavable("audio", null);
+        length = in.readFloat("length", length);
+        startOffset = in.readFloat("startOffset", 0);
+    }
+}
index f1343a5..0ffe374 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  */
 package com.jme3.animation;
 
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
-import com.jme3.math.Matrix3f;
-import com.jme3.math.Matrix4f;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Transform;
-import com.jme3.math.Vector3f;
+import com.jme3.export.*;
+import com.jme3.math.*;
 import com.jme3.scene.Node;
 import com.jme3.util.TempVars;
 import java.io.IOException;
@@ -63,7 +55,6 @@ public final class Bone implements Savable {
      * Animation transforms are not applied to this bone when enabled.
      */
     private boolean userControl = false;
-    private boolean useModelSpaceVectors = false;
     /**
      * The attachment node.
      */
@@ -94,8 +85,16 @@ public final class Bone implements Savable {
     private Vector3f worldPos = new Vector3f();
     private Quaternion worldRot = new Quaternion();
     private Vector3f worldScale = new Vector3f();
-    //used for getCombinedTransform 
+    
+    // Used for getCombinedTransform
     private Transform tmpTransform = new Transform();
+    
+    /**
+     * Used to handle blending from one animation to another.
+     * See {@link #blendAnimTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion, com.jme3.math.Vector3f, float)}
+     * on how this variable is used.
+     */
+    private transient float currentWeightSum = -1;
 
     /**
      * Creates a new bone with the given name.
@@ -332,27 +331,45 @@ public final class Bone implements Savable {
      * world transform with this bones' local transform.
      */
     public final void updateWorldVectors() {
-        if (true || !useModelSpaceVectors) {
-            if (parent != null) {
-                //rotation
-                parent.worldRot.mult(localRot, worldRot);
-
-                //scale
-                //For scale parent scale is not taken into account!
-                // worldScale.set(localScale);
-                parent.worldScale.mult(localScale, worldScale);
-
-                //translation
-                //scale and rotation of parent affect bone position            
-                parent.worldRot.mult(localPos, worldPos);
-                worldPos.multLocal(parent.worldScale);
-                worldPos.addLocal(parent.worldPos);
+        if (currentWeightSum == 1f) {
+            currentWeightSum = -1;
+        } else if (currentWeightSum != -1f) {
+            // Apply the weight to the local transform
+            if (currentWeightSum == 0) {
+                localRot.set(initialRot);
+                localPos.set(initialPos);
+                localScale.set(initialScale);
             } else {
-                worldRot.set(localRot);
-                worldPos.set(localPos);
-                worldScale.set(localScale);
+                float invWeightSum = 1f - currentWeightSum;
+                localRot.nlerp(initialRot, invWeightSum);
+                localPos.interpolate(initialPos, invWeightSum);
+                localScale.interpolate(initialScale, invWeightSum);
             }
+            
+            // Future invocations of transform blend will start over.
+            currentWeightSum = -1;
+        }
+        
+        if (parent != null) {
+            //rotation
+            parent.worldRot.mult(localRot, worldRot);
+
+            //scale
+            //For scale parent scale is not taken into account!
+            // worldScale.set(localScale);
+            parent.worldScale.mult(localScale, worldScale);
+
+            //translation
+            //scale and rotation of parent affect bone position            
+            parent.worldRot.mult(localPos, worldPos);
+            worldPos.multLocal(parent.worldScale);
+            worldPos.addLocal(parent.worldPos);
+        } else {
+            worldRot.set(localRot);
+            worldPos.set(localPos);
+            worldScale.set(localScale);
         }
+
         if (attachNode != null) {
             attachNode.setLocalTranslation(worldPos);
             attachNode.setLocalRotation(worldRot);
@@ -471,6 +488,12 @@ public final class Bone implements Savable {
         // TODO: add scale here ???
         worldPos.set(translation);
         worldRot.set(rotation);
+        
+        //if there is an attached Node we need to set it's local transforms too.
+        if(attachNode != null){
+            attachNode.setLocalTranslation(translation);
+            attachNode.setLocalRotation(rotation);
+        }
     }
 
     /**
@@ -489,7 +512,7 @@ public final class Bone implements Savable {
      * Attach models and effects to this node to make
      * them follow this bone's motions.
      */
-    public Node getAttachmentsNode() {
+    Node getAttachmentsNode() {
         if (attachNode == null) {
             attachNode = new Node(name + "_attachnode");
             attachNode.setUserData("AttachedBone", this);
@@ -526,34 +549,70 @@ public final class Bone implements Savable {
         }
     }
 
+    /**
+     * Blends the given animation transform onto the bone's local transform.
+     * <p>
+     * Subsequent calls of this method stack up, with the final transformation
+     * of the bone computed at {@link #updateWorldVectors() } which resets
+     * the stack.
+     * <p>
+     * E.g. a single transform blend with weight = 0.5 followed by an
+     * updateWorldVectors() call will result in final transform = transform * 0.5.
+     * Two transform blends with weight = 0.5 each will result in the two
+     * transforms blended together (nlerp) with blend = 0.5.
+     * 
+     * @param translation The translation to blend in
+     * @param rotation The rotation to blend in
+     * @param scale The scale to blend in
+     * @param weight The weight of the transform to apply. Set to 1.0 to prevent
+     * any other transform from being applied until updateWorldVectors().
+     */
     void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) {
         if (userControl) {
             return;
         }
-
-        TempVars vars = TempVars.get();
-//        assert vars.lock();
-
-        Vector3f tmpV = vars.vect1;
-        Vector3f tmpV2 = vars.vect2;
-        Quaternion tmpQ = vars.quat1;
-
-        //location
-        tmpV.set(initialPos).addLocal(translation);
-        localPos.interpolate(tmpV, weight);
-
-        //rotation
-        tmpQ.set(initialRot).multLocal(rotation);
-        localRot.nlerp(tmpQ, weight);
-
-        //scale
-        if (scale != null) {
-            tmpV2.set(initialScale).multLocal(scale);
-            localScale.interpolate(tmpV2, weight);
+        
+        if (weight == 0) {
+            // Do not apply this transform at all.
+            return;
         }
 
-
-        vars.release();
+        if (currentWeightSum == 1){
+            return; // More than 2 transforms are being blended
+        } else if (currentWeightSum == -1 || currentWeightSum == 0) {
+            // Set the transform fully
+            localPos.set(initialPos).addLocal(translation);
+            localRot.set(initialRot).multLocal(rotation);
+            if (scale != null) {
+                localScale.set(initialScale).multLocal(scale);
+            }
+            // Set the weight. It will be applied in updateWorldVectors().
+            currentWeightSum = weight;
+        } else {
+            // The weight is already set. 
+            // Blend in the new transform.
+            TempVars vars = TempVars.get();
+
+            Vector3f tmpV = vars.vect1;
+            Vector3f tmpV2 = vars.vect2;
+            Quaternion tmpQ = vars.quat1;
+            
+            tmpV.set(initialPos).addLocal(translation);
+            localPos.interpolate(tmpV, weight);
+
+            tmpQ.set(initialRot).multLocal(rotation);
+            localRot.nlerp(tmpQ, weight);
+
+            if (scale != null) {
+                tmpV2.set(initialScale).multLocal(scale);
+                localScale.interpolate(tmpV2, weight);
+            }
+        
+            // Ensures no new weights will be blended in the future.
+            currentWeightSum = 1;
+            
+            vars.release();
+        }
     }
 
     /**
@@ -593,14 +652,6 @@ public final class Bone implements Savable {
         return this.toString(0);
     }
 
-    public boolean isUseModelSpaceVectors() {
-        return useModelSpaceVectors;
-    }
-
-    public void setUseModelSpaceVectors(boolean useModelSpaceVectors) {
-        this.useModelSpaceVectors = useModelSpaceVectors;
-    }
-
     @Override
     @SuppressWarnings("unchecked")
     public void read(JmeImporter im) throws IOException {
index f5ffd40..735ee05 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
index aa73106..dda20d7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  */
 package com.jme3.animation;
 
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.util.TempVars;
@@ -186,8 +182,10 @@ public final class BoneTrack implements Track {
      * The transforms can be interpolated in some method from the keyframes.
      *
      * @param time the current time of the animation
-     * @param skeleton the skeleton to which the bone belong
      * @param weight the weight of the animation
+     * @param control
+     * @param channel
+     * @param vars
      */
     public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
         BitSet affectedBones = channel.getAffectedBones();
@@ -245,11 +243,11 @@ public final class BoneTrack implements Track {
             tempS.interpolate(tempS2, blend);
         }
 
-        if (weight != 1f) {
+//        if (weight != 1f) {
             target.blendAnimTransforms(tempV, tempQ, scales != null ? tempS : null, weight);
-        } else {
-            target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null);
-        }
+//        } else {
+//            target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null);
+//        }
     }
     
     /**
diff --git a/engine/src/core/com/jme3/animation/ClonableTrack.java b/engine/src/core/com/jme3/animation/ClonableTrack.java
new file mode 100644 (file)
index 0000000..ae7ae67
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2009-2012 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 com.jme3.animation;
+
+import com.jme3.scene.Spatial;
+
+/**
+ * An interface that allow to clone a Track for a given Spatial.
+ * The spatial fed to the method is the Spatial holding the AnimControl controling the Animation using this track.
+ * 
+ * Implement this interface only if you make your own Savable Track and that the track has a direct reference to a Spatial in the scene graph.
+ * This Spatial is assumed to be a child of the spatial holding the AnimControl.
+ *  
+ *
+ * @author Nehon
+ */
+public interface ClonableTrack extends Track {
+
+    /**
+     * Allows to clone the track for a given Spatial.
+     * The spatial fed to the method is the Spatial holding the AnimControl controling the Animation using this track.
+     * This method will be called during the loading process of a j3o model by the assetManager.
+     * The assetManager keeps the original model in cache and returns a clone of the model.
+     * 
+     * This method prupose is to find the cloned reference of the original spatial which it refers to in the cloned model.
+     * 
+     * See EffectTrack for a proper implementation.
+     * 
+     * @param spatial the spatial holding the AnimControl
+     * @return  the cloned Track
+     */
+    public Track cloneForSpatial(Spatial spatial);
+    
+    /**
+     * Method responsible of cleaning UserData on referenced Spatials when the Track is deleted
+     */
+    public void cleanUp();
+}
index 6a2a9be..03983a1 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
index 8dc24ad..21b1bd9 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
  */\r
 package com.jme3.animation;\r
 \r
-import java.io.IOException;\r
-\r
-import com.jme3.export.InputCapsule;\r
-import com.jme3.export.JmeExporter;\r
-import com.jme3.export.JmeImporter;\r
-import com.jme3.export.OutputCapsule;\r
-import com.jme3.export.Savable;\r
+import com.jme3.export.*;\r
 import com.jme3.math.Quaternion;\r
+import java.io.IOException;\r
 \r
 /**\r
  * Serialize and compress {@link Quaternion}[] by indexing same values\r
index c6edf3d..289f81e 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
  * All rights reserved.\r
  *\r
  * Redistribution and use in source and binary forms, with or without\r
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
  */\r
-\r
 package com.jme3.animation;\r
 \r
-import java.io.IOException;\r
-\r
-import com.jme3.export.InputCapsule;\r
-import com.jme3.export.JmeExporter;\r
-import com.jme3.export.JmeImporter;\r
-import com.jme3.export.OutputCapsule;\r
-import com.jme3.export.Savable;\r
+import com.jme3.export.*;\r
 import com.jme3.math.Vector3f;\r
+import java.io.IOException;\r
 \r
 /**\r
  * Serialize and compress Vector3f[] by indexing same values\r
diff --git a/engine/src/core/com/jme3/animation/EffectTrack.java b/engine/src/core/com/jme3/animation/EffectTrack.java
new file mode 100644 (file)
index 0000000..9d6b9b4
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2009-2012 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 com.jme3.animation;
+
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * EffectTrack is a track to add to an existing animation, to emmit particles
+ * during animations for example : exhausts, dust raised by foot steps, shock
+ * waves, lightnings etc...
+ *
+ * usage is
+ * <pre>
+ * AnimControl control model.getControl(AnimControl.class);
+ * EffectTrack track = new EffectTrack(existingEmmitter, control.getAnim("TheAnim").getLength());
+ * control.getAnim("TheAnim").addTrack(track);
+ * </pre>
+ *
+ * if the emitter has emmits 0 particles per seconds emmitAllPArticles will be
+ * called on it at time 0 + startOffset. if it he it has more it will start
+ * emmit normally at time 0 + startOffset.
+ *
+ *
+ * @author Nehon
+ */
+public class EffectTrack implements ClonableTrack {
+
+    private static final Logger logger = Logger.getLogger(EffectTrack.class.getName());
+    private ParticleEmitter emitter;
+    private float startOffset = 0;
+    private float particlesPerSeconds = 0;
+    private float length = 0;
+    private boolean emitted = false;
+    private boolean initialized = false;
+    //control responsible for disable and cull the emitter once all particles are gone
+    private KillParticleControl killParticles = new KillParticleControl();
+
+    public static class KillParticleControl extends AbstractControl {
+
+        ParticleEmitter emitter;
+        boolean stopRequested = false;
+        boolean remove = false;
+
+        public KillParticleControl() {
+        }
+
+        @Override
+        public void setSpatial(Spatial spatial) {
+            super.setSpatial(spatial);
+            if (spatial != null) {
+                if (spatial instanceof ParticleEmitter) {
+                    emitter = (ParticleEmitter) spatial;
+                } else {
+                    throw new IllegalArgumentException("KillParticleEmitter can only ba attached to ParticleEmitter");
+                }
+            }
+
+
+        }
+
+        @Override
+        protected void controlUpdate(float tpf) {
+            if (remove) {
+                emitter.removeControl(this);
+                return;
+            }
+            if (emitter.getNumVisibleParticles() == 0) {
+                emitter.setCullHint(CullHint.Always);
+                emitter.setEnabled(false);
+                emitter.removeControl(this);
+                stopRequested = false;
+            }
+        }
+
+        @Override
+        protected void controlRender(RenderManager rm, ViewPort vp) {
+        }
+
+        @Override
+        public Control cloneForSpatial(Spatial spatial) {
+
+            KillParticleControl c = new KillParticleControl();
+            //this control should be removed as it shouldn't have been persisted in the first place
+            //In the quest to find the less hackish solution to achieve this, 
+            //making it remove itself from the spatial in the first update loop when loaded was the less bad. 
+            c.remove = true;
+            c.setSpatial(spatial);
+            return c;
+
+        }
+    };
+
+    //Anim listener that stops the Emmitter when the animation is finished or changed.
+    private class OnEndListener implements AnimEventListener {
+
+        public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
+            stop();
+        }
+
+        public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
+        }
+    }
+
+    /**
+     * default constructor only for serialization
+     */
+    public EffectTrack() {
+    }
+
+    /**
+     * Creates and EffectTrack
+     *
+     * @param emitter the emmitter of the track
+     * @param length the length of the track (usually the length of the
+     * animation you want to add the track to)
+     */
+    public EffectTrack(ParticleEmitter emitter, float length) {
+        this.emitter = emitter;
+        //saving particles per second value
+        this.particlesPerSeconds = emitter.getParticlesPerSec();
+        //setting the emmitter to not emmit.
+        this.emitter.setParticlesPerSec(0);
+        this.length = length;
+        //Marking the emitter with a reference to this track for further use in deserialization.
+        setUserData(this);
+
+    }
+
+    /**
+     * Creates and EffectTrack
+     *
+     * @param emitter the emmitter of the track
+     * @param length the length of the track (usually the length of the
+     * animation you want to add the track to)
+     * @param startOffset the time in second when the emitter will be triggerd
+     * after the animation starts (default is 0)
+     */
+    public EffectTrack(ParticleEmitter emitter, float length, float startOffset) {
+        this(emitter, length);
+        this.startOffset = startOffset;
+    }
+
+    /**
+     * Internal use only
+     *
+     * @see Track#setTime(float, float, com.jme3.animation.AnimControl,
+     * com.jme3.animation.AnimChannel, com.jme3.util.TempVars)
+     */
+    public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
+
+        if (time >= length) {
+            return;
+        }
+        //first time adding the Animation listener to stop the track at the end of the animation
+        if (!initialized) {
+            control.addListener(new OnEndListener());
+            initialized = true;
+        }
+        //checking fo time to trigger the effect
+        if (!emitted && time >= startOffset) {
+            emitted = true;
+            emitter.setCullHint(CullHint.Dynamic);
+            emitter.setEnabled(true);
+            //if the emitter has 0 particles per seconds emmit all particles in one shot
+            if (particlesPerSeconds == 0) {
+                emitter.emitAllParticles();
+                if (!killParticles.stopRequested) {
+                    emitter.addControl(killParticles);
+                    killParticles.stopRequested = true;
+                }
+            } else {
+                //else reset its former particlePerSec value to let it emmit.
+                emitter.setParticlesPerSec(particlesPerSeconds);
+            }
+        }
+    }
+
+    //stops the emmiter to emit.
+    private void stop() {
+        emitter.setParticlesPerSec(0);
+        emitted = false;
+        if (!killParticles.stopRequested) {
+            emitter.addControl(killParticles);
+            killParticles.stopRequested = true;
+        }
+
+    }
+
+    /**
+     * Retruns the length of the track
+     *
+     * @return length of the track
+     */
+    public float getLength() {
+        return length;
+    }
+
+    /**
+     * Clone this track
+     *
+     * @return
+     */
+    @Override
+    public Track clone() {
+        return new EffectTrack(emitter, length, startOffset);
+    }
+
+    /**
+     * This method clone the Track and search for the cloned counterpart of the
+     * original emmitter in the given cloned spatial. The spatial is assumed to
+     * be the Spatial holding the AnimControl controling the animation using
+     * this Track.
+     *
+     * @param spatial the Spatial holding the AnimControl
+     * @return the cloned Track with proper reference
+     */
+    public Track cloneForSpatial(Spatial spatial) {
+        EffectTrack effectTrack = new EffectTrack();
+        effectTrack.particlesPerSeconds = this.particlesPerSeconds;
+        effectTrack.length = this.length;
+        effectTrack.startOffset = this.startOffset;
+
+        //searching for the newly cloned ParticleEmitter
+        effectTrack.emitter = findEmitter(spatial);
+        if (effectTrack.emitter == null) {
+            logger.log(Level.WARNING, "{0} was not found in {1} or is not bound to this track", new Object[]{emitter.getName(), spatial.getName()});
+            effectTrack.emitter = emitter;
+        }
+
+        removeUserData(this);
+        //setting user data on the new emmitter and marking it with a reference to the cloned Track.
+        setUserData(effectTrack);
+        effectTrack.emitter.setParticlesPerSec(0);
+        return effectTrack;
+    }
+
+    /**
+     * recursive function responsible for finding the newly cloned Emitter
+     *
+     * @param spat
+     * @return
+     */
+    private ParticleEmitter findEmitter(Spatial spat) {
+        if (spat instanceof ParticleEmitter) {
+            //spat is a PArticleEmitter
+            ParticleEmitter em = (ParticleEmitter) spat;
+            //getting the UserData TrackInfo so check if it should be attached to this Track
+            TrackInfo t = (TrackInfo) em.getUserData("TrackInfo");
+            if (t != null && t.getTracks().contains(this)) {
+                return em;
+            }
+            return null;
+
+        } else if (spat instanceof Node) {
+            for (Spatial child : ((Node) spat).getChildren()) {
+                ParticleEmitter em = findEmitter(child);
+                if (em != null) {
+                    return em;
+                }
+            }
+        }
+        return null;
+    }
+
+    public void cleanUp() {
+        TrackInfo t = (TrackInfo) emitter.getUserData("TrackInfo");
+        t.getTracks().remove(this);
+        if (t.getTracks().isEmpty()) {
+            emitter.setUserData("TrackInfo", null);
+        }
+    }
+
+    /**
+     *
+     * @return the emitter used by this track
+     */
+    public ParticleEmitter getEmitter() {
+        return emitter;
+    }
+
+    /**
+     * Sets the Emitter to use in this track
+     *
+     * @param emitter
+     */
+    public void setEmitter(ParticleEmitter emitter) {
+        if (this.emitter != null) {
+            TrackInfo data = (TrackInfo) emitter.getUserData("TrackInfo");
+            data.getTracks().remove(this);
+        }
+        this.emitter = emitter;
+        //saving particles per second value
+        this.particlesPerSeconds = emitter.getParticlesPerSec();
+        //setting the emmitter to not emmit.
+        this.emitter.setParticlesPerSec(0);
+        setUserData(this);
+    }
+
+    /**
+     *
+     * @return the start offset of the track
+     */
+    public float getStartOffset() {
+        return startOffset;
+    }
+
+    /**
+     * set the start offset of the track
+     *
+     * @param startOffset
+     */
+    public void setStartOffset(float startOffset) {
+        this.startOffset = startOffset;
+    }
+
+    private void setUserData(EffectTrack effectTrack) {
+        //fetching the UserData TrackInfo.
+        TrackInfo data = (TrackInfo) effectTrack.emitter.getUserData("TrackInfo");
+
+        //if it does not exist, we create it and attach it to the emitter.
+        if (data == null) {
+            data = new TrackInfo();
+            effectTrack.emitter.setUserData("TrackInfo", data);
+        }
+
+        //adding the given Track to the TrackInfo.
+        data.addTrack(effectTrack);
+
+
+    }
+
+    private void removeUserData(EffectTrack effectTrack) {
+        //fetching the UserData TrackInfo.
+        TrackInfo data = (TrackInfo) effectTrack.emitter.getUserData("TrackInfo");
+
+        //if it does not exist, we create it and attach it to the emitter.
+        if (data == null) {
+            return;
+        }
+
+        //removing the given Track to the TrackInfo.
+        data.getTracks().remove(effectTrack);
+
+
+    }
+
+    /**
+     * Internal use only serialization
+     *
+     * @param ex exporter
+     * @throws IOException exception
+     */
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule out = ex.getCapsule(this);
+        //reseting the particle emission rate on the emitter before saving.
+        emitter.setParticlesPerSec(particlesPerSeconds);
+        out.write(emitter, "emitter", null);
+        out.write(particlesPerSeconds, "particlesPerSeconds", 0);
+        out.write(length, "length", 0);
+        out.write(startOffset, "startOffset", 0);
+        //Setting emission rate to 0 so that this track can go on being used.
+        emitter.setParticlesPerSec(0);
+    }
+
+    /**
+     * Internal use only serialization
+     *
+     * @param im importer
+     * @throws IOException Exception
+     */
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule in = im.getCapsule(this);
+        this.particlesPerSeconds = in.readFloat("particlesPerSeconds", 0);
+        //reading the emitter even if the track will then reference its cloned counter part if it's loaded with the assetManager.
+        //This also avoid null pointer exception if the model is not loaded via the AssetManager.
+        emitter = (ParticleEmitter) in.readSavable("emitter", null);
+        emitter.setParticlesPerSec(0);
+        //if the emitter was saved with a KillParticleControl we remove it.
+//        Control c = emitter.getControl(KillParticleControl.class);
+//        if(c!=null){
+//            emitter.removeControl(c);
+//        }
+        //emitter.removeControl(KillParticleControl.class);
+        length = in.readFloat("length", length);
+        startOffset = in.readFloat("startOffset", 0);
+    }
+}
index b74461e..ca7b1eb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -29,7 +29,6 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.animation;
 
 /**
index d1b447c..fc06d0d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.animation;
 
-import java.io.IOException;
-import java.nio.FloatBuffer;
-
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
 import com.jme3.math.Vector3f;
 import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
 
 /**
  * A pose is a list of offsets that say where a mesh vertices should be for this pose.
@@ -64,6 +58,13 @@ public final class Pose implements Savable, Cloneable {
         this.indices = indices;
     }
 
+    /**
+     * Serialization-only. Do not use.
+     */
+    public Pose()
+    {
+    }
+    
     public int getTargetMeshIndex(){
         return targetMeshIndex;
     }
@@ -97,21 +98,22 @@ public final class Pose implements Savable, Cloneable {
      * This method creates a clone of the current object.
      * @return a clone of the current object
      */
+    @Override
     public Pose clone() {
-               try {
-                       Pose result = (Pose) super.clone();
+        try {
+            Pose result = (Pose) super.clone();
             result.indices = this.indices.clone();
-            if(this.offsets!=null) {
-               result.offsets = new Vector3f[this.offsets.length];
-               for(int i=0;i<this.offsets.length;++i) {
-                       result.offsets[i] = this.offsets[i].clone();
-               }
+            if (this.offsets != null) {
+                result.offsets = new Vector3f[this.offsets.length];
+                for (int i = 0; i < this.offsets.length; ++i) {
+                    result.offsets[i] = this.offsets[i].clone();
+                }
             }
-               return result;
+            return result;
         } catch (CloneNotSupportedException e) {
             throw new AssertionError();
         }
-       }
+    }
 
     public void write(JmeExporter e) throws IOException {
         OutputCapsule out = e.getCapsule(this);
@@ -125,7 +127,12 @@ public final class Pose implements Savable, Cloneable {
         InputCapsule in = i.getCapsule(this);
         name = in.readString("name", "");
         targetMeshIndex = in.readInt("meshIndex", -1);
-        offsets = (Vector3f[]) in.readSavableArray("offsets", null);
         indices = in.readIntArray("indices", null);
+
+        Savable[] readSavableArray = in.readSavableArray("offsets", null);
+        if (readSavableArray != null) {
+            offsets = new Vector3f[readSavableArray.length];
+            System.arraycopy(readSavableArray, 0, offsets, 0, readSavableArray.length);
+        }
     }
 }
index 8378574..92c24ce 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.animation;
 
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.VertexBuffer;
 import com.jme3.scene.VertexBuffer.Type;
@@ -65,6 +60,13 @@ public final class PoseTrack implements Track {
         }
         
         /**
+         * Serialization-only. Do not use.
+         */
+        public PoseFrame()
+        {
+        }
+        
+        /**
          * This method creates a clone of the current object.
          * @return a clone of the current object
          */
@@ -93,8 +95,13 @@ public final class PoseTrack implements Track {
 
         public void read(JmeImporter i) throws IOException {
             InputCapsule in = i.getCapsule(this);
-            poses = (Pose[]) in.readSavableArray("poses", null);
             weights = in.readFloatArray("weights", null);
+            
+            Savable[] readSavableArray = in.readSavableArray("poses", null);
+            if (readSavableArray != null) {
+                poses = new Pose[readSavableArray.length];
+                System.arraycopy(readSavableArray, 0, poses, 0, readSavableArray.length);
+            }
         }
     }
 
@@ -104,6 +111,13 @@ public final class PoseTrack implements Track {
         this.frames = frames;
     }
     
+    /**
+     * Serialization-only. Do not use.
+     */
+    public PoseTrack()
+    {
+    }
+    
     private void applyFrame(Mesh target, int frameIndex, float weight){
         PoseFrame frame = frames[frameIndex];
         VertexBuffer pb = target.getBuffer(Type.Position);
@@ -184,7 +198,12 @@ public final class PoseTrack implements Track {
     public void read(JmeImporter i) throws IOException {
         InputCapsule in = i.getCapsule(this);
         targetMeshIndex = in.readInt("meshIndex", 0);
-        frames = (PoseFrame[]) in.readSavableArray("frames", null);
         times = in.readFloatArray("times", null);
+        
+        Savable[] readSavableArray = in.readSavableArray("frames", null);
+        if (readSavableArray != null) {
+            frames = new PoseFrame[readSavableArray.length];
+            System.arraycopy(readSavableArray, 0, frames, 0, readSavableArray.length);
+        }
     }
 }
index 59e78ed..b0d3419 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  */
 package com.jme3.animation;
 
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
 import com.jme3.math.Matrix4f;
 import com.jme3.util.TempVars;
 import java.io.IOException;
-
 import java.util.ArrayList;
 import java.util.List;
 
index 661002a..4683308 100644 (file)
 /*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
+ * Copyright (c) 2009-2012 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 com.jme3.animation;
 
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
+import com.jme3.material.Material;
 import com.jme3.math.FastMath;
 import com.jme3.math.Matrix4f;
 import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.RendererException;
 import com.jme3.renderer.ViewPort;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.Mesh;
-import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
-import com.jme3.scene.UserData;
-import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.*;
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.scene.control.Control;
+import com.jme3.shader.VarType;
+import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import java.io.IOException;
+import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
- * The Skeleton control deforms a model according to a skeleton, 
- * It handles the computation of the deformation matrices and performs 
- * the transformations on the mesh
- * 
+ * The Skeleton control deforms a model according to a skeleton, It handles the
+ * computation of the deformation matrices and performs the transformations on
+ * the mesh
+ *
  * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
  */
 public class SkeletonControl extends AbstractControl implements Cloneable {
 
     /**
-     * The skeleton of the model
+     * The skeleton of the model.
      */
     private Skeleton skeleton;
     /**
      * List of targets which this controller effects.
      */
-    private Mesh[] targets;
+    private SafeArrayList<Mesh> targets = new SafeArrayList<Mesh>(Mesh.class);
     /**
-     * Used to track when a mesh was updated. Meshes are only updated
-     * if they are visible in at least one camera.
+     * Used to track when a mesh was updated. Meshes are only updated if they
+     * are visible in at least one camera.
      */
     private boolean wasMeshUpdated = false;
+    
+    /**
+     * User wishes to use hardware skinning if available.
+     */
+    private transient boolean hwSkinningDesired = false;
+    
+    /**
+     * Hardware skinning is currently being used.
+     */
+    private transient boolean hwSkinningEnabled = false;
+    
+    /**
+     * Hardware skinning was tested on this GPU, results
+     * are stored in {@link #hwSkinningSupported} variable.
+     */
+    private transient boolean hwSkinningTested = false;
+    
+    /**
+     * If hardware skinning was {@link #hwSkinningTested tested}, then
+     * this variable will be set to true if supported, and false if otherwise.
+     */
+    private transient boolean hwSkinningSupported = false;
+    
+    /**
+     * Bone offset matrices, recreated each frame
+     */
+    private transient Matrix4f[] offsetMatrices;
+    /**
+     * Material references used for hardware skinning
+     */
+    private Set<Material> materials = new HashSet<Material>();
 
     /**
      * Serialization only. Do not use.
@@ -57,100 +115,207 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
     public SkeletonControl() {
     }
 
+    private void switchToHardware() {
+        // Next full 10 bones (e.g. 30 on 24 bones)
+        int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10;
+        for (Material m : materials) {
+            m.setInt("NumberOfBones", numBones);
+        }
+        for (Mesh mesh : targets) {
+            if (mesh.isAnimated()) {
+                mesh.prepareForAnim(false);
+            }
+        }
+    }
+
+    private void switchToSoftware() {
+        for (Material m : materials) {
+            if (m.getParam("NumberOfBones") != null) {
+                m.clearParam("NumberOfBones");
+            }
+        }
+        for (Mesh mesh : targets) {
+            if (mesh.isAnimated()) {
+                mesh.prepareForAnim(true);
+            }
+        }
+    }
+
+    private boolean testHardwareSupported(RenderManager rm) {
+        for (Material m : materials) {
+            // Some of the animated mesh(es) do not support hardware skinning,
+            // so it is not supported by the model.
+            if (m.getMaterialDef().getMaterialParam("NumberOfBones") == null) {
+                Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING, 
+                        "Not using hardware skinning for {0}, " + 
+                        "because material {1} doesn''t support it.", 
+                        new Object[]{spatial, m.getMaterialDef().getName()});
+                
+                return false;
+            }
+        }
+
+        switchToHardware();
+        
+        try {
+            rm.preloadScene(spatial);
+            return true;
+        } catch (RendererException e) {
+            Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING, "Could not enable HW skinning due to shader compile error:", e);
+            return false;
+        }
+    }
+
     /**
-     * Creates a skeleton control.
-     * The list of targets will be acquired automatically when
-     * the control is attached to a node.
+     * Specifies if hardware skinning is preferred. If it is preferred and
+     * supported by GPU, it shall be enabled, if its not preferred, or not
+     * supported by GPU, then it shall be disabled.
+     * 
+     * @see #isHardwareSkinningUsed() 
+     */
+    public void setHardwareSkinningPreferred(boolean preferred) {
+        hwSkinningDesired = preferred;
+    }
+    
+    /**
+     * @return True if hardware skinning is preferable to software skinning.
+     * Set to false by default.
      * 
+     * @see #setHardwareSkinningPreferred(boolean) 
+     */
+    public boolean isHardwareSkinningPreferred() {
+        return hwSkinningDesired;
+    }
+    
+    /**
+     * @return True is hardware skinning is activated and is currently used, false otherwise.
+     */
+    public boolean isHardwareSkinningUsed() {
+        return hwSkinningEnabled;
+    }
+    
+    /**
+     * Creates a skeleton control. The list of targets will be acquired
+     * automatically when the control is attached to a node.
+     *
      * @param skeleton the skeleton
      */
     public SkeletonControl(Skeleton skeleton) {
         this.skeleton = skeleton;
     }
-    
+
     /**
      * Creates a skeleton control.
-     * 
+     *
      * @param targets the meshes controlled by the skeleton
      * @param skeleton the skeleton
      */
     @Deprecated
-    SkeletonControl(Mesh[] targets, Skeleton skeleton){
+    SkeletonControl(Mesh[] targets, Skeleton skeleton) {
         this.skeleton = skeleton;
-        this.targets = targets;
-    }
-    
-    private boolean isMeshAnimated(Mesh mesh){
-        return mesh.getBuffer(Type.BindPosePosition) != null;
+        this.targets = new SafeArrayList<Mesh>(Mesh.class, Arrays.asList(targets));
     }
 
-    private Mesh[] findTargets(Node node){
-        Mesh sharedMesh = null;
-        ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>();
-        
-        for (Spatial child : node.getChildren()){
-            if (!(child instanceof Geometry)){
-                continue; // could be an attachment node, ignore.
-            }
-            
-            Geometry geom = (Geometry) child;
-            
-            // is this geometry using a shared mesh?
-            Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
-            
-            if (childSharedMesh != null){
-                // Don't bother with non-animated shared meshes
-                if (isMeshAnimated(childSharedMesh)){
-                    // child is using shared mesh,
-                    // so animate the shared mesh but ignore child
-                    if (sharedMesh == null){
-                        sharedMesh = childSharedMesh;
-                    }else if (sharedMesh != childSharedMesh){
-                        throw new IllegalStateException("Two conflicting shared meshes for " + node);
+
+    private void findTargets(Node node) {
+        Mesh sharedMesh = null;        
+
+        for (Spatial child : node.getChildren()) {
+            if (child instanceof Geometry) {
+                Geometry geom = (Geometry) child;
+
+                // is this geometry using a shared mesh?
+                Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
+
+                if (childSharedMesh != null) {
+                    // Don’t bother with non-animated shared meshes
+                    if (childSharedMesh.isAnimated()) {
+                        // child is using shared mesh,
+                        // so animate the shared mesh but ignore child
+                        if (sharedMesh == null) {
+                            sharedMesh = childSharedMesh;
+                        } else if (sharedMesh != childSharedMesh) {
+                            throw new IllegalStateException("Two conflicting shared meshes for " + node);
+                        }
+                        materials.add(geom.getMaterial());
+                    }
+                } else {
+                    Mesh mesh = geom.getMesh();
+                    if (mesh.isAnimated()) {
+                        targets.add(mesh);
+                        materials.add(geom.getMaterial());
                     }
                 }
-            }else{
-                Mesh mesh = geom.getMesh();
-                if (isMeshAnimated(mesh)){
-                    animatedMeshes.add(mesh);
-                }
+            } else if (child instanceof Node) {
+                findTargets((Node) child);
             }
         }
-        
-        if (sharedMesh != null){
-            animatedMeshes.add(sharedMesh);
+
+        if (sharedMesh != null) {
+            targets.add(sharedMesh);
         }
-        
-        return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]);
+
     }
-    
+
     @Override
-    public void setSpatial(Spatial spatial){
+    public void setSpatial(Spatial spatial) {
         super.setSpatial(spatial);
-        if (spatial != null){
-            Node node = (Node) spatial;
-            targets = findTargets(node);
-        }else{
-            targets = null;
+        updateTargetsAndMaterials(spatial);
+    }
+
+    private void controlRenderSoftware() {
+        resetToBind(); // reset morph meshes to bind pose
+
+        offsetMatrices = skeleton.computeSkinningMatrices();
+
+        for (Mesh mesh : targets) {
+            // NOTE: This assumes that code higher up
+            // Already ensured those targets are animated
+            // otherwise a crash will happen in skin update
+            softwareSkinUpdate(mesh, offsetMatrices);
+        }     
+    }
+
+    private void controlRenderHardware() {
+        offsetMatrices = skeleton.computeSkinningMatrices();
+        for (Material m : materials) {
+            m.setParam("BoneMatrices", VarType.Matrix4Array, offsetMatrices);
         }
     }
+
     
     @Override
     protected void controlRender(RenderManager rm, ViewPort vp) {
         if (!wasMeshUpdated) {
-            resetToBind(); // reset morph meshes to bind pose
-
-            Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
-
-            // if hardware skinning is supported, the matrices and weight buffer
-            // will be sent by the SkinningShaderLogic object assigned to the shader
-            for (int i = 0; i < targets.length; i++) {
-                               // NOTE: This assumes that code higher up
-                               // Already ensured those targets are animated
-                               // otherwise a crash will happen in skin update
-                //if (isMeshAnimated(targets[i])) {
-                    softwareSkinUpdate(targets[i], offsetMatrices);
-                //}
+            updateTargetsAndMaterials(spatial);
+            
+            // Prevent illegal cases. These should never happen.
+            assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled);
+            assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported);
+
+            if (hwSkinningDesired && !hwSkinningTested) {
+                hwSkinningTested = true;
+                hwSkinningSupported = testHardwareSupported(rm);
+
+                if (hwSkinningSupported) {
+                    hwSkinningEnabled = true;
+                    
+                    Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for " + spatial);
+                } else {
+                    switchToSoftware();
+                }
+            } else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) {
+                switchToHardware();
+                hwSkinningEnabled = true;
+            } else if (!hwSkinningDesired && hwSkinningEnabled) {
+                switchToSoftware();
+                hwSkinningEnabled = false;
+            }
+
+            if (hwSkinningEnabled) {
+                controlRenderHardware();
+            } else {
+                controlRenderSoftware();
             }
 
             wasMeshUpdated = true;
@@ -160,14 +325,15 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
     @Override
     protected void controlUpdate(float tpf) {
         wasMeshUpdated = false;
-    }
+     }
 
+    //only do this for software updates
     void resetToBind() {
-        for (Mesh mesh : targets){
-            if (isMeshAnimated(mesh)) {
-                VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
-                ByteBuffer bib = (ByteBuffer) bi.getData();
-                if (!bib.hasArray()) {
+        for (Mesh mesh : targets) {
+            if (mesh.isAnimated()) {
+                Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData();
+                Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData();
+                if (!biBuff.hasArray() || !bwBuff.hasArray()) {
                     mesh.prepareForAnim(true); // prepare for software animation
                 }
                 VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
@@ -182,6 +348,19 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
                 nb.clear();
                 bpb.clear();
                 bnb.clear();
+
+                //reseting bind tangents if there is a bind tangent buffer
+                VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
+                if (bindTangents != null) {
+                    VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
+                    FloatBuffer tb = (FloatBuffer) tangents.getData();
+                    FloatBuffer btb = (FloatBuffer) bindTangents.getData();
+                    tb.clear();
+                    btb.clear();
+                    tb.put(btb).clear();
+                }
+
+
                 pb.put(bpb).clear();
                 nb.put(bnb).clear();
             }
@@ -192,13 +371,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
         Node clonedNode = (Node) spatial;
         AnimControl ctrl = spatial.getControl(AnimControl.class);
         SkeletonControl clone = new SkeletonControl();
-        clone.setSpatial(clonedNode);
 
         clone.skeleton = ctrl.getSkeleton();
-               // Fix animated targets for the cloned node
-        clone.targets = findTargets(clonedNode);
 
-               // Fix attachments for the cloned node
+        clone.setSpatial(clonedNode);
+
+        // Fix attachments for the cloned node
         for (int i = 0; i < clonedNode.getQuantity(); i++) {
             // go through attachment nodes, apply them to correct bone
             Spatial child = clonedNode.getChild(i);
@@ -219,9 +397,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
     }
 
     /**
-     * 
+     *
      * @param boneName the name of the bone
-     * @return the node attached to this bone    
+     * @return the node attached to this bone
      */
     public Node getAttachmentsNode(String boneName) {
         Bone b = skeleton.getBone(boneName);
@@ -238,38 +416,162 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
 
     /**
      * returns the skeleton of this control
-     * @return 
+     *
+     * @return
      */
     public Skeleton getSkeleton() {
         return skeleton;
     }
 
     /**
-     * sets the skeleton for this control
-     * @param skeleton 
+     * returns a copy of array of the targets meshes of this control
+     *
+     * @return
      */
-//    public void setSkeleton(Skeleton skeleton) {
-//        this.skeleton = skeleton;
-//    }
+    public Mesh[] getTargets() {        
+        return targets.toArray(new Mesh[targets.size()]);
+    }
 
     /**
-     * returns the targets meshes of this control
-     * @return 
+     * Update the mesh according to the given transformation matrices
+     *
+     * @param mesh then mesh
+     * @param offsetMatrices the transformation matrices to apply
      */
-    public Mesh[] getTargets() {
-        return targets;
+    private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
+
+        VertexBuffer tb = mesh.getBuffer(Type.Tangent);
+        if (tb == null) {
+            //if there are no tangents use the classic skinning
+            applySkinning(mesh, offsetMatrices);
+        } else {
+            //if there are tangents use the skinning with tangents
+            applySkinningTangents(mesh, offsetMatrices, tb);
+        }
+
+
     }
 
     /**
-     * sets the target  meshes of this control
-     * @param targets 
+     * Method to apply skinning transforms to a mesh's buffers
+     *
+     * @param mesh the mesh
+     * @param offsetMatrices the offset matices to apply
      */
-//    public void setTargets(Mesh[] targets) {
-//        this.targets = targets;
-//    }
+    private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {
+        int maxWeightsPerVert = mesh.getMaxNumWeights();
+        if (maxWeightsPerVert <= 0) {
+            throw new IllegalStateException("Max weights per vert is incorrectly set!");
+        }
 
-    private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
+        int fourMinusMaxWeights = 4 - maxWeightsPerVert;
+
+        // NOTE: This code assumes the vertex buffer is in bind pose
+        // resetToBind() has been called this frame
+        VertexBuffer vb = mesh.getBuffer(Type.Position);
+        FloatBuffer fvb = (FloatBuffer) vb.getData();
+        fvb.rewind();
+
+        VertexBuffer nb = mesh.getBuffer(Type.Normal);
+        FloatBuffer fnb = (FloatBuffer) nb.getData();
+        fnb.rewind();
+
+        // get boneIndexes and weights for mesh
+        ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+        FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+        ib.rewind();
+        wb.rewind();
+
+        float[] weights = wb.array();
+        byte[] indices = ib.array();
+        int idxWeights = 0;
+
+        TempVars vars = TempVars.get();
+
+        float[] posBuf = vars.skinPositions;
+        float[] normBuf = vars.skinNormals;
+
+        int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length));
+        int bufLength = posBuf.length;
+        for (int i = iterations - 1; i >= 0; i--) {
+            // read next set of positions and normals from native buffer
+            bufLength = Math.min(posBuf.length, fvb.remaining());
+            fvb.get(posBuf, 0, bufLength);
+            fnb.get(normBuf, 0, bufLength);
+            int verts = bufLength / 3;
+            int idxPositions = 0;
+
+            // iterate vertices and apply skinning transform for each effecting bone
+            for (int vert = verts - 1; vert >= 0; vert--) {
+                // Skip this vertex if the first weight is zero.
+                if (weights[idxWeights] == 0) {
+                    idxPositions += 3;
+                    idxWeights += 4;
+                    continue;
+                }
+
+                float nmx = normBuf[idxPositions];
+                float vtx = posBuf[idxPositions++];
+                float nmy = normBuf[idxPositions];
+                float vty = posBuf[idxPositions++];
+                float nmz = normBuf[idxPositions];
+                float vtz = posBuf[idxPositions++];
+
+                float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
+
+                for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
+                    float weight = weights[idxWeights];
+                    Matrix4f mat = offsetMatrices[indices[idxWeights++] & 0xff];
+
+                    rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
+                    ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
+                    rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
+
+                    rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
+                    rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
+                    rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+                }
+
+                idxWeights += fourMinusMaxWeights;
+
+                idxPositions -= 3;
+                normBuf[idxPositions] = rnx;
+                posBuf[idxPositions++] = rx;
+                normBuf[idxPositions] = rny;
+                posBuf[idxPositions++] = ry;
+                normBuf[idxPositions] = rnz;
+                posBuf[idxPositions++] = rz;
+            }
+
+            fvb.position(fvb.position() - bufLength);
+            fvb.put(posBuf, 0, bufLength);
+            fnb.position(fnb.position() - bufLength);
+            fnb.put(normBuf, 0, bufLength);
+        }
+
+        vars.release();
+
+        vb.updateData(fvb);
+        nb.updateData(fnb);
+
+    }
+
+    /**
+     * Specific method for skinning with tangents to avoid cluttering the
+     * classic skinning calculation with null checks that would slow down the
+     * process even if tangents don't have to be computed. Also the iteration
+     * has additional indexes since tangent has 4 components instead of 3 for
+     * pos and norm
+     *
+     * @param maxWeightsPerVert maximum number of weights per vertex
+     * @param mesh the mesh
+     * @param offsetMatrices the offsetMaytrices to apply
+     * @param tb the tangent vertexBuffer
+     */
+    private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
         int maxWeightsPerVert = mesh.getMaxNumWeights();
+
         if (maxWeightsPerVert <= 0) {
             throw new IllegalStateException("Max weights per vert is incorrectly set!");
         }
@@ -283,9 +585,15 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
         fvb.rewind();
 
         VertexBuffer nb = mesh.getBuffer(Type.Normal);
+
         FloatBuffer fnb = (FloatBuffer) nb.getData();
         fnb.rewind();
 
+
+        FloatBuffer ftb = (FloatBuffer) tb.getData();
+        ftb.rewind();
+
+
         // get boneIndexes and weights for mesh
         ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
         FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
@@ -302,19 +610,33 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
 
         float[] posBuf = vars.skinPositions;
         float[] normBuf = vars.skinNormals;
+        float[] tanBuf = vars.skinTangents;
 
-        int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
-        int bufLength = posBuf.length * 3;
+        int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length));
+        int bufLength = 0;
+        int tanLength = 0;
         for (int i = iterations - 1; i >= 0; i--) {
             // read next set of positions and normals from native buffer
             bufLength = Math.min(posBuf.length, fvb.remaining());
+            tanLength = Math.min(tanBuf.length, ftb.remaining());
             fvb.get(posBuf, 0, bufLength);
             fnb.get(normBuf, 0, bufLength);
+            ftb.get(tanBuf, 0, tanLength);
             int verts = bufLength / 3;
             int idxPositions = 0;
+            //tangents has their own index because of the 4 components
+            int idxTangents = 0;
 
             // iterate vertices and apply skinning transform for each effecting bone
             for (int vert = verts - 1; vert >= 0; vert--) {
+                // Skip this vertex if the first weight is zero.
+                if (weights[idxWeights] == 0) {
+                    idxTangents += 4;
+                    idxPositions += 3;
+                    idxWeights += 4;
+                    continue;
+                }
+
                 float nmx = normBuf[idxPositions];
                 float vtx = posBuf[idxPositions++];
                 float nmy = normBuf[idxPositions];
@@ -322,7 +644,14 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
                 float nmz = normBuf[idxPositions];
                 float vtz = posBuf[idxPositions++];
 
-                float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
+                float tnx = tanBuf[idxTangents++];
+                float tny = tanBuf[idxTangents++];
+                float tnz = tanBuf[idxTangents++];
+
+                // skipping the 4th component of the tangent since it doesn't have to be transformed
+                idxTangents++;
+
+                float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;
 
                 for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
                     float weight = weights[idxWeights];
@@ -335,50 +664,71 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
                     rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
                     rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
                     rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+
+                    rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
+                    rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
+                    rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
                 }
 
                 idxWeights += fourMinusMaxWeights;
 
                 idxPositions -= 3;
+
                 normBuf[idxPositions] = rnx;
                 posBuf[idxPositions++] = rx;
                 normBuf[idxPositions] = rny;
                 posBuf[idxPositions++] = ry;
                 normBuf[idxPositions] = rnz;
                 posBuf[idxPositions++] = rz;
+
+                idxTangents -= 4;
+
+                tanBuf[idxTangents++] = rtx;
+                tanBuf[idxTangents++] = rty;
+                tanBuf[idxTangents++] = rtz;
+
+                //once again skipping the 4th component of the tangent
+                idxTangents++;
             }
 
             fvb.position(fvb.position() - bufLength);
             fvb.put(posBuf, 0, bufLength);
             fnb.position(fnb.position() - bufLength);
             fnb.put(normBuf, 0, bufLength);
+            ftb.position(ftb.position() - tanLength);
+            ftb.put(tanBuf, 0, tanLength);
         }
 
         vars.release();
 
         vb.updateData(fvb);
         nb.updateData(fnb);
+        tb.updateData(ftb);
+
 
-//        mesh.updateBound();
     }
 
     @Override
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
-        oc.write(targets, "targets", null);
         oc.write(skeleton, "skeleton", null);
+        //Targets and materials don't need to be saved, they'll be gathered on each frame
     }
 
     @Override
     public void read(JmeImporter im) throws IOException {
         super.read(im);
         InputCapsule in = im.getCapsule(this);
-        Savable[] sav = in.readSavableArray("targets", null);
-        if (sav != null) {
-            targets = new Mesh[sav.length];
-            System.arraycopy(sav, 0, targets, 0, sav.length);
-        }
         skeleton = (Skeleton) in.readSavable("skeleton", null);
     }
+
+    private void updateTargetsAndMaterials(Spatial spatial) {
+        targets.clear();
+        materials.clear();           
+        if (spatial != null && spatial instanceof Node) {
+            Node node = (Node) spatial;                        
+            findTargets(node);
+        }
+    }
 }
index 54f5058..4a552bb 100644 (file)
@@ -1,3 +1,34 @@
+/*\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are\r
+ * met:\r
+ *\r
+ * * Redistributions of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * * Redistributions in binary form must reproduce the above copyright\r
+ *   notice, this list of conditions and the following disclaimer in the\r
+ *   documentation and/or other materials provided with the distribution.\r
+ *\r
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors\r
+ *   may be used to endorse or promote products derived from this software\r
+ *   without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\r
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
 package com.jme3.animation;\r
 \r
 /**\r
index c839ecb..a124e92 100644 (file)
@@ -1,7 +1,36 @@
+/*\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are\r
+ * met:\r
+ *\r
+ * * Redistributions of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * * Redistributions in binary form must reproduce the above copyright\r
+ *   notice, this list of conditions and the following disclaimer in the\r
+ *   documentation and/or other materials provided with the distribution.\r
+ *\r
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors\r
+ *   may be used to endorse or promote products derived from this software\r
+ *   without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\r
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
 package com.jme3.animation;\r
 \r
-import java.io.IOException;\r
-\r
 import com.jme3.export.InputCapsule;\r
 import com.jme3.export.JmeExporter;\r
 import com.jme3.export.JmeImporter;\r
@@ -10,6 +39,8 @@ import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;\r
 import com.jme3.scene.Spatial;\r
 import com.jme3.util.TempVars;\r
+import java.io.IOException;\r
+import java.util.Arrays;\r
 \r
 /**\r
  * This class represents the track for spatial animation.\r
@@ -64,8 +95,6 @@ public class SpatialTrack implements Track {
      * \r
      * @param time\r
      *            the current time of the animation\r
-     * @param spatial\r
-     *            the spatial that should be animated with this track\r
      */\r
     public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {\r
         Spatial spatial = control.getSpatial();\r
@@ -79,14 +108,18 @@ public class SpatialTrack implements Track {
         \r
         int lastFrame = times.length - 1;\r
         if (time < 0 || lastFrame == 0) {\r
-            rotations.get(0, tempQ);\r
-            translations.get(0, tempV);\r
+            if (rotations != null)\r
+                rotations.get(0, tempQ);\r
+            if (translations != null)\r
+                translations.get(0, tempV);\r
             if (scales != null) {\r
                 scales.get(0, tempS);\r
             }\r
         } else if (time >= times[lastFrame]) {\r
-            rotations.get(lastFrame, tempQ);\r
-            translations.get(lastFrame, tempV);\r
+            if (rotations != null)\r
+                rotations.get(lastFrame, tempQ);\r
+            if (translations != null)\r
+                translations.get(lastFrame, tempV);\r
             if (scales != null) {\r
                 scales.get(lastFrame, tempS);\r
             }\r
@@ -101,13 +134,17 @@ public class SpatialTrack implements Track {
 \r
             float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]);\r
 \r
-            rotations.get(startFrame, tempQ);\r
-            translations.get(startFrame, tempV);\r
+            if (rotations != null)\r
+                rotations.get(startFrame, tempQ);\r
+            if (translations != null)\r
+                translations.get(startFrame, tempV);\r
             if (scales != null) {\r
                 scales.get(startFrame, tempS);\r
             }\r
-            rotations.get(endFrame, tempQ2);\r
-            translations.get(endFrame, tempV2);\r
+            if (rotations != null)\r
+                rotations.get(endFrame, tempQ2);\r
+            if (translations != null)\r
+                translations.get(endFrame, tempV2);\r
             if (scales != null) {\r
                 scales.get(endFrame, tempS2);\r
             }\r
@@ -116,8 +153,10 @@ public class SpatialTrack implements Track {
             tempS.interpolate(tempS2, blend);\r
         }\r
         \r
-        spatial.setLocalTranslation(tempV);\r
-        spatial.setLocalRotation(tempQ);\r
+        if (translations != null)\r
+            spatial.setLocalTranslation(tempV);\r
+        if (rotations != null)\r
+            spatial.setLocalRotation(tempQ);\r
         if (scales != null) {\r
             spatial.setLocalScale(tempS);\r
         }\r
@@ -141,25 +180,24 @@ public class SpatialTrack implements Track {
             throw new RuntimeException("BoneTrack with no keyframes!");\r
         }\r
 \r
-        assert times.length == translations.length\r
-                && times.length == rotations.length;\r
-\r
         this.times = times;\r
-        this.translations = new CompactVector3Array();\r
-        this.translations.add(translations);\r
-        this.translations.freeze();\r
-        this.rotations = new CompactQuaternionArray();\r
-        this.rotations.add(rotations);\r
-        this.rotations.freeze();\r
-\r
+        if (translations != null) {\r
+            assert times.length == translations.length;\r
+            this.translations = new CompactVector3Array();\r
+            this.translations.add(translations);\r
+            this.translations.freeze();\r
+        }\r
+        if (rotations != null) {\r
+            assert times.length == rotations.length;\r
+            this.rotations = new CompactQuaternionArray();\r
+            this.rotations.add(rotations);\r
+            this.rotations.freeze();\r
+        }\r
         if (scales != null) {\r
             assert times.length == scales.length;\r
-            \r
             this.scales = new CompactVector3Array();\r
             this.scales.add(scales);\r
             this.scales.freeze();\r
-            \r
-            \r
         }\r
     }\r
 \r
@@ -167,7 +205,7 @@ public class SpatialTrack implements Track {
      * @return the array of rotations of this track\r
      */\r
     public Quaternion[] getRotations() {\r
-            return rotations.toObjectArray();\r
+            return rotations == null ? null : rotations.toObjectArray();\r
     }\r
 \r
     /**\r
@@ -188,7 +226,7 @@ public class SpatialTrack implements Track {
      * @return the array of translations of this track\r
      */\r
     public Vector3f[] getTranslations() {\r
-            return translations.toObjectArray();\r
+            return translations == null ? null : translations.toObjectArray();\r
     }\r
 \r
     /**\r
@@ -206,21 +244,13 @@ public class SpatialTrack implements Track {
     public SpatialTrack clone() {\r
         int tablesLength = times.length;\r
 \r
-        float[] times = this.times.clone();\r
-        Vector3f[] sourceTranslations = this.getTranslations();\r
-        Quaternion[] sourceRotations = this.getRotations();\r
-        Vector3f[] sourceScales = this.getScales();\r
-\r
-        Vector3f[] translations = new Vector3f[tablesLength];\r
-        Quaternion[] rotations = new Quaternion[tablesLength];\r
-        Vector3f[] scales = new Vector3f[tablesLength];\r
-        for (int i = 0; i < tablesLength; ++i) {\r
-            translations[i] = sourceTranslations[i].clone();\r
-            rotations[i] = sourceRotations[i].clone();\r
-            scales[i] = sourceScales != null ? sourceScales[i].clone() : new Vector3f(1.0f, 1.0f, 1.0f);\r
-        }\r
+        float[] timesCopy = this.times.clone();\r
+        Vector3f[] translationsCopy = this.getTranslations() == null ? null : Arrays.copyOf(this.getTranslations(), tablesLength);\r
+        Quaternion[] rotationsCopy = this.getRotations() == null ? null : Arrays.copyOf(this.getRotations(), tablesLength);\r
+        Vector3f[] scalesCopy = this.getScales() == null ? null : Arrays.copyOf(this.getScales(), tablesLength);\r
+\r
         //need to use the constructor here because of the final fields used in this class\r
-        return new SpatialTrack(times, translations, rotations, scales);\r
+        return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy);\r
     }\r
        \r
     @Override\r
index a56807d..e77d67f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
diff --git a/engine/src/core/com/jme3/animation/TrackInfo.java b/engine/src/core/com/jme3/animation/TrackInfo.java
new file mode 100644 (file)
index 0000000..b6bff10
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2009-2012 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 com.jme3.animation;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is intended as a UserData added to a Spatial that is referenced by a Track.
+ * (ParticleEmitter for EffectTrack and AudioNode for AudioTrack)
+ * It holds the list of tracks that are directly referencing the Spatial.
+ * 
+ * This is used when loading a Track to find the cloned reference of a Spatial in the cloned model returned by the assetManager.
+ *
+ * @author Nehon
+ */
+public class TrackInfo implements Savable {
+
+    ArrayList<Track> tracks = new ArrayList<Track>();
+
+    public TrackInfo() {
+    }
+
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule c = ex.getCapsule(this);
+        c.writeSavableArrayList(tracks, "tracks", null);
+    }
+
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule c = im.getCapsule(this);
+        tracks = c.readSavableArrayList("tracks", null);
+    }
+
+    public ArrayList<Track> getTracks() {
+        return tracks;
+    }
+
+    public void addTrack(Track track) {
+        tracks.add(track);
+    }
+}
index 554bab9..a69904f 100644 (file)
@@ -64,7 +64,7 @@ the transformations for bones when no animated pose is applied to the skeleton.
 All bones <em>must</em> have a bind pose transformation before they can be
 animated. To set the bind pose for the skeleton, set the local (relative
 to parent) transformations for all the bones using the call
-{@link com.jme3.animation.Bone#setBindTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion) }.
+{@link com.jme3.animation.Bone#setBindTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion, com.jme3.math.Vector3f) }.
 Then call {@link com.jme3.animation.Skeleton#updateWorldVectors() } followed by
 {@link com.jme3.animation.Skeleton#setBindingPose() }. <br>
 <p>