From 84ab4ce00706723a88e7b47666bccf7375984af8 Mon Sep 17 00:00:00 2001 From: Chris Sinclair Date: Wed, 24 Feb 2021 08:25:20 +0000 Subject: [PATCH] Patch up GLTF Skeleton loading (#1610) * Add support for u8 bone indicies when loading glTF * Fix segfault for glTF animations not keyframed at 0 When loading glTF animations we lerp between keyframes, and previously assume that if the frame we are considering has a later keyframe, there must be a previous keyframe. This is not true if the animation's first keyframe is some time into the animation. In this case we now effectively clamp to that first keyframe for any time prior to it. * 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 | 105 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 23 deletions(-) diff --git a/src/models.c b/src/models.c index 5c54902a9..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++) @@ -3879,7 +3898,28 @@ static Model LoadGLTF(const char *fileName) short* bones = RL_MALLOC(sizeof(short) * acc->count * 4); LOAD_ACCESSOR(short, 4, acc, bones); - for (unsigned int a = 0; a < acc->count * 4; a ++) + for (unsigned int a = 0; a < acc->count * 4; a++) + { + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (&(data->nodes[k]) == skinJoint) + { + model.meshes[primitiveIndex].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else if (acc->component_type == cgltf_component_type_r_8u) + { + model.meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int) * acc->count * 4); + unsigned char* bones = RL_MALLOC(sizeof(unsigned char) * acc->count * 4); + + LOAD_ACCESSOR(unsigned char, 4, acc, bones); + for (unsigned int a = 0; a < acc->count * 4; a++) { cgltf_node* skinJoint = data->skins->joints[bones[a]]; @@ -4009,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)); @@ -4054,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 @@ -4099,11 +4142,11 @@ static ModelAnimation* LoadGLTFModelAnimations(const char *fileName, int *animCo if (frameTime < inputFrameTime) { shouldSkipFurtherTransformation = false; - outputMin = j - 1; + outputMin = (j == 0) ? 0 : j - 1; outputMax = j; float previousInputTime = 0.0f; - if (GltfReadFloat(sampler->input, j - 1, (float*)&previousInputTime, 1)) + if (GltfReadFloat(sampler->input, outputMin, (float*)&previousInputTime, 1)) { lerpPercent = (frameTime - previousInputTime) / (inputFrameTime - previousInputTime); } @@ -4163,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); } }