diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java new file mode 100644 index 000000000..890b74239 --- /dev/null +++ b/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 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 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 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 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) { + 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; + } + +} \ No newline at end of file diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java index 0c6e98f72..a1353d33f 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java +++ b/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;