Преглед на файлове

Hardware Morph animation implementation and glTF loading

Rémy Bouquet преди 7 години
родител
ревизия
42215f4890
променени са 31 файла, в които са добавени 1442 реда и са изтрити 128 реда
  1. 5 5
      jme3-core/src/main/java/com/jme3/anim/AnimClip.java
  2. 12 0
      jme3-core/src/main/java/com/jme3/anim/AnimTrack.java
  3. 165 0
      jme3-core/src/main/java/com/jme3/anim/MorphControl.java
  4. 217 0
      jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
  5. 2 11
      jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
  6. 95 0
      jme3-core/src/main/java/com/jme3/anim/Weights.java
  7. 0 2
      jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java
  8. 15 0
      jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java
  9. 37 14
      jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
  10. 5 1
      jme3-core/src/main/java/com/jme3/animation/CompactArray.java
  11. 100 0
      jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java
  12. 105 22
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  13. 54 12
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  14. 40 0
      jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java
  15. 17 0
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  16. 10 0
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert
  17. 17 0
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  18. 12 5
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert
  19. 10 0
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  20. 32 17
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  21. 6 0
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert
  22. 1 1
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag
  23. 5 2
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
  24. 7 1
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert
  25. 212 0
      jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib
  26. 35 9
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  27. 2 2
      jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java
  28. 121 0
      jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java
  29. 91 21
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  30. 2 0
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
  31. 10 3
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java

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

@@ -16,7 +16,7 @@ public class AnimClip implements JmeCloneable, Savable {
     private String name;
     private double length;
 
-    private TransformTrack[] tracks;
+    private AnimTrack[] tracks;
 
     public AnimClip() {
     }
@@ -25,9 +25,9 @@ public class AnimClip implements JmeCloneable, Savable {
         this.name = name;
     }
 
-    public void setTracks(TransformTrack[] tracks) {
+    public void setTracks(AnimTrack[] tracks) {
         this.tracks = tracks;
-        for (TransformTrack track : tracks) {
+        for (AnimTrack track : tracks) {
             if (track.getLength() > length) {
                 length = track.getLength();
             }
@@ -44,7 +44,7 @@ public class AnimClip implements JmeCloneable, Savable {
     }
 
 
-    public TransformTrack[] getTracks() {
+    public AnimTrack[] getTracks() {
         return tracks;
     }
 
@@ -59,7 +59,7 @@ public class AnimClip implements JmeCloneable, Savable {
 
     @Override
     public void cloneFields(Cloner cloner, Object original) {
-        TransformTrack[] newTracks = new TransformTrack[tracks.length];
+        AnimTrack[] newTracks = new AnimTrack[tracks.length];
         for (int i = 0; i < tracks.length; i++) {
             newTracks[i] = (cloner.clone(tracks[i]));
         }

+ 12 - 0
jme3-core/src/main/java/com/jme3/anim/AnimTrack.java

@@ -0,0 +1,12 @@
+package com.jme3.anim;
+
+import com.jme3.export.Savable;
+import com.jme3.util.clone.JmeCloneable;
+
+public interface AnimTrack<T> extends Savable, JmeCloneable {
+
+    public void getDataAtTime(double time, T store);
+    public double getLength();
+
+
+}

+ 165 - 0
jme3-core/src/main/java/com/jme3/anim/MorphControl.java

@@ -0,0 +1,165 @@
+package com.jme3.anim;
+
+import com.jme3.material.*;
+import com.jme3.renderer.*;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.SceneGraphVisitorAdapter;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.mesh.MorphTarget;
+import com.jme3.shader.VarType;
+import com.jme3.util.SafeArrayList;
+
+import java.nio.FloatBuffer;
+
+/**
+ * A control that handle morph animation for Position, Normal and Tangent buffers.
+ * All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers.
+ * If you want to use other types of buffers you will need a custom MorphControl and a custom shader.
+ * @author Rémy Bouquet
+ */
+public class MorphControl extends AbstractControl {
+
+    private static final int MAX_MORPH_BUFFERS = 14;
+    private final static float MIN_WEIGHT = 0.005f;
+
+    private SafeArrayList<Geometry> targets = new SafeArrayList<>(Geometry.class);
+    private TargetLocator targetLocator = new TargetLocator();
+
+    private boolean approximateTangents = true;
+    private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null);
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        // gathering geometries in the sub graph.
+        // This must be done in the update phase as the gathering might add a matparam override
+        targets.clear();
+        this.spatial.depthFirstTraversal(targetLocator);
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+        for (Geometry target : targets) {
+            Mesh mesh = target.getMesh();
+            if (!mesh.isDirtyMorph()) {
+                continue;
+            }
+            int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer());
+            Material m = target.getMaterial();
+
+            float weights[] = mesh.getMorphState();
+            MorphTarget morphTargets[] = mesh.getMorphTargets();
+            float matWeights[];
+            MatParam param = m.getParam("MorphWeights");
+
+            //Number of buffer to handle for each morph target
+            int targetNumBuffers = getTargetNumBuffers(morphTargets[0]);
+            // compute the max number of targets to send to the GPU
+            int maxGPUTargets = Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS) / targetNumBuffers;
+            if (param == null) {
+                matWeights = new float[maxGPUTargets];
+                m.setParam("MorphWeights", VarType.FloatArray, matWeights);
+            } else {
+                matWeights = (float[]) param.getValue();
+            }
+
+            // setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define.
+            m.setInt("NumberOfMorphTargets", maxGPUTargets);
+            m.setInt("NumberOfTargetsBuffers", targetNumBuffers);
+
+            int nbGPUTargets = 0;
+            int nbCPUBuffers = 0;
+            int boundBufferIdx = 0;
+            for (int i = 0; i < morphTargets.length; i++) {
+                if (weights[i] < MIN_WEIGHT) {
+                    continue;
+                }
+                if (nbGPUTargets >= maxGPUTargets) {
+                    //TODO we should fallback to CPU there.
+                    nbCPUBuffers++;
+                    continue;
+                }
+                int start = VertexBuffer.Type.MorphTarget0.ordinal();
+                MorphTarget t = morphTargets[i];
+                if (targetNumBuffers >= 1) {
+                    activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position));
+                    boundBufferIdx++;
+                }
+                if (targetNumBuffers >= 2) {
+                    activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Normal));
+                    boundBufferIdx++;
+                }
+                if (!approximateTangents && targetNumBuffers == 3) {
+                    activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent));
+                    boundBufferIdx++;
+                }
+                matWeights[nbGPUTargets] = weights[i];
+                nbGPUTargets++;
+
+            }
+            if (nbGPUTargets < matWeights.length) {
+                for (int i = nbGPUTargets; i < matWeights.length; i++) {
+                    matWeights[i] = 0;
+                }
+            }
+        }
+    }
+
+    private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) {
+        mesh.setBuffer(VertexBuffer.Type.values()[start + idx], 3, b);
+    }
+
+    private int getTargetNumBuffers(MorphTarget morphTarget) {
+        int num = 0;
+        if (morphTarget.getBuffer(VertexBuffer.Type.Position) != null) num++;
+        if (morphTarget.getBuffer(VertexBuffer.Type.Normal) != null) num++;
+
+        // if tangents are not needed we don't count the tangent buffer
+        if (!approximateTangents && morphTarget.getBuffer(VertexBuffer.Type.Tangent) != null) {
+            num++;
+        }
+        return num;
+    }
+
+    private int getRemainingBuffers(Mesh mesh, Renderer renderer) {
+        int nbUsedBuffers = 0;
+        for (VertexBuffer vb : mesh.getBufferList().getArray()) {
+            boolean isMorphBuffer = vb.getBufferType().ordinal() >= VertexBuffer.Type.MorphTarget0.ordinal() && vb.getBufferType().ordinal() <= VertexBuffer.Type.MorphTarget9.ordinal();
+            if (vb.getBufferType() == VertexBuffer.Type.Index || isMorphBuffer) continue;
+            if (vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
+                nbUsedBuffers++;
+            }
+        }
+        return renderer.getLimits().get(Limits.VertexAttributes) - nbUsedBuffers;
+    }
+
+    public void setApproximateTangents(boolean approximateTangents) {
+        this.approximateTangents = approximateTangents;
+    }
+
+    public boolean isApproximateTangents() {
+        return approximateTangents;
+    }
+
+    private class TargetLocator extends SceneGraphVisitorAdapter {
+        @Override
+        public void visit(Geometry geom) {
+            MatParam p = geom.getMaterial().getMaterialDef().getMaterialParam("MorphWeights");
+            if (p == null) {
+                return;
+            }
+            Mesh mesh = geom.getMesh();
+            if (mesh != null && mesh.hasMorphTargets()) {
+                targets.add(geom);
+                // If the mesh is in a subgraph of a node with a SkinningControl it might have hardware skinning activated through mat param override even if it's not skinned.
+                // this code makes sure that if the mesh has no hardware skinning buffers hardware skinning won't be activated.
+                // this is important, because if HW skinning is activated the shader will declare 2 additional useless attributes,
+                // and we desperately need all the attributes we can find for Morph animation.
+                if (mesh.getBuffer(VertexBuffer.Type.HWBoneIndex) == null && !geom.getLocalMatParamOverrides().contains(nullNumberOfBones)) {
+                    geom.addMatParamOverride(nullNumberOfBones);
+                }
+            }
+        }
+    }
+}

