glTF: Fixed animation data interpolation, also renamed AnimData to TrackData to match JME paradigm.
This commit is contained in:
parent
132b0abc50
commit
54061124a9
@ -19,7 +19,6 @@ 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;
|
||||||
@ -714,7 +713,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
assertNotNull(samplers, "No samplers for animation " + name);
|
assertNotNull(samplers, "No samplers for animation " + name);
|
||||||
|
|
||||||
//temp data storage of track data
|
//temp data storage of track data
|
||||||
AnimData[] animatedNodes = new AnimData[nodes.size()];
|
TrackData[] animatedNodes = new TrackData[nodes.size()];
|
||||||
|
|
||||||
for (JsonElement channel : channels) {
|
for (JsonElement channel : channels) {
|
||||||
|
|
||||||
@ -733,10 +732,10 @@ public class GltfLoader implements AssetLoader {
|
|||||||
logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation");
|
logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
AnimData animData = animatedNodes[targetNode];
|
TrackData trackData = animatedNodes[targetNode];
|
||||||
if (animData == null) {
|
if (trackData == null) {
|
||||||
animData = new AnimData();
|
trackData = new TrackData();
|
||||||
animatedNodes[targetNode] = animData;
|
animatedNodes[targetNode] = trackData;
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer samplerIndex = getAsInteger(channel.getAsJsonObject(), "sampler");
|
Integer samplerIndex = getAsInteger(channel.getAsJsonObject(), "sampler");
|
||||||
@ -754,7 +753,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
logger.log(Level.WARNING, "JME only supports linear interpolation for animations");
|
logger.log(Level.WARNING, "JME only supports linear interpolation for animations");
|
||||||
}
|
}
|
||||||
|
|
||||||
animData = customContentManager.readExtensionAndExtras("animation.sampler", sampler, animData);
|
trackData = customContentManager.readExtensionAndExtras("animation.sampler", sampler, trackData);
|
||||||
|
|
||||||
float[] times = fetchFromCache("accessors", timeIndex, float[].class);
|
float[] times = fetchFromCache("accessors", timeIndex, float[].class);
|
||||||
if (times == null) {
|
if (times == null) {
|
||||||
@ -763,17 +762,17 @@ public class GltfLoader implements AssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetPath.equals("translation")) {
|
if (targetPath.equals("translation")) {
|
||||||
animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Translation));
|
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Translation));
|
||||||
Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator);
|
Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator);
|
||||||
animData.translations = translations;
|
trackData.translations = translations;
|
||||||
} else if (targetPath.equals("scale")) {
|
} else if (targetPath.equals("scale")) {
|
||||||
animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Scale));
|
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Scale));
|
||||||
Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator);
|
Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator);
|
||||||
animData.scales = scales;
|
trackData.scales = scales;
|
||||||
} else if (targetPath.equals("rotation")) {
|
} else if (targetPath.equals("rotation")) {
|
||||||
animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Rotation));
|
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Rotation));
|
||||||
Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
|
Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
|
||||||
animData.rotations = rotations;
|
trackData.rotations = rotations;
|
||||||
} else {
|
} else {
|
||||||
//TODO support weights
|
//TODO support weights
|
||||||
logger.log(Level.WARNING, "Morph animation is not supported");
|
logger.log(Level.WARNING, "Morph animation is not supported");
|
||||||
@ -781,7 +780,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
}
|
}
|
||||||
animatedNodes[targetNode] = customContentManager.readExtensionAndExtras("channel", channel, animData);
|
animatedNodes[targetNode] = customContentManager.readExtensionAndExtras("channel", channel, trackData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
@ -794,26 +793,26 @@ public class GltfLoader implements AssetLoader {
|
|||||||
int skinIndex = -1;
|
int skinIndex = -1;
|
||||||
|
|
||||||
for (int i = 0; i < animatedNodes.length; i++) {
|
for (int i = 0; i < animatedNodes.length; i++) {
|
||||||
AnimData animData = animatedNodes[i];
|
TrackData trackData = animatedNodes[i];
|
||||||
if (animData == null) {
|
if (trackData == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
animData.update();
|
trackData.update();
|
||||||
if (animData.length > anim.getLength()) {
|
if (trackData.length > anim.getLength()) {
|
||||||
anim.setLength(animData.length);
|
anim.setLength(trackData.length);
|
||||||
}
|
}
|
||||||
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;
|
||||||
spatials.add(s);
|
spatials.add(s);
|
||||||
SpatialTrack track = new SpatialTrack(animData.times, animData.translations, animData.rotations, animData.scales);
|
SpatialTrack track = new SpatialTrack(trackData.times, trackData.translations, trackData.rotations, trackData.scales);
|
||||||
track.setTrackSpatial(s);
|
track.setTrackSpatial(s);
|
||||||
anim.addTrack(track);
|
anim.addTrack(track);
|
||||||
} else if (node instanceof BoneWrapper) {
|
} else if (node instanceof BoneWrapper) {
|
||||||
BoneWrapper b = (BoneWrapper) node;
|
BoneWrapper b = (BoneWrapper) node;
|
||||||
//apply the inverseBindMatrix to animation data.
|
//apply the inverseBindMatrix to animation data.
|
||||||
b.update(animData);
|
b.update(trackData);
|
||||||
BoneTrack track = new BoneTrack(b.boneIndex, animData.times, animData.translations, animData.rotations, animData.scales);
|
BoneTrack track = new BoneTrack(b.boneIndex, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
|
||||||
anim.addTrack(track);
|
anim.addTrack(track);
|
||||||
if (skinIndex == -1) {
|
if (skinIndex == -1) {
|
||||||
skinIndex = b.skinIndex;
|
skinIndex = b.skinIndex;
|
||||||
@ -961,6 +960,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
bw.bone.setLocalTranslation(bw.localTransform.getTranslation());
|
bw.bone.setLocalTranslation(bw.localTransform.getTranslation());
|
||||||
bw.bone.setLocalRotation(bw.localTransform.getRotation());
|
bw.bone.setLocalRotation(bw.localTransform.getRotation());
|
||||||
bw.bone.setLocalScale(bw.localTransform.getScale());
|
bw.bone.setLocalScale(bw.localTransform.getScale());
|
||||||
|
bw.bone.setUserControl(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void computeBindTransforms(BoneWrapper boneWrapper, Skeleton skeleton) {
|
private void computeBindTransforms(BoneWrapper boneWrapper, Skeleton skeleton) {
|
||||||
@ -1159,12 +1159,17 @@ public class GltfLoader implements AssetLoader {
|
|||||||
/**
|
/**
|
||||||
* Applies the inverse Bind transforms to anim data. and the armature transforms if relevant.
|
* Applies the inverse Bind transforms to anim data. and the armature transforms if relevant.
|
||||||
*/
|
*/
|
||||||
public void update(AnimData data) {
|
public void update(TrackData data) {
|
||||||
Transform bindTransforms = new Transform(bone.getBindPosition(), bone.getBindRotation(), bone.getBindScale());
|
Transform bindTransforms = new Transform(bone.getBindPosition(), bone.getBindRotation(), bone.getBindScale());
|
||||||
SkinData skinData = fetchFromCache("skins", skinIndex, SkinData.class);
|
SkinData skinData = fetchFromCache("skins", skinIndex, SkinData.class);
|
||||||
|
|
||||||
for (int i = 0; i < data.translations.length; i++) {
|
for (int i = 0; i < data.getNbKeyFrames(); i++) {
|
||||||
Transform t = new Transform(data.translations[i], data.rotations[i], data.scales[i]);
|
|
||||||
|
Vector3f translation = getTranslation(data, bindTransforms, i);
|
||||||
|
Quaternion rotation = getRotation(data, bindTransforms, i);
|
||||||
|
Vector3f scale = getScale(data, bindTransforms, i);
|
||||||
|
|
||||||
|
Transform t = new Transform(translation, rotation, scale);
|
||||||
if (isRoot) {
|
if (isRoot) {
|
||||||
//Apply the armature transforms to the root bone anim track.
|
//Apply the armature transforms to the root bone anim track.
|
||||||
t.combineWithParent(skinData.armatureTransforms);
|
t.combineWithParent(skinData.armatureTransforms);
|
||||||
@ -1180,10 +1185,48 @@ public class GltfLoader implements AssetLoader {
|
|||||||
tmpQuat.set(bindTransforms.getRotation()).inverseLocal().multLocal(t.getRotation());
|
tmpQuat.set(bindTransforms.getRotation()).inverseLocal().multLocal(t.getRotation());
|
||||||
t.setRotation(tmpQuat);
|
t.setRotation(tmpQuat);
|
||||||
|
|
||||||
data.translations[i] = t.getTranslation();
|
if(data.translations != null) {
|
||||||
data.rotations[i] = t.getRotation();
|
data.translations[i] = t.getTranslation();
|
||||||
data.scales[i] = t.getScale();
|
}
|
||||||
|
if(data.rotations != null) {
|
||||||
|
data.rotations[i] = t.getRotation();
|
||||||
|
}
|
||||||
|
if(data.scales != null) {
|
||||||
|
data.scales[i] = t.getScale();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.ensureTranslationRotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3f getTranslation(TrackData data, Transform bindTransforms, int i) {
|
||||||
|
Vector3f translation;
|
||||||
|
if(data.translations == null){
|
||||||
|
translation = bindTransforms.getTranslation();
|
||||||
|
} else {
|
||||||
|
translation = data.translations[i];
|
||||||
|
}
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Quaternion getRotation(TrackData data, Transform bindTransforms, int i) {
|
||||||
|
Quaternion rotation;
|
||||||
|
if(data.rotations == null){
|
||||||
|
rotation = bindTransforms.getRotation();
|
||||||
|
} else {
|
||||||
|
rotation = data.rotations[i];
|
||||||
|
}
|
||||||
|
return rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3f getScale(TrackData data, Transform bindTransforms, int i) {
|
||||||
|
Vector3f scale;
|
||||||
|
if(data.scales == null){
|
||||||
|
scale = bindTransforms.getScale();
|
||||||
|
} else {
|
||||||
|
scale = data.scales[i];
|
||||||
|
}
|
||||||
|
return scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import com.jme3.math.*;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class AnimData {
|
public class TrackData {
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
Translation,
|
Translation,
|
||||||
@ -27,7 +27,6 @@ public class AnimData {
|
|||||||
|
|
||||||
if (equalTimes(timeArrays)) {
|
if (equalTimes(timeArrays)) {
|
||||||
times = timeArrays.get(0).times;
|
times = timeArrays.get(0).times;
|
||||||
ensureArraysInit();
|
|
||||||
} else {
|
} else {
|
||||||
//Times array are different and contains different sampling times.
|
//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.
|
//We have to merge them because JME needs the 3 types of transforms for each keyFrame.
|
||||||
@ -69,7 +68,7 @@ public class AnimData {
|
|||||||
// populating transforms array from the keyframes, interpolating
|
// populating transforms array from the keyframes, interpolating
|
||||||
times = new float[keyFrames.size()];
|
times = new float[keyFrames.size()];
|
||||||
|
|
||||||
ensureArraysInit();
|
ensureArraysLength();
|
||||||
|
|
||||||
TransformIndices translationIndices = new TransformIndices();
|
TransformIndices translationIndices = new TransformIndices();
|
||||||
TransformIndices rotationIndices = new TransformIndices();
|
TransformIndices rotationIndices = new TransformIndices();
|
||||||
@ -79,14 +78,18 @@ public class AnimData {
|
|||||||
KeyFrame kf = keyFrames.get(i);
|
KeyFrame kf = keyFrames.get(i);
|
||||||
//we need Interpolate between keyframes when transforms are sparse.
|
//we need Interpolate between keyframes when transforms are sparse.
|
||||||
times[i] = kf.time;
|
times[i] = kf.time;
|
||||||
populateTransform(Type.Translation, i, keyFrames, kf, translationIndices);
|
if(translations != null) {
|
||||||
populateTransform(Type.Rotation, i, keyFrames, kf, rotationIndices);
|
populateTransform(Type.Translation, i, keyFrames, kf, translationIndices);
|
||||||
populateTransform(Type.Scale, i, keyFrames, kf, scaleIndices);
|
}
|
||||||
|
if(rotations != null) {
|
||||||
|
populateTransform(Type.Rotation, i, keyFrames, kf, rotationIndices);
|
||||||
|
}
|
||||||
|
if(scales != null) {
|
||||||
|
populateTransform(Type.Scale, i, keyFrames, kf, scaleIndices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureArraysInit();
|
|
||||||
|
|
||||||
if (times[0] > 0) {
|
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.
|
//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
|
//we need to add a frame at 0 that copies the first real frame
|
||||||
@ -174,6 +177,19 @@ public class AnimData {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNbKeyFrames(){
|
||||||
|
if(translations != null){
|
||||||
|
return translations.length;
|
||||||
|
}
|
||||||
|
if(rotations != null){
|
||||||
|
return rotations.length;
|
||||||
|
}
|
||||||
|
if(scales != null){
|
||||||
|
return scales.length;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void interpolate(Type type, float ratio, KeyFrame lastKeyFrame, KeyFrame nextKeyFrame, int currentIndex) {
|
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.
|
//TODO here we should interpolate differently according to the interpolation given in the gltf file.
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -217,25 +233,34 @@ public class AnimData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureArraysInit() {
|
private void ensureArraysLength() {
|
||||||
if (translations == null || translations.length < times.length) {
|
if (translations != null && translations.length < times.length) {
|
||||||
|
translations = new Vector3f[times.length];
|
||||||
|
}
|
||||||
|
if (rotations != null && rotations.length < times.length) {
|
||||||
|
rotations = new Quaternion[times.length];
|
||||||
|
}
|
||||||
|
if (scales != null && scales.length < times.length) {
|
||||||
|
scales = new Vector3f[times.length];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//JME assumes there are translation and rotation track every time, so we create them with identity transforms if they don't exist
|
||||||
|
//TODO change this behavior in BoneTrack.
|
||||||
|
public void ensureTranslationRotations() {
|
||||||
|
if (translations == null) {
|
||||||
translations = new Vector3f[times.length];
|
translations = new Vector3f[times.length];
|
||||||
for (int i = 0; i < translations.length; i++) {
|
for (int i = 0; i < translations.length; i++) {
|
||||||
translations[i] = new Vector3f();
|
translations[i] = new Vector3f();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rotations == null || rotations.length < times.length) {
|
if (rotations == null) {
|
||||||
rotations = new Quaternion[times.length];
|
rotations = new Quaternion[times.length];
|
||||||
for (int i = 0; i < rotations.length; i++) {
|
for (int i = 0; i < rotations.length; i++) {
|
||||||
rotations[i] = new Quaternion();
|
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) {
|
private void setKeyFrameTransforms(Type type, KeyFrame keyFrame, float[] transformTimes) {
|
Loading…
x
Reference in New Issue
Block a user