Browse Source

REVIEWED: `LoadGLTF()`

Reorganized code and added some feature and listed restrictions. Some gltf models do not work yet.
raysan5 3 years ago
parent
commit
18db2c4f01
1 changed files with 265 additions and 149 deletions
  1. 265 149
      src/rmodels.c

+ 265 - 149
src/rmodels.c

@@ -4494,109 +4494,111 @@ static ModelAnimation* LoadModelAnimationsIQM(const char *fileName, unsigned int
 #endif
 #endif
 
 
 #if defined(SUPPORT_FILEFORMAT_GLTF)
 #if defined(SUPPORT_FILEFORMAT_GLTF)
-static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Color tint)
+// Load image from different glTF provided methods (uri, path, buffer_view)
+static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPath)
 {
 {
-    Image rimage = { 0 };
+    Image image = { 0 };
 
 
-    if (image->uri)
+    if (cgltfImage->uri != NULL)     // Check if image data is provided as a uri (base64 or path)
     {
     {
-        if ((strlen(image->uri) > 5) &&
-            (image->uri[0] == 'd') &&
-            (image->uri[1] == 'a') &&
-            (image->uri[2] == 't') &&
-            (image->uri[3] == 'a') &&
-            (image->uri[4] == ':'))
+        if ((strlen(cgltfImage->uri) > 5) &&
+            (cgltfImage->uri[0] == 'd') &&
+            (cgltfImage->uri[1] == 'a') &&
+            (cgltfImage->uri[2] == 't') &&
+            (cgltfImage->uri[3] == 'a') &&
+            (cgltfImage->uri[4] == ':'))     // Check if image is provided as base64 text data
         {
         {
-            // Data URI
-            // Format: data:<mediatype>;base64,<data>
+            // Data URI Format: data:<mediatype>;base64,<data>
 
 
             // Find the comma
             // Find the comma
             int i = 0;
             int i = 0;
-            while ((image->uri[i] != ',') && (image->uri[i] != 0)) i++;
+            while ((cgltfImage->uri[i] != ',') && (cgltfImage->uri[i] != 0)) i++;
 
 
-            if (image->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image");
+            if (cgltfImage->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image");
             else
             else
             {
             {
-                int base64Size = strlen(image->uri + i + 1);
+                int base64Size = strlen(cgltfImage->uri + i + 1);
                 int outSize = 3*(base64Size/4);         // TODO: Consider padding (-numberOfPaddingCharacters)
                 int outSize = 3*(base64Size/4);         // TODO: Consider padding (-numberOfPaddingCharacters)
                 char *data = NULL;
                 char *data = NULL;
 
 
                 cgltf_options options = { 0 };
                 cgltf_options options = { 0 };
-                cgltf_result result = cgltf_load_buffer_base64(&options, outSize, image->uri + i + 1, &data);
+                cgltf_result result = cgltf_load_buffer_base64(&options, outSize, cgltfImage->uri + i + 1, &data);
                 
                 
                 if (result == cgltf_result_success)
                 if (result == cgltf_result_success)
                 {
                 {
-                    rimage = LoadImageFromMemory(".png", data, outSize);
+                    image = LoadImageFromMemory(".png", data, outSize);
                     cgltf_free(data);
                     cgltf_free(data);
                 }
                 }
-
-                // TODO: Tint shouldn't be applied here!
-                ImageColorTint(&rimage, tint);
             }
             }
         }
         }
-        else
+        else     // Check if image is provided as image path
         {
         {
-            rimage = LoadImage(TextFormat("%s/%s", texPath, image->uri));
-
-            // TODO: Tint shouldn't be applied here!
-            ImageColorTint(&rimage, tint);
+            image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri));
         }
         }
     }
     }
-    else if (image->buffer_view)
+    else if (cgltfImage->buffer_view->buffer->data != NULL)    // Check if image is provided as data buffer
     {
     {
-        unsigned char *data = RL_MALLOC(image->buffer_view->size);
-        int n = (int)image->buffer_view->offset;
-        int stride = (int)image->buffer_view->stride ? (int)image->buffer_view->stride : 1;
+        unsigned char *data = RL_MALLOC(cgltfImage->buffer_view->size);
+        int offset = (int)cgltfImage->buffer_view->offset;
+        int stride = (int)cgltfImage->buffer_view->stride? (int)cgltfImage->buffer_view->stride : 1;
 
 
-        for (unsigned int i = 0; i < image->buffer_view->size; i++)
+        // Copy buffer data to memory for loading
+        for (unsigned int i = 0; i < cgltfImage->buffer_view->size; i++)
         {
         {
-            data[i] = ((unsigned char *)image->buffer_view->buffer->data)[n];
-            n += stride;
+            data[i] = ((unsigned char *)cgltfImage->buffer_view->buffer->data)[offset];
+            offset += stride;
         }
         }
 
 
-        rimage = LoadImageFromMemory(".png", data, (int)image->buffer_view->size);
+        // Check mime_type for image: (cgltfImage->mime_type == "image\\/png")
+        if (strcmp(cgltfImage->mime_type, "image\\/png") == 0) image = LoadImageFromMemory(".png", data, (int)cgltfImage->buffer_view->size);
+        else if (strcmp(cgltfImage->mime_type, "image\\/jpeg") == 0) image = LoadImageFromMemory(".jpg", data, (int)cgltfImage->buffer_view->size);
+        else TRACELOG(LOG_WARNING, "MODEL: glTF image data MIME type not recognized", TextFormat("%s/%s", texPath, cgltfImage->uri));
+        
         RL_FREE(data);
         RL_FREE(data);
-
-        // TODO: Tint shouldn't be applied here!
-        ImageColorTint(&rimage, tint);
     }
     }
-    else rimage = GenImageColor(1, 1, tint);
 
 
-    return rimage;
+    return image;
 }
 }
 
 
