Browse Source

Changed the way EffectTrack and AudioTrack are serialized.
EffectTrack and AudioTrack can now porperly update their reference to the Spatial they are using upon loading.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9634 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

rem..om 13 years ago
parent
commit
3bd77d3048

+ 7 - 3
engine/src/core/com/jme3/animation/AnimControl.java

@@ -43,6 +43,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Map.Entry;
 
 /**
  * <code>AnimControl</code> is a Spatial control that allows manipulation
@@ -118,9 +119,12 @@ public final class AnimControl extends AbstractControl implements Cloneable {
                 clone.skeleton = new Skeleton(skeleton);
             }
 
-            // animationMap is reference-copied, animation data should be shared
-            // to reduce memory usage.
-
+            // animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial
+            clone.animationMap = new HashMap<String, Animation>();
+            for (Entry<String, Animation> animEntry : animationMap.entrySet()) {
+                clone.animationMap.put(animEntry.getKey(), animEntry.getValue().cloneForSpatial(spatial));
+            }
+            
             return clone;
         } catch (CloneNotSupportedException ex) {
             throw new AssertionError();

+ 57 - 32
engine/src/core/com/jme3/animation/Animation.java

@@ -32,6 +32,7 @@
 package com.jme3.animation;
 
 import com.jme3.export.*;
+import com.jme3.scene.Spatial;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import java.io.IOException;
@@ -42,27 +43,26 @@ import java.io.IOException;
  * @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 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.
      * 
@@ -73,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.
@@ -102,58 +102,61 @@ 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 (Track track : tracks) {
             track.setTime(time, blendAmount, control, channel, vars);
         }
     }
-    
+
     /**
      * Set the {@link Track}s to be used by this animation.
      * <p>
      * 
      * @param tracks The tracks to set.
      */
-    public void setTracks(Track[] tracksArray){
+    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 addTrack(Track track){
+    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){
+     */
+    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.getArray();
+        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 = new SafeArrayList<Track>(Track.class);
@@ -166,12 +169,34 @@ public class Animation implements Savable, Cloneable {
         }
     }
 
+    /**
+     * 
+     * @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) {
+            throw new AssertionError();
+        }
+    }
+
     @Override
     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);
@@ -184,7 +209,7 @@ 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);
         if (arr != null) {
             // NOTE: Backward compat only .. Some animations have no
@@ -193,8 +218,8 @@ public class Animation implements Savable, Cloneable {
             // 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);
-            }            
+                tracks.add((Track) savable);
+            }
         }
     }
 }

+ 86 - 1
engine/src/core/com/jme3/animation/AudioTrack.java

@@ -36,8 +36,12 @@ 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
@@ -55,8 +59,9 @@ import java.io.IOException;
  *
  * @author Nehon
  */
-public class AudioTrack implements Track {
+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;
@@ -89,6 +94,7 @@ public class AudioTrack implements Track {
     public AudioTrack(AudioNode audio, float length) {
         this.audio = audio;
         this.length = length;
+        setUserData(this);
     }
 
     /**
@@ -141,6 +147,80 @@ public class AudioTrack implements Track {
         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
@@ -154,7 +234,12 @@ public class AudioTrack implements 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);
     }
 
     /**

+ 40 - 0
engine/src/core/com/jme3/animation/ClonableTrack.java

@@ -0,0 +1,40 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+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();
+}

+ 119 - 22
engine/src/core/com/jme3/animation/EffectTrack.java

@@ -38,12 +38,15 @@ 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
@@ -62,8 +65,9 @@ import java.io.IOException;
  *
  * @author Nehon
  */
-public class EffectTrack implements Track {
-
+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;
@@ -71,10 +75,9 @@ public class EffectTrack implements Track {
     private boolean emitted = false;
     private boolean initialized = false;
     private boolean stopRequested = false;
-    
     //control responsible for disable and cull the emitter once all particles are gone
     private AbstractControl killParticles = new AbstractControl() {
-
+        
         @Override
         protected void controlUpdate(float tpf) {
             if (emitter.getNumVisibleParticles() == 0) {
@@ -84,29 +87,26 @@ public class EffectTrack implements Track {
                 stopRequested = false;
             }
         }
-
+        
         @Override
         protected void controlRender(RenderManager rm, ViewPort vp) {
         }
-
+        
         public Control cloneForSpatial(Spatial spatial) {
             return null;
         }
     };
 
+   
     //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) {
-            if(!stopRequested){
-                stop();
-            }
+            stop();
         }
-
+        
         public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
-            if(!stopRequested){
-                stop();
-            }
+            stop();
         }
     }
 
@@ -128,7 +128,9 @@ public class EffectTrack implements Track {
         //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);
+        
     }
 
     /**
@@ -156,14 +158,15 @@ public class EffectTrack implements Track {
         //checking fo time to trigger the effect
         if (!emitted && time >= startOffset) {
             emitted = true;
-            stopRequested = false;
             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();
-                emitter.addControl(killParticles);
-                stopRequested = true;
+                if (!stopRequested) {
+                    emitter.addControl(killParticles);
+                    stopRequested = true;
+                }
             } else {
                 //else reset its former particlePerSec value to let it emmit.
                 emitter.setParticlesPerSec(particlesPerSeconds);
@@ -175,10 +178,15 @@ public class EffectTrack implements Track {
     private void stop() {
         emitter.setParticlesPerSec(0);
         emitted = false;
-        emitter.addControl(killParticles);   
-        stopRequested = true;
+        if (!stopRequested) {
+            emitter.addControl(killParticles);
+            stopRequested = true;
+        }
+        
     }
 
+
+    
     /**
      * Retruns the length of the track
      * @return length of the track
@@ -194,7 +202,67 @@ public class EffectTrack implements Track {
     @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;
+        }
+
+        //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);
+       }
     }
 
     /**
@@ -210,7 +278,12 @@ public class EffectTrack implements 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;
+        setUserData(this);
     }
 
     /**
@@ -228,6 +301,22 @@ public class EffectTrack implements Track {
     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);
+        
+        
+    }
 
     /**
      * Internal use only serialization
@@ -236,10 +325,16 @@ public class EffectTrack implements Track {
      */
     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);
+        //removing eventual unpersisted control off the emitter
+        emitter.removeControl(killParticles);
         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);
     }
 
     /**
@@ -249,8 +344,10 @@ public class EffectTrack implements Track {
      */
     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);
-        this.particlesPerSeconds = emitter.getParticlesPerSec();
         emitter.setParticlesPerSec(0);
         length = in.readFloat("length", length);
         startOffset = in.readFloat("startOffset", 0);

+ 48 - 0
engine/src/core/com/jme3/animation/TrackInfo.java

@@ -0,0 +1,48 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+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);
+    }
+}