/// <summary> /// Export the bones and their animation for the given skin /// </summary> /// <param name="skin">The skin to export</param> /// <returns></returns> private BabylonBone[] ExportBones(IIGameSkin skin) { List <BabylonBone> bones = new List <BabylonBone>(); List <int> nodeIndices = GetNodeIndices(skin); List <IIGameNode> revelantNodes = GetSkinnedBones(skin); foreach (IIGameNode node in revelantNodes) { int parentIndex = (node.NodeParent == null) ? -1 : nodeIndices.IndexOf(node.NodeParent.NodeID); string boneId = node.MaxNode.GetGuid().ToString(); // create the bone BabylonBone bone = new BabylonBone() { id = (isGltfExported)?boneId:boneId + "-bone",// the suffix "-bone" is added in babylon export format to assure the uniqueness of IDs parentNodeId = (parentIndex != -1)?node.NodeParent.MaxNode.GetGuid().ToString():null, name = node.Name, index = nodeIndices.IndexOf(node.NodeID), parentBoneIndex = parentIndex, matrix = (parentIndex == -1)?node.GetWorldTM(0).ToArray():node.GetLocalTM(0).ToArray() }; // Apply unit conversion factor to meter // Affect translation only bone.matrix[12] *= scaleFactorToMeters; bone.matrix[13] *= scaleFactorToMeters; bone.matrix[14] *= scaleFactorToMeters; if (exportParameters.exportAnimations) { // export its animation var babylonAnimation = ExportMatrixAnimation("_matrix", key => { IGMatrix mat = node.GetLocalTM(key); float[] matrix = mat.ToArray(); // Apply unit conversion factor to meter // Affect translation only matrix[12] *= scaleFactorToMeters; matrix[13] *= scaleFactorToMeters; matrix[14] *= scaleFactorToMeters; return(matrix); }, false); // Do not remove linear animation keys for bones if (babylonAnimation != null) { babylonAnimation.name = node.Name + "Animation"; // override default animation name bone.animation = babylonAnimation; } } bones.Add(bone); } return(bones.ToArray()); }
/// <summary> /// Export the bones and their animation for the given skin /// </summary> /// <param name="skin">The skin to export</param> /// <returns></returns> private BabylonBone[] ExportBones(IIGameSkin skin, IIGameNode meshNode) { List <BabylonBone> bones = new List <BabylonBone>(); List <int> nodeIndices = GetNodeIndices(skin, meshNode); List <IIGameNode> revelantNodes = GetSkinnedBones(skin, meshNode); foreach (IIGameNode node in revelantNodes) { int parentIndex = (node.NodeParent == null) ? -1 : nodeIndices.IndexOf(node.NodeParent.NodeID); string boneId = node.MaxNode.GetGuid().ToString(); string animationTargetID = node.MaxNode.GetUniqueID(); // create the bone BabylonBone bone = new BabylonBone() { id = (isGltfExported)?boneId:boneId + "-bone",// the suffix "-bone" is added in babylon export format to assure the uniqueness of IDs AnimationTargetId = animationTargetID, parentNodeId = (parentIndex != -1)?node.NodeParent.MaxNode.GetGuid().ToString():null, name = node.Name, index = nodeIndices.IndexOf(node.NodeID), parentBoneIndex = parentIndex, matrix = (parentIndex == -1)?node.GetWorldTM(0).ToArray():node.GetLocalTM(0).ToArray() }; // export its animation var babylonAnimation = ExportMatrixAnimation("_matrix", key => { var objectTM = node.GetObjectTM(key); var parentNode = node.NodeParent; IGMatrix mat; if (parentNode == null || bone.parentBoneIndex == -1) { mat = objectTM; } else { mat = node.GetLocalTM(key); } return(mat.ToArray()); }, false, true, node.Name); // Do not remove linear animation keys for bones; Always optimize animations if (babylonAnimation != null) { babylonAnimation.name = node.Name + "Animation"; // override default animation name bone.animation = babylonAnimation; } bones.Add(bone); } return(bones.ToArray()); }
List<int> SortBones(IIGameSkin skin) { var boneIds = new List<int>(); var boneIndex = new Dictionary<int, IIGameNode>(); for (var index = 0; index < skin.TotalSkinBoneCount; index++) { var bone = skin.GetIGameBone(index, false); if (bone == null) { // non bone in skeletton boneIds.Add(-2); } else { boneIds.Add(bone.NodeID); boneIndex[bone.NodeID] = bone; } } while (true) { bool foundMisMatch = false; for (int i = 0; i < boneIds.Count; ++i) { var id = boneIds[i]; if (id == -2) { continue; } var parent = boneIndex[id].NodeParent; if (parent != null) { var parentId = parent.NodeID; if (boneIds.IndexOf(parentId) > i) { boneIds.RemoveAt(i); boneIds.Insert(boneIds.IndexOf(parentId) + 1, id); foundMisMatch = true; break; } } } if (!foundMisMatch) { break; } } return boneIds; }
int CreateGlobalVertex(IIGameMesh mesh, IFaceEx face, int facePart, List<GlobalVertex> vertices, bool hasUV, bool hasUV2, bool hasColor, bool hasAlpha, List<GlobalVertex>[] verticesAlreadyExported, IIGameSkin skin, List<int> boneIds) { var vertexIndex = (int)face.Vert[facePart]; var vertex = new GlobalVertex { BaseIndex = vertexIndex, Position = mesh.GetVertex(vertexIndex, true), Normal = mesh.GetNormal((int)face.Norm[facePart], true) }; if (hasUV) { var indices = new int[3]; unsafe { fixed (int* indicesPtr = indices) { mesh.GetMapFaceIndex(1, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var texCoord = mesh.GetMapVertex(1, indices[facePart]); vertex.UV = Loader.Global.Point2.Create(texCoord.X, -texCoord.Y); } if (hasUV2) { var indices = new int[3]; unsafe { fixed (int* indicesPtr = indices) { mesh.GetMapFaceIndex(2, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var texCoord = mesh.GetMapVertex(2, indices[facePart]); vertex.UV2 = Loader.Global.Point2.Create(texCoord.X, -texCoord.Y); } if (hasColor) { var vertexColorIndex = (int)face.Color[facePart]; var vertexColor = mesh.GetColorVertex(vertexColorIndex); float alpha = 1; if (hasAlpha) { var indices = new int[3]; unsafe { fixed (int* indicesPtr = indices) { mesh.GetMapFaceIndex(-2, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var color = mesh.GetMapVertex(-2, indices[facePart]); alpha = color.X; } vertex.Color = new[] { vertexColor.X, vertexColor.Y, vertexColor.Z, alpha }; } if (skin != null) { float weight0 = 0; float weight1 = 0; float weight2 = 0; int bone0 = bonesCount; int bone1 = bonesCount; int bone2 = bonesCount; int bone3 = bonesCount; int nbBones = skin.GetNumberOfBones(vertexIndex); if (nbBones > 0) { bone0 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 0).NodeID); weight0 = skin.GetWeight(vertexIndex, 0); } if (nbBones > 1) { bone1 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 1).NodeID); weight1 = skin.GetWeight(vertexIndex, 1); } if (nbBones > 2) { bone2 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 2).NodeID); weight2 = skin.GetWeight(vertexIndex, 2); } if (nbBones > 3) { bone3 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 3).NodeID); } if (nbBones == 0) { weight0 = 1.0f; bone0 = bonesCount; } if (nbBones > 4) { RaiseError("Too many bones influences per vertex: " + nbBones + ". Babylon.js only support 4 bones influences per vertex.", 2); } vertex.Weights = Loader.Global.Point4.Create(weight0, weight1, weight2, 1.0 - weight0 - weight1 - weight2); vertex.BonesIndices = (bone3 << 24) | (bone2 << 16) | (bone1 << 8) | bone0; } if (verticesAlreadyExported != null) { if (verticesAlreadyExported[vertexIndex] != null) { var index = verticesAlreadyExported[vertexIndex].IndexOf(vertex); if (index > -1) { return verticesAlreadyExported[vertexIndex][index].CurrentIndex; } } else { verticesAlreadyExported[vertexIndex] = new List<GlobalVertex>(); } vertex.CurrentIndex = vertices.Count; verticesAlreadyExported[vertexIndex].Add(vertex); } vertices.Add(vertex); return vertices.Count - 1; }
private void ExtractFace(IIGameSkin skin, IIGameMesh unskinnedMesh, List<GlobalVertex> vertices, List<int> indices, bool hasUV, bool hasUV2, bool hasColor, bool hasAlpha, List<GlobalVertex>[] verticesAlreadyExported, ref int indexCount, ref int minVertexIndex, ref int maxVertexIndex, IFaceEx face, List<int> boneIds) { var a = CreateGlobalVertex(unskinnedMesh, face, 0, vertices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, skin, boneIds); var b = CreateGlobalVertex(unskinnedMesh, face, 2, vertices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, skin, boneIds); var c = CreateGlobalVertex(unskinnedMesh, face, 1, vertices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, skin, boneIds); indices.Add(a); indices.Add(b); indices.Add(c); if (a < minVertexIndex) { minVertexIndex = a; } if (b < minVertexIndex) { minVertexIndex = b; } if (c < minVertexIndex) { minVertexIndex = c; } if (a > maxVertexIndex) { maxVertexIndex = a; } if (b > maxVertexIndex) { maxVertexIndex = b; } if (c > maxVertexIndex) { maxVertexIndex = c; } indexCount += 3; CheckCancelled(); }
private void ExportSkin(IIGameSkin skin, BabylonScene babylonScene) { var babylonSkeleton = new BabylonSkeleton { id = skins.IndexOf(skin) }; babylonSkeleton.name = "skeleton #" + babylonSkeleton.id; RaiseMessage(babylonSkeleton.name, 1); var skinIndex = skins.IndexOf(skin); var bones = new List<BabylonBone>(); var gameBones = new List<IIGameNode>(); var boneIds = new List<int>(); var bindPoseInfos = new List<BonePoseInfo>(); for(int i = 0; i < skin.TotalSkinBoneCount; ++i) { bones.Add(null); gameBones.Add(null); boneIds.Add(-1); bindPoseInfos.Add(null); } for (var index = 0; index < skin.TotalSkinBoneCount; index++) { var gameBone = skin.GetIGameBone(index, false); var sortedIndex = skinSortedBones[skin].IndexOf(gameBone.NodeID); gameBones[sortedIndex] = (gameBone); boneIds[sortedIndex] =(gameBone.NodeID); bones[sortedIndex]=(new BabylonBone { index = sortedIndex, name = gameBone.Name }); var boneInitMatrix = gameBone.GetObjectTM(0); bindPoseInfos[sortedIndex] = (new BonePoseInfo { AbsoluteTransform = boneInitMatrix }); } // fix hierarchy an generate animation keys var exportNonOptimizedAnimations = Loader.Core.RootNode.GetBoolProperty("babylonjs_exportnonoptimizedanimations"); for (var index = 0; index < skin.TotalSkinBoneCount; index++) { var gameBone = gameBones[index]; var parent = gameBone.NodeParent; var babBone = bones[index]; if (parent != null) { babBone.parentBoneIndex = boneIds.IndexOf(parent.NodeID); } if (babBone.parentBoneIndex == -1) { bindPoseInfos[index].LocalTransform = bindPoseInfos[index].AbsoluteTransform; } else { var parentBindPoseInfos = bindPoseInfos[babBone.parentBoneIndex]; bindPoseInfos[index].LocalTransform = bindPoseInfos[index].AbsoluteTransform.Multiply(parentBindPoseInfos.AbsoluteTransform.Inverse); } babBone.matrix = bindPoseInfos[index].LocalTransform.ToArray(); var babylonAnimation = new BabylonAnimation { name = gameBone.Name + "Animation", property = "_matrix", dataType = (int)BabylonAnimation.DataType.Matrix, loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, framePerSecond = Loader.Global.FrameRate }; var start = Loader.Core.AnimRange.Start; var end = Loader.Core.AnimRange.End; float[] previous = null; var keys = new List<BabylonAnimationKey>(); for (var key = start; key <= end; key += Ticks) { var objectTM = gameBone.GetObjectTM(key); var parentNode = gameBone.NodeParent; IGMatrix mat; if (parentNode == null || babBone.parentBoneIndex == -1) { mat = objectTM; } else { mat = objectTM.Multiply(parentNode.GetObjectTM(key).Inverse); } var current = mat.ToArray(); if (key == start || key == end || exportNonOptimizedAnimations || !(previous.IsEqualTo(current))) { keys.Add(new BabylonAnimationKey { frame = key / Ticks, values = current }); } previous = current; } babylonAnimation.keys = keys.ToArray(); babBone.animation = babylonAnimation; } babylonSkeleton.needInitialSkinMatrix = true; babylonSkeleton.bones = bones.ToArray(); babylonScene.SkeletonsList.Add(babylonSkeleton); }
private List <IIGameNode> GetRelevantNodes(IIGameSkin skin) { int logRank = 2; // For optimization if (relevantNodesBySkin.ContainsKey(skin)) { return(relevantNodesBySkin[skin]); } List <IIGameNode> bones = GetBones(skin); if (bones.Count == 0) { RaiseWarning("Skin has no bones.", logRank); return(new List <IIGameNode>()); } if (bones.Contains(null)) { RaiseError("Skin has bones that are outside of the exported hierarchy.", logRank); RaiseError("The skin cannot be exported", logRank); return(new List <IIGameNode>()); } List <IIGameNode> allHierarchyNodes = null; IIGameNode lowestCommonAncestor = GetLowestCommonAncestor(bones, ref allHierarchyNodes); if (lowestCommonAncestor == null) { RaiseError($"More than one root node for the skin. The skeleton bones need to be part of the same hierarchy.", logRank); RaiseError($"The skin cannot be exported", logRank); return(new List <IIGameNode>()); } // starting from the root, sort the nodes by depth first (add the children before the siblings) List <IIGameNode> sorted = new List <IIGameNode>(); Stack <IIGameNode> siblings = new Stack <IIGameNode>(); // keep the siblings in a LIFO list to add them after the children siblings.Push(lowestCommonAncestor); // add the skeletonroot: // - as a fallback for vertices without any joint weights (although invalid joints could also be "ok"?) // - to have easy access to the root node for the gltf's [skin.skeleton] property (skeleton root node) // [##onlyBones] commented for now because uncertain if it will work with babylon bone exports //sorted.Add(lowestCommonAncestor); while (siblings.Count > 0) { IIGameNode currentNode = siblings.Pop(); if (allHierarchyNodes.Contains(currentNode)) // The node is part of the skeleton hierarchy { // only add if the node is an actual bone (to keep the joint list small) // [##onlyBones] commented for now because uncertain if it will work with babylon bone exports //if (bones.Contains(currentNode)) sorted.Add(currentNode); // Add its children to the stack (in reverse order because it's a LIFO) int childCount = currentNode.ChildCount; for (int index = 0; index < childCount; index++) { siblings.Push(currentNode.GetNodeChild(childCount - 1 - index)); } } } relevantNodesBySkin.Add(skin, sorted); // Stock the result for optimization return(sorted); }
private void ExtractGeometry(BabylonAbstractMesh babylonAbstractMesh, List <GlobalVertex> vertices, List <int> indices, List <BabylonSubMesh> subMeshes, List <int> boneIds, IIGameSkin skin, IIGameMesh unskinnedMesh, IMatrix3 invertedWorldMatrix, IMatrix3 offsetTM, bool hasUV, bool hasUV2, bool hasColor, bool hasAlpha, bool optimizeVertices, int multiMatsCount, IIGameNode meshNode, ref List <int> faceIndexes) { Dictionary <GlobalVertex, List <GlobalVertex> > verticesAlreadyExported = null; if (optimizeVertices) { verticesAlreadyExported = new Dictionary <GlobalVertex, List <GlobalVertex> >(); } var indexStart = 0; // Whether or not to store order in which faces are exported // Storage is used when exporting Morph Targets geometry // To ensure face order is identical, especially with multimaterials involved bool storeFaceIndexes = faceIndexes == null; if (storeFaceIndexes) { faceIndexes = new List <int>(); } int indexInFaceIndexesArray = 0; for (int i = 0; i < multiMatsCount; ++i) { int materialId = i; var indexCount = 0; var minVertexIndex = int.MaxValue; var maxVertexIndex = int.MinValue; var subMesh = new BabylonSubMesh { indexStart = indexStart, materialIndex = i }; if (multiMatsCount == 1) { for (int j = 0; j < unskinnedMesh.NumberOfFaces; ++j) { IFaceEx face = null; if (storeFaceIndexes) { face = unskinnedMesh.GetFace(j); // Store face index (j = face.MeshFaceIndex) faceIndexes.Add(j); } else { face = unskinnedMesh.GetFace(faceIndexes[indexInFaceIndexesArray++]); } ExtractFace(skin, unskinnedMesh, babylonAbstractMesh, invertedWorldMatrix, offsetTM, vertices, indices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, ref indexCount, ref minVertexIndex, ref maxVertexIndex, face, boneIds); } } else { if (i == 0 || isMaterialDoubleSided == false) { ITab <IFaceEx> materialFaces = unskinnedMesh.GetFacesFromMatID(materialId); for (int j = 0; j < materialFaces.Count; ++j) { IFaceEx face = null; if (storeFaceIndexes) { // Retreive face #if MAX2017 || MAX2018 || MAX2019 || MAX2020 face = materialFaces[j]; #else face = materialFaces[new IntPtr(j)]; #endif // Store face index faceIndexes.Add(face.MeshFaceIndex); } else { face = unskinnedMesh.GetFace(faceIndexes[indexInFaceIndexesArray++]); } ExtractFace(skin, unskinnedMesh, babylonAbstractMesh, invertedWorldMatrix, offsetTM, vertices, indices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, ref indexCount, ref minVertexIndex, ref maxVertexIndex, face, boneIds); } } else { // It's a double sided material // The back faces are created at runtime // WARNING - Nested multimaterial and double sided material are not supported minVertexIndex = vertices.Count; maxVertexIndex = vertices.Count * 2 - 1; // Vertices int nbVertices = vertices.Count; for (int index = 0; index < nbVertices; index++) { GlobalVertex vertexOrg = vertices[index]; // Duplicate vertex GlobalVertex vertexNew = new GlobalVertex(vertexOrg); // Inverse back vertices normal vertexNew.Normal = vertexNew.Normal.MultiplyBy(-1); vertexNew.Tangent = vertexNew.Tangent.MultiplyBy(-1); vertices.Add(vertexNew); } // Faces int nbIndices = indices.Count; for (int index = 0; index < nbIndices; index += 3) { // Duplicate and flip faces indices.Add(indices[index + 2] + nbIndices); indices.Add(indices[index + 1] + nbIndices); indices.Add(indices[index] + nbIndices); indexCount += 3; } } } if (indexCount != 0) { subMesh.indexCount = indexCount; subMesh.verticesStart = minVertexIndex; subMesh.verticesCount = maxVertexIndex - minVertexIndex + 1; indexStart += indexCount; subMeshes.Add(subMesh); } } }
private void ExportSkin(IIGameSkin skin, BabylonScene babylonScene) { var babylonSkeleton = new BabylonSkeleton { id = skins.IndexOf(skin) }; babylonSkeleton.name = "skeleton #" + babylonSkeleton.id; RaiseMessage(babylonSkeleton.name, 1); var skinIndex = skins.IndexOf(skin); var bones = new List <BabylonBone>(); var gameBones = new List <IIGameNode>(); var boneIds = new List <int>(); var bindPoseInfos = new List <BonePoseInfo>(); for (int i = 0; i < skin.TotalSkinBoneCount; ++i) { bones.Add(null); gameBones.Add(null); boneIds.Add(-1); bindPoseInfos.Add(null); } for (var index = 0; index < skin.TotalSkinBoneCount; index++) { var gameBone = skin.GetIGameBone(index, false); var sortedIndex = skinSortedBones[skin].IndexOf(gameBone.NodeID); gameBones[sortedIndex] = (gameBone); boneIds[sortedIndex] = (gameBone.NodeID); bones[sortedIndex] = (new BabylonBone { index = sortedIndex, name = gameBone.Name }); var boneInitMatrix = gameBone.GetObjectTM(0); bindPoseInfos[sortedIndex] = (new BonePoseInfo { AbsoluteTransform = boneInitMatrix }); } // fix hierarchy an generate animation keys var exportNonOptimizedAnimations = Loader.Core.RootNode.GetBoolProperty("babylonjs_exportnonoptimizedanimations"); for (var index = 0; index < skin.TotalSkinBoneCount; index++) { var gameBone = gameBones[index]; var parent = gameBone.NodeParent; var babBone = bones[index]; if (parent != null) { babBone.parentBoneIndex = boneIds.IndexOf(parent.NodeID); } if (babBone.parentBoneIndex == -1) { bindPoseInfos[index].LocalTransform = bindPoseInfos[index].AbsoluteTransform; } else { var parentBindPoseInfos = bindPoseInfos[babBone.parentBoneIndex]; bindPoseInfos[index].LocalTransform = bindPoseInfos[index].AbsoluteTransform.Multiply(parentBindPoseInfos.AbsoluteTransform.Inverse); } babBone.matrix = bindPoseInfos[index].LocalTransform.ToArray(); var babylonAnimation = new BabylonAnimation { name = gameBone.Name + "Animation", property = "_matrix", dataType = (int)BabylonAnimation.DataType.Matrix, loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, framePerSecond = Loader.Global.FrameRate }; var start = Loader.Core.AnimRange.Start; var end = Loader.Core.AnimRange.End; float[] previous = null; var keys = new List <BabylonAnimationKey>(); for (var key = start; key <= end; key += Ticks) { var objectTM = gameBone.GetObjectTM(key); var parentNode = gameBone.NodeParent; IGMatrix mat; if (parentNode == null || babBone.parentBoneIndex == -1) { mat = objectTM; } else { mat = objectTM.Multiply(parentNode.GetObjectTM(key).Inverse); } var current = mat.ToArray(); if (key == start || key == end || exportNonOptimizedAnimations || !(previous.IsEqualTo(current))) { keys.Add(new BabylonAnimationKey { frame = key / Ticks, values = current }); } previous = current; } babylonAnimation.keys = keys.ToArray(); babBone.animation = babylonAnimation; } babylonSkeleton.needInitialSkinMatrix = true; babylonSkeleton.bones = bones.ToArray(); babylonScene.SkeletonsList.Add(babylonSkeleton); }
private void ExportSkin(IIGameSkin skin, BabylonScene babylonScene) { var babylonSkeleton = new BabylonSkeleton { id = skins.IndexOf(skin) }; babylonSkeleton.name = "skeleton #" + babylonSkeleton.id; RaiseMessage(babylonSkeleton.name, 1); var skinIndex = skins.IndexOf(skin); var bones = new List <BabylonBone>(); var gameBones = new List <IIGameNode>(); var boneIds = new List <int>(); var bindPoseInfos = new List <BonePoseInfo>(); for (int i = 0; i < skin.TotalSkinBoneCount; ++i) { bones.Add(null); gameBones.Add(null); boneIds.Add(-1); bindPoseInfos.Add(null); } for (var index = 0; index < skin.TotalSkinBoneCount; index++) { var gameBone = skin.GetIGameBone(index, false); var sortedIndex = skinSortedBones[skin].IndexOf(gameBone.NodeID); gameBones[sortedIndex] = (gameBone); boneIds[sortedIndex] = (gameBone.NodeID); bones[sortedIndex] = (new BabylonBone { index = sortedIndex, name = gameBone.Name }); var boneInitMatrix = gameBone.GetObjectTM(0); bindPoseInfos[sortedIndex] = (new BonePoseInfo { AbsoluteTransform = boneInitMatrix }); } // fix hierarchy and generate animation keys for (var index = 0; index < skin.TotalSkinBoneCount; index++) { var gameBone = gameBones[index]; var parent = gameBone.NodeParent; var babBone = bones[index]; if (parent != null) { babBone.parentBoneIndex = boneIds.IndexOf(parent.NodeID); } if (babBone.parentBoneIndex == -1) { bindPoseInfos[index].LocalTransform = bindPoseInfos[index].AbsoluteTransform; } else { var parentBindPoseInfos = bindPoseInfos[babBone.parentBoneIndex]; bindPoseInfos[index].LocalTransform = bindPoseInfos[index].AbsoluteTransform.Multiply(parentBindPoseInfos.AbsoluteTransform.Inverse); } babBone.matrix = bindPoseInfos[index].LocalTransform.ToArray(); var babylonAnimation = ExportMatrixAnimation("_matrix", key => { var objectTM = gameBone.GetObjectTM(key); var parentNode = gameBone.NodeParent; IGMatrix mat; if (parentNode == null || babBone.parentBoneIndex == -1) { mat = objectTM; } else { mat = objectTM.Multiply(parentNode.GetObjectTM(key).Inverse); } return(mat.ToArray()); }, false); // Do not remove linear animation keys for bones if (babylonAnimation != null) { babylonAnimation.name = gameBone.Name + "Animation"; // override default animation name babBone.animation = babylonAnimation; } } babylonSkeleton.needInitialSkinMatrix = true; babylonSkeleton.bones = bones.ToArray(); babylonScene.SkeletonsList.Add(babylonSkeleton); }