diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index 2189e7bdc3..b7eb56c921 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -33,9 +33,8 @@ #include "ModelBakingLoggingCategory.h" #include "TextureBaker.h" -FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : - ModelBaker(inputModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) { +FBXBaker::FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : + ModelBaker(inputModelURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) { if (hasBeenBaked) { // Look for the original model file one directory higher. Perhaps this is an oven output directory. QUrl originalRelativePath = QUrl("../original/" + inputModelURL.fileName().replace(BAKED_FBX_EXTENSION, FBX_EXTENSION)); @@ -45,15 +44,6 @@ FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputText } void FBXBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) { - _hfmModel = hfmModel; - - if (shouldStop()) { - return; - } - - // enumerate the models and textures found in the scene and start a bake for them - rewriteAndBakeSceneTextures(); - if (shouldStop()) { return; } @@ -114,15 +104,15 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const int meshIndex = 0; for (FBXNode& rootChild : _rootNode.children) { if (rootChild.name == "Objects") { - for (FBXNode& object : rootChild.children) { - if (object.name == "Geometry") { - if (object.properties.at(2) == "Mesh") { + for (auto object = rootChild.children.begin(); object != rootChild.children.end(); object++) { + if (object->name == "Geometry") { + if (object->properties.at(2) == "Mesh") { int meshNum = meshIndexToRuntimeOrder[meshIndex]; - replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]); + replaceMeshNodeWithDraco(*object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]); meshIndex++; } - } else if (object.name == "Model") { - for (FBXNode& modelChild : object.children) { + } else if (object->name == "Model") { + for (FBXNode& modelChild : object->children) { if (modelChild.name == "Properties60" || modelChild.name == "Properties70") { // This is a properties node // Remove the geometric transform because that has been applied directly to the vertices in FBXSerializer @@ -142,10 +132,13 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const } else if (modelChild.name == "Vertices") { // This model is also a mesh int meshNum = meshIndexToRuntimeOrder[meshIndex]; - replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]); + replaceMeshNodeWithDraco(*object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]); meshIndex++; } } + } else if (object->name == "Texture" || object->name == "Video") { + // this is an embedded texture, we need to remove it from the FBX + object = rootChild.children.erase(object); } if (hasErrors()) { @@ -154,82 +147,4 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const } } } -} - -void FBXBaker::rewriteAndBakeSceneTextures() { - using namespace image::TextureUsage; - QHash textureTypes; - - // enumerate the materials in the extracted geometry so we can determine the texture type for each texture ID - for (const auto& material : _hfmModel->materials) { - if (material.normalTexture.isBumpmap) { - textureTypes[material.normalTexture.id] = BUMP_TEXTURE; - } else { - textureTypes[material.normalTexture.id] = NORMAL_TEXTURE; - } - - textureTypes[material.albedoTexture.id] = ALBEDO_TEXTURE; - textureTypes[material.glossTexture.id] = GLOSS_TEXTURE; - textureTypes[material.roughnessTexture.id] = ROUGHNESS_TEXTURE; - textureTypes[material.specularTexture.id] = SPECULAR_TEXTURE; - textureTypes[material.metallicTexture.id] = METALLIC_TEXTURE; - textureTypes[material.emissiveTexture.id] = EMISSIVE_TEXTURE; - textureTypes[material.occlusionTexture.id] = OCCLUSION_TEXTURE; - textureTypes[material.lightmapTexture.id] = LIGHTMAP_TEXTURE; - } - - // enumerate the children of the root node - for (FBXNode& rootChild : _rootNode.children) { - - if (rootChild.name == "Objects") { - - // enumerate the objects - auto object = rootChild.children.begin(); - while (object != rootChild.children.end()) { - if (object->name == "Texture") { - - // double check that we didn't get an abort while baking the last texture - if (shouldStop()) { - return; - } - - // enumerate the texture children - for (FBXNode& textureChild : object->children) { - - if (textureChild.name == "RelativeFilename") { - QString hfmTextureFileName { textureChild.properties.at(0).toString() }; - - // grab the ID for this texture so we can figure out the - // texture type from the loaded materials - auto textureID { object->properties[0].toString() }; - auto textureType = textureTypes[textureID]; - - // Compress the texture information and return the new filename to be added into the FBX scene - auto bakedTextureFile = compressTexture(hfmTextureFileName, textureType); - - // If no errors or warnings have occurred during texture compression add the filename to the FBX scene - if (!bakedTextureFile.isNull()) { - textureChild.properties[0] = bakedTextureFile; - } else { - // if bake fails - return, if there were errors and continue, if there were warnings. - if (hasErrors()) { - return; - } else if (hasWarnings()) { - continue; - } - } - } - } - - ++object; - - } else if (object->name == "Video") { - // this is an embedded texture, we need to remove it from the FBX - object = rootChild.children.erase(object); - } else { - ++object; - } - } - } - } -} +} \ No newline at end of file diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index 59ef5e349d..5daf8a8adf 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -31,20 +31,14 @@ using TextureBakerThreadGetter = std::function; class FBXBaker : public ModelBaker { Q_OBJECT public: - FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); + FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); protected: virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) override; private: void rewriteAndBakeSceneModels(const QVector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists); - void rewriteAndBakeSceneTextures(); void replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector& dracoMaterialList); - - hfm::Model::Pointer _hfmModel; - - bool _pendingErrorEmission { false }; }; #endif // hifi_FBXBaker_h diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp index dd1ba55e54..9fcd7d0354 100644 --- a/libraries/baking/src/MaterialBaker.cpp +++ b/libraries/baking/src/MaterialBaker.cpp @@ -27,21 +27,11 @@ std::function MaterialBaker::_getNextOvenWorkerThreadOperator; static int materialNum = 0; -namespace std { - template <> - struct hash { - size_t operator()(const graphics::Material::MapChannel& a) const { - return std::hash()((size_t)a); - } - }; -}; - -MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath) : +MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir) : _materialData(materialData), _isURL(isURL), _bakedOutputDir(bakedOutputDir), - _textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)), - _destinationPath(destinationPath) + _textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)) { } @@ -64,6 +54,14 @@ void MaterialBaker::bake() { } } +void MaterialBaker::abort() { + Baker::abort(); + + for (auto& textureBaker : _textureBakers) { + textureBaker->abort(); + } +} + void MaterialBaker::loadMaterial() { if (!_isURL) { qCDebug(material_baking) << "Loading local material" << _materialData; @@ -104,45 +102,42 @@ void MaterialBaker::processMaterial() { for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) { if (networkMaterial.second) { - auto textureMaps = networkMaterial.second->getTextureMaps(); - for (auto textureMap : textureMaps) { - if (textureMap.second && textureMap.second->getTextureSource()) { - graphics::Material::MapChannel mapChannel = textureMap.first; - auto texture = textureMap.second->getTextureSource(); + auto textures = networkMaterial.second->getTextures(); + for (auto texturePair : textures) { + auto mapChannel = texturePair.first; + auto textureMap = texturePair.second; + if (textureMap.texture && textureMap.texture->_textureSource) { + auto type = textureMap.texture->getTextureType(); - QUrl url = texture->getUrl(); - QString cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString(); + QByteArray content; + QUrl textureURL; + { + bool foundEmbeddedTexture = false; + auto textureContentMapIter = _textureContentMap.find(networkMaterial.second->getName()); + if (textureContentMapIter != _textureContentMap.end()) { + auto textureUsageIter = textureContentMapIter->second.find(type); + if (textureUsageIter != textureContentMapIter->second.end()) { + content = textureUsageIter->second.first; + textureURL = textureUsageIter->second.second; + foundEmbeddedTexture = true; + } + } + if (!foundEmbeddedTexture && textureMap.texture->_textureSource) { + textureURL = textureMap.texture->_textureSource->getUrl().adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); + } + } + + QString cleanURL = textureURL.toDisplayString(); auto idx = cleanURL.lastIndexOf('.'); - auto extension = idx >= 0 ? url.toDisplayString().mid(idx + 1).toLower() : ""; + QString extension = idx >= 0 ? cleanURL.mid(idx + 1).toLower() : ""; if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) { - QUrl textureURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); - - // FIXME: this isn't properly handling bumpMaps or glossMaps - static std::unordered_map MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP; - if (MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.empty()) { - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::EMISSIVE_MAP] = image::TextureUsage::EMISSIVE_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ALBEDO_MAP] = image::TextureUsage::ALBEDO_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::METALLIC_MAP] = image::TextureUsage::METALLIC_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ROUGHNESS_MAP] = image::TextureUsage::ROUGHNESS_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::NORMAL_MAP] = image::TextureUsage::NORMAL_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::OCCLUSION_MAP] = image::TextureUsage::OCCLUSION_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::LIGHTMAP_MAP] = image::TextureUsage::LIGHTMAP_TEXTURE; - MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::SCATTERING_MAP] = image::TextureUsage::SCATTERING_TEXTURE; - } - - auto it = MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.find(mapChannel); - if (it == MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.end()) { - handleError("Unknown map channel"); - return; - } - - QPair textureKey(textureURL, it->second); + QPair textureKey(textureURL, type); if (!_textureBakers.contains(textureKey)) { - auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), it->second); + auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), type); QSharedPointer textureBaker { - new TextureBaker(textureURL, it->second, _textureOutputDir, "", baseTextureFileName), + new TextureBaker(textureURL, type, _textureOutputDir, "", baseTextureFileName, content), &TextureBaker::deleteLater }; textureBaker->setMapChannel(mapChannel); @@ -179,7 +174,7 @@ void MaterialBaker::handleFinishedTextureBaker() { // Replace the old texture URLs for (auto networkMaterial : _materialsNeedingRewrite.values(textureKey)) { - networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(_destinationPath.resolved(relativeURL)); + networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(relativeURL); } } else { // this texture failed to bake - this doesn't fail the entire bake but we need to add the errors from @@ -245,3 +240,30 @@ void MaterialBaker::outputMaterial() { // emit signal to indicate the material baking is finished emit finished(); } + +void MaterialBaker::addTexture(const QString& materialName, image::TextureUsage::Type textureUsage, const hfm::Texture& texture) { + auto& textureUsageMap = _textureContentMap[materialName.toStdString()]; + if (textureUsageMap.find(textureUsage) == textureUsageMap.end() && !texture.content.isEmpty()) { + textureUsageMap[textureUsage] = { texture.content, texture.filename }; + } +}; + +void MaterialBaker::setMaterials(const QHash& materials, const QString& baseURL) { + _materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); + for (auto& material : materials) { + _materialResource->parsedMaterials.names.push_back(material.name.toStdString()); + _materialResource->parsedMaterials.networkMaterials[material.name.toStdString()] = std::make_shared(material, baseURL); + + // Store any embedded texture content + addTexture(material.name, image::TextureUsage::NORMAL_TEXTURE, material.normalTexture); + addTexture(material.name, image::TextureUsage::ALBEDO_TEXTURE, material.albedoTexture); + addTexture(material.name, image::TextureUsage::GLOSS_TEXTURE, material.glossTexture); + addTexture(material.name, image::TextureUsage::ROUGHNESS_TEXTURE, material.roughnessTexture); + addTexture(material.name, image::TextureUsage::SPECULAR_TEXTURE, material.specularTexture); + addTexture(material.name, image::TextureUsage::METALLIC_TEXTURE, material.metallicTexture); + addTexture(material.name, image::TextureUsage::EMISSIVE_TEXTURE, material.emissiveTexture); + addTexture(material.name, image::TextureUsage::OCCLUSION_TEXTURE, material.occlusionTexture); + addTexture(material.name, image::TextureUsage::SCATTERING_TEXTURE, material.scatteringTexture); + addTexture(material.name, image::TextureUsage::LIGHTMAP_TEXTURE, material.lightmapTexture); + } +} \ No newline at end of file diff --git a/libraries/baking/src/MaterialBaker.h b/libraries/baking/src/MaterialBaker.h index 41ce31380e..ab2a0a5901 100644 --- a/libraries/baking/src/MaterialBaker.h +++ b/libraries/baking/src/MaterialBaker.h @@ -24,16 +24,19 @@ static const QString BAKED_MATERIAL_EXTENSION = ".baked.json"; class MaterialBaker : public Baker { Q_OBJECT public: - MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath); + MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir); QString getMaterialData() const { return _materialData; } bool isURL() const { return _isURL; } QString getBakedMaterialData() const { return _bakedMaterialData; } + void setMaterials(const QHash& materials, const QString& baseURL); + static void setNextOvenWorkerThreadOperator(std::function getNextOvenWorkerThreadOperator) { _getNextOvenWorkerThreadOperator = getNextOvenWorkerThreadOperator; } public slots: virtual void bake() override; + virtual void abort() override; signals: void originalMaterialLoaded(); @@ -57,11 +60,18 @@ private: QString _bakedOutputDir; QString _textureOutputDir; QString _bakedMaterialData; - QUrl _destinationPath; QScriptEngine _scriptEngine; static std::function _getNextOvenWorkerThreadOperator; TextureFileNamer _textureFileNamer; + + void addTexture(const QString& materialName, image::TextureUsage::Type textureUsage, const hfm::Texture& texture); + struct TextureUsageHash { + std::size_t operator()(image::TextureUsage::Type textureUsage) const { + return static_cast(textureUsage); + } + }; + std::unordered_map, TextureUsageHash>> _textureContentMap; }; #endif // !hifi_MaterialBaker_h diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 9568a81578..82af2f94e9 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -42,12 +42,12 @@ #include "baking/BakerLibrary.h" -ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : +#include + +ModelBaker::ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : _modelURL(inputModelURL), _bakedOutputDir(bakedOutputDirectory), _originalOutputDir(originalOutputDirectory), - _textureThreadGetter(inputTextureThreadGetter), _hasBeenBaked(hasBeenBaked) { auto bakedFilename = _modelURL.fileName(); @@ -209,7 +209,6 @@ void ModelBaker::bakeSourceCopy() { } hifi::ByteArray modelData = modelFile.readAll(); - hfm::Model::Pointer bakedModel; std::vector dracoMeshes; std::vector> dracoMaterialLists; // Material order for per-mesh material lookup used by dracoMeshes @@ -245,40 +244,76 @@ void ModelBaker::bakeSourceCopy() { // Begin hfm baking baker.run(); - bakedModel = baker.getHFMModel(); + _hfmModel = baker.getHFMModel(); dracoMeshes = baker.getDracoMeshes(); dracoMaterialLists = baker.getDracoMaterialLists(); } - // Populate _textureContentMap with path to content mappings, for quick lookup by URL - for (auto materialIt = bakedModel->materials.cbegin(); materialIt != bakedModel->materials.cend(); materialIt++) { - static const auto addTexture = [](QHash& textureContentMap, const hfm::Texture& texture) { - if (!textureContentMap.contains(texture.filename)) { - // Content may be empty, unless the data is inlined - textureContentMap[texture.filename] = texture.content; - } - }; - const hfm::Material& material = *materialIt; - addTexture(_textureContentMap, material.normalTexture); - addTexture(_textureContentMap, material.albedoTexture); - addTexture(_textureContentMap, material.opacityTexture); - addTexture(_textureContentMap, material.glossTexture); - addTexture(_textureContentMap, material.roughnessTexture); - addTexture(_textureContentMap, material.specularTexture); - addTexture(_textureContentMap, material.metallicTexture); - addTexture(_textureContentMap, material.emissiveTexture); - addTexture(_textureContentMap, material.occlusionTexture); - addTexture(_textureContentMap, material.scatteringTexture); - addTexture(_textureContentMap, material.lightmapTexture); - } - // Do format-specific baking - bakeProcessedSource(bakedModel, dracoMeshes, dracoMaterialLists); + bakeProcessedSource(_hfmModel, dracoMeshes, dracoMaterialLists); if (shouldStop()) { return; } + if (_hfmModel->materials.size() > 0) { + _materialBaker = QSharedPointer( + new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir), + &MaterialBaker::deleteLater + ); + _materialBaker->setMaterials(_hfmModel->materials, _modelURL.toString()); + connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialBaker); + _materialBaker->bake(); + } else { + outputBakedFST(); + } +} + +void ModelBaker::handleFinishedMaterialBaker() { + auto baker = qobject_cast(sender()); + + if (baker) { + if (!baker->hasErrors()) { + // this MaterialBaker is done and everything went according to plan + qCDebug(model_baking) << "Adding baked material to FST mapping " << baker->getBakedMaterialData(); + + QString relativeBakedMaterialURL = _modelURL.fileName(); + auto baseName = relativeBakedMaterialURL.left(relativeBakedMaterialURL.lastIndexOf('.')); + relativeBakedMaterialURL = baseName + BAKED_MATERIAL_EXTENSION; + + // First we add the materials in the model + QJsonArray materialMapping; + for (auto material : _hfmModel->materials) { + QJsonObject json; + json["mat::" + material.name] = relativeBakedMaterialURL + "?" + material.name; + materialMapping.push_back(json); + } + + // The we add any existing mappings from the mapping + if (_mapping.contains(MATERIAL_MAPPING_FIELD)) { + QByteArray materialMapValue = _mapping[MATERIAL_MAPPING_FIELD].toByteArray(); + QJsonObject oldMaterialMapping = QJsonDocument::fromJson(materialMapValue).object(); + for (auto key : oldMaterialMapping.keys()) { + QJsonObject json; + json[key] = oldMaterialMapping[key]; + materialMapping.push_back(json); + } + } + + _mapping[MATERIAL_MAPPING_FIELD] = QJsonDocument(materialMapping).toJson(QJsonDocument::Compact); + } else { + // this material failed to bake - this doesn't fail the entire bake but we need to add the errors from + // the material to our warnings + _warningList << baker->getWarnings(); + } + } else { + handleWarning("Failed to bake the materials for model with URL " + _modelURL.toString()); + } + + outputBakedFST(); +} + +void ModelBaker::outputBakedFST() { // Output FST file, copying over input mappings if available QString outputFSTFilename = !_mappingURL.isEmpty() ? _mappingURL.fileName() : _modelURL.fileName(); auto extensionStart = outputFSTFilename.indexOf("."); @@ -291,8 +326,7 @@ void ModelBaker::bakeSourceCopy() { auto outputMapping = _mapping; outputMapping[FST_VERSION_FIELD] = FST_VERSION; outputMapping[FILENAME_FIELD] = _bakedModelURL.fileName(); - // All textures will be found in the same directory as the model - outputMapping[TEXDIR_FIELD] = "."; + outputMapping.remove(TEXDIR_FIELD); hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping); QFile fstOutputFile { outputFSTURL }; @@ -307,17 +341,16 @@ void ModelBaker::bakeSourceCopy() { _outputFiles.push_back(outputFSTURL); _outputMappingURL = outputFSTURL; - // check if we're already done with textures (in case we had none to re-write) - checkIfTexturesFinished(); + exportScene(); + qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL; + emit finished(); } void ModelBaker::abort() { Baker::abort(); - // tell our underlying TextureBaker instances to abort - // the ModelBaker will wait until all are aborted before emitting its own abort signal - for (auto& textureBaker : _bakingTextures) { - textureBaker->abort(); + if (_materialBaker) { + _materialBaker->abort(); } } @@ -354,247 +387,6 @@ bool ModelBaker::buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dr return true; } -QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) { - - QFileInfo modelTextureFileInfo { modelTextureFileName.replace("\\", "/") }; - - if (modelTextureFileInfo.suffix().toLower() == BAKED_TEXTURE_KTX_EXT.mid(1)) { - // re-baking a model that already references baked textures - // this is an error - return from here - handleError("Cannot re-bake a file that already references compressed textures"); - return QString::null; - } - - if (!image::getSupportedFormats().contains(modelTextureFileInfo.suffix())) { - // this is a texture format we don't bake, skip it - handleWarning(modelTextureFileName + " is not a bakeable texture format"); - return QString::null; - } - - // make sure this texture points to something and isn't one we've already re-mapped - QString textureChild { QString::null }; - if (!modelTextureFileInfo.filePath().isEmpty()) { - // check if this was an embedded texture that we already have in-memory content for - QByteArray textureContent; - - // figure out the URL to this texture, embedded or external - if (!modelTextureFileInfo.filePath().isEmpty()) { - textureContent = _textureContentMap.value(modelTextureFileName.toLocal8Bit()); - } - auto urlToTexture = getTextureURL(modelTextureFileInfo, !textureContent.isNull()); - - TextureKey textureKey { urlToTexture, textureType }; - auto bakingTextureIt = _bakingTextures.find(textureKey); - if (bakingTextureIt == _bakingTextures.cend()) { - // construct the new baked texture file name and file path - // ensuring that the baked texture will have a unique name - // even if there was another texture with the same name at a different path - QString baseTextureFileName = _textureFileNamer.createBaseTextureFileName(modelTextureFileInfo, textureType); - - QString bakedTextureFilePath { - _bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX - }; - - textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX; - - _outputFiles.push_back(bakedTextureFilePath); - - // bake this texture asynchronously - bakeTexture(textureKey, _bakedOutputDir, baseTextureFileName, textureContent); - } else { - // Fetch existing texture meta name - textureChild = (*bakingTextureIt)->getBaseFilename() + BAKED_META_TEXTURE_SUFFIX; - } - } - - qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName - << "to" << textureChild; - - return textureChild; -} - -void ModelBaker::bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) { - // start a bake for this texture and add it to our list to keep track of - QSharedPointer bakingTexture{ - new TextureBaker(textureKey.first, textureKey.second, outputDir, "../", bakedFilename, textureContent), - &TextureBaker::deleteLater - }; - - // make sure we hear when the baking texture is done or aborted - connect(bakingTexture.data(), &Baker::finished, this, &ModelBaker::handleBakedTexture); - connect(bakingTexture.data(), &TextureBaker::aborted, this, &ModelBaker::handleAbortedTexture); - - // keep a shared pointer to the baking texture - _bakingTextures.insert(textureKey, bakingTexture); - - // start baking the texture on one of our available worker threads - bakingTexture->moveToThread(_textureThreadGetter()); - QMetaObject::invokeMethod(bakingTexture.data(), "bake"); -} - -void ModelBaker::handleBakedTexture() { - TextureBaker* bakedTexture = qobject_cast(sender()); - qDebug() << "Handling baked texture" << bakedTexture->getTextureURL(); - - // make sure we haven't already run into errors, and that this is a valid texture - if (bakedTexture) { - if (!shouldStop()) { - if (!bakedTexture->hasErrors()) { - if (!_originalOutputDir.isEmpty()) { - // we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture - - // use the path to the texture being baked to determine if this was an embedded or a linked texture - - // it is embeddded if the texure being baked was inside a folder with the name of the model - // since that is the fake URL we provide when baking external textures - - if (!_modelURL.isParentOf(bakedTexture->getTextureURL())) { - // for linked textures we want to save a copy of original texture beside the original model - - qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); - - // check if we have a relative path to use for the texture - auto relativeTexturePath = texturePathRelativeToModel(_modelURL, bakedTexture->getTextureURL()); - - QFile originalTextureFile{ - _originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName() - }; - - if (relativeTexturePath.length() > 0) { - // make the folders needed by the relative path - } - - if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { - qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() - << "for" << _modelURL; - } else { - handleError("Could not save original external texture " + originalTextureFile.fileName() - + " for " + _modelURL.toString()); - return; - } - } - } - - - // now that this texture has been baked and handled, we can remove that TextureBaker from our hash - _bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() }); - - checkIfTexturesFinished(); - } else { - // there was an error baking this texture - add it to our list of errors - _errorList.append(bakedTexture->getErrors()); - - // we don't emit finished yet so that the other textures can finish baking first - _pendingErrorEmission = true; - - // now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list - _bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() }); - - // abort any other ongoing texture bakes since we know we'll end up failing - for (auto& bakingTexture : _bakingTextures) { - bakingTexture->abort(); - } - - checkIfTexturesFinished(); - } - } else { - // we have errors to attend to, so we don't do extra processing for this texture - // but we do need to remove that TextureBaker from our list - // and then check if we're done with all textures - _bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() }); - - checkIfTexturesFinished(); - } - } -} - -void ModelBaker::handleAbortedTexture() { - // grab the texture bake that was aborted and remove it from our hash since we don't need to track it anymore - TextureBaker* bakedTexture = qobject_cast(sender()); - - qDebug() << "Texture aborted: " << bakedTexture->getTextureURL(); - - if (bakedTexture) { - _bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() }); - } - - // since a texture we were baking aborted, our status is also aborted - _shouldAbort.store(true); - - // abort any other ongoing texture bakes since we know we'll end up failing - for (auto& bakingTexture : _bakingTextures) { - bakingTexture->abort(); - } - - checkIfTexturesFinished(); -} - -QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded) { - QUrl urlToTexture; - - if (isEmbedded) { - urlToTexture = _modelURL.toString() + "/" + textureFileInfo.filePath(); - } else { - if (textureFileInfo.exists() && textureFileInfo.isFile()) { - // set the texture URL to the local texture that we have confirmed exists - urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); - } else { - // external texture that we'll need to download or find - - // this is a relative file path which will require different handling - // depending on the location of the original model - if (_modelURL.isLocalFile() && textureFileInfo.exists() && textureFileInfo.isFile()) { - // the absolute path we ran into for the texture in the model exists on this machine - // so use that file - urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); - } else { - // we didn't find the texture on this machine at the absolute path - // so assume that it is right beside the model to match the behaviour of interface - urlToTexture = _modelURL.resolved(textureFileInfo.fileName()); - } - } - } - - return urlToTexture; -} - -QString ModelBaker::texturePathRelativeToModel(QUrl modelURL, QUrl textureURL) { - auto modelPath = modelURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - - if (texturePath.startsWith(modelPath)) { - // texture path is a child of the model path, return the texture path without the model path - return texturePath.mid(modelPath.length()); - } else { - // the texture path was not a child of the model path, return the empty string - return ""; - } -} - -void ModelBaker::checkIfTexturesFinished() { - // check if we're done everything we need to do for this model - // and emit our finished signal if we're done - - if (_bakingTextures.isEmpty()) { - if (shouldStop()) { - // if we're checking for completion but we have errors - // that means one or more of our texture baking operations failed - - if (_pendingErrorEmission) { - setIsFinished(true); - } - - return; - } else { - qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL; - - texturesFinished(); - - setIsFinished(true); - } - } -} - void ModelBaker::setWasAborted(bool wasAborted) { if (wasAborted != _wasAborted.load()) { Baker::setWasAborted(wasAborted); @@ -605,70 +397,6 @@ void ModelBaker::setWasAborted(bool wasAborted) { } } -void ModelBaker::texturesFinished() { - embedTextureMetaData(); - exportScene(); -} - -void ModelBaker::embedTextureMetaData() { - std::vector embeddedTextureNodes; - - for (FBXNode& rootChild : _rootNode.children) { - if (rootChild.name == "Objects") { - qlonglong maxId = 0; - for (auto &child : rootChild.children) { - if (child.properties.length() == 3) { - maxId = std::max(maxId, child.properties[0].toLongLong()); - } - } - - for (auto& object : rootChild.children) { - if (object.name == "Texture") { - QVariant relativeFilename; - for (auto& child : object.children) { - if (child.name == "RelativeFilename") { - relativeFilename = child.properties[0]; - break; - } - } - - if (relativeFilename.isNull() - || !relativeFilename.toString().endsWith(BAKED_META_TEXTURE_SUFFIX)) { - continue; - } - if (object.properties.length() < 2) { - qWarning() << "Found texture with unexpected number of properties: " << object.name; - continue; - } - - FBXNode videoNode; - videoNode.name = "Video"; - videoNode.properties.append(++maxId); - videoNode.properties.append(object.properties[1]); - videoNode.properties.append("Clip"); - - QString bakedTextureFilePath { - _bakedOutputDir + "/" + relativeFilename.toString() - }; - - QFile textureFile { bakedTextureFilePath }; - if (!textureFile.open(QIODevice::ReadOnly)) { - qWarning() << "Failed to open: " << bakedTextureFilePath; - continue; - } - - videoNode.children.append({ "RelativeFilename", { relativeFilename }, { } }); - videoNode.children.append({ "Content", { textureFile.readAll() }, { } }); - - rootChild.children.append(videoNode); - - textureFile.close(); - } - } - } - } -} - void ModelBaker::exportScene() { auto fbxData = FBXWriter::encodeFBX(_rootNode); diff --git a/libraries/baking/src/ModelBaker.h b/libraries/baking/src/ModelBaker.h index d9a559392f..d612a0a43a 100644 --- a/libraries/baking/src/ModelBaker.h +++ b/libraries/baking/src/ModelBaker.h @@ -18,17 +18,13 @@ #include #include "Baker.h" -#include "TextureBaker.h" -#include "baking/TextureFileNamer.h" +#include "MaterialBaker.h" #include "ModelBakingLoggingCategory.h" -#include - #include #include -using TextureBakerThreadGetter = std::function; using GetMaterialIDCallback = std::function ; static const QString FST_EXTENSION { ".fst" }; @@ -42,10 +38,7 @@ class ModelBaker : public Baker { Q_OBJECT public: - using TextureKey = QPair; - - ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); + ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); void setOutputURLSuffix(const QUrl& urlSuffix); void setMappingURL(const QUrl& mappingURL); @@ -54,7 +47,6 @@ public: void initializeOutputDirs(); bool buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dracoMeshBytes, const std::vector& dracoMaterialList); - QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE); virtual void setWasAborted(bool wasAborted) override; QUrl getModelURL() const { return _modelURL; } @@ -71,20 +63,15 @@ public slots: protected: void saveSourceModel(); virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) = 0; - void checkIfTexturesFinished(); - void texturesFinished(); - void embedTextureMetaData(); void exportScene(); FBXNode _rootNode; - QHash _textureContentMap; QUrl _modelURL; QUrl _outputURLSuffix; QUrl _mappingURL; hifi::VariantHash _mapping; QString _bakedOutputDir; QString _originalOutputDir; - TextureBakerThreadGetter _textureThreadGetter; QString _originalOutputModelPath; QString _outputMappingURL; QUrl _bakedModelURL; @@ -92,23 +79,15 @@ protected: protected slots: void handleModelNetworkReply(); virtual void bakeSourceCopy(); - -private slots: - void handleBakedTexture(); - void handleAbortedTexture(); + void handleFinishedMaterialBaker(); private: - QUrl getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded = false); - void bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent); - QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL); - - QMultiHash> _bakingTextures; - QHash _textureNameMatchCount; - bool _pendingErrorEmission { false }; + void outputBakedFST(); bool _hasBeenBaked { false }; - TextureFileNamer _textureFileNamer; + hfm::Model::Pointer _hfmModel; + QSharedPointer _materialBaker; }; #endif // hifi_ModelBaker_h diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp index 70bdeb2071..a2d0ab1094 100644 --- a/libraries/baking/src/OBJBaker.cpp +++ b/libraries/baking/src/OBJBaker.cpp @@ -132,55 +132,6 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h handleWarning("Baked mesh for OBJ model '" + _modelURL.toString() + "' is empty"); } - // Generating Texture Node - // iterate through mesh parts and process the associated textures - auto size = meshParts.size(); - for (int i = 0; i < size; i++) { - QString material = meshParts[i].materialID; - HFMMaterial currentMaterial = hfmModel->materials[material]; - if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) { - auto textureID = nextNodeID(); - _mapTextureMaterial.emplace_back(textureID, i); - - FBXNode textureNode; - { - textureNode.name = TEXTURE_NODE_NAME; - textureNode.properties = { textureID, "texture" + QString::number(textureID) }; - } - - // Texture node child - TextureName node - FBXNode textureNameNode; - { - textureNameNode.name = TEXTURENAME_NODE_NAME; - QByteArray propertyString = (!currentMaterial.albedoTexture.filename.isEmpty()) ? "Kd" : "Ka"; - textureNameNode.properties = { propertyString }; - } - - // Texture node child - Relative Filename node - FBXNode relativeFilenameNode; - { - relativeFilenameNode.name = RELATIVEFILENAME_NODE_NAME; - } - - QByteArray textureFileName = (!currentMaterial.albedoTexture.filename.isEmpty()) ? currentMaterial.albedoTexture.filename : currentMaterial.specularTexture.filename; - - auto textureType = (!currentMaterial.albedoTexture.filename.isEmpty()) ? image::TextureUsage::Type::ALBEDO_TEXTURE : image::TextureUsage::Type::SPECULAR_TEXTURE; - - // Compress the texture using ModelBaker::compressTexture() and store compressed file's name in the node - auto textureFile = compressTexture(textureFileName, textureType); - if (textureFile.isNull()) { - // Baking failed return - handleError("Failed to compress texture: " + textureFileName); - return; - } - relativeFilenameNode.properties = { textureFile }; - - textureNode.children = { textureNameNode, relativeFilenameNode }; - - objectNode.children.append(textureNode); - } - } - // Generating Connections node connectionsNode.name = CONNECTIONS_NODE_NAME; @@ -199,29 +150,6 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, modelID }; connectionsNode.children.append(cNode); } - - // Connect textures to materials - for (const auto& texMat : _mapTextureMaterial) { - FBXNode cAmbientNode; - cAmbientNode.name = C_NODE_NAME; - cAmbientNode.properties = { - CONNECTIONS_NODE_PROPERTY_1, - texMat.first, - _materialIDs[texMat.second], - "AmbientFactor" - }; - connectionsNode.children.append(cAmbientNode); - - FBXNode cDiffuseNode; - cDiffuseNode.name = C_NODE_NAME; - cDiffuseNode.properties = { - CONNECTIONS_NODE_PROPERTY_1, - texMat.first, - _materialIDs[texMat.second], - "DiffuseColor" - }; - connectionsNode.children.append(cDiffuseNode); - } } // Set properties for material nodes diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h index d1eced5452..9bd1431d28 100644 --- a/libraries/baking/src/OBJBaker.h +++ b/libraries/baking/src/OBJBaker.h @@ -35,9 +35,7 @@ private: void setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel); NodeID nextNodeID() { return _nodeID++; } - NodeID _nodeID { 0 }; std::vector _materialIDs; - std::vector> _mapTextureMaterial; }; #endif // hifi_OBJBaker_h diff --git a/libraries/baking/src/baking/BakerLibrary.cpp b/libraries/baking/src/baking/BakerLibrary.cpp index f9445bd432..e500f9d83f 100644 --- a/libraries/baking/src/baking/BakerLibrary.cpp +++ b/libraries/baking/src/baking/BakerLibrary.cpp @@ -44,7 +44,7 @@ bool isModelBaked(const QUrl& bakeableModelURL) { return beforeModelExtension.endsWith(".baked"); } -std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath) { +std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, const QString& contentOutputPath) { auto filename = bakeableModelURL.fileName(); // Output in a sub-folder with the name of the model, potentially suffixed by a number to make it unique @@ -58,20 +58,20 @@ std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, TextureB QString bakedOutputDirectory = contentOutputPath + subDirName + "/baked"; QString originalOutputDirectory = contentOutputPath + subDirName + "/original"; - return getModelBakerWithOutputDirectories(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory); + return getModelBakerWithOutputDirectories(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory); } -std::unique_ptr getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) { +std::unique_ptr getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) { auto filename = bakeableModelURL.fileName(); std::unique_ptr baker; if (filename.endsWith(FST_EXTENSION, Qt::CaseInsensitive)) { - baker = std::make_unique(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive)); + baker = std::make_unique(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive)); } else if (filename.endsWith(FBX_EXTENSION, Qt::CaseInsensitive)) { - baker = std::make_unique(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive)); + baker = std::make_unique(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive)); } else if (filename.endsWith(OBJ_EXTENSION, Qt::CaseInsensitive)) { - baker = std::make_unique(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory); + baker = std::make_unique(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory); //} else if (filename.endsWith(GLTF_EXTENSION, Qt::CaseInsensitive)) { //baker = std::make_unique(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory); } else { diff --git a/libraries/baking/src/baking/BakerLibrary.h b/libraries/baking/src/baking/BakerLibrary.h index a646c8d36a..8f82661b25 100644 --- a/libraries/baking/src/baking/BakerLibrary.h +++ b/libraries/baking/src/baking/BakerLibrary.h @@ -23,9 +23,9 @@ bool isModelBaked(const QUrl& bakeableModelURL); // Assuming the URL is valid, gets the appropriate baker for the given URL, and creates the base directory where the baker's output will later be stored // Returns an empty pointer if a baker could not be created -std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath); +std::unique_ptr getModelBaker(const QUrl& bakeableModelURL, const QString& contentOutputPath); // Similar to getModelBaker, but gives control over where the output folders will be -std::unique_ptr getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory); +std::unique_ptr getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory); #endif // hifi_BakerLibrary_h diff --git a/libraries/baking/src/baking/FSTBaker.cpp b/libraries/baking/src/baking/FSTBaker.cpp index acf3bfe1c7..176c35c059 100644 --- a/libraries/baking/src/baking/FSTBaker.cpp +++ b/libraries/baking/src/baking/FSTBaker.cpp @@ -18,9 +18,8 @@ #include -FSTBaker::FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : - ModelBaker(inputMappingURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) { +FSTBaker::FSTBaker(const QUrl& inputMappingURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) : + ModelBaker(inputMappingURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) { if (hasBeenBaked) { // Look for the original model file one directory higher. Perhaps this is an oven output directory. QUrl originalRelativePath = QUrl("../original/" + inputMappingURL.fileName().replace(BAKED_FST_EXTENSION, FST_EXTENSION)); @@ -70,7 +69,7 @@ void FSTBaker::bakeSourceCopy() { return; } - auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _textureThreadGetter, _bakedOutputDir, _originalOutputDir); + auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _bakedOutputDir, _originalOutputDir); _modelBaker = std::unique_ptr(dynamic_cast(baker.release())); if (!_modelBaker) { handleError("The model url '" + bakeableModelURL.toString() + "' from the FST file '" + _originalOutputModelPath + "' (property: '" + FILENAME_FIELD + "') could not be used to initialize a valid model baker"); diff --git a/libraries/baking/src/baking/FSTBaker.h b/libraries/baking/src/baking/FSTBaker.h index 85c7c93a37..32997680f5 100644 --- a/libraries/baking/src/baking/FSTBaker.h +++ b/libraries/baking/src/baking/FSTBaker.h @@ -18,8 +18,7 @@ class FSTBaker : public ModelBaker { Q_OBJECT public: - FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter, - const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); + FSTBaker(const QUrl& inputMappingURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false); virtual QUrl getFullOutputMappingURL() const override; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 79bd7431cc..f8339ddd31 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -97,7 +97,7 @@ QString processID(const QString& id) { return id.mid(id.lastIndexOf(':') + 1); } -QString getName(const QVariantList& properties) { +QString getModelName(const QVariantList& properties) { QString name; if (properties.size() == 3) { name = properties.at(1).toString(); @@ -108,6 +108,17 @@ QString getName(const QVariantList& properties) { return name; } +QString getMaterialName(const QVariantList& properties) { + QString name; + if (properties.size() == 1 || properties.at(1).toString().isEmpty()) { + name = properties.at(0).toString(); + name = processID(name.left(name.indexOf(QChar('\0')))); + } else { + name = processID(properties.at(1).toString()); + } + return name; +} + QString getID(const QVariantList& properties, int index = 0) { return processID(properties.at(index).toString()); } @@ -508,7 +519,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const blendshapes.append(extracted); } } else if (object.name == "Model") { - QString name = getName(object.properties); + QString name = getModelName(object.properties); QString id = getID(object.properties); modelIDsToNames.insert(id, name); @@ -827,7 +838,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } else if (object.name == "Material") { HFMMaterial material; MaterialParam materialParam; - material.name = (object.properties.at(1).toString()); + material.name = getMaterialName(object.properties); foreach (const FBXNode& subobject, object.children) { bool properties = false; diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index fade0fa5bc..2b13bf3078 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -32,6 +32,7 @@ static const QString JOINT_FIELD = "joint"; static const QString BLENDSHAPE_FIELD = "bs"; static const QString SCRIPT_FIELD = "script"; static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; +static const QString MATERIAL_MAPPING_FIELD = "materialMap"; class FSTReader { public: diff --git a/libraries/fbx/src/OBJSerializer.cpp b/libraries/fbx/src/OBJSerializer.cpp index c2e9c08463..416f343a47 100644 --- a/libraries/fbx/src/OBJSerializer.cpp +++ b/libraries/fbx/src/OBJSerializer.cpp @@ -891,12 +891,14 @@ HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::V if (!objMaterial.used) { continue; } - hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor, - objMaterial.specularColor, - objMaterial.emissiveColor, - objMaterial.shininess, - objMaterial.opacity); - HFMMaterial& hfmMaterial = hfmModel.materials[materialID]; + + HFMMaterial& hfmMaterial = hfmModel.materials[materialID] = HFMMaterial(objMaterial.diffuseColor, + objMaterial.specularColor, + objMaterial.emissiveColor, + objMaterial.shininess, + objMaterial.opacity); + + hfmMaterial.name = materialID; hfmMaterial.materialID = materialID; hfmMaterial._material = std::make_shared(); graphics::MaterialPointer modelMaterial = hfmMaterial._material; diff --git a/libraries/material-networking/src/material-networking/MaterialCache.cpp b/libraries/material-networking/src/material-networking/MaterialCache.cpp index 6561fc697e..9eef89d5c9 100644 --- a/libraries/material-networking/src/material-networking/MaterialCache.cpp +++ b/libraries/material-networking/src/material-networking/MaterialCache.cpp @@ -559,8 +559,7 @@ void NetworkMaterial::setLightmapMap(const QUrl& url) { } NetworkMaterial::NetworkMaterial(const HFMMaterial& material, const QUrl& textureBaseUrl) : - graphics::Material(*material._material), - _textures(MapChannel::NUM_MAP_CHANNELS) + graphics::Material(*material._material) { _name = material.name.toStdString(); if (!material.albedoTexture.filename.isEmpty()) { @@ -709,7 +708,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { bool NetworkMaterial::isMissingTexture() { for (auto& networkTexture : _textures) { - auto& texture = networkTexture.texture; + auto& texture = networkTexture.second.texture; if (!texture) { continue; } diff --git a/libraries/material-networking/src/material-networking/MaterialCache.h b/libraries/material-networking/src/material-networking/MaterialCache.h index d327aedb22..4894054de4 100644 --- a/libraries/material-networking/src/material-networking/MaterialCache.h +++ b/libraries/material-networking/src/material-networking/MaterialCache.h @@ -36,15 +36,21 @@ public: bool isMissingTexture(); void checkResetOpacityMap(); -protected: - friend class Geometry; - class Texture { public: QString name; NetworkTexturePointer texture; }; - using Textures = std::vector; + struct MapChannelHash { + std::size_t operator()(MapChannel mapChannel) const { + return static_cast(mapChannel); + } + }; + using Textures = std::unordered_map; + Textures getTextures() { return _textures; } + +protected: + friend class Geometry; Textures _textures; diff --git a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp index acb2bdc1c5..17b62d0915 100644 --- a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp +++ b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp @@ -10,6 +10,62 @@ #include "ModelBakerLogging.h" +#include + +void processMaterialMapping(MaterialMapping& materialMapping, const QJsonObject& materialMap, const hifi::URL& url) { + auto mappingKeys = materialMap.keys(); + for (auto mapping : mappingKeys) { + auto mappingJSON = materialMap[mapping]; + if (mappingJSON.isObject()) { + auto mappingValue = mappingJSON.toObject(); + + // Old subsurface scattering mapping + { + auto scatteringIter = mappingValue.find("scattering"); + auto scatteringMapIter = mappingValue.find("scatteringMap"); + if (scatteringIter != mappingValue.end() || scatteringMapIter != mappingValue.end()) { + std::shared_ptr material = std::make_shared(); + + if (scatteringIter != mappingValue.end()) { + float scattering = (float)scatteringIter.value().toDouble(); + material->setScattering(scattering); + } + + if (scatteringMapIter != mappingValue.end()) { + QString scatteringMap = scatteringMapIter.value().toString(); + material->setScatteringMap(scatteringMap); + } + + material->setDefaultFallthrough(true); + + NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), + [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); + materialResource->moveToThread(qApp->thread()); + materialResource->parsedMaterials.names.push_back("scattering"); + materialResource->parsedMaterials.networkMaterials["scattering"] = material; + + materialMapping.push_back(std::pair("mat::" + mapping.toStdString(), materialResource)); + continue; + } + } + + // Material JSON description + { + NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), + [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); + materialResource->moveToThread(qApp->thread()); + materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), url); + materialMapping.push_back(std::pair(mapping.toStdString(), materialResource)); + } + + } else if (mappingJSON.isString()) { + auto mappingValue = mappingJSON.toString(); + materialMapping.push_back(std::pair(mapping.toStdString(), + MaterialCache::instance().getMaterial(url.resolved(mappingValue)))); + } + } +} + void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { const auto& mapping = input.get0(); const auto& url = input.get1(); @@ -18,56 +74,18 @@ void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, con auto mappingIter = mapping.find("materialMap"); if (mappingIter != mapping.end()) { QByteArray materialMapValue = mappingIter.value().toByteArray(); - QJsonObject materialMap = QJsonDocument::fromJson(materialMapValue).object(); - if (materialMap.isEmpty()) { + QJsonDocument materialMapJSON = QJsonDocument::fromJson(materialMapValue); + if (materialMapJSON.isEmpty()) { qCDebug(model_baker) << "Material Map found but did not produce valid JSON:" << materialMapValue; + } else if (materialMapJSON.isObject()) { + QJsonObject materialMap = materialMapJSON.object(); + processMaterialMapping(materialMapping, materialMap, url); } else { - auto mappingKeys = materialMap.keys(); - for (auto mapping : mappingKeys) { - auto mappingJSON = materialMap[mapping]; - if (mappingJSON.isObject()) { - auto mappingValue = mappingJSON.toObject(); - - // Old subsurface scattering mapping - { - auto scatteringIter = mappingValue.find("scattering"); - auto scatteringMapIter = mappingValue.find("scatteringMap"); - if (scatteringIter != mappingValue.end() || scatteringMapIter != mappingValue.end()) { - std::shared_ptr material = std::make_shared(); - - if (scatteringIter != mappingValue.end()) { - float scattering = (float)scatteringIter.value().toDouble(); - material->setScattering(scattering); - } - - if (scatteringMapIter != mappingValue.end()) { - QString scatteringMap = scatteringMapIter.value().toString(); - material->setScatteringMap(scatteringMap); - } - - material->setDefaultFallthrough(true); - - NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); - materialResource->moveToThread(qApp->thread()); - materialResource->parsedMaterials.names.push_back("scattering"); - materialResource->parsedMaterials.networkMaterials["scattering"] = material; - - materialMapping.push_back(std::pair("mat::" + mapping.toStdString(), materialResource)); - continue; - } - } - - // Material JSON description - { - NetworkMaterialResourcePointer materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); }); - materialResource->moveToThread(qApp->thread()); - materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument(mappingValue), url); - materialMapping.push_back(std::pair(mapping.toStdString(), materialResource)); - } - - } else if (mappingJSON.isString()) { - auto mappingValue = mappingJSON.toString(); - materialMapping.push_back(std::pair(mapping.toStdString(), MaterialCache::instance().getMaterial(url.resolved(mappingValue)))); + QJsonArray materialMapArray = materialMapJSON.array(); + for (auto materialMapIter : materialMapArray) { + if (materialMapIter.isObject()) { + QJsonObject materialMap = materialMapIter.toObject(); + processMaterialMapping(materialMapping, materialMap, url); } } } diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 4cf7609ee9..23b365dd03 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -437,8 +437,8 @@ const QVariantMap Geometry::getTextures() const { QVariantMap textures; for (const auto& material : _materials) { for (const auto& texture : material->_textures) { - if (texture.texture) { - textures[texture.name] = texture.texture->getURL(); + if (texture.second.texture) { + textures[texture.second.name] = texture.second.texture->getURL(); } } } @@ -467,7 +467,7 @@ void Geometry::setTextures(const QVariantMap& textureMap) { for (auto& material : _materials) { // Check if any material textures actually changed if (std::any_of(material->_textures.cbegin(), material->_textures.cend(), - [&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.texture && textureMap.contains(it.name); })) { + [&textureMap](const NetworkMaterial::Textures::value_type& it) { return it.second.texture && textureMap.contains(it.second.name); })) { // FIXME: The Model currently caches the materials (waste of space!) // so they must be copied in the Geometry copy-ctor diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 4cceb5ccb3..6b33012adf 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1546,18 +1546,40 @@ void Model::applyMaterialMapping() { continue; } - auto materialLoaded = [this, networkMaterialResource, shapeIDs, renderItemsKey, primitiveMode, useDualQuaternionSkinning]() { + // This needs to be precomputed before the lambda, since the lambdas could be called out of order + std::unordered_map priorityMapPerResource; + for (auto shapeID : shapeIDs) { + priorityMapPerResource[shapeID] = ++_priorityMap[shapeID]; + } + + auto materialLoaded = [this, networkMaterialResource, shapeIDs, priorityMapPerResource, renderItemsKey, primitiveMode, useDualQuaternionSkinning]() { if (networkMaterialResource->isFailed() || networkMaterialResource->parsedMaterials.names.size() == 0) { return; } render::Transaction transaction; - auto networkMaterial = networkMaterialResource->parsedMaterials.networkMaterials[networkMaterialResource->parsedMaterials.names[0]]; + std::shared_ptr networkMaterial; + { + QString url = networkMaterialResource->getURL().toString(); + bool foundMaterialName = false; + if (url.contains("?")) { + auto split = url.split("?"); + std::string materialName = split.last().toStdString(); + auto networkMaterialIter = networkMaterialResource->parsedMaterials.networkMaterials.find(materialName); + if (networkMaterialIter != networkMaterialResource->parsedMaterials.networkMaterials.end()) { + networkMaterial = networkMaterialIter->second; + foundMaterialName = true; + } + } + if (!foundMaterialName) { + networkMaterial = networkMaterialResource->parsedMaterials.networkMaterials[networkMaterialResource->parsedMaterials.names[0]]; + } + } for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); - graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, ++_priorityMap[shapeID]); + graphics::MaterialLayer material = graphics::MaterialLayer(networkMaterial, priorityMapPerResource.at(shapeID)); _materialMapping[shapeID].push_back(material); transaction.updateItem(itemID, [material, renderItemsKey, invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) { diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 2946db650c..f09db3ed26 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -49,10 +49,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& if (type == MODEL_EXTENSION || type == FBX_EXTENSION) { QUrl bakeableModelURL = getBakeableModelURL(inputUrl); if (!bakeableModelURL.isEmpty()) { - auto getWorkerThreadCallback = []() -> QThread* { - return Oven::instance().getNextWorkerThread(); - }; - _baker = getModelBaker(bakeableModelURL, getWorkerThreadCallback, outputPath); + _baker = getModelBaker(bakeableModelURL, outputPath); if (_baker) { _baker->moveToThread(Oven::instance().getNextWorkerThread()); } @@ -61,7 +58,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& _baker = std::unique_ptr { new JSBaker(inputUrl, outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else if (type == MATERIAL_EXTENSION) { - _baker = std::unique_ptr { new MaterialBaker(inputUrl.toDisplayString(), true, outputPath, QUrl(outputPath)) }; + _baker = std::unique_ptr { new MaterialBaker(inputUrl.toDisplayString(), true, outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else { // If the type doesn't match the above, we assume we have a texture, and the type specified is the diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 05745aad24..b92a310f5d 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -152,10 +152,7 @@ void DomainBaker::addModelBaker(const QString& property, const QString& url, con // setup a ModelBaker for this URL, as long as we don't already have one bool haveBaker = _modelBakers.contains(bakeableModelURL); if (!haveBaker) { - auto getWorkerThreadCallback = []() -> QThread* { - return Oven::instance().getNextWorkerThread(); - }; - QSharedPointer baker = QSharedPointer(getModelBaker(bakeableModelURL, getWorkerThreadCallback, _contentOutputPath).release(), &Baker::deleteLater); + QSharedPointer baker = QSharedPointer(getModelBaker(bakeableModelURL, _contentOutputPath).release(), &Baker::deleteLater); if (baker) { // Hold on to the old url userinfo/query/fragment data so ModelBaker::getFullOutputMappingURL retains that data from the original model URL // Note: The ModelBaker currently doesn't store this in the FST because the equal signs mess up FST parsing. @@ -275,7 +272,7 @@ void DomainBaker::addMaterialBaker(const QString& property, const QString& data, // setup a baker for this material QSharedPointer materialBaker { - new MaterialBaker(data, isURL, _contentOutputPath, _destinationPath), + new MaterialBaker(data, isURL, _contentOutputPath), &MaterialBaker::deleteLater }; diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 79ab733b0c..7561a8e009 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -180,11 +180,7 @@ void ModelBakeWidget::bakeButtonClicked() { QUrl bakeableModelURL = getBakeableModelURL(modelToBakeURL); if (!bakeableModelURL.isEmpty()) { - auto getWorkerThreadCallback = []() -> QThread* { - return Oven::instance().getNextWorkerThread(); - }; - - std::unique_ptr baker = getModelBaker(bakeableModelURL, getWorkerThreadCallback, outputDirectory.path()); + std::unique_ptr baker = getModelBaker(bakeableModelURL, outputDirectory.path()); if (baker) { // everything seems to be in place, kick off a bake for this model now