1/******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17package com.badlogic.gdx.graphics.g3d.utils; 18 19import com.badlogic.gdx.graphics.g3d.ModelInstance; 20import com.badlogic.gdx.graphics.g3d.model.Animation; 21import com.badlogic.gdx.graphics.g3d.model.Node; 22import com.badlogic.gdx.math.MathUtils; 23import com.badlogic.gdx.utils.GdxRuntimeException; 24import com.badlogic.gdx.utils.Pool; 25 26/** Class to control one or more {@link Animation}s on a {@link ModelInstance}. Use the 27 * {@link #setAnimation(String, int, float, AnimationListener)} method to change the current animation. Use the 28 * {@link #animate(String, int, float, AnimationListener, float)} method to start an animation, optionally blending onto the 29 * current animation. Use the {@link #queue(String, int, float, AnimationListener, float)} method to queue an animation to be 30 * played when the current animation is finished. Use the {@link #action(String, int, float, AnimationListener, float)} method to 31 * play a (short) animation on top of the current animation. 32 * 33 * You can use multiple AnimationControllers on the same ModelInstance, as long as they don't interfere with each other (don't 34 * affect the same {@link Node}s). 35 * 36 * @author Xoppa */ 37public class AnimationController extends BaseAnimationController { 38 39 /** Listener that will be informed when an animation is looped or completed. 40 * @author Xoppa */ 41 public interface AnimationListener { 42 /** Gets called when an animation is completed. 43 * @param animation The animation which just completed. */ 44 void onEnd (final AnimationDesc animation); 45 46 /** Gets called when an animation is looped. The {@link AnimationDesc#loopCount} is updated prior to this call and can be 47 * read or written to alter the number of remaining loops. 48 * @param animation The animation which just looped. */ 49 void onLoop (final AnimationDesc animation); 50 } 51 52 /** Class describing how to play and {@link Animation}. You can read the values within this class to get the progress of the 53 * animation. Do not change the values. Only valid when the animation is currently played. 54 * @author Xoppa */ 55 public static class AnimationDesc { 56 /** Listener which will be informed when the animation is looped or ended. */ 57 public AnimationListener listener; 58 /** The animation to be applied. */ 59 public Animation animation; 60 /** The speed at which to play the animation (can be negative), 1.0 for normal speed. */ 61 public float speed; 62 /** The current animation time. */ 63 public float time; 64 /** The offset within the animation (animation time = offsetTime + time) */ 65 public float offset; 66 /** The duration of the animation */ 67 public float duration; 68 /** The number of remaining loops, negative for continuous, zero if stopped. */ 69 public int loopCount; 70 71 protected AnimationDesc () { 72 } 73 74 /** @return the remaining time or 0 if still animating. */ 75 protected float update (float delta) { 76 if (loopCount != 0 && animation != null) { 77 int loops; 78 final float diff = speed * delta; 79 if (!MathUtils.isZero(duration)) { 80 time += diff; 81 loops = (int)Math.abs(time / duration); 82 if (time < 0f) { 83 loops++; 84 while (time < 0f) 85 time += duration; 86 } 87 time = Math.abs(time % duration); 88 } else 89 loops = 1; 90 for (int i = 0; i < loops; i++) { 91 if (loopCount > 0) loopCount--; 92 if (loopCount != 0 && listener != null) listener.onLoop(this); 93 if (loopCount == 0) { 94 final float result = ((loops - 1) - i) * duration + (diff < 0f ? duration - time : time); 95 time = (diff < 0f) ? 0f : duration; 96 if (listener != null) listener.onEnd(this); 97 return result; 98 } 99 } 100 return 0f; 101 } else 102 return delta; 103 } 104 } 105 106 protected final Pool<AnimationDesc> animationPool = new Pool<AnimationDesc>() { 107 @Override 108 protected AnimationDesc newObject () { 109 return new AnimationDesc(); 110 } 111 }; 112 113 /** The animation currently playing. Do not alter this value. */ 114 public AnimationDesc current; 115 /** The animation queued to be played when the {@link #current} animation is completed. Do not alter this value. */ 116 public AnimationDesc queued; 117 /** The transition time which should be applied to the queued animation. Do not alter this value. */ 118 public float queuedTransitionTime; 119 /** The animation which previously played. Do not alter this value. */ 120 public AnimationDesc previous; 121 /** The current transition time. Do not alter this value. */ 122 public float transitionCurrentTime; 123 /** The target transition time. Do not alter this value. */ 124 public float transitionTargetTime; 125 /** Whether an action is being performed. Do not alter this value. */ 126 public boolean inAction; 127 /** When true a call to {@link #update(float)} will not be processed. */ 128 public boolean paused; 129 /** Whether to allow the same animation to be played while playing that animation. */ 130 public boolean allowSameAnimation; 131 132 private boolean justChangedAnimation = false; 133 134 /** Construct a new AnimationController. 135 * @param target The {@link ModelInstance} on which the animations will be performed. */ 136 public AnimationController (final ModelInstance target) { 137 super(target); 138 } 139 140 private AnimationDesc obtain (final Animation anim, float offset, float duration, int loopCount, float speed, 141 final AnimationListener listener) { 142 if (anim == null) return null; 143 final AnimationDesc result = animationPool.obtain(); 144 result.animation = anim; 145 result.listener = listener; 146 result.loopCount = loopCount; 147 result.speed = speed; 148 result.offset = offset; 149 result.duration = duration < 0 ? (anim.duration - offset) : duration; 150 result.time = speed < 0 ? result.duration : 0.f; 151 return result; 152 } 153 154 private AnimationDesc obtain (final String id, float offset, float duration, int loopCount, float speed, 155 final AnimationListener listener) { 156 if (id == null) return null; 157 final Animation anim = target.getAnimation(id); 158 if (anim == null) throw new GdxRuntimeException("Unknown animation: " + id); 159 return obtain(anim, offset, duration, loopCount, speed, listener); 160 } 161 162 private AnimationDesc obtain (final AnimationDesc anim) { 163 return obtain(anim.animation, anim.offset, anim.duration, anim.loopCount, anim.speed, anim.listener); 164 } 165 166 /** Update any animations currently being played. 167 * @param delta The time elapsed since last update, change this to alter the overall speed (can be negative). */ 168 public void update (float delta) { 169 if (paused) return; 170 if (previous != null && ((transitionCurrentTime += delta) >= transitionTargetTime)) { 171 removeAnimation(previous.animation); 172 justChangedAnimation = true; 173 animationPool.free(previous); 174 previous = null; 175 } 176 if (justChangedAnimation) { 177 target.calculateTransforms(); 178 justChangedAnimation = false; 179 } 180 if (current == null || current.loopCount == 0 || current.animation == null) return; 181 final float remain = current.update(delta); 182 if (remain != 0f && queued != null) { 183 inAction = false; 184 animate(queued, queuedTransitionTime); 185 queued = null; 186 update(remain); 187 return; 188 } 189 if (previous != null) 190 applyAnimations(previous.animation, previous.offset + previous.time, current.animation, current.offset + current.time, 191 transitionCurrentTime / transitionTargetTime); 192 else 193 applyAnimation(current.animation, current.offset + current.time); 194 } 195 196 /** Set the active animation, replacing any current animation. 197 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 198 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 199 * is completed. */ 200 public AnimationDesc setAnimation (final String id) { 201 return setAnimation(id, 1, 1.0f, null); 202 } 203 204 /** Set the active animation, replacing any current animation. 205 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 206 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 207 * loop the animation. 208 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 209 * is completed. */ 210 public AnimationDesc setAnimation (final String id, int loopCount) { 211 return setAnimation(id, loopCount, 1.0f, null); 212 } 213 214 /** Set the active animation, replacing any current animation. 215 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 216 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 217 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 218 * is completed. */ 219 public AnimationDesc setAnimation (final String id, final AnimationListener listener) { 220 return setAnimation(id, 1, 1.0f, listener); 221 } 222 223 /** Set the active animation, replacing any current animation. 224 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 225 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 226 * loop the animation. 227 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 228 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 229 * is completed. */ 230 public AnimationDesc setAnimation (final String id, int loopCount, final AnimationListener listener) { 231 return setAnimation(id, loopCount, 1.0f, listener); 232 } 233 234 /** Set the active animation, replacing any current animation. 235 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 236 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 237 * loop the animation. 238 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at 239 * twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be 240 * negative, causing the animation to played in reverse. This value cannot be zero. 241 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 242 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 243 * is completed. */ 244 public AnimationDesc setAnimation (final String id, int loopCount, float speed, final AnimationListener listener) { 245 return setAnimation(id, 0f, -1f, loopCount, speed, listener); 246 } 247 248 /** Set the active animation, replacing any current animation. 249 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 250 * @param offset The offset in seconds to the start of the animation. 251 * @param duration The duration in seconds of the animation (or negative to play till the end of the animation). 252 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 253 * loop the animation. 254 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at 255 * twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be 256 * negative, causing the animation to played in reverse. This value cannot be zero. 257 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 258 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 259 * is completed. */ 260 public AnimationDesc setAnimation (final String id, float offset, float duration, int loopCount, float speed, 261 final AnimationListener listener) { 262 return setAnimation(obtain(id, offset, duration, loopCount, speed, listener)); 263 } 264 265 /** Set the active animation, replacing any current animation. */ 266 protected AnimationDesc setAnimation (final Animation anim, float offset, float duration, int loopCount, float speed, 267 final AnimationListener listener) { 268 return setAnimation(obtain(anim, offset, duration, loopCount, speed, listener)); 269 } 270 271 /** Set the active animation, replacing any current animation. */ 272 protected AnimationDesc setAnimation (final AnimationDesc anim) { 273 if (current == null) 274 current = anim; 275 else { 276 if (!allowSameAnimation && anim != null && current.animation == anim.animation) 277 anim.time = current.time; 278 else 279 removeAnimation(current.animation); 280 animationPool.free(current); 281 current = anim; 282 } 283 justChangedAnimation = true; 284 return anim; 285 } 286 287 /** Changes the current animation by blending the new on top of the old during the transition time. 288 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 289 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 290 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 291 * is completed. */ 292 public AnimationDesc animate (final String id, float transitionTime) { 293 return animate(id, 1, 1.0f, null, transitionTime); 294 } 295 296 /** Changes the current animation by blending the new on top of the old during the transition time. 297 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 298 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 299 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 300 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 301 * is completed. */ 302 public AnimationDesc animate (final String id, final AnimationListener listener, float transitionTime) { 303 return animate(id, 1, 1.0f, listener, transitionTime); 304 } 305 306 /** Changes the current animation by blending the new on top of the old during the transition time. 307 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 308 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 309 * loop the animation. 310 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 311 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 312 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 313 * is completed. */ 314 public AnimationDesc animate (final String id, int loopCount, final AnimationListener listener, float transitionTime) { 315 return animate(id, loopCount, 1.0f, listener, transitionTime); 316 } 317 318 /** Changes the current animation by blending the new on top of the old during the transition time. 319 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 320 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 321 * loop the animation. 322 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at 323 * twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be 324 * negative, causing the animation to played in reverse. This value cannot be zero. 325 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 326 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 327 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 328 * is completed. */ 329 public AnimationDesc animate (final String id, int loopCount, float speed, final AnimationListener listener, 330 float transitionTime) { 331 return animate(id, 0f, -1f, loopCount, speed, listener, transitionTime); 332 } 333 334 /** Changes the current animation by blending the new on top of the old during the transition time. 335 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 336 * @param offset The offset in seconds to the start of the animation. 337 * @param duration The duration in seconds of the animation (or negative to play till the end of the animation). 338 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 339 * loop the animation. 340 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at 341 * twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be 342 * negative, causing the animation to played in reverse. This value cannot be zero. 343 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 344 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 345 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 346 * is completed. */ 347 public AnimationDesc animate (final String id, float offset, float duration, int loopCount, float speed, 348 final AnimationListener listener, float transitionTime) { 349 return animate(obtain(id, offset, duration, loopCount, speed, listener), transitionTime); 350 } 351 352 /** Changes the current animation by blending the new on top of the old during the transition time. */ 353 protected AnimationDesc animate (final Animation anim, float offset, float duration, int loopCount, float speed, 354 final AnimationListener listener, float transitionTime) { 355 return animate(obtain(anim, offset, duration, loopCount, speed, listener), transitionTime); 356 } 357 358 /** Changes the current animation by blending the new on top of the old during the transition time. */ 359 protected AnimationDesc animate (final AnimationDesc anim, float transitionTime) { 360 if (current == null) 361 current = anim; 362 else if (inAction) 363 queue(anim, transitionTime); 364 else if (!allowSameAnimation && anim != null && current.animation == anim.animation) { 365 anim.time = current.time; 366 animationPool.free(current); 367 current = anim; 368 } else { 369 if (previous != null) { 370 removeAnimation(previous.animation); 371 animationPool.free(previous); 372 } 373 previous = current; 374 current = anim; 375 transitionCurrentTime = 0f; 376 transitionTargetTime = transitionTime; 377 } 378 return anim; 379 } 380 381 /** Queue an animation to be applied when the {@link #current} animation is finished. If the current animation is continuously 382 * looping it will be synchronized on next loop. 383 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 384 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 385 * loop the animation. 386 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at 387 * twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be 388 * negative, causing the animation to played in reverse. This value cannot be zero. 389 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 390 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 391 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 392 * is completed. */ 393 public AnimationDesc queue (final String id, int loopCount, float speed, final AnimationListener listener, float transitionTime) { 394 return queue(id, 0f, -1f, loopCount, speed, listener, transitionTime); 395 } 396 397 /** Queue an animation to be applied when the {@link #current} animation is finished. If the current animation is continuously 398 * looping it will be synchronized on next loop. 399 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 400 * @param offset The offset in seconds to the start of the animation. 401 * @param duration The duration in seconds of the animation (or negative to play till the end of the animation). 402 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 403 * loop the animation. 404 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at 405 * twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be 406 * negative, causing the animation to played in reverse. This value cannot be zero. 407 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 408 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 409 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 410 * is completed. */ 411 public AnimationDesc queue (final String id, float offset, float duration, int loopCount, float speed, 412 final AnimationListener listener, float transitionTime) { 413 return queue(obtain(id, offset, duration, loopCount, speed, listener), transitionTime); 414 } 415 416 /** Queue an animation to be applied when the current is finished. If current is continuous it will be synced on next loop. */ 417 protected AnimationDesc queue (final Animation anim, float offset, float duration, int loopCount, float speed, 418 final AnimationListener listener, float transitionTime) { 419 return queue(obtain(anim, offset, duration, loopCount, speed, listener), transitionTime); 420 } 421 422 /** Queue an animation to be applied when the current is finished. If current is continuous it will be synced on next loop. */ 423 protected AnimationDesc queue (final AnimationDesc anim, float transitionTime) { 424 if (current == null || current.loopCount == 0) 425 animate(anim, transitionTime); 426 else { 427 if (queued != null) animationPool.free(queued); 428 queued = anim; 429 queuedTransitionTime = transitionTime; 430 if (current.loopCount < 0) current.loopCount = 1; 431 } 432 return anim; 433 } 434 435 /** Apply an action animation on top of the current animation. 436 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 437 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 438 * loop the animation. 439 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at 440 * twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be 441 * negative, causing the animation to played in reverse. This value cannot be zero. 442 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 443 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 444 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 445 * is completed. */ 446 public AnimationDesc action (final String id, int loopCount, float speed, final AnimationListener listener, 447 float transitionTime) { 448 return action(id, 0, -1f, loopCount, speed, listener, transitionTime); 449 } 450 451 /** Apply an action animation on top of the current animation. 452 * @param id The ID of the {@link Animation} within the {@link ModelInstance}. 453 * @param offset The offset in seconds to the start of the animation. 454 * @param duration The duration in seconds of the animation (or negative to play till the end of the animation). 455 * @param loopCount The number of times to loop the animation, zero to play the animation only once, negative to continuously 456 * loop the animation. 457 * @param speed The speed at which the animation should be played. Default is 1.0f. A value of 2.0f will play the animation at 458 * twice the normal speed, a value of 0.5f will play the animation at half the normal speed, etc. This value can be 459 * negative, causing the animation to played in reverse. This value cannot be zero. 460 * @param listener The {@link AnimationListener} which will be informed when the animation is looped or completed. 461 * @param transitionTime The time to transition the new animation on top of the currently playing animation (if any). 462 * @return The {@link AnimationDesc} which can be read to get the progress of the animation. Will be invalid when the animation 463 * is completed. */ 464 public AnimationDesc action (final String id, float offset, float duration, int loopCount, float speed, 465 final AnimationListener listener, float transitionTime) { 466 return action(obtain(id, offset, duration, loopCount, speed, listener), transitionTime); 467 } 468 469 /** Apply an action animation on top of the current animation. */ 470 protected AnimationDesc action (final Animation anim, float offset, float duration, int loopCount, float speed, 471 final AnimationListener listener, float transitionTime) { 472 return action(obtain(anim, offset, duration, loopCount, speed, listener), transitionTime); 473 } 474 475 /** Apply an action animation on top of the current animation. */ 476 protected AnimationDesc action (final AnimationDesc anim, float transitionTime) { 477 if (anim.loopCount < 0) throw new GdxRuntimeException("An action cannot be continuous"); 478 if (current == null || current.loopCount == 0) 479 animate(anim, transitionTime); 480 else { 481 AnimationDesc toQueue = inAction ? null : obtain(current); 482 inAction = false; 483 animate(anim, transitionTime); 484 inAction = true; 485 if (toQueue != null) queue(toQueue, transitionTime); 486 } 487 return anim; 488 } 489} 490