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 <nospam@antispam.com>
This commit is contained in:
chriscamacho 2020-10-08 19:31:59 +01:00 committed by GitHub
parent 41192c6d4a
commit 6ebf6b4e72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 137 additions and 114 deletions

View file

@ -24,6 +24,10 @@
#ifndef TINOBJ_LOADER_C_H_ #ifndef TINOBJ_LOADER_C_H_
#define 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. */ /* @todo { Remove stddef dependency. unsigned int? } ---> RAY: DONE. */
//#include <stddef.h> //#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'))) { if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) {
break; break;
} }
// codifies - added this, to get this working
if ((token[len] == '\n') && ((len < (n - 2)) && (token[len + 1] != '\r'))) {
break;
}
} }
return len; return len;
@ -547,7 +555,7 @@ static void initMaterial(tinyobj_material_t *material) {
#define HASH_TABLE_ERROR 1 #define HASH_TABLE_ERROR 1
#define HASH_TABLE_SUCCESS 0 #define HASH_TABLE_SUCCESS 0
#define HASH_TABLE_DEFAULT_SIZE 10 #define HASH_TABLE_DEFAULT_SIZE 11
typedef struct hash_table_entry_t typedef struct hash_table_entry_t
{ {
@ -571,11 +579,11 @@ static unsigned long hash_djb2(const unsigned char* str)
{ {
unsigned long hash = 5381; unsigned long hash = 5381;
int c; int c;
//printf("hashed >>>%s<<< into ",str);
while ((c = *str++)) { while ((c = *str++)) {
hash = ((hash << 5) + hash) + (unsigned long)(c); hash = ((hash << 5) + hash) + (unsigned long)(c);
} }
//printf("into %lu\n",hash);
return 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++) 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; return HASH_TABLE_ERROR;
}
index = (start_index + (i * i)) % hash_table->capacity; index = (start_index + (i * i)) % hash_table->capacity;
} }
@ -623,11 +633,14 @@ static int hash_table_insert_value(unsigned long hash, long value, hash_table_t*
start_entry->next = entry; start_entry->next = entry;
} }
//printf("using hash %lu \n",hash);
return HASH_TABLE_SUCCESS; return HASH_TABLE_SUCCESS;
} }
static int hash_table_insert(unsigned long hash, long value, hash_table_t* hash_table) 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); int ret = hash_table_insert_value(hash, value, hash_table);
if (ret == HASH_TABLE_SUCCESS) 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) 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); hash_table_entry_t* entry = hash_table->entries + (hash % hash_table->capacity);
while (entry) while (entry)
{ {
if (entry->hash == hash && entry->filled) if (entry->hash == hash && entry->filled)
{ {
//printf(" found\n");
return entry; return entry;
} }
entry = entry->next; entry = entry->next;
} }
//printf(" NOT found\n");
return NULL; 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; hash_table_t new_hash_table;
unsigned int i; unsigned int i;
// extra room for collisions
new_n *= 2;
if (new_n <= hash_table->capacity) { if (new_n <= hash_table->capacity) {
return; 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; new_hash_table.n = hash_table->n;
/* Rehash */ /* 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_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); 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) 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); 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; return ret->value;
} }
@ -1151,8 +1174,7 @@ static int parseLine(Command *command, const char *p, unsigned int p_len,
skip_space(&token); skip_space(&token);
command->mtllib_name = p + (token - linebuf); command->mtllib_name = p + (token - linebuf);
command->mtllib_name_len = (unsigned int)length_until_newline( command->mtllib_name_len = (unsigned int)length_until_newline(
token, p_len - (unsigned int)(token - linebuf)) + token, (p_len - (unsigned int)(token - linebuf)) + 1);
1;
command->type = COMMAND_MTLLIB; command->type = COMMAND_MTLLIB;
return 1; return 1;
@ -1377,7 +1399,7 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes,
/* Create a null terminated string */ /* Create a null terminated string */
char* material_name_null_term = (char*) TINYOBJ_MALLOC(commands[i].material_name_len + 1); 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); 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)) if (hash_table_exists(material_name_null_term, &material_table))
material_id = (int)hash_table_get(material_name_null_term, &material_table); material_id = (int)hash_table_get(material_name_null_term, &material_table);

View file

