Jelajahi Sumber

glTf: proper animation data padding when transforms are given as sparse arrays

Nehon 8 tahun lalu
induk
melakukan
db23985f92

+ 296 - 0
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java

@@ -0,0 +1,296 @@
+package com.jme3.scene.plugins.gltf;
+
+import com.jme3.math.*;
+
+import java.util.*;
+
+public class AnimData {
+
+    public enum Type {
+        Translation,
+        Rotation,
+        Scale
+    }
+
+    Float length;
+    float[] times;
+    List<TimeData> timeArrays = new ArrayList<>();
+
+
+    Vector3f[] translations;
+    Quaternion[] rotations;
+    Vector3f[] scales;
+    //not used for now
+    float[] weights;
+
+    public void update() {
+
+        if (equalTimes(timeArrays)) {
+            times = timeArrays.get(0).times;
+            ensureArraysInit();
+        } else {
+            //Times array are different and contains different sampling times.
+            //We have to merge them because JME needs the 3 types of transforms for each keyFrame.
+
+            //extracting keyframes information
+            List<KeyFrame> keyFrames = new ArrayList<>();
+            TimeData timeData = timeArrays.get(0);
+            Type type = timeData.type;
+            for (int i = 0; i < timeData.times.length; i++) {
+                float time = timeData.times[i];
+                KeyFrame keyFrame = new KeyFrame();
+                keyFrame.time = time;
+                setKeyFrameTransforms(type, keyFrame, timeData.times);
+                keyFrames.add(keyFrame);
+            }
+
+            for (int i = 1; i < timeArrays.size(); i++) {
+                timeData = timeArrays.get(i);
+                type = timeData.type;
+                for (float time : timeData.times) {
+                    for (int j = 0; j < keyFrames.size(); j++) {
+                        KeyFrame kf = keyFrames.get(j);
+                        if (Float.floatToIntBits(kf.time) != Float.floatToIntBits(time)) {
+                            if (time > kf.time) {
+                                continue;
+                            } else {
+                                kf = new KeyFrame();
+                                kf.time = time;
+                                keyFrames.add(j, kf);
+                                //we inserted a keyframe let's shift the counter.
+                                j++;
+                            }
+                        }
+                        setKeyFrameTransforms(type, kf, timeData.times);
+                        break;
+                    }
+                }
+            }
+            // populating transforms array from the keyframes, interpolating
+            times = new float[keyFrames.size()];
+
+            ensureArraysInit();
+
+            TransformIndices translationIndices = new TransformIndices();
+            TransformIndices rotationIndices = new TransformIndices();
+            TransformIndices scaleIndices = new TransformIndices();
+
+            for (int i = 0; i < keyFrames.size(); i++) {
+                KeyFrame kf = keyFrames.get(i);
+                //we need Interpolate between keyframes when transforms are sparse.
+                times[i] = kf.time;
+                populateTransform(Type.Translation, i, keyFrames, kf, translationIndices);
+                populateTransform(Type.Rotation, i, keyFrames, kf, rotationIndices);
+                populateTransform(Type.Scale, i, keyFrames, kf, scaleIndices);
+            }
+        }
+
+        ensureArraysInit();
+
+        if (times[0] > 0) {
+            //Anim doesn't start at 0, JME can't handle that and will interpolate transforms linearly from 0 to the first frame of the anim.
+            //we need to add a frame at 0 that copies the first real frame
+
+            float[] newTimes = new float[times.length + 1];
+            newTimes[0] = 0f;
+            System.arraycopy(times, 0, newTimes, 1, times.length);
+            times = newTimes;
+
+            if (translations != null) {
+                Vector3f[] newTranslations = new Vector3f[translations.length + 1];
+                newTranslations[0] = translations[0];
+                System.arraycopy(translations, 0, newTranslations, 1, translations.length);
+                translations = newTranslations;
+            }
+            if (rotations != null) {
+                Quaternion[] newRotations = new Quaternion[rotations.length + 1];
+                newRotations[0] = rotations[0];
+                System.arraycopy(rotations, 0, newRotations, 1, rotations.length);
+                rotations = newRotations;
+            }
+            if (scales != null) {
+                Vector3f[] newScales = new Vector3f[scales.length + 1];
+                newScales[0] = scales[0];
+                System.arraycopy(scales, 0, newScales, 1, scales.length);
+                scales = newScales;
+            }
+        }
+
+        length = times[times.length - 1];
+    }
+
+    private void populateTransform(Type type, int index, List<KeyFrame> keyFrames, KeyFrame currentKeyFrame, TransformIndices transformIndices) {
+        Object transform = getTransform(type, currentKeyFrame);
+        if (transform != null) {
+            getArray(type)[index] = transform;
+            transformIndices.last = index;
+        } else {
+            transformIndices.next = findNext(keyFrames, type, index);
+            if (transformIndices.next == -1) {
+                //no next let's use prev value.
+                if (transformIndices.last == -1) {
+                    //last Transform Index = -1 it means there are no transforms. nothing more to do
+                    return;
+                }
+                KeyFrame lastKeyFrame = keyFrames.get(transformIndices.last);
+                getArray(type)[index] = getTransform(type, lastKeyFrame);
+                return;
+            }
+            KeyFrame nextKeyFrame = keyFrames.get(transformIndices.next);
+            if (transformIndices.last == -1) {
+                //no previous transforms let's use the new one.
+                translations[index] = nextKeyFrame.translation;
+            } else {
+                //interpolation between the previous transform and the next one.
+                KeyFrame lastKeyFrame = keyFrames.get(transformIndices.last);
+                float ratio = currentKeyFrame.time / (nextKeyFrame.time - lastKeyFrame.time);
+                interpolate(type, ratio, lastKeyFrame, nextKeyFrame, index);
+            }
+
+        }
+    }
+
+    private int findNext(List<KeyFrame> keyFrames, Type type, int fromIndex) {
+        for (int i = fromIndex + 1; i < keyFrames.size(); i++) {
+            KeyFrame kf = keyFrames.get(i);
+            switch (type) {
+                case Translation:
+                    if (kf.translation != null) {
+                        return i;
+                    }
+                    break;
+                case Rotation:
+                    if (kf.rotation != null) {
+                        return i;
+                    }
+                    break;
+                case Scale:
+                    if (kf.scale != null) {
+                        return i;
+                    }
+                    break;
+            }
+        }
+        return -1;
+    }
+
+    private void interpolate(Type type, float ratio, KeyFrame lastKeyFrame, KeyFrame nextKeyFrame, int currentIndex) {
+        //TODO here we should interpolate differently according to the interpolation given in the gltf file.
+        switch (type) {
+            case Translation:
+                translations[currentIndex] = FastMath.interpolateLinear(ratio, lastKeyFrame.translation, nextKeyFrame.translation);
+                break;
+            case Rotation:
+                Quaternion rot = new Quaternion().set(lastKeyFrame.rotation);
+                rot.nlerp(nextKeyFrame.rotation, ratio);
+                rotations[currentIndex] = rot;
+                break;
+            case Scale:
+                scales[currentIndex] = FastMath.interpolateLinear(ratio, lastKeyFrame.scale, nextKeyFrame.scale);
+                break;
+        }
+    }
+
+    private Object[] getArray(Type type) {
+        switch (type) {
+            case Translation:
+                return translations;
+            case Rotation:
+                return rotations;
+            case Scale:
+                return scales;
+            default:
+                return translations;
+        }
+    }
+
+    private Object getTransform(Type type, KeyFrame kf) {
+        switch (type) {
+            case Translation:
+                return kf.translation;
+            case Rotation:
+                return kf.rotation;
+            case Scale:
+                return kf.scale;
+            default:
+                return kf.translation;
+        }
+    }
+
+    private void ensureArraysInit() {
+        if (translations == null || translations.length < times.length) {
+            translations = new Vector3f[times.length];
+            for (int i = 0; i < translations.length; i++) {
+                translations[i] = new Vector3f();
+            }
+        }
+        if (rotations == null || rotations.length < times.length) {
+            rotations = new Quaternion[times.length];
+            for (int i = 0; i < rotations.length; i++) {
+                rotations[i] = new Quaternion();
+            }
+        }
+        if (scales == null || scales.length < times.length) {
+            scales = new Vector3f[times.length];
+            for (int i = 0; i < scales.length; i++) {
+                scales[i] = new Vector3f().set(Vector3f.UNIT_XYZ);
+            }
+        }
+    }
+
+    private void setKeyFrameTransforms(Type type, KeyFrame keyFrame, float[] transformTimes) {
+        int index = 0;
+        while (Float.floatToIntBits(transformTimes[index]) != Float.floatToIntBits(keyFrame.time)) {
+            index++;
+        }
+        switch (type) {
+            case Translation:
+                keyFrame.translation = translations[index];
+                break;
+            case Rotation:
+                keyFrame.rotation = rotations[index];
+                break;
+            case Scale:
+                keyFrame.scale = scales[index];
+                break;
+        }
+    }
+
+    private boolean equalTimes(List<TimeData> timeData) {
+        if (timeData.size() == 1) {
+            return true;
+        }
+        float[] times0 = timeData.get(0).times;
+        for (int i = 1; i < timeData.size(); i++) {
+            float[] timesI = timeData.get(i).times;
+            if (!Arrays.equals(times0, timesI)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static class TimeData {
+
+        float[] times;
+        Type type;
+
+        public TimeData(float[] times, Type type) {
+            this.times = times;
+            this.type = type;
+        }
+    }
+
+    private class TransformIndices {
+        int last = -1;
+        int next = -1;
+    }
+
+    private class KeyFrame {
+        float time;
+        Vector3f translation;
+        Quaternion rotation;
+        Vector3f scale;
+    }
+
+}

+ 16 - 74
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

@@ -19,6 +19,7 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
 import javax.xml.bind.DatatypeConverter;
 import java.io.*;
 import java.nio.Buffer;
+import java.sql.Time;
 import java.util.*;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -760,27 +761,27 @@ public class GltfLoader implements AssetLoader {
                 times = readAccessorData(timeIndex, floatArrayPopulator);
                 addToCache("accessors", timeIndex, times, accessors.size());
             }
-            if (animData.times == null) {
-                animData.times = times;
-            } else {
-                //check if we are loading the same time array
-                if (animData.times != times) {
-                    //TODO there might be work to do here... if the inputs are different we might want to merge the different times array...
-                    //easier said than done.
-                    logger.log(Level.WARNING, "Channel has different input accessors for samplers");
-                }
-            }
-            if (animData.length == null) {
-                //animation length is the last timestamp
-                animData.length = times[times.length - 1];
-            }
+//            if (animData.times == null) {
+//                animData.times = times;
+//            } else {
+//                //check if we are loading the same time array
+//                if (animData.times != times) {
+//                    //TODO there might be work to do here... if the inputs are different we might want to merge the different times array...
+//                    //easier said than done.
+//                    logger.log(Level.WARNING, "Channel has different input accessors for samplers");
+//                }
+//            }
+
             if (targetPath.equals("translation")) {
+                animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Translation));
                 Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator);
                 animData.translations = translations;
             } else if (targetPath.equals("scale")) {
+                animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Scale));
                 Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator);
                 animData.scales = scales;
             } else if (targetPath.equals("rotation")) {
+                animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Rotation));
                 Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
                 animData.rotations = rotations;
             }
