@@ -2016,6 +2016,8 @@ static void ProcessMaterialsOBJ(Material *materials, tinyobj_material_t *mats, i
20162016 // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE
20172017 materials [m ] = LoadMaterialDefault ();
20182018
2019+ if (mats == NULL ) continue ;
2020+
20192021 // Get default texture, in case no texture is defined
20202022 // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8
20212023 materials [m ].maps [MATERIAL_MAP_DIFFUSE ].texture = (Texture2D ){ rlGetTextureIdDefault (), 1 , 1 , 1 , PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
@@ -4073,132 +4075,238 @@ static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform
40734075// - the mesh is automatically triangulated by tinyobj
40744076static Model LoadOBJ (const char * fileName )
40754077{
4078+ tinyobj_attrib_t objAttributes = { 0 };
4079+ tinyobj_shape_t * objShapes = NULL ;
4080+ unsigned int objShapeCount = 0 ;
4081+
4082+ tinyobj_material_t * objMaterials = NULL ;
4083+ unsigned int objMaterialCount = 0 ;
4084+
40764085 Model model = { 0 };
4086+ model .transform = MatrixIdentity ();
40774087
4078- tinyobj_attrib_t attrib = { 0 };
4079- tinyobj_shape_t * meshes = NULL ;
4080- unsigned int meshCount = 0 ;
4088+ char * fileText = LoadFileText (fileName );
40814089
4082- tinyobj_material_t * materials = NULL ;
4083- unsigned int materialCount = 0 ;
4090+ if (fileText == NULL )
4091+ {
4092+ TRACELOG (LOG_ERROR , "MODEL Unable to read obj file %s" , fileName );
4093+ return model ;
4094+ }
40844095
4085- char * fileText = LoadFileText (fileName );
4096+ char currentDir [1024 ] = { 0 };
4097+ strcpy (currentDir , GetWorkingDirectory ()); // Save current working directory
4098+ const char * workingDir = GetDirectoryPath (fileName ); // Switch to OBJ directory for material path correctness
4099+ if (CHDIR (workingDir ) != 0 )
4100+ {
4101+ TRACELOG (LOG_WARNING , "MODEL: [%s] Failed to change working directory" , workingDir );
4102+ }
40864103
4087- if (fileText != NULL )
4104+ unsigned int dataSize = (unsigned int )strlen (fileText );
4105+
4106+ unsigned int flags = TINYOBJ_FLAG_TRIANGULATE ;
4107+ int ret = tinyobj_parse_obj (& objAttributes , & objShapes , & objShapeCount , & objMaterials , & objMaterialCount , fileText , dataSize , flags );
4108+
4109+ if (ret != TINYOBJ_SUCCESS )
40884110 {
4089- unsigned int dataSize = (unsigned int )strlen (fileText );
4111+ TRACELOG (LOG_ERROR , "MODEL Unable to read obj data %s" , fileName );
4112+ return model ;
4113+ }
4114+
4115+ UnloadFileText (fileText );
4116+
4117+ unsigned int faceVertIndex = 0 ;
4118+ unsigned int nextShape = 1 ;
4119+ int lastMaterial = -1 ;
4120+ unsigned int meshIndex = 0 ;
4121+
4122+ // count meshes
4123+ unsigned int nextShapeEnd = objAttributes .num_face_num_verts ;
4124+
4125+ // see how many verts till the next shape
40904126
4091- char currentDir [1024 ] = { 0 };
4092- strcpy (currentDir , GetWorkingDirectory ()); // Save current working directory
4093- const char * workingDir = GetDirectoryPath (fileName ); // Switch to OBJ directory for material path correctness
4094- if (CHDIR (workingDir ) != 0 )
4127+ if (objShapeCount > 1 ) nextShapeEnd = objShapes [nextShape ].face_offset ;
4128+
4129+ // walk all the faces
4130+ for (unsigned int faceId = 0 ; faceId < objAttributes .num_faces ; faceId ++ )
4131+ {
4132+ if (faceVertIndex >= nextShapeEnd )
40954133 {
4096- TRACELOG (LOG_WARNING , "MODEL: [%s] Failed to change working directory" , workingDir );
4134+ // try to find the last vert in the next shape
4135+ nextShape ++ ;
4136+ if (nextShape < objShapeCount ) nextShapeEnd = objShapes [nextShape ].face_offset ;
4137+ else nextShapeEnd = objAttributes .num_face_num_verts ; // this is actually the total number of face verts in the file, not faces
4138+ meshIndex ++ ;
4139+ }
4140+ else if (lastMaterial != -1 && objAttributes .material_ids [faceId ] != lastMaterial )
4141+ {
4142+ meshIndex ++ ;// if this is a new material, we need to allocate a new mesh
40974143 }
40984144
4099- unsigned int flags = TINYOBJ_FLAG_TRIANGULATE ;
4100- int ret = tinyobj_parse_obj (& attrib , & meshes , & meshCount , & materials , & materialCount , fileText , dataSize , flags );
4145+ lastMaterial = objAttributes .material_ids [faceId ];
4146+ faceVertIndex += objAttributes .face_num_verts [faceId ];
4147+ }
41014148
4102- if (ret != TINYOBJ_SUCCESS ) TRACELOG (LOG_WARNING , "MODEL: [%s] Failed to load OBJ data" , fileName );
4103- else TRACELOG (LOG_INFO , "MODEL: [%s] OBJ data loaded successfully: %i meshes/%i materials" , fileName , meshCount , materialCount );
4149+ // allocate the base meshes and materials
4150+ model .meshCount = meshIndex + 1 ;
4151+ model .meshes = (Mesh * )MemAlloc (sizeof (Mesh ) * model .meshCount );
41044152
4105- // WARNING: We are not splitting meshes by materials (previous implementation)
4106- // Depending on the provided OBJ that was not the best option and it just crashed
4107- // so, implementation was simplified to prioritize parsed meshes
4108- model .meshCount = meshCount ;
4153+ if (objMaterialCount > 0 )
4154+ {
4155+ model .materialCount = objMaterialCount ;
4156+ model .materials = (Material * )MemAlloc (sizeof (Material ) * objMaterialCount );
4157+ }
4158+ else // we must allocate at least one material
4159+ {
4160+ model .materialCount = 1 ;
4161+ model .materials = (Material * )MemAlloc (sizeof (Material ) * 1 );
4162+ }
4163+
4164+ model .meshMaterial = (int * )MemAlloc (sizeof (int ) * model .meshCount );
4165+
4166+ // see how many verts are in each mesh
4167+ unsigned int * localMeshVertexCounts = (unsigned int * )MemAlloc (sizeof (unsigned int ) * model .meshCount );
4168+
4169+ faceVertIndex = 0 ;
4170+ nextShapeEnd = objAttributes .num_face_num_verts ;
4171+ lastMaterial = -1 ;
4172+ meshIndex = 0 ;
4173+ unsigned int localMeshVertexCount = 0 ;
41094174
4110- // Set number of materials available
4111- // NOTE: There could be more materials available than meshes but it will be resolved at
4112- // model.meshMaterial, just assigning the right material to corresponding mesh
4113- model .materialCount = materialCount ;
4114- if (model .materialCount == 0 )
4175+ nextShape = 1 ;
4176+ if (objShapeCount > 1 )
4177+ nextShapeEnd = objShapes [nextShape ].face_offset ;
4178+
4179+ // walk all the faces
4180+ for (unsigned int faceId = 0 ; faceId < objAttributes .num_faces ; faceId ++ )
4181+ {
4182+ bool newMesh = false; // do we need a new mesh?
4183+ if (faceVertIndex >= nextShapeEnd )
41154184 {
4116- model .materialCount = 1 ;
4117- TRACELOG (LOG_INFO , "MODEL: No materials provided, setting one default material for all meshes" );
4185+ // try to find the last vert in the next shape
4186+ nextShape ++ ;
4187+ if (nextShape < objShapeCount ) nextShapeEnd = objShapes [nextShape ].face_offset ;
4188+ else nextShapeEnd = objAttributes .num_face_num_verts ; // this is actually the total number of face verts in the file, not faces
4189+
4190+ newMesh = true;
41184191 }
4119- else if (model . materialCount > 1 && model . meshCount > 1 )
4192+ else if (lastMaterial != - 1 && objAttributes . material_ids [ faceId ] != lastMaterial )
41204193 {
4121- // TEMP warning about multiple materials, to be removed when proper splitting code is implemented
4122- // any obj with multiple materials will need to have it's materials assigned by the user in code to work at this time
4123- TRACELOG (LOG_INFO , "MODEL: OBJ has multiple materials, manual material assignment will be required." );
4194+ newMesh = true;
41244195 }
41254196
4126- // Init model meshes and materials
4127- model .meshes = (Mesh * )RL_CALLOC (model .meshCount , sizeof (Mesh ));
4128- model .meshMaterial = (int * )RL_CALLOC (model .meshCount , sizeof (int )); // Material index assigned to each mesh
4129- model .materials = (Material * )RL_CALLOC (model .materialCount , sizeof (Material ));
4197+ lastMaterial = objAttributes .material_ids [faceId ];
41304198
4131- // Process each provided mesh
4132- for (int i = 0 ; i < model .meshCount ; i ++ )
4199+ if (newMesh )
41334200 {
4134- // WARNING: We need to calculate the mesh triangles manually using meshes[i].face_offset
4135- // because in case of triangulated quads, meshes[i].length actually report quads,
4136- // despite the triangulation that is efectively considered on attrib.num_faces
4137- unsigned int tris = 0 ;
4138- if (i == model .meshCount - 1 ) tris = attrib .num_faces - meshes [i ].face_offset ;
4139- else tris = meshes [i + 1 ].face_offset ;
4140-
4141- model .meshes [i ].vertexCount = tris * 3 ;
4142- model .meshes [i ].triangleCount = tris ; // Face count (triangulated)
4143- model .meshes [i ].vertices = (float * )RL_CALLOC (model .meshes [i ].vertexCount * 3 , sizeof (float ));
4144- model .meshes [i ].texcoords = (float * )RL_CALLOC (model .meshes [i ].vertexCount * 2 , sizeof (float ));
4145- model .meshes [i ].normals = (float * )RL_CALLOC (model .meshes [i ].vertexCount * 3 , sizeof (float ));
4146- model .meshMaterial [i ] = 0 ; // By default, assign material 0 to each mesh
4147-
4148- // Process all mesh faces
4149- for (unsigned int face = 0 , f = meshes [i ].face_offset , v = 0 , vt = 0 , vn = 0 ; face < tris ; face ++ , f ++ , v += 3 , vt += 3 , vn += 3 )
4150- {
4151- // Get indices for the face
4152- tinyobj_vertex_index_t idx0 = attrib .faces [f * 3 + 0 ];
4153- tinyobj_vertex_index_t idx1 = attrib .faces [f * 3 + 1 ];
4154- tinyobj_vertex_index_t idx2 = attrib .faces [f * 3 + 2 ];
4201+ localMeshVertexCounts [meshIndex ] = localMeshVertexCount ;
41554202
4156- // Fill vertices buffer (float) using vertex index of the face
4157- for (int n = 0 ; n < 3 ; n ++ ) { model .meshes [i ].vertices [v * 3 + n ] = attrib .vertices [idx0 .v_idx * 3 + n ]; }
4158- for (int n = 0 ; n < 3 ; n ++ ) { model .meshes [i ].vertices [(v + 1 )* 3 + n ] = attrib .vertices [idx1 .v_idx * 3 + n ]; }
4159- for (int n = 0 ; n < 3 ; n ++ ) { model .meshes [i ].vertices [(v + 2 )* 3 + n ] = attrib .vertices [idx2 .v_idx * 3 + n ]; }
4203+ localMeshVertexCount = 0 ;
4204+ meshIndex ++ ;
4205+ }
41604206
4161- if (attrib .num_texcoords > 0 )
4162- {
4163- // Fill texcoords buffer (float) using vertex index of the face
4164- // NOTE: Y-coordinate must be flipped upside-down
4165- model .meshes [i ].texcoords [vt * 2 + 0 ] = attrib .texcoords [idx0 .vt_idx * 2 + 0 ];
4166- model .meshes [i ].texcoords [vt * 2 + 1 ] = 1.0f - attrib .texcoords [idx0 .vt_idx * 2 + 1 ];
4207+ faceVertIndex += objAttributes .face_num_verts [faceId ];
4208+ localMeshVertexCount += objAttributes .face_num_verts [faceId ];
4209+ }
4210+ localMeshVertexCounts [meshIndex ] = localMeshVertexCount ;
41674211
4168- model .meshes [i ].texcoords [(vt + 1 )* 2 + 0 ] = attrib .texcoords [idx1 .vt_idx * 2 + 0 ];
4169- model .meshes [i ].texcoords [(vt + 1 )* 2 + 1 ] = 1.0f - attrib .texcoords [idx1 .vt_idx * 2 + 1 ];
4212+ for (int i = 0 ; i < model .meshCount ; i ++ )
4213+ {
4214+ // allocate the buffers for each mesh
4215+ unsigned int vertexCount = localMeshVertexCounts [i ];
41704216
4171- model .meshes [i ].texcoords [(vt + 2 )* 2 + 0 ] = attrib .texcoords [idx2 .vt_idx * 2 + 0 ];
4172- model .meshes [i ].texcoords [(vt + 2 )* 2 + 1 ] = 1.0f - attrib .texcoords [idx2 .vt_idx * 2 + 1 ];
4173- }
4217+ model .meshes [i ].vertexCount = vertexCount ;
4218+ model .meshes [i ].triangleCount = vertexCount / 3 ;
41744219
4175- if (attrib .num_normals > 0 )
4176- {
4177- // Fill normals buffer (float) using vertex index of the face
4178- for (int n = 0 ; n < 3 ; n ++ ) { model .meshes [i ].normals [vn * 3 + n ] = attrib .normals [idx0 .vn_idx * 3 + n ]; }
4179- for (int n = 0 ; n < 3 ; n ++ ) { model .meshes [i ].normals [(vn + 1 )* 3 + n ] = attrib .normals [idx1 .vn_idx * 3 + n ]; }
4180- for (int n = 0 ; n < 3 ; n ++ ) { model .meshes [i ].normals [(vn + 2 )* 3 + n ] = attrib .normals [idx2 .vn_idx * 3 + n ]; }
4181- }
4182- }
4220+ model .meshes [i ].vertices = (float * )MemAlloc (sizeof (float ) * vertexCount * 3 );
4221+ model .meshes [i ].normals = (float * )MemAlloc (sizeof (float ) * vertexCount * 3 );
4222+ model .meshes [i ].texcoords = (float * )MemAlloc (sizeof (float ) * vertexCount * 2 );
4223+ model .meshes [i ].colors = (unsigned char * )MemAlloc (sizeof (unsigned char ) * vertexCount * 4 );
4224+ }
4225+
4226+ MemFree (localMeshVertexCounts );
4227+ localMeshVertexCounts = NULL ;
4228+
4229+ // fill meshes
4230+ faceVertIndex = 0 ;
4231+
4232+ nextShapeEnd = objAttributes .num_face_num_verts ;
4233+
4234+ // see how many verts till the next shape
4235+ nextShape = 1 ;
4236+ if (objShapeCount > 1 ) nextShapeEnd = objShapes [nextShape ].face_offset ;
4237+ lastMaterial = -1 ;
4238+ meshIndex = 0 ;
4239+ localMeshVertexCount = 0 ;
4240+
4241+ // walk all the faces
4242+ for (unsigned int faceId = 0 ; faceId < objAttributes .num_faces ; faceId ++ )
4243+ {
4244+ bool newMesh = false; // do we need a new mesh?
4245+ if (faceVertIndex >= nextShapeEnd )
4246+ {
4247+ // try to find the last vert in the next shape
4248+ nextShape ++ ;
4249+ if (nextShape < objShapeCount ) nextShapeEnd = objShapes [nextShape ].face_offset ;
4250+ else nextShapeEnd = objAttributes .num_face_num_verts ; // this is actually the total number of face verts in the file, not faces
4251+ newMesh = true;
41834252 }
4253+ // if this is a new material, we need to allocate a new mesh
4254+ if (lastMaterial != -1 && objAttributes .material_ids [faceId ] != lastMaterial ) newMesh = true;
4255+ lastMaterial = objAttributes .material_ids [faceId ];;
41844256
4185- // Init model materials
4186- if (materialCount > 0 ) ProcessMaterialsOBJ (model .materials , materials , materialCount );
4187- else model .materials [0 ] = LoadMaterialDefault (); // Set default material for the mesh
4257+ if (newMesh )
4258+ {
4259+ localMeshVertexCount = 0 ;
4260+ meshIndex ++ ;
4261+ }
41884262
4189- tinyobj_attrib_free ( & attrib ) ;
4190- tinyobj_shapes_free ( meshes , model . meshCount );
4191- tinyobj_materials_free ( materials , materialCount ) ;
4263+ int matId = 0 ;
4264+ if ( lastMaterial >= 0 && lastMaterial < ( int ) objMaterialCount )
4265+ matId = lastMaterial ;
41924266
4193- UnloadFileText ( fileText ) ;
4267+ model . meshMaterial [ meshIndex ] = matId ;
41944268
4195- // Restore current working directory
4196- if (CHDIR (currentDir ) != 0 )
4269+ for (int f = 0 ; f < objAttributes .face_num_verts [faceId ]; f ++ )
41974270 {
4198- TRACELOG (LOG_WARNING , "MODEL: [%s] Failed to change working directory" , currentDir );
4271+ int vertIndex = objAttributes .faces [faceVertIndex ].v_idx ;
4272+ int normalIndex = objAttributes .faces [faceVertIndex ].vn_idx ;
4273+ int texcordIndex = objAttributes .faces [faceVertIndex ].vt_idx ;
4274+
4275+ for (int i = 0 ; i < 3 ; i ++ )
4276+ model .meshes [meshIndex ].vertices [localMeshVertexCount * 3 + i ] = objAttributes .vertices [vertIndex * 3 + i ];
4277+
4278+ for (int i = 0 ; i < 3 ; i ++ )
4279+ model .meshes [meshIndex ].normals [localMeshVertexCount * 3 + i ] = objAttributes .normals [normalIndex * 3 + i ];
4280+
4281+ for (int i = 0 ; i < 2 ; i ++ )
4282+ model .meshes [meshIndex ].texcoords [localMeshVertexCount * 2 + i ] = objAttributes .texcoords [texcordIndex * 2 + i ];
4283+
4284+ model .meshes [meshIndex ].texcoords [localMeshVertexCount * 2 + 1 ] = 1.0f - model .meshes [meshIndex ].texcoords [localMeshVertexCount * 2 + 1 ];
4285+
4286+ for (int i = 0 ; i < 4 ; i ++ )
4287+ model .meshes [meshIndex ].colors [localMeshVertexCount * 4 + i ] = 255 ;
4288+
4289+ faceVertIndex ++ ;
4290+ localMeshVertexCount ++ ;
41994291 }
42004292 }
42014293
4294+ if (objMaterialCount > 0 ) ProcessMaterialsOBJ (model .materials , objMaterials , objMaterialCount );
4295+ else model .materials [0 ] = LoadMaterialDefault (); // Set default material for the mesh
4296+
4297+ tinyobj_attrib_free (& objAttributes );
4298+ tinyobj_shapes_free (objShapes , objShapeCount );
4299+ tinyobj_materials_free (objMaterials , objMaterialCount );
4300+
4301+ for (int i = 0 ; i < model .meshCount ; i ++ )
4302+ UploadMesh (model .meshes + i , true);
4303+
4304+ // Restore current working directory
4305+ if (CHDIR (currentDir ) != 0 )
4306+ {
4307+ TRACELOG (LOG_WARNING , "MODEL: [%s] Failed to change working directory" , currentDir );
4308+ }
4309+
42024310 return model ;
42034311}
42044312#endif
0 commit comments