소스 검색

New anim system proper serialization

Nehon 7 년 전
부모
커밋
bbb3cf59b3

+ 1 - 1
jme3-core/src/main/java/com/jme3/anim/AnimClip.java

@@ -105,7 +105,7 @@ public class AnimClip implements Tween, JmeCloneable, Savable {
         if (arr != null) {
             tracks = new SafeArrayList<>(Tween.class);
             for (Savable savable : arr) {
-                tracks.add((Tween) savable);
+                addTrack((Tween) savable);
             }
         }
     }

+ 37 - 0
jme3-core/src/main/java/com/jme3/anim/AnimComposer.java

@@ -1,9 +1,12 @@
 package com.jme3.anim;
 
+import com.jme3.export.*;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.control.AbstractControl;
+import com.jme3.util.clone.Cloner;
 
+import java.io.IOException;
 import java.util.*;
 
 /**
@@ -87,4 +90,38 @@ public class AnimComposer extends AbstractControl {
     protected void controlRender(RenderManager rm, ViewPort vp) {
 
     }
+
+    @Override
+    public Object jmeClone() {
+        try {
+            AnimComposer clone = (AnimComposer) super.clone();
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public void cloneFields(Cloner cloner, Object original) {
+        super.cloneFields(cloner, original);
+        Map<String, AnimClip> clips = new HashMap<>();
+        for (String key : animClipMap.keySet()) {
+            clips.put(key, cloner.clone(animClipMap.get(key)));
+        }
+        animClipMap = clips;
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        animClipMap = (Map<String, AnimClip>) ic.readStringSavableMap("animClipMap", new HashMap<String, AnimClip>());
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>());
+    }
 }

+ 26 - 10
jme3-core/src/main/java/com/jme3/anim/Armature.java

@@ -1,6 +1,7 @@
 package com.jme3.anim;
 
 import com.jme3.anim.util.JointModelTransform;
+import com.jme3.asset.AssetLoadException;
 import com.jme3.export.*;
 import com.jme3.math.Matrix4f;
 import com.jme3.util.clone.Cloner;
@@ -46,11 +47,7 @@ public class Armature implements JmeCloneable, Savable {
         List<Joint> rootJointList = new ArrayList<>();
         for (int i = jointList.length - 1; i >= 0; i--) {
             Joint joint = jointList[i];
-            try {
-                joint.setJointModelTransform(modelTransformClass.newInstance());
-            } catch (InstantiationException | IllegalAccessException e) {
-                throw new IllegalArgumentException(e);
-            }
+            instanciateJointModelTransform(joint);
             if (joint.getParent() == null) {
                 rootJointList.add(joint);
             }
@@ -94,11 +91,15 @@ public class Armature implements JmeCloneable, Savable {
             return;
         }
         for (Joint joint : jointList) {
-            try {
-                joint.setJointModelTransform(modelTransformClass.newInstance());
-            } catch (InstantiationException | IllegalAccessException e) {
-                throw new IllegalArgumentException(e);
-            }
+            instanciateJointModelTransform(joint);
+        }
+    }
+
+    private void instanciateJointModelTransform(Joint joint) {
+        try {
+            joint.setJointModelTransform(modelTransformClass.newInstance());
+        } catch (InstantiationException | IllegalAccessException e) {
+            throw new IllegalArgumentException(e);
         }
     }
 
@@ -226,6 +227,9 @@ public class Armature implements JmeCloneable, Savable {
         this.rootJoints = cloner.clone(rootJoints);
         this.jointList = cloner.clone(jointList);
         this.skinningMatrixes = cloner.clone(skinningMatrixes);
+        for (Joint joint : jointList) {
+            instanciateJointModelTransform(joint);
+        }
     }
 
 
@@ -241,11 +245,22 @@ public class Armature implements JmeCloneable, Savable {
         jointList = new Joint[jointListAsSavable.length];
         System.arraycopy(jointListAsSavable, 0, jointList, 0, jointListAsSavable.length);
 
+        String className = input.readString("modelTransformClass", MatrixJointModelTransform.class.getCanonicalName());
+        try {
+            modelTransformClass = (Class<? extends JointModelTransform>) Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            throw new AssetLoadException("Cannnot find class for name " + className);
+        }
+
+        for (Joint joint : jointList) {
+            instanciateJointModelTransform(joint);
+        }
         createSkinningMatrices();
 
         for (Joint rootJoint : rootJoints) {
             rootJoint.update();
         }
+        resetToBindPose();
     }
 
     @Override
@@ -253,5 +268,6 @@ public class Armature implements JmeCloneable, Savable {
         OutputCapsule output = ex.getCapsule(this);
         output.write(rootJoints, "rootJoints", null);
         output.write(jointList, "jointList", null);
+        output.write(modelTransformClass.getCanonicalName(), "modelTransformClass", MatrixJointModelTransform.class.getCanonicalName());
     }
 }

+ 1 - 3
jme3-core/src/main/java/com/jme3/anim/Joint.java

@@ -259,10 +259,10 @@ public class Joint implements Savable, JmeCloneable {
     @Override
     public void cloneFields(Cloner cloner, Object original) {
         this.children = cloner.clone(children);
+        this.parent = cloner.clone(parent);
         this.attachedNode = cloner.clone(attachedNode);
         this.targetGeometry = cloner.clone(targetGeometry);
         this.localTransform = cloner.clone(localTransform);
-        this.jointModelTransform = cloner.clone(jointModelTransform);
         this.inverseModelBindMatrix = cloner.clone(inverseModelBindMatrix);
     }
 
@@ -276,7 +276,6 @@ public class Joint implements Savable, JmeCloneable {
         attachedNode = (Node) input.readSavable("attachedNode", null);
         targetGeometry = (Geometry) input.readSavable("targetGeometry", null);
         inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix);
-        jointModelTransform = (JointModelTransform) input.readSavable("jointModelTransform", null);
 
         ArrayList<Joint> childList = input.readSavableArrayList("children", null);
         for (int i = childList.size() - 1; i >= 0; i--) {
@@ -293,7 +292,6 @@ public class Joint implements Savable, JmeCloneable {
         output.write(targetGeometry, "targetGeometry", null);
         output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f());
         output.writeSavableArrayList(children, "children", null);
-        output.write(jointModelTransform, "jointModelTransform", null);
     }
 
 }

+ 0 - 32
jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java

@@ -1,12 +1,8 @@
 package com.jme3.anim;
 
 import com.jme3.anim.util.JointModelTransform;
-import com.jme3.export.*;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Transform;
-import com.jme3.util.clone.Cloner;
-
-import java.io.IOException;
 
 /**
  * This JointModelTransform implementation accumulate joints transforms in a Matrix4f to properly
@@ -47,32 +43,4 @@ public class MatrixJointModelTransform implements JointModelTransform {
     public Transform getModelTransform() {
         return modelTransform;
     }
-
-    @Override
-    public void write(JmeExporter ex) throws IOException {
-        OutputCapsule oc = ex.getCapsule(this);
-        oc.write(modelTransformMatrix, "modelTransformMatrix", new Matrix4f());
-    }
-
-    @Override
-    public void read(JmeImporter im) throws IOException {
-        InputCapsule ic = im.getCapsule(this);
-        modelTransformMatrix = (Matrix4f) ic.readSavable("modelTransformMatrix", new Matrix4f());
-        modelTransform.fromTransformMatrix(modelTransformMatrix);
-    }
-
-    @Override
-    public Object jmeClone() {
-        try {
-            MatrixJointModelTransform clone = (MatrixJointModelTransform) super.clone();
-            return clone;
-        } catch (CloneNotSupportedException ex) {
-            throw new AssertionError();
-        }
-    }
-
-    @Override
-    public void cloneFields(Cloner cloner, Object original) {
-        modelTransformMatrix = modelTransformMatrix.clone();
-    }
 }

+ 1 - 32
jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java

@@ -1,12 +1,8 @@
 package com.jme3.anim;
 
 import com.jme3.anim.util.JointModelTransform;
-import com.jme3.export.*;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Transform;
-import com.jme3.util.clone.Cloner;
-
-import java.io.IOException;
 
 /**
  * This JointModelTransform implementation accumulates model transform in a Transform class
@@ -33,8 +29,7 @@ public class SeparateJointModelTransform implements JointModelTransform {
 
     @Override
     public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) {
-        localTransform.fromTransformMatrix(inverseModelBindMatrix);
-        localTransform.invert(); //model transform
+        localTransform.fromTransformMatrix(inverseModelBindMatrix.invert());
         if (parent != null) {
             localTransform.combineWithParent(parent.getModelTransform().invert());
         }
@@ -45,30 +40,4 @@ public class SeparateJointModelTransform implements JointModelTransform {
         return modelTransform;
     }
 
-    @Override
-    public void write(JmeExporter ex) throws IOException {
-        OutputCapsule oc = ex.getCapsule(this);
-        oc.write(modelTransform, "modelTransform", new Transform());
-    }
-
-    @Override
-    public void read(JmeImporter im) throws IOException {
-        InputCapsule ic = im.getCapsule(this);
-        modelTransform = (Transform) ic.readSavable("modelTransform", new Transform());
-    }
-
-    @Override
-    public Object jmeClone() {
-        try {
-            SeparateJointModelTransform clone = (SeparateJointModelTransform) super.clone();
-            return clone;
-        } catch (CloneNotSupportedException ex) {
-            throw new AssertionError();
-        }
-    }
-
-    @Override
-    public void cloneFields(Cloner cloner, Object original) {
-        modelTransform = modelTransform.clone();
-    }
 }

+ 26 - 15
jme3-core/src/main/java/com/jme3/anim/TransformTrack.java

@@ -307,6 +307,7 @@ public abstract class TransformTrack implements Tween, JmeCloneable, Savable {
         rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
         times = ic.readFloatArray("times", null);
         scales = (CompactVector3Array) ic.readSavable("scales", null);
+        setTimes(times);
     }
 
     @Override
@@ -322,23 +323,33 @@ public abstract class TransformTrack implements Tween, JmeCloneable, Savable {
     public void cloneFields(Cloner cloner, Object original) {
         int tablesLength = times.length;
 
-        times = this.times.clone();
-        Vector3f[] sourceTranslations = this.getTranslations();
-        Quaternion[] sourceRotations = this.getRotations();
-        Vector3f[] sourceScales = this.getScales();
-
-        Vector3f[] translations = new Vector3f[tablesLength];
-        Quaternion[] rotations = new Quaternion[tablesLength];
-        Vector3f[] scales = new Vector3f[tablesLength];
-        for (int i = 0; i < tablesLength; ++i) {
-            translations[i] = sourceTranslations[i].clone();
-            rotations[i] = sourceRotations[i].clone();
-            scales[i] = sourceScales != null ? sourceScales[i].clone() : new Vector3f(1.0f, 1.0f, 1.0f);
+        setTimes(this.times.clone());
+        if (translations != null) {
+            Vector3f[] sourceTranslations = this.getTranslations();
+            Vector3f[] translations = new Vector3f[tablesLength];
+            for (int i = 0; i < tablesLength; ++i) {
+                translations[i] = sourceTranslations[i].clone();
+            }
+            setKeyframesTranslation(translations);
+        }
+        if (rotations != null) {
+            Quaternion[] sourceRotations = this.getRotations();
+            Quaternion[] rotations = new Quaternion[tablesLength];
+            for (int i = 0; i < tablesLength; ++i) {
+                rotations[i] = sourceRotations[i].clone();
+            }
+            setKeyframesRotation(rotations);
+        }
+
+        if (scales != null) {
+            Vector3f[] sourceScales = this.getScales();
+            Vector3f[] scales = new Vector3f[tablesLength];
+            for (int i = 0; i < tablesLength; ++i) {
+                scales[i] = sourceScales[i].clone();
+            }
+            setKeyframesScale(scales);
         }
 
-        setKeyframesTranslation(translations);
-        setKeyframesScale(scales);
-        setKeyframesRotation(rotations);
         setFrameInterpolator(this.interpolator);
     }
 }

+ 1 - 3
jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java

@@ -1,16 +1,14 @@
 package com.jme3.anim.util;
 
 import com.jme3.anim.Joint;
-import com.jme3.export.Savable;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Transform;
-import com.jme3.util.clone.JmeCloneable;
 
 /**
  * Implementations of this interface holds accumulated model transform of a Joint.
  * Implementation might choose different accumulation strategy.
  */
