diff --git a/models/mtllib-multiple-files-issue-112.mtl b/models/mtllib-multiple-files-issue-112.mtl new file mode 100644 index 0000000..5ebd668 --- /dev/null +++ b/models/mtllib-multiple-files-issue-112.mtl @@ -0,0 +1,6 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +map_Kd tmp.png + diff --git a/models/mtllib-multiple-files-issue-112.obj b/models/mtllib-multiple-files-issue-112.obj new file mode 100644 index 0000000..9966dfb --- /dev/null +++ b/models/mtllib-multiple-files-issue-112.obj @@ -0,0 +1,7 @@ +mtllib invalid-file-aaa.mtl invalid-file-bbb.mtl mtllib-multiple-files-issue-112.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/tests/tester.cc b/tests/tester.cc index b5ac70f..28d3871 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -530,6 +530,21 @@ TEST_CASE("texture_opts", "[Issue85]") { REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_BACK == materials[2].displacement_texopt.type); } +TEST_CASE("mtllib_multiple_filenames", "[Issue112]") { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/mtllib-multiple-files-issue-112.obj", gMtlBasePath); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == materials.size()); +} + #if 0 int main( diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index b975601..6947bbd 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -23,6 +23,7 @@ THE SOFTWARE. */ // +// version 1.0.4 : Support multiple filenames for 'mtllib'(#112) // version 1.0.3 : Support parsing texture options(#85) // version 1.0.2 : Improve parsing speed by about a factor of 2 for large // files(#105) @@ -935,6 +936,18 @@ static bool exportFaceGroupToShape( return true; } +// Split a string with specified delimiter character. +// http://stackoverflow.com/questions/236129/split-a-string-in-c +static void SplitString(const std::string &s, char delim, std::vector &elems) +{ + std::stringstream ss; + ss.str(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } +} + void LoadMtl(std::map *material_map, std::vector *materials, std::istream *inStream) { // Create a default material anyway. @@ -1287,15 +1300,17 @@ bool MaterialFileReader::operator()(const std::string &matId, } std::ifstream matIStream(filepath.c_str()); - LoadMtl(matMap, materials, &matIStream); if (!matIStream) { std::stringstream ss; ss << "WARN: Material file [ " << filepath - << " ] not found. Created a default material."; + << " ] not found." << std::endl; if (err) { (*err) += ss.str(); } + return false; } + + LoadMtl(matMap, materials, &matIStream); return true; } @@ -1304,15 +1319,16 @@ bool MaterialStreamReader::operator()(const std::string &matId, std::map *matMap, std::string *err) { (void)matId; - LoadMtl(matMap, materials, &m_inStream); if (!m_inStream) { std::stringstream ss; - ss << "WARN: Material stream in error state." - << " Created a default material."; + ss << "WARN: Material stream in error state. " << std::endl; if (err) { (*err) += ss.str(); } + return false; } + + LoadMtl(matMap, materials, &m_inStream); return true; } @@ -1482,23 +1498,37 @@ bool LoadObj(attrib_t *attrib, std::vector *shapes, // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { if (readMatFn) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif - std::string err_mtl; - bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } + std::vector filenames; + SplitString(std::string(token), ' ', filenames); - if (!ok) { - faceGroup.clear(); // for safety - return false; + if (filenames.empty()) { + if (err) { + (*err) += "WARN: Looks like empty filename for mtllib. Use default material. \n"; + } + } else { + + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), materials, &material_map, &err_mtl); + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; // This should be warn message. + } + + if (ok) { + found = true; + break; + } + } + + if (!found) { + if (err) { + (*err) += "WARN: Failed to load material file(s). Use default material.\n"; + } + } } } @@ -1774,28 +1804,44 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, // load mtl if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { if (readMatFn) { - char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; token += 7; -#ifdef _MSC_VER - sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); -#else - sscanf(token, "%s", namebuf); -#endif + token += 7; - std::string err_mtl; - materials.clear(); - bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl); - if (err) { - (*err) += err_mtl; - } + std::vector filenames; + SplitString(std::string(token), ' ', filenames); - if (!ok) { - return false; - } + if (filenames.empty()) { + if (err) { + (*err) += "WARN: Looks like empty filename for mtllib. Use default material. \n"; + } + } else { - if (callback.mtllib_cb) { - callback.mtllib_cb(user_data, &materials.at(0), - static_cast(materials.size())); + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), &materials, &material_map, &err_mtl); + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; // This should be warn message. + } + + if (ok) { + found = true; + break; + } + } + + if (!found) { + if (err) { + (*err) += "WARN: Failed to load material file(s). Use default material.\n"; + } + } else { + + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + } } }