/**********************************************************************************************
*
*   riqm - InterQuake Model format (IQM) loader for animated meshes
*
*   CONFIGURATION:
*
*   #define RIQM_IMPLEMENTATION
*       Generates the implementation of the library into the included file.
*       If not defined, the library is in header only mode and can be included in other headers
*       or source files without problems. But only ONE file should hold the implementation.
*
*
*   LICENSE: zlib/libpng
*
*   Copyright (c) 2018 Jonas Daeyaert (@culacant) and Ramon Santamaria (@raysan5)
*
*   This software is provided "as-is", without any express or implied warranty. In no event
*   will the authors be held liable for any damages arising from the use of this software.
*
*   Permission is granted to anyone to use this software for any purpose, including commercial
*   applications, and to alter it and redistribute it freely, subject to the following restrictions:
*
*     1. The origin of this software must not be misrepresented; you must not claim that you
*     wrote the original software. If you use this software in a product, an acknowledgment
*     in the product documentation would be appreciated but is not required.
*
*     2. Altered source versions must be plainly marked as such, and must not be misrepresented
*     as being the original software.
*
*     3. This notice may not be removed or altered from any source distribution.
*
**********************************************************************************************/

#ifndef RIQM_H
#define RIQM_H

//#define RIQM_STATIC
#ifdef RIQM_STATIC
    #define RIQMDEF static              // Functions just visible to module including this file
#else
    #ifdef __cplusplus
        #define RIQMDEF extern "C"      // Functions visible from other files (no name mangling of functions in C++)
    #else
        #define RIQMDEF extern          // Functions visible from other files
    #endif
#endif

//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------

#define JOINT_NAME_LENGTH    32          // Joint name string length
#define MESH_NAME_LENGTH     32          // Mesh name string length

typedef struct Joint {
    char name[JOINT_NAME_LENGTH];
    int parent;
} Joint;

typedef struct Pose {
    Vector3 translation;
    Quaternion rotation;
    Vector3 scale;
} Pose;

typedef struct Animation {
    int jointCount;
    Joint *joints;      // NOTE: Joints in anims do not have names

    int frameCount;
    float framerate;

    Pose **framepose;
} Animation;

// Animated Model type
typedef struct AnimatedModel {
    int meshCount;
    Mesh *mesh;

    int materialCount;
    int *meshMaterialId;
    Material *materials;

    int jointCount;
    Joint *joints;

    Pose *basepose;
    Matrix transform;
} AnimatedModel;

//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------

// Loading/Unloading functions
RIQMDEF AnimatedModel LoadAnimatedModel(const char *filename);
RIQMDEF void UnloadAnimatedModel(AnimatedModel model);
RIQMDEF Animation LoadAnimation(const char *filename);
RIQMDEF void UnloadAnimation(Animation anim);

RIQMDEF AnimatedModel AnimatedModelAddTexture(AnimatedModel model, const char *filename);    // GENERIC!
RIQMDEF AnimatedModel SetMeshMaterial(AnimatedModel model, int meshid, int textureid);       // GENERIC!

// Usage functionality
RIQMDEF bool CheckSkeletonsMatch(AnimatedModel model, Animation anim);
RIQMDEF void AnimateModel(AnimatedModel model, Animation anim, int frame);
RIQMDEF void DrawAnimatedModel(AnimatedModel model, Vector3 position, float scale, Color tint);
RIQMDEF void DrawAnimatedModelEx(AnimatedModel model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint);

#endif // RIQM_H


/***********************************************************************************
*
*  RIQM IMPLEMENTATION
*
************************************************************************************/

#if defined(RIQM_IMPLEMENTATION)

//#include "utils.h"          // Required for: fopen() Android mapping

#include <stdio.h>          // Required for: FILE, fopen(), fclose(), feof(), fseek(), fread()
#include <stdlib.h>         // Required for: malloc(), free()
#include <string.h>         // Required for: strncmp(),strcpy()

