Further improvemens to texture resolution. (#16)

* Further improvemens to texture resolution.

- Move towards std::string over char * and FbxString where convenient,
- Make a clear distinction between textures whose image files have been
  located and those who haven't; warn early in the latter case.
- Extend RawTexture so we always know logical name in FBX, original file
  name in FBX, and inferred location in local filesystem.
- In non-binary mode, simply output the inferred local file basename as
  the URI; this will be the correct relative path as long as the texture
  files are located next to the .gltf and .bin files.

Primary remaining urge for a follow-up PR:

- We should be copying texture image files into the .gltf output folder,
  but before that we should switch to an off-the-shelf cross-platform
  file manipulation library like https://github.com/cginternals/cppfs.
  When we make that transition, all this texture resolution code will
  undergo another refactoring.
This commit is contained in:
Pär Winzell 2017-10-20 09:42:39 -07:00 committed by GitHub
parent 946f12361c
commit 8cf7f446b7
10 changed files with 125 additions and 95 deletions

View File

@ -106,7 +106,7 @@ class FbxMaterialAccess
private:
const FbxSurfaceMaterial *fbxMaterial;
const std::map<const FbxTexture *, FbxString> &textureNames;
const std::map<const FbxTexture *, FbxString> &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<FbxFileTexture>();
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<FbxFileTexture>();
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<FbxFileTexture>();
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<FbxDouble3>();
}
FbxFileTexture *facTex = facProp.GetSrcObject<FbxFileTexture>();
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<const FbxTexture *, FbxString> &textureNames) :
FbxMaterialsAccess(const FbxMesh *pMesh, const std::map<const FbxTexture *, FbxString> &textureLocations) :
mappingMode(FbxGeometryElement::eNone),
mesh(nullptr),
indices(nullptr)
@ -272,7 +272,7 @@ public:
if (summary == nullptr) {
summary = summaries[materialNum] = std::make_shared<FbxMaterialAccess>(
mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(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<const FbxTexture *, FbxString> &textureNames)
static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std::map<const FbxTexture *, FbxString> &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<FbxVector2> uvLayer0(pMesh->GetElementUV(0), pMesh->GetElementUVCount());
const FbxLayerElementAccess<FbxVector2> 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<const FbxTexture *, FbxString> &textureNames)
static void ReadNodeAttributes(
RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std::map<const FbxTexture *, FbxString> &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<std::string> &directoryFileList)
static std::string GetInferredFileName(const std::string &fbxFileName, const std::string &directory, const std::vector<std::string> &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<const FbxTexture *, FbxString> &textureNames)
FindFbxTextures(
FbxScene *pScene, const char *fbxFileName, const char *extensions, std::map<const FbxTexture *, FbxString> &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<std::string> fileList;
FileUtils::ListFolderFiles(fileList, searchFolder, extensions);
std::vector<std::string> 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<FbxFileTexture>(pTexture);
const FbxFileTexture *pFileTexture = FbxCast<FbxFileTexture>(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<const FbxTexture *, FbxString> textureNames;
FindFbxTextures(pScene, fbxFileName, textureExtensions, textureNames);
std::map<const FbxTexture *, FbxString> 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();

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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 }
};
}

View File

@ -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;
}

View File

@ -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<std::string> &fileList, const char *folder, const char *matchExtensions)
std::vector<std::string> ListFolderFiles(const char *folder, const char *matchExtensions)
{
std::vector<std::string> 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)

View File

@ -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<std::string> &fileList, const char *folder, const char *matchExtensions);
std::vector<std::string> ListFolderFiles(const char *folder, const char *matchExtensions);
bool CreatePath(const char *path);
}

View File

@ -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__

View File

@ -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);