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