Browse Source

Improve gltf support (#1647)

* Implement a load values from accessor function.
Added some more value types for the different GLTF attributes.
Fixed crash when loading animated triangle.

* Split GLTF model loading into separate functions for readability.

* Fixed the already working models that I broke when introducing GLTFReadValue.
Improved the example for gltf models to be able to switch between a few models.

* Removed license from screen. It is pu inside a license file anyway.

* Small improvements on the naming of functions
Removed (*model). and replaced it with model->
Hristo Stamenov 4 years ago
parent
commit
f9b79403d1

+ 36 - 6
examples/models/models_gltf_model.c

@@ -39,7 +39,18 @@ int main(void)
     camera.fovy = 45.0f;                                // Camera field-of-view Y
     camera.type = CAMERA_PERSPECTIVE;                   // Camera mode type
 
-    Model model = LoadModel("resources/gltf/Avocado.glb");               // Load the animated model mesh and
+    Model model[7];
+    
+    model[0] = LoadModel("resources/gltf/raylib_32x32.glb");
+    model[1] = LoadModel("resources/gltf/rigged_figure.glb");
+    model[2] = LoadModel("resources/gltf/Avocado.glb");
+    model[3] = LoadModel("resources/gltf/GearboxAssy.glb");
+    model[4] = LoadModel("resources/gltf/BoxAnimated.glb");
+    model[5] = LoadModel("resources/gltf/AnimatedTriangle.gltf");
+    model[6] = LoadModel("resources/gltf/AnimatedMorphCube.glb");
+    
+    int currentModel = 0;
+    int modelCount = 7;
 
     Vector3 position = { 0.0f, 0.0f, 0.0f };            // Set model position
 
@@ -54,23 +65,39 @@ int main(void)
         // Update
         //----------------------------------------------------------------------------------
         UpdateCamera(&camera);
+        
+        if(IsKeyReleased(KEY_RIGHT))
+        {
+            currentModel++;
+            if(currentModel == modelCount)
+            {
+                currentModel = 0;
+            }
+        }
+    
+        if(IsKeyReleased(KEY_LEFT))
+        {
+            currentModel--;
+            if(currentModel < 0)
+            {
+                currentModel = modelCount - 1;
+            }
+        }
 
         // Draw
         //----------------------------------------------------------------------------------
         BeginDrawing();
 
-             ClearBackground(RAYWHITE);
+            ClearBackground(SKYBLUE);
 
             BeginMode3D(camera);
 
-                DrawModelEx(model, position, (Vector3){ 0.0f, 1.0f, 0.0f }, 180.0f, (Vector3){ 15.0f, 15.0f, 15.0f }, WHITE);
+                DrawModelEx(model[currentModel], position, (Vector3){ 0.0f, 1.0f, 0.0f }, 180.0f, (Vector3){ 2.0f, 2.0f, 2.0f }, WHITE);
 
                 DrawGrid(10, 1.0f);         // Draw a grid
 
             EndMode3D();
 
-            DrawText("(cc0) Avocado by @Microsoft", screenWidth - 200, screenHeight - 20, 10, GRAY);
-
         EndDrawing();
         //----------------------------------------------------------------------------------
     }
@@ -78,7 +105,10 @@ int main(void)
     // De-Initialization
     //--------------------------------------------------------------------------------------
 
-    UnloadModel(model);         // Unload model
+    for(int i = 0; i < modelCount; i++)
+    {
+        UnloadModel(model[i]);         // Unload model
+    }
 
     CloseWindow();              // Close window and OpenGL context
     //--------------------------------------------------------------------------------------

BIN
examples/models/resources/gltf/Textures/raylib_32x32.png


BIN
examples/models/resources/gltf/raylib_32x32.glb


+ 432 - 301
src/models.c

@@ -117,6 +117,11 @@ static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCou
 #if defined(SUPPORT_FILEFORMAT_GLTF)
 static Model LoadGLTF(const char *fileName);    // Load GLTF mesh data
 static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount);    // Load GLTF animation data
+static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex);
+static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex);
+static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex);
+static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data);
+static void InitGLTFBones(Model* model, const cgltf_data* data);
 #endif
 
 //----------------------------------------------------------------------------------
@@ -1087,13 +1092,16 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame)
 
                 // 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;