#include "raymath.h"        // Required for: Vector3, Quaternion functions

//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
#define IQM_MAGIC       "INTERQUAKEMODEL"   // IQM file magic number
#define IQM_VERSION     2                   // only IQM version 2 supported
#define ANIMJOINTNAME   "ANIMJOINT"         // default joint name (used in Animation)

//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// iqm file structs
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 IQMMesh {
    unsigned int name;
    unsigned int material;
    unsigned int first_vertex, num_vertexes;
    unsigned int first_triangle, num_triangles;
} IQMMesh;

typedef struct IQMTriangle {
    unsigned int vertex[3];
} IQMTriangle;

typedef struct IQMAdjacency {                 // adjacency unused by default
    unsigned int triangle[3];
} IQMAdjacency;

typedef struct IQMJoint {
    unsigned int name;
    int parent;
    float translate[3], rotate[4], scale[3];
} IQMJoint;

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;

typedef struct IQMVertexArray {
    unsigned int type;
    unsigned int flags;
    unsigned int format;
    unsigned int size;
    unsigned int offset;
} IQMVertexArray;

typedef struct IQMBounds {                    // bounds unused by default
    float bbmin[3], bbmax[3];
    float xyradius, radius;
} IQMBounds;


typedef enum {
    IQM_POSITION     = 0,
    IQM_TEXCOORD     = 1,
    IQM_NORMAL       = 2,
    IQM_TANGENT      = 3,                    // tangents unused by default
    IQM_BLENDINDEXES = 4,
    IQM_BLENDWEIGHTS = 5,
    IQM_COLOR        = 6,                    // vertex colors unused by default
    IQM_CUSTOM       = 0x10                  // custom vertex values unused by default
} IQMVertexType;

//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------

//----------------------------------------------------------------------------------
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
static AnimatedModel LoadIQM(const char *filename);

