Explorar o código

BIG UPDATE: New models functions for animations!

Multiple functions added and some reviewed to adapt to the new multi-mesh, multi-material and animated models.
Ray %!s(int64=6) %!d(string=hai) anos
pai
achega
92733d6695

+ 27 - 24
examples/others/iqm_loader/models_iqm_animation.c → examples/models/models_animation.c

@@ -1,19 +1,16 @@
 /*******************************************************************************************
 *
-*   raylib [models] example - Load IQM 3d model with animations and play them
+*   raylib [models] example - Load 3d model with animations and play them
 *
-*   This example has been created using raylib 2.0 (www.raylib.com)
+*   This example has been created using raylib 2.5 (www.raylib.com)
 *   raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
 *
-*   Copyright (c) 2018 @culacant and @raysan5
+*   Copyright (c) 2019 Ramon Santamaria (@raysan5) and @culacant
 *
 ********************************************************************************************/
 
 #include "raylib.h"
 
-#define RIQM_IMPLEMENTATION
-#include "riqm.h"
-
 int main()
 {
     // Initialization
@@ -21,7 +18,7 @@ int main()
     int screenWidth = 800;
     int screenHeight = 450;
 
-    InitWindow(screenWidth, screenHeight, "raylib [models] example - iqm animation");
+    InitWindow(screenWidth, screenHeight, "raylib [models] example - model animation");
 
     // Define the camera to look into our 3d world
     Camera camera = { 0 };
@@ -31,26 +28,25 @@ int main()
     camera.fovy = 45.0f;                                // Camera field-of-view Y
     camera.type = CAMERA_PERSPECTIVE;                   // Camera mode type
 
-    // Load the animated model mesh and basic data
-    AnimatedModel model = LoadAnimatedModel("resources/guy.iqm");
 
-    // Load model texture and set material
-    // NOTE: There is only 1 mesh and 1 material (both at index 0), thats what the 2 0's are
-    model = AnimatedModelAddTexture(model, "resources/guytex.png");   // REPLACE!
-    model = SetMeshMaterial(model, 0, 0);                             // REPLACE!
+    Model model = LoadModel("resources/guy/guy.iqm");               // 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
-    Animation anim = LoadAnimationFromIQM("resources/guyanim.iqm");
-
+    int animsCount = 0;
+    ModelAnimation *anims = LoadModelAnimations("resources/guy/guyanim.iqm", &animsCount);
     int animFrameCounter = 0;
 
-    SetCameraMode(camera, CAMERA_FREE);  // Set free camera mode
+    SetCameraMode(camera, CAMERA_FREE); // Set free camera mode
 
-    SetTargetFPS(60);   // Set our game to run at 60 frames-per-second
+    SetTargetFPS(60);                   // Set our game to run at 60 frames-per-second
     //--------------------------------------------------------------------------------------
 
     // Main game loop
-    while (!WindowShouldClose())    // Detect window close button or ESC key
+    while (!WindowShouldClose())        // Detect window close button or ESC key
     {
         // Update
         //----------------------------------------------------------------------------------
@@ -60,7 +56,8 @@ int main()
         if (IsKeyDown(KEY_SPACE))
         {
             animFrameCounter++;
-            AnimateModel(model, anim, animFrameCounter); // Animate the model with animation data and frame
+            UpdateModelAnimation(model, anims[0], animFrameCounter);
+            if (animFrameCounter >= anims[0].frameCount) animFrameCounter = 0;
         }
         //----------------------------------------------------------------------------------
 
@@ -72,14 +69,18 @@ int main()
 
             BeginMode3D(camera);
 
-                DrawAnimatedModel(model, Vector3Zero(), 1.0f, WHITE);  // Draw animated model
+                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 IQM MODEL ANIMATION", 10, 10, 20, MAROON);
 
+            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();
@@ -88,8 +89,10 @@ int main()
 
     // De-Initialization
     //--------------------------------------------------------------------------------------
-    UnloadAnimation(anim);      // Unload animation data
-    UnloadAnimatedModel(model); // Unload animated model
+    // Unload model animations data
+    for (int i = 0; i < animsCount; i++) UnloadModelAnimation(anims[i]);
+    
+    UnloadModel(model);         // Unload model
 
     CloseWindow();              // Close window and OpenGL context
     //--------------------------------------------------------------------------------------

+ 0 - 0
examples/others/iqm_loader/resources/guy.blend → examples/models/resources/guy/guy.blend


+ 0 - 0
examples/others/iqm_loader/resources/guy.iqm → examples/models/resources/guy/guy.iqm


+ 0 - 0
examples/others/iqm_loader/resources/guyanim.iqm → examples/models/resources/guy/guyanim.iqm


+ 0 - 0
examples/others/iqm_loader/resources/guytex.png → examples/models/resources/guy/guytex.png


+ 2 - 737
examples/others/iqm_loader/riqm.h

@@ -49,67 +49,13 @@
 // 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;         // Number of joints (bones)
-    Joint *joints;          // Joints array
-                            // NOTE: Joints in anims do not have names
-
-    int frameCount;         // Number of animation frames
-    float framerate;        // Frame change speed
-
-    Pose **framepose;       // Poses array by frame (and one pose by joint)
-} Animation;
-
-// Animated Model type
-typedef struct AnimatedModel {
-    Matrix transform;       // Local transform matrix
-    
-    int meshCount;          // Number of meshes
-    Mesh *meshes;           // Meshes array
-
-    int materialCount;      // Number of materials
-    Material *materials;    // Materials array
-    
-    int *meshMaterialId;    // Mesh materials ids
-
-    // Animation required data
-    int jointCount;         // Number of joints (and keyposes)
-    Joint *joints;          // Mesh joints (bones)
-    Pose *basepose;         // Mesh base-poses by joint
-} AnimatedModel;
+#define BONE_NAME_LENGTH    32          // BoneInfo name string length
+#define MESH_NAME_LENGTH    32          // Mesh name string length
 
 //----------------------------------------------------------------------------------
 // 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
 
@@ -133,90 +79,6 @@ RIQMDEF void DrawAnimatedModelEx(AnimatedModel model, Vector3 position, Vector3
 //----------------------------------------------------------------------------------
 // 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
@@ -225,609 +87,12 @@ typedef enum {
 //----------------------------------------------------------------------------------
 // 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.meshes[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 meshes
-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.meshes[i]);
-
-    free(model.meshes);
-}
-
-// 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.meshes[m].vertexCount; i++)
-        {
-            weightId = model.meshes[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 meshes.baseVertices (default position) to calculate meshes.vertices (animated position)
-            outv = (Vector3){ model.meshes[m].baseVertices[vcounter], model.meshes[m].baseVertices[vcounter + 1], model.meshes[m].baseVertices[vcounter + 2] };
-            outv = Vector3MultiplyV(outv, outs);
-            outv = Vector3Subtract(outv, baset);
-            outv = Vector3RotateByQuaternion(outv, QuaternionMultiply(outr, QuaternionInvert(baser)));
-            outv = Vector3Add(outv, outt);
-            model.meshes[m].vertices[vcounter] = outv.x;
-            model.meshes[m].vertices[vcounter + 1] = outv.y;
-            model.meshes[m].vertices[vcounter + 2] = outv.z;
-
-            // normals
-            // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
-            outn = (Vector3){ model.meshes[m].baseNormals[vcounter], model.meshes[m].baseNormals[vcounter + 1], model.meshes[m].baseNormals[vcounter + 2] };
-            outn = Vector3RotateByQuaternion(outn, QuaternionMultiply(outr, QuaternionInvert(baser)));
-            model.meshes[m].normals[vcounter] = outn.x;
-            model.meshes[m].normals[vcounter + 1] = outn.y;
-            model.meshes[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 meshes\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.meshes[i], 0, model.meshes[i].vertexCount);      // Update vertex position
-        rlUpdateMesh(model.meshes[i], 2, model.meshes[i].vertexCount);      // Update vertex normals
-        rlDrawMesh(model.meshes[i], model.materials[model.meshMaterialId[i]], model.transform);   // Draw meshes
-    }
-}
-
-// 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.meshes = 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.meshes[i].vertexCount = imesh[i].num_vertexes;
-        
-        model.meshes[i].baseVertices = malloc(sizeof(float)*imesh[i].num_vertexes*3);     // Default IQM base position
-        model.meshes[i].baseNormals = malloc(sizeof(float)*imesh[i].num_vertexes*3);      // Default IQM base normal
-        
-        model.meshes[i].texcoords = malloc(sizeof(float)*imesh[i].num_vertexes*2);
-        model.meshes[i].weightId = malloc(sizeof(int)*imesh[i].num_vertexes*4);
-        model.meshes[i].weightBias = malloc(sizeof(float)*imesh[i].num_vertexes*4);
-        
-        model.meshes[i].triangleCount = imesh[i].num_triangles;
-        model.meshes[i].indices = malloc(sizeof(unsigned short)*imesh[i].num_triangles*3);
-        
-        // What we actually process for rendering, should be updated transforming meshes.vertices and meshes.normals
-        model.meshes[i].vertices = malloc(sizeof(float)*imesh[i].num_vertexes*3);     
-        model.meshes[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.meshes[m].indices[tcounter+2] = tri[i].vertex[0] - imesh[m].first_vertex;
-            model.meshes[m].indices[tcounter+1] = tri[i].vertex[1] - imesh[m].first_vertex;
-            model.meshes[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.meshes[m].vertices[vcounter] = vertex[i];
-                        model.meshes[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.meshes[m].normals[vcounter] = normal[i];
-                        model.meshes[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.meshes[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.meshes[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.meshes[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

+ 493 - 159
src/models.c

@@ -697,6 +697,18 @@ void UnloadModel(Model model)
     TraceLog(LOG_INFO, "Unloaded model data from RAM and VRAM");
 }
 
+// Load meshes from model file
+Mesh *LoadMeshes(const char *fileName, int *meshCount)
+{
+    Mesh *meshes = NULL;
+    int count = 0;
+    
+    // TODO: Load meshes from file (OBJ, IQM, GLTF)
+    
+    *meshCount = count;
+    return meshes;
+}
+
 // Unload mesh from memory (RAM and/or VRAM)
 void UnloadMesh(Mesh *mesh)
 {
@@ -759,6 +771,386 @@ void ExportMesh(Mesh mesh, const char *fileName)
     else TraceLog(LOG_WARNING, "Mesh could not be exported.");
 }
 
+// Load materials from model file
+Material *LoadMaterials(const char *fileName, int *materialCount)
+{
+    Material *materials = NULL;
+    unsigned int count = 0;
+    
+    // TODO: Support IQM and GLTF for materials parsing
+
+#if defined(SUPPORT_FILEFORMAT_MTL)
+    if (IsFileExtension(fileName, ".mtl"))
+    {
+        tinyobj_material_t *mats;
+
+        int result = tinyobj_parse_mtl_file(&mats, &count, fileName);
+
+        // TODO: Process materials to return
+
+        tinyobj_materials_free(mats, count);
+    }
+#else
+    TraceLog(LOG_WARNING, "[%s] Materials file not supported", fileName);
+#endif
+
+    // Set materials shader to default (DIFFUSE, SPECULAR, NORMAL)
+    for (int i = 0; i < count; i++) materials[i].shader = GetShaderDefault();
+
+    *materialCount = count;
+    return materials;
+}
+
+// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
+Material LoadMaterialDefault(void)
+{
+    Material material = { 0 };
+
+    material.shader = GetShaderDefault();
+    material.maps[MAP_DIFFUSE].texture = GetTextureDefault();   // White texture (1x1 pixel)
+    //material.maps[MAP_NORMAL].texture;         // NOTE: By default, not set
+    //material.maps[MAP_SPECULAR].texture;       // NOTE: By default, not set
+
+    material.maps[MAP_DIFFUSE].color = WHITE;    // Diffuse color
+    material.maps[MAP_SPECULAR].color = WHITE;   // Specular color
+
+    return material;
+}
+
+// Unload material from memory
+void UnloadMaterial(Material material)
+{
+    // Unload material shader (avoid unloading default shader, managed by raylib)
+    if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader);
+
+    // Unload loaded texture maps (avoid unloading default texture, managed by raylib)
+    for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
+    {
+        if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id);
+    }
+}
+
+// Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...)
+// NOTE: Previous texture should be manually unloaded
+void SetMaterialTexture(Material *material, int mapType, Texture2D texture)
+{
+    material->maps[mapType].texture = texture;
+}
+
+// Set the material for a mesh
+void SetModelMeshMaterial(Model *model, int meshId, int materialId)
+{
+    if (meshId >= model->meshCount) TraceLog(LOG_WARNING, "Mesh id greater than mesh count");
+    else if (materialId >= model->materialCount) TraceLog(LOG_WARNING,"Material id greater than material count");
+    else  model->meshMaterial[meshId] = materialId;
+}
+
+// Load model animations from file
+ModelAnimation *LoadModelAnimations(const char *filename, int *animCount)
+{
+    ModelAnimation *animations = (ModelAnimation *)malloc(1*sizeof(ModelAnimation));
+    int count = 1;
+    
+    #define IQM_MAGIC       "INTERQUAKEMODEL"   // IQM file magic number
+    #define IQM_VERSION     2                   // only IQM version 2 supported
+
+    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;
+    
+    ModelAnimation animation = { 0 };
+
+    FILE *iqmFile;
+    IQMHeader iqm;
+
+    iqmFile = fopen(filename,"rb");
+
+    if (!iqmFile)
+    {
+        TraceLog(LOG_ERROR, "[%s] Unable to open file", filename);
+    }
+
+    // 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);
+    }
+
+    if (iqm.version != IQM_VERSION)
+    {
+        TraceLog(LOG_ERROR, "IQM version %i is incorrect.", iqm.version);
+        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
+    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.boneCount = iqm.num_poses;
+    animation.bones = 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
+    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.framePoses = malloc(sizeof(Transform*)*anim.num_frames);
+    for (int j = 0; j < anim.num_frames; j++) animation.framePoses[j] = malloc(sizeof(Transform)*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.framePoses[frame][i].translation.x = poses[i].channeloffset[0];
+
+            if (poses[i].mask & 0x01)
+            {
+                animation.framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].translation.y = poses[i].channeloffset[1];
+
+            if (poses[i].mask & 0x02)
+            {
+                animation.framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].translation.z = poses[i].channeloffset[2];
+
+            if (poses[i].mask & 0x04)
+            {
+                animation.framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].rotation.x = poses[i].channeloffset[3];
+
+            if (poses[i].mask & 0x08)
+            {
+                animation.framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].rotation.y = poses[i].channeloffset[4];
+
+            if (poses[i].mask & 0x10)
+            {
+                animation.framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].rotation.z = poses[i].channeloffset[5];
+
+            if (poses[i].mask & 0x20)
+            {
+                animation.framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].rotation.w = poses[i].channeloffset[6];
+
+            if (poses[i].mask & 0x40)
+            {
+                animation.framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].scale.x = poses[i].channeloffset[7];
+
+            if (poses[i].mask & 0x80)
+            {
+                animation.framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].scale.y = poses[i].channeloffset[8];
+
+            if (poses[i].mask & 0x100)
+            {
+                animation.framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].scale.z = poses[i].channeloffset[9];
+
+            if (poses[i].mask & 0x200)
+            {
+                animation.framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9];
+                dcounter++;
+            }
+
+            animation.framePoses[frame][i].rotation = QuaternionNormalize(animation.framePoses[frame][i].rotation);
+        }
+    }
+
+    // Build frameposes
+    for (int frame = 0; frame < anim.num_frames; frame++)
+    {
+        for (int i = 0; i < animation.boneCount; i++)
+        {
+            if (animation.bones[i].parent >= 0)
+            {
+                animation.framePoses[frame][i].rotation = QuaternionMultiply(animation.framePoses[frame][animation.bones[i].parent].rotation, animation.framePoses[frame][i].rotation);
+                animation.framePoses[frame][i].translation = Vector3RotateByQuaternion(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].rotation);
+                animation.framePoses[frame][i].translation = Vector3Add(animation.framePoses[frame][i].translation, animation.framePoses[frame][animation.bones[i].parent].translation);
+                animation.framePoses[frame][i].scale = Vector3MultiplyV(animation.framePoses[frame][i].scale, animation.framePoses[frame][animation.bones[i].parent].scale);
+            }
+        }
+    }
+
+    free(framedata);
+    free(poses);
+    
+    fclose(iqmFile);
+
+    animations[0] = animation;
+    
+    *animCount = count;
+    return animations;
+}
+
+// Update model animated vertex data (positions and normals) for a given frame
+// NOTE: Updated data is uploaded to GPU
+void UpdateModelAnimation(Model model, ModelAnimation anim, int frame)
+{
+    if (frame >= anim.frameCount) frame = frame%anim.frameCount;
+
+    for (int m = 0; m < model.meshCount; m++)
+    {
+        Vector3 animVertex = { 0 };
+        Vector3 animNormal = { 0 };
+
+        Vector3 inTranslation = { 0 };
+        Quaternion inRotation = { 0 };
+        Vector3 inScale = { 0 };
+
+        Vector3 outTranslation = { 0 };
+        Quaternion outRotation = { 0 };
+        Vector3 outScale = { 0 };
+
+        int vCounter = 0;
+        int boneCounter = 0;
+        int boneId = 0;
+
+        for (int i = 0; i < model.meshes[m].vertexCount; i++)
+        {
+            boneId = model.meshes[m].boneIds[boneCounter];
+            inTranslation = model.bindPose[boneId].translation;
+            inRotation = model.bindPose[boneId].rotation;
+            inScale = model.bindPose[boneId].scale;
+            outTranslation = anim.framePoses[frame][boneId].translation;
+            outRotation = anim.framePoses[frame][boneId].rotation;
+            outScale = anim.framePoses[frame][boneId].scale;
+
+            // Vertices processing
+            // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position)
+            animVertex = (Vector3){ model.meshes[m].vertices[vCounter], model.meshes[m].vertices[vCounter + 1], model.meshes[m].vertices[vCounter + 2] };
+            animVertex = Vector3MultiplyV(animVertex, outScale);
+            animVertex = Vector3Subtract(animVertex, inTranslation);
+            animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
+            animVertex = Vector3Add(animVertex, outTranslation);
+            model.meshes[m].animVertices[vCounter] = animVertex.x;
+            model.meshes[m].animVertices[vCounter + 1] = animVertex.y;
+            model.meshes[m].animVertices[vCounter + 2] = animVertex.z;
+
+            // Normals processing
+            // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
+            animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] };
+            animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation)));
+            model.meshes[m].animNormals[vCounter] = animNormal.x;
+            model.meshes[m].animNormals[vCounter + 1] = animNormal.y;
+            model.meshes[m].animNormals[vCounter + 2] = animNormal.z;
+            vCounter += 3;
+
+            boneCounter += 4;
+        }
+
+        // Upload new vertex data to GPU for model drawing
+        rlUpdateBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float));    // Update vertex position
+        rlUpdateBuffer(model.meshes[m].vboId[2], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float));    // Update vertex normals
+    }
+}
+
+// Unload animation data
+void UnloadModelAnimation(ModelAnimation anim)
+{
+    for (int i = 0; i < anim.frameCount; i++) free(anim.framePoses[i]);
+    
+    free(anim.bones);
+    free(anim.framePoses);
+}
+
+// Check model animation skeleton match
+// NOTE: Only number of bones and parent connections are checked
+bool IsModelAnimationValid(Model model, ModelAnimation anim)
+{
+    int result = true;
+    
+    if (model.boneCount != anim.boneCount) result = false;
+    else
+    {
+        for (int i = 0; i < model.boneCount; i++)
+        {
+            if (model.bones[i].parent != anim.bones[i].parent) { result = false; break; }
+        }
+    }
+
+    return result;
+}
+
 #if defined(SUPPORT_MESH_GENERATION)
 // Generate polygonal mesh
 Mesh GenMeshPoly(int sides, float radius)
