Merge pull request #10116 from birarda/bug/rel-ext-textures

fix relative/absolute path handling for external FBX textures
This commit is contained in:
Chris Collins 2017-04-06 15:58:02 -07:00 committed by GitHub
commit 4e9e3cccea
6 changed files with 57 additions and 39 deletions

View file

@ -74,7 +74,7 @@ void AnimationReader::run() {
// Parse the FBX directly from the QNetworkReply // Parse the FBX directly from the QNetworkReply
FBXGeometry::Pointer fbxgeo; FBXGeometry::Pointer fbxgeo;
if (_url.path().toLower().endsWith(".fbx")) { if (_url.path().toLower().endsWith(".fbx")) {
fbxgeo.reset(readFBX(_data, QVariantHash(), _url.path())); fbxgeo.reset(readFBX(_data, QVariantHash(), _url));
} else { } else {
QString errorStr("usupported format"); QString errorStr("usupported format");
emit onError(299, errorStr); emit onError(299, errorStr);

View file

@ -376,10 +376,10 @@ public:
}; };
bool checkMaterialsHaveTextures(const QHash<QString, FBXMaterial>& materials, bool checkMaterialsHaveTextures(const QHash<QString, FBXMaterial>& materials,
const QHash<QString, QByteArray>& textureFilenames, const QMultiMap<QString, QString>& _connectionChildMap) { const QHash<QString, QByteArray>& textureFilepaths, const QMultiMap<QString, QString>& _connectionChildMap) {
foreach (const QString& materialID, materials.keys()) { foreach (const QString& materialID, materials.keys()) {
foreach (const QString& childID, _connectionChildMap.values(materialID)) { foreach (const QString& childID, _connectionChildMap.values(materialID)) {
if (textureFilenames.contains(childID)) { if (textureFilepaths.contains(childID)) {
return true; return true;
} }
} }
@ -443,21 +443,48 @@ FBXLight extractLight(const FBXNode& object) {
return light; return light;
} }
QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { QByteArray fixedTextureFilepath(QByteArray fbxRelativeFilepath, QUrl url) {
QString path = QFileInfo(url).path(); // first setup a QFileInfo for the passed relative filepath, with backslashes replaced by forward slashes
QByteArray filename = filepath; auto fileInfo = QFileInfo { fbxRelativeFilepath.replace("\\", "/") };
QFileInfo checkFile(path + "/" + filepath);
// check if the file exists at the RelativeFilename #ifndef Q_OS_WIN
if (!(checkFile.exists() && checkFile.isFile())) { // it turns out that absolute windows paths starting with drive letters look like relative paths to QFileInfo on UNIX
// if not, assume it is in the fbx directory // so we add a check for that here to work around it
filename = filename.mid(filename.lastIndexOf('/') + 1); bool isRelative = fbxRelativeFilepath[1] != ':' && fileInfo.isRelative();
#else
bool isRelative = fileInfo.isRelative();
#endif
if (isRelative) {
// the RelativeFilename pulled from the FBX is already correctly relative
// so simply return this as the filepath to use
return fbxRelativeFilepath;
} else {
// the RelativeFilename pulled from the FBX is an absolute path
// use the URL to figure out where the FBX is being loaded from
auto filename = fileInfo.fileName();
if (url.isLocalFile()) {
// the FBX is being loaded from the local filesystem
if (fileInfo.exists() && fileInfo.isFile()) {
// found a file at the absolute path in the FBX, return that path
return fbxRelativeFilepath;
} else {
// didn't find a file at the absolute path, assume it is right beside the FBX
// return just the filename as the relative path
return filename.toUtf8();
}
} else {
// this is a remote file, meaning we can't really do anything with the absolute path to the texture
// so assume it will be right beside the fbx
return filename.toUtf8();
}
} }
return filename;
} }
FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QUrl& url) {
const FBXNode& node = _fbxNode; const FBXNode& node = _fbxNode;
QMap<QString, ExtractedMesh> meshes; QMap<QString, ExtractedMesh> meshes;
QHash<QString, QString> modelIDsToNames; QHash<QString, QString> modelIDsToNames;
@ -833,11 +860,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
const int MODEL_UV_SCALING_MIN_SIZE = 2; const int MODEL_UV_SCALING_MIN_SIZE = 2;
const int CROPPING_MIN_SIZE = 4; const int CROPPING_MIN_SIZE = 4;
if (subobject.name == "RelativeFilename" && subobject.properties.length() >= RELATIVE_FILENAME_MIN_SIZE) { if (subobject.name == "RelativeFilename" && subobject.properties.length() >= RELATIVE_FILENAME_MIN_SIZE) {
QByteArray filename = subobject.properties.at(0).toByteArray(); auto filepath = fixedTextureFilepath(subobject.properties.at(0).toByteArray(), url);
QByteArray filepath = filename.replace('\\', '/');
filename = fileOnUrl(filepath, url);
_textureFilepaths.insert(getID(object.properties), filepath); _textureFilepaths.insert(getID(object.properties), filepath);
_textureFilenames.insert(getID(object.properties), filename);
} else if (subobject.name == "TextureName" && subobject.properties.length() >= TEXTURE_NAME_MIN_SIZE) { } else if (subobject.name == "TextureName" && subobject.properties.length() >= TEXTURE_NAME_MIN_SIZE) {
// trim the name from the timestamp // trim the name from the timestamp
QString name = QString(subobject.properties.at(0).toByteArray()); QString name = QString(subobject.properties.at(0).toByteArray());
@ -930,7 +955,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
QByteArray content; QByteArray content;
foreach (const FBXNode& subobject, object.children) { foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "RelativeFilename") { if (subobject.name == "RelativeFilename") {
filepath= subobject.properties.at(0).toByteArray(); filepath = subobject.properties.at(0).toByteArray();
filepath = filepath.replace('\\', '/'); filepath = filepath.replace('\\', '/');
} else if (subobject.name == "Content" && !subobject.properties.isEmpty()) { } else if (subobject.name == "Content" && !subobject.properties.isEmpty()) {
@ -1502,7 +1527,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
geometry.materials = _fbxMaterials; geometry.materials = _fbxMaterials;
// see if any materials have texture children // see if any materials have texture children
bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, _textureFilenames, _connectionChildMap); bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, _textureFilepaths, _connectionChildMap);
for (QMap<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) { for (QMap<QString, ExtractedMesh>::iterator it = meshes.begin(); it != meshes.end(); it++) {
ExtractedMesh& extracted = it.value(); ExtractedMesh& extracted = it.value();
@ -1547,7 +1572,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
materialIndex++; materialIndex++;
} else if (_textureFilenames.contains(childID)) { } else if (_textureFilepaths.contains(childID)) {
FBXTexture texture = getTexture(childID); FBXTexture texture = getTexture(childID);
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
int partTexture = extracted.partMaterialTextures.at(j).second; int partTexture = extracted.partMaterialTextures.at(j).second;
@ -1818,13 +1843,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
return geometryPtr; return geometryPtr;
} }
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) {
QBuffer buffer(const_cast<QByteArray*>(&model)); QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly); buffer.open(QIODevice::ReadOnly);
return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel); return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel);
} }
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) {
FBXReader reader; FBXReader reader;
reader._fbxNode = FBXReader::parseFBX(device); reader._fbxNode = FBXReader::parseFBX(device);
reader._loadLightmaps = loadLightmaps; reader._loadLightmaps = loadLightmaps;

View file

@ -268,7 +268,7 @@ class FBXGeometry {
public: public:
using Pointer = std::shared_ptr<FBXGeometry>; using Pointer = std::shared_ptr<FBXGeometry>;
QString originalURL; QUrl originalURL;
QString author; QString author;
QString applicationName; ///< the name of the application that generated the model QString applicationName; ///< the name of the application that generated the model
@ -330,11 +330,11 @@ Q_DECLARE_METATYPE(FBXGeometry::Pointer)
/// Reads FBX geometry from the supplied model and mapping data. /// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing /// \exception QString if an error occurs in parsing
FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QUrl& url = QUrl(), bool loadLightmaps = true, float lightmapLevel = 1.0f);
/// Reads FBX geometry from the supplied model and mapping data. /// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing /// \exception QString if an error occurs in parsing
FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QUrl& url = QUrl(), bool loadLightmaps = true, float lightmapLevel = 1.0f);
class TextureParam { class TextureParam {
public: public:
@ -402,19 +402,17 @@ public:
FBXNode _fbxNode; FBXNode _fbxNode;
static FBXNode parseFBX(QIODevice* device); static FBXNode parseFBX(QIODevice* device);
FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QString& url); FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QUrl& url);
ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex); ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex);
QHash<QString, ExtractedMesh> meshes; QHash<QString, ExtractedMesh> meshes;
static void buildModelMesh(FBXMesh& extractedMesh, const QString& url); static void buildModelMesh(FBXMesh& extractedMesh, const QUrl& url);
FBXTexture getTexture(const QString& textureID); FBXTexture getTexture(const QString& textureID);
QHash<QString, QString> _textureNames; QHash<QString, QString> _textureNames;
// Hashes the original RelativeFilename of textures // Hashes the original RelativeFilename of textures
QHash<QString, QByteArray> _textureFilepaths; QHash<QString, QByteArray> _textureFilepaths;
// Hashes the place to look for textures, in case they are not inlined
QHash<QString, QByteArray> _textureFilenames;
// Hashes texture content by filepath, in case they are inlined // Hashes texture content by filepath, in case they are inlined
QHash<QByteArray, QByteArray> _textureContent; QHash<QByteArray, QByteArray> _textureContent;
QHash<QString, TextureParam> _textureParams; QHash<QString, TextureParam> _textureParams;

View file

@ -85,12 +85,7 @@ FBXTexture FBXReader::getTexture(const QString& textureID) {
FBXTexture texture; FBXTexture texture;
const QByteArray& filepath = _textureFilepaths.value(textureID); const QByteArray& filepath = _textureFilepaths.value(textureID);
texture.content = _textureContent.value(filepath); texture.content = _textureContent.value(filepath);
texture.filename = filepath;
if (texture.content.isEmpty()) { // the content is not inlined
texture.filename = _textureFilenames.value(textureID);
} else { // use supplied filepath for inlined content
texture.filename = filepath;
}
texture.name = _textureNames.value(textureID); texture.name = _textureNames.value(textureID);
texture.transform.setIdentity(); texture.transform.setIdentity();
@ -155,7 +150,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) {
// FBX files generated by 3DSMax have an intermediate texture parent, apparently // FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach(const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) { foreach(const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) {
if (_textureFilenames.contains(childTextureID)) { if (_textureFilepaths.contains(childTextureID)) {
diffuseTexture = getTexture(diffuseTextureID); diffuseTexture = getTexture(diffuseTextureID);
} }
} }

View file

@ -388,7 +388,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn
return data.extracted; return data.extracted;
} }
void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QUrl& url) {
static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*"); static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*");
unsigned int totalSourceIndices = 0; unsigned int totalSourceIndices = 0;

View file

@ -173,7 +173,7 @@ void GeometryReader::run() {
FBXGeometry::Pointer fbxGeometry; FBXGeometry::Pointer fbxGeometry;
if (_url.path().toLower().endsWith(".fbx")) { if (_url.path().toLower().endsWith(".fbx")) {
fbxGeometry.reset(readFBX(_data, _mapping, _url.path())); fbxGeometry.reset(readFBX(_data, _mapping, _url));
if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) { if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) {
throw QString("empty geometry, possibly due to an unsupported FBX version"); throw QString("empty geometry, possibly due to an unsupported FBX version");
} }