Browse Source

Merge pull request #842 from wbrbr/gltf2

Load glTF
Ray 6 years ago
parent
commit
9a52c716cc
1 changed files with 287 additions and 21 deletions
  1. 287 21
      src/models.c

+ 287 - 21
src/models.c

@@ -60,6 +60,7 @@
 #if defined(SUPPORT_FILEFORMAT_GLTF)
     #define CGLTF_IMPLEMENTATION
     #include "external/cgltf.h"         // glTF file format loading
+    #include "external/stb_image.h"
 #endif
 
 #if defined(SUPPORT_MESH_GENERATION)
@@ -637,7 +638,7 @@ Model LoadModel(const char *fileName)
     if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName);
 #endif
 #if defined(SUPPORT_FILEFORMAT_GLTF)
-    if (IsFileExtension(fileName, ".gltf")) model = LoadGLTF(fileName);
+    if (IsFileExtension(fileName, ".gltf") || IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName);
 #endif
 #if defined(SUPPORT_FILEFORMAT_IQM)
     if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName);
@@ -3241,6 +3242,108 @@ static Model LoadIQM(const char *fileName)
 #endif
 
 #if defined(SUPPORT_FILEFORMAT_GLTF)
+
+const unsigned char base64_table[] = {
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 62, 0, 0, 0, 63, 52, 53,
+    54, 55, 56, 57, 58, 59, 60, 61, 0, 0,
+    0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
+    5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+    25, 0, 0, 0, 0, 0, 0, 26, 27, 28,
+    29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+    39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+    49, 50, 51
+};
+
+int GetSizeBase64(char* input)
+{
+    int size = 0;
+    for (int i = 0; input[4*i] != 0; i++)
+    {
+        if (input[4*i+3] == '=')
+        {
+            if (input[4*i+2] == '=')
+            {
+                size += 1;
+            }
+            else
+            {
+                size += 2;
+            }
+        }
+        else size += 3;
+    }
+    return size;
+}
+
+unsigned char* DecodeBase64(char* input, int* size)
+{
+    *size = 0;
+    for (int i = 0; input[4*i] != 0; i++)
+    {
+        if (input[4*i+3] == '=')
+        {
+            if (input[4*i+2] == '=')
+            {
+                *size += 1;
+            }
+            else
+            {
+                *size += 2;
+            }
+        }
+        else *size += 3;
+    }
+
+    unsigned char* buf = (unsigned char*)RL_MALLOC(*size);
+    for (int i = 0; i < *size/3; i++)
+    {
+        unsigned char a = base64_table[(int)input[4*i]];
+        unsigned char b = base64_table[(int)input[4*i+1]];
+        unsigned char c = base64_table[(int)input[4*i+2]];
+        unsigned char d = base64_table[(int)input[4*i+3]];
+
+        buf[3*i] = (a << 2) | (b >> 4);
+        buf[3*i+1] = (b << 4) | (c >> 2);
+        buf[3*i+2] = (c << 6) | d;
+    }
+
+    if (*size % 3 == 1)
+    {
+        int n = *size/3;
+        unsigned char a = base64_table[(int)input[4*n]];
+        unsigned char b = base64_table[(int)input[4*n+1]];
+        buf[*size-1] = (a << 2) | (b >> 4);
+    }
+    else if (*size % 3 == 2)
+    {
+        int n = *size/3 ;
+        unsigned char a = base64_table[(int)input[4*n]];
+        unsigned char b = base64_table[(int)input[4*n+1]];
+        unsigned char c = base64_table[(int)input[4*n+2]];
+        buf[*size-2] = (a << 2) | (b >> 4);
+        buf[*size-1] = (b << 4) | (c >> 2);
+    }
+    return buf;
+}
+
+#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 (int k = 0; k < acc->count; k++) {\
+        for (int l = 0; l < nbcomp; l++) {\
+            dst[nbcomp*k+l] = buf[n+l];\
+        }\
+        n += acc->stride/sizeof(type);\
+    }\
+}
+
+
 // Load glTF mesh data
 static Model LoadGLTF(const char *fileName)
 {
@@ -3266,48 +3369,211 @@ static Model LoadGLTF(const char *fileName)
 
     // glTF data loading
     cgltf_options options = { 0 };
-    cgltf_data *data;
+    cgltf_data *data = NULL;
     cgltf_result result = cgltf_parse(&options, buffer, size, &data);
 
-    RL_FREE(buffer);
-
     if (result == cgltf_result_success)
     {
-        TraceLog(LOG_INFO, "[%s][%s] Model meshes/materials: %i/%i", (data->file_type == 2)? "glb" : "gltf", data->meshes_count, data->materials_count);
+
+        TraceLog(LOG_INFO, "[%s][%s] Model meshes/materials: %i/%i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count, data->materials_count);
 
         // Read data buffers
         result = cgltf_load_buffers(&options, data, fileName);
 
+        int nb_primitives = 0;
+        for (int i = 0; i < data->meshes_count; i++)
+        {
+            nb_primitives += (int)data->meshes[i].primitives_count;
+        }
+
         // Process glTF data and map to model
-        model.meshCount = data->meshes_count;
-        model.meshes = RL_MALLOC(model.meshCount*sizeof(Mesh));
+        model.meshCount = nb_primitives;
+        model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh));
+        model.materialCount = 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.materialCount - 1; i++)
+        {
+
+            Texture2D texture;
+            const char* dir_path = GetDirectoryPath(fileName);
+            Color tint;
+            if (data->materials[i].pbr_metallic_roughness.base_color_factor)
+            {
+                tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255.99f);
+                tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1] * 255.99f);
+                tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2] * 255.99f);
+                tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3] * 255.99f);
+            }
+            else
+            {
+                tint.r = 1.f;
+                tint.g = 1.f;
+                tint.b = 1.f;
+                tint.a = 1.f;
+            }
+            if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture)
+            {
+
+                cgltf_image* img = data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image;
+                if (img->uri) {
+                    if (strlen(img->uri) > 5 && img->uri[0] == 'd' 
+                            && img->uri[1] == 'a'
+                            && img->uri[2] == 't'
+                            && img->uri[3] == 'a'
+                            && img->uri[4] == ':')
+                    {
+                        // data URI
+                        // format: data:<mediatype>;base64,<data>
+
+                        // find the comma
+                        int i = 0;
+                        while (img->uri[i] != ',' && img->uri[i] != 0)
+                        {
+                            i++;
+                        }
+                        if (img->uri[i] == 0) {
+                            TraceLog(LOG_WARNING, "[%s] Invalid data URI", fileName);
+                        }
+                        else
+                        {
+                            int size;
+                            unsigned char* data = DecodeBase64(img->uri+i+1, &size);
+                            int w, h;
+                            unsigned char* raw = stbi_load_from_memory(data, size, &w, &h, NULL, 4);
+                            Image image = LoadImagePro(raw, w, h, UNCOMPRESSED_R8G8B8A8);
+                            ImageColorTint(&image, tint);
+                            texture = LoadTextureFromImage(image);
+                            UnloadImage(image);
+                        }
+                    }
+                    else
+                    {
+                        char* texture_name = img->uri;
+                        char* texture_path = RL_MALLOC(strlen(dir_path) + strlen(texture_name) + 2);
+                        strcpy(texture_path, dir_path);
+                        strcat(texture_path, "/");
+                        strcat(texture_path, texture_name);
+
+                        Image image = LoadImage(texture_path);
+                        ImageColorTint(&image, tint);
+                        texture = LoadTextureFromImage(image);
+                        UnloadImage(image);
+                    }
+                }
+                else if (img->buffer_view)
+                {
+                    unsigned char* data = RL_MALLOC(img->buffer_view->size);
+                    int n = img->buffer_view->offset;
+                    int stride = img->buffer_view->stride ? img->buffer_view->stride : 1;
+                    for (int i = 0; i < img->buffer_view->size; i++)
+                    {
+                        data[i] = ((unsigned char*)img->buffer_view->buffer->data)[n];
+                        n += stride;
+                    }
+
+                    int w, h;
+                    unsigned char* raw = stbi_load_from_memory(data, img->buffer_view->size, &w, &h, NULL, 4);
+                    Image image = LoadImagePro(raw, w, h, UNCOMPRESSED_R8G8B8A8);
+                    ImageColorTint(&image, tint);
+                    texture = LoadTextureFromImage(image);
+                    UnloadImage(image);
+                }
+                else
+                {
+                    Image image = LoadImageEx(&tint, 1, 1);
+                    texture = LoadTextureFromImage(image);
+                    UnloadImage(image);
+                }
+                model.materials[i] = LoadMaterialDefault();
+                model.materials[i].maps[MAP_DIFFUSE].texture = texture;
+            }
+        }
+        model.materials[model.materialCount-1] = LoadMaterialDefault();
+
+        int prim_index = 0;
         
