From 9346b45188462056698083f4f83fe8fecbe19bdf Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <andreykproductengine@lindenlab.com>
Date: Thu, 29 Sep 2022 22:38:40 +0300
Subject: SL-17653 Multi-material file support for local materials

---
 indra/newview/llgltfmateriallist.cpp   |   2 +-
 indra/newview/lllocalgltfmaterials.cpp | 190 ++++++++++++++++++++++-----------
 indra/newview/lllocalgltfmaterials.h   |  12 ++-
 indra/newview/llmaterialeditor.cpp     |  15 ++-
 indra/newview/llmaterialeditor.h       |   4 +-
 indra/newview/lltexturectrl.cpp        |   7 +-
 indra/newview/lltinygltfhelper.cpp     |  33 +++---
 indra/newview/lltinygltfhelper.h       |   2 +-
 8 files changed, 173 insertions(+), 92 deletions(-)

(limited to 'indra')

diff --git a/indra/newview/llgltfmateriallist.cpp b/indra/newview/llgltfmateriallist.cpp
index b2d223a3e8..5cbf853179 100644
--- a/indra/newview/llgltfmateriallist.cpp
+++ b/indra/newview/llgltfmateriallist.cpp
@@ -92,7 +92,7 @@ LLGLTFMaterial* LLGLTFMaterialList::getMaterial(const LLUUID& id)
 
                                 if (loader.LoadASCIIFromString(&model_in, &error_msg, &warn_msg, data.c_str(), data.length(), ""))
                                 {
-                                    LLTinyGLTFHelper::setFromModel(mat, model_in);
+                                    LLTinyGLTFHelper::setFromModel(mat, model_in, 0);
                                 }
                                 else
                                 {
diff --git a/indra/newview/lllocalgltfmaterials.cpp b/indra/newview/lllocalgltfmaterials.cpp
index 524693e7ea..6e6671d360 100644
--- a/indra/newview/lllocalgltfmaterials.cpp
+++ b/indra/newview/lllocalgltfmaterials.cpp
@@ -59,70 +59,76 @@ static const S32 LL_LOCAL_UPDATE_RETRIES    = 5;
 /*=======================================*/
 /*  LLLocalGLTFMaterial: unit class            */
 /*=======================================*/ 
-LLLocalGLTFMaterial::LLLocalGLTFMaterial(std::string filename)
-	: mFilename(filename)
-	, mShortName(gDirUtilp->getBaseFileName(filename, true))
-	, mValid(false)
-	, mLastModified()
-	, mLinkStatus(LS_ON)
-	, mUpdateRetries(LL_LOCAL_UPDATE_RETRIES)
+LLLocalGLTFMaterial::LLLocalGLTFMaterial(std::string filename, S32 index)
+    : mFilename(filename)
+    , mShortName(gDirUtilp->getBaseFileName(filename, true))
+    , mValid(false)
+    , mLastModified()
+    , mLinkStatus(LS_ON)
+    , mUpdateRetries(LL_LOCAL_UPDATE_RETRIES)
+    , mMaterialIndex(index)
 {
-	mTrackingID.generate();
+    mTrackingID.generate();
 
-	/* extension */
-	std::string temp_exten = gDirUtilp->getExtension(mFilename);
+    /* extension */
+    std::string temp_exten = gDirUtilp->getExtension(mFilename);
 
-	if (temp_exten == "gltf")
-	{ 
-		mExtension = ET_MATERIAL_GLTF;
-	}
-	else if (temp_exten == "glb")
-	{
-		mExtension = ET_MATERIAL_GLB;
-	}
-	else
-	{
-		LL_WARNS() << "File of no valid extension given, local material creation aborted." << "\n"
-			    << "Filename: " << mFilename << LL_ENDL;
-		return; // no valid extension.
-	}
+    if (temp_exten == "gltf")
+    {
+        mExtension = ET_MATERIAL_GLTF;
+    }
+    else if (temp_exten == "glb")
+    {
+        mExtension = ET_MATERIAL_GLB;
+    }
+    else
+    {
+        LL_WARNS() << "File of no valid extension given, local material creation aborted." << "\n"
+            << "Filename: " << mFilename << LL_ENDL;
+        return; // no valid extension.
+    }
 
-	/* next phase of unit creation is nearly the same as an update cycle.
-	   we're running updateSelf as a special case with the optional UT_FIRSTUSE
-	   which omits the parts associated with removing the outdated texture */
-	mValid = updateSelf();
+    /* next phase of unit creation is nearly the same as an update cycle.
+       we're running updateSelf as a special case with the optional UT_FIRSTUSE
+       which omits the parts associated with removing the outdated texture */
+    mValid = updateSelf();
 }
 
 LLLocalGLTFMaterial::~LLLocalGLTFMaterial()
 {
-	// delete self from material list
+    // delete self from material list
     gGLTFMaterialList.removeMaterial(mWorldID);
 }
 
 /* accessors */
 std::string LLLocalGLTFMaterial::getFilename()
 {
-	return mFilename;
+    return mFilename;
 }
 
 std::string LLLocalGLTFMaterial::getShortName()
 {
-	return mShortName;
+    return mShortName;
 }
 
 LLUUID LLLocalGLTFMaterial::getTrackingID()
 {
-	return mTrackingID;
+    return mTrackingID;
 }
 
 LLUUID LLLocalGLTFMaterial::getWorldID()
 {
-	return mWorldID;
+    return mWorldID;
+}
+
+S32 LLLocalGLTFMaterial::getIndexInFile()
+{
+    return mMaterialIndex;
 }
 
 bool LLLocalGLTFMaterial::getValid()
 {
-	return mValid;
+    return mValid;
 }
 
 /* update functions */
@@ -147,7 +153,7 @@ bool LLLocalGLTFMaterial::updateSelf()
 			if (mLastModified.asString() != new_last_modified.asString())
 			{
 				LLPointer<LLGLTFMaterial> raw_material = new LLGLTFMaterial();
-				if (loadMaterial(raw_material))
+				if (loadMaterial(raw_material, mMaterialIndex))
 				{
 					// decode is successful, we can safely proceed.
                     if (mWorldID.isNull())
@@ -207,7 +213,7 @@ bool LLLocalGLTFMaterial::updateSelf()
 	return updated;
 }
 
-bool LLLocalGLTFMaterial::loadMaterial(LLPointer<LLGLTFMaterial> mat)
+bool LLLocalGLTFMaterial::loadMaterial(LLPointer<LLGLTFMaterial> mat, S32 index)
 {
     bool decode_successful = false;
 
@@ -238,26 +244,31 @@ bool LLLocalGLTFMaterial::loadMaterial(LLPointer<LLGLTFMaterial> mat)
 
             if (!decode_successful)
             {
-                LL_WARNS() << "Cannot Upload Material, error: " << error_msg
+                LL_WARNS() << "Cannot load Material, error: " << error_msg
                     << ", warning:" << warn_msg
                     << " file: " << mFilename
                     << LL_ENDL;
                 break;
             }
 
-            if (model_in.materials.empty())
+            if (model_in.materials.size() <= index)
             {
                 // materials are missing
-                LL_WARNS() << "Cannot Upload Material, Material missing, " << mFilename << LL_ENDL;
+                LL_WARNS() << "Cannot load Material, Material " << index << " is missing, " << mFilename << LL_ENDL;
                 decode_successful = false;
                 break;
             }
 
             // sets everything, but textures will have inaccurate ids
-            LLTinyGLTFHelper::setFromModel(mat, model_in);
+            LLTinyGLTFHelper::setFromModel(mat, model_in, index);
 
             std::string folder = gDirUtilp->getDirName(filename_lc);
-            tinygltf::Material material_in = model_in.materials[0];
+            tinygltf::Material material_in = model_in.materials[index];
+
+            if (!material_in.name.empty())
+            {
+                mShortName = material_in.name;
+            }
 
             // get base color texture
             LLPointer<LLImageRaw> base_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.baseColorTexture.index);
@@ -362,44 +373,95 @@ LLLocalGLTFMaterialMgr::~LLLocalGLTFMaterialMgr()
     mMaterialList.clear();
 }
 
-bool LLLocalGLTFMaterialMgr::addUnit(const std::vector<std::string>& filenames)
+S32 LLLocalGLTFMaterialMgr::addUnit(const std::vector<std::string>& filenames)
 {
-    bool add_successful = false;
+    S32 add_count = 0;
     std::vector<std::string>::const_iterator iter = filenames.begin();
     while (iter != filenames.end())
     {
-        if (!iter->empty() && addUnit(*iter).notNull())
+        if (!iter->empty())
         {
-            add_successful = true;
+            add_count += addUnit(*iter);
         }
         iter++;
     }
-    return add_successful;
+    return add_count;
 }
 
-LLUUID LLLocalGLTFMaterialMgr::addUnit(const std::string& filename)
+S32 LLLocalGLTFMaterialMgr::addUnit(const std::string& filename)
 {
-    LLLocalGLTFMaterial* unit = new LLLocalGLTFMaterial(filename);
+    std::string exten = gDirUtilp->getExtension(filename);
+    S32 materials_in_file = 0;
 
-    if (unit->getValid())
+    if (exten == "gltf" || exten == "glb")
     {
-        mMaterialList.push_back(unit);
-        return unit->getTrackingID();
+        tinygltf::TinyGLTF loader;
+        std::string        error_msg;
+        std::string        warn_msg;
+
+        tinygltf::Model model_in;
+
+        std::string filename_lc = filename;
+        LLStringUtil::toLower(filename_lc);
+
+        // Load a tinygltf model fom a file. Assumes that the input filename has already been
+        // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish.
+        bool decode_successful = false;
+        if (std::string::npos == filename_lc.rfind(".gltf"))
+        {  // file is binary
+            decode_successful = loader.LoadBinaryFromFile(&model_in, &error_msg, &warn_msg, filename_lc);
+        }
+        else
+        {  // file is ascii
+            decode_successful = loader.LoadASCIIFromFile(&model_in, &error_msg, &warn_msg, filename_lc);
+        }
+
+        if (!decode_successful)
+        {
+            LL_WARNS() << "Cannot load, error: Failed to decode" << error_msg
+                << ", warning:" << warn_msg
+                << " file: " << filename
+                << LL_ENDL;
+            return 0;
+        }
+
+        if (model_in.materials.empty())
+        {
+            // materials are missing
+            LL_WARNS() << "Cannot load. File has no materials " << filename << LL_ENDL;
+            return 0;
+        }
+        materials_in_file = model_in.materials.size();
     }
-    else
+
+    S32 loaded_materials = 0;
+    for (S32 i = 0; i < materials_in_file; i++)
     {
-        LL_WARNS() << "Attempted to add invalid or unreadable image file, attempt cancelled.\n"
-            << "Filename: " << filename << LL_ENDL;
+        // Todo: this is rather inefficient, files will be spammed with
+        // separate loads and date checks, find a way to improve this.
+        // May be doUpdates() should be checking individual files.
+        LLLocalGLTFMaterial* unit = new LLLocalGLTFMaterial(filename, i);
+
+        if (unit->getValid())
+        {
+            mMaterialList.push_back(unit);
+            loaded_materials++;
+        }
+        else
+        {
+            LL_WARNS() << "Attempted to add invalid or unreadable image file, attempt cancelled.\n"
+                << "Filename: " << filename << LL_ENDL;
 
-        LLSD notif_args;
-        notif_args["FNAME"] = filename;
-        LLNotificationsUtil::add("LocalGLTFVerifyFail", notif_args);
+            LLSD notif_args;
+            notif_args["FNAME"] = filename;
+            LLNotificationsUtil::add("LocalGLTFVerifyFail", notif_args);
 
-        delete unit;
-        unit = NULL;
+            delete unit;
+            unit = NULL;
+        }
     }
 
-    return LLUUID::null;
+    return loaded_materials;
 }
 
 void LLLocalGLTFMaterialMgr::delUnit(LLUUID tracking_id)
@@ -456,9 +518,10 @@ bool LLLocalGLTFMaterialMgr::isLocal(const LLUUID world_id)
     return false;
 }
 
