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
3.0
rem..om 12 years ago
parent ae3cc96caa
commit 3bd77d3048
  1. 10
      engine/src/core/com/jme3/animation/AnimControl.java
  2. 89
      engine/src/core/com/jme3/animation/Animation.java
  3. 87
      engine/src/core/com/jme3/animation/AudioTrack.java
  4. 40
      engine/src/core/com/jme3/animation/ClonableTrack.java
  5. 141
      engine/src/core/com/jme3/animation/EffectTrack.java
  6. 48
      engine/src/core/com/jme3/animation/TrackInfo.java

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

@ -32,6 +32,7 @@
package com.jme3.animation; package com.jme3.animation;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.scene.Spatial;
import com.jme3.util.SafeArrayList; import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.io.IOException; import java.io.IOException;
@ -42,27 +43,26 @@ import java.io.IOException;
* @author Kirill Vainer, Marcin Roguski (Kaelthas) * @author Kirill Vainer, Marcin Roguski (Kaelthas)
*/ */
public class Animation implements Savable, Cloneable { public class Animation implements Savable, Cloneable {
/** /**
* The name of the animation. * The name of the animation.
*/ */
private String name; private String name;
/** /**
* The length of the animation. * The length of the animation.
*/ */
private float length; private float length;
/** /**
* The tracks of the animation. * The tracks of the animation.
*/ */
private SafeArrayList<Track> tracks = new SafeArrayList<Track>(Track.class); private SafeArrayList<Track> tracks = new SafeArrayList<Track>(Track.class);
/** /**
* Serialization-only. Do not use. * Serialization-only. Do not use.
*/ */
public Animation() {} public Animation() {
}
/** /**
* Creates a new <code>Animation</code> with the given name and length. * 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.name = name;
this.length = length; this.length = length;
} }
/** /**
* The name of the bone animation * The name of the bone animation
* @return name of the bone animation * @return name of the bone animation
*/ */
public String getName() { public String getName() {
return name; return name;
} }
/** /**
* Returns the length in seconds of this animation * Returns the length in seconds of this animation
* *
* @return the length in seconds of this animation * @return the length in seconds of this animation
*/ */
public float getLength() { public float getLength() {
return length; return length;
} }
/** /**
* This method sets the current time of the animation. * This method sets the current time of the animation.
* This method behaves differently for every known track type. * This method behaves differently for every known track type.
@ -102,58 +102,61 @@ public class Animation implements Savable, Cloneable {
* @param channel the animation channel * @param channel the animation channel
*/ */
void setTime(float time, float blendAmount, AnimControl control, AnimChannel channel, TempVars vars) { void setTime(float time, float blendAmount, AnimControl control, AnimChannel channel, TempVars vars) {
if (tracks == null) if (tracks == null) {
return; return;
}
for (Track track : tracks) { for (Track track : tracks) {
track.setTime(time, blendAmount, control, channel, vars); track.setTime(time, blendAmount, control, channel, vars);
} }
} }
/** /**
* Set the {@link Track}s to be used by this animation. * Set the {@link Track}s to be used by this animation.
* <p> * <p>
* *
* @param tracks The tracks to set. * @param tracks The tracks to set.
*/ */
public void setTracks(Track[] tracksArray){ public void setTracks(Track[] tracksArray) {
for (Track track : tracksArray) { for (Track track : tracksArray) {
tracks.add(track); tracks.add(track);
} }
} }
/** /**
* Adds a track to this animation * Adds a track to this animation
* @param track the track to add * @param track the track to add
*/ */
public void addTrack(Track track){ public void addTrack(Track track) {
tracks.add(track); tracks.add(track);
} }
/** /**
* removes a track from this animation * removes a track from this animation
* @param track the track to remove * @param track the track to remove
*/ */
public void removeTrack(Track track){ public void removeTrack(Track track) {
tracks.remove(track); tracks.remove(track);
} if (track instanceof ClonableTrack) {
((ClonableTrack) track).cleanUp();
}
}
/** /**
* Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }. * Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }.
* *
* @return the tracks set previously * @return the tracks set previously
*/ */
public Track[] getTracks() { public Track[] getTracks() {
return tracks.getArray(); return tracks.getArray();
} }
/** /**
* This method creates a clone of the current object. * This method creates a clone of the current object.
* @return a clone of the current object * @return a clone of the current object
*/ */
@Override @Override
public Animation clone() { public Animation clone() {
try { try {
Animation result = (Animation) super.clone(); Animation result = (Animation) super.clone();
result.tracks = new SafeArrayList<Track>(Track.class); 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 @Override
public String toString() { public String toString() {
return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']'; return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';
} }
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule out = ex.getCapsule(this); OutputCapsule out = ex.getCapsule(this);
out.write(name, "name", null); out.write(name, "name", null);
@ -184,7 +209,7 @@ public class Animation implements Savable, Cloneable {
InputCapsule in = im.getCapsule(this); InputCapsule in = im.getCapsule(this);
name = in.readString("name", null); name = in.readString("name", null);
length = in.readFloat("length", 0f); length = in.readFloat("length", 0f);
Savable[] arr = in.readSavableArray("tracks", null); Savable[] arr = in.readSavableArray("tracks", null);
if (arr != null) { if (arr != null) {
// NOTE: Backward compat only .. Some animations have no // 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. // its only appropriate that the check is made here as well.
tracks = new SafeArrayList<Track>(Track.class); tracks = new SafeArrayList<Track>(Track.class);
for (Savable savable : arr) { for (Savable savable : arr) {
tracks.add((Track)savable); tracks.add((Track) savable);
} }
} }
} }
} }

@ -36,8 +36,12 @@ import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter; import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter; import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule; import com.jme3.export.OutputCapsule;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.io.IOException; 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 * 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 * @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 AudioNode audio;
private float startOffset = 0; private float startOffset = 0;
private float length = 0; private float length = 0;
@ -89,6 +94,7 @@ public class AudioTrack implements Track {
public AudioTrack(AudioNode audio, float length) { public AudioTrack(AudioNode audio, float length) {
this.audio = audio; this.audio = audio;
this.length = length; this.length = length;
setUserData(this);
} }
/** /**
@ -141,6 +147,80 @@ public class AudioTrack implements Track {
return new AudioTrack(audio, length, startOffset); 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 * @return the audio node used by this track
@ -154,7 +234,12 @@ public class AudioTrack implements Track {
* @param audio * @param audio
*/ */
public void setAudio(AudioNode audio) { public void setAudio(AudioNode audio) {
if (this.audio != null) {
TrackInfo data = (TrackInfo) audio.getUserData("TrackInfo");
data.getTracks().remove(this);
}
this.audio = audio; this.audio = audio;
setUserData(this);
} }
/** /**

@ -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();
}

@ -38,12 +38,15 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule; import com.jme3.export.OutputCapsule;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.scene.Spatial.CullHint; import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control; import com.jme3.scene.control.Control;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.io.IOException; 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 * 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 * @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 ParticleEmitter emitter;
private float startOffset = 0; private float startOffset = 0;
private float particlesPerSeconds = 0; private float particlesPerSeconds = 0;
@ -71,10 +75,9 @@ public class EffectTrack implements Track {
private boolean emitted = false; private boolean emitted = false;
private boolean initialized = false; private boolean initialized = false;
private boolean stopRequested = false; private boolean stopRequested = false;
//control responsible for disable and cull the emitter once all particles are gone //control responsible for disable and cull the emitter once all particles are gone
private AbstractControl killParticles = new AbstractControl() { private AbstractControl killParticles = new AbstractControl() {
@Override @Override
protected void controlUpdate(float tpf) { protected void controlUpdate(float tpf) {
if (emitter.getNumVisibleParticles() == 0) { if (emitter.getNumVisibleParticles() == 0) {
@ -84,29 +87,26 @@ public class EffectTrack implements Track {
stopRequested = false; stopRequested = false;
} }
} }
@Override @Override
protected void controlRender(RenderManager rm, ViewPort vp) { protected void controlRender(RenderManager rm, ViewPort vp) {
} }
public Control cloneForSpatial(Spatial spatial) { public Control cloneForSpatial(Spatial spatial) {
return null; return null;
} }
}; };
//Anim listener that stops the Emmitter when the animation is finished or changed. //Anim listener that stops the Emmitter when the animation is finished or changed.
private class OnEndListener implements AnimEventListener { private class OnEndListener implements AnimEventListener {
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
if(!stopRequested){ stop();
stop();
}
} }
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { 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. //setting the emmitter to not emmit.
this.emitter.setParticlesPerSec(0); this.emitter.setParticlesPerSec(0);
this.length = length; 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 //checking fo time to trigger the effect
if (!emitted && time >= startOffset) { if (!emitted && time >= startOffset) {
emitted = true; emitted = true;
stopRequested = false;
emitter.setCullHint(CullHint.Dynamic); emitter.setCullHint(CullHint.Dynamic);
emitter.setEnabled(true); emitter.setEnabled(true);
//if the emitter has 0 particles per seconds emmit all particles in one shot //if the emitter has 0 particles per seconds emmit all particles in one shot
if (particlesPerSeconds == 0) { if (particlesPerSeconds == 0) {
emitter.emitAllParticles(); emitter.emitAllParticles();
emitter.addControl(killParticles); if (!stopRequested) {
stopRequested = true; emitter.addControl(killParticles);
stopRequested = true;
}
} else { } else {
//else reset its former particlePerSec value to let it emmit. //else reset its former particlePerSec value to let it emmit.
emitter.setParticlesPerSec(particlesPerSeconds); emitter.setParticlesPerSec(particlesPerSeconds);
@ -175,10 +178,15 @@ public class EffectTrack implements Track {
private void stop() { private void stop() {
emitter.setParticlesPerSec(0); emitter.setParticlesPerSec(0);
emitted = false; emitted = false;
emitter.addControl(killParticles); if (!stopRequested) {
stopRequested = true; emitter.addControl(killParticles);
stopRequested = true;
}
} }
/** /**
* Retruns the length of the track * Retruns the length of the track
* @return length of the track * @return length of the track
@ -194,7 +202,67 @@ public class EffectTrack implements Track {
@Override @Override
public Track clone() { public Track clone() {
return new EffectTrack(emitter, length, startOffset); 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 * @param emitter
*/ */
public void setEmitter(ParticleEmitter emitter) { public void setEmitter(ParticleEmitter emitter) {
if (this.emitter != null) {
TrackInfo data = (TrackInfo) emitter.getUserData("TrackInfo");
data.getTracks().remove(this);
}
this.emitter = emitter; this.emitter = emitter;
setUserData(this);
} }
/** /**
@ -228,6 +301,22 @@ public class EffectTrack implements Track {
public void setStartOffset(float startOffset) { public void setStartOffset(float startOffset) {
this.startOffset = 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 * Internal use only serialization
@ -236,10 +325,16 @@ public class EffectTrack implements Track {
*/ */
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule out = ex.getCapsule(this); 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(emitter, "emitter", null);
out.write(particlesPerSeconds, "particlesPerSeconds", 0);
out.write(length, "length", 0); out.write(length, "length", 0);
out.write(startOffset, "startOffset", 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 { public void read(JmeImporter im) throws IOException {
InputCapsule in = im.getCapsule(this); 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 = (ParticleEmitter) in.readSavable("emitter", null);
this.particlesPerSeconds = emitter.getParticlesPerSec();
emitter.setParticlesPerSec(0); emitter.setParticlesPerSec(0);
length = in.readFloat("length", length); length = in.readFloat("length", length);
startOffset = in.readFloat("startOffset", 0); startOffset = in.readFloat("startOffset", 0);

@ -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);
}
}
Loading…
Cancel
Save