-// LoadGLTF loads in model data from given filename, supporting both .gltf and .glb
+// Load glTF file into model struct, .gltf and .glb supported
 static Model LoadGLTF(const char *fileName)
 static Model LoadGLTF(const char *fileName)
 {
 {
-    /***********************************************************************************
+    /*********************************************************************************************
 
 
         Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend)
         Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend)
+        Reviewed by Ramon Santamaria (@raysan5)
 
 
-        Features:
+        FEATURES:
           - Supports .gltf and .glb files
           - Supports .gltf and .glb files
           - Supports embedded (base64) or external textures
           - Supports embedded (base64) or external textures
-          - Loads all raylib supported material textures, values and colors
-          - Supports multiple mesh per model and multiple primitives per model
-
-        Some restrictions (not exhaustive):
-          - Triangle-only meshes
-          - Not supported node hierarchies or transforms
-          - Only supports unsigned short indices (no byte/unsigned int)
-          - Only supports float for texture coordinates (no byte/unsigned short)
-
-    *************************************************************************************/
-
-    #define LOAD_ACCESSOR(type, nbcomp, acc, dst) \
+          - Supports PBR metallic/roughness flow, loads material textures, values and colors
+                     PBR specular/glossiness flow and extended texture flows not supported
+          - Supports multiple meshes per model (every primitives is loaded as a separate mesh)
+
+        RESTRICTIONS:
+          - Only triangle meshes supported
+          - Vertex attibute types and formats supported:
+              > Vertices (position): vec3: float
+              > Normals: vec3: float
+              > Texcoords: vec2: float
+              > Colors: vec4: u8, u16, f32 (normalized)
+              > Indices: u16, u32 (truncated to u16)
+          - Node hierarchies or transforms not supported
+
+    ***********************************************************************************************/
+
+    // Macro to simplify attributes loading code
+    #define LOAD_ATTRIBUTE(accesor, numComp, dataType, dstPtr) \
     { \
     { \
         int n = 0; \
         int n = 0; \
-        type *buffer = (type *)acc->buffer_view->buffer->data + acc->buffer_view->offset/sizeof(type) + acc->offset/sizeof(type); \
-        for (unsigned int k = 0; k < acc->count; k++) \
+        dataType *buffer = (dataType *)accesor->buffer_view->buffer->data + accesor->buffer_view->offset/sizeof(dataType) + accesor->offset/sizeof(dataType); \
+        for (unsigned int k = 0; k < accesor->count; k++) \
         {\
         {\
-            for (int l = 0; l < nbcomp; l++) \
+            for (int l = 0; l < numComp; l++) \
             {\
             {\
-                dst[nbcomp*k + l] = buffer[n + l];\
+                dstPtr[numComp*k + l] = buffer[n + l];\
             }\
             }\
-            n += (int)(acc->stride/sizeof(type));\
+            n += (int)(accesor->stride/sizeof(dataType));\
         }\
         }\
     }
     }
 
 
@@ -4612,178 +4614,292 @@ static Model LoadGLTF(const char *fileName)
     cgltf_options options = { 0 };
     cgltf_options options = { 0 };
     cgltf_data *data = NULL;
     cgltf_data *data = NULL;
     cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
     cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data);
-
+    
     if (result == cgltf_result_success)
     if (result == cgltf_result_success)
     {
     {
-        TRACELOG(LOG_INFO, "MODEL: [%s] glTF meshes (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count);
-        TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->materials_count);
-
-        // Read data buffers
+        if (data->file_type == cgltf_file_type_glb) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glb) loaded successfully", fileName);
+        else if (data->file_type == cgltf_file_type_gltf) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glTF) loaded successfully", fileName);
+        else TRACELOG(LOG_WARNING, "MODEL: [%s] Model format not recognized", fileName);
+
+        TRACELOG(LOG_INFO, "    > Meshes count: %i", data->meshes_count);
+        TRACELOG(LOG_INFO, "    > Materials count: %i", data->materials_count);
+        TRACELOG(LOG_DEBUG, "    > Buffers count: %i", data->buffers_count);
+        TRACELOG(LOG_DEBUG, "    > Images count: %i", data->images_count);
+        TRACELOG(LOG_DEBUG, "    > Textures count: %i", data->textures_count);
+
+        // Force reading data buffers (fills buffer_view->buffer->data)
+        // NOTE: If an uri is defined to base64 data or external path, it's automatically loaded -> TODO: Verify this assumption
         result = cgltf_load_buffers(&options, data, fileName);
         result = cgltf_load_buffers(&options, data, fileName);
         if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName);
         if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName);
 
 
         int primitivesCount = 0;
         int primitivesCount = 0;
