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

empirephoenix-patch-1
Nehon 7 years ago
parent 082fea969e
commit db23985f92
  1. 296
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java
  2. 90
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.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;
}
}

@ -19,6 +19,7 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
import javax.xml.bind.DatatypeConverter; import javax.xml.bind.DatatypeConverter;
import java.io.*; import java.io.*;
import java.nio.Buffer; import java.nio.Buffer;
import java.sql.Time;
import java.util.*; import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -760,27 +761,27 @@ public class GltfLoader implements AssetLoader {
times = readAccessorData(timeIndex, floatArrayPopulator); times = readAccessorData(timeIndex, floatArrayPopulator);
addToCache("accessors", timeIndex, times, accessors.size()); addToCache("accessors", timeIndex, times, accessors.size());
} }
if (animData.times == null) { // if (animData.times == null) {
animData.times = times; // animData.times = times;
} else { // } else {
//check if we are loading the same time array // //check if we are loading the same time array
if (animData.times != times) { // 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... // //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. // //easier said than done.
logger.log(Level.WARNING, "Channel has different input accessors for samplers"); // 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 (targetPath.equals("translation")) { if (targetPath.equals("translation")) {
animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Translation));
Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator); Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator);
animData.translations = translations; animData.translations = translations;
} else if (targetPath.equals("scale")) { } else if (targetPath.equals("scale")) {
animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Scale));
Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator); Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator);
animData.scales = scales; animData.scales = scales;
} else if (targetPath.equals("rotation")) { } else if (targetPath.equals("rotation")) {
animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Rotation));
Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator); Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
animData.rotations = rotations; animData.rotations = rotations;
} }
@ -801,10 +802,10 @@ public class GltfLoader implements AssetLoader {
if (animData == null) { if (animData == null) {
continue; continue;
} }
animData.update();
if (animData.length > anim.getLength()) { if (animData.length > anim.getLength()) {
anim.setLength(animData.length); anim.setLength(animData.length);
} }
animData.update();
Object node = fetchFromCache("nodes", i, Object.class); Object node = fetchFromCache("nodes", i, Object.class);
if (node instanceof Spatial) { if (node instanceof Spatial) {
Spatial s = (Spatial) node; 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 { private class BoneWrapper {
Bone bone; Bone bone;

Loading…
Cancel
Save