+                if(model.meshes[m].normals != NULL)
+                {
+                    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;
             }
 
@@ -3650,12 +3658,37 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co
     return rimage;
 }
 
+
+static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void* variable, unsigned int elements, unsigned int size)
+{
+    if (acc->count == 2)
+    {
+        if (index > 1)
+        {
+            return false;
+        }
+        
+        memcpy(variable, index == 0 ? acc->min : acc->max, elements * size);
+        return true;
+    }
+    
+    unsigned int stride = size * elements;
+    memset(variable, 0, stride);
+    
+    if(acc->buffer_view == NULL || acc->buffer_view->buffer == NULL || acc->buffer_view->buffer->data == NULL)
+        return false;
+    
+    void* readPosition = ((char*)acc->buffer_view->buffer->data) + (index * stride) + acc->buffer_view->offset + acc->offset;
+    memcpy(variable, readPosition, stride);
+    return true;
+}
+
 // LoadGLTF loads in model data from given filename, supporting both .gltf and .glb
 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) and Hristo Stamenov(@object71)
 
         Features:
           - Supports .gltf and .glb files
@@ -3671,18 +3704,6 @@ static Model LoadGLTF(const char *fileName)
 
     *************************************************************************************/
 
-    #define LOAD_ACCESSOR(type, nbcomp, acc, dst) \
-    { \
-        int n = 0; \
-        type* buf = (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++) {\
-            for (int l = 0; l < nbcomp; l++) {\
-                dst[nbcomp*k + l] = buf[n + l];\
-            }\
-            n += (int)(acc->stride/sizeof(type));\
-        }\
-    }
-
     Model model = { 0 };
 
     // glTF file loading
@@ -3719,131 +3740,10 @@ static Model LoadGLTF(const char *fileName)
         model.boneCount = (int)data->nodes_count;
         model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo));
         model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform));
-
-        for (unsigned int j = 0; j < data->nodes_count; j++)
-        {
-            strcpy(model.bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name);
-            model.bones[j].parent = (data->nodes[j].parent != NULL) ? data->nodes[j].parent - data->nodes : -1;
-        }
-        
-        for (unsigned int i = 0; i < data->nodes_count; i++)
-        {
-            if (data->nodes[i].has_translation) memcpy(&model.bindPose[i].translation, data->nodes[i].translation, 3 * sizeof(float));
-            else model.bindPose[i].translation = Vector3Zero();
-
-            if (data->nodes[i].has_rotation) memcpy(&model.bindPose[i].rotation, data->nodes[i].rotation, 4 * sizeof(float));
-            else model.bindPose[i].rotation = QuaternionIdentity();
-
-            model.bindPose[i].rotation = QuaternionNormalize(model.bindPose[i].rotation);
-            
-            if (data->nodes[i].has_scale) memcpy(&model.bindPose[i].scale, data->nodes[i].scale, 3 * sizeof(float));
-            else model.bindPose[i].scale = Vector3One();
-        }
-
-        {
-            bool* completedBones = RL_CALLOC(model.boneCount, sizeof(bool));
-            int numberCompletedBones = 0;
-
-            while (numberCompletedBones < model.boneCount) {
-                for (int i = 0; i < model.boneCount; i++)
-                {
-                    if (completedBones[i]) continue;
-
-                    if (model.bones[i].parent < 0) {
-                        completedBones[i] = true;
-                        numberCompletedBones++;
-                        continue;
-                    }
-
-                    if (!completedBones[model.bones[i].parent]) continue;
-
-                    Transform* currentTransform = &model.bindPose[i];
-                    BoneInfo* currentBone = &model.bones[i];
-                    int root = currentBone->parent;
-                    if (root >= model.boneCount)
-                        root = 0;
-                    Transform* parentTransform = &model.bindPose[root];
-
-                    currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation);
-                    currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation);
-                    currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation);
-                    currentTransform->scale = Vector3Multiply(parentTransform->scale, parentTransform->scale);
-                    completedBones[i] = true;
-                    numberCompletedBones++;
-                }
-            }
-
-            RL_FREE(completedBones);
-        }
-        
-        for (int i = 0; i < model.materialCount - 1; i++)
-        {
-            model.materials[i] = LoadMaterialDefault();
-            Color tint = (Color){ 255, 255, 255, 255 };
-            const char *texPath = GetDirectoryPath(fileName);
-
-            // Ensure material follows raylib support for PBR (metallic/roughness flow)
-            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;
-
-                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);
-                }
-
-                tint = WHITE;   // Set tint to white after it's been used by Albedo
-
-                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);
-
-                    float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor;
-                    model.materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness;
-
-                    float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
-                    model.materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic;
-
-                    UnloadImage(metallicRoughness);
-                }
-
-                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);
-                }
-
-                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);
-                }
-
-                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);
-                }
-            }
-        }
-
-        model.materials[model.materialCount - 1] = LoadMaterialDefault();
-
+    
+        InitGLTFBones(&model, data);
+        LoadGLTFMaterial(&model, fileName, data);
+    
         int primitiveIndex = 0;
 
         for (unsigned int i = 0; i < data->meshes_count; i++)
