• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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