From 852510929e5fde86401bf27e42101891573a730a Mon Sep 17 00:00:00 2001 From: Hristo Stamenov Date: Sun, 3 Jan 2021 18:44:06 +0200 Subject: [PATCH] Added example for gltf animation and split some functions for loading model animations into IQM and GLTF similar to how models are being loaded. --- examples/models/models_gltf_animation.c | 115 +++++ .../models/resources/models/rigged_figure.glb | Bin 0 -> 50116 bytes src/models.c | 476 ++++++++++-------- src/raylib.h | 1 + 4 files changed, 388 insertions(+), 204 deletions(-) create mode 100644 examples/models/models_gltf_animation.c create mode 100644 examples/models/resources/models/rigged_figure.glb diff --git a/examples/models/models_gltf_animation.c b/examples/models/models_gltf_animation.c new file mode 100644 index 000000000..e2405832c --- /dev/null +++ b/examples/models/models_gltf_animation.c @@ -0,0 +1,115 @@ +/******************************************************************************************* +* +* raylib [models] example - Load 3d gltf model with animations and play them +* +* This example has been created using raylib 3.5 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Example contributed by Hristo Stamenov (@object71) and reviewed by Ramon Santamaria (@raysan5) +* +* Copyright (c) 2021 Hristo Stamenov (@object71) and Ramon Santamaria (@raysan5) +* +******************************************************************************************** +* +* To export a model from blender, make sure it is not posed, the vertices need to be in the +* same position as they would be in edit mode. +* and that the scale of your models is set to 0. Scaling can be done from the export menu. +* +********************************************************************************************/ + +#include "raylib.h" + +#include + + +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [models] example - model animation"); + + // 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.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target) + camera.fovy = 45.0f; // Camera field-of-view Y + camera.type = CAMERA_PERSPECTIVE; // Camera mode type + + Model model = LoadModel("resources/models/rigged_figure.glb"); // Load the animated model mesh and + // basic data +// Texture2D texture = LoadTexture("resources/guy/guytex.png"); // Load model texture and set material +// SetMaterialTexture(&model.materials[0], MAP_DIFFUSE, texture); // Set model material map texture + + Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position + + // Load animation data + int animsCount = 0; + ModelAnimation *anims = LoadModelAnimations("resources/models/rigged_figure.glb", &animsCount); + int animFrameCounter = 0; + + SetCameraMode(camera, CAMERA_FREE); // Set free camera mode + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera); + + // Play animation when spacebar is held down + if (IsKeyDown(KEY_SPACE)) + { + animFrameCounter++; + UpdateModelAnimation(model, anims[0], animFrameCounter); + if (animFrameCounter >= anims[0].frameCount) animFrameCounter = 0; + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + DrawModelEx(model, position, (Vector3){ 1.0f, 0.0f, 0.0f }, -90.0f, (Vector3){ 1.0f, 1.0f, 1.0f }, WHITE); + + for (int i = 0; i < model.boneCount; i++) + { + DrawCube(anims[0].framePoses[animFrameCounter][i].translation, 0.2f, 0.2f, 0.2f, RED); + } + + DrawGrid(10, 1.0f); // Draw a grid + + EndMode3D(); + + DrawText("PRESS SPACE to PLAY MODEL ANIMATION", 10, 10, 20, MAROON); + DrawText("(c) Guy IQM 3D model by @culacant", screenWidth - 200, screenHeight - 20, 10, GRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- +// UnloadTexture(texture); // Unload texture + + // Unload model animations data + for (int i = 0; i < animsCount; i++) UnloadModelAnimation(anims[i]); + RL_FREE(anims); + + UnloadModel(model); // Unload model + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/models/resources/models/rigged_figure.glb b/examples/models/resources/models/rigged_figure.glb new file mode 100644 index 0000000000000000000000000000000000000000..c505a33dfa5ba84bd5f3dd0a85f74ee4c9978322 GIT binary patch literal 50116 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 literal 0 HcmV?d00001 diff --git a/src/models.c b/src/models.c index b014352df..a9cf2c845 100644 --- a/src/models.c +++ b/src/models.c @@ -114,9 +114,11 @@ 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 *LoadIQMModelAnimations(const char *fileName, int *animCount); // Load IQM animation data #endif #if defined(SUPPORT_FILEFORMAT_GLTF) static Model LoadGLTF(const char *fileName); // Load GLTF mesh data +static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data #endif //---------------------------------------------------------------------------------- @@ -1008,211 +1010,15 @@ void SetModelMeshMaterial(Model *model, int meshId, int materialId) // Load model animations from file ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount) { - #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number - #define IQM_VERSION 2 // only IQM version 2 supported - - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - unsigned char *fileDataPtr = fileData; - - typedef struct IQMHeader { - char magic[16]; - unsigned int version; - unsigned int filesize; - unsigned int flags; - unsigned int num_text, ofs_text; - unsigned int num_meshes, ofs_meshes; - unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; - unsigned int num_triangles, ofs_triangles, ofs_adjacency; - unsigned int num_joints, ofs_joints; - unsigned int num_poses, ofs_poses; - unsigned int num_anims, ofs_anims; - unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; - unsigned int num_comment, ofs_comment; - unsigned int num_extensions, ofs_extensions; - } IQMHeader; - - typedef struct IQMPose { - int parent; - unsigned int mask; - float channeloffset[10]; - float channelscale[10]; - } IQMPose; - - typedef struct IQMAnim { - unsigned int name; - unsigned int first_frame, num_frames; - float framerate; - unsigned int flags; - } IQMAnim; - - // In case file can not be read, return an empty model - if (fileDataPtr == NULL) return NULL; - - // Read IQM header - IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; - - if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) - { - TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); - return NULL; - } - - if (iqmHeader->version != IQM_VERSION) - { - TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); - return NULL; - } - - // Get bones data - IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); - //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET); - //fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile); - memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose)); - - // Get animations data - *animCount = iqmHeader->num_anims; - IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); - //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET); - //fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile); - memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim)); - - ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation)); - - // frameposes - unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); - //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET); - //fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile); - memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); - - for (unsigned int a = 0; a < iqmHeader->num_anims; a++) - { - animations[a].frameCount = anim[a].num_frames; - animations[a].boneCount = iqmHeader->num_poses; - animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); - animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); - //animations[a].framerate = anim.framerate; // TODO: Use framerate? - - for (unsigned int j = 0; j < iqmHeader->num_poses; j++) - { - strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); - animations[a].bones[j].parent = poses[j].parent; - } - - for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); - - int dcounter = anim[a].first_frame*iqmHeader->num_framechannels; - - for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) - { - for (unsigned int i = 0; i < iqmHeader->num_poses; i++) - { - animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0]; - - if (poses[i].mask & 0x01) - { - animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; - dcounter++; - } - - animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1]; - - if (poses[i].mask & 0x02) - { - animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; - dcounter++; - } - - animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2]; - - if (poses[i].mask & 0x04) - { - animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; - - if (poses[i].mask & 0x08) - { - animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; - - if (poses[i].mask & 0x10) - { - animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; - - if (poses[i].mask & 0x20) - { - animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; - - if (poses[i].mask & 0x40) - { - animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; - dcounter++; - } - - animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7]; - - if (poses[i].mask & 0x80) - { - animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; - dcounter++; - } - - animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8]; - - if (poses[i].mask & 0x100) - { - animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; - dcounter++; - } - - animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9]; - - if (poses[i].mask & 0x200) - { - animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation); - } - } - - // Build frameposes - for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) - { - for (int i = 0; i < animations[a].boneCount; i++) - { - if (animations[a].bones[i].parent >= 0) - { - animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation); - animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation); - animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation); - animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale); - } - } - } - } - - RL_FREE(fileData); - - RL_FREE(framedata); - RL_FREE(poses); - RL_FREE(anim); + ModelAnimation *animations = NULL; +#if defined(SUPPORT_FILEFORMAT_IQM) + if (IsFileExtension(fileName, ".iqm")) animations = LoadIQMModelAnimations(fileName, animCount); +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) + if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadGLTFModelAnimations(fileName, animCount); +#endif + return animations; } @@ -3549,6 +3355,218 @@ static Model LoadIQM(const char *fileName) return model; } + +// Load IQM animation data +static ModelAnimation* LoadIQMModelAnimations(const char* fileName, int* animCount) +{ +#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number +#define IQM_VERSION 2 // only IQM version 2 supported + + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + unsigned char *fileDataPtr = fileData; + + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + // In case file can not be read, return an empty model + if (fileDataPtr == NULL) return NULL; + + // Read IQM header + IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; + + if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); + return NULL; + } + + if (iqmHeader->version != IQM_VERSION) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); + return NULL; + } + + // Get bones data + IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); + //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET); + //fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile); + memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose)); + + // Get animations data + *animCount = iqmHeader->num_anims; + IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); + //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET); + //fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile); + memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim)); + + ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation)); + + // frameposes + unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET); + //fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile); + memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + + for (unsigned int a = 0; a < iqmHeader->num_anims; a++) + { + animations[a].frameCount = anim[a].num_frames; + animations[a].boneCount = iqmHeader->num_poses; + animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); + animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); + //animations[a].framerate = anim.framerate; // TODO: Use framerate? + + for (unsigned int j = 0; j < iqmHeader->num_poses; j++) + { + strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); + animations[a].bones[j].parent = poses[j].parent; + } + + for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); + + int dcounter = anim[a].first_frame*iqmHeader->num_framechannels; + + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (unsigned int i = 0; i < iqmHeader->num_poses; i++) + { + animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0]; + + if (poses[i].mask & 0x01) + { + animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; + dcounter++; + } + + animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1]; + + if (poses[i].mask & 0x02) + { + animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; + dcounter++; + } + + animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2]; + + if (poses[i].mask & 0x04) + { + animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; + + if (poses[i].mask & 0x08) + { + animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; + + if (poses[i].mask & 0x10) + { + animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; + + if (poses[i].mask & 0x20) + { + animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; + + if (poses[i].mask & 0x40) + { + animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7]; + + if (poses[i].mask & 0x80) + { + animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8]; + + if (poses[i].mask & 0x100) + { + animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9]; + + if (poses[i].mask & 0x200) + { + animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation); + } + } + + // Build frameposes + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (int i = 0; i < animations[a].boneCount; i++) + { + if (animations[a].bones[i].parent >= 0) + { + animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation); + animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation); + animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation); + animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale); + } + } + } + } + + RL_FREE(fileData); + + RL_FREE(framedata); + RL_FREE(poses); + RL_FREE(anim); + + return animations; +} + #endif #if defined(SUPPORT_FILEFORMAT_GLTF) @@ -3921,4 +3939,54 @@ static Model LoadGLTF(const char *fileName) return model; } + +// LoadGLTF loads in animation data from given filename +static ModelAnimation* LoadGLTFModelAnimations(const char *fileName, int *animCount) +{ + /*********************************************************************************** + + Function implemented by Hristo Stamenov (@object71) + + Features: + - Supports .gltf and .glb files + + Some restrictions (not exhaustive): + - ... + + *************************************************************************************/ + + // glTF file loading + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + ModelAnimation *animations = NULL; + + if (fileData == NULL) return animations; + + // 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_INFO, "MODEL: [%s] glTF animations (%s) count: %i", fileName, (data->file_type == 2)? "glb" : + "gltf", data->animations_count); + + animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation)); + + for (unsigned int a = 0; a < data->animations_count; a++) + { + cgltf_animation *animation = data->animations + a; + } + + cgltf_free(data); + } + else TRACELOG(LOG_WARNING, ": [%s] Failed to load glTF data", fileName); + + RL_FREE(fileData); + + return animations; +} + #endif diff --git a/src/raylib.h b/src/raylib.h index a28711174..df0e5e431 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1350,6 +1350,7 @@ RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Model animations loading/unloading functions RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animsCount); // Load model animations from file +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animsCount); // Load model animations from file RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match