@@ -3859,18 +3759,65 @@ static Model LoadGLTF(const char *fileName)
                         int bufferSize = model.meshes[primitiveIndex].vertexCount * 3 * sizeof(float);
                         model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize);
                         model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize);
-
-                        LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices);
+    
+                        if(acc->component_type == cgltf_component_type_r_32f)
+                        {
+                            for(int a = 0; a < acc->count; a++)
+                            {
+                                GLTFReadValue(acc, a, model.meshes[primitiveIndex].vertices + (a * 3), 3, sizeof(float));
+                            }
+                        }
+                        else if (acc->component_type == cgltf_component_type_r_32u)
+                        {
+                            int readValue[3];
+                            for(int a = 0; a < acc->count; a++)
+                            {
+                                GLTFReadValue(acc, a, readValue, 3, sizeof(int));
+                                model.meshes[primitiveIndex].vertices[(a * 3) + 0] = readValue[0];
+                                model.meshes[primitiveIndex].vertices[(a * 3) + 1] = readValue[1];
+                                model.meshes[primitiveIndex].vertices[(a * 3) + 2] = readValue[2];
+                            }
+                        }
+                        else
+                        {
+                            // TODO: Support normalized unsigned byte/unsigned short vertices
+                            TRACELOG(LOG_WARNING, "MODEL: [%s] glTF vertices must be float or int", fileName);
+                        }
+                        
                         memcpy(model.meshes[primitiveIndex].animVertices, model.meshes[primitiveIndex].vertices, bufferSize);
                     }
                     else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal)
                     {
                         cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
+                        
                         int bufferSize = (int)(acc->count*3*sizeof(float));
                         model.meshes[primitiveIndex].normals = RL_MALLOC(bufferSize);
                         model.meshes[primitiveIndex].animNormals = RL_MALLOC(bufferSize);
-
-                        LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals);
+                        
+                        if(acc->component_type == cgltf_component_type_r_32f)
+                        {
+                            for(int a = 0; a < acc->count; a++)
+                            {
+                                GLTFReadValue(acc, a, model.meshes[primitiveIndex].normals + (a * 3), 3, sizeof(float));
+                            }
+                        }
+                        else if (acc->component_type == cgltf_component_type_r_32u)
+                        {
+                            int readValue[3];
+                            for(int a = 0; a < acc->count; a++)
+                            {
+                                GLTFReadValue(acc, a, readValue, 3, sizeof(int));
+                                model.meshes[primitiveIndex].normals[(a * 3) + 0] = readValue[0];
+                                model.meshes[primitiveIndex].normals[(a * 3) + 1] = readValue[1];
+                                model.meshes[primitiveIndex].normals[(a * 3) + 2] = readValue[2];
+                            }
+                        }
+                        else
+                        {
+                            // TODO: Support normalized unsigned byte/unsigned short normals
+                            TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName);
+                        }
+                        
                         memcpy(model.meshes[primitiveIndex].animNormals, model.meshes[primitiveIndex].normals, bufferSize);
                     }
                     else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord)
@@ -3880,7 +3827,11 @@ static Model LoadGLTF(const char *fileName)
                         if (acc->component_type == cgltf_component_type_r_32f)
                         {
                             model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float));
