diff --git a/examples/models/models_loading_gltf.c b/examples/models/models_loading_gltf.c index 879715906..c19df3f5b 100644 --- a/examples/models/models_loading_gltf.c +++ b/examples/models/models_loading_gltf.c @@ -27,46 +27,43 @@ int main(void) // Define the camera to look into our 3d world Camera camera = { 0 }; - camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position - camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point + camera.position = (Vector3){ 5.0f, 5.0f, 5.0f }; // Camera position + camera.target = (Vector3){ 0.0f, 2.0f, 0.0f }; // Camera looking at point camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target) camera.fovy = 45.0f; // Camera field-of-view Y camera.projection = CAMERA_PERSPECTIVE; // Camera mode type - // Loaf gltf model + // Load gltf model Model model = LoadModel("resources/models/gltf/robot.glb"); + + // Load gltf model animations unsigned int animsCount = 0; - ModelAnimation *modelAnimations = LoadModelAnimations("resources/models/gltf/robot.glb", &animsCount); - unsigned int animIndex = 0; + unsigned int animCurrentFrame = 0; + ModelAnimation *modelAnimations = LoadModelAnimations("resources/models/gltf/robot.glb", &animsCount); Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position - SetCameraMode(camera, CAMERA_FREE); // Set free camera mode + SetCameraMode(camera, CAMERA_FREE); // Set free camera mode - SetTargetFPS(60); // Set our game to run at 60 frames-per-second + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- - unsigned int currentFrame = 0; + // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key + while (!WindowShouldClose()) // Detect window close button or ESC key { // Update //---------------------------------------------------------------------------------- ModelAnimation anim = modelAnimations[animIndex]; - if (IsKeyPressed(KEY_UP)) - { - animIndex = (animIndex + 1) % animsCount; - } + + if (IsKeyPressed(KEY_UP)) animIndex = (animIndex + 1)%animsCount; + else if (IsKeyPressed(KEY_DOWN)) animIndex = (animIndex + animsCount - 1)%animsCount; - if (IsKeyPressed(KEY_DOWN)) - { - animIndex = (animIndex + animsCount - 1) % animsCount; - } - - currentFrame = (currentFrame + 1) % anim.frameCount; - UpdateModelAnimation(model, anim, currentFrame); + animCurrentFrame = (animCurrentFrame + 1)%anim.frameCount; + UpdateModelAnimation(model, anim, animCurrentFrame); + UpdateCamera(&camera); //---------------------------------------------------------------------------------- @@ -74,7 +71,7 @@ int main(void) //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(SKYBLUE); + ClearBackground(RAYWHITE); BeginMode3D(camera); @@ -83,7 +80,7 @@ int main(void) EndMode3D(); - DrawText("Use the up/down arrow keys to switch animation.", 10, 10, 20, WHITE); + DrawText("Use the UP/DOWN arrow keys to switch animation", 10, 10, 20, GRAY); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/examples/models/models_loading_gltf.png b/examples/models/models_loading_gltf.png index 654444b84..34748a22e 100644 Binary files a/examples/models/models_loading_gltf.png and b/examples/models/models_loading_gltf.png differ diff --git a/src/rmodels.c b/src/rmodels.c index ed32751c3..786e5ac79 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -142,18 +142,18 @@ static Model LoadOBJ(const char *fileName); // Load OBJ mesh data #endif #if defined(SUPPORT_FILEFORMAT_IQM) static Model LoadIQM(const char *fileName); // Load IQM mesh data -static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, unsigned int *animCount); // Load IQM animation data +static ModelAnimation *LoadModelAnimationsIQM(const char *fileName, unsigned int *animCount); // Load IQM animation data #endif #if defined(SUPPORT_FILEFORMAT_GLTF) static Model LoadGLTF(const char *fileName); // Load GLTF mesh data -static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned int *animCount); // Load GLTF animation data +static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned int *animCount); // Load GLTF animation data #endif #if defined(SUPPORT_FILEFORMAT_VOX) static Model LoadVOX(const char *filename); // Load VOX mesh data #endif #if defined(SUPPORT_FILEFORMAT_M3D) static Model LoadM3D(const char *filename); // Load M3D mesh data -static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount); // Load M3D animation data +static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount); // Load M3D animation data #endif //---------------------------------------------------------------------------------- @@ -3829,6 +3829,12 @@ RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Ve return collision; } +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(SUPPORT_FILEFORMAT_IQM) || defined(SUPPORT_FILEFORMAT_GLTF) +// Build pose from parent joints +// NOTE: Required for animations loading (required by IQM and GLTF) static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform *transforms) { for (int i = 0; i < boneCount; i++) @@ -3847,10 +3853,8 @@ static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform } } } +#endif -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_OBJ) // Load OBJ mesh data // @@ -4690,7 +4694,8 @@ static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPat return image; } -static BoneInfo *LoadGLTFBoneInfo(cgltf_skin skin, int *boneCount) +// Load bone info from GLTF skin data +static BoneInfo *LoadBoneInfoGLTF(cgltf_skin skin, int *boneCount) { *boneCount = skin.joints_count; BoneInfo *bones = RL_MALLOC(skin.joints_count*sizeof(BoneInfo)); @@ -4700,8 +4705,9 @@ static BoneInfo *LoadGLTFBoneInfo(cgltf_skin skin, int *boneCount) cgltf_node node = *skin.joints[i]; strncpy(bones[i].name, node.name, sizeof(bones[i].name)); - // find parent bone index + // Find parent bone index unsigned int parentIndex = -1; + for (unsigned int j = 0; j < skin.joints_count; j++) { if (skin.joints[j] == node.parent) @@ -4731,7 +4737,7 @@ static Model LoadGLTF(const char *fileName) - Supports PBR metallic/roughness flow, loads material textures, values and colors PBR specular/glossiness flow and extended texture flows not supported - Supports multiple meshes per model (every primitives is loaded as a separate mesh) - - Supports basic animation + - Supports basic animations RESTRICTIONS: - Only triangle meshes supported @@ -5079,12 +5085,16 @@ static Model LoadGLTF(const char *fileName) // Load glTF meshes animation data // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes + // + // LIMITATIONS: + // - Only supports 1 armature per file, and skips loading it if there are multiple armatures + // - Only supports linear interpolation (default method in Blender when checked "Always Sample Animations" when exporting a GLTF file) + // - Only supports translation/rotation/scale animation channel.path, weights not considered (i.e. morph targets) //---------------------------------------------------------------------------------------------------- - if (data->skins_count == 1) { cgltf_skin skin = data->skins[0]; - model.bones = LoadGLTFBoneInfo(skin, &model.boneCount); + model.bones = LoadBoneInfoGLTF(skin, &model.boneCount); model.bindPose = RL_MALLOC(model.boneCount*sizeof(Transform)); for (unsigned int i = 0; i < model.boneCount; i++) @@ -5181,19 +5191,19 @@ static Model LoadGLTF(const char *fileName) } // 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) +static bool GetPoseAtTimeGLTF(cgltf_accessor *input, cgltf_accessor *output, float time, void *data) { - // input and output should have the same count - + // Input and output should have the same count float tstart = 0.0f; float tend = 0.0f; - - int keyframe = 0; // defaults to first pose + int keyframe = 0; // Defaults to first pose + for (int i = 0; i < input->count - 1; i++) { cgltf_bool r1 = cgltf_accessor_read_float(input, i, &tstart, 1); if (!r1) return false; - cgltf_bool r2 = cgltf_accessor_read_float(input, i+1, &tend, 1); + + cgltf_bool r2 = cgltf_accessor_read_float(input, i + 1, &tend, 1); if (!r2) return false; if ((tstart <= time) && (time < tend)) @@ -5227,13 +5237,16 @@ static bool GetGLTFPoseAtTime(cgltf_accessor* input, cgltf_accessor *output, flo cgltf_accessor_read_float(output, keyframe+1, tmp, 4); Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]}; Vector4 *r = data; - // only v4 is for rotations, so we know it's a quat. + + // 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) +#define GLTF_ANIMDELAY 17 // Animation frames delay, (~1000 ms/60 FPS = 16.666666* ms) + static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned int *animCount) { // glTF file loading @@ -5241,10 +5254,12 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in unsigned char *fileData = LoadFileData(fileName, &dataSize); ModelAnimation *animations = NULL; + // glTF data loading cgltf_options options = { 0 }; cgltf_data *data = NULL; cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); + if (result != cgltf_result_success) { TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); @@ -5262,9 +5277,10 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in 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++) { - animations[i].bones = LoadGLTFBoneInfo(skin, &animations[i].boneCount); + animations[i].bones = LoadBoneInfoGLTF(skin, &animations[i].boneCount); cgltf_animation animData = data->animations[i]; @@ -5276,10 +5292,12 @@ 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++) { 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]) @@ -5291,7 +5309,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in if (boneIndex == -1) { - // animation channel for a node not in the armature. + // Animation channel for a node not in the armature continue; } @@ -5313,10 +5331,12 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in { 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); + } + 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); + if (!r) { TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load input time", fileName); @@ -5333,17 +5353,16 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in { 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++) { Vector3 translation = {0, 0, 0}; Quaternion rotation = {0, 0, 0, 1}; Vector3 scale = {1, 1, 1}; + if (boneChannels[k].translate) { - if (!GetGLTFPoseAtTime(boneChannels[k].translate->sampler->input, - boneChannels[k].translate->sampler->output, - time, - &translation)) + if (!GetPoseAtTimeGLTF(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); } @@ -5351,10 +5370,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in if (boneChannels[k].rotate) { - if (!GetGLTFPoseAtTime(boneChannels[k].rotate->sampler->input, - boneChannels[k].rotate->sampler->output, - time, - &rotation)) + if (!GetPoseAtTimeGLTF(boneChannels[k].rotate->sampler->input, boneChannels[k].rotate->sampler->output, time, &rotation)) { TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load rotate pose data for bone %s", fileName, animations[i].bones[k].name); } @@ -5362,10 +5378,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in if (boneChannels[k].scale) { - if (!GetGLTFPoseAtTime(boneChannels[k].scale->sampler->input, - boneChannels[k].scale->sampler->output, - time, - &scale)) + if (!GetPoseAtTimeGLTF(boneChannels[k].scale->sampler->input, boneChannels[k].scale->sampler->output, time, &scale)) { TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load scale pose data for bone %s", fileName, animations[i].bones[k].name); } @@ -5374,7 +5387,8 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in animations[i].framePoses[j][k] = (Transform){ .translation = translation, .rotation = rotation, - .scale = scale}; + .scale = scale + }; } BuildPoseFromParentJoints(animations[i].bones, animations[i].boneCount, animations[i].framePoses[j]); @@ -5383,7 +5397,8 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, unsigned in TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s (%d frames, %fs)", fileName, animData.name, animations[i].frameCount, animDuration); RL_FREE(boneChannels); } - } else TRACELOG(LOG_ERROR, "MODEL: [%s] expected exactly one skin to load animation data from, but found %i", fileName, data->skins_count); + } + else TRACELOG(LOG_ERROR, "MODEL: [%s] expected exactly one skin to load animation data from, but found %i", fileName, data->skins_count); cgltf_free(data); } @@ -5576,12 +5591,14 @@ static Model LoadM3D(const char *fileName) model.meshes[k].vertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); model.meshes[k].texcoords = (float *)RL_CALLOC(model.meshes[k].vertexCount*2, sizeof(float)); model.meshes[k].normals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); + // without material, we rely on vertex colors if (mi == M3D_UNDEF && model.meshes[k].colors == NULL) { model.meshes[k].colors = RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); for (j = 0; j < model.meshes[k].vertexCount*4; j += 4) memcpy(&model.meshes[k].colors[j], &WHITE, 4); } + if (m3d->numbone && m3d->numskin) { model.meshes[k].boneIds = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); @@ -5589,6 +5606,7 @@ static Model LoadM3D(const char *fileName) model.meshes[k].animVertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); model.meshes[k].animNormals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); } + model.meshMaterial[k] = mi + 1; l = 0; } @@ -5617,25 +5635,25 @@ static Model LoadM3D(const char *fileName) if (m3d->face[i].texcoord[0] != M3D_UNDEF) { - model.meshes[k].texcoords[l * 6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u; - model.meshes[k].texcoords[l * 6 + 1] = 1.0 - m3d->tmap[m3d->face[i].texcoord[0]].v; - model.meshes[k].texcoords[l * 6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u; - model.meshes[k].texcoords[l * 6 + 3] = 1.0 - m3d->tmap[m3d->face[i].texcoord[1]].v; - model.meshes[k].texcoords[l * 6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u; - model.meshes[k].texcoords[l * 6 + 5] = 1.0 - m3d->tmap[m3d->face[i].texcoord[2]].v; + model.meshes[k].texcoords[l*6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u; + model.meshes[k].texcoords[l*6 + 1] = 1.0 - m3d->tmap[m3d->face[i].texcoord[0]].v; + model.meshes[k].texcoords[l*6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u; + model.meshes[k].texcoords[l*6 + 3] = 1.0 - m3d->tmap[m3d->face[i].texcoord[1]].v; + model.meshes[k].texcoords[l*6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u; + model.meshes[k].texcoords[l*6 + 5] = 1.0 - m3d->tmap[m3d->face[i].texcoord[2]].v; } if (m3d->face[i].normal[0] != M3D_UNDEF) { - model.meshes[k].normals[l * 9 + 0] = m3d->vertex[m3d->face[i].normal[0]].x; - model.meshes[k].normals[l * 9 + 1] = m3d->vertex[m3d->face[i].normal[0]].y; - model.meshes[k].normals[l * 9 + 2] = m3d->vertex[m3d->face[i].normal[0]].z; - model.meshes[k].normals[l * 9 + 3] = m3d->vertex[m3d->face[i].normal[1]].x; - model.meshes[k].normals[l * 9 + 4] = m3d->vertex[m3d->face[i].normal[1]].y; - model.meshes[k].normals[l * 9 + 5] = m3d->vertex[m3d->face[i].normal[1]].z; - model.meshes[k].normals[l * 9 + 6] = m3d->vertex[m3d->face[i].normal[2]].x; - model.meshes[k].normals[l * 9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y; - model.meshes[k].normals[l * 9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z; + model.meshes[k].normals[l*9 + 0] = m3d->vertex[m3d->face[i].normal[0]].x; + model.meshes[k].normals[l*9 + 1] = m3d->vertex[m3d->face[i].normal[0]].y; + model.meshes[k].normals[l*9 + 2] = m3d->vertex[m3d->face[i].normal[0]].z; + model.meshes[k].normals[l*9 + 3] = m3d->vertex[m3d->face[i].normal[1]].x; + model.meshes[k].normals[l*9 + 4] = m3d->vertex[m3d->face[i].normal[1]].y; + model.meshes[k].normals[l*9 + 5] = m3d->vertex[m3d->face[i].normal[1]].z; + model.meshes[k].normals[l*9 + 6] = m3d->vertex[m3d->face[i].normal[2]].x; + model.meshes[k].normals[l*9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y; + model.meshes[k].normals[l*9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z; } // Add skin (vertex / bone weight pairs) @@ -5658,8 +5676,8 @@ static Model LoadM3D(const char *fileName) { // raylib does not handle boneless meshes with skeletal animations, so // we put all vertices without a bone into a special "no bone" bone - model.meshes[k].boneIds[l * 12 + n * 4] = m3d->numbone; - model.meshes[k].boneWeights[l * 12 + n * 4] = 1.0f; + model.meshes[k].boneIds[l*12 + n*4] = m3d->numbone; + model.meshes[k].boneWeights[l*12 + n*4] = 1.0f; } } } @@ -5754,6 +5772,7 @@ static Model LoadM3D(const char *fileName) model.bindPose[i].rotation.y = m3d->vertex[m3d->bone[i].ori].y; model.bindPose[i].rotation.z = m3d->vertex[m3d->bone[i].ori].z; model.bindPose[i].rotation.w = m3d->vertex[m3d->bone[i].ori].w; + // TODO: if the orientation quaternion not normalized, then that's encoding scaling model.bindPose[i].rotation = QuaternionNormalize(model.bindPose[i].rotation); model.bindPose[i].scale.x = model.bindPose[i].scale.y = model.bindPose[i].scale.z = 1.0f; @@ -5799,15 +5818,16 @@ static Model LoadM3D(const char *fileName) return model; } +#define M3D_ANIMDELAY 17 // Animation frames delay, (~1000 ms/60 FPS = 16.666666* ms) + // Load M3D animation data -#define M3D_ANIMDELAY 17 // that's roughly ~1000 msec / 60 FPS (16.666666* msec) static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount) { m3d_t *m3d = NULL; unsigned int bytesRead = 0; unsigned char *fileData = LoadFileData(fileName, &bytesRead); ModelAnimation *animations = NULL; - int i, j; + int i = 0, j = 0; *animCount = 0; @@ -5824,7 +5844,7 @@ static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i animations, %i bones, %i skins", fileName, m3d->numaction, m3d->numbone, m3d->numskin); - // no animation or bone+skin? + // No animation or bone+skin? if (!m3d->numaction || !m3d->numbone || !m3d->numskin) { m3d_free(m3d); @@ -5860,7 +5880,8 @@ static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int { animations[a].framePoses[i] = RL_MALLOC((m3d->numbone + 1)*sizeof(Transform)); - m3db_t *pose = m3d_pose(m3d, a, i * M3D_ANIMDELAY); + m3db_t *pose = m3d_pose(m3d, a, i*M3D_ANIMDELAY); + if (pose != NULL) { for (j = 0; j < m3d->numbone; j++)