/** * @file llmaterialeditor.cpp * @brief Implementation of the notecard 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 "llappviewer.h" #include "llcombobox.h" #include "llinventorymodel.h" #include "llnotificationsutil.h" #include "lltexturectrl.h" #include "lltrans.h" #include "llviewermenufile.h" #include "llviewertexture.h" #include "llsdutil.h" #include "llselectmgr.h" #include "llviewerinventory.h" #include "llviewerregion.h" #include "llvovolume.h" #include "tinygltf/tiny_gltf.h" ///---------------------------------------------------------------------------- /// Class LLPreviewNotecard ///---------------------------------------------------------------------------- // Default constructor LLMaterialEditor::LLMaterialEditor(const LLSD& key) : LLFloater(key) , mHasUnsavedChanges(false) { } BOOL LLMaterialEditor::postBuild() { mAlbedoTextureCtrl = getChild("albedo_texture"); mMetallicTextureCtrl = getChild("metallic_roughness_texture"); mEmissiveTextureCtrl = getChild("emissive_texture"); mNormalTextureCtrl = getChild("normal_texture"); mAlbedoTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitAlbedoTexture, this, _1, _2)); mMetallicTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitMetallicTexture, this, _1, _2)); mEmissiveTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitEmissiveTexture, this, _1, _2)); mNormalTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitNormalTexture, this, _1, _2)); childSetAction("save", boost::bind(&LLMaterialEditor::onClickSave, this)); childSetAction("save_as", boost::bind(&LLMaterialEditor::onClickSaveAs, this)); childSetAction("cancel", boost::bind(&LLMaterialEditor::onClickCancel, this)); boost::function changes_callback = [this](LLUICtrl * ctrl, void*) { setHasUnsavedChanges(true); // Apply changes to object live applyToSelection(); }; childSetCommitCallback("double sided", changes_callback, NULL); // Albedo childSetCommitCallback("albedo color", changes_callback, NULL); childSetCommitCallback("transparency", changes_callback, NULL); childSetCommitCallback("alpha mode", changes_callback, NULL); childSetCommitCallback("alpha cutoff", changes_callback, NULL); // Metallic-Roughness childSetCommitCallback("metalness factor", changes_callback, NULL); childSetCommitCallback("roughness factor", changes_callback, NULL); // Metallic-Roughness childSetCommitCallback("metalness factor", changes_callback, NULL); childSetCommitCallback("roughness factor", changes_callback, NULL); // Emissive childSetCommitCallback("emissive color", changes_callback, NULL); childSetVisible("unsaved_changes", mHasUnsavedChanges); // Todo: // Disable/enable setCanApplyImmediately() based on // working from inventory, upload or editing inworld return LLFloater::postBuild(); } void LLMaterialEditor::onClickCloseBtn(bool app_quitting) { if (app_quitting) { closeFloater(app_quitting); } else { onClickCancel(); } } LLUUID LLMaterialEditor::getAlbedoId() { return mAlbedoTextureCtrl->getValue().asUUID(); } void LLMaterialEditor::setAlbedoId(const LLUUID& id) { mAlbedoTextureCtrl->setValue(id); mAlbedoTextureCtrl->setDefaultImageAssetID(id); if (id.notNull()) { // todo: this does not account for posibility of texture // being from inventory, need to check that childSetValue("albedo_upload_fee", getString("upload_fee_string")); // Only set if we will need to upload this texture mAlbedoTextureUploadId = id; } } LLColor4 LLMaterialEditor::getAlbedoColor() { LLColor4 ret = LLColor4(childGetValue("albedo color")); ret.mV[3] = getTransparency(); return ret; } void LLMaterialEditor::setAlbedoColor(const LLColor4& color) { childSetValue("albedo color", color.getValue()); childSetValue("transparency", color.mV[3]); } F32 LLMaterialEditor::getTransparency() { return childGetValue("transparency").asReal(); } 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); 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; } } 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); 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; } } LLColor4 LLMaterialEditor::getEmissiveColor() { return LLColor4(childGetValue("emissive color")); } void LLMaterialEditor::setEmissiveColor(const LLColor4& color) { childSetValue("emissive color", color.getValue()); } LLUUID LLMaterialEditor::getNormalId() { return mNormalTextureCtrl->getValue().asUUID(); } void LLMaterialEditor::setNormalId(const LLUUID& id) { mNormalTextureCtrl->setValue(id); mNormalTextureCtrl->setDefaultImageAssetID(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; } } bool LLMaterialEditor::getDoubleSided() { return childGetValue("double sided").asBoolean(); } void LLMaterialEditor::setDoubleSided(bool double_sided) { childSetValue("double sided", double_sided); } void LLMaterialEditor::setHasUnsavedChanges(bool value) { if (value != mHasUnsavedChanges) { mHasUnsavedChanges = value; childSetVisible("unsaved_changes", value); } } void LLMaterialEditor::onCommitAlbedoTexture(LLUICtrl * ctrl, const LLSD & data) { // might be better to use arrays, to have a single callback // and not to repeat the same thing for each tecture control LLUUID new_val = mAlbedoTextureCtrl->getValue().asUUID(); if (new_val == mAlbedoTextureUploadId && mAlbedoTextureUploadId.notNull()) { childSetValue("albedo_upload_fee", getString("upload_fee_string")); } else { childSetValue("albedo_upload_fee", getString("no_upload_fee_string")); } setHasUnsavedChanges(true); applyToSelection(); } void LLMaterialEditor::onCommitMetallicTexture(LLUICtrl * ctrl, const LLSD & data) { LLUUID new_val = mMetallicTextureCtrl->getValue().asUUID(); if (new_val == mMetallicTextureUploadId && mMetallicTextureUploadId.notNull()) { childSetValue("metallic_upload_fee", getString("upload_fee_string")); } else { childSetValue("metallic_upload_fee", getString("no_upload_fee_string")); } setHasUnsavedChanges(true); applyToSelection(); } void LLMaterialEditor::onCommitEmissiveTexture(LLUICtrl * ctrl, const LLSD & data) { LLUUID new_val = mEmissiveTextureCtrl->getValue().asUUID(); if (new_val == mEmissiveTextureUploadId && mEmissiveTextureUploadId.notNull()) { childSetValue("emissive_upload_fee", getString("upload_fee_string")); } else { childSetValue("emissive_upload_fee", getString("no_upload_fee_string")); } setHasUnsavedChanges(true); applyToSelection(); } void LLMaterialEditor::onCommitNormalTexture(LLUICtrl * ctrl, const LLSD & data) { LLUUID new_val = mNormalTextureCtrl->getValue().asUUID(); if (new_val == mNormalTextureUploadId && mNormalTextureUploadId.notNull()) { childSetValue("normal_upload_fee", getString("upload_fee_string")); } else { childSetValue("normal_upload_fee", getString("no_upload_fee_string")); } setHasUnsavedChanges(true); applyToSelection(); } static void write_color(const LLColor4& color, std::vector& c) { for (int i = 0; i < c.size(); ++i) // NOTE -- use c.size because some gltf colors are 3-component { c[i] = color.mV[i]; } } static U32 write_texture(const LLUUID& id, tinygltf::Model& model) { tinygltf::Image image; image.uri = id.asString(); model.images.push_back(image); U32 image_idx = model.images.size() - 1; tinygltf::Texture texture; texture.source = image_idx; model.textures.push_back(texture); U32 texture_idx = model.textures.size() - 1; return texture_idx; } void LLMaterialEditor::onClickSave() { applyToSelection(); tinygltf::Model model; model.materials.resize(1); tinygltf::PbrMetallicRoughness& pbrMaterial = model.materials[0].pbrMetallicRoughness; // write albedo LLColor4 albedo_color = getAlbedoColor(); albedo_color.mV[3] = getTransparency(); write_color(albedo_color, pbrMaterial.baseColorFactor); model.materials[0].alphaCutoff = getAlphaCutoff(); model.materials[0].alphaMode = getAlphaMode(); LLUUID albedo_id = getAlbedoId(); if (albedo_id.notNull()) { U32 texture_idx = write_texture(albedo_id, model); pbrMaterial.baseColorTexture.index = texture_idx; } // write metallic/roughness F32 metalness = getMetalnessFactor(); F32 roughness = getRoughnessFactor(); pbrMaterial.metallicFactor = metalness; pbrMaterial.roughnessFactor = roughness; LLUUID mr_id = getMetallicRoughnessId(); if (mr_id.notNull()) { U32 texture_idx = write_texture(mr_id, model); pbrMaterial.metallicRoughnessTexture.index = texture_idx; } //write emissive LLColor4 emissive_color = getEmissiveColor(); model.materials[0].emissiveFactor.resize(3); write_color(emissive_color, model.materials[0].emissiveFactor); LLUUID emissive_id = getEmissiveId(); if (emissive_id.notNull()) { U32 idx = write_texture(emissive_id, model); model.materials[0].emissiveTexture.index = idx; } //write normal LLUUID normal_id = getNormalId(); if (normal_id.notNull()) { U32 idx = write_texture(normal_id, model); model.materials[0].normalTexture.index = idx; } //write doublesided model.materials[0].doubleSided = getDoubleSided(); std::ostringstream str; tinygltf::TinyGLTF gltf; model.asset.version = "2.0"; gltf.WriteGltfSceneToStream(&model, str, true, false); std::string dump = str.str(); LL_INFOS() << mMaterialName << ": " << dump << LL_ENDL; // gen a new uuid for this asset LLTransactionID tid; tid.generate(); // timestamp-based randomization + uniquification LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); std::string res_desc = "Saved Material"; U32 next_owner_perm = LLPermissions::DEFAULT.getMaskNextOwner(); LLUUID parent = gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL); const U8 subtype = NO_INV_SUBTYPE; // TODO maybe use AT_SETTINGS and LLSettingsType::ST_MATERIAL ? create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent, tid, mMaterialName, res_desc, LLAssetType::AT_MATERIAL, LLInventoryType::IT_MATERIAL, subtype, next_owner_perm, new LLBoostFuncInventoryCallback([output=dump](LLUUID const & inv_item_id){ // from reference in LLSettingsVOBase::createInventoryItem()/updateInventoryItem() LLResourceUploadInfo::ptr_t uploadInfo = std::make_shared( inv_item_id, LLAssetType::AT_SETTINGS, // TODO switch to AT_MATERIAL output, [](LLUUID item_id, LLUUID new_asset_id, LLUUID new_item_id, LLSD response) { LL_INFOS("Material") << "inventory item uploaded. item: " << item_id << " asset: " << new_asset_id << " new_item_id: " << new_item_id << " response: " << response << LL_ENDL; LLSD params = llsd::map("ASSET_ID", new_asset_id); LLNotificationsUtil::add("MaterialCreated", params); }); const LLViewerRegion* region = gAgent.getRegion(); if (region) { std::string agent_url(region->getCapability("UpdateSettingsAgentInventory")); if (agent_url.empty()) { LL_ERRS() << "missing required agent inventory cap url" << LL_ENDL; } LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo); } }) ); } void LLMaterialEditor::onClickSaveAs() { 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(); LLStringUtil::trim(new_name); if (!new_name.empty()) { setMaterialName(new_name); onClickSave(); } else { LLNotificationsUtil::add("InvalidMaterialName"); } } } void LLMaterialEditor::onClickCancel() { if (mHasUnsavedChanges) { 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(); } } class LLMaterialFilePicker : public LLFilePickerThread { public: LLMaterialFilePicker(LLMaterialEditor* me); virtual void notify(const std::vector& filenames); void loadMaterial(const std::string& filename); static void textureLoadedCallback(BOOL success, LLViewerFetchedTexture* src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata); private: LLMaterialEditor* mME; }; LLMaterialFilePicker::LLMaterialFilePicker(LLMaterialEditor* me) : LLFilePickerThread(LLFilePicker::FFLOAD_MATERIAL) { mME = me; } void LLMaterialFilePicker::notify(const std::vector& filenames) { if (LLAppViewer::instance()->quitRequested()) { return; } if (filenames.size() > 0) { loadMaterial(filenames[0]); } } const tinygltf::Image* get_image_from_texture_index(const tinygltf::Model& model, S32 texture_index) { if (texture_index >= 0) { S32 source_idx = model.textures[texture_index].source; if (source_idx >= 0) { return &(model.images[source_idx]); } } return nullptr; } static LLImageRaw* get_texture(const std::string& folder, const tinygltf::Model& model, S32 texture_index) { const tinygltf::Image* image = get_image_from_texture_index(model, texture_index); LLImageRaw* rawImage = nullptr; if (image != nullptr && image->bits == 8 && !image->image.empty() && image->component <= 4) { rawImage = new LLImageRaw(&image->image[0], image->width, image->height, image->component); rawImage->verticalFlip(); } return rawImage; } static void strip_alpha_channel(LLPointer& img) { if (img->getComponents() == 4) { LLImageRaw* tmp = new LLImageRaw(img->getWidth(), img->getHeight(), 3); tmp->copyUnscaled4onto3(img); img = tmp; } } // copy red channel from src_img to dst_img // PRECONDITIONS: // dst_img must be 3 component // src_img and dst_image must have the same dimensions static void copy_red_channel(LLPointer& src_img, LLPointer& dst_img) { llassert(src_img->getWidth() == dst_img->getWidth() && src_img->getHeight() == dst_img->getHeight()); llassert(dst_img->getComponents() == 3); U32 pixel_count = dst_img->getWidth() * dst_img->getHeight(); U8* src = src_img->getData(); U8* dst = dst_img->getData(); S8 src_components = src_img->getComponents(); for (U32 i = 0; i < pixel_count; ++i) { dst[i * 3] = src[i * src_components]; } } static void pack_textures(tinygltf::Model& model, tinygltf::Material& material, LLPointer& albedo_img, LLPointer& normal_img, LLPointer& mr_img, LLPointer& emissive_img, LLPointer& occlusion_img, LLPointer& albedo_tex, LLPointer& normal_tex, LLPointer& mr_tex, LLPointer& emissive_tex) { // TODO: downscale if needed if (albedo_img) { albedo_tex = LLViewerTextureManager::getFetchedTexture(albedo_img, FTType::FTT_LOCAL_FILE, true); } if (normal_img) { strip_alpha_channel(normal_img); normal_tex = LLViewerTextureManager::getFetchedTexture(normal_img, FTType::FTT_LOCAL_FILE, true); } if (mr_img) { strip_alpha_channel(mr_img); if (occlusion_img && material.pbrMetallicRoughness.metallicRoughnessTexture.index != material.occlusionTexture.index) { // occlusion is a distinct texture from pbrMetallicRoughness // pack into mr red channel int occlusion_idx = material.occlusionTexture.index; int mr_idx = material.pbrMetallicRoughness.metallicRoughnessTexture.index; if (occlusion_idx != mr_idx) { //scale occlusion image to match resolution of mr image occlusion_img->scale(mr_img->getWidth(), mr_img->getHeight()); copy_red_channel(occlusion_img, mr_img); } } } else if (occlusion_img) { //no mr but occlusion exists, make a white mr_img and copy occlusion red channel over mr_img = new LLImageRaw(occlusion_img->getWidth(), occlusion_img->getHeight(), 3); mr_img->clear(255, 255, 255); copy_red_channel(occlusion_img, mr_img); } if (mr_img) { mr_tex = LLViewerTextureManager::getFetchedTexture(mr_img, FTType::FTT_LOCAL_FILE, true); } if (emissive_img) { strip_alpha_channel(emissive_img); emissive_tex = LLViewerTextureManager::getFetchedTexture(emissive_img, FTType::FTT_LOCAL_FILE, true); } } static LLColor4 get_color(const std::vector& in) { LLColor4 out; for (S32 i = 0; i < llmin((S32) in.size(), 4); ++i) { out.mV[i] = in[i]; } return out; } void LLMaterialFilePicker::textureLoadedCallback(BOOL success, LLViewerFetchedTexture* src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata) { } void LLMaterialFilePicker::loadMaterial(const std::string& filename) { 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; } std::string folder = gDirUtilp->getDirName(filename); tinygltf::Material material_in = model_in.materials[0]; tinygltf::Model model_out; model_out.asset.version = "2.0"; model_out.materials.resize(1); // get albedo texture LLPointer albedo_img = get_texture(folder, model_in, material_in.pbrMetallicRoughness.baseColorTexture.index); // get normal map LLPointer normal_img = get_texture(folder, model_in, material_in.normalTexture.index); // get metallic-roughness texture LLPointer mr_img = get_texture(folder, model_in, material_in.pbrMetallicRoughness.metallicRoughnessTexture.index); // get emissive texture LLPointer emissive_img = get_texture(folder, model_in, material_in.emissiveTexture.index); // get occlusion map if needed LLPointer occlusion_img; if (material_in.occlusionTexture.index != material_in.pbrMetallicRoughness.metallicRoughnessTexture.index) { occlusion_img = get_texture(folder, model_in, material_in.occlusionTexture.index); } LLPointer albedo_tex; LLPointer normal_tex; LLPointer mr_tex; LLPointer emissive_tex; pack_textures(model_in, material_in, albedo_img, normal_img, mr_img, emissive_img, occlusion_img, albedo_tex, normal_tex, mr_tex, emissive_tex); LLUUID albedo_id; if (albedo_tex != nullptr) { albedo_tex->forceToSaveRawImage(0, F32_MAX); albedo_id = albedo_tex->getID(); } LLUUID normal_id; if (normal_tex != nullptr) { normal_tex->forceToSaveRawImage(0, F32_MAX); normal_id = normal_tex->getID(); } LLUUID mr_id; if (mr_tex != nullptr) { mr_tex->forceToSaveRawImage(0, F32_MAX); mr_id = mr_tex->getID(); } LLUUID emissive_id; if (emissive_tex != nullptr) { emissive_tex->forceToSaveRawImage(0, F32_MAX); emissive_id = emissive_tex->getID(); } mME->setAlbedoId(albedo_id); mME->setMetallicRoughnessId(mr_id); mME->setEmissiveId(emissive_id); mME->setNormalId(normal_id); mME->setAlphaMode(material_in.alphaMode); mME->setAlphaCutoff(material_in.alphaCutoff); mME->setAlbedoColor(get_color(material_in.pbrMetallicRoughness.baseColorFactor)); mME->setEmissiveColor(get_color(material_in.emissiveFactor)); mME->setMetalnessFactor(material_in.pbrMetallicRoughness.metallicFactor); mME->setRoughnessFactor(material_in.pbrMetallicRoughness.roughnessFactor); mME->setDoubleSided(material_in.doubleSided); std::string new_material = LLTrans::getString("New Material"); mME->setMaterialName(new_material); mME->setHasUnsavedChanges(true); mME->openFloater(); mME->applyToSelection(); } void LLMaterialEditor::importMaterial() { (new LLMaterialFilePicker(this))->getFile(); } void LLMaterialEditor::applyToSelection() { // Todo: associate with a specific 'selection' instead // of modifying something that is selected // This should be disabled when working from agent's // inventory and for initial upload LLViewerObject* objectp = LLSelectMgr::instance().getSelection()->getFirstObject(); if (objectp && objectp->getVolume()) { LLGLTFMaterial* mat = new LLGLTFMaterial(); mat->mAlbedoColor = getAlbedoColor(); mat->mAlbedoColor.mV[3] = getTransparency(); mat->mAlbedoId = getAlbedoId(); mat->mNormalId = getNormalId(); mat->mMetallicRoughnessId = getMetallicRoughnessId(); mat->mMetallicFactor = getMetalnessFactor(); mat->mRoughnessFactor = getRoughnessFactor(); mat->mEmissiveColor = getEmissiveColor(); mat->mEmissiveId = getEmissiveId(); mat->mDoubleSided = getDoubleSided(); mat->setAlphaMode(getAlphaMode()); LLVOVolume* vobjp = (LLVOVolume*)objectp; for (int i = 0; i < vobjp->getNumTEs(); ++i) { vobjp->getTE(i)->setGLTFMaterial(mat); vobjp->updateTEMaterialTextures(i); } vobjp->markForUpdate(TRUE); } }