summaryrefslogtreecommitdiff
path: root/indra/newview/llmaterialeditor.cpp
diff options
context:
space:
mode:
authorAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 21:25:21 +0200
committerAndrey Lihatskiy <alihatskiy@productengine.com>2024-05-22 22:40:26 +0300
commite2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch)
tree1bb897489ce524986f6196201c10ac0d8861aa5f /indra/newview/llmaterialeditor.cpp
parent069ea06848f766466f1a281144c82a0f2bd79f3a (diff)
Fix line endlings
Diffstat (limited to 'indra/newview/llmaterialeditor.cpp')
-rw-r--r--indra/newview/llmaterialeditor.cpp7540
1 files changed, 3770 insertions, 3770 deletions
diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp
index 10f4e2440b..7b0e8d0e89 100644
--- a/indra/newview/llmaterialeditor.cpp
+++ b/indra/newview/llmaterialeditor.cpp
@@ -1,3770 +1,3770 @@
-/**
- * @file llmaterialeditor.cpp
- * @brief Implementation of the gltf material editor
- *
- * $LicenseInfo:firstyear=2022&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include "llmaterialeditor.h"
-
-#include "llagent.h"
-#include "llagentbenefits.h"
-#include "llappviewer.h"
-#include "llcolorswatch.h"
-#include "llcombobox.h"
-#include "llfloaterreg.h"
-#include "llfilesystem.h"
-#include "llgltfmateriallist.h"
-#include "llinventorymodel.h"
-#include "llinventoryobserver.h"
-#include "llinventoryfunctions.h"
-#include "lllocalgltfmaterials.h"
-#include "llnotificationsutil.h"
-#include "lltexturectrl.h"
-#include "lltrans.h"
-#include "llviewercontrol.h"
-#include "llviewermenufile.h"
-#include "llviewertexture.h"
-#include "llsdutil.h"
-#include "llselectmgr.h"
-#include "llstatusbar.h" // can_afford_transaction()
-#include "lltoolpie.h"
-#include "llviewerinventory.h"
-#include "llinventory.h"
-#include "llviewerregion.h"
-#include "llvovolume.h"
-#include "roles_constants.h"
-#include "llviewerobjectlist.h"
-#include "llsdserialize.h"
-#include "llimagej2c.h"
-#include "llviewertexturelist.h"
-#include "llfloaterperms.h"
-
-#include "tinygltf/tiny_gltf.h"
-#include "lltinygltfhelper.h"
-#include <strstream>
-
-
-const std::string MATERIAL_BASE_COLOR_DEFAULT_NAME = "Base Color";
-const std::string MATERIAL_NORMAL_DEFAULT_NAME = "Normal";
-const std::string MATERIAL_METALLIC_DEFAULT_NAME = "Metallic Roughness";
-const std::string MATERIAL_EMISSIVE_DEFAULT_NAME = "Emissive";
-
-// Dirty flags
-static const U32 MATERIAL_BASE_COLOR_DIRTY = 0x1 << 0;
-static const U32 MATERIAL_BASE_COLOR_TEX_DIRTY = 0x1 << 1;
-
-static const U32 MATERIAL_NORMAL_TEX_DIRTY = 0x1 << 2;
-
-static const U32 MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY = 0x1 << 3;
-static const U32 MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY = 0x1 << 4;
-static const U32 MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY = 0x1 << 5;
-
-static const U32 MATERIAL_EMISIVE_COLOR_DIRTY = 0x1 << 6;
-static const U32 MATERIAL_EMISIVE_TEX_DIRTY = 0x1 << 7;
-
-static const U32 MATERIAL_DOUBLE_SIDED_DIRTY = 0x1 << 8;
-static const U32 MATERIAL_ALPHA_MODE_DIRTY = 0x1 << 9;
-static const U32 MATERIAL_ALPHA_CUTOFF_DIRTY = 0x1 << 10;
-
-LLUUID LLMaterialEditor::mOverrideObjectId;
-S32 LLMaterialEditor::mOverrideObjectTE = -1;
-bool LLMaterialEditor::mOverrideInProgress = false;
-bool LLMaterialEditor::mSelectionNeedsUpdate = true;
-
-LLFloaterComboOptions::LLFloaterComboOptions()
- : LLFloater(LLSD())
-{
- buildFromFile("floater_combobox_ok_cancel.xml");
-}
-
-LLFloaterComboOptions::~LLFloaterComboOptions()
-{
-
-}
-
-bool LLFloaterComboOptions::postBuild()
-{
- mConfirmButton = getChild<LLButton>("combo_ok", true);
- mCancelButton = getChild<LLButton>("combo_cancel", true);
- mComboOptions = getChild<LLComboBox>("combo_options", true);
- mComboText = getChild<LLTextBox>("combo_text", true);
-
- mConfirmButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) {onConfirm(); });
- mCancelButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) {onCancel(); });
-
- return true;
-}
-
-LLFloaterComboOptions* LLFloaterComboOptions::showUI(
- combo_callback callback,
- const std::string &title,
- const std::string &description,
- const std::list<std::string> &options)
-{
- LLFloaterComboOptions* combo_picker = new LLFloaterComboOptions();
- if (combo_picker)
- {
- combo_picker->mCallback = callback;
- combo_picker->setTitle(title);
-
- combo_picker->mComboText->setText(description);
-
- std::list<std::string>::const_iterator iter = options.begin();
- std::list<std::string>::const_iterator end = options.end();
- for (; iter != end; iter++)
- {
- combo_picker->mComboOptions->addSimpleElement(*iter);
- }
- combo_picker->mComboOptions->selectFirstItem();
-
- combo_picker->openFloater(LLSD(title));
- combo_picker->setFocus(true);
- combo_picker->center();
- }
- return combo_picker;
-}
-
-LLFloaterComboOptions* LLFloaterComboOptions::showUI(
- combo_callback callback,
- const std::string &title,
- const std::string &description,
- const std::string &ok_text,
- const std::string &cancel_text,
- const std::list<std::string> &options)
-{
- LLFloaterComboOptions* combo_picker = showUI(callback, title, description, options);
- if (combo_picker)
- {
- combo_picker->mConfirmButton->setLabel(ok_text);
- combo_picker->mCancelButton->setLabel(cancel_text);
- }
- return combo_picker;
-}
-
-void LLFloaterComboOptions::onConfirm()
-{
- mCallback(mComboOptions->getSimple(), mComboOptions->getCurrentIndex());
- closeFloater();
-}
-
-void LLFloaterComboOptions::onCancel()
-{
- mCallback(std::string(), -1);
- closeFloater();
-}
-
-class LLMaterialEditorCopiedCallback : public LLInventoryCallback
-{
-public:
- LLMaterialEditorCopiedCallback(
- const std::string &buffer,
- const LLSD &old_key,
- bool has_unsaved_changes)
- : mBuffer(buffer),
- mOldKey(old_key),
- mHasUnsavedChanges(has_unsaved_changes)
- {}
-
- LLMaterialEditorCopiedCallback(
- const LLSD &old_key,
- const std::string &new_name)
- : mOldKey(old_key),
- mNewName(new_name),
- mHasUnsavedChanges(false)
- {}
-
- virtual void fire(const LLUUID& inv_item_id)
- {
- if (!mNewName.empty())
- {
- // making a copy from a notecard doesn't change name, do it now
- LLViewerInventoryItem* item = gInventory.getItem(inv_item_id);
- if (item->getName() != mNewName)
- {
- LLSD updates;
- updates["name"] = mNewName;
- update_inventory_item(inv_item_id, updates, NULL);
- }
- }
- LLMaterialEditor::finishSaveAs(mOldKey, inv_item_id, mBuffer, mHasUnsavedChanges);
- }
-
-private:
- std::string mBuffer;
- LLSD mOldKey;
- std::string mNewName;
- bool mHasUnsavedChanges;
-};
-
-///----------------------------------------------------------------------------
-/// Class LLSelectedTEGetMatData
-/// For finding selected applicable inworld material
-///----------------------------------------------------------------------------
-
-struct LLSelectedTEGetMatData : public LLSelectedTEFunctor
-{
- LLSelectedTEGetMatData(bool for_override);
-
- bool apply(LLViewerObject* objectp, S32 te_index);
-
- bool mIsOverride;
- bool mIdenticalTexColor;
- bool mIdenticalTexMetal;
- bool mIdenticalTexEmissive;
- bool mIdenticalTexNormal;
- bool mFirst;
- LLUUID mTexColorId;
- LLUUID mTexMetalId;
- LLUUID mTexEmissiveId;
- LLUUID mTexNormalId;
- LLUUID mObjectId;
- LLViewerObject* mObject = nullptr;
- S32 mObjectTE;
- LLUUID mMaterialId;
- LLPointer<LLGLTFMaterial> mMaterial;
- LLPointer<LLLocalGLTFMaterial> mLocalMaterial;
-};
-
-LLSelectedTEGetMatData::LLSelectedTEGetMatData(bool for_override)
- : mIsOverride(for_override)
- , mIdenticalTexColor(true)
- , mIdenticalTexMetal(true)
- , mIdenticalTexEmissive(true)
- , mIdenticalTexNormal(true)
- , mObjectTE(-1)
- , mFirst(true)
-{}
-
-bool LLSelectedTEGetMatData::apply(LLViewerObject* objectp, S32 te_index)
-{
- if (!objectp)
- {
- return false;
- }
- LLUUID mat_id = objectp->getRenderMaterialID(te_index);
- mMaterialId = mat_id;
- bool can_use = mIsOverride ? objectp->permModify() : objectp->permCopy();
- LLTextureEntry *tep = objectp->getTE(te_index);
- // We might want to disable this entirely if at least
- // something in selection is no-copy or no modify
- // or has no base material
- if (can_use && tep && mat_id.notNull())
- {
- if (mIsOverride)
- {
- LLPointer<LLGLTFMaterial> mat = tep->getGLTFRenderMaterial();
-
- LLUUID tex_color_id;
- LLUUID tex_metal_id;
- LLUUID tex_emissive_id;
- LLUUID tex_normal_id;
- llassert(mat.notNull()); // by this point shouldn't be null
- if (mat.notNull())
- {
- tex_color_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR];
- tex_metal_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS];
- tex_emissive_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE];
- tex_normal_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL];
- }
- if (mFirst)
- {
- mMaterial = mat;
- mTexColorId = tex_color_id;
- mTexMetalId = tex_metal_id;
- mTexEmissiveId = tex_emissive_id;
- mTexNormalId = tex_normal_id;
- mObjectTE = te_index;
- mObject = objectp;
- mObjectId = objectp->getID();
- mFirst = false;
- }
- else
- {
- if (mTexColorId != tex_color_id)
- {
- mIdenticalTexColor = false;
- }
- if (mTexMetalId != tex_metal_id)
- {
- mIdenticalTexMetal = false;
- }
- if (mTexEmissiveId != tex_emissive_id)
- {
- mIdenticalTexEmissive = false;
- }
- if (mTexNormalId != tex_normal_id)
- {
- mIdenticalTexNormal = false;
- }
- }
- }
- else
- {
- LLGLTFMaterial *mat = tep->getGLTFMaterial();
- LLLocalGLTFMaterial *local_mat = dynamic_cast<LLLocalGLTFMaterial*>(mat);
-
- mObject = objectp;
- mObjectId = objectp->getID();
- if (local_mat)
- {
- mLocalMaterial = local_mat;
- }
- mMaterial = tep->getGLTFRenderMaterial();
-
- if (mMaterial.isNull())
- {
- // Shouldn't be possible?
- LL_WARNS("MaterialEditor") << "Object has material id, but no material" << LL_ENDL;
- mMaterial = gGLTFMaterialList.getMaterial(mat_id);
- }
- }
- return true;
- }
- return false;
-}
-
-class LLSelectedTEUpdateOverrides: public LLSelectedNodeFunctor
-{
-public:
- LLSelectedTEUpdateOverrides(LLMaterialEditor* me) : mEditor(me) {}
-
- virtual bool apply(LLSelectNode* nodep);
-
- LLMaterialEditor* mEditor;
-};
-
-bool LLSelectedTEUpdateOverrides::apply(LLSelectNode* nodep)
-{
- LLViewerObject* objectp = nodep->getObject();
- if (!objectp)
- {
- return false;
- }
- S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces
- for (S32 te_index = 0; te_index < num_tes; ++te_index)
- {
-
- LLTextureEntry* tep = objectp->getTE(te_index);
- LLGLTFMaterial* override_mat = tep->getGLTFMaterialOverride();
- if (mEditor->updateMaterialLocalSubscription(override_mat))
- {
- LLGLTFMaterial* render_mat = tep->getGLTFRenderMaterial();
- mEditor->updateMaterialLocalSubscription(render_mat);
- }
- }
-
- return true;
-}
-
-///----------------------------------------------------------------------------
-/// Class LLMaterialEditor
-///----------------------------------------------------------------------------
-
-// Default constructor
-LLMaterialEditor::LLMaterialEditor(const LLSD& key)
- : LLPreview(key)
- , mUnsavedChanges(0)
- , mRevertedChanges(0)
- , mExpectedUploadCost(0)
- , mUploadingTexturesCount(0)
- , mUploadingTexturesFailure(false)
-{
- const LLInventoryItem* item = getItem();
- if (item)
- {
- mAssetID = item->getAssetUUID();
- }
-}
-
-LLMaterialEditor::~LLMaterialEditor()
-{
-}
-
-void LLMaterialEditor::setObjectID(const LLUUID& object_id)
-{
- LLPreview::setObjectID(object_id);
- const LLInventoryItem* item = getItem();
- if (item)
- {
- mAssetID = item->getAssetUUID();
- }
-}
-
-void LLMaterialEditor::setAuxItem(const LLInventoryItem* item)
-{
- LLPreview::setAuxItem(item);
- if (item)
- {
- mAssetID = item->getAssetUUID();
- }
-}
-
-bool LLMaterialEditor::postBuild()
-{
- // if this is a 'live editor' instance, it is also
- // single instance and uses live overrides
- mIsOverride = getIsSingleInstance();
-
- mBaseColorTextureCtrl = getChild<LLTextureCtrl>("base_color_texture");
- mMetallicTextureCtrl = getChild<LLTextureCtrl>("metallic_roughness_texture");
- mEmissiveTextureCtrl = getChild<LLTextureCtrl>("emissive_texture");
- mNormalTextureCtrl = getChild<LLTextureCtrl>("normal_texture");
- mBaseColorCtrl = getChild<LLColorSwatchCtrl>("base color");
- mEmissiveColorCtrl = getChild<LLColorSwatchCtrl>("emissive color");
-
- if (!gAgent.isGodlike())
- {
- // Only allow fully permissive textures
- mBaseColorTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER);
- mMetallicTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER);
- mEmissiveTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER);
- mNormalTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER);
- }
-
- // Texture callback
- mBaseColorTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY));
- mMetallicTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY));
- mEmissiveTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY));
- mNormalTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY));
-
- mNormalTextureCtrl->setBlankImageAssetID(BLANK_OBJECT_NORMAL);
-
- if (mIsOverride)
- {
- // Live editing needs a recovery mechanism on cancel
- mBaseColorTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY));
- mMetallicTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY));
- mEmissiveTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY));
- mNormalTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY));
-
- // Save applied changes on 'OK' to our recovery mechanism.
- mBaseColorTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY));
- mMetallicTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY));
- mEmissiveTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY));
- mNormalTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY));
- }
- else
- {
- mBaseColorTextureCtrl->setCanApplyImmediately(false);
- mMetallicTextureCtrl->setCanApplyImmediately(false);
- mEmissiveTextureCtrl->setCanApplyImmediately(false);
- mNormalTextureCtrl->setCanApplyImmediately(false);
- }
-
- if (!mIsOverride)
- {
- childSetAction("save", boost::bind(&LLMaterialEditor::onClickSave, this));
- childSetAction("save_as", boost::bind(&LLMaterialEditor::onClickSaveAs, this));
- childSetAction("cancel", boost::bind(&LLMaterialEditor::onClickCancel, this));
- }
-
- if (mIsOverride)
- {
- childSetVisible("base_color_upload_fee", false);
- childSetVisible("metallic_upload_fee", false);
- childSetVisible("emissive_upload_fee", false);
- childSetVisible("normal_upload_fee", false);
- }
- else
- {
- S32 upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost();
- getChild<LLUICtrl>("base_color_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
- getChild<LLUICtrl>("metallic_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
- getChild<LLUICtrl>("emissive_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
- getChild<LLUICtrl>("normal_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
- }
-
- boost::function<void(LLUICtrl*, void*)> changes_callback = [this](LLUICtrl * ctrl, void* userData)
- {
- const U32 *flag = (const U32*)userData;
- markChangesUnsaved(*flag);
- // Apply changes to object live
- applyToSelection();
- };
-
- childSetCommitCallback("double sided", changes_callback, (void*)&MATERIAL_DOUBLE_SIDED_DIRTY);
-
- // BaseColor
- mBaseColorCtrl->setCommitCallback(changes_callback, (void*)&MATERIAL_BASE_COLOR_DIRTY);
- if (mIsOverride)
- {
- mBaseColorCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_BASE_COLOR_DIRTY));
- mBaseColorCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_BASE_COLOR_DIRTY));
- }
- else
- {
- mBaseColorCtrl->setCanApplyImmediately(false);
- }
- // transparency is a part of base color
- childSetCommitCallback("transparency", changes_callback, (void*)&MATERIAL_BASE_COLOR_DIRTY);
- childSetCommitCallback("alpha mode", changes_callback, (void*)&MATERIAL_ALPHA_MODE_DIRTY);
- childSetCommitCallback("alpha cutoff", changes_callback, (void*)&MATERIAL_ALPHA_CUTOFF_DIRTY);
-
- // Metallic-Roughness
- childSetCommitCallback("metalness factor", changes_callback, (void*)&MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY);
- childSetCommitCallback("roughness factor", changes_callback, (void*)&MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY);
-
- // Emissive
- mEmissiveColorCtrl->setCommitCallback(changes_callback, (void*)&MATERIAL_EMISIVE_COLOR_DIRTY);
- if (mIsOverride)
- {
- mEmissiveColorCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_EMISIVE_COLOR_DIRTY));
- mEmissiveColorCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_EMISIVE_COLOR_DIRTY));
- }
- else
- {
- mEmissiveColorCtrl->setCanApplyImmediately(false);
- }
-
- if (!mIsOverride)
- {
- // "unsaved_changes" doesn't exist in live editor
- childSetVisible("unsaved_changes", mUnsavedChanges);
-
- // Doesn't exist in live editor
- getChild<LLUICtrl>("total_upload_fee")->setTextArg("[FEE]", llformat("%d", 0));
- }
-
- // Todo:
- // Disable/enable setCanApplyImmediately() based on
- // working from inventory, upload or editing inworld
-
- return LLPreview::postBuild();
-}
-
-void LLMaterialEditor::onClickCloseBtn(bool app_quitting)
-{
- if (app_quitting || mIsOverride)
- {
- closeFloater(app_quitting);
- }
- else
- {
- onClickCancel();
- }
-}
-
-void LLMaterialEditor::onClose(bool app_quitting)
-{
- if (mSelectionUpdateSlot.connected())
- {
- mSelectionUpdateSlot.disconnect();
- }
- for (mat_connection_map_t::value_type &cn : mTextureChangesUpdates)
- {
- cn.second.mConnection.disconnect();
- }
- mTextureChangesUpdates.clear();
-
- LLPreview::onClose(app_quitting);
-}
-
-void LLMaterialEditor::draw()
-{
- if (mIsOverride)
- {
- if (mSelectionNeedsUpdate)
- {
- mSelectionNeedsUpdate = false;
- clearTextures();
- setFromSelection();
- }
- }
- LLPreview::draw();
-}
-
-void LLMaterialEditor::handleReshape(const LLRect& new_rect, bool by_user)
-{
- if (by_user)
- {
- const LLRect old_rect = getRect();
- LLRect clamp_rect(new_rect);
- clamp_rect.mRight = clamp_rect.mLeft + old_rect.getWidth();
- LLPreview::handleReshape(clamp_rect, by_user);
- }
- else
- {
- LLPreview::handleReshape(new_rect, by_user);
- }
-}
-
-LLUUID LLMaterialEditor::getBaseColorId()
-{
- return mBaseColorTextureCtrl->getValue().asUUID();
-}
-
-void LLMaterialEditor::setBaseColorId(const LLUUID& id)
-{
- mBaseColorTextureCtrl->setValue(id);
- mBaseColorTextureCtrl->setDefaultImageAssetID(id);
- mBaseColorTextureCtrl->setTentative(false);
-}
-
-void LLMaterialEditor::setBaseColorUploadId(const LLUUID& id)
-{
- // Might be better to use local textures and
- // assign a fee in case of a local texture
- if (id.notNull())
- {
- // todo: this does not account for posibility of texture
- // being from inventory, need to check that
- childSetValue("base_color_upload_fee", getString("upload_fee_string"));
- // Only set if we will need to upload this texture
- mBaseColorTextureUploadId = id;
- }
- markChangesUnsaved(MATERIAL_BASE_COLOR_TEX_DIRTY);
-}
-
-LLColor4 LLMaterialEditor::getBaseColor()
-{
- LLColor4 ret = linearColor4(LLColor4(mBaseColorCtrl->getValue()));
- ret.mV[3] = getTransparency();
- return ret;
-}
-
-void LLMaterialEditor::setBaseColor(const LLColor4& color)
-{
- mBaseColorCtrl->setValue(srgbColor4(color).getValue());
- setTransparency(color.mV[3]);
-}
-
-F32 LLMaterialEditor::getTransparency()
-{
- return childGetValue("transparency").asReal();
-}
-
-void LLMaterialEditor::setTransparency(F32 transparency)
-{
- childSetValue("transparency", transparency);
-}
-
-std::string LLMaterialEditor::getAlphaMode()
-{
- return childGetValue("alpha mode").asString();
-}
-
-void LLMaterialEditor::setAlphaMode(const std::string& alpha_mode)
-{
- childSetValue("alpha mode", alpha_mode);
-}
-
-F32 LLMaterialEditor::getAlphaCutoff()
-{
- return childGetValue("alpha cutoff").asReal();
-}
-
-void LLMaterialEditor::setAlphaCutoff(F32 alpha_cutoff)
-{
- childSetValue("alpha cutoff", alpha_cutoff);
-}
-
-void LLMaterialEditor::setMaterialName(const std::string &name)
-{
- setTitle(name);
- mMaterialName = name;
-}
-
-LLUUID LLMaterialEditor::getMetallicRoughnessId()
-{
- return mMetallicTextureCtrl->getValue().asUUID();
-}
-
-void LLMaterialEditor::setMetallicRoughnessId(const LLUUID& id)
-{
- mMetallicTextureCtrl->setValue(id);
- mMetallicTextureCtrl->setDefaultImageAssetID(id);
- mMetallicTextureCtrl->setTentative(false);
-}
-
-void LLMaterialEditor::setMetallicRoughnessUploadId(const LLUUID& id)
-{
- if (id.notNull())
- {
- // todo: this does not account for posibility of texture
- // being from inventory, need to check that
- childSetValue("metallic_upload_fee", getString("upload_fee_string"));
- mMetallicTextureUploadId = id;
- }
- markChangesUnsaved(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY);
-}
-
-F32 LLMaterialEditor::getMetalnessFactor()
-{
- return childGetValue("metalness factor").asReal();
-}
-
-void LLMaterialEditor::setMetalnessFactor(F32 factor)
-{
- childSetValue("metalness factor", factor);
-}
-
-F32 LLMaterialEditor::getRoughnessFactor()
-{
- return childGetValue("roughness factor").asReal();
-}
-
-void LLMaterialEditor::setRoughnessFactor(F32 factor)
-{
- childSetValue("roughness factor", factor);
-}
-
-LLUUID LLMaterialEditor::getEmissiveId()
-{
- return mEmissiveTextureCtrl->getValue().asUUID();
-}
-
-void LLMaterialEditor::setEmissiveId(const LLUUID& id)
-{
- mEmissiveTextureCtrl->setValue(id);
- mEmissiveTextureCtrl->setDefaultImageAssetID(id);
- mEmissiveTextureCtrl->setTentative(false);
-}
-
-void LLMaterialEditor::setEmissiveUploadId(const LLUUID& id)
-{
- if (id.notNull())
- {
- // todo: this does not account for posibility of texture
- // being from inventory, need to check that
- childSetValue("emissive_upload_fee", getString("upload_fee_string"));
- mEmissiveTextureUploadId = id;
- }
- markChangesUnsaved(MATERIAL_EMISIVE_TEX_DIRTY);
-}
-
-LLColor4 LLMaterialEditor::getEmissiveColor()
-{
- return linearColor4(LLColor4(mEmissiveColorCtrl->getValue()));
-}
-
-void LLMaterialEditor::setEmissiveColor(const LLColor4& color)
-{
- mEmissiveColorCtrl->setValue(srgbColor4(color).getValue());
-}
-
-LLUUID LLMaterialEditor::getNormalId()
-{
- return mNormalTextureCtrl->getValue().asUUID();
-}
-
-void LLMaterialEditor::setNormalId(const LLUUID& id)
-{
- mNormalTextureCtrl->setValue(id);
- mNormalTextureCtrl->setDefaultImageAssetID(id);
- mNormalTextureCtrl->setTentative(false);
-}
-
-void LLMaterialEditor::setNormalUploadId(const LLUUID& id)
-{
- if (id.notNull())
- {
- // todo: this does not account for posibility of texture
- // being from inventory, need to check that
- childSetValue("normal_upload_fee", getString("upload_fee_string"));
- mNormalTextureUploadId = id;
- }
- markChangesUnsaved(MATERIAL_NORMAL_TEX_DIRTY);
-}
-
-bool LLMaterialEditor::getDoubleSided()
-{
- return childGetValue("double sided").asBoolean();
-}
-
-void LLMaterialEditor::setDoubleSided(bool double_sided)
-{
- childSetValue("double sided", double_sided);
-}
-
-void LLMaterialEditor::resetUnsavedChanges()
-{
- mUnsavedChanges = 0;
- mRevertedChanges = 0;
- if (!mIsOverride)
- {
- childSetVisible("unsaved_changes", false);
- setCanSave(false);
-
- mExpectedUploadCost = 0;
- getChild<LLUICtrl>("total_upload_fee")->setTextArg("[FEE]", llformat("%d", mExpectedUploadCost));
- }
-}
-
-void LLMaterialEditor::markChangesUnsaved(U32 dirty_flag)
-{
- mUnsavedChanges |= dirty_flag;
- if (mIsOverride)
- {
- // at the moment live editing (mIsOverride) applies everything 'live'
- // and "unsaved_changes", save/cancel buttons don't exist there
- return;
- }
-
- childSetVisible("unsaved_changes", mUnsavedChanges);
-
- if (mUnsavedChanges)
- {
- const LLInventoryItem* item = getItem();
- if (item)
- {
- //LLPermissions perm(item->getPermissions());
- bool allow_modify = canModify(mObjectUUID, item);
- bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
- bool source_notecard = mNotecardInventoryID.notNull();
-
- setCanSave(allow_modify && !source_library && !source_notecard);
- }
- }
- else
- {
- setCanSave(false);
- }
-
- S32 upload_texture_count = 0;
- if (mBaseColorTextureUploadId.notNull() && mBaseColorTextureUploadId == getBaseColorId())
- {
- upload_texture_count++;
- }
- if (mMetallicTextureUploadId.notNull() && mMetallicTextureUploadId == getMetallicRoughnessId())
- {
- upload_texture_count++;
- }
- if (mEmissiveTextureUploadId.notNull() && mEmissiveTextureUploadId == getEmissiveId())
- {
- upload_texture_count++;
- }
- if (mNormalTextureUploadId.notNull() && mNormalTextureUploadId == getNormalId())
- {
- upload_texture_count++;
- }
-
- mExpectedUploadCost = upload_texture_count * LLAgentBenefitsMgr::current().getTextureUploadCost();
- getChild<LLUICtrl>("total_upload_fee")->setTextArg("[FEE]", llformat("%d", mExpectedUploadCost));
-}
-
-void LLMaterialEditor::setCanSaveAs(bool value)
-{
- if (!mIsOverride)
- {
- childSetEnabled("save_as", value);
- }
-}
-
-void LLMaterialEditor::setCanSave(bool value)
-{
- if (!mIsOverride)
- {
- childSetEnabled("save", value);
- }
-}
-
-void LLMaterialEditor::setEnableEditing(bool can_modify)
-{
- childSetEnabled("double sided", can_modify);
-
- // BaseColor
- childSetEnabled("base color", can_modify);
- childSetEnabled("transparency", can_modify);
- childSetEnabled("alpha mode", can_modify);
- childSetEnabled("alpha cutoff", can_modify);
-
- // Metallic-Roughness
- childSetEnabled("metalness factor", can_modify);
- childSetEnabled("roughness factor", can_modify);
-
- // Metallic-Roughness
- childSetEnabled("metalness factor", can_modify);
- childSetEnabled("roughness factor", can_modify);
-
- // Emissive
- childSetEnabled("emissive color", can_modify);
-
- mBaseColorTextureCtrl->setEnabled(can_modify);
- mMetallicTextureCtrl->setEnabled(can_modify);
- mEmissiveTextureCtrl->setEnabled(can_modify);
- mNormalTextureCtrl->setEnabled(can_modify);
-}
-
-void LLMaterialEditor::subscribeToLocalTexture(S32 dirty_flag, const LLUUID& tracking_id)
-{
- if (mTextureChangesUpdates[dirty_flag].mTrackingId != tracking_id)
- {
- mTextureChangesUpdates[dirty_flag].mConnection.disconnect();
- mTextureChangesUpdates[dirty_flag].mTrackingId = tracking_id;
- mTextureChangesUpdates[dirty_flag].mConnection = LLLocalBitmapMgr::getInstance()->setOnChangedCallback(tracking_id,
- [this, dirty_flag](const LLUUID& tracking_id, const LLUUID& old_id, const LLUUID& new_id)
- {
- if (new_id.isNull())
- {
- mTextureChangesUpdates[dirty_flag].mConnection.disconnect();
- //mTextureChangesUpdates.erase(dirty_flag);
- }
- else
- {
- replaceLocalTexture(old_id, new_id);
- }
- });
- }
-}
-
-LLUUID LLMaterialEditor::getLocalTextureTrackingIdFromFlag(U32 flag)
-{
- mat_connection_map_t::iterator found = mTextureChangesUpdates.find(flag);
- if (found != mTextureChangesUpdates.end())
- {
- return found->second.mTrackingId;
- }
- return LLUUID();
-}
-
-bool LLMaterialEditor::updateMaterialLocalSubscription(LLGLTFMaterial* mat)
-{
- if (!mat)
- {
- return false;
- }
-
- bool res = false;
- for (mat_connection_map_t::value_type& cn : mTextureChangesUpdates)
- {
- LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(cn.second.mTrackingId);
- if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR])
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat);
- res = true;
- continue;
- }
- if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS])
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat);
- res = true;
- continue;
- }
- if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE])
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat);
- res = true;
- continue;
- }
- if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL])
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat);
- res = true;
- continue;
- }
- }
- return res;
-}
-
-void LLMaterialEditor::replaceLocalTexture(const LLUUID& old_id, const LLUUID& new_id)
-{
- // todo: might be a good idea to set mBaseColorTextureUploadId here
- // and when texturectrl picks a local texture
- if (getBaseColorId() == old_id)
- {
- mBaseColorTextureCtrl->setValue(new_id);
- }
- if (mBaseColorTextureCtrl->getDefaultImageAssetID() == old_id)
- {
- mBaseColorTextureCtrl->setDefaultImageAssetID(new_id);
- }
-
- if (getMetallicRoughnessId() == old_id)
- {
- mMetallicTextureCtrl->setValue(new_id);
- }
- if (mMetallicTextureCtrl->getDefaultImageAssetID() == old_id)
- {
- mMetallicTextureCtrl->setDefaultImageAssetID(new_id);
- }
-
- if (getEmissiveId() == old_id)
- {
- mEmissiveTextureCtrl->setValue(new_id);
- }
- if (mEmissiveTextureCtrl->getDefaultImageAssetID() == old_id)
- {
- mEmissiveTextureCtrl->setDefaultImageAssetID(new_id);
- }
-
- if (getNormalId() == old_id)
- {
- mNormalTextureCtrl->setValue(new_id);
- }
- if (mNormalTextureCtrl->getDefaultImageAssetID() == old_id)
- {
- mNormalTextureCtrl->setDefaultImageAssetID(new_id);
- }
-}
-
-void LLMaterialEditor::onCommitTexture(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag)
-{
- if (!mIsOverride)
- {
- std::string upload_fee_ctrl_name;
- LLUUID old_uuid;
-
- switch (dirty_flag)
- {
- case MATERIAL_BASE_COLOR_TEX_DIRTY:
- {
- upload_fee_ctrl_name = "base_color_upload_fee";
- old_uuid = mBaseColorTextureUploadId;
- break;
- }
- case MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY:
- {
- upload_fee_ctrl_name = "metallic_upload_fee";
- old_uuid = mMetallicTextureUploadId;
- break;
- }
- case MATERIAL_EMISIVE_TEX_DIRTY:
- {
- upload_fee_ctrl_name = "emissive_upload_fee";
- old_uuid = mEmissiveTextureUploadId;
- break;
- }
- case MATERIAL_NORMAL_TEX_DIRTY:
- {
- upload_fee_ctrl_name = "normal_upload_fee";
- old_uuid = mNormalTextureUploadId;
- break;
- }
- default:
- break;
- }
- LLUUID new_val = ctrl->getValue().asUUID();
- if (new_val == old_uuid && old_uuid.notNull())
- {
- childSetValue(upload_fee_ctrl_name, getString("upload_fee_string"));
- }
- else
- {
- // Texture picker has 'apply now' with 'cancel' support.
- // Don't clean mBaseColorJ2C and mBaseColorFetched, it's our
- // storage in case user decides to cancel changes.
- // Without mBaseColorFetched, viewer will eventually cleanup
- // the texture that is not in use
- childSetValue(upload_fee_ctrl_name, getString("no_upload_fee_string"));
- }
- }
-
- LLTextureCtrl* tex_ctrl = (LLTextureCtrl*)ctrl;
- if (tex_ctrl->isImageLocal())
- {
- subscribeToLocalTexture(dirty_flag, tex_ctrl->getLocalTrackingID());
- }
- else
- {
- // unsubcribe potential old callabck
- mat_connection_map_t::iterator found = mTextureChangesUpdates.find(dirty_flag);
- if (found != mTextureChangesUpdates.end())
- {
- found->second.mConnection.disconnect();
- }
- }
-
- markChangesUnsaved(dirty_flag);
- applyToSelection();
-}
-
-void LLMaterialEditor::onCancelCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag)
-{
- mRevertedChanges |= dirty_flag;
- applyToSelection();
-}
-
-void update_local_texture(LLUICtrl* ctrl, LLGLTFMaterial* mat)
-{
- LLTextureCtrl* tex_ctrl = (LLTextureCtrl*)ctrl;
- if (tex_ctrl->isImageLocal())
- {
- // subscrive material to updates of local textures
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tex_ctrl->getLocalTrackingID(), mat);
- }
-}
-
-void LLMaterialEditor::onSelectCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag)
-{
- mUnsavedChanges |= dirty_flag;
- applyToSelection();
-
- struct f : public LLSelectedNodeFunctor
- {
- f(LLUICtrl* ctrl, S32 dirty_flag) : mCtrl(ctrl), mDirtyFlag(dirty_flag)
- {
- }
-
- virtual bool apply(LLSelectNode* nodep)
- {
- LLViewerObject* objectp = nodep->getObject();
- if (!objectp)
- {
- return false;
- }
- S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces
- for (S32 te = 0; te < num_tes; ++te)
- {
- if (nodep->isTESelected(te) && nodep->mSavedGLTFOverrideMaterials.size() > te)
- {
- if (nodep->mSavedGLTFOverrideMaterials[te].isNull())
- {
- // populate with default values, default values basically mean 'not in use'
- nodep->mSavedGLTFOverrideMaterials[te] = new LLGLTFMaterial();
- }
-
- switch (mDirtyFlag)
- {
- //Textures
- case MATERIAL_BASE_COLOR_TEX_DIRTY:
- {
- nodep->mSavedGLTFOverrideMaterials[te]->setBaseColorId(mCtrl->getValue().asUUID(), true);
- update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get());
- break;
- }
- case MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY:
- {
- nodep->mSavedGLTFOverrideMaterials[te]->setOcclusionRoughnessMetallicId(mCtrl->getValue().asUUID(), true);
- update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get());
- break;
- }
- case MATERIAL_EMISIVE_TEX_DIRTY:
- {
- nodep->mSavedGLTFOverrideMaterials[te]->setEmissiveId(mCtrl->getValue().asUUID(), true);
- update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get());
- break;
- }
- case MATERIAL_NORMAL_TEX_DIRTY:
- {
- nodep->mSavedGLTFOverrideMaterials[te]->setNormalId(mCtrl->getValue().asUUID(), true);
- update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get());
- break;
- }
- // Colors
- case MATERIAL_BASE_COLOR_DIRTY:
- {
- LLColor4 ret = linearColor4(LLColor4(mCtrl->getValue()));
- // except transparency
- ret.mV[3] = nodep->mSavedGLTFOverrideMaterials[te]->mBaseColor.mV[3];
- nodep->mSavedGLTFOverrideMaterials[te]->setBaseColorFactor(ret, true);
- break;
- }
- case MATERIAL_EMISIVE_COLOR_DIRTY:
- {
- nodep->mSavedGLTFOverrideMaterials[te]->setEmissiveColorFactor(LLColor3(mCtrl->getValue()), true);
- break;
- }
- default:
- break;
- }
- }
- }
- return true;
- }
-
- LLUICtrl* mCtrl;
- S32 mDirtyFlag;
- } func(ctrl, dirty_flag);
-
- LLSelectMgr::getInstance()->getSelection()->applyToNodes(&func);
-}
-
-void LLMaterialEditor::onClickSave()
-{
- if (!capabilitiesAvailable())
- {
- LLNotificationsUtil::add("MissingMaterialCaps");
- return;
- }
- if (!can_afford_transaction(mExpectedUploadCost))
- {
- LLSD args;
- args["COST"] = llformat("%d", mExpectedUploadCost);
- LLNotificationsUtil::add("ErrorCannotAffordUpload", args);
- return;
- }
-
- applyToSelection();
- saveIfNeeded();
-}
-
-std::string LLMaterialEditor::getEncodedAsset()
-{
- LLSD asset;
- asset["version"] = LLGLTFMaterial::ASSET_VERSION;
- asset["type"] = LLGLTFMaterial::ASSET_TYPE;
- LLGLTFMaterial mat;
- getGLTFMaterial(&mat);
- asset["data"] = mat.asJSON();
-
- std::ostringstream str;
- LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY);
-
- return str.str();
-}
-
-bool LLMaterialEditor::decodeAsset(const std::vector<char>& buffer)
-{
- LLSD asset;
-
- std::istrstream str(&buffer[0], buffer.size());
- if (LLSDSerialize::deserialize(asset, str, buffer.size()))
- {
- if (asset.has("version") && LLGLTFMaterial::isAcceptedVersion(asset["version"].asString()))
- {
- if (asset.has("type") && asset["type"] == LLGLTFMaterial::ASSET_TYPE)
- {
- if (asset.has("data") && asset["data"].isString())
- {
- std::string data = asset["data"];
-
- tinygltf::TinyGLTF gltf;
- tinygltf::TinyGLTF loader;
- std::string error_msg;
- std::string warn_msg;
-
- tinygltf::Model model_in;
-
- if (loader.LoadASCIIFromString(&model_in, &error_msg, &warn_msg, data.c_str(), data.length(), ""))
- {
- // assets are only supposed to have one item
- // *NOTE: This duplicates some functionality from
- // LLGLTFMaterial::fromJSON, but currently does the job
- // better for the material editor use case.
- // However, LLGLTFMaterial::asJSON should always be
- // used when uploading materials, to ensure the
- // asset is valid.
- return setFromGltfModel(model_in, 0, true);
- }
- else
- {
- LL_WARNS("MaterialEditor") << "Floater " << getKey() << " Failed to decode material asset: " << LL_NEWLINE
- << warn_msg << LL_NEWLINE
- << error_msg << LL_ENDL;
- }
- }
- }
- }
- else
- {
- LL_WARNS("MaterialEditor") << "Invalid LLSD content "<< asset << " for flaoter " << getKey() << LL_ENDL;
- }
- }
- else
- {
- LL_WARNS("MaterialEditor") << "Failed to deserialize material LLSD for flaoter " << getKey() << LL_ENDL;
- }
-
- return false;
-}
-
-/**
- * Build a description of the material we just imported.
- * Currently this means a list of the textures present but we
- * may eventually want to make it more complete - will be guided
- * by what the content creators say they need.
- */
-const std::string LLMaterialEditor::buildMaterialDescription()
-{
- std::ostringstream desc;
- desc << LLTrans::getString("Material Texture Name Header");
-
- // add the texture names for each just so long as the material
- // we loaded has an entry for it (i think testing the texture
- // control UUI for NULL is a valid metric for if it was loaded
- // or not but I suspect this code will change a lot so may need
- // to revisit
- if (!mBaseColorTextureCtrl->getValue().asUUID().isNull())
- {
- desc << mBaseColorName;
- desc << ", ";
- }
- if (!mMetallicTextureCtrl->getValue().asUUID().isNull())
- {
- desc << mMetallicRoughnessName;
- desc << ", ";
- }
- if (!mEmissiveTextureCtrl->getValue().asUUID().isNull())
- {
- desc << mEmissiveName;
- desc << ", ";
- }
- if (!mNormalTextureCtrl->getValue().asUUID().isNull())
- {
- desc << mNormalName;
- }
-
- // trim last char if it's a ',' in case there is no normal texture
- // present and the code above inserts one
- // (no need to check for string length - always has initial string)
- std::string::iterator iter = desc.str().end() - 1;
- if (*iter == ',')
- {
- desc.str().erase(iter);
- }
-
- // sanitize the material description so that it's compatible with the inventory
- // note: split this up because clang doesn't like operating directly on the
- // str() - error: lvalue reference to type 'basic_string<...>' cannot bind to a
- // temporary of type 'basic_string<...>'
- std::string inv_desc = desc.str();
- LLInventoryObject::correctInventoryName(inv_desc);
-
- return inv_desc;
-}
-
-bool LLMaterialEditor::saveIfNeeded()
-{
- if (mUploadingTexturesCount > 0)
- {
- // Upload already in progress, wait until
- // textures upload will retry saving on callback.
- // Also should prevent some failure-callbacks
- return true;
- }
-
- if (saveTextures() > 0)
- {
- // started texture upload
- setEnabled(false);
- return true;
- }
-
- std::string buffer = getEncodedAsset();
-
- const LLInventoryItem* item = getItem();
- // save it out to database
- if (item)
- {
- if (!updateInventoryItem(buffer, mItemUUID, mObjectUUID))
- {
- return false;
- }
-
- if (mCloseAfterSave)
- {
- closeFloater();
- }
- else
- {
- mAssetStatus = PREVIEW_ASSET_LOADING;
- setEnabled(false);
- }
- }
- else
- {
- // Make a new inventory item and set upload permissions
- LLPermissions local_permissions;
- local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
-
- if (mIsOverride)
- {
- // Shouldn't happen, but just in case it ever changes
- U32 everyone_perm = LLFloaterPerms::getEveryonePerms("Materials");
- U32 group_perm = LLFloaterPerms::getGroupPerms("Materials");
- U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Materials");
- local_permissions.initMasks(PERM_ALL, PERM_ALL, everyone_perm, group_perm, next_owner_perm);
-
- }
- else
- {
- // Uploads are supposed to use Upload permissions, not material permissions
- U32 everyone_perm = LLFloaterPerms::getEveryonePerms("Uploads");
- U32 group_perm = LLFloaterPerms::getGroupPerms("Uploads");
- U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Uploads");
- local_permissions.initMasks(PERM_ALL, PERM_ALL, everyone_perm, group_perm, next_owner_perm);
- }
-
- std::string res_desc = buildMaterialDescription();
- createInventoryItem(buffer, mMaterialName, res_desc, local_permissions);
-
- // We do not update floater with uploaded asset yet, so just close it.
- closeFloater();
- }
-
- return true;
-}
-
-// static
-bool LLMaterialEditor::updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id)
-{
- const LLViewerRegion* region = gAgent.getRegion();
- if (!region)
- {
- LL_WARNS("MaterialEditor") << "Not connected to a region, cannot save material." << LL_ENDL;
- return false;
- }
- std::string agent_url = region->getCapability("UpdateMaterialAgentInventory");
- std::string task_url = region->getCapability("UpdateMaterialTaskInventory");
-
- if (!agent_url.empty() && !task_url.empty())
- {
- std::string url;
- LLResourceUploadInfo::ptr_t uploadInfo;
-
- if (task_id.isNull() && !agent_url.empty())
- {
- uploadInfo = std::make_shared<LLBufferedAssetUploadInfo>(item_id, LLAssetType::AT_MATERIAL, buffer,
- [](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD)
- {
- // done callback
- LLMaterialEditor::finishInventoryUpload(itemId, newAssetId, newItemId);
- },
- [](LLUUID itemId, LLUUID taskId, LLSD response, std::string reason)
- {
- // failure callback
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", LLSD(itemId));
- if (me)
- {
- me->setEnabled(true);
- }
- return true;
- }
- );
- url = agent_url;
- }
- else if (!task_id.isNull() && !task_url.empty())
- {
- uploadInfo = std::make_shared<LLBufferedAssetUploadInfo>(task_id, item_id, LLAssetType::AT_MATERIAL, buffer,
- [](LLUUID itemId, LLUUID task_id, LLUUID newAssetId, LLSD)
- {
- // done callback
- LLMaterialEditor::finishTaskUpload(itemId, newAssetId, task_id);
- },
- [](LLUUID itemId, LLUUID task_id, LLSD response, std::string reason)
- {
- // failure callback
- LLSD floater_key;
- floater_key["taskid"] = task_id;
- floater_key["itemid"] = itemId;
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", floater_key);
- if (me)
- {
- me->setEnabled(true);
- }
- return true;
- }
- );
- url = task_url;
- }
-
- if (!url.empty() && uploadInfo)
- {
- LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo);
- }
- else
- {
- return false;
- }
-
- }
- else // !gAssetStorage
- {
- LL_WARNS("MaterialEditor") << "Not connected to an materials capable region." << LL_ENDL;
- return false;
- }
-
- // todo: apply permissions from textures here if server doesn't
- // if any texture is 'no transfer', material should be 'no transfer' as well
-
- return true;
-}
-
-// Callback intended for when a material is saved from an object and needs to
-// be modified to reflect the new asset/name.
-class LLObjectsMaterialItemCallback : public LLInventoryCallback
-{
-public:
- LLObjectsMaterialItemCallback(const LLPermissions& permissions, const std::string& asset_data, const std::string& new_name)
- : mPermissions(permissions),
- mAssetData(asset_data),
- mNewName(new_name)
- {
- }
-
- void fire(const LLUUID& inv_item_id) override
- {
- LLViewerInventoryItem* item = gInventory.getItem(inv_item_id);
- if (!item)
- {
- return;
- }
-
- // Name may or may not have already been applied
- const bool changed_name = item->getName() != mNewName;
- // create_inventory_item/copy_inventory_item don't allow presetting some permissions, fix it now
- const bool changed_permissions = item->getPermissions() != mPermissions;
- const bool changed = changed_name || changed_permissions;
- LLSD updates;
- if (changed)
- {
- if (changed_name)
- {
- updates["name"] = mNewName;
- }
- if (changed_permissions)
- {
- updates["permissions"] = ll_create_sd_from_permissions(mPermissions);
- }
- update_inventory_item(inv_item_id, updates, NULL);
- }
-
- // from reference in LLSettingsVOBase::createInventoryItem()/updateInventoryItem()
- LLResourceUploadInfo::ptr_t uploadInfo =
- std::make_shared<LLBufferedAssetUploadInfo>(
- inv_item_id,
- LLAssetType::AT_MATERIAL,
- mAssetData,
- [changed, updates](LLUUID item_id, LLUUID new_asset_id, LLUUID new_item_id, LLSD response)
- {
- // done callback
- LL_INFOS("Material") << "inventory item uploaded. item: " << item_id << " new_item_id: " << new_item_id << " response: " << response << LL_ENDL;
-
- // *HACK: Sometimes permissions do not stick in the UI. They are correct on the server-side, though.
- if (changed)
- {
- update_inventory_item(new_item_id, updates, NULL);
- }
- },
- nullptr // failure callback, floater already closed
- );
-
- const LLViewerRegion* region = gAgent.getRegion();
- if (region)
- {
- std::string agent_url(region->getCapability("UpdateMaterialAgentInventory"));
- if (agent_url.empty())
- {
- LL_ERRS("MaterialEditor") << "missing required agent inventory cap url" << LL_ENDL;
- }
- LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo);
- }
- }
-private:
- LLPermissions mPermissions;
- std::string mAssetData;
- std::string mNewName;
-};
-
-void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions)
-{
- // gen a new uuid for this asset
- LLTransactionID tid;
- tid.generate(); // timestamp-based randomization + uniquification
- LLUUID parent = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_MATERIAL);
- const U8 subtype = NO_INV_SUBTYPE; // TODO maybe use AT_SETTINGS and LLSettingsType::ST_MATERIAL ?
-
- LLPointer<LLObjectsMaterialItemCallback> cb = new LLObjectsMaterialItemCallback(permissions, buffer, name);
- create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent, tid, name, desc,
- LLAssetType::AT_MATERIAL, LLInventoryType::IT_MATERIAL, subtype, permissions.getMaskNextOwner(),
- cb);
-}
-
-void LLMaterialEditor::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId)
-{
- // Update the UI with the new asset.
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", LLSD(itemId));
- if (me)
- {
- if (newItemId.isNull())
- {
- me->setAssetId(newAssetId);
- me->refreshFromInventory();
- }
- else if (newItemId.notNull())
- {
- // Not supposed to happen?
- me->refreshFromInventory(newItemId);
- }
- else
- {
- me->refreshFromInventory(itemId);
- }
-
- if (me && !me->mTextureChangesUpdates.empty())
- {
- const LLInventoryItem* item = me->getItem();
- if (item)
- {
- // local materials were assigned, force load material and init tracking
- LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(item->getAssetUUID());
- for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates)
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat);
- }
- }
- }
- }
-}
-
-void LLMaterialEditor::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId)
-{
- LLSD floater_key;
- floater_key["taskid"] = taskId;
- floater_key["itemid"] = itemId;
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", floater_key);
- if (me)
- {
- me->setAssetId(newAssetId);
- me->refreshFromInventory();
- me->setEnabled(true);
-
- if (me && !me->mTextureChangesUpdates.empty())
- {
- // local materials were assigned, force load material and init tracking
- LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(newAssetId);
- for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates)
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat);
- }
- }
- }
-}
-
-void LLMaterialEditor::finishSaveAs(
- const LLSD &oldKey,
- const LLUUID &newItemId,
- const std::string &buffer,
- bool has_unsaved_changes)
-{
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", oldKey);
- LLViewerInventoryItem* item = gInventory.getItem(newItemId);
- if (item)
- {
- if (me)
- {
- me->mItemUUID = newItemId;
- me->mObjectUUID = LLUUID::null;
- me->mNotecardInventoryID = LLUUID::null;
- me->mNotecardObjectID = LLUUID::null;
- me->mAuxItem = nullptr;
- me->setKey(LLSD(newItemId)); // for findTypedInstance
- me->setMaterialName(item->getName());
- if (has_unsaved_changes)
- {
- if (!updateInventoryItem(buffer, newItemId, LLUUID::null))
- {
- me->setEnabled(true);
- }
- }
- else
- {
- me->loadAsset();
- me->setEnabled(true);
-
- // Local texure support
- if (!me->mTextureChangesUpdates.empty())
- {
- // local materials were assigned, force load material and init tracking
- LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(item->getAssetUUID());
- for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates)
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat);
- }
- }
- }
- }
- else if(has_unsaved_changes)
- {
- updateInventoryItem(buffer, newItemId, LLUUID::null);
- }
- }
- else if (me)
- {
- me->setEnabled(true);
- LL_WARNS("MaterialEditor") << "Item does not exist, floater " << me->getKey() << LL_ENDL;
- }
-}
-
-void LLMaterialEditor::refreshFromInventory(const LLUUID& new_item_id)
-{
- if (mIsOverride)
- {
- // refreshFromInventory shouldn't be called for overrides,
- // but just in case.
- LL_WARNS("MaterialEditor") << "Tried to refresh from inventory for live editor" << LL_ENDL;
- return;
- }
- LLSD old_key = getKey();
- if (new_item_id.notNull())
- {
- mItemUUID = new_item_id;
- if (mNotecardInventoryID.notNull())
- {
- LLSD floater_key;
- floater_key["objectid"] = mNotecardObjectID;
- floater_key["notecardid"] = mNotecardInventoryID;
- setKey(floater_key);
- }
- else if (mObjectUUID.notNull())
- {
- LLSD floater_key;
- floater_key["taskid"] = new_item_id;
- floater_key["itemid"] = mObjectUUID;
- setKey(floater_key);
- }
- else
- {
- setKey(LLSD(new_item_id));
- }
- }
- LL_DEBUGS("MaterialEditor") << "New floater key: " << getKey() << " Old key: " << old_key << LL_ENDL;
- loadAsset();
-}
-
-
-void LLMaterialEditor::onClickSaveAs()
-{
- if (!LLMaterialEditor::capabilitiesAvailable())
- {
- LLNotificationsUtil::add("MissingMaterialCaps");
- return;
- }
-
- if (!can_afford_transaction(mExpectedUploadCost))
- {
- LLSD args;
- args["COST"] = llformat("%d", mExpectedUploadCost);
- LLNotificationsUtil::add("ErrorCannotAffordUpload", args);
- return;
- }
-
- LLSD args;
- args["DESC"] = mMaterialName;
-
- LLNotificationsUtil::add("SaveMaterialAs", args, LLSD(), boost::bind(&LLMaterialEditor::onSaveAsMsgCallback, this, _1, _2));
-}
-
-void LLMaterialEditor::onSaveAsMsgCallback(const LLSD& notification, const LLSD& response)
-{
- S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
- if (0 == option)
- {
- std::string new_name = response["message"].asString();
- LLInventoryObject::correctInventoryName(new_name);
- if (!new_name.empty())
- {
- const LLInventoryItem* item;
- if (mNotecardInventoryID.notNull())
- {
- item = mAuxItem.get();
- }
- else
- {
- item = getItem();
- }
- if (item)
- {
- const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
- LLUUID parent_id = item->getParentUUID();
- if (mObjectUUID.notNull() || marketplacelistings_id == parent_id || gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID()))
- {
- parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL);
- }
-
- // A two step process, first copy an existing item, then create new asset
- if (mNotecardInventoryID.notNull())
- {
- LLPointer<LLInventoryCallback> cb = new LLMaterialEditorCopiedCallback(getKey(), new_name);
- copy_inventory_from_notecard(parent_id,
- mNotecardObjectID,
- mNotecardInventoryID,
- mAuxItem.get(),
- gInventoryCallbacks.registerCB(cb));
- }
- else
- {
- std::string buffer = getEncodedAsset();
- LLPointer<LLInventoryCallback> cb = new LLMaterialEditorCopiedCallback(buffer, getKey(), mUnsavedChanges);
- copy_inventory_item(
- gAgent.getID(),
- item->getPermissions().getOwner(),
- item->getUUID(),
- parent_id,
- new_name,
- cb);
- }
-
- mAssetStatus = PREVIEW_ASSET_LOADING;
- setEnabled(false);
- }
- else
- {
- setMaterialName(new_name);
- onClickSave();
- }
- }
- else
- {
- LLNotificationsUtil::add("InvalidMaterialName", LLSD(), LLSD(), [this](const LLSD& notification, const LLSD& response)
- {
- LLNotificationsUtil::add("SaveMaterialAs", LLSD().with("DESC", mMaterialName), LLSD(),
- boost::bind(&LLMaterialEditor::onSaveAsMsgCallback, this, _1, _2));
- });
- }
- }
-}
-
-void LLMaterialEditor::onClickCancel()
-{
- if (mUnsavedChanges)
- {
- LLNotificationsUtil::add("UsavedMaterialChanges", LLSD(), LLSD(), boost::bind(&LLMaterialEditor::onCancelMsgCallback, this, _1, _2));
- }
- else
- {
- closeFloater();
- }
-}
-
-void LLMaterialEditor::onCancelMsgCallback(const LLSD& notification, const LLSD& response)
-{
- S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
- if (0 == option)
- {
- closeFloater();
- }
-}
-
-static void pack_textures(
- LLPointer<LLImageRaw>& base_color_img,
- LLPointer<LLImageRaw>& normal_img,
- LLPointer<LLImageRaw>& mr_img,
- LLPointer<LLImageRaw>& emissive_img,
- LLPointer<LLImageRaw>& occlusion_img,
- LLPointer<LLImageJ2C>& base_color_j2c,
- LLPointer<LLImageJ2C>& normal_j2c,
- LLPointer<LLImageJ2C>& mr_j2c,
- LLPointer<LLImageJ2C>& emissive_j2c)
-{
- // NOTE : remove log spam and lossless vs lossy comparisons when the logs are no longer useful
-
- if (base_color_img)
- {
- base_color_j2c = LLViewerTextureList::convertToUploadFile(base_color_img);
- LL_DEBUGS("MaterialEditor") << "BaseColor: " << base_color_j2c->getDataSize() << LL_ENDL;
- }
-
- if (normal_img)
- {
- // create a losslessly compressed version of the normal map
- normal_j2c = LLViewerTextureList::convertToUploadFile(normal_img, 1024, false, true);
- LL_DEBUGS("MaterialEditor") << "Normal: " << normal_j2c->getDataSize() << LL_ENDL;
- }
-
- if (mr_img)
- {
- mr_j2c = LLViewerTextureList::convertToUploadFile(mr_img);
- LL_DEBUGS("MaterialEditor") << "Metallic/Roughness: " << mr_j2c->getDataSize() << LL_ENDL;
- }
-
- if (emissive_img)
- {
- emissive_j2c = LLViewerTextureList::convertToUploadFile(emissive_img);
- LL_DEBUGS("MaterialEditor") << "Emissive: " << emissive_j2c->getDataSize() << LL_ENDL;
- }
-}
-
-void LLMaterialEditor::uploadMaterialFromModel(const std::string& filename, tinygltf::Model& model_in, S32 index)
-{
- if (index < 0 || !LLMaterialEditor::capabilitiesAvailable())
- {
- return;
- }
-
- if (model_in.materials.empty())
- {
- // materials are missing
- return;
- }
-
- if (index >= 0 && model_in.materials.size() <= index)
- {
- // material is missing
- return;
- }
-
- // Todo: no point in loading whole editor
- // This uses 'filename' to make sure multiple bulk uploads work
- // instead of fighting for a single instance.
- LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor", LLSD().with("filename", filename).with("index", LLSD::Integer(index)));
- me->loadMaterial(model_in, filename, index, false);
- me->saveIfNeeded();
-}
-
-
-void LLMaterialEditor::loadMaterialFromFile(const std::string& filename, S32 index)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
-
- tinygltf::TinyGLTF loader;
- std::string error_msg;
- std::string warn_msg;
-
- bool loaded = false;
- 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.
- if (std::string::npos == filename_lc.rfind(".gltf"))
- { // file is binary
- loaded = loader.LoadBinaryFromFile(&model_in, &error_msg, &warn_msg, filename);
- }
- else
- { // file is ascii
- loaded = loader.LoadASCIIFromFile(&model_in, &error_msg, &warn_msg, filename);
- }
-
- if (!loaded)
- {
- LLNotificationsUtil::add("CannotUploadMaterial");
- return;
- }
-
- if (model_in.materials.empty())
- {
- // materials are missing
- LLNotificationsUtil::add("CannotUploadMaterial");
- return;
- }
-
- if (index >= 0 && model_in.materials.size() <= index)
- {
- // material is missing
- LLNotificationsUtil::add("CannotUploadMaterial");
- return;
- }
-
- LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor");
-
- if (index >= 0)
- {
- // Prespecified material
- me->loadMaterial(model_in, filename, index);
- }
- else if (model_in.materials.size() == 1)
- {
- // Only one, just load it
- me->loadMaterial(model_in, filename, 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();
-
- for (; mat_iter != mat_end; mat_iter++)
- {
- std::string mat_name = mat_iter->name;
- if (mat_name.empty())
- {
- material_list.push_back("Material " + std::to_string(material_list.size()));
- }
- else
- {
- material_list.push_back(mat_name);
- }
- }
-
- material_list.push_back(me->getString("material_batch_import_text"));
-
- LLFloaterComboOptions::showUI(
- [me, model_in, filename](const std::string& option, S32 index)
- {
- me->loadMaterial(model_in, filename, index);
- },
- me->getString("material_selection_title"),
- me->getString("material_selection_text"),
- material_list
- );
- }
-}
-
-void LLMaterialEditor::onSelectionChanged()
-{
- // Drop selection updates if we are waiting for
- // overrides to finish applying to not reset values
- // (might need a timeout)
- if (!mOverrideInProgress)
- {
- // mUpdateSignal triggers a lot per frame, breakwater
- mSelectionNeedsUpdate = true;
- }
-}
-
-void LLMaterialEditor::updateLive()
-{
- mSelectionNeedsUpdate = true;
- mOverrideInProgress = false;
-}
-
-void LLMaterialEditor::loadLive()
-{
- LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("live_material_editor");
- if (me)
- {
- me->mOverrideInProgress = false;
- me->setFromSelection();
-
- // Set up for selection changes updates
- if (!me->mSelectionUpdateSlot.connected())
- {
- me->mSelectionUpdateSlot = LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLMaterialEditor::onSelectionChanged, me));
- }
-
- me->openFloater();
- me->setFocus(true);
- }
-}
-
-namespace
-{
- // Which inventory to consult for item permissions
- enum class ItemSource
- {
- // Consult the permissions of the item in the object's inventory. If
- // the item is not present, then usage of the asset is allowed.
- OBJECT,
- // Consult the permissions of the item in the agent's inventory. If
- // the item is not present, then usage of the asset is not allowed.
- AGENT
- };
-
- class LLAssetIDMatchesWithPerms : public LLInventoryCollectFunctor
- {
- public:
- LLAssetIDMatchesWithPerms(const LLUUID& asset_id, const std::vector<PermissionBit>& ops) : mAssetID(asset_id), mOps(ops) {}
- virtual ~LLAssetIDMatchesWithPerms() {}
- bool operator()(LLInventoryCategory* cat, LLInventoryItem* item)
- {
- if (!item || item->getAssetUUID() != mAssetID)
- {
- return false;
- }
- LLPermissions item_permissions = item->getPermissions();
- for (PermissionBit op : mOps)
- {
- if (!gAgent.allowOperation(op, item_permissions, GP_OBJECT_MANIPULATE))
- {
- return false;
- }
- }
- return true;
- }
-
- protected:
- LLUUID mAssetID;
- std::vector<PermissionBit> mOps;
- };
-};
-
-// *NOTE: permissions_out includes user preferences for new item creation (LLFloaterPerms)
-bool can_use_objects_material(LLSelectedTEGetMatData& func, const std::vector<PermissionBit>& ops, const ItemSource item_source, LLPermissions& permissions_out, LLViewerInventoryItem*& item_out)
-{
- if (!LLMaterialEditor::capabilitiesAvailable())
- {
- return false;
- }
-
- // func.mIsOverride=true is used for the singleton material editor floater
- // associated with the build floater. This flag also excludes objects from
- // the selection that do not satisfy PERM_MODIFY.
- llassert(func.mIsOverride);
- LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/);
-
- if (item_source == ItemSource::AGENT)
- {
- func.mObjectId = LLUUID::null;
- }
- LLViewerObject* selected_object = func.mObject;
- if (!selected_object)
- {
- // LLSelectedTEGetMatData can fail if there are no selected faces
- // with materials, but we expect at least some object is selected.
- llassert(LLSelectMgr::getInstance()->getSelection()->getFirstObject());
- return false;
- }
- if (selected_object->isInventoryPending())
- {
- return false;
- }
- for (PermissionBit op : ops)
- {
- if (op == PERM_MODIFY && selected_object->isPermanentEnforced())
- {
- return false;
- }
- }
-
- // Look for the item to base permissions off of
- item_out = nullptr;
- const bool blank_material = func.mMaterialId == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID;
- if (!blank_material)
- {
- LLAssetIDMatchesWithPerms item_has_perms(func.mMaterialId, ops);
- if (item_source == ItemSource::OBJECT)
- {
- LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId);
- if (item && !item_has_perms(nullptr, item))
- {
- return false;
- }
- item_out = item;
- }
- else
- {
- llassert(item_source == ItemSource::AGENT);
-
- LLViewerInventoryCategory::cat_array_t cats;
- LLViewerInventoryItem::item_array_t items;
- gInventory.collectDescendentsIf(LLUUID::null,
- cats,
- items,
- // *NOTE: PBRPickerAgentListener will need
- // to be changed if checking the trash is
- // disabled
- LLInventoryModel::INCLUDE_TRASH,
- item_has_perms);
- if (items.empty())
- {
- return false;
- }
- item_out = items[0];
- }
- }
-
- LLPermissions item_permissions;
- if (item_out)
- {
- item_permissions = item_out->getPermissions();
- // Update flags for new owner
- if (!item_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true))
- {
- llassert(false);
- return false;
- }
- }
- else
- {
- item_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
- }
-
- // Use root object for permissions checking
- LLViewerObject* root_object = selected_object->getRootEdit();
- LLPermissions* object_permissions_p = LLSelectMgr::getInstance()->findObjectPermissions(root_object);
- LLPermissions object_permissions;
- if (object_permissions_p)
- {
- object_permissions.set(*object_permissions_p);
- for (PermissionBit op : ops)
- {
- if (!gAgent.allowOperation(op, object_permissions, GP_OBJECT_MANIPULATE))
- {
- return false;
- }
- }
- // Update flags for new owner
- if (!object_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true))
- {
- llassert(false);
- return false;
- }
- }
- else
- {
- object_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
- }
-
- LLPermissions floater_perm;
- floater_perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
- floater_perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Materials"));
- floater_perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Materials"));
- floater_perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Materials"));
-
- // *NOTE: A close inspection of LLPermissions::accumulate shows that
- // conflicting UUIDs will be unset. This is acceptable behavior for now.
- // The server will populate creator info based on the item creation method
- // used.
- // *NOTE: As far as I'm aware, there is currently no good way to preserve
- // creation history when there's no material item present. In that case,
- // the agent who saved the material will be considered the creator.
- // -Cosmic,2023-08-07
- if (item_source == ItemSource::AGENT)
- {
- llassert(blank_material || item_out); // See comment at ItemSource::AGENT definition
-
- permissions_out.set(item_permissions);
- }
- else
- {
- llassert(item_source == ItemSource::OBJECT);
-
- if (item_out)
- {
- permissions_out.set(item_permissions);
- }
- else
- {
- permissions_out.set(object_permissions);
- }
- }
- permissions_out.accumulate(floater_perm);
-
- return true;
-}
-
-bool LLMaterialEditor::canModifyObjectsMaterial()
-{
- LLSelectedTEGetMatData func(true);
- LLPermissions permissions;
- LLViewerInventoryItem* item_out;
- return can_use_objects_material(func, std::vector<PermissionBit>({PERM_MODIFY}), ItemSource::OBJECT, permissions, item_out);
-}
-
-bool LLMaterialEditor::canSaveObjectsMaterial()
-{
- LLSelectedTEGetMatData func(true);
- LLPermissions permissions;
- LLViewerInventoryItem* item_out;
- return can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item_out);
-}
-
-bool LLMaterialEditor::canClipboardObjectsMaterial()
-{
- if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() != 1)
- {
- return false;
- }
-
- struct LLSelectedTEGetNullMat : public LLSelectedTEFunctor
- {
- bool apply(LLViewerObject* objectp, S32 te_index)
- {
- return objectp->getRenderMaterialID(te_index).isNull();
- }
- } null_func;
-
- if (LLSelectMgr::getInstance()->getSelection()->applyToTEs(&null_func))
- {
- return true;
- }
-
- LLSelectedTEGetMatData func(true);
- LLPermissions permissions;
- LLViewerInventoryItem* item_out;
- return can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY, PERM_TRANSFER}), ItemSource::OBJECT, permissions, item_out);
-}
-
-void LLMaterialEditor::saveObjectsMaterialAs()
-{
- LLSelectedTEGetMatData func(true);
- LLPermissions permissions;
- LLViewerInventoryItem* item = nullptr;
- bool allowed = can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item);
- if (!allowed)
- {
- LL_WARNS("MaterialEditor") << "Failed to save GLTF material from object" << LL_ENDL;
- return;
- }
- const LLUUID item_id = item ? item->getUUID() : LLUUID::null;
- saveObjectsMaterialAs(func.mMaterial, func.mLocalMaterial, permissions, func.mObjectId, item_id);
-}
-
-
-void LLMaterialEditor::saveObjectsMaterialAs(const LLGLTFMaterial* render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id, const LLUUID& item_id)
-{
- if (local_material)
- {
- // This is a local material, reload it from file
- // so that user won't end up with grey textures
- // on next login.
- LLMaterialEditor::loadMaterialFromFile(local_material->getFilename(), local_material->getIndexInFile());
-
- LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor");
- if (me)
- {
- // don't use override material here, it has 'hacked ids'
- // and values, use end result, apply it on top of local.
- const LLColor4& base_color = render_material->mBaseColor;
- me->setBaseColor(LLColor3(base_color));
- me->setTransparency(base_color[VW]);
- me->setMetalnessFactor(render_material->mMetallicFactor);
- me->setRoughnessFactor(render_material->mRoughnessFactor);
- me->setEmissiveColor(render_material->mEmissiveColor);
- me->setDoubleSided(render_material->mDoubleSided);
- me->setAlphaMode(render_material->getAlphaMode());
- me->setAlphaCutoff(render_material->mAlphaCutoff);
-
- // most things like colors we can apply without verifying
- // but texture ids are going to be different from both, base and override
- // so only apply override id if there is actually a difference
- if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR])
- {
- me->setBaseColorId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]);
- me->childSetValue("base_color_upload_fee", me->getString("no_upload_fee_string"));
- }
- if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL])
- {
- me->setNormalId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]);
- me->childSetValue("normal_upload_fee", me->getString("no_upload_fee_string"));
- }
- if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS])
- {
- me->setMetallicRoughnessId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]);
- me->childSetValue("metallic_upload_fee", me->getString("no_upload_fee_string"));
- }
- if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE])
- {
- me->setEmissiveId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]);
- me->childSetValue("emissive_upload_fee", me->getString("no_upload_fee_string"));
- }
-
- // recalculate upload prices
- me->markChangesUnsaved(0);
- }
-
- return;
- }
-
- LLSD payload;
- if (render_material)
- {
- // Make a copy of the render material with unsupported transforms removed
- LLGLTFMaterial asset_material = *render_material;
- asset_material.sanitizeAssetMaterial();
- // Serialize the sanitized render material
- payload["data"] = asset_material.asJSON();
- }
- else
- {
- // Menu shouldn't allow this, but as a fallback
- // pick defaults from a blank material
- LLGLTFMaterial blank_mat;
- payload["data"] = blank_mat.asJSON();
- LL_WARNS() << "Got no material when trying to save material" << LL_ENDL;
- }
-
- LLSD args;
- args["DESC"] = LLTrans::getString("New Material");
-
- if (local_material)
- {
- LLPermissions local_permissions;
- local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
- LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, local_permissions));
- }
- else
- {
- llassert(object_id.isNull()); // Case for copying item from object inventory is no longer implemented
- LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, permissions));
- }
-}
-
-// static
-void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions)
-{
- S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
- if (0 != option)
- {
- return;
- }
-
- LLSD asset;
- asset["version"] = LLGLTFMaterial::ASSET_VERSION;
- asset["type"] = LLGLTFMaterial::ASSET_TYPE;
- // This is the string serialized from LLGLTFMaterial::asJSON
- asset["data"] = notification["payload"]["data"];
-
- std::ostringstream str;
- LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY);
-
- std::string new_name = response["message"].asString();
- LLInventoryObject::correctInventoryName(new_name);
- if (new_name.empty())
- {
- return;
- }
-
- createInventoryItem(str.str(), new_name, std::string(), permissions);
-}
-
-const void upload_bulk(const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter type);
-
-void LLMaterialEditor::loadMaterial(const tinygltf::Model &model_in, const std::string &filename, S32 index, bool open_floater)
-{
- if (index == model_in.materials.size())
- {
- // bulk upload all the things
- upload_bulk({ filename }, LLFilePicker::FFLOAD_MATERIAL);
- return;
- }
-
- if (model_in.materials.size() <= index)
- {
- return;
- }
- std::string folder = gDirUtilp->getDirName(filename);
-
- tinygltf::Material material_in = model_in.materials[index];
-
- tinygltf::Model model_out;
- model_out.asset.version = "2.0";
- model_out.materials.resize(1);
-
- // get base color texture
- LLPointer<LLImageRaw> base_color_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.baseColorTexture.index, mBaseColorName);
- // get normal map
- LLPointer<LLImageRaw> normal_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.normalTexture.index, mNormalName);
- // get metallic-roughness texture
- LLPointer<LLImageRaw> mr_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.metallicRoughnessTexture.index, mMetallicRoughnessName);
- // get emissive texture
- LLPointer<LLImageRaw> emissive_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.emissiveTexture.index, mEmissiveName);
- // get occlusion map if needed
- LLPointer<LLImageRaw> occlusion_img;
- if (material_in.occlusionTexture.index != material_in.pbrMetallicRoughness.metallicRoughnessTexture.index)
- {
- std::string tmp;
- occlusion_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.occlusionTexture.index, tmp);
- }
-
- LLTinyGLTFHelper::initFetchedTextures(material_in, base_color_img, normal_img, mr_img, emissive_img, occlusion_img,
- mBaseColorFetched, mNormalFetched, mMetallicRoughnessFetched, mEmissiveFetched);
- pack_textures(base_color_img, normal_img, mr_img, emissive_img, occlusion_img,
- mBaseColorJ2C, mNormalJ2C, mMetallicRoughnessJ2C, mEmissiveJ2C);
-
- LLUUID base_color_id;
- if (mBaseColorFetched.notNull())
- {
- mBaseColorFetched->forceToSaveRawImage(0, F32_MAX);
- base_color_id = mBaseColorFetched->getID();
-
- if (mBaseColorName.empty())
- {
- mBaseColorName = MATERIAL_BASE_COLOR_DEFAULT_NAME;
- }
- }
-
- LLUUID normal_id;
- if (mNormalFetched.notNull())
- {
- mNormalFetched->forceToSaveRawImage(0, F32_MAX);
- normal_id = mNormalFetched->getID();
-
- if (mNormalName.empty())
- {
- mNormalName = MATERIAL_NORMAL_DEFAULT_NAME;
- }
- }
-
- LLUUID mr_id;
- if (mMetallicRoughnessFetched.notNull())
- {
- mMetallicRoughnessFetched->forceToSaveRawImage(0, F32_MAX);
- mr_id = mMetallicRoughnessFetched->getID();
-
- if (mMetallicRoughnessName.empty())
- {
- mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME;
- }
- }
-
- LLUUID emissive_id;
- if (mEmissiveFetched.notNull())
- {
- mEmissiveFetched->forceToSaveRawImage(0, F32_MAX);
- emissive_id = mEmissiveFetched->getID();
-
- if (mEmissiveName.empty())
- {
- mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME;
- }
- }
-
- setBaseColorId(base_color_id);
- setBaseColorUploadId(base_color_id);
- setMetallicRoughnessId(mr_id);
- setMetallicRoughnessUploadId(mr_id);
- setEmissiveId(emissive_id);
- setEmissiveUploadId(emissive_id);
- setNormalId(normal_id);
- setNormalUploadId(normal_id);
-
- setFromGltfModel(model_in, index);
-
- setFromGltfMetaData(filename, model_in, index);
-
- if (getDoubleSided())
- {
- // SL-19392 Double sided materials double the number of pixels that must be rasterized,
- // and a great many tools that export GLTF simply leave double sided enabled whether
- // or not it is necessary.
- LL_DEBUGS("MaterialEditor") << "Defaulting Double Sided to disabled on import" << LL_ENDL;
- setDoubleSided(false);
- }
-
- markChangesUnsaved(U32_MAX);
-
- if (open_floater)
- {
- openFloater(getKey());
- setFocus(true);
- setCanSave(true);
- setCanSaveAs(true);
-
- applyToSelection();
- }
-}
-
-bool LLMaterialEditor::setFromGltfModel(const tinygltf::Model& model, S32 index, bool set_textures)
-{
- if (model.materials.size() > index)
- {
- const tinygltf::Material& material_in = model.materials[index];
-
- if (set_textures)
- {
- S32 index;
- LLUUID id;
-
- // get base color texture
- index = material_in.pbrMetallicRoughness.baseColorTexture.index;
- if (index >= 0)
- {
- id.set(model.images[index].uri);
- setBaseColorId(id);
- }
- else
- {
- setBaseColorId(LLUUID::null);
- }
-
- // get normal map
- index = material_in.normalTexture.index;
- if (index >= 0)
- {
- id.set(model.images[index].uri);
- setNormalId(id);
- }
- else
- {
- setNormalId(LLUUID::null);
- }
-
- // get metallic-roughness texture
- index = material_in.pbrMetallicRoughness.metallicRoughnessTexture.index;
- if (index >= 0)
- {
- id.set(model.images[index].uri);
- setMetallicRoughnessId(id);
- }
- else
- {
- setMetallicRoughnessId(LLUUID::null);
- }
-
- // get emissive texture
- index = material_in.emissiveTexture.index;
- if (index >= 0)
- {
- id.set(model.images[index].uri);
- setEmissiveId(id);
- }
- else
- {
- setEmissiveId(LLUUID::null);
- }
- }
-
- setAlphaMode(material_in.alphaMode);
- setAlphaCutoff(material_in.alphaCutoff);
-
- setBaseColor(LLTinyGLTFHelper::getColor(material_in.pbrMetallicRoughness.baseColorFactor));
- setEmissiveColor(LLTinyGLTFHelper::getColor(material_in.emissiveFactor));
-
- setMetalnessFactor(material_in.pbrMetallicRoughness.metallicFactor);
- setRoughnessFactor(material_in.pbrMetallicRoughness.roughnessFactor);
-
- setDoubleSided(material_in.doubleSided);
- }
-
- return true;
-}
-
-/**
- * Build a texture name from the contents of the (in tinyGLFT parlance)
- * Image URI. This often is filepath to the original image on the users'
- * local file system.
- */
-const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, const std::string texture_type)
-{
- // getBaseFileName() works differently on each platform and file patchs
- // can contain both types of delimiter so unify them then extract the
- // base name (no path or extension)
- std::replace(image_uri.begin(), image_uri.end(), '\\', gDirUtilp->getDirDelimiter()[0]);
- std::replace(image_uri.begin(), image_uri.end(), '/', gDirUtilp->getDirDelimiter()[0]);
- const bool strip_extension = true;
- std::string stripped_uri = gDirUtilp->getBaseFileName(image_uri, strip_extension);
-
- // sometimes they can be really long and unwieldy - 64 chars is enough for anyone :)
- const int max_texture_name_length = 64;
- if (stripped_uri.length() > max_texture_name_length)
- {
- stripped_uri = stripped_uri.substr(0, max_texture_name_length - 1);
- }
-
- // We intend to append the type of texture (base color, emissive etc.) to the
- // name of the texture but sometimes the creator already did that. To try
- // to avoid repeats (not perfect), we look for the texture type in the name
- // and if we find it, do not append the type, later on. One way this fails
- // (and it's fine for now) is I see some texture/image uris have a name like
- // "metallic roughness" and of course, that doesn't match our predefined
- // name "metallicroughness" - consider fix later..
- bool name_includes_type = false;
- std::string stripped_uri_lower = stripped_uri;
- LLStringUtil::toLower(stripped_uri_lower);
- stripped_uri_lower.erase(std::remove_if(stripped_uri_lower.begin(), stripped_uri_lower.end(), isspace), stripped_uri_lower.end());
- std::string texture_type_lower = texture_type;
- LLStringUtil::toLower(texture_type_lower);
- texture_type_lower.erase(std::remove_if(texture_type_lower.begin(), texture_type_lower.end(), isspace), texture_type_lower.end());
- if (stripped_uri_lower.find(texture_type_lower) != std::string::npos)
- {
- name_includes_type = true;
- }
-
- // uri doesn't include the type at all
- if (!name_includes_type)
- {
- // uri doesn't include the type and the uri is not empty
- // so we can include everything
- if (stripped_uri.length() > 0)
- {
- // example "DamagedHelmet: base layer"
- return STRINGIZE(
- mMaterialNameShort <<
- ": " <<
- stripped_uri <<
- " (" <<
- texture_type <<
- ")"
- );
- }
- else
- // uri doesn't include the type (because the uri is empty)
- // so we must reorganize the string a bit to include the name
- // and an explicit name type
- {
- // example "DamagedHelmet: (Emissive)"
- return STRINGIZE(
- mMaterialNameShort <<
- " (" <<
- texture_type <<
- ")"
- );
- }
- }
- else
- // uri includes the type so just use it directly with the
- // name of the material
- {
- return STRINGIZE(
- // example: AlienBust: normal_layer
- mMaterialNameShort <<
- ": " <<
- stripped_uri
- );
- }
-}
-
-/**
- * Update the metadata for the material based on what we find in the loaded
- * file (along with some assumptions and interpretations...). Fields include
- * the name of the material, a material description and the names of the
- * composite textures.
- */
-void LLMaterialEditor::setFromGltfMetaData(const std::string& filename, const tinygltf::Model& model, S32 index)
-{
- // Use the name (without any path/extension) of the file that was
- // uploaded as the base of the material name. Then if the name of the
- // scene is present and not blank, append that and use the result as
- // the name of the material. This is a first pass at creating a
- // naming scheme that is useful to real content creators and hopefully
- // avoid 500 materials in your inventory called "scene" or "Default"
- const bool strip_extension = true;
- std::string base_filename = gDirUtilp->getBaseFileName(filename, strip_extension);
-
- // Extract the name of the scene. Note it is often blank or some very
- // generic name like "Scene" or "Default" so using this in the name
- // is less useful than you might imagine.
- std::string material_name;
- if (model.materials.size() > index && !model.materials[index].name.empty())
- {
- material_name = model.materials[index].name;
- }
- else if (model.scenes.size() > 0)
- {
- const tinygltf::Scene& scene_in = model.scenes[0];
- if (scene_in.name.length())
- {
- material_name = scene_in.name;
- }
- else
- {
- // scene name is empty so no point using it
- }
- }
- else
- {
- // scene name isn't present so no point using it
- }
-
- // If we have a valid material or scene name, use it to build the short and
- // long versions of the material name. The long version is used
- // as you might expect, for the material name. The short version is
- // used as part of the image/texture name - the theory is that will
- // allow content creators to track the material and the corresponding
- // textures
- if (material_name.length())
- {
- mMaterialNameShort = base_filename;
-
- mMaterialName = STRINGIZE(
- base_filename <<
- " " <<
- "(" <<
- material_name <<
- ")"
- );
- }
- else
- // otherwise, just use the trimmed filename as is
- {
- mMaterialNameShort = base_filename;
- mMaterialName = base_filename;
- }
-
- // sanitize the material name so that it's compatible with the inventory
- LLInventoryObject::correctInventoryName(mMaterialName);
- LLInventoryObject::correctInventoryName(mMaterialNameShort);
-
- // We also set the title of the floater to match the
- // name of the material
- setTitle(mMaterialName);
-
- /**
- * Extract / derive the names of each composite texture. For each, the
- * index is used to to determine which of the "Images" is used. If the index
- * is -1 then that texture type is not present in the material (Seems to be
- * quite common that a material is missing 1 or more types of texture)
- */
- if (model.materials.size() > index)
- {
- const tinygltf::Material& first_material = model.materials[index];
-
- mBaseColorName = MATERIAL_BASE_COLOR_DEFAULT_NAME;
- // note: unlike the other textures, base color doesn't have its own entry
- // in the tinyGLTF Material struct. Rather, it is taken from a
- // sub-texture in the pbrMetallicRoughness member
- int index = first_material.pbrMetallicRoughness.baseColorTexture.index;
- if (index > -1 && index < model.images.size())
- {
- // sanitize the name we decide to use for each texture
- std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_BASE_COLOR_DEFAULT_NAME);
- LLInventoryObject::correctInventoryName(texture_name);
- mBaseColorName = texture_name;
- }
-
- mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME;
- index = first_material.emissiveTexture.index;
- if (index > -1 && index < model.images.size())
- {
- std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_EMISSIVE_DEFAULT_NAME);
- LLInventoryObject::correctInventoryName(texture_name);
- mEmissiveName = texture_name;
- }
-
- mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME;
- index = first_material.pbrMetallicRoughness.metallicRoughnessTexture.index;
- if (index > -1 && index < model.images.size())
- {
- std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_METALLIC_DEFAULT_NAME);
- LLInventoryObject::correctInventoryName(texture_name);
- mMetallicRoughnessName = texture_name;
- }
-
- mNormalName = MATERIAL_NORMAL_DEFAULT_NAME;
- index = first_material.normalTexture.index;
- if (index > -1 && index < model.images.size())
- {
- std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_NORMAL_DEFAULT_NAME);
- LLInventoryObject::correctInventoryName(texture_name);
- mNormalName = texture_name;
- }
- }
-}
-
-void LLMaterialEditor::importMaterial()
-{
- LLFilePickerReplyThread::startPicker(
- [](const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter)
- {
- if (LLAppViewer::instance()->quitRequested())
- {
- return;
- }
- if (filenames.size() > 0)
- {
- LLMaterialEditor::loadMaterialFromFile(filenames[0], -1);
- }
- },
- LLFilePicker::FFLOAD_MATERIAL,
- true);
-}
-
-class LLRenderMaterialFunctor : public LLSelectedTEFunctor
-{
-public:
- LLRenderMaterialFunctor(const LLUUID &id)
- : mMatId(id)
- {
- }
-
- bool apply(LLViewerObject* objectp, S32 te) override
- {
- if (objectp && objectp->permModify() && objectp->getVolume())
- {
- LLVOVolume* vobjp = (LLVOVolume*)objectp;
- vobjp->setRenderMaterialID(te, mMatId, false /*preview only*/);
- vobjp->updateTEMaterialTextures(te);
- }
- return true;
- }
-private:
- LLUUID mMatId;
-};
-
-class LLRenderMaterialOverrideFunctor : public LLSelectedNodeFunctor
-{
-public:
- LLRenderMaterialOverrideFunctor(
- LLMaterialEditor * me,
- const LLUUID &report_on_object_id,
- S32 report_on_te)
- : mEditor(me)
- , mSuccess(false)
- , mObjectId(report_on_object_id)
- , mObjectTE(report_on_te)
- {
- }
-
- virtual bool apply(LLSelectNode* nodep) override
- {
- LLViewerObject* objectp = nodep->getObject();
- if (!objectp || !objectp->permModify() || !objectp->getVolume())
- {
- return false;
- }
- S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces
-
- // post override from given object and te to the simulator
- // requestData should have:
- // object_id - UUID of LLViewerObject
- // side - S32 index of texture entry
- // gltf_json - String of GLTF json for override data
-
- for (S32 te = 0; te < num_tes; ++te)
- {
- if (!nodep->isTESelected(te))
- {
- continue;
- }
-
- // Get material from object
- // Selection can cover multiple objects, and live editor is
- // supposed to overwrite changed values only
- LLTextureEntry* tep = objectp->getTE(te);
-
- if (tep->getGLTFMaterial() == nullptr)
- {
- // overrides are not supposed to work or apply if
- // there is no base material to work from
- continue;
- }
-
- LLPointer<LLGLTFMaterial> material = tep->getGLTFMaterialOverride();
- // make a copy to not invalidate existing
- // material for multiple objects
- if (material.isNull())
- {
- // Start with a material override which does not make any changes
- material = new LLGLTFMaterial();
- }
- else
- {
- material = new LLGLTFMaterial(*material);
- }
-
- U32 changed_flags = mEditor->getUnsavedChangesFlags();
- U32 reverted_flags = mEditor->getRevertedChangesFlags();
-
- LLPointer<LLGLTFMaterial> revert_mat;
- if (nodep->mSavedGLTFOverrideMaterials.size() > te)
- {
- if (nodep->mSavedGLTFOverrideMaterials[te].notNull())
- {
- revert_mat = nodep->mSavedGLTFOverrideMaterials[te];
- }
- else
- {
- // mSavedGLTFOverrideMaterials[te] being present but null
- // means we need to use a default value
- revert_mat = new LLGLTFMaterial();
- }
- }
- // else can not revert at all
-
- // Override object's values with values from editor where appropriate
- if (changed_flags & MATERIAL_BASE_COLOR_DIRTY)
- {
- material->setBaseColorFactor(mEditor->getBaseColor(), true);
- }
- else if ((reverted_flags & MATERIAL_BASE_COLOR_DIRTY) && revert_mat.notNull())
- {
- material->setBaseColorFactor(revert_mat->mBaseColor, false);
- }
-
- if (changed_flags & MATERIAL_BASE_COLOR_TEX_DIRTY)
- {
- material->setBaseColorId(mEditor->getBaseColorId(), true);
- LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_BASE_COLOR_TEX_DIRTY);
- if (tracking_id.notNull())
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
- }
- }
- else if ((reverted_flags & MATERIAL_BASE_COLOR_TEX_DIRTY) && revert_mat.notNull())
- {
- material->setBaseColorId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR], false);
- LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_BASE_COLOR_TEX_DIRTY);
- if (tracking_id.notNull())
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
- }
- }
-
- if (changed_flags & MATERIAL_NORMAL_TEX_DIRTY)
- {
- material->setNormalId(mEditor->getNormalId(), true);
- LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_NORMAL_TEX_DIRTY);
- if (tracking_id.notNull())
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
- }
- }
- else if ((reverted_flags & MATERIAL_NORMAL_TEX_DIRTY) && revert_mat.notNull())
- {
- material->setNormalId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL], false);
- LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_NORMAL_TEX_DIRTY);
- if (tracking_id.notNull())
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
- }
- }
-
- if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY)
- {
- material->setOcclusionRoughnessMetallicId(mEditor->getMetallicRoughnessId(), true);
- LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY);
- if (tracking_id.notNull())
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
- }
- }
- else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY) && revert_mat.notNull())
- {
- material->setOcclusionRoughnessMetallicId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS], false);
- LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY);
- if (tracking_id.notNull())
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
- }
- }
-
- if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY)
- {
- material->setMetallicFactor(mEditor->getMetalnessFactor(), true);
- }
- else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY) && revert_mat.notNull())
- {
- material->setMetallicFactor(revert_mat->mMetallicFactor, false);
- }
-
- if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY)
- {
- material->setRoughnessFactor(mEditor->getRoughnessFactor(), true);
- }
- else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY) && revert_mat.notNull())
- {
- material->setRoughnessFactor(revert_mat->mRoughnessFactor, false);
- }
-
- if (changed_flags & MATERIAL_EMISIVE_COLOR_DIRTY)
- {
- material->setEmissiveColorFactor(LLColor3(mEditor->getEmissiveColor()), true);
- }
- else if ((reverted_flags & MATERIAL_EMISIVE_COLOR_DIRTY) && revert_mat.notNull())
- {
- material->setEmissiveColorFactor(revert_mat->mEmissiveColor, false);
- }
-
- if (changed_flags & MATERIAL_EMISIVE_TEX_DIRTY)
- {
- material->setEmissiveId(mEditor->getEmissiveId(), true);
- LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_EMISIVE_TEX_DIRTY);
- if (tracking_id.notNull())
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
- }
- }
- else if ((reverted_flags & MATERIAL_EMISIVE_TEX_DIRTY) && revert_mat.notNull())
- {
- material->setEmissiveId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE], false);
- LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_EMISIVE_TEX_DIRTY);
- if (tracking_id.notNull())
- {
- LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
- }
- }
-
- if (changed_flags & MATERIAL_DOUBLE_SIDED_DIRTY)
- {
- material->setDoubleSided(mEditor->getDoubleSided(), true);
- }
- else if ((reverted_flags & MATERIAL_DOUBLE_SIDED_DIRTY) && revert_mat.notNull())
- {
- material->setDoubleSided(revert_mat->mDoubleSided, false);
- }
-
- if (changed_flags & MATERIAL_ALPHA_MODE_DIRTY)
- {
- material->setAlphaMode(mEditor->getAlphaMode(), true);
- }
- else if ((reverted_flags & MATERIAL_ALPHA_MODE_DIRTY) && revert_mat.notNull())
- {
- material->setAlphaMode(revert_mat->mAlphaMode, false);
- }
-
- if (changed_flags & MATERIAL_ALPHA_CUTOFF_DIRTY)
- {
- material->setAlphaCutoff(mEditor->getAlphaCutoff(), true);
- }
- else if ((reverted_flags & MATERIAL_ALPHA_CUTOFF_DIRTY) && revert_mat.notNull())
- {
- material->setAlphaCutoff(revert_mat->mAlphaCutoff, false);
- }
-
- if (mObjectTE == te
- && mObjectId == objectp->getID())
- {
- mSuccess = true;
- }
- LLGLTFMaterialList::queueModify(objectp, te, material);
- }
- return true;
- }
-
- static void modifyCallback(bool success)
- {
- if (!success)
- {
- // something went wrong update selection
- LLMaterialEditor::updateLive();
- }
- // else we will get updateLive() from panel face
- }
-
- bool getResult() { return mSuccess; }
-
-private:
- LLMaterialEditor * mEditor;
- LLUUID mObjectId;
- S32 mObjectTE;
- bool mSuccess;
-};
-
-void LLMaterialEditor::applyToSelection()
-{
- if (!mIsOverride)
- {
- // Only apply if working with 'live' materials
- // Might need a better way to distinguish 'live' mode.
- // But only one live edit is supposed to work at a time
- // as a pair to tools floater.
- return;
- }
-
- std::string url = gAgent.getRegionCapability("ModifyMaterialParams");
- if (!url.empty())
- {
- // Don't send data if there is nothing to send.
- // Some UI elements will cause multiple commits,
- // like spin ctrls on click and on down
- if (mUnsavedChanges != 0 || mRevertedChanges != 0)
- {
- mOverrideInProgress = true;
- LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection();
- LLRenderMaterialOverrideFunctor override_func(this, mOverrideObjectId, mOverrideObjectTE);
- selected_objects->applyToNodes(&override_func);
-
- void(*done_callback)(bool) = LLRenderMaterialOverrideFunctor::modifyCallback;
-
- LLGLTFMaterialList::flushUpdates(done_callback);
-
- if (!override_func.getResult())
- {
- // OverrideFunctor didn't find expected object or face
- mOverrideInProgress = false;
- }
-
- // we posted all changes
- mUnsavedChanges = 0;
- mRevertedChanges = 0;
- }
- }
- else
- {
- LL_WARNS("MaterialEditor") << "Not connected to materials capable region, missing ModifyMaterialParams cap" << LL_ENDL;
-
- // Fallback local preview. Will be removed once override systems is finished and new cap is deployed everywhere.
- LLPointer<LLFetchedGLTFMaterial> mat = new LLFetchedGLTFMaterial();
- getGLTFMaterial(mat);
- static const LLUUID placeholder("984e183e-7811-4b05-a502-d79c6f978a98");
- gGLTFMaterialList.addMaterial(placeholder, mat);
- LLRenderMaterialFunctor mat_func(placeholder);
- LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection();
- selected_objects->applyToTEs(&mat_func);
- }
-}
-
-// Get a dump of the json representation of the current state of the editor UI
-// in GLTF format, excluding transforms as they are not supported in material
-// assets. (See also LLGLTFMaterial::sanitizeAssetMaterial())
-void LLMaterialEditor::getGLTFMaterial(LLGLTFMaterial* mat)
-{
- mat->mBaseColor = getBaseColor();
- mat->mBaseColor.mV[3] = getTransparency();
- mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] = getBaseColorId();
-
- mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] = getNormalId();
-
- mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] = getMetallicRoughnessId();
- mat->mMetallicFactor = getMetalnessFactor();
- mat->mRoughnessFactor = getRoughnessFactor();
-
- mat->mEmissiveColor = getEmissiveColor();
- mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] = getEmissiveId();
-
- mat->mDoubleSided = getDoubleSided();
- mat->setAlphaMode(getAlphaMode());
- mat->mAlphaCutoff = getAlphaCutoff();
-}
-
-void LLMaterialEditor::setFromGLTFMaterial(LLGLTFMaterial* mat)
-{
- setBaseColor(mat->mBaseColor);
- setBaseColorId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]);
- setNormalId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]);
-
- setMetallicRoughnessId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]);
- setMetalnessFactor(mat->mMetallicFactor);
- setRoughnessFactor(mat->mRoughnessFactor);
-
- setEmissiveColor(mat->mEmissiveColor);
- setEmissiveId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]);
-
- setDoubleSided(mat->mDoubleSided);
- setAlphaMode(mat->getAlphaMode());
- setAlphaCutoff(mat->mAlphaCutoff);
-
- if (mat->hasLocalTextures())
- {
- for (LLGLTFMaterial::local_tex_map_t::value_type &val : mat->mTrackingIdToLocalTexture)
- {
- LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(val.first);
- if (val.second != world_id)
- {
- LL_WARNS() << "world id mismatch" << LL_ENDL;
- }
- if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR])
- {
- subscribeToLocalTexture(MATERIAL_BASE_COLOR_TEX_DIRTY, val.first);
- }
- if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS])
- {
- subscribeToLocalTexture(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY, val.first);
- }
- if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE])
- {
- subscribeToLocalTexture(MATERIAL_EMISIVE_TEX_DIRTY, val.first);
- }
- if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL])
- {
- subscribeToLocalTexture(MATERIAL_NORMAL_TEX_DIRTY, val.first);
- }
- }
- }
-}
-
-bool LLMaterialEditor::setFromSelection()
-{
- LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection();
- LLSelectedTEGetMatData func(mIsOverride);
-
- selected_objects->applyToTEs(&func);
- mHasSelection = !selected_objects->isEmpty();
- mSelectionNeedsUpdate = false;
-
- if (func.mMaterial.notNull())
- {
- setFromGLTFMaterial(func.mMaterial);
- LLViewerObject* selected_object = func.mObject;
- const LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId);
- const bool allow_modify = !item || canModify(selected_object, item);
- setEnableEditing(allow_modify);
-
- // todo: apply local texture data to all materials in selection
- }
- else
- {
- // pick defaults from a blank material;
- LLGLTFMaterial blank_mat;
- setFromGLTFMaterial(&blank_mat);
- if (mIsOverride)
- {
- setEnableEditing(false);
- }
- }
-
- if (mIsOverride)
- {
- mBaseColorTextureCtrl->setTentative(!func.mIdenticalTexColor);
- mMetallicTextureCtrl->setTentative(!func.mIdenticalTexMetal);
- mEmissiveTextureCtrl->setTentative(!func.mIdenticalTexEmissive);
- mNormalTextureCtrl->setTentative(!func.mIdenticalTexNormal);
-
- // Memorize selection data for filtering further updates
- mOverrideObjectId = func.mObjectId;
- mOverrideObjectTE = func.mObjectTE;
-
- // Ovverdired might have been updated,
- // refresh state of local textures in overrides
- //
- // Todo: this probably shouldn't be here, but in localbitmap,
- // subscried to all material overrides if we want copied
- // objects to get properly updated as well
- LLSelectedTEUpdateOverrides local_tex_func(this);
- selected_objects->applyToNodes(&local_tex_func);
- }
-
- return func.mMaterial.notNull();
-}
-
-
-void LLMaterialEditor::loadAsset()
-{
- const LLInventoryItem* item;
- if (mNotecardInventoryID.notNull())
- {
- item = mAuxItem.get();
- }
- else
- {
- item = getItem();
- }
-
- bool fail = false;
-
- if (item)
- {
- LLPermissions perm(item->getPermissions());
- bool allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE);
- bool allow_modify = canModify(mObjectUUID, item);
- bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
-
- setCanSaveAs(allow_copy);
- setMaterialName(item->getName());
-
- {
- mAssetID = item->getAssetUUID();
-
- if (mAssetID.isNull())
- {
- mAssetStatus = PREVIEW_ASSET_LOADED;
- loadDefaults();
- resetUnsavedChanges();
- setEnableEditing(allow_modify && !source_library);
- }
- else
- {
- LLHost source_sim = LLHost();
- LLSD* user_data = new LLSD();
-
- if (mNotecardInventoryID.notNull())
- {
- user_data->with("objectid", mNotecardObjectID).with("notecardid", mNotecardInventoryID);
- }
- else if (mObjectUUID.notNull())
- {
- LLViewerObject* objectp = gObjectList.findObject(mObjectUUID);
- if (objectp && objectp->getRegion())
- {
- source_sim = objectp->getRegion()->getHost();
- }
- else
- {
- // The object that we're trying to look at disappeared, bail.
- LL_WARNS("MaterialEditor") << "Can't find object " << mObjectUUID << " associated with material." << LL_ENDL;
- mAssetID.setNull();
- mAssetStatus = PREVIEW_ASSET_LOADED;
- resetUnsavedChanges();
- setEnableEditing(allow_modify && !source_library);
- return;
- }
- user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID);
- }
- else
- {
- user_data = new LLSD(mItemUUID);
- }
-
- setEnableEditing(false); // wait for it to load
-
- mAssetStatus = PREVIEW_ASSET_LOADING;
-
- // May callback immediately
- gAssetStorage->getAssetData(item->getAssetUUID(),
- LLAssetType::AT_MATERIAL,
- &onLoadComplete,
- (void*)user_data,
- true);
- }
- }
- }
- else if (mObjectUUID.notNull() && mItemUUID.notNull())
- {
- LLViewerObject* objectp = gObjectList.findObject(mObjectUUID);
- if (objectp && (objectp->isInventoryPending() || objectp->isInventoryDirty()))
- {
- // It's a material in object's inventory and we failed to get it because inventory is not up to date.
- // Subscribe for callback and retry at inventoryChanged()
- registerVOInventoryListener(objectp, NULL); //removes previous listener
-
- if (objectp->isInventoryDirty())
- {
- objectp->requestInventory();
- }
- }
- else
- {
- fail = true;
- }
- }
- else
- {
- fail = true;
- }
-
- if (fail)
- {
- /*editor->setText(LLStringUtil::null);
- editor->makePristine();
- editor->setEnabled(true);*/
- // Don't set asset status here; we may not have set the item id yet
- // (e.g. when this gets called initially)
- //mAssetStatus = PREVIEW_ASSET_LOADED;
- }
-}
-
-// static
-void LLMaterialEditor::onLoadComplete(const LLUUID& asset_uuid,
- LLAssetType::EType type,
- void* user_data, S32 status, LLExtStat ext_status)
-{
- LLSD* floater_key = (LLSD*)user_data;
- LL_DEBUGS("MaterialEditor") << "loading " << asset_uuid << " for " << *floater_key << LL_ENDL;
- LLMaterialEditor* editor = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", *floater_key);
- if (editor)
- {
- if (asset_uuid != editor->mAssetID)
- {
- LL_WARNS("MaterialEditor") << "Asset id mismatch, expected: " << editor->mAssetID << " got: " << asset_uuid << LL_ENDL;
- }
- if (0 == status)
- {
- LLFileSystem file(asset_uuid, type, LLFileSystem::READ);
-
- S32 file_length = file.getSize();
-
- std::vector<char> buffer(file_length + 1);
- file.read((U8*)&buffer[0], file_length);
-
- editor->decodeAsset(buffer);
-
- bool allow_modify = editor->canModify(editor->mObjectUUID, editor->getItem());
- bool source_library = editor->mObjectUUID.isNull() && gInventory.isObjectDescendentOf(editor->mItemUUID, gInventory.getLibraryRootFolderID());
- editor->setEnableEditing(allow_modify && !source_library);
- editor->resetUnsavedChanges();
- editor->mAssetStatus = PREVIEW_ASSET_LOADED;
- editor->setEnabled(true); // ready for use
- }
- else
- {
- if (LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ||
- LL_ERR_FILE_EMPTY == status)
- {
- LLNotificationsUtil::add("MaterialMissing");
- }
- else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status)
- {
- // Not supposed to happen?
- LL_WARNS("MaterialEditor") << "No permission to view material " << asset_uuid << LL_ENDL;
- LLNotificationsUtil::add("MaterialNoPermissions");
- }
- else
- {
- LLNotificationsUtil::add("UnableToLoadMaterial");
- }
- editor->setEnableEditing(false);
-
- LL_WARNS("MaterialEditor") << "Problem loading material: " << status << LL_ENDL;
- editor->mAssetStatus = PREVIEW_ASSET_ERROR;
- }
- }
- else
- {
- LL_DEBUGS("MaterialEditor") << "Floater " << *floater_key << " does not exist." << LL_ENDL;
- }
- delete floater_key;
-}
-
-void LLMaterialEditor::inventoryChanged(LLViewerObject* object,
- LLInventoryObject::object_list_t* inventory,
- S32 serial_num,
- void* user_data)
-{
- removeVOInventoryListener();
- loadAsset();
-}
-
-
-void LLMaterialEditor::saveTexture(LLImageJ2C* img, const std::string& name, const LLUUID& asset_id, upload_callback_f cb)
-{
- LLImageDataSharedLock lock(img);
-
- if (asset_id.isNull()
- || img == nullptr
- || img->getDataSize() == 0)
- {
- return;
- }
-
- // copy image bytes into string
- std::string buffer;
- buffer.assign((const char*) img->getData(), img->getDataSize());
-
- U32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost();
-
- LLSD key = getKey();
- std::function<bool(LLUUID itemId, LLSD response, std::string reason)> failed_upload([key](LLUUID assetId, LLSD response, std::string reason)
- {
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", key);
- if (me)
- {
- me->setFailedToUploadTexture();
- }
- return true; // handled
- });
-
- LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLNewBufferedResourceUploadInfo>(
- buffer,
- asset_id,
- name,
- name,
- 0,
- LLFolderType::FT_TEXTURE,
- LLInventoryType::IT_TEXTURE,
- LLAssetType::AT_TEXTURE,
- LLFloaterPerms::getNextOwnerPerms("Uploads"),
- LLFloaterPerms::getGroupPerms("Uploads"),
- LLFloaterPerms::getEveryonePerms("Uploads"),
- expected_upload_cost,
- false,
- cb,
- failed_upload));
-
- upload_new_resource(uploadInfo);
-}
-
-void LLMaterialEditor::setFailedToUploadTexture()
-{
- mUploadingTexturesFailure = true;
- mUploadingTexturesCount--;
- if (mUploadingTexturesCount == 0)
- {
- setEnabled(true);
- }
-}
-
-S32 LLMaterialEditor::saveTextures()
-{
- mUploadingTexturesFailure = false; // not supposed to get here if already uploading
-
- S32 work_count = 0;
- LLSD key = getKey(); // must be locally declared for lambda's capture to work
- if (mBaseColorTextureUploadId == getBaseColorId() && mBaseColorTextureUploadId.notNull())
- {
- mUploadingTexturesCount++;
- work_count++;
-
- // For ease of inventory management, we prepend the material name.
- std::string name = mMaterialName + ": " + mBaseColorName;
-
- saveTexture(mBaseColorJ2C, name, mBaseColorTextureUploadId, [key](LLUUID newAssetId, LLSD response)
- {
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", key);
- if (me)
- {
- if (response["success"].asBoolean())
- {
- me->setBaseColorId(newAssetId);
-
- // discard upload buffers once texture have been saved
- me->mBaseColorJ2C = nullptr;
- me->mBaseColorFetched = nullptr;
- me->mBaseColorTextureUploadId.setNull();
-
- me->mUploadingTexturesCount--;
-
- if (!me->mUploadingTexturesFailure)
- {
- // try saving
- me->saveIfNeeded();
- }
- else if (me->mUploadingTexturesCount == 0)
- {
- me->setEnabled(true);
- }
- }
- else
- {
- // stop upload if possible, unblock and let user decide
- me->setFailedToUploadTexture();
- }
- }
- });
- }
- if (mNormalTextureUploadId == getNormalId() && mNormalTextureUploadId.notNull())
- {
- mUploadingTexturesCount++;
- work_count++;
-
- // For ease of inventory management, we prepend the material name.
- std::string name = mMaterialName + ": " + mNormalName;
-
- saveTexture(mNormalJ2C, name, mNormalTextureUploadId, [key](LLUUID newAssetId, LLSD response)
- {
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", key);
- if (me)
- {
- if (response["success"].asBoolean())
- {
- me->setNormalId(newAssetId);
-
- // discard upload buffers once texture have been saved
- me->mNormalJ2C = nullptr;
- me->mNormalFetched = nullptr;
- me->mNormalTextureUploadId.setNull();
-
- me->mUploadingTexturesCount--;
-
- if (!me->mUploadingTexturesFailure)
- {
- // try saving
- me->saveIfNeeded();
- }
- else if (me->mUploadingTexturesCount == 0)
- {
- me->setEnabled(true);
- }
- }
- else
- {
- // stop upload if possible, unblock and let user decide
- me->setFailedToUploadTexture();
- }
- }
- });
- }
- if (mMetallicTextureUploadId == getMetallicRoughnessId() && mMetallicTextureUploadId.notNull())
- {
- mUploadingTexturesCount++;
- work_count++;
-
- // For ease of inventory management, we prepend the material name.
- std::string name = mMaterialName + ": " + mMetallicRoughnessName;
-
- saveTexture(mMetallicRoughnessJ2C, name, mMetallicTextureUploadId, [key](LLUUID newAssetId, LLSD response)
- {
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", key);
- if (me)
- {
- if (response["success"].asBoolean())
- {
- me->setMetallicRoughnessId(newAssetId);
-
- // discard upload buffers once texture have been saved
- me->mMetallicRoughnessJ2C = nullptr;
- me->mMetallicRoughnessFetched = nullptr;
- me->mMetallicTextureUploadId.setNull();
-
- me->mUploadingTexturesCount--;
-
- if (!me->mUploadingTexturesFailure)
- {
- // try saving
- me->saveIfNeeded();
- }
- else if (me->mUploadingTexturesCount == 0)
- {
- me->setEnabled(true);
- }
- }
- else
- {
- // stop upload if possible, unblock and let user decide
- me->setFailedToUploadTexture();
- }
- }
- });
- }
-
- if (mEmissiveTextureUploadId == getEmissiveId() && mEmissiveTextureUploadId.notNull())
- {
- mUploadingTexturesCount++;
- work_count++;
-
- // For ease of inventory management, we prepend the material name.
- std::string name = mMaterialName + ": " + mEmissiveName;
-
- saveTexture(mEmissiveJ2C, name, mEmissiveTextureUploadId, [key](LLUUID newAssetId, LLSD response)
- {
- LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", LLSD(key));
- if (me)
- {
- if (response["success"].asBoolean())
- {
- me->setEmissiveId(newAssetId);
-
- // discard upload buffers once texture have been saved
- me->mEmissiveJ2C = nullptr;
- me->mEmissiveFetched = nullptr;
- me->mEmissiveTextureUploadId.setNull();
-
- me->mUploadingTexturesCount--;
-
- if (!me->mUploadingTexturesFailure)
- {
- // try saving
- me->saveIfNeeded();
- }
- else if (me->mUploadingTexturesCount == 0)
- {
- me->setEnabled(true);
- }
- }
- else
- {
- // stop upload if possible, unblock and let user decide
- me->setFailedToUploadTexture();
- }
- }
- });
- }
-
- if (!work_count)
- {
- // Discard upload buffers once textures have been confirmed as saved.
- // Otherwise we keep buffers for potential upload failure recovery.
- clearTextures();
- }
-
- // asset storage can callback immediately, causing a decrease
- // of mUploadingTexturesCount, report amount of work scheduled
- // not amount of work remaining
- return work_count;
-}
-
-void LLMaterialEditor::clearTextures()
-{
- mBaseColorJ2C = nullptr;
- mNormalJ2C = nullptr;
- mEmissiveJ2C = nullptr;
- mMetallicRoughnessJ2C = nullptr;
-
- mBaseColorFetched = nullptr;
- mNormalFetched = nullptr;
- mMetallicRoughnessFetched = nullptr;
- mEmissiveFetched = nullptr;
-
- mBaseColorTextureUploadId.setNull();
- mNormalTextureUploadId.setNull();
- mMetallicTextureUploadId.setNull();
- mEmissiveTextureUploadId.setNull();
-}
-
-void LLMaterialEditor::loadDefaults()
-{
- tinygltf::Model model_in;
- model_in.materials.resize(1);
- setFromGltfModel(model_in, 0, true);
-}
-
-bool LLMaterialEditor::capabilitiesAvailable()
-{
- const LLViewerRegion* region = gAgent.getRegion();
- if (!region)
- {
- LL_WARNS("MaterialEditor") << "Not connected to a region, cannot save material." << LL_ENDL;
- return false;
- }
- std::string agent_url = region->getCapability("UpdateMaterialAgentInventory");
- std::string task_url = region->getCapability("UpdateMaterialTaskInventory");
-
- return (!agent_url.empty() && !task_url.empty());
-}
-
+/**
+ * @file llmaterialeditor.cpp
+ * @brief Implementation of the gltf material editor
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llmaterialeditor.h"
+
+#include "llagent.h"
+#include "llagentbenefits.h"
+#include "llappviewer.h"
+#include "llcolorswatch.h"
+#include "llcombobox.h"
+#include "llfloaterreg.h"
+#include "llfilesystem.h"
+#include "llgltfmateriallist.h"
+#include "llinventorymodel.h"
+#include "llinventoryobserver.h"
+#include "llinventoryfunctions.h"
+#include "lllocalgltfmaterials.h"
+#include "llnotificationsutil.h"
+#include "lltexturectrl.h"
+#include "lltrans.h"
+#include "llviewercontrol.h"
+#include "llviewermenufile.h"
+#include "llviewertexture.h"
+#include "llsdutil.h"
+#include "llselectmgr.h"
+#include "llstatusbar.h" // can_afford_transaction()
+#include "lltoolpie.h"
+#include "llviewerinventory.h"
+#include "llinventory.h"
+#include "llviewerregion.h"
+#include "llvovolume.h"
+#include "roles_constants.h"
+#include "llviewerobjectlist.h"
+#include "llsdserialize.h"
+#include "llimagej2c.h"
+#include "llviewertexturelist.h"
+#include "llfloaterperms.h"
+
+#include "tinygltf/tiny_gltf.h"
+#include "lltinygltfhelper.h"
+#include <strstream>
+
+
+const std::string MATERIAL_BASE_COLOR_DEFAULT_NAME = "Base Color";
+const std::string MATERIAL_NORMAL_DEFAULT_NAME = "Normal";
+const std::string MATERIAL_METALLIC_DEFAULT_NAME = "Metallic Roughness";
+const std::string MATERIAL_EMISSIVE_DEFAULT_NAME = "Emissive";
+
+// Dirty flags
+static const U32 MATERIAL_BASE_COLOR_DIRTY = 0x1 << 0;
+static const U32 MATERIAL_BASE_COLOR_TEX_DIRTY = 0x1 << 1;
+
+static const U32 MATERIAL_NORMAL_TEX_DIRTY = 0x1 << 2;
+
+static const U32 MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY = 0x1 << 3;
+static const U32 MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY = 0x1 << 4;
+static const U32 MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY = 0x1 << 5;
+
+static const U32 MATERIAL_EMISIVE_COLOR_DIRTY = 0x1 << 6;
+static const U32 MATERIAL_EMISIVE_TEX_DIRTY = 0x1 << 7;
+
+static const U32 MATERIAL_DOUBLE_SIDED_DIRTY = 0x1 << 8;
+static const U32 MATERIAL_ALPHA_MODE_DIRTY = 0x1 << 9;
+static const U32 MATERIAL_ALPHA_CUTOFF_DIRTY = 0x1 << 10;
+
+LLUUID LLMaterialEditor::mOverrideObjectId;
+S32 LLMaterialEditor::mOverrideObjectTE = -1;
+bool LLMaterialEditor::mOverrideInProgress = false;
+bool LLMaterialEditor::mSelectionNeedsUpdate = true;
+
+LLFloaterComboOptions::LLFloaterComboOptions()
+ : LLFloater(LLSD())
+{
+ buildFromFile("floater_combobox_ok_cancel.xml");
+}
+
+LLFloaterComboOptions::~LLFloaterComboOptions()
+{
+
+}
+
+bool LLFloaterComboOptions::postBuild()
+{
+ mConfirmButton = getChild<LLButton>("combo_ok", true);
+ mCancelButton = getChild<LLButton>("combo_cancel", true);
+ mComboOptions = getChild<LLComboBox>("combo_options", true);
+ mComboText = getChild<LLTextBox>("combo_text", true);
+
+ mConfirmButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) {onConfirm(); });
+ mCancelButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) {onCancel(); });
+
+ return true;
+}
+
+LLFloaterComboOptions* LLFloaterComboOptions::showUI(
+ combo_callback callback,
+ const std::string &title,
+ const std::string &description,
+ const std::list<std::string> &options)
+{
+ LLFloaterComboOptions* combo_picker = new LLFloaterComboOptions();
+ if (combo_picker)
+ {
+ combo_picker->mCallback = callback;
+ combo_picker->setTitle(title);
+
+ combo_picker->mComboText->setText(description);
+
+ std::list<std::string>::const_iterator iter = options.begin();
+ std::list<std::string>::const_iterator end = options.end();
+ for (; iter != end; iter++)
+ {
+ combo_picker->mComboOptions->addSimpleElement(*iter);
+ }
+ combo_picker->mComboOptions->selectFirstItem();
+
+ combo_picker->openFloater(LLSD(title));
+ combo_picker->setFocus(true);
+ combo_picker->center();
+ }
+ return combo_picker;
+}
+
+LLFloaterComboOptions* LLFloaterComboOptions::showUI(
+ combo_callback callback,
+ const std::string &title,
+ const std::string &description,
+ const std::string &ok_text,
+ const std::string &cancel_text,
+ const std::list<std::string> &options)
+{
+ LLFloaterComboOptions* combo_picker = showUI(callback, title, description, options);
+ if (combo_picker)
+ {
+ combo_picker->mConfirmButton->setLabel(ok_text);
+ combo_picker->mCancelButton->setLabel(cancel_text);
+ }
+ return combo_picker;
+}
+
+void LLFloaterComboOptions::onConfirm()
+{
+ mCallback(mComboOptions->getSimple(), mComboOptions->getCurrentIndex());
+ closeFloater();
+}
+
+void LLFloaterComboOptions::onCancel()
+{
+ mCallback(std::string(), -1);
+ closeFloater();
+}
+
+class LLMaterialEditorCopiedCallback : public LLInventoryCallback
+{
+public:
+ LLMaterialEditorCopiedCallback(
+ const std::string &buffer,
+ const LLSD &old_key,
+ bool has_unsaved_changes)
+ : mBuffer(buffer),
+ mOldKey(old_key),
+ mHasUnsavedChanges(has_unsaved_changes)
+ {}
+
+ LLMaterialEditorCopiedCallback(
+ const LLSD &old_key,
+ const std::string &new_name)
+ : mOldKey(old_key),
+ mNewName(new_name),
+ mHasUnsavedChanges(false)
+ {}
+
+ virtual void fire(const LLUUID& inv_item_id)
+ {
+ if (!mNewName.empty())
+ {
+ // making a copy from a notecard doesn't change name, do it now
+ LLViewerInventoryItem* item = gInventory.getItem(inv_item_id);
+ if (item->getName() != mNewName)
+ {
+ LLSD updates;
+ updates["name"] = mNewName;
+ update_inventory_item(inv_item_id, updates, NULL);
+ }
+ }
+ LLMaterialEditor::finishSaveAs(mOldKey, inv_item_id, mBuffer, mHasUnsavedChanges);
+ }
+
+private:
+ std::string mBuffer;
+ LLSD mOldKey;
+ std::string mNewName;
+ bool mHasUnsavedChanges;
+};
+
+///----------------------------------------------------------------------------
+/// Class LLSelectedTEGetMatData
+/// For finding selected applicable inworld material
+///----------------------------------------------------------------------------
+
+struct LLSelectedTEGetMatData : public LLSelectedTEFunctor
+{
+ LLSelectedTEGetMatData(bool for_override);
+
+ bool apply(LLViewerObject* objectp, S32 te_index);
+
+ bool mIsOverride;
+ bool mIdenticalTexColor;
+ bool mIdenticalTexMetal;
+ bool mIdenticalTexEmissive;
+ bool mIdenticalTexNormal;
+ bool mFirst;
+ LLUUID mTexColorId;
+ LLUUID mTexMetalId;
+ LLUUID mTexEmissiveId;
+ LLUUID mTexNormalId;
+ LLUUID mObjectId;
+ LLViewerObject* mObject = nullptr;
+ S32 mObjectTE;
+ LLUUID mMaterialId;
+ LLPointer<LLGLTFMaterial> mMaterial;
+ LLPointer<LLLocalGLTFMaterial> mLocalMaterial;
+};
+
+LLSelectedTEGetMatData::LLSelectedTEGetMatData(bool for_override)
+ : mIsOverride(for_override)
+ , mIdenticalTexColor(true)
+ , mIdenticalTexMetal(true)
+ , mIdenticalTexEmissive(true)
+ , mIdenticalTexNormal(true)
+ , mObjectTE(-1)
+ , mFirst(true)
+{}
+
+bool LLSelectedTEGetMatData::apply(LLViewerObject* objectp, S32 te_index)
+{
+ if (!objectp)
+ {
+ return false;
+ }
+ LLUUID mat_id = objectp->getRenderMaterialID(te_index);
+ mMaterialId = mat_id;
+ bool can_use = mIsOverride ? objectp->permModify() : objectp->permCopy();
+ LLTextureEntry *tep = objectp->getTE(te_index);
+ // We might want to disable this entirely if at least
+ // something in selection is no-copy or no modify
+ // or has no base material
+ if (can_use && tep && mat_id.notNull())
+ {
+ if (mIsOverride)
+ {
+ LLPointer<LLGLTFMaterial> mat = tep->getGLTFRenderMaterial();
+
+ LLUUID tex_color_id;
+ LLUUID tex_metal_id;
+ LLUUID tex_emissive_id;
+ LLUUID tex_normal_id;
+ llassert(mat.notNull()); // by this point shouldn't be null
+ if (mat.notNull())
+ {
+ tex_color_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR];
+ tex_metal_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS];
+ tex_emissive_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE];
+ tex_normal_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL];
+ }
+ if (mFirst)
+ {
+ mMaterial = mat;
+ mTexColorId = tex_color_id;
+ mTexMetalId = tex_metal_id;
+ mTexEmissiveId = tex_emissive_id;
+ mTexNormalId = tex_normal_id;
+ mObjectTE = te_index;
+ mObject = objectp;
+ mObjectId = objectp->getID();
+ mFirst = false;
+ }
+ else
+ {
+ if (mTexColorId != tex_color_id)
+ {
+ mIdenticalTexColor = false;
+ }
+ if (mTexMetalId != tex_metal_id)
+ {
+ mIdenticalTexMetal = false;
+ }
+ if (mTexEmissiveId != tex_emissive_id)
+ {
+ mIdenticalTexEmissive = false;
+ }
+ if (mTexNormalId != tex_normal_id)
+ {
+ mIdenticalTexNormal = false;
+ }
+ }
+ }
+ else
+ {
+ LLGLTFMaterial *mat = tep->getGLTFMaterial();
+ LLLocalGLTFMaterial *local_mat = dynamic_cast<LLLocalGLTFMaterial*>(mat);
+
+ mObject = objectp;
+ mObjectId = objectp->getID();
+ if (local_mat)
+ {
+ mLocalMaterial = local_mat;
+ }
+ mMaterial = tep->getGLTFRenderMaterial();
+
+ if (mMaterial.isNull())
+ {
+ // Shouldn't be possible?
+ LL_WARNS("MaterialEditor") << "Object has material id, but no material" << LL_ENDL;
+ mMaterial = gGLTFMaterialList.getMaterial(mat_id);
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+class LLSelectedTEUpdateOverrides: public LLSelectedNodeFunctor
+{
+public:
+ LLSelectedTEUpdateOverrides(LLMaterialEditor* me) : mEditor(me) {}
+
+ virtual bool apply(LLSelectNode* nodep);
+
+ LLMaterialEditor* mEditor;
+};
+
+bool LLSelectedTEUpdateOverrides::apply(LLSelectNode* nodep)
+{
+ LLViewerObject* objectp = nodep->getObject();
+ if (!objectp)
+ {
+ return false;
+ }
+ S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces
+ for (S32 te_index = 0; te_index < num_tes; ++te_index)
+ {
+
+ LLTextureEntry* tep = objectp->getTE(te_index);
+ LLGLTFMaterial* override_mat = tep->getGLTFMaterialOverride();
+ if (mEditor->updateMaterialLocalSubscription(override_mat))
+ {
+ LLGLTFMaterial* render_mat = tep->getGLTFRenderMaterial();
+ mEditor->updateMaterialLocalSubscription(render_mat);
+ }
+ }
+
+ return true;
+}
+
+///----------------------------------------------------------------------------
+/// Class LLMaterialEditor
+///----------------------------------------------------------------------------
+
+// Default constructor
+LLMaterialEditor::LLMaterialEditor(const LLSD& key)
+ : LLPreview(key)
+ , mUnsavedChanges(0)
+ , mRevertedChanges(0)
+ , mExpectedUploadCost(0)
+ , mUploadingTexturesCount(0)
+ , mUploadingTexturesFailure(false)
+{
+ const LLInventoryItem* item = getItem();
+ if (item)
+ {
+ mAssetID = item->getAssetUUID();
+ }
+}
+
+LLMaterialEditor::~LLMaterialEditor()
+{
+}
+
+void LLMaterialEditor::setObjectID(const LLUUID& object_id)
+{
+ LLPreview::setObjectID(object_id);
+ const LLInventoryItem* item = getItem();
+ if (item)
+ {
+ mAssetID = item->getAssetUUID();
+ }
+}
+
+void LLMaterialEditor::setAuxItem(const LLInventoryItem* item)
+{
+ LLPreview::setAuxItem(item);
+ if (item)
+ {
+ mAssetID = item->getAssetUUID();
+ }
+}
+
+bool LLMaterialEditor::postBuild()
+{
+ // if this is a 'live editor' instance, it is also
+ // single instance and uses live overrides
+ mIsOverride = getIsSingleInstance();
+
+ mBaseColorTextureCtrl = getChild<LLTextureCtrl>("base_color_texture");
+ mMetallicTextureCtrl = getChild<LLTextureCtrl>("metallic_roughness_texture");
+ mEmissiveTextureCtrl = getChild<LLTextureCtrl>("emissive_texture");
+ mNormalTextureCtrl = getChild<LLTextureCtrl>("normal_texture");
+ mBaseColorCtrl = getChild<LLColorSwatchCtrl>("base color");
+ mEmissiveColorCtrl = getChild<LLColorSwatchCtrl>("emissive color");
+
+ if (!gAgent.isGodlike())
+ {
+ // Only allow fully permissive textures
+ mBaseColorTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER);
+ mMetallicTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER);
+ mEmissiveTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER);
+ mNormalTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER);
+ }
+
+ // Texture callback
+ mBaseColorTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY));
+ mMetallicTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY));
+ mEmissiveTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY));
+ mNormalTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY));
+
+ mNormalTextureCtrl->setBlankImageAssetID(BLANK_OBJECT_NORMAL);
+
+ if (mIsOverride)
+ {
+ // Live editing needs a recovery mechanism on cancel
+ mBaseColorTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY));
+ mMetallicTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY));
+ mEmissiveTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY));
+ mNormalTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY));
+
+ // Save applied changes on 'OK' to our recovery mechanism.
+ mBaseColorTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY));
+ mMetallicTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY));
+ mEmissiveTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY));
+ mNormalTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY));
+ }
+ else
+ {
+ mBaseColorTextureCtrl->setCanApplyImmediately(false);
+ mMetallicTextureCtrl->setCanApplyImmediately(false);
+ mEmissiveTextureCtrl->setCanApplyImmediately(false);
+ mNormalTextureCtrl->setCanApplyImmediately(false);
+ }
+
+ if (!mIsOverride)
+ {
+ childSetAction("save", boost::bind(&LLMaterialEditor::onClickSave, this));
+ childSetAction("save_as", boost::bind(&LLMaterialEditor::onClickSaveAs, this));
+ childSetAction("cancel", boost::bind(&LLMaterialEditor::onClickCancel, this));
+ }
+
+ if (mIsOverride)
+ {
+ childSetVisible("base_color_upload_fee", false);
+ childSetVisible("metallic_upload_fee", false);
+ childSetVisible("emissive_upload_fee", false);
+ childSetVisible("normal_upload_fee", false);
+ }
+ else
+ {
+ S32 upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost();
+ getChild<LLUICtrl>("base_color_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
+ getChild<LLUICtrl>("metallic_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
+ getChild<LLUICtrl>("emissive_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
+ getChild<LLUICtrl>("normal_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
+ }
+
+ boost::function<void(LLUICtrl*, void*)> changes_callback = [this](LLUICtrl * ctrl, void* userData)
+ {
+ const U32 *flag = (const U32*)userData;
+ markChangesUnsaved(*flag);
+ // Apply changes to object live
+ applyToSelection();
+ };
+
+ childSetCommitCallback("double sided", changes_callback, (void*)&MATERIAL_DOUBLE_SIDED_DIRTY);
+
+ // BaseColor
+ mBaseColorCtrl->setCommitCallback(changes_callback, (void*)&MATERIAL_BASE_COLOR_DIRTY);
+ if (mIsOverride)
+ {
+ mBaseColorCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_BASE_COLOR_DIRTY));
+ mBaseColorCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_BASE_COLOR_DIRTY));
+ }
+ else
+ {
+ mBaseColorCtrl->setCanApplyImmediately(false);
+ }
+ // transparency is a part of base color
+ childSetCommitCallback("transparency", changes_callback, (void*)&MATERIAL_BASE_COLOR_DIRTY);
+ childSetCommitCallback("alpha mode", changes_callback, (void*)&MATERIAL_ALPHA_MODE_DIRTY);
+ childSetCommitCallback("alpha cutoff", changes_callback, (void*)&MATERIAL_ALPHA_CUTOFF_DIRTY);
+
+ // Metallic-Roughness
+ childSetCommitCallback("metalness factor", changes_callback, (void*)&MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY);
+ childSetCommitCallback("roughness factor", changes_callback, (void*)&MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY);
+
+ // Emissive
+ mEmissiveColorCtrl->setCommitCallback(changes_callback, (void*)&MATERIAL_EMISIVE_COLOR_DIRTY);
+ if (mIsOverride)
+ {
+ mEmissiveColorCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_EMISIVE_COLOR_DIRTY));
+ mEmissiveColorCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_EMISIVE_COLOR_DIRTY));
+ }
+ else
+ {
+ mEmissiveColorCtrl->setCanApplyImmediately(false);
+ }
+
+ if (!mIsOverride)
+ {
+ // "unsaved_changes" doesn't exist in live editor
+ childSetVisible("unsaved_changes", mUnsavedChanges);
+
+ // Doesn't exist in live editor
+ getChild<LLUICtrl>("total_upload_fee")->setTextArg("[FEE]", llformat("%d", 0));
+ }
+
+ // Todo:
+ // Disable/enable setCanApplyImmediately() based on
+ // working from inventory, upload or editing inworld
+
+ return LLPreview::postBuild();
+}
+
+void LLMaterialEditor::onClickCloseBtn(bool app_quitting)
+{
+ if (app_quitting || mIsOverride)
+ {
+ closeFloater(app_quitting);
+ }
+ else
+ {
+ onClickCancel();
+ }
+}
+
+void LLMaterialEditor::onClose(bool app_quitting)
+{
+ if (mSelectionUpdateSlot.connected())
+ {
+ mSelectionUpdateSlot.disconnect();
+ }
+ for (mat_connection_map_t::value_type &cn : mTextureChangesUpdates)
+ {
+ cn.second.mConnection.disconnect();
+ }
+ mTextureChangesUpdates.clear();
+
+ LLPreview::onClose(app_quitting);
+}
+
+void LLMaterialEditor::draw()
+{
+ if (mIsOverride)
+ {
+ if (mSelectionNeedsUpdate)
+ {
+ mSelectionNeedsUpdate = false;
+ clearTextures();
+ setFromSelection();
+ }
+ }
+ LLPreview::draw();
+}
+
+void LLMaterialEditor::handleReshape(const LLRect& new_rect, bool by_user)
+{
+ if (by_user)
+ {
+ const LLRect old_rect = getRect();
+ LLRect clamp_rect(new_rect);
+ clamp_rect.mRight = clamp_rect.mLeft + old_rect.getWidth();
+ LLPreview::handleReshape(clamp_rect, by_user);
+ }
+ else
+ {
+ LLPreview::handleReshape(new_rect, by_user);
+ }
+}
+
+LLUUID LLMaterialEditor::getBaseColorId()
+{
+ return mBaseColorTextureCtrl->getValue().asUUID();
+}
+
+void LLMaterialEditor::setBaseColorId(const LLUUID& id)
+{
+ mBaseColorTextureCtrl->setValue(id);
+ mBaseColorTextureCtrl->setDefaultImageAssetID(id);
+ mBaseColorTextureCtrl->setTentative(false);
+}
+
+void LLMaterialEditor::setBaseColorUploadId(const LLUUID& id)
+{
+ // Might be better to use local textures and
+ // assign a fee in case of a local texture
+ if (id.notNull())
+ {
+ // todo: this does not account for posibility of texture
+ // being from inventory, need to check that
+ childSetValue("base_color_upload_fee", getString("upload_fee_string"));
+ // Only set if we will need to upload this texture
+ mBaseColorTextureUploadId = id;
+ }
+ markChangesUnsaved(MATERIAL_BASE_COLOR_TEX_DIRTY);
+}
+
+LLColor4 LLMaterialEditor::getBaseColor()
+{
+ LLColor4 ret = linearColor4(LLColor4(mBaseColorCtrl->getValue()));
+ ret.mV[3] = getTransparency();
+ return ret;
+}
+
+void LLMaterialEditor::setBaseColor(const LLColor4& color)
+{
+ mBaseColorCtrl->setValue(srgbColor4(color).getValue());
+ setTransparency(color.mV[3]);
+}
+
+F32 LLMaterialEditor::getTransparency()
+{
+ return childGetValue("transparency").asReal();
+}
+
+void LLMaterialEditor::setTransparency(F32 transparency)
+{
+ childSetValue("transparency", transparency);
+}
+
+std::string LLMaterialEditor::getAlphaMode()
+{
+ return childGetValue("alpha mode").asString();
+}
+
+void LLMaterialEditor::setAlphaMode(const std::string& alpha_mode)
+{
+ childSetValue("alpha mode", alpha_mode);
+}
+
+F32 LLMaterialEditor::getAlphaCutoff()
+{
+ return childGetValue("alpha cutoff").asReal();
+}
+
+void LLMaterialEditor::setAlphaCutoff(F32 alpha_cutoff)
+{
+ childSetValue("alpha cutoff", alpha_cutoff);
+}
+
+void LLMaterialEditor::setMaterialName(const std::string &name)
+{
+ setTitle(name);
+ mMaterialName = name;
+}
+
+LLUUID LLMaterialEditor::getMetallicRoughnessId()
+{
+ return mMetallicTextureCtrl->getValue().asUUID();
+}
+
+void LLMaterialEditor::setMetallicRoughnessId(const LLUUID& id)
+{
+ mMetallicTextureCtrl->setValue(id);
+ mMetallicTextureCtrl->setDefaultImageAssetID(id);
+ mMetallicTextureCtrl->setTentative(false);
+}
+
+void LLMaterialEditor::setMetallicRoughnessUploadId(const LLUUID& id)
+{
+ if (id.notNull())
+ {
+ // todo: this does not account for posibility of texture
+ // being from inventory, need to check that
+ childSetValue("metallic_upload_fee", getString("upload_fee_string"));
+ mMetallicTextureUploadId = id;
+ }
+ markChangesUnsaved(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY);
+}
+
+F32 LLMaterialEditor::getMetalnessFactor()
+{
+ return childGetValue("metalness factor").asReal();
+}
+
+void LLMaterialEditor::setMetalnessFactor(F32 factor)
+{
+ childSetValue("metalness factor", factor);
+}
+
+F32 LLMaterialEditor::getRoughnessFactor()
+{
+ return childGetValue("roughness factor").asReal();
+}
+
+void LLMaterialEditor::setRoughnessFactor(F32 factor)
+{
+ childSetValue("roughness factor", factor);
+}
+
+LLUUID LLMaterialEditor::getEmissiveId()
+{
+ return mEmissiveTextureCtrl->getValue().asUUID();
+}
+
+void LLMaterialEditor::setEmissiveId(const LLUUID& id)
+{
+ mEmissiveTextureCtrl->setValue(id);
+ mEmissiveTextureCtrl->setDefaultImageAssetID(id);
+ mEmissiveTextureCtrl->setTentative(false);
+}
+
+void LLMaterialEditor::setEmissiveUploadId(const LLUUID& id)
+{
+ if (id.notNull())
+ {
+ // todo: this does not account for posibility of texture
+ // being from inventory, need to check that
+ childSetValue("emissive_upload_fee", getString("upload_fee_string"));
+ mEmissiveTextureUploadId = id;
+ }
+ markChangesUnsaved(MATERIAL_EMISIVE_TEX_DIRTY);
+}
+
+LLColor4 LLMaterialEditor::getEmissiveColor()
+{
+ return linearColor4(LLColor4(mEmissiveColorCtrl->getValue()));
+}
+
+void LLMaterialEditor::setEmissiveColor(const LLColor4& color)
+{
+ mEmissiveColorCtrl->setValue(srgbColor4(color).getValue());
+}
+
+LLUUID LLMaterialEditor::getNormalId()
+{
+ return mNormalTextureCtrl->getValue().asUUID();
+}
+
+void LLMaterialEditor::setNormalId(const LLUUID& id)
+{
+ mNormalTextureCtrl->setValue(id);
+ mNormalTextureCtrl->setDefaultImageAssetID(id);
+ mNormalTextureCtrl->setTentative(false);
+}
+
+void LLMaterialEditor::setNormalUploadId(const LLUUID& id)
+{
+ if (id.notNull())
+ {
+ // todo: this does not account for posibility of texture
+ // being from inventory, need to check that
+ childSetValue("normal_upload_fee", getString("upload_fee_string"));
+ mNormalTextureUploadId = id;
+ }
+ markChangesUnsaved(MATERIAL_NORMAL_TEX_DIRTY);
+}
+
+bool LLMaterialEditor::getDoubleSided()
+{
+ return childGetValue("double sided").asBoolean();
+}
+
+void LLMaterialEditor::setDoubleSided(bool double_sided)
+{
+ childSetValue("double sided", double_sided);
+}
+
+void LLMaterialEditor::resetUnsavedChanges()
+{
+ mUnsavedChanges = 0;
+ mRevertedChanges = 0;
+ if (!mIsOverride)
+ {
+ childSetVisible("unsaved_changes", false);
+ setCanSave(false);
+
+ mExpectedUploadCost = 0;
+ getChild<LLUICtrl>("total_upload_fee")->setTextArg("[FEE]", llformat("%d", mExpectedUploadCost));
+ }
+}
+
+void LLMaterialEditor::markChangesUnsaved(U32 dirty_flag)
+{
+ mUnsavedChanges |= dirty_flag;
+ if (mIsOverride)
+ {
+ // at the moment live editing (mIsOverride) applies everything 'live'
+ // and "unsaved_changes", save/cancel buttons don't exist there
+ return;
+ }
+
+ childSetVisible("unsaved_changes", mUnsavedChanges);
+
+ if (mUnsavedChanges)
+ {
+ const LLInventoryItem* item = getItem();
+ if (item)
+ {
+ //LLPermissions perm(item->getPermissions());
+ bool allow_modify = canModify(mObjectUUID, item);
+ bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
+ bool source_notecard = mNotecardInventoryID.notNull();
+
+ setCanSave(allow_modify && !source_library && !source_notecard);
+ }
+ }
+ else
+ {
+ setCanSave(false);
+ }
+
+ S32 upload_texture_count = 0;
+ if (mBaseColorTextureUploadId.notNull() && mBaseColorTextureUploadId == getBaseColorId())
+ {
+ upload_texture_count++;
+ }
+ if (mMetallicTextureUploadId.notNull() && mMetallicTextureUploadId == getMetallicRoughnessId())
+ {
+ upload_texture_count++;
+ }
+ if (mEmissiveTextureUploadId.notNull() && mEmissiveTextureUploadId == getEmissiveId())
+ {
+ upload_texture_count++;
+ }
+ if (mNormalTextureUploadId.notNull() && mNormalTextureUploadId == getNormalId())
+ {
+ upload_texture_count++;
+ }
+
+ mExpectedUploadCost = upload_texture_count * LLAgentBenefitsMgr::current().getTextureUploadCost();
+ getChild<LLUICtrl>("total_upload_fee")->setTextArg("[FEE]", llformat("%d", mExpectedUploadCost));
+}
+
+void LLMaterialEditor::setCanSaveAs(bool value)
+{
+ if (!mIsOverride)
+ {
+ childSetEnabled("save_as", value);
+ }
+}
+
+void LLMaterialEditor::setCanSave(bool value)
+{
+ if (!mIsOverride)
+ {
+ childSetEnabled("save", value);
+ }
+}
+
+void LLMaterialEditor::setEnableEditing(bool can_modify)
+{
+ childSetEnabled("double sided", can_modify);
+
+ // BaseColor
+ childSetEnabled("base color", can_modify);
+ childSetEnabled("transparency", can_modify);
+ childSetEnabled("alpha mode", can_modify);
+ childSetEnabled("alpha cutoff", can_modify);
+
+ // Metallic-Roughness
+ childSetEnabled("metalness factor", can_modify);
+ childSetEnabled("roughness factor", can_modify);
+
+ // Metallic-Roughness
+ childSetEnabled("metalness factor", can_modify);
+ childSetEnabled("roughness factor", can_modify);
+
+ // Emissive
+ childSetEnabled("emissive color", can_modify);
+
+ mBaseColorTextureCtrl->setEnabled(can_modify);
+ mMetallicTextureCtrl->setEnabled(can_modify);
+ mEmissiveTextureCtrl->setEnabled(can_modify);
+ mNormalTextureCtrl->setEnabled(can_modify);
+}
+
+void LLMaterialEditor::subscribeToLocalTexture(S32 dirty_flag, const LLUUID& tracking_id)
+{
+ if (mTextureChangesUpdates[dirty_flag].mTrackingId != tracking_id)
+ {
+ mTextureChangesUpdates[dirty_flag].mConnection.disconnect();
+ mTextureChangesUpdates[dirty_flag].mTrackingId = tracking_id;
+ mTextureChangesUpdates[dirty_flag].mConnection = LLLocalBitmapMgr::getInstance()->setOnChangedCallback(tracking_id,
+ [this, dirty_flag](const LLUUID& tracking_id, const LLUUID& old_id, const LLUUID& new_id)
+ {
+ if (new_id.isNull())
+ {
+ mTextureChangesUpdates[dirty_flag].mConnection.disconnect();
+ //mTextureChangesUpdates.erase(dirty_flag);
+ }
+ else
+ {
+ replaceLocalTexture(old_id, new_id);
+ }
+ });
+ }
+}
+
+LLUUID LLMaterialEditor::getLocalTextureTrackingIdFromFlag(U32 flag)
+{
+ mat_connection_map_t::iterator found = mTextureChangesUpdates.find(flag);
+ if (found != mTextureChangesUpdates.end())
+ {
+ return found->second.mTrackingId;
+ }
+ return LLUUID();
+}
+
+bool LLMaterialEditor::updateMaterialLocalSubscription(LLGLTFMaterial* mat)
+{
+ if (!mat)
+ {
+ return false;
+ }
+
+ bool res = false;
+ for (mat_connection_map_t::value_type& cn : mTextureChangesUpdates)
+ {
+ LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(cn.second.mTrackingId);
+ if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR])
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat);
+ res = true;
+ continue;
+ }
+ if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS])
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat);
+ res = true;
+ continue;
+ }
+ if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE])
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat);
+ res = true;
+ continue;
+ }
+ if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL])
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat);
+ res = true;
+ continue;
+ }
+ }
+ return res;
+}
+
+void LLMaterialEditor::replaceLocalTexture(const LLUUID& old_id, const LLUUID& new_id)
+{
+ // todo: might be a good idea to set mBaseColorTextureUploadId here
+ // and when texturectrl picks a local texture
+ if (getBaseColorId() == old_id)
+ {
+ mBaseColorTextureCtrl->setValue(new_id);
+ }
+ if (mBaseColorTextureCtrl->getDefaultImageAssetID() == old_id)
+ {
+ mBaseColorTextureCtrl->setDefaultImageAssetID(new_id);
+ }
+
+ if (getMetallicRoughnessId() == old_id)
+ {
+ mMetallicTextureCtrl->setValue(new_id);
+ }
+ if (mMetallicTextureCtrl->getDefaultImageAssetID() == old_id)
+ {
+ mMetallicTextureCtrl->setDefaultImageAssetID(new_id);
+ }
+
+ if (getEmissiveId() == old_id)
+ {
+ mEmissiveTextureCtrl->setValue(new_id);
+ }
+ if (mEmissiveTextureCtrl->getDefaultImageAssetID() == old_id)
+ {
+ mEmissiveTextureCtrl->setDefaultImageAssetID(new_id);
+ }
+
+ if (getNormalId() == old_id)
+ {
+ mNormalTextureCtrl->setValue(new_id);
+ }
+ if (mNormalTextureCtrl->getDefaultImageAssetID() == old_id)
+ {
+ mNormalTextureCtrl->setDefaultImageAssetID(new_id);
+ }
+}
+
+void LLMaterialEditor::onCommitTexture(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag)
+{
+ if (!mIsOverride)
+ {
+ std::string upload_fee_ctrl_name;
+ LLUUID old_uuid;
+
+ switch (dirty_flag)
+ {
+ case MATERIAL_BASE_COLOR_TEX_DIRTY:
+ {
+ upload_fee_ctrl_name = "base_color_upload_fee";
+ old_uuid = mBaseColorTextureUploadId;
+ break;
+ }
+ case MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY:
+ {
+ upload_fee_ctrl_name = "metallic_upload_fee";
+ old_uuid = mMetallicTextureUploadId;
+ break;
+ }
+ case MATERIAL_EMISIVE_TEX_DIRTY:
+ {
+ upload_fee_ctrl_name = "emissive_upload_fee";
+ old_uuid = mEmissiveTextureUploadId;
+ break;
+ }
+ case MATERIAL_NORMAL_TEX_DIRTY:
+ {
+ upload_fee_ctrl_name = "normal_upload_fee";
+ old_uuid = mNormalTextureUploadId;
+ break;
+ }
+ default:
+ break;
+ }
+ LLUUID new_val = ctrl->getValue().asUUID();
+ if (new_val == old_uuid && old_uuid.notNull())
+ {
+ childSetValue(upload_fee_ctrl_name, getString("upload_fee_string"));
+ }
+ else
+ {
+ // Texture picker has 'apply now' with 'cancel' support.
+ // Don't clean mBaseColorJ2C and mBaseColorFetched, it's our
+ // storage in case user decides to cancel changes.
+ // Without mBaseColorFetched, viewer will eventually cleanup
+ // the texture that is not in use
+ childSetValue(upload_fee_ctrl_name, getString("no_upload_fee_string"));
+ }
+ }
+
+ LLTextureCtrl* tex_ctrl = (LLTextureCtrl*)ctrl;
+ if (tex_ctrl->isImageLocal())
+ {
+ subscribeToLocalTexture(dirty_flag, tex_ctrl->getLocalTrackingID());
+ }
+ else
+ {
+ // unsubcribe potential old callabck
+ mat_connection_map_t::iterator found = mTextureChangesUpdates.find(dirty_flag);
+ if (found != mTextureChangesUpdates.end())
+ {
+ found->second.mConnection.disconnect();
+ }
+ }
+
+ markChangesUnsaved(dirty_flag);
+ applyToSelection();
+}
+
+void LLMaterialEditor::onCancelCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag)
+{
+ mRevertedChanges |= dirty_flag;
+ applyToSelection();
+}
+
+void update_local_texture(LLUICtrl* ctrl, LLGLTFMaterial* mat)
+{
+ LLTextureCtrl* tex_ctrl = (LLTextureCtrl*)ctrl;
+ if (tex_ctrl->isImageLocal())
+ {
+ // subscrive material to updates of local textures
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tex_ctrl->getLocalTrackingID(), mat);
+ }
+}
+
+void LLMaterialEditor::onSelectCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag)
+{
+ mUnsavedChanges |= dirty_flag;
+ applyToSelection();
+
+ struct f : public LLSelectedNodeFunctor
+ {
+ f(LLUICtrl* ctrl, S32 dirty_flag) : mCtrl(ctrl), mDirtyFlag(dirty_flag)
+ {
+ }
+
+ virtual bool apply(LLSelectNode* nodep)
+ {
+ LLViewerObject* objectp = nodep->getObject();
+ if (!objectp)
+ {
+ return false;
+ }
+ S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces
+ for (S32 te = 0; te < num_tes; ++te)
+ {
+ if (nodep->isTESelected(te) && nodep->mSavedGLTFOverrideMaterials.size() > te)
+ {
+ if (nodep->mSavedGLTFOverrideMaterials[te].isNull())
+ {
+ // populate with default values, default values basically mean 'not in use'
+ nodep->mSavedGLTFOverrideMaterials[te] = new LLGLTFMaterial();
+ }
+
+ switch (mDirtyFlag)
+ {
+ //Textures
+ case MATERIAL_BASE_COLOR_TEX_DIRTY:
+ {
+ nodep->mSavedGLTFOverrideMaterials[te]->setBaseColorId(mCtrl->getValue().asUUID(), true);
+ update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get());
+ break;
+ }
+ case MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY:
+ {
+ nodep->mSavedGLTFOverrideMaterials[te]->setOcclusionRoughnessMetallicId(mCtrl->getValue().asUUID(), true);
+ update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get());
+ break;
+ }
+ case MATERIAL_EMISIVE_TEX_DIRTY:
+ {
+ nodep->mSavedGLTFOverrideMaterials[te]->setEmissiveId(mCtrl->getValue().asUUID(), true);
+ update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get());
+ break;
+ }
+ case MATERIAL_NORMAL_TEX_DIRTY:
+ {
+ nodep->mSavedGLTFOverrideMaterials[te]->setNormalId(mCtrl->getValue().asUUID(), true);
+ update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get());
+ break;
+ }
+ // Colors
+ case MATERIAL_BASE_COLOR_DIRTY:
+ {
+ LLColor4 ret = linearColor4(LLColor4(mCtrl->getValue()));
+ // except transparency
+ ret.mV[3] = nodep->mSavedGLTFOverrideMaterials[te]->mBaseColor.mV[3];
+ nodep->mSavedGLTFOverrideMaterials[te]->setBaseColorFactor(ret, true);
+ break;
+ }
+ case MATERIAL_EMISIVE_COLOR_DIRTY:
+ {
+ nodep->mSavedGLTFOverrideMaterials[te]->setEmissiveColorFactor(LLColor3(mCtrl->getValue()), true);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ return true;
+ }
+
+ LLUICtrl* mCtrl;
+ S32 mDirtyFlag;
+ } func(ctrl, dirty_flag);
+
+ LLSelectMgr::getInstance()->getSelection()->applyToNodes(&func);
+}
+
+void LLMaterialEditor::onClickSave()
+{
+ if (!capabilitiesAvailable())
+ {
+ LLNotificationsUtil::add("MissingMaterialCaps");
+ return;
+ }
+ if (!can_afford_transaction(mExpectedUploadCost))
+ {
+ LLSD args;
+ args["COST"] = llformat("%d", mExpectedUploadCost);
+ LLNotificationsUtil::add("ErrorCannotAffordUpload", args);
+ return;
+ }
+
+ applyToSelection();
+ saveIfNeeded();
+}
+
+std::string LLMaterialEditor::getEncodedAsset()
+{
+ LLSD asset;
+ asset["version"] = LLGLTFMaterial::ASSET_VERSION;
+ asset["type"] = LLGLTFMaterial::ASSET_TYPE;
+ LLGLTFMaterial mat;
+ getGLTFMaterial(&mat);
+ asset["data"] = mat.asJSON();
+
+ std::ostringstream str;
+ LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY);
+
+ return str.str();
+}
+
+bool LLMaterialEditor::decodeAsset(const std::vector<char>& buffer)
+{
+ LLSD asset;
+
+ std::istrstream str(&buffer[0], buffer.size());
+ if (LLSDSerialize::deserialize(asset, str, buffer.size()))
+ {
+ if (asset.has("version") && LLGLTFMaterial::isAcceptedVersion(asset["version"].asString()))
+ {
+ if (asset.has("type") && asset["type"] == LLGLTFMaterial::ASSET_TYPE)
+ {
+ if (asset.has("data") && asset["data"].isString())
+ {
+ std::string data = asset["data"];
+
+ tinygltf::TinyGLTF gltf;
+ tinygltf::TinyGLTF loader;
+ std::string error_msg;
+ std::string warn_msg;
+
+ tinygltf::Model model_in;
+
+ if (loader.LoadASCIIFromString(&model_in, &error_msg, &warn_msg, data.c_str(), data.length(), ""))
+ {
+ // assets are only supposed to have one item
+ // *NOTE: This duplicates some functionality from
+ // LLGLTFMaterial::fromJSON, but currently does the job
+ // better for the material editor use case.
+ // However, LLGLTFMaterial::asJSON should always be
+ // used when uploading materials, to ensure the
+ // asset is valid.
+ return setFromGltfModel(model_in, 0, true);
+ }
+ else
+ {
+ LL_WARNS("MaterialEditor") << "Floater " << getKey() << " Failed to decode material asset: " << LL_NEWLINE
+ << warn_msg << LL_NEWLINE
+ << error_msg << LL_ENDL;
+ }
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS("MaterialEditor") << "Invalid LLSD content "<< asset << " for flaoter " << getKey() << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("MaterialEditor") << "Failed to deserialize material LLSD for flaoter " << getKey() << LL_ENDL;
+ }
+
+ return false;
+}
+
+/**
+ * Build a description of the material we just imported.
+ * Currently this means a list of the textures present but we
+ * may eventually want to make it more complete - will be guided
+ * by what the content creators say they need.
+ */
+const std::string LLMaterialEditor::buildMaterialDescription()
+{
+ std::ostringstream desc;
+ desc << LLTrans::getString("Material Texture Name Header");
+
+ // add the texture names for each just so long as the material
+ // we loaded has an entry for it (i think testing the texture
+ // control UUI for NULL is a valid metric for if it was loaded
+ // or not but I suspect this code will change a lot so may need
+ // to revisit
+ if (!mBaseColorTextureCtrl->getValue().asUUID().isNull())
+ {
+ desc << mBaseColorName;
+ desc << ", ";
+ }
+ if (!mMetallicTextureCtrl->getValue().asUUID().isNull())
+ {
+ desc << mMetallicRoughnessName;
+ desc << ", ";
+ }
+ if (!mEmissiveTextureCtrl->getValue().asUUID().isNull())
+ {
+ desc << mEmissiveName;
+ desc << ", ";
+ }
+ if (!mNormalTextureCtrl->getValue().asUUID().isNull())
+ {
+ desc << mNormalName;
+ }
+
+ // trim last char if it's a ',' in case there is no normal texture
+ // present and the code above inserts one
+ // (no need to check for string length - always has initial string)
+ std::string::iterator iter = desc.str().end() - 1;
+ if (*iter == ',')
+ {
+ desc.str().erase(iter);
+ }
+
+ // sanitize the material description so that it's compatible with the inventory
+ // note: split this up because clang doesn't like operating directly on the
+ // str() - error: lvalue reference to type 'basic_string<...>' cannot bind to a
+ // temporary of type 'basic_string<...>'
+ std::string inv_desc = desc.str();
+ LLInventoryObject::correctInventoryName(inv_desc);
+
+ return inv_desc;
+}
+
+bool LLMaterialEditor::saveIfNeeded()
+{
+ if (mUploadingTexturesCount > 0)
+ {
+ // Upload already in progress, wait until
+ // textures upload will retry saving on callback.
+ // Also should prevent some failure-callbacks
+ return true;
+ }
+
+ if (saveTextures() > 0)
+ {
+ // started texture upload
+ setEnabled(false);
+ return true;
+ }
+
+ std::string buffer = getEncodedAsset();
+
+ const LLInventoryItem* item = getItem();
+ // save it out to database
+ if (item)
+ {
+ if (!updateInventoryItem(buffer, mItemUUID, mObjectUUID))
+ {
+ return false;
+ }
+
+ if (mCloseAfterSave)
+ {
+ closeFloater();
+ }
+ else
+ {
+ mAssetStatus = PREVIEW_ASSET_LOADING;
+ setEnabled(false);
+ }
+ }
+ else
+ {
+ // Make a new inventory item and set upload permissions
+ LLPermissions local_permissions;
+ local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
+
+ if (mIsOverride)
+ {
+ // Shouldn't happen, but just in case it ever changes
+ U32 everyone_perm = LLFloaterPerms::getEveryonePerms("Materials");
+ U32 group_perm = LLFloaterPerms::getGroupPerms("Materials");
+ U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Materials");
+ local_permissions.initMasks(PERM_ALL, PERM_ALL, everyone_perm, group_perm, next_owner_perm);
+
+ }
+ else
+ {
+ // Uploads are supposed to use Upload permissions, not material permissions
+ U32 everyone_perm = LLFloaterPerms::getEveryonePerms("Uploads");
+ U32 group_perm = LLFloaterPerms::getGroupPerms("Uploads");
+ U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Uploads");
+ local_permissions.initMasks(PERM_ALL, PERM_ALL, everyone_perm, group_perm, next_owner_perm);
+ }
+
+ std::string res_desc = buildMaterialDescription();
+ createInventoryItem(buffer, mMaterialName, res_desc, local_permissions);
+
+ // We do not update floater with uploaded asset yet, so just close it.
+ closeFloater();
+ }
+
+ return true;
+}
+
+// static
+bool LLMaterialEditor::updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id)
+{
+ const LLViewerRegion* region = gAgent.getRegion();
+ if (!region)
+ {
+ LL_WARNS("MaterialEditor") << "Not connected to a region, cannot save material." << LL_ENDL;
+ return false;
+ }
+ std::string agent_url = region->getCapability("UpdateMaterialAgentInventory");
+ std::string task_url = region->getCapability("UpdateMaterialTaskInventory");
+
+ if (!agent_url.empty() && !task_url.empty())
+ {
+ std::string url;
+ LLResourceUploadInfo::ptr_t uploadInfo;
+
+ if (task_id.isNull() && !agent_url.empty())
+ {
+ uploadInfo = std::make_shared<LLBufferedAssetUploadInfo>(item_id, LLAssetType::AT_MATERIAL, buffer,
+ [](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD)
+ {
+ // done callback
+ LLMaterialEditor::finishInventoryUpload(itemId, newAssetId, newItemId);
+ },
+ [](LLUUID itemId, LLUUID taskId, LLSD response, std::string reason)
+ {
+ // failure callback
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", LLSD(itemId));
+ if (me)
+ {
+ me->setEnabled(true);
+ }
+ return true;
+ }
+ );
+ url = agent_url;
+ }
+ else if (!task_id.isNull() && !task_url.empty())
+ {
+ uploadInfo = std::make_shared<LLBufferedAssetUploadInfo>(task_id, item_id, LLAssetType::AT_MATERIAL, buffer,
+ [](LLUUID itemId, LLUUID task_id, LLUUID newAssetId, LLSD)
+ {
+ // done callback
+ LLMaterialEditor::finishTaskUpload(itemId, newAssetId, task_id);
+ },
+ [](LLUUID itemId, LLUUID task_id, LLSD response, std::string reason)
+ {
+ // failure callback
+ LLSD floater_key;
+ floater_key["taskid"] = task_id;
+ floater_key["itemid"] = itemId;
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", floater_key);
+ if (me)
+ {
+ me->setEnabled(true);
+ }
+ return true;
+ }
+ );
+ url = task_url;
+ }
+
+ if (!url.empty() && uploadInfo)
+ {
+ LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo);
+ }
+ else
+ {
+ return false;
+ }
+
+ }
+ else // !gAssetStorage
+ {
+ LL_WARNS("MaterialEditor") << "Not connected to an materials capable region." << LL_ENDL;
+ return false;
+ }
+
+ // todo: apply permissions from textures here if server doesn't
+ // if any texture is 'no transfer', material should be 'no transfer' as well
+
+ return true;
+}
+
+// Callback intended for when a material is saved from an object and needs to
+// be modified to reflect the new asset/name.
+class LLObjectsMaterialItemCallback : public LLInventoryCallback
+{
+public:
+ LLObjectsMaterialItemCallback(const LLPermissions& permissions, const std::string& asset_data, const std::string& new_name)
+ : mPermissions(permissions),
+ mAssetData(asset_data),
+ mNewName(new_name)
+ {
+ }
+
+ void fire(const LLUUID& inv_item_id) override
+ {
+ LLViewerInventoryItem* item = gInventory.getItem(inv_item_id);
+ if (!item)
+ {
+ return;
+ }
+
+ // Name may or may not have already been applied
+ const bool changed_name = item->getName() != mNewName;
+ // create_inventory_item/copy_inventory_item don't allow presetting some permissions, fix it now
+ const bool changed_permissions = item->getPermissions() != mPermissions;
+ const bool changed = changed_name || changed_permissions;
+ LLSD updates;
+ if (changed)
+ {
+ if (changed_name)
+ {
+ updates["name"] = mNewName;
+ }
+ if (changed_permissions)
+ {
+ updates["permissions"] = ll_create_sd_from_permissions(mPermissions);
+ }
+ update_inventory_item(inv_item_id, updates, NULL);
+ }
+
+ // from reference in LLSettingsVOBase::createInventoryItem()/updateInventoryItem()
+ LLResourceUploadInfo::ptr_t uploadInfo =
+ std::make_shared<LLBufferedAssetUploadInfo>(
+ inv_item_id,
+ LLAssetType::AT_MATERIAL,
+ mAssetData,
+ [changed, updates](LLUUID item_id, LLUUID new_asset_id, LLUUID new_item_id, LLSD response)
+ {
+ // done callback
+ LL_INFOS("Material") << "inventory item uploaded. item: " << item_id << " new_item_id: " << new_item_id << " response: " << response << LL_ENDL;
+
+ // *HACK: Sometimes permissions do not stick in the UI. They are correct on the server-side, though.
+ if (changed)
+ {
+ update_inventory_item(new_item_id, updates, NULL);
+ }
+ },
+ nullptr // failure callback, floater already closed
+ );
+
+ const LLViewerRegion* region = gAgent.getRegion();
+ if (region)
+ {
+ std::string agent_url(region->getCapability("UpdateMaterialAgentInventory"));
+ if (agent_url.empty())
+ {
+ LL_ERRS("MaterialEditor") << "missing required agent inventory cap url" << LL_ENDL;
+ }
+ LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo);
+ }
+ }
+private:
+ LLPermissions mPermissions;
+ std::string mAssetData;
+ std::string mNewName;
+};
+
+void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions)
+{
+ // gen a new uuid for this asset
+ LLTransactionID tid;
+ tid.generate(); // timestamp-based randomization + uniquification
+ LLUUID parent = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_MATERIAL);
+ const U8 subtype = NO_INV_SUBTYPE; // TODO maybe use AT_SETTINGS and LLSettingsType::ST_MATERIAL ?
+
+ LLPointer<LLObjectsMaterialItemCallback> cb = new LLObjectsMaterialItemCallback(permissions, buffer, name);
+ create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent, tid, name, desc,
+ LLAssetType::AT_MATERIAL, LLInventoryType::IT_MATERIAL, subtype, permissions.getMaskNextOwner(),
+ cb);
+}
+
+void LLMaterialEditor::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId)
+{
+ // Update the UI with the new asset.
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", LLSD(itemId));
+ if (me)
+ {
+ if (newItemId.isNull())
+ {
+ me->setAssetId(newAssetId);
+ me->refreshFromInventory();
+ }
+ else if (newItemId.notNull())
+ {
+ // Not supposed to happen?
+ me->refreshFromInventory(newItemId);
+ }
+ else
+ {
+ me->refreshFromInventory(itemId);
+ }
+
+ if (me && !me->mTextureChangesUpdates.empty())
+ {
+ const LLInventoryItem* item = me->getItem();
+ if (item)
+ {
+ // local materials were assigned, force load material and init tracking
+ LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(item->getAssetUUID());
+ for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates)
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat);
+ }
+ }
+ }
+ }
+}
+
+void LLMaterialEditor::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId)
+{
+ LLSD floater_key;
+ floater_key["taskid"] = taskId;
+ floater_key["itemid"] = itemId;
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", floater_key);
+ if (me)
+ {
+ me->setAssetId(newAssetId);
+ me->refreshFromInventory();
+ me->setEnabled(true);
+
+ if (me && !me->mTextureChangesUpdates.empty())
+ {
+ // local materials were assigned, force load material and init tracking
+ LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(newAssetId);
+ for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates)
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat);
+ }
+ }
+ }
+}
+
+void LLMaterialEditor::finishSaveAs(
+ const LLSD &oldKey,
+ const LLUUID &newItemId,
+ const std::string &buffer,
+ bool has_unsaved_changes)
+{
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", oldKey);
+ LLViewerInventoryItem* item = gInventory.getItem(newItemId);
+ if (item)
+ {
+ if (me)
+ {
+ me->mItemUUID = newItemId;
+ me->mObjectUUID = LLUUID::null;
+ me->mNotecardInventoryID = LLUUID::null;
+ me->mNotecardObjectID = LLUUID::null;
+ me->mAuxItem = nullptr;
+ me->setKey(LLSD(newItemId)); // for findTypedInstance
+ me->setMaterialName(item->getName());
+ if (has_unsaved_changes)
+ {
+ if (!updateInventoryItem(buffer, newItemId, LLUUID::null))
+ {
+ me->setEnabled(true);
+ }
+ }
+ else
+ {
+ me->loadAsset();
+ me->setEnabled(true);
+
+ // Local texure support
+ if (!me->mTextureChangesUpdates.empty())
+ {
+ // local materials were assigned, force load material and init tracking
+ LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(item->getAssetUUID());
+ for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates)
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat);
+ }
+ }
+ }
+ }
+ else if(has_unsaved_changes)
+ {
+ updateInventoryItem(buffer, newItemId, LLUUID::null);
+ }
+ }
+ else if (me)
+ {
+ me->setEnabled(true);
+ LL_WARNS("MaterialEditor") << "Item does not exist, floater " << me->getKey() << LL_ENDL;
+ }
+}
+
+void LLMaterialEditor::refreshFromInventory(const LLUUID& new_item_id)
+{
+ if (mIsOverride)
+ {
+ // refreshFromInventory shouldn't be called for overrides,
+ // but just in case.
+ LL_WARNS("MaterialEditor") << "Tried to refresh from inventory for live editor" << LL_ENDL;
+ return;
+ }
+ LLSD old_key = getKey();
+ if (new_item_id.notNull())
+ {
+ mItemUUID = new_item_id;
+ if (mNotecardInventoryID.notNull())
+ {
+ LLSD floater_key;
+ floater_key["objectid"] = mNotecardObjectID;
+ floater_key["notecardid"] = mNotecardInventoryID;
+ setKey(floater_key);
+ }
+ else if (mObjectUUID.notNull())
+ {
+ LLSD floater_key;
+ floater_key["taskid"] = new_item_id;
+ floater_key["itemid"] = mObjectUUID;
+ setKey(floater_key);
+ }
+ else
+ {
+ setKey(LLSD(new_item_id));
+ }
+ }
+ LL_DEBUGS("MaterialEditor") << "New floater key: " << getKey() << " Old key: " << old_key << LL_ENDL;
+ loadAsset();
+}
+
+
+void LLMaterialEditor::onClickSaveAs()
+{
+ if (!LLMaterialEditor::capabilitiesAvailable())
+ {
+ LLNotificationsUtil::add("MissingMaterialCaps");
+ return;
+ }
+
+ if (!can_afford_transaction(mExpectedUploadCost))
+ {
+ LLSD args;
+ args["COST"] = llformat("%d", mExpectedUploadCost);
+ LLNotificationsUtil::add("ErrorCannotAffordUpload", args);
+ return;
+ }
+
+ LLSD args;
+ args["DESC"] = mMaterialName;
+
+ LLNotificationsUtil::add("SaveMaterialAs", args, LLSD(), boost::bind(&LLMaterialEditor::onSaveAsMsgCallback, this, _1, _2));
+}
+
+void LLMaterialEditor::onSaveAsMsgCallback(const LLSD& notification, const LLSD& response)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (0 == option)
+ {
+ std::string new_name = response["message"].asString();
+ LLInventoryObject::correctInventoryName(new_name);
+ if (!new_name.empty())
+ {
+ const LLInventoryItem* item;
+ if (mNotecardInventoryID.notNull())
+ {
+ item = mAuxItem.get();
+ }
+ else
+ {
+ item = getItem();
+ }
+ if (item)
+ {
+ const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
+ LLUUID parent_id = item->getParentUUID();
+ if (mObjectUUID.notNull() || marketplacelistings_id == parent_id || gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID()))
+ {
+ parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL);
+ }
+
+ // A two step process, first copy an existing item, then create new asset
+ if (mNotecardInventoryID.notNull())
+ {
+ LLPointer<LLInventoryCallback> cb = new LLMaterialEditorCopiedCallback(getKey(), new_name);
+ copy_inventory_from_notecard(parent_id,
+ mNotecardObjectID,
+ mNotecardInventoryID,
+ mAuxItem.get(),
+ gInventoryCallbacks.registerCB(cb));
+ }
+ else
+ {
+ std::string buffer = getEncodedAsset();
+ LLPointer<LLInventoryCallback> cb = new LLMaterialEditorCopiedCallback(buffer, getKey(), mUnsavedChanges);
+ copy_inventory_item(
+ gAgent.getID(),
+ item->getPermissions().getOwner(),
+ item->getUUID(),
+ parent_id,
+ new_name,
+ cb);
+ }
+
+ mAssetStatus = PREVIEW_ASSET_LOADING;
+ setEnabled(false);
+ }
+ else
+ {
+ setMaterialName(new_name);
+ onClickSave();
+ }
+ }
+ else
+ {
+ LLNotificationsUtil::add("InvalidMaterialName", LLSD(), LLSD(), [this](const LLSD& notification, const LLSD& response)
+ {
+ LLNotificationsUtil::add("SaveMaterialAs", LLSD().with("DESC", mMaterialName), LLSD(),
+ boost::bind(&LLMaterialEditor::onSaveAsMsgCallback, this, _1, _2));
+ });
+ }
+ }
+}
+
+void LLMaterialEditor::onClickCancel()
+{
+ if (mUnsavedChanges)
+ {
+ LLNotificationsUtil::add("UsavedMaterialChanges", LLSD(), LLSD(), boost::bind(&LLMaterialEditor::onCancelMsgCallback, this, _1, _2));
+ }
+ else
+ {
+ closeFloater();
+ }
+}
+
+void LLMaterialEditor::onCancelMsgCallback(const LLSD& notification, const LLSD& response)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (0 == option)
+ {
+ closeFloater();
+ }
+}
+
+static void pack_textures(
+ LLPointer<LLImageRaw>& base_color_img,
+ LLPointer<LLImageRaw>& normal_img,
+ LLPointer<LLImageRaw>& mr_img,
+ LLPointer<LLImageRaw>& emissive_img,
+ LLPointer<LLImageRaw>& occlusion_img,
+ LLPointer<LLImageJ2C>& base_color_j2c,
+ LLPointer<LLImageJ2C>& normal_j2c,
+ LLPointer<LLImageJ2C>& mr_j2c,
+ LLPointer<LLImageJ2C>& emissive_j2c)
+{
+ // NOTE : remove log spam and lossless vs lossy comparisons when the logs are no longer useful
+
+ if (base_color_img)
+ {
+ base_color_j2c = LLViewerTextureList::convertToUploadFile(base_color_img);
+ LL_DEBUGS("MaterialEditor") << "BaseColor: " << base_color_j2c->getDataSize() << LL_ENDL;
+ }
+
+ if (normal_img)
+ {
+ // create a losslessly compressed version of the normal map
+ normal_j2c = LLViewerTextureList::convertToUploadFile(normal_img, 1024, false, true);
+ LL_DEBUGS("MaterialEditor") << "Normal: " << normal_j2c->getDataSize() << LL_ENDL;
+ }
+
+ if (mr_img)
+ {
+ mr_j2c = LLViewerTextureList::convertToUploadFile(mr_img);
+ LL_DEBUGS("MaterialEditor") << "Metallic/Roughness: " << mr_j2c->getDataSize() << LL_ENDL;
+ }
+
+ if (emissive_img)
+ {
+ emissive_j2c = LLViewerTextureList::convertToUploadFile(emissive_img);
+ LL_DEBUGS("MaterialEditor") << "Emissive: " << emissive_j2c->getDataSize() << LL_ENDL;
+ }
+}
+
+void LLMaterialEditor::uploadMaterialFromModel(const std::string& filename, tinygltf::Model& model_in, S32 index)
+{
+ if (index < 0 || !LLMaterialEditor::capabilitiesAvailable())
+ {
+ return;
+ }
+
+ if (model_in.materials.empty())
+ {
+ // materials are missing
+ return;
+ }
+
+ if (index >= 0 && model_in.materials.size() <= index)
+ {
+ // material is missing
+ return;
+ }
+
+ // Todo: no point in loading whole editor
+ // This uses 'filename' to make sure multiple bulk uploads work
+ // instead of fighting for a single instance.
+ LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor", LLSD().with("filename", filename).with("index", LLSD::Integer(index)));
+ me->loadMaterial(model_in, filename, index, false);
+ me->saveIfNeeded();
+}
+
+
+void LLMaterialEditor::loadMaterialFromFile(const std::string& filename, S32 index)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
+
+ tinygltf::TinyGLTF loader;
+ std::string error_msg;
+ std::string warn_msg;
+
+ bool loaded = false;
+ 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.
+ if (std::string::npos == filename_lc.rfind(".gltf"))
+ { // file is binary
+ loaded = loader.LoadBinaryFromFile(&model_in, &error_msg, &warn_msg, filename);
+ }
+ else
+ { // file is ascii
+ loaded = loader.LoadASCIIFromFile(&model_in, &error_msg, &warn_msg, filename);
+ }
+
+ if (!loaded)
+ {
+ LLNotificationsUtil::add("CannotUploadMaterial");
+ return;
+ }
+
+ if (model_in.materials.empty())
+ {
+ // materials are missing
+ LLNotificationsUtil::add("CannotUploadMaterial");
+ return;
+ }
+
+ if (index >= 0 && model_in.materials.size() <= index)
+ {
+ // material is missing
+ LLNotificationsUtil::add("CannotUploadMaterial");
+ return;
+ }
+
+ LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor");
+
+ if (index >= 0)
+ {
+ // Prespecified material
+ me->loadMaterial(model_in, filename, index);
+ }
+ else if (model_in.materials.size() == 1)
+ {
+ // Only one, just load it
+ me->loadMaterial(model_in, filename, 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();
+
+ for (; mat_iter != mat_end; mat_iter++)
+ {
+ std::string mat_name = mat_iter->name;
+ if (mat_name.empty())
+ {
+ material_list.push_back("Material " + std::to_string(material_list.size()));
+ }
+ else
+ {
+ material_list.push_back(mat_name);
+ }
+ }
+
+ material_list.push_back(me->getString("material_batch_import_text"));
+
+ LLFloaterComboOptions::showUI(
+ [me, model_in, filename](const std::string& option, S32 index)
+ {
+ me->loadMaterial(model_in, filename, index);
+ },
+ me->getString("material_selection_title"),
+ me->getString("material_selection_text"),
+ material_list
+ );
+ }
+}
+
+void LLMaterialEditor::onSelectionChanged()
+{
+ // Drop selection updates if we are waiting for
+ // overrides to finish applying to not reset values
+ // (might need a timeout)
+ if (!mOverrideInProgress)
+ {
+ // mUpdateSignal triggers a lot per frame, breakwater
+ mSelectionNeedsUpdate = true;
+ }
+}
+
+void LLMaterialEditor::updateLive()
+{
+ mSelectionNeedsUpdate = true;
+ mOverrideInProgress = false;
+}
+
+void LLMaterialEditor::loadLive()
+{
+ LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("live_material_editor");
+ if (me)
+ {
+ me->mOverrideInProgress = false;
+ me->setFromSelection();
+
+ // Set up for selection changes updates
+ if (!me->mSelectionUpdateSlot.connected())
+ {
+ me->mSelectionUpdateSlot = LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLMaterialEditor::onSelectionChanged, me));
+ }
+
+ me->openFloater();
+ me->setFocus(true);
+ }
+}
+
+namespace
+{
+ // Which inventory to consult for item permissions
+ enum class ItemSource
+ {
+ // Consult the permissions of the item in the object's inventory. If
+ // the item is not present, then usage of the asset is allowed.
+ OBJECT,
+ // Consult the permissions of the item in the agent's inventory. If
+ // the item is not present, then usage of the asset is not allowed.
+ AGENT
+ };
+
+ class LLAssetIDMatchesWithPerms : public LLInventoryCollectFunctor
+ {
+ public:
+ LLAssetIDMatchesWithPerms(const LLUUID& asset_id, const std::vector<PermissionBit>& ops) : mAssetID(asset_id), mOps(ops) {}
+ virtual ~LLAssetIDMatchesWithPerms() {}
+ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item)
+ {
+ if (!item || item->getAssetUUID() != mAssetID)
+ {
+ return false;
+ }
+ LLPermissions item_permissions = item->getPermissions();
+ for (PermissionBit op : mOps)
+ {
+ if (!gAgent.allowOperation(op, item_permissions, GP_OBJECT_MANIPULATE))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected:
+ LLUUID mAssetID;
+ std::vector<PermissionBit> mOps;
+ };
+};
+
+// *NOTE: permissions_out includes user preferences for new item creation (LLFloaterPerms)
+bool can_use_objects_material(LLSelectedTEGetMatData& func, const std::vector<PermissionBit>& ops, const ItemSource item_source, LLPermissions& permissions_out, LLViewerInventoryItem*& item_out)
+{
+ if (!LLMaterialEditor::capabilitiesAvailable())
+ {
+ return false;
+ }
+
+ // func.mIsOverride=true is used for the singleton material editor floater
+ // associated with the build floater. This flag also excludes objects from
+ // the selection that do not satisfy PERM_MODIFY.
+ llassert(func.mIsOverride);
+ LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/);
+
+ if (item_source == ItemSource::AGENT)
+ {
+ func.mObjectId = LLUUID::null;
+ }
+ LLViewerObject* selected_object = func.mObject;
+ if (!selected_object)
+ {
+ // LLSelectedTEGetMatData can fail if there are no selected faces
+ // with materials, but we expect at least some object is selected.
+ llassert(LLSelectMgr::getInstance()->getSelection()->getFirstObject());
+ return false;
+ }
+ if (selected_object->isInventoryPending())
+ {
+ return false;
+ }
+ for (PermissionBit op : ops)
+ {
+ if (op == PERM_MODIFY && selected_object->isPermanentEnforced())
+ {
+ return false;
+ }
+ }
+
+ // Look for the item to base permissions off of
+ item_out = nullptr;
+ const bool blank_material = func.mMaterialId == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID;
+ if (!blank_material)
+ {
+ LLAssetIDMatchesWithPerms item_has_perms(func.mMaterialId, ops);
+ if (item_source == ItemSource::OBJECT)
+ {
+ LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId);
+ if (item && !item_has_perms(nullptr, item))
+ {
+ return false;
+ }
+ item_out = item;
+ }
+ else
+ {
+ llassert(item_source == ItemSource::AGENT);
+
+ LLViewerInventoryCategory::cat_array_t cats;
+ LLViewerInventoryItem::item_array_t items;
+ gInventory.collectDescendentsIf(LLUUID::null,
+ cats,
+ items,
+ // *NOTE: PBRPickerAgentListener will need
+ // to be changed if checking the trash is
+ // disabled
+ LLInventoryModel::INCLUDE_TRASH,
+ item_has_perms);
+ if (items.empty())
+ {
+ return false;
+ }
+ item_out = items[0];
+ }
+ }
+
+ LLPermissions item_permissions;
+ if (item_out)
+ {
+ item_permissions = item_out->getPermissions();
+ // Update flags for new owner
+ if (!item_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true))
+ {
+ llassert(false);
+ return false;
+ }
+ }
+ else
+ {
+ item_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
+ }
+
+ // Use root object for permissions checking
+ LLViewerObject* root_object = selected_object->getRootEdit();
+ LLPermissions* object_permissions_p = LLSelectMgr::getInstance()->findObjectPermissions(root_object);
+ LLPermissions object_permissions;
+ if (object_permissions_p)
+ {
+ object_permissions.set(*object_permissions_p);
+ for (PermissionBit op : ops)
+ {
+ if (!gAgent.allowOperation(op, object_permissions, GP_OBJECT_MANIPULATE))
+ {
+ return false;
+ }
+ }
+ // Update flags for new owner
+ if (!object_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true))
+ {
+ llassert(false);
+ return false;
+ }
+ }
+ else
+ {
+ object_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
+ }
+
+ LLPermissions floater_perm;
+ floater_perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
+ floater_perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Materials"));
+ floater_perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Materials"));
+ floater_perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Materials"));
+
+ // *NOTE: A close inspection of LLPermissions::accumulate shows that
+ // conflicting UUIDs will be unset. This is acceptable behavior for now.
+ // The server will populate creator info based on the item creation method
+ // used.
+ // *NOTE: As far as I'm aware, there is currently no good way to preserve
+ // creation history when there's no material item present. In that case,
+ // the agent who saved the material will be considered the creator.
+ // -Cosmic,2023-08-07
+ if (item_source == ItemSource::AGENT)
+ {
+ llassert(blank_material || item_out); // See comment at ItemSource::AGENT definition
+
+ permissions_out.set(item_permissions);
+ }
+ else
+ {
+ llassert(item_source == ItemSource::OBJECT);
+
+ if (item_out)
+ {
+ permissions_out.set(item_permissions);
+ }
+ else
+ {
+ permissions_out.set(object_permissions);
+ }
+ }
+ permissions_out.accumulate(floater_perm);
+
+ return true;
+}
+
+bool LLMaterialEditor::canModifyObjectsMaterial()
+{
+ LLSelectedTEGetMatData func(true);
+ LLPermissions permissions;
+ LLViewerInventoryItem* item_out;
+ return can_use_objects_material(func, std::vector<PermissionBit>({PERM_MODIFY}), ItemSource::OBJECT, permissions, item_out);
+}
+
+bool LLMaterialEditor::canSaveObjectsMaterial()
+{
+ LLSelectedTEGetMatData func(true);
+ LLPermissions permissions;
+ LLViewerInventoryItem* item_out;
+ return can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item_out);
+}
+
+bool LLMaterialEditor::canClipboardObjectsMaterial()
+{
+ if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() != 1)
+ {
+ return false;
+ }
+
+ struct LLSelectedTEGetNullMat : public LLSelectedTEFunctor
+ {
+ bool apply(LLViewerObject* objectp, S32 te_index)
+ {
+ return objectp->getRenderMaterialID(te_index).isNull();
+ }
+ } null_func;
+
+ if (LLSelectMgr::getInstance()->getSelection()->applyToTEs(&null_func))
+ {
+ return true;
+ }
+
+ LLSelectedTEGetMatData func(true);
+ LLPermissions permissions;
+ LLViewerInventoryItem* item_out;
+ return can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY, PERM_TRANSFER}), ItemSource::OBJECT, permissions, item_out);
+}
+
+void LLMaterialEditor::saveObjectsMaterialAs()
+{
+ LLSelectedTEGetMatData func(true);
+ LLPermissions permissions;
+ LLViewerInventoryItem* item = nullptr;
+ bool allowed = can_use_objects_material(func, std::vector<PermissionBit>({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item);
+ if (!allowed)
+ {
+ LL_WARNS("MaterialEditor") << "Failed to save GLTF material from object" << LL_ENDL;
+ return;
+ }
+ const LLUUID item_id = item ? item->getUUID() : LLUUID::null;
+ saveObjectsMaterialAs(func.mMaterial, func.mLocalMaterial, permissions, func.mObjectId, item_id);
+}
+
+
+void LLMaterialEditor::saveObjectsMaterialAs(const LLGLTFMaterial* render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id, const LLUUID& item_id)
+{
+ if (local_material)
+ {
+ // This is a local material, reload it from file
+ // so that user won't end up with grey textures
+ // on next login.
+ LLMaterialEditor::loadMaterialFromFile(local_material->getFilename(), local_material->getIndexInFile());
+
+ LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor");
+ if (me)
+ {
+ // don't use override material here, it has 'hacked ids'
+ // and values, use end result, apply it on top of local.
+ const LLColor4& base_color = render_material->mBaseColor;
+ me->setBaseColor(LLColor3(base_color));
+ me->setTransparency(base_color[VW]);
+ me->setMetalnessFactor(render_material->mMetallicFactor);
+ me->setRoughnessFactor(render_material->mRoughnessFactor);
+ me->setEmissiveColor(render_material->mEmissiveColor);
+ me->setDoubleSided(render_material->mDoubleSided);
+ me->setAlphaMode(render_material->getAlphaMode());
+ me->setAlphaCutoff(render_material->mAlphaCutoff);
+
+ // most things like colors we can apply without verifying
+ // but texture ids are going to be different from both, base and override
+ // so only apply override id if there is actually a difference
+ if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR])
+ {
+ me->setBaseColorId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]);
+ me->childSetValue("base_color_upload_fee", me->getString("no_upload_fee_string"));
+ }
+ if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL])
+ {
+ me->setNormalId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]);
+ me->childSetValue("normal_upload_fee", me->getString("no_upload_fee_string"));
+ }
+ if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS])
+ {
+ me->setMetallicRoughnessId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]);
+ me->childSetValue("metallic_upload_fee", me->getString("no_upload_fee_string"));
+ }
+ if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE])
+ {
+ me->setEmissiveId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]);
+ me->childSetValue("emissive_upload_fee", me->getString("no_upload_fee_string"));
+ }
+
+ // recalculate upload prices
+ me->markChangesUnsaved(0);
+ }
+
+ return;
+ }
+
+ LLSD payload;
+ if (render_material)
+ {
+ // Make a copy of the render material with unsupported transforms removed
+ LLGLTFMaterial asset_material = *render_material;
+ asset_material.sanitizeAssetMaterial();
+ // Serialize the sanitized render material
+ payload["data"] = asset_material.asJSON();
+ }
+ else
+ {
+ // Menu shouldn't allow this, but as a fallback
+ // pick defaults from a blank material
+ LLGLTFMaterial blank_mat;
+ payload["data"] = blank_mat.asJSON();
+ LL_WARNS() << "Got no material when trying to save material" << LL_ENDL;
+ }
+
+ LLSD args;
+ args["DESC"] = LLTrans::getString("New Material");
+
+ if (local_material)
+ {
+ LLPermissions local_permissions;
+ local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
+ LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, local_permissions));
+ }
+ else
+ {
+ llassert(object_id.isNull()); // Case for copying item from object inventory is no longer implemented
+ LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, permissions));
+ }
+}
+
+// static
+void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (0 != option)
+ {
+ return;
+ }
+
+ LLSD asset;
+ asset["version"] = LLGLTFMaterial::ASSET_VERSION;
+ asset["type"] = LLGLTFMaterial::ASSET_TYPE;
+ // This is the string serialized from LLGLTFMaterial::asJSON
+ asset["data"] = notification["payload"]["data"];
+
+ std::ostringstream str;
+ LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY);
+
+ std::string new_name = response["message"].asString();
+ LLInventoryObject::correctInventoryName(new_name);
+ if (new_name.empty())
+ {
+ return;
+ }
+
+ createInventoryItem(str.str(), new_name, std::string(), permissions);
+}
+
+const void upload_bulk(const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter type);
+
+void LLMaterialEditor::loadMaterial(const tinygltf::Model &model_in, const std::string &filename, S32 index, bool open_floater)
+{
+ if (index == model_in.materials.size())
+ {
+ // bulk upload all the things
+ upload_bulk({ filename }, LLFilePicker::FFLOAD_MATERIAL);
+ return;
+ }
+
+ if (model_in.materials.size() <= index)
+ {
+ return;
+ }
+ std::string folder = gDirUtilp->getDirName(filename);
+
+ tinygltf::Material material_in = model_in.materials[index];
+
+ tinygltf::Model model_out;
+ model_out.asset.version = "2.0";
+ model_out.materials.resize(1);
+
+ // get base color texture
+ LLPointer<LLImageRaw> base_color_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.baseColorTexture.index, mBaseColorName);
+ // get normal map
+ LLPointer<LLImageRaw> normal_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.normalTexture.index, mNormalName);
+ // get metallic-roughness texture
+ LLPointer<LLImageRaw> mr_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.metallicRoughnessTexture.index, mMetallicRoughnessName);
+ // get emissive texture
+ LLPointer<LLImageRaw> emissive_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.emissiveTexture.index, mEmissiveName);
+ // get occlusion map if needed
+ LLPointer<LLImageRaw> occlusion_img;
+ if (material_in.occlusionTexture.index != material_in.pbrMetallicRoughness.metallicRoughnessTexture.index)
+ {
+ std::string tmp;
+ occlusion_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.occlusionTexture.index, tmp);
+ }
+
+ LLTinyGLTFHelper::initFetchedTextures(material_in, base_color_img, normal_img, mr_img, emissive_img, occlusion_img,
+ mBaseColorFetched, mNormalFetched, mMetallicRoughnessFetched, mEmissiveFetched);
+ pack_textures(base_color_img, normal_img, mr_img, emissive_img, occlusion_img,
+ mBaseColorJ2C, mNormalJ2C, mMetallicRoughnessJ2C, mEmissiveJ2C);
+
+ LLUUID base_color_id;
+ if (mBaseColorFetched.notNull())
+ {
+ mBaseColorFetched->forceToSaveRawImage(0, F32_MAX);
+ base_color_id = mBaseColorFetched->getID();
+
+ if (mBaseColorName.empty())
+ {
+ mBaseColorName = MATERIAL_BASE_COLOR_DEFAULT_NAME;
+ }
+ }
+
+ LLUUID normal_id;
+ if (mNormalFetched.notNull())
+ {
+ mNormalFetched->forceToSaveRawImage(0, F32_MAX);
+ normal_id = mNormalFetched->getID();
+
+ if (mNormalName.empty())
+ {
+ mNormalName = MATERIAL_NORMAL_DEFAULT_NAME;
+ }
+ }
+
+ LLUUID mr_id;
+ if (mMetallicRoughnessFetched.notNull())
+ {
+ mMetallicRoughnessFetched->forceToSaveRawImage(0, F32_MAX);
+ mr_id = mMetallicRoughnessFetched->getID();
+
+ if (mMetallicRoughnessName.empty())
+ {
+ mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME;
+ }
+ }
+
+ LLUUID emissive_id;
+ if (mEmissiveFetched.notNull())
+ {
+ mEmissiveFetched->forceToSaveRawImage(0, F32_MAX);
+ emissive_id = mEmissiveFetched->getID();
+
+ if (mEmissiveName.empty())
+ {
+ mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME;
+ }
+ }
+
+ setBaseColorId(base_color_id);
+ setBaseColorUploadId(base_color_id);
+ setMetallicRoughnessId(mr_id);
+ setMetallicRoughnessUploadId(mr_id);
+ setEmissiveId(emissive_id);
+ setEmissiveUploadId(emissive_id);
+ setNormalId(normal_id);
+ setNormalUploadId(normal_id);
+
+ setFromGltfModel(model_in, index);
+
+ setFromGltfMetaData(filename, model_in, index);
+
+ if (getDoubleSided())
+ {
+ // SL-19392 Double sided materials double the number of pixels that must be rasterized,
+ // and a great many tools that export GLTF simply leave double sided enabled whether
+ // or not it is necessary.
+ LL_DEBUGS("MaterialEditor") << "Defaulting Double Sided to disabled on import" << LL_ENDL;
+ setDoubleSided(false);
+ }
+
+ markChangesUnsaved(U32_MAX);
+
+ if (open_floater)
+ {
+ openFloater(getKey());
+ setFocus(true);
+ setCanSave(true);
+ setCanSaveAs(true);
+
+ applyToSelection();
+ }
+}
+
+bool LLMaterialEditor::setFromGltfModel(const tinygltf::Model& model, S32 index, bool set_textures)
+{
+ if (model.materials.size() > index)
+ {
+ const tinygltf::Material& material_in = model.materials[index];
+
+ if (set_textures)
+ {
+ S32 index;
+ LLUUID id;
+
+ // get base color texture
+ index = material_in.pbrMetallicRoughness.baseColorTexture.index;
+ if (index >= 0)
+ {
+ id.set(model.images[index].uri);
+ setBaseColorId(id);
+ }
+ else
+ {
+ setBaseColorId(LLUUID::null);
+ }
+
+ // get normal map
+ index = material_in.normalTexture.index;
+ if (index >= 0)
+ {
+ id.set(model.images[index].uri);
+ setNormalId(id);
+ }
+ else
+ {
+ setNormalId(LLUUID::null);
+ }
+
+ // get metallic-roughness texture
+ index = material_in.pbrMetallicRoughness.metallicRoughnessTexture.index;
+ if (index >= 0)
+ {
+ id.set(model.images[index].uri);
+ setMetallicRoughnessId(id);
+ }
+ else
+ {
+ setMetallicRoughnessId(LLUUID::null);
+ }
+
+ // get emissive texture
+ index = material_in.emissiveTexture.index;
+ if (index >= 0)
+ {
+ id.set(model.images[index].uri);
+ setEmissiveId(id);
+ }
+ else
+ {
+ setEmissiveId(LLUUID::null);
+ }
+ }
+
+ setAlphaMode(material_in.alphaMode);
+ setAlphaCutoff(material_in.alphaCutoff);
+
+ setBaseColor(LLTinyGLTFHelper::getColor(material_in.pbrMetallicRoughness.baseColorFactor));
+ setEmissiveColor(LLTinyGLTFHelper::getColor(material_in.emissiveFactor));
+
+ setMetalnessFactor(material_in.pbrMetallicRoughness.metallicFactor);
+ setRoughnessFactor(material_in.pbrMetallicRoughness.roughnessFactor);
+
+ setDoubleSided(material_in.doubleSided);
+ }
+
+ return true;
+}
+
+/**
+ * Build a texture name from the contents of the (in tinyGLFT parlance)
+ * Image URI. This often is filepath to the original image on the users'
+ * local file system.
+ */
+const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, const std::string texture_type)
+{
+ // getBaseFileName() works differently on each platform and file patchs
+ // can contain both types of delimiter so unify them then extract the
+ // base name (no path or extension)
+ std::replace(image_uri.begin(), image_uri.end(), '\\', gDirUtilp->getDirDelimiter()[0]);
+ std::replace(image_uri.begin(), image_uri.end(), '/', gDirUtilp->getDirDelimiter()[0]);
+ const bool strip_extension = true;
+ std::string stripped_uri = gDirUtilp->getBaseFileName(image_uri, strip_extension);
+
+ // sometimes they can be really long and unwieldy - 64 chars is enough for anyone :)
+ const int max_texture_name_length = 64;
+ if (stripped_uri.length() > max_texture_name_length)
+ {
+ stripped_uri = stripped_uri.substr(0, max_texture_name_length - 1);
+ }
+
+ // We intend to append the type of texture (base color, emissive etc.) to the
+ // name of the texture but sometimes the creator already did that. To try
+ // to avoid repeats (not perfect), we look for the texture type in the name
+ // and if we find it, do not append the type, later on. One way this fails
+ // (and it's fine for now) is I see some texture/image uris have a name like
+ // "metallic roughness" and of course, that doesn't match our predefined
+ // name "metallicroughness" - consider fix later..
+ bool name_includes_type = false;
+ std::string stripped_uri_lower = stripped_uri;
+ LLStringUtil::toLower(stripped_uri_lower);
+ stripped_uri_lower.erase(std::remove_if(stripped_uri_lower.begin(), stripped_uri_lower.end(), isspace), stripped_uri_lower.end());
+ std::string texture_type_lower = texture_type;
+ LLStringUtil::toLower(texture_type_lower);
+ texture_type_lower.erase(std::remove_if(texture_type_lower.begin(), texture_type_lower.end(), isspace), texture_type_lower.end());
+ if (stripped_uri_lower.find(texture_type_lower) != std::string::npos)
+ {
+ name_includes_type = true;
+ }
+
+ // uri doesn't include the type at all
+ if (!name_includes_type)
+ {
+ // uri doesn't include the type and the uri is not empty
+ // so we can include everything
+ if (stripped_uri.length() > 0)
+ {
+ // example "DamagedHelmet: base layer"
+ return STRINGIZE(
+ mMaterialNameShort <<
+ ": " <<
+ stripped_uri <<
+ " (" <<
+ texture_type <<
+ ")"
+ );
+ }
+ else
+ // uri doesn't include the type (because the uri is empty)
+ // so we must reorganize the string a bit to include the name
+ // and an explicit name type
+ {
+ // example "DamagedHelmet: (Emissive)"
+ return STRINGIZE(
+ mMaterialNameShort <<
+ " (" <<
+ texture_type <<
+ ")"
+ );
+ }
+ }
+ else
+ // uri includes the type so just use it directly with the
+ // name of the material
+ {
+ return STRINGIZE(
+ // example: AlienBust: normal_layer
+ mMaterialNameShort <<
+ ": " <<
+ stripped_uri
+ );
+ }
+}
+
+/**
+ * Update the metadata for the material based on what we find in the loaded
+ * file (along with some assumptions and interpretations...). Fields include
+ * the name of the material, a material description and the names of the
+ * composite textures.
+ */
+void LLMaterialEditor::setFromGltfMetaData(const std::string& filename, const tinygltf::Model& model, S32 index)
+{
+ // Use the name (without any path/extension) of the file that was
+ // uploaded as the base of the material name. Then if the name of the
+ // scene is present and not blank, append that and use the result as
+ // the name of the material. This is a first pass at creating a
+ // naming scheme that is useful to real content creators and hopefully
+ // avoid 500 materials in your inventory called "scene" or "Default"
+ const bool strip_extension = true;
+ std::string base_filename = gDirUtilp->getBaseFileName(filename, strip_extension);
+
+ // Extract the name of the scene. Note it is often blank or some very
+ // generic name like "Scene" or "Default" so using this in the name
+ // is less useful than you might imagine.
+ std::string material_name;
+ if (model.materials.size() > index && !model.materials[index].name.empty())
+ {
+ material_name = model.materials[index].name;
+ }
+ else if (model.scenes.size() > 0)
+ {
+ const tinygltf::Scene& scene_in = model.scenes[0];
+ if (scene_in.name.length())
+ {
+ material_name = scene_in.name;
+ }
+ else
+ {
+ // scene name is empty so no point using it
+ }
+ }
+ else
+ {
+ // scene name isn't present so no point using it
+ }
+
+ // If we have a valid material or scene name, use it to build the short and
+ // long versions of the material name. The long version is used
+ // as you might expect, for the material name. The short version is
+ // used as part of the image/texture name - the theory is that will
+ // allow content creators to track the material and the corresponding
+ // textures
+ if (material_name.length())
+ {
+ mMaterialNameShort = base_filename;
+
+ mMaterialName = STRINGIZE(
+ base_filename <<
+ " " <<
+ "(" <<
+ material_name <<
+ ")"
+ );
+ }
+ else
+ // otherwise, just use the trimmed filename as is
+ {
+ mMaterialNameShort = base_filename;
+ mMaterialName = base_filename;
+ }
+
+ // sanitize the material name so that it's compatible with the inventory
+ LLInventoryObject::correctInventoryName(mMaterialName);
+ LLInventoryObject::correctInventoryName(mMaterialNameShort);
+
+ // We also set the title of the floater to match the
+ // name of the material
+ setTitle(mMaterialName);
+
+ /**
+ * Extract / derive the names of each composite texture. For each, the
+ * index is used to to determine which of the "Images" is used. If the index
+ * is -1 then that texture type is not present in the material (Seems to be
+ * quite common that a material is missing 1 or more types of texture)
+ */
+ if (model.materials.size() > index)
+ {
+ const tinygltf::Material& first_material = model.materials[index];
+
+ mBaseColorName = MATERIAL_BASE_COLOR_DEFAULT_NAME;
+ // note: unlike the other textures, base color doesn't have its own entry
+ // in the tinyGLTF Material struct. Rather, it is taken from a
+ // sub-texture in the pbrMetallicRoughness member
+ int index = first_material.pbrMetallicRoughness.baseColorTexture.index;
+ if (index > -1 && index < model.images.size())
+ {
+ // sanitize the name we decide to use for each texture
+ std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_BASE_COLOR_DEFAULT_NAME);
+ LLInventoryObject::correctInventoryName(texture_name);
+ mBaseColorName = texture_name;
+ }
+
+ mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME;
+ index = first_material.emissiveTexture.index;
+ if (index > -1 && index < model.images.size())
+ {
+ std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_EMISSIVE_DEFAULT_NAME);
+ LLInventoryObject::correctInventoryName(texture_name);
+ mEmissiveName = texture_name;
+ }
+
+ mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME;
+ index = first_material.pbrMetallicRoughness.metallicRoughnessTexture.index;
+ if (index > -1 && index < model.images.size())
+ {
+ std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_METALLIC_DEFAULT_NAME);
+ LLInventoryObject::correctInventoryName(texture_name);
+ mMetallicRoughnessName = texture_name;
+ }
+
+ mNormalName = MATERIAL_NORMAL_DEFAULT_NAME;
+ index = first_material.normalTexture.index;
+ if (index > -1 && index < model.images.size())
+ {
+ std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_NORMAL_DEFAULT_NAME);
+ LLInventoryObject::correctInventoryName(texture_name);
+ mNormalName = texture_name;
+ }
+ }
+}
+
+void LLMaterialEditor::importMaterial()
+{
+ LLFilePickerReplyThread::startPicker(
+ [](const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter)
+ {
+ if (LLAppViewer::instance()->quitRequested())
+ {
+ return;
+ }
+ if (filenames.size() > 0)
+ {
+ LLMaterialEditor::loadMaterialFromFile(filenames[0], -1);
+ }
+ },
+ LLFilePicker::FFLOAD_MATERIAL,
+ true);
+}
+
+class LLRenderMaterialFunctor : public LLSelectedTEFunctor
+{
+public:
+ LLRenderMaterialFunctor(const LLUUID &id)
+ : mMatId(id)
+ {
+ }
+
+ bool apply(LLViewerObject* objectp, S32 te) override
+ {
+ if (objectp && objectp->permModify() && objectp->getVolume())
+ {
+ LLVOVolume* vobjp = (LLVOVolume*)objectp;
+ vobjp->setRenderMaterialID(te, mMatId, false /*preview only*/);
+ vobjp->updateTEMaterialTextures(te);
+ }
+ return true;
+ }
+private:
+ LLUUID mMatId;
+};
+
+class LLRenderMaterialOverrideFunctor : public LLSelectedNodeFunctor
+{
+public:
+ LLRenderMaterialOverrideFunctor(
+ LLMaterialEditor * me,
+ const LLUUID &report_on_object_id,
+ S32 report_on_te)
+ : mEditor(me)
+ , mSuccess(false)
+ , mObjectId(report_on_object_id)
+ , mObjectTE(report_on_te)
+ {
+ }
+
+ virtual bool apply(LLSelectNode* nodep) override
+ {
+ LLViewerObject* objectp = nodep->getObject();
+ if (!objectp || !objectp->permModify() || !objectp->getVolume())
+ {
+ return false;
+ }
+ S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces
+
+ // post override from given object and te to the simulator
+ // requestData should have:
+ // object_id - UUID of LLViewerObject
+ // side - S32 index of texture entry
+ // gltf_json - String of GLTF json for override data
+
+ for (S32 te = 0; te < num_tes; ++te)
+ {
+ if (!nodep->isTESelected(te))
+ {
+ continue;
+ }
+
+ // Get material from object
+ // Selection can cover multiple objects, and live editor is
+ // supposed to overwrite changed values only
+ LLTextureEntry* tep = objectp->getTE(te);
+
+ if (tep->getGLTFMaterial() == nullptr)
+ {
+ // overrides are not supposed to work or apply if
+ // there is no base material to work from
+ continue;
+ }
+
+ LLPointer<LLGLTFMaterial> material = tep->getGLTFMaterialOverride();
+ // make a copy to not invalidate existing
+ // material for multiple objects
+ if (material.isNull())
+ {
+ // Start with a material override which does not make any changes
+ material = new LLGLTFMaterial();
+ }
+ else
+ {
+ material = new LLGLTFMaterial(*material);
+ }
+
+ U32 changed_flags = mEditor->getUnsavedChangesFlags();
+ U32 reverted_flags = mEditor->getRevertedChangesFlags();
+
+ LLPointer<LLGLTFMaterial> revert_mat;
+ if (nodep->mSavedGLTFOverrideMaterials.size() > te)
+ {
+ if (nodep->mSavedGLTFOverrideMaterials[te].notNull())
+ {
+ revert_mat = nodep->mSavedGLTFOverrideMaterials[te];
+ }
+ else
+ {
+ // mSavedGLTFOverrideMaterials[te] being present but null
+ // means we need to use a default value
+ revert_mat = new LLGLTFMaterial();
+ }
+ }
+ // else can not revert at all
+
+ // Override object's values with values from editor where appropriate
+ if (changed_flags & MATERIAL_BASE_COLOR_DIRTY)
+ {
+ material->setBaseColorFactor(mEditor->getBaseColor(), true);
+ }
+ else if ((reverted_flags & MATERIAL_BASE_COLOR_DIRTY) && revert_mat.notNull())
+ {
+ material->setBaseColorFactor(revert_mat->mBaseColor, false);
+ }
+
+ if (changed_flags & MATERIAL_BASE_COLOR_TEX_DIRTY)
+ {
+ material->setBaseColorId(mEditor->getBaseColorId(), true);
+ LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_BASE_COLOR_TEX_DIRTY);
+ if (tracking_id.notNull())
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
+ }
+ }
+ else if ((reverted_flags & MATERIAL_BASE_COLOR_TEX_DIRTY) && revert_mat.notNull())
+ {
+ material->setBaseColorId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR], false);
+ LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_BASE_COLOR_TEX_DIRTY);
+ if (tracking_id.notNull())
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
+ }
+ }
+
+ if (changed_flags & MATERIAL_NORMAL_TEX_DIRTY)
+ {
+ material->setNormalId(mEditor->getNormalId(), true);
+ LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_NORMAL_TEX_DIRTY);
+ if (tracking_id.notNull())
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
+ }
+ }
+ else if ((reverted_flags & MATERIAL_NORMAL_TEX_DIRTY) && revert_mat.notNull())
+ {
+ material->setNormalId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL], false);
+ LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_NORMAL_TEX_DIRTY);
+ if (tracking_id.notNull())
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
+ }
+ }
+
+ if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY)
+ {
+ material->setOcclusionRoughnessMetallicId(mEditor->getMetallicRoughnessId(), true);
+ LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY);
+ if (tracking_id.notNull())
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
+ }
+ }
+ else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY) && revert_mat.notNull())
+ {
+ material->setOcclusionRoughnessMetallicId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS], false);
+ LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY);
+ if (tracking_id.notNull())
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
+ }
+ }
+
+ if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY)
+ {
+ material->setMetallicFactor(mEditor->getMetalnessFactor(), true);
+ }
+ else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY) && revert_mat.notNull())
+ {
+ material->setMetallicFactor(revert_mat->mMetallicFactor, false);
+ }
+
+ if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY)
+ {
+ material->setRoughnessFactor(mEditor->getRoughnessFactor(), true);
+ }
+ else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY) && revert_mat.notNull())
+ {
+ material->setRoughnessFactor(revert_mat->mRoughnessFactor, false);
+ }
+
+ if (changed_flags & MATERIAL_EMISIVE_COLOR_DIRTY)
+ {
+ material->setEmissiveColorFactor(LLColor3(mEditor->getEmissiveColor()), true);
+ }
+ else if ((reverted_flags & MATERIAL_EMISIVE_COLOR_DIRTY) && revert_mat.notNull())
+ {
+ material->setEmissiveColorFactor(revert_mat->mEmissiveColor, false);
+ }
+
+ if (changed_flags & MATERIAL_EMISIVE_TEX_DIRTY)
+ {
+ material->setEmissiveId(mEditor->getEmissiveId(), true);
+ LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_EMISIVE_TEX_DIRTY);
+ if (tracking_id.notNull())
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
+ }
+ }
+ else if ((reverted_flags & MATERIAL_EMISIVE_TEX_DIRTY) && revert_mat.notNull())
+ {
+ material->setEmissiveId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE], false);
+ LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_EMISIVE_TEX_DIRTY);
+ if (tracking_id.notNull())
+ {
+ LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material);
+ }
+ }
+
+ if (changed_flags & MATERIAL_DOUBLE_SIDED_DIRTY)
+ {
+ material->setDoubleSided(mEditor->getDoubleSided(), true);
+ }
+ else if ((reverted_flags & MATERIAL_DOUBLE_SIDED_DIRTY) && revert_mat.notNull())
+ {
+ material->setDoubleSided(revert_mat->mDoubleSided, false);
+ }
+
+ if (changed_flags & MATERIAL_ALPHA_MODE_DIRTY)
+ {
+ material->setAlphaMode(mEditor->getAlphaMode(), true);
+ }
+ else if ((reverted_flags & MATERIAL_ALPHA_MODE_DIRTY) && revert_mat.notNull())
+ {
+ material->setAlphaMode(revert_mat->mAlphaMode, false);
+ }
+
+ if (changed_flags & MATERIAL_ALPHA_CUTOFF_DIRTY)
+ {
+ material->setAlphaCutoff(mEditor->getAlphaCutoff(), true);
+ }
+ else if ((reverted_flags & MATERIAL_ALPHA_CUTOFF_DIRTY) && revert_mat.notNull())
+ {
+ material->setAlphaCutoff(revert_mat->mAlphaCutoff, false);
+ }
+
+ if (mObjectTE == te
+ && mObjectId == objectp->getID())
+ {
+ mSuccess = true;
+ }
+ LLGLTFMaterialList::queueModify(objectp, te, material);
+ }
+ return true;
+ }
+
+ static void modifyCallback(bool success)
+ {
+ if (!success)
+ {
+ // something went wrong update selection
+ LLMaterialEditor::updateLive();
+ }
+ // else we will get updateLive() from panel face
+ }
+
+ bool getResult() { return mSuccess; }
+
+private:
+ LLMaterialEditor * mEditor;
+ LLUUID mObjectId;
+ S32 mObjectTE;
+ bool mSuccess;
+};
+
+void LLMaterialEditor::applyToSelection()
+{
+ if (!mIsOverride)
+ {
+ // Only apply if working with 'live' materials
+ // Might need a better way to distinguish 'live' mode.
+ // But only one live edit is supposed to work at a time
+ // as a pair to tools floater.
+ return;
+ }
+
+ std::string url = gAgent.getRegionCapability("ModifyMaterialParams");
+ if (!url.empty())
+ {
+ // Don't send data if there is nothing to send.
+ // Some UI elements will cause multiple commits,
+ // like spin ctrls on click and on down
+ if (mUnsavedChanges != 0 || mRevertedChanges != 0)
+ {
+ mOverrideInProgress = true;
+ LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection();
+ LLRenderMaterialOverrideFunctor override_func(this, mOverrideObjectId, mOverrideObjectTE);
+ selected_objects->applyToNodes(&override_func);
+
+ void(*done_callback)(bool) = LLRenderMaterialOverrideFunctor::modifyCallback;
+
+ LLGLTFMaterialList::flushUpdates(done_callback);
+
+ if (!override_func.getResult())
+ {
+ // OverrideFunctor didn't find expected object or face
+ mOverrideInProgress = false;
+ }
+
+ // we posted all changes
+ mUnsavedChanges = 0;
+ mRevertedChanges = 0;
+ }
+ }
+ else
+ {
+ LL_WARNS("MaterialEditor") << "Not connected to materials capable region, missing ModifyMaterialParams cap" << LL_ENDL;
+
+ // Fallback local preview. Will be removed once override systems is finished and new cap is deployed everywhere.
+ LLPointer<LLFetchedGLTFMaterial> mat = new LLFetchedGLTFMaterial();
+ getGLTFMaterial(mat);
+ static const LLUUID placeholder("984e183e-7811-4b05-a502-d79c6f978a98");
+ gGLTFMaterialList.addMaterial(placeholder, mat);
+ LLRenderMaterialFunctor mat_func(placeholder);
+ LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection();
+ selected_objects->applyToTEs(&mat_func);
+ }
+}
+
+// Get a dump of the json representation of the current state of the editor UI
+// in GLTF format, excluding transforms as they are not supported in material
+// assets. (See also LLGLTFMaterial::sanitizeAssetMaterial())
+void LLMaterialEditor::getGLTFMaterial(LLGLTFMaterial* mat)
+{
+ mat->mBaseColor = getBaseColor();
+ mat->mBaseColor.mV[3] = getTransparency();
+ mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] = getBaseColorId();
+
+ mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] = getNormalId();
+
+ mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] = getMetallicRoughnessId();
+ mat->mMetallicFactor = getMetalnessFactor();
+ mat->mRoughnessFactor = getRoughnessFactor();
+
+ mat->mEmissiveColor = getEmissiveColor();
+ mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] = getEmissiveId();
+
+ mat->mDoubleSided = getDoubleSided();
+ mat->setAlphaMode(getAlphaMode());
+ mat->mAlphaCutoff = getAlphaCutoff();
+}
+
+void LLMaterialEditor::setFromGLTFMaterial(LLGLTFMaterial* mat)
+{
+ setBaseColor(mat->mBaseColor);
+ setBaseColorId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]);
+ setNormalId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]);
+
+ setMetallicRoughnessId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]);
+ setMetalnessFactor(mat->mMetallicFactor);
+ setRoughnessFactor(mat->mRoughnessFactor);
+
+ setEmissiveColor(mat->mEmissiveColor);
+ setEmissiveId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]);
+
+ setDoubleSided(mat->mDoubleSided);
+ setAlphaMode(mat->getAlphaMode());
+ setAlphaCutoff(mat->mAlphaCutoff);
+
+ if (mat->hasLocalTextures())
+ {
+ for (LLGLTFMaterial::local_tex_map_t::value_type &val : mat->mTrackingIdToLocalTexture)
+ {
+ LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(val.first);
+ if (val.second != world_id)
+ {
+ LL_WARNS() << "world id mismatch" << LL_ENDL;
+ }
+ if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR])
+ {
+ subscribeToLocalTexture(MATERIAL_BASE_COLOR_TEX_DIRTY, val.first);
+ }
+ if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS])
+ {
+ subscribeToLocalTexture(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY, val.first);
+ }
+ if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE])
+ {
+ subscribeToLocalTexture(MATERIAL_EMISIVE_TEX_DIRTY, val.first);
+ }
+ if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL])
+ {
+ subscribeToLocalTexture(MATERIAL_NORMAL_TEX_DIRTY, val.first);
+ }
+ }
+ }
+}
+
+bool LLMaterialEditor::setFromSelection()
+{
+ LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection();
+ LLSelectedTEGetMatData func(mIsOverride);
+
+ selected_objects->applyToTEs(&func);
+ mHasSelection = !selected_objects->isEmpty();
+ mSelectionNeedsUpdate = false;
+
+ if (func.mMaterial.notNull())
+ {
+ setFromGLTFMaterial(func.mMaterial);
+ LLViewerObject* selected_object = func.mObject;
+ const LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId);
+ const bool allow_modify = !item || canModify(selected_object, item);
+ setEnableEditing(allow_modify);
+
+ // todo: apply local texture data to all materials in selection
+ }
+ else
+ {
+ // pick defaults from a blank material;
+ LLGLTFMaterial blank_mat;
+ setFromGLTFMaterial(&blank_mat);
+ if (mIsOverride)
+ {
+ setEnableEditing(false);
+ }
+ }
+
+ if (mIsOverride)
+ {
+ mBaseColorTextureCtrl->setTentative(!func.mIdenticalTexColor);
+ mMetallicTextureCtrl->setTentative(!func.mIdenticalTexMetal);
+ mEmissiveTextureCtrl->setTentative(!func.mIdenticalTexEmissive);
+ mNormalTextureCtrl->setTentative(!func.mIdenticalTexNormal);
+
+ // Memorize selection data for filtering further updates
+ mOverrideObjectId = func.mObjectId;
+ mOverrideObjectTE = func.mObjectTE;
+
+ // Ovverdired might have been updated,
+ // refresh state of local textures in overrides
+ //
+ // Todo: this probably shouldn't be here, but in localbitmap,
+ // subscried to all material overrides if we want copied
+ // objects to get properly updated as well
+ LLSelectedTEUpdateOverrides local_tex_func(this);
+ selected_objects->applyToNodes(&local_tex_func);
+ }
+
+ return func.mMaterial.notNull();
+}
+
+
+void LLMaterialEditor::loadAsset()
+{
+ const LLInventoryItem* item;
+ if (mNotecardInventoryID.notNull())
+ {
+ item = mAuxItem.get();
+ }
+ else
+ {
+ item = getItem();
+ }
+
+ bool fail = false;
+
+ if (item)
+ {
+ LLPermissions perm(item->getPermissions());
+ bool allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE);
+ bool allow_modify = canModify(mObjectUUID, item);
+ bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
+
+ setCanSaveAs(allow_copy);
+ setMaterialName(item->getName());
+
+ {
+ mAssetID = item->getAssetUUID();
+
+ if (mAssetID.isNull())
+ {
+ mAssetStatus = PREVIEW_ASSET_LOADED;
+ loadDefaults();
+ resetUnsavedChanges();
+ setEnableEditing(allow_modify && !source_library);
+ }
+ else
+ {
+ LLHost source_sim = LLHost();
+ LLSD* user_data = new LLSD();
+
+ if (mNotecardInventoryID.notNull())
+ {
+ user_data->with("objectid", mNotecardObjectID).with("notecardid", mNotecardInventoryID);
+ }
+ else if (mObjectUUID.notNull())
+ {
+ LLViewerObject* objectp = gObjectList.findObject(mObjectUUID);
+ if (objectp && objectp->getRegion())
+ {
+ source_sim = objectp->getRegion()->getHost();
+ }
+ else
+ {
+ // The object that we're trying to look at disappeared, bail.
+ LL_WARNS("MaterialEditor") << "Can't find object " << mObjectUUID << " associated with material." << LL_ENDL;
+ mAssetID.setNull();
+ mAssetStatus = PREVIEW_ASSET_LOADED;
+ resetUnsavedChanges();
+ setEnableEditing(allow_modify && !source_library);
+ return;
+ }
+ user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID);
+ }
+ else
+ {
+ user_data = new LLSD(mItemUUID);
+ }
+
+ setEnableEditing(false); // wait for it to load
+
+ mAssetStatus = PREVIEW_ASSET_LOADING;
+
+ // May callback immediately
+ gAssetStorage->getAssetData(item->getAssetUUID(),
+ LLAssetType::AT_MATERIAL,
+ &onLoadComplete,
+ (void*)user_data,
+ true);
+ }
+ }
+ }
+ else if (mObjectUUID.notNull() && mItemUUID.notNull())
+ {
+ LLViewerObject* objectp = gObjectList.findObject(mObjectUUID);
+ if (objectp && (objectp->isInventoryPending() || objectp->isInventoryDirty()))
+ {
+ // It's a material in object's inventory and we failed to get it because inventory is not up to date.
+ // Subscribe for callback and retry at inventoryChanged()
+ registerVOInventoryListener(objectp, NULL); //removes previous listener
+
+ if (objectp->isInventoryDirty())
+ {
+ objectp->requestInventory();
+ }
+ }
+ else
+ {
+ fail = true;
+ }
+ }
+ else
+ {
+ fail = true;
+ }
+
+ if (fail)
+ {
+ /*editor->setText(LLStringUtil::null);
+ editor->makePristine();
+ editor->setEnabled(true);*/
+ // Don't set asset status here; we may not have set the item id yet
+ // (e.g. when this gets called initially)
+ //mAssetStatus = PREVIEW_ASSET_LOADED;
+ }
+}
+
+// static
+void LLMaterialEditor::onLoadComplete(const LLUUID& asset_uuid,
+ LLAssetType::EType type,
+ void* user_data, S32 status, LLExtStat ext_status)
+{
+ LLSD* floater_key = (LLSD*)user_data;
+ LL_DEBUGS("MaterialEditor") << "loading " << asset_uuid << " for " << *floater_key << LL_ENDL;
+ LLMaterialEditor* editor = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", *floater_key);
+ if (editor)
+ {
+ if (asset_uuid != editor->mAssetID)
+ {
+ LL_WARNS("MaterialEditor") << "Asset id mismatch, expected: " << editor->mAssetID << " got: " << asset_uuid << LL_ENDL;
+ }
+ if (0 == status)
+ {
+ LLFileSystem file(asset_uuid, type, LLFileSystem::READ);
+
+ S32 file_length = file.getSize();
+
+ std::vector<char> buffer(file_length + 1);
+ file.read((U8*)&buffer[0], file_length);
+
+ editor->decodeAsset(buffer);
+
+ bool allow_modify = editor->canModify(editor->mObjectUUID, editor->getItem());
+ bool source_library = editor->mObjectUUID.isNull() && gInventory.isObjectDescendentOf(editor->mItemUUID, gInventory.getLibraryRootFolderID());
+ editor->setEnableEditing(allow_modify && !source_library);
+ editor->resetUnsavedChanges();
+ editor->mAssetStatus = PREVIEW_ASSET_LOADED;
+ editor->setEnabled(true); // ready for use
+ }
+ else
+ {
+ if (LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ||
+ LL_ERR_FILE_EMPTY == status)
+ {
+ LLNotificationsUtil::add("MaterialMissing");
+ }
+ else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status)
+ {
+ // Not supposed to happen?
+ LL_WARNS("MaterialEditor") << "No permission to view material " << asset_uuid << LL_ENDL;
+ LLNotificationsUtil::add("MaterialNoPermissions");
+ }
+ else
+ {
+ LLNotificationsUtil::add("UnableToLoadMaterial");
+ }
+ editor->setEnableEditing(false);
+
+ LL_WARNS("MaterialEditor") << "Problem loading material: " << status << LL_ENDL;
+ editor->mAssetStatus = PREVIEW_ASSET_ERROR;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("MaterialEditor") << "Floater " << *floater_key << " does not exist." << LL_ENDL;
+ }
+ delete floater_key;
+}
+
+void LLMaterialEditor::inventoryChanged(LLViewerObject* object,
+ LLInventoryObject::object_list_t* inventory,
+ S32 serial_num,
+ void* user_data)
+{
+ removeVOInventoryListener();
+ loadAsset();
+}
+
+
+void LLMaterialEditor::saveTexture(LLImageJ2C* img, const std::string& name, const LLUUID& asset_id, upload_callback_f cb)
+{
+ LLImageDataSharedLock lock(img);
+
+ if (asset_id.isNull()
+ || img == nullptr
+ || img->getDataSize() == 0)
+ {
+ return;
+ }
+
+ // copy image bytes into string
+ std::string buffer;
+ buffer.assign((const char*) img->getData(), img->getDataSize());
+
+ U32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost();
+
+ LLSD key = getKey();
+ std::function<bool(LLUUID itemId, LLSD response, std::string reason)> failed_upload([key](LLUUID assetId, LLSD response, std::string reason)
+ {
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", key);
+ if (me)
+ {
+ me->setFailedToUploadTexture();
+ }
+ return true; // handled
+ });
+
+ LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLNewBufferedResourceUploadInfo>(
+ buffer,
+ asset_id,
+ name,
+ name,
+ 0,
+ LLFolderType::FT_TEXTURE,
+ LLInventoryType::IT_TEXTURE,
+ LLAssetType::AT_TEXTURE,
+ LLFloaterPerms::getNextOwnerPerms("Uploads"),
+ LLFloaterPerms::getGroupPerms("Uploads"),
+ LLFloaterPerms::getEveryonePerms("Uploads"),
+ expected_upload_cost,
+ false,
+ cb,
+ failed_upload));
+
+ upload_new_resource(uploadInfo);
+}
+
+void LLMaterialEditor::setFailedToUploadTexture()
+{
+ mUploadingTexturesFailure = true;
+ mUploadingTexturesCount--;
+ if (mUploadingTexturesCount == 0)
+ {
+ setEnabled(true);
+ }
+}
+
+S32 LLMaterialEditor::saveTextures()
+{
+ mUploadingTexturesFailure = false; // not supposed to get here if already uploading
+
+ S32 work_count = 0;
+ LLSD key = getKey(); // must be locally declared for lambda's capture to work
+ if (mBaseColorTextureUploadId == getBaseColorId() && mBaseColorTextureUploadId.notNull())
+ {
+ mUploadingTexturesCount++;
+ work_count++;
+
+ // For ease of inventory management, we prepend the material name.
+ std::string name = mMaterialName + ": " + mBaseColorName;
+
+ saveTexture(mBaseColorJ2C, name, mBaseColorTextureUploadId, [key](LLUUID newAssetId, LLSD response)
+ {
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", key);
+ if (me)
+ {
+ if (response["success"].asBoolean())
+ {
+ me->setBaseColorId(newAssetId);
+
+ // discard upload buffers once texture have been saved
+ me->mBaseColorJ2C = nullptr;
+ me->mBaseColorFetched = nullptr;
+ me->mBaseColorTextureUploadId.setNull();
+
+ me->mUploadingTexturesCount--;
+
+ if (!me->mUploadingTexturesFailure)
+ {
+ // try saving
+ me->saveIfNeeded();
+ }
+ else if (me->mUploadingTexturesCount == 0)
+ {
+ me->setEnabled(true);
+ }
+ }
+ else
+ {
+ // stop upload if possible, unblock and let user decide
+ me->setFailedToUploadTexture();
+ }
+ }
+ });
+ }
+ if (mNormalTextureUploadId == getNormalId() && mNormalTextureUploadId.notNull())
+ {
+ mUploadingTexturesCount++;
+ work_count++;
+
+ // For ease of inventory management, we prepend the material name.
+ std::string name = mMaterialName + ": " + mNormalName;
+
+ saveTexture(mNormalJ2C, name, mNormalTextureUploadId, [key](LLUUID newAssetId, LLSD response)
+ {
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", key);
+ if (me)
+ {
+ if (response["success"].asBoolean())
+ {
+ me->setNormalId(newAssetId);
+
+ // discard upload buffers once texture have been saved
+ me->mNormalJ2C = nullptr;
+ me->mNormalFetched = nullptr;
+ me->mNormalTextureUploadId.setNull();
+
+ me->mUploadingTexturesCount--;
+
+ if (!me->mUploadingTexturesFailure)
+ {
+ // try saving
+ me->saveIfNeeded();
+ }
+ else if (me->mUploadingTexturesCount == 0)
+ {
+ me->setEnabled(true);
+ }
+ }
+ else
+ {
+ // stop upload if possible, unblock and let user decide
+ me->setFailedToUploadTexture();
+ }
+ }
+ });
+ }
+ if (mMetallicTextureUploadId == getMetallicRoughnessId() && mMetallicTextureUploadId.notNull())
+ {
+ mUploadingTexturesCount++;
+ work_count++;
+
+ // For ease of inventory management, we prepend the material name.
+ std::string name = mMaterialName + ": " + mMetallicRoughnessName;
+
+ saveTexture(mMetallicRoughnessJ2C, name, mMetallicTextureUploadId, [key](LLUUID newAssetId, LLSD response)
+ {
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", key);
+ if (me)
+ {
+ if (response["success"].asBoolean())
+ {
+ me->setMetallicRoughnessId(newAssetId);
+
+ // discard upload buffers once texture have been saved
+ me->mMetallicRoughnessJ2C = nullptr;
+ me->mMetallicRoughnessFetched = nullptr;
+ me->mMetallicTextureUploadId.setNull();
+
+ me->mUploadingTexturesCount--;
+
+ if (!me->mUploadingTexturesFailure)
+ {
+ // try saving
+ me->saveIfNeeded();
+ }
+ else if (me->mUploadingTexturesCount == 0)
+ {
+ me->setEnabled(true);
+ }
+ }
+ else
+ {
+ // stop upload if possible, unblock and let user decide
+ me->setFailedToUploadTexture();
+ }
+ }
+ });
+ }
+
+ if (mEmissiveTextureUploadId == getEmissiveId() && mEmissiveTextureUploadId.notNull())
+ {
+ mUploadingTexturesCount++;
+ work_count++;
+
+ // For ease of inventory management, we prepend the material name.
+ std::string name = mMaterialName + ": " + mEmissiveName;
+
+ saveTexture(mEmissiveJ2C, name, mEmissiveTextureUploadId, [key](LLUUID newAssetId, LLSD response)
+ {
+ LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", LLSD(key));
+ if (me)
+ {
+ if (response["success"].asBoolean())
+ {
+ me->setEmissiveId(newAssetId);
+
+ // discard upload buffers once texture have been saved
+ me->mEmissiveJ2C = nullptr;
+ me->mEmissiveFetched = nullptr;
+ me->mEmissiveTextureUploadId.setNull();
+
+ me->mUploadingTexturesCount--;
+
+ if (!me->mUploadingTexturesFailure)
+ {
+ // try saving
+ me->saveIfNeeded();
+ }
+ else if (me->mUploadingTexturesCount == 0)
+ {
+ me->setEnabled(true);
+ }
+ }
+ else
+ {
+ // stop upload if possible, unblock and let user decide
+ me->setFailedToUploadTexture();
+ }
+ }
+ });
+ }
+
+ if (!work_count)
+ {
+ // Discard upload buffers once textures have been confirmed as saved.
+ // Otherwise we keep buffers for potential upload failure recovery.
+ clearTextures();
+ }
+
+ // asset storage can callback immediately, causing a decrease
+ // of mUploadingTexturesCount, report amount of work scheduled
+ // not amount of work remaining
+ return work_count;
+}
+
+void LLMaterialEditor::clearTextures()
+{
+ mBaseColorJ2C = nullptr;
+ mNormalJ2C = nullptr;
+ mEmissiveJ2C = nullptr;
+ mMetallicRoughnessJ2C = nullptr;
+
+ mBaseColorFetched = nullptr;
+ mNormalFetched = nullptr;
+ mMetallicRoughnessFetched = nullptr;
+ mEmissiveFetched = nullptr;
+
+ mBaseColorTextureUploadId.setNull();
+ mNormalTextureUploadId.setNull();
+ mMetallicTextureUploadId.setNull();
+ mEmissiveTextureUploadId.setNull();
+}
+
+void LLMaterialEditor::loadDefaults()
+{
+ tinygltf::Model model_in;
+ model_in.materials.resize(1);
+ setFromGltfModel(model_in, 0, true);
+}
+
+bool LLMaterialEditor::capabilitiesAvailable()
+{
+ const LLViewerRegion* region = gAgent.getRegion();
+ if (!region)
+ {
+ LL_WARNS("MaterialEditor") << "Not connected to a region, cannot save material." << LL_ENDL;
+ return false;
+ }
+ std::string agent_url = region->getCapability("UpdateMaterialAgentInventory");
+ std::string task_url = region->getCapability("UpdateMaterialTaskInventory");
+
+ return (!agent_url.empty() && !task_url.empty());
+}
+