-        for (int i = 0; i < model.meshCount; i++)
+        for (int i = 0; i < data->meshes_count; i++)
         {
-            // NOTE: Only support meshes defined by triangle primitives
-            //if (data->meshes[i].primitives[n].type == cgltf_primitive_type_triangles)
+
+            for (int p = 0; p < data->meshes[i].primitives_count; p++)
             {
-                // data.meshes[i].name not used
-                model.meshes[i].vertexCount = data->meshes[i].primitives_count*3;
-                model.meshes[i].triangleCount = data->meshes[i].primitives_count;
-                // data.meshes[i].weights not used (array of weights to be applied to the Morph Targets)
 
-                model.meshes[i].vertices = RL_MALLOC(sizeof(float)*model.meshes[i].vertexCount*3);       // Default vertex positions
-                model.meshes[i].normals = RL_MALLOC(sizeof(float)*model.meshes[i].vertexCount*3);        // Default vertex normals
-                model.meshes[i].texcoords = RL_MALLOC(sizeof(float)*model.meshes[i].vertexCount*2);      // Default vertex texcoords
+                for (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)
+                    {
+                        cgltf_accessor* acc = data->meshes[i].primitives[p].attributes[j].data;
+                        model.meshes[prim_index].vertexCount = acc->count;
+                        model.meshes[prim_index].vertices = RL_MALLOC(sizeof(float)*model.meshes[prim_index].vertexCount*3);
+
+                        LOAD_ACCESSOR(float, 3, acc, model.meshes[prim_index].vertices)
+                    }
+                    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;
+                        model.meshes[prim_index].normals = RL_MALLOC(sizeof(float)*acc->count*3);
+
+                        LOAD_ACCESSOR(float, 3, acc, model.meshes[prim_index].normals)
+
+                    }
+                    else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord)
+                    {
+                        cgltf_accessor* acc = data->meshes[i].primitives[p].attributes[j].data;
+                        if (acc->component_type == cgltf_component_type_r_32f)
+                        {
+                            model.meshes[prim_index].texcoords = RL_MALLOC(sizeof(float)*acc->count*2);
+                            LOAD_ACCESSOR(float, 2, acc, model.meshes[prim_index].texcoords)
+                        }
+                        else
+                        {
+                            // TODO: support normalized unsigned byte/unsigned short texture coordinates
+                            TraceLog(LOG_WARNING, "[%s] Texture coordinates must be float", fileName);
+                        }
+                    }
+                }
 