-public interface JointModelTransform extends JmeCloneable, Savable {
+public interface JointModelTransform {
 
     void updateModelTransform(Transform localTransform, Joint parent);
 

+ 3 - 3
jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java

@@ -41,9 +41,9 @@ public class TestAnimMigration extends SimpleApplication {
         rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()));
         rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray));
 
-        Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
+        //Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
         //Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
-        //Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
+        Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
         //Spatial model = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml");
 
         AnimMigrationUtils.migrate(model);
@@ -52,7 +52,7 @@ public class TestAnimMigration extends SimpleApplication {
 
 
         debugAppState = new ArmatureDebugAppState();
-        stateManager.attach(debugAppState);
+        //stateManager.attach(debugAppState);
 
         setupModel(model);
 

+ 168 - 0
jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java

@@ -0,0 +1,168 @@
+package jme3test.model.anim;
+
+import com.jme3.anim.AnimComposer;
+import com.jme3.anim.SkinningControl;
+import com.jme3.anim.util.AnimMigrationUtils;
+import com.jme3.app.ChaseCameraAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.FileLocator;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.*;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.custom.ArmatureDebugAppState;
+import com.jme3.system.JmeSystem;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Created by Nehon on 18/12/2017.
+ */
+public class TestAnimSerialization extends SimpleApplication {
+
+    ArmatureDebugAppState debugAppState;
+    AnimComposer composer;
+    Queue<String> anims = new LinkedList<>();
+    boolean playAnim = true;
+    File file;
+
+    public static void main(String... argv) {
+        TestAnimSerialization app = new TestAnimSerialization();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        setTimer(new EraseTimer());
+        //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f);
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+        rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()));
+        rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray));
+
+        Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
+
+        AnimMigrationUtils.migrate(model);
+
+        File storageFolder = JmeSystem.getStorageFolder();
+        file = new File(storageFolder.getPath() + File.separator + "newJaime.j3o");
+        BinaryExporter be = new BinaryExporter();
+        try {
+            be.save(model, file);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        assetManager.registerLocator(storageFolder.getPath(), FileLocator.class);
+        model = assetManager.loadModel("newJaime.j3o");
+
+        rootNode.attachChild(model);
+
+        debugAppState = new ArmatureDebugAppState();
+        stateManager.attach(debugAppState);
+
+        setupModel(model);
+
+        flyCam.setEnabled(false);
+
+        Node target = new Node("CamTarget");
+        //target.setLocalTransform(model.getLocalTransform());
+        target.move(0, 1, 0);
+        ChaseCameraAppState chaseCam = new ChaseCameraAppState();
+        chaseCam.setTarget(target);
+        getStateManager().attach(chaseCam);
+        chaseCam.setInvertHorizontalAxis(true);
+        chaseCam.setInvertVerticalAxis(true);
+        chaseCam.setZoomSpeed(0.5f);
+        chaseCam.setMinVerticalRotation(-FastMath.HALF_PI);
+        chaseCam.setRotationSpeed(3);
+        chaseCam.setDefaultDistance(3);
+        chaseCam.setMinDistance(0.01f);
+        chaseCam.setZoomSpeed(0.01f);
+        chaseCam.setDefaultVerticalRotation(0.3f);
+
+        initInputs();
+    }
+
+    public void initInputs() {
+        inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN));
+
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed) {
+                    playAnim = !playAnim;
+                    if (playAnim) {
+                        String anim = anims.poll();
+                        anims.add(anim);
+                        composer.setCurrentAnimClip(anim);
+                        System.err.println(anim);
+                    } else {
+                        composer.reset();
+                    }
+                }
+            }
+        }, "toggleAnim");
+        inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT));
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed && composer != null) {
+                    String anim = anims.poll();
+                    anims.add(anim);
+                    composer.setCurrentAnimClip(anim);
+                    System.err.println(anim);
+                }
+            }
+        }, "nextAnim");
+    }
+
+    private void setupModel(Spatial model) {
+        if (composer != null) {
+            return;
+        }
+        composer = model.getControl(AnimComposer.class);
+        if (composer != null) {
+
+            SkinningControl sc = model.getControl(SkinningControl.class);
+
+            debugAppState.addArmatureFrom(sc);
+            anims.clear();
+            for (String name : composer.getAnimClipsNames()) {
+                anims.add(name);
+            }
+            if (anims.isEmpty()) {
+                return;
+            }
+            if (playAnim) {
+                String anim = anims.poll();
+                anims.add(anim);
+                composer.setCurrentAnimClip(anim);
+                System.err.println(anim);
+            }
+
+        } else {
+            if (model instanceof Node) {
+                Node n = (Node) model;
+                for (Spatial child : n.getChildren()) {
+                    setupModel(child);
+                }
+            }
+        }
+
+    }
+
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        file.delete();
+    }
+}

