diff options
Diffstat (limited to 'engine/src/core/com/jme3/animation/AnimationFactory.java')
-rw-r--r-- | engine/src/core/com/jme3/animation/AnimationFactory.java | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/animation/AnimationFactory.java b/engine/src/core/com/jme3/animation/AnimationFactory.java new file mode 100644 index 0000000..b994ccb --- /dev/null +++ b/engine/src/core/com/jme3/animation/AnimationFactory.java @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2009-2011 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.animation; + +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; + +/** + * A convenience class to easily setup a spatial keyframed animation + * you can add some keyFrames for a given time or a given keyFrameIndex, for translation rotation and scale. + * The animationHelper will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames. + * <br><br> + * Usage is : <br> + * - Create the AnimationHelper<br> + * - add some keyFrames<br> + * - call the buildAnimation() method that will retruna new Animation<br> + * - add the generated Animation to any existing AnimationControl<br> + * <br><br> + * Note that the first keyFrame (index 0) is defaulted with the identy transforms. + * If you want to change that you have to replace this keyFrame with any transform you want. + * + * @author Nehon + */ +public class AnimationFactory { + + /** + * step for splitting rotation that have a n ange above PI/2 + */ + private final static float EULER_STEP = FastMath.QUARTER_PI * 3; + + /** + * enum to determine the type of interpolation + */ + private enum Type { + + Translation, Rotation, Scale; + } + + /** + * Inner Rotation type class to kep track on a rotation Euler angle + */ + protected class Rotation { + + /** + * The rotation Quaternion + */ + Quaternion rotation = new Quaternion(); + /** + * This rotation expressed in Euler angles + */ + Vector3f eulerAngles = new Vector3f(); + /** + * the index of the parent key frame is this keyFrame is a splitted rotation + */ + int masterKeyFrame = -1; + + public Rotation() { + rotation.loadIdentity(); + } + + void set(Quaternion rot) { + rotation.set(rot); + float[] a = new float[3]; + rotation.toAngles(a); + eulerAngles.set(a[0], a[1], a[2]); + } + + void set(float x, float y, float z) { + float[] a = {x, y, z}; + rotation.fromAngles(a); + eulerAngles.set(x, y, z); + } + } + /** + * Name of the animation + */ + protected String name; + /** + * frames per seconds + */ + protected int fps; + /** + * Animation duration in seconds + */ + protected float duration; + /** + * total number of frames + */ + protected int totalFrames; + /** + * time per frame + */ + protected float tpf; + /** + * Time array for this animation + */ + protected float[] times; + /** + * Translation array for this animation + */ + protected Vector3f[] translations; + /** + * rotation array for this animation + */ + protected Quaternion[] rotations; + /** + * scales array for this animation + */ + protected Vector3f[] scales; + /** + * The map of keyFrames to compute the animation. The key is the index of the frame + */ + protected Vector3f[] keyFramesTranslation; + protected Vector3f[] keyFramesScale; + protected Rotation[] keyFramesRotation; + + /** + * Creates and AnimationHelper + * @param duration the desired duration for the resulting animation + * @param name the name of the resulting animation + */ + public AnimationFactory(float duration, String name) { + this(duration, name, 30); + } + + /** + * Creates and AnimationHelper + * @param duration the desired duration for the resulting animation + * @param name the name of the resulting animation + * @param fps the number of frames per second for this animation (default is 30) + */ + public AnimationFactory(float duration, String name, int fps) { + this.name = name; + this.duration = duration; + this.fps = fps; + totalFrames = (int) (fps * duration) + 1; + tpf = 1 / (float) fps; + times = new float[totalFrames]; + translations = new Vector3f[totalFrames]; + rotations = new Quaternion[totalFrames]; + scales = new Vector3f[totalFrames]; + keyFramesTranslation = new Vector3f[totalFrames]; + keyFramesTranslation[0] = new Vector3f(); + keyFramesScale = new Vector3f[totalFrames]; + keyFramesScale[0] = new Vector3f(1, 1, 1); + keyFramesRotation = new Rotation[totalFrames]; + keyFramesRotation[0] = new Rotation(); + + } + + /** + * Adds a key frame for the given Transform at the given time + * @param time the time at which the keyFrame must be inserted + * @param transform the transforms to use for this keyFrame + */ + public void addTimeTransform(float time, Transform transform) { + addKeyFrameTransform((int) (time / tpf), transform); + } + + /** + * Adds a key frame for the given Transform at the given keyFrame index + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param transform the transforms to use for this keyFrame + */ + public void addKeyFrameTransform(int keyFrameIndex, Transform transform) { + addKeyFrameTranslation(keyFrameIndex, transform.getTranslation()); + addKeyFrameScale(keyFrameIndex, transform.getScale()); + addKeyFrameRotation(keyFrameIndex, transform.getRotation()); + } + + /** + * Adds a key frame for the given translation at the given time + * @param time the time at which the keyFrame must be inserted + * @param translation the translation to use for this keyFrame + */ + public void addTimeTranslation(float time, Vector3f translation) { + addKeyFrameTranslation((int) (time / tpf), translation); + } + + /** + * Adds a key frame for the given translation at the given keyFrame index + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param translation the translation to use for this keyFrame + */ + public void addKeyFrameTranslation(int keyFrameIndex, Vector3f translation) { + Vector3f t = getTranslationForFrame(keyFrameIndex); + t.set(translation); + } + + /** + * Adds a key frame for the given rotation at the given time<br> + * This can't be used if the interpolated angle is higher than PI (180°)<br> + * Use {@link addTimeRotationAngles(float time, float x, float y, float z)} instead that uses Euler angles rotations.<br> * + * @param time the time at which the keyFrame must be inserted + * @param rotation the rotation Quaternion to use for this keyFrame + * @see #addTimeRotationAngles(float time, float x, float y, float z) + */ + public void addTimeRotation(float time, Quaternion rotation) { + addKeyFrameRotation((int) (time / tpf), rotation); + } + + /** + * Adds a key frame for the given rotation at the given keyFrame index<br> + * This can't be used if the interpolated angle is higher than PI (180°)<br> + * Use {@link addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angles rotations. + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param rotation the rotation Quaternion to use for this keyFrame + * @see #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) + */ + public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) { + Rotation r = getRotationForFrame(keyFrameIndex); + r.set(rotation); + } + + /** + * Adds a key frame for the given rotation at the given time.<br> + * Rotation is expressed by Euler angles values in radians.<br> + * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br> + * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br> + * + * @param time the time at which the keyFrame must be inserted + * @param x the rotation around the x axis (aka yaw) in radians + * @param y the rotation around the y axis (aka roll) in radians + * @param z the rotation around the z axis (aka pitch) in radians + */ + public void addTimeRotationAngles(float time, float x, float y, float z) { + addKeyFrameRotationAngles((int) (time / tpf), x, y, z); + } + + /** + * Adds a key frame for the given rotation at the given key frame index.<br> + * Rotation is expressed by Euler angles values in radians.<br> + * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br> + * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br> + * + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param x the rotation around the x axis (aka yaw) in radians + * @param y the rotation around the y axis (aka roll) in radians + * @param z the rotation around the z axis (aka pitch) in radians + */ + public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) { + Rotation r = getRotationForFrame(keyFrameIndex); + r.set(x, y, z); + + // if the delta of euler angles is higher than PI, we create intermediate keyframes + // since we are using quaternions and slerp for rotation interpolation, we cannot interpolate over an angle higher than PI + int prev = getPreviousKeyFrame(keyFrameIndex, keyFramesRotation); + //previous rotation keyframe + Rotation prevRot = keyFramesRotation[prev]; + //the maximum delta angle (x,y or z) + float delta = Math.max(Math.abs(x - prevRot.eulerAngles.x), Math.abs(y - prevRot.eulerAngles.y)); + delta = Math.max(delta, Math.abs(z - prevRot.eulerAngles.z)); + //if delta > PI we have to create intermediates key frames + if (delta >= FastMath.PI) { + //frames delta + int dF = keyFrameIndex - prev; + //angle per frame for x,y ,z + float dXAngle = (x - prevRot.eulerAngles.x) / (float) dF; + float dYAngle = (y - prevRot.eulerAngles.y) / (float) dF; + float dZAngle = (z - prevRot.eulerAngles.z) / (float) dF; + + // the keyFrame step + int keyStep = (int) (((float) (dF)) / delta * (float) EULER_STEP); + // the current keyFrame + int cursor = prev + keyStep; + while (cursor < keyFrameIndex) { + //for each step we create a new rotation by interpolating the angles + Rotation dr = getRotationForFrame(cursor); + dr.masterKeyFrame = keyFrameIndex; + dr.set(prevRot.eulerAngles.x + cursor * dXAngle, prevRot.eulerAngles.y + cursor * dYAngle, prevRot.eulerAngles.z + cursor * dZAngle); + cursor += keyStep; + } + + } + + } + + /** + * Adds a key frame for the given scale at the given time + * @param time the time at which the keyFrame must be inserted + * @param scale the scale to use for this keyFrame + */ + public void addTimeScale(float time, Vector3f scale) { + addKeyFrameScale((int) (time / tpf), scale); + } + + /** + * Adds a key frame for the given scale at the given keyFrame index + * @param keyFrameIndex the index at which the keyFrame must be inserted + * @param scale the scale to use for this keyFrame + */ + public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) { + Vector3f s = getScaleForFrame(keyFrameIndex); + s.set(scale); + } + + /** + * returns the translation for a given frame index + * creates the translation if it doesn't exists + * @param keyFrameIndex index + * @return the translation + */ + private Vector3f getTranslationForFrame(int keyFrameIndex) { + if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { + throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); + } + Vector3f v = keyFramesTranslation[keyFrameIndex]; + if (v == null) { + v = new Vector3f(); + keyFramesTranslation[keyFrameIndex] = v; + } + return v; + } + + /** + * returns the scale for a given frame index + * creates the scale if it doesn't exists + * @param keyFrameIndex index + * @return the scale + */ + private Vector3f getScaleForFrame(int keyFrameIndex) { + if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { + throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); + } + Vector3f v = keyFramesScale[keyFrameIndex]; + if (v == null) { + v = new Vector3f(); + keyFramesScale[keyFrameIndex] = v; + } + return v; + } + + /** + * returns the rotation for a given frame index + * creates the rotation if it doesn't exists + * @param keyFrameIndex index + * @return the rotation + */ + private Rotation getRotationForFrame(int keyFrameIndex) { + if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) { + throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")"); + } + Rotation v = keyFramesRotation[keyFrameIndex]; + if (v == null) { + v = new Rotation(); + keyFramesRotation[keyFrameIndex] = v; + } + return v; + } + + /** + * Creates an Animation based on the keyFrames previously added to the helper. + * @return the generated animation + */ + public Animation buildAnimation() { + interpolateTime(); + interpolate(keyFramesTranslation, Type.Translation); + interpolate(keyFramesRotation, Type.Rotation); + interpolate(keyFramesScale, Type.Scale); + + SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales); + + //creating the animation + Animation spatialAnimation = new Animation(name, duration); + spatialAnimation.setTracks(new SpatialTrack[]{spatialTrack}); + + return spatialAnimation; + } + + /** + * interpolates time values + */ + private void interpolateTime() { + for (int i = 0; i < totalFrames; i++) { + times[i] = i * tpf; + } + } + + /** + * Interpolates over the key frames for the given keyFrame array and the given type of transform + * @param keyFrames the keyFrames array + * @param type the type of transforms + */ + private void interpolate(Object[] keyFrames, Type type) { + int i = 0; + while (i < totalFrames) { + //fetching the next keyFrame index transform in the array + int key = getNextKeyFrame(i, keyFrames); + if (key != -1) { + //computing the frame span to interpolate over + int span = key - i; + //interating over the frames + for (int j = i; j <= key; j++) { + // computing interpolation value + float val = (float) (j - i) / (float) span; + //interpolationg depending on the transform type + switch (type) { + case Translation: + translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); + break; + case Rotation: + Quaternion rot = new Quaternion(); + rotations[j] = rot.slerp(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation, val); + break; + case Scale: + scales[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); + break; + } + } + //jumping to the next keyFrame + i = key; + } else { + //No more key frame, filling the array witht he last transform computed. + for (int j = i; j < totalFrames; j++) { + + switch (type) { + case Translation: + translations[j] = ((Vector3f) keyFrames[i]).clone(); + break; + case Rotation: + rotations[j] = ((Quaternion) ((Rotation) keyFrames[i]).rotation).clone(); + break; + case Scale: + scales[j] = ((Vector3f) keyFrames[i]).clone(); + break; + } + } + //we're done + i = totalFrames; + } + } + } + + /** + * Get the index of the next keyFrame that as a transform + * @param index the start index + * @param keyFrames the keyFrames array + * @return the index of the next keyFrame + */ + private int getNextKeyFrame(int index, Object[] keyFrames) { + for (int i = index + 1; i < totalFrames; i++) { + if (keyFrames[i] != null) { + return i; + } + } + return -1; + } + + /** + * Get the index of the previous keyFrame that as a transform + * @param index the start index + * @param keyFrames the keyFrames array + * @return the index of the previous keyFrame + */ + private int getPreviousKeyFrame(int index, Object[] keyFrames) { + for (int i = index - 1; i >= 0; i--) { + if (keyFrames[i] != null) { + return i; + } + } + return -1; + } +} |