@@ -801,10 +802,10 @@ public class GltfLoader implements AssetLoader {
             if (animData == null) {
                 continue;
             }
+            animData.update();
             if (animData.length > anim.getLength()) {
                 anim.setLength(animData.length);
             }
-            animData.update();
             Object node = fetchFromCache("nodes", i, Object.class);
             if (node instanceof Spatial) {
                 Spatial s = (Spatial) node;
@@ -1140,65 +1141,6 @@ public class GltfLoader implements AssetLoader {
         }
     }
 
-    private class AnimData {
-        Float length;
-        float[] times;
-        Vector3f[] translations;
-        Quaternion[] rotations;
-        Vector3f[] scales;
-        //not used for now
-        float[] weights;
-
-        public void update() {
-            if (translations == null) {
-                translations = new Vector3f[times.length];
-                for (int i = 0; i < translations.length; i++) {
-                    translations[i] = new Vector3f();
-                }
-            }
-            if (rotations == null) {
-                rotations = new Quaternion[times.length];
-                for (int i = 0; i < rotations.length; i++) {
-                    rotations[i] = new Quaternion();
-                }
-            }
-            if (scales == null) {
-                scales = new Vector3f[times.length];
-                for (int i = 0; i < scales.length; i++) {
-                    scales[i] = new Vector3f().set(Vector3f.UNIT_XYZ);
-                }
-            }
-
-            if (times[0] > 0) {
-                //Anim doesn't start at 0, JME can't handle that and will interpolate transforms linearly from 0 to the first frame of the anim.
-                //we need to add a frame at 0 that copies the first real frame
-
-                float[] newTimes = new float[times.length + 1];
-                newTimes[0] = 0f;
-                System.arraycopy(times, 0, newTimes, 1, times.length);
-                times = newTimes;
-
-                if (translations != null) {
-                    Vector3f[] newTranslations = new Vector3f[translations.length + 1];
-                    newTranslations[0] = translations[0];
-                    System.arraycopy(translations, 0, newTranslations, 1, translations.length);
-                    translations = newTranslations;
-                }
-                if (rotations != null) {
-                    Quaternion[] newRotations = new Quaternion[rotations.length + 1];
-                    newRotations[0] = rotations[0];
-                    System.arraycopy(rotations, 0, newRotations, 1, rotations.length);
-                    rotations = newRotations;
-                }
-                if (scales != null) {
-                    Vector3f[] newScales = new Vector3f[scales.length + 1];
-                    newScales[0] = scales[0];
-                    System.arraycopy(scales, 0, newScales, 1, scales.length);
-                    scales = newScales;
-                }
-            }
-        }
-    }
 
     private class BoneWrapper {
         Bone bone;