@@ -1807,59 +2199,124 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
 }
 #endif      // SUPPORT_MESH_GENERATION
 
-// Load material data (from file)
-Material LoadMaterial(const char *fileName)
+// Compute mesh bounding box limits
+// NOTE: minVertex and maxVertex should be transformed by model transform matrix
+BoundingBox MeshBoundingBox(Mesh mesh)
 {
-    Material material = { 0 };
+    // Get min and max vertex to construct bounds (AABB)
+    Vector3 minVertex = { 0 };
+    Vector3 maxVertex = { 0 };
 
-#if defined(SUPPORT_FILEFORMAT_MTL)
-    if (IsFileExtension(fileName, ".mtl"))
+    if (mesh.vertices != NULL)
     {
-        tinyobj_material_t *materials;
-        unsigned int materialCount = 0;
-
-        int result = tinyobj_parse_mtl_file(&materials, &materialCount, fileName);
-
-        // TODO: Process materials to return
+        minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
+        maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
 
-        tinyobj_materials_free(materials, materialCount);
+        for (int i = 1; i < mesh.vertexCount; i++)
+        {
+            minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
+            maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
+        }
     }
-#else
-    TraceLog(LOG_WARNING, "[%s] Material fileformat not supported, it can't be loaded", fileName);
-#endif
 
-    // Our material uses the default shader (DIFFUSE, SPECULAR, NORMAL)
-    material.shader = GetShaderDefault();
+    // Create the bounding box
+    BoundingBox box = { 0 };
+    box.min = minVertex;
+    box.max = maxVertex;
 
-    return material;
+    return box;
 }
 
-// Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
-Material LoadMaterialDefault(void)
+// Compute mesh tangents
+// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates
+// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html
+void MeshTangents(Mesh *mesh)
 {
-    Material material = { 0 };
+    if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float));
+    else TraceLog(LOG_WARNING, "Mesh tangents already exist");
 
-    material.shader = GetShaderDefault();
-    material.maps[MAP_DIFFUSE].texture = GetTextureDefault();   // White texture (1x1 pixel)
-    //material.maps[MAP_NORMAL].texture;         // NOTE: By default, not set
-    //material.maps[MAP_SPECULAR].texture;       // NOTE: By default, not set
+    Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
+    Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
 
-    material.maps[MAP_DIFFUSE].color = WHITE;    // Diffuse color
-    material.maps[MAP_SPECULAR].color = WHITE;   // Specular color
+    for (int i = 0; i < mesh->vertexCount; i += 3)
+    {
+        // Get triangle vertices
+        Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] };
+        Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] };
+        Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] };
 
-    return material;
+        // Get triangle texcoords
+        Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] };
+        Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] };
+        Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] };
+
+        float x1 = v2.x - v1.x;
+        float y1 = v2.y - v1.y;
+        float z1 = v2.z - v1.z;
+        float x2 = v3.x - v1.x;
+        float y2 = v3.y - v1.y;
+        float z2 = v3.z - v1.z;
+
+        float s1 = uv2.x - uv1.x;
+        float t1 = uv2.y - uv1.y;
+        float s2 = uv3.x - uv1.x;
+        float t2 = uv3.y - uv1.y;
+
+        float div = s1*t2 - s2*t1;
+        float r = (div == 0.0f)? 0.0f : 1.0f/div;
+
+        Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r };
+        Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r };
+
+        tan1[i + 0] = sdir;
+        tan1[i + 1] = sdir;
+        tan1[i + 2] = sdir;
+
+        tan2[i + 0] = tdir;
+        tan2[i + 1] = tdir;
+        tan2[i + 2] = tdir;
+    }
+
+    // Compute tangents considering normals
+    for (int i = 0; i < mesh->vertexCount; ++i)
+    {
+        Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
+        Vector3 tangent = tan1[i];
+
+        // TODO: Review, not sure if tangent computation is right, just used reference proposed maths...
+    #if defined(COMPUTE_TANGENTS_METHOD_01)
+        Vector3 tmp = Vector3Subtract(tangent, Vector3Multiply(normal, Vector3DotProduct(normal, tangent)));
+        tmp = Vector3Normalize(tmp);
+        mesh->tangents[i*4 + 0] = tmp.x;
+        mesh->tangents[i*4 + 1] = tmp.y;
+        mesh->tangents[i*4 + 2] = tmp.z;
+        mesh->tangents[i*4 + 3] = 1.0f;
+    #else
+        Vector3OrthoNormalize(&normal, &tangent);
+        mesh->tangents[i*4 + 0] = tangent.x;
+        mesh->tangents[i*4 + 1] = tangent.y;
+        mesh->tangents[i*4 + 2] = tangent.z;
+        mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f;
+    #endif
+    }
+
+    free(tan1);
+    free(tan2);
+
+    TraceLog(LOG_INFO, "Tangents computed for mesh");
 }
 
