Jelajahi Sumber

allow for multiple materials in obj files (#1408)

* allow for multiple materials in obj files also fix obj_loader hash map issues

* minor fix for warning

Co-authored-by: codifies <[email protected]>
chriscamacho 5 tahun lalu
induk
melakukan
6ebf6b4e72
2 mengubah file dengan 136 tambahan dan 113 penghapusan
  1. 31 9
      src/external/tinyobj_loader_c.h
  2. 105 104
      src/models.c

+ 31 - 9
src/external/tinyobj_loader_c.h

@@ -24,6 +24,10 @@
 #ifndef TINOBJ_LOADER_C_H_
 #define TINOBJ_LOADER_C_H_
 
+// TODO (codifies) I have commented out many printf's I used to observe
+// the several bugs in action related to hashmap, they can be removed at
+// some point - I only left them in case (paranoid - me?)
+
 /* @todo { Remove stddef dependency. unsigned int? } ---> RAY: DONE. */
 //#include <stddef.h>
 
@@ -166,6 +170,10 @@ static unsigned int length_until_newline(const char *token, unsigned int n) {
     if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) {
       break;
     }
+    // codifies  -  added this, to get this working
+    if ((token[len] == '\n') && ((len < (n - 2)) && (token[len + 1] != '\r'))) {
+      break;
+    }
   }
 
   return len;
