summaryrefslogtreecommitdiff
path: root/indra/newview/llfloatergltfasseteditor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llfloatergltfasseteditor.cpp')
-rw-r--r--indra/newview/llfloatergltfasseteditor.cpp622
1 files changed, 622 insertions, 0 deletions
diff --git a/indra/newview/llfloatergltfasseteditor.cpp b/indra/newview/llfloatergltfasseteditor.cpp
new file mode 100644
index 0000000000..d2cf24f1dd
--- /dev/null
+++ b/indra/newview/llfloatergltfasseteditor.cpp
@@ -0,0 +1,622 @@
+/**
+ * @file llfloatergltfasseteditor.cpp
+ * @author Andrii Kleshchev
+ * @brief LLFloaterGltfAssetEditor class implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "llfloatergltfasseteditor.h"
+
+#include "gltf/asset.h"
+#include "llcallbacklist.h"
+#include "llmenubutton.h"
+#include "llselectmgr.h"
+#include "llspinctrl.h"
+#include "llviewerobject.h"
+
+const LLColor4U DEFAULT_WHITE(255, 255, 255);
+
+/// LLFloaterGLTFAssetEditor
+
+LLFloaterGLTFAssetEditor::LLFloaterGLTFAssetEditor(const LLSD& key)
+ : LLFloater(key)
+ , mUIColor(LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE))
+{
+ setTitle("GLTF Asset Editor (WIP)");
+
+ mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", [this](LLUICtrl* ctrl, const LLSD& data) { onMenuDoToSelected(data); });
+ mEnableCallbackRegistrar.add("PanelObject.menuEnable", [this](LLUICtrl* ctrl, const LLSD& data) { return onMenuEnableItem(data); });
+}
+
+LLFloaterGLTFAssetEditor::~LLFloaterGLTFAssetEditor()
+{
+ if (mScroller)
+ {
+ mItemListPanel->removeChild(mScroller);
+ delete mScroller;
+ mScroller = NULL;
+ }
+}
+
+bool LLFloaterGLTFAssetEditor::postBuild()
+{
+ // Position
+ mMenuClipboardPos = getChild<LLMenuButton>("clipboard_pos_btn");
+ mCtrlPosX = getChild<LLSpinCtrl>("Pos X", true);
+ mCtrlPosX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlPosY = getChild<LLSpinCtrl>("Pos Y", true);
+ mCtrlPosY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlPosZ = getChild<LLSpinCtrl>("Pos Z", true);
+ mCtrlPosZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+
+ // Scale
+ mMenuClipboardScale = getChild<LLMenuButton>("clipboard_size_btn");
+ mCtrlScaleX = getChild<LLSpinCtrl>("Scale X", true);
+ mCtrlScaleX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlScaleY = getChild<LLSpinCtrl>("Scale Y", true);
+ mCtrlScaleY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlScaleZ = getChild<LLSpinCtrl>("Scale Z", true);
+ mCtrlScaleZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+
+ // Rotation
+ mMenuClipboardRot = getChild<LLMenuButton>("clipboard_rot_btn");
+ mCtrlRotX = getChild<LLSpinCtrl>("Rot X", true);
+ mCtrlRotX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlRotY = getChild<LLSpinCtrl>("Rot Y", true);
+ mCtrlRotY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlRotZ = getChild<LLSpinCtrl>("Rot Z", true);
+ mCtrlPosZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ setTransformsEnabled(false);
+ // todo: do multiple panels based on selected element.
+ mTransformsPanel = getChild<LLPanel>("transform_panel", true);
+ mTransformsPanel->setVisible(false);
+
+ mItemListPanel = getChild<LLPanel>("item_list_panel", true);
+ initFolderRoot();
+
+ return true;
+}
+
+void LLFloaterGLTFAssetEditor::initFolderRoot()
+{
+ if (mScroller || mFolderRoot)
+ {
+ LL_ERRS() << "Folder root already initialized" << LL_ENDL;
+ return;
+ }
+
+ LLRect scroller_view_rect = mItemListPanel->getRect();
+ scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom);
+ LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams<LLFolderViewScrollContainer>());
+ scroller_params.rect(scroller_view_rect);
+ scroller_params.name("folder_scroller");
+ mScroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(scroller_params);
+ mScroller->setFollowsAll();
+
+ // Insert that scroller into the panel widgets hierarchy
+ mItemListPanel->addChild(mScroller);
+
+ // Create the root model
+ LLGLTFFolderItem* base_item = new LLGLTFFolderItem(mGLTFViewModel);
+
+ LLFolderView::Params p(LLUICtrlFactory::getDefaultParams<LLFolderView>());
+ p.name = "Root";
+ p.title = "Root";
+ p.rect = LLRect(0, 0, getRect().getWidth(), 0);
+ p.parent_panel = mItemListPanel;
+ p.tool_tip = p.name;
+ p.listener = base_item;
+ p.view_model = &mGLTFViewModel;
+ p.root = NULL;
+ p.use_ellipses = true;
+ p.options_menu = "menu_gltf.xml"; // *TODO : create this or fix to be optional
+ mFolderRoot = LLUICtrlFactory::create<LLFolderView>(p);
+ mFolderRoot->setCallbackRegistrar(&mCommitCallbackRegistrar);
+ mFolderRoot->setEnableRegistrar(&mEnableCallbackRegistrar);
+ // Attach root to the scroller
+ mScroller->addChild(mFolderRoot);
+ mFolderRoot->setScrollContainer(mScroller);
+ mFolderRoot->setFollowsAll();
+ mFolderRoot->setOpen(true);
+ mFolderRoot->setSelectCallback([this](const std::deque<LLFolderViewItem*>& items, bool user_action) { onFolderSelectionChanged(items, user_action); });
+ mScroller->setVisible(true);
+}
+
+void LLFloaterGLTFAssetEditor::onOpen(const LLSD& key)
+{
+ gIdleCallbacks.addFunction(idle, this);
+ loadFromSelection();
+}
+
+void LLFloaterGLTFAssetEditor::onClose(bool app_quitting)
+{
+ gIdleCallbacks.deleteFunction(idle, this);
+ mAsset = nullptr;
+ mObject = nullptr;
+}
+
+void LLFloaterGLTFAssetEditor::clearRoot()
+{
+ LLFolderViewFolder::folders_t::iterator folders_it = mFolderRoot->getFoldersBegin();
+ while (folders_it != mFolderRoot->getFoldersEnd())
+ {
+ (*folders_it)->destroyView();
+ folders_it = mFolderRoot->getFoldersBegin();
+ }
+ mNodeToItemMap.clear();
+}
+
+void LLFloaterGLTFAssetEditor::idle(void* user_data)
+{
+ LLFloaterGLTFAssetEditor* floater = (LLFloaterGLTFAssetEditor*)user_data;
+
+ if (floater->mFolderRoot)
+ {
+ floater->mFolderRoot->update();
+ }
+}
+
+void LLFloaterGLTFAssetEditor::loadItem(S32 id, const std::string& name, LLGLTFFolderItem::EType type, LLFolderViewFolder* parent)
+{
+ LLGLTFFolderItem* listener = new LLGLTFFolderItem(id, name, type, mGLTFViewModel);
+
+ LLFolderViewItem::Params params;
+ params.name(name);
+ params.creation_date(0);
+ params.root(mFolderRoot);
+ params.listener(listener);
+ params.rect(LLRect());
+ params.tool_tip = params.name;
+ params.font_color = mUIColor;
+ params.font_highlight_color = mUIColor;
+ LLFolderViewItem* view = LLUICtrlFactory::create<LLFolderViewItem>(params);
+
+ view->addToFolder(parent);
+ view->setVisible(true);
+}
+
+void LLFloaterGLTFAssetEditor::loadFromNode(S32 node_id, LLFolderViewFolder* parent)
+{
+ if (mAsset->mNodes.size() <= node_id)
+ {
+ return;
+ }
+
+ LL::GLTF::Node& node = mAsset->mNodes[node_id];
+
+ std::string name = node.mName;
+ if (node.mName.empty())
+ {
+ name = getString("node_tittle");
+ }
+ else
+ {
+ name = node.mName;
+ }
+
+ LLGLTFFolderItem* listener = new LLGLTFFolderItem(node_id, name, LLGLTFFolderItem::TYPE_NODE, mGLTFViewModel);
+
+ LLFolderViewFolder::Params p;
+ p.root = mFolderRoot;
+ p.listener = listener;
+ p.name = name;
+ p.tool_tip = name;
+ p.font_color = mUIColor;
+ p.font_highlight_color = mUIColor;
+ LLFolderViewFolder* view = LLUICtrlFactory::create<LLFolderViewFolder>(p);
+
+ view->addToFolder(parent);
+ view->setVisible(true);
+ view->setOpen(true);
+
+ mNodeToItemMap[node_id] = view;
+
+ for (S32& node_id : node.mChildren)
+ {
+ loadFromNode(node_id, view);
+ }
+
+ if (node.mMesh != LL::GLTF::INVALID_INDEX && mAsset->mMeshes.size() > node.mMesh)
+ {
+ std::string name = mAsset->mMeshes[node.mMesh].mName;
+ if (name.empty())
+ {
+ name = getString("mesh_tittle");
+ }
+ loadItem(node.mMesh, name, LLGLTFFolderItem::TYPE_MESH, view);
+ }
+
+ if (node.mSkin != LL::GLTF::INVALID_INDEX && mAsset->mSkins.size() > node.mSkin)
+ {
+ std::string name = mAsset->mSkins[node.mSkin].mName;
+ if (name.empty())
+ {
+ name = getString("skin_tittle");
+ }
+ loadItem(node.mSkin, name, LLGLTFFolderItem::TYPE_SKIN, view);
+ }
+
+ view->setChildrenInited(true);
+}
+
+void LLFloaterGLTFAssetEditor::loadFromSelection()
+{
+ clearRoot();
+
+ if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() != 1)
+ {
+ mAsset = nullptr;
+ mObject = nullptr;
+ return;
+ }
+
+ LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(NULL);
+ LLViewerObject* objectp = node->getObject();
+ if (!objectp)
+ {
+ mAsset = nullptr;
+ mObject = nullptr;
+ return;
+ }
+
+ if (!objectp->mGLTFAsset)
+ {
+ mAsset = nullptr;
+ mObject = nullptr;
+ return;
+ }
+ mAsset = objectp->mGLTFAsset;
+ mObject = objectp;
+
+ if (node->mName.empty())
+ {
+ setTitle(getString("floater_title"));
+ }
+ else
+ {
+ setTitle(node->mName);
+ }
+
+ LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
+ for (S32 i = 0; i < mAsset->mScenes.size(); i++)
+ {
+ LL::GLTF::Scene& scene = mAsset->mScenes[i];
+ std::string name = scene.mName;
+ if (scene.mName.empty())
+ {
+ name = getString("scene_tittle");
+ }
+ else
+ {
+ name = scene.mName;
+ }
+
+ LLGLTFFolderItem* listener = new LLGLTFFolderItem(i, name, LLGLTFFolderItem::TYPE_SCENE, mGLTFViewModel);
+
+ LLFolderViewFolder::Params p;
+ p.name = name;
+ p.root = mFolderRoot;
+ p.listener = listener;
+ p.tool_tip = name;
+ p.font_color = mUIColor;
+ p.font_highlight_color = mUIColor;
+ LLFolderViewFolder* view = LLUICtrlFactory::create<LLFolderViewFolder>(p);
+
+ view->addToFolder(mFolderRoot);
+ view->setVisible(true);
+ view->setOpen(true);
+
+ for (S32& node_id : scene.mNodes)
+ {
+ loadFromNode(node_id, view);
+ }
+ view->setChildrenInited(true);
+ }
+
+ mGLTFViewModel.requestSortAll();
+ mFolderRoot->setChildrenInited(true);
+ mFolderRoot->arrangeAll();
+ mFolderRoot->update();
+}
+
+void LLFloaterGLTFAssetEditor::dirty()
+{
+ if (!mObject || !mAsset || !mFolderRoot)
+ {
+ return;
+ }
+
+ if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() > 1)
+ {
+ if (getVisible())
+ {
+ closeFloater();
+ }
+ return;
+ }
+
+ LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(NULL);
+ if (!node)
+ {
+ // not yet updated?
+ // Todo: Subscribe to deletion in some way
+ return;
+ }
+
+ LLViewerObject* objectp = node->getObject();
+ if (mObject != objectp || !objectp->mGLTFAsset)
+ {
+ if (getVisible())
+ {
+ closeFloater();
+ }
+ return;
+ }
+
+ if (mAsset != objectp->mGLTFAsset)
+ {
+ loadFromSelection();
+ return;
+ }
+
+ auto found = mNodeToItemMap.find(node->mSelectedGLTFNode);
+ if (found != mNodeToItemMap.end())
+ {
+ LLFolderViewItem* itemp = found->second;
+ itemp->arrangeAndSet(true, false);
+ loadNodeTransforms(node->mSelectedGLTFNode);
+ }
+}
+
+void LLFloaterGLTFAssetEditor::onFolderSelectionChanged(const std::deque<LLFolderViewItem*>& items, bool user_action)
+{
+ if (items.empty())
+ {
+ setTransformsEnabled(false);
+ return;
+ }
+
+ LLFolderViewItem* item = items.front();
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ switch (vmi->getType())
+ {
+ case LLGLTFFolderItem::TYPE_SCENE:
+ {
+ setTransformsEnabled(false);
+ LLSelectMgr::getInstance()->selectObjectOnly(mObject, SELECT_ALL_TES, -1, -1);
+ break;
+ }
+ case LLGLTFFolderItem::TYPE_NODE:
+ {
+ setTransformsEnabled(true);
+ loadNodeTransforms(vmi->getItemId());
+ LLSelectMgr::getInstance()->selectObjectOnly(mObject, SELECT_ALL_TES, vmi->getItemId(), 0);
+ break;
+ }
+ case LLGLTFFolderItem::TYPE_MESH:
+ case LLGLTFFolderItem::TYPE_SKIN:
+ {
+ if (item->getParent()) // should be a node
+ {
+ LLFolderViewFolder* parent = item->getParentFolder();
+ LLGLTFFolderItem* parent_vmi = static_cast<LLGLTFFolderItem*>(parent->getViewModelItem());
+ LLSelectMgr::getInstance()->selectObjectOnly(mObject, SELECT_ALL_TES, parent_vmi->getItemId(), 0);
+ }
+
+ setTransformsEnabled(false);
+ break;
+ }
+ default:
+ {
+ setTransformsEnabled(false);
+ break;
+ }
+ }
+}
+
+void LLFloaterGLTFAssetEditor::setTransformsEnabled(bool val)
+{
+ mMenuClipboardPos->setEnabled(val);
+ mCtrlPosX->setEnabled(val);
+ mCtrlPosY->setEnabled(val);
+ mCtrlPosZ->setEnabled(val);
+ mMenuClipboardScale->setEnabled(val);
+ mCtrlScaleX->setEnabled(val);
+ mCtrlScaleY->setEnabled(val);
+ mCtrlScaleZ->setEnabled(val);
+ mMenuClipboardRot->setEnabled(val);
+ mCtrlRotX->setEnabled(val);
+ mCtrlRotY->setEnabled(val);
+ mCtrlRotZ->setEnabled(val);
+}
+
+void LLFloaterGLTFAssetEditor::loadNodeTransforms(S32 node_id)
+{
+ if (node_id < 0 || node_id >= mAsset->mNodes.size())
+ {
+ LL_ERRS() << "Node id out of range: " << node_id << LL_ENDL;
+ return;
+ }
+
+ LL::GLTF::Node& node = mAsset->mNodes[node_id];
+ node.makeTRSValid();
+
+ mCtrlPosX->set(node.mTranslation[0]);
+ mCtrlPosY->set(node.mTranslation[1]);
+ mCtrlPosZ->set(node.mTranslation[2]);
+
+ mCtrlScaleX->set(node.mScale[0]);
+ mCtrlScaleY->set(node.mScale[1]);
+ mCtrlScaleZ->set(node.mScale[2]);
+
+ LLQuaternion object_rot = LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3]);
+ object_rot.getEulerAngles(&(mLastEulerDegrees.mV[VX]), &(mLastEulerDegrees.mV[VY]), &(mLastEulerDegrees.mV[VZ]));
+ mLastEulerDegrees *= RAD_TO_DEG;
+ mLastEulerDegrees.mV[VX] = fmod(ll_round(mLastEulerDegrees.mV[VX], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+ mLastEulerDegrees.mV[VY] = fmod(ll_round(mLastEulerDegrees.mV[VY], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+ mLastEulerDegrees.mV[VZ] = fmod(ll_round(mLastEulerDegrees.mV[VZ], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+
+ mCtrlRotX->set(mLastEulerDegrees.mV[VX]);
+ mCtrlRotY->set(mLastEulerDegrees.mV[VY]);
+ mCtrlRotZ->set(mLastEulerDegrees.mV[VZ]);
+}
+
+void LLFloaterGLTFAssetEditor::onCommitTransform()
+{
+ if (!mFolderRoot)
+ {
+ LL_ERRS() << "Folder root not initialized" << LL_ENDL;
+ return;
+ }
+
+ LLFolderViewItem* item = mFolderRoot->getCurSelectedItem();
+ if (!item)
+ {
+ LL_ERRS() << "Nothing selected" << LL_ENDL;
+ return;
+ }
+
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ if (!vmi || vmi->getType() != LLGLTFFolderItem::TYPE_NODE)
+ {
+ LL_ERRS() << "Only nodes implemented" << LL_ENDL;
+ return;
+ }
+ S32 node_id = vmi->getItemId();
+ LL::GLTF::Node& node = mAsset->mNodes[node_id];
+
+ LL::GLTF::vec3 tr(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get());
+ node.setTranslation(tr);
+
+ LL::GLTF::vec3 scale(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get());
+ node.setScale(scale);
+
+ LLVector3 new_rot(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get());
+ new_rot.mV[VX] = ll_round(new_rot.mV[VX], OBJECT_ROTATION_PRECISION);
+ new_rot.mV[VY] = ll_round(new_rot.mV[VY], OBJECT_ROTATION_PRECISION);
+ new_rot.mV[VZ] = ll_round(new_rot.mV[VZ], OBJECT_ROTATION_PRECISION);
+
+ // Note: must compare before conversion to radians, some value can go 'around' 360
+ LLVector3 delta = new_rot - mLastEulerDegrees;
+
+ if (delta.magVec() >= 0.0005f)
+ {
+ mLastEulerDegrees = new_rot;
+ new_rot *= DEG_TO_RAD;
+
+ LLQuaternion rotation;
+ rotation.setQuat(new_rot.mV[VX], new_rot.mV[VY], new_rot.mV[VZ]);
+ LL::GLTF::quat q;
+ q[0] = rotation.mQ[VX];
+ q[1] = rotation.mQ[VY];
+ q[2] = rotation.mQ[VZ];
+ q[3] = rotation.mQ[VW];
+
+ node.setRotation(q);
+ }
+
+ mAsset->updateTransforms();
+}
+
+void LLFloaterGLTFAssetEditor::onMenuDoToSelected(const LLSD& userdata)
+{
+ std::string command = userdata.asString();
+
+ if (command == "psr_paste")
+ {
+ // todo: implement
+ // onPastePos();
+ // onPasteSize();
+ // onPasteRot();
+ }
+ else if (command == "pos_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "size_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "rot_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "psr_copy")
+ {
+ // onCopyPos();
+ // onCopySize();
+ // onCopyRot();
+ }
+ else if (command == "pos_copy")
+ {
+ // todo: implement
+ }
+ else if (command == "size_copy")
+ {
+ // todo: implement
+ }
+ else if (command == "rot_copy")
+ {
+ // todo: implement
+ }
+}
+
+bool LLFloaterGLTFAssetEditor::onMenuEnableItem(const LLSD& userdata)
+{
+ if (!mFolderRoot)
+ {
+ return false;
+ }
+
+ LLFolderViewItem* item = mFolderRoot->getCurSelectedItem();
+ if (!item)
+ {
+ return false;
+ }
+
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ if (!vmi || vmi->getType() != LLGLTFFolderItem::TYPE_NODE)
+ {
+ return false;
+ }
+
+ std::string command = userdata.asString();
+ if (command == "pos_paste" || command == "size_paste" || command == "rot_paste")
+ {
+ // todo: implement
+ return true;
+ }
+ if (command == "psr_copy")
+ {
+ // todo: implement
+ return true;
+ }
+
+ return false;
+}
+