-                            LOAD_ACCESSOR(float, 2, acc, model.meshes[primitiveIndex].texcoords)
+                            
+                            for(int a = 0; a < acc->count; a++)
+                            {
+                                GLTFReadValue(acc, a, model.meshes[primitiveIndex].texcoords + (a * 2), 2, sizeof(float));
+                            }
                         }
                         else
                         {
@@ -3889,89 +3840,46 @@ static Model LoadGLTF(const char *fileName)
                         }
                     }
                     else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints)
+                    {
+                        cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
+                        LoadGLTFBoneAttribute(&model, acc, data, primitiveIndex);
+                    }
+                    else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights)
                     {
                         cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
     
-                        if (acc->component_type == cgltf_component_type_r_16u)
+                        model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float));
+    
+                        if(acc->component_type == cgltf_component_type_r_32f)
                         {
-                            model.meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int) * acc->count * 4);
-                            short* bones = RL_MALLOC(sizeof(short) * acc->count * 4);
-                            
-                            LOAD_ACCESSOR(short, 4, acc, bones);
-                            for (unsigned int a = 0; a < acc->count * 4; a++)
+                            for(int a = 0; a < acc->count; a++)
                             {
-                                cgltf_node* skinJoint = data->skins->joints[bones[a]];
-
-                                for (unsigned int k = 0; k < data->nodes_count; k++)
-                                {
-                                    if (&(data->nodes[k]) == skinJoint)
-                                    {
-                                        model.meshes[primitiveIndex].boneIds[a] = k;
-                                        break;
-                                    }
-                                }
+                                GLTFReadValue(acc, a, model.meshes[primitiveIndex].boneWeights + (a * 4), 4, sizeof(float));
                             }
-                            RL_FREE(bones);
                         }
-                        else if (acc->component_type == cgltf_component_type_r_8u)
+                        else if (acc->component_type == cgltf_component_type_r_32u)
                         {
-                            model.meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int) * acc->count * 4);
-                            unsigned char* bones = RL_MALLOC(sizeof(unsigned char) * acc->count * 4);
-
-                            LOAD_ACCESSOR(unsigned char, 4, acc, bones);
-                            for (unsigned int a = 0; a < acc->count * 4; a++)
+                            unsigned int readValue[4];
+                            for(int a = 0; a < acc->count; a++)
                             {
-                                cgltf_node* skinJoint = data->skins->joints[bones[a]];
-                                
-                                for (unsigned int k = 0; k < data->nodes_count; k++)
-                                {
-                                    if (&(data->nodes[k]) == skinJoint)
-                                    {
-                                        model.meshes[primitiveIndex].boneIds[a] = k;
-                                        break;
-                                    }
-                                }
+                                GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned int));
+                                model.meshes[primitiveIndex].normals[(a * 4) + 0] = readValue[0];
+                                model.meshes[primitiveIndex].normals[(a * 4) + 1] = readValue[1];
+                                model.meshes[primitiveIndex].normals[(a * 4) + 2] = readValue[2];
+                                model.meshes[primitiveIndex].normals[(a * 4) + 3] = readValue[3];
                             }
-                            RL_FREE(bones);
                         }
                         else
                         {
-                            // TODO: Support other size of bone index?
-                            TRACELOG(LOG_WARNING, "MODEL: [%s] glTF bones in unexpected format", fileName);
+                            // TODO: Support normalized unsigned byte/unsigned short weights
+                            TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName);
                         }
-                    
-                    }
-                    else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights)
-                    {
-                        cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data;
-    
-                        model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float));
-                        LOAD_ACCESSOR(float, 4, acc, model.meshes[primitiveIndex].boneWeights)
                     }
                 }
 
                 cgltf_accessor *acc = data->meshes[i].primitives[p].indices;
