• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics.g3d.model.NodeAnimation;
23 import com.badlogic.gdx.graphics.g3d.model.NodeKeyframe;
24 import com.badlogic.gdx.math.Matrix4;
25 import com.badlogic.gdx.math.Quaternion;
26 import com.badlogic.gdx.math.Vector3;
27 import com.badlogic.gdx.utils.Array;
28 import com.badlogic.gdx.utils.GdxRuntimeException;
29 import com.badlogic.gdx.utils.ObjectMap;
30 import com.badlogic.gdx.utils.ObjectMap.Entry;
31 import com.badlogic.gdx.utils.Pool;
32 import com.badlogic.gdx.utils.Pool.Poolable;
33 
34 /** Base class for applying one or more {@link Animation}s to a {@link ModelInstance}. This class only applies the actual
35  * {@link Node} transformations, it does not manage animations or keep track of animation states. See {@link AnimationController}
36  * for an implementation of this class which does manage animations.
37  *
38  * @author Xoppa */
39 public class BaseAnimationController {
40 	public final static class Transform implements Poolable {
41 		public final Vector3 translation = new Vector3();
42 		public final Quaternion rotation = new Quaternion();
43 		public final Vector3 scale = new Vector3(1, 1, 1);
44 
Transform()45 		public Transform () {
46 		}
47 
idt()48 		public Transform idt () {
49 			translation.set(0, 0, 0);
50 			rotation.idt();
51 			scale.set(1, 1, 1);
52 			return this;
53 		}
54 
set(final Vector3 t, final Quaternion r, final Vector3 s)55 		public Transform set (final Vector3 t, final Quaternion r, final Vector3 s) {
56 			translation.set(t);
57 			rotation.set(r);
58 			scale.set(s);
59 			return this;
60 		}
61 
set(final Transform other)62 		public Transform set (final Transform other) {
63 			return set(other.translation, other.rotation, other.scale);
64 		}
65 
lerp(final Transform target, final float alpha)66 		public Transform lerp (final Transform target, final float alpha) {
67 			return lerp(target.translation, target.rotation, target.scale, alpha);
68 		}
69 
lerp(final Vector3 targetT, final Quaternion targetR, final Vector3 targetS, final float alpha)70 		public Transform lerp (final Vector3 targetT, final Quaternion targetR, final Vector3 targetS, final float alpha) {
71 			translation.lerp(targetT, alpha);
72 			rotation.slerp(targetR, alpha);
73 			scale.lerp(targetS, alpha);
74 			return this;
75 		}
76 
toMatrix4(final Matrix4 out)77 		public Matrix4 toMatrix4 (final Matrix4 out) {
78 			return out.set(translation, rotation, scale);
79 		}
80 
81 		@Override
reset()82 		public void reset () {
83 			idt();
84 		}
85 
86 		@Override
toString()87 		public String toString () {
88 			return translation.toString() + " - " + rotation.toString() + " - " + scale.toString();
89 		}
90 	}
91 
92 	private final Pool<Transform> transformPool = new Pool<Transform>() {
93 		@Override
94 		protected Transform newObject () {
95 			return new Transform();
96 		}
97 	};
98 	private final static ObjectMap<Node, Transform> transforms = new ObjectMap<Node, Transform>();
99 	private boolean applying = false;
100 	/** The {@link ModelInstance} on which the animations are being performed. */
101 	public final ModelInstance target;
102 
103 	/** Construct a new BaseAnimationController.
104 	 * @param target The {@link ModelInstance} on which the animations are being performed. */
BaseAnimationController(final ModelInstance target)105 	public BaseAnimationController (final ModelInstance target) {
106 		this.target = target;
107 	}
108 
109 	/** Begin applying multiple animations to the instance, must followed by one or more calls to {
110 	 * {@link #apply(Animation, float, float)} and finally {{@link #end()}. */
begin()111 	protected void begin () {
112 		if (applying) throw new GdxRuntimeException("You must call end() after each call to being()");
113 		applying = true;
114 	}
115 
116 	/** Apply an animation, must be called between {{@link #begin()} and {{@link #end()}.
117 	 * @param weight The blend weight of this animation relative to the previous applied animations. */
apply(final Animation animation, final float time, final float weight)118 	protected void apply (final Animation animation, final float time, final float weight) {
119 		if (!applying) throw new GdxRuntimeException("You must call begin() before adding an animation");
120 		applyAnimation(transforms, transformPool, weight, animation, time);
121 	}
122 
123 	/** End applying multiple animations to the instance and update it to reflect the changes. */
end()124 	protected void end () {
125 		if (!applying) throw new GdxRuntimeException("You must call begin() first");
126 		for (Entry<Node, Transform> entry : transforms.entries()) {
127 			entry.value.toMatrix4(entry.key.localTransform);
128 			transformPool.free(entry.value);
129 		}
130 		transforms.clear();
131 		target.calculateTransforms();
132 		applying = false;
133 	}
134 
135 	/** Apply a single animation to the {@link ModelInstance} and update the it to reflect the changes. */
applyAnimation(final Animation animation, final float time)136 	protected void applyAnimation (final Animation animation, final float time) {
137 		if (applying) throw new GdxRuntimeException("Call end() first");
138 		applyAnimation(null, null, 1.f, animation, time);
139 		target.calculateTransforms();
140 	}
141 
142 	/** Apply two animations, blending the second onto to first using weight. */
applyAnimations(final Animation anim1, final float time1, final Animation anim2, final float time2, final float weight)143 	protected void applyAnimations (final Animation anim1, final float time1, final Animation anim2, final float time2,
144 		final float weight) {
145 		if (anim2 == null || weight == 0.f)
146 			applyAnimation(anim1, time1);
147 		else if (anim1 == null || weight == 1.f)
148 			applyAnimation(anim2, time2);
149 		else if (applying)
150 			throw new GdxRuntimeException("Call end() first");
151 		else {
152 			begin();
153 			apply(anim1, time1, 1.f);
154 			apply(anim2, time2, weight);
155 			end();
156 		}
157 	}
158 
159 	private final static Transform tmpT = new Transform();
160 
getFirstKeyframeIndexAtTime(final Array<NodeKeyframe<T>> arr, final float time)161 	private final static <T> int getFirstKeyframeIndexAtTime (final Array<NodeKeyframe<T>> arr, final float time) {
162 		final int n = arr.size - 1;
163 		for (int i = 0; i < n; i++) {
164 			if (time >= arr.get(i).keytime && time <= arr.get(i + 1).keytime) {
165 				return i;
166 			}
167 		}
168 		return 0;
169 	}
170 
getTranslationAtTime(final NodeAnimation nodeAnim, final float time, final Vector3 out)171 	private final static Vector3 getTranslationAtTime (final NodeAnimation nodeAnim, final float time, final Vector3 out) {
172 		if (nodeAnim.translation == null) return out.set(nodeAnim.node.translation);
173 		if (nodeAnim.translation.size == 1) return out.set(nodeAnim.translation.get(0).value);
174 
175 		int index = getFirstKeyframeIndexAtTime(nodeAnim.translation, time);
176 		final NodeKeyframe firstKeyframe = nodeAnim.translation.get(index);
177 		out.set((Vector3)firstKeyframe.value);
178 
179 		if (++index < nodeAnim.translation.size) {
180 			final NodeKeyframe<Vector3> secondKeyframe = nodeAnim.translation.get(index);
181 			final float t = (time - firstKeyframe.keytime) / (secondKeyframe.keytime - firstKeyframe.keytime);
182 			out.lerp(secondKeyframe.value, t);
183 		}
184 		return out;
185 	}
186 
getRotationAtTime(final NodeAnimation nodeAnim, final float time, final Quaternion out)187 	private final static Quaternion getRotationAtTime (final NodeAnimation nodeAnim, final float time, final Quaternion out) {
188 		if (nodeAnim.rotation == null) return out.set(nodeAnim.node.rotation);
189 		if (nodeAnim.rotation.size == 1) return out.set(nodeAnim.rotation.get(0).value);
190 
191 		int index = getFirstKeyframeIndexAtTime(nodeAnim.rotation, time);
192 		final NodeKeyframe firstKeyframe = nodeAnim.rotation.get(index);
193 		out.set((Quaternion)firstKeyframe.value);
194 
195 		if (++index < nodeAnim.rotation.size) {
196 			final NodeKeyframe<Quaternion> secondKeyframe = nodeAnim.rotation.get(index);
197 			final float t = (time - firstKeyframe.keytime) / (secondKeyframe.keytime - firstKeyframe.keytime);
198 			out.slerp(secondKeyframe.value, t);
199 		}
200 		return out;
201 	}
202 
getScalingAtTime(final NodeAnimation nodeAnim, final float time, final Vector3 out)203 	private final static Vector3 getScalingAtTime (final NodeAnimation nodeAnim, final float time, final Vector3 out) {
204 		if (nodeAnim.scaling == null) return out.set(nodeAnim.node.scale);
205 		if (nodeAnim.scaling.size == 1) return out.set(nodeAnim.scaling.get(0).value);
206 
207 		int index = getFirstKeyframeIndexAtTime(nodeAnim.scaling, time);
208 		final NodeKeyframe firstKeyframe = nodeAnim.scaling.get(index);
209 		out.set((Vector3)firstKeyframe.value);
210 
211 		if (++index < nodeAnim.scaling.size) {
212 			final NodeKeyframe<Vector3> secondKeyframe = nodeAnim.scaling.get(index);
213 			final float t = (time - firstKeyframe.keytime) / (secondKeyframe.keytime - firstKeyframe.keytime);
214 			out.lerp(secondKeyframe.value, t);
215 		}
216 		return out;
217 	}
218 
getNodeAnimationTransform(final NodeAnimation nodeAnim, final float time)219 	private final static Transform getNodeAnimationTransform (final NodeAnimation nodeAnim, final float time) {
220 		final Transform transform = tmpT;
221 		getTranslationAtTime(nodeAnim, time, transform.translation);
222 		getRotationAtTime(nodeAnim, time, transform.rotation);
223 		getScalingAtTime(nodeAnim, time, transform.scale);
224 		return transform;
225 	}
226 
applyNodeAnimationDirectly(final NodeAnimation nodeAnim, final float time)227 	private final static void applyNodeAnimationDirectly (final NodeAnimation nodeAnim, final float time) {
228 		final Node node = nodeAnim.node;
229 		node.isAnimated = true;
230 		final Transform transform = getNodeAnimationTransform(nodeAnim, time);
231 		transform.toMatrix4(node.localTransform);
232 	}
233 
applyNodeAnimationBlending(final NodeAnimation nodeAnim, final ObjectMap<Node, Transform> out, final Pool<Transform> pool, final float alpha, final float time)234 	private final static void applyNodeAnimationBlending (final NodeAnimation nodeAnim, final ObjectMap<Node, Transform> out,
235 		final Pool<Transform> pool, final float alpha, final float time) {
236 
237 		final Node node = nodeAnim.node;
238 		node.isAnimated = true;
239 		final Transform transform = getNodeAnimationTransform(nodeAnim, time);
240 
241 		Transform t = out.get(node, null);
242 		if (t != null) {
243 			if (alpha > 0.999999f)
244 				t.set(transform);
245 			else
246 				t.lerp(transform, alpha);
247 		} else {
248 			if (alpha > 0.999999f)
249 				out.put(node, pool.obtain().set(transform));
250 			else
251 				out.put(node, pool.obtain().set(node.translation, node.rotation, node.scale).lerp(transform, alpha));
252 		}
253 	}
254 
255 	/** Helper method to apply one animation to either an objectmap for blending or directly to the bones. */
applyAnimation(final ObjectMap<Node, Transform> out, final Pool<Transform> pool, final float alpha, final Animation animation, final float time)256 	protected static void applyAnimation (final ObjectMap<Node, Transform> out, final Pool<Transform> pool, final float alpha,
257 		final Animation animation, final float time) {
258 
259 		if (out == null) {
260 			for (final NodeAnimation nodeAnim : animation.nodeAnimations)
261 				applyNodeAnimationDirectly(nodeAnim, time);
262 		} else {
263 			for (final Node node : out.keys())
264 				node.isAnimated = false;
265 			for (final NodeAnimation nodeAnim : animation.nodeAnimations)
266 				applyNodeAnimationBlending(nodeAnim, out, pool, alpha, time);
267 			for (final ObjectMap.Entry<Node, Transform> e : out.entries()) {
268 				if (!e.key.isAnimated) {
269 					e.key.isAnimated = true;
270 					e.value.lerp(e.key.translation, e.key.rotation, e.key.scale, alpha);
271 				}
272 			}
273 		}
274 	}
275 
276 	/** Remove the specified animation, by marking the affected nodes as not animated. When switching animation, this should be call
277 	 * prior to applyAnimation(s). */
removeAnimation(final Animation animation)278 	protected void removeAnimation (final Animation animation) {
279 		for (final NodeAnimation nodeAnim : animation.nodeAnimations) {
280 			nodeAnim.node.isAnimated = false;
281 		}
282 	}
283 }
284