Properly Read and use inverseBindMatrices for skeleton bind pose.
This commit is contained in:
parent
225afd0f92
commit
de78c8deb6
@ -11,7 +11,6 @@ import com.jme3.renderer.queue.RenderQueue;
|
|||||||
import com.jme3.scene.*;
|
import com.jme3.scene.*;
|
||||||
import com.jme3.texture.Texture;
|
import com.jme3.texture.Texture;
|
||||||
import com.jme3.texture.Texture2D;
|
import com.jme3.texture.Texture2D;
|
||||||
import com.jme3.util.BufferUtils;
|
|
||||||
import com.jme3.util.IntMap;
|
import com.jme3.util.IntMap;
|
||||||
import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
|
import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
|
||||||
|
|
||||||
@ -52,9 +51,13 @@ public class GltfLoader implements AssetLoader {
|
|||||||
private FloatArrayPopulator floatArrayPopulator = new FloatArrayPopulator();
|
private FloatArrayPopulator floatArrayPopulator = new FloatArrayPopulator();
|
||||||
private Vector3fArrayPopulator vector3fArrayPopulator = new Vector3fArrayPopulator();
|
private Vector3fArrayPopulator vector3fArrayPopulator = new Vector3fArrayPopulator();
|
||||||
private QuaternionArrayPopulator quaternionArrayPopulator = new QuaternionArrayPopulator();
|
private QuaternionArrayPopulator quaternionArrayPopulator = new QuaternionArrayPopulator();
|
||||||
|
private Matrix4fArrayPopulator matrix4fArrayPopulator = new Matrix4fArrayPopulator();
|
||||||
private static Map<String, MaterialAdapter> defaultMaterialAdapters = new HashMap<>();
|
private static Map<String, MaterialAdapter> defaultMaterialAdapters = new HashMap<>();
|
||||||
private boolean useNormalsFlag = false;
|
private boolean useNormalsFlag = false;
|
||||||
private Quaternion tmpQuat = new Quaternion();
|
private Quaternion tmpQuat = new Quaternion();
|
||||||
|
private Transform tmpTransforms = new Transform();
|
||||||
|
private Transform tmpTransforms2 = new Transform();
|
||||||
|
private Matrix4f tmpMat = new Matrix4f();
|
||||||
|
|
||||||
Map<SkinData, List<Spatial>> skinnedSpatials = new HashMap<>();
|
Map<SkinData, List<Spatial>> skinnedSpatials = new HashMap<>();
|
||||||
IntMap<SkinBuffers> skinBuffers = new IntMap<>();
|
IntMap<SkinBuffers> skinBuffers = new IntMap<>();
|
||||||
@ -246,7 +249,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (loaded instanceof BoneWrapper) {
|
} else if (loaded instanceof BoneWrapper) {
|
||||||
//parent is the Armature Node, we have to apply its transforms to the root bone's bind pose and to its animation data
|
//parent is the Armature Node, we have to apply its transforms to the root bone's animation data
|
||||||
BoneWrapper bw = (BoneWrapper) loaded;
|
BoneWrapper bw = (BoneWrapper) loaded;
|
||||||
bw.isRoot = true;
|
bw.isRoot = true;
|
||||||
SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class);
|
SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class);
|
||||||
@ -789,14 +792,26 @@ public class GltfLoader implements AssetLoader {
|
|||||||
JsonArray joints = skin.getAsJsonArray("joints");
|
JsonArray joints = skin.getAsJsonArray("joints");
|
||||||
assertNotNull(joints, "No joints defined for skin");
|
assertNotNull(joints, "No joints defined for skin");
|
||||||
|
|
||||||
//inverseBindMatrices are also intentionally ignored. JME computes them from the bind transforms when initializing the skeleton.
|
//These inverse bind matrices, once inverted again, will give us the real bind pose of the bones (in model space),
|
||||||
//Integer matricesIndex = getAsInteger(skin, "inverseBindMatrices");
|
//since the skeleton in not guaranteed to be exported in bind pose.
|
||||||
|
Integer matricesIndex = getAsInteger(skin, "inverseBindMatrices");
|
||||||
|
Matrix4f[] inverseBindMatrices = null;
|
||||||
|
if (matricesIndex != null) {
|
||||||
|
inverseBindMatrices = readAccessorData(matricesIndex, matrix4fArrayPopulator);
|
||||||
|
} else {
|
||||||
|
inverseBindMatrices = new Matrix4f[joints.size()];
|
||||||
|
for (int i = 0; i < inverseBindMatrices.length; i++) {
|
||||||
|
inverseBindMatrices[i] = new Matrix4f();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Bone[] bones = new Bone[joints.size()];
|
Bone[] bones = new Bone[joints.size()];
|
||||||
for (int i = 0; i < joints.size(); i++) {
|
for (int i = 0; i < joints.size(); i++) {
|
||||||
int boneIndex = joints.get(i).getAsInt();
|
int boneIndex = joints.get(i).getAsInt();
|
||||||
|
//we don't need the inverse bind matrix, we need the bind matrix so let's invert it.
|
||||||
|
Matrix4f modelBindMatrix = inverseBindMatrices[i].invertLocal();
|
||||||
//TODO actually a regular node or a geometry can be attached to a bone, we have to handle this and attach it to the AttachementNode.
|
//TODO actually a regular node or a geometry can be attached to a bone, we have to handle this and attach it to the AttachementNode.
|
||||||
bones[i] = readNodeAsBone(boneIndex, i, index);
|
bones[i] = readNodeAsBone(boneIndex, i, index, modelBindMatrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < joints.size(); i++) {
|
for (int i = 0; i < joints.size(); i++) {
|
||||||
@ -805,16 +820,55 @@ public class GltfLoader implements AssetLoader {
|
|||||||
|
|
||||||
Skeleton skeleton = new Skeleton(bones);
|
Skeleton skeleton = new Skeleton(bones);
|
||||||
|
|
||||||
|
for (Bone bone : skeleton.getRoots()) {
|
||||||
|
BoneWrapper bw = findBoneWrapper(bone);
|
||||||
|
computeBindTransforms(bw, skeleton);
|
||||||
|
}
|
||||||
|
|
||||||
SkinData skinData = new SkinData();
|
SkinData skinData = new SkinData();
|
||||||
skinData.skeletonControl = new SkeletonControl(skeleton);
|
skinData.skeletonControl = new SkeletonControl(skeleton);
|
||||||
addToCache("skins", index, skinData, nodes.size());
|
addToCache("skins", index, skinData, nodes.size());
|
||||||
skinnedSpatials.put(skinData, new ArrayList<Spatial>());
|
skinnedSpatials.put(skinData, new ArrayList<Spatial>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeBindTransforms(BoneWrapper boneWrapper, Skeleton skeleton) {
|
||||||
|
Bone bone = boneWrapper.bone;
|
||||||
|
tmpTransforms.fromTransformMatrix(boneWrapper.modelBindMatrix);
|
||||||
|
if (bone.getParent() != null) {
|
||||||
|
//root bone, model transforms are the same as the local transforms
|
||||||
|
//but for child bones we need to combine it with the parents inverse model transforms.
|
||||||
|
tmpMat.setTranslation(bone.getParent().getModelSpacePosition());
|
||||||
|
tmpMat.setRotationQuaternion(bone.getParent().getModelSpaceRotation());
|
||||||
|
tmpMat.setScale(bone.getParent().getModelSpaceScale());
|
||||||
|
tmpMat.invertLocal();
|
||||||
|
tmpTransforms2.fromTransformMatrix(tmpMat);
|
||||||
|
tmpTransforms.combineWithParent(tmpTransforms2);
|
||||||
|
}
|
||||||
|
bone.setBindTransforms(tmpTransforms.getTranslation(), tmpTransforms.getRotation(), tmpTransforms.getScale());
|
||||||
|
|
||||||
|
//resets the local transforms to bind transforms for all bones.
|
||||||
|
//then computes the model transforms from local transforms for each bone.
|
||||||
|
skeleton.resetAndUpdate();
|
||||||
|
skeleton.setBindingPose();
|
||||||
|
for (Integer childIndex : boneWrapper.children) {
|
||||||
|
BoneWrapper child = fetchFromCache("nodes", childIndex, BoneWrapper.class);
|
||||||
|
computeBindTransforms(child, skeleton);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bone readNodeAsBone(int nodeIndex, int boneIndex, int skinIndex) throws IOException {
|
private BoneWrapper findBoneWrapper(Bone bone) {
|
||||||
|
for (int i = 0; i < nodes.size(); i++) {
|
||||||
|
BoneWrapper bw = fetchFromCache("nodes", i, BoneWrapper.class);
|
||||||
|
if (bw != null && bw.bone == bone) {
|
||||||
|
return bw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bone readNodeAsBone(int nodeIndex, int boneIndex, int skinIndex, Matrix4f modelBindMatrix) throws IOException {
|
||||||
|
|
||||||
BoneWrapper boneWrapper = fetchFromCache("nodes", nodeIndex, BoneWrapper.class);
|
BoneWrapper boneWrapper = fetchFromCache("nodes", nodeIndex, BoneWrapper.class);
|
||||||
if (boneWrapper != null) {
|
if (boneWrapper != null) {
|
||||||
@ -829,7 +883,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
Transform boneTransforms = readTransforms(nodeData);
|
Transform boneTransforms = readTransforms(nodeData);
|
||||||
bone.setBindTransforms(boneTransforms.getTranslation(), boneTransforms.getRotation(), boneTransforms.getScale());
|
bone.setBindTransforms(boneTransforms.getTranslation(), boneTransforms.getRotation(), boneTransforms.getScale());
|
||||||
|
|
||||||
addToCache("nodes", nodeIndex, new BoneWrapper(bone, boneIndex, skinIndex), nodes.size());
|
addToCache("nodes", nodeIndex, new BoneWrapper(bone, boneIndex, skinIndex, modelBindMatrix), nodes.size());
|
||||||
|
|
||||||
return bone;
|
return bone;
|
||||||
}
|
}
|
||||||
@ -844,6 +898,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
BoneWrapper cbw = fetchFromCache("nodes", childIndex, BoneWrapper.class);
|
BoneWrapper cbw = fetchFromCache("nodes", childIndex, BoneWrapper.class);
|
||||||
if (cbw != null) {
|
if (cbw != null) {
|
||||||
bw.bone.addChild(cbw.bone);
|
bw.bone.addChild(cbw.bone);
|
||||||
|
bw.children.add(childIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -910,12 +965,15 @@ public class GltfLoader implements AssetLoader {
|
|||||||
Bone bone;
|
Bone bone;
|
||||||
int boneIndex;
|
int boneIndex;
|
||||||
int skinIndex;
|
int skinIndex;
|
||||||
|
Matrix4f modelBindMatrix;
|
||||||
boolean isRoot = false;
|
boolean isRoot = false;
|
||||||
|
List<Integer> children = new ArrayList<>();
|
||||||
|
|
||||||
public BoneWrapper(Bone bone, int boneIndex, int skinIndex) {
|
public BoneWrapper(Bone bone, int boneIndex, int skinIndex, Matrix4f modelBindMatrix) {
|
||||||
this.bone = bone;
|
this.bone = bone;
|
||||||
this.boneIndex = boneIndex;
|
this.boneIndex = boneIndex;
|
||||||
this.skinIndex = skinIndex;
|
this.skinIndex = skinIndex;
|
||||||
|
this.modelBindMatrix = modelBindMatrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -924,11 +982,6 @@ public class GltfLoader implements AssetLoader {
|
|||||||
public void update(AnimData data) {
|
public void update(AnimData 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);
|
||||||
if (isRoot) {
|
|
||||||
|
|
||||||
bindTransforms.combineWithParent(skinData.armatureTransforms);
|
|
||||||
bone.setBindTransforms(bindTransforms.getTranslation(), bindTransforms.getRotation(), bindTransforms.getScale());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < data.translations.length; i++) {
|
for (int i = 0; i < data.translations.length; i++) {
|
||||||
Transform t = new Transform(data.translations[i], data.rotations[i], data.scales[i]);
|
Transform t = new Transform(data.translations[i], data.rotations[i], data.scales[i]);
|
||||||
@ -1076,13 +1129,23 @@ public class GltfLoader implements AssetLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class JointData {
|
private class Matrix4fArrayPopulator implements Populator<Matrix4f[]> {
|
||||||
short[] joints;
|
|
||||||
int componentSize;
|
|
||||||
|
|
||||||
public JointData(short[] joints, int componentSize) {
|
@Override
|
||||||
this.joints = joints;
|
public Matrix4f[] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset) throws IOException {
|
||||||
this.componentSize = componentSize;
|
|
||||||
|
int numComponents = getNumberOfComponents(type);
|
||||||
|
int dataSize = numComponents * count;
|
||||||
|
Matrix4f[] data = new Matrix4f[count];
|
||||||
|
|
||||||
|
if (bufferViewIndex == null) {
|
||||||
|
//no referenced buffer, specs says to pad the data with zeros.
|
||||||
|
padBuffer(data, dataSize);
|
||||||
|
} else {
|
||||||
|
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,7 +461,8 @@ public class GltfUtils {
|
|||||||
stream.skipBytes(byteOffset);
|
stream.skipBytes(byteOffset);
|
||||||
int arrayIndex = 0;
|
int arrayIndex = 0;
|
||||||
while (index < end) {
|
while (index < end) {
|
||||||
array[arrayIndex] = new Matrix4f(
|
|
||||||
|
array[arrayIndex] = toRowMajor(
|
||||||
stream.readFloat(),
|
stream.readFloat(),
|
||||||
stream.readFloat(),
|
stream.readFloat(),
|
||||||
stream.readFloat(),
|
stream.readFloat(),
|
||||||
@ -479,6 +480,7 @@ public class GltfUtils {
|
|||||||
stream.readFloat(),
|
stream.readFloat(),
|
||||||
stream.readFloat()
|
stream.readFloat()
|
||||||
);
|
);
|
||||||
|
//gltf matrix are column major, JME ones are row major.
|
||||||
|
|
||||||
arrayIndex++;
|
arrayIndex++;
|
||||||
|
|
||||||
@ -486,6 +488,13 @@ public class GltfUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Matrix4f toRowMajor(float m00, float m01, float m02, float m03,
|
||||||
|
float m10, float m11, float m12, float m13,
|
||||||
|
float m20, float m21, float m22, float m23,
|
||||||
|
float m30, float m31, float m32, float m33) {
|
||||||
|
return new Matrix4f(m00, m10, m20, m30, m01, m11, m21, m31, m02, m12, m22, m32, m03, m13, m23, m33);
|
||||||
|
}
|
||||||
|
|
||||||
private static LittleEndien getStream(byte[] buffer) {
|
private static LittleEndien getStream(byte[] buffer) {
|
||||||
return new LittleEndien(new DataInputStream(new ByteArrayInputStream(buffer)));
|
return new LittleEndien(new DataInputStream(new ByteArrayInputStream(buffer)));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user