diff --git a/examples/models/models_loading_gltf.c b/examples/models/models_loading_gltf.c index 37c74891a..3d69cf943 100644 --- a/examples/models/models_loading_gltf.c +++ b/examples/models/models_loading_gltf.c @@ -55,11 +55,13 @@ int main(void) // Update //---------------------------------------------------------------------------------- ModelAnimation anim = modelAnimations[animIndex]; - if (IsKeyPressed(KEY_UP)) { + if (IsKeyPressed(KEY_UP)) + { animIndex = (animIndex + 1) % animsCount; } - if (IsKeyPressed(KEY_DOWN)) { + if (IsKeyPressed(KEY_DOWN)) + { animIndex = (animIndex + animsCount - 1) % animsCount; } diff --git a/src/rmodels.c b/src/rmodels.c index 486a7a14b..15784bd75 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -3829,14 +3829,17 @@ RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Ve return collision; } -static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform *transforms) { +static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform *transforms) +{ for (int i = 0; i < boneCount; i++) { if (bones[i].parent >= 0) { - // Assumes bones are topologically sorted and we've transformed - // the parent before this node. - assert(bones[i].parent < i); + if (bones[i].parent > i) + { + TRACELOG(LOG_WARNING, "Assumes bones are toplogically sorted, but bone %d has parent %d. Skipping.", i, bones[i].parent); + continue; + } transforms[i].rotation = QuaternionMultiply(transforms[bones[i].parent].rotation, transforms[i].rotation); transforms[i].translation = Vector3RotateByQuaternion(transforms[i].translation, transforms[bones[i].parent].rotation); transforms[i].translation = Vector3Add(transforms[i].translation, transforms[bones[i].parent].translation); @@ -4687,18 +4690,22 @@ static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPat return image; } -static BoneInfo *LoadGLTFBoneInfo(cgltf_skin skin, int *boneCount) { +static BoneInfo *LoadGLTFBoneInfo(cgltf_skin skin, int *boneCount) +{ *boneCount = skin.joints_count; BoneInfo *bones = RL_MALLOC(skin.joints_count*sizeof(BoneInfo)); - for (unsigned int i = 0; i < skin.joints_count; i++) { + for (unsigned int i = 0; i < skin.joints_count; i++) + { cgltf_node node = *skin.joints[i]; strncpy(bones[i].name, node.name, sizeof(bones[i].name)); // find parent bone index unsigned int parentIndex = -1; - for (unsigned int j = 0; j < skin.joints_count; j++) { - if (skin.joints[j] == node.parent) { + for (unsigned int j = 0; j < skin.joints_count; j++) + { + if (skin.joints[j] == node.parent) + { parentIndex = j; break; } @@ -5074,12 +5081,14 @@ static Model LoadGLTF(const char *fileName) // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes //---------------------------------------------------------------------------------------------------- - if (data->skins_count == 1) { + if (data->skins_count == 1) + { cgltf_skin skin = data->skins[0]; model.bones = LoadGLTFBoneInfo(skin, &model.boneCount); model.bindPose = RL_MALLOC(model.boneCount*sizeof(Transform)); - for (unsigned int i = 0; i < model.boneCount; i++) { + for (unsigned int i = 0; i < model.boneCount; i++) + { cgltf_node node = *skin.joints[i]; model.bindPose[i].translation.x = node.translation[0]; model.bindPose[i].translation.y = node.translation[1]; @@ -5096,7 +5105,9 @@ static Model LoadGLTF(const char *fileName) } BuildPoseFromParentJoints(model.bones, model.boneCount, model.bindPose); - } else if (data->skins_count > 1) { + } + else if (data->skins_count > 1) + { TRACELOG(LOG_ERROR, "MODEL: [%s] can only load one skin (armature) per model, but gltf skins_count == %i", fileName, data->skins_count); } @@ -5121,7 +5132,6 @@ static Model LoadGLTF(const char *fileName) model.meshes[meshIndex].boneIds = RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); // Load 4 components of unsigned char data type into mesh.boneIds - // TODO: It seems LOAD_ATTRIBUTE() macro does not work as expected in some cases, // for cgltf_attribute_type_joints we have: // - data.meshes[0] (256 vertices) // - 256 values, provided as cgltf_type_vec4 of bytes (4 byte per joint, stride 4) @@ -5170,20 +5180,24 @@ static Model LoadGLTF(const char *fileName) return model; } -static void GetGLTFPoseAtTime(cgltf_accessor* input, cgltf_accessor *output, float time, void *data) { - assert(input->count == output->count); +// Get interpolated pose for bone sampler at a specific time. Returns true on success. +static bool GetGLTFPoseAtTime(cgltf_accessor* input, cgltf_accessor *output, float time, void *data) +{ + // input and output should have the same count float tstart = 0.0f; float tend = 0.0f; int keyframe = 0; // defaults to first pose - for (int i = 0; i < input->count - 1; i++) { + for (int i = 0; i < input->count - 1; i++) + { cgltf_bool r1 = cgltf_accessor_read_float(input, i, &tstart, 1); - assert(r1); + if (!r1) return false; cgltf_bool r2 = cgltf_accessor_read_float(input, i+1, &tend, 1); - assert(r2); + if (!r2) return false; - if ((tstart <= time) && (time < tend)) { + if ((tstart <= time) && (time < tend)) + { keyframe = i; break; } @@ -5193,8 +5207,10 @@ static void GetGLTFPoseAtTime(cgltf_accessor* input, cgltf_accessor *output, flo t = (t < 0.0f)? 0.0f : t; t = (t > 1.0f)? 1.0f : t; - assert(output->component_type == cgltf_component_type_r_32f); - if (output->type == cgltf_type_vec3) { + if (output->component_type != cgltf_component_type_r_32f) return false; + + if (output->type == cgltf_type_vec3) + { float tmp[3] = { 0.0f }; cgltf_accessor_read_float(output, keyframe, tmp, 3); Vector3 v1 = {tmp[0], tmp[1], tmp[2]}; @@ -5202,7 +5218,9 @@ static void GetGLTFPoseAtTime(cgltf_accessor* input, cgltf_accessor *output, flo Vector3 v2 = {tmp[0], tmp[1], tmp[2]}; Vector3 *r = data; *r = Vector3Lerp(v1, v2, t); - } else if (output->type == cgltf_type_vec4) { + } + else if (output->type == cgltf_type_vec4) + { float tmp[4] = { 0.0f }; cgltf_accessor_read_float(output, keyframe, tmp, 4); Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; @@ -5212,10 +5230,12 @@ static void GetGLTFPoseAtTime(cgltf_accessor* input, cgltf_accessor *output, flo // only v4 is for rotations, so we know it's a quat. *r = QuaternionSlerp(v1, v2, t); } + return true; } #define GLTF_ANIMDELAY 17 // that's roughly ~1000 msec / 60 FPS (16.666666* msec) -static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned int *animCount) { +static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned int *animCount) +{ // glTF file loading unsigned int dataSize = 0; unsigned char *fileData = LoadFileData(fileName, &dataSize); @@ -5225,7 +5245,8 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in cgltf_options options = { 0 }; cgltf_data *data = NULL; cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); - if(result != cgltf_result_success) { + if (result != cgltf_result_success) + { TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); *animCount = 0; return NULL; @@ -5234,12 +5255,15 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in result = cgltf_load_buffers(&options, data, fileName); if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load animation buffers", fileName); - if (result == cgltf_result_success) { - if (data->skins_count == 1) { + if (result == cgltf_result_success) + { + if (data->skins_count == 1) + { cgltf_skin skin = data->skins[0]; *animCount = data->animations_count; animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation)); - for(unsigned int i = 0; i < data->animations_count; i++) { + for (unsigned int i = 0; i < data->animations_count; i++) + { animations[i].bones = LoadGLTFBoneInfo(skin, &animations[i].boneCount); cgltf_animation animData = data->animations[i]; @@ -5252,36 +5276,52 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in struct Channels *boneChannels = RL_CALLOC(animations[i].boneCount, sizeof(struct Channels)); float animDuration = 0.0f; - for (unsigned int j = 0; j < animData.channels_count; j++) { + for (unsigned int j = 0; j < animData.channels_count; j++) + { cgltf_animation_channel channel = animData.channels[j]; int boneIndex = -1; - for (unsigned int k = 0; k < skin.joints_count; k++) { - if (animData.channels[j].target_node == skin.joints[k]) { + for (unsigned int k = 0; k < skin.joints_count; k++) + { + if (animData.channels[j].target_node == skin.joints[k]) + { boneIndex = k; break; } } - if (boneIndex == -1) { + if (boneIndex == -1) + { // animation channel for a node not in the armature. continue; } - if (animData.channels[j].sampler->interpolation == cgltf_interpolation_type_linear) { - if (channel.target_path == cgltf_animation_path_type_translation) { + if (animData.channels[j].sampler->interpolation == cgltf_interpolation_type_linear) + { + if (channel.target_path == cgltf_animation_path_type_translation) + { boneChannels[boneIndex].translate = &animData.channels[j]; - } else if (channel.target_path == cgltf_animation_path_type_rotation) { + } + else if (channel.target_path == cgltf_animation_path_type_rotation) + { boneChannels[boneIndex].rotate = &animData.channels[j]; - } else if (channel.target_path == cgltf_animation_path_type_scale) { + } + else if (channel.target_path == cgltf_animation_path_type_scale) + { boneChannels[boneIndex].scale = &animData.channels[j]; - } else { + } + else + { TRACELOG(LOG_WARNING, "MODEL: [%s] Unsupported target_path on channel %d's sampler for animation %d. Skipping.", fileName, j, i); } } else TRACELOG(LOG_WARNING, "MODEL: [%s] Only linear interpolation curves are supported for GLTF animation.", fileName); float t = 0.0f; cgltf_bool r = cgltf_accessor_read_float(channel.sampler->input, channel.sampler->input->count - 1, &t, 1); - assert(r); + if (!r) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load input time", fileName); + continue; + } animDuration = (t > animDuration)? t : animDuration; } @@ -5289,32 +5329,46 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in animations[i].frameCount = (int)(animDuration*1000.0f/GLTF_ANIMDELAY); animations[i].framePoses = RL_MALLOC(animations[i].frameCount*sizeof(Transform *)); - for (unsigned int j = 0; j < animations[i].frameCount; j++) { + for (unsigned int j = 0; j < animations[i].frameCount; j++) + { animations[i].framePoses[j] = RL_MALLOC(animations[i].boneCount*sizeof(Transform)); float time = ((float) j*GLTF_ANIMDELAY)/1000.0f; - for (unsigned int k = 0; k < animations[i].boneCount; k++) { + for (unsigned int k = 0; k < animations[i].boneCount; k++) + { Vector3 translation = {0, 0, 0}; Quaternion rotation = {0, 0, 0, 1}; Vector3 scale = {1, 1, 1}; - if (boneChannels[k].translate) { - GetGLTFPoseAtTime(boneChannels[k].translate->sampler->input, - boneChannels[k].translate->sampler->output, - time, - &translation); + if (boneChannels[k].translate) + { + if (!GetGLTFPoseAtTime(boneChannels[k].translate->sampler->input, + boneChannels[k].translate->sampler->output, + time, + &translation)) + { + TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load translate pose data for bone %s", fileName, animations[i].bones[k].name); + } } - if (boneChannels[k].rotate) { - GetGLTFPoseAtTime(boneChannels[k].rotate->sampler->input, + if (boneChannels[k].rotate) + { + if (!GetGLTFPoseAtTime(boneChannels[k].rotate->sampler->input, boneChannels[k].rotate->sampler->output, time, - &rotation); + &rotation)) + { + TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load rotate pose data for bone %s", fileName, animations[i].bones[k].name); + } } - if (boneChannels[k].scale) { - GetGLTFPoseAtTime(boneChannels[k].scale->sampler->input, + if (boneChannels[k].scale) + { + if (!GetGLTFPoseAtTime(boneChannels[k].scale->sampler->input, boneChannels[k].scale->sampler->output, time, - &scale); + &scale)) + { + TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load scale pose data for bone %s", fileName, animations[i].bones[k].name); + } } animations[i].framePoses[j][k] = (Transform){