-// Unload material from memory
-void UnloadMaterial(Material material)
+// Compute mesh binormals (aka bitangent)
+void MeshBinormals(Mesh *mesh)
 {
-    // Unload material shader (avoid unloading default shader, managed by raylib)
-    if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader);
-
-    // Unload loaded texture maps (avoid unloading default texture, managed by raylib)
-    for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
+    for (int i = 0; i < mesh->vertexCount; i++)
     {
-        if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id);
+        Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
+        Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] };
+        float tangentW = mesh->tangents[i*4 + 3];
+
+        // TODO: Register computed binormal in mesh->binormal?
+        // Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW);
     }
 }
 
@@ -2239,129 +2696,6 @@ RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight)
     return result;
 }
 
-// Compute mesh bounding box limits
-// NOTE: minVertex and maxVertex should be transformed by model transform matrix
-BoundingBox MeshBoundingBox(Mesh mesh)
-{
-    // Get min and max vertex to construct bounds (AABB)
-    Vector3 minVertex = { 0 };
-    Vector3 maxVertex = { 0 };
-
-    printf("Mesh vertex count: %i\n", mesh.vertexCount);
-
-    if (mesh.vertices != NULL)
-    {
-        minVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
-        maxVertex = (Vector3){ mesh.vertices[0], mesh.vertices[1], mesh.vertices[2] };
-
-        for (int i = 1; i < mesh.vertexCount; i++)
-        {
-            minVertex = Vector3Min(minVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
-            maxVertex = Vector3Max(maxVertex, (Vector3){ mesh.vertices[i*3], mesh.vertices[i*3 + 1], mesh.vertices[i*3 + 2] });
-        }
-    }
-
-    // Create the bounding box
-    BoundingBox box = { 0 };
-    box.min = minVertex;
-    box.max = maxVertex;
-
-    return box;
-}
-
-// Compute mesh tangents
-// NOTE: To calculate mesh tangents and binormals we need mesh vertex positions and texture coordinates
-// Implementation base don: https://answers.unity.com/questions/7789/calculating-tangents-vector4.html
-void MeshTangents(Mesh *mesh)
-{
-    if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float));
-    else TraceLog(LOG_WARNING, "Mesh tangents already exist");
-
-    Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
-    Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
-
-    for (int i = 0; i < mesh->vertexCount; i += 3)
-    {
-        // Get triangle vertices
-        Vector3 v1 = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] };
-        Vector3 v2 = { mesh->vertices[(i + 1)*3 + 0], mesh->vertices[(i + 1)*3 + 1], mesh->vertices[(i + 1)*3 + 2] };
-        Vector3 v3 = { mesh->vertices[(i + 2)*3 + 0], mesh->vertices[(i + 2)*3 + 1], mesh->vertices[(i + 2)*3 + 2] };
-
-        // Get triangle texcoords
-        Vector2 uv1 = { mesh->texcoords[(i + 0)*2 + 0], mesh->texcoords[(i + 0)*2 + 1] };
-        Vector2 uv2 = { mesh->texcoords[(i + 1)*2 + 0], mesh->texcoords[(i + 1)*2 + 1] };
-        Vector2 uv3 = { mesh->texcoords[(i + 2)*2 + 0], mesh->texcoords[(i + 2)*2 + 1] };
-
-        float x1 = v2.x - v1.x;
-        float y1 = v2.y - v1.y;
-        float z1 = v2.z - v1.z;
-        float x2 = v3.x - v1.x;
-        float y2 = v3.y - v1.y;
-        float z2 = v3.z - v1.z;
-
-        float s1 = uv2.x - uv1.x;
-        float t1 = uv2.y - uv1.y;
-        float s2 = uv3.x - uv1.x;
-        float t2 = uv3.y - uv1.y;
-
-        float div = s1*t2 - s2*t1;
-        float r = (div == 0.0f)? 0.0f : 1.0f/div;
-
-        Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r };
-        Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r };
-
-        tan1[i + 0] = sdir;
-        tan1[i + 1] = sdir;
-        tan1[i + 2] = sdir;
-
-        tan2[i + 0] = tdir;
-        tan2[i + 1] = tdir;
-        tan2[i + 2] = tdir;
-    }
-
-    // Compute tangents considering normals
-    for (int i = 0; i < mesh->vertexCount; ++i)
-    {
-        Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
-        Vector3 tangent = tan1[i];
-
-        // TODO: Review, not sure if tangent computation is right, just used reference proposed maths...
-    #if defined(COMPUTE_TANGENTS_METHOD_01)
-        Vector3 tmp = Vector3Subtract(tangent, Vector3Multiply(normal, Vector3DotProduct(normal, tangent)));
-        tmp = Vector3Normalize(tmp);
-        mesh->tangents[i*4 + 0] = tmp.x;
-        mesh->tangents[i*4 + 1] = tmp.y;
-        mesh->tangents[i*4 + 2] = tmp.z;
-        mesh->tangents[i*4 + 3] = 1.0f;
-    #else
-        Vector3OrthoNormalize(&normal, &tangent);
-        mesh->tangents[i*4 + 0] = tangent.x;
-        mesh->tangents[i*4 + 1] = tangent.y;
-        mesh->tangents[i*4 + 2] = tangent.z;
-        mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f;
-    #endif
-    }
-
-    free(tan1);
-    free(tan2);
-
-    TraceLog(LOG_INFO, "Tangents computed for mesh");
-}
-
-// Compute mesh binormals (aka bitangent)
-void MeshBinormals(Mesh *mesh)
-{
-    for (int i = 0; i < mesh->vertexCount; i++)
-    {
-        Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
-        Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] };
-        float tangentW = mesh->tangents[i*4 + 3];
-
-        // TODO: Register computed binormal in mesh->binormal?
-        // Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW);
-    }
-}
-
 //----------------------------------------------------------------------------------
 // Module specific Functions Definition
 //----------------------------------------------------------------------------------

