diff --git a/src/user/user_flexcomp.cc b/src/user/user_flexcomp.cc index 9281402a..a669a550 100644 --- a/src/user/user_flexcomp.cc +++ b/src/user/user_flexcomp.cc @@ -900,7 +900,7 @@ bool mjCFlexcomp::MakeMesh(mjCModel* model, char* error, int error_sz) { } // load resource - std::string filename = mjuu_makefullname(mjs_getString(model->spec.modelfiledir), + std::string filename = mjuu_combinePaths(mjs_getString(model->spec.modelfiledir), mjs_getString(model->spec.meshdir), file); mjResource* resource = nullptr; @@ -999,7 +999,7 @@ bool mjCFlexcomp::MakeGMSH(mjCModel* model, char* error, int error_sz) { } // open resource - std::string filename = mjuu_makefullname(mjs_getString(model->spec.modelfiledir), + std::string filename = mjuu_combinePaths(mjs_getString(model->spec.modelfiledir), mjs_getString(model->spec.meshdir), file); mjResource* resource = nullptr; diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 39f9e18c..450174c0 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -368,7 +368,7 @@ void mjCMesh::Compile(const mjVFS* vfs) { throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - string filename = mjuu_makefullname(model->modelfiledir_, model->meshdir_, file_); + string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); mjResource* resource = LoadResource(filename, vfs); try { @@ -2091,7 +2091,7 @@ void mjCSkin::Compile(const mjVFS* vfs) { throw mjCError(this, "Unknown skin file type: %s", file_.c_str()); } - string filename = mjuu_makefullname(model->modelfiledir_, model->meshdir_, file_); + string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); mjResource* resource = LoadResource(filename, vfs); try { diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index bb92f68d..4260da3c 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -3105,7 +3105,7 @@ void mjCHField::Compile(const mjVFS* vfs) { throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - string filename = mjuu_makefullname(model->modelfiledir_, model->meshdir_, file_); + string filename = mjuu_combinePaths(model->modelfiledir_, model->meshdir_, file_); mjResource* resource = LoadResource(filename, vfs); try { @@ -3723,7 +3723,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { } // make filename - string filename = mjuu_makefullname(model->modelfiledir_, model->texturedir_, cubefiles_[i]); + string filename = mjuu_combinePaths(model->modelfiledir_, model->texturedir_, cubefiles_[i]); // load PNG or custom unsigned int w, h; @@ -3821,7 +3821,7 @@ void mjCTexture::Compile(const mjVFS* vfs) { } // make filename - string filename = mjuu_makefullname(model->modelfiledir_, model->texturedir_, file_); + string filename = mjuu_combinePaths(model->modelfiledir_, model->texturedir_, file_); // dispatch if (type==mjTEXTURE_2D) { diff --git a/src/user/user_util.cc b/src/user/user_util.cc index 652ab05b..4d640091 100644 --- a/src/user/user_util.cc +++ b/src/user/user_util.cc @@ -837,23 +837,30 @@ bool mjuu_isabspath(string path) { -// assemble full filename -string mjuu_makefullname(string filedir, string meshdir, string filename) { - // filename has absolute path: filename - if (mjuu_isabspath(filename)) { - return filename; +// assemble two file paths +std::string mjuu_combinePaths(const string& path1, const string& path2) { + // path2 has absolute path + if (mjuu_isabspath(path2)) { + return path2; } - // meshdir has absolute path: meshdir + filename - if (mjuu_isabspath(meshdir)) { - return meshdir + filename; + std::size_t n = path1.size(); + if (n > 0 && path1[n - 1] != '\\' && path1[n - 1] != '/') { + return path1 + "/" + path2; } - - // default - return filedir + meshdir + filename; + return path1 + path2; } + +// assemble three file paths +std::string mjuu_combinePaths(const string& path1, const string& path2, + const string& path3) { + return mjuu_combinePaths(path1, mjuu_combinePaths(path2, path3)); +} + + + // return true if the text is in a valid content type format: // {type}/{subtype}[;{parameter}={value}] static bool mjuu_isValidContentType(std::string_view text) { diff --git a/src/user/user_util.h b/src/user/user_util.h index 63bdc34d..c67a0a6a 100644 --- a/src/user/user_util.h +++ b/src/user/user_util.h @@ -167,8 +167,10 @@ std::string mjuu_getext(std::string_view filename); // check if path is absolute bool mjuu_isabspath(std::string path); -// assemble full filename -std::string mjuu_makefullname(std::string filedir, std::string meshdir, std::string filename); +// assemble file paths +std::string mjuu_combinePaths(const std::string& path1, const std::string& path2); +std::string mjuu_combinePaths(const std::string& path1, const std::string& path2, + const std::string& path3); // return type from content_type format {type}/{subtype}[;{parameter}={value}] std::optional mjuu_parseContentTypeAttrType(std::string_view text); diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 9547cac5..d5f20190 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -4120,56 +4120,36 @@ mjsDefault* mjXReader::GetClass(XMLElement* section) { return def; } - - - -// return true if c is a directory path separator (i.e. '/' or '\' on windows) -static bool IsSeperator(char c) { - return c == '/' || c == '\\'; -} - void mjXReader::SetModelFileDir(std::string modelfiledir) { modelfiledir_ = modelfiledir; - if (!modelfiledir_.empty() && !IsSeperator(modelfiledir_.back())) { - modelfiledir_.append("/"); - } } void mjXReader::SetAssetDir(std::string assetdir) { assetdir_ = assetdir; - if (!assetdir_.empty() && !IsSeperator(assetdir_.back())) { - assetdir_.append("/"); - } } void mjXReader::SetMeshDir(std::string meshdir) { meshdir_ = meshdir; - if (!meshdir_.empty() && !IsSeperator(meshdir_.back())) { - meshdir_.append("/"); - } } void mjXReader::SetTextureDir(std::string texturedir) { texturedir_ = texturedir; - if (!texturedir_.empty() && !IsSeperator(texturedir_.back())) { - texturedir_.append("/"); - } } std::string mjXReader::AssetDir() const { - return modelfiledir_ + assetdir_; + return mjuu_combinePaths(modelfiledir_, assetdir_); } std::string mjXReader::MeshDir() const { if (meshdir_.empty()) { return AssetDir(); } - return modelfiledir_ + meshdir_; + return mjuu_combinePaths(modelfiledir_, meshdir_); } std::string mjXReader::TextureDir() const { if (texturedir_.empty()) { return AssetDir(); } - return modelfiledir_ + texturedir_; + return mjuu_combinePaths(modelfiledir_, texturedir_); } diff --git a/src/xml/xml_util.cc b/src/xml/xml_util.cc index eec0dd39..6cc1f4fd 100644 --- a/src/xml/xml_util.cc +++ b/src/xml/xml_util.cc @@ -168,7 +168,7 @@ static std::string ResolveFilePath(XMLElement* e, std::string filename, // TODO(kylebayes): We first look in the base model directory for files to // remain backwards compatible. - std::string full_filename = dir + filename; + std::string full_filename = mjuu_combinePaths(dir, filename); mjResource *resource = mju_openResource(full_filename.c_str(), nullptr, 0); if (resource != nullptr) { mju_closeResource(resource); @@ -185,7 +185,7 @@ static std::string ResolveFilePath(XMLElement* e, std::string filename, break; } } - return path + filename; + return mjuu_combinePaths(path, filename); } // constructor diff --git a/test/xml/xml_native_reader_test.cc b/test/xml/xml_native_reader_test.cc index f71e66fa..06a3268b 100644 --- a/test/xml/xml_native_reader_test.cc +++ b/test/xml/xml_native_reader_test.cc @@ -906,6 +906,47 @@ TEST_F(XMLReaderTest, IncludeAbsoluteTest) { mj_deleteModel(model); } +TEST_F(XMLReaderTest, IncludeAbsoluteMeshDirTest) { + static constexpr char xml[] = R"( + + + + )"; + static constexpr char assets[] = R"( + + + + + + + )"; + + static constexpr char cube[] = R"( + v -0.500000 -0.500000 0.500000 + v 0.500000 -0.500000 0.500000 + v -0.500000 0.500000 0.500000 + v 0.500000 0.500000 0.500000 + v -0.500000 0.500000 -0.500000 + v 0.500000 0.500000 -0.500000 + v -0.500000 -0.500000 -0.500000 + v 0.500000 -0.500000 -0.500000)"; + + MockFilesystem fs("IncludeAbsoluteMeshDirTest"); + fs.AddFile("/assets/cube.obj", (const unsigned char*) cube, sizeof(cube)); + fs.AddFile("assets.xml", (const unsigned char*) assets, + sizeof(assets)); + fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml)); + std::string modelpath = fs.FullPath("model.xml"); + + std::array error; + // loading the file should be successful + mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, + error.data(), error.size()); + ASSERT_THAT(model, NotNull()) << "Failed to load model: " << error.data(); + + mj_deleteModel(model); +} + TEST_F(XMLReaderTest, ParsePolycoef) { static constexpr char xml[] = R"(