+ 217 - 0
jme3-core/src/main/java/com/jme3/anim/MorphTrack.java

@@ -0,0 +1,217 @@
+/*
+ * 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.animation.*;
+import com.jme3.export.*;
+import com.jme3.scene.Geometry;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
+
+import java.io.IOException;
+
+/**
+ * Contains a list of weights and times for each keyframe.
+ *
+ * @author Rémy Bouquet
+ */
+public class MorphTrack implements AnimTrack<float[]> {
+
+    private double length;
+    private Geometry target;
+
+    /**
+     * Weights and times for track.
+     */
+    private float[] weights;
+    private FrameInterpolator interpolator = FrameInterpolator.DEFAULT;
+    private float[] times;
+    private int nbMorphTargets;
+
+    /**
+     * Serialization-only. Do not use.
+     */
+    public MorphTrack() {
+    }
+
+    /**
+     * Creates a morph track with the given Geometry as a target
+     *
+     * @param times        a float array with the time of each frame
+     * @param weights       the morphs for each frames
+     */
+    public MorphTrack(Geometry target, float[] times, float[] weights, int nbMorphTargets) {
+        this.target = target;
+        this.nbMorphTargets = nbMorphTargets;
+        this.setKeyframes(times, weights);
+    }
+
+    /**
+     * return the array of weights of this track
+     *
+     * @return
+     */
+    public float[] getWeights() {
+        return weights;
+    }
+
+    /**
+     * returns the arrays of time for this track
+     *
+     * @return
+     */
+    public float[] getTimes() {
+        return times;
+    }
+
+    /**
+     * 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 weight for this morph track
+     *
+     * @param times        a float array with the time of each frame
+     * @param weights      the weights of the morphs for each frame
+
+     */
+    public void setKeyframes(float[] times, float[] weights) {
+        setTimes(times);
+        if (weights != null) {
+            if (times == null) {
+                throw new RuntimeException("MorphTrack doesn't have any time for key frames, please call setTimes first");
+            }
+
+            this.weights = weights;
+
+            assert times != null && times.length == weights.length;
+        }
+    }
+
+    @Override
+    public double getLength() {
+        return length;
+    }
+
+    @Override
+    public void getDataAtTime(double t, float[] store) {
+        float time = (float) t;
+
+        int lastFrame = times.length - 1;
+        if (time < 0 || lastFrame == 0) {
+            if (weights != null) {
+                System.arraycopy(weights,0,store,0, nbMorphTargets);
+            }
+            return;
+        }
+
+        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]);
+        }
+
+        interpolator.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store);
+    }
+
+    public void setFrameInterpolator(FrameInterpolator interpolator) {
+        this.interpolator = interpolator;
+    }
+
+    public Geometry getTarget() {
+        return target;
+    }
+
+    public void setTarget(Geometry target) {
+        this.target = target;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(weights, "weights", null);
+        oc.write(times, "times", null);
+        oc.write(target, "target", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        weights = ic.readFloatArray("weights", null);
+        times = ic.readFloatArray("times", null);
+        target = (Geometry) ic.readSavable("target", null);
+        setTimes(times);
+    }
+
+    @Override
+    public Object jmeClone() {
+        try {
+            MorphTrack clone = (MorphTrack) super.clone();
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public void cloneFields(Cloner cloner, Object original) {
+        this.target = cloner.clone(target);
+    }
+
+
+}

+ 2 - 11
jme3-core/src/main/java/com/jme3/anim/TransformTrack.java

@@ -48,7 +48,7 @@ import java.io.IOException;
  *
  * @author Rémy Bouquet
  */
-public class TransformTrack implements JmeCloneable, Savable {
+public class TransformTrack implements AnimTrack<Transform> {
 
     private double length;
     private HasLocalTransform target;
@@ -81,15 +81,6 @@ public class TransformTrack implements JmeCloneable, Savable {
         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
      *
@@ -223,7 +214,7 @@ public class TransformTrack implements JmeCloneable, Savable {
         return length;
     }
 
-    public void getTransformAtTime(double t, Transform transform) {
+    public void getDataAtTime(double t, Transform transform) {
         float time = (float) t;
 
         int lastFrame = times.length - 1;

+ 95 - 0
jme3-core/src/main/java/com/jme3/anim/Weights.java

@@ -0,0 +1,95 @@
+package com.jme3.anim;
+
+import java.util.ArrayList;
+
+public class Weights {//}  extends Savable, JmeCloneable{
+
+
+    private final static float MIN_WEIGHT = 0.005f;
+
+    private int[] indices;
+    private float[] data;
+    private int size;
+
+    public Weights(float[] array, int start, int length) {
+        ArrayList<Float> list = new ArrayList<>();
+        ArrayList<Integer> idx = new ArrayList<>();
+
+        for (int i = start; i < length; i++) {
+            float val = array[i];
+            if (val > MIN_WEIGHT) {
+                list.add(val);
+                idx.add(i);
+            }
+        }
+        size = list.size();
+        data = new float[size];
+        indices = new int[size];
+        for (int i = 0; i < size; i++) {
+            data[i] = list.get(i);
+            indices[i] = idx.get(i);
+        }
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    //    public Weights(float[] array, int start, int length) {
+//        LinkedList<Float> list = new LinkedList<>();
+//        LinkedList<Integer> idx = new LinkedList<>();
+//        for (int i = start; i < length; i++) {
+//            float val = array[i];
+//            if (val > MIN_WEIGHT) {
+//                int index = insert(list, val);
+//                if (idx.size() < index) {
+//                    idx.add(i);
+//                } else {
+//                    idx.add(index, i);
+//                }
+//            }
+//        }
+//        data = new float[list.size()];
+//        for (int i = 0; i < data.length; i++) {
+//            data[i] = list.get(i);
+//        }
+//
+//        indices = new int[idx.size()];
+//        for (int i = 0; i < indices.length; i++) {
+//            indices[i] = idx.get(i);
+//        }
+//    }
+//
+//    private int insert(LinkedList<Float> list, float value) {
+//        for (int i = 0; i < list.size(); i++) {
+//            float w = list.get(i);
+//            if (value > w) {
+//                list.add(i, value);
+//                return i;
+//            }
+//        }
+//
+//        list.add(value);
+//        return list.size();
+//    }
+
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder();
+        for (int i = 0; i < indices.length; i++) {
+            b.append(indices[i]).append(",");
+        }
+        b.append("\n");
+        for (int i = 0; i < data.length; i++) {
+            b.append(data[i]).append(",");
+        }
+        return b.toString();
+    }
+
+    public static void main(String... args) {
+        // 6 7 4 8
+        float values[] = {0, 0, 0, 0, 0.5f, 0.001f, 0.7f, 0.6f, 0.2f, 0, 0, 0};
+        Weights w = new Weights(values, 0, values.length);
+        System.err.println(w);
+    }
+}

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

@@ -58,7 +58,6 @@ public class AnimInterpolators {
     };
 
     //Position / Scale interpolators
-
     public static final AnimInterpolator<Vector3f> LinearVec3f = new AnimInterpolator<Vector3f>() {
         private Vector3f next = new Vector3f();
 
@@ -70,7 +69,6 @@ public class AnimInterpolators {
             return store;
         }
     };
-
     /**
      * CatmullRom interpolation
      */

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

@@ -20,6 +20,7 @@ public class FrameInterpolator {
     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){
@@ -42,6 +43,20 @@ public class FrameInterpolator {
         return transforms;
     }
 
+    public void interpolateWeights(float t, int currentIndex, float[] weights, int nbMorphTargets, float[] store) {
+        int start = currentIndex * nbMorphTargets;
+        for (int i = 0; i < nbMorphTargets; i++) {
+            int current = start + i;
+            int next = current + nbMorphTargets;
+            if (next >= weights.length) {
+                next = current;
+            }
+
+            float val =  FastMath.interpolateLinear(t, weights[current], weights[next]);
+            store[i] = val;
+        }
+    }
+
     public void setTimeInterpolator(AnimInterpolator<Float> timeInterpolator) {
         this.timeInterpolator = timeInterpolator;
     }

+ 37 - 14
jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java

@@ -1,10 +1,10 @@
 package com.jme3.anim.tween.action;
 
-import com.jme3.anim.AnimClip;
-import com.jme3.anim.TransformTrack;
+import com.jme3.anim.*;
 import com.jme3.anim.tween.AbstractTween;
 import com.jme3.anim.util.HasLocalTransform;
 import com.jme3.math.Transform;
+import com.jme3.scene.Geometry;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -22,20 +22,41 @@ public class ClipAction extends BlendableAction {
 
     @Override
     public void doInterpolate(double t) {
-        TransformTrack[] tracks = clip.getTracks();
-        for (TransformTrack track : tracks) {
-            HasLocalTransform target = track.getTarget();
-            transform.set(target.getLocalTransform());
-            track.getTransformAtTime(t, transform);
-
-            if (collectTransformDelegate != null) {
-                collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
-            } else {
-                this.collectTransform(target, transform, getTransitionWeight(), this);
+        AnimTrack[] tracks = clip.getTracks();
+        for (AnimTrack track : tracks) {
+            if (track instanceof TransformTrack) {
+                interpolateTransformTrack(t, (TransformTrack) track);
+            } else if (track instanceof MorphTrack) {
+                interpolateMorphTrack(t, (MorphTrack) track);
             }
         }
     }
 
+    private void interpolateTransformTrack(double t, TransformTrack track) {
+        HasLocalTransform target = track.getTarget();
+        transform.set(target.getLocalTransform());
+        track.getDataAtTime(t, transform);
+
+        if (collectTransformDelegate != null) {
+            collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
+        } else {
+            this.collectTransform(target, transform, getTransitionWeight(), this);
+        }
+    }
+    private void interpolateMorphTrack(double t, MorphTrack track) {
+        Geometry target = track.getTarget();
+        float[] weights = new float[target.getMesh().getMorphTargets().length];
+        track.getDataAtTime(t, weights);
+        target.getMesh().setMorphState(weights);
+
+
+//        if (collectTransformDelegate != null) {
+//            collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
+//        } else {
+//            this.collectTransform(target, transform, getTransitionWeight(), this);
+//        }
+    }
+
     public void reset() {
 
     }
@@ -43,8 +64,10 @@ public class ClipAction extends BlendableAction {
     @Override
     public Collection<HasLocalTransform> getTargets() {
         List<HasLocalTransform> targets = new ArrayList<>(clip.getTracks().length);
-        for (TransformTrack track : clip.getTracks()) {
-            targets.add(track.getTarget());
+        for (AnimTrack track : clip.getTracks()) {
+            if (track instanceof TransformTrack) {
+                targets.add(((TransformTrack) track).getTarget());
+            }
         }
         return targets;
     }

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

@@ -44,7 +44,7 @@ import java.util.Map;
  */
 public abstract class CompactArray<T> implements JmeCloneable {
 
-    private Map<T, Integer> indexPool = new HashMap<T, Integer>();
+    protected Map<T, Integer> indexPool = new HashMap<T, Integer>();
     protected int[] index;
     protected float[] array;
     private boolean invalid;
@@ -114,6 +114,10 @@ public abstract class CompactArray<T> implements JmeCloneable {
         indexPool.clear();
     }
 
+    protected void setInvalid(boolean invalid) {
+        this.invalid = invalid;
+    }
+
     /**
      * @param index
      * @param value

+ 100 - 0
jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java

@@ -0,0 +1,100 @@
+/*
+ * 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.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Vector3f;
+
+import java.io.IOException;
+
+/**
+ * Serialize and compress Float by indexing similar values
+ * @author Lim, YongHoon
+ */
+public class CompactFloatArray extends CompactArray<Float> implements Savable {
+
+    /**
+     * Creates a compact vector array
+     */
+    public CompactFloatArray() {
+    }
+
+    /**
+     * creates a compact vector array
+     * @param dataArray the data array
+     * @param index the indices
+     */
+    public CompactFloatArray(float[] dataArray, int[] index) {
+        super(dataArray, index);
+    }
+
+    @Override
+    protected final int getTupleSize() {
+        return 1;
+    }
+
+    @Override
+    protected final Class<Float> getElementClass() {
+        return Float.class;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        serialize();
+        OutputCapsule out = ex.getCapsule(this);
+        out.write(array, "array", null);
+        out.write(index, "index", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule in = im.getCapsule(this);
+        array = in.readFloatArray("array", null);
+        index = in.readIntArray("index", null);
+    }
+
+    public void fill(int startIndex, float[] store ){
+        for (int i = 0; i < store.length; i++) {
+            store[i] = get(startIndex + i, null);
+        }
+    }
+    
+    @Override
+    protected void serialize(int i, Float data) {
+        array[i] = data;
+    }
+
+    @Override
+    protected Float deserialize(int i, Float store) {
+        return array[i];
+    }
+}

+ 105 - 22
jme3-core/src/main/java/com/jme3/scene/Mesh.java

@@ -50,6 +50,7 @@ import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.nio.*;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * <code>Mesh</code> is used to store rendering data.
@@ -164,8 +165,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
 
     private CollisionData collisionTree = null;
 
-    private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
-    private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
+    private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<>(VertexBuffer.class);
+    private IntMap<VertexBuffer> buffers = new IntMap<>();
     private VertexBuffer[] lodLevels;
     private float pointSize = 1;
     private float lineWidth = 1;
@@ -183,6 +184,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
 
     private Mode mode = Mode.Triangles;
 
+    private SafeArrayList<MorphTarget> morphTargets;
+    private int numMorphBuffers = 0;
+    private float[] morphState;
+    private boolean dirtyMorph = true;
+
     /**
      * Creates a new mesh with no {@link VertexBuffer vertex buffers}.
      */
@@ -203,7 +209,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
             clone.meshBound = meshBound.clone();
             clone.collisionTree = collisionTree != null ? collisionTree : null;
             clone.buffers = buffers.clone();
-            clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
+            clone.buffersList = new SafeArrayList<>(VertexBuffer.class, buffersList);
             clone.vertexArrayID = -1;
             if (elementLengths != null) {
                 clone.elementLengths = elementLengths.clone();
@@ -233,8 +239,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
             //clone.collisionTree = collisionTree != null ? collisionTree : null;
             clone.collisionTree = null; // it will get re-generated in any case
 
-            clone.buffers = new IntMap<VertexBuffer>();
-            clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+            clone.buffers = new IntMap<>();
+            clone.buffersList = new SafeArrayList<>(VertexBuffer.class);
             for (VertexBuffer vb : buffersList.getArray()){
                 VertexBuffer bufClone = vb.clone();
                 clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
@@ -697,7 +703,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      */
     @Deprecated
     public void setInterleaved(){
-        ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>();
+        ArrayList<VertexBuffer> vbs = new ArrayList<>();
         vbs.addAll(buffersList);
 
 //        ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
@@ -820,8 +826,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      * {@link #setInterleaved() interleaved} format.
      */
     public void updateCounts(){
-        if (getBuffer(Type.InterleavedData) != null)
+        if (getBuffer(Type.InterleavedData) != null) {
             throw new IllegalStateException("Should update counts before interleave");
+        }
 
         VertexBuffer pb = getBuffer(Type.Position);
         VertexBuffer ib = getBuffer(Type.Index);
@@ -844,11 +851,13 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      */
     public int getTriangleCount(int lod){
         if (lodLevels != null){
-            if (lod < 0)
+            if (lod < 0) {
                 throw new IllegalArgumentException("LOD level cannot be < 0");
+            }
 
-            if (lod >= lodLevels.length)
-                throw new IllegalArgumentException("LOD level "+lod+" does not exist!");
+            if (lod >= lodLevels.length) {
+                throw new IllegalArgumentException("LOD level " + lod + " does not exist!");
+            }
 
             return computeNumElements(lodLevels[lod].getData().limit());
         }else if (lod == 0){
@@ -968,8 +977,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      * Sets the mesh's VAO ID. Internal use only.
      */
     public void setId(int id){
-        if (vertexArrayID != -1)
+        if (vertexArrayID != -1) {
             throw new IllegalStateException("ID has already been set.");
+        }
 
         vertexArrayID = id;
     }
@@ -1037,8 +1047,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      * @throws IllegalArgumentException If the buffer type is already set
      */
     public void setBuffer(VertexBuffer vb){
-        if (buffers.containsKey(vb.getBufferType().ordinal()))
-            throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType());
+        if (buffers.containsKey(vb.getBufferType().ordinal())) {
+            throw new IllegalArgumentException("Buffer type already set: " + vb.getBufferType());
+        }
 
         buffers.put(vb.getBufferType().ordinal(), vb);
         buffersList.add(vb);
@@ -1151,8 +1162,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      */
     public FloatBuffer getFloatBuffer(Type type) {
         VertexBuffer vb = getBuffer(type);
-        if (vb == null)
+        if (vb == null) {
             return null;
+        }
 
         return (FloatBuffer) vb.getData();
     }
@@ -1166,8 +1178,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      */
     public ShortBuffer getShortBuffer(Type type) {
         VertexBuffer vb = getBuffer(type);
-        if (vb == null)
+        if (vb == null) {
             return null;
+        }
 
         return (ShortBuffer) vb.getData();
     }
@@ -1179,8 +1192,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      * @return A virtual or wrapped index buffer to read the data as a list
      */
     public IndexBuffer getIndicesAsList(){
-        if (mode == Mode.Hybrid)
+        if (mode == Mode.Hybrid) {
             throw new UnsupportedOperationException("Hybrid mode not supported");
+        }
 
         IndexBuffer ib = getIndexBuffer();
         if (ib != null){
@@ -1209,8 +1223,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      */
     public IndexBuffer getIndexBuffer() {
         VertexBuffer vb = getBuffer(Type.Index);
-        if (vb == null)
+        if (vb == null) {
             return null;
+        }
 
         return IndexBuffer.wrapIndexBuffer(vb.getData());
     }
@@ -1233,8 +1248,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
         IndexBuffer indexBuf = getIndexBuffer();
         int numIndices = indexBuf.size();
 
-        IntMap<Integer> oldIndicesToNewIndices = new IntMap<Integer>(numIndices);
-        ArrayList<Integer> newIndicesToOldIndices = new ArrayList<Integer>();
+        IntMap<Integer> oldIndicesToNewIndices = new IntMap<>(numIndices);
+        ArrayList<Integer> newIndicesToOldIndices = new ArrayList<>();
         int newIndex = 0;
 
         for (int i = 0; i < numIndices; i++) {
@@ -1345,14 +1360,17 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      */
     public void scaleTextureCoordinates(Vector2f scaleFactor){
         VertexBuffer tc = getBuffer(Type.TexCoord);
-        if (tc == null)
+        if (tc == null) {
             throw new IllegalStateException("The mesh has no texture coordinates");
+        }
 
-        if (tc.getFormat() != VertexBuffer.Format.Float)
+        if (tc.getFormat() != VertexBuffer.Format.Float) {
             throw new UnsupportedOperationException("Only float texture coord format is supported");
+        }
 
-        if (tc.getNumComponents() != 2)
+        if (tc.getNumComponents() != 2) {
             throw new UnsupportedOperationException("Only 2D texture coords are supported");
+        }
 
         FloatBuffer fb = (FloatBuffer) tc.getData();
         fb.clear();
@@ -1504,6 +1522,70 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
         return patchVertexCount;
     }
 
+
+    public void addMorphTarget(MorphTarget target) {
+        if (morphTargets == null) {
+            morphTargets = new SafeArrayList<>(MorphTarget.class);
+        }
+//        if (numMorphBuffers == 0) {
+//            numMorphBuffers = target.getNumBuffers();
+//            int start = Type.MorphTarget0.ordinal();
+//            int end = start + numMorphBuffers;
+//            for (int i = start; i < end; i++) {
+//                VertexBuffer vb = new VertexBuffer(Type.values()[i]);
+//                setBuffer(vb);
+//            }
+//        } else if (target.getNumBuffers() != numMorphBuffers) {
+//            throw new IllegalArgumentException("Morph target has different number of buffers");
+//        }
+
+        morphTargets.add(target);
+    }
+
+    public void setMorphState(float[] state) {
+        if (morphTargets.isEmpty()) {
+            return;
+        }
+        if (morphState == null) {
+            morphState = new float[morphTargets.size()];
+        }
+        System.arraycopy(state, 0, morphState, 0, morphState.length);
+        this.dirtyMorph = true;
+    }
+
+    public float[] getMorphState() {
+        if (morphState == null) {
+            morphState = new float[morphTargets.size()];
+        }
+        return morphState;
+    }
+
+    public void setActiveMorphTargets(int... targetsIndex) {
+        int start = Type.MorphTarget0.ordinal();
+        for (int i = 0; i < targetsIndex.length; i++) {
+            MorphTarget t = morphTargets.get(targetsIndex[i]);
+            int idx = 0;
+            for (Type type : t.getBuffers().keySet()) {
+                FloatBuffer b = t.getBuffer(type);
+                setBuffer(Type.values()[start + i + idx], 3, b);
+                idx++;
+            }
+        }
+    }
+
+    public MorphTarget[] getMorphTargets() {
+        return morphTargets.getArray();
+    }
+
+    public boolean hasMorphTargets() {
+        return morphTargets != null && !morphTargets.isEmpty();
+    }
+
+    public boolean isDirtyMorph() {
+        return dirtyMorph;
+    }
+
+    @Override
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule out = ex.getCapsule(this);
 
@@ -1550,6 +1632,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
         out.write(lodLevels, "lodLevels", null);
     }
 
+    @Override
     public void read(JmeImporter im) throws IOException {
         InputCapsule in = im.getCapsule(this);
         meshBound = (BoundingVolume) in.readSavable("modelBound", null);

+ 54 - 12
jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java

@@ -212,7 +212,41 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
          * Format should be {@link Format#Float} and number of components
          * should be 16.
          */
-        InstanceData
+        InstanceData,
+
+        /**
+         * Morph animations targets.
+         * Supports up tp 8 morph target buffers at the same time
+         * Limited due to the limited number of attributes you can bind to a vertex shader usually 16
+         * <p>
+         * MorphTarget buffers are either POSITION, NORMAL or TANGENT buffers.
+         * So we can support up to
+         * 10 simultaneous POSITION targets
+         * 5 simultaneous POSITION and NORMAL targets
+         * 3 simultaneous POSTION, NORMAL and TANGENT targets.
+         * <p>
+         * Note that all buffers have 3 components (Vector3f) even the Tangent buffer that
+         * does not contain the w (handedness) component that will not be interpolated for morph animation.
+         * <p>
+         * Note that those buffers contain the difference between the base buffer (POSITION, NORMAL or TANGENT) and the target value
+         * So that you can interpolate with a MADD operation in the vertex shader
+         * position = weight * diffPosition + basePosition;
+         */
+        MorphTarget0,
+        MorphTarget1,
+        MorphTarget2,
+        MorphTarget3,
+        MorphTarget4,
+        MorphTarget5,
+        MorphTarget6,
+        MorphTarget7,
+        MorphTarget8,
+        MorphTarget9,
+        MorphTarget10,
+        MorphTarget11,
+        MorphTarget12,
+        MorphTarget13,
+
     }
 
     /**
@@ -241,7 +275,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
          * Mesh data is <em>not</em> sent to GPU at all. It is only
          * used by the CPU.
          */
-        CpuOnly;
+        CpuOnly
     }
 
     /**
@@ -610,8 +644,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
             return 0;
         }
         int elements = data.limit() / components;
-        if (format == Format.Half)
+        if (format == Format.Half) {
             elements /= 2;
+        }
         return elements;
     }
 
@@ -642,14 +677,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
      * argument.
      */
     public void setupData(Usage usage, int components, Format format, Buffer data){
-        if (id != -1)
+        if (id != -1) {
             throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
+        }
 
-        if (usage == null || format == null || data == null)
+        if (usage == null || format == null || data == null) {
             throw new IllegalArgumentException("None of the arguments can be null");
-            
-        if (data.isReadOnly()) 
-            throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
+        }
+
+        if (data.isReadOnly()) {
+            throw new IllegalArgumentException("VertexBuffer data cannot be read-only.");
+        }
 
         if (bufType != Type.InstanceData) {
             if (components < 1 || components > 4) {
@@ -720,11 +758,13 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
      * Converts single floating-point data to {@link Format#Half half} floating-point data.
      */
     public void convertToHalf(){
-        if (id != -1)
+        if (id != -1) {
             throw new UnsupportedOperationException("Data has already been sent.");
+        }
 
-        if (format != Format.Float)
+        if (format != Format.Float) {
             throw new IllegalStateException("Format must be float!");
+        }
 
         int numElements = data.limit() / components;
         format = Format.Half;
@@ -913,8 +953,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
      * match.
      */
     public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){
-        if (outVb.format != format || outVb.components != components)
+        if (outVb.format != format || outVb.components != components) {
             throw new IllegalArgumentException("Buffer format mismatch. Cannot copy");
+        }
 
         int inPos  = inIndex  * components;
         int outPos = outIndex * components;
@@ -981,8 +1022,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
      * of elements with the given number of components in each element.
      */
     public static Buffer createBuffer(Format format, int components, int numElements){
-        if (components < 1 || components > 4)
+        if (components < 1 || components > 4) {
             throw new IllegalArgumentException("Num components must be between 1 and 4");
+        }
 
         int total = numElements * components;
 

+ 40 - 0
jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java

@@ -0,0 +1,40 @@
+package com.jme3.scene.mesh;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import com.jme3.scene.VertexBuffer;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.EnumMap;
+
+public class MorphTarget implements Savable {
+    private EnumMap<VertexBuffer.Type, FloatBuffer> buffers = new EnumMap<>(VertexBuffer.Type.class);
+
+    public void setBuffer(VertexBuffer.Type type, FloatBuffer buffer) {
+        buffers.put(type, buffer);
+    }
+
+    public FloatBuffer getBuffer(VertexBuffer.Type type) {
+        return buffers.get(type);
+    }
+
+    public EnumMap<VertexBuffer.Type, FloatBuffer> getBuffers() {
+        return buffers;
+    }
+
+    public int getNumBuffers() {
+        return buffers.size();
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+
+    }
+}

+ 17 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md

@@ -110,6 +110,11 @@ MaterialDef Phong Lighting {
         // For hardware skinning
         Int NumberOfBones
         Matrix4Array BoneMatrices
+
+        // For Morph animation
+        FloatArray MorphWeights
+        Int NumberOfMorphTargets
+        Int NumberOfTargetsBuffers : 1
                 
         //For instancing
         Boolean UseInstancing
@@ -152,6 +157,8 @@ MaterialDef Phong Lighting {
             SPHERE_MAP : EnvMapAsSphereMap  
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
     }
 
@@ -191,6 +198,8 @@ MaterialDef Phong Lighting {
             SPHERE_MAP : EnvMapAsSphereMap  
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
     }
 
@@ -210,6 +219,8 @@ MaterialDef Phong Lighting {
             DISCARD_ALPHA : AlphaDiscardThreshold
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
         ForcedRenderState {
@@ -247,6 +258,8 @@ MaterialDef Phong Lighting {
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
             BACKFACE_SHADOWS: BackfaceShadows
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
         ForcedRenderState {
@@ -274,6 +287,8 @@ MaterialDef Phong Lighting {
             DIFFUSEMAP_ALPHA : DiffuseMap
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
     }
@@ -296,6 +311,8 @@ MaterialDef Phong Lighting {
 
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
     }
 

+ 10 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert

@@ -2,6 +2,8 @@
 #import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
 #import "Common/ShaderLib/Lighting.glsllib"
+#import "Common/ShaderLib/MorphAnim.glsllib"
+
 #ifdef VERTEX_LIGHTING
     #import "Common/ShaderLib/BlinnPhongLighting.glsllib"    
 #endif
@@ -90,6 +92,14 @@ void main(){
         vec3 modelSpaceTan  = inTangent.xyz;
    #endif
 
+   #ifdef NUM_MORPH_TARGETS
+        #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
+           Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
+        #else
+           Morph_Compute(modelSpacePos, modelSpaceNorm);
+        #endif
+   #endif
+
    #ifdef NUM_BONES
         #ifndef VERTEX_LIGHTING
         Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);

+ 17 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md

@@ -110,6 +110,11 @@ MaterialDef PBR Lighting {
         // For hardware skinning
         Int NumberOfBones
         Matrix4Array BoneMatrices
+
+        // For Morph animation
+        FloatArray MorphWeights
+        Int NumberOfMorphTargets
+        Int NumberOfTargetsBuffers : 1
                 
         //For instancing
         Boolean UseInstancing
@@ -158,6 +163,8 @@ MaterialDef PBR Lighting {
             NORMAL_TYPE: NormalType
             VERTEX_COLOR : UseVertexColor
             AO_MAP: LightMapAsAOMap
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
     }
 
@@ -178,6 +185,8 @@ MaterialDef PBR Lighting {
             DISCARD_ALPHA : AlphaDiscardThreshold
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
         ForcedRenderState {
@@ -215,6 +224,8 @@ MaterialDef PBR Lighting {
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
             BACKFACE_SHADOWS: BackfaceShadows
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
         ForcedRenderState {
@@ -247,6 +258,8 @@ MaterialDef PBR Lighting {
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
             BACKFACE_SHADOWS: BackfaceShadows
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
         ForcedRenderState {
@@ -272,6 +285,8 @@ MaterialDef PBR Lighting {
         Defines {
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
     }
@@ -291,6 +306,8 @@ MaterialDef PBR Lighting {
             NEED_TEXCOORD1
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
     }
 

+ 12 - 5
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert

@@ -1,10 +1,9 @@
 #import "Common/ShaderLib/GLSLCompat.glsllib"
 #import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
-
+#import "Common/ShaderLib/MorphAnim.glsllib"
 
 uniform vec4 m_BaseColor;
-
 uniform vec4 g_AmbientLightColor;
 varying vec2 texCoord;
 
@@ -38,11 +37,19 @@ void main(){
          vec3 modelSpaceTan  = inTangent.xyz;
     #endif
 
+    #ifdef NUM_MORPH_TARGETS
+         #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
+            Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
+         #else
+            Morph_Compute(modelSpacePos, modelSpaceNorm);
+         #endif
+    #endif
+
     #ifdef NUM_BONES
          #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
-         Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
+            Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
          #else
-         Skinning_Compute(modelSpacePos, modelSpaceNorm);
+            Skinning_Compute(modelSpacePos, modelSpaceNorm);
          #endif
     #endif
 
@@ -64,4 +71,4 @@ void main(){
     #ifdef VERTEX_COLOR                    
         Color *= inColor;
     #endif
-}
+}

+ 10 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert

@@ -2,6 +2,8 @@
 #import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
 #import "Common/ShaderLib/Lighting.glsllib"
+#import "Common/ShaderLib/MorphAnim.glsllib"
+
 #ifdef VERTEX_LIGHTING
     #import "Common/ShaderLib/BlinnPhongLighting.glsllib"
 #endif
@@ -84,6 +86,14 @@ void main(){
         vec3 modelSpaceTan  = inTangent.xyz;
    #endif
 
+   #ifdef NUM_MORPH_TARGETS
+        #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
+           Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
+        #else
+           Morph_Compute(modelSpacePos, modelSpaceNorm);
+        #endif
+   #endif
+
    #ifdef NUM_BONES
         #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
         Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);

+ 32 - 17
jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md

@@ -20,6 +20,11 @@ MaterialDef Unshaded {
         Int NumberOfBones
         Matrix4Array BoneMatrices
 
+        // For Morph animation
+        FloatArray MorphWeights
+        Int NumberOfMorphTargets
+        Int NumberOfTargetsBuffers : 1
+
         // Alpha threshold for fragment discarding
         Float AlphaDiscardThreshold (AlphaTestFallOff)
 
@@ -76,26 +81,30 @@ MaterialDef Unshaded {
             HAS_COLOR : Color
             NUM_BONES : NumberOfBones
             DISCARD_ALPHA : AlphaDiscardThreshold
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
     }
 
     Technique PreNormalPass {
 
-          VertexShader GLSL100 GLSL150 :   Common/MatDefs/SSAO/normal.vert
-          FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag
-
-          WorldParameters {
-              WorldViewProjectionMatrix
-              WorldViewMatrix
-              NormalMatrix
-              ViewProjectionMatrix
-              ViewMatrix
-          }
-
-          Defines {
-              NUM_BONES : NumberOfBones
-              INSTANCING : UseInstancing
-          }
+        VertexShader GLSL100 GLSL150 :   Common/MatDefs/SSAO/normal.vert
+        FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldViewMatrix
+            NormalMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
+        }
    }
 
     Technique PreShadow {
@@ -115,6 +124,8 @@ MaterialDef Unshaded {
             DISCARD_ALPHA : AlphaDiscardThreshold
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
         ForcedRenderState {
@@ -150,8 +161,10 @@ MaterialDef Unshaded {
             PSSM : Splits
             POINTLIGHT : LightViewProjectionMatrix5
             NUM_BONES : NumberOfBones
-	    INSTANCING : UseInstancing
+	        INSTANCING : UseInstancing
 	        BACKFACE_SHADOWS: BackfaceShadows
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
 
         ForcedRenderState {
@@ -177,8 +190,10 @@ MaterialDef Unshaded {
             HAS_GLOWMAP : GlowMap
             HAS_GLOWCOLOR : GlowColor
             NUM_BONES : NumberOfBones
-	    INSTANCING : UseInstancing
+	        INSTANCING : UseInstancing
             HAS_POINTSIZE : PointSize
+            NUM_MORPH_TARGETS: NumberOfMorphTargets
+            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
         }
     }
 }

+ 6 - 0
jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert

@@ -1,6 +1,7 @@
 #import "Common/ShaderLib/GLSLCompat.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
 #import "Common/ShaderLib/Instancing.glsllib"
+#import "Common/ShaderLib/MorphAnim.glsllib"
 
 attribute vec3 inPosition;
 
@@ -38,6 +39,11 @@ void main(){
     #endif
 
     vec4 modelSpacePos = vec4(inPosition, 1.0);
+
+    #ifdef NUM_MORPH_TARGETS
+        Morph_Compute(modelSpacePos);
+    #endif
+
     #ifdef NUM_BONES
         Skinning_Compute(modelSpacePos);
     #endif

+ 1 - 1
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag

@@ -1,5 +1,5 @@
 void main(){
-	vec4 worldPos = worldMatrix * vec4(modelPosition, 1.0);
+	vec4 worldPos = worldMatrix * vec4(0.0, 0.0, 0.0, 1.0);
 	vec3 dir = worldPos.xyz - cameraPos;
 	float distance = dot(cameraDir, dir);
 	float m11 = projectionMatrix[1][1];

+ 5 - 2
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert

@@ -49,10 +49,13 @@ const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
                           0.0, 0.0, 0.5, 0.0,
                           0.5, 0.5, 0.5, 1.0);
 
-
 void main(){
    vec4 modelSpacePos = vec4(inPosition, 1.0);
-  
+
+   #ifdef NUM_MORPH_TARGETS
+       Morph_Compute(modelSpacePos);
+   #endif
+
    #ifdef NUM_BONES
        Skinning_Compute(modelSpacePos);
    #endif

+ 7 - 1
jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert

@@ -1,6 +1,8 @@
 #import "Common/ShaderLib/GLSLCompat.glsllib"
 #import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
+#import "Common/ShaderLib/MorphAnim.glsllib"
+
 attribute vec3 inPosition;
 attribute vec2 inTexCoord;
 
@@ -8,7 +10,11 @@ varying vec2 texCoord;
 
 void main(){
     vec4 modelSpacePos = vec4(inPosition, 1.0);
-  
+
+   #ifdef NUM_MORPH_TARGETS
+           Morph_Compute(modelSpacePos, modelSpaceNorm);
+   #endif
+
    #ifdef NUM_BONES
        Skinning_Compute(modelSpacePos);
    #endif

+ 212 - 0
jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib

@@ -0,0 +1,212 @@
+/**
+A glsllib that perform morph animation.
+Note that it only handles morphing position, normals and tangents.
+*/
+#ifdef NUM_MORPH_TARGETS
+    vec3 dummy_norm = vec3(0.0);
+    vec3 dummy_tan = vec3(0.0);
+    #define NUM_BUFFERS NUM_MORPH_TARGETS * NUM_TARGETS_BUFFERS
+    #if (NUM_BUFFERS > 0)
+        uniform float m_MorphWeights[NUM_MORPH_TARGETS];
+        attribute vec3 inMorphTarget0;
+    #endif
+    #if (NUM_BUFFERS > 1)
+        attribute vec3 inMorphTarget1;
+    #endif
+    #if (NUM_BUFFERS > 2)
+        attribute vec3 inMorphTarget2;
+    #endif
+    #if (NUM_BUFFERS > 3)
+        attribute vec3 inMorphTarget3;
+    #endif
+    #if (NUM_BUFFERS > 4)
+        attribute vec3 inMorphTarget4;
+    #endif
+    #if (NUM_BUFFERS > 5)
+        attribute vec3 inMorphTarget5;
+    #endif
+    #if (NUM_BUFFERS > 6)
+        attribute vec3 inMorphTarget6;
+    #endif
+    #if (NUM_BUFFERS > 7)
+        attribute vec3 inMorphTarget7;
+    #endif
+    #if (NUM_BUFFERS > 8)
+        attribute vec3 inMorphTarget8;
+    #endif
+    #if (NUM_BUFFERS > 9)
+        attribute vec3 inMorphTarget9;
+    #endif
+    #if (NUM_BUFFERS > 10)
+        attribute vec3 inMorphTarget10;
+    #endif
+    #if (NUM_BUFFERS > 11)
+        attribute vec3 inMorphTarget11;
+    #endif
+    #if (NUM_BUFFERS > 12)
+        attribute vec3 inMorphTarget12;
+    #endif
+    #if (NUM_BUFFERS > 13)
+        attribute vec3 inMorphTarget13;
+    #endif
+
+    void Morph_Compute_Pos(inout vec4 pos){
+        #if (NUM_TARGETS_BUFFERS == 1)
+            #if (NUM_MORPH_TARGETS > 0)
+                pos.xyz += m_MorphWeights[0] * inMorphTarget0;
+            #endif
+            #if (NUM_MORPH_TARGETS > 1)
+                pos.xyz += m_MorphWeights[1] * inMorphTarget1;
+            #endif
+            #if (NUM_MORPH_TARGETS > 2)
+                pos.xyz += m_MorphWeights[2] * inMorphTarget2;
+            #endif
+            #if (NUM_MORPH_TARGETS > 3)
+                pos.xyz += m_MorphWeights[3] * inMorphTarget3;
+            #endif
+            #if (NUM_MORPH_TARGETS > 4)
+                pos.xyz += m_MorphWeights[4] * inMorphTarget4;
+            #endif
+            #if (NUM_MORPH_TARGETS > 5)
+                pos.xyz += m_MorphWeights[5] * inMorphTarget5;
+            #endif
+            #if (NUM_MORPH_TARGETS > 6)
+                pos.xyz += m_MorphWeights[6] * inMorphTarget6;
+            #endif
+            #if (NUM_MORPH_TARGETS > 7)
+                pos.xyz += m_MorphWeights[7] * inMorphTarget7;
+            #endif
+            #if (NUM_MORPH_TARGETS > 8)
+                pos.xyz += m_MorphWeights[8] * inMorphTarget8;
+            #endif
+            #if (NUM_MORPH_TARGETS > 9)
+                pos.xyz += m_MorphWeights[9] * inMorphTarget9;
+            #endif
+            #if (NUM_MORPH_TARGETS > 10)
+                pos.xyz += m_MorphWeights[10] * inMorphTarget10;
+            #endif
+            #if (NUM_MORPH_TARGETS > 11)
+                pos.xyz += m_MorphWeights[11] * inMorphTarget11;
+            #endif
+            #if (NUM_MORPH_TARGETS > 12)
+                pos.xyz += m_MorphWeights[12] * inMorphTarget12;
+            #endif
+            #if (NUM_MORPH_TARGETS > 13)
+                pos.xyz += m_MorphWeights[13] * inMorphTarget13;
+            #endif
+        #endif
+    }
+
+    float Get_Inverse_Weights_Sum(){
+        float sum = 0;
+        for( int i = 0;i < NUM_MORPH_TARGETS; i++){
+            sum += m_MorphWeights[i];
+        }
+        return 1.0 / max(1.0, sum);
+    }
+
+    void Morph_Compute_Pos_Norm(inout vec4 pos, inout vec3 norm){
+        #if (NUM_TARGETS_BUFFERS == 2)
+            // weight sum may be over 1.0. It's totallyvalid for position
+            // but for normals. the weights needs to be normalized.
+            float invWeightsSum = Get_Inverse_Weights_Sum();
+            #if (NUM_BUFFERS > 1)
+                pos.xyz += m_MorphWeights[0] * inMorphTarget0;
+                norm += m_MorphWeights[0] * invWeightsSum * inMorphTarget1;
+            #endif
+            #if (NUM_BUFFERS > 3)
+                pos.xyz += m_MorphWeights[1] * inMorphTarget2;
+                norm.xyz += m_MorphWeights[1] * invWeightsSum * inMorphTarget3;
+            #endif
+            #if (NUM_BUFFERS > 5)
+                pos.xyz += m_MorphWeights[2] * inMorphTarget4;
+                norm += m_MorphWeights[2] * invWeightsSum * inMorphTarget5;
+            #endif
+            #if (NUM_BUFFERS > 7)
+                pos.xyz += m_MorphWeights[3] * inMorphTarget6;
+                norm += m_MorphWeights[3] * invWeightsSum * inMorphTarget7;
+            #endif
+            #if (NUM_BUFFERS > 9)
+                pos.xyz += m_MorphWeights[4] * inMorphTarget8;
+                norm += m_MorphWeights[4] * invWeightsSum * inMorphTarget9;
+            #endif
+            #if (NUM_BUFFERS > 11)
+                pos.xyz += m_MorphWeights[5] * inMorphTarget10;
+                norm += m_MorphWeights[5] * invWeightsSum * inMorphTarget11;
+            #endif
+            #if (NUM_BUFFERS > 13)
+                pos.xyz += m_MorphWeights[6] * inMorphTarget12;
+                norm += m_MorphWeights[6] * invWeightsSum * inMorphTarget13;
+            #endif
+        #endif
+    }
+
+    void Morph_Compute_Pos_Norm_Tan(inout vec4 pos, inout vec3 norm, inout vec3 tan){
+        #if (NUM_TARGETS_BUFFERS == 3)
+            // weight sum may be over 1.0. It's totallyvalid for position
+            // but for normals. the weights needs to be normalized.
+            float invWeightsSum = Get_Inverse_Weights_Sum();
+            #if (NUM_BUFFERS > 2)
+                float normWeight =  m_MorphWeights[0] * invWeightsSum;
+                pos.xyz += m_MorphWeights[0] * inMorphTarget0;
+                norm += normWeight * inMorphTarget1;
+                tan += normWeight * inMorphTarget2;
+            #endif
+            #if (NUM_BUFFERS > 5)
+                float normWeight =  m_MorphWeights[1] * invWeightsSum;
+                pos.xyz += m_MorphWeights[1] * inMorphTarget3;
+                norm += normWeight * inMorphTarget4;
+                tan += normWeight * inMorphTarget5;
+            #endif
+            #if (NUM_BUFFERS > 8)
+                float normWeight =  m_MorphWeights[2] * invWeightsSum;
+                pos.xyz += m_MorphWeights[2] * inMorphTarget6;
+                norm += normWeight * inMorphTarget7;
+                tan += normWeight * inMorphTarget8;
+            #endif
+            #if (NUM_BUFFERS > 11)
+                float normWeight =  m_MorphWeights[3] * invWeightsSum;
+                pos.xyz += m_MorphWeights[3] * inMorphTarget9;
+                norm += normWeight * inMorphTarget10;
+                tan += normWeight * inMorphTarget11;
+            #endif
+        #endif
+    }
+
+    void Morph_Compute(inout vec4 pos){
+        #if (NUM_TARGETS_BUFFERS == 2)
+            Morph_Compute_Pos_Norm(pos,dummy_norm);
+            return;
+        #elif (NUM_TARGETS_BUFFERS == 3)
+            Morph_Compute_Pos_Norm_Tan(pos, dummy_norm, dummy_tan);
+            return;
+        #endif
+        Morph_Compute_Pos(pos);
+    }
+
+    void Morph_Compute(inout vec4 pos, inout vec3 norm){
+        #if (NUM_TARGETS_BUFFERS == 1)
+            Morph_Compute_Pos(pos);
+            return;
+        #elif (NUM_TARGETS_BUFFERS == 3)
+            Morph_Compute_Pos_Norm_Tan(pos, dummy_norm, dummy_tan);
+            return;
+        #elif  (NUM_TARGETS_BUFFERS == 2)
+            Morph_Compute_Pos_Norm(pos, norm);
+        #endif
+    }
+
+    void Morph_Compute(inout vec4 pos, inout vec3 norm, inout vec3 tan){
+        #if (NUM_TARGETS_BUFFERS == 1)
+            Morph_Compute_Pos(pos);
+            return;
+        #elif (NUM_TARGETS_BUFFERS == 2)
+            Morph_Compute_Pos_Norm(pos, norm);
+            tan = normalize(tan - dot(tan, norm) * norm);
+            return;
+        #elif (NUM_TARGETS_BUFFERS == 3)
+            Morph_Compute_Pos_Norm_Tan(pos, norm, tan);
+        #endif
+    }
+
+#endif

+ 35 - 9
jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java

@@ -41,11 +41,12 @@ import com.jme3.input.controls.ActionListener;
 import com.jme3.input.controls.KeyTrigger;
 import com.jme3.math.*;
 import com.jme3.renderer.Limits;
-import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
+import com.jme3.scene.*;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.debug.custom.ArmatureDebugAppState;
 import com.jme3.scene.plugins.gltf.GltfModelKey;
+import com.jme3.shader.VarType;
+import jme3test.model.anim.EraseTimer;
 
 import java.util.*;
 
@@ -58,9 +59,13 @@ public class TestGltfLoading extends SimpleApplication {
     int assetIndex = 0;
     boolean useAutoRotate = false;
     private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
-    int duration = 2;
+    int duration = 1;
     boolean playAnim = true;
 
+    Geometry g;
+    int morphIndex = 0;
+
+
     public static void main(String[] args) {
         TestGltfLoading app = new TestGltfLoading();
         app.start();
@@ -73,10 +78,12 @@ public class TestGltfLoading extends SimpleApplication {
     https://sketchfab.com/features/gltf
     You have to copy them in Model/gltf folder in the test-data project.
      */
+    @Override
     public void simpleInitApp() {
 
         ArmatureDebugAppState armatureDebugappState = new ArmatureDebugAppState();
         getStateManager().attach(armatureDebugappState);
+        setTimer(new EraseTimer());
 
         String folder = System.getProperty("user.home");
         assetManager.registerLocator(folder, FileLocator.class);
@@ -110,7 +117,14 @@ public class TestGltfLoading extends SimpleApplication {
 //        PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
 //        rootNode.addLight(pl1);
 
-        loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f);
+        //loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f);
+        //loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.1f);
+        loadModel("Models/gltf/scifigirl/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
+        //loadModel("Models/gltf/man/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
+       //loadModel("Models/gltf/torus/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
+        //loadModel("Models/gltf/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f);
+        //loadModel("Models/gltf/morphCube/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 1f);
+       // loadModel("Models/gltf/morph/SimpleMorph.gltf", new Vector3f(0, 0, 0), 0.1f);
         //loadModel("Models/gltf/nier/scene.gltf", new Vector3f(0, -1.5f, 0), 0.01f);
         //loadModel("Models/gltf/izzy/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
         //loadModel("Models/gltf/darth/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
@@ -149,6 +163,8 @@ public class TestGltfLoading extends SimpleApplication {
 
         probeNode.attachChild(assets.get(0));
 
+        // setMorphTarget(morphIndex);
+
         ChaseCameraAppState chaseCam = new ChaseCameraAppState();
         chaseCam.setTarget(probeNode);
         getStateManager().attach(chaseCam);
@@ -200,6 +216,15 @@ public class TestGltfLoading extends SimpleApplication {
         dumpScene(rootNode, 0);
     }
 
+    public void setMorphTarget(int index) {
+        g = (Geometry) probeNode.getChild("0");
+        g.getMesh().setActiveMorphTargets(index);
+        g.getMaterial().setInt("NumberOfMorphTargets", 1);
+        g.getMaterial().setInt("NumberOfTargetsBuffers", 3);
+        float[] weights = {1.0f};
+        g.getMaterial().setParam("MorphWeights", VarType.FloatArray, weights);
+    }
+
     private <T extends Control> T findControl(Spatial s, Class<T> controlClass) {
         T ctrl = s.getControl(controlClass);
         if (ctrl != null) {
@@ -291,18 +316,19 @@ public class TestGltfLoading extends SimpleApplication {
 
     @Override
     public void simpleUpdate(float tpf) {
-
         if (!useAutoRotate) {
             return;
         }
         time += tpf;
-        autoRotate.rotate(0, tpf * 0.5f, 0);
+      //  autoRotate.rotate(0, tpf * 0.5f, 0);
         if (time > duration) {
+            // morphIndex++;
+            //  setMorphTarget(morphIndex);
             assets.get(assetIndex).removeFromParent();
             assetIndex = (assetIndex + 1) % assets.size();
-            if (assetIndex == 0) {
-                duration = 10;
-            }
+//            if (assetIndex == 0) {
+//                duration = 10;
+//            }
             probeNode.attachChild(assets.get(assetIndex));
             time = 0;
         }

+ 2 - 2
jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java

@@ -52,9 +52,9 @@ public class TestHWSkinning extends SimpleApplication implements ActionListener{
 
     // private AnimComposer composer;
     private String[] animNames = {"Dodge", "Walk", "pull", "push"};
-    private final static int SIZE = 60;
+    private final static int SIZE = 40;
     private boolean hwSkinningEnable = true;
-    private List<SkinningControl> skControls = new ArrayList<SkinningControl>();
+    private List<SkinningControl> skControls = new ArrayList<>();
     private BitmapText hwsText;
 
     public static void main(String[] args) {

+ 121 - 0
jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java

@@ -0,0 +1,121 @@
+package jme3test.model.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.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.mesh.MorphTarget;
+import com.jme3.scene.shape.Box;
+import com.jme3.shader.VarType;
+import com.jme3.util.BufferUtils;
+
+import java.nio.FloatBuffer;
+
+public class TestMorph extends SimpleApplication {
+
+    float[] weights = new float[2];
+
+    public static void main(String... args) {
+        TestMorph app = new TestMorph();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        final Box box = new Box(1, 1, 1);
+        FloatBuffer buffer = BufferUtils.createVector3Buffer(box.getVertexCount());
+
+        float[] d = new float[box.getVertexCount() * 3];
+        for (int i = 0; i < d.length; i++) {
+            d[i] = 0;
+        }
+
+        d[12] = 1f;
+        d[15] = 1f;
+        d[18] = 1f;
+        d[21] = 1f;
+
+        buffer.put(d);
+        buffer.rewind();
+
+        MorphTarget target = new MorphTarget();
+        target.setBuffer(VertexBuffer.Type.Position, buffer);
+        box.addMorphTarget(target);
+
+
+        buffer = BufferUtils.createVector3Buffer(box.getVertexCount());
+
+        for (int i = 0; i < d.length; i++) {
+            d[i] = 0;
+        }
+
+        d[13] = 1f;
+        d[16] = 1f;
+        d[19] = 1f;
+        d[22] = 1f;
+
+        buffer.put(d);
+        buffer.rewind();
+
+        final MorphTarget target2 = new MorphTarget();
+        target2.setBuffer(VertexBuffer.Type.Position, buffer);
+        box.addMorphTarget(target2);
+
+        Geometry g = new Geometry("box", box);
+        final Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        g.setMaterial(m);
+        m.setColor("Color", ColorRGBA.Red);
+        m.setInt("NumberOfMorphTargets", 2);
+
+        rootNode.attachChild(g);
+
+        box.setActiveMorphTargets(0,1);
+        m.setParam("MorphWeights", VarType.FloatArray, weights);
+
+        ChaseCameraAppState chase = new ChaseCameraAppState();
+        chase.setTarget(rootNode);
+        getStateManager().attach(chase);
+        flyCam.setEnabled(false);
+
+        inputManager.addMapping("morphright", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("morphleft", new KeyTrigger(KeyInput.KEY_Y));
+        inputManager.addMapping("morphup", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("morphdown", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addMapping("change", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(new AnalogListener() {
+            @Override
+            public void onAnalog(String name, float value, float tpf) {
+                if (name.equals("morphleft")) {
+                    weights[0] -= tpf;
+                }
+                if (name.equals("morphright")) {
+                    weights[0] += tpf;
+                }
+                if (name.equals("morphup")) {
+                    weights[1] += tpf;
+                }
+                if (name.equals("morphdown")) {
+                    weights[1] -= tpf;
+                }
+                m.setParam("MorphWeights", VarType.FloatArray, weights);
+
+            }
+        }, "morphup", "morphdown", "morphleft", "morphright");
+
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (name.equals("change") && isPressed) {
+                    box.setBuffer(VertexBuffer.Type.MorphTarget0, 3, target2.getBuffer(VertexBuffer.Type.Position));
+                }
+            }
+        }, "change");
+    }
+}

+ 91 - 21
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

@@ -11,6 +11,7 @@ import com.jme3.renderer.Camera;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.scene.*;
 import com.jme3.scene.control.CameraControl;
+import com.jme3.scene.mesh.MorphTarget;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture2D;
 import com.jme3.util.IntMap;
@@ -19,6 +20,8 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
 import javax.xml.bind.DatatypeConverter;
 import java.io.*;
 import java.nio.Buffer;
+import java.nio.FloatBuffer;
+import java.rmi.ServerError;
 import java.util.*;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -128,8 +131,6 @@ public class GltfLoader implements AssetLoader {
 
             rootNode = customContentManager.readExtensionAndExtras("root", docRoot, rootNode);
 
-            setupControls();
-
             //Loading animations
             if (animations != null) {
                 for (int i = 0; i < animations.size(); i++) {
@@ -137,6 +138,8 @@ public class GltfLoader implements AssetLoader {
                 }
             }
 
+            setupControls();
+
             //only one scene let's not return the root.
             if (rootNode.getChildren().size() == 1) {
                 rootNode = (Node) rootNode.getChild(0);
@@ -389,7 +392,7 @@ public class GltfLoader implements AssetLoader {
                 //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
+                //setting usage to cpuOnly so that the buffer is not sent empty to the GPU
                 indicesHW.setUsage(VertexBuffer.Usage.CpuOnly);
                 weightsHW.setUsage(VertexBuffer.Usage.CpuOnly);
                 mesh.setBuffer(weightsHW);
@@ -397,6 +400,22 @@ public class GltfLoader implements AssetLoader {
                 mesh.generateBindPose();
             }
 
+            JsonArray targets = meshObject.getAsJsonArray("targets");
+            if(targets != null){
+                for (JsonElement target : targets) {
+                    MorphTarget morphTarget = new MorphTarget();
+                    for (Map.Entry<String, JsonElement> entry : target.getAsJsonObject().entrySet()) {
+                        String bufferType = entry.getKey();
+                        VertexBuffer.Type type = getVertexBufferType(bufferType);
+                        VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(type));
+                        if (vb != null) {
+                            morphTarget.setBuffer(type, (FloatBuffer)vb.getData());
+                        }
+                    }
+                    mesh.addMorphTarget(morphTarget);
+                }
+            }
+
             mesh = customContentManager.readExtensionAndExtras("primitive", meshObject, mesh);
 
             Geometry geom = new Geometry(null, mesh);
@@ -425,7 +444,6 @@ public class GltfLoader implements AssetLoader {
             geomArray[index] = geom;
             index++;
 
-            //TODO targets(morph anim...)
         }
 
         geomArray = customContentManager.readExtensionAndExtras("mesh", meshData, geomArray);
@@ -721,6 +739,7 @@ public class GltfLoader implements AssetLoader {
 
         //temp data storage of track data
         TrackData[] tracks = new TrackData[nodes.size()];
+        boolean hasMorphTrack = false;
 
         for (JsonElement channel : channels) {
 
@@ -733,12 +752,12 @@ public class GltfLoader implements AssetLoader {
                 continue;
             }
             assertNotNull(targetPath, "No target path for channel");
-
-            if (targetPath.equals("weight")) {
-                //Morph animation, not implemented in JME, let's warn the user and skip the channel
-                logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation");
-                continue;
-            }
+//
+//            if (targetPath.equals("weights")) {
+//                //Morph animation, not implemented in JME, let's warn the user and skip the channel
+//                logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation track");
+//                continue;
+//            }
 
             TrackData trackData = tracks[targetNode];
             if (trackData == null) {
@@ -782,9 +801,15 @@ public class GltfLoader implements AssetLoader {
                 Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
                 trackData.rotations = rotations;
             } else {
-                //TODO support weights
-                logger.log(Level.WARNING, "Morph animation is not supported");
-                continue;
+                trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph));
+                float[] weights = readAccessorData(dataIndex, floatArrayPopulator);
+                Geometry g = fetchFromCache("nodes", targetNode, Geometry.class);
+                int expectedSize = g.getMesh().getMorphTargets().length * times.length;
+                if( expectedSize != weights.length ){
+                    throw new AssetLoadException("Morph animation should contain " + expectedSize + " entries, got" + weights.length);
+                }
+                trackData.weights = weights;
+                hasMorphTrack = true;
             }
             tracks[targetNode] = customContentManager.readExtensionAndExtras("channel", channel, trackData);
         }
@@ -795,7 +820,7 @@ public class GltfLoader implements AssetLoader {
 
         List<Spatial> spatials = new ArrayList<>();
         AnimClip anim = new AnimClip(name);
-        List<TransformTrack> ttracks = new ArrayList<>();
+        List<AnimTrack> aTracks = new ArrayList<>();
         int skinIndex = -1;
 
         List<Joint> usedJoints = new ArrayList<>();
@@ -809,8 +834,22 @@ public class GltfLoader implements AssetLoader {
             if (node instanceof Spatial) {
                 Spatial s = (Spatial) node;
                 spatials.add(s);
-                TransformTrack track = new TransformTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
-                ttracks.add(track);
+                if (trackData.rotations != null || trackData.translations != null || trackData.scales != null) {
+                    TransformTrack track = new TransformTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
+                    aTracks.add(track);
+                }
+                if( trackData.weights != null && s instanceof Geometry){
+                    Geometry g = (Geometry)s;
+                    int nbMorph = g.getMesh().getMorphTargets().length;
+//                    for (int k = 0; k < trackData.weights.length; k++) {
+//                        System.err.print(trackData.weights[k] + ",");
+//                        if(k % nbMorph == 0 && k!=0){
+//                            System.err.println(" ");
+//                        }
+//                    }
+                    MorphTrack track = new MorphTrack(g, trackData.times, trackData.weights, nbMorph);
+                    aTracks.add(track);
+                }
             } else if (node instanceof JointWrapper) {
                 JointWrapper jw = (JointWrapper) node;
                 usedJoints.add(jw.joint);
@@ -826,8 +865,7 @@ public class GltfLoader implements AssetLoader {
                 }
 
                 TransformTrack track = new TransformTrack(jw.joint, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
-                ttracks.add(track);
-
+                aTracks.add(track);
             }
         }
 
@@ -845,12 +883,12 @@ public class GltfLoader implements AssetLoader {
                     Quaternion[] rotations = new Quaternion[]{joint.getLocalRotation()};
                     Vector3f[] scales = new Vector3f[]{joint.getLocalScale()};
                     TransformTrack track = new TransformTrack(joint, times, translations, rotations, scales);
-                    ttracks.add(track);
+                    aTracks.add(track);
                 }
             }
         }
 
-        anim.setTracks(ttracks.toArray(new TransformTrack[ttracks.size()]));
+        anim.setTracks(aTracks.toArray(new AnimTrack[aTracks.size()]));
 
         anim = customContentManager.readExtensionAndExtras("animations", animation, anim);
 
@@ -862,11 +900,14 @@ public class GltfLoader implements AssetLoader {
 
         if (!spatials.isEmpty()) {
             if (skinIndex != -1) {
-                //there are some spatial tracks in this bone animation... or the other way around. Let's add the spatials in the skinnedSpatials.
+                //there are some spatial or moph tracks in this bone animation... or the other way around. Let's add the spatials in the skinnedSpatials.
                 SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
                 List<Spatial> spat = skinnedSpatials.get(skin);
                 spat.addAll(spatials);
                 //the animControl will be added in the setupControls();
+                if (hasMorphTrack && skin.morphControl == null) {
+                    skin.morphControl = new MorphControl();
+                }
             } else {
                 //Spatial animation
                 Spatial spatial = null;
@@ -882,6 +923,9 @@ public class GltfLoader implements AssetLoader {
                     spatial.addControl(composer);
                 }
                 composer.addAnimClip(anim);
+                if (hasMorphTrack && spatial.getControl(MorphControl.class) == null) {
+                    spatial.addControl(new MorphControl());
+                }
             }
         }
     }
@@ -1049,6 +1093,9 @@ public class GltfLoader implements AssetLoader {
                 spatial.addControl(skinData.animComposer);
             }
             spatial.addControl(skinData.skinningControl);
+            if (skinData.morphControl != null) {
+                spatial.addControl(skinData.morphControl);
+            }
         }
 
         for (int i = 0; i < nodes.size(); i++) {
@@ -1131,7 +1178,9 @@ public class GltfLoader implements AssetLoader {
 
     private class SkinData {
         SkinningControl skinningControl;
+        MorphControl morphControl;
         AnimComposer animComposer;
+        Spatial spatial;
         Spatial parent;
         Transform rootBoneTransformOffset;
         Joint[] joints;
@@ -1221,6 +1270,27 @@ public class GltfLoader implements AssetLoader {
         }
 
     }
+//
+//    private class FloaGridPopulator implements Populator<float[]> {
+//
+//        @Override
+//        public float[][] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException {
+//
+//            int numComponents = getNumberOfComponents(type);
+//            int dataSize = numComponents * count;
+//            float[] data = new float[dataSize];
+//
+//            if (bufferViewIndex == null) {
+//                //no referenced buffer, specs says to pad the data with zeros.
+//                padBuffer(data, dataSize);
+//            } else {
+//                readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, getVertexBufferFormat(componentType));
+//            }
+//
+//            return data;
+//        }
+//
+//    }
 
     private class Vector3fArrayPopulator implements Populator<Vector3f[]> {
 

+ 2 - 0
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java

@@ -486,6 +486,8 @@ public class GltfUtils {
             mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, BufferUtils.createShortBuffer(jointsArray));
         }
         mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, BufferUtils.createFloatBuffer(weightsArray));
+        mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(VertexBuffer.Usage.CpuOnly);
+        mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(VertexBuffer.Usage.CpuOnly);
     }
 
     private static void populateFloatArray(float[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {

+ 10 - 3
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java

@@ -10,7 +10,8 @@ public class TrackData {
     public enum Type {
         Translation,
         Rotation,
-        Scale
+        Scale,
+        Morph
     }
 
     Float length;
@@ -21,7 +22,6 @@ public class TrackData {
     Vector3f[] translations;
     Quaternion[] rotations;
     Vector3f[] scales;
-    //not used for now
     float[] weights;
 
     public void update() {
@@ -125,6 +125,13 @@ public class TrackData {
                 System.arraycopy(scales, 0, newScales, 1, scales.length);
                 scales = newScales;
             }
+            if (weights != null) {
+                int nbMorph = weights.length / (times.length - 1);
+                float[] newWeights = new float[weights.length + nbMorph];
+                System.arraycopy(weights, 0, newWeights, 0, nbMorph);
+                System.arraycopy(weights, 0, newWeights, nbMorph, weights.length);
+                weights = newWeights;
+            }
         }
 
         checkTimesConsistantcy();
@@ -336,4 +343,4 @@ public class TrackData {
         Vector3f scale;
     }
 
-}
+}