diff options
Diffstat (limited to 'indra/newview/llmaterialeditor.cpp')
| -rw-r--r-- | indra/newview/llmaterialeditor.cpp | 7540 | 
1 files changed, 3770 insertions, 3770 deletions
diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 98d2a6d375..10f4e2440b 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());
 +}
 +
  | 
