diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc new file mode 100644 index 0000000000..8da68e4e19 Binary files /dev/null and b/interface/resources/qml/js/Utils.jsc differ diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index e40b218344..224d19fe96 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -180,6 +180,8 @@ public: float emissiveIntensity{ 1.0f }; float ambientFactor{ 1.0f }; + float bumpMultiplier { 1.0f }; // TODO: to be implemented + QString materialID; QString name; QString shadingModel; diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index ba93a49cb9..63fb93ae46 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -15,6 +15,7 @@ #include "OBJReader.h" #include // .obj files are not locale-specific. The C/ASCII charset applies. +#include #include #include @@ -35,6 +36,11 @@ QHash COMMENT_SCALE_HINTS = {{"This file uses centimeters as uni 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 T& checked_at(QVector& vector, int i) { @@ -70,6 +76,7 @@ int OBJTokenizer::nextToken(bool allowSpaceChar /*= false*/) { } switch (ch) { case '#': { + _datum = ""; _comment = _device->readLine(); // stash comment for a future call to getComment return COMMENT_TOKEN; } @@ -256,7 +263,14 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Last material shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << " diffuse color:" << currentMaterial.diffuseColor << " specular color:" << currentMaterial.specularColor << " diffuse texture:" << currentMaterial.diffuseTextureFilename << " specular texture:" << currentMaterial.specularTextureFilename; + qCDebug(modelformat) << "OBJ Reader 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; #endif return; } @@ -272,20 +286,46 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { qCDebug(modelformat) << "OBJ Reader Starting new material definition " << matName; #endif currentMaterial.diffuseTextureFilename = ""; + currentMaterial.emissiveTextureFilename = ""; + currentMaterial.specularTextureFilename = ""; + currentMaterial.bumpTextureFilename = ""; } else if (token == "Ns") { currentMaterial.shininess = tokenizer.getFloat(); - } else if ((token == "d") || (token == "Tr")) { + } else if (token == "Ni") { + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader 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) << "OBJ Reader Ignoring material Tf " << tokenizer.getVec3(); + #else + tokenizer.getVec3(); + #endif } else if (token == "Ka") { #ifdef WANT_DEBUG - qCDebug(modelformat) << "OBJ Reader Ignoring material Ka " << tokenizer.getVec3(); + qCDebug(modelformat) << "OBJ Reader 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_Ks")) { - QByteArray filename = QUrl(tokenizer.getLineAsDatum()).fileName().toUtf8(); + } else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump")) { + const QByteArray textureLine = tokenizer.getLineAsDatum(); + QByteArray filename; + OBJMaterialTextureOptions textureOptions; + parseTextureLine(textureLine, filename, textureOptions); if (filename.endsWith(".tga")) { #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader WARNING: currently ignoring tga texture " << filename << " in " << _url; @@ -294,11 +334,104 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } if (token == "map_Kd") { currentMaterial.diffuseTextureFilename = filename; - } else if( token == "map_Ks" ) { + } 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; } } } +} + +void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& 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 parser(std::istream_iterator{iss}, std::istream_iterator()); + + 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) << "OBJ Reader 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) << "OBJ Reader 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) << "OBJ Reader 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) << "OBJ Reader 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) << "OBJ Reader 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) << "OBJ Reader 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) << "OBJ Reader 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) << "OBJ Reader 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) << "OBJ Reader WARNING: Ignoring texture option" << option.c_str() << type.c_str(); + #endif + } else if (option[0] == '-') { + #ifdef WANT_DEBUG + qCDebug(modelformat) << "OBJ Reader 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 requestData(QUrl& url) { @@ -745,7 +878,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, } geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor, objMaterial.specularColor, - glm::vec3(0.0f), + objMaterial.emissiveColor, objMaterial.shininess, objMaterial.opacity); FBXMaterial& fbxMaterial = geometry.materials[materialID]; @@ -759,17 +892,88 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, if (!objMaterial.specularTextureFilename.isEmpty()) { fbxMaterial.specularTexture.filename = objMaterial.specularTextureFilename; } + if (!objMaterial.emissiveTextureFilename.isEmpty()) { + fbxMaterial.emissiveTexture.filename = objMaterial.emissiveTextureFilename; + } + if (!objMaterial.bumpTextureFilename.isEmpty()) { + fbxMaterial.normalTexture.filename = objMaterial.bumpTextureFilename; + fbxMaterial.normalTexture.isBumpmap = true; + fbxMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; + } modelMaterial->setEmissive(fbxMaterial.emissiveColor); modelMaterial->setAlbedo(fbxMaterial.diffuseColor); modelMaterial->setMetallic(glm::length(fbxMaterial.specularColor)); modelMaterial->setRoughness(graphics::Material::shininessToRoughness(fbxMaterial.shininess)); - if (fbxMaterial.opacity <= 0.0f) { - modelMaterial->setOpacity(1.0f); - } else { - modelMaterial->setOpacity(fbxMaterial.opacity); + bool applyTransparency = false; + bool applyShininess = false; + bool applyRoughness = false; + bool applyNonMetallic = false; + bool fresnelOn = false; + + // Illumination model reference http://paulbourke.net/dataformats/mtl/ + switch (objMaterial.illuminationModel) { + case 0: // Color on and Ambient off + // We don't support ambient = do nothing? + break; + case 1: // Color on and Ambient on + // We don't support ambient = do nothing? + break; + case 2: // Highlight on + // Change specular intensity = do nothing for now? + break; + case 3: // Reflection on and Ray trace on + applyShininess = true; + break; + case 4: // Transparency: Glass on and Reflection: Ray trace on + applyTransparency = true; + applyShininess = true; + break; + case 5: // Reflection: Fresnel on and Ray trace on + applyShininess = true; + fresnelOn = true; + break; + case 6: // Transparency: Refraction on and Reflection: Fresnel off and Ray trace on + applyTransparency = true; + applyNonMetallic = true; + applyShininess = true; + break; + case 7: // Transparency: Refraction on and Reflection: Fresnel on and Ray trace on + applyTransparency = true; + applyNonMetallic = true; + applyShininess = true; + fresnelOn = true; + break; + case 8: // Reflection on and Ray trace off + applyShininess = true; + break; + case 9: // Transparency: Glass on and Reflection: Ray trace off + applyTransparency = true; + applyNonMetallic = true; + applyRoughness = true; + break; + case 10: // Casts shadows onto invisible surfaces + // Do nothing? + break; + } + + if (applyTransparency) { + fbxMaterial.opacity = std::max(fbxMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); } + if (applyShininess) { + modelMaterial->setRoughness(ILLUMINATION_MODEL_APPLY_SHININESS); + } else if (applyRoughness) { + modelMaterial->setRoughness(ILLUMINATION_MODEL_APPLY_ROUGHNESS); + } + if (applyNonMetallic) { + modelMaterial->setMetallic(ILLUMINATION_MODEL_APPLY_NON_METALLIC); + } + if (fresnelOn) { + modelMaterial->setFresnel(glm::vec3(1.0f)); + } + + modelMaterial->setOpacity(fbxMaterial.opacity); } return geometryPtr; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 45e3f79480..df356fada8 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -48,6 +48,11 @@ private: void addFrom(const OBJFace* face, int index); }; +class OBJMaterialTextureOptions { +public: + float bumpMultiplier { 1.0f }; +} +; // Materials and references to material names can come in any order, and different mesh parts can refer to the same material. // Therefore it would get pretty hacky to try to use FBXMeshPart to store these as we traverse the files. class OBJMaterial { @@ -56,11 +61,16 @@ public: float opacity; glm::vec3 diffuseColor; glm::vec3 specularColor; + glm::vec3 emissiveColor; QByteArray diffuseTextureFilename; QByteArray specularTextureFilename; + QByteArray emissiveTextureFilename; + QByteArray bumpTextureFilename; + OBJMaterialTextureOptions bumpTextureOptions; + int illuminationModel; bool used { false }; bool userSpecifiesUV { false }; - OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f) {} + OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f), emissiveColor(0.0f), illuminationModel(-1) {} }; class OBJReader: public QObject { // QObject so we can make network requests. @@ -84,6 +94,7 @@ private: bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess, bool combineParts); void parseMaterialLibrary(QIODevice* device); + void parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions); bool isValidTexture(const QByteArray &filename); // true if the file exists. TODO?: check content-type header and that it is a supported format. int _partCounter { 0 };