Added example for gltf animation and split some functions for loading model animations into IQM and GLTF similar to how models are being loaded.
This commit is contained in:
parent
320732ae6b
commit
852510929e
115
examples/models/models_gltf_animation.c
Normal file
115
examples/models/models_gltf_animation.c
Normal file
|
|
@ -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 <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
BIN
examples/models/resources/models/rigged_figure.glb
Normal file
BIN
examples/models/resources/models/rigged_figure.glb
Normal file
Binary file not shown.
476
src/models.c
476
src/models.c
|
|
@ -114,9 +114,11 @@ static Model LoadOBJ(const char *fileName); // Load OBJ mesh data
|
||||||
#endif
|
#endif
|
||||||
#if defined(SUPPORT_FILEFORMAT_IQM)
|
#if defined(SUPPORT_FILEFORMAT_IQM)
|
||||||
static Model LoadIQM(const char *fileName); // Load IQM mesh data
|
static Model LoadIQM(const char *fileName); // Load IQM mesh data
|
||||||
|
static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCount); // Load IQM animation data
|
||||||
#endif
|
#endif
|
||||||
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||||||
static Model LoadGLTF(const char *fileName); // Load GLTF mesh data
|
static Model LoadGLTF(const char *fileName); // Load GLTF mesh data
|
||||||
|
static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
|
|
@ -1008,211 +1010,15 @@ void SetModelMeshMaterial(Model *model, int meshId, int materialId)
|
||||||
// Load model animations from file
|
// Load model animations from file
|
||||||
ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount)
|
ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount)
|
||||||
{
|
{
|
||||||
#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
|
ModelAnimation *animations = NULL;
|
||||||
#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);
|
|
||||||
|
|
||||||
|
#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;
|
return animations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3549,6 +3355,218 @@ static Model LoadIQM(const char *fileName)
|
||||||
|
|
||||||
return model;
|
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
|
#endif
|
||||||
|
|
||||||
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||||||
|
|
@ -3921,4 +3939,54 @@ static Model LoadGLTF(const char *fileName)
|
||||||
|
|
||||||
return model;
|
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1350,6 +1350,7 @@ RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId);
|
||||||
|
|
||||||
// Model animations loading/unloading functions
|
// 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 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 UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose
|
||||||
RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data
|
RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data
|
||||||
RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match
|
RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user