+ 1 - 1
jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java

@@ -51,7 +51,7 @@ public class TestArmature extends SimpleApplication {
         Joint[] joints = new Joint[]{root, j1, j2, j3};
 
         final Armature armature = new Armature(joints);
-//        armature.setModelTransformClass(SeparateJointModelTransform.class);
+        //armature.setModelTransformClass(SeparateJointModelTransform.class);
         armature.setBindPose();
 
         //create animations

+ 217 - 0
jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java

@@ -0,0 +1,217 @@
+package jme3test.model.anim;
+
+import com.jme3.anim.*;
+import com.jme3.app.ChaseCameraAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.plugins.FileLocator;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.scene.*;
+import com.jme3.scene.debug.custom.ArmatureDebugAppState;
+import com.jme3.scene.shape.Cylinder;
+import com.jme3.system.JmeSystem;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Created by Nehon on 18/12/2017.
+ */
+public class TestBaseAnimSerialization extends SimpleApplication {
+
+    Joint j1;
+    Joint j2;
+    AnimComposer composer;
+    Armature armature;
+    File file;
+
+    public static void main(String... argv) {
+        TestBaseAnimSerialization app = new TestBaseAnimSerialization();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        setTimer(new EraseTimer());
+        renderManager.setSinglePassLightBatchSize(2);
+        //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f);
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        //create armature
+        Joint root = new Joint("Root_Joint");
+        j1 = new Joint("Joint_1");
+        j2 = new Joint("Joint_2");
+        Joint j3 = new Joint("Joint_3");
+        root.addChild(j1);
+        j1.addChild(j2);
+        j2.addChild(j3);
+        root.setLocalTranslation(new Vector3f(0, 0, 0.5f));
+        j1.setLocalTranslation(new Vector3f(0, 0.0f, -0.5f));
+        j2.setLocalTranslation(new Vector3f(0, 0.0f, -0.3f));
+        j3.setLocalTranslation(new Vector3f(0, 0, -0.2f));
+        Joint[] joints = new Joint[]{root, j1, j2, j3};
+
+        armature = new Armature(joints);
+        //armature.setModelTransformClass(SeparateJointModelTransform.class);
+        armature.setBindPose();
+
+        //create animations
+        AnimClip clip = new AnimClip("anim");
+        float[] times = new float[]{0, 2, 4};
+        Quaternion[] rotations = new Quaternion[]{
+                new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X),
+                new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X),
+                new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)
+        };
+        Vector3f[] translations = new Vector3f[]{
+                new Vector3f(0, 0.2f, 0),
+                new Vector3f(0, 1.0f, 0),
+                new Vector3f(0, 0.2f, 0),
+        };
+        Vector3f[] scales = new Vector3f[]{
+                new Vector3f(1, 1, 1),
+                new Vector3f(1, 1, 2),
+                new Vector3f(1, 1, 1),
+        };
+        Vector3f[] scales2 = new Vector3f[]{
+                new Vector3f(1, 1, 1),
+                new Vector3f(1, 1, 0.5f),
+                new Vector3f(1, 1, 1),
+        };
+
+        JointTrack track1 = new JointTrack(j1, times, null, rotations, scales);
+        JointTrack track2 = new JointTrack(j2, times, null, rotations, null);
+        clip.addTrack(track1);
+        clip.addTrack(track2);
+
+        //create the animComposer control
+        composer = new AnimComposer();
+        composer.addAnimClip(clip);
+
+        //create the SkinningControl
+        SkinningControl ac = new SkinningControl(armature);
+        Node node = new Node("Test Armature");
+
+        //Create the mesh to deform.
+        Geometry cylinder = new Geometry("cylinder", createMesh());
+        Material m = new Material(assetManager, "Common/MatDefs/Misc/fakeLighting.j3md");
+        m.setColor("Color", ColorRGBA.randomColor());
+        cylinder.setMaterial(m);
+        node.attachChild(cylinder);
+        node.addControl(composer);
+        node.addControl(ac);
+
+        File storageFolder = JmeSystem.getStorageFolder();
+        file = new File(storageFolder.getPath() + File.separator + "test.j3o");
+        BinaryExporter be = new BinaryExporter();
+        try {
+            be.save(node, file);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        assetManager.registerLocator(storageFolder.getPath(), FileLocator.class);
+        Node newNode = (Node) assetManager.loadModel("test.j3o");
+
+        rootNode.attachChild(newNode);
+
+        composer = newNode.getControl(AnimComposer.class);
+        ac = newNode.getControl(SkinningControl.class);
+        ac.setHardwareSkinningPreferred(false);
+        armature = ac.getArmature();
+        composer.setCurrentAnimClip("anim");
+
+        ArmatureDebugAppState debugAppState = new ArmatureDebugAppState();
+        debugAppState.addArmatureFrom(ac);
+        stateManager.attach(debugAppState);
+
+        flyCam.setEnabled(false);
+
+        ChaseCameraAppState chaseCam = new ChaseCameraAppState();
+        chaseCam.setTarget(node);
+        getStateManager().attach(chaseCam);
+        chaseCam.setInvertHorizontalAxis(true);
+        chaseCam.setInvertVerticalAxis(true);
+        chaseCam.setZoomSpeed(0.5f);
+        chaseCam.setMinVerticalRotation(-FastMath.HALF_PI);
+        chaseCam.setRotationSpeed(3);
+        chaseCam.setDefaultDistance(3);
+        chaseCam.setMinDistance(0.01f);
+        chaseCam.setZoomSpeed(0.01f);
+        chaseCam.setDefaultVerticalRotation(0.3f);
+
+
+        inputManager.addMapping("bind", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed) {
+                    composer.reset();
+                    armature.resetToBindPose();
+
+                } else {
+                    composer.setCurrentAnimClip("anim");
+                }
+            }
+        }, "bind");
+    }
+
+    private Mesh createMesh() {
+        Cylinder c = new Cylinder(30, 16, 0.1f, 1, true);
+
+        ShortBuffer jointIndex = (ShortBuffer) VertexBuffer.createBuffer(VertexBuffer.Format.UnsignedShort, 4, c.getVertexCount());
+        jointIndex.rewind();
+        c.setMaxNumWeights(1);
+        FloatBuffer jointWeight = (FloatBuffer) VertexBuffer.createBuffer(VertexBuffer.Format.Float, 4, c.getVertexCount());
+        jointWeight.rewind();
+        VertexBuffer vb = c.getBuffer(VertexBuffer.Type.Position);
+        FloatBuffer fvb = (FloatBuffer) vb.getData();
+        fvb.rewind();
+        for (int i = 0; i < c.getVertexCount(); i++) {
+            fvb.get();
+            fvb.get();
+            float z = fvb.get();
+            int index = 0;
+            if (z > 0) {
+                index = 0;
+            } else if (z > -0.2) {
+                index = 1;
+            } else {
+                index = 2;
+            }
+            jointIndex.put((short) index).put((short) 0).put((short) 0).put((short) 0);
+            jointWeight.put(1f).put(0f).put(0f).put(0f);
+
+        }
+        c.setBuffer(VertexBuffer.Type.BoneIndex, 4, jointIndex);
+        c.setBuffer(VertexBuffer.Type.BoneWeight, 4, jointWeight);
+
+        c.updateCounts();
+        c.updateBound();
+
+        VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight);
+        VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex);
+
+        indicesHW.setUsage(VertexBuffer.Usage.CpuOnly);
+        weightsHW.setUsage(VertexBuffer.Usage.CpuOnly);
+        c.setBuffer(weightsHW);
+        c.setBuffer(indicesHW);
+        c.generateBindPose();
+
+        c.prepareForAnim(false);
+
+        return c;
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        file.delete();
+    }
+}