Home | History | Annotate | Download | only in animation
      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 
     88         public Rotation() {
     89             rotation.loadIdentity();
     90         }
     91 
     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 
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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