glTf: proper animation data padding when transforms are given as sparse arrays
This commit is contained in:
parent
082fea969e
commit
db23985f92
@ -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 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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user