-
-                if (acc)
-                {
-                    if (acc->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)
-                    }
-                    else
-                    {
-                        // TODO: Support unsigned byte/unsigned int
-                        TRACELOG(LOG_WARNING, "MODEL: [%s] glTF index data must be unsigned short", fileName);
-                    }
-                }
-                else
-                {
-                    // Unindexed mesh
-                    model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3;
-                }
-
+                LoadGLTFModelIndices(&model, acc, primitiveIndex);
+    
                 if (data->meshes[i].primitives[p].material)
                 {
                     // Compute the offset
@@ -3981,79 +3889,14 @@ static Model LoadGLTF(const char *fileName)
                 {
                     model.meshMaterial[primitiveIndex] = model.materialCount - 1;
                 }
-                
-//                if (data->meshes[i].)
-
-                if (model.meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0)
-                {
-                    for (int nodeId = 0; nodeId < data->nodes_count; nodeId++)
-                    {
-                        if (data->nodes[nodeId].mesh == &(data->meshes[i]))
-                        {
-                            model.meshes[primitiveIndex].boneIds = RL_CALLOC(4 * model.meshes[primitiveIndex].vertexCount, sizeof(int));
-                            model.meshes[primitiveIndex].boneWeights = RL_CALLOC(4 * model.meshes[primitiveIndex].vertexCount, sizeof(float));
-
-                        	for (int b = 0; b < 4 * model.meshes[primitiveIndex].vertexCount; b++)
-                            {
-                        		if(b % 4 == 0)
-                        		{
-                                    model.meshes[primitiveIndex].boneIds[b] = nodeId;
-                                    model.meshes[primitiveIndex].boneWeights[b] = 1.0f;
-                        		}
-                        		else
-                        		{
-                                    model.meshes[primitiveIndex].boneIds[b] = 0;
-                                    model.meshes[primitiveIndex].boneWeights[b] = 0.0f;
-                        		}
-                            
-                            }
     
-                            Vector3 boundVertex = { 0 };
-                            Vector3 boundNormal = { 0 };
+                BindGLTFPrimitiveToBones(&model, data, primitiveIndex);
     
-                            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[primitiveIndex].vertexCount; i++)
-                            {
-                                boneId = model.meshes[primitiveIndex].boneIds[boneCounter];
-                                outTranslation = model.bindPose[boneId].translation;
-                                outRotation = model.bindPose[boneId].rotation;
-                                outScale = model.bindPose[boneId].scale;
-        
-                                // Vertices processing
-                                boundVertex = (Vector3){ model.meshes[primitiveIndex].vertices[vCounter], model.meshes[primitiveIndex].vertices[vCounter + 1], model.meshes[primitiveIndex].vertices[vCounter + 2] };
-                                boundVertex = Vector3Multiply(boundVertex, outScale);
-                                boundVertex = Vector3RotateByQuaternion(boundVertex, outRotation);
-                                boundVertex = Vector3Add(boundVertex, outTranslation);
-                                model.meshes[primitiveIndex].vertices[vCounter] = boundVertex.x;
-                                model.meshes[primitiveIndex].vertices[vCounter + 1] = boundVertex.y;
-                                model.meshes[primitiveIndex].vertices[vCounter + 2] = boundVertex.z;
-        
-                                // Normals processing
-                                boundNormal = (Vector3){ model.meshes[primitiveIndex].normals[vCounter], model.meshes[primitiveIndex].normals[vCounter + 1], model.meshes[primitiveIndex].normals[vCounter + 2] };
-                                boundNormal = Vector3RotateByQuaternion(boundNormal, outRotation);
-                                model.meshes[primitiveIndex].normals[vCounter] = boundNormal.x;
-                                model.meshes[primitiveIndex].normals[vCounter + 1] = boundNormal.y;
-                                model.meshes[primitiveIndex].normals[vCounter + 2] = boundNormal.z;
-                                vCounter += 3;
-        
-                                boneCounter += 4;
-                            }
-                        }
-                    }
-                }
-                
                 primitiveIndex++;
             }
-    
+            
         }
-
+        
         cgltf_free(data);
     }
     else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName);
@@ -4063,24 +3906,312 @@ static Model LoadGLTF(const char *fileName)
     return model;
 }
 