@@ -547,7 +555,7 @@ static void initMaterial(tinyobj_material_t *material) {
 #define HASH_TABLE_ERROR 1 
 #define HASH_TABLE_SUCCESS 0
 
-#define HASH_TABLE_DEFAULT_SIZE 10
+#define HASH_TABLE_DEFAULT_SIZE 11
 
 typedef struct hash_table_entry_t
 {
@@ -571,11 +579,11 @@ static unsigned long hash_djb2(const unsigned char* str)
 {
   unsigned long hash = 5381;
   int c;
-
+  //printf("hashed >>>%s<<< into ",str);
   while ((c = *str++)) {
     hash = ((hash << 5) + hash) + (unsigned long)(c);
   }
-
+  //printf("into %lu\n",hash);
   return hash;
 }
 
@@ -607,8 +615,10 @@ static int hash_table_insert_value(unsigned long hash, long value, hash_table_t*
 
   for (i = 1; hash_table->entries[index].filled; i++)
   {
-    if (i >= hash_table->capacity)
+    if (i >= hash_table->capacity) {
+      //printf("insert failed\n");
       return HASH_TABLE_ERROR;
+    }
     index = (start_index + (i * i)) % hash_table->capacity; 
   }
 
@@ -622,12 +632,15 @@ static int hash_table_insert_value(unsigned long hash, long value, hash_table_t*
     entry->next = start_entry->next;
     start_entry->next = entry;
   }
+  
+  //printf("using hash %lu \n",hash);
 
   return HASH_TABLE_SUCCESS;
 }
 
 static int hash_table_insert(unsigned long hash, long value, hash_table_t* hash_table)
 {
+  //printf("inserting %i into hash ",value);   
   int ret = hash_table_insert_value(hash, value, hash_table);
   if (ret == HASH_TABLE_SUCCESS)
   {
@@ -639,15 +652,19 @@ static int hash_table_insert(unsigned long hash, long value, hash_table_t* hash_
 
 static hash_table_entry_t* hash_table_find(unsigned long hash, hash_table_t* hash_table)
 {
+  //printf("looking for hash %lu  - ",hash);
   hash_table_entry_t* entry = hash_table->entries + (hash % hash_table->capacity);
   while (entry)
   {
     if (entry->hash == hash && entry->filled)
     {
+      //printf(" found\n");
       return entry;
     }
     entry = entry->next;
   }
+  //printf(" NOT found\n");
+  
   return NULL;
 }
 
@@ -657,6 +674,9 @@ static void hash_table_maybe_grow(unsigned int new_n, hash_table_t* hash_table)
   hash_table_t new_hash_table;
   unsigned int i;
 
+  // extra room for collisions
+  new_n *= 2;
+
   if (new_n <= hash_table->capacity) {
     return;
   }
@@ -668,7 +688,9 @@ static void hash_table_maybe_grow(unsigned int new_n, hash_table_t* hash_table)
   new_hash_table.n = hash_table->n;
 
   /* Rehash */
-  for (i = 0; i < hash_table->capacity; i++)
+  // we asked for twice as much as was in there so just rehash the first half which
+  // is the whole source table
+  for (i = 0; i < hash_table->capacity / 2; i++)
   {
     hash_table_entry_t* entry = hash_table_find(hash_table->hashes[i], hash_table);
     hash_table_insert_value(hash_table->hashes[i], entry->value, &new_hash_table);
@@ -708,6 +730,7 @@ static void hash_table_set(const char* name, unsigned int val, hash_table_t* has
 static long hash_table_get(const char* name, hash_table_t* hash_table)
 {
   hash_table_entry_t* ret = hash_table_find(hash_djb2((const unsigned char*)(name)), hash_table);
+  //printf("found a value of %i for %s\n",ret->value, name);
   return ret->value;
 }
 
@@ -1137,7 +1160,7 @@ static int parseLine(Command *command, const char *p, unsigned int p_len,
     skip_space(&token);
     command->material_name = p + (token - linebuf);
     command->material_name_len = (unsigned int)length_until_newline(
-                                                                    token, (p_len - (unsigned int)(token - linebuf)) + 1);
+                                      token, (p_len - (unsigned int)(token - linebuf)) + 1);
     command->type = COMMAND_USEMTL;
 
     return 1;
@@ -1151,8 +1174,7 @@ static int parseLine(Command *command, const char *p, unsigned int p_len,
     skip_space(&token);
     command->mtllib_name = p + (token - linebuf);
     command->mtllib_name_len = (unsigned int)length_until_newline(
-                                                                  token, p_len - (unsigned int)(token - linebuf)) +
-      1;
+                                    token, (p_len - (unsigned int)(token - linebuf)) + 1);
     command->type = COMMAND_MTLLIB;
 
     return 1;
@@ -1377,7 +1399,7 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes,
           /* Create a null terminated string */
           char* material_name_null_term = (char*) TINYOBJ_MALLOC(commands[i].material_name_len + 1);
           memcpy((void*) material_name_null_term, (const void*) commands[i].material_name, commands[i].material_name_len);
-          material_name_null_term[commands[i].material_name_len - 1] = 0;
+          material_name_null_term[commands[i].material_name_len] = 0;
 
           if (hash_table_exists(material_name_null_term, &material_table))
             material_id = (int)hash_table_get(material_name_null_term, &material_table);

+ 105 - 104
src/models.c

@@ -896,8 +896,8 @@ Material *LoadMaterials(const char *fileName, int *materialCount)
 #if defined(SUPPORT_FILEFORMAT_MTL)
     if (IsFileExtension(fileName, ".mtl"))
     {
-        tinyobj_material_t *mats;
-
+        tinyobj_material_t *mats = NULL;
+                                           
         int result = tinyobj_parse_mtl_file(&mats, &count, fileName);
         if (result != TINYOBJ_SUCCESS) {
             TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName);
@@ -2566,6 +2566,12 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota
     for (int i = 0; i < model.meshCount; i++)
     {
         // TODO: Review color + tint premultiplication mechanism
+        
+        // (codifies)  Ray not only does this work as expected but
+        // multiplying *is* definately the way to tint 
+        // can we call it reviewed ?
+        
+        // would you prefer an extra model.tint, that rlDrawMesh uses ?
         Color color = model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color;
 
         Color colorTint = WHITE;
@@ -2942,11 +2948,15 @@ RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight)
 
 #if defined(SUPPORT_FILEFORMAT_OBJ)
 // Load OBJ mesh data
+
+// TODO used by loadOBJ, could change to a function that could handle
+// data coming from a file, memory or archive...
+
 static Model LoadOBJ(const char *fileName)
 {
     Model model = { 0 };
 
-    tinyobj_attrib_t attrib;
+    tinyobj_attrib_t attrib = { 0 };
     tinyobj_shape_t *meshes = NULL;
     unsigned int meshCount = 0;
 
@@ -2968,126 +2978,112 @@ static Model LoadOBJ(const char *fileName)
         if (ret != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load OBJ data", fileName);
         else TRACELOG(LOG_INFO, "MODEL: [%s] OBJ data loaded successfully: %i meshes / %i materials", fileName, meshCount, materialCount);
 
-        // Init model meshes array
-        // TODO: Support multiple meshes... in the meantime, only one mesh is returned
-        //model.meshCount = meshCount;
-        model.meshCount = 1;
-        model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
-
+       
+        model.meshCount = materialCount;
+        
+                
         // Init model materials array
         if (materialCount > 0)
         {
             model.materialCount = materialCount;
             model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material));
+            TraceLog(LOG_INFO, "MODEL: model has %i material meshes", materialCount);
+        } else {
+            model.meshCount = 1;  
+            TraceLog(LOG_INFO, "MODEL: No materials, putting all meshes in a default material");         
         }
 
+        model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
         model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
-
-        /*
-        // Multiple meshes data reference
-        // NOTE: They are provided as a faces offset
-        typedef struct {
-            char *name;         // group name or object name
-            unsigned int face_offset;
-            unsigned int length;
-        } tinyobj_shape_t;
-        */
-
-        // Init model meshes
-        for (int m = 0; m < 1; m++)
+        
+        // count the faces for each material
+        int* matFaces = RL_CALLOC(meshCount, sizeof(int));
+        
+        for (int mi=0; mi<meshCount; mi++) {
+            for (int fi=0; fi<meshes[mi].length; fi++) {
+                int idx = attrib.material_ids[meshes[mi].face_offset + fi];
+                if (idx == -1) idx = 0; // for no material face (which could be the whole model)
+                matFaces[idx]++;
+            }
+        }
+        
+        //--------------------------------------
+        // create the material meshes
+ 
+        // running counts / indexes for each material mesh as we are 
+        // building them at the same time     
+        int* vCount = RL_CALLOC(model.meshCount, sizeof(int));
+        int* vtCount = RL_CALLOC(model.meshCount, sizeof(int));
+        int* vnCount = RL_CALLOC(model.meshCount, sizeof(int));
+        int* faceCount = RL_CALLOC(model.meshCount, sizeof(int));
+ 
+        // allocate space for each of the material meshes
+        for (int mi=0; mi<model.meshCount; mi++) 
+        {
+            model.meshes[mi].vertexCount = matFaces[mi] * 3;
+            model.meshes[mi].triangleCount = matFaces[mi];
+            model.meshes[mi].vertices = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float));
+            model.meshes[mi].texcoords = (float *)RL_CALLOC(model.meshes[mi].vertexCount*2, sizeof(float));
+            model.meshes[mi].normals = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float));
+            model.meshes[mi].vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
+            model.meshMaterial[mi] = mi;
+        }
+        
+        // scan through the combined sub meshes and pick out each material mesh
+        for (unsigned int af = 0; af < attrib.num_faces; af++) 
         {
-            Mesh mesh = { 0 };
-            memset(&mesh, 0, sizeof(Mesh));
-            mesh.vertexCount = attrib.num_faces*3;
-            mesh.triangleCount = attrib.num_faces;
-            mesh.vertices = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float));
-            mesh.texcoords = (float *)RL_CALLOC(mesh.vertexCount*2, sizeof(float));
-            mesh.normals = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float));
-            mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
-
-            int vCount = 0;
-            int vtCount = 0;
-            int vnCount = 0;
-
-            for (unsigned int f = 0; f < attrib.num_faces; f++)
+            int mm = attrib.material_ids[af];   // mesh material for this face
+            if (mm == -1) { mm = 0; }           // no material object..
+            // Get indices for the face
+            tinyobj_vertex_index_t idx0 = attrib.faces[3 * af + 0];
+            tinyobj_vertex_index_t idx1 = attrib.faces[3 * af + 1];
+            tinyobj_vertex_index_t idx2 = attrib.faces[3 * af + 2]; 
+            
+            // Fill vertices buffer (float) using vertex index of the face
+            for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount[mm] +=3;
+            for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount[mm] +=3;
+            for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount[mm] +=3; 
+            
+            if (attrib.num_texcoords > 0)
             {
-                // Get indices for the face
-                tinyobj_vertex_index_t idx0 = attrib.faces[3*f + 0];
-                tinyobj_vertex_index_t idx1 = attrib.faces[3*f + 1];
-                tinyobj_vertex_index_t idx2 = attrib.faces[3*f + 2];
-
-                // Fill vertices buffer (float) using vertex index of the face
-                for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount +=3;
-                for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount +=3;
-                for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount +=3;
-
-                if (attrib.num_texcoords > 0)
-                {
-                    // Fill texcoords buffer (float) using vertex index of the face
-                    // NOTE: Y-coordinate must be flipped upside-down
-                    mesh.texcoords[vtCount + 0] = attrib.texcoords[idx0.vt_idx*2 + 0];
-                    mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount += 2;
-                    mesh.texcoords[vtCount + 0] = attrib.texcoords[idx1.vt_idx*2 + 0];
-                    mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount += 2;
-                    mesh.texcoords[vtCount + 0] = attrib.texcoords[idx2.vt_idx*2 + 0];
-                    mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount += 2;
-                }
-
-                if (attrib.num_normals > 0)
-                {
-                    // Fill normals buffer (float) using vertex index of the face
-                    for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount +=3;
-                    for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount +=3;
-                    for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount +=3;
-                }
-            }
-
-            model.meshes[m] = mesh;                 // Assign mesh data to model
-
-            // Assign mesh material for current mesh
-            model.meshMaterial[m] = attrib.material_ids[m];
-
-            // Set unfound materials to default
-            if (model.meshMaterial[m] == -1) model.meshMaterial[m] = 0;
+                // Fill texcoords buffer (float) using vertex index of the face
+                // NOTE: Y-coordinate must be flipped upside-down to account for 
+                // raylib's upside down textures...
+                model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx0.vt_idx*2 + 0];
+                model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount[mm] += 2;
+                model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx1.vt_idx*2 + 0];
+                model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount[mm] += 2;
+                model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx2.vt_idx*2 + 0];
+                model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount[mm] += 2;
+            }  
+            
+            if (attrib.num_normals > 0)
+            {
+                // Fill normals buffer (float) using vertex index of the face
+                for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount[mm] +=3;
+                for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount[mm] +=3;
+                for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount[mm] +=3;
+            }                                            
         }
 
         // Init model materials
         for (unsigned int m = 0; m < materialCount; m++)
