From 7de07d056c3a4951559d78a4b32da531da336326 Mon Sep 17 00:00:00 2001 From: "rem..om" Date: Sat, 24 Dec 2011 16:11:49 +0000 Subject: [PATCH] Created an AnimationHelper to ease the creation process of a spatial animation, based on key frames interpolation. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8955 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../core/com/jme3/animation/AnimControl.java | 30 +- .../com/jme3/animation/AnimationHelper.java | 494 ++++++++++++++++++ .../model/anim/TestAnimationHelper.java | 85 +++ 3 files changed, 595 insertions(+), 14 deletions(-) create mode 100644 engine/src/core/com/jme3/animation/AnimationHelper.java create mode 100644 engine/src/test/jme3test/model/anim/TestAnimationHelper.java diff --git a/engine/src/core/com/jme3/animation/AnimControl.java b/engine/src/core/com/jme3/animation/AnimControl.java index 3bddd60ee..590801c94 100644 --- a/engine/src/core/com/jme3/animation/AnimControl.java +++ b/engine/src/core/com/jme3/animation/AnimControl.java @@ -70,21 +70,17 @@ public final class AnimControl extends AbstractControl implements Cloneable { * Skeleton object must contain corresponding data for the targets' weight buffers. */ Skeleton skeleton; - /** only used for backward compatibility */ @Deprecated private SkeletonControl skeletonControl; - /** * List of animations */ HashMap animationMap; - /** * Animation channels */ private transient ArrayList channels = new ArrayList(); - /** * Animation event listeners */ @@ -117,14 +113,14 @@ public final class AnimControl extends AbstractControl implements Cloneable { clone.spatial = spatial; clone.channels = new ArrayList(); clone.listeners = new ArrayList(); - - if (skeleton != null){ + + if (skeleton != null) { clone.skeleton = new Skeleton(skeleton); } - + // animationMap is reference-copied, animation data should be shared // to reduce memory usage. - + return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(); @@ -147,6 +143,9 @@ public final class AnimControl extends AbstractControl implements Cloneable { * such named animation exists. */ public Animation getAnim(String name) { + if (animationMap == null) { + animationMap = new HashMap(); + } return animationMap.get(name); } @@ -156,6 +155,9 @@ public final class AnimControl extends AbstractControl implements Cloneable { * @param anim The animation to add. */ public void addAnim(Animation anim) { + if (animationMap == null) { + animationMap = new HashMap(); + } animationMap.put(anim.getName(), anim); } @@ -273,7 +275,7 @@ public final class AnimControl extends AbstractControl implements Cloneable { */ @Override public void setSpatial(Spatial spatial) { - if (spatial == null && skeletonControl != null){ + if (spatial == null && skeletonControl != null) { this.spatial.removeControl(skeletonControl); } @@ -313,13 +315,13 @@ public final class AnimControl extends AbstractControl implements Cloneable { return a.getLength(); } - + /** * Internal use only. */ @Override protected void controlUpdate(float tpf) { - if (skeleton != null){ + if (skeleton != null) { skeleton.reset(); // reset skeleton to bind pose } @@ -329,7 +331,7 @@ public final class AnimControl extends AbstractControl implements Cloneable { } vars.release(); - if (skeleton != null){ + if (skeleton != null) { skeleton.updateWorldVectors(); } } @@ -356,10 +358,10 @@ public final class AnimControl extends AbstractControl implements Cloneable { skeleton = (Skeleton) in.readSavable("skeleton", null); animationMap = (HashMap) in.readStringSavableMap("animations", null); - if (im.getFormatVersion() == 0){ + if (im.getFormatVersion() == 0) { // Changed for backward compatibility with j3o files generated // before the AnimControl/SkeletonControl split. - + // If we find a target mesh array the AnimControl creates the // SkeletonControl for old files and add it to the spatial. // When backward compatibility won't be needed anymore this can deleted diff --git a/engine/src/core/com/jme3/animation/AnimationHelper.java b/engine/src/core/com/jme3/animation/AnimationHelper.java new file mode 100644 index 000000000..c2ae950da --- /dev/null +++ b/engine/src/core/com/jme3/animation/AnimationHelper.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. + *

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

+ * 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 AnimationHelper { + + /** + * 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 AnimationHelper(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 AnimationHelper(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
+ * 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 angles 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 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.
+ * Rotation is expressed by Euler angles values 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 values 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); + //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; + } +} diff --git a/engine/src/test/jme3test/model/anim/TestAnimationHelper.java b/engine/src/test/jme3test/model/anim/TestAnimationHelper.java new file mode 100644 index 000000000..9ef7b6201 --- /dev/null +++ b/engine/src/test/jme3test/model/anim/TestAnimationHelper.java @@ -0,0 +1,85 @@ +package jme3test.model.anim; + +import com.jme3.animation.AnimControl; +import com.jme3.animation.AnimationHelper; +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.util.TangentBinormalGenerator; + +public class TestAnimationHelper extends SimpleApplication { + + public static void main(String[] args) { + TestSpatialAnim app = new TestSpatialAnim(); + app.start(); + } + + @Override + public void simpleInitApp() { + + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(Vector3f.UNIT_XYZ.negate()); + rootNode.addLight(dl); + + // Create model + Box box = new Box(1, 1, 1); + Geometry geom = new Geometry("box", box); + geom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node model = new Node("model"); + model.attachChild(geom); + + Box child = new Box(0.5f, 0.5f, 0.5f); + Geometry childGeom = new Geometry("box", child); + childGeom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); + Node childModel = new Node("childmodel"); + childModel.setLocalTranslation(2, 2, 2); + childModel.attachChild(childGeom); + model.attachChild(childModel); + TangentBinormalGenerator.generate(model); + + //creating quite complex animation witht the AnimationHelper + // animation of 6 seconds named "anim" and with 25 frames per second + AnimationHelper animationHelper = new AnimationHelper(6, "anim", 25); + + //creating a translation keyFrame at time = 3 with a translation on the x axis of 5 WU + animationHelper.addTimeTranslation(3, new Vector3f(5, 0, 0)); + //reseting the translation to the start position at time = 6 + animationHelper.addTimeTranslation(6, new Vector3f(0, 0, 0)); + + //Creating a scale keyFrame at time = 2 with the unit scale. + animationHelper.addTimeScale(2, new Vector3f(1, 1, 1)); + //Creating a scale keyFrame at time = 4 scaling to 1.5 + animationHelper.addTimeScale(4, new Vector3f(1.5f, 1.5f, 1.5f)); + //reseting the scale to the start value at time = 5 + animationHelper.addTimeScale(5, new Vector3f(1, 1, 1)); + + + //Creating a rotation keyFrame at time = 0.5 of quarter PI around the Z axis + animationHelper.addTimeRotation(0.5f,new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Z)); + //rotating back to initial rotation value at time = 1 + animationHelper.addTimeRotation(1,Quaternion.IDENTITY); + //Creating a rotation keyFrame at time = 2. Note that i used the Euler angle version because the angle is higher than PI + //this should result in a complete revolution of the spatial around the x axis in 1 second (from 1 to 2) + animationHelper.addTimeRotationAngles(2, FastMath.TWO_PI,0, 0); + + + AnimControl control = new AnimControl(); + control.addAnim(animationHelper.buildAnimation()); + + model.addControl(control); + + rootNode.attachChild(model); + + //run animation + control.createChannel().setAnim("anim"); + } +}