From f07363c7b094e610b558a2fb5b1ba50d0d2d2124 Mon Sep 17 00:00:00 2001 From: Chris Sinclair Date: Tue, 23 Feb 2021 17:14:01 +0000 Subject: [PATCH] Respect parent bones tranform when loading glTF animations We previously assumed that when loading glTF animations, the bones were ordered with those higher up the skeleton tree (i.e. closer to the root) came first in the list of nodes. This may not be true, so now we repeatedly loop, preparing each level of the skeleton tree one after the other, starting at the root level. This ensures that any parent transforms are applied before transforming any child bones. We also ensure that we have forced the loading of animation data before attempting to interpolate to generate the animation frames for use later, without this no animations are applied. Finally we remove the check that assumed the first node in the nodes list is the root, and use an invalid index value as the sentinal value for when a node has no parent. Previously this was 0, which made distinguishing between root nodes and children of the first node impossible. --- src/models.c | 78 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/src/models.c b/src/models.c index 0ee2804ee..b92228556 100644 --- a/src/models.c +++ b/src/models.c @@ -3719,11 +3719,11 @@ static Model LoadGLTF(const char *fileName) model.boneCount = (int)data->nodes_count; model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo)); model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform)); - + for (unsigned int j = 0; j < data->nodes_count; j++) { strcpy(model.bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); - model.bones[j].parent = (int)((j != 0 && data->nodes[j].parent != NULL) ? data->nodes[j].parent - data->nodes : 0); + model.bones[j].parent = (data->nodes[j].parent != NULL) ? data->nodes[j].parent - data->nodes : -1; } for (unsigned int i = 0; i < data->nodes_count; i++) @@ -3739,22 +3739,41 @@ static Model LoadGLTF(const char *fileName) if (data->nodes[i].has_scale) memcpy(&model.bindPose[i].scale, data->nodes[i].scale, 3 * sizeof(float)); else model.bindPose[i].scale = Vector3One(); } - - for (int i = 0; i < model.boneCount; i++) + { - Transform *currentTransform = model.bindPose + i; - BoneInfo *currentBone = model.bones + i; - int root = currentBone->parent; - if (root >= model.boneCount) root = 0; - Transform *parentTransform = model.bindPose + root; - - if (currentBone->parent >= 0) - { - currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation); - currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation); - currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation); - currentTransform->scale = Vector3Multiply(parentTransform->scale, parentTransform->scale); + bool* completedBones = RL_CALLOC(model.boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < model.boneCount) { + for (int i = 0; i < model.boneCount; i++) + { + if (completedBones[i]) continue; + + if (model.bones[i].parent < 0) { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[model.bones[i].parent]) continue; + + Transform* currentTransform = &model.bindPose[i]; + BoneInfo* currentBone = &model.bones[i]; + int root = currentBone->parent; + if (root >= model.boneCount) + root = 0; + Transform* parentTransform = &model.bindPose[root]; + + currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation); + currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation); + currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation); + currentTransform->scale = Vector3Multiply(parentTransform->scale, parentTransform->scale); + completedBones[i] = true; + numberCompletedBones++; + } } + + RL_FREE(completedBones); } for (int i = 0; i < model.materialCount - 1; i++) @@ -4030,6 +4049,9 @@ static ModelAnimation* LoadGLTFModelAnimations(const char *fileName, int *animCo { TRACELOG(LOG_INFO, "MODEL: [%s] glTF animations (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->animations_count); + + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TRACELOG(LOG_WARNING, "MODEL: [%s] unable to load glTF animations data", fileName); animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation)); @@ -4075,7 +4097,7 @@ static ModelAnimation* LoadGLTFModelAnimations(const char *fileName, int *animCo for (unsigned int j = 0; j < data->nodes_count; j++) { strcpy(output->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); - output->bones[j].parent = j != 0 ? (int)(data->nodes[j].parent - data->nodes) : 0; + output->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1; } // Allocate data for frames @@ -4184,16 +4206,32 @@ static ModelAnimation* LoadGLTFModelAnimations(const char *fileName, int *animCo // Build frameposes for (int frame = 0; frame < output->frameCount; frame++) { - for (int i = 0; i < output->boneCount; i++) - { - if (output->bones[i].parent >= 0) + bool* completedBones = RL_CALLOC(output->boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < output->boneCount) { + for (int i = 0; i < output->boneCount; i++) { + if (completedBones[i]) continue; + + if (output->bones[i].parent < 0) { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[output->bones[i].parent]) continue; + output->framePoses[frame][i].rotation = QuaternionMultiply(output->framePoses[frame][output->bones[i].parent].rotation, output->framePoses[frame][i].rotation); output->framePoses[frame][i].translation = Vector3RotateByQuaternion(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].rotation); output->framePoses[frame][i].translation = Vector3Add(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].translation); output->framePoses[frame][i].scale = Vector3Multiply(output->framePoses[frame][i].scale, output->framePoses[frame][output->bones[i].parent].scale); + completedBones[i] = true; + numberCompletedBones++; } } + + RL_FREE(completedBones); } }