/* * 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; /** * <code>AnimChannel</code> 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. * <p> * This resets the time to zero, and optionally blends the animation * over <code>blendTime</code> 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. * <p> * 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; } } }