-static bool GltfReadFloat(cgltf_accessor* acc, unsigned int index, float* variable, unsigned int elements)
+static void InitGLTFBones(Model* model, const cgltf_data* data)
 {
-    if (acc->count == 2)
+    for (unsigned int j = 0; j < data->nodes_count; j++)
     {
-        if (index > 1)
+        strcpy(model->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name);
+        model->bones[j].parent = (data->nodes[j].parent != NULL) ? data->nodes[j].parent - data->nodes : -1;
+    }
+    
+    for (unsigned int i = 0; i < data->nodes_count; i++)
+    {
+        if (data->nodes[i].has_translation) memcpy(&model->bindPose[i].translation, data->nodes[i].translation, 3 * sizeof(float));
+        else model->bindPose[i].translation = Vector3Zero();
+
+        if (data->nodes[i].has_rotation) memcpy(&model->bindPose[i].rotation, data->nodes[i].rotation, 4 * sizeof(float));
+        else model->bindPose[i].rotation = QuaternionIdentity();
+
+        model->bindPose[i].rotation = QuaternionNormalize(model->bindPose[i].rotation);
+        
+        if (data->nodes[i].has_scale) memcpy(&model->bindPose[i].scale, data->nodes[i].scale, 3 * sizeof(float));
+        else model->bindPose[i].scale = Vector3One();
+    }
+    
+    {
+        bool* completedBones = RL_CALLOC(model->boneCount, sizeof(bool));
+        int numberCompletedBones = 0;
+
+        while (numberCompletedBones < model->boneCount) {
+            for (int i = 0; i < model->boneCount; i++)
+            {
+                if (completedBones[i]) continue;
+
+                if (model->bones[i].parent < 0) {
+                    completedBones[i] = true;
+                    numberCompletedBones++;
+                    continue;
+                }
+
+                if (!completedBones[model->bones[i].parent]) continue;
+
+                Transform* currentTransform = &model->bindPose[i];
+                BoneInfo* currentBone = &model->bones[i];
+                int root = currentBone->parent;
+                if (root >= model->boneCount)
+                    root = 0;
+                Transform* parentTransform = &model->bindPose[root];
+
+                currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation);
+                currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation);
+                currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation);
+                currentTransform->scale = Vector3Multiply(parentTransform->scale, parentTransform->scale);
+                completedBones[i] = true;
+                numberCompletedBones++;
+            }
+        }
+
+        RL_FREE(completedBones);
+    }
+}
+
+static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data)
+{
+    for (int i = 0; i < model->materialCount - 1; i++)
+    {
+        model->materials[i] = LoadMaterialDefault();
+        Color tint = (Color){ 255, 255, 255, 255 };
+        const char *texPath = GetDirectoryPath(fileName);
+
+        // Ensure material follows raylib support for PBR (metallic/roughness flow)
+        if (data->materials[i].has_pbr_metallic_roughness)
         {
-            return false;
+            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;
+
+            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);
+            }
+
+            tint = WHITE;   // Set tint to white after it's been used by Albedo
+
+            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);
+
+                float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor;
+                model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness;
+
+                float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor;
+                model->materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic;
+
+                UnloadImage(metallicRoughness);
+            }
+
+            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);
+            }
+
+            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);
+            }
+
+            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);
+            }
+        }
+    }
+    
+    model->materials[model->materialCount - 1] = LoadMaterialDefault();
+}
+
+static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex)
+{
+    if (jointsAccessor->component_type == cgltf_component_type_r_16u)
+    {
+        model->meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int) * jointsAccessor->count * 4);
+        short* bones = RL_MALLOC(sizeof(short) * jointsAccessor->count * 4);
+
+        for(int a = 0; a < jointsAccessor->count; a++)
+        {
+            GLTFReadValue(jointsAccessor, a, bones + (a * 4), 4, sizeof(short));
         }
         
-        memcpy(variable, index == 0 ? acc->min : acc->max, elements * sizeof(float));
-        return true;
+        for (unsigned int a = 0; a < jointsAccessor->count * 4; a++)
+        {
+            cgltf_node* skinJoint = data->skins->joints[bones[a]];
+
+            for (unsigned int k = 0; k < data->nodes_count; k++)
+            {
+                if (&(data->nodes[k]) == skinJoint)
+                {
+                    model->meshes[primitiveIndex].boneIds[a] = k;
+                    break;
+                }
+            }
+        }
+        RL_FREE(bones);
     }
