1 /* 2 * Copyright (c) 2009-2011 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.jme3.animation; 33 34 import com.jme3.math.FastMath; 35 import com.jme3.math.Quaternion; 36 import com.jme3.math.Transform; 37 import com.jme3.math.Vector3f; 38 39 /** 40 * A convenience class to easily setup a spatial keyframed animation 41 * you can add some keyFrames for a given time or a given keyFrameIndex, for translation rotation and scale. 42 * The animationHelper will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames. 43 * <br><br> 44 * Usage is : <br> 45 * - Create the AnimationHelper<br> 46 * - add some keyFrames<br> 47 * - call the buildAnimation() method that will retruna new Animation<br> 48 * - add the generated Animation to any existing AnimationControl<br> 49 * <br><br> 50 * Note that the first keyFrame (index 0) is defaulted with the identy transforms. 51 * If you want to change that you have to replace this keyFrame with any transform you want. 52 * 53 * @author Nehon 54 */ 55 public class AnimationFactory { 56 57 /** 58 * step for splitting rotation that have a n ange above PI/2 59 */ 60 private final static float EULER_STEP = FastMath.QUARTER_PI * 3; 61 62 /** 63 * enum to determine the type of interpolation 64 */ 65 private enum Type { 66 67 Translation, Rotation, Scale; 68 } 69 70 /** 71 * Inner Rotation type class to kep track on a rotation Euler angle 72 */ 73 protected class Rotation { 74 75 /** 76 * The rotation Quaternion 77 */ 78 Quaternion rotation = new Quaternion(); 79 /** 80 * This rotation expressed in Euler angles 81 */ 82 Vector3f eulerAngles = new Vector3f(); 83 /** 84 * the index of the parent key frame is this keyFrame is a splitted rotation 85 */ 86 int masterKeyFrame = -1; 87 Rotation()88 public Rotation() { 89 rotation.loadIdentity(); 90 } 91 set(Quaternion rot)92 void set(Quaternion rot) { 93 rotation.set(rot); 94 float[] a = new float[3]; 95 rotation.toAngles(a); 96 eulerAngles.set(a[0], a[1], a[2]); 97 } 98 set(float x, float y, float z)99 void set(float x, float y, float z) { 100 float[] a = {x, y, z}; 101 rotation.fromAngles(a); 102 eulerAngles.set(x, y, z); 103 } 104 } 105 /** 106 * Name of the animation 107 */ 108 protected String name; 109 /** 110 * frames per seconds 111 */ 112 protected int fps; 113 /** 114 * Animation duration in seconds 115 */ 116 protected float duration; 117 /** 118 * total number of frames 119 */ 120 protected int totalFrames; 121 /** 122 * time per frame 123 */ 124 protected float tpf; 125 /** 126 * Time array for this animation 127 */ 128 protected float[] times; 129 /** 130 * Translation array for this animation 131 */ 132 protected Vector3f[] translations; 133 /** 134 * rotation array for this animation 135 */ 136 protected Quaternion[] rotations; 137 /** 138 * scales array for this animation 139 */ 140 protected Vector3f[] scales; 141 /** 142 * The map of keyFrames to compute the animation. The key is the index of the frame 143 */ 144 protected Vector3f[] keyFramesTranslation; 145 protected Vector3f[] keyFramesScale; 146 protected Rotation[] keyFramesRotation; 147 148 /** 149 * Creates and AnimationHelper 150 * @param duration the desired duration for the resulting animation 151 * @param name the name of the resulting animation 152 */ AnimationFactory(float duration, String name)153 public AnimationFactory(float duration, String name) { 154 this(duration, name, 30); 155 } 156 157 /** 158 * Creates and AnimationHelper 159 * @param duration the desired duration for the resulting animation 160 * @param name the name of the resulting animation 161 * @param fps the number of frames per second for this animation (default is 30) 162 */ AnimationFactory(float duration, String name, int fps)163 public AnimationFactory(float duration, String name, int fps) { 164 this.name = name; 165 this.duration = duration; 166 this.fps = fps; 167 totalFrames = (int) (fps * duration) + 1; 168 tpf = 1 / (float) fps; 169 times = new float[totalFrames]; 170 translations = new Vector3f[totalFrames]; 171 rotations = new Quaternion[totalFrames]; 172 scales = new Vector3f[totalFrames]; 173 keyFramesTranslation = new Vector3f[totalFrames]; 174 keyFramesTranslation[0] = new Vector3f(); 175 keyFramesScale = new Vector3f[totalFrames]; 176 keyFramesScale[0] = new Vector3f(1, 1, 1); 177 keyFramesRotation = new Rotation[totalFrames]; 178 keyFramesRotation[0] = new Rotation(); 179 180 } 181 182 /** 183 * Adds a key frame for the given Transform at the given time 184 * @param time the time at which the keyFrame must be inserted 185 * @param transform the transforms to use for this keyFrame 186 */ addTimeTransform(float time, Transform transform)187 public void addTimeTransform(float time, Transform transform) { 188 addKeyFrameTransform((int) (time / tpf), transform); 189 } 190 191 /** 192 * Adds a key frame for the given Transform at the given keyFrame index 193 * @param keyFrameIndex the index at which the keyFrame must be inserted 194 * @param transform the transforms to use for this keyFrame 195 */ addKeyFrameTransform(int keyFrameIndex, Transform transform)196 public void addKeyFrameTransform(int keyFrameIndex, Transform transform) { 197 addKeyFrameTranslation(keyFrameIndex, transform.getTranslation()); 198 addKeyFrameScale(keyFrameIndex, transform.getScale()); 199 addKeyFrameRotation(keyFrameIndex, transform.getRotation()); 200 } 201 202 /** 203 * Adds a key frame for the given translation at the given time 204 * @param time the time at which the keyFrame must be inserted 205 * @param translation the translation to use for this keyFrame 206 */ addTimeTranslation(float time, Vector3f translation)207 public void addTimeTranslation(float time, Vector3f translation) { 208 addKeyFrameTranslation((int) (time / tpf), translation); 209 } 210 211 /** 212 * Adds a key frame for the given translation at the given keyFrame index 213 * @param keyFrameIndex the index at which the keyFrame must be inserted 214 * @param translation the translation to use for this keyFrame 215 */ addKeyFrameTranslation(int keyFrameIndex, Vector3f translation)216 public void addKeyFrameTranslation(int keyFrameIndex, Vector3f translation) { 217 Vector3f t = getTranslationForFrame(keyFrameIndex); 218 t.set(translation); 219 } 220 221 /** 222 * Adds a key frame for the given rotation at the given time<br> 223 * This can't be used if the interpolated angle is higher than PI (180°)<br> 224 * Use {@link addTimeRotationAngles(float time, float x, float y, float z)} instead that uses Euler angles rotations.<br> * 225 * @param time the time at which the keyFrame must be inserted 226 * @param rotation the rotation Quaternion to use for this keyFrame 227 * @see #addTimeRotationAngles(float time, float x, float y, float z) 228 */ addTimeRotation(float time, Quaternion rotation)229 public void addTimeRotation(float time, Quaternion rotation) { 230 addKeyFrameRotation((int) (time / tpf), rotation); 231 } 232 233 /** 234 * Adds a key frame for the given rotation at the given keyFrame index<br> 235 * This can't be used if the interpolated angle is higher than PI (180°)<br> 236 * Use {@link addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angles rotations. 237 * @param keyFrameIndex the index at which the keyFrame must be inserted 238 * @param rotation the rotation Quaternion to use for this keyFrame 239 * @see #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) 240 */ addKeyFrameRotation(int keyFrameIndex, Quaternion rotation)241 public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) { 242 Rotation r = getRotationForFrame(keyFrameIndex); 243 r.set(rotation); 244 } 245 246 /** 247 * Adds a key frame for the given rotation at the given time.<br> 248 * Rotation is expressed by Euler angles values in radians.<br> 249 * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br> 250 * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br> 251 * 252 * @param time the time at which the keyFrame must be inserted 253 * @param x the rotation around the x axis (aka yaw) in radians 254 * @param y the rotation around the y axis (aka roll) in radians 255 * @param z the rotation around the z axis (aka pitch) in radians 256 */ addTimeRotationAngles(float time, float x, float y, float z)257 public void addTimeRotationAngles(float time, float x, float y, float z) { 258 addKeyFrameRotationAngles((int) (time / tpf), x, y, z); 259 } 260 261 /** 262 * Adds a key frame for the given rotation at the given key frame index.<br> 263 * Rotation is expressed by Euler angles values in radians.<br> 264 * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br> 265 * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br> 266 * 267 * @param keyFrameIndex the index at which the keyFrame must be inserted 268 * @param x the rotation around the x axis (aka yaw) in radians 269 * @param y the rotation around the y axis (aka roll) in radians 270 * @param z the rotation around the z axis (aka pitch) in radians 271 */ addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)272 public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) { 273 Rotation r = getRotationForFrame(keyFrameIndex); 274 r.set(x, y, z); 275 276 // if the delta of euler angles is higher than PI, we create intermediate keyframes 277 // since we are using quaternions and slerp for rotation interpolation, we cannot interpolate over an angle higher than PI 278 int prev = getPreviousKeyFrame(keyFrameIndex, keyFramesRotation); 279 //previous rotation keyframe 280 Rotation prevRot = keyFramesRotation[prev]; 281 //the maximum delta angle (x,y or z) 282 float delta = Math.max(Math.abs(x - prevRot.eulerAngles.x), Math.abs(y - prevRot.eulerAngles.y)); 283 delta = Math.max(delta, Math.abs(z - prevRot.eulerAngles.z)); 284 //if delta > PI we have to create intermediates key frames 285 if (delta >= FastMath.PI) { 286 //frames delta 287 int dF = keyFrameIndex - prev; 288 //angle per frame for x,y ,z 289 float dXAngle = (x - prevRot.eulerAngles.x) / (float) dF; 290 float dYAngle = (y - prevRot.eulerAngles.y) / (float) dF; 291 float dZAngle = (z - prevRot.eulerAngles.z) / (float) dF; 292 293 // the keyFrame step 294 int keyStep = (int) (((float) (dF)) / delta * (float) EULER_STEP); 295 // the current keyFrame 296 int cursor = prev + keyStep; 297 while (cursor < keyFrameIndex) { 298 //for each step we create a new rotation by interpolating the angles 299 Rotation dr = getRotationForFrame(cursor); 300 dr.masterKeyFrame = keyFrameIndex; 301 dr.set(prevRot.eulerAngles.x + cursor * dXAngle, prevRot.eulerAngles.y + cursor * dYAngle, prevRot.eulerAngles.z + cursor * dZAngle); 302 cursor += keyStep; 303 } 304 305 } 306 307 } 308 309 /** 310 * Adds a key frame for the given scale at the given time 311 * @param time the time at which the keyFrame must be inserted 312 * @param scale the scale to use for this keyFrame 313 */ addTimeScale(float time, Vector3f scale)314 public void addTimeScale(float time, Vector3f scale) { 315 addKeyFrameScale((int) (time / tpf), scale); 316 } 317 318 /** 319 * Adds a key frame for the given scale at the given keyFrame index 320 * @param keyFrameIndex the index at which the keyFrame must be inserted 321 * @param scale the scale to use for this keyFrame 322 */ addKeyFrameScale(int keyFrameIndex, Vector3f scale)323 public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) { 324 Vector3f s = getScaleForFrame(keyFrameIndex); 325 s.set(scale); 326 } 327 328 /** 329 * returns the translation for a given frame index 330 * creates the translation if it doesn't exists 331 * @param keyFrameIndex index 332 * @return the translation 333 */ getTranslationForFrame(int keyFrameIndex)334 private Vector3f getTranslationForFrame(int keyFrameIndex) { 335 if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { 336 throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); 337 } 338 Vector3f v = keyFramesTranslation[keyFrameIndex]; 339 if (v == null) { 340 v = new Vector3f(); 341 keyFramesTranslation[keyFrameIndex] = v; 342 } 343 return v; 344 } 345 346 /** 347 * returns the scale for a given frame index 348 * creates the scale if it doesn't exists 349 * @param keyFrameIndex index 350 * @return the scale 351 */ getScaleForFrame(int keyFrameIndex)352 private Vector3f getScaleForFrame(int keyFrameIndex) { 353 if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { 354 throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); 355 } 356 Vector3f v = keyFramesScale[keyFrameIndex]; 357 if (v == null) { 358 v = new Vector3f(); 359 keyFramesScale[keyFrameIndex] = v; 360 } 361 return v; 362 } 363 364 /** 365 * returns the rotation for a given frame index 366 * creates the rotation if it doesn't exists 367 * @param keyFrameIndex index 368 * @return the rotation 369 */ getRotationForFrame(int keyFrameIndex)370 private Rotation getRotationForFrame(int keyFrameIndex) { 371 if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { 372 throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); 373 } 374 Rotation v = keyFramesRotation[keyFrameIndex]; 375 if (v == null) { 376 v = new Rotation(); 377 keyFramesRotation[keyFrameIndex] = v; 378 } 379 return v; 380 } 381 382 /** 383 * Creates an Animation based on the keyFrames previously added to the helper. 384 * @return the generated animation 385 */ buildAnimation()386 public Animation buildAnimation() { 387 interpolateTime(); 388 interpolate(keyFramesTranslation, Type.Translation); 389 interpolate(keyFramesRotation, Type.Rotation); 390 interpolate(keyFramesScale, Type.Scale); 391 392 SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales); 393 394 //creating the animation 395 Animation spatialAnimation = new Animation(name, duration); 396 spatialAnimation.setTracks(new SpatialTrack[]{spatialTrack}); 397 398 return spatialAnimation; 399 } 400 401 /** 402 * interpolates time values 403 */ interpolateTime()404 private void interpolateTime() { 405 for (int i = 0; i < totalFrames; i++) { 406 times[i] = i * tpf; 407 } 408 } 409 410 /** 411 * Interpolates over the key frames for the given keyFrame array and the given type of transform 412 * @param keyFrames the keyFrames array 413 * @param type the type of transforms 414 */ interpolate(Object[] keyFrames, Type type)415 private void interpolate(Object[] keyFrames, Type type) { 416 int i = 0; 417 while (i < totalFrames) { 418 //fetching the next keyFrame index transform in the array 419 int key = getNextKeyFrame(i, keyFrames); 420 if (key != -1) { 421 //computing the frame span to interpolate over 422 int span = key - i; 423 //interating over the frames 424 for (int j = i; j <= key; j++) { 425 // computing interpolation value 426 float val = (float) (j - i) / (float) span; 427 //interpolationg depending on the transform type 428 switch (type) { 429 case Translation: 430 translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); 431 break; 432 case Rotation: 433 Quaternion rot = new Quaternion(); 434 rotations[j] = rot.slerp(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation, val); 435 break; 436 case Scale: 437 scales[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); 438 break; 439 } 440 } 441 //jumping to the next keyFrame 442 i = key; 443 } else { 444 //No more key frame, filling the array witht he last transform computed. 445 for (int j = i; j < totalFrames; j++) { 446 447 switch (type) { 448 case Translation: 449 translations[j] = ((Vector3f) keyFrames[i]).clone(); 450 break; 451 case Rotation: 452 rotations[j] = ((Quaternion) ((Rotation) keyFrames[i]).rotation).clone(); 453 break; 454 case Scale: 455 scales[j] = ((Vector3f) keyFrames[i]).clone(); 456 break; 457 } 458 } 459 //we're done 460 i = totalFrames; 461 } 462 } 463 } 464 465 /** 466 * Get the index of the next keyFrame that as a transform 467 * @param index the start index 468 * @param keyFrames the keyFrames array 469 * @return the index of the next keyFrame 470 */ getNextKeyFrame(int index, Object[] keyFrames)471 private int getNextKeyFrame(int index, Object[] keyFrames) { 472 for (int i = index + 1; i < totalFrames; i++) { 473 if (keyFrames[i] != null) { 474 return i; 475 } 476 } 477 return -1; 478 } 479 480 /** 481 * Get the index of the previous keyFrame that as a transform 482 * @param index the start index 483 * @param keyFrames the keyFrames array 484 * @return the index of the previous keyFrame 485 */ getPreviousKeyFrame(int index, Object[] keyFrames)486 private int getPreviousKeyFrame(int index, Object[] keyFrames) { 487 for (int i = index - 1; i >= 0; i--) { 488 if (keyFrames[i] != null) { 489 return i; 490 } 491 } 492 return -1; 493 } 494 } 495