diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java
index 44b836115..0cd24cab8 100644
--- a/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java
+++ b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java
@@ -1,365 +1,369 @@
-/*
- * Copyright (c) 2009-2012 jMonkeyEngine
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.jme3.animation;
-
-import com.jme3.math.FastMath;
-import com.jme3.util.TempVars;
-import java.util.BitSet;
-
-/**
- * AnimChannel provides controls, such as play, pause,
- * fast forward, etc, for an animation. The animation
- * channel may influence the entire model or specific bones of the model's
- * skeleton. A single model may have multiple animation channels influencing
- * various parts of its body. For example, a character model may have an
- * animation channel for its feet, and another for its torso, and
- * the animations for each channel are controlled independently.
- *
- * @author Kirill Vainer
- */
-public final class AnimChannel {
-
- private static final float DEFAULT_BLEND_TIME = 0.15f;
-
- private AnimControl control;
-
- private BitSet affectedBones;
-
- private Animation animation;
- private Animation blendFrom;
- private float time;
- private float speed;
- private float timeBlendFrom;
- private float blendTime;
- private float speedBlendFrom;
- private boolean notified=false;
-
- private LoopMode loopMode, loopModeBlendFrom;
-
- private float blendAmount = 1f;
- private float blendRate = 0;
-
- AnimChannel(AnimControl control){
- this.control = control;
- }
-
- /**
- * Returns the parent control of this AnimChannel.
- *
- * @return the parent control of this AnimChannel.
- * @see AnimControl
- */
- public AnimControl getControl() {
- return control;
- }
-
- /**
- * @return The name of the currently playing animation, or null if
- * none is assigned.
- *
- * @see AnimChannel#setAnim(java.lang.String)
- */
- public String getAnimationName() {
- return animation != null ? animation.getName() : null;
- }
-
- /**
- * @return The loop mode currently set for the animation. The loop mode
- * determines what will happen to the animation once it finishes
- * playing.
- *
- * For more information, see the LoopMode enum class.
- * @see LoopMode
- * @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode)
- */
- public LoopMode getLoopMode() {
- return loopMode;
- }
-
- /**
- * @param loopMode Set the loop mode for the channel. The loop mode
- * determines what will happen to the animation once it finishes
- * playing.
- *
- * For more information, see the LoopMode enum class.
- * @see LoopMode
- */
- public void setLoopMode(LoopMode loopMode) {
- this.loopMode = loopMode;
- }
-
- /**
- * @return The speed that is assigned to the animation channel. The speed
- * is a scale value starting from 0.0, at 1.0 the animation will play
- * at its default speed.
- *
- * @see AnimChannel#setSpeed(float)
- */
- public float getSpeed() {
- return speed;
- }
-
- /**
- * @param speed Set the speed of the animation channel. The speed
- * is a scale value starting from 0.0, at 1.0 the animation will play
- * at its default speed.
- */
- public void setSpeed(float speed) {
- this.speed = speed;
- if(blendTime>0){
- this.speedBlendFrom = speed;
- blendTime = Math.min(blendTime, animation.getLength() / speed);
- blendRate = 1/ blendTime;
- }
- }
-
- /**
- * @return The time of the currently playing animation. The time
- * starts at 0 and continues on until getAnimMaxTime().
- *
- * @see AnimChannel#setTime(float)
- */
- public float getTime() {
- return time;
- }
-
- /**
- * @param time Set the time of the currently playing animation, the time
- * is clamped from 0 to {@link #getAnimMaxTime()}.
- */
- public void setTime(float time) {
- this.time = FastMath.clamp(time, 0, getAnimMaxTime());
- }
-
- /**
- * @return The length of the currently playing animation, or zero
- * if no animation is playing.
- *
- * @see AnimChannel#getTime()
- */
- public float getAnimMaxTime(){
- return animation != null ? animation.getLength() : 0f;
- }
-
- /**
- * Set the current animation that is played by this AnimChannel.
- *
- * This resets the time to zero, and optionally blends the animation
- * over blendTime seconds with the currently playing animation.
- * Notice that this method will reset the control's speed to 1.0.
- *
- * @param name The name of the animation to play
- * @param blendTime The blend time over which to blend the new animation
- * with the old one. If zero, then no blending will occur and the new
- * animation will be applied instantly.
- */
- public void setAnim(String name, float blendTime){
- if (name == null)
- throw new IllegalArgumentException("name cannot be null");
-
- if (blendTime < 0f)
- throw new IllegalArgumentException("blendTime cannot be less than zero");
-
- Animation anim = control.animationMap.get(name);
- if (anim == null)
- throw new IllegalArgumentException("Cannot find animation named: '"+name+"'");
-
- control.notifyAnimChange(this, name);
-
- if (animation != null && blendTime > 0f){
- this.blendTime = blendTime;
- // activate blending
- blendTime = Math.min(blendTime, anim.getLength() / speed);
- blendFrom = animation;
- timeBlendFrom = time;
- speedBlendFrom = speed;
- loopModeBlendFrom = loopMode;
- blendAmount = 0f;
- blendRate = 1f / blendTime;
- }else{
- blendFrom = null;
- }
-
- animation = anim;
- time = 0;
- speed = 1f;
- loopMode = LoopMode.Loop;
- notified = false;
- }
-
- /**
- * Set the current animation that is played by this AnimChannel.
- *
- * See {@link #setAnim(java.lang.String, float)}.
- * The blendTime argument by default is 150 milliseconds.
- *
- * @param name The name of the animation to play
- */
- public void setAnim(String name){
- setAnim(name, DEFAULT_BLEND_TIME);
- }
-
- /**
- * Add all the bones of the model's skeleton to be
- * influenced by this animation channel.
- */
- public void addAllBones() {
- affectedBones = null;
- }
-
- /**
- * Add a single bone to be influenced by this animation channel.
- */
- public void addBone(String name) {
- addBone(control.getSkeleton().getBone(name));
- }
-
- /**
- * Add a single bone to be influenced by this animation channel.
- */
- public void addBone(Bone bone) {
- int boneIndex = control.getSkeleton().getBoneIndex(bone);
- if(affectedBones == null) {
- affectedBones = new BitSet(control.getSkeleton().getBoneCount());
- }
- affectedBones.set(boneIndex);
- }
-
- /**
- * Add bones to be influenced by this animation channel starting from the
- * given bone name and going toward the root bone.
- */
- public void addToRootBone(String name) {
- addToRootBone(control.getSkeleton().getBone(name));
- }
-
- /**
- * Add bones to be influenced by this animation channel starting from the
- * given bone and going toward the root bone.
- */
- public void addToRootBone(Bone bone) {
- addBone(bone);
- while (bone.getParent() != null) {
- bone = bone.getParent();
- addBone(bone);
- }
- }
-
- /**
- * Add bones to be influenced by this animation channel, starting
- * from the given named bone and going toward its children.
- */
- public void addFromRootBone(String name) {
- addFromRootBone(control.getSkeleton().getBone(name));
- }
-
- /**
- * Add bones to be influenced by this animation channel, starting
- * from the given bone and going toward its children.
- */
- public void addFromRootBone(Bone bone) {
- addBone(bone);
- if (bone.getChildren() == null)
- return;
- for (Bone childBone : bone.getChildren()) {
- addBone(childBone);
- addFromRootBone(childBone);
- }
- }
-
- BitSet getAffectedBones(){
- return affectedBones;
- }
-
- public void reset(boolean rewind){
- if(rewind){
- setTime(0);
- if(control.getSkeleton()!=null){
- control.getSkeleton().resetAndUpdate();
- }else{
- TempVars vars = TempVars.get();
- update(0, vars);
- vars.release();
- }
- }
- animation = null;
- notified = false;
- }
-
- void update(float tpf, TempVars vars) {
- if (animation == null)
- return;
-
- if (blendFrom != null && blendAmount != 1.0f){
- // The blendFrom anim is set, the actual animation
- // playing will be set
-// blendFrom.setTime(timeBlendFrom, 1f, control, this, vars);
- blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
-
- timeBlendFrom += tpf * speedBlendFrom;
- timeBlendFrom = AnimationUtils.clampWrapTime(timeBlendFrom,
- blendFrom.getLength(),
- loopModeBlendFrom);
- if (timeBlendFrom < 0){
- timeBlendFrom = -timeBlendFrom;
- speedBlendFrom = -speedBlendFrom;
- }
-
- blendAmount += tpf * blendRate;
- if (blendAmount > 1f){
- blendAmount = 1f;
- blendFrom = null;
- }
- }
-
- animation.setTime(time, blendAmount, control, this, vars);
- time += tpf * speed;
- if (animation.getLength() > 0){
- if (!notified && (time >= animation.getLength() || time < 0)) {
- if (loopMode == LoopMode.DontLoop) {
- // Note that this flag has to be set before calling the notify
- // since the notify may start a new animation and then unset
- // the flag.
- notified = true;
- }
- control.notifyAnimCycleDone(this, animation.getName());
- }
- }
- time = AnimationUtils.clampWrapTime(time, animation.getLength(), loopMode);
- if (time < 0){
- // Negative time indicates that speed should be inverted
- // (for cycle loop mode only)
- time = -time;
- speed = -speed;
- }
- }
-}
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.math.FastMath;
+import com.jme3.util.TempVars;
+import java.util.BitSet;
+
+/**
+ * AnimChannel provides controls, such as play, pause,
+ * fast forward, etc, for an animation. The animation
+ * channel may influence the entire model or specific bones of the model's
+ * skeleton. A single model may have multiple animation channels influencing
+ * various parts of its body. For example, a character model may have an
+ * animation channel for its feet, and another for its torso, and
+ * the animations for each channel are controlled independently.
+ *
+ * @author Kirill Vainer
+ */
+public final class AnimChannel {
+
+ private static final float DEFAULT_BLEND_TIME = 0.15f;
+
+ private AnimControl control;
+
+ private BitSet affectedBones;
+
+ private Animation animation;
+ private Animation blendFrom;
+ private float time;
+ private float speed;
+ private float timeBlendFrom;
+ private float blendTime;
+ private float speedBlendFrom;
+ private boolean notified=false;
+
+ private LoopMode loopMode, loopModeBlendFrom;
+
+ private float blendAmount = 1f;
+ private float blendRate = 0;
+
+ public AnimChannel(){
+
+ }
+
+ public AnimChannel(AnimControl control){
+ this.control = control;
+ }
+
+ /**
+ * Returns the parent control of this AnimChannel.
+ *
+ * @return the parent control of this AnimChannel.
+ * @see AnimControl
+ */
+ public AnimControl getControl() {
+ return control;
+ }
+
+ /**
+ * @return The name of the currently playing animation, or null if
+ * none is assigned.
+ *
+ * @see AnimChannel#setAnim(java.lang.String)
+ */
+ public String getAnimationName() {
+ return animation != null ? animation.getName() : null;
+ }
+
+ /**
+ * @return The loop mode currently set for the animation. The loop mode
+ * determines what will happen to the animation once it finishes
+ * playing.
+ *
+ * For more information, see the LoopMode enum class.
+ * @see LoopMode
+ * @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode)
+ */
+ public LoopMode getLoopMode() {
+ return loopMode;
+ }
+
+ /**
+ * @param loopMode Set the loop mode for the channel. The loop mode
+ * determines what will happen to the animation once it finishes
+ * playing.
+ *
+ * For more information, see the LoopMode enum class.
+ * @see LoopMode
+ */
+ public void setLoopMode(LoopMode loopMode) {
+ this.loopMode = loopMode;
+ }
+
+ /**
+ * @return The speed that is assigned to the animation channel. The speed
+ * is a scale value starting from 0.0, at 1.0 the animation will play
+ * at its default speed.
+ *
+ * @see AnimChannel#setSpeed(float)
+ */
+ public float getSpeed() {
+ return speed;
+ }
+
+ /**
+ * @param speed Set the speed of the animation channel. The speed
+ * is a scale value starting from 0.0, at 1.0 the animation will play
+ * at its default speed.
+ */
+ public void setSpeed(float speed) {
+ this.speed = speed;
+ if(blendTime>0){
+ this.speedBlendFrom = speed;
+ blendTime = Math.min(blendTime, animation.getLength() / speed);
+ blendRate = 1/ blendTime;
+ }
+ }
+
+ /**
+ * @return The time of the currently playing animation. The time
+ * starts at 0 and continues on until getAnimMaxTime().
+ *
+ * @see AnimChannel#setTime(float)
+ */
+ public float getTime() {
+ return time;
+ }
+
+ /**
+ * @param time Set the time of the currently playing animation, the time
+ * is clamped from 0 to {@link #getAnimMaxTime()}.
+ */
+ public void setTime(float time) {
+ this.time = FastMath.clamp(time, 0, getAnimMaxTime());
+ }
+
+ /**
+ * @return The length of the currently playing animation, or zero
+ * if no animation is playing.
+ *
+ * @see AnimChannel#getTime()
+ */
+ public float getAnimMaxTime(){
+ return animation != null ? animation.getLength() : 0f;
+ }
+
+ /**
+ * Set the current animation that is played by this AnimChannel.
+ *
+ * This resets the time to zero, and optionally blends the animation
+ * over blendTime seconds with the currently playing animation.
+ * Notice that this method will reset the control's speed to 1.0.
+ *
+ * @param name The name of the animation to play
+ * @param blendTime The blend time over which to blend the new animation
+ * with the old one. If zero, then no blending will occur and the new
+ * animation will be applied instantly.
+ */
+ public void setAnim(String name, float blendTime){
+ if (name == null)
+ throw new IllegalArgumentException("name cannot be null");
+
+ if (blendTime < 0f)
+ throw new IllegalArgumentException("blendTime cannot be less than zero");
+
+ Animation anim = control.animationMap.get(name);
+ if (anim == null)
+ throw new IllegalArgumentException("Cannot find animation named: '"+name+"'");
+
+ control.notifyAnimChange(this, name);
+
+ if (animation != null && blendTime > 0f){
+ this.blendTime = blendTime;
+ // activate blending
+ blendTime = Math.min(blendTime, anim.getLength() / speed);
+ blendFrom = animation;
+ timeBlendFrom = time;
+ speedBlendFrom = speed;
+ loopModeBlendFrom = loopMode;
+ blendAmount = 0f;
+ blendRate = 1f / blendTime;
+ }else{
+ blendFrom = null;
+ }
+
+ animation = anim;
+ time = 0;
+ speed = 1f;
+ loopMode = LoopMode.Loop;
+ notified = false;
+ }
+
+ /**
+ * Set the current animation that is played by this AnimChannel.
+ *
+ * See {@link #setAnim(java.lang.String, float)}.
+ * The blendTime argument by default is 150 milliseconds.
+ *
+ * @param name The name of the animation to play
+ */
+ public void setAnim(String name){
+ setAnim(name, DEFAULT_BLEND_TIME);
+ }
+
+ /**
+ * Add all the bones of the model's skeleton to be
+ * influenced by this animation channel.
+ */
+ public void addAllBones() {
+ affectedBones = null;
+ }
+
+ /**
+ * Add a single bone to be influenced by this animation channel.
+ */
+ public void addBone(String name) {
+ addBone(control.getSkeleton().getBone(name));
+ }
+
+ /**
+ * Add a single bone to be influenced by this animation channel.
+ */
+ public void addBone(Bone bone) {
+ int boneIndex = control.getSkeleton().getBoneIndex(bone);
+ if(affectedBones == null) {
+ affectedBones = new BitSet(control.getSkeleton().getBoneCount());
+ }
+ affectedBones.set(boneIndex);
+ }
+
+ /**
+ * Add bones to be influenced by this animation channel starting from the
+ * given bone name and going toward the root bone.
+ */
+ public void addToRootBone(String name) {
+ addToRootBone(control.getSkeleton().getBone(name));
+ }
+
+ /**
+ * Add bones to be influenced by this animation channel starting from the
+ * given bone and going toward the root bone.
+ */
+ public void addToRootBone(Bone bone) {
+ addBone(bone);
+ while (bone.getParent() != null) {
+ bone = bone.getParent();
+ addBone(bone);
+ }
+ }
+
+ /**
+ * Add bones to be influenced by this animation channel, starting
+ * from the given named bone and going toward its children.
+ */
+ public void addFromRootBone(String name) {
+ addFromRootBone(control.getSkeleton().getBone(name));
+ }
+
+ /**
+ * Add bones to be influenced by this animation channel, starting
+ * from the given bone and going toward its children.
+ */
+ public void addFromRootBone(Bone bone) {
+ addBone(bone);
+ if (bone.getChildren() == null)
+ return;
+ for (Bone childBone : bone.getChildren()) {
+ addBone(childBone);
+ addFromRootBone(childBone);
+ }
+ }
+
+ BitSet getAffectedBones(){
+ return affectedBones;
+ }
+
+ public void reset(boolean rewind){
+ if(rewind){
+ setTime(0);
+ if(control.getSkeleton()!=null){
+ control.getSkeleton().resetAndUpdate();
+ }else{
+ TempVars vars = TempVars.get();
+ update(0, vars);
+ vars.release();
+ }
+ }
+ animation = null;
+ notified = false;
+ }
+
+ void update(float tpf, TempVars vars) {
+ if (animation == null)
+ return;
+
+ if (blendFrom != null && blendAmount != 1.0f){
+ // The blendFrom anim is set, the actual animation
+ // playing will be set
+// blendFrom.setTime(timeBlendFrom, 1f, control, this, vars);
+ blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
+
+ timeBlendFrom += tpf * speedBlendFrom;
+ timeBlendFrom = AnimationUtils.clampWrapTime(timeBlendFrom,
+ blendFrom.getLength(),
+ loopModeBlendFrom);
+ if (timeBlendFrom < 0){
+ timeBlendFrom = -timeBlendFrom;
+ speedBlendFrom = -speedBlendFrom;
+ }
+
+ blendAmount += tpf * blendRate;
+ if (blendAmount > 1f){
+ blendAmount = 1f;
+ blendFrom = null;
+ }
+ }
+
+ animation.setTime(time, blendAmount, control, this, vars);
+ time += tpf * speed;
+ if (animation.getLength() > 0){
+ if (!notified && (time >= animation.getLength() || time < 0)) {
+ if (loopMode == LoopMode.DontLoop) {
+ // Note that this flag has to be set before calling the notify
+ // since the notify may start a new animation and then unset
+ // the flag.
+ notified = true;
+ }
+ control.notifyAnimCycleDone(this, animation.getName());
+ }
+ }
+ time = AnimationUtils.clampWrapTime(time, animation.getLength(), loopMode);
+ if (time < 0){
+ // Negative time indicates that speed should be inverted
+ // (for cycle loop mode only)
+ time = -time;
+ speed = -speed;
+ }
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java b/jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java
index 9272fe5e3..9cbe5d7dd 100644
--- a/jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java
+++ b/jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java
@@ -41,7 +41,9 @@ import static com.jme3.animation.LoopMode.Loop;
*/
public class AnimationUtils {
-
+ public AnimationUtils(){
+
+ }
/**
* Clamps the time according to duration and loopMode
* @param time
diff --git a/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java
index 6e1432f4a..8a9e99b65 100644
--- a/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java
+++ b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java
@@ -1,720 +1,706 @@
-/*
- * Copyright (c) 2009-2012 jMonkeyEngine
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.jme3.cinematic;
-
-import com.jme3.animation.LoopMode;
-import com.jme3.app.Application;
-import com.jme3.app.state.AppState;
-import com.jme3.app.state.AppStateManager;
-import com.jme3.cinematic.events.AbstractCinematicEvent;
-import com.jme3.cinematic.events.CinematicEvent;
-import com.jme3.export.*;
-import com.jme3.renderer.Camera;
-import com.jme3.renderer.RenderManager;
-import com.jme3.scene.CameraNode;
-import com.jme3.scene.Node;
-import com.jme3.scene.control.CameraControl;
-import com.jme3.scene.control.CameraControl.ControlDirection;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * An appstate for composing and playing cut scenes in a game. The cinematic
- * schedules CinematicEvents over a timeline. Once the Cinematic created it has
- * to be attached 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 methods 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
- */
-public class Cinematic extends AbstractCinematicEvent implements AppState {
-
- private static final Logger logger = Logger.getLogger(Application.class.getName());
- private Node scene;
- protected TimeLine timeLine = new TimeLine();
- private int lastFetchedKeyFrame = -1;
- private List cinematicEvents = new ArrayList();
- private Map cameras = new HashMap();
- private CameraNode currentCam;
- private boolean initialized = false;
- 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()) {
- if (playState == PlayState.Paused) {
- for (int i = 0; i < cinematicEvents.size(); i++) {
- CinematicEvent ce = cinematicEvents.get(i);
- if (ce.getPlayState() == PlayState.Paused) {
- ce.play();
- }
- }
- }
- }
- }
-
- /**
- * called internally
- */
- @Override
- public void onStop() {
- time = 0;
- lastFetchedKeyFrame = -1;
- for (int i = 0; i < cinematicEvents.size(); i++) {
- CinematicEvent ce = cinematicEvents.get(i);
- ce.setTime(0);
- ce.forceStop();
- }
- setEnableCurrentCam(false);
- }
-
- /**
- * called internally
- */
- @Override
- public void onPause() {
- for (int i = 0; i < cinematicEvents.size(); i++) {
- CinematicEvent ce = cinematicEvents.get(i);
- if (ce.getPlayState() == PlayState.Playing) {
- ce.pause();
- }
- }
- }
-
- /**
- * used internally for serialization
- *
- * @param ex
- * @throws IOException
- */
- @Override
- public void write(JmeExporter ex) throws IOException {
- super.write(ex);
- OutputCapsule oc = ex.getCapsule(this);
-
- oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null);
- oc.writeStringSavableMap(cameras, "cameras", null);
- oc.write(timeLine, "timeLine", null);
-
- }
-
- /**
- * used internally for serialization
- *
- * @param im
- * @throws IOException
- */
- @Override
- public void read(JmeImporter im) throws IOException {
- super.read(im);
- InputCapsule ic = im.getCapsule(this);
-
- cinematicEvents = ic.readSavableArrayList("cinematicEvents", null);
- cameras = (Map) ic.readStringSavableMap("cameras", 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
- public void setSpeed(float speed) {
- super.setSpeed(speed);
- for (int i = 0; i < cinematicEvents.size(); i++) {
- CinematicEvent ce = cinematicEvents.get(i);
- ce.setSpeed(speed);
- }
-
-
- }
-
- /**
- * 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) {
- cinematicEvent.initEvent(app, this);
- }
-
- 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 cinematic 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) {
- int keyFrameIndex = timeLine.getKeyFrameIndexFromTime(time);
-
- //iterate to make sure every key frame is triggered
- for (int i = lastFetchedKeyFrame + 1; i <= keyFrameIndex; i++) {
- KeyFrame keyFrame = timeLine.get(i);
- if (keyFrame != null) {
- keyFrame.trigger();
- }
- }
-
-
- for (int i = 0; i < cinematicEvents.size(); i++) {
- CinematicEvent ce = cinematicEvents.get(i);
- ce.internalUpdate(tpf);
- }
-
-
- lastFetchedKeyFrame = keyFrameIndex;
- }
-
- /**
- * This is used internally but can be called to shuffle through the
- * cinematic.
- *
- * @param time the time to shuffle to.
- */
- @Override
- public void setTime(float time) {
-
- //stopping all events
- onStop();
- super.setTime(time);
-
- int keyFrameIndex = timeLine.getKeyFrameIndexFromTime(time);
- //triggering all the event from start to "time"
- //then computing timeOffset for each event
- for (int i = 0; i <= keyFrameIndex; i++) {
- KeyFrame keyFrame = timeLine.get(i);
- if (keyFrame != null) {
- for (CinematicEvent ce : keyFrame.getCinematicEvents()) {
- float t = this.time - timeLine.getKeyFrameTime(keyFrame);
- if (t >= 0 && (t <= ce.getInitialDuration() || ce.getLoopMode() != LoopMode.DontLoop)) {
- ce.play();
- }
- ce.setTime(t);
- }
- }
- }
- lastFetchedKeyFrame = keyFrameIndex;
- if (playState != PlayState.Playing) {
- pause();
- }
- }
-
- /**
- * Adds a cinematic event to this cinematic at the given timestamp. This
- * operation returns a keyFrame
- *
- * @param timeStamp the time when the event will start after the beginning of
- * the cinematic
- * @param cinematicEvent the cinematic event
- * @return the keyFrame for that event.
- */
- public KeyFrame addCinematicEvent(float timeStamp, CinematicEvent cinematicEvent) {
- KeyFrame keyFrame = timeLine.getKeyFrameAtTime(timeStamp);
- if (keyFrame == null) {
- keyFrame = new KeyFrame();
- timeLine.addKeyFrameAtTime(timeStamp, keyFrame);
- }
- keyFrame.cinematicEvents.add(cinematicEvent);
- 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 event 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.
- *
- * @param cinematicEvent the cinematicEvent to remove
- * @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)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * 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
- *
- * @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());
- }
- return ret;
- }
-
- /**
- * called internally
- *
- * @see AppState#render(com.jme3.renderer.RenderManager)
- */
- public void render(RenderManager rm) {
- }
-
- /**
- * called internally
- *
- * @see AppState#postRender()
- */
- public void postRender() {
- }
-
- /**
- * called internally
- *
- * @see AppState#cleanup()
- */
- public void cleanup() {
- }
-
- /**
- * fits the duration of the cinematic to the duration of all its child
- * cinematic events
- */
- public void fitDuration() {
- KeyFrame kf = timeLine.getKeyFrameAtIndex(timeLine.getLastKeyFrameIndex());
- float d = 0;
- for (int i = 0; i < kf.getCinematicEvents().size(); i++) {
- CinematicEvent ce = kf.getCinematicEvents().get(i);
- 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);
- cameras.put(cameraName, node);
- scene.attachChild(node);
- return node;
- }
-
- /**
- * returns a cameraNode given its name
- *
- * @param cameraName the camera name (as registered in
- * Cinematic#bindCamera())
- * @return the cameraNode for this name
- */
- public CameraNode getCamera(String cameraName) {
- return cameras.get(cameraName);
- }
-
- /**
- * 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 registered in
- * Cinematic#bindCamera())
- */
- public void setActiveCamera(String cameraName) {
- 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);
- }
- 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 registered in
- * Cinematic#bindCamera())
- */
- public void activateCamera(final float timeStamp, final String cameraName) {
- addCinematicEvent(timeStamp, new AbstractCinematicEvent() {
- @Override
- public void play() {
- super.play();
- stop();
- }
-
- @Override
- public void onPlay() {
- setActiveCamera(cameraName);
- }
-
- @Override
- public void onUpdate(float tpf) {
- }
-
- @Override
- public void onStop() {
- }
-
- @Override
- public void onPause() {
- }
-
- @Override
- public void forceStop() {
- }
-
- @Override
- public void setTime(float time) {
- play();
- }
- });
- }
-
- /**
- * returns the complete eventdata map
- *
- * @return the eventdata map
- */
- private Map> getEventsData() {
- if (eventsData == null) {
- eventsData = new HashMap>();
- }
- return eventsData;
- }
-
- /**
- * 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