Fix loading multiple animations from .iqm file (#928)

* Fix loading multiple animations from .iqm file

* Fix memory leak in models_animation example

* Added export instructions to the animation example

* use raylib free

* include <stdlib.h> to appease the travis CI gods

* replace tabs with spaces
This commit is contained in:
culacant 2019-08-06 23:08:58 +02:00 committed by Ray
parent e53e42f43d
commit f518c4e939
2 changed files with 127 additions and 121 deletions

View file

@ -9,8 +9,15 @@
* *
* Copyright (c) 2019 Culacant (@culacant) and Ramon Santamaria (@raysan5) * Copyright (c) 2019 Culacant (@culacant) 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 <stdlib.h>
#include "raylib.h" #include "raylib.h"
int main(void) int main(void)
@ -93,6 +100,7 @@ int main(void)
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
// Unload model animations data // Unload model animations data
for (int i = 0; i < animsCount; i++) UnloadModelAnimation(anims[i]); for (int i = 0; i < animsCount; i++) UnloadModelAnimation(anims[i]);
RL_FREE(anims);
UnloadModel(model); // Unload model UnloadModel(model); // Unload model

View file

@ -867,9 +867,6 @@ 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)
{ {
ModelAnimation *animations = (ModelAnimation *)RL_MALLOC(1*sizeof(ModelAnimation));
int count = 1;
#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number
#define IQM_VERSION 2 // only IQM version 2 supported #define IQM_VERSION 2 // only IQM version 2 supported
@ -904,8 +901,6 @@ ModelAnimation *LoadModelAnimations(const char *filename, int *animCount)
unsigned int flags; unsigned int flags;
} IQMAnim; } IQMAnim;
ModelAnimation animation = { 0 };
FILE *iqmFile; FILE *iqmFile;
IQMHeader iqm; IQMHeader iqm;
@ -931,153 +926,156 @@ ModelAnimation *LoadModelAnimations(const char *filename, int *animCount)
fclose(iqmFile); fclose(iqmFile);
} }
// header
if (iqm.num_anims > 1) TraceLog(LOG_WARNING, "More than 1 animation in file, only the first one will be loaded");
// bones // bones
IQMPose *poses; IQMPose *poses;
poses = RL_MALLOC(sizeof(IQMPose)*iqm.num_poses); poses = RL_MALLOC(sizeof(IQMPose)*iqm.num_poses);
fseek(iqmFile, iqm.ofs_poses, SEEK_SET); fseek(iqmFile, iqm.ofs_poses, SEEK_SET);
fread(poses, sizeof(IQMPose)*iqm.num_poses, 1, iqmFile); fread(poses, sizeof(IQMPose)*iqm.num_poses, 1, iqmFile);
animation.boneCount = iqm.num_poses;
animation.bones = RL_MALLOC(sizeof(BoneInfo)*iqm.num_poses);
for (int j = 0; j < iqm.num_poses; j++)
{
strcpy(animation.bones[j].name, "ANIMJOINTNAME");
animation.bones[j].parent = poses[j].parent;
}
// animations // animations
IQMAnim anim = {0}; *animCount = iqm.num_anims;
IQMAnim *anim = RL_MALLOC(iqm.num_anims*sizeof(IQMAnim));
fseek(iqmFile, iqm.ofs_anims, SEEK_SET); fseek(iqmFile, iqm.ofs_anims, SEEK_SET);
fread(&anim, sizeof(IQMAnim), 1, iqmFile); fread(anim, iqm.num_anims*sizeof(IQMAnim), 1, iqmFile);
ModelAnimation *animations = RL_MALLOC(iqm.num_anims*sizeof(ModelAnimation));
animation.frameCount = anim.num_frames;
//animation.framerate = anim.framerate;
// frameposes // frameposes
unsigned short *framedata = RL_MALLOC(sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels); unsigned short *framedata = RL_MALLOC(sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels);
fseek(iqmFile, iqm.ofs_frames, SEEK_SET); fseek(iqmFile, iqm.ofs_frames, SEEK_SET);
fread(framedata, sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels, 1, iqmFile); fread(framedata, sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels, 1, iqmFile);
animation.framePoses = RL_MALLOC(sizeof(Transform*)*anim.num_frames); for(int a=0;a<iqm.num_anims;a++)
for (int j = 0; j < anim.num_frames; j++) animation.framePoses[j] = RL_MALLOC(sizeof(Transform)*iqm.num_poses); {
int dcounter = anim.first_frame*iqm.num_framechannels; animations[a].frameCount = anim[a].num_frames;
animations[a].boneCount = iqm.num_poses;
animations[a].bones = RL_MALLOC(sizeof(BoneInfo)*iqm.num_poses);
animations[a].framePoses = RL_MALLOC(sizeof(Transform*)*anim[a].num_frames);
// unused for now
//animations[a].framerate = anim.framerate;
for (int frame = 0; frame < anim.num_frames; frame++) for (int j = 0; j < iqm.num_poses; j++)
{
strcpy(animations[a].bones[j].name, "ANIMJOINTNAME");
animations[a].bones[j].parent = poses[j].parent;
}
for (int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(sizeof(Transform)*iqm.num_poses);
int dcounter = anim[a].first_frame*iqm.num_framechannels;
for (int frame = 0; frame < anim[a].num_frames; frame++)
{ {
for (int i = 0; i < iqm.num_poses; i++) for (int i = 0; i < iqm.num_poses; i++)
{ {
animation.framePoses[frame][i].translation.x = poses[i].channeloffset[0]; animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0];
if (poses[i].mask & 0x01) if (poses[i].mask & 0x01)
{ {
animation.framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].translation.y = poses[i].channeloffset[1]; animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1];
if (poses[i].mask & 0x02) if (poses[i].mask & 0x02)
{ {
animation.framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].translation.z = poses[i].channeloffset[2]; animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2];
if (poses[i].mask & 0x04) if (poses[i].mask & 0x04)
{ {
animation.framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3];
if (poses[i].mask & 0x08) if (poses[i].mask & 0x08)
{ {
animation.framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4];
if (poses[i].mask & 0x10) if (poses[i].mask & 0x10)
{ {
animation.framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5];
if (poses[i].mask & 0x20) if (poses[i].mask & 0x20)
{ {
animation.framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6];
if (poses[i].mask & 0x40) if (poses[i].mask & 0x40)
{ {
animation.framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].scale.x = poses[i].channeloffset[7]; animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7];
if (poses[i].mask & 0x80) if (poses[i].mask & 0x80)
{ {
animation.framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].scale.y = poses[i].channeloffset[8]; animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8];
if (poses[i].mask & 0x100) if (poses[i].mask & 0x100)
{ {
animation.framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].scale.z = poses[i].channeloffset[9]; animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9];
if (poses[i].mask & 0x200) if (poses[i].mask & 0x200)
{ {
animation.framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
dcounter++; dcounter++;
} }
animation.framePoses[frame][i].rotation = QuaternionNormalize(animation.framePoses[frame][i].rotation); animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation);
} }
} }
// Build frameposes // Build frameposes
for (int frame = 0; frame < anim.num_frames; frame++) for (int frame = 0; frame < anim[a].num_frames; frame++)
{ {
for (int i = 0; i < animation.boneCount; i++) for (int i = 0; i < animations[a].boneCount; i++)
{ {
if (animation.bones[i].parent >= 0) if (animations[a].bones[i].parent >= 0)
{ {
animation.framePoses[frame][i].rotation = QuaternionMultiply(animation.framePoses[frame][animation.bones[i].parent].rotation, animation.framePoses[frame][i].rotation); animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation);
animation.framePoses[frame][i].translation = Vector3RotateByQuaternion(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].rotation); animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation);
animation.framePoses[frame][i].translation = Vector3Add(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].translation); animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation);
animation.framePoses[frame][i].scale = Vector3MultiplyV(animation.framePoses[frame][i].scale, animation.framePoses[frame][animation.bones[i].parent].scale); animations[a].framePoses[frame][i].scale = Vector3MultiplyV(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale);
}
} }
} }
} }
RL_FREE(framedata); RL_FREE(framedata);
RL_FREE(poses); RL_FREE(poses);
RL_FREE(anim);
fclose(iqmFile); fclose(iqmFile);
animations[0] = animation;
*animCount = count;
return animations; return animations;
} }