From 6161aab0c183c5d350a83ca711ce98b289090fec Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 15 Mar 2016 17:05:01 -0700 Subject: [PATCH 1/5] checkpoint --- libraries/fbx/src/OBJReader.cpp | 120 +++++++++++++++++++------------- libraries/fbx/src/OBJReader.h | 2 + 2 files changed, 72 insertions(+), 50 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index ee8a2a2fae..3cbac606b1 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -192,6 +192,9 @@ bool OBJReader::isValidTexture(const QByteArray &filename) { return isValid; } +//FIXME +#define WANT_DEBUG 1 + void OBJReader::parseMaterialLibrary(QIODevice* device) { OBJTokenizer tokenizer(device); QString matName = SMART_DEFAULT_MATERIAL_NAME; @@ -360,9 +363,12 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { break; } - currentMaterialName = tokenizer.getDatum(); + QString nextName = tokenizer.getDatum(); #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader new current material:" << currentMaterialName; + if (nextName != currentMaterialName) { + currentMaterialName = nextName; + qCDebug(modelformat) << "OBJ Reader new current material:" << currentMaterialName; + } #endif } else if (token == "v") { vertices.append(tokenizer.getVec3()); @@ -427,6 +433,8 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, OBJTokenizer tokenizer { &buffer }; float scaleGuess = 1.0f; + bool needsMaterialLibrary = false; + _url = url; geometry.meshExtents.reset(); geometry.meshes.append(FBXMesh()); @@ -459,55 +467,10 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, 0, 0, 0, 1); mesh.clusters.append(cluster); - // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use - // a texture with the same basename as the .obj file. - if (!url.isEmpty()) { - QString filename = url.fileName(); - int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail - QString basename = filename.remove(extIndex + 1, sizeof("obj")); - OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME]; - preDefinedMaterial.diffuseColor = glm::vec3(1.0f); - QVector extensions = {"jpg", "jpeg", "png", "tga"}; - QByteArray base = basename.toUtf8(), textName = ""; - for (int i = 0; i < extensions.count(); i++) { - QByteArray candidateString = base + extensions[i]; - if (isValidTexture(candidateString)) { - textName = candidateString; - break; - } - } - - if (!textName.isEmpty()) { - #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader found a default texture: " << textName; - #endif - preDefinedMaterial.diffuseTextureFilename = textName; - } - materials[SMART_DEFAULT_MATERIAL_NAME] = preDefinedMaterial; - } - for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) { FBXMeshPart& meshPart = mesh.parts[i]; FaceGroup faceGroup = faceGroups[meshPartCount]; - OBJFace leadFace = faceGroup[0]; // All the faces in the same group will have the same name and material. - QString groupMaterialName = leadFace.materialName; - if (groupMaterialName.isEmpty() && (leadFace.textureUVIndices.count() > 0)) { - #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader WARNING: " << url - << " needs a texture that isn't specified. Using default mechanism."; - #endif - groupMaterialName = SMART_DEFAULT_MATERIAL_NAME; - } else if (!groupMaterialName.isEmpty() && !materials.contains(groupMaterialName)) { - #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader WARNING: " << url - << " specifies a material " << groupMaterialName - << " that is not defined. Using default mechanism."; - #endif - groupMaterialName = SMART_DEFAULT_MATERIAL_NAME; - } - if (!groupMaterialName.isEmpty()) { - meshPart.materialID = groupMaterialName; - } + bool specifiesUV = false; foreach(OBJFace face, faceGroup) { glm::vec3 v0 = vertices[face.vertexIndices[0]]; glm::vec3 v1 = vertices[face.vertexIndices[1]]; @@ -529,6 +492,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, } mesh.normals << n0 << n1 << n2; if (face.textureUVIndices.count()) { + specifiesUV = true; mesh.texCoords << textureUVs[face.textureUVIndices[0]] << textureUVs[face.textureUVIndices[1]] @@ -538,6 +502,30 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, mesh.texCoords << corner << corner << corner; } } + // All the faces in the same group will have the same name and material. + OBJFace leadFace = faceGroup[0]; + QString groupMaterialName = leadFace.materialName; + if (groupMaterialName.isEmpty() && specifiesUV) { + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader WARNING: " << url + << " needs a texture that isn't specified. Using default mechanism."; + #endif + groupMaterialName = SMART_DEFAULT_MATERIAL_NAME; + } + if (!groupMaterialName.isEmpty()) { + OBJMaterial& material = materials[groupMaterialName]; + if (specifiesUV) { + material.userSpecifiesUV = true; // Note might not be true in a later usage. + } + if (specifiesUV || (0 != groupMaterialName.compare("none", Qt::CaseInsensitive))) { + // Blender has a convention that a material named "None" isn't really used (or defined). + needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME; + material.used = true; + } + materials[groupMaterialName] = material; + meshPart.materialID = groupMaterialName; + } + } // if we got a hint about units, scale all the points @@ -559,8 +547,41 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, qCDebug(modelformat) << "OBJ reader fail: " << e.what(); } + OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME]; + if (preDefinedMaterial.userSpecifiesUV && !url.isEmpty()) { + // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use + // a texture with the same basename as the .obj file. + QString filename = url.fileName(); + int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail + QString basename = filename.remove(extIndex + 1, sizeof("obj")); + preDefinedMaterial.diffuseColor = glm::vec3(1.0f); + QVector extensions = { "jpg", "jpeg", "png", "tga" }; + QByteArray base = basename.toUtf8(), textName = ""; + qCDebug(modelformat) << "OBJ Reader looking for default texture of" << url; + for (int i = 0; i < extensions.count(); i++) { + QByteArray candidateString = base + extensions[i]; + if (isValidTexture(candidateString)) { + textName = candidateString; + break; + } + } + + if (!textName.isEmpty()) { + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader found a default texture: " << textName; + #endif + preDefinedMaterial.diffuseTextureFilename = textName; + } + materials[SMART_DEFAULT_MATERIAL_NAME] = preDefinedMaterial; + } + foreach (QString materialID, materials.keys()) { - OBJMaterial& objMaterial = materials[materialID]; + OBJMaterial& objMaterial = materials[materialID]; + if (!objMaterial.used) { + qCDebug(modelformat) << "fixme skipping" << materialID; + continue; + assert(false); + } geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor, objMaterial.specularColor, glm::vec3(0.0f), @@ -591,7 +612,6 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, } - void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << "---------------- fbxGeometry ----------------"; qCDebug(modelformat) << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 0e59c5ad8a..200f11548d 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -56,6 +56,8 @@ public: glm::vec3 specularColor; QByteArray diffuseTextureFilename; QByteArray specularTextureFilename; + bool used { false }; + bool userSpecifiesUV { false }; OBJMaterial() : shininess(96.0f), opacity(1.0f), diffuseColor(1.0f), specularColor(1.0f) {} }; From c9f6b15ac5dd4d7fecc16980a7b694f76038b606 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 15 Mar 2016 18:35:59 -0700 Subject: [PATCH 2/5] checkpoint after lazy mtllib reading --- libraries/fbx/src/OBJReader.cpp | 47 ++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 3cbac606b1..d23f4e3b69 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -340,25 +340,8 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi break; } QByteArray libraryName = tokenizer.getDatum(); - if (librariesSeen.contains(libraryName)) { - break; // Some files use mtllib over and over again for the same libraryName - } librariesSeen[libraryName] = true; - // Throw away any path part of libraryName, and merge against original url. - QUrl libraryUrl = _url.resolved(QUrl(libraryName).fileName()); - #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader new library:" << libraryName << " at:" << libraryUrl; - #endif - QNetworkReply* netReply = request(libraryUrl, false); - if (netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200)) { - parseMaterialLibrary(netReply); - } else { - #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader " << libraryName << " did not answer. Got " - << netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); - #endif - } - netReply->deleteLater(); + // We'll read it later only if we actually need it. } else if (token == "usemtl") { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { break; @@ -519,8 +502,8 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, } if (specifiesUV || (0 != groupMaterialName.compare("none", Qt::CaseInsensitive))) { // Blender has a convention that a material named "None" isn't really used (or defined). - needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME; material.used = true; + needsMaterialLibrary = groupMaterialName != SMART_DEFAULT_MATERIAL_NAME; } materials[groupMaterialName] = material; meshPart.materialID = groupMaterialName; @@ -548,9 +531,9 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, } OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME]; + // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use + // a texture with the same basename as the .obj file. if (preDefinedMaterial.userSpecifiesUV && !url.isEmpty()) { - // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use - // a texture with the same basename as the .obj file. QString filename = url.fileName(); int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail QString basename = filename.remove(extIndex + 1, sizeof("obj")); @@ -574,6 +557,28 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, } materials[SMART_DEFAULT_MATERIAL_NAME] = preDefinedMaterial; } + if (needsMaterialLibrary) { + foreach (QString libraryName, librariesSeen.keys()) { + // Throw away any path part of libraryName, and merge against original url. + QUrl libraryUrl = _url.resolved(QUrl(libraryName).fileName()); + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader material library" << libraryName << "used in" << _url; + #endif + QNetworkReply* netReply = request(libraryUrl, false); + if (netReply->isFinished() && + (libraryUrl.toString().startsWith("file", Qt::CaseInsensitive) ? // file urls don't have http status codes + netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString().isEmpty() : + (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200))) { + parseMaterialLibrary(netReply); + } else { + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader" << libraryName << "did not answer. Got" + << netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + #endif + } + netReply->deleteLater(); + } + } foreach (QString materialID, materials.keys()) { OBJMaterial& objMaterial = materials[materialID]; From 76b5b6aeeb60bb76e2235e49c6cd1f3965d06a3a Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 15 Mar 2016 20:31:57 -0700 Subject: [PATCH 3/5] clean --- libraries/fbx/src/OBJReader.cpp | 35 ++++++++++++++------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index d23f4e3b69..c05b296c66 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -178,6 +178,13 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f } } +static bool replyOK(QNetworkReply* netReply, QUrl url) { // This will be reworked when we make things asynchronous + return netReply->isFinished() && + (url.toString().startsWith("file", Qt::CaseInsensitive) ? // file urls don't have http status codes + netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString().isEmpty() : + (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200)); +} + bool OBJReader::isValidTexture(const QByteArray &filename) { if (_url.isEmpty()) { return false; @@ -187,14 +194,11 @@ bool OBJReader::isValidTexture(const QByteArray &filename) { if (!netReply) { return false; } - bool isValid = netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200); + bool isValid = replyOK(netReply, candidateUrl); netReply->deleteLater(); return isValid; } -//FIXME -#define WANT_DEBUG 1 - void OBJReader::parseMaterialLibrary(QIODevice* device) { OBJTokenizer tokenizer(device); QString matName = SMART_DEFAULT_MATERIAL_NAME; @@ -347,12 +351,12 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi break; } QString nextName = tokenizer.getDatum(); - #ifdef WANT_DEBUG if (nextName != currentMaterialName) { currentMaterialName = nextName; + #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader new current material:" << currentMaterialName; + #endif } - #endif } else if (token == "v") { vertices.append(tokenizer.getVec3()); } else if (token == "vn") { @@ -561,21 +565,14 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, foreach (QString libraryName, librariesSeen.keys()) { // Throw away any path part of libraryName, and merge against original url. QUrl libraryUrl = _url.resolved(QUrl(libraryName).fileName()); - #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader material library" << libraryName << "used in" << _url; - #endif QNetworkReply* netReply = request(libraryUrl, false); - if (netReply->isFinished() && - (libraryUrl.toString().startsWith("file", Qt::CaseInsensitive) ? // file urls don't have http status codes - netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString().isEmpty() : - (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200))) { - parseMaterialLibrary(netReply); - } else { - #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader" << libraryName << "did not answer. Got" + if (replyOK(netReply, libraryUrl)) { + parseMaterialLibrary(netReply); + } else { + qCDebug(modelformat) << "OBJ Reader WARNING:" << libraryName << "did not answer. Got" << netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); - #endif - } + } netReply->deleteLater(); } } @@ -583,9 +580,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, foreach (QString materialID, materials.keys()) { OBJMaterial& objMaterial = materials[materialID]; if (!objMaterial.used) { - qCDebug(modelformat) << "fixme skipping" << materialID; continue; - assert(false); } geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor, objMaterial.specularColor, From 798a225573139b3094159501d83e315ff14ffe70 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 15 Mar 2016 21:11:54 -0700 Subject: [PATCH 4/5] Hack to suppress the attempted loading of .mtllib files if the url has a query part that includes "hifiusemat". --- libraries/fbx/src/OBJReader.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index c05b296c66..2bc403a808 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -251,16 +251,10 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { #endif break; } - if (isValidTexture(filename)) { - if (token == "map_Kd") { - currentMaterial.diffuseTextureFilename = filename; - } else { - currentMaterial.specularTextureFilename = filename; - } + if (token == "map_Kd") { + currentMaterial.diffuseTextureFilename = filename; } else { - #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader WARNING: " << _url << " ignoring missing texture " << filename; - #endif + currentMaterial.specularTextureFilename = filename; } } } @@ -534,7 +528,12 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, qCDebug(modelformat) << "OBJ reader fail: " << e.what(); } + QString queryPart = _url.query(); + bool suppressMaterialsHack = queryPart.contains("hifiusemat"); // If this appears in query string, don't fetch mtl even if used. OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME]; + if (suppressMaterialsHack) { + needsMaterialLibrary = preDefinedMaterial.userSpecifiesUV = false; // I said it was a hack... + } // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use // a texture with the same basename as the .obj file. if (preDefinedMaterial.userSpecifiesUV && !url.isEmpty()) { From e25c547cb7e5a87f13f9c1d01aa20aba02ae1ea2 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 16 Mar 2016 09:50:05 -0700 Subject: [PATCH 5/5] whitespace --- libraries/fbx/src/OBJReader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 2bc403a808..c0552f989b 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -179,10 +179,10 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f } static bool replyOK(QNetworkReply* netReply, QUrl url) { // This will be reworked when we make things asynchronous - return netReply->isFinished() && - (url.toString().startsWith("file", Qt::CaseInsensitive) ? // file urls don't have http status codes - netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString().isEmpty() : - (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200)); + return (netReply->isFinished() && + (url.toString().startsWith("file", Qt::CaseInsensitive) ? // file urls don't have http status codes + netReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString().isEmpty() : + (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200))); } bool OBJReader::isValidTexture(const QByteArray &filename) {