From 170765fd3505410dced83b342f87030fd9151e35 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Tue, 30 Apr 2024 21:57:42 -0500 Subject: #1357 Proof of concept on decomposing a GLTF scene into its component parts --- indra/newview/gltf/accessor.cpp | 18 ++++ indra/newview/gltf/accessor.h | 4 + indra/newview/gltf/asset.cpp | 102 +++++++++++++++++++++ indra/newview/gltf/asset.h | 11 ++- indra/newview/gltfscenemanager.cpp | 45 +++++++++ indra/newview/gltfscenemanager.h | 4 +- indra/newview/llviewermenu.cpp | 11 +++ indra/newview/skins/default/xui/en/menu_viewer.xml | 6 ++ 8 files changed, 199 insertions(+), 2 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index 55d36b7a32..9bfdc2afa6 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -30,6 +30,24 @@ using namespace LL::GLTF; +void Buffer::erase(Asset& asset, S32 offset, S32 length) +{ + S32 idx = this - &asset.mBuffers[0]; + + mData.erase(mData.begin() + offset, mData.begin() + offset + length); + + for (BufferView& view : asset.mBufferViews) + { + if (view.mBuffer == idx) + { + if (view.mByteOffset >= offset) + { + view.mByteOffset -= length; + } + } + } +} + const Buffer& Buffer::operator=(const tinygltf::Buffer& src) { mData = src.data; diff --git a/indra/newview/gltf/accessor.h b/indra/newview/gltf/accessor.h index 9b8265d8da..6849cd8609 100644 --- a/indra/newview/gltf/accessor.h +++ b/indra/newview/gltf/accessor.h @@ -45,6 +45,10 @@ namespace LL std::string mName; std::string mUri; + // erase the given range from this buffer. + // also updates all buffer views in given asset that reference this buffer + void erase(Asset& asset, S32 offset, S32 length); + const Buffer& operator=(const tinygltf::Buffer& src); }; diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 233faac545..475cbcb6e5 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -30,9 +30,11 @@ #include "llvolumeoctree.h" #include "../llviewershadermgr.h" #include "../llviewercontrol.h" +#include "../llviewertexturelist.h" using namespace LL::GLTF; +#pragma optimize("", off) namespace LL { @@ -821,6 +823,106 @@ void Asset::save(tinygltf::Model& dst) LL::GLTF::copy(*this, dst); } +void Asset::decompose(const std::string& filename) +{ + // get folder path + std::string folder = gDirUtilp->getDirName(filename); + + // decompose images + for (auto& image : mImages) + { + image.decompose(*this, folder); + } +} + +void Asset::eraseBufferView(S32 bufferView) +{ + mBufferViews.erase(mBufferViews.begin() + bufferView); + + for (auto& accessor : mAccessors) + { + if (accessor.mBufferView > bufferView) + { + accessor.mBufferView--; + } + } + + for (auto& image : mImages) + { + if (image.mBufferView > bufferView) + { + image.mBufferView--; + } + } + +} + +void Image::decompose(Asset& asset, const std::string& folder) +{ + std::string name = mName; + if (name.empty()) + { + S32 idx = this - asset.mImages.data(); + name = llformat("image_%d", idx); + } + + if (mBufferView != INVALID_INDEX) + { + // save original image + BufferView& bufferView = asset.mBufferViews[mBufferView]; + Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; + + std::string extension; + + if (mMimeType == "image/jpeg") + { + extension = ".jpg"; + } + else if (mMimeType == "image/png") + { + extension = ".png"; + } + else + { + extension = ".bin"; + } + + std::string filename = folder + "/" + name + "." + extension; + + // set URI to non-j2c file for now, but later we'll want to reference the j2c hash + mUri = name + "." + extension; + + std::ofstream file(filename, std::ios::binary); + file.write((const char*)buffer.mData.data() + bufferView.mByteOffset, bufferView.mByteLength); + + buffer.erase(asset, bufferView.mByteOffset, bufferView.mByteLength); + + asset.eraseBufferView(mBufferView); + } + + if (!mData.empty()) + { + // save j2c image + std::string filename = folder + "/" + name + ".j2c"; + + LLPointer raw = new LLImageRaw(mWidth, mHeight, mComponent); + U8* data = raw->allocateData(); + llassert(mData.size() == raw->getDataSize()); + memcpy(data, mData.data(), mData.size()); + + LLViewerTextureList::createUploadFile(raw, filename, 4096); + + mData.clear(); + } + + mWidth = -1; + mHeight = -1; + mComponent = -1; + mBits = -1; + mPixelType = -1; + mMimeType = ""; + +} const Material::TextureInfo& Material::TextureInfo::operator=(const tinygltf::TextureInfo& src) { diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index b8300c2d8a..cb28c4a572 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -254,6 +254,9 @@ namespace LL return *this; } + // save image clear local data, and set uri + void decompose(Asset& asset, const std::string& filename); + void allocateGLResources() { // allocate texture @@ -322,7 +325,13 @@ namespace LL // save the asset to a tinygltf model void save(tinygltf::Model& dst); - + + // decompose the asset to the given .gltf file + void decompose(const std::string& filename); + + // remove the bufferview at the given index + // updates all bufferview indices in this Asset as needed + void eraseBufferView(S32 bufferView); }; } } diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index f9f1240469..0aedcd653d 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -100,6 +100,51 @@ void GLTFSceneManager::saveAs() } } +void GLTFSceneManager::decomposeSelection() +{ + LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); + if (obj && obj->mGLTFAsset.notNull()) + { + LLFilePickerReplyThread::startPicker( + [](const std::vector& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter) + { + if (LLAppViewer::instance()->quitRequested()) + { + return; + } + if (filenames.size() > 0) + { + GLTFSceneManager::instance().decomposeSelection(filenames[0]); + } + }, + LLFilePicker::FFSAVE_GLTF, + "scene.gltf"); + } + else + { + LLNotificationsUtil::add("GLTFSaveSelection"); + } +} + +void GLTFSceneManager::decomposeSelection(const std::string& filename) +{ + LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); + if (obj && obj->mGLTFAsset.notNull()) + { + // copy asset out for decomposition + Asset asset = *obj->mGLTFAsset; + + // decompose the asset into component parts + asset.decompose(filename); + + // copy decomposed asset into tinygltf for serialization + tinygltf::Model model; + asset.save(model); + + LLTinyGLTFHelper::saveModel(filename, model); + } +} + void GLTFSceneManager::save(const std::string& filename) { LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); diff --git a/indra/newview/gltfscenemanager.h b/indra/newview/gltfscenemanager.h index ec50a0952f..57d9e019a5 100644 --- a/indra/newview/gltfscenemanager.h +++ b/indra/newview/gltfscenemanager.h @@ -41,7 +41,9 @@ namespace LL void load(const std::string& filename); // load asset from filename void saveAs(); // open filepicker and choose file to save selected asset to - void save(const std::string& filename); // save selected asset to filename + void save(const std::string& filename); // save selected asset to filename (suitable for use in external programs) + void decomposeSelection(); // open file picker and choose a location to decompose to + void decomposeSelection(const std::string& filename); // decompose selected asset into simulator-ready .gltf, .bin, and .j2c files void update(); void render(bool opaque, bool rigged = false); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index fcda8fa767..631e1b57d9 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -8000,6 +8000,16 @@ class LLAdvancedClickGLTFSaveAs : public view_listener_t } }; +class LLAdvancedClickGLTFDecompose : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // open personal lighting floater when previewing an HDRI (keeps HDRI from implicitly unloading when opening build tools) + LL::GLTFSceneManager::instance().decomposeSelection(); + return true; + } +}; + // these are used in the gl menus to set control values that require shader recompilation class LLToggleShaderControl : public view_listener_t { @@ -9649,6 +9659,7 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedClickHDRIPreview(), "Advanced.ClickHDRIPreview"); view_listener_t::addMenu(new LLAdvancedClickGLTFOpen(), "Advanced.ClickGLTFOpen"); view_listener_t::addMenu(new LLAdvancedClickGLTFSaveAs(), "Advanced.ClickGLTFSaveAs"); + view_listener_t::addMenu(new LLAdvancedClickGLTFDecompose(), "Advanced.ClickGLTFDecompose"); view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache"); view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain"); diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 9544a926f3..6238efe688 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2861,6 +2861,12 @@ function="World.EnvPreset" + + +