Prechádzať zdrojové kódy

Draft of the new animation system

Nehon 7 rokov pred
rodič
commit
0e3ab8dd14
41 zmenil súbory, kde vykonal 1805 pridanie a 120 odobranie
  1. 1 1
      .gitignore
  2. 113 0
      jme3-core/src/main/java/com/jme3/anim/AnimClip.java
  3. 83 0
      jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
  4. 14 35
      jme3-core/src/main/java/com/jme3/anim/Armature.java
  5. 28 14
      jme3-core/src/main/java/com/jme3/anim/Joint.java
  6. 113 0
      jme3-core/src/main/java/com/jme3/anim/JointTrack.java
  7. 16 14
      jme3-core/src/main/java/com/jme3/anim/SkinningControl.java
  8. 344 0
      jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
  9. 13 0
      jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java
  10. 151 0
      jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java
  11. 121 0
      jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java
  12. 110 0
      jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java
  13. 71 0
      jme3-core/src/main/java/com/jme3/anim/tween/Tween.java
  14. 2 0
      jme3-core/src/main/java/com/jme3/animation/AnimChannel.java
  15. 2 0
      jme3-core/src/main/java/com/jme3/animation/AnimControl.java
  16. 1 0
      jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java
  17. 3 0
      jme3-core/src/main/java/com/jme3/animation/Animation.java
  18. 3 5
      jme3-core/src/main/java/com/jme3/animation/AudioTrack.java
  19. 2 0
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  20. 6 0
      jme3-core/src/main/java/com/jme3/animation/BoneTrack.java
  21. 1 0
      jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java
  22. 10 5
      jme3-core/src/main/java/com/jme3/animation/EffectTrack.java
  23. 1 0
      jme3-core/src/main/java/com/jme3/animation/LoopMode.java
  24. 2 0
      jme3-core/src/main/java/com/jme3/animation/Pose.java
  25. 5 1
      jme3-core/src/main/java/com/jme3/animation/Skeleton.java
  26. 3 0
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  27. 3 5
      jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java
  28. 1 0
      jme3-core/src/main/java/com/jme3/animation/Track.java
  29. 3 5
      jme3-core/src/main/java/com/jme3/animation/TrackInfo.java
  30. 13 0
      jme3-core/src/main/java/com/jme3/math/EaseFunction.java
  31. 163 0
      jme3-core/src/main/java/com/jme3/math/Easing.java
  32. 165 0
      jme3-core/src/main/java/com/jme3/math/MathUtils.java
  33. 2 1
      jme3-core/src/main/java/com/jme3/math/Transform.java
  34. 2 2
      jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureBone.java
  35. 4 4
      jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java
  36. 5 3
      jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java
  37. 2 2
      jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java
  38. 8 7
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  39. 7 6
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading2.java
  40. 76 0
      jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java
  41. 132 10
      jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java

+ 1 - 1
.gitignore

@@ -2,7 +2,7 @@
 **/.classpath
 **/.settings
 **/.project
-**/out
+**/out/
 /.gradle/
 /.nb-gradle/
 /.idea/

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

@@ -0,0 +1,113 @@
+package com.jme3.anim;
+
+import com.jme3.anim.tween.Tween;
+import com.jme3.export.*;
+import com.jme3.util.SafeArrayList;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
+
+import java.io.IOException;
+
+/**
+ * Created by Nehon on 20/12/2017.
+ */
+public class AnimClip implements Tween, JmeCloneable, Savable {
+
+    private String name;
+    private double length;
+
+    private SafeArrayList<Tween> tracks = new SafeArrayList<>(Tween.class);
+
+    public AnimClip() {
+    }
+
+    public AnimClip(String name) {
+        this.name = name;
+    }
+
+    public void setTracks(Tween[] tracks) {
+        for (Tween track : tracks) {
+            addTrack(track);
+        }
+    }
+
+    public void addTrack(Tween track) {
+        tracks.add(track);
+        if (track.getLength() > length) {
+            length = track.getLength();
+        }
+    }
+
+    public void removeTrack(Tween track) {
+        if (tracks.remove(track)) {
+            length = 0;
+            for (Tween t : tracks.getArray()) {
+                if (t.getLength() > length) {
+                    length = t.getLength();
+                }
+            }
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public double getLength() {
+        return length;
+    }
+
+    @Override
+    public boolean interpolate(double t) {
+        // Sanity check the inputs
+        if (t < 0) {
+            return true;
+        }
+
+        for (Tween track : tracks.getArray()) {
+            track.interpolate(t);
+        }
+        return t <= length;
+    }
+
+    @Override
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("Error cloning", e);
+        }
+    }
+
+    @Override
+    public void cloneFields(Cloner cloner, Object original) {
+        SafeArrayList<Tween> newTracks = new SafeArrayList<>(Tween.class);
+        for (Tween track : tracks) {
+            newTracks.add(cloner.clone(track));
+        }
+        this.tracks = newTracks;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(name, "name", null);
+        oc.write(tracks.getArray(), "tracks", null);
+
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        name = ic.readString("name", null);
+        Savable[] arr = ic.readSavableArray("tracks", null);
+        if (arr != null) {
+            tracks = new SafeArrayList<>(Tween.class);
+            for (Savable savable : arr) {
+                tracks.add((Tween) savable);
+            }
+        }
+    }
+
+}

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

@@ -0,0 +1,83 @@
+package com.jme3.anim;
+
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.control.AbstractControl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by Nehon on 20/12/2017.
+ */
+public class AnimComposer extends AbstractControl {
+
+    private Map<String, AnimClip> animClipMap = new HashMap<>();
+
+    private AnimClip currentAnimClip;
+    private float time;
+
+    /**
+     * Retrieve an animation from the list of animations.
+     *
+     * @param name The name of the animation to retrieve.
+     * @return The animation corresponding to the given name, or null, if no
+     * such named animation exists.
+     */
+    public AnimClip getAnimClip(String name) {
+        return animClipMap.get(name);
+    }
+
+    /**
+     * Adds an animation to be available for playing to this
+     * <code>AnimControl</code>.
+     *
+     * @param anim The animation to add.
+     */
+    public void addAnimClip(AnimClip anim) {
+        animClipMap.put(anim.getName(), anim);
+    }
+
+    /**
+     * Remove an animation so that it is no longer available for playing.
+     *
+     * @param anim The animation to remove.
+     */
+    public void removeAnimClip(AnimClip anim) {
+        if (!animClipMap.containsKey(anim.getName())) {
+            throw new IllegalArgumentException("Given animation does not exist "
+                    + "in this AnimControl");
+        }
+
+        animClipMap.remove(anim.getName());
+    }
+
+    public void setCurrentAnimClip(String name) {
+        currentAnimClip = animClipMap.get(name);
+        time = 0;
+        if (currentAnimClip == null) {
+            throw new IllegalArgumentException("Unknown clip " + name);
+        }
+    }
+
+    public void reset() {
+        currentAnimClip = null;
+        time = 0;
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        if (currentAnimClip != null) {
+            time += tpf;
+            boolean running = currentAnimClip.interpolate(time);
+            if (!running) {
+                time -= currentAnimClip.getLength();
+            }
+        }
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+
+    }
+}

+ 14 - 35
jme3-core/src/main/java/com/jme3/animation/Armature.java → jme3-core/src/main/java/com/jme3/anim/Armature.java

@@ -1,8 +1,7 @@
-package com.jme3.animation;
+package com.jme3.anim;
 
 import com.jme3.export.*;
 import com.jme3.math.Matrix4f;
-import com.jme3.util.TempVars;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.JmeCloneable;
 
@@ -60,33 +59,6 @@ public class Armature implements JmeCloneable, Savable {
         }
     }
 
-//
-//    /**
-//     * Special-purpose copy constructor.
-//     * <p>
-//     * Shallow copies bind pose data from the source skeleton, does not
-//     * copy any other data.
-//     *
-//     * @param source The source Skeleton to copy from
-//     */
-//    public Armature(Armature source) {
-//        Joint[] sourceList = source.jointList;
-//        jointList = new Joint[sourceList.length];
-//        for (int i = 0; i < sourceList.length; i++) {
-//            jointList[i] = new Joint(sourceList[i]);
-//        }
-//
-//        rootJoints = new Bone[source.rootJoints.length];
-//        for (int i = 0; i < rootJoints.length; i++) {
-//            rootJoints[i] = recreateBoneStructure(source.rootJoints[i]);
-//        }
-//        createSkinningMatrices();
-//
-//        for (int i = rootJoints.length - 1; i >= 0; i--) {
-//            rootJoints[i].update();
-//        }
-//    }
-
     /**
      * Update all joints sin this Amature.
      */
