1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.animation; 34 35 import com.jme3.math.FastMath; 36 import com.jme3.util.TempVars; 37 import java.util.BitSet; 38 39 /** 40 * <code>AnimChannel</code> provides controls, such as play, pause, 41 * fast forward, etc, for an animation. The animation 42 * channel may influence the entire model or specific bones of the model's 43 * skeleton. A single model may have multiple animation channels influencing 44 * various parts of its body. For example, a character model may have an 45 * animation channel for its feet, and another for its torso, and 46 * the animations for each channel are controlled independently. 47 * 48 * @author Kirill Vainer 49 */ 50 public final class AnimChannel { 51 52 private static final float DEFAULT_BLEND_TIME = 0.15f; 53 54 private AnimControl control; 55 56 private BitSet affectedBones; 57 58 private Animation animation; 59 private Animation blendFrom; 60 private float time; 61 private float speed; 62 private float timeBlendFrom; 63 private float speedBlendFrom; 64 65 private LoopMode loopMode, loopModeBlendFrom; 66 67 private float blendAmount = 1f; 68 private float blendRate = 0; 69 70 private static float clampWrapTime(float t, float max, LoopMode loopMode){ 71 if (max == Float.POSITIVE_INFINITY) 72 return t; 73 74 if (t < 0f){ 75 //float tMod = -(-t % max); 76 switch (loopMode){ 77 case DontLoop: 78 return 0; 79 case Cycle: 80 return t; 81 case Loop: 82 return max - t; 83 } 84 }else if (t > max){ 85 switch (loopMode){ 86 case DontLoop: 87 return max; 88 case Cycle: 89 return /*-max;*/-(2f * max - t); 90 case Loop: 91 return t - max; 92 } 93 } 94 95 return t; 96 } 97 98 AnimChannel(AnimControl control){ 99 this.control = control; 100 } 101 102 /** 103 * Returns the parent control of this AnimChannel. 104 * 105 * @return the parent control of this AnimChannel. 106 * @see AnimControl 107 */ 108 public AnimControl getControl() { 109 return control; 110 } 111 112 /** 113 * @return The name of the currently playing animation, or null if 114 * none is assigned. 115 * 116 * @see AnimChannel#setAnim(java.lang.String) 117 */ 118 public String getAnimationName() { 119 return animation != null ? animation.getName() : null; 120 } 121 122 /** 123 * @return The loop mode currently set for the animation. The loop mode 124 * determines what will happen to the animation once it finishes 125 * playing. 126 * 127 * For more information, see the LoopMode enum class. 128 * @see LoopMode 129 * @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode) 130 */ 131 public LoopMode getLoopMode() { 132 return loopMode; 133 } 134 135 /** 136 * @param loopMode Set the loop mode for the channel. The loop mode 137 * determines what will happen to the animation once it finishes 138 * playing. 139 * 140 * For more information, see the LoopMode enum class. 141 * @see LoopMode 142 */ 143 public void setLoopMode(LoopMode loopMode) { 144 this.loopMode = loopMode; 145 } 146 147 /** 148 * @return The speed that is assigned to the animation channel. The speed 149 * is a scale value starting from 0.0, at 1.0 the animation will play 150 * at its default speed. 151 * 152 * @see AnimChannel#setSpeed(float) 153 */ 154 public float getSpeed() { 155 return speed; 156 } 157 158 /** 159 * @param speed Set the speed of the animation channel. The speed 160 * is a scale value starting from 0.0, at 1.0 the animation will play 161 * at its default speed. 162 */ 163 public void setSpeed(float speed) { 164 this.speed = speed; 165 } 166 167 /** 168 * @return The time of the currently playing animation. The time 169 * starts at 0 and continues on until getAnimMaxTime(). 170 * 171 * @see AnimChannel#setTime(float) 172 */ 173 public float getTime() { 174 return time; 175 } 176 177 /** 178 * @param time Set the time of the currently playing animation, the time 179 * is clamped from 0 to {@link #getAnimMaxTime()}. 180 */ 181 public void setTime(float time) { 182 this.time = FastMath.clamp(time, 0, getAnimMaxTime()); 183 } 184 185 /** 186 * @return The length of the currently playing animation, or zero 187 * if no animation is playing. 188 * 189 * @see AnimChannel#getTime() 190 */ 191 public float getAnimMaxTime(){ 192 return animation != null ? animation.getLength() : 0f; 193 } 194 195 /** 196 * Set the current animation that is played by this AnimChannel. 197 * <p> 198 * This resets the time to zero, and optionally blends the animation 199 * over <code>blendTime</code> seconds with the currently playing animation. 200 * Notice that this method will reset the control's speed to 1.0. 201 * 202 * @param name The name of the animation to play 203 * @param blendTime The blend time over which to blend the new animation 204 * with the old one. If zero, then no blending will occur and the new 205 * animation will be applied instantly. 206 */ 207 public void setAnim(String name, float blendTime){ 208 if (name == null) 209 throw new NullPointerException(); 210 211 if (blendTime < 0f) 212 throw new IllegalArgumentException("blendTime cannot be less than zero"); 213 214 Animation anim = control.animationMap.get(name); 215 if (anim == null) 216 throw new IllegalArgumentException("Cannot find animation named: '"+name+"'"); 217 218 control.notifyAnimChange(this, name); 219 220 if (animation != null && blendTime > 0f){ 221 // activate blending 222 blendFrom = animation; 223 timeBlendFrom = time; 224 speedBlendFrom = speed; 225 loopModeBlendFrom = loopMode; 226 blendAmount = 0f; 227 blendRate = 1f / blendTime; 228 } 229 230 animation = anim; 231 time = 0; 232 speed = 1f; 233 loopMode = LoopMode.Loop; 234 } 235 236 /** 237 * Set the current animation that is played by this AnimChannel. 238 * <p> 239 * See {@link #setAnim(java.lang.String, float)}. 240 * The blendTime argument by default is 150 milliseconds. 241 * 242 * @param name The name of the animation to play 243 */ 244 public void setAnim(String name){ 245 setAnim(name, DEFAULT_BLEND_TIME); 246 } 247 248 /** 249 * Add all the bones of the model's skeleton to be 250 * influenced by this animation channel. 251 */ 252 public void addAllBones() { 253 affectedBones = null; 254 } 255 256 /** 257 * Add a single bone to be influenced by this animation channel. 258 */ 259 public void addBone(String name) { 260 addBone(control.getSkeleton().getBone(name)); 261 } 262 263 /** 264 * Add a single bone to be influenced by this animation channel. 265 */ 266 public void addBone(Bone bone) { 267 int boneIndex = control.getSkeleton().getBoneIndex(bone); 268 if(affectedBones == null) { 269 affectedBones = new BitSet(control.getSkeleton().getBoneCount()); 270 } 271 affectedBones.set(boneIndex); 272 } 273 274 /** 275 * Add bones to be influenced by this animation channel starting from the 276 * given bone name and going toward the root bone. 277 */ 278 public void addToRootBone(String name) { 279 addToRootBone(control.getSkeleton().getBone(name)); 280 } 281 282 /** 283 * Add bones to be influenced by this animation channel starting from the 284 * given bone and going toward the root bone. 285 */ 286 public void addToRootBone(Bone bone) { 287 addBone(bone); 288 while (bone.getParent() != null) { 289 bone = bone.getParent(); 290 addBone(bone); 291 } 292 } 293 294 /** 295 * Add bones to be influenced by this animation channel, starting 296 * from the given named bone and going toward its children. 297 */ 298 public void addFromRootBone(String name) { 299 addFromRootBone(control.getSkeleton().getBone(name)); 300 } 301 302 /** 303 * Add bones to be influenced by this animation channel, starting 304 * from the given bone and going toward its children. 305 */ 306 public void addFromRootBone(Bone bone) { 307 addBone(bone); 308 if (bone.getChildren() == null) 309 return; 310 for (Bone childBone : bone.getChildren()) { 311 addBone(childBone); 312 addFromRootBone(childBone); 313 } 314 } 315 316 BitSet getAffectedBones(){ 317 return affectedBones; 318 } 319 320 void update(float tpf, TempVars vars) { 321 if (animation == null) 322 return; 323 324 if (blendFrom != null){ 325 blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars); 326 //blendFrom.setTime(timeBlendFrom, control.skeleton, 1f - blendAmount, affectedBones); 327 timeBlendFrom += tpf * speedBlendFrom; 328 timeBlendFrom = clampWrapTime(timeBlendFrom, 329 blendFrom.getLength(), 330 loopModeBlendFrom); 331 if (timeBlendFrom < 0){ 332 timeBlendFrom = -timeBlendFrom; 333 speedBlendFrom = -speedBlendFrom; 334 } 335 336 blendAmount += tpf * blendRate; 337 if (blendAmount > 1f){ 338 blendAmount = 1f; 339 blendFrom = null; 340 } 341 } 342 343 animation.setTime(time, blendAmount, control, this, vars); 344 //animation.setTime(time, control.skeleton, blendAmount, affectedBones); 345 time += tpf * speed; 346 347 if (animation.getLength() > 0){ 348 if (time >= animation.getLength()) { 349 control.notifyAnimCycleDone(this, animation.getName()); 350 } else if (time < 0) { 351 control.notifyAnimCycleDone(this, animation.getName()); 352 } 353 } 354 355 time = clampWrapTime(time, animation.getLength(), loopMode); 356 if (time < 0){ 357 time = -time; 358 speed = -speed; 359 } 360 361 362 } 363 } 364