#ifdef __cplusplus
extern "C" {            // Prevents name mangling of functions
#endif

// Load .iqm file and initialize animated model
AnimatedModel LoadAnimatedModel(const char *filename)
{
    AnimatedModel out = LoadIQM(filename);

    for (int i = 0; i < out.meshCount; i++) rlLoadMesh(&out.mesh[i], false);

    out.transform = MatrixIdentity();
    out.meshMaterialId = malloc(sizeof(int)*out.meshCount);
    out.materials = NULL;
    out.materialCount = 0;

    for (int i = 0; i < out.meshCount; i++) out.meshMaterialId[i] = -1;

    return out;
}

// Add a texture to an animated model
AnimatedModel AnimatedModelAddTexture(AnimatedModel model, const char *filename)
{
    Texture2D texture = LoadTexture(filename);

    model.materials = realloc(model.materials, sizeof(Material)*(model.materialCount + 1));
    model.materials[model.materialCount] = LoadMaterialDefault();
    model.materials[model.materialCount].maps[MAP_DIFFUSE].texture = texture;
    model.materialCount++;

    return model;
}

// Set the material for a mesh
AnimatedModel SetMeshMaterial(AnimatedModel model, int meshid, int textureid)
{
    if (meshid > model.meshCount)
    {
        TraceLog(LOG_WARNING, "MeshId greater than meshCount\n");
        return model;
    }

    if (textureid > model.materialCount)
    {
        TraceLog(LOG_WARNING,"textureid greater than materialCount\n");
        return model;
    }

    model.meshMaterialId[meshid] = textureid;

    return model;
}

// Load animations from a .iqm file
Animation LoadAnimationFromIQM(const char *filename)
{
    Animation animation = { 0 };

    FILE *iqmFile;
    IQMHeader iqm;

    iqmFile = fopen(filename,"rb");

    if (!iqmFile)
    {
        TraceLog(LOG_ERROR, "[%s] Unable to open file", filename);
        return animation;
    }

    // header
    fread(&iqm, sizeof(IQMHeader), 1, iqmFile);

    if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC)))
    {
        TraceLog(LOG_ERROR, "Magic Number \"%s\"does not match.", iqm.magic);
        fclose(iqmFile);
        return animation;
    }

    if (iqm.version != IQM_VERSION)
    {
        TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version);
        fclose(iqmFile);
        return animation;
    }

    // header
    if (iqm.num_anims > 1) TraceLog(LOG_WARNING, "More than 1 animation in file, only the first one will get loaded");

    // joints
    IQMPose *poses;
    poses = malloc(sizeof(IQMPose)*iqm.num_poses);
    fseek(iqmFile, iqm.ofs_poses, SEEK_SET);
    fread(poses, sizeof(IQMPose)*iqm.num_poses, 1, iqmFile);

    animation.jointCount = iqm.num_poses;
    animation.joints = malloc(sizeof(Joint)*iqm.num_poses);

    for (int j = 0; j < iqm.num_poses; j++)
    {
        strcpy(animation.joints[j].name, ANIMJOINTNAME);
        animation.joints[j].parent = poses[j].parent;
    }

    // animations
    IQMAnim anim = {0};
    fseek(iqmFile, iqm.ofs_anims, SEEK_SET);
    fread(&anim, sizeof(IQMAnim), 1, iqmFile);

    animation.frameCount = anim.num_frames;
    animation.framerate = anim.framerate;

    // frameposes
    unsigned short *framedata = malloc(sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels);
    fseek(iqmFile, iqm.ofs_frames, SEEK_SET);
    fread(framedata, sizeof(unsigned short)*iqm.num_frames*iqm.num_framechannels, 1, iqmFile);

    animation.framepose = malloc(sizeof(Pose*)*anim.num_frames);
    for (int j = 0; j < anim.num_frames; j++) animation.framepose[j] = malloc(sizeof(Pose)*iqm.num_poses);

    int dcounter = anim.first_frame*iqm.num_framechannels;

    for (int frame = 0; frame < anim.num_frames; frame++)
    {
        for (int i = 0; i < iqm.num_poses; i++)
        {
            animation.framepose[frame][i].translation.x = poses[i].channeloffset[0];

            if (poses[i].mask & 0x01)
            {
                animation.framepose[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
                dcounter++;
            }

            animation.framepose[frame][i].translation.y = poses[i].channeloffset[1];

            if (poses[i].mask & 0x02)
            {
                animation.framepose[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
                dcounter++;
            }

            animation.framepose[frame][i].translation.z = poses[i].channeloffset[2];

            if (poses[i].mask & 0x04)
            {
                animation.framepose[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
                dcounter++;
            }

            animation.framepose[frame][i].rotation.x = poses[i].channeloffset[3];

            if (poses[i].mask & 0x08)
            {
                animation.framepose[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
                dcounter++;
            }

            animation.framepose[frame][i].rotation.y = poses[i].channeloffset[4];

            if (poses[i].mask & 0x10)
            {
                animation.framepose[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
                dcounter++;
            }

            animation.framepose[frame][i].rotation.z = poses[i].channeloffset[5];

            if (poses[i].mask & 0x20)
            {
                animation.framepose[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
                dcounter++;
            }

            animation.framepose[frame][i].rotation.w = poses[i].channeloffset[6];

            if (poses[i].mask & 0x40)
            {
                animation.framepose[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
                dcounter++;
            }

            animation.framepose[frame][i].scale.x = poses[i].channeloffset[7];

            if (poses[i].mask & 0x80)
            {
                animation.framepose[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
                dcounter++;
            }

            animation.framepose[frame][i].scale.y = poses[i].channeloffset[8];

            if (poses[i].mask & 0x100)
            {
                animation.framepose[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
                dcounter++;
            }

            animation.framepose[frame][i].scale.z = poses[i].channeloffset[9];

            if (poses[i].mask & 0x200)
            {
                animation.framepose[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
                dcounter++;
            }

            animation.framepose[frame][i].rotation = QuaternionNormalize(animation.framepose[frame][i].rotation);
        }
    }

    // Build frameposes
    for (int frame = 0; frame < anim.num_frames; frame++)
    {
        for (int i = 0; i < animation.jointCount; i++)
        {
            if (animation.joints[i].parent >= 0)
            {
                animation.framepose[frame][i].rotation = QuaternionMultiply(animation.framepose[frame][animation.joints[i].parent].rotation, animation.framepose[frame][i].rotation);
                animation.framepose[frame][i].translation = Vector3RotateByQuaternion(animation.framepose[frame][i].translation, animation.framepose[frame][animation.joints[i].parent].rotation);
                animation.framepose[frame][i].translation = Vector3Add(animation.framepose[frame][i].translation, animation.framepose[frame][animation.joints[i].parent].translation);
                animation.framepose[frame][i].scale = Vector3MultiplyV(animation.framepose[frame][i].scale, animation.framepose[frame][animation.joints[i].parent].scale);
            }
        }
    }

    free(framedata);
    free(poses);

    fclose(iqmFile);

    return animation;
}

// Unload animated model
void UnloadAnimatedModel(AnimatedModel model)
{
    free(model.materials);
    free(model.meshMaterialId);
    free(model.joints);
    free(model.basepose);

    for (int i = 0; i < model.meshCount; i++) rlUnloadMesh(&model.mesh[i]);

    free(model.mesh);
}

// Unload animation
void UnloadAnimation(Animation anim)
{
    free(anim.joints);
    free(anim.framepose);

    for (int i = 0; i < anim.frameCount; i++) free(anim.framepose[i]);
}

// Check if skeletons match, only parents and jointCount are checked
bool CheckSkeletonsMatch(AnimatedModel model, Animation anim)
{
    if (model.jointCount != anim.jointCount) return 0;

    for (int i = 0; i < model.jointCount; i++)
    {
        if (model.joints[i].parent != anim.joints[i].parent) return 0;
    }

    return 1;
}

// Calculate the animated vertex positions and normals based on an animation at a given frame
void AnimateModel(AnimatedModel model, Animation anim, int frame)
{
    if (frame >= anim.frameCount) frame = frame%anim.frameCount;

    for (int m = 0; m < model.meshCount; m++)
    {
        Vector3 outv = {0};
        Vector3 outn = {0};

        Vector3 baset = {0};
        Quaternion baser = {0};
        Vector3 bases = {0};

        Vector3 outt = {0};
        Quaternion outr = {0};
        Vector3 outs = {0};

        int vcounter = 0;
        int wcounter = 0;
        int weightId = 0;

        for (int i = 0; i < model.mesh[m].vertexCount; i++)
        {
            weightId = model.mesh[m].weightId[wcounter];
            baset = model.basepose[weightId].translation;
            baser = model.basepose[weightId].rotation;
            bases = model.basepose[weightId].scale;
            outt = anim.framepose[frame][weightId].translation;
            outr = anim.framepose[frame][weightId].rotation;
            outs = anim.framepose[frame][weightId].scale;

            // vertices
            // NOTE: We use mesh.baseVertices (default position) to calculate mesh.vertices (animated position)
            outv = (Vector3){ model.mesh[m].baseVertices[vcounter], model.mesh[m].baseVertices[vcounter + 1], model.mesh[m].baseVertices[vcounter + 2] };
            outv = Vector3MultiplyV(outv, outs);
            outv = Vector3Subtract(outv, baset);
            outv = Vector3RotateByQuaternion(outv, QuaternionMultiply(outr, QuaternionInvert(baser)));
            outv = Vector3Add(outv, outt);
            model.mesh[m].vertices[vcounter] = outv.x;
            model.mesh[m].vertices[vcounter + 1] = outv.y;
            model.mesh[m].vertices[vcounter + 2] = outv.z;

            // normals
            // NOTE: We use mesh.baseNormals (default normal) to calculate mesh.normals (animated normals)
            outn = (Vector3){ model.mesh[m].baseNormals[vcounter], model.mesh[m].baseNormals[vcounter + 1], model.mesh[m].baseNormals[vcounter + 2] };
            outn = Vector3RotateByQuaternion(outn, QuaternionMultiply(outr, QuaternionInvert(baser)));
            model.mesh[m].normals[vcounter] = outn.x;
            model.mesh[m].normals[vcounter + 1] = outn.y;
            model.mesh[m].normals[vcounter + 2] = outn.z;
            vcounter += 3;
            wcounter += 4;
        }
    }
}

// Draw an animated model
void DrawAnimatedModel(AnimatedModel model, Vector3 position, float scale, Color tint)
{
    Vector3 vScale = { scale, scale, scale };
    Vector3 rotationAxis = { 1.0f, 0.0f,0.0f };

    DrawAnimatedModelEx(model, position, rotationAxis, -90.0f, vScale, tint);
}

// Draw an animated model with extended parameters
void DrawAnimatedModelEx(AnimatedModel model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint)
{
    if (model.materialCount == 0)
    {
        TraceLog(LOG_WARNING,"No materials set, can't draw animated mesh\n");
        return;
    }

    Matrix matScale = MatrixScale(scale.x, scale.y, scale.z);
    Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD);
    Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z);

    Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation);
    model.transform = MatrixMultiply(model.transform, matTransform);

    for (int i = 0; i < model.meshCount; i++)
    {
        rlUpdateMesh(model.mesh[i], 0, model.mesh[i].vertexCount);      // Update vertex position
        rlUpdateMesh(model.mesh[i], 2, model.mesh[i].vertexCount);      // Update vertex normals
        rlDrawMesh(model.mesh[i], model.materials[model.meshMaterialId[i]], model.transform);   // Draw mesh
    }
}

// Load animated model meshes from IQM file
static AnimatedModel LoadIQM(const char *filename)
{
    AnimatedModel model = { 0 };

    FILE *iqmFile;
    IQMHeader iqm;

    IQMMesh *imesh;
    IQMTriangle *tri;
    IQMVertexArray *va;
    IQMJoint *ijoint;

    float *vertex;
    float *normal;
    float *text;
    char *blendi;
    unsigned char *blendw;

    iqmFile = fopen(filename, "rb");

    if (!iqmFile)
    {
        TraceLog(LOG_ERROR, "[%s] Unable to open file", filename);
        return model;
    }

    // header
    fread(&iqm,sizeof(IQMHeader), 1, iqmFile);

    if (strncmp(iqm.magic, IQM_MAGIC, sizeof(IQM_MAGIC)))
    {
        TraceLog(LOG_ERROR, "Magic Number \"%s\"does not match.", iqm.magic);
        fclose(iqmFile);
        return model;
    }

    if(iqm.version != IQM_VERSION)
    {
        TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version);
        fclose(iqmFile);
        return model;
    }

    // meshes
    imesh = malloc(sizeof(IQMMesh)*iqm.num_meshes);
    fseek(iqmFile, iqm.ofs_meshes, SEEK_SET);
    fread(imesh, sizeof(IQMMesh)*iqm.num_meshes, 1, iqmFile);

    model.meshCount = iqm.num_meshes;
    model.mesh = malloc(sizeof(Mesh)*iqm.num_meshes);
    
    char name[MESH_NAME_LENGTH];

    for (int i = 0; i < iqm.num_meshes; i++)
    {
        fseek(iqmFile,iqm.ofs_text+imesh[i].name,SEEK_SET);
        fread(name, sizeof(char)*MESH_NAME_LENGTH, 1, iqmFile);         // Mesh name not used...
        model.mesh[i].vertexCount = imesh[i].num_vertexes;
        
        model.mesh[i].baseVertices = malloc(sizeof(float)*imesh[i].num_vertexes*3);     // Default IQM base position
        model.mesh[i].baseNormals = malloc(sizeof(float)*imesh[i].num_vertexes*3);      // Default IQM base normal
        
        model.mesh[i].texcoords = malloc(sizeof(float)*imesh[i].num_vertexes*2);
        model.mesh[i].weightId = malloc(sizeof(int)*imesh[i].num_vertexes*4);
        model.mesh[i].weightBias = malloc(sizeof(float)*imesh[i].num_vertexes*4);
        
        model.mesh[i].triangleCount = imesh[i].num_triangles;
        model.mesh[i].indices = malloc(sizeof(unsigned short)*imesh[i].num_triangles*3);
        
        // What we actually process for rendering, should be updated transforming mesh.vertices and mesh.normals
        model.mesh[i].vertices = malloc(sizeof(float)*imesh[i].num_vertexes*3);     
        model.mesh[i].normals = malloc(sizeof(float)*imesh[i].num_vertexes*3);
    }

    // tris
    tri = malloc(sizeof(IQMTriangle)*iqm.num_triangles);
    fseek(iqmFile, iqm.ofs_triangles, SEEK_SET);
    fread(tri, sizeof(IQMTriangle)*iqm.num_triangles, 1, iqmFile);

    for (int m = 0; m < iqm.num_meshes; m++)
    {
        int tcounter = 0;

        for (int i = imesh[m].first_triangle; i < imesh[m].first_triangle+imesh[m].num_triangles; i++)
        {
            // IQM triangles are stored counter clockwise, but raylib sets opengl to clockwise drawing, so we swap them around
            model.mesh[m].indices[tcounter+2] = tri[i].vertex[0] - imesh[m].first_vertex;
            model.mesh[m].indices[tcounter+1] = tri[i].vertex[1] - imesh[m].first_vertex;
            model.mesh[m].indices[tcounter] = tri[i].vertex[2] - imesh[m].first_vertex;
            tcounter += 3;
        }
    }

    // vertarrays
    va = malloc(sizeof(IQMVertexArray)*iqm.num_vertexarrays);
    fseek(iqmFile, iqm.ofs_vertexarrays, SEEK_SET);
    fread(va, sizeof(IQMVertexArray)*iqm.num_vertexarrays, 1, iqmFile);

    for (int i = 0; i < iqm.num_vertexarrays; i++)
    {
        switch (va[i].type)
        {
            case IQM_POSITION:
            {
                vertex = malloc(sizeof(float)*iqm.num_vertexes*3);
                fseek(iqmFile, va[i].offset, SEEK_SET);
                fread(vertex, sizeof(float)*iqm.num_vertexes*3, 1, iqmFile);

                for (int m = 0; m < iqm.num_meshes; m++)
                {
                    int vcounter = 0;
                    for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
                    {
                        model.mesh[m].vertices[vcounter] = vertex[i];
                        model.mesh[m].baseVertices[vcounter] = vertex[i];
                        vcounter++;
                    }
                }
            } break;
            case IQM_NORMAL:
            {
                normal = malloc(sizeof(float)*iqm.num_vertexes*3);
                fseek(iqmFile, va[i].offset, SEEK_SET);
                fread(normal, sizeof(float)*iqm.num_vertexes*3, 1, iqmFile);

                for (int m = 0; m < iqm.num_meshes; m++)
                {
                    int vcounter = 0;
                    for (int i = imesh[m].first_vertex*3; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*3; i++)
                    {
                        model.mesh[m].normals[vcounter] = normal[i];
                        model.mesh[m].baseNormals[vcounter] = normal[i];
                        vcounter++;
                    }
                }
            } break;
            case IQM_TEXCOORD:
            {
                text = malloc(sizeof(float)*iqm.num_vertexes*2);
                fseek(iqmFile, va[i].offset, SEEK_SET);
                fread(text, sizeof(float)*iqm.num_vertexes*2, 1, iqmFile);

                for (int m = 0; m < iqm.num_meshes; m++)
                {
                    int vcounter = 0;
                    for (int i = imesh[m].first_vertex*2; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*2; i++)
                    {
                        model.mesh[m].texcoords[vcounter] = text[i];
                        vcounter++;
                    }
                }
            } break;
            case IQM_BLENDINDEXES:
            {
                blendi = malloc(sizeof(char)*iqm.num_vertexes*4);
                fseek(iqmFile, va[i].offset, SEEK_SET);
                fread(blendi, sizeof(char)*iqm.num_vertexes*4, 1, iqmFile);

                for (int m = 0; m < iqm.num_meshes; m++)
                {
                    int vcounter = 0;
                    for (int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
                    {
                        model.mesh[m].weightId[vcounter] = blendi[i];
                        vcounter++;
                    }
                }
            } break;
            case IQM_BLENDWEIGHTS:
            {
                blendw = malloc(sizeof(unsigned char)*iqm.num_vertexes*4);
                fseek(iqmFile,va[i].offset,SEEK_SET);
                fread(blendw,sizeof(unsigned char)*iqm.num_vertexes*4,1,iqmFile);

                for (int m = 0; m < iqm.num_meshes; m++)
                {
                    int vcounter = 0;
                    for (int i = imesh[m].first_vertex*4; i < (imesh[m].first_vertex + imesh[m].num_vertexes)*4; i++)
                    {
                        model.mesh[m].weightBias[vcounter] = blendw[i]/255.0f;
                        vcounter++;
                    }
                }
            } break;
        }
    }

    // joints, include base poses
    ijoint = malloc(sizeof(IQMJoint)*iqm.num_joints);
    fseek(iqmFile, iqm.ofs_joints, SEEK_SET);
    fread(ijoint, sizeof(IQMJoint)*iqm.num_joints, 1, iqmFile);

    model.jointCount = iqm.num_joints;
    model.joints = malloc(sizeof(Joint)*iqm.num_joints);
    model.basepose = malloc(sizeof(Pose)*iqm.num_joints);

    for (int i = 0; i < iqm.num_joints; i++)
    {
        // joints
        model.joints[i].parent = ijoint[i].parent;
        fseek(iqmFile, iqm.ofs_text + ijoint[i].name, SEEK_SET);
        fread(model.joints[i].name,sizeof(char)*JOINT_NAME_LENGTH, 1, iqmFile);

        // basepose
        model.basepose[i].translation.x = ijoint[i].translate[0];
        model.basepose[i].translation.y = ijoint[i].translate[1];
        model.basepose[i].translation.z = ijoint[i].translate[2];

        model.basepose[i].rotation.x = ijoint[i].rotate[0];
        model.basepose[i].rotation.y = ijoint[i].rotate[1];
        model.basepose[i].rotation.z = ijoint[i].rotate[2];
        model.basepose[i].rotation.w = ijoint[i].rotate[3];

        model.basepose[i].scale.x = ijoint[i].scale[0];
        model.basepose[i].scale.y = ijoint[i].scale[1];
        model.basepose[i].scale.z = ijoint[i].scale[2];
    }

    // build base pose
    for (int i = 0; i < model.jointCount; i++)
    {
        if (model.joints[i].parent >= 0)
        {
            model.basepose[i].rotation = QuaternionMultiply(model.basepose[model.joints[i].parent].rotation, model.basepose[i].rotation);
            model.basepose[i].translation = Vector3RotateByQuaternion(model.basepose[i].translation, model.basepose[model.joints[i].parent].rotation);
            model.basepose[i].translation = Vector3Add(model.basepose[i].translation, model.basepose[model.joints[i].parent].translation);
            model.basepose[i].scale = Vector3MultiplyV(model.basepose[i].scale, model.basepose[model.joints[i].parent].scale);
        }
    }

    fclose(iqmFile);
    free(imesh);
    free(tri);
    free(va);
    free(vertex);
    free(normal);
    free(text);
    free(blendi);
    free(blendw);
    free(ijoint);

    return model;
}

#endif