-    else if (cgltf_accessor_read_float(acc, index, variable, elements))
+    else if (jointsAccessor->component_type == cgltf_component_type_r_8u)
     {
-        return true;
+        model->meshes[primitiveIndex].boneIds = RL_MALLOC(sizeof(int) * jointsAccessor->count * 4);
+        unsigned char* bones = RL_MALLOC(sizeof(unsigned char) * jointsAccessor->count * 4);
+
+        for(int a = 0; a < jointsAccessor->count; a++)
+        {
+            GLTFReadValue(jointsAccessor, a, bones + (a * 4), 4, sizeof(unsigned char));
+        }
+        
+        for (unsigned int a = 0; a < jointsAccessor->count * 4; a++)
+        {
+            cgltf_node* skinJoint = data->skins->joints[bones[a]];
+            
+            for (unsigned int k = 0; k < data->nodes_count; k++)
+            {
+                if (&(data->nodes[k]) == skinJoint)
+                {
+                    model->meshes[primitiveIndex].boneIds[a] = k;
+                    break;
+                }
+            }
+        }
+        RL_FREE(bones);
+    }
+    else
+    {
+        // TODO: Support other size of bone index?
+        TRACELOG(LOG_WARNING, "MODEL: glTF bones in unexpected format");
+    }
+}
+
+static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex)
+{
+    if (model->meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0)
+    {
+        for (int nodeId = 0; nodeId < data->nodes_count; nodeId++)
+        {
+            if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex]))
+            {
+                model->meshes[primitiveIndex].boneIds = RL_CALLOC(4 * model->meshes[primitiveIndex].vertexCount, sizeof(int));
+                model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4 * model->meshes[primitiveIndex].vertexCount, sizeof(float));
+
+                for (int b = 0; b < 4 * model->meshes[primitiveIndex].vertexCount; b++)
+                {
+                    if(b % 4 == 0)
+                    {
+                        model->meshes[primitiveIndex].boneIds[b] = nodeId;
+                        model->meshes[primitiveIndex].boneWeights[b] = 1.0f;
+                    }
+                    else
+                    {
+                        model->meshes[primitiveIndex].boneIds[b] = 0;
+                        model->meshes[primitiveIndex].boneWeights[b] = 0.0f;
+                    }
+                
+                }
+
+                Vector3 boundVertex = { 0 };
+                Vector3 boundNormal = { 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[primitiveIndex].vertexCount; i++)
+                {
+                    boneId = model->meshes[primitiveIndex].boneIds[boneCounter];
+                    outTranslation = model->bindPose[boneId].translation;
+                    outRotation = model->bindPose[boneId].rotation;
+                    outScale = model->bindPose[boneId].scale;
+
+                    // Vertices processing
+                    boundVertex = (Vector3){ model->meshes[primitiveIndex].vertices[vCounter], model->meshes[primitiveIndex].vertices[vCounter + 1], model->meshes[primitiveIndex].vertices[vCounter + 2] };
+                    boundVertex = Vector3Multiply(boundVertex, outScale);
+                    boundVertex = Vector3RotateByQuaternion(boundVertex, outRotation);
+                    boundVertex = Vector3Add(boundVertex, outTranslation);
+                    model->meshes[primitiveIndex].vertices[vCounter] = boundVertex.x;
+                    model->meshes[primitiveIndex].vertices[vCounter + 1] = boundVertex.y;
+                    model->meshes[primitiveIndex].vertices[vCounter + 2] = boundVertex.z;
+
+                    // Normals processing
+                    if(model->meshes[primitiveIndex].normals != NULL)
+                    {
+                        boundNormal = (Vector3){ model->meshes[primitiveIndex].normals[vCounter], model->meshes[primitiveIndex].normals[vCounter + 1], model->meshes[primitiveIndex].normals[vCounter + 2] };
+                        boundNormal = Vector3RotateByQuaternion(boundNormal, outRotation);
+                        model->meshes[primitiveIndex].normals[vCounter] = boundNormal.x;
+                        model->meshes[primitiveIndex].normals[vCounter + 1] = boundNormal.y;
+                        model->meshes[primitiveIndex].normals[vCounter + 2] = boundNormal.z;
+                    }
+                    
+                    vCounter += 3;
+                    boneCounter += 4;
+                }
+            }
+        }
+    }
+}
+
+static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex)
+{
+    if (indexAccessor)
+    {
+        if (indexAccessor->component_type == cgltf_component_type_r_16u || indexAccessor->component_type == cgltf_component_type_r_16)
+        {
+            model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count / 3;
+            model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount * 3 * sizeof(unsigned short));
+            
+            unsigned short readValue = 0;
+            for(int a = 0; a < indexAccessor->count; a++)
+            {
+                GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(short));
+                model->meshes[primitiveIndex].indices[a] = readValue;
+            }
+        }
+        else if (indexAccessor->component_type == cgltf_component_type_r_8u || indexAccessor->component_type == cgltf_component_type_r_8)
+        {
+            model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count / 3;
+            model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount * 3 * sizeof(unsigned short));
+            
+            unsigned char readValue = 0;
+            for(int a = 0; a < indexAccessor->count; a++)
+            {
+                GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(char));
+                model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue;
+            }
+        }
+        else if (indexAccessor->component_type == cgltf_component_type_r_32u)
+        {
+            model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count / 3;
+            model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount * 3 * sizeof(unsigned short));
+
+            unsigned int readValue;
+            for(int a = 0; a < indexAccessor->count; a++)
+            {
+                GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(unsigned int));
+                model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue;
+            }
+        }
+    }
+    else
+    {
+        // Unindexed mesh
+        model->meshes[primitiveIndex].triangleCount = model->meshes[primitiveIndex].vertexCount / 3;
     }