-
+        // NOTE: We will load every primitive in the glTF as a separate raylib mesh
         for (unsigned int i = 0; i < data->meshes_count; i++) primitivesCount += (int)data->meshes[i].primitives_count;
         for (unsigned int i = 0; i < data->meshes_count; i++) primitivesCount += (int)data->meshes[i].primitives_count;
 
 
-        // Process glTF data and map to model
+        // Load our model data: meshes and materials
         model.meshCount = primitivesCount;
         model.meshCount = primitivesCount;
         model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh));
         model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh));
-        model.materialCount = (int)data->materials_count + 1;
-        model.materials = RL_MALLOC(model.materialCount*sizeof(Material));
-        model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int));
+        for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int*)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
+
+        // NOTE: We keep an extra slot for default material, in case some mesh requires it
+        model.materialCount = (int)data->materials_count + 1;       
+        model.materials = RL_CALLOC(model.materialCount, sizeof(Material));
+        model.materials[0] = LoadMaterialDefault();     // Load default material (index: 0)
 
 
-        for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
+        // Load mesh-material indices, by default all meshes are mapped to material index: 0
+        model.meshMaterial = RL_CALLOC(model.meshCount, sizeof(int));
 
 
-        for (int i = 0; i < model.materialCount - 1; i++)
+        // Load materials data
+        for (unsigned int i = 0, j = 1; i < data->materials_count; i++, j++)
         {
         {
-            model.materials[i] = LoadMaterialDefault();
-            Color tint = (Color){ 255, 255, 255, 255 };
+            model.materials[j] = LoadMaterialDefault();
             const char *texPath = GetDirectoryPath(fileName);
             const char *texPath = GetDirectoryPath(fileName);
 
 
-            //Ensure material follows raylib support for PBR (metallic/roughness flow)
+            // Check glTF material flow: PBR metallic/roughness flow
+            // NOTE: Alternatively, materials can follow PBR specular/glossiness flow
             if (data->materials[i].has_pbr_metallic_roughness)
             if (data->materials[i].has_pbr_metallic_roughness)
             {
             {
-                tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255);
-                tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1] * 255);
-                tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2] * 255);
-                tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3] * 255);
-
-                model.materials[i].maps[MATERIAL_MAP_ALBEDO].color = tint;
-
+                // Load base color texture (albedo)
                 if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture)
                 if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture)
                 {
                 {
-                    Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint);
-                    model.materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo);
-                    UnloadImage(albedo);
-                }
+                    Image imAlbedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath);
+                    if (imAlbedo.data != NULL)
+                    {
+                        model.materials[j].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(imAlbedo);
+                        UnloadImage(imAlbedo);
+                    }
 
 
-                tint = WHITE;   // Set tint to white after it's been used by Albedo
+                    // Load base color factor (tint)
+                    model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255);
+                    model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255);
+                    model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255);
+                    model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255);
+                }
 
 
+                // Load metallic/roughness texture
                 if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture)
                 if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture)
                 {
                 {
-                    Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint);
-                    model.materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness);
-
+                    Image imMetallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath);
+                    if (imMetallicRoughness.data != NULL)
+                    {
+                        model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(imMetallicRoughness);
+                        UnloadImage(imMetallicRoughness);
+                    }
+                    
+                    // Load metallic/roughness material properties 
                     float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor;
                     float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor;