@@ -176,8 +148,18 @@ public class Armature implements JmeCloneable, Savable {
         //make sure all bones are updated
         update();
         //Save the current pose as bind pose
-        for (Joint rootJoint : rootJoints) {
-            rootJoint.setBindPose();
+        for (Joint joint : jointList) {
+            joint.setBindPose();
+        }
+    }
+
+    /**
+     * This methods sets this armature in its bind pose (aligned with the undeformed mesh)
+     * Note that this is only useful for debugging porpose.
+     */
+    public void resetToBindPose() {
+        for (Joint joint : rootJoints) {
+            joint.resetToBindPose();
         }
     }
 
@@ -187,11 +169,9 @@ public class Armature implements JmeCloneable, Savable {
      * @return
      */
     public Matrix4f[] computeSkinningMatrices() {
-        TempVars vars = TempVars.get();
         for (int i = 0; i < jointList.length; i++) {
-            jointList[i].getOffsetTransform(skinningMatrixes[i], vars.quat1, vars.vect1, vars.vect2, vars.tempMat3);
+            jointList[i].getOffsetTransform(skinningMatrixes[i]);
         }
-        vars.release();
         return skinningMatrixes;
     }
 
@@ -247,5 +227,4 @@ public class Armature implements JmeCloneable, Savable {
         output.write(rootJoints, "rootJoints", null);
         output.write(jointList, "jointList", null);
     }
-
 }

+ 28 - 14
jme3-core/src/main/java/com/jme3/animation/Joint.java → jme3-core/src/main/java/com/jme3/anim/Joint.java

@@ -1,4 +1,4 @@
-package com.jme3.animation;
+package com.jme3.anim;
 
 import com.jme3.export.*;
 import com.jme3.material.MatParamOverride;
@@ -23,13 +23,6 @@ public class Joint implements Savable, JmeCloneable {
     private ArrayList<Joint> children = new ArrayList<>();
     private Geometry targetGeometry;
 
-    public Joint() {
-    }
-
-    public Joint(String name) {
-        this.name = name;
-    }
-
     /**
      * The attachment node.
      */
@@ -43,29 +36,37 @@ public class Joint implements Savable, JmeCloneable {
 
     /**
      * The base transform of the joint in local space.
-     * Those transform are the bone's initial value.
+     * Those transform are the joint's initial value.
      */
     private Transform baseLocalTransform = new Transform();
 
     /**
-     * The transform of the bone in model space. Relative to the origin of the model.
+     * The transform of the joint in model space. Relative to the origin of the model.
      */
     private Transform modelTransform = new Transform();
 
     /**
-     * The matrix used to transform affected vertices position into the bone model space.
+     * The matrix used to transform affected vertices position into the joint model space.
      * Used for skinning.
      */
     private Matrix4f inverseModelBindMatrix = new Matrix4f();
 
+
+    public Joint() {
+    }
+
+    public Joint(String name) {
+        this.name = name;
+    }
+
     /**
      * Updates world transforms for this bone and it's children.
      */
     public final void update() {
         this.updateModelTransforms();
 
-        for (int i = children.size() - 1; i >= 0; i--) {
-            children.get(i).update();
+        for (Joint child : children) {
+            child.update();
         }
     }
 
@@ -128,7 +129,7 @@ public class Joint implements Savable, JmeCloneable {
      *
      * @param outTransform
      */
-    void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) {
+    void getOffsetTransform(Matrix4f outTransform) {
         modelTransform.toTransformMatrix(outTransform).mult(inverseModelBindMatrix, outTransform);
     }
 
@@ -136,6 +137,19 @@ public class Joint implements Savable, JmeCloneable {
         //Note that the whole Armature must be updated before calling this method.
         modelTransform.toTransformMatrix(inverseModelBindMatrix);
         inverseModelBindMatrix.invertLocal();
+        baseLocalTransform.set(localTransform);
+    }
+
+    protected void resetToBindPose() {
+        localTransform.fromTransformMatrix(inverseModelBindMatrix.invert()); // local = modelBind
+        if (parent != null) {
+            localTransform.combineWithParent(parent.modelTransform.invert()); // local = local Bind
+        }
+        updateModelTransforms();
+
+        for (Joint child : children) {
+            child.resetToBindPose();
+        }
     }
 
     public Vector3f getLocalTranslation() {

+ 113 - 0
jme3-core/src/main/java/com/jme3/anim/JointTrack.java

@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2009-2012 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.anim;
+
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
+
+import java.io.IOException;
+
+/**
+ * Contains a list of transforms and times for each keyframe.
+ *
+ * @author Rémy Bouquet
+ */
+public final class JointTrack extends TransformTrack implements JmeCloneable, Savable {
+
+    private Joint target;
+
+    /**
+     * Serialization-only. Do not use.
+     */
+    public JointTrack() {
+        super();
+    }
+
+    /**
+     * Creates a bone track for the given bone index
+     *
+     * @param target       The Joint target of this track
+     * @param times        a float array with the time of each frame
+     * @param translations the translation of the bone for each frame
+     * @param rotations    the rotation of the bone for each frame
+     * @param scales       the scale of the bone for each frame
+     */
+    public JointTrack(Joint target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
+        super(times, translations, rotations, scales);
+        this.target = target;
+    }
+
+    @Override
+    public boolean interpolate(double t) {
+        setDefaultTransform(target.getLocalTransform());
+        boolean running = super.interpolate(t);
+        Transform transform = getInterpolatedTransform();
+        target.setLocalTransform(transform);
+        return running;
+    }
+
+    public void setTarget(Joint target) {
+        this.target = target;
+    }
+
+    @Override
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("Error cloning", e);
+        }
+    }
+
+    @Override
+    public void cloneFields(Cloner cloner, Object original) {
+        super.cloneFields(cloner, original);
+        this.target = cloner.clone(target);
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(target, "target", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        target = (Joint) ic.readSavable("target", null);
+    }
+
+}

+ 16 - 14
jme3-core/src/main/java/com/jme3/animation/ArmatureControl.java → jme3-core/src/main/java/com/jme3/anim/SkinningControl.java

@@ -29,7 +29,7 @@
  * 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;
+package com.jme3.anim;
 
 import com.jme3.export.*;
 import com.jme3.material.MatParamOverride;
@@ -53,15 +53,17 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
- * The Skeleton control deforms a model according to a armature, It handles the
+ * The Skinning control deforms a model according to an armature, It handles the
  * computation of the deformation matrices and performs the transformations on
  * the mesh
+ * <p>
+ * It can perform software skinning or Hardware skinning
  *
- * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
+ * @author Rémy Bouquet Based on SkeletonControl by Kirill Vainer
  */
-public class ArmatureControl extends AbstractControl implements Cloneable, JmeCloneable {
+public class SkinningControl extends AbstractControl implements Cloneable, JmeCloneable {
 
-    private static final Logger logger = Logger.getLogger(ArmatureControl.class.getName());
+    private static final Logger logger = Logger.getLogger(SkinningControl.class.getName());
 
     /**
      * The armature of the model.
@@ -71,7 +73,7 @@ public class ArmatureControl extends AbstractControl implements Cloneable, JmeCl
     /**
      * List of geometries affected by this control.
      */
-    private SafeArrayList<Geometry> targets = new SafeArrayList<Geometry>(Geometry.class);
+    private SafeArrayList<Geometry> targets = new SafeArrayList<>(Geometry.class);
 
     /**
      * Used to track when a mesh was updated. Meshes are only updated if they
@@ -113,16 +115,16 @@ public class ArmatureControl extends AbstractControl implements Cloneable, JmeCl
     /**
      * Serialization only. Do not use.
      */
-    public ArmatureControl() {
+    public SkinningControl() {
     }
 
     /**
      * Creates a armature control. The list of targets will be acquired
      * automatically when the control is attached to a node.
      *
-     * @param skeleton the armature
+     * @param armature the armature
      */
-    public ArmatureControl(Armature armature) {
+    public SkinningControl(Armature armature) {
         if (armature == null) {
             throw new IllegalArgumentException("armature cannot be null");
         }
@@ -283,7 +285,7 @@ public class ArmatureControl extends AbstractControl implements Cloneable, JmeCl
                 if (hwSkinningSupported) {
                     hwSkinningEnabled = true;
 
-                    Logger.getLogger(ArmatureControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial);
+                    Logger.getLogger(SkinningControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial);
                 } else {
                     switchToSoftware();
                 }
@@ -308,6 +310,7 @@ public class ArmatureControl extends AbstractControl implements Cloneable, JmeCl
     @Override
     protected void controlUpdate(float tpf) {
         wasMeshUpdated = false;
+        armature.update();
     }
 
     //only do this for software updates
@@ -558,10 +561,9 @@ public class ArmatureControl extends AbstractControl implements Cloneable, JmeCl
      * has additional indexes since tangent has 4 components instead of 3 for
      * pos and norm
      *
-     * @param maxWeightsPerVert maximum number of weights per vertex
-     * @param mesh              the mesh
-     * @param offsetMatrices    the offsetMaytrices to apply
-     * @param tb                the tangent vertexBuffer
+     * @param mesh           the mesh
+     * @param offsetMatrices the offsetMatrices to apply
+     * @param tb             the tangent vertexBuffer
      */
     private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
         int maxWeightsPerVert = mesh.getMaxNumWeights();

+ 344 - 0
jme3-core/src/main/java/com/jme3/anim/TransformTrack.java

@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2009-2012 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.anim;
+
+import com.jme3.anim.interpolator.FrameInterpolator;
+import com.jme3.anim.tween.Tween;
+import com.jme3.animation.CompactQuaternionArray;
+import com.jme3.animation.CompactVector3Array;
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
+
+import java.io.IOException;
+
+/**
+ * Contains a list of transforms and times for each keyframe.
+ *
+ * @author Rémy Bouquet
+ */
+public abstract class TransformTrack implements Tween, JmeCloneable, Savable {
+
+    private double length;
+
+    /**
+     * Transforms and times for track.
+     */
+    private CompactVector3Array translations;
+    private CompactQuaternionArray rotations;
+    private CompactVector3Array scales;
+    private Transform transform = new Transform();
+    private Transform defaultTransform = new Transform();
+    private FrameInterpolator interpolator = FrameInterpolator.DEFAULT;
+    private float[] times;
+
+    /**
+     * Serialization-only. Do not use.
+     */
+    public TransformTrack() {
+    }
+
+    /**
+     * Creates a transform track for the given bone index
+     *
+     * @param times        a float array with the time of each frame
+     * @param translations the translation of the bone for each frame
+     * @param rotations    the rotation of the bone for each frame
+     * @param scales       the scale of the bone for each frame
+     */
+    public TransformTrack(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
+        this.setKeyframes(times, translations, rotations, scales);
+    }
+
+    /**
+     * Creates a bone track for the given bone index
+     *
+     * @param targetJointIndex the bone's index
+     */
+    public TransformTrack(int targetJointIndex) {
+        this();
+    }
+
+    /**
+     * return the array of rotations of this track
+     *
+     * @return
+     */
+    public Quaternion[] getRotations() {
+        return rotations.toObjectArray();
+    }
+
+    /**
+     * returns the array of scales for this track
+     *
+     * @return
+     */
+    public Vector3f[] getScales() {
+        return scales == null ? null : scales.toObjectArray();
+    }
+
+    /**
+     * returns the arrays of time for this track
+     *
+     * @return
+     */
+    public float[] getTimes() {
+        return times;
+    }
+
+    /**
+     * returns the array of translations of this track
+     *
+     * @return
+     */
+    public Vector3f[] getTranslations() {
+        return translations.toObjectArray();
+    }
+
+
+    /**
+     * Sets the keyframes times for this Joint track
+     *
+     * @param times the keyframes times
+     */
+    public void setTimes(float[] times) {
+        if (times.length == 0) {
+            throw new RuntimeException("TransformTrack with no keyframes!");
+        }
+        this.times = times;
+        length = times[times.length - 1] - times[0];
+    }
+
+    /**
+     * Set the translations for this joint track
+     *
+     * @param translations the translation of the bone for each frame
+     */
+    public void setKeyframesTranslation(Vector3f[] translations) {
+        if (times == null) {
+            throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
+        }
+        if (translations.length == 0) {
+            throw new RuntimeException("TransformTrack with no translation keyframes!");
+        }
+        this.translations = new CompactVector3Array();
+        this.translations.add(translations);
+        this.translations.freeze();
+
+        assert times != null && times.length == translations.length;
+    }
+
+    /**
+     * Set the scales for this joint track
+     *
+     * @param scales the scales of the bone for each frame
+     */
+    public void setKeyframesScale(Vector3f[] scales) {
+        if (times == null) {
+            throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
+        }
+        if (scales.length == 0) {
+            throw new RuntimeException("TransformTrack with no scale keyframes!");
+        }
+        this.scales = new CompactVector3Array();
+        this.scales.add(scales);
+        this.scales.freeze();
+
+        assert times != null && times.length == scales.length;
+    }
+
+    /**
+     * Set the rotations for this joint track
+     *
+     * @param rotations the rotations of the bone for each frame
+     */
+    public void setKeyframesRotation(Quaternion[] rotations) {
+        if (times == null) {
+            throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
+        }
+        if (rotations.length == 0) {
+            throw new RuntimeException("TransformTrack with no rotation keyframes!");
+        }
+        this.rotations = new CompactQuaternionArray();
+        this.rotations.add(rotations);
+        this.rotations.freeze();
+
+        assert times != null && times.length == rotations.length;
+    }
+
+
+    /**
+     * Set the translations, rotations and scales for this bone track
+     *
+     * @param times        a float array with the time of each frame
+     * @param translations the translation of the bone for each frame
+     * @param rotations    the rotation of the bone for each frame
+     * @param scales       the scale of the bone for each frame
+     */
+    public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
+        setTimes(times);
+        if (translations != null) {
+            setKeyframesTranslation(translations);
+        }
+        if (rotations != null) {
+            setKeyframesRotation(rotations);
+        }
+        if (scales != null) {
+            setKeyframesScale(scales);
+        }
+    }
+
+    @Override
+    public double getLength() {
+        return length;
+    }
+
+    @Override
+    public boolean interpolate(double t) {
+        float time = (float) t;
+
+        transform.set(defaultTransform);
+        int lastFrame = times.length - 1;
+        if (time < 0 || lastFrame == 0) {
+            if (translations != null) {
+                translations.get(0, transform.getTranslation());
+            }
+            if (rotations != null) {
+                rotations.get(0, transform.getRotation());
+            }
+            if (scales != null) {
+                scales.get(0, transform.getScale());
+            }
+            return true;
+        }
+
+        int startFrame = 0;
+        int endFrame = 1;
+        float blend = 0;
+        if (time >= times[lastFrame]) {
+            startFrame = lastFrame;
+
+            time = time - times[startFrame] + times[startFrame - 1];
+            blend = (time - times[startFrame - 1])
+                    / (times[startFrame] - times[startFrame - 1]);
+
+        } else {
+            // use lastFrame so we never overflow the array
+            int i;
+            for (i = 0; i < lastFrame && times[i] < time; i++) {
+                startFrame = i;
+                endFrame = i + 1;
+            }
+            blend = (time - times[startFrame])
+                    / (times[endFrame] - times[startFrame]);
+        }
+
+        Transform interpolated = interpolator.interpolate(blend, startFrame, translations, rotations, scales, times);
+
+        if (translations != null) {
+            transform.setTranslation(interpolated.getTranslation());
+        }
+        if (rotations != null) {
+            transform.setRotation(interpolated.getRotation());
+        }
+        if (scales != null) {
+            transform.setScale(interpolated.getScale());
+        }
+
+        return time < length;
+    }
+
+
+    public Transform getInterpolatedTransform() {
+        return transform;
+    }
+
+    public void setDefaultTransform(Transform transforms) {
+        defaultTransform.set(transforms);
+    }
+
+    public void setFrameInterpolator(FrameInterpolator interpolator) {
+        this.interpolator = interpolator;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(translations, "translations", null);
+        oc.write(rotations, "rotations", null);
+        oc.write(times, "times", null);
+        oc.write(scales, "scales", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        translations = (CompactVector3Array) ic.readSavable("translations", null);
+        rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
+        times = ic.readFloatArray("times", null);
+        scales = (CompactVector3Array) ic.readSavable("scales", null);
+    }
+
+    @Override
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException("Error cloning", e);
+        }
+    }
+
+    @Override
+    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);
+        }
+
+        setKeyframesTranslation(translations);
+        setKeyframesScale(scales);
+        setKeyframesRotation(rotations);
+        setFrameInterpolator(this.interpolator);
+    }
+}

+ 13 - 0
jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java

@@ -0,0 +1,13 @@
+package com.jme3.anim.interpolator;
+
+import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader;
+import static com.jme3.anim.interpolator.FrameInterpolator.TrackTimeReader;
+
+/**
+ * Created by nehon on 15/04/17.
+ */
+public abstract class AnimInterpolator<T> {
+
+    public abstract T interpolate(float t, int currentIndex, TrackDataReader<T> data, TrackTimeReader times, T store);
+
+}

+ 151 - 0
jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java

@@ -0,0 +1,151 @@
+package com.jme3.anim.interpolator;
+
+import com.jme3.math.*;
+
+import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader;
+import static com.jme3.anim.interpolator.FrameInterpolator.TrackTimeReader;
+
+/**
+ * Created by nehon on 15/04/17.
+ */
+public class AnimInterpolators {
+
+    //Rotation interpolators
+
+    public static final AnimInterpolator<Quaternion> NLerp = new AnimInterpolator<Quaternion>() {
+        private Quaternion next = new Quaternion();
+
+        @Override
+        public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
+            data.getEntryClamp(currentIndex, store);
+            data.getEntryClamp(currentIndex + 1, next);
+            store.nlerp(next, t);
+            return store;
+        }
+    };
+
+    public static final AnimInterpolator<Quaternion> SLerp = new AnimInterpolator<Quaternion>() {
+        private Quaternion next = new Quaternion();
+
+        @Override
+        public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
+            data.getEntryClamp(currentIndex, store);
+            data.getEntryClamp(currentIndex + 1, next);
+            //MathUtils.slerpNoInvert(store, next, t, store);
+            MathUtils.slerp(store, next, t, store);
+            return store;
+        }
+    };
+
+    public static final AnimInterpolator<Quaternion> SQuad = new AnimInterpolator<Quaternion>() {
+        private Quaternion a = new Quaternion();
+        private Quaternion b = new Quaternion();
+
+        private Quaternion q0 = new Quaternion();
+        private Quaternion q1 = new Quaternion();
+        private Quaternion q2 = new Quaternion();
+        private Quaternion q3 = new Quaternion();
+
+        @Override
+        public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
+            data.getEntryModSkip(currentIndex - 1, q0);
+            data.getEntryModSkip(currentIndex, q1);
+            data.getEntryModSkip(currentIndex + 1, q2);
+            data.getEntryModSkip(currentIndex + 2, q3);
+            MathUtils.squad(q0, q1, q2, q3, a, b, t, store);
+            return store;
+        }
+    };
+
+    //Position / Scale interpolators
+
+    public static final AnimInterpolator<Vector3f> LinearVec3f = new AnimInterpolator<Vector3f>() {
+        private Vector3f next = new Vector3f();
+
+        @Override
+        public Vector3f interpolate(float t, int currentIndex, TrackDataReader<Vector3f> data, TrackTimeReader times, Vector3f store) {
+            data.getEntryClamp(currentIndex, store);
+            data.getEntryClamp(currentIndex + 1, next);
+            store.interpolateLocal(next, t);
+            return store;
+        }
+    };
+
+    /**
+     * CatmullRom interpolation
+     */
+    public static final CatmullRomInterpolator CatmullRom = new CatmullRomInterpolator();
+
+    public static class CatmullRomInterpolator extends AnimInterpolator<Vector3f> {
+        private Vector3f p0 = new Vector3f();
+        private Vector3f p1 = new Vector3f();
+        private Vector3f p2 = new Vector3f();
+        private Vector3f p3 = new Vector3f();
+        private float tension = 0.7f;
+
+        public CatmullRomInterpolator(float tension) {
+            this.tension = tension;
+        }
+
+        public CatmullRomInterpolator() {
+        }
+
+        @Override
+        public Vector3f interpolate(float t, int currentIndex, TrackDataReader<Vector3f> data, TrackTimeReader times, Vector3f store) {
+            data.getEntryModSkip(currentIndex - 1, p0);
+            data.getEntryModSkip(currentIndex, p1);
+            data.getEntryModSkip(currentIndex + 1, p2);
+            data.getEntryModSkip(currentIndex + 2, p3);
+
+            FastMath.interpolateCatmullRom(t, tension, p0, p1, p2, p3, store);
+            return store;
+        }
+    }
+
+    //Time Interpolators
+
+    public static class TimeInterpolator extends AnimInterpolator<Float> {
+        private EaseFunction ease;
+
+        public TimeInterpolator(EaseFunction ease) {
+            this.ease = ease;
+        }
+
+        @Override
+        public Float interpolate(float t, int currentIndex, TrackDataReader<Float> data, TrackTimeReader times, Float store) {
+            return ease.apply(t);
+        }
+    }
+
+    //in
+    public static final TimeInterpolator easeInQuad = new TimeInterpolator(Easing.inQuad);
+    public static final TimeInterpolator easeInCubic = new TimeInterpolator(Easing.inCubic);
+    public static final TimeInterpolator easeInQuart = new TimeInterpolator(Easing.inQuart);
+    public static final TimeInterpolator easeInQuint = new TimeInterpolator(Easing.inQuint);
+    public static final TimeInterpolator easeInBounce = new TimeInterpolator(Easing.inBounce);
+    public static final TimeInterpolator easeInElastic = new TimeInterpolator(Easing.inElastic);
+
+    //out
+    public static final TimeInterpolator easeOutQuad = new TimeInterpolator(Easing.outQuad);
+    public static final TimeInterpolator easeOutCubic = new TimeInterpolator(Easing.outCubic);
+    public static final TimeInterpolator easeOutQuart = new TimeInterpolator(Easing.outQuart);
+    public static final TimeInterpolator easeOutQuint = new TimeInterpolator(Easing.outQuint);
+    public static final TimeInterpolator easeOutBounce = new TimeInterpolator(Easing.outBounce);
+    public static final TimeInterpolator easeOutElastic = new TimeInterpolator(Easing.outElastic);
+
+    //inout
+    public static final TimeInterpolator easeInOutQuad = new TimeInterpolator(Easing.inOutQuad);
+    public static final TimeInterpolator easeInOutCubic = new TimeInterpolator(Easing.inOutCubic);
+    public static final TimeInterpolator easeInOutQuart = new TimeInterpolator(Easing.inOutQuart);
+    public static final TimeInterpolator easeInOutQuint = new TimeInterpolator(Easing.inOutQuint);
+    public static final TimeInterpolator easeInOutBounce = new TimeInterpolator(Easing.inOutBounce);
+    public static final TimeInterpolator easeInOutElastic = new TimeInterpolator(Easing.inOutElastic);
+
+    //extra
+    public static final TimeInterpolator smoothStep = new TimeInterpolator(Easing.smoothStep);
+    public static final TimeInterpolator smootherStep = new TimeInterpolator(Easing.smootherStep);
+
+    public static final TimeInterpolator constant = new TimeInterpolator(Easing.constant);
+
+
+}

+ 121 - 0
jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java

@@ -0,0 +1,121 @@
+package com.jme3.anim.interpolator;
+
+import com.jme3.animation.*;
+import com.jme3.math.*;
+
+/**
+ * Created by nehon on 15/04/17.
+ */
+public class FrameInterpolator {
+
+    public static final FrameInterpolator DEFAULT = new FrameInterpolator();
+
+    private AnimInterpolator<Float> timeInterpolator;
+    private AnimInterpolator<Vector3f> translationInterpolator = AnimInterpolators.LinearVec3f;
+    private AnimInterpolator<Quaternion> rotationInterpolator = AnimInterpolators.NLerp;
+    private AnimInterpolator<Vector3f> scaleInterpolator = AnimInterpolators.LinearVec3f;
+
+    private TrackDataReader<Vector3f> translationReader = new TrackDataReader<>();
+    private TrackDataReader<Quaternion> rotationReader = new TrackDataReader<>();
+    private TrackDataReader<Vector3f> scaleReader = new TrackDataReader<>();
+    private TrackTimeReader timesReader = new TrackTimeReader();
+
+    private Transform transforms = new Transform();
+
+    public Transform interpolate(float t, int currentIndex, CompactVector3Array translations, CompactQuaternionArray rotations, CompactVector3Array scales, float[] times){
+        timesReader.setData(times);
+        if( timeInterpolator != null){
+            t = timeInterpolator.interpolate(t,currentIndex, null, timesReader, null );
+        }
+        if(translations != null) {
+            translationReader.setData(translations);
+            translationInterpolator.interpolate(t, currentIndex, translationReader, timesReader, transforms.getTranslation());
+        }
+        if(rotations != null) {
+            rotationReader.setData(rotations);
+            rotationInterpolator.interpolate(t, currentIndex, rotationReader, timesReader, transforms.getRotation());
+        }
+        if(scales != null){
+            scaleReader.setData(scales);
+            scaleInterpolator.interpolate(t, currentIndex, scaleReader, timesReader, transforms.getScale());
+        }
+        return transforms;
+    }
+
+    public void setTimeInterpolator(AnimInterpolator<Float> timeInterpolator) {
+        this.timeInterpolator = timeInterpolator;
+    }
+
+    public void setTranslationInterpolator(AnimInterpolator<Vector3f> translationInterpolator) {
+        this.translationInterpolator = translationInterpolator;
+    }
+
+    public void setRotationInterpolator(AnimInterpolator<Quaternion> rotationInterpolator) {
+        this.rotationInterpolator = rotationInterpolator;
+    }
+
+    public void setScaleInterpolator(AnimInterpolator<Vector3f> scaleInterpolator) {
+        this.scaleInterpolator = scaleInterpolator;
+    }
+
+
+    public static class TrackTimeReader {
+        private float[] data;
+
+        protected void setData(float[] data) {
+            this.data = data;
+        }
+
+        public float getEntry(int index) {
+            return data[mod(index, data.length)];
+        }
+
+        public int getLength() {
+            return data.length;
+        }
+    }
+
+    public static class TrackDataReader<T> {
+
+        private CompactArray<T> data;
+
+        protected void setData(CompactArray<T> data) {
+            this.data = data;
+        }
+
+        public T getEntryMod(int index, T store) {
+            return data.get(mod(index, data.getTotalObjectSize()), store);
+        }
+
+        public T getEntryClamp(int index, T store) {
+            index = (int) FastMath.clamp(index, 0, data.getTotalObjectSize() - 1);
+            return data.get(index, store);
+        }
+
+        public T getEntryModSkip(int index, T store) {
+            int total = data.getTotalObjectSize();
+            if (index == -1) {
+                index--;
+            } else if (index >= total) {
+                index++;
+            }
+
+            index = mod(index, total);
+
+
+            return data.get(index, store);
+        }
+    }
+
+    /**
+     * Euclidean modulo (cycle on 0,n instead of -n,0; 0,n)
+     *
+     * @param val
+     * @param n
+     * @return
+     */
+    private static int mod(int val, int n) {
+        return ((val % n) + n) % n;
+    }
+
+}

+ 110 - 0
jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java

@@ -0,0 +1,110 @@
+/*
+ * $Id$
+ * 
+ * Copyright (c) 2015, Simsilica, LLC
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions 
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright 
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. 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.
+ * 
+ * 3. Neither the name of the copyright holder 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 HOLDER 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.anim.tween;
+
+
+import com.jme3.export.*;
+
+import java.io.IOException;
+
+/**
+ * Base implementation of the Tween interface that provides
+ * default implementations of the getLength() and interopolate()
+ * methods that provide common tween clamping and bounds checking.
+ * Subclasses need only override the doInterpolate() method and
+ * the rest is handled for them.
+ *
+ * @author Paul Speed
+ */
+public abstract class AbstractTween implements Tween {
+
+    private double length;
+
+    protected AbstractTween(double length) {
+        this.length = length;
+    }
+
+    @Override
+    public double getLength() {
+        return length;
+    }
+
+    public void setLength(double length) {
+        this.length = length;
+    }
+
+    /**
+     * Default implementation clamps the time value, converts
+     * it to 0 to 1.0 based on getLength(), and calls doInterpolate().
+     */
+    @Override
+    public boolean interpolate(double t) {
+        if (t < 0) {
+            return true;
+        }
+
+        // Scale t to be between 0 and 1 for our length
+        if (length == 0) {
+            t = 1;
+        } else {
+            t = t / length;
+        }
+
+        boolean done = false;
+        if (t >= 1.0) {
+            t = 1.0;
+            done = true;
+        }
+        doInterpolate(t);
+        return !done;
+    }
+
+    protected abstract void doInterpolate(double t);
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(length, "length", 0);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        length = ic.readDouble("length", 0);
+    }
+}
+ 

+ 71 - 0
jme3-core/src/main/java/com/jme3/anim/tween/Tween.java

@@ -0,0 +1,71 @@
+/*
+ * $Id$
+ * 
+ * Copyright (c) 2015, Simsilica, LLC
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions 
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright 
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. 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.
+ * 
+ * 3. Neither the name of the copyright holder 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 HOLDER 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.anim.tween;
+
+
+import com.jme3.export.Savable;
+
+/**
+ * Represents some action that interpolates across input between 0
+ * and some length value.  (For example, movement, rotation, fading.)
+ * It's also possible to have zero length 'instant' tweens.
+ *
+ * @author Paul Speed
+ */
+public interface Tween extends Savable, Cloneable {
+
+    /**
+     * Returns the length of the tween.  If 't' represents time in
+     * seconds then this is the notional time in seconds that the tween
+     * will run.  Note: all of the caveats are because tweens may be
+     * externally scaled in such a way that 't' no longer represents
+     * actual time.
+     */
+    public double getLength();
+
+    /**
+     * Sets the implementation specific interpolation to the
+     * specified 'tween' value as a value in the range from 0 to
+     * getLength().  If the value is greater or equal to getLength()
+     * then it is internally clamped and the method returns false.
+     * If 't' is still in the tween's range then this method returns
+     * true.
+     */
+    public boolean interpolate(double t);
+
+}
+

+ 2 - 0
jme3-core/src/main/java/com/jme3/animation/AnimChannel.java

@@ -33,6 +33,7 @@ package com.jme3.animation;
 
 import com.jme3.math.FastMath;
 import com.jme3.util.TempVars;
+
 import java.util.BitSet;
 
 /**
@@ -46,6 +47,7 @@ import java.util.BitSet;
  * 
  * @author Kirill Vainer
  */
+@Deprecated
 public final class AnimChannel {
 
     private static final float DEFAULT_BLEND_TIME = 0.15f;

+ 2 - 0
jme3-core/src/main/java/com/jme3/animation/AnimControl.java

@@ -64,7 +64,9 @@ import java.util.Map;
  * 1) Morph/Pose animation
  *
  * @author Kirill Vainer
+ * @deprecated use {@link com.jme3.anim.AnimComposer}
  */
+@Deprecated
 public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable {
 
     /**

+ 1 - 0
jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java

@@ -37,6 +37,7 @@ package com.jme3.animation;
  * 
  * @author Kirill Vainer
  */
+@Deprecated
 public interface AnimEventListener {
 
     /**

+ 3 - 0
jme3-core/src/main/java/com/jme3/animation/Animation.java

@@ -37,13 +37,16 @@ import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.JmeCloneable;
+
 import java.io.IOException;
 
 /**
  * The animation class updates the animation target with the tracks of a given type.
  * 
  * @author Kirill Vainer, Marcin Roguski (Kaelthas)
+ * @deprecated use {@link com.jme3.anim.AnimClip}
  */
+@Deprecated
 public class Animation implements Savable, Cloneable, JmeCloneable {
 
     /** 

+ 3 - 5
jme3-core/src/main/java/com/jme3/animation/AudioTrack.java

@@ -32,15 +32,12 @@
 package com.jme3.animation;
 
 import com.jme3.audio.AudioNode;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
+import com.jme3.export.*;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.util.TempVars;
 import com.jme3.util.clone.Cloner;
-import com.jme3.util.clone.JmeCloneable;
+
 import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -62,6 +59,7 @@ import java.util.logging.Logger;
  *
  * @author Nehon
  */
+@Deprecated
 public class AudioTrack implements ClonableTrack {
 
     private static final Logger logger = Logger.getLogger(AudioTrack.class.getName());

+ 2 - 0
jme3-core/src/main/java/com/jme3/animation/Bone.java

@@ -67,7 +67,9 @@ import java.util.ArrayList;
  *
  * @author Kirill Vainer
  * @author Rémy Bouquet
+ * @deprecated use {@link com.jme3.anim.Joint}
  */
+@Deprecated
 public final class Bone implements Savable, JmeCloneable {
 
     // Version #2: Changed naming of transforms as they were misleading

+ 6 - 0
jme3-core/src/main/java/com/jme3/animation/BoneTrack.java

@@ -35,8 +35,12 @@ import com.jme3.export.*;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.util.TempVars;
+<<<<<<< HEAD
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.JmeCloneable;
+=======
+
+>>>>>>> Draft of the new animation system
 import java.io.IOException;
 import java.util.BitSet;
 
@@ -44,7 +48,9 @@ import java.util.BitSet;
  * Contains a list of transforms and times for each keyframe.
  * 
  * @author Kirill Vainer
+ * @deprecated use {@link com.jme3.anim.JointTrack}
  */
+@Deprecated
 public final class BoneTrack implements JmeCloneable, Track {
 
     /**

+ 1 - 0
jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java

@@ -44,6 +44,7 @@ import com.jme3.util.clone.JmeCloneable;
  *
  * @author Nehon
  */
+@Deprecated
 public interface ClonableTrack extends Track, JmeCloneable {
 
     /**

+ 10 - 5
jme3-core/src/main/java/com/jme3/animation/EffectTrack.java

@@ -32,10 +32,7 @@
 package com.jme3.animation;
 
 import com.jme3.effect.ParticleEmitter;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
+import com.jme3.export.*;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Node;
@@ -67,6 +64,7 @@ import java.util.logging.Logger;
  *
  * @author Nehon
  */
+@Deprecated
 public class EffectTrack implements ClonableTrack {
 
     private static final Logger logger = Logger.getLogger(EffectTrack.class.getName());
@@ -130,15 +128,17 @@ public class EffectTrack implements ClonableTrack {
         @Override
         protected void controlRender(RenderManager rm, ViewPort vp) {
         }
-    };
+    }
 
     //Anim listener that stops the Emmitter when the animation is finished or changed.
     private class OnEndListener implements AnimEventListener {
 
+        @Override
         public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
             stop();
         }
 
+        @Override
         public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
         }
     }
@@ -188,6 +188,7 @@ public class EffectTrack implements ClonableTrack {
      * @see Track#setTime(float, float, com.jme3.animation.AnimControl,
      * com.jme3.animation.AnimChannel, com.jme3.util.TempVars)
      */
+    @Override
     public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
 
         if (time >= length) {
@@ -233,6 +234,7 @@ public class EffectTrack implements ClonableTrack {
      *
      * @return length of the track
      */
+    @Override
     public float getLength() {
         return length;
     }
@@ -325,6 +327,7 @@ public class EffectTrack implements ClonableTrack {
         return null;
     }
 
+    @Override
     public void cleanUp() {
         TrackInfo t = (TrackInfo) emitter.getUserData("TrackInfo");
         t.getTracks().remove(this);
@@ -413,6 +416,7 @@ public class EffectTrack implements ClonableTrack {
      * @param ex exporter
      * @throws IOException exception
      */
+    @Override
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule out = ex.getCapsule(this);
         //reset the particle emission rate on the emitter before saving.
@@ -431,6 +435,7 @@ public class EffectTrack implements ClonableTrack {
      * @param im importer
      * @throws IOException Exception
      */
+    @Override
     public void read(JmeImporter im) throws IOException {
         InputCapsule in = im.getCapsule(this);
         this.particlesPerSeconds = in.readFloat("particlesPerSeconds", 0);

+ 1 - 0
jme3-core/src/main/java/com/jme3/animation/LoopMode.java

@@ -35,6 +35,7 @@ package com.jme3.animation;
  * <code>LoopMode</code> determines how animations repeat, or if they
  * do not repeat.
  */
+@Deprecated
 public enum LoopMode {
     /**
      * The animation will play repeatedly, when it reaches the end

+ 2 - 0
jme3-core/src/main/java/com/jme3/animation/Pose.java

@@ -34,12 +34,14 @@ package com.jme3.animation;
 import com.jme3.export.*;
 import com.jme3.math.Vector3f;
 import com.jme3.util.BufferUtils;
+
 import java.io.IOException;
 import java.nio.FloatBuffer;
 
 /**
  * A pose is a list of offsets that say where a mesh vertices should be for this pose.
  */
+@Deprecated
 public final class Pose implements Savable, Cloneable {
 
     private String name;

+ 5 - 1
jme3-core/src/main/java/com/jme3/animation/Skeleton.java

@@ -31,11 +31,13 @@
  */
 package com.jme3.animation;
 
+import com.jme3.anim.Armature;
 import com.jme3.export.*;
 import com.jme3.math.Matrix4f;
 import com.jme3.util.TempVars;
-import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -46,7 +48,9 @@ import java.util.List;
  * animated matrixes.
  * 
  * @author Kirill Vainer
+ * @deprecated use {@link Armature}
  */
+@Deprecated
 public final class Skeleton implements Savable, JmeCloneable {
 
     private Bone[] rootBones;

+ 3 - 0
jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java

@@ -31,6 +31,7 @@
  */
 package com.jme3.animation;
 
+import com.jme3.anim.SkinningControl;
 import com.jme3.export.*;
 import com.jme3.material.MatParamOverride;
 import com.jme3.math.FastMath;
@@ -57,7 +58,9 @@ import java.util.logging.Logger;
  * the mesh
  *
  * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
+ * @deprecated use {@link SkinningControl}
  */
+@Deprecated
 public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable {
 
     /**

+ 3 - 5
jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java

@@ -31,10 +31,7 @@
  */
 package com.jme3.animation;
 
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
+import com.jme3.export.*;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Spatial;
@@ -48,8 +45,9 @@ import java.io.IOException;
  * 
  * @author Marcin Roguski (Kaelthas)
  */
+@Deprecated
 public class SpatialTrack implements JmeCloneable, Track {
-    
+
     /** 
      * Translations of the track. 
      */

+ 1 - 0
jme3-core/src/main/java/com/jme3/animation/Track.java

@@ -34,6 +34,7 @@ package com.jme3.animation;
 import com.jme3.export.Savable;
 import com.jme3.util.TempVars;
 
+@Deprecated
 public interface Track extends Savable, Cloneable {
 
     /**

+ 3 - 5
jme3-core/src/main/java/com/jme3/animation/TrackInfo.java

@@ -31,13 +31,10 @@
  */
 package com.jme3.animation;
 
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.JmeCloneable;
+
 import java.io.IOException;
 import java.util.ArrayList;
 
@@ -50,6 +47,7 @@ import java.util.ArrayList;
  *
  * @author Nehon
  */
+@Deprecated
 public class TrackInfo implements Savable, JmeCloneable {
 
     ArrayList<Track> tracks = new ArrayList<Track>();

+ 13 - 0
jme3-core/src/main/java/com/jme3/math/EaseFunction.java

@@ -0,0 +1,13 @@
+package com.jme3.math;
+
+/**
+ * Created by Nehon on 26/03/2017.
+ */
+public interface EaseFunction {
+
+    /**
+     * @param value a value from 0 to 1. Passing a value out of this range will have unexpected behavior.
+     * @return
+     */
+    float apply(float value);
+}

+ 163 - 0
jme3-core/src/main/java/com/jme3/math/Easing.java

@@ -0,0 +1,163 @@
+package com.jme3.math;
+
+/**
+ * Expose several Easing function from Robert Penner
+ * Created by Nehon on 26/03/2017.
+ */
+public class Easing {
+
+
+    public static EaseFunction constant = new EaseFunction() {
+        @Override
+        public float apply(float value) {
+            return 0;
+        }
+    };
+    /**
+     * In
+     */
+    public static EaseFunction linear = new EaseFunction() {
+        @Override
+        public float apply(float value) {
+            return value;
+        }
+    };
+
+    public static EaseFunction inQuad = new EaseFunction() {
+        @Override
+        public float apply(float value) {
+            return value * value;
+        }
+    };
+
+    public static EaseFunction inCubic = new EaseFunction() {
+        @Override
+        public float apply(float value) {
+            return value * value * value;
+        }
+    };
+
+    public static EaseFunction inQuart = new EaseFunction() {
+        @Override
+        public float apply(float value) {
+            return value * value * value * value;
+        }
+    };
+
+    public static EaseFunction inQuint = new EaseFunction() {
+        @Override
+        public float apply(float value) {
+            return value * value * value * value * value;
+        }
+    };
+
+
+    /**
+     * Out Elastic and bounce
+     */
+    public static EaseFunction outElastic = new EaseFunction() {
+        @Override
+        public float apply(float value) {
+            return FastMath.pow(2f, -10f * value) * FastMath.sin((value - 0.3f / 4f) * (2f * FastMath.PI) / 0.3f) + 1f;
+        }
+    };
+
+    public static EaseFunction outBounce = new EaseFunction() {
+        @Override
+        public float apply(float value) {
+            if (value < (1f / 2.75f)) {
+                return (7.5625f * value * value);
+            } else if (value < (2f / 2.75f)) {
+                return (7.5625f * (value -= (1.5f / 2.75f)) * value + 0.75f);
+            } else if (value < (2.5 / 2.75)) {
+                return (7.5625f * (value -= (2.25f / 2.75f)) * value + 0.9375f);
+            } else {
+                return (7.5625f * (value -= (2.625f / 2.75f)) * value + 0.984375f);
+            }
+        }
+    };
+
+    /**
+     * In Elastic and bounce
+     */
+    public static EaseFunction inElastic = new Invert(outElastic);
+    public static EaseFunction inBounce = new Invert(outBounce);
+
+    /**
+     * Out
+     */
+    public static EaseFunction outQuad = new Invert(inQuad);
+    public static EaseFunction outCubic = new Invert(inCubic);
+    public static EaseFunction outQuart = new Invert(inQuart);
+    public static EaseFunction outQuint = new Invert(inQuint);
+
+    /**
+     * inOut
+     */
+    public static EaseFunction inOutQuad = new InOut(inQuad, outQuad);
+    public static EaseFunction inOutCubic = new InOut(inCubic, outCubic);
+    public static EaseFunction inOutQuart = new InOut(inQuart, outQuart);
+    public static EaseFunction inOutQuint = new InOut(inQuint, outQuint);
+    public static EaseFunction inOutElastic = new InOut(inElastic, outElastic);
+    public static EaseFunction inOutBounce = new InOut(inBounce, outBounce);
+
+
+    /**
+     * Extra functions
+     */
+
+    public static EaseFunction smoothStep = new EaseFunction() {
+        @Override
+        public float apply(float t) {
+            return t * t * (3f - 2f * t);
+        }
+    };
+
+    public static EaseFunction smootherStep = new EaseFunction() {
+        @Override
+        public float apply(float t) {
+            return t * t * t * (t * (t * 6f - 15f) + 10f);
+        }
+    };
+
+    /**
+     * An Ease function composed of 2 sb function for custom in and out easing
+     */
+    public static class InOut implements EaseFunction {
+
+        private EaseFunction in;
+        private EaseFunction out;
+
+        public InOut(EaseFunction in, EaseFunction out) {
+            this.in = in;
+            this.out = out;
+        }
+
+        @Override
+        public float apply(float value) {
+            if (value < 0.5) {
+                value = value * 2;
+                return inQuad.apply(value) / 2;
+            } else {
+                value = (value - 0.5f) * 2;
+                return outQuad.apply(value) / 2 + 0.5f;
+            }
+        }
+    }
+
+    private static class Invert implements EaseFunction {
+
+        private EaseFunction func;
+
+        public Invert(EaseFunction func) {
+            this.func = func;
+        }
+
+        @Override
+        public float apply(float value) {
+            return 1f - func.apply(1f - value);
+        }
+    }
+
+
+}

+ 165 - 0
jme3-core/src/main/java/com/jme3/math/MathUtils.java

@@ -0,0 +1,165 @@
+package com.jme3.math;
+
+/**
+ * Created by Nehon on 23/04/2017.
+ */
+public class MathUtils {
+
+    public static Quaternion log(Quaternion q, Quaternion store) {
+        float a = FastMath.acos(q.w);
+        float sina = FastMath.sin(a);
+
+        store.w = 0;
+        if (sina > 0) {
+            store.x = a * q.x / sina;
+            store.y = a * q.y / sina;
+            store.z = a * q.z / sina;
+        } else {
+            store.x = 0;
+            store.y = 0;
+            store.z = 0;
+        }
+        return store;
+    }
+
+    public static Quaternion exp(Quaternion q, Quaternion store) {
+
+        float len = FastMath.sqrt(q.x * q.x + q.y * q.y + q.z * q.z);
+        float sinLen = FastMath.sin(len);
+        float cosLen = FastMath.cos(len);
+
+        store.w = cosLen;
+        if (len > 0) {
+            store.x = sinLen * q.x / len;
+            store.y = sinLen * q.y / len;
+            store.z = sinLen * q.z / len;
+        } else {
+            store.x = 0;
+            store.y = 0;
+            store.z = 0;
+        }
+        return store;
+    }
+
+    //! This version of slerp, used by squad, does not check for theta > 90.
+    public static Quaternion slerpNoInvert(Quaternion q1, Quaternion q2, float t, Quaternion store) {
+        float dot = q1.dot(q2);
+
+        if (dot > -0.95f && dot < 0.95f) {
+            float angle = FastMath.acos(dot);
+            float sin1 = FastMath.sin(angle * (1 - t));
+            float sin2 = FastMath.sin(angle * t);
+            float sin3 = FastMath.sin(angle);
+            store.x = (q1.x * sin1 + q2.x * sin2) / sin3;
+            store.y = (q1.y * sin1 + q2.y * sin2) / sin3;
+            store.z = (q1.z * sin1 + q2.z * sin2) / sin3;
+            store.w = (q1.w * sin1 + q2.w * sin2) / sin3;
+            System.err.println("real slerp");
+        } else {
+            // if the angle is small, use linear interpolation
+            store.set(q1).nlerp(q2, t);
+            System.err.println("nlerp");
+        }
+        return store;
+    }
+
+    public static Quaternion slerp(Quaternion q1, Quaternion q2, float t, Quaternion store) {
+
+        float dot = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z)
+                + (q1.w * q2.w);
+
+        if (dot < 0.0f) {
+            // Negate the second quaternion and the result of the dot product
+            q2.x = -q2.x;
+            q2.y = -q2.y;
+            q2.z = -q2.z;
+            q2.w = -q2.w;
+            dot = -dot;
+        }
+
+        // Set the first and second scale for the interpolation
+        float scale0 = 1 - t;
+        float scale1 = t;
+
+        // Check if the angle between the 2 quaternions was big enough to
+        // warrant such calculations
+        if (dot < 0.9f) {// Get the angle between the 2 quaternions,
+            // and then store the sin() of that angle
+            float theta = FastMath.acos(dot);
+            float invSinTheta = 1f / FastMath.sin(theta);
+
+            // Calculate the scale for q1 and q2, according to the angle and
+            // it's sine value
+            scale0 = FastMath.sin((1 - t) * theta) * invSinTheta;
+            scale1 = FastMath.sin((t * theta)) * invSinTheta;
+
+            // Calculate the x, y, z and w values for the quaternion by using a
+            // special
+            // form of linear interpolation for quaternions.
+            store.x = (scale0 * q1.x) + (scale1 * q2.x);
+            store.y = (scale0 * q1.y) + (scale1 * q2.y);
+            store.z = (scale0 * q1.z) + (scale1 * q2.z);
+            store.w = (scale0 * q1.w) + (scale1 * q2.w);
+        } else {
+            store.x = (scale0 * q1.x) + (scale1 * q2.x);
+            store.y = (scale0 * q1.y) + (scale1 * q2.y);
+            store.z = (scale0 * q1.z) + (scale1 * q2.z);
+            store.w = (scale0 * q1.w) + (scale1 * q2.w);
+            store.normalizeLocal();
+        }
+        // Return the interpolated quaternion
+        return store;
+    }
+
+//    //! Given 3 quaternions, qn-1,qn and qn+1, calculate a control point to be used in spline interpolation
+//    private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) {
+//        store.set(-qn.x, -qn.y, -qn.z, qn.w);
+//        //store.set(qn).inverseLocal();
+//        tmp.set(store);
+//
+//        log(store.multLocal(qnm1), store);
+//        log(tmp.multLocal(qnp1), tmp);
+//        store.addLocal(tmp).multLocal(1f / -4f);
+//        exp(store, tmp);
+//        store.set(tmp).multLocal(qn);
+//
+//        return store.normalizeLocal();
+//        //return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp();
+//    }
+
+    //! Given 3 quaternions, qn-1,qn and qn+1, calculate a control point to be used in spline interpolation
+    private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) {
+        Quaternion invQn = new Quaternion(-qn.x, -qn.y, -qn.z, qn.w);
+
+
+        log(invQn.mult(qnp1), tmp);
+        log(invQn.mult(qnm1), store);
+        store.addLocal(tmp).multLocal(-1f / 4f);
+        exp(store, tmp);
+        store.set(qn).multLocal(tmp);
+
+        return store.normalizeLocal();
+        //return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp();
+    }
+
+
+    //! spherical cubic interpolation
+    public static Quaternion squad(Quaternion q0, Quaternion q1, Quaternion q2, Quaternion q3, Quaternion a, Quaternion b, float t, Quaternion store) {
+
+        spline(q0, q1, q2, a, store);
+        spline(q1, q2, q3, b, store);
+
+        slerp(a, b, t, store);
+        slerp(q1, q2, t, a);
+        return slerp(a, store, 2 * t * (1 - t), b);
+        //slerpNoInvert(a, b, t, store);
+        //slerpNoInvert(q1, q2, t, a);
+        //return slerpNoInvert(a, store, 2 * t * (1 - t), b);
+
+//        quaternion c = slerpNoInvert(q1, q2, t),
+//                d = slerpNoInvert(a, b, t);
+//        return slerpNoInvert(c, d, 2 * t * (1 - t));
+    }
+
+
+}

+ 2 - 1
jme3-core/src/main/java/com/jme3/math/Transform.java

@@ -181,7 +181,8 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
      * @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2.
      */
     public void interpolateTransforms(Transform t1, Transform t2, float delta) {
-        this.rot.slerp(t1.rot,t2.rot,delta);
+        t1.rot.nlerp(t2.rot, delta);
+        this.rot.set(t1.rot);
         this.translation.interpolateLocal(t1.translation,t2.translation,delta);
         this.scale.interpolateLocal(t1.scale,t2.scale,delta);
     }

+ 2 - 2
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureBone.java

@@ -32,8 +32,8 @@ package com.jme3.scene.debug.custom;
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import com.jme3.animation.Armature;
-import com.jme3.animation.Joint;
+import com.jme3.anim.Armature;
+import com.jme3.anim.Joint;
 import com.jme3.bounding.*;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;

+ 4 - 4
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java

@@ -4,7 +4,7 @@
  */
 package com.jme3.scene.debug.custom;
 
-import com.jme3.animation.*;
+import com.jme3.anim.*;
 import com.jme3.app.Application;
 import com.jme3.app.state.AbstractAppState;
 import com.jme3.app.state.AppStateManager;
@@ -55,9 +55,9 @@ public class ArmatureDebugAppState extends AbstractAppState {
         debugNode.updateGeometricState();
     }
 
-    public ArmatureDebugger addArmature(ArmatureControl armatureControl, boolean guessJointsOrientation) {
-        Armature armature = armatureControl.getArmature();
-        Spatial forSpatial = armatureControl.getSpatial();
+    public ArmatureDebugger addArmature(SkinningControl skinningControl, boolean guessJointsOrientation) {
+        Armature armature = skinningControl.getArmature();
+        Spatial forSpatial = skinningControl.getSpatial();
         return addArmature(armature, forSpatial, guessJointsOrientation);
     }
 

+ 5 - 3
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java

@@ -32,7 +32,9 @@ package com.jme3.scene.debug.custom;
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import com.jme3.animation.*;
+import com.jme3.anim.Armature;
+import com.jme3.anim.Joint;
+import com.jme3.animation.Bone;
 import com.jme3.asset.AssetManager;
 import com.jme3.material.Material;
 import com.jme3.math.ColorRGBA;
@@ -91,7 +93,7 @@ public class ArmatureDebugger extends BatchNode {
 
         interJointWires = new ArmatureInterJointsWire(armature, bonesLength, guessJointsOrientation);
         wires = new Geometry(name + "_interwires", interJointWires);
-        this.attachChild(wires);
+        //       this.attachChild(wires);
     }
 
     protected void initialize(AssetManager assetManager) {
@@ -152,7 +154,7 @@ public class ArmatureDebugger extends BatchNode {
         super.updateLogicalState(tpf);
         bones.updateGeometry();
         if (interJointWires != null) {
-            interJointWires.updateGeometry();
+            //        interJointWires.updateGeometry();
         }
     }
 

+ 2 - 2
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java

@@ -33,8 +33,8 @@ package com.jme3.scene.debug.custom;
  */
 
 
-import com.jme3.animation.Armature;
-import com.jme3.animation.Joint;
+import com.jme3.anim.Armature;
+import com.jme3.anim.Joint;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.VertexBuffer;

+ 8 - 7
jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java

@@ -43,11 +43,12 @@ import com.jme3.renderer.Limits;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
-import com.jme3.scene.debug.custom.SkeletonDebugAppState;
 
 import java.util.ArrayList;
 import java.util.List;
 
+//import com.jme3.scene.debug.custom.SkeletonDebugAppState;
+
 public class TestGltfLoading extends SimpleApplication {
 
     Node autoRotate = new Node("autoRotate");
@@ -74,8 +75,8 @@ public class TestGltfLoading extends SimpleApplication {
      */
     public void simpleInitApp() {
 
-        SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState();
-        getStateManager().attach(skeletonDebugAppState);
+//        SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState();
+//        getStateManager().attach(skeletonDebugAppState);
 
         String folder = System.getProperty("user.home");
         assetManager.registerLocator(folder, FileLocator.class);
@@ -112,7 +113,7 @@ public class TestGltfLoading extends SimpleApplication {
 //        loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1);
 //        loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1);
 //        loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1);
-//        loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f);
+        loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f);
 ////        loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f);
         //      loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f);
 //        loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f);
@@ -206,14 +207,14 @@ public class TestGltfLoading extends SimpleApplication {
             return;
         }
         ctrl.setHardwareSkinningPreferred(false);
-        getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
+        //getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
 //        AnimControl aCtrl = findControl(s, AnimControl.class);
 //        //ctrl.getSpatial().removeControl(ctrl);
 //        if (aCtrl == null) {
 //            return;
 //        }
-//        if (aCtrl.getSkeleton() != null) {
-//            getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getSkeleton(), aCtrl.getSpatial(), true);
+//        if (aCtrl.getArmature() != null) {
+//            getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getArmature(), aCtrl.getSpatial(), true);
 //        }
 
     }

+ 7 - 6
jme3-examples/src/main/java/jme3test/model/TestGltfLoading2.java

@@ -42,11 +42,12 @@ import com.jme3.renderer.Limits;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
-import com.jme3.scene.debug.custom.SkeletonDebugAppState;
 import com.jme3.scene.plugins.gltf.GltfModelKey;
 
 import java.util.*;
 
+//import com.jme3.scene.debug.custom.SkeletonDebugAppState;
+
 public class TestGltfLoading2 extends SimpleApplication {
 
     Node autoRotate = new Node("autoRotate");
@@ -73,8 +74,8 @@ public class TestGltfLoading2 extends SimpleApplication {
      */
     public void simpleInitApp() {
 
-        SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState();
-        getStateManager().attach(skeletonDebugAppState);
+//        SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState();
+//        getStateManager().attach(skeletonDebugAppState);
 
         // cam.setLocation(new Vector3f(4.0339394f, 2.645184f, 6.4627485f));
         // cam.setRotation(new Quaternion(-0.013950467f, 0.98604023f, -0.119502485f, -0.11510504f));
@@ -226,7 +227,7 @@ public class TestGltfLoading2 extends SimpleApplication {
         if (ctrl == null) {
             return;
         }
-        //System.err.println(ctrl.getSkeleton().toString());
+        //System.err.println(ctrl.getArmature().toString());
         //ctrl.setHardwareSkinningPreferred(false);
         // getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
 //        AnimControl aCtrl = findControl(s, AnimControl.class);
@@ -234,8 +235,8 @@ public class TestGltfLoading2 extends SimpleApplication {
 //        if (aCtrl == null) {
 //            return;
 //        }
-//        if (aCtrl.getSkeleton() != null) {
-//            getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getSkeleton(), aCtrl.getSpatial(), true);
+//        if (aCtrl.getArmature() != null) {
+//            getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getArmature(), aCtrl.getSpatial(), true);
 //        }
 
     }

+ 76 - 0
jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java

@@ -0,0 +1,76 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3test.model.anim;
+
+import com.jme3.system.Timer;
+
+/**
+ * @author Nehon
+ */
+public class EraseTimer extends Timer {
+
+
+    //private static final long TIMER_RESOLUTION = 1000L;
+    //private static final float INVERSE_TIMER_RESOLUTION = 1f/1000L;
+    private static final long TIMER_RESOLUTION = 1000000000L;
+    private static final float INVERSE_TIMER_RESOLUTION = 1f / 1000000000L;
+
+    private long startTime;
+    private long previousTime;
+    private float tpf;
+    private float fps;
+
+    public EraseTimer() {
+        //startTime = System.currentTimeMillis();
+        startTime = System.nanoTime();
+    }
+
+    /**
+     * Returns the time in seconds. The timer starts
+     * at 0.0 seconds.
+     *
+     * @return the current time in seconds
+     */
+    @Override
+    public float getTimeInSeconds() {
+        return getTime() * INVERSE_TIMER_RESOLUTION;
+    }
+
+    public long getTime() {
+        //return System.currentTimeMillis() - startTime;
+        return System.nanoTime() - startTime;
+    }
+
+    public long getResolution() {
+        return TIMER_RESOLUTION;
+    }
+
+    public float getFrameRate() {
+        return fps;
+    }
+
+    public float getTimePerFrame() {
+        return tpf;
+    }
+
+    public void update() {
+        tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION);
+        if (tpf >= 0.2) {
+            //the frame lasted more than 200ms we erase its time to 16ms.
+            tpf = 0.016666f;
+        } else {
+            fps = 1.0f / tpf;
+        }
+        previousTime = getTime();
+    }
+
+    public void reset() {
+        //startTime = System.currentTimeMillis();
+        startTime = System.nanoTime();
+        previousTime = getTime();
+    }
+
+
+}

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

@@ -1,15 +1,22 @@
 package jme3test.model.anim;
 
-import com.jme3.animation.*;
+import com.jme3.anim.*;
 import com.jme3.app.ChaseCameraAppState;
 import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
 import com.jme3.light.DirectionalLight;
 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.util.TangentBinormalGenerator;
 
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
 /**
  * Created by Nehon on 18/12/2017.
  */
@@ -25,6 +32,7 @@ public class TestArmature extends SimpleApplication {
 
     @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);
@@ -34,20 +42,61 @@ public class TestArmature extends SimpleApplication {
         j2 = new Joint("Joint_2");
         root.addChild(j1);
         j1.addChild(j2);
-        j1.setLocalTranslation(new Vector3f(0, 0.5f, 0));
-        j1.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI * 0.3f, Vector3f.UNIT_Z));
-        j2.setLocalTranslation(new Vector3f(0, 0.2f, 0));
+        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.2f));
         Joint[] joints = new Joint[]{root, j1, j2};
 
-        Armature armature = new Armature(joints);
+        final Armature armature = new Armature(joints);
         armature.setBindPose();
 
-        ArmatureControl ac = new ArmatureControl(armature);
+        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(2, 2, 2),
+                new Vector3f(1, 1, 1),
+        };
+        Vector3f[] scales2 = new Vector3f[]{
+                new Vector3f(1, 1, 1),
+                new Vector3f(0.5f, 0.5f, 0.5f),
+                new Vector3f(1, 1, 1),
+        };
+
+        JointTrack track1 = new JointTrack(j1, times, null, rotations, null);
+        JointTrack track2 = new JointTrack(j2, times, null, rotations, null);
+        clip.addTrack(track1);
+        clip.addTrack(track2);
+
+        final AnimComposer composer = new AnimComposer();
+        composer.addAnimClip(clip);
+
+        SkinningControl ac = new SkinningControl(armature);
+        ac.setHardwareSkinningPreferred(false);
         Node node = new Node("Test Armature");
+
         rootNode.attachChild(node);
 
+        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);
 
+        composer.setCurrentAnimClip("anim");
+
         ArmatureDebugAppState debugAppState = new ArmatureDebugAppState();
         debugAppState.addArmature(ac, true);
         stateManager.attach(debugAppState);
@@ -71,6 +120,23 @@ public class TestArmature extends SimpleApplication {
         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) {
+                    play = false;
+                    composer.reset();
+                    armature.resetToBindPose();
+
+                } else {
+                    play = true;
+                    composer.setCurrentAnimClip("anim");
+                }
+            }
+        }, "bind");
     }
 
 
@@ -99,14 +165,70 @@ public class TestArmature extends SimpleApplication {
         });
     }
 
+    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();
+        //the mesh has some skinning let's create needed buffers for HW skinning
+        //creating empty buffers for HW skinning
+        //the buffers will be setup if ever used.
+        VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight);
+        VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex);
+        //setting usage to cpuOnly so that the buffer is not send empty to the GPU
+        indicesHW.setUsage(VertexBuffer.Usage.CpuOnly);
+        weightsHW.setUsage(VertexBuffer.Usage.CpuOnly);
+        c.setBuffer(weightsHW);
+        c.setBuffer(indicesHW);
+        c.generateBindPose();
+
+        c.prepareForAnim(false);
+
+        return c;
+    }
+
+
     float time = 0;
+    boolean play = true;
 
     @Override
     public void simpleUpdate(float tpf) {
-        time += tpf;
-        float rot = FastMath.sin(time);
-        j1.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI * rot, Vector3f.UNIT_Z));
-        j2.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI * rot, Vector3f.UNIT_Z));
+
+
+//        if (play == false) {
+//            return;
+//        }
+//        time += tpf;
+//        float rot = FastMath.sin(time);
+//        j1.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI * rot, Vector3f.UNIT_Z));
+//        j2.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI * rot, Vector3f.UNIT_Z));
 
     }
 }