/* ----------------------------------------------------------------------------- GSFramework Copyright 2001-2013 Emmanuel Julien. All Rights Reserved. ----------------------------------------------------------------------------- */ #include "import_obj/import_obj.h" #include "scene3d/scene.h" #include "scene3d/mobject.h" #include "core/graphic_resource_factory.h" #include "core/geometry.h" #include "metafile/nml_object.h" #include "ascii/parser.h" #include "filesystem/filesystem.h" #include "filesystem/io_handle.h" #include "platform.h" using namespace GS; using namespace GS::Core; using namespace GS::S3D; using namespace GS::AsciiParser; //------------------------------------------------------------------------------ struct ObjPoly { List vtx, uv, nrm; uint mat; ObjPoly() : mat(0) {} }; struct ObjGroup { String name; List pol_list; void Free() { ListDeleteAllPtr(ObjPoly *, pol_list) } ~ObjGroup() { Free(); } }; struct ObjMtl { String name; String path; }; //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ static void ExportMaterialStage(NML::Tag *mtag, const String &texture, const char *channel, int index) { if (texture.IsEmpty()) return; if (NML::Tag *ttag = mtag->AddChild("TextureStage")) { ttag->AddChild("Active"); ttag->AddChild("Texture", texture); ttag->AddChild("Channel", channel); ttag->AddChild("Index", index); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool OBJImporter::LoadMaterialLibrary(List &mtl_list, const char *uri) { Array obj; if (!Platform::Get().io->FileLoad(uri, obj)) __ERR__(__LOG_E__ << "OBJ material library file '" << uri << "' not found.\n", false) // Now parse. const char *pobj = &obj[0], *eobj = pobj + obj.GetSize(); ObjMtl *current_mtl = NULL; String map_Ka, map_Kd, map_Ks, map_Ke; forever { String word(pobj, SkipEntry(pobj, eobj)); // Export material to file. if ((word == "newmtl") || (pobj >= eobj)) if (current_mtl) { current_mtl->path = String::Format("%s/%s.nmm", config->base_path.c_str(), current_mtl->name.c_str()); // Only export material if it does not exist. if (!Platform::Get().io->Exists(current_mtl->path)) { String name = Platform::Get().io->StripRootPath(current_mtl->path); NML::File file; if (NML::Tag *mtag = file.AddRoot("Material")) { mtag->AddChild("Id", name); ExportMaterialStage(mtag, map_Kd, "Diffuse", 0); ExportMaterialStage(mtag, map_Ka, "Opacity", 1); ExportMaterialStage(mtag, map_Ks, "Specular", 2); ExportMaterialStage(mtag, map_Ke, "Emissive", 3); if (NML::Tag *rtag = mtag->AddChild("RenderMask")) rtag->AddChild("NormalMapTangent"); } NML::Parser::Save(current_mtl->path, file); } } if (pobj >= eobj) break; // Create new material. if (word == "newmtl") { // Create new material. mtl_list.Add(current_mtl = new ObjMtl); pobj = NextEntry(pobj + 1, eobj); const char *eon = SkipEntry(pobj, eobj); current_mtl->name.Set(pobj, eon); pobj = NextEntry(eon, eobj, true); } else if (current_mtl) { //------------------------------------------------------------------ #define __ReadObjMaterialMap(_map) \ { \ pobj = NextEntry(pobj + 1, eobj); \ const char *eon = RunToEOL(pobj, eobj); \ _map.Set(pobj, eon); \ pobj = NextEntry(eon, eobj, true); \ } //------------------------------------------------------------------ if (word == "map_Ka") __ReadObjMaterialMap(map_Ka) else if (word == "map_Ks") __ReadObjMaterialMap(map_Ks) else if (word == "map_Kd") __ReadObjMaterialMap(map_Kd) else if (word == "map_Ke") __ReadObjMaterialMap(map_Ke) else pobj = NextEntry(RunToEOL(pobj, eobj), eobj); } else pobj = NextEntry(RunToEOL(pobj, eobj), eobj); } return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ bool OBJImporter::TestImport(const char *uri) { String ext = String::FileGetExtension(uri).Lower(); return ext == "obj"; } bool OBJImporter::ImportScene(Scene *scene, const char *uri, const Config &_config, Group **) { Array obj; if (!Platform::Get().io->FileLoad(uri, obj)) __ERR__(__LOG_E__ << "OBJ file '" << uri << "' not found.\n", false) // Now parse. config = &_config; if (config->event_handler) config->event_handler->OpenLoad(); const char *pobj = &obj[0], *eobj = pobj + obj.GetSize(); //-------------------------------------------------------------------------- { bool has_uv = false, has_vnm = false; AutoList vtx_list, vnm_list; AutoList uv_list; AutoList group_list; AutoList mat_list; ObjGroup *current_group = new ObjGroup; current_group->name = "default"; group_list.Add(current_group); int current_mat = 0; while (pobj < eobj) { String word(pobj, SkipEntry(pobj, eobj)); if (config->event_handler) config->event_handler->LoadProgress(String::Format("Parsing tag '%s'...", word.c_str()), (float)(pobj - &obj[0]) / obj.GetSize()); // Declare material library. if (word == "mtllib") { pobj = NextEntry(pobj + 1, eobj); const char *eon = RunToEOL(pobj, eobj); String lib_path = String(pobj, eon); if (!lib_path.IsAbsolutePath()) lib_path = String(uri).GetFilePath() + "/" + lib_path; LoadMaterialLibrary(mat_list, lib_path); pobj = NextEntry(eon, eobj, true); } // Use material. else if (word == "usemtl") { pobj = NextEntry(pobj + 1, eobj); const char *eon = SkipEntry(pobj, eobj); String name(pobj, eon); // Locate in library. current_mat = 0; ListForeachPtr(ObjMtl *, mat, mat_list) { if (mat->name == name) break; current_mat++; } pobj = NextEntry(eon, eobj, true); } // Append vertex. else if (word == "v") { // Parse vertex. pobj = NextEntry(pobj, eobj); float x = 0, y = 0, z = 0; x = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); y = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); z = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); vtx_list.Add(new Vector4(x * config->scale, y * config->scale, -z * config->scale)); } // Append vertex normal. else if (word == "vn") { pobj = NextEntry(pobj, eobj); float x = 0, y = 0, z = 0; x = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); y = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); z = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); // vnm_list.Add(new nVector(-y, -z, -x)); vnm_list.Add(new Vector4(x, y, -z)); } // Append UV. else if (word == "vt") { pobj = NextEntry(pobj, eobj); float u = 0, v = 0; u = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); v = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); uv_list.Add(new Vector2(u, 1.f - v)); } // Declare group. else if (word == "g") { group_list.Add(current_group = new ObjGroup); // Check for named group. const char *eol = RunToEOL(pobj, eobj); pobj = NextEntry(pobj + 1, eobj); if (pobj <= eol) { const char *eon = SkipEntry(pobj, eobj); current_group->name.Set(pobj, eon); pobj = NextEntry(eon, eobj, true); } else // no name pobj = NextEntry(eol, eobj, true); } // Build group polygons. else if (word == "f") { pobj = NextEntry(pobj, eobj); ObjPoly *pol = new ObjPoly; pol->mat = current_mat; const char *eol = RunToEOL(pobj, eobj); while (pobj < eol) { pol->vtx.Add(String::atoi(pobj)); // Index. pobj = NextEntry(pobj, eobj, true); if (pobj[0] == '/') { pobj++; if (pobj[0] != '/') { has_uv = true; pol->uv.Add(String::atoi(pobj)); // UV index. pobj = NextEntry(pobj, eobj, true); } } if (pobj[0] == '/') { pobj++; has_vnm = true; pol->nrm.Add(String::atoi(pobj)); // Normal index. pobj = NextEntry(pobj, eobj, true); } } if (pol->vtx.GetCount() < 3) delete pol; else current_group->pol_list.Add(pol); } else pobj = NextEntry(RunToEOL(pobj, eobj), eobj); } obj.Free(); //-------------------------------------------------------------------------- // Get flat UV array. Array flat_uv; if (has_uv && flat_uv.Allocate(uv_list.GetCount())) { Vector2 *pflat_uv = flat_uv; ListForeachPtr(Vector2 *, uv, uv_list) *pflat_uv++ = *uv; } // Get flat vertex normal array. Array flat_vnm; if (has_vnm && flat_vnm.Allocate(vnm_list.GetCount())) { Vector4 *pflat_vnm = flat_vnm; ListForeachPtr(Vector4 *, vnm, vnm_list) *pflat_vnm++ = *vnm; } //-------------------------------------------------------------------------- Array vtx_map(vtx_list.GetCount()), mat_map(mat_list.GetCount()); //-------------------------------------------------------------------------- // Build scene. int n_pg = 0; ListForeachPtr(ObjGroup *, group, group_list) { ++n_pg; if (!vtx_list.GetCount() || !group->pol_list.GetCount()) continue; if (config->event_handler) config->event_handler->LoadProgress(String::Format("Importing group '%s'...", group->name.toUtf8()), (float)(n_pg - 1) / group_list.GetCount()); // Create geometry. Geometry *geo = new Geometry; // Remap vertices. for (uint n = 0; n < vtx_map.GetCount(); ++n) vtx_map[n] = -1; // Total vertex count. uint vtx_count = 0; ListForeachPtr(ObjPoly *, p, group->pol_list) ListForeachPtr(int, i, p->vtx) if (vtx_map[i - 1] == -1) vtx_map[i - 1] = vtx_count++; // Transfer vertices. if (geo->vtx.Allocate(vtx_count)) { for (uint i = 0; i < vtx_list.GetCount(); ++i) if (vtx_map[i] != -1) geo->vtx[vtx_map[i]] = *vtx_list[i]; } else __LOG_E__ << "Failed to allocate " << geo->vtx.GetCount() << " vertices.\n"; // Transfer topology and attributes. if (geo->pol.Allocate(group->pol_list.GetCount())) { // Compute bind index count. uint binding_count = 0; ListForeachPtr(ObjPoly *, p, group->pol_list) binding_count += p->vtx.GetCount(); if (geo->binding.Allocate(binding_count)) { uint *pbind = &geo->binding[0]; // Allocate attributes. Vector2 *puv = NULL; if (has_uv) { geo->uv[0].Allocate(binding_count); puv = &geo->uv[0][0]; } Vector4 *pvnm = NULL; if (has_vnm) { geo->vtx_normal.Allocate(binding_count); pvnm = &geo->vtx_normal[0]; } // Transfer data. uint cpol = 0; ListForeachPtr(ObjPoly *, p, group->pol_list) { Polygon *pol = &geo->pol[cpol++]; pol->vtx_count = (ushort)p->vtx.GetCount(); pol->binding = pbind; pol->material = 0; for (int i = p->vtx.GetCount(); i > 0; --i) { int idx = p->vtx[i - 1] - 1; if (idx > (int)vtx_map.GetCount()) idx = 0; *pbind++ = vtx_map[idx]; } if (puv) for (int i = p->uv.GetCount(); i > 0; --i) *puv++ = flat_uv[p->uv[i - 1] - 1]; if (pvnm) for (int i = p->nrm.GetCount(); i > 0; --i) *pvnm++ = flat_vnm[p->nrm[i - 1] - 1]; } } else __LOG_E__ << "Failed to allocate " << geo->binding.GetCount() << " polygon binding entries.\n"; // Remap and assign materials. for (uint i = 0; i < mat_list.GetCount(); ++i) mat_map[i] = -1; int mat_count = 0; ListForeachPtr(ObjPoly *, p, group->pol_list) if (p->mat < mat_map.GetCount()) if (mat_map[p->mat] == -1) mat_map[p->mat] = mat_count++; uint cpol = 0; ListForeachPtr(ObjPoly *, p, group->pol_list) geo->pol[cpol++].material = (ushort)(p->mat < mat_map.GetCount() ? mat_map[p->mat] : 0); // Load materials. if (mat_list.GetCount()) { geo->material_table.Allocate(mat_count); for (uint i = 0; i < mat_list.GetCount(); ++i) if (mat_map[i] != -1) geo->material_table[mat_map[i]].name = mat_list[i]->path; } else geo->material_table.Allocate(1); // Vertex normal. if (!has_vnm) geo->ComputeVertexNormal(true); // Save geometry. geo->name = String::Format("%s/%s.nmg", config->base_path.c_str(), group->name.toUtf8()); NML::SaveToFile(*geo, geo->name); geo->name = Platform::Get().io->StripRootPath(geo->name); // Add to scene. MObject *object = new MObject; object->name = group->name; scene->AddItem(object, true); object->geometry = geo->name; // Ease up memory. group->Free(); } else __LOG_E__ << "Failed to allocate " << geo->pol.GetCount() << " polygons.\n"; } } //-------------------------------------------------------------------------- // Save scene. scene->name = String::Format("%s/scene.nms", config->base_path.c_str()); NML::SaveToFile(*scene, scene->name); scene->name = Platform::Get().io->StripRootPath(scene->name); if (config->event_handler) config->event_handler->EndLoad(); return true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ Geometry *OBJImporter::ImportGeometry(const char *uri, IResourceFactoryEvent *event_handler) { if (!uri) __ERR__(__LOG_E__ << "No URI to import.\n", NULL) AutoPtr geo(new Geometry); // Load file in memory. Array obj; if (!Platform::Get().io->FileLoad(uri, obj)) __ERR__(__LOG_E__ << "File '" << uri << "' not found.\n", NULL) // Now parse. List vtx_list; List uv_list; List pol_list; bool has_uv = false; const char *pobj = &obj[0], *eobj = pobj + obj.GetSize(); while (pobj < eobj) { String word(pobj, SkipEntry(pobj, eobj)); if (word == "v") { pobj = NextEntry(pobj, eobj); float x = 0, y = 0, z = 0; x = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); y = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); z = -String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); vtx_list.Add(new Vector4(x, y, z)); } else if (word == "vt") { pobj = NextEntry(pobj, eobj); float u = 0, v = 0; u = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); v = String::atof(pobj, eobj); pobj = NextEntry(pobj, eobj, true); uv_list.Add(new Vector2(u, v)); } else if (word == "f") { pobj = NextEntry(pobj, eobj); ObjPoly *pol = new ObjPoly; pol_list.Add(pol); const char *eol = RunToEOL(pobj, eobj); while (pobj < eol) { pol->vtx.Add(String::atoi(pobj)); // Index. pobj = NextEntry(pobj, eobj, true); if (pobj[0] == '/') { pobj++; if (pobj[0] != '/') { has_uv = true; pol->uv.Add(String::atoi(pobj)); // UV index. pobj = NextEntry(pobj, eobj, true); } } if (pobj[0] == '/') { pobj++; pol->nrm.Add(String::atoi(pobj)); // Normal index. pobj = NextEntry(pobj, eobj, true); } } } else pobj = NextEntry(RunToEOL(pobj, eobj), eobj); } // Drop OBJ. obj.Free(); // Transfer vertice. if (geo->vtx.Allocate(vtx_list.GetCount())) { uint cvtx = 0; ListForeachPtr(Vector4 *, v, vtx_list) geo->vtx[cvtx++] = *v; } else __LOG_E__ << "Failed to allocate " << vtx_list.GetCount() << " vertices.\n"; ListDeleteAllPtr(Vector4 *, vtx_list) // Transfer polygon. if (geo->pol.Allocate(pol_list.GetCount())) { uint binding_count = 0; ListForeachPtr(ObjPoly *, p, pol_list) binding_count += p->vtx.GetCount(); geo->binding.Allocate(binding_count); Array flat_uv; if (has_uv) if (flat_uv.Allocate(binding_count)) { geo->uv[0].Allocate(binding_count); Vector2 *pflat_uv = &flat_uv[0]; ListForeachPtr(Vector2 *, uv, uv_list) *pflat_uv++ = *uv; ListDeleteAllPtr(Vector2 *, uv_list) } Vector2 *puv = &geo->uv[0][0]; if (geo->binding) { uint *pbind = &geo->binding[0]; uint cpol = 0; ListForeachPtr(ObjPoly *, p, pol_list) { Polygon *pol = &geo->pol[cpol]; pol->vtx_count = (ushort)p->vtx.GetCount(); pol->binding = pbind; pol->material = 0; for (int v = p->vtx.GetCount(); v > 0; --v) *pbind++ = p->vtx.ObjectAt(v - 1) - 1; ListForeachPtr(int, i, p->uv) *puv++ = flat_uv[i - 1]; } } else __LOG_E__ << "Failed to allocate " << geo->binding.GetCount() << " polygon binding entries.\n"; } else __LOG_E__ << "Failed to allocate " << geo->pol.GetCount() << " polygons.\n"; ListDeleteAllPtr(ObjPoly *, pol_list) // Load materials. geo->material_table.Allocate(1); geo->material_table[0].name = "Default"; // Setup defaults. geo->ComputeVertexNormal(); return geo.Detach(); } //------------------------------------------------------------------------------