-                    model.materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness;
+                    model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].value = roughness;
 
 
                     float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
                     float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
-                    model.materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic;
-
-                    UnloadImage(metallicRoughness);
+                    model.materials[j].maps[MATERIAL_MAP_METALNESS].value = metallic;
                 }
                 }
 
 
+                // Load normal texture
                 if (data->materials[i].normal_texture.texture)
                 if (data->materials[i].normal_texture.texture)
                 {
                 {
-                    Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint);
-                    model.materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage);
-                    UnloadImage(normalImage);
+                    Image imNormal = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath);
+                    if (imNormal.data != NULL)
+                    {
+                        model.materials[j].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(imNormal);
+                        UnloadImage(imNormal);
+                    }
                 }
                 }
 
 
+                // Load ambient occlusion texture
                 if (data->materials[i].occlusion_texture.texture)
                 if (data->materials[i].occlusion_texture.texture)
                 {
                 {
-                    Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint);
-                    model.materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage);
-                    UnloadImage(occulsionImage);
+                    Image imOcclusion = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath);
+                    if (imOcclusion.data != NULL)
+                    {
+                        model.materials[j].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(imOcclusion);
+                        UnloadImage(imOcclusion);
+                    }
                 }
                 }
 
 
+                // Load emissive texture
                 if (data->materials[i].emissive_texture.texture)
                 if (data->materials[i].emissive_texture.texture)
                 {
                 {
-                    Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint);
-                    model.materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage);
-                    tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255);
-                    tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255);
-                    tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255);
-                    model.materials[i].maps[MATERIAL_MAP_EMISSION].color = tint;
-                    UnloadImage(emissiveImage);
+                    Image imEmissive = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath);
+                    if (imEmissive.data != NULL)
+                    {
+                        model.materials[j].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(imEmissive);
+                        UnloadImage(imEmissive);
+                    }
+
+                    // Load emissive color factor
+                    model.materials[j].maps[MATERIAL_MAP_EMISSION].color.r = (unsigned char)(data->materials[i].emissive_factor[0]*255);
+                    model.materials[j].maps[MATERIAL_MAP_EMISSION].color.g = (unsigned char)(data->materials[i].emissive_factor[1]*255);
+                    model.materials[j].maps[MATERIAL_MAP_EMISSION].color.b = (unsigned char)(data->materials[i].emissive_factor[2]*255);
+                    model.materials[j].maps[MATERIAL_MAP_EMISSION].color.a = 255;
                 }
                 }
             }
             }
-        }
 
 
-        model.materials[model.materialCount - 1] = LoadMaterialDefault();
-
-        int primitiveIndex = 0;
+            // Other possible materials not supported by raylib pipeline: 
+            // has_clearcoat, has_transmission, has_volume, has_ior, has specular, has_sheen
+        }
 
 
+        // Load meshes data
         for (unsigned int i = 0; i < data->meshes_count; i++)
         for (unsigned int i = 0; i < data->meshes_count; i++)
         {
         {
-            for (unsigned int p = 0; p < data->meshes[i].primitives_count; p++)
+            for (unsigned int p = 0, primitiveIndex = 0; p < data->meshes[i].primitives_count; p++)
             {
             {
+                // NOTE: We only support primitives defined by triangles
+                // Other alternatives: points, lines, line_strip, triangle_strip
+                if (data->meshes[i].primitives[p].type != cgltf_primitive_type_triangles) continue;
+
+                // NOTE: Attributes data could be provided in several data formats (8, 8u, 16u, 32...),
+                // Only some formats for each attribute type are supported, read info at the top of this function!
+
                 for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++)
                 for (unsigned int j = 0; j < data->meshes[i].primitives[p].attributes_count; j++)
                 {
                 {
-                    if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position)
+                    // Check the different attributes for every pimitive
+                    if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_position)      // POSITION
                     {
                     {
-                        cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
-                        model.meshes[primitiveIndex].vertexCount = (int)acc->count;
-                        model.meshes[primitiveIndex].vertices = RL_MALLOC(model.meshes[primitiveIndex].vertexCount*3*sizeof(float));
+                        cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
+
+                        if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3))
+                        {
+                            // Init raylib mesh vertices to copy glTF attribute data 
+                            model.meshes[primitiveIndex].vertexCount = (int)attribute->count;
+                            model.meshes[primitiveIndex].vertices = RL_MALLOC(attribute->count*3*sizeof(float));
 
 
-                        LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices)
+                            // Load 3 components of float data type into mesh.vertices
+                            LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[primitiveIndex].vertices)
+                        }
+                        else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName);
                     }
                     }
