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 */
32package com.jme3.animation;
33
34import com.jme3.math.FastMath;
35import com.jme3.math.Quaternion;
36import com.jme3.math.Transform;
37import 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 */
55public 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