-    
-    return false;
 }
 
 // LoadGLTF loads in animation data from given filename
@@ -4148,7 +4279,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo
                 int frameCounts = (int)channel->sampler->input->count;
                 float lastFrameTime = 0.0f;
                 
-                if (GltfReadFloat(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1))
+                if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1, sizeof(float)))
                 {
                     animationDuration = fmaxf(lastFrameTime, animationDuration);
                 }
@@ -4204,7 +4335,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo
                     for (unsigned int j = 0; j < sampler->input->count; j++)
                     {
                         float inputFrameTime;
-                        if (GltfReadFloat(sampler->input, j, (float *)&inputFrameTime, 1))
+                        if (GLTFReadValue(sampler->input, j, &inputFrameTime, 1, sizeof(float)))
                         {
                             if (frameTime < inputFrameTime)
                             {
@@ -4213,7 +4344,7 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo
                                 outputMax = j;
         
                                 float previousInputTime = 0.0f;
-                                if (GltfReadFloat(sampler->input, outputMin, (float *)&previousInputTime, 1))
+                                if (GLTFReadValue(sampler->input, outputMin, &previousInputTime, 1, sizeof(float)))
                                 {
                                     if((inputFrameTime - previousInputTime) != 0)
                                     {
@@ -4235,8 +4366,8 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo
                         Vector3 translationStart;
                         Vector3 translationEnd;
                         
-                        bool success = GltfReadFloat(sampler->output, outputMin, (float *)&translationStart, 3);
-                        success = GltfReadFloat(sampler->output, outputMax, (float *)&translationEnd, 3) || success;
+                        bool success = GLTFReadValue(sampler->output, outputMin, &translationStart, 3, sizeof(float));
+                        success = GLTFReadValue(sampler->output, outputMax, &translationEnd, 3, sizeof(float)) || success;
                         
                         if (success) output->framePoses[frame][boneId].translation = Vector3Lerp(translationStart, translationEnd, lerpPercent);
                     }
@@ -4245,8 +4376,8 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo
                         Quaternion rotationStart;
                         Quaternion rotationEnd;
     
-                        bool success = GltfReadFloat(sampler->output, outputMin, (float *)&rotationStart, 4);
-                        success = GltfReadFloat(sampler->output, outputMax, (float *)&rotationEnd, 4) || success;
+                        bool success = GLTFReadValue(sampler->output, outputMin, &rotationStart, 4, sizeof(float));
+                        success = GLTFReadValue(sampler->output, outputMax, &rotationEnd, 4, sizeof(float)) || success;
     
                         if (success)
                         {
@@ -4259,8 +4390,8 @@ static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCo
                         Vector3 scaleStart;
                         Vector3 scaleEnd;
     
-                        bool success = GltfReadFloat(sampler->output, outputMin, (float *)&scaleStart, 3);
-                        success = GltfReadFloat(sampler->output, outputMax, (float *)&scaleEnd, 3) || success;
+                        bool success = GLTFReadValue(sampler->output, outputMin, &scaleStart, 3, sizeof(float));
+                        success = GLTFReadValue(sampler->output, outputMax, &scaleEnd, 3, sizeof(float)) || success;
     
                         if (success) output->framePoses[frame][boneId].scale = Vector3Lerp(scaleStart, scaleEnd, lerpPercent);
                     }