From 5dbbaf0f065864f3bb6040e69238e105245fcd6d Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 8 Dec 2017 22:07:07 +0100 Subject: [PATCH] glTF: Fixes additional issues with bones transforms --- .../model/shape/TestGltfLoading2.java | 7 + .../jme3/scene/plugins/gltf/GltfLoader.java | 123 +++++++++++++++--- .../jme3/scene/plugins/gltf/GltfUtils.java | 28 ++++ 3 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 jme3-examples/src/main/java/jme3test/model/shape/TestGltfLoading2.java diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestGltfLoading2.java b/jme3-examples/src/main/java/jme3test/model/shape/TestGltfLoading2.java new file mode 100644 index 000000000..561d6005d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestGltfLoading2.java @@ -0,0 +1,7 @@ +package jme3test.model.shape; + +/** + * Created by Nehon on 09/12/2017. + */ +public class TestGltfLoading2 { +} 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 1e555c64d..350c08d6b 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 @@ -246,6 +246,7 @@ public class GltfLoader implements AssetLoader { SkinData skinData = fetchFromCache("skins", skinIndex, SkinData.class); List spatials = skinnedSpatials.get(skinData); spatials.add(spatial); + skinData.used = true; } spatial.setLocalTransform(readTransforms(nodeData)); @@ -732,6 +733,17 @@ public class GltfLoader implements AssetLoader { logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation"); continue; } + + //if targetNode is a bone, check if it's in a used skin. + BoneWrapper bw = fetchFromCache("nodes", targetNode, BoneWrapper.class); + if (bw != null) { + SkinData skin = fetchFromCache("skins", bw.skinIndex, SkinData.class); + if (skin == null || !skin.used) { + //this skin is not referenced by any mesh, let's not load animation for it. + continue; + } + } + TrackData trackData = tracks[targetNode]; if (trackData == null) { trackData = new TrackData(); @@ -790,6 +802,7 @@ public class GltfLoader implements AssetLoader { anim.setName(name); int skinIndex = -1; + List usedBones = new ArrayList<>(); for (int i = 0; i < tracks.length; i++) { TrackData trackData = tracks[i]; if (trackData == null || trackData.timeArrays.isEmpty()) { @@ -810,6 +823,7 @@ public class GltfLoader implements AssetLoader { BoneWrapper b = (BoneWrapper) node; //apply the inverseBindMatrix to animation data. b.update(trackData); + usedBones.add(b.bone); BoneTrack track = new BoneTrack(b.boneIndex, trackData.times, trackData.translations, trackData.rotations, trackData.scales); anim.addTrack(track); if (skinIndex == -1) { @@ -824,6 +838,32 @@ public class GltfLoader implements AssetLoader { } } + // Check each bone to see if their local pose is different from their bind pose. + // If it is, we ensure that the bone has an animation track, else JME way of applying anim transforms will apply the bind pose to those bones, + // instead of the local pose that is supposed to be the default + if (skinIndex != -1) { + SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class); + Skeleton skeleton = skin.skeletonControl.getSkeleton(); + for (Bone bone : skin.bones) { + if (!usedBones.contains(bone) && !equalBindAndLocalTransforms(bone)) { + //create a track + float[] times = new float[]{0, anim.getLength()}; + + Vector3f t = bone.getLocalPosition().subtract(bone.getBindPosition()); + Quaternion r = tmpQuat.set(bone.getBindRotation()).inverse().multLocal(bone.getLocalRotation()); + Vector3f s = bone.getLocalScale().divide(bone.getBindScale()); + + Vector3f[] translations = new Vector3f[]{t, t}; + Quaternion[] rotations = new Quaternion[]{r, r}; + Vector3f[] scales = new Vector3f[]{s, s}; + + int boneIndex = skeleton.getBoneIndex(bone); + BoneTrack track = new BoneTrack(boneIndex, times, translations, rotations, scales); + anim.addTrack(track); + } + } + } + anim = customContentManager.readExtensionAndExtras("animations", animation, anim); if (skinIndex != -1) { @@ -935,17 +975,25 @@ public class GltfLoader implements AssetLoader { computeBindTransforms(bw, skeleton); } - if (isKeepSkeletonPose(info)) { - //Set local transforms.The skeleton may come in a given pose, that is not the rest pose, so let 's apply it. - for (int i = 0; i < joints.size(); i++) { - applyPose(joints.get(i).getAsInt()); + // Set local transforms. + // The skeleton may come in a given pose, that is not the rest pose, so let 's apply it. + // We will need it later for animation + for (int i = 0; i < joints.size(); i++) { + applyPose(joints.get(i).getAsInt()); + } + skeleton.updateWorldVectors(); + + //If the user didn't ask to keep the pose we reset the skeleton user control + if (!isKeepSkeletonPose(info)) { + for (Bone bone : bones) { + bone.setUserControl(false); } - skeleton.updateWorldVectors(); } skeleton = customContentManager.readExtensionAndExtras("skin", skin, skeleton); SkinData skinData = new SkinData(); + skinData.bones = bones; skinData.skeletonControl = new SkeletonControl(skeleton); addToCache("skins", index, skinData, nodes.size()); skinnedSpatials.put(skinData, new ArrayList()); @@ -1140,6 +1188,7 @@ public class GltfLoader implements AssetLoader { int boneIndex; int skinIndex; Transform localTransform; + Transform localTransformOffset; Matrix4f modelBindMatrix; boolean isRoot = false; boolean localUpdated = false; @@ -1152,6 +1201,7 @@ public class GltfLoader implements AssetLoader { this.skinIndex = skinIndex; this.modelBindMatrix = modelBindMatrix; this.localTransform = localTransform; + this.localTransformOffset = localTransform.clone(); } /** @@ -1164,15 +1214,15 @@ public class GltfLoader implements AssetLoader { if (!localUpdated) { //LocalTransform of the bone are default position to use for animations when there is no track. //We need to transform them so that JME can us them in blendAnimTransform. - reverseBlendAnimTransforms(localTransform, bindTransforms); + reverseBlendAnimTransforms(localTransformOffset, bindTransforms); localUpdated = true; } for (int i = 0; i < data.getNbKeyFrames(); i++) { - Vector3f translation = getTranslation(data, bindTransforms, i); - Quaternion rotation = getRotation(data, bindTransforms, i); - Vector3f scale = getScale(data, bindTransforms, i); + Vector3f translation = getTranslation(data, i); + Quaternion rotation = getRotation(data, i); + Vector3f scale = getScale(data, i); Transform t = new Transform(translation, rotation, scale); if (isRoot) { @@ -1193,7 +1243,7 @@ public class GltfLoader implements AssetLoader { } } - data.ensureTranslationRotations(localTransform); + data.ensureTranslationRotations(localTransformOffset); } private void reverseBlendAnimTransforms(Transform t, Transform bindTransforms) { @@ -1208,30 +1258,30 @@ public class GltfLoader implements AssetLoader { t.setRotation(tmpQuat); } - private Vector3f getTranslation(TrackData data, Transform bindTransforms, int i) { + private Vector3f getTranslation(TrackData data, int i) { Vector3f translation; if (data.translations == null) { - translation = bindTransforms.getTranslation(); + translation = bone.getLocalPosition(); } else { translation = data.translations[i]; } return translation; } - private Quaternion getRotation(TrackData data, Transform bindTransforms, int i) { + private Quaternion getRotation(TrackData data, int i) { Quaternion rotation; if (data.rotations == null) { - rotation = bindTransforms.getRotation(); + rotation = bone.getLocalRotation(); } else { rotation = data.rotations[i]; } return rotation; } - private Vector3f getScale(TrackData data, Transform bindTransforms, int i) { + private Vector3f getScale(TrackData data, int i) { Vector3f scale; if (data.scales == null) { - scale = bindTransforms.getScale(); + scale = bone.getLocalScale(); } else { scale = data.scales[i]; } @@ -1243,6 +1293,47 @@ public class GltfLoader implements AssetLoader { SkeletonControl skeletonControl; AnimControl animControl; Transform armatureTransforms; + Bone[] bones; + boolean used = false; + } + + private class PartialTransforms { + Vector3f translation; + Quaternion rotation; + Vector3f scale; + Transform transform; + + Transform getTransforms() { + if (transform == null) { + if (translation == null) { + translation = new Vector3f(); + } + if (rotation == null) { + rotation = new Quaternion(); + } + if (scale == null) { + scale = new Vector3f(1, 1, 1); + } + transform = new Transform(translation, rotation, scale); + } + return transform; + } + + Transform getTransforms(Transform bindTransforms) { + if (transform == null) { + if (translation == null) { + translation = bindTransforms.getTranslation(); + } + if (rotation == null) { + rotation = bindTransforms.getRotation(); + } + if (scale == null) { + scale = bindTransforms.getScale(); + } + transform = new Transform(translation, rotation, scale); + } + return transform; + } } public static class SkinBuffers { diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java index 88ebf9741..68675e680 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -1,6 +1,7 @@ package com.jme3.scene.plugins.gltf; import com.google.gson.*; +import com.jme3.animation.Bone; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoadException; import com.jme3.math.*; @@ -685,6 +686,33 @@ public class GltfUtils { } } + public static boolean equalBindAndLocalTransforms(Bone b) { + return equalsEpsilon(b.getBindPosition(), b.getLocalPosition()) + && equalsEpsilon(b.getBindRotation(), b.getLocalRotation()) + && equalsEpsilon(b.getBindScale(), b.getLocalScale()); + } + + private static float epsilon = 0.0001f; + + public static boolean equalsEpsilon(Vector3f v1, Vector3f v2) { + return FastMath.abs(v1.x - v2.x) < epsilon + && FastMath.abs(v1.y - v2.y) < epsilon + && FastMath.abs(v1.z - v2.z) < epsilon; + } + + public static boolean equalsEpsilon(Quaternion q1, Quaternion q2) { + return (FastMath.abs(q1.getX() - q2.getX()) < epsilon + && FastMath.abs(q1.getY() - q2.getY()) < epsilon + && FastMath.abs(q1.getZ() - q2.getZ()) < epsilon + && FastMath.abs(q1.getW() - q2.getW()) < epsilon) + || + (FastMath.abs(q1.getX() + q2.getX()) < epsilon + && FastMath.abs(q1.getY() + q2.getY()) < epsilon + && FastMath.abs(q1.getZ() + q2.getZ()) < epsilon + && FastMath.abs(q1.getW() + q2.getW()) < epsilon); + } + + public static void dumpArray(Object[] array) { if (array == null) { System.err.println("null");