-                model.meshes[i].indices = RL_MALLOC(sizeof(unsigned short)*model.meshes[i].triangleCount*3);
+                cgltf_accessor* acc = data->meshes[i].primitives[p].indices;
+                if (acc)
+                {
+                    if (acc->component_type == cgltf_component_type_r_16u)
+                    {
+                        model.meshes[prim_index].triangleCount = acc->count / 3;
+                        model.meshes[prim_index].indices = RL_MALLOC(sizeof(unsigned short)*model.meshes[prim_index].triangleCount*3);
+                        LOAD_ACCESSOR(unsigned short, 1, acc, model.meshes[prim_index].indices)
+                    }
+                    else
+                    {
+                        // TODO: support unsigned byte/unsigned int
+                        TraceLog(LOG_WARNING, "[%s] Indices must be unsigned short", fileName);
+                    }
+                }
+                else
+                {
+                    // unindexed mesh
+                    model.meshes[prim_index].triangleCount = model.meshes[prim_index].vertexCount / 3;
+                }
 
+                if (data->meshes[i].primitives[p].material)
+                {
+                    // compute the offset
+                    model.meshMaterial[prim_index] = data->meshes[i].primitives[p].material - data->materials;
+                }
+                else
+                {
+                    model.meshMaterial[prim_index] = model.materialCount - 1;;
+                }
+                prim_index++;
             }
         }
 
-        // NOTE: data.buffers[] should be loaded to model.meshes and data.images[] should be loaded to model.materials
-        // Use buffers[n].uri and images[n].uri... or use cgltf_load_buffers(&options, data, fileName);
-
         cgltf_free(data);
     }
     else TraceLog(LOG_WARNING, "[%s] glTF data could not be loaded", fileName);
 
+    RL_FREE(buffer);
+
     return model;
 }
 #endif