-std::string LLLocalGLTFMaterialMgr::getFilename(LLUUID tracking_id)
+void LLLocalGLTFMaterialMgr::getFilenameAndIndex(LLUUID tracking_id, std::string &filename, S32 &index)
 {
-    std::string filename = "";
+    filename = "";
+    index = 0;
 
     for (local_list_iter iter = mMaterialList.begin(); iter != mMaterialList.end(); iter++)
     {
@@ -466,10 +529,9 @@ std::string LLLocalGLTFMaterialMgr::getFilename(LLUUID tracking_id)
         if (unit->getTrackingID() == tracking_id)
         {
             filename = unit->getFilename();
+            index = unit->getIndexInFile();
         }
     }
-
-    return filename;
 }
 
 // probably shouldn't be here, but at the moment this mirrors lllocalbitmaps
diff --git a/indra/newview/lllocalgltfmaterials.h b/indra/newview/lllocalgltfmaterials.h
index de775615a8..f762dcc2ce 100644
--- a/indra/newview/lllocalgltfmaterials.h
+++ b/indra/newview/lllocalgltfmaterials.h
@@ -38,7 +38,7 @@ class LLViewerFetchedTexture;
 class LLLocalGLTFMaterial
 {
 public: /* main */
-    LLLocalGLTFMaterial(std::string filename);
+    LLLocalGLTFMaterial(std::string filename, S32 index);
     ~LLLocalGLTFMaterial();
 
 public: /* accessors */
@@ -46,13 +46,14 @@ public: /* accessors */
     std::string	getShortName();
     LLUUID		getTrackingID();
     LLUUID		getWorldID();
+    S32			getIndexInFile();
     bool		getValid();
 
 public:
     bool updateSelf();
 
 private:
-    bool loadMaterial(LLPointer<LLGLTFMaterial> raw);
+    bool loadMaterial(LLPointer<LLGLTFMaterial> raw, S32 index);
 
 private: /* private enums */
     enum ELinkStatus
@@ -77,6 +78,7 @@ private: /* members */
     EExtension  mExtension;
     ELinkStatus mLinkStatus;
     S32         mUpdateRetries;
+    S32         mMaterialIndex; // Single file can have more than one
 
     // material needs to maintain textures
     LLPointer<LLViewerFetchedTexture> mBaseColorFetched;
@@ -103,13 +105,13 @@ class LLLocalGLTFMaterialMgr : public LLSingleton<LLLocalGLTFMaterialMgr>
     LLSINGLETON(LLLocalGLTFMaterialMgr);
     ~LLLocalGLTFMaterialMgr();
 public:
-    bool         addUnit(const std::vector<std::string>& filenames);
-    LLUUID       addUnit(const std::string& filename);
+    S32          addUnit(const std::vector<std::string>& filenames);
+    S32          addUnit(const std::string& filename); // file can hold multiple materials
     void         delUnit(LLUUID tracking_id);
 
     LLUUID       getWorldID(LLUUID tracking_id);
     bool         isLocal(LLUUID world_id);
-    std::string  getFilename(LLUUID tracking_id);
+    void         getFilenameAndIndex(LLUUID tracking_id, std::string &filename, S32 &index);
 
     void         feedScrollList(LLScrollListCtrl* ctrl);
     void         doUpdates();
diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp
index 27694e9ce4..7270317b8c 100644
--- a/indra/newview/llmaterialeditor.cpp
+++ b/indra/newview/llmaterialeditor.cpp
@@ -1176,6 +1176,8 @@ void LLMaterialFilePicker::notify(const std::vector<std::string>& filenames)
     
     if (filenames.size() > 0)
     {
+        // Todo: there is no point creating LLMaterialEditor before
+        // loading material, just creates unnessesary work if decode fails
         LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor");
         if (me)
         {
@@ -1235,7 +1237,7 @@ void LLMaterialFilePicker::textureLoadedCallback(BOOL success, LLViewerFetchedTe
 {
 }
 
-void LLMaterialEditor::loadMaterialFromFile(const std::string& filename)
+void LLMaterialEditor::loadMaterialFromFile(const std::string& filename, S32 index)
 {
     tinygltf::TinyGLTF loader;
     std::string        error_msg;
@@ -1264,19 +1266,26 @@ void LLMaterialEditor::loadMaterialFromFile(const std::string& filename)
         return;
     }
 
-    if (model_in.materials.empty())
+    if (model_in.materials.empty() || (index >= model_in.materials.size()))
     {
         // materials are missing
         LLNotificationsUtil::add("CannotUploadMaterial");
         return;
     }
 
-    if (model_in.materials.size() == 1)
+    if (index >= 0)
     {
+        // Prespecified material
+        loadMaterial(model_in, filename_lc, index);
+    }
+    else if (model_in.materials.size() == 1)
+    {
+        // Only one, just load it
         loadMaterial(model_in, filename_lc, 0);
     }
     else
     {
+        // Promt user to select material
         std::list<std::string> material_list;
         std::vector<tinygltf::Material>::const_iterator mat_iter = model_in.materials.begin();
         std::vector<tinygltf::Material>::const_iterator mat_end = model_in.materials.end();
diff --git a/indra/newview/llmaterialeditor.h b/indra/newview/llmaterialeditor.h
index c6b976ea5a..23cfc93763 100644
--- a/indra/newview/llmaterialeditor.h
+++ b/indra/newview/llmaterialeditor.h
@@ -101,7 +101,9 @@ public:
     void setFromGLTFMaterial(LLGLTFMaterial* mat);
 
     void loadAsset() override;
-    void loadMaterialFromFile(const std::string& filename);
+    // @index if -1 and file contains more than one material,
+    // will promt to select specific one
+    void loadMaterialFromFile(const std::string& filename, S32 index = -1);
 
     static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status);
 
diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp
index f4649a672b..ff0d74a7c9 100644
--- a/indra/newview/lltexturectrl.cpp
+++ b/indra/newview/lltexturectrl.cpp
@@ -965,13 +965,16 @@ void LLFloaterTexturePicker::onBtnUpload(void* userdata)
 
     if (LLAssetType::AT_MATERIAL == asset_type)
     {
-        std::string filename = LLLocalGLTFMaterialMgr::getInstance()->getFilename(tracking_id);
+        std::string filename;
+        S32 index;
+        LLLocalGLTFMaterialMgr::getInstance()->getFilenameAndIndex(tracking_id, filename, index);
         if (!filename.empty())
         {
             LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor");
             if (me)
             {
-                me->loadMaterialFromFile(filename);
+                me->loadMaterialFromFile(filename, index);
+                me->setFocus(TRUE);
             }
         }
     }
diff --git a/indra/newview/lltinygltfhelper.cpp b/indra/newview/lltinygltfhelper.cpp
index 3125cacbd5..c3dc10c2a0 100644
--- a/indra/newview/lltinygltfhelper.cpp
+++ b/indra/newview/lltinygltfhelper.cpp
@@ -122,17 +122,20 @@ void LLTinyGLTFHelper::initFetchedTextures(tinygltf::Material& material,
     }
 }
 
-void LLTinyGLTFHelper::setFromModel(LLGLTFMaterial* mat, tinygltf::Model& model)
+void LLTinyGLTFHelper::setFromModel(LLGLTFMaterial* mat, tinygltf::Model& model, S32 mat_index)
 {
-    S32 index;
+    if (model.materials.size() <= mat_index)
+    {
+        return;
+    }
 
-    auto& material_in = model.materials[0];
+    tinygltf::Material& material_in = model.materials[mat_index];
 
     // get base color texture
-    index = material_in.pbrMetallicRoughness.baseColorTexture.index;
-    if (index >= 0)
+    S32 tex_index = material_in.pbrMetallicRoughness.baseColorTexture.index;
+    if (tex_index >= 0)
     {
-        mat->mBaseColorId.set(model.images[index].uri);
+        mat->mBaseColorId.set(model.images[tex_index].uri);
     }
     else
     {
@@ -140,10 +143,10 @@ void LLTinyGLTFHelper::setFromModel(LLGLTFMaterial* mat, tinygltf::Model& model)
     }
 
     // get normal map
-    index = material_in.normalTexture.index;
-    if (index >= 0)
+    tex_index = material_in.normalTexture.index;
+    if (tex_index >= 0)
     {
-        mat->mNormalId.set(model.images[index].uri);
+        mat->mNormalId.set(model.images[tex_index].uri);
     }
     else
     {
@@ -151,10 +154,10 @@ void LLTinyGLTFHelper::setFromModel(LLGLTFMaterial* mat, tinygltf::Model& model)
     }
 
     // get metallic-roughness texture
-    index = material_in.pbrMetallicRoughness.metallicRoughnessTexture.index;
-    if (index >= 0)
+    tex_index = material_in.pbrMetallicRoughness.metallicRoughnessTexture.index;
+    if (tex_index >= 0)
     {
-        mat->mMetallicRoughnessId.set(model.images[index].uri);
+        mat->mMetallicRoughnessId.set(model.images[tex_index].uri);
     }
     else
     {
@@ -162,10 +165,10 @@ void LLTinyGLTFHelper::setFromModel(LLGLTFMaterial* mat, tinygltf::Model& model)
     }
 
     // get emissive texture
-    index = material_in.emissiveTexture.index;
-    if (index >= 0)
+    tex_index = material_in.emissiveTexture.index;
+    if (tex_index >= 0)
     {
-        mat->mEmissiveId.set(model.images[index].uri);
+        mat->mEmissiveId.set(model.images[tex_index].uri);
     }
     else
     {
diff --git a/indra/newview/lltinygltfhelper.h b/indra/newview/lltinygltfhelper.h
index 89e09b189e..afe4517417 100644
--- a/indra/newview/lltinygltfhelper.h
+++ b/indra/newview/lltinygltfhelper.h
@@ -35,7 +35,7 @@ class LLViewerFetchedTexture;
 
 namespace LLTinyGLTFHelper
 {
-    void setFromModel(LLGLTFMaterial* mat, tinygltf::Model& model);
+    void setFromModel(LLGLTFMaterial* mat, tinygltf::Model& model, S32 index);
     LLColor4 getColor(const std::vector<double>& in);
     const tinygltf::Image* getImageFromTextureIndex(const tinygltf::Model& model, S32 texture_index);
     LLImageRaw* getTexture(const std::string& folder, const tinygltf::Model& model, S32 texture_index, std::string& name);
-- 
cgit v1.2.3