/** * \file obj_exporter.c * \date 27/07/2022 * \author JackCarterSmith * \copyright GPL-v3.0 * \brief Export datas to Waveform OBJ format. */ #include #include #include #include "options.h" #include #include #include "rlk/obj.h" #include "obj_exporter.h" static void mtlPathPatch(const char* out_file, const char* obj_name) ; unsigned char exportOBJModel(T_RSPMODEL_OBJECT* hob_objects, const char *out_path, T_PROG_OPTIONS* p_opts) { char objExport_path[128]; char mtlExport_path[128]; obj* objConstruct = NULL; unsigned int i,j; int surfID = 0, materialID = 0, tmpVertex = 0, tmpIndex = 0; int indexOffset = 0; // Used to compensate reset of indices between face group float vertexBuff[3] = {0}, textureBuff[2] = {0}; int indicesBuff[3] = {0}; if (hob_objects == NULL || out_path == NULL) return RSPLIB_ERROR_ARGS_NULL; if (p_opts->output_dir) { strcpy(objExport_path, out_path); #ifdef _WIN32 strcat(objExport_path, "-out\\"); #else strcat(objExport_path, "-out/"); #endif strcat(objExport_path, hob_objects->name); } else { strcpy(objExport_path, hob_objects->name); } strcpy(mtlExport_path, objExport_path); strcat(objExport_path, ".obj\0"); strcat(mtlExport_path, ".mtl\0"); objConstruct = obj_create(NULL); // Build face/surface material group for ( i = 0; i < hob_objects->face_group_count; i++) { surfID = obj_add_surf(objConstruct); materialID = obj_add_mtrl(objConstruct); // Build vertex container for ( j = 0; j < hob_objects->object_parts[i].vertex_count; j++ ) { tmpVertex = obj_add_vert(objConstruct); vertexBuff[0] = ((float)1/1024) * -hob_objects->object_parts[i].vertices[j].x; // Invert X to fix mirror display vertexBuff[1] = ((float)1/1024) * -hob_objects->object_parts[i].vertices[j].y; // Invert Y to render upside up vertexBuff[2] = ((float)1/1024) * hob_objects->object_parts[i].vertices[j].z; obj_set_vert_v(objConstruct, tmpVertex, vertexBuff); } // Build indices container and UV mapping for ( j = 0; j < hob_objects->object_parts[i].face_count; j++ ) { tmpIndex = obj_add_poly(objConstruct, surfID); indicesBuff[0] = indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[0]; indicesBuff[1] = indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[1]; indicesBuff[2] = indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[2]; obj_set_poly(objConstruct, surfID, tmpIndex, indicesBuff); if (hob_objects->object_parts[i].faces[j].flags_bits.fHasTexture) { textureBuff[0] = ((float)1/4096) * hob_objects->object_parts[i].faces[j].tex_coords[0].u; textureBuff[1] = ((float)1/4096) * hob_objects->object_parts[i].faces[j].tex_coords[0].v; obj_set_vert_t(objConstruct, indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[0], textureBuff); textureBuff[0] = ((float)1/4096) * hob_objects->object_parts[i].faces[j].tex_coords[1].u; textureBuff[1] = ((float)1/4096) * hob_objects->object_parts[i].faces[j].tex_coords[1].v; obj_set_vert_t(objConstruct, indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[1], textureBuff); textureBuff[0] = ((float)1/4096) * hob_objects->object_parts[i].faces[j].tex_coords[2].u; textureBuff[1] = ((float)1/4096) * hob_objects->object_parts[i].faces[j].tex_coords[2].v; obj_set_vert_t(objConstruct, indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[2], textureBuff); } // Process 2 triangles if face is Quad if (hob_objects->object_parts[i].faces[j].flags_bits.fIsQuad) { tmpIndex = obj_add_poly(objConstruct, surfID); indicesBuff[0] = indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[0]; indicesBuff[1] = indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[2]; indicesBuff[2] = indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[3]; obj_set_poly(objConstruct, surfID, tmpIndex, indicesBuff); if (hob_objects->object_parts[i].faces[j].flags_bits.fHasTexture) { textureBuff[0] = ((float)1/4096) * hob_objects->object_parts[i].faces[j].tex_coords[3].u; textureBuff[1] = ((float)1/4096) * hob_objects->object_parts[i].faces[j].tex_coords[3].v; obj_set_vert_t(objConstruct, indexOffset + (int)hob_objects->object_parts[i].faces[j].indices[3], textureBuff); } } } indexOffset = obj_num_vert(objConstruct); } if (p_opts->export_mtl) { obj_write(objConstruct, objExport_path, mtlExport_path, 8); #if defined(__GNUC__) //TODO: review MSVC file management or include and rewrite obj lib? if (p_opts->output_dir) mtlPathPatch(objExport_path, hob_objects->name); #endif } else obj_write(objConstruct, objExport_path, NULL, 8); obj_delete(objConstruct); return NO_ERROR; } static void mtlPathPatch(const char* out_file, const char* obj_name) { FILE* obj = NULL; char* memFile = NULL; long fileSize,i,pos = 0,lines; char _path[128],b; obj = fopen(out_file, "r"); if ( obj != NULL ) { fseek(obj, 0, SEEK_END); fileSize = ftell(obj); fseek(obj, 0, SEEK_SET); // Find the end of first line for ( i = 0; i < fileSize + 1; i++) { b = (char)fgetc(obj); if (b == '\n') { if (pos == 0) pos = i; lines++; } } // Prepare mtl path for output strcpy(_path, obj_name); strcat(_path, ".mtl"); memFile = malloc(fileSize - (pos + lines)); if ( memFile != NULL ) { // Read the rest of file in memory fseek(obj, pos, SEEK_SET); fread(memFile, fileSize - (pos + lines), 1, obj); fclose(obj); // Begin rewrite file obj = fopen(out_file, "w"); fprintf(obj, "mtllib %s", _path); #if defined(_MSC_VER) fwrite(memFile, fileSize - pos , 1, obj); #elif defined(__GNUC__) fwrite(memFile, fileSize - (pos + lines), 1, obj); #endif free(memFile); } fclose(obj); } }