@ -896,7 +896,7 @@ Material *LoadMaterials(const char *fileName, int *materialCount)
#if defined(SUPPORT_FILEFORMAT_MTL) #if defined(SUPPORT_FILEFORMAT_MTL)
if (IsFileExtension(fileName, ".mtl")) if (IsFileExtension(fileName, ".mtl"))
{ {
tinyobj_material_t *mats; tinyobj_material_t *mats = NULL;
int result = tinyobj_parse_mtl_file(&mats, &count, fileName); int result = tinyobj_parse_mtl_file(&mats, &count, fileName);
if (result != TINYOBJ_SUCCESS) { if (result != TINYOBJ_SUCCESS) {
@ -2566,6 +2566,12 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota
for (int i = 0; i < model.meshCount; i++) for (int i = 0; i < model.meshCount; i++)
{ {
// TODO: Review color + tint premultiplication mechanism // 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 color = model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color;
Color colorTint = WHITE; Color colorTint = WHITE;
@ -2942,11 +2948,15 @@ RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight)
#if defined(SUPPORT_FILEFORMAT_OBJ) #if defined(SUPPORT_FILEFORMAT_OBJ)
// Load OBJ mesh data // 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) static Model LoadOBJ(const char *fileName)
{ {
Model model = { 0 }; Model model = { 0 };
tinyobj_attrib_t attrib; tinyobj_attrib_t attrib = { 0 };
tinyobj_shape_t *meshes = NULL; tinyobj_shape_t *meshes = NULL;
unsigned int meshCount = 0; 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); 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); 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 = materialCount;
//model.meshCount = meshCount;
model.meshCount = 1;
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
// Init model materials array // Init model materials array
if (materialCount > 0) if (materialCount > 0)
{ {
model.materialCount = materialCount; model.materialCount = materialCount;
model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); 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)); model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
/* // count the faces for each material
// Multiple meshes data reference int* matFaces = RL_CALLOC(meshCount, sizeof(int));
// 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 mi=0; mi<meshCount; mi++) {
for (int m = 0; m < 1; m++) 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++)
{ {
Mesh mesh = { 0 }; model.meshes[mi].vertexCount = matFaces[mi] * 3;
memset(&mesh, 0, sizeof(Mesh)); model.meshes[mi].triangleCount = matFaces[mi];
mesh.vertexCount = attrib.num_faces*3; model.meshes[mi].vertices = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float));
mesh.triangleCount = attrib.num_faces; model.meshes[mi].texcoords = (float *)RL_CALLOC(model.meshes[mi].vertexCount*2, sizeof(float));
mesh.vertices = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float)); model.meshes[mi].normals = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float));
mesh.texcoords = (float *)RL_CALLOC(mesh.vertexCount*2, sizeof(float)); model.meshes[mi].vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int));
mesh.normals = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float)); model.meshMaterial[mi] = mi;
mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); }
int vCount = 0; // scan through the combined sub meshes and pick out each material mesh
int vtCount = 0; for (unsigned int af = 0; af < attrib.num_faces; af++)
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 // Get indices for the face
tinyobj_vertex_index_t idx0 = attrib.faces[3*f + 0]; tinyobj_vertex_index_t idx0 = attrib.faces[3 * af + 0];
tinyobj_vertex_index_t idx1 = attrib.faces[3*f + 1]; tinyobj_vertex_index_t idx1 = attrib.faces[3 * af + 1];
tinyobj_vertex_index_t idx2 = attrib.faces[3*f + 2]; tinyobj_vertex_index_t idx2 = attrib.faces[3 * af + 2];
// Fill vertices buffer (float) using vertex index of the face // 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++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount[mm] +=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++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount[mm] +=3;
for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount +=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) if (attrib.num_texcoords > 0)
{ {
// Fill texcoords buffer (float) using vertex index of the face // Fill texcoords buffer (float) using vertex index of the face
// NOTE: Y-coordinate must be flipped upside-down // NOTE: Y-coordinate must be flipped upside-down to account for
mesh.texcoords[vtCount + 0] = attrib.texcoords[idx0.vt_idx*2 + 0]; // raylib's upside down textures...
mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount += 2; model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx0.vt_idx*2 + 0];
mesh.texcoords[vtCount + 0] = attrib.texcoords[idx1.vt_idx*2 + 0]; model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount[mm] += 2;
mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount += 2; model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx1.vt_idx*2 + 0];
mesh.texcoords[vtCount + 0] = attrib.texcoords[idx2.vt_idx*2 + 0]; model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount[mm] += 2;
mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount += 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) if (attrib.num_normals > 0)
{ {
// Fill normals buffer (float) using vertex index of the face // 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++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount[mm] +=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++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount[mm] +=3;
for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount +=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;
} }
} }
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;
}
// Init model materials // Init model materials
for (unsigned int m = 0; m < materialCount; m++) for (unsigned int m = 0; m < materialCount; m++)
{ {
// Init material to default // 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(); 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 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].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; model.materials[m].maps[MAP_DIFFUSE].value = 0.0f;
@ -3110,6 +3106,11 @@ static Model LoadOBJ(const char *fileName)
RL_FREE(fileData); RL_FREE(fileData);
RL_FREE(vCount);
RL_FREE(vtCount);
RL_FREE(vnCount);
RL_FREE(faceCount);
chdir(currentDir); chdir(currentDir);
} }