Cinematic, added an enqueueEvent method that allow to stack events without knowing their duration.

added support for blending animations transitions in AnimationEvent, added support for having several channels in AnimationEvent.
added javadoc

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10045 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 12 years ago
parent 3c84826db1
commit 7722b7b09d
  1. 338
      engine/src/core/com/jme3/cinematic/Cinematic.java
  2. 19
      engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java
  3. 256
      engine/src/core/com/jme3/cinematic/events/AnimationEvent.java
  4. 13
      engine/src/core/com/jme3/cinematic/events/CinematicEvent.java
  5. 43
      engine/src/core/com/jme3/cinematic/events/SoundEvent.java

@ -53,6 +53,36 @@ import java.util.logging.Level;
import java.util.logging.Logger; 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 * @author Nehon
*/ */
@ -66,30 +96,64 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
private Map<String, CameraNode> cameras = new HashMap<String, CameraNode>(); private Map<String, CameraNode> cameras = new HashMap<String, CameraNode>();
private CameraNode currentCam; private CameraNode currentCam;
private boolean initialized = false; private boolean initialized = false;
private Map<String, Map<String, Object>> eventsData; private Map<String, Map<Object, Object>> eventsData;
private float nextEnqueue = 0;
/**
* Used for serialization creates a cinematic, don't use this constructor
* directly
*/
public Cinematic() { public Cinematic() {
} }
/**
* creates a cinematic
*
* @param scene the scene in which the cinematic should take place
*/
public Cinematic(Node scene) { public Cinematic(Node scene) {
this.scene = 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) { public Cinematic(Node scene, float initialDuration) {
super(initialDuration); super(initialDuration);
this.scene = scene; 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) { public Cinematic(Node scene, LoopMode loopMode) {
super(loopMode); super(loopMode);
this.scene = 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)
* @param loopMode tells if this cinematic should be looped or not
*/
public Cinematic(Node scene, float initialDuration, LoopMode loopMode) { public Cinematic(Node scene, float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode); super(initialDuration, loopMode);
this.scene = scene; this.scene = scene;
} }
/**
* called internally
*/
@Override @Override
public void onPlay() { public void onPlay() {
if (isInitialized()) { if (isInitialized()) {
@ -104,6 +168,9 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
} }
} }
/**
* called internally
*/
@Override @Override
public void onStop() { public void onStop() {
time = 0; time = 0;
@ -111,11 +178,14 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
for (int i = 0; i < cinematicEvents.size(); i++) { for (int i = 0; i < cinematicEvents.size(); i++) {
CinematicEvent ce = cinematicEvents.get(i); CinematicEvent ce = cinematicEvents.get(i);
ce.setTime(0); ce.setTime(0);
ce.stop(); ce.forceStop();
} }
enableCurrentCam(false); setEnableCurrentCam(false);
} }
/**
* called internally
*/
@Override @Override
public void onPause() { public void onPause() {
for (int i = 0; i < cinematicEvents.size(); i++) { 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 @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);
@ -137,6 +213,12 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
} }
/**
* used internally for srialization
*
* @param im
* @throws IOException
*/
@Override @Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
super.read(im); super.read(im);
@ -147,6 +229,13 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
timeLine = (TimeLine) ic.readSavable("timeLine", null); 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 @Override
public void setSpeed(float speed) { public void setSpeed(float speed) {
super.setSpeed(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) { public void initialize(AppStateManager stateManager, Application app) {
initEvent(app, this); initEvent(app, this);
for (CinematicEvent cinematicEvent : cinematicEvents) { for (CinematicEvent cinematicEvent : cinematicEvents) {
@ -167,33 +262,70 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
initialized = true; initialized = true;
} }
/**
* used internally
*
* @return
*/
public boolean isInitialized() { public boolean isInitialized() {
return initialized; 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) { public void setEnabled(boolean enabled) {
if (enabled) { if (enabled) {
play(); play();
} }
} }
/**
* return true if the cinematic appstate is enabled (the cinematic is
* playing)
*
* @return true if enabled
*/
public boolean isEnabled() { public boolean isEnabled() {
return playState == PlayState.Playing; return playState == PlayState.Playing;
} }
/**
* called internally
*
* @param stateManager the state manager
*/
public void stateAttached(AppStateManager stateManager) { public void stateAttached(AppStateManager stateManager) {
} }
/**
* called internally
*
* @param stateManager the state manager
*/
public void stateDetached(AppStateManager stateManager) { public void stateDetached(AppStateManager stateManager) {
stop(); stop();
} }
/**
* called internally don't call it directly.
*
* @param tpf
*/
public void update(float tpf) { public void update(float tpf) {
if (isInitialized()) { if (isInitialized()) {
internalUpdate(tpf); internalUpdate(tpf);
} }
} }
/**
* used internally, don't call this directly.
*
* @param tpf
*/
@Override @Override
public void onUpdate(float tpf) { public void onUpdate(float tpf) {
for (int i = 0; i < cinematicEvents.size(); i++) { for (int i = 0; i < cinematicEvents.size(); i++) {
@ -214,6 +346,12 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
lastFetchedKeyFrame = keyFrameIndex; lastFetchedKeyFrame = keyFrameIndex;
} }
/**
* This is used internally but can be alled to shuffle through the
* cinematic.
*
* @param time the time to shuffle to.
*/
@Override @Override
public void setTime(float time) { public void setTime(float time) {
@ -259,12 +397,27 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
} }
keyFrame.cinematicEvents.add(cinematicEvent); keyFrame.cinematicEvents.add(cinematicEvent);
cinematicEvents.add(cinematicEvent); cinematicEvents.add(cinematicEvent);
if(isInitialized()){ if (isInitialized()) {
cinematicEvent.initEvent(null, this); cinematicEvent.initEvent(null, this);
} }
return keyFrame; 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. * 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 * @return true if the element has been removed
*/ */
public boolean removeCinematicEvent(CinematicEvent cinematicEvent) { public boolean removeCinematicEvent(CinematicEvent cinematicEvent) {
cinematicEvent.dispose();
cinematicEvents.remove(cinematicEvent); cinematicEvents.remove(cinematicEvent);
for (KeyFrame keyFrame : timeLine.values()) { for (KeyFrame keyFrame : timeLine.values()) {
if (keyFrame.cinematicEvents.remove(cinematicEvent)) { if (keyFrame.cinematicEvents.remove(cinematicEvent)) {
@ -282,23 +436,29 @@ 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 timeStamp the timestamp when the cinematicEvent has been added
* @param cinematicEvent the cinematicEvent to remove * @param cinematicEvent the cinematicEvent to remove
* @return true if the element has been removed * @return true if the element has been removed
*/ */
public boolean removeCinematicEvent(float timeStamp, CinematicEvent cinematicEvent) { public boolean removeCinematicEvent(float timeStamp, CinematicEvent cinematicEvent) {
cinematicEvent.dispose();
KeyFrame keyFrame = timeLine.getKeyFrameAtTime(timeStamp); KeyFrame keyFrame = timeLine.getKeyFrameAtTime(timeStamp);
return removeCinematicEvent(keyFrame, cinematicEvent); 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 keyFrame the keyFrame returned by the addCinematicEvent method.
* @param cinematicEvent the cinematicEvent to remove * @param cinematicEvent the cinematicEvent to remove
* @return true if the element has been removed * @return true if the element has been removed
*/ */
public boolean removeCinematicEvent(KeyFrame keyFrame, CinematicEvent cinematicEvent) { public boolean removeCinematicEvent(KeyFrame keyFrame, CinematicEvent cinematicEvent) {
cinematicEvent.dispose();
boolean ret = keyFrame.cinematicEvents.remove(cinematicEvent); boolean ret = keyFrame.cinematicEvents.remove(cinematicEvent);
cinematicEvents.remove(cinematicEvent); cinematicEvents.remove(cinematicEvent);
if (keyFrame.isEmpty()) { if (keyFrame.isEmpty()) {
@ -307,13 +467,27 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
return ret; return ret;
} }
/**
* called internally
*
* @see AppState#render()
*/
public void render(RenderManager rm) { public void render(RenderManager rm) {
} }
/**
* called internally
*
* @see AppState#postRender()
*/
public void postRender() { public void postRender() {
} }
/**
* called internally
*
* @see AppState#cleanup()
*/
public void cleanup() { public void cleanup() {
} }
@ -322,19 +496,34 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
* cinematic events * cinematic events
*/ */
public void fitDuration() { public void fitDuration() {
KeyFrame kf = timeLine.getKeyFrameAtTime(timeLine.getLastKeyFrameIndex()); KeyFrame kf = timeLine.getKeyFrameAtIndex(timeLine.getLastKeyFrameIndex());
float d = 0; float d = 0;
for (int i = 0; i < kf.getCinematicEvents().size(); i++) { for (int i = 0; i < kf.getCinematicEvents().size(); i++) {
CinematicEvent ce = kf.getCinematicEvents().get(i); CinematicEvent ce = kf.getCinematicEvents().get(i);
if (d < (ce.getDuration() * ce.getSpeed())) { float dur = timeLine.getKeyFrameTime(kf) + ce.getDuration() * ce.getSpeed();
d = (ce.getDuration() * ce.getSpeed()); if (d < dur) {
d = dur;
} }
} }
initialDuration = d; 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) { 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); CameraNode node = new CameraNode(cameraName, cam);
node.setControlDir(ControlDirection.SpatialToCamera); node.setControlDir(ControlDirection.SpatialToCamera);
node.getControl(CameraControl.class).setEnabled(false); node.getControl(CameraControl.class).setEnabled(false);
@ -343,25 +532,51 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
return node; 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) { public CameraNode getCamera(String cameraName) {
return cameras.get(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) { if (currentCam != null) {
currentCam.getControl(CameraControl.class).setEnabled(enabled); 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) { public void setActiveCamera(String cameraName) {
enableCurrentCam(false); setEnableCurrentCam(false);
currentCam = cameras.get(cameraName); currentCam = cameras.get(cameraName);
if (currentCam == null) { if (currentCam == null) {
logger.log(Level.WARNING, "{0} is not a camera bond to the cinematic, cannot activate", cameraName); 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) { public void activateCamera(final float timeStamp, final String cameraName) {
addCinematicEvent(timeStamp, new AbstractCinematicEvent() { addCinematicEvent(timeStamp, new AbstractCinematicEvent() {
@Override @Override
@ -387,6 +602,10 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
public void onPause() { public void onPause() {
} }
@Override
public void forceStop() {
}
@Override @Override
public void setTime(float time) { public void setTime(float time) {
play(); play();
@ -394,47 +613,104 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
}); });
} }
public void setScene(Node scene) { /**
this.scene = scene; * returns the complete eventdata map
} *
* @return the eventdata map
private Map<String, Map<String, Object>> getEventsData() { */
private Map<String, Map<Object, Object>> getEventsData() {
if (eventsData == null) { if (eventsData == null) {
eventsData = new HashMap<String, Map<String, Object>>(); eventsData = new HashMap<String, Map<Object, Object>>();
} }
return eventsData; return eventsData;
} }
public void putEventData(String type, String name, Object object) { /**
Map<String, Map<String, Object>> data = getEventsData(); * used internally put an eventdata in the cinematic
Map<String, Object> row = data.get(type); *
* @param type the type of data
* @param key the key
* @param object the data
*/
public void putEventData(String type, Object key, Object object) {
Map<String, Map<Object, Object>> data = getEventsData();
Map<Object, Object> row = data.get(type);
if (row == null) { if (row == null) {
row = new HashMap<String, Object>(); row = new HashMap<Object, Object>();
} }
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) { if (eventsData != null) {
Map<String, Object> row = eventsData.get(type); Map<Object, Object> row = eventsData.get(type);
if (row != null) { if (row != null) {
return row.get(name); return row.get(key);
} }
} }
return null; 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) { if (eventsData != null) {
Map<String, Object> row = eventsData.get(type); Map<Object, Object> row = eventsData.get(type);
if (row != null) { 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() { public Node getScene() {
return scene; 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();
}
}
} }

@ -96,6 +96,16 @@ public abstract class AbstractCinematicEvent implements CinematicEvent {
this.loopMode = loopMode; 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 * Play this event
*/ */
@ -123,8 +133,10 @@ public abstract class AbstractCinematicEvent implements CinematicEvent {
if (playState == PlayState.Playing) { if (playState == PlayState.Playing) {
time = time + (tpf * speed); time = time + (tpf * speed);
onUpdate(tpf); onUpdate(tpf);
if (time >= initialDuration && loopMode == loopMode.DontLoop) { if (time >= initialDuration && loopMode == LoopMode.DontLoop) {
stop(); stop();
}else if(time >= initialDuration && loopMode == LoopMode.Loop){
setTime(0);
} }
} }
@ -315,4 +327,9 @@ public abstract class AbstractCinematicEvent implements CinematicEvent {
public float getTime() { public float getTime() {
return time; return time;
} }
public void dispose() {
}
} }

@ -43,62 +43,245 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule; import com.jme3.export.OutputCapsule;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import java.io.IOException; 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; 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 * @author Nehon
*/ */
public class AnimationEvent extends AbstractCinematicEvent { 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()); private static final Logger log = Logger.getLogger(AnimationEvent.class.getName());
public static final String MODEL_CHANNELS = "modelChannels";
protected AnimChannel channel; protected AnimChannel channel;
protected String animationName; protected String animationName;
protected Spatial model;
//kept for backward compatibility
protected String modelName; 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() { 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) { public AnimationEvent(Spatial model, String animationName) {
modelName = model.getName(); this.model = model;
this.animationName = animationName; this.animationName = animationName;
initialDuration = model.getControl(AnimControl.class).getAnimationLength(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) { public AnimationEvent(Spatial model, String animationName, float initialDuration) {
super(initialDuration); super(initialDuration);
modelName = model.getName(); this.model = model;
this.animationName = animationName; 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) { public AnimationEvent(Spatial model, String animationName, LoopMode loopMode) {
super(loopMode); super(loopMode);
initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
modelName = model.getName(); this.model = model;
this.animationName = animationName; 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) { public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode) {
super(initialDuration, 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.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 @Override
public void initEvent(Application app, Cinematic cinematic) { public void initEvent(Application app, Cinematic cinematic) {
super.initEvent(app, cinematic); super.initEvent(app, cinematic);
this.cinematic = cinematic;
if (channel == null) { if (channel == null) {
Object s = cinematic.getEventData("modelChannels", modelName); Object s = cinematic.getEventData(MODEL_CHANNELS, model);
if (s != null && s instanceof AnimChannel) { if (s == null) {
this.channel = (AnimChannel) s; s = new HashMap<Integer, AnimChannel>();
} else if (s == null) { cinematic.putEventData(MODEL_CHANNELS, model, s);
Spatial model = cinematic.getScene().getChild(modelName); }
Map<Integer, AnimChannel> map = (Map<Integer, AnimChannel>) 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) { if (model != null) {
channel = model.getControl(AnimControl.class).createChannel(); channel = model.getControl(AnimControl.class).createChannel();
cinematic.putEventData("modelChannels", modelName, channel); map.put(channelIndex, channel);
} else { } 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 @Override
public void setTime(float time) { public void setTime(float time) {
super.setTime(time); super.setTime(time);
if (channel.getAnimationName() == null) { if (!animationName.equals(channel.getAnimationName())) {
channel.setAnim(animationName); channel.setAnim(animationName, blendTime);
} }
float t = time; float t = time;
if (loopMode == loopMode.Loop) { if (loopMode == loopMode.Loop) {
@ -142,10 +325,10 @@ public class AnimationEvent extends AbstractCinematicEvent {
public void onPlay() { public void onPlay() {
channel.getControl().setEnabled(true); channel.getControl().setEnabled(true);
if (playState == PlayState.Stopped) { if (playState == PlayState.Stopped) {
channel.setAnim(animationName); channel.setAnim(animationName, blendTime);
channel.setSpeed(speed); channel.setSpeed(speed);
channel.setLoopMode(loopMode); channel.setLoopMode(loopMode);
channel.setTime(time); channel.setTime(0);
} }
} }
@ -163,10 +346,15 @@ public class AnimationEvent extends AbstractCinematicEvent {
@Override @Override
public void onStop() { public void onStop() {
}
@Override
public void forceStop() {
if (channel != null) { if (channel != null) {
channel.setTime(0); channel.setTime(time);
channel.reset(false); channel.reset(false);
} }
super.forceStop();
} }
@Override @Override
@ -188,15 +376,47 @@ public class AnimationEvent extends AbstractCinematicEvent {
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(modelName, "modelName", "");
oc.write(model, "model", null);
oc.write(animationName, "animationName", ""); oc.write(animationName, "animationName", "");
oc.write(blendTime, "blendTime", 0f);
oc.write(channelIndex, "channelIndex", 0);
} }
@Override @Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
super.read(im); super.read(im);
InputCapsule ic = im.getCapsule(this); InputCapsule ic = im.getCapsule(this);
if (im.getFormatVersion() == 0) {
modelName = ic.readString("modelName", ""); 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", ""); 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<AnimChannel> list = (ArrayList<AnimChannel>) o;
list.remove(channel);
if (list.isEmpty()) {
cinematic.removeEventData(MODEL_CHANNELS, model);
}
}
cinematic = null;
channel = null;
} }
} }

@ -53,6 +53,14 @@ public interface CinematicEvent extends Savable {
*/ */
public void stop(); 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 * Pauses the animation
*/ */
@ -139,5 +147,10 @@ public interface CinematicEvent extends Savable {
*/ */
public float getTime(); 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();
} }

@ -69,39 +69,82 @@ public class SoundEvent extends AbstractCinematicEvent {
this.stream = stream; 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) { public SoundEvent(String path, boolean stream, float initialDuration) {
super(initialDuration); super(initialDuration);
this.path = path; this.path = path;
this.stream = stream; 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) { public SoundEvent(String path, boolean stream, LoopMode loopMode) {
super(loopMode); super(loopMode);
this.path = path; this.path = path;
this.stream = stream; 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) { public SoundEvent(String path, boolean stream, float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode); super(initialDuration, loopMode);
this.path = path; this.path = path;
this.stream = stream; 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) { public SoundEvent(String path, float initialDuration) {
super(initialDuration); super(initialDuration);
this.path = path; 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) { public SoundEvent(String path, LoopMode loopMode) {
super(loopMode); super(loopMode);
this.path = path; 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) { public SoundEvent(String path, float initialDuration, LoopMode loopMode) {
super(initialDuration, loopMode); super(initialDuration, loopMode);
this.path = path; this.path = path;
} }
/**
* creates a sound event
* used for serialization
*/
public SoundEvent() { public SoundEvent() {
} }

Loading…
Cancel
Save