diff --git a/engine/src/core/com/jme3/cinematic/Cinematic.java b/engine/src/core/com/jme3/cinematic/Cinematic.java index 9aed688db..4872bb195 100644 --- a/engine/src/core/com/jme3/cinematic/Cinematic.java +++ b/engine/src/core/com/jme3/cinematic/Cinematic.java @@ -53,6 +53,36 @@ import java.util.logging.Level; import java.util.logging.Logger; /** + * An appstate for composing and playing cut scenes in a game. The cineamtic + * schedules CinematicEvents over a timeline. Once the Cinematic created it has + * to be attched to the stateManager. + * + * You can add various CinematicEvents to a Cinematic, see package + * com.jme3.cinematic.events + * + * Two main methods can be used to add an event : + * + * @see Cinematic#addCinematicEvent(float, + * com.jme3.cinematic.events.CinematicEvent) , that adds an event at the given + * time form the cinematic start. + * + * @see + * Cinematic#enqueueCinematicEvent(com.jme3.cinematic.events.CinematicEvent) + * that enqueue events one after the other according to their initialDuration + * + * a cinematic has convenient mathods to handle the playback : + * @see Cinematic#play() + * @see Cinematic#pause() + * @see Cinematic#stop() + * + * A cinematic is itself a CinematicEvent, meaning you can embed several + * Cinematics Embed cinematics must not be added to the stateManager though. + * + * Cinematic has a way to handle several point of view by creating CameraNode + * over a cam and activating them on schedule. + * @see Cinematic#bindCamera(java.lang.String, com.jme3.renderer.Camera) + * @see Cinematic#activateCamera(float, java.lang.String) + * @see Cinematic#setActiveCamera(java.lang.String) * * @author Nehon */ @@ -66,30 +96,64 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { private Map cameras = new HashMap(); private CameraNode currentCam; private boolean initialized = false; - private Map> eventsData; + private Map> eventsData; + private float nextEnqueue = 0; + /** + * Used for serialization creates a cinematic, don't use this constructor + * directly + */ public Cinematic() { } + /** + * creates a cinematic + * + * @param scene the scene in which the cinematic should take place + */ public Cinematic(Node scene) { this.scene = scene; } + /** + * creates a cinematic + * + * @param scene the scene in which the cinematic should take place + * @param initialDuration the duration of the cinematic (without considering + * the speed) + */ public Cinematic(Node scene, float initialDuration) { super(initialDuration); this.scene = scene; } + /** + * creates a cinematic + * + * @param scene the scene in which the cinematic should take place + * @param loopMode tells if this cinematic should be looped or not + */ public Cinematic(Node scene, LoopMode loopMode) { super(loopMode); this.scene = scene; } + /** + * creates a cinematic + * + * @param scene the scene in which the cinematic should take place + * @param initialDuration the duration of the cinematic (without considering + * the speed) + * @param loopMode tells if this cinematic should be looped or not + */ public Cinematic(Node scene, float initialDuration, LoopMode loopMode) { super(initialDuration, loopMode); this.scene = scene; } + /** + * called internally + */ @Override public void onPlay() { if (isInitialized()) { @@ -104,6 +168,9 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { } } + /** + * called internally + */ @Override public void onStop() { time = 0; @@ -111,11 +178,14 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { for (int i = 0; i < cinematicEvents.size(); i++) { CinematicEvent ce = cinematicEvents.get(i); ce.setTime(0); - ce.stop(); + ce.forceStop(); } - enableCurrentCam(false); + setEnableCurrentCam(false); } + /** + * called internally + */ @Override public void onPause() { for (int i = 0; i < cinematicEvents.size(); i++) { @@ -126,6 +196,12 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { } } + /** + * used internally for serialization + * + * @param ex + * @throws IOException + */ @Override public void write(JmeExporter ex) throws IOException { super.write(ex); @@ -137,6 +213,12 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { } + /** + * used internally for srialization + * + * @param im + * @throws IOException + */ @Override public void read(JmeImporter im) throws IOException { super.read(im); @@ -147,6 +229,13 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { timeLine = (TimeLine) ic.readSavable("timeLine", null); } + /** + * sets the speed of the cinematic. Note that it will set the speed of all + * events in the cinematic. 1 is normal speed. use 0.5f to make the + * cinematic twice slower, use 2 to make it twice faster + * + * @param speed the speed + */ @Override public void setSpeed(float speed) { super.setSpeed(speed); @@ -158,6 +247,12 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { } + /** + * used internally + * + * @param stateManager the state manager + * @param app the application + */ public void initialize(AppStateManager stateManager, Application app) { initEvent(app, this); for (CinematicEvent cinematicEvent : cinematicEvents) { @@ -167,33 +262,70 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { initialized = true; } + /** + * used internally + * + * @return + */ public boolean isInitialized() { return initialized; } + /** + * passing true has the same effect as play() you should use play(), + * pause(), stop() to handle the cinemaic playing state. + * + * @param enabled true or false + */ public void setEnabled(boolean enabled) { if (enabled) { play(); } } + /** + * return true if the cinematic appstate is enabled (the cinematic is + * playing) + * + * @return true if enabled + */ public boolean isEnabled() { return playState == PlayState.Playing; } + /** + * called internally + * + * @param stateManager the state manager + */ public void stateAttached(AppStateManager stateManager) { } + /** + * called internally + * + * @param stateManager the state manager + */ public void stateDetached(AppStateManager stateManager) { stop(); } + /** + * called internally don't call it directly. + * + * @param tpf + */ public void update(float tpf) { if (isInitialized()) { internalUpdate(tpf); } } + /** + * used internally, don't call this directly. + * + * @param tpf + */ @Override public void onUpdate(float tpf) { for (int i = 0; i < cinematicEvents.size(); i++) { @@ -214,6 +346,12 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { lastFetchedKeyFrame = keyFrameIndex; } + /** + * This is used internally but can be alled to shuffle through the + * cinematic. + * + * @param time the time to shuffle to. + */ @Override public void setTime(float time) { @@ -258,13 +396,28 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { timeLine.addKeyFrameAtTime(timeStamp, keyFrame); } keyFrame.cinematicEvents.add(cinematicEvent); - cinematicEvents.add(cinematicEvent); - if(isInitialized()){ + cinematicEvents.add(cinematicEvent); + if (isInitialized()) { cinematicEvent.initEvent(null, this); } return keyFrame; } + /** + * enqueue a cinematic event to a cinematic. This is a handy method when you + * want to chain event of a given duration without knowing their initial + * duration + * + * @param cinematicEvent the cinematic event to enqueue + * @return the timestamp the evnt was scheduled. + */ + public float enqueueCinematicEvent(CinematicEvent cinematicEvent) { + float scheduleTime = nextEnqueue; + addCinematicEvent(scheduleTime, cinematicEvent); + nextEnqueue += cinematicEvent.getInitialDuration(); + return scheduleTime; + } + /** * removes the first occurrence found of the given cinematicEvent. * @@ -272,6 +425,7 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { * @return true if the element has been removed */ public boolean removeCinematicEvent(CinematicEvent cinematicEvent) { + cinematicEvent.dispose(); cinematicEvents.remove(cinematicEvent); for (KeyFrame keyFrame : timeLine.values()) { if (keyFrame.cinematicEvents.remove(cinematicEvent)) { @@ -282,38 +436,58 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { } /** - * removes the first occurrence found of the given cinematicEvent for the given time stamp. + * removes the first occurrence found of the given cinematicEvent for the + * given time stamp. + * * @param timeStamp the timestamp when the cinematicEvent has been added * @param cinematicEvent the cinematicEvent to remove * @return true if the element has been removed */ public boolean removeCinematicEvent(float timeStamp, CinematicEvent cinematicEvent) { + cinematicEvent.dispose(); KeyFrame keyFrame = timeLine.getKeyFrameAtTime(timeStamp); return removeCinematicEvent(keyFrame, cinematicEvent); } - + /** - * removes the first occurrence found of the given cinematicEvent for the given keyFrame + * removes the first occurrence found of the given cinematicEvent for the + * given keyFrame + * * @param keyFrame the keyFrame returned by the addCinematicEvent method. * @param cinematicEvent the cinematicEvent to remove * @return true if the element has been removed */ public boolean removeCinematicEvent(KeyFrame keyFrame, CinematicEvent cinematicEvent) { + cinematicEvent.dispose(); boolean ret = keyFrame.cinematicEvents.remove(cinematicEvent); cinematicEvents.remove(cinematicEvent); if (keyFrame.isEmpty()) { - timeLine.removeKeyFrame(keyFrame.getIndex()); + timeLine.removeKeyFrame(keyFrame.getIndex()); } return ret; } - + /** + * called internally + * + * @see AppState#render() + */ public void render(RenderManager rm) { } + /** + * called internally + * + * @see AppState#postRender() + */ public void postRender() { } + /** + * called internally + * + * @see AppState#cleanup() + */ public void cleanup() { } @@ -322,19 +496,34 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { * cinematic events */ public void fitDuration() { - KeyFrame kf = timeLine.getKeyFrameAtTime(timeLine.getLastKeyFrameIndex()); + KeyFrame kf = timeLine.getKeyFrameAtIndex(timeLine.getLastKeyFrameIndex()); float d = 0; for (int i = 0; i < kf.getCinematicEvents().size(); i++) { CinematicEvent ce = kf.getCinematicEvents().get(i); - if (d < (ce.getDuration() * ce.getSpeed())) { - d = (ce.getDuration() * ce.getSpeed()); + float dur = timeLine.getKeyFrameTime(kf) + ce.getDuration() * ce.getSpeed(); + if (d < dur) { + d = dur; } } initialDuration = d; } + /** + * Binds a camera to this cinematic, tagged by a unique name. This methods + * creates and returns a CameraNode for the cam and attach it to the scene. + * The control direction is set to SpatialToCamera. This camera Node can + * then be used in other events to handle the camera movements during the + * playback + * + * @param cameraName the unique tag the camera should have + * @param cam the scene camera. + * @return the created CameraNode. + */ public CameraNode bindCamera(String cameraName, Camera cam) { + if (cameras.containsKey(cameraName)) { + throw new IllegalArgumentException("Camera " + cameraName + " is already binded to this cinematic"); + } CameraNode node = new CameraNode(cameraName, cam); node.setControlDir(ControlDirection.SpatialToCamera); node.getControl(CameraControl.class).setEnabled(false); @@ -343,25 +532,51 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { return node; } + /** + * returns a cameraNode given its name + * + * @param cameraName the camera name (as registerd in + * Cinematic#bindCamera()) + * @return the cameraNode for this name + */ public CameraNode getCamera(String cameraName) { return cameras.get(cameraName); } - private void enableCurrentCam(boolean enabled) { + /** + * enable/disable the camera control of the cameraNode of the current cam + * + * @param enabled + */ + private void setEnableCurrentCam(boolean enabled) { if (currentCam != null) { currentCam.getControl(CameraControl.class).setEnabled(enabled); } } + /** + * Sets the active camera instantly (use activateCamera if you want to + * schedule that event) + * + * @param cameraName the camera name (as registerd in + * Cinematic#bindCamera()) + */ public void setActiveCamera(String cameraName) { - enableCurrentCam(false); + setEnableCurrentCam(false); currentCam = cameras.get(cameraName); if (currentCam == null) { logger.log(Level.WARNING, "{0} is not a camera bond to the cinematic, cannot activate", cameraName); } - enableCurrentCam(true); + setEnableCurrentCam(true); } + /** + * schedule an event that will activate the camera at the given time + * + * @param timeStamp the time to activate the cam + * @param cameraName the camera name (as registerd in + * Cinematic#bindCamera()) + */ public void activateCamera(final float timeStamp, final String cameraName) { addCinematicEvent(timeStamp, new AbstractCinematicEvent() { @Override @@ -387,6 +602,10 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { public void onPause() { } + @Override + public void forceStop() { + } + @Override public void setTime(float time) { play(); @@ -394,47 +613,104 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { }); } - public void setScene(Node scene) { - this.scene = scene; - } - - private Map> getEventsData() { + /** + * returns the complete eventdata map + * + * @return the eventdata map + */ + private Map> getEventsData() { if (eventsData == null) { - eventsData = new HashMap>(); + eventsData = new HashMap>(); } return eventsData; } - public void putEventData(String type, String name, Object object) { - Map> data = getEventsData(); - Map row = data.get(type); + /** + * used internally put an eventdata in the cinematic + * + * @param type the type of data + * @param key the key + * @param object the data + */ + public void putEventData(String type, Object key, Object object) { + Map> data = getEventsData(); + Map row = data.get(type); if (row == null) { - row = new HashMap(); + row = new HashMap(); } - row.put(name, object); + row.put(key, object); + data.put(type, row); } - public Object getEventData(String type, String name) { + /** + * used internally return and event data + * + * @param type the type of data + * @param key the key + * @return + */ + public Object getEventData(String type, Object key) { if (eventsData != null) { - Map row = eventsData.get(type); + Map row = eventsData.get(type); if (row != null) { - return row.get(name); + return row.get(key); } } return null; } - public Savable removeEventData(String type, String name) { + /** + * Used internally remove an eventData + * + * @param type the type of data + * @param key the key of the data + */ + public void removeEventData(String type, Object key) { if (eventsData != null) { - Map row = eventsData.get(type); + Map row = eventsData.get(type); if (row != null) { - row.remove(name); + row.remove(key); } } - return null; } + /** + * sets the scene to use for this cinematic it is expected that the scene is + * added before adding events to the cinematic + * + * @param scene the scene where the cinematic should ttake place. + */ + public void setScene(Node scene) { + this.scene = scene; + } + + /** + * return the scene where the cinematic occur + * + * @return the scene + */ public Node getScene() { return scene; } + + /** + * clear the cinematic of its events. + */ + public void clear() { + dispose(); + cinematicEvents.clear(); + timeLine.clear(); + eventsData.clear(); + } + + /** + * used internally to cleanup the cinematic. Called when the clear() method + * is called + */ + @Override + public void dispose() { + for (CinematicEvent event : cinematicEvents) { + event.dispose(); + } + } } diff --git a/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java b/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java index 634232997..a68b84e4a 100644 --- a/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java +++ b/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java @@ -95,6 +95,16 @@ public abstract class AbstractCinematicEvent implements CinematicEvent { this.initialDuration = initialDuration; this.loopMode = loopMode; } + + /** + * this method can be implemented if the event needs different handling when + * stopped naturally (when the event reach its end) + * or when it was forced stopped during playback + * otherwise it just call regular stop() + */ + public void forceStop(){ + stop(); + } /** * Play this event @@ -123,8 +133,10 @@ public abstract class AbstractCinematicEvent implements CinematicEvent { if (playState == PlayState.Playing) { time = time + (tpf * speed); onUpdate(tpf); - if (time >= initialDuration && loopMode == loopMode.DontLoop) { + if (time >= initialDuration && loopMode == LoopMode.DontLoop) { stop(); + }else if(time >= initialDuration && loopMode == LoopMode.Loop){ + setTime(0); } } @@ -315,4 +327,9 @@ public abstract class AbstractCinematicEvent implements CinematicEvent { public float getTime() { return time; } + + public void dispose() { + } + + } diff --git a/engine/src/core/com/jme3/cinematic/events/AnimationEvent.java b/engine/src/core/com/jme3/cinematic/events/AnimationEvent.java index a3028cdf5..1376f78eb 100644 --- a/engine/src/core/com/jme3/cinematic/events/AnimationEvent.java +++ b/engine/src/core/com/jme3/cinematic/events/AnimationEvent.java @@ -43,64 +43,247 @@ import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.scene.Spatial; import java.io.IOException; -import java.util.logging.Level; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Logger; /** + * An event based on an animation of a model. The model has to hold an + * AnimControl with valid animation (bone or spatial animations). + * + * It helps to schedule the playback of an animation on a model in a Cinematic. + * * * @author Nehon */ public class AnimationEvent extends AbstractCinematicEvent { + // Version #2: directly keeping track on the model instead of trying to retrieve + //it from the scene according to its name, because the name is not supposed to be unique + //For backward compatibility, if the model is null it's looked up into the scene + public static final int SAVABLE_VERSION = 2; private static final Logger log = Logger.getLogger(AnimationEvent.class.getName()); + public static final String MODEL_CHANNELS = "modelChannels"; protected AnimChannel channel; protected String animationName; + protected Spatial model; + //kept for backward compatibility protected String modelName; + protected float blendTime = 0; + protected int channelIndex = 0; + // parent cinematic + protected Cinematic cinematic; + /** + * used for serialization don't call directly use one of the following + * contructors + */ public AnimationEvent() { } + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + */ public AnimationEvent(Spatial model, String animationName) { - modelName = model.getName(); + this.model = model; this.animationName = animationName; initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); } + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + */ public AnimationEvent(Spatial model, String animationName, float initialDuration) { super(initialDuration); - modelName = model.getName(); + this.model = model; this.animationName = animationName; } + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param loopMode the loopMode + * @see LoopMode + */ public AnimationEvent(Spatial model, String animationName, LoopMode loopMode) { super(loopMode); initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); - modelName = model.getName(); + this.model = model; this.animationName = animationName; } + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param loopMode the loopMode + * @see LoopMode + */ public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode) { super(initialDuration, loopMode); - modelName = model.getName(); + this.model = model; + this.animationName = animationName; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param blendTime the time during the animation are gonna be blended + * @see AnimChannel#setAnim(java.lang.String, float) + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, float blendTime) { + super(initialDuration); + this.model = model; + this.animationName = animationName; + this.blendTime = blendTime; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param loopMode the loopMode + * @see LoopMode + * @param blendTime the time during the animation are gonna be blended + * @see AnimChannel#setAnim(java.lang.String, float) + */ + public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, float blendTime) { + super(loopMode); + initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); + this.model = model; + this.animationName = animationName; + this.blendTime = blendTime; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param loopMode the loopMode + * @see LoopMode + * @param blendTime the time during the animation are gonna be blended + * @see AnimChannel#setAnim(java.lang.String, float) + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, float blendTime) { + super(initialDuration, loopMode); + this.model = model; + this.animationName = animationName; + this.blendTime = blendTime; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param loopMode the loopMode + * @see LoopMode + * @param channelIndex the index of the channel default is 0. Events on the + * same channelIndex will use the same channel. + */ + public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, int channelIndex) { + super(loopMode); + initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); + this.model = model; this.animationName = animationName; + this.channelIndex = channelIndex; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param channelIndex the index of the channel default is 0. Events on the + * same channelIndex will use the same channel. + */ + public AnimationEvent(Spatial model, String animationName, int channelIndex) { + this.model = model; + this.animationName = animationName; + initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); + this.channelIndex = channelIndex; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param channelIndex the index of the channel default is 0. Events on the + * same channelIndex will use the same channel. + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, int channelIndex) { + super(initialDuration); + this.model = model; + this.animationName = animationName; + this.channelIndex = channelIndex; + } + + /** + * creates an animation event + * + * @param model the model on which the animation will be played + * @param animationName the name of the animation to play + * @param initialDuration the initialduration of the event + * @param loopMode the loopMode + * @see LoopMode + * @param channelIndex the index of the channel default is 0. Events on the + * same channelIndex will use the same channel. + */ + public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, int channelIndex) { + super(initialDuration, loopMode); + this.model = model; + this.animationName = animationName; + this.channelIndex = channelIndex; } @Override public void initEvent(Application app, Cinematic cinematic) { super.initEvent(app, cinematic); + this.cinematic = cinematic; if (channel == null) { - Object s = cinematic.getEventData("modelChannels", modelName); - if (s != null && s instanceof AnimChannel) { - this.channel = (AnimChannel) s; - } else if (s == null) { - Spatial model = cinematic.getScene().getChild(modelName); + Object s = cinematic.getEventData(MODEL_CHANNELS, model); + if (s == null) { + s = new HashMap(); + cinematic.putEventData(MODEL_CHANNELS, model, s); + } + + Map map = (Map) s; + this.channel = map.get(channelIndex); + if (this.channel == null) { + if (model == null) { + //the model is null we try to find it according to the name + //this should occur only when loading an old saved cinematic + //othewise it's an error + model = cinematic.getScene().getChild(modelName); + } if (model != null) { channel = model.getControl(AnimControl.class).createChannel(); - cinematic.putEventData("modelChannels", modelName, channel); + map.put(channelIndex, channel); } else { - log.log(Level.WARNING, "spatial {0} not found in the scene, cannot perform animation", modelName); + //it's an error + throw new UnsupportedOperationException("model should not be null"); } - } + } } } @@ -108,8 +291,8 @@ public class AnimationEvent extends AbstractCinematicEvent { @Override public void setTime(float time) { super.setTime(time); - if (channel.getAnimationName() == null) { - channel.setAnim(animationName); + if (!animationName.equals(channel.getAnimationName())) { + channel.setAnim(animationName, blendTime); } float t = time; if (loopMode == loopMode.Loop) { @@ -142,10 +325,10 @@ public class AnimationEvent extends AbstractCinematicEvent { public void onPlay() { channel.getControl().setEnabled(true); if (playState == PlayState.Stopped) { - channel.setAnim(animationName); + channel.setAnim(animationName, blendTime); channel.setSpeed(speed); channel.setLoopMode(loopMode); - channel.setTime(time); + channel.setTime(0); } } @@ -163,10 +346,15 @@ public class AnimationEvent extends AbstractCinematicEvent { @Override public void onStop() { + } + + @Override + public void forceStop() { if (channel != null) { - channel.setTime(0); + channel.setTime(time); channel.reset(false); } + super.forceStop(); } @Override @@ -188,15 +376,47 @@ public class AnimationEvent extends AbstractCinematicEvent { public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write(modelName, "modelName", ""); + + oc.write(model, "model", null); oc.write(animationName, "animationName", ""); + oc.write(blendTime, "blendTime", 0f); + oc.write(channelIndex, "channelIndex", 0); + } @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); - modelName = ic.readString("modelName", ""); + if (im.getFormatVersion() == 0) { + modelName = ic.readString("modelName", ""); + } + //FIXME always the same issue, because of the clonning of assets, this won't work + //we have to somehow store userdata in the spatial and then recurse the + //scene sub scenegraph to find the correct instance of the model + //This brings a reflaxion about the cinematic being an appstate, + //shouldn't it be a control over the scene + // this would allow to use the cloneForSpatial method and automatically + //rebind cloned references of original objects. + //for now as nobody probably ever saved a cinematic, this is not a critical issue + model = (Spatial) ic.readSavable("model", null); animationName = ic.readString("animationName", ""); + blendTime = ic.readFloat("blendTime", 0f); + channelIndex = ic.readInt("channelIndex", 0); + } + + @Override + public void dispose() { + super.dispose(); + Object o = cinematic.getEventData(MODEL_CHANNELS, model); + if (o != null) { + ArrayList list = (ArrayList) o; + list.remove(channel); + if (list.isEmpty()) { + cinematic.removeEventData(MODEL_CHANNELS, model); + } + } + cinematic = null; + channel = null; } } diff --git a/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java b/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java index 37e70f920..ddac3f01f 100644 --- a/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java +++ b/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java @@ -52,6 +52,14 @@ public interface CinematicEvent extends Savable { * Stops the animation */ public void stop(); + + /** + * this method can be implemented if the event needs different handling when + * stopped naturally (when the event reach its end) + * or when it was forced stopped during playback + * otherwise it just call regular stop() + */ + public void forceStop(); /** * Pauses the animation @@ -138,6 +146,11 @@ public interface CinematicEvent extends Savable { * @return the time */ public float getTime(); - + + /** + * method called when an event is removed from a cinematic + * this method should remove any reference to any external objects. + */ + public void dispose(); } diff --git a/engine/src/core/com/jme3/cinematic/events/SoundEvent.java b/engine/src/core/com/jme3/cinematic/events/SoundEvent.java index 23dcb89bd..1b4c65b09 100644 --- a/engine/src/core/com/jme3/cinematic/events/SoundEvent.java +++ b/engine/src/core/com/jme3/cinematic/events/SoundEvent.java @@ -69,39 +69,82 @@ public class SoundEvent extends AbstractCinematicEvent { this.stream = stream; } + /** + * creates a sound track from the given resource path + * @param path the path to an audi file (ie : "Sounds/mySound.wav") + * @param stream true to make the audio data streamed + * @param initialDuration the nitial duration of the event + */ public SoundEvent(String path, boolean stream, float initialDuration) { super(initialDuration); this.path = path; this.stream = stream; } + /** + * creates a sound track from the given resource path + * @param path the path to an audi file (ie : "Sounds/mySound.wav") + * @param stream true to make the audio data streamed + * @param loopMode the loopMode + * @see LoopMode + */ public SoundEvent(String path, boolean stream, LoopMode loopMode) { super(loopMode); this.path = path; this.stream = stream; } + /** + * creates a sound track from the given resource path + * @param path the path to an audi file (ie : "Sounds/mySound.wav") + * @param stream true to make the audio data streamed + * @param initialDuration the nitial duration of the event + * @param loopMode the loopMode + * @see LoopMode + */ public SoundEvent(String path, boolean stream, float initialDuration, LoopMode loopMode) { super(initialDuration, loopMode); this.path = path; this.stream = stream; } + /** + * creates a sound track from the given resource path + * @param path the path to an audi file (ie : "Sounds/mySound.wav") + * @param initialDuration the nitial duration of the event + */ public SoundEvent(String path, float initialDuration) { super(initialDuration); this.path = path; } + /** + * creates a sound track from the given resource path + * @param path the path to an audi file (ie : "Sounds/mySound.wav") + * @param loopMode the loopMode + * @see LoopMode + */ public SoundEvent(String path, LoopMode loopMode) { super(loopMode); this.path = path; } + /** + * creates a sound track from the given resource path + * @param path the path to an audi file (ie : "Sounds/mySound.wav") + * @param initialDuration the nitial duration of the event + * @param loopMode the loopMode + * @see LoopMode + */ public SoundEvent(String path, float initialDuration, LoopMode loopMode) { super(initialDuration, loopMode); this.path = path; } + /** + * creates a sound event + * used for serialization + */ public SoundEvent() { }