-                    else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal)
+                    else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal)   // NORMAL
                     {
                     {
-                        cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
-                        model.meshes[primitiveIndex].normals = RL_MALLOC(acc->count*3*sizeof(float));
+                        cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
+
+                        if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3))
+                        {
+                            // Init raylib mesh normals to copy glTF attribute data
+                            model.meshes[primitiveIndex].normals = RL_MALLOC(attribute->count*3*sizeof(float));
 
 
-                        LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals)
+                            // Load 3 components of float data type into mesh.normals
+                            LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[primitiveIndex].normals)
+                        }
+                        else TRACELOG(LOG_WARNING, "MODEL: [%s] Normal attribute data format not supported, use vec3 float", fileName);
                     }
                     }
-                    else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord)
+                    else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) // TEXCOORD_0
                     {
                     {
-                        cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
+                        cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
 
 
-                        if (acc->component_type == cgltf_component_type_r_32f)
+                        if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec2))
                         {
                         {
-                            model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float));
-                            LOAD_ACCESSOR(float, 2, acc, model.meshes[primitiveIndex].texcoords)
+                            // Init raylib mesh texcoords to copy glTF attribute data
+                            model.meshes[primitiveIndex].texcoords = RL_MALLOC(attribute->count*2*sizeof(float));
+
+                            // Load 3 components of float data type into mesh.texcoords
+                            LOAD_ATTRIBUTE(attribute, 2, float, model.meshes[primitiveIndex].texcoords)
                         }
                         }
-                        else
+                        else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName);
+                    }
+                    else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_color)    // COLOR_0
+                    {
+                        cgltf_accessor *attribute = data->meshes[i].primitives[p].attributes[j].data;
+
+                        if ((attribute->component_type == cgltf_component_type_r_8u) && (attribute->type == cgltf_type_vec4))
                         {
                         {
-                            // TODO: Support normalized unsigned byte/unsigned short texture coordinates
-                            TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName);
+                            // Init raylib mesh color to copy glTF attribute data
+                            model.meshes[primitiveIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char));
+
+                            // Load 4 components of unsigned char data type into mesh.colors
+                            LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[primitiveIndex].colors)
+                        }
+                        else if ((attribute->component_type == cgltf_component_type_r_16u) && (attribute->type == cgltf_type_vec4))
+                        {
+                            // Init raylib mesh color to copy glTF attribute data
+                            model.meshes[primitiveIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char));
+
+                            // Load data into a temp buffer to be converted to raylib data type
+                            unsigned short *temp = RL_MALLOC(attribute->count*4*sizeof(unsigned short));
+                            LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp);
+
+                            // Convert data to raylib color data type (4 bytes)
+                            for (int c = 0; c < attribute->count*4; c++) model.meshes[primitiveIndex].colors[c] = (unsigned char)(((float)temp[c]/65535.0f)*255.0f);
+
+                            RL_FREE(temp);
+                        }
+                        else if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec4))
+                        {
+                            // Init raylib mesh color to copy glTF attribute data
+                            model.meshes[primitiveIndex].colors = RL_MALLOC(attribute->count*4*sizeof(unsigned char));
+
+                            // Load data into a temp buffer to be converted to raylib data type
+                            float *temp = RL_MALLOC(attribute->count*4*sizeof(float));
+                            LOAD_ATTRIBUTE(attribute, 4, float, temp);
+
+                            // Convert data to raylib color data type (4 bytes), we expect the color data normalized
+                            for (int c = 0; c < attribute->count*4; c++) model.meshes[primitiveIndex].colors[c] = (unsigned char)(temp[c]*255.0f);
+
+                            RL_FREE(temp);
                         }
                         }
