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.
This commit is contained in:
Chris Sinclair 2021-02-24 08:25:20 +00:00 committed by GitHub
parent 0cb748f30b
commit 84ab4ce007
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -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);
}
}