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