diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index 6ccc94f..761ab07 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -106,7 +106,7 @@ class FbxMaterialAccess private: const FbxSurfaceMaterial *fbxMaterial; - const std::map &textureNames; + const std::map &textureLocations; public: const FbxString name; @@ -119,7 +119,7 @@ public: fbxMaterial(fbxMaterial), name(fbxMaterial->GetName()), shadingModel(fbxMaterial->ShadingModel), - textureNames(textureNames), + textureLocations(textureNames), props(extractTextures()) {} @@ -180,7 +180,7 @@ public: FbxDouble val(0); FbxFileTexture *tex = prop.GetSrcObject(); - if (tex != nullptr && textureNames.find(tex) == textureNames.end()) { + if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) { tex = nullptr; } if (tex == nullptr && prop.IsValid()) { @@ -195,7 +195,7 @@ public: FbxDouble3 val(1, 1, 1); FbxFileTexture *tex = prop.GetSrcObject(); - if (tex != nullptr && textureNames.find(tex) == textureNames.end()) { + if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) { tex = nullptr; } if (tex == nullptr && prop.IsValid()) { @@ -213,14 +213,14 @@ public: FbxDouble factorVal(1); FbxFileTexture *colTex = colProp.GetSrcObject(); - if (colTex != nullptr && textureNames.find(colTex) == textureNames.end()) { + if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) { colTex = nullptr; } if (colTex == nullptr && colProp.IsValid()) { colorVal = colProp.Get(); } FbxFileTexture *facTex = facProp.GetSrcObject(); - if (facTex != nullptr && textureNames.find(facTex) == textureNames.end()) { + if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) { facTex = nullptr; } if (facTex == nullptr && facProp.IsValid()) { @@ -240,7 +240,7 @@ class FbxMaterialsAccess { public: - FbxMaterialsAccess(const FbxMesh *pMesh, const std::map &textureNames) : + FbxMaterialsAccess(const FbxMesh *pMesh, const std::map &textureLocations) : mappingMode(FbxGeometryElement::eNone), mesh(nullptr), indices(nullptr) @@ -272,7 +272,7 @@ public: if (summary == nullptr) { summary = summaries[materialNum] = std::make_shared( mesh->GetNode()->GetSrcObject(materialNum), - textureNames); + textureLocations); } } } @@ -473,7 +473,7 @@ GetMaterialType(const RawModel &raw, const int textures[RAW_TEXTURE_USAGE_MAX], return skinned ? RAW_MATERIAL_TYPE_SKINNED_OPAQUE : RAW_MATERIAL_TYPE_OPAQUE; } -static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std::map &textureNames) +static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std::map &textureLocations) { FbxGeometryConverter meshConverter(pScene->GetFbxManager()); meshConverter.Triangulate(pNode->GetNodeAttribute(), true); @@ -490,7 +490,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: const FbxLayerElementAccess uvLayer0(pMesh->GetElementUV(0), pMesh->GetElementUVCount()); const FbxLayerElementAccess uvLayer1(pMesh->GetElementUV(1), pMesh->GetElementUVCount()); const FbxSkinningAccess skinning(pMesh, pScene, pNode); - const FbxMaterialsAccess materials(pMesh, textureNames); + const FbxMaterialsAccess materials(pMesh, textureLocations); if (verboseOutput) { fmt::printf( @@ -562,9 +562,9 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: const auto maybeAddTexture = [&](FbxFileTexture *tex, RawTextureUsage usage) { if (tex != nullptr) { - // dig out the inferred filename from the textureNames map - const char *inferredPath = textureNames.find(tex)->second; - textures[usage] = raw.AddTexture(tex->GetName(), inferredPath, usage); + // dig out the inferred filename from the textureLocations map + FbxString inferredPath = textureLocations.find(tex)->second; + textures[usage] = raw.AddTexture(tex->GetName(), tex->GetFileName(), inferredPath.Buffer(), usage); } }; @@ -708,7 +708,8 @@ static void ReadCamera(RawModel &raw, FbxScene *pScene, FbxNode *pNode) } } -static void ReadNodeAttributes(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std::map &textureNames) +static void ReadNodeAttributes( + RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std::map &textureLocations) { if (!pNode->GetVisibility()) { return; @@ -723,7 +724,7 @@ static void ReadNodeAttributes(RawModel &raw, FbxScene *pScene, FbxNode *pNode, case FbxNodeAttribute::eNurbsSurface: case FbxNodeAttribute::eTrimNurbsSurface: case FbxNodeAttribute::ePatch: { - ReadMesh(raw, pScene, pNode, textureNames); + ReadMesh(raw, pScene, pNode, textureLocations); break; } case FbxNodeAttribute::eCamera: { @@ -752,7 +753,7 @@ static void ReadNodeAttributes(RawModel &raw, FbxScene *pScene, FbxNode *pNode, } for (int child = 0; child < pNode->GetChildCount(); child++) { - ReadNodeAttributes(raw, pScene, pNode->GetChild(child), textureNames); + ReadNodeAttributes(raw, pScene, pNode->GetChild(child), textureLocations); } } @@ -944,7 +945,7 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene) } } -static std::string GetInferredFileName(const char *fbxFileName, const char *directory, const std::vector &directoryFileList) +static std::string GetInferredFileName(const std::string &fbxFileName, const std::string &directory, const std::vector &directoryFileList) { // Get the file name with file extension. const std::string fileName = Gltf::StringUtils::GetFileNameString(Gltf::StringUtils::GetCleanPathString(fbxFileName)); @@ -956,25 +957,19 @@ static std::string GetInferredFileName(const char *fbxFileName, const char *dire } } - // Some FBX textures end with "_c.dds" while the source texture is a ".tga". - const bool isDDS = fileName.rfind("_c.dds") != std::string::npos; - // Get the file name without file extension. - const std::string fileBase = isDDS ? fileName.substr(0, fileName.length() - 6) : Gltf::StringUtils::GetFileBaseString(fileName); + const std::string fileBase = Gltf::StringUtils::GetFileBaseString(fileName); // Try to find a match without file extension. for (const auto &file : directoryFileList) { - const std::string listedFileBase = Gltf::StringUtils::GetFileBaseString(file.c_str()); - // If the two extension-less base names match. - if (Gltf::StringUtils::CompareNoCase(fileBase, listedFileBase) == 0) { + if (Gltf::StringUtils::CompareNoCase(fileBase, Gltf::StringUtils::GetFileBaseString(file)) == 0) { // Return the name with extension of the file in the directory. return std::string(directory) + file; } } - // Return the original file with extension - return fbxFileName; + return ""; } /* @@ -987,30 +982,34 @@ static std::string GetInferredFileName(const char *fbxFileName, const char *dire it to a list of existing texture files in the same directory as the FBX file. */ static void -FindFbxTextures(FbxScene *pScene, const char *fbxFileName, const char *extensions, std::map &textureNames) +FindFbxTextures( + FbxScene *pScene, const char *fbxFileName, const char *extensions, std::map &textureLocations) { // Get the folder the FBX file is in. - const FbxString folder = Gltf::StringUtils::GetFolderString(fbxFileName).c_str(); + const std::string folder = Gltf::StringUtils::GetFolderString(fbxFileName); // Check if there is a filename.fbm folder to which embedded textures were extracted. - const FbxString fbmFolderName = folder + Gltf::StringUtils::GetFileBaseString(fbxFileName).c_str() + ".fbm/"; + const std::string fbmFolderName = folder + Gltf::StringUtils::GetFileBaseString(fbxFileName) + ".fbm/"; // Search either in the folder with embedded textures or in the same folder as the FBX file. - const FbxString searchFolder = FileUtils::FolderExists(fbmFolderName) ? fbmFolderName : folder; + const std::string searchFolder = FileUtils::FolderExists(fbmFolderName) ? fbmFolderName : folder; // Get a list with all the texture files from either the folder with embedded textures or the same folder as the FBX file. - std::vector fileList; - FileUtils::ListFolderFiles(fileList, searchFolder, extensions); + std::vector fileList = FileUtils::ListFolderFiles(searchFolder.c_str(), extensions); // Try to match the FBX texture names with the actual files on disk. for (int i = 0; i < pScene->GetTextureCount(); i++) { - const FbxTexture *pTexture = pScene->GetTexture(i); - const FbxFileTexture *pFileTexture = FbxCast(pTexture); + const FbxFileTexture *pFileTexture = FbxCast(pScene->GetTexture(i)); if (pFileTexture == nullptr) { continue; } - const FbxString name = GetInferredFileName(pFileTexture->GetFileName(), searchFolder, fileList).c_str(); - textureNames.emplace(pTexture, name); + const std::string inferredName = GetInferredFileName(pFileTexture->GetFileName(), searchFolder, fileList); + if (inferredName.empty()) { + fmt::printf("Warning: could not find a local image file for texture: %s.\n" + "Original filename: %s\n", pFileTexture->GetName(), pFileTexture->GetFileName()); + } + // always extend the mapping, even for files we didn't find + textureLocations.emplace(pFileTexture, inferredName.c_str()); } } @@ -1041,8 +1040,8 @@ bool LoadFBXFile(RawModel &raw, const char *fbxFileName, const char *textureExte return false; } - std::map textureNames; - FindFbxTextures(pScene, fbxFileName, textureExtensions, textureNames); + std::map textureLocations; + FindFbxTextures(pScene, fbxFileName, textureExtensions, textureLocations); // Use Y up for glTF FbxAxisSystem::MayaYUp.ConvertScene(pScene); @@ -1054,7 +1053,7 @@ bool LoadFBXFile(RawModel &raw, const char *fbxFileName, const char *textureExte } ReadNodeHierarchy(raw, pScene, pScene->GetRootNode(), "", ""); - ReadNodeAttributes(raw, pScene, pScene->GetRootNode(), textureNames); + ReadNodeAttributes(raw, pScene, pScene->GetRootNode(), textureLocations); ReadAnimations(raw, pScene); pScene->Destroy(); diff --git a/src/Raw2Gltf.cpp b/src/Raw2Gltf.cpp index 3c8e759..0c65bbc 100644 --- a/src/Raw2Gltf.cpp +++ b/src/Raw2Gltf.cpp @@ -14,6 +14,7 @@ #include "FBX2glTF.h" #include "utils/String_Utils.h" +#include "utils/Image_Utils.h" #include "RawModel.h" #include "Raw2Gltf.h" @@ -360,27 +361,27 @@ ModelData *Raw2Gltf( // for (int textureIndex = 0; textureIndex < raw.GetTextureCount(); textureIndex++) { - const RawTexture &texture = raw.GetTexture(textureIndex); - const std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name); - const std::string texFilename = texture.fileName; + const RawTexture &texture = raw.GetTexture(textureIndex); + const std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name); + const std::string relativeFilename = Gltf::StringUtils::GetFileNameString(texture.fileLocation); ImageData *source = nullptr; if (options.outputBinary) { - auto bufferView = gltf->AddBufferViewForFile(buffer, texFilename); + auto bufferView = gltf->AddBufferViewForFile(buffer, texture.fileLocation); if (bufferView) { - source = new ImageData(textureName, *bufferView, "image/png"); + std::string suffix = Gltf::StringUtils::GetFileSuffixString(texture.fileLocation); + source = new ImageData(relativeFilename, *bufferView, suffixToMimeType(suffix)); } } else { - // TODO: don't add .ktx here; try to work out a reasonable relative path. - source = new ImageData(textureName, textureName + ".ktx"); + source = new ImageData(relativeFilename, relativeFilename); } if (!source) { // fallback is tiny transparent gif source = new ImageData(textureName, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="); } - TextureData &texDat = *gltf->textures.hold( + const TextureData &texDat = *gltf->textures.hold( new TextureData(textureName, defaultSampler, *gltf->images.hold(source))); assert(texDat.ix == textureIndex); } diff --git a/src/RawModel.cpp b/src/RawModel.cpp index d7231f1..5b625db 100644 --- a/src/RawModel.cpp +++ b/src/RawModel.cpp @@ -84,9 +84,9 @@ int RawModel::AddTriangle(const int v0, const int v1, const int v2, const int ma return (int) triangles.size() - 1; } -int RawModel::AddTexture(const char *name, const char *fileName, const RawTextureUsage usage) +int RawModel::AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage) { - if (name[0] == '\0') { + if (name.empty()) { return -1; } for (size_t i = 0; i < textures.size(); i++) { @@ -95,17 +95,18 @@ int RawModel::AddTexture(const char *name, const char *fileName, const RawTextur } } - const ImageProperties properties = GetImageProperties(fileName); + const ImageProperties properties = GetImageProperties(!fileLocation.empty() ? fileLocation.c_str() : fileName.c_str()); RawTexture texture; - texture.name = name; - texture.width = properties.width; - texture.height = properties.height; - texture.mipLevels = (int) ceilf(Log2f(std::max((float) properties.width, (float) properties.height))); - texture.usage = usage; - texture.occlusion = (properties.occlusion == IMAGE_TRANSPARENT) ? - RAW_TEXTURE_OCCLUSION_TRANSPARENT : RAW_TEXTURE_OCCLUSION_OPAQUE; - texture.fileName = fileName; + texture.name = name; + texture.width = properties.width; + texture.height = properties.height; + texture.mipLevels = (int) ceilf(Log2f(std::max((float) properties.width, (float) properties.height))); + texture.usage = usage; + texture.occlusion = (properties.occlusion == IMAGE_TRANSPARENT) ? + RAW_TEXTURE_OCCLUSION_TRANSPARENT : RAW_TEXTURE_OCCLUSION_OPAQUE; + texture.fileName = fileName; + texture.fileLocation = fileLocation; textures.emplace_back(texture); return (int) textures.size() - 1; } @@ -312,9 +313,9 @@ void RawModel::Condense() for (auto &material : materials) { for (int j = 0; j < RAW_TEXTURE_USAGE_MAX; j++) { if (material.textures[j] >= 0) { - const RawTexture &texture = oldTextures[material.textures[j]]; - const int textureIndex = AddTexture(texture.name.c_str(), texture.fileName.c_str(), texture.usage); - textures[textureIndex] = texture; + const RawTexture &texture = oldTextures[material.textures[j]]; + const int textureIndex = AddTexture(texture.name, texture.fileName, texture.fileLocation, texture.usage); + textures[textureIndex] = texture; material.textures[j] = textureIndex; } } diff --git a/src/RawModel.h b/src/RawModel.h index 2da08eb..1dae913 100644 --- a/src/RawModel.h +++ b/src/RawModel.h @@ -122,13 +122,14 @@ enum RawTextureOcclusion struct RawTexture { - std::string name; + std::string name; // logical name in FBX file int width; int height; int mipLevels; RawTextureUsage usage; RawTextureOcclusion occlusion; - std::string fileName; + std::string fileName; // original filename in FBX file + std::string fileLocation; // inferred path in local filesystem, or "" }; enum RawMaterialType @@ -233,7 +234,7 @@ public: void AddVertexAttribute(const RawVertexAttribute attrib); int AddVertex(const RawVertex &vertex); int AddTriangle(const int v0, const int v1, const int v2, const int materialIndex, const int surfaceIndex); - int AddTexture(const char *name, const char *fileName, const RawTextureUsage usage); + int AddTexture(const std::string &name, const std::string &fileName, const std::string &fileLocation, RawTextureUsage usage); int AddMaterial(const RawMaterial &material); int AddMaterial( const char *name, const char *shadingModel, RawMaterialType materialType, diff --git a/src/glTF/ImageData.cpp b/src/glTF/ImageData.cpp index 98a7555..e68a38e 100644 --- a/src/glTF/ImageData.cpp +++ b/src/glTF/ImageData.cpp @@ -31,8 +31,9 @@ ImageData::ImageData(std::string name, const BufferViewData &bufferView, std::st json ImageData::serialize() const { - if (mimeType.empty()) { + if (bufferView < 0) { return { + { "name", name }, { "uri", uri } }; } diff --git a/src/main.cpp b/src/main.cpp index eb2d324..d5002e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -182,7 +182,7 @@ Copyright (c) 2016-2017 Oculus VR, LLC. if (verboseOutput) { fmt::printf("Loading FBX File: %s\n", inputPath); } - if (!LoadFBXFile(raw, inputPath.c_str(), "tga;bmp;png;jpg")) { + if (!LoadFBXFile(raw, inputPath.c_str(), "png;jpg;jpeg")) { fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath); return 1; } diff --git a/src/utils/File_Utils.cpp b/src/utils/File_Utils.cpp index 89cce98..bb4eddb 100644 --- a/src/utils/File_Utils.cpp +++ b/src/utils/File_Utils.cpp @@ -54,17 +54,17 @@ namespace FileUtils { return std::string(cwd); } - bool FolderExists(const char *folderPath) + bool FolderExists(const std::string &folderPath) { #if defined( __unix__ ) || defined( __APPLE__ ) - DIR *dir = opendir(folderPath); + DIR *dir = opendir(folderPath.c_str()); if (dir) { closedir(dir); return true; } return false; #else - const DWORD ftyp = GetFileAttributesA( folderPath ); + const DWORD ftyp = GetFileAttributesA( folderPath.c_str() ); if ( ftyp == INVALID_FILE_ATTRIBUTES ) { return false; // bad path @@ -97,34 +97,34 @@ namespace FileUtils { return false; } - void ListFolderFiles(std::vector &fileList, const char *folder, const char *matchExtensions) + std::vector ListFolderFiles(const char *folder, const char *matchExtensions) { + std::vector fileList; #if defined( __unix__ ) || defined( __APPLE__ ) DIR *dir = opendir(folder); - if (dir == nullptr) { - return; + if (dir != nullptr) { + for (;;) { + struct dirent *dp = readdir(dir); + if (dp == nullptr) { + break; + } + + if (dp->d_type == DT_DIR) { + continue; + } + + const char *fileName = dp->d_name; + const char *fileExt = strrchr(fileName, '.'); + + if (!fileExt || !MatchExtension(fileExt, matchExtensions)) { + continue; + } + + fileList.emplace_back(fileName); + } + + closedir(dir); } - for (;;) { - struct dirent *dp = readdir(dir); - if (dp == nullptr) { - break; - } - - if (dp->d_type == DT_DIR) { - continue; - } - - const char *fileName = dp->d_name; - const char *fileExt = strrchr(fileName, '.'); - - if (!fileExt || !MatchExtension(fileExt, matchExtensions)) { - continue; - } - - fileList.emplace_back(fileName); - } - - closedir(dir); #else std::string pathStr = folder; pathStr += "*"; @@ -148,6 +148,7 @@ namespace FileUtils { FindClose( hFind ); } #endif + return fileList; } bool CreatePath(const char *path) diff --git a/src/utils/File_Utils.h b/src/utils/File_Utils.h index 770ab13..97ee358 100644 --- a/src/utils/File_Utils.h +++ b/src/utils/File_Utils.h @@ -13,10 +13,10 @@ namespace FileUtils { std::string GetCurrentFolder(); - bool FolderExists(const char *folderPath); + bool FolderExists(const std::string &folderPath); bool MatchExtension(const char *fileExtension, const char *matchExtensions); - void ListFolderFiles(std::vector &fileList, const char *folder, const char *matchExtensions); + std::vector ListFolderFiles(const char *folder, const char *matchExtensions); bool CreatePath(const char *path); } diff --git a/src/utils/Image_Utils.h b/src/utils/Image_Utils.h index d1b3858..48cd6fb 100644 --- a/src/utils/Image_Utils.h +++ b/src/utils/Image_Utils.h @@ -26,4 +26,20 @@ struct ImageProperties ImageProperties GetImageProperties(char const *filePath); +/** + * Very simple method for mapping filename suffix to mime type. The glTF 2.0 spec only accepts values + * "image/jpeg" and "image/png" so we don't need to get too fancy. + */ +inline std::string suffixToMimeType(std::string suffix) { + std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower); + + if (suffix == "jpg" || suffix == "jpeg") { + return "image/jpeg"; + } + if (suffix == "png") { + return "image/png"; + } + return "image/unknown"; +} + #endif // !__IMAGE_UTILS_H__ diff --git a/src/utils/String_Utils.h b/src/utils/String_Utils.h index 5c7c750..322add6 100644 --- a/src/utils/String_Utils.h +++ b/src/utils/String_Utils.h @@ -73,6 +73,16 @@ namespace Gltf // TODO replace return fileName.substr(0, fileName.rfind('.')).c_str(); } + inline const std::string GetFileSuffixString(const std::string &path) + { + const std::string fileName = GetFileNameString(path); + unsigned long pos = fileName.rfind('.'); + if (pos == std::string::npos) { + return ""; + } + return fileName.substr(++pos); + } + inline int CompareNoCase(const std::string &s1, const std::string &s2) { return strncasecmp(s1.c_str(), s2.c_str(), MAX_PATH_LENGTH);