//// OBJSerializer.cpp// libraries/model-serializers/src//// Created by Seth Alves on 3/7/15.// Copyright 2013 High Fidelity, Inc.// Copyright 2022 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//// http://en.wikipedia.org/wiki/Wavefront_.obj_file// http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/obj-file-format/obj-file-format/// http://paulbourke.net/dataformats/obj/ #include "OBJSerializer.h" #include <ctype.h> // .obj files are not locale-specific. The C/ASCII charset applies.#include <sstream> #include <QtCore/QBuffer>#include <QtCore/QIODevice>#include <QtCore/QEventLoop>#include <QtNetwork/QNetworkAccessManager>#include <QtNetwork/QNetworkRequest> #include <shared/NsightHelpers.h>#include <NetworkAccessManager.h>#include <ResourceManager.h> #include "FBXSerializer.h"#include <hfm/ModelFormatLogging.h>#include <shared/PlatformHacks.h> QHash<QString, float> COMMENT_SCALE_HINTS = {{"This file uses centimeters as units", 1.0f / 100.0f}, {"This file uses millimeters as units", 1.0f / 1000.0f}}; const QString SMART_DEFAULT_MATERIAL_NAME = "High Fidelity smart default material name"; const float ILLUMINATION_MODEL_MIN_OPACITY = 0.1f;const float ILLUMINATION_MODEL_APPLY_SHININESS = 0.5f;const float ILLUMINATION_MODEL_APPLY_ROUGHNESS = 1.0f;const float ILLUMINATION_MODEL_APPLY_NON_METALLIC = 0.0f; namespace {template<class T>T& checked_at(QVector<T>& vector, int i) { if (i < 0 || i >= vector.size()) { throw std::out_of_range("index " + std::to_string(i) + "is out of range"); } return vector[i];}} OBJTokenizer::OBJTokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) {} const hifi::ByteArray OBJTokenizer::getLineAsDatum() { return _device->readLine().trimmed();} float OBJTokenizer::getFloat() { std::istringstream ss((nextToken() != OBJTokenizer::DATUM_TOKEN) ? nullptr : getDatum().data()); ss.imbue(std::locale::classic()); float f; ss >> f; return f;} int OBJTokenizer::nextToken(bool allowSpaceChar /*= false*/) { if (_pushedBackToken != NO_PUSHBACKED_TOKEN) { int token = _pushedBackToken; _pushedBackToken = NO_PUSHBACKED_TOKEN; return token; } char ch; while (_device->getChar(&ch)) { if (QChar(ch).isSpace()) { continue; // skip whitespace } switch (ch) { case '#': { _datum = ""; _comment = _device->readLine(); // stash comment for a future call to getComment return COMMENT_TOKEN; } case '\"': _datum = ""; while (_device->getChar(&ch)) { if (ch == '\"') { // end on closing quote break; } if (ch == '\\') { // handle escaped quotes if (_device->getChar(&ch) && ch != '\"') { _datum.append('\\'); } } _datum.append(ch); } return DATUM_TOKEN; default: _datum = ""; _datum.append(ch); while (_device->getChar(&ch)) { if ((QChar(ch).isSpace() || ch == '\"') && (!allowSpaceChar || ch != ' ')) { ungetChar(ch); // read until we encounter a special character, then replace it break; } _datum.append(ch); } return DATUM_TOKEN; } } return NO_TOKEN;} bool OBJTokenizer::isNextTokenFloat() { if (nextToken() != OBJTokenizer::DATUM_TOKEN) { return false; } hifi::ByteArray token = getDatum(); pushBackToken(OBJTokenizer::DATUM_TOKEN); bool ok; token.toFloat(&ok); return ok;} glm::vec3 OBJTokenizer::getVec3() { auto x = getFloat(); // N.B.: getFloat() has side-effect auto y = getFloat(); // And order of arguments is different on Windows/Linux. auto z = getFloat(); auto v = glm::vec3(x, y, z); while (isNextTokenFloat()) { // ignore any following floats nextToken(); } return v;}bool OBJTokenizer::getVertex(glm::vec3& vertex, glm::vec3& vertexColor) { // Used for vertices which may also have a vertex color (RGB [0,1]) to follow. // NOTE: Returns true if there is a vertex color. auto x = getFloat(); // N.B.: getFloat() has side-effect auto y = getFloat(); // And order of arguments is different on Windows/Linux. auto z = getFloat(); vertex = glm::vec3(x, y, z); auto r = 1.0f, g = 1.0f, b = 1.0f; bool hasVertexColor = false; if (isNextTokenFloat()) { // If there's another float it's one of two things: a W component or an R component. The standard OBJ spec // doesn't output a W component, so we're making the assumption that if a float follows (unless it's // only a single value) that it's a vertex color. r = getFloat(); if (isNextTokenFloat()) { // Safe to assume the following values are the green/blue components. g = getFloat(); b = getFloat(); hasVertexColor = true; } vertexColor = glm::vec3(r, g, b); } return hasVertexColor;}glm::vec2 OBJTokenizer::getVec2() { float uCoord = getFloat(); float vCoord = 1.0f - getFloat(); auto v = glm::vec2(uCoord, vCoord); while (isNextTokenFloat()) { // there can be a w, but we don't handle that nextToken(); } return v;} void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID) { meshPart.materialID = materialID;} // OBJFace// NOTE (trent, 7/20/17): The vertexColors vector being passed-in isn't necessary here, but I'm just// pairing it with the vertices vector for consistency.bool OBJFace::add(const hifi::ByteArray& vertexIndex, const hifi::ByteArray& textureIndex, const hifi::ByteArray& normalIndex, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& vertexColors) { bool ok; int index = vertexIndex.toInt(&ok); if (!ok) { return false; } vertexIndices.append(index - 1); if (!textureIndex.isEmpty()) { index = textureIndex.toInt(&ok); if (!ok) { return false; } if (index < 0) { // Count backwards from the last one added. index = vertices.count() + 1 + index; } textureUVIndices.append(index - 1); } if (!normalIndex.isEmpty()) { index = normalIndex.toInt(&ok); if (!ok) { return false; } normalIndices.append(index - 1); } return true;} QVector<OBJFace> OBJFace::triangulate() { QVector<OBJFace> newFaces; const int nVerticesInATriangle = 3; if (vertexIndices.count() == nVerticesInATriangle) { newFaces.append(*this); } else { for (int i = 1; i < vertexIndices.count() - 1; i++) { OBJFace newFace; newFace.addFrom(this, 0); newFace.addFrom(this, i); newFace.addFrom(this, i + 1); newFace.groupName = groupName; newFace.materialName = materialName; newFaces.append(newFace); } } return newFaces;} void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f at index i vertexIndices.append(face->vertexIndices[index]); if (face->textureUVIndices.count() > 0) { // Any at all. Runtime error if not consistent. textureUVIndices.append(face->textureUVIndices[index]); } if (face->normalIndices.count() > 0) { normalIndices.append(face->normalIndices[index]); }} bool OBJSerializer::isValidTexture(const hifi::ByteArray &filename) { if (_url.isEmpty()) { return false; } hifi::URL candidateUrl = _url.resolved(hifi::URL(filename)); return DependencyManager::get<ResourceManager>()->resourceExists(candidateUrl);} void OBJSerializer::parseMaterialLibrary(QIODevice* device) { OBJTokenizer tokenizer(device); QString matName = SMART_DEFAULT_MATERIAL_NAME; OBJMaterial& currentMaterial = materials[matName]; while (true) { switch (tokenizer.nextToken()) { case OBJTokenizer::COMMENT_TOKEN: #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer MTLLIB comment:" << tokenizer.getComment(); #endif break; case OBJTokenizer::DATUM_TOKEN: break; default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Last material illumination model:" << currentMaterial.illuminationModel << " shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << " diffuse color:" << currentMaterial.diffuseColor << " specular color:" << currentMaterial.specularColor << " emissive color:" << currentMaterial.emissiveColor << " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << currentMaterial.specularTextureFilename << " emissive texture:" << currentMaterial.emissiveTextureFilename << " bump texture:" << currentMaterial.bumpTextureFilename << " opacity texture:" << currentMaterial.opacityTextureFilename;#endif return; } hifi::ByteArray token = tokenizer.getDatum(); if (token == "newmtl") { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { return; } materials[matName] = currentMaterial; matName = tokenizer.getDatum(); currentMaterial = materials[matName]; #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Starting new material definition " << matName; #endif currentMaterial.diffuseTextureFilename = ""; currentMaterial.emissiveTextureFilename = ""; currentMaterial.specularTextureFilename = ""; currentMaterial.bumpTextureFilename = ""; currentMaterial.opacityTextureFilename = ""; } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); } else if (token == "Ni") { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Ignoring material Ni " << tokenizer.getFloat(); #else tokenizer.getFloat(); #endif } else if (token == "d") { currentMaterial.opacity = tokenizer.getFloat(); } else if (token == "Tr") { currentMaterial.opacity = 1.0f - tokenizer.getFloat(); } else if (token == "illum") { currentMaterial.illuminationModel = tokenizer.getFloat(); } else if (token == "Tf") { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Ignoring material Tf " << tokenizer.getVec3(); #else tokenizer.getVec3(); #endif } else if (token == "Ka") { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Ignoring material Ka " << tokenizer.getVec3();; #else tokenizer.getVec3(); #endif } else if (token == "Kd") { currentMaterial.diffuseColor = tokenizer.getVec3(); } else if (token == "Ke") { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump") || (token == "map_d")) { const hifi::ByteArray textureLine = tokenizer.getLineAsDatum(); hifi::ByteArray filename; OBJMaterialTextureOptions textureOptions; parseTextureLine(textureLine, filename, textureOptions); if (filename.endsWith(".tga")) { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer WARNING: currently ignoring tga texture " << filename << " in " << _url; #endif break; } if (token == "map_Kd") { currentMaterial.diffuseTextureFilename = filename; } else if (token == "map_Ke") { currentMaterial.emissiveTextureFilename = filename; } else if (token == "map_Ks" ) { currentMaterial.specularTextureFilename = filename; } else if ((token == "map_bump") || (token == "bump")) { currentMaterial.bumpTextureFilename = filename; currentMaterial.bumpTextureOptions = textureOptions; } else if (token == "map_d") { currentMaterial.opacityTextureFilename = filename; } } }} void OBJSerializer::parseTextureLine(const hifi::ByteArray& textureLine, hifi::ByteArray& filename, OBJMaterialTextureOptions& textureOptions) { // Texture options reference http://paulbourke.net/dataformats/mtl/ // and https://wikivisually.com/wiki/Material_Template_Library std::istringstream iss(textureLine.toStdString()); const std::vector<std::string> parser(std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>()); uint i = 0; while (i < parser.size()) { if (i + 1 < parser.size() && parser[i][0] == '-') { const std::string& option = parser[i++]; if (option == "-blendu" || option == "-blendv") { #ifdef WANT_DEBUG const std::string& onoff = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str(); #endif } else if (option == "-bm") { const std::string& bm = parser[i++]; textureOptions.bumpMultiplier = std::stof(bm); } else if (option == "-boost") { #ifdef WANT_DEBUG const std::string& boost = parser[i++]; float boostFloat = std::stof(boost); qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << boost.c_str(); #endif } else if (option == "-cc") { #ifdef WANT_DEBUG const std::string& onoff = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str(); #endif } else if (option == "-clamp") { #ifdef WANT_DEBUG const std::string& onoff = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str(); #endif } else if (option == "-imfchan") { #ifdef WANT_DEBUG const std::string& imfchan = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << imfchan.c_str(); #endif } else if (option == "-mm") { if (i + 1 < parser.size()) { #ifdef WANT_DEBUG const std::string& mmBase = parser[i++]; const std::string& mmGain = parser[i++]; float mmBaseFloat = std::stof(mmBase); float mmGainFloat = std::stof(mmGain); qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << mmBase.c_str() << mmGain.c_str(); #endif } } else if (option == "-o" || option == "-s" || option == "-t") { if (i + 2 < parser.size()) { #ifdef WANT_DEBUG const std::string& u = parser[i++]; const std::string& v = parser[i++]; const std::string& w = parser[i++]; float uFloat = std::stof(u); float vFloat = std::stof(v); float wFloat = std::stof(w); qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << u.c_str() << v.c_str() << w.c_str(); #endif } } else if (option == "-texres") { #ifdef WANT_DEBUG const std::string& texres = parser[i++]; float texresFloat = std::stof(texres); qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << texres.c_str(); #endif } else if (option == "-type") { #ifdef WANT_DEBUG const std::string& type = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << type.c_str(); #endif } else if (option[0] == '-') { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring unsupported texture option" << option.c_str(); #endif } } else { // assume filename at end when no more options std::string filenameString = parser[i++]; while (i < parser.size()) { // filename has space in it filenameString += " " + parser[i++]; } filename = filenameString.c_str(); } }} std::tuple<bool, hifi::ByteArray> requestData(hifi::URL& url) { auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( nullptr, url, true, -1, "(OBJSerializer) requestData"); if (!request) { return std::make_tuple(false, hifi::ByteArray()); } QEventLoop loop; QObject::connect(request, &ResourceRequest::finished, &loop, &QEventLoop::quit); request->send(); loop.exec(); if (request->getResult() == ResourceRequest::Success) { return std::make_tuple(true, request->getData()); } else { return std::make_tuple(false, hifi::ByteArray()); }} QNetworkReply* request(hifi::URL& url, bool isTest) { if (!qApp) { return nullptr; } bool aboutToQuit{ false }; auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { aboutToQuit = true; }); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest netRequest(url); netRequest.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply* netReply = isTest ? networkAccessManager.head(netRequest) : networkAccessManager.get(netRequest); if (!qApp || aboutToQuit) { netReply->deleteLater(); return nullptr; } QEventLoop loop; // Create an event loop that will quit when we get the finished signal QObject::connect(netReply, SIGNAL(finished()), &loop, SLOT(quit())); loop.exec(); // Nothing is going to happen on this whole run thread until we get this QObject::disconnect(connection); return netReply; // trying to sync later on.} bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHash& mapping, HFMModel& hfmModel, float& scaleGuess, bool combineParts) { FaceGroup faces; HFMMesh& mesh = hfmModel.meshes[0]; mesh.parts.append(HFMMeshPart());Lines 1–500 of 1076. Load more
// // OBJSerializer.cpp // libraries/model-serializers/src // // Created by Seth Alves on 3/7/15. // Copyright 2013 High Fidelity, Inc. // Copyright 2022 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // // http://en.wikipedia.org/wiki/Wavefront_.obj_file // http://www.scratchapixel.com/old/lessons/3d-advanced-lessons/obj-file-format/obj-file-format/ // http://paulbourke.net/dataformats/obj/ #include "OBJSerializer.h" #include <ctype.h> // .obj files are not locale-specific. The C/ASCII charset applies. #include <sstream> #include <QtCore/QBuffer> #include <QtCore/QIODevice> #include <QtCore/QEventLoop> #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkRequest> #include <shared/NsightHelpers.h> #include <NetworkAccessManager.h> #include <ResourceManager.h> #include "FBXSerializer.h" #include <hfm/ModelFormatLogging.h> #include <shared/PlatformHacks.h> QHash<QString, float> COMMENT_SCALE_HINTS = {{"This file uses centimeters as units", 1.0f / 100.0f}, {"This file uses millimeters as units", 1.0f / 1000.0f}}; const QString SMART_DEFAULT_MATERIAL_NAME = "High Fidelity smart default material name"; const float ILLUMINATION_MODEL_MIN_OPACITY = 0.1f; const float ILLUMINATION_MODEL_APPLY_SHININESS = 0.5f; const float ILLUMINATION_MODEL_APPLY_ROUGHNESS = 1.0f; const float ILLUMINATION_MODEL_APPLY_NON_METALLIC = 0.0f; namespace { template<class T> T& checked_at(QVector<T>& vector, int i) { if (i < 0 || i >= vector.size()) { throw std::out_of_range("index " + std::to_string(i) + "is out of range"); } return vector[i]; } } OBJTokenizer::OBJTokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) { } const hifi::ByteArray OBJTokenizer::getLineAsDatum() { return _device->readLine().trimmed(); } float OBJTokenizer::getFloat() { std::istringstream ss((nextToken() != OBJTokenizer::DATUM_TOKEN) ? nullptr : getDatum().data()); ss.imbue(std::locale::classic()); float f; ss >> f; return f; } int OBJTokenizer::nextToken(bool allowSpaceChar /*= false*/) { if (_pushedBackToken != NO_PUSHBACKED_TOKEN) { int token = _pushedBackToken; _pushedBackToken = NO_PUSHBACKED_TOKEN; return token; } char ch; while (_device->getChar(&ch)) { if (QChar(ch).isSpace()) { continue; // skip whitespace } switch (ch) { case '#': { _datum = ""; _comment = _device->readLine(); // stash comment for a future call to getComment return COMMENT_TOKEN; } case '\"': _datum = ""; while (_device->getChar(&ch)) { if (ch == '\"') { // end on closing quote break; } if (ch == '\\') { // handle escaped quotes if (_device->getChar(&ch) && ch != '\"') { _datum.append('\\'); } } _datum.append(ch); } return DATUM_TOKEN; default: _datum = ""; _datum.append(ch); while (_device->getChar(&ch)) { if ((QChar(ch).isSpace() || ch == '\"') && (!allowSpaceChar || ch != ' ')) { ungetChar(ch); // read until we encounter a special character, then replace it break; } _datum.append(ch); } return DATUM_TOKEN; } } return NO_TOKEN; } bool OBJTokenizer::isNextTokenFloat() { if (nextToken() != OBJTokenizer::DATUM_TOKEN) { return false; } hifi::ByteArray token = getDatum(); pushBackToken(OBJTokenizer::DATUM_TOKEN); bool ok; token.toFloat(&ok); return ok; } glm::vec3 OBJTokenizer::getVec3() { auto x = getFloat(); // N.B.: getFloat() has side-effect auto y = getFloat(); // And order of arguments is different on Windows/Linux. auto z = getFloat(); auto v = glm::vec3(x, y, z); while (isNextTokenFloat()) { // ignore any following floats nextToken(); } return v; } bool OBJTokenizer::getVertex(glm::vec3& vertex, glm::vec3& vertexColor) { // Used for vertices which may also have a vertex color (RGB [0,1]) to follow. // NOTE: Returns true if there is a vertex color. auto x = getFloat(); // N.B.: getFloat() has side-effect auto y = getFloat(); // And order of arguments is different on Windows/Linux. auto z = getFloat(); vertex = glm::vec3(x, y, z); auto r = 1.0f, g = 1.0f, b = 1.0f; bool hasVertexColor = false; if (isNextTokenFloat()) { // If there's another float it's one of two things: a W component or an R component. The standard OBJ spec // doesn't output a W component, so we're making the assumption that if a float follows (unless it's // only a single value) that it's a vertex color. r = getFloat(); if (isNextTokenFloat()) { // Safe to assume the following values are the green/blue components. g = getFloat(); b = getFloat(); hasVertexColor = true; } vertexColor = glm::vec3(r, g, b); } return hasVertexColor; } glm::vec2 OBJTokenizer::getVec2() { float uCoord = getFloat(); float vCoord = 1.0f - getFloat(); auto v = glm::vec2(uCoord, vCoord); while (isNextTokenFloat()) { // there can be a w, but we don't handle that nextToken(); } return v; } void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID) { meshPart.materialID = materialID; } // OBJFace // NOTE (trent, 7/20/17): The vertexColors vector being passed-in isn't necessary here, but I'm just // pairing it with the vertices vector for consistency. bool OBJFace::add(const hifi::ByteArray& vertexIndex, const hifi::ByteArray& textureIndex, const hifi::ByteArray& normalIndex, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& vertexColors) { bool ok; int index = vertexIndex.toInt(&ok); if (!ok) { return false; } vertexIndices.append(index - 1); if (!textureIndex.isEmpty()) { index = textureIndex.toInt(&ok); if (!ok) { return false; } if (index < 0) { // Count backwards from the last one added. index = vertices.count() + 1 + index; } textureUVIndices.append(index - 1); } if (!normalIndex.isEmpty()) { index = normalIndex.toInt(&ok); if (!ok) { return false; } normalIndices.append(index - 1); } return true; } QVector<OBJFace> OBJFace::triangulate() { QVector<OBJFace> newFaces; const int nVerticesInATriangle = 3; if (vertexIndices.count() == nVerticesInATriangle) { newFaces.append(*this); } else { for (int i = 1; i < vertexIndices.count() - 1; i++) { OBJFace newFace; newFace.addFrom(this, 0); newFace.addFrom(this, i); newFace.addFrom(this, i + 1); newFace.groupName = groupName; newFace.materialName = materialName; newFaces.append(newFace); } } return newFaces; } void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f at index i vertexIndices.append(face->vertexIndices[index]); if (face->textureUVIndices.count() > 0) { // Any at all. Runtime error if not consistent. textureUVIndices.append(face->textureUVIndices[index]); } if (face->normalIndices.count() > 0) { normalIndices.append(face->normalIndices[index]); } } bool OBJSerializer::isValidTexture(const hifi::ByteArray &filename) { if (_url.isEmpty()) { return false; } hifi::URL candidateUrl = _url.resolved(hifi::URL(filename)); return DependencyManager::get<ResourceManager>()->resourceExists(candidateUrl); } void OBJSerializer::parseMaterialLibrary(QIODevice* device) { OBJTokenizer tokenizer(device); QString matName = SMART_DEFAULT_MATERIAL_NAME; OBJMaterial& currentMaterial = materials[matName]; while (true) { switch (tokenizer.nextToken()) { case OBJTokenizer::COMMENT_TOKEN: #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer MTLLIB comment:" << tokenizer.getComment(); #endif break; case OBJTokenizer::DATUM_TOKEN: break; default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Last material illumination model:" << currentMaterial.illuminationModel << " shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << " diffuse color:" << currentMaterial.diffuseColor << " specular color:" << currentMaterial.specularColor << " emissive color:" << currentMaterial.emissiveColor << " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << currentMaterial.specularTextureFilename << " emissive texture:" << currentMaterial.emissiveTextureFilename << " bump texture:" << currentMaterial.bumpTextureFilename << " opacity texture:" << currentMaterial.opacityTextureFilename; #endif return; } hifi::ByteArray token = tokenizer.getDatum(); if (token == "newmtl") { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { return; } materials[matName] = currentMaterial; matName = tokenizer.getDatum(); currentMaterial = materials[matName]; #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Starting new material definition " << matName; #endif currentMaterial.diffuseTextureFilename = ""; currentMaterial.emissiveTextureFilename = ""; currentMaterial.specularTextureFilename = ""; currentMaterial.bumpTextureFilename = ""; currentMaterial.opacityTextureFilename = ""; } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); } else if (token == "Ni") { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Ignoring material Ni " << tokenizer.getFloat(); #else tokenizer.getFloat(); #endif } else if (token == "d") { currentMaterial.opacity = tokenizer.getFloat(); } else if (token == "Tr") { currentMaterial.opacity = 1.0f - tokenizer.getFloat(); } else if (token == "illum") { currentMaterial.illuminationModel = tokenizer.getFloat(); } else if (token == "Tf") { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Ignoring material Tf " << tokenizer.getVec3(); #else tokenizer.getVec3(); #endif } else if (token == "Ka") { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer Ignoring material Ka " << tokenizer.getVec3();; #else tokenizer.getVec3(); #endif } else if (token == "Kd") { currentMaterial.diffuseColor = tokenizer.getVec3(); } else if (token == "Ke") { currentMaterial.emissiveColor = tokenizer.getVec3(); } else if (token == "Ks") { currentMaterial.specularColor = tokenizer.getVec3(); } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump") || (token == "map_d")) { const hifi::ByteArray textureLine = tokenizer.getLineAsDatum(); hifi::ByteArray filename; OBJMaterialTextureOptions textureOptions; parseTextureLine(textureLine, filename, textureOptions); if (filename.endsWith(".tga")) { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer WARNING: currently ignoring tga texture " << filename << " in " << _url; #endif break; } if (token == "map_Kd") { currentMaterial.diffuseTextureFilename = filename; } else if (token == "map_Ke") { currentMaterial.emissiveTextureFilename = filename; } else if (token == "map_Ks" ) { currentMaterial.specularTextureFilename = filename; } else if ((token == "map_bump") || (token == "bump")) { currentMaterial.bumpTextureFilename = filename; currentMaterial.bumpTextureOptions = textureOptions; } else if (token == "map_d") { currentMaterial.opacityTextureFilename = filename; } } } } void OBJSerializer::parseTextureLine(const hifi::ByteArray& textureLine, hifi::ByteArray& filename, OBJMaterialTextureOptions& textureOptions) { // Texture options reference http://paulbourke.net/dataformats/mtl/ // and https://wikivisually.com/wiki/Material_Template_Library std::istringstream iss(textureLine.toStdString()); const std::vector<std::string> parser(std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>()); uint i = 0; while (i < parser.size()) { if (i + 1 < parser.size() && parser[i][0] == '-') { const std::string& option = parser[i++]; if (option == "-blendu" || option == "-blendv") { #ifdef WANT_DEBUG const std::string& onoff = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str(); #endif } else if (option == "-bm") { const std::string& bm = parser[i++]; textureOptions.bumpMultiplier = std::stof(bm); } else if (option == "-boost") { #ifdef WANT_DEBUG const std::string& boost = parser[i++]; float boostFloat = std::stof(boost); qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << boost.c_str(); #endif } else if (option == "-cc") { #ifdef WANT_DEBUG const std::string& onoff = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str(); #endif } else if (option == "-clamp") { #ifdef WANT_DEBUG const std::string& onoff = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << onoff.c_str(); #endif } else if (option == "-imfchan") { #ifdef WANT_DEBUG const std::string& imfchan = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << imfchan.c_str(); #endif } else if (option == "-mm") { if (i + 1 < parser.size()) { #ifdef WANT_DEBUG const std::string& mmBase = parser[i++]; const std::string& mmGain = parser[i++]; float mmBaseFloat = std::stof(mmBase); float mmGainFloat = std::stof(mmGain); qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << mmBase.c_str() << mmGain.c_str(); #endif } } else if (option == "-o" || option == "-s" || option == "-t") { if (i + 2 < parser.size()) { #ifdef WANT_DEBUG const std::string& u = parser[i++]; const std::string& v = parser[i++]; const std::string& w = parser[i++]; float uFloat = std::stof(u); float vFloat = std::stof(v); float wFloat = std::stof(w); qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << u.c_str() << v.c_str() << w.c_str(); #endif } } else if (option == "-texres") { #ifdef WANT_DEBUG const std::string& texres = parser[i++]; float texresFloat = std::stof(texres); qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << texres.c_str(); #endif } else if (option == "-type") { #ifdef WANT_DEBUG const std::string& type = parser[i++]; qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring texture option" << option.c_str() << type.c_str(); #endif } else if (option[0] == '-') { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJSerializer WARNING: Ignoring unsupported texture option" << option.c_str(); #endif } } else { // assume filename at end when no more options std::string filenameString = parser[i++]; while (i < parser.size()) { // filename has space in it filenameString += " " + parser[i++]; } filename = filenameString.c_str(); } } } std::tuple<bool, hifi::ByteArray> requestData(hifi::URL& url) { auto request = DependencyManager::get<ResourceManager>()->createResourceRequest( nullptr, url, true, -1, "(OBJSerializer) requestData"); if (!request) { return std::make_tuple(false, hifi::ByteArray()); } QEventLoop loop; QObject::connect(request, &ResourceRequest::finished, &loop, &QEventLoop::quit); request->send(); loop.exec(); if (request->getResult() == ResourceRequest::Success) { return std::make_tuple(true, request->getData()); } else { return std::make_tuple(false, hifi::ByteArray()); } } QNetworkReply* request(hifi::URL& url, bool isTest) { if (!qApp) { return nullptr; } bool aboutToQuit{ false }; auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { aboutToQuit = true; }); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest netRequest(url); netRequest.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply* netReply = isTest ? networkAccessManager.head(netRequest) : networkAccessManager.get(netRequest); if (!qApp || aboutToQuit) { netReply->deleteLater(); return nullptr; } QEventLoop loop; // Create an event loop that will quit when we get the finished signal QObject::connect(netReply, SIGNAL(finished()), &loop, SLOT(quit())); loop.exec(); // Nothing is going to happen on this whole run thread until we get this QObject::disconnect(connection); return netReply; // trying to sync later on. } bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHash& mapping, HFMModel& hfmModel, float& scaleGuess, bool combineParts) { FaceGroup faces; HFMMesh& mesh = hfmModel.meshes[0]; mesh.parts.append(HFMMeshPart());