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 17 package com.badlogic.gdx.graphics.g3d.utils; 18 19 import com.badlogic.gdx.graphics.g3d.ModelInstance; 20 import com.badlogic.gdx.graphics.g3d.model.Animation; 21 import com.badlogic.gdx.graphics.g3d.model.Node; 22 import com.badlogic.gdx.math.MathUtils; 23 import com.badlogic.gdx.utils.GdxRuntimeException; 24 import 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 */ 37 public 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. */ onEnd(final AnimationDesc animation)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. */ onLoop(final AnimationDesc animation)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 AnimationDesc()71 protected AnimationDesc () { 72 } 73 74 /** @return the remaining time or 0 if still animating. */ update(float delta)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. */ AnimationController(final ModelInstance target)136 public AnimationController (final ModelInstance target) { 137 super(target); 138 } 139 obtain(final Animation anim, float offset, float duration, int loopCount, float speed, final AnimationListener listener)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