+                        else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName);
                     }
                     }
+
+                    // TODO: Additional attributes that could be supported (some related to animations):
+                    // cgltf_attribute_type_tangent, cgltf_attribute_type_joints, cgltf_attribute_type_weights
                 }
                 }
 
 
-                cgltf_accessor *acc = data->meshes[i].primitives[p].indices;
+                // Load primitive indices data (if provided)
+                cgltf_accessor *attribute = data->meshes[i].primitives[p].indices;
 
 
-                if (acc)
+                if (attribute != NULL)
                 {
                 {
-                    if (acc->component_type == cgltf_component_type_r_16u)
+                    model.meshes[primitiveIndex].triangleCount = (int)attribute->count/3;
+
+                    if (attribute->component_type == cgltf_component_type_r_16u)
                     {
                     {
-                        model.meshes[primitiveIndex].triangleCount = (int)acc->count/3;
-                        model.meshes[primitiveIndex].indices = RL_MALLOC(model.meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short));
-                        LOAD_ACCESSOR(unsigned short, 1, acc, model.meshes[primitiveIndex].indices)
+                        // Init raylib mesh indices to copy glTF attribute data
+                        model.meshes[primitiveIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short));
+
+                        // Load unsigned short data type into mesh.indices
+                        LOAD_ATTRIBUTE(attribute, 1, unsigned short, model.meshes[primitiveIndex].indices)
                     }
                     }
-                    else
+                    else if (attribute->component_type == cgltf_component_type_r_32u)
                     {
                     {
-                        // TODO: Support unsigned byte/unsigned int
-                        TRACELOG(LOG_WARNING, "MODEL: [%s] glTF index data must be unsigned short", fileName);
+                        // Init raylib mesh indices to copy glTF attribute data
+                        model.meshes[primitiveIndex].indices = RL_MALLOC(attribute->count*sizeof(unsigned short));
+
+                        // Load data into a temp buffer to be converted to raylib data type
+                        unsigned int *temp = RL_MALLOC(attribute->count*sizeof(unsigned int));
+                        LOAD_ATTRIBUTE(attribute, 1, unsigned int, temp);
+
+                        // Convert data to raylib indices data type (unsigned short)
+                        for (int d = 0; d < attribute->count; d++) model.meshes[primitiveIndex].indices[d] = (unsigned short)temp[d];
+
+                        TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName);
+
+                        RL_FREE(temp);
                     }
                     }
+                    else TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data format not supported, use u16", fileName);
                 }
                 }
-                else
-                {
-                    // Unindexed mesh
-                    model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3;
-                }
+                else model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3;    // Unindexed mesh
 
 
-                if (data->meshes[i].primitives[p].material)
-                {
-                    // Compute the offset
-                    model.meshMaterial[primitiveIndex] = (int)(data->meshes[i].primitives[p].material - data->materials);
-                }
-                else
+                // Assign to the primitive mesh the corresponding material index
+                // NOTE: If no material defined, mesh uses the already assigned default material (index: 0)
+                for (int m = 0; m < data->materials_count; m++)
                 {
                 {
-                    model.meshMaterial[primitiveIndex] = model.materialCount - 1;;
+                    // The primitive actually keeps the pointer to the corresponding material,
+                    // raylib instead assigns to the mesh the by its index, as loaded in model.materials array
+                    // To get the index, we check if material pointers match and we assign the corresponding index,
+                    // skipping index 0, the default material
+                    if (&data->materials[i] == data->meshes[i].primitives[p].material) model.meshMaterial[primitiveIndex] = m + 1;
                 }
                 }
 
 
-                primitiveIndex++;
+                primitiveIndex++;       // Move to next primitive
             }
             }
         }
         }
 
 
+        // Free all cgltf loaded data
         cgltf_free(data);
         cgltf_free(data);
     }
     }
     else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
     else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
 
 
-    RL_FREE(fileData);
+    // WARNING: cgltf requires the file pointer available while reading data
+    UnloadFileData(fileData);
 
 
     return model;
     return model;
 }
 }