From 28baed18c0a56fd2b7f3085da12cdef0db7a5365 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Fri, 20 Oct 2017 18:55:41 -0700 Subject: [PATCH] Added code for OBJBaker and moved Texture and Mesh compression to ModelBaker superclass --- libraries/baking/src/FBXBaker.cpp | 404 +++++++++---------- libraries/baking/src/FBXBaker.h | 12 +- libraries/baking/src/ModelBaker.cpp | 532 +++++++++++++++++++++++++ libraries/baking/src/ModelBaker.h | 68 ++++ libraries/baking/src/OBJBaker.cpp | 533 ++++++++++++++++++++++++++ libraries/baking/src/OBJBaker.h | 63 +++ tools/oven/src/Oven.cpp | 13 + tools/oven/src/ui/ModelBakeWidget.cpp | 90 +++-- tools/oven/src/ui/ModelBakeWidget.h | 8 +- 9 files changed, 1452 insertions(+), 271 deletions(-) create mode 100644 libraries/baking/src/ModelBaker.cpp create mode 100644 libraries/baking/src/ModelBaker.h create mode 100644 libraries/baking/src/OBJBaker.cpp create mode 100644 libraries/baking/src/OBJBaker.h diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index e4d7f6bbc9..e49e015c2d 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -51,8 +51,7 @@ FBXBaker::FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGet _fbxURL(fbxURL), _bakedOutputDir(bakedOutputDir), _originalOutputDir(originalOutputDir), - _textureThreadGetter(textureThreadGetter) -{ + _textureThreadGetter(textureThreadGetter) { } @@ -79,7 +78,7 @@ void FBXBaker::abort() { void FBXBaker::bake() { qDebug() << "FBXBaker" << _fbxURL << "bake starting"; - + auto tempDir = PathUtils::generateTemporaryDir(); if (tempDir.isEmpty()) { @@ -162,7 +161,7 @@ void FBXBaker::loadSourceFBX() { // check if the FBX is local or first needs to be downloaded if (_fbxURL.isLocalFile()) { // load up the local file - QFile localFBX { _fbxURL.toLocalFile() }; + QFile localFBX{ _fbxURL.toLocalFile() }; qDebug() << "Local file url: " << _fbxURL << _fbxURL.toString() << _fbxURL.toLocalFile() << ", copying to: " << _originalFBXFilePath; @@ -273,7 +272,7 @@ QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { // in case another texture referenced by this model has the same base name auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; - QString bakedTextureFileName { textureFileInfo.completeBaseName() }; + QString bakedTextureFileName{ textureFileInfo.completeBaseName() }; if (nameMatches > 0) { // there are already nameMatches texture with this name @@ -297,7 +296,7 @@ QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeF if (isEmbedded) { urlToTexture = _fbxURL.toString() + "/" + apparentRelativePath.filePath(); - } else { + } else { if (textureFileInfo.exists() && textureFileInfo.isFile()) { // set the texture URL to the local texture that we have confirmed exists urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); @@ -323,7 +322,7 @@ QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeF void FBXBaker::rewriteAndBakeSceneModels() { unsigned int meshIndex = 0; - bool hasDeformers { false }; + bool hasDeformers{ false }; for (FBXNode& rootChild : _rootNode.children) { if (rootChild.name == "Objects") { for (FBXNode& objectChild : rootChild.children) { @@ -344,178 +343,19 @@ void FBXBaker::rewriteAndBakeSceneModels() { // TODO Pull this out of _geometry instead so we don't have to reprocess it auto extractedMesh = FBXReader::extractMesh(objectChild, meshIndex, false); - auto& mesh = extractedMesh.mesh; - if (mesh.wasCompressed) { - handleError("Cannot re-bake a file that contains compressed mesh"); - return; - } - - Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size()); - Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size()); - Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size()); - - int64_t numTriangles { 0 }; - for (auto& part : mesh.parts) { - if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) { - handleWarning("Found a mesh part with invalid index data, skipping"); - continue; - } - numTriangles += part.quadTrianglesIndices.size() / 3; - numTriangles += part.triangleIndices.size() / 3; - } - - if (numTriangles == 0) { + // Callback to get MaterialID from FBXBaker in ModelBaker + getMaterialIDCallback materialIDcallback = [extractedMesh](int partIndex) {return extractedMesh.partMaterialTextures[partIndex].first;}; + // Compress mesh information and store in dracoMeshNode + FBXNode* dracoMeshNode = this->compressMesh(extractedMesh.mesh, hasDeformers, materialIDcallback); + // if dracoMeshNode is null (i.e error occurred while baking), continue iterating through Object node children + if (!dracoMeshNode) { continue; } - draco::TriangleSoupMeshBuilder meshBuilder; + objectChild.children.push_back(*dracoMeshNode); - meshBuilder.Start(numTriangles); - - bool hasNormals { mesh.normals.size() > 0 }; - bool hasColors { mesh.colors.size() > 0 }; - bool hasTexCoords { mesh.texCoords.size() > 0 }; - bool hasTexCoords1 { mesh.texCoords1.size() > 0 }; - bool hasPerFaceMaterials { mesh.parts.size() > 1 }; - bool needsOriginalIndices { hasDeformers }; - - int normalsAttributeID { -1 }; - int colorsAttributeID { -1 }; - int texCoordsAttributeID { -1 }; - int texCoords1AttributeID { -1 }; - int faceMaterialAttributeID { -1 }; - int originalIndexAttributeID { -1 }; - - const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION, - 3, draco::DT_FLOAT32); - if (needsOriginalIndices) { - originalIndexAttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX, - 1, draco::DT_INT32); - } - - if (hasNormals) { - normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL, - 3, draco::DT_FLOAT32); - } - if (hasColors) { - colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR, - 3, draco::DT_FLOAT32); - } - if (hasTexCoords) { - texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD, - 2, draco::DT_FLOAT32); - } - if (hasTexCoords1) { - texCoords1AttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1, - 2, draco::DT_FLOAT32); - } - if (hasPerFaceMaterials) { - faceMaterialAttributeID = meshBuilder.AddAttribute( - (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID, - 1, draco::DT_UINT16); - } - - - auto partIndex = 0; - draco::FaceIndex face; - for (auto& part : mesh.parts) { - const auto& matTex = extractedMesh.partMaterialTextures[partIndex]; - uint16_t materialID = matTex.first; - - auto addFace = [&](QVector& indices, int index, draco::FaceIndex face) { - int32_t idx0 = indices[index]; - int32_t idx1 = indices[index + 1]; - int32_t idx2 = indices[index + 2]; - - if (hasPerFaceMaterials) { - meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID); - } - - meshBuilder.SetAttributeValuesForFace(positionAttributeID, face, - &mesh.vertices[idx0], &mesh.vertices[idx1], - &mesh.vertices[idx2]); - - if (needsOriginalIndices) { - meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face, - &mesh.originalIndices[idx0], - &mesh.originalIndices[idx1], - &mesh.originalIndices[idx2]); - } - if (hasNormals) { - meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face, - &mesh.normals[idx0], &mesh.normals[idx1], - &mesh.normals[idx2]); - } - if (hasColors) { - meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face, - &mesh.colors[idx0], &mesh.colors[idx1], - &mesh.colors[idx2]); - } - if (hasTexCoords) { - meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face, - &mesh.texCoords[idx0], &mesh.texCoords[idx1], - &mesh.texCoords[idx2]); - } - if (hasTexCoords1) { - meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face, - &mesh.texCoords1[idx0], &mesh.texCoords1[idx1], - &mesh.texCoords1[idx2]); - } - }; - - for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) { - addFace(part.quadTrianglesIndices, i, face++); - } - - for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) { - addFace(part.triangleIndices, i, face++); - } - - partIndex++; - } - - auto dracoMesh = meshBuilder.Finalize(); - - if (!dracoMesh) { - handleWarning("Failed to finalize the baking of a draco Geometry node"); - continue; - } - - // we need to modify unique attribute IDs for custom attributes - // so the attributes are easily retrievable on the other side - if (hasPerFaceMaterials) { - dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID); - } - - if (hasTexCoords1) { - dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1); - } - - if (needsOriginalIndices) { - dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX); - } - - draco::Encoder encoder; - - encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14); - encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12); - encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10); - encoder.SetSpeedOptions(0, 5); - - draco::EncoderBuffer buffer; - encoder.EncodeMeshToBuffer(*dracoMesh, &buffer); - - FBXNode dracoMeshNode; - dracoMeshNode.name = "DracoMesh"; - auto value = QVariant::fromValue(QByteArray(buffer.data(), (int) buffer.size())); - dracoMeshNode.properties.append(value); - - objectChild.children.push_back(dracoMeshNode); - - static const std::vector nodeNamesToDelete { + static const std::vector nodeNamesToDelete{ // Node data that is packed into the draco mesh "Vertices", "PolygonVertexIndex", @@ -548,6 +388,7 @@ void FBXBaker::rewriteAndBakeSceneModels() { } } + void FBXBaker::rewriteAndBakeSceneTextures() { using namespace image::TextureUsage; QHash textureTypes; @@ -591,69 +432,97 @@ void FBXBaker::rewriteAndBakeSceneTextures() { if (textureChild.name == "RelativeFilename") { // use QFileInfo to easily split up the existing texture filename into its components - QString fbxTextureFileName { textureChild.properties.at(0).toByteArray() }; - QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") }; + QString fbxTextureFileName{ textureChild.properties.at(0).toByteArray() }; - if (hasErrors()) { - return; - } + // Callback to get texture content and type from FBXBaker in ModelBaker + //auto textureContent = _textureContent.value(fbxTextureFileName.toLocal8Bit()); + //qCDebug(model_baking) << "TextureContent" << textureContent.size(); + + getTextureContentTypeCallback textureContentTypeCallback = [=]() { + QPair result; + result.first = _textureContent.value(fbxTextureFileName.toLocal8Bit());; + result.second = textureTypes[object->properties[0].toByteArray()]; + return result; + }; + + // Compress the texture information and return the new filename to be added into the FBX scene + QByteArray* bakedTextureFile = this->compressTexture(fbxTextureFileName, _fbxURL, _bakedOutputDir, _textureThreadGetter, textureContentTypeCallback, _originalOutputDir); + + // If no errors or warnings have occurred during texture compression add the filename to the FBX scene + if (bakedTextureFile) { + textureChild.properties[0] = *bakedTextureFile; + } else { + if (hasErrors()) { + return; + } else if (hasWarnings()) { + continue; + } + } - if (textureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) { - // re-baking an FBX that already references baked textures is a fail - // so we add an error and return from here - handleError("Cannot re-bake a file that references compressed textures"); - return; - } + //QFileInfo textureFileInfo{ fbxTextureFileName.replace("\\", "/") }; - if (!TextureBaker::getSupportedFormats().contains(textureFileInfo.suffix())) { - // this is a texture format we don't bake, skip it - handleWarning(fbxTextureFileName + " is not a bakeable texture format"); - continue; - } + //if (hasErrors()) { + // return; + //} - // make sure this texture points to something and isn't one we've already re-mapped - if (!textureFileInfo.filePath().isEmpty()) { - // check if this was an embedded texture we have already have in-memory content for - auto textureContent = _textureContent.value(fbxTextureFileName.toLocal8Bit()); + //if (textureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) { + // // re-baking an FBX that already references baked textures is a fail + // // so we add an error and return from here + // handleError("Cannot re-bake a file that references compressed textures"); - // figure out the URL to this texture, embedded or external - auto urlToTexture = getTextureURL(textureFileInfo, fbxTextureFileName, - !textureContent.isNull()); + // return; + //} - QString bakedTextureFileName; - if (_remappedTexturePaths.contains(urlToTexture)) { - bakedTextureFileName = _remappedTexturePaths[urlToTexture]; - } else { - // 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 - bakedTextureFileName = createBakedTextureFileName(textureFileInfo); - _remappedTexturePaths[urlToTexture] = bakedTextureFileName; - } + //if (!TextureBaker::getSupportedFormats().contains(textureFileInfo.suffix())) { + // // this is a texture format we don't bake, skip it + // handleWarning(fbxTextureFileName + " is not a bakeable texture format"); + // continue; + //} - qCDebug(model_baking).noquote() << "Re-mapping" << fbxTextureFileName - << "to" << bakedTextureFileName; + //// make sure this texture points to something and isn't one we've already re-mapped + //if (!textureFileInfo.filePath().isEmpty()) { + // // check if this was an embedded texture we have already have in-memory content for + // auto textureContent = _textureContent.value(fbxTextureFileName.toLocal8Bit()); - QString bakedTextureFilePath { - _bakedOutputDir + "/" + bakedTextureFileName - }; + // // figure out the URL to this texture, embedded or external + // qCDebug(model_baking) << "TextureContent" << textureContent.size(); + // auto urlToTexture = getTextureURL(textureFileInfo, fbxTextureFileName, + // !textureContent.isNull()); - // write the new filename into the FBX scene - textureChild.properties[0] = bakedTextureFileName.toLocal8Bit(); + // QString bakedTextureFileName; + // if (_remappedTexturePaths.contains(urlToTexture)) { + // bakedTextureFileName = _remappedTexturePaths[urlToTexture]; + // } else { + // // 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 + // bakedTextureFileName = createBakedTextureFileName(textureFileInfo); + // _remappedTexturePaths[urlToTexture] = bakedTextureFileName; + // } - if (!_bakingTextures.contains(urlToTexture)) { - _outputFiles.push_back(bakedTextureFilePath); + // qCDebug(model_baking).noquote() << "Re-mapping" << fbxTextureFileName + // << "to" << bakedTextureFileName; - // grab the ID for this texture so we can figure out the - // texture type from the loaded materials - QString textureID { object->properties[0].toByteArray() }; - auto textureType = textureTypes[textureID]; + // QString bakedTextureFilePath{ + // _bakedOutputDir + "/" + bakedTextureFileName + // }; - // bake this texture asynchronously - bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent); - } - } + // // write the new filename into the FBX scene + // textureChild.properties[0] = bakedTextureFileName.toLocal8Bit(); + + // if (!_bakingTextures.contains(urlToTexture)) { + // _outputFiles.push_back(bakedTextureFilePath); + + // // grab the ID for this texture so we can figure out the + // // texture type from the loaded materials + // QString textureID{ object->properties[0].toByteArray() }; + // auto textureType = textureTypes[textureID]; + + // // bake this texture asynchronously + // bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent); + // } + //} } } @@ -670,10 +539,93 @@ void FBXBaker::rewriteAndBakeSceneTextures() { } } +//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 : _geometry->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 fbxTextureFileName{ textureChild.properties.at(0).toByteArray() }; +// +// // Callback to get texture content and type from FBXBaker in ModelBaker +// auto textureContent = _textureContent.value(fbxTextureFileName.toLocal8Bit()); +// qCDebug(model_baking) << "TextureContent" << textureContent; +// +// getTextureContentTypeCallback textureContentTypeCallback = [fbxTextureFileName, textureTypes, object, textureContent]() { +// QPair result; +// result.first = textureContent; +// result.second = textureTypes[object->properties[0].toByteArray()]; +// return result; +// }; +// +// // Compress the texture information and return the new filename to be added into the FBX scene +// QByteArray* bakedTextureFile = this->compressTexture(fbxTextureFileName, _fbxURL, _bakedOutputDir, _textureThreadGetter, textureContentTypeCallback, _originalOutputDir); +// +// // If no errors or warnings have occurred during texture compression add the filename to the FBX scene +// if (bakedTextureFile) { +// textureChild.properties[0] = *bakedTextureFile; +// } else { +// 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; +// } +// } +// } +// } +//} + void FBXBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, 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 { + QSharedPointer bakingTexture{ new TextureBaker(textureURL, textureType, outputDir, bakedFilename, textureContent), &TextureBaker::deleteLater }; @@ -713,7 +665,7 @@ void FBXBaker::handleBakedTexture() { // check if we have a relative path to use for the texture auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); - QFile originalTextureFile { + QFile originalTextureFile{ _originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName() }; diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index a6034ee2b7..dbb7ab823e 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -19,7 +19,7 @@ #include "Baker.h" #include "TextureBaker.h" - +#include "ModelBaker.h" #include "ModelBakingLoggingCategory.h" #include @@ -30,7 +30,7 @@ static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; using TextureBakerThreadGetter = std::function; -class FBXBaker : public Baker { +class FBXBaker : public ModelBaker { Q_OBJECT public: FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter, @@ -42,14 +42,14 @@ public: virtual void setWasAborted(bool wasAborted) override; -public slots: + public slots: virtual void bake() override; virtual void abort() override; signals: void sourceCopyReadyToLoad(); -private slots: + private slots: void bakeSourceCopy(); void handleFBXNetworkReply(); void handleBakedTexture(); @@ -79,7 +79,7 @@ private: FBXNode _rootNode; FBXGeometry* _geometry; QHash _textureContent; - + QString _bakedFBXFilePath; QString _bakedOutputDir; @@ -96,7 +96,7 @@ private: TextureBakerThreadGetter _textureThreadGetter; - bool _pendingErrorEmission { false }; + bool _pendingErrorEmission{ false }; }; #endif // hifi_FBXBaker_h diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp new file mode 100644 index 0000000000..1ac27ce570 --- /dev/null +++ b/libraries/baking/src/ModelBaker.cpp @@ -0,0 +1,532 @@ +// +// ModelBaker.cpp +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ModelBaker.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include + +#include "ModelBakingLoggingCategory.h" +#include "TextureBaker.h" + +#include "FBXBaker.h" + +#ifdef _WIN32 +#pragma warning( push ) +#pragma warning( disable : 4267 ) +#endif + +#include +#include + +#ifdef _WIN32 +#pragma warning( pop ) +#endif + +ModelBaker::ModelBaker() {} + +void ModelBaker::bake() {} + +//void ModelBaker::abort() { +// Baker::abort(); +// +// // tell our underlying TextureBaker instances to abort +// // the FBXBaker will wait until all are aborted before emitting its own abort signal +// for (auto& textureBaker : _bakingTextures) { +// textureBaker->abort(); +// } +//} + +FBXNode* ModelBaker::compressMesh(FBXMesh& mesh, bool hasDeformers, getMaterialIDCallback materialIDCallback) { + if (mesh.wasCompressed) { + handleError("Cannot re-bake a file that contains compressed mesh"); + return nullptr; + } + + Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size()); + Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size()); + Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size()); + + int64_t numTriangles{ 0 }; + for (auto& part : mesh.parts) { + if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) { + handleWarning("Found a mesh part with invalid index data, skipping"); + continue; + } + numTriangles += part.quadTrianglesIndices.size() / 3; + numTriangles += part.triangleIndices.size() / 3; + } + + if (numTriangles == 0) { + return nullptr; + } + + draco::TriangleSoupMeshBuilder meshBuilder; + + meshBuilder.Start(numTriangles); + + bool hasNormals{ mesh.normals.size() > 0 }; + bool hasColors{ mesh.colors.size() > 0 }; + bool hasTexCoords{ mesh.texCoords.size() > 0 }; + bool hasTexCoords1{ mesh.texCoords1.size() > 0 }; + bool hasPerFaceMaterials; + if (materialIDCallback) { + if (mesh.parts.size() > 1 || materialIDCallback(0) != 0) { + hasPerFaceMaterials = true; + } + } else { + hasPerFaceMaterials = true; + } + + bool needsOriginalIndices{ hasDeformers }; + + int normalsAttributeID{ -1 }; + int colorsAttributeID{ -1 }; + int texCoordsAttributeID{ -1 }; + int texCoords1AttributeID{ -1 }; + int faceMaterialAttributeID{ -1 }; + int originalIndexAttributeID{ -1 }; + + const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION, + 3, draco::DT_FLOAT32); + if (needsOriginalIndices) { + originalIndexAttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX, + 1, draco::DT_INT32); + } + + if (hasNormals) { + normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL, + 3, draco::DT_FLOAT32); + } + if (hasColors) { + colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR, + 3, draco::DT_FLOAT32); + } + if (hasTexCoords) { + texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD, + 2, draco::DT_FLOAT32); + } + if (hasTexCoords1) { + texCoords1AttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1, + 2, draco::DT_FLOAT32); + } + if (hasPerFaceMaterials) { + faceMaterialAttributeID = meshBuilder.AddAttribute( + (draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID, + 1, draco::DT_UINT16); + } + + + auto partIndex = 0; + draco::FaceIndex face; + uint16_t materialID; + + for (auto& part : mesh.parts) { + if (materialIDCallback) { + materialID = materialIDCallback(partIndex); + } else { + materialID = partIndex; + } + + auto addFace = [&](QVector& indices, int index, draco::FaceIndex face) { + int32_t idx0 = indices[index]; + int32_t idx1 = indices[index + 1]; + int32_t idx2 = indices[index + 2]; + + if (hasPerFaceMaterials) { + meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID); + } + + meshBuilder.SetAttributeValuesForFace(positionAttributeID, face, + &mesh.vertices[idx0], &mesh.vertices[idx1], + &mesh.vertices[idx2]); + + if (needsOriginalIndices) { + meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face, + &mesh.originalIndices[idx0], + &mesh.originalIndices[idx1], + &mesh.originalIndices[idx2]); + } + if (hasNormals) { + meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face, + &mesh.normals[idx0], &mesh.normals[idx1], + &mesh.normals[idx2]); + } + if (hasColors) { + meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face, + &mesh.colors[idx0], &mesh.colors[idx1], + &mesh.colors[idx2]); + } + if (hasTexCoords) { + meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face, + &mesh.texCoords[idx0], &mesh.texCoords[idx1], + &mesh.texCoords[idx2]); + } + if (hasTexCoords1) { + meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face, + &mesh.texCoords1[idx0], &mesh.texCoords1[idx1], + &mesh.texCoords1[idx2]); + } + }; + + for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) { + addFace(part.quadTrianglesIndices, i, face++); + } + + for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) { + addFace(part.triangleIndices, i, face++); + } + + partIndex++; + } + + auto dracoMesh = meshBuilder.Finalize(); + + if (!dracoMesh) { + handleWarning("Failed to finalize the baking of a draco Geometry node"); + return nullptr; + } + + // we need to modify unique attribute IDs for custom attributes + // so the attributes are easily retrievable on the other side + if (hasPerFaceMaterials) { + dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID); + } + + if (hasTexCoords1) { + dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1); + } + + if (needsOriginalIndices) { + dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX); + } + + draco::Encoder encoder; + + encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14); + encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12); + encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10); + encoder.SetSpeedOptions(0, 5); + + draco::EncoderBuffer buffer; + encoder.EncodeMeshToBuffer(*dracoMesh, &buffer); + + static FBXNode dracoMeshNode; + dracoMeshNode.name = "DracoMesh"; + auto value = QVariant::fromValue(QByteArray(buffer.data(), (int)buffer.size())); + dracoMeshNode.properties.append(value); + + return &dracoMeshNode; +} + +QByteArray* ModelBaker::compressTexture(QString modelTextureFileName, QUrl modelURL, QString bakedOutputDir, TextureBakerThreadGetter textureThreadGetter, + getTextureContentTypeCallback textureContentTypeCallback, const QString& originalOutputDir) { + _modelURL = modelURL; + _textureThreadGetter = textureThreadGetter; + _originalOutputDir = originalOutputDir; + + static QByteArray textureChild; + + QPair textureContentType; + // grab the ID for this texture so we can figure out the + // texture type from the loaded materials + textureContentType = textureContentTypeCallback(); + + QByteArray textureContent = textureContentType.first; + image::TextureUsage::Type textureType = textureContentType.second; + + QFileInfo modelTextureFileInfo{ modelTextureFileName.replace("\\", "/") }; + + if (modelTextureFileInfo.suffix() == BAKED_TEXTURE_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 nullptr; + } + + if (!TextureBaker::getSupportedFormats().contains(modelTextureFileInfo.suffix())) { + // this is a texture format we don't bake, skip it + handleWarning(modelTextureFileName + " is not a bakeable texture format"); + return nullptr; + } + + // make sure this texture points to something and isn't one we've already re-mapped + if (!modelTextureFileInfo.filePath().isEmpty()) { + // check if this was an embedded texture that we already have in-memory content for + + // figure out the URL to this texture, embedded or external + //qCDebug(model_baking) << "TextureContent" << !textureContent.isNull(); + auto urlToTexture = getTextureURL(modelTextureFileInfo, modelTextureFileName, + true); + + QString bakedTextureFileName; + if (_remappedTexturePaths.contains(urlToTexture)) { + bakedTextureFileName = _remappedTexturePaths[urlToTexture]; + } else { + // 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 + bakedTextureFileName = createBakedTextureFileName(modelTextureFileInfo); + _remappedTexturePaths[urlToTexture] = bakedTextureFileName; + } + + qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName + << "to" << bakedTextureFileName; + + QString bakedTextureFilePath{ + bakedOutputDir + "/" + bakedTextureFileName + }; + + // write the new filename into the FBX scene + textureChild = bakedTextureFileName.toLocal8Bit(); + + if (!_bakingTextures.contains(urlToTexture)) { + _outputFiles.push_back(bakedTextureFilePath); + + // bake this texture asynchronously + qCDebug(model_baking) << "URLHere" << urlToTexture; + bakeTexture(urlToTexture, textureType, bakedOutputDir, bakedTextureFileName, textureContent); + } + } + + return &textureChild; +} + +QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded) { + QUrl urlToTexture; + + // use QFileInfo to easily split up the existing texture filename into its components + auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); + + if (isEmbedded) { + urlToTexture = _modelURL.toString() + "/" + apparentRelativePath.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 FBX + if (_modelURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) { + // the absolute path we ran into for the texture in the FBX exists on this machine + // so use that file + urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); + } else { + // we didn't find the texture on this machine at the absolute path + // so assume that it is right beside the FBX to match the behaviour of interface + urlToTexture = _modelURL.resolved(apparentRelativePath.fileName()); + } + } + } + + return urlToTexture; +} + +void ModelBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, + 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(textureURL, textureType, 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(textureURL, 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()); + + // 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 FBX + // 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 FBX + + 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()); + + 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()); + + // 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()); + + checkIfTexturesFinished(); + } + } +} + +QString ModelBaker::texturePathRelativeToModel(QUrl fbxURL, QUrl textureURL) { + auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); + auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); + + if (texturePath.startsWith(fbxPath)) { + // texture path is a child of the FBX path, return the texture path without the fbx path + return texturePath.mid(fbxPath.length()); + } else { + // the texture path was not a child of the FBX path, return the empty string + return ""; + } +} + +void ModelBaker::checkIfTexturesFinished() { + // check if we're done everything we need to do for this FBX + // 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; + + setIsFinished(true); + } + } +} + +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()); + + if (bakedTexture) { + _bakingTextures.remove(bakedTexture->getTextureURL()); + } + + // 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(); +} + +QString ModelBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { + // first make sure we have a unique base name for this texture + // in case another texture referenced by this model has the same base name + auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; + + QString bakedTextureFileName{ textureFileInfo.completeBaseName() }; + + if (nameMatches > 0) { + // there are already nameMatches texture with this name + // append - and that number to our baked texture file name so that it is unique + bakedTextureFileName += "-" + QString::number(nameMatches); + } + + bakedTextureFileName += BAKED_TEXTURE_EXT; + + // increment the number of name matches + ++nameMatches; + + return bakedTextureFileName; +} + +void ModelBaker::setWasAborted(bool wasAborted) { + if (wasAborted != _wasAborted.load()) { + Baker::setWasAborted(wasAborted); + + if (wasAborted) { + qCDebug(model_baking) << "Aborted baking" << _modelURL; + } + } +} diff --git a/libraries/baking/src/ModelBaker.h b/libraries/baking/src/ModelBaker.h new file mode 100644 index 0000000000..efe6dff874 --- /dev/null +++ b/libraries/baking/src/ModelBaker.h @@ -0,0 +1,68 @@ +// +// ModelBaker.h +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelBaker_h +#define hifi_ModelBaker_h + +#include +#include +#include +#include + +#include "Baker.h" +#include "TextureBaker.h" + +#include "ModelBakingLoggingCategory.h" + +#include + +#include + +using TextureBakerThreadGetter = std::function; +using getMaterialIDCallback = std::function ; +using getTextureContentTypeCallback = std::function()>; + +class ModelBaker : public Baker{ + Q_OBJECT + +public: + ModelBaker(); + FBXNode* compressMesh(FBXMesh& mesh, bool hasDeformers, getMaterialIDCallback materialIDCallback = NULL); + QByteArray* compressTexture(QString textureFileName, QUrl modelUrl, QString bakedOutputDir, TextureBakerThreadGetter textureThreadGetter, + getTextureContentTypeCallback textureContentTypeCallback = NULL, const QString& originalOutputDir = ""); + virtual void setWasAborted(bool wasAborted) override; + +public slots: + virtual void bake() override; + //virtual void abort() override; + +private slots: + void handleBakedTexture(); + void handleAbortedTexture(); + +private: + QString createBakedTextureFileName(const QFileInfo & textureFileInfo); + QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false); + void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir, + const QString & bakedFilename, const QByteArray & textureContent); + QString texturePathRelativeToModel(QUrl fbxURL, QUrl textureURL); + void checkIfTexturesFinished(); + + QHash _textureNameMatchCount; + QHash _remappedTexturePaths; + QUrl _modelURL; + QMultiHash> _bakingTextures; + TextureBakerThreadGetter _textureThreadGetter; + QString _originalOutputDir; + bool _pendingErrorEmission{ false }; +}; + +#endif // hifi_ModelBaker_h diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp new file mode 100644 index 0000000000..a0eed5c5a8 --- /dev/null +++ b/libraries/baking/src/OBJBaker.cpp @@ -0,0 +1,533 @@ +// +// OBJBaker.cpp +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "OBJBaker.h" +#include "OBJReader.h" +#include "FBXWriter.h" + +OBJBaker::OBJBaker(const QUrl& objURL, TextureBakerThreadGetter textureThreadGetter, + const QString& bakedOutputDir, const QString& originalOutputDir) : + _objURL(objURL), + _bakedOutputDir(bakedOutputDir), + _originalOutputDir(originalOutputDir), + _textureThreadGetter(textureThreadGetter) +{ + +} + +OBJBaker::~OBJBaker() { + if (_tempDir.exists()) { + if (!_tempDir.remove(_originalOBJFilePath)) { + qCWarning(model_baking) << "Failed to remove temporary copy of fbx file:" << _originalOBJFilePath; + } + if (!_tempDir.rmdir(".")) { + qCWarning(model_baking) << "Failed to remove temporary directory:" << _tempDir; + } + } +} + +void OBJBaker::bake() { + qDebug() << "OBJBaker" << _objURL << "bake starting"; + + auto tempDir = PathUtils::generateTemporaryDir(); + + if (tempDir.isEmpty()) { + handleError("Failed to create a temporary directory."); + return; + } + + _tempDir = tempDir; + + _originalOBJFilePath = _tempDir.filePath(_objURL.fileName()); + qDebug() << "Made temporary dir " << _tempDir; + qDebug() << "Origin file path: " << _originalOBJFilePath; + + // trigger startBake once OBJ is loaded + connect(this, &OBJBaker::OBJLoaded, this, &OBJBaker::startBake); + + // make a local copy of the OBJ + loadOBJ(); +} + +void OBJBaker::loadOBJ() { + // check if the OBJ is local or it needs to be downloaded + if (_objURL.isLocalFile()) { + // loading the local OBJ + QFile localOBJ{ _objURL.toLocalFile() }; + + qDebug() << "Local file url: " << _objURL << _objURL.toString() << _objURL.toLocalFile() << ", copying to: " << _originalOBJFilePath; + + if (!localOBJ.exists()) { + handleError("Could not find " + _objURL.toString()); + return; + } + + // make a copy in the output folder + if (!_originalOutputDir.isEmpty()) { + qDebug() << "Copying to: " << _originalOutputDir << "/" << _objURL.fileName(); + localOBJ.copy(_originalOutputDir + "/" + _objURL.fileName()); + } + + localOBJ.copy(_originalOBJFilePath); + + // local OBJ is loaded emit signal to trigger its baking + emit OBJLoaded(); + } else { + // OBJ is remote, start download + auto& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest networkRequest; + + // setup the request to follow re-directs and always hit the network + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + networkRequest.setUrl(_objURL); + + qCDebug(model_baking) << "Downloading" << _objURL; + auto networkReply = networkAccessManager.get(networkRequest); + + connect(networkReply, &QNetworkReply::finished, this, &OBJBaker::handleOBJNetworkReply); + } +} + +void OBJBaker::handleOBJNetworkReply() { + auto requestReply = qobject_cast(sender()); + + if (requestReply->error() == QNetworkReply::NoError) { + qCDebug(model_baking) << "Downloaded" << _objURL; + + // grab the contents of the reply and make a copy in the output folder + QFile copyOfOriginal(_originalOBJFilePath); + + qDebug(model_baking) << "Writing copy of original obj to" << _originalOBJFilePath << copyOfOriginal.fileName(); + + if (!copyOfOriginal.open(QIODevice::WriteOnly)) { + // add an error to the error list for this obj stating that a duplicate of the original obj could not be made + handleError("Could not create copy of " + _objURL.toString() + " (Failed to open " + _originalOBJFilePath + ")"); + return; + } + if (copyOfOriginal.write(requestReply->readAll()) == -1) { + handleError("Could not create copy of " + _objURL.toString() + " (Failed to write)"); + return; + } + + // close that file now that we are done writing to it + copyOfOriginal.close(); + + if (!_originalOutputDir.isEmpty()) { + copyOfOriginal.copy(_originalOutputDir + "/" + _objURL.fileName()); + } + + // emit our signal to start the import of the obj source copy + emit OBJLoaded(); + } else { + // add an error to our list stating that the obj could not be downloaded + handleError("Failed to download " + _objURL.toString()); + } +} + + +void OBJBaker::startBake() { + // Read the OBJ + QFile objFile(_originalOBJFilePath); + if (!objFile.open(QIODevice::ReadOnly)) { + handleError("Error opening " + _originalOBJFilePath + " for reading"); + return; + } + + QByteArray objData = objFile.readAll(); + + bool combineParts = true; + OBJReader reader; + FBXGeometry* geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _objURL); + + // Write OBJ Data in FBX tree nodes + FBXNode objRoot; + createFBXNodeTree(&objRoot, geometry); + + // Serialize the resultant FBX tree + auto encodedFBX = FBXWriter::encodeFBX(objRoot); + + // Export as baked FBX + auto fileName = _objURL.fileName(); + auto baseName = fileName.left(fileName.lastIndexOf('.')); + auto bakedFilename = baseName + ".baked.fbx"; + + _bakedOBJFilePath = _bakedOutputDir + "/" + bakedFilename; + + QFile bakedFile; + bakedFile.setFileName(_bakedOBJFilePath); + if (!bakedFile.open(QIODevice::WriteOnly)) { + handleError("Error opening " + _bakedOBJFilePath + " for writing"); + return; + } + + bakedFile.write(encodedFBX); + + // Export successful + _outputFiles.push_back(_bakedOBJFilePath); + qCDebug(model_baking) << "Exported" << _objURL << "to" << _bakedOBJFilePath; + // Export done emit finished + emit finished(); +} + +void OBJBaker::createFBXNodeTree(FBXNode* objRoot, FBXGeometry* geometry) { + // Generating FBX Header Node + FBXNode headerNode; + headerNode.name = "FBXHeaderExtension"; + + // Generating global settings node + // Required for Unit Scale Factor + FBXNode globalSettingsNode; + globalSettingsNode.name = "GlobalSettings"; + FBXNode properties70Node; + properties70Node.name = "Properties70"; + FBXNode pNode; + pNode.name = "P"; + setProperties(&pNode); + properties70Node.children = { pNode }; + globalSettingsNode.children = { properties70Node }; + + // Generating Object node + _objectNode.name = "Objects"; + + // Generating Object node's child - Geometry node + FBXNode geometryNode; + geometryNode.name = "Geometry"; + setProperties(&geometryNode); + + // Compress the mesh information and store in dracoNode + bool hasDeformers = false; + ModelBaker modelBaker; + FBXNode* dracoNode = this->compressMesh(geometry->meshes[0], hasDeformers); + geometryNode.children.append(*dracoNode); + + // Generating Object node's child - Model node + FBXNode modelNode; + modelNode.name = "Model"; + setProperties(&modelNode); + _objectNode.children = { geometryNode, modelNode }; + + // Generating Objects node's child - Material node + QVector meshParts = geometry->meshes[0].parts; + for (auto p : meshParts) { + FBXNode materialNode; + materialNode.name = "Material"; + if (geometry->materials.size() == 1) { + foreach(QString materialID, geometry->materials.keys()) { + setMaterialNodeProperties(&materialNode, materialID, geometry); + } + } else { + setMaterialNodeProperties(&materialNode, p.materialID, geometry); + } + _objectNode.children.append(materialNode); + } + + // Texture Node + int count = 0; + for (int i = 0;i < meshParts.size();i++) { + QString material = meshParts[i].materialID; + FBXMaterial currentMaterial = geometry->materials[material]; + if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) { + _textureID = _nodeID; + mapTextureMaterial.push_back(QPair(_textureID, i)); + QVariant property0(_nodeID++); + FBXNode textureNode; + textureNode.name = "Texture"; + textureNode.properties = { property0 }; + + FBXNode textureNameNode; + textureNameNode.name = "TextureName"; + QByteArray propertyString = (!currentMaterial.albedoTexture.filename.isEmpty()) ? "Kd" : "Ka"; + auto prop0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + textureNameNode.properties = { prop0 }; + + FBXNode relativeFilenameNode; + relativeFilenameNode.name = "RelativeFilename"; + QByteArray textureFileName = (!currentMaterial.albedoTexture.filename.isEmpty()) ? currentMaterial.albedoTexture.filename : currentMaterial.specularTexture.filename; + getTextureContentTypeCallback textureContentTypeCallback = [=]() { + QPair result; + result.first = NULL; + result.second = (!currentMaterial.albedoTexture.filename.isEmpty()) ? image::TextureUsage::Type::ALBEDO_TEXTURE : image::TextureUsage::Type::SPECULAR_TEXTURE; + return result; + }; + QByteArray* textureFile = this->compressTexture(textureFileName, _objURL, _bakedOutputDir, _textureThreadGetter, textureContentTypeCallback,_originalOutputDir); + QVariant textureProperty0; + textureProperty0 = QVariant::fromValue(QByteArray(textureFile->data(), (int)textureFile->size())); + relativeFilenameNode.properties = { textureProperty0 }; + + FBXNode properties70Node; + properties70Node.name = "Properties70"; + + QVariant texProperty0; + QVariant texProperty1; + QVariant texProperty2; + QVariant texProperty3; + QVariant texProperty4; + + double value; + + // Set UseMaterial + FBXNode pUseMaterial; + pUseMaterial.name = "P"; + propertyString = "UseMaterial"; + texProperty0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "bool"; + texProperty1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + texProperty2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + texProperty3 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + + int texVal = 1; + texProperty4 = texVal; + + pUseMaterial.properties = { texProperty0, texProperty1, texProperty2, texProperty3, texProperty4 }; + + FBXNode pUVSet; + pUVSet.name = "P"; + propertyString = "UVSet"; + texProperty0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "KString"; + texProperty1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + texProperty2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + texProperty3 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + texProperty4 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + + pUVSet.properties = { texProperty0, texProperty1, texProperty2, texProperty3, texProperty4 }; + + FBXNode pUseMipMap; + pUseMipMap.name = "P"; + propertyString = "UseMipMap"; + texProperty0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "bool"; + texProperty1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + texProperty2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + texProperty3 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + + texVal = 1; + texProperty4 = texVal; + + pUseMipMap.properties = { texProperty0, texProperty1, texProperty2, texProperty3, texProperty4 }; + + properties70Node.children = { pUVSet, pUseMaterial, pUseMipMap }; + + textureNode.children = { textureNameNode,relativeFilenameNode, properties70Node }; + + _objectNode.children.append(textureNode); + } + } + + // Generating Connections node + FBXNode connectionsNode; + connectionsNode.name = "Connections"; + // connect Geometry -> Model + FBXNode cNode1; + cNode1.name = "C"; + QByteArray propertyString("OO"); + QVariant property0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + qlonglong childID = _geometryID; + QVariant property1(childID); + qlonglong parentID = _modelID; + QVariant property2(parentID); + cNode1.properties = { property0, property1, property2 }; + connectionsNode.children = { cNode1}; + + // connect materials to model + for (int i = 0;i < geometry->materials.size();i++) { + FBXNode cNode; + cNode.name = "C"; + property0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + property1 = _materialIDs[i]; + property2 = _modelID; + cNode.properties = { property0, property1, property2 }; + connectionsNode.children.append(cNode); + } + + // Texture to material + for (int i = 0;i < mapTextureMaterial.size();i++) { + FBXNode cNode2; + cNode2.name = "C"; + QByteArray propertyString1("OP"); + property0 = QVariant::fromValue(QByteArray(propertyString1.data(), (int)propertyString1.size())); + property1 = mapTextureMaterial[i].first; + int matID = mapTextureMaterial[i].second; + property2 = _materialIDs[matID]; + propertyString1 = "AmbientFactor"; + QVariant connectionProperty = QVariant::fromValue(QByteArray(propertyString1.data(), (int)propertyString1.size())); + cNode2.properties = { property0, property1, property2, connectionProperty }; + connectionsNode.children.append(cNode2); + + FBXNode cNode4; + cNode4.name = "C"; + propertyString1 = "OP"; + property0 = QVariant::fromValue(QByteArray(propertyString1.data(), (int)propertyString1.size())); + property1 = mapTextureMaterial[i].first; + property2 = _materialIDs[matID]; + propertyString1 = "DiffuseColor"; + connectionProperty = QVariant::fromValue(QByteArray(propertyString1.data(), (int)propertyString1.size())); + cNode4.properties = { property0, property1, property2, connectionProperty }; + connectionsNode.children.append(cNode4); + } + + + // Connect all generated nodes to rootNode + objRoot->children = { globalSettingsNode, _objectNode, connectionsNode }; +} + +void OBJBaker::setProperties(FBXNode* parentNode) { + if (parentNode->name == "P") { + QByteArray propertyString("UnitScaleFactor"); + QVariant property0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "double"; + QVariant property1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "Number"; + QVariant property2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + QVariant property3 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + double unitScaleFactor = 100; + QVariant property4(unitScaleFactor); + + parentNode->properties = { property0, property1, property2, property3, property4 }; + } else if (parentNode->name == "Geometry") { + _geometryID = _nodeID; + QVariant property0(_nodeID++); + QByteArray propertyString("Geometry"); + QVariant property1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "Mesh"; + QVariant property2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + + parentNode->properties = { property0, property1, property2 }; + } else if (parentNode->name == "Model") { + _modelID = _nodeID; + QVariant property0(_nodeID++); + QByteArray propertyString("Model"); + QVariant property1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "Mesh"; + QVariant property2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + + parentNode->properties = { property0, property1, property2 }; + } +} + +void OBJBaker::setMaterialNodeProperties(FBXNode* materialNode, QString material, FBXGeometry* geometry) { + // Set materialNode properties + _materialIDs.push_back(_nodeID); + QVariant property0(_nodeID++); + QByteArray propertyString(material.toLatin1()); + QVariant property1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "Mesh"; + QVariant property2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + + materialNode->properties = { property0, property1, property2 }; + + FBXMaterial currentMaterial = geometry->materials[material]; + + FBXNode properties70Node; + properties70Node.name = "Properties70"; + + QVariant materialProperty0; + QVariant materialProperty1; + QVariant materialProperty2; + QVariant materialProperty3; + QVariant materialProperty4; + QVariant materialProperty5; + QVariant materialProperty6; + + double value; + + // Set diffuseColor + FBXNode pNodeDiffuseColor; + pNodeDiffuseColor.name = "P"; + propertyString = "DiffuseColor"; + materialProperty0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "Color"; + materialProperty1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + materialProperty2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "A"; + materialProperty3 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + value = (double)currentMaterial.diffuseColor[0]; + materialProperty4 = value; + value = (double)currentMaterial.diffuseColor[1]; + materialProperty5 = value; + value = (double)currentMaterial.diffuseColor[2]; + materialProperty6 = value; + + pNodeDiffuseColor.properties = { materialProperty0, materialProperty1, materialProperty2, materialProperty3, materialProperty4, materialProperty5, materialProperty6 }; + properties70Node.children.append(pNodeDiffuseColor); + + // Set specularColor + FBXNode pNodeSpecularColor; + pNodeSpecularColor.name = "P"; + propertyString = "SpecularColor"; + materialProperty0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "Color"; + materialProperty1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + materialProperty2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "A"; + materialProperty3 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + value = (double)currentMaterial.specularColor[0]; + materialProperty4 = value; + value = (double)currentMaterial.specularColor[1]; + materialProperty5 = value; + value = (double)currentMaterial.specularColor[2]; + materialProperty6 = value; + + pNodeSpecularColor.properties = { materialProperty0, materialProperty1, materialProperty2, materialProperty3, materialProperty4, materialProperty5, materialProperty6 }; + properties70Node.children.append(pNodeSpecularColor); + + // Set Shininess + FBXNode pNodeShininess; + pNodeShininess.name = "P"; + propertyString = "Shininess"; + materialProperty0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "Number"; + materialProperty1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + materialProperty2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "A"; + materialProperty3 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + value = (double)currentMaterial.shininess; + materialProperty4 = value; + + pNodeShininess.properties = { materialProperty0, materialProperty1, materialProperty2, materialProperty3, materialProperty4 }; + properties70Node.children.append(pNodeShininess); + + // Set Opacity + FBXNode pNodeOpacity; + pNodeOpacity.name = "P"; + propertyString = "Opacity"; + materialProperty0 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "Number"; + materialProperty1 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = ""; + materialProperty2 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + propertyString = "A"; + materialProperty3 = QVariant::fromValue(QByteArray(propertyString.data(), (int)propertyString.size())); + value = (double)currentMaterial.opacity; + materialProperty4 = value; + + pNodeOpacity.properties = { materialProperty0, materialProperty1, materialProperty2, materialProperty3, materialProperty4 }; + properties70Node.children.append(pNodeOpacity); + + materialNode->children.append(properties70Node); +} diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h new file mode 100644 index 0000000000..2d894c6626 --- /dev/null +++ b/libraries/baking/src/OBJBaker.h @@ -0,0 +1,63 @@ +// +// OBJBaker.h +// libraries/baking/src +// +// Created by Utkarsh Gautam on 9/29/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OBJBaker_h +#define hifi_OBJBaker_h + +#include "Baker.h" +#include "TextureBaker.h" +#include "ModelBaker.h" + +#include "ModelBakingLoggingCategory.h" + +using TextureBakerThreadGetter = std::function; + +class OBJBaker : public ModelBaker { + Q_OBJECT + +public: + OBJBaker(const QUrl& objURL, TextureBakerThreadGetter textureThreadGetter, + const QString& bakedOutputDir, const QString& originalOutputDir = ""); + ~OBJBaker() override; + void loadOBJ(); + void createFBXNodeTree(FBXNode* objRoot, FBXGeometry* geometry); + void setProperties(FBXNode * parentNode); + void setMaterialNodeProperties(FBXNode* materialNode, QString material, FBXGeometry* geometry); + +public slots: + virtual void bake() override; + +signals: + void OBJLoaded(); + +private slots: + void startBake(); + void handleOBJNetworkReply(); + +private: + qlonglong _nodeID = 0; + QUrl _objURL; + QString _bakedOBJFilePath; + QString _bakedOutputDir; + QString _originalOutputDir; + QDir _tempDir; + QString _originalOBJFilePath; + TextureBakerThreadGetter _textureThreadGetter; + QMultiHash> _bakingTextures; + + qlonglong _geometryID; + qlonglong _modelID; + std::vector _materialIDs; + qlonglong _textureID; + std::vector> mapTextureMaterial; + FBXNode _objectNode; +}; +#endif // hifi_OBJBaker_h diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index d91206a592..764f4518c1 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -20,6 +20,13 @@ #include "Oven.h" #include "BakerCLI.h" +#include +#include +#include +#include +#include + + static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export"; static const QString CLI_INPUT_PARAMETER = "i"; @@ -31,6 +38,12 @@ Oven::Oven(int argc, char* argv[]) : QCoreApplication::setOrganizationName("High Fidelity"); QCoreApplication::setApplicationName("Oven"); + // Initialize classes from Dependency Manager for OBJ Baker + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(NodeType::Unassigned, -1); + DependencyManager::set(); + // init the settings interface so we can save and load settings Setting::init(); diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 7963b3f3c4..d733a23d23 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -23,7 +23,7 @@ #include "../Oven.h" #include "OvenMainWindow.h" - +#include "OBJBaker.h" #include "ModelBakeWidget.h" static const auto EXPORT_DIR_SETTING_KEY = "model_export_directory"; @@ -32,8 +32,7 @@ static const auto MODEL_START_DIR_SETTING_KEY = "model_search_directory"; ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : BakeWidget(parent, flags), _exportDirectory(EXPORT_DIR_SETTING_KEY), - _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) -{ + _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) { setupUI(); } @@ -114,7 +113,7 @@ void ModelBakeWidget::chooseFileButtonClicked() { startDir = QDir::homePath(); } - auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx)"); + auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx *.obj)"); if (!selectedFiles.isEmpty()) { // set the contents of the model file text box to be the path to the selected file @@ -165,7 +164,7 @@ void ModelBakeWidget::bakeButtonClicked() { // split the list from the model line edit to see how many models we need to bake auto fileURLStrings = _modelLineEdit->text().split(','); - foreach (QString fileURLString, fileURLStrings) { + foreach(QString fileURLString, fileURLStrings) { // construct a URL from the path in the model file text box QUrl modelToBakeURL(fileURLString); @@ -201,25 +200,35 @@ void ModelBakeWidget::bakeButtonClicked() { QDir bakedOutputDirectory = outputDirectory.absoluteFilePath("baked"); QDir originalOutputDirectory = outputDirectory.absoluteFilePath("original"); - + bakedOutputDirectory.mkdir("."); originalOutputDirectory.mkdir("."); // everything seems to be in place, kick off a bake for this model now - auto baker = std::unique_ptr { - new FBXBaker(modelToBakeURL, []() -> QThread* { + if (modelToBakeURL.fileName().endsWith(".fbx")) { + _baker = std::unique_ptr{ + new FBXBaker(modelToBakeURL, []() -> QThread* { return qApp->getNextWorkerThread(); - }, bakedOutputDirectory.absolutePath(), originalOutputDirectory.absolutePath()) - }; + }, bakedOutputDirectory.absolutePath(), originalOutputDirectory.absolutePath()) + }; + _isFBX = true; + } else if (modelToBakeURL.fileName().endsWith(".obj")) { + _baker = std::unique_ptr{ + new OBJBaker(modelToBakeURL, []() -> QThread* { + return qApp->getNextWorkerThread(); + }, bakedOutputDirectory.absolutePath(), originalOutputDirectory.absolutePath()) + }; + _isOBJ = true; + } - // move the baker to the FBX baker thread - baker->moveToThread(qApp->getNextWorkerThread()); + // move the baker to the FBX/OBJ baker thread + _baker->moveToThread(qApp->getNextWorkerThread()); // invoke the bake method on the baker thread - QMetaObject::invokeMethod(baker.get(), "bake"); + QMetaObject::invokeMethod(_baker.get(), "bake"); // make sure we hear about the results of this baker when it is done - connect(baker.get(), &FBXBaker::finished, this, &ModelBakeWidget::handleFinishedBaker); + connect(_baker.get(), &Baker::finished, this, &ModelBakeWidget::handleFinishedBaker); // add a pending row to the results window to show that this bake is in process auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); @@ -227,32 +236,37 @@ void ModelBakeWidget::bakeButtonClicked() { // keep a unique_ptr to this baker // and remember the row that represents it in the results table - _bakers.emplace_back(std::move(baker), resultsRow); + _bakers.emplace_back(std::move(_baker), resultsRow); } } void ModelBakeWidget::handleFinishedBaker() { - if (auto baker = qobject_cast(sender())) { - // add the results of this bake to the results window - auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { - return value.first.get() == baker; - }); - - for (auto& file : baker->getOutputFiles()) { - qDebug() << "Baked file: " << file; - } - - if (it != _bakers.end()) { - auto resultRow = it->second; - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); - - if (baker->hasErrors()) { - resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); - } else { - resultsWindow->changeStatusForRow(resultRow, "Success"); - } - - _bakers.erase(it); - } + Baker* baker; + if (_isFBX) { + baker = qobject_cast(sender()); + } else if (_isOBJ) { + baker = qobject_cast(sender()); } -} + + // add the results of this bake to the results window + auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + return value.first.get() == baker; + }); + + for (auto& file : baker->getOutputFiles()) { + qDebug() << "Baked file: " << file; + } + + if (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + if (baker->hasErrors()) { + resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + } else { + resultsWindow->changeStatusForRow(resultRow, "Success"); + } + + _bakers.erase(it); + } +} \ No newline at end of file diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index b42b8725f6..e91e5655c6 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -17,6 +17,7 @@ #include #include +#include #include "BakeWidget.h" @@ -46,6 +47,11 @@ private: Setting::Handle _exportDirectory; Setting::Handle _modelStartDirectory; + + std::unique_ptr _baker; + + bool _isOBJ = false; + bool _isFBX = false; }; -#endif // hifi_ModelBakeWidget_h +#endif // hifi_ModelBakeWidget_h \ No newline at end of file