+ 21 - 12
src/raylib.h

@@ -752,7 +752,7 @@ typedef enum {
     MAP_IRRADIANCE,          // NOTE: Uses GL_TEXTURE_CUBE_MAP
     MAP_PREFILTER,           // NOTE: Uses GL_TEXTURE_CUBE_MAP
     MAP_BRDF
-} TexmapIndex;
+} MaterialMapType;
 
 #define MAP_DIFFUSE      MAP_ALBEDO
 #define MAP_SPECULAR     MAP_METALNESS
@@ -1256,16 +1256,25 @@ RLAPI void DrawGizmo(Vector3 position);
 // Model loading/unloading functions
 RLAPI Model LoadModel(const char *fileName);                                                            // Load model from files (meshes and materials)
 RLAPI Model LoadModelFromMesh(Mesh mesh);                                                               // Load model from generated mesh
-//RLAPI void LoadModelAnimations(const char fileName, ModelAnimation *anims, int *animsCount);            // Load model animations from file
-//RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame);                           // Update model animation pose
 RLAPI void UnloadModel(Model model);                                                                    // Unload model from memory (RAM and/or VRAM)
 
-// Mesh manipulation functions
-RLAPI BoundingBox MeshBoundingBox(Mesh mesh);                                                           // Compute mesh bounding box limits
-RLAPI void MeshTangents(Mesh *mesh);                                                                    // Compute mesh tangents
-RLAPI void MeshBinormals(Mesh *mesh);                                                                   // Compute mesh binormals
-RLAPI void UnloadMesh(Mesh *mesh);                                                                      // Unload mesh from memory (RAM and/or VRAM)
+// Mesh loading/unloading functions
+RLAPI Mesh *LoadMeshes(const char *fileName, int *meshCount);                                           // Load meshes from model file
 RLAPI void ExportMesh(Mesh mesh, const char *fileName);                                                 // Export mesh data to file
+RLAPI void UnloadMesh(Mesh *mesh);                                                                      // Unload mesh from memory (RAM and/or VRAM)
+
+// Material loading/unloading functions
+RLAPI Material *LoadMaterials(const char *fileName, int *materialCount);                                // Load materials from model file
+RLAPI Material LoadMaterialDefault(void);                                                               // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
+RLAPI void UnloadMaterial(Material material);                                                           // Unload material from GPU memory (VRAM)
+RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture);                      // Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...)
+RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId);                              // Set material for a mesh
+
+// Model animations loading/unloading functions
+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
 
 // Mesh generation functions
 RLAPI Mesh GenMeshPoly(int sides, float radius);                                                        // Generate polygonal mesh
@@ -1279,10 +1288,10 @@ RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides);
 RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size);                                             // Generate heightmap mesh from image data
 RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize);                                           // Generate cubes-based map mesh from image data
 
-// Material loading/unloading functions
-RLAPI Material LoadMaterial(const char *fileName);                                                      // Load material from file
-RLAPI Material LoadMaterialDefault(void);                                                               // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
-RLAPI void UnloadMaterial(Material material);                                                           // Unload material from GPU memory (VRAM)
+// Mesh manipulation functions
+RLAPI BoundingBox MeshBoundingBox(Mesh mesh);                                                           // Compute mesh bounding box limits
+RLAPI void MeshTangents(Mesh *mesh);                                                                    // Compute mesh tangents
+RLAPI void MeshBinormals(Mesh *mesh);                                                                   // Compute mesh binormals
 
 // Model drawing functions
 RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint);                           // Draw a model (with texture if set)