All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jme3.animation.AnimationFactory Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2009-2021 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 set up a spatial keyframe animation.
 * You can add some keyFrames for a given time or a given keyFrameIndex, for translation, rotation, and scale.
 * The animationFactory will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames.
 * 

* Usage is :
* - Create the AnimationFactory
* - add some keyFrames
* - call the buildAnimation() method that will return a new Animation
* - add the generated Animation to any existing AnimationControl
*

* Note that the first keyFrame (index 0) is defaulted with the identity transform. * If you want to change that you have to replace this keyFrame with the transform you want. * * @author Nehon * @deprecated use {@link com.jme3.anim.AnimFactory} */ @Deprecated public class AnimationFactory { /** * step for splitting rotations that have an angle 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 class to keep track of a rotation using Euler angles */ @Deprecated 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 split 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 an AnimationFactory * @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 an AnimationFactory * @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 transform 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 transform 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
* This can't be used if the interpolated angle is higher than PI (180°)
* Use {@link #addTimeRotationAngles(float time, float x, float y, float z)} instead that uses Euler angle rotations.
* @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
* This can't be used if the interpolated angle is higher than PI (180°)
* Use {@link #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angle 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.
* Rotation is expressed by Euler angles in radians.
* Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)
* Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation
* * @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.
* Rotation is expressed by Euler angles in radians.
* Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)
* Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation
* * @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); if (prev != -1) { //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) / dF; float dYAngle = (y - prevRot.eulerAngles.y) / dF; float dZAngle = (z - prevRot.eulerAngles.z) / dF; // the keyFrame step int keyStep = (int) (dF / delta * 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 exist * @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 exist * @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 exist * @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 factory. * @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; //iterating over the frames for (int j = i; j <= key; j++) { // computing interpolation value float val = (j - i) / (float) span; //interpolating 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 with the 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] = ((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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy