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:
parent
0cb748f30b
commit
84ab4ce007
105
src/models.c
105
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user