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.
This commit is contained in:
Chris Sinclair 2021-02-23 17:14:01 +00:00
parent 6322facb53
commit f07363c7b0
No known key found for this signature in database
GPG Key ID: 5C323201E3EF4E75

View File

@ -3723,7 +3723,7 @@ static Model LoadGLTF(const char *fileName)
for (unsigned int j = 0; j < data->nodes_count; j++) 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); 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++) for (unsigned int i = 0; i < data->nodes_count; i++)
@ -3740,21 +3740,40 @@ static Model LoadGLTF(const char *fileName)
else model.bindPose[i].scale = Vector3One(); else model.bindPose[i].scale = Vector3One();
} }
for (int i = 0; i < model.boneCount; i++)
{ {
Transform *currentTransform = model.bindPose + i; bool* completedBones = RL_CALLOC(model.boneCount, sizeof(bool));
BoneInfo *currentBone = model.bones + i; int numberCompletedBones = 0;
int root = currentBone->parent;
if (root >= model.boneCount) root = 0;
Transform *parentTransform = model.bindPose + root;
if (currentBone->parent >= 0) while (numberCompletedBones < model.boneCount) {
{ for (int i = 0; i < model.boneCount; i++)
currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation); {
currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation); if (completedBones[i]) continue;
currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation);
currentTransform->scale = Vector3Multiply(parentTransform->scale, parentTransform->scale); 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++) for (int i = 0; i < model.materialCount - 1; i++)
@ -4031,6 +4050,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" : TRACELOG(LOG_INFO, "MODEL: [%s] glTF animations (%s) count: %i", fileName, (data->file_type == 2)? "glb" :
"gltf", data->animations_count); "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)); animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation));
for (unsigned int a = 0; a < data->animations_count; a++) for (unsigned int a = 0; a < data->animations_count; a++)
@ -4075,7 +4097,7 @@ static ModelAnimation* LoadGLTFModelAnimations(const char *fileName, int *animCo
for (unsigned int j = 0; j < data->nodes_count; j++) 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); 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 // Allocate data for frames
@ -4184,16 +4206,32 @@ static ModelAnimation* LoadGLTFModelAnimations(const char *fileName, int *animCo
// Build frameposes // Build frameposes
for (int frame = 0; frame < output->frameCount; frame++) for (int frame = 0; frame < output->frameCount; frame++)
{ {
for (int i = 0; i < output->boneCount; i++) bool* completedBones = RL_CALLOC(output->boneCount, sizeof(bool));
{ int numberCompletedBones = 0;
if (output->bones[i].parent >= 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].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 = 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].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); 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);
} }
} }