-        {
+        {            
             // Init material to default
-            // NOTE: Uses default shader, only MAP_DIFFUSE supported
+            // NOTE: Uses default shader, which only supports MAP_DIFFUSE
+            
+            // (codifies)  TODO my lighting shader should support at least
+            // diffuse AND specular ...
             model.materials[m] = LoadMaterialDefault();
 
-            /*
-            typedef struct {
-                char *name;
-
-                float ambient[3];
-                float diffuse[3];
-                float specular[3];
-                float transmittance[3];
-                float emission[3];
-                float shininess;
-                float ior;          // index of refraction
-                float dissolve;     // 1 == opaque; 0 == fully transparent
-                // illumination model (Ref: http://www.fileformat.info/format/material/)
-                int illum;
-
-                int pad0;
-
-                char *ambient_texname;            // map_Ka
-                char *diffuse_texname;            // map_Kd
-                char *specular_texname;           // map_Ks
-                char *specular_highlight_texname; // map_Ns
-                char *bump_texname;               // map_bump, bump
-                char *displacement_texname;       // disp
-                char *alpha_texname;              // map_d
-            } tinyobj_material_t;
-            */
-
             model.materials[m].maps[MAP_DIFFUSE].texture = GetTextureDefault();     // Get default texture, in case no texture is defined
 
-            if (materials[m].diffuse_texname != NULL) model.materials[m].maps[MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname);  //char *diffuse_texname; // map_Kd
+            if (materials[m].diffuse_texname != NULL) {
+                model.materials[m].maps[MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname);  //char *diffuse_texname; // map_Kd
+            } else {
+                model.materials[m].maps[MAP_DIFFUSE].texture = GetTextureDefault();
+            }
+    
             model.materials[m].maps[MAP_DIFFUSE].color = (Color){ (unsigned char)(materials[m].diffuse[0]*255.0f), (unsigned char)(materials[m].diffuse[1]*255.0f), (unsigned char)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3];
             model.materials[m].maps[MAP_DIFFUSE].value = 0.0f;
 
@@ -3109,6 +3105,11 @@ static Model LoadOBJ(const char *fileName)
         tinyobj_materials_free(materials, materialCount);
         
         RL_FREE(fileData);
+        
+        RL_FREE(vCount);
+        RL_FREE(vtCount);
+        RL_FREE(vnCount);
+        RL_FREE(faceCount);
 
         chdir(currentDir);
     }