local mesh beta
parent
01d857ec17
commit
c38855897e
|
|
@ -222,6 +222,11 @@ set(viewer_SOURCE_FILES
|
|||
# <FS:Ansariel> [Legacy Bake]
|
||||
llagentwearablesfetch.cpp
|
||||
|
||||
# local mesh
|
||||
lllocalmesh.cpp
|
||||
llfloaterlocalmesh.cpp
|
||||
lllocalmeshimportdae.cpp
|
||||
|
||||
llaccountingcostmanager.cpp
|
||||
llaisapi.cpp
|
||||
llagent.cpp
|
||||
|
|
@ -996,6 +1001,11 @@ set(viewer_HEADER_FILES
|
|||
# <FS:Ansariel> [Legacy Bake]
|
||||
llagentwearablesfetch.h
|
||||
|
||||
# local mesh
|
||||
lllocalmesh.h
|
||||
llfloaterlocalmesh.h
|
||||
lllocalmeshimportdae.h
|
||||
|
||||
llaccountingcostmanager.h
|
||||
llaisapi.h
|
||||
llagent.h
|
||||
|
|
|
|||
|
|
@ -0,0 +1,613 @@
|
|||
|
||||
// linden headers
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
|
||||
#include "llfilepicker.h"
|
||||
#include "llviewermenufile.h"
|
||||
#include "llscrolllistctrl.h"
|
||||
#include "llcheckboxctrl.h"
|
||||
#include "llviewertexteditor.h"
|
||||
#include "llcombobox.h"
|
||||
#include "llselectmgr.h"
|
||||
#include "lltoolmgr.h"
|
||||
#include "lltoolcomp.h"
|
||||
|
||||
#include "llviewerobjectlist.h"
|
||||
|
||||
// own headers
|
||||
#include "llfloaterlocalmesh.h"
|
||||
#include "lllocalmesh.h"
|
||||
|
||||
static const S32 LOCAL_TRACKING_ID_COLUMN = 4;
|
||||
|
||||
|
||||
/*================================*/
|
||||
/* LLFloaterLocalMeshFilePicker */
|
||||
/*================================*/
|
||||
class LLFloaterLocalMeshFilePicker : public LLFilePickerThread
|
||||
{
|
||||
public:
|
||||
LLFloaterLocalMeshFilePicker(LLFloaterLocalMesh* parent_floater);
|
||||
virtual void notify(const std::vector<std::string>& filenames);
|
||||
|
||||
private:
|
||||
LLFloaterLocalMesh* mParentFloater;
|
||||
};
|
||||
|
||||
LLFloaterLocalMeshFilePicker::LLFloaterLocalMeshFilePicker(LLFloaterLocalMesh* parent_floater)
|
||||
: LLFilePickerThread(LLFilePicker::FFLOAD_COLLADA)
|
||||
{
|
||||
mParentFloater = parent_floater;
|
||||
}
|
||||
|
||||
void LLFloaterLocalMeshFilePicker::notify(const std::vector<std::string>& filenames)
|
||||
{
|
||||
if ((!mParentFloater) || filenames.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mParentFloater->onBtnAddCallback(filenames[0]);
|
||||
}
|
||||
|
||||
|
||||
/*======================*/
|
||||
/* LLFloaterLocalMesh */
|
||||
/*======================*/
|
||||
LLFloaterLocalMesh::LLFloaterLocalMesh(const LLSD & key) :
|
||||
LLFloater(key)
|
||||
{
|
||||
mLastSelectedObject.setNull();
|
||||
}
|
||||
|
||||
LLFloaterLocalMesh::~LLFloaterLocalMesh(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onOpen(const LLSD & key)
|
||||
{
|
||||
reloadFileList(false);
|
||||
|
||||
// register with local mesh system
|
||||
LLLocalMeshSystem::getInstance()->registerFloaterPointer(this);
|
||||
|
||||
// toggle select tool
|
||||
toggleSelectTool(true);
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onClose(bool app_quitting)
|
||||
{
|
||||
// deregister from local mesh system
|
||||
LLLocalMeshSystem::getInstance()->registerFloaterPointer(nullptr);
|
||||
|
||||
// toggle select tool
|
||||
toggleSelectTool(false);
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onSelectionChangedCallback()
|
||||
{
|
||||
reloadLowerUI();
|
||||
}
|
||||
|
||||
BOOL LLFloaterLocalMesh::postBuild()
|
||||
{
|
||||
childSetAction("btn_add", LLFloaterLocalMesh::onBtnAdd, this);
|
||||
childSetAction("btn_reload", LLFloaterLocalMesh::onBtnReload, this);
|
||||
childSetAction("btn_remove", LLFloaterLocalMesh::onBtnRemove, this);
|
||||
childSetAction("btn_apply", LLFloaterLocalMesh::onBtnApply, this);
|
||||
childSetAction("btn_clear", LLFloaterLocalMesh::onBtnClear, this);
|
||||
childSetAction("btn_showlog", LLFloaterLocalMesh::onBtnShowLog, this);
|
||||
|
||||
LLScrollListCtrl* scroll_ctrl = getChild<LLScrollListCtrl>("l_name_list");
|
||||
if (scroll_ctrl)
|
||||
{
|
||||
scroll_ctrl->setCommitCallback(boost::bind(&LLFloaterLocalMesh::onFileListCommitCallback, this));
|
||||
}
|
||||
|
||||
reloadLowerUI();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::draw()
|
||||
{
|
||||
// check if selection has changed.
|
||||
LLUUID selected_id;
|
||||
selected_id.setNull();
|
||||
|
||||
auto current_object = LLSelectMgr::getInstance()->getSelection()->getFirstObject();
|
||||
if (current_object)
|
||||
{
|
||||
selected_id = current_object->getID();
|
||||
}
|
||||
|
||||
if (selected_id != mLastSelectedObject)
|
||||
{
|
||||
mLastSelectedObject = selected_id;
|
||||
onSelectionChangedCallback();
|
||||
}
|
||||
|
||||
// continue drawing
|
||||
LLFloater::draw();
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onBtnAdd(void* userdata)
|
||||
{
|
||||
LLFloaterLocalMesh* self = (LLFloaterLocalMesh*)userdata;
|
||||
(new LLFloaterLocalMeshFilePicker(self))->getFile();
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onBtnAddCallback(std::string filename)
|
||||
{
|
||||
bool try_lods = false;
|
||||
auto checkbox_use_lods = getChild<LLCheckBoxCtrl>("chkbox_use_lods");
|
||||
if (checkbox_use_lods)
|
||||
{
|
||||
try_lods = checkbox_use_lods->get();
|
||||
}
|
||||
|
||||
LLLocalMeshSystem::getInstance()->addFile(filename, try_lods);
|
||||
}
|
||||
|
||||
|
||||
void LLFloaterLocalMesh::onBtnReload(void* userdata)
|
||||
{
|
||||
LLFloaterLocalMesh* self = (LLFloaterLocalMesh*)userdata;
|
||||
if (!self)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LLScrollListCtrl* scroll_ctrl = self->getChild<LLScrollListCtrl>("l_name_list");
|
||||
if (!scroll_ctrl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto selected_item = scroll_ctrl->getFirstSelected();
|
||||
if (!selected_item)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto selected_column = selected_item->getColumn(LOCAL_TRACKING_ID_COLUMN);
|
||||
if (!selected_column)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LLUUID selected_id = selected_column->getValue().asUUID();
|
||||
LLLocalMeshSystem::getInstance()->reloadFile(selected_id);
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onBtnRemove(void* userdata)
|
||||
{
|
||||
LLFloaterLocalMesh* self = (LLFloaterLocalMesh*)userdata;
|
||||
if (!self)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LLScrollListCtrl* scroll_ctrl = self->getChild<LLScrollListCtrl>("l_name_list");
|
||||
if (!scroll_ctrl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// more checks necessary, apparently.
|
||||
auto selected_item = scroll_ctrl->getFirstSelected();
|
||||
if (!selected_item)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto selected_column = selected_item->getColumn(LOCAL_TRACKING_ID_COLUMN);
|
||||
if (!selected_column)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LLUUID selected_id = selected_column->getValue().asUUID();
|
||||
LLLocalMeshSystem::getInstance()->deleteFile(selected_id);
|
||||
self->reloadLowerUI();
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onBtnApply(void* userdata)
|
||||
{
|
||||
LLFloaterLocalMesh* self = (LLFloaterLocalMesh*)userdata;
|
||||
if (!self)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check scroll_ctrl pointers
|
||||
auto scroll_ctrl = self->getChild<LLScrollListCtrl>("l_name_list");
|
||||
if (!scroll_ctrl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto scroll_ctrl_selected_item = scroll_ctrl->getFirstSelected();
|
||||
if (!scroll_ctrl_selected_item)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto scroll_ctrl_selected_column = scroll_ctrl_selected_item->getColumn(LOCAL_TRACKING_ID_COLUMN);
|
||||
if (!scroll_ctrl_selected_column)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check combobox pointer
|
||||
auto objectlist_combo_box = self->getChild<LLComboBox>("object_apply_list");
|
||||
if (!objectlist_combo_box)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// checkbox pointer
|
||||
bool use_scale = false;
|
||||
auto checkbox_use_scale = self->getChild<LLCheckBoxCtrl>("chkbox_use_scale");
|
||||
if (checkbox_use_scale)
|
||||
{
|
||||
use_scale = checkbox_use_scale->get();
|
||||
}
|
||||
|
||||
// make sure the selection is still valid, and if so - get id.
|
||||
LLUUID selected_object_id = self->getCurrentSelectionIfValid();
|
||||
if (selected_object_id.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// get selected local file id, object idx and use_scale boolean
|
||||
LLUUID file_id = scroll_ctrl_selected_column->getValue().asUUID();
|
||||
int object_idx = objectlist_combo_box->getFirstSelectedIndex();
|
||||
|
||||
// finally tell local mesh system to apply
|
||||
LLLocalMeshSystem::getInstance()->applyVObject(selected_object_id, file_id, object_idx, use_scale);
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onBtnClear(void* userdata)
|
||||
{
|
||||
LLFloaterLocalMesh* self = (LLFloaterLocalMesh*)userdata;
|
||||
if (!self)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LLUUID selected_object_id = self->getCurrentSelectionIfValid();
|
||||
if (selected_object_id.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LLLocalMeshSystem::getInstance()->clearVObject(selected_object_id);
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onBtnShowLog(void* userdata)
|
||||
{
|
||||
LLFloaterLocalMesh* self = (LLFloaterLocalMesh*)userdata;
|
||||
if (!self)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LLScrollListCtrl* scroll_ctrl = self->getChild<LLScrollListCtrl>("l_name_list");
|
||||
LLTextEditor* text_ctrl = self->getChild<LLViewerTextEditor>("text_log");
|
||||
LLButton* log_btn = self->getChild<LLButton>("btn_showlog");
|
||||
|
||||
if ((!scroll_ctrl) || (!text_ctrl) || (!log_btn))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool scroll_visible = scroll_ctrl->getVisible();
|
||||
|
||||
std::string btn_text = (scroll_visible) ? "Show List" : "Show Log";
|
||||
log_btn->setLabel(btn_text);
|
||||
|
||||
scroll_ctrl->setVisible(!scroll_visible);
|
||||
text_ctrl->setVisible(scroll_visible);
|
||||
|
||||
if (text_ctrl->getVisible())
|
||||
{
|
||||
text_ctrl->clear();
|
||||
|
||||
// make sure something is actually selected, or we crash
|
||||
auto scroll_ctrl_selected_item = scroll_ctrl->getFirstSelected();
|
||||
if (!scroll_ctrl_selected_item)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto scroll_ctrl_selected_column = scroll_ctrl_selected_item->getColumn(LOCAL_TRACKING_ID_COLUMN);
|
||||
if (!scroll_ctrl_selected_column)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// something is selected, yay. request log
|
||||
LLUUID file_id = scroll_ctrl_selected_column->getValue().asUUID();
|
||||
auto log = LLLocalMeshSystem::getInstance()->getFileLog(file_id);
|
||||
|
||||
for (auto log_string : log)
|
||||
{
|
||||
text_ctrl->appendText(log_string, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::reloadFileList(bool keep_selection)
|
||||
{
|
||||
LLScrollListCtrl* scroll_ctrl = getChild<LLScrollListCtrl>("l_name_list");
|
||||
if (!scroll_ctrl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto& fileinfo_vec = LLLocalMeshSystem::getInstance()->getFileInfoVector();
|
||||
int selected_num = scroll_ctrl->getFirstSelectedIndex();
|
||||
|
||||
scroll_ctrl->clearRows();
|
||||
for (auto& cinfo : fileinfo_vec)
|
||||
{
|
||||
std::string status_text;
|
||||
switch (cinfo.mStatus)
|
||||
{
|
||||
case LLLocalMeshFile::LLLocalMeshFileStatus::STATUS_NONE:
|
||||
{
|
||||
status_text = "None";
|
||||
break;
|
||||
}
|
||||
|
||||
case LLLocalMeshFile::LLLocalMeshFileStatus::STATUS_LOADING:
|
||||
{
|
||||
status_text = "Loading";
|
||||
break;
|
||||
}
|
||||
|
||||
case LLLocalMeshFile::LLLocalMeshFileStatus::STATUS_ACTIVE:
|
||||
{
|
||||
status_text = "Active";
|
||||
break;
|
||||
}
|
||||
|
||||
case LLLocalMeshFile::LLLocalMeshFileStatus::STATUS_ERROR:
|
||||
{
|
||||
status_text = "Error";
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
status_text = "Error";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string lod_state;
|
||||
for (int lod_reverse_iter = 3; lod_reverse_iter >= 0; --lod_reverse_iter)
|
||||
{
|
||||
std::string current_state = "[";
|
||||
|
||||
if (cinfo.mLODAvailability[lod_reverse_iter])
|
||||
{
|
||||
current_state += std::to_string(lod_reverse_iter);
|
||||
}
|
||||
|
||||
current_state += "]";
|
||||
|
||||
if (lod_reverse_iter > 0)
|
||||
{
|
||||
current_state += " ";
|
||||
}
|
||||
|
||||
lod_state += current_state;
|
||||
}
|
||||
|
||||
LLSD element;
|
||||
element["columns"][0]["column"] = "unit_status";
|
||||
element["columns"][0]["type"] = "text";
|
||||
element["columns"][0]["value"] = status_text;
|
||||
|
||||
element["columns"][1]["column"] = "unit_name";
|
||||
element["columns"][1]["type"] = "text";
|
||||
element["columns"][1]["value"] = cinfo.mName;
|
||||
|
||||
element["columns"][2]["column"] = "unit_lods";
|
||||
element["columns"][2]["type"] = "text";
|
||||
element["columns"][2]["value"] = lod_state;
|
||||
|
||||
element["columns"][3]["column"] = "unit_objects";
|
||||
element["columns"][3]["type"] = "text";
|
||||
element["columns"][3]["value"] = std::to_string(cinfo.mObjectList.size());
|
||||
|
||||
element["columns"][4]["column"] = "unit_id_HIDDEN";
|
||||
element["columns"][4]["type"] = "text";
|
||||
element["columns"][4]["value"] = cinfo.mLocalID;
|
||||
|
||||
scroll_ctrl->addElement(element);
|
||||
}
|
||||
|
||||
if ((keep_selection) && (selected_num >= 0) && (selected_num < scroll_ctrl->getItemCount()))
|
||||
{
|
||||
scroll_ctrl->selectNthItem(selected_num);
|
||||
reloadLowerUI();
|
||||
}
|
||||
else if (scroll_ctrl->getItemCount() == 1)
|
||||
{
|
||||
// if we've just got the one item, select it.
|
||||
// this prevents the need to explicitly select a list item when first adding a file.
|
||||
scroll_ctrl->selectNthItem(0);
|
||||
}
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::onFileListCommitCallback()
|
||||
{
|
||||
// i rather not register reloadLowerUI as callback, unless that's preferable?
|
||||
reloadLowerUI();
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::reloadLowerUI()
|
||||
{
|
||||
// do we have a valid target selected?
|
||||
bool selected_target_valid = !getCurrentSelectionIfValid().isNull();
|
||||
|
||||
// do we have a valid file in list and selected?
|
||||
bool selected_file_loaded = false;
|
||||
bool selected_file_active = false;
|
||||
LLUUID selected_file_id;
|
||||
selected_file_id.setNull();
|
||||
|
||||
auto scroll_ctrl = getChild<LLScrollListCtrl>("l_name_list");
|
||||
if (scroll_ctrl)
|
||||
{
|
||||
auto selected_item = scroll_ctrl->getFirstSelected();
|
||||
if (selected_item)
|
||||
{
|
||||
auto selected_column = selected_item->getColumn(LOCAL_TRACKING_ID_COLUMN);
|
||||
if (selected_column)
|
||||
{
|
||||
selected_file_id = selected_column->getValue().asUUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> selected_object_list;
|
||||
if (!selected_file_id.isNull())
|
||||
{
|
||||
auto& fileinfo_vector = LLLocalMeshSystem::getInstance()->getFileInfoVector();
|
||||
for (auto fileinfo : fileinfo_vector)
|
||||
{
|
||||
if (selected_file_id == fileinfo.mLocalID)
|
||||
{
|
||||
switch (fileinfo.mStatus)
|
||||
{
|
||||
case LLLocalMeshFile::STATUS_ACTIVE:
|
||||
{
|
||||
selected_file_loaded = true;
|
||||
selected_file_active = true;
|
||||
selected_object_list = fileinfo.mObjectList;
|
||||
break;
|
||||
}
|
||||
|
||||
case LLLocalMeshFile::STATUS_ERROR:
|
||||
{
|
||||
selected_file_loaded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// no other status enables anything.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// checks done
|
||||
// toggle target-relevant elements
|
||||
auto btn_clear = getChild<LLButton>("btn_clear");
|
||||
if (btn_clear)
|
||||
{
|
||||
btn_clear->setEnabled(selected_target_valid);
|
||||
}
|
||||
|
||||
// toggle elements that need a file to be loaded, even if it's got an error
|
||||
auto btn_remove = getChild<LLButton>("btn_remove");
|
||||
if (btn_remove)
|
||||
{
|
||||
btn_remove->setEnabled(selected_file_loaded);
|
||||
|
||||
}
|
||||
|
||||
auto btn_reload = getChild<LLButton>("btn_reload");
|
||||
if (btn_reload)
|
||||
{
|
||||
btn_reload->setEnabled(selected_file_loaded);
|
||||
}
|
||||
|
||||
// apply needs a target to be selected AND a file to be loaded and active
|
||||
auto btn_apply = getChild<LLButton>("btn_apply");
|
||||
if (btn_apply)
|
||||
{
|
||||
btn_apply->setEnabled((selected_target_valid) && (selected_file_active));
|
||||
}
|
||||
|
||||
// objectlist needs the file to be loaded and active
|
||||
auto objectlist_combo_box = getChild<LLComboBox>("object_apply_list");
|
||||
if (objectlist_combo_box)
|
||||
{
|
||||
objectlist_combo_box->setEnabled(selected_file_active);
|
||||
objectlist_combo_box->clearRows();
|
||||
|
||||
// and if it is loaded & active, fill object list
|
||||
if ((selected_file_active) && (!selected_object_list.empty()))
|
||||
{
|
||||
for (auto object_name : selected_object_list)
|
||||
{
|
||||
objectlist_combo_box->addSimpleElement(object_name);
|
||||
}
|
||||
|
||||
objectlist_combo_box->selectFirstItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLFloaterLocalMesh::toggleSelectTool(bool toggle)
|
||||
{
|
||||
// hiding this in it's own function so i can experiment with the ux of
|
||||
// toggling it in either onOpen/onClose, or onFocusReceived/onFocusLost
|
||||
|
||||
if (toggle)
|
||||
{
|
||||
LLSelectMgr::getInstance()->setForceSelection(TRUE);
|
||||
LLToolMgr::getInstance()->setTransientTool(LLToolCompInspect::getInstance());
|
||||
|
||||
// this one's necessary for the tool to actually be active, go figure.
|
||||
mObjectSelection = LLSelectMgr::getInstance()->getSelection();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (LLToolMgr::getInstance()->getBaseTool() == LLToolCompInspect::getInstance())
|
||||
{
|
||||
LLToolMgr::getInstance()->clearTransientTool();
|
||||
}
|
||||
|
||||
LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset);
|
||||
}
|
||||
}
|
||||
|
||||
LLUUID LLFloaterLocalMesh::getCurrentSelectionIfValid()
|
||||
{
|
||||
LLUUID result;
|
||||
result.setNull();
|
||||
|
||||
auto current_object = gObjectList.findObject(mLastSelectedObject);
|
||||
if (!current_object)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// turning a non-mesh object into mesh is immensely more hacky, let's not do that.
|
||||
if (!current_object->isMesh())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// any other rules can be set here
|
||||
|
||||
result = mLastSelectedObject;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "llfloater.h"
|
||||
class LLObjectSelection;
|
||||
|
||||
|
||||
class LLFloaterLocalMesh : public LLFloater
|
||||
{
|
||||
public:
|
||||
LLFloaterLocalMesh(const LLSD& key);
|
||||
virtual ~LLFloaterLocalMesh(void);
|
||||
|
||||
public:
|
||||
void onOpen(const LLSD& key);
|
||||
void onClose(bool app_quitting);
|
||||
void onSelectionChangedCallback();
|
||||
virtual BOOL postBuild();
|
||||
virtual void draw();
|
||||
|
||||
/* add - loads a new file, adds it to the list and reads it.
|
||||
reload - re-loads a selected file, reapplies it to viewer objects.
|
||||
remove - clears all affected viewer objects and unloads selected file
|
||||
apply - applies selected file onto a selected viewer object
|
||||
clear - reverts a selected viewer object to it's normal state
|
||||
show log/show list - toggles between loaded file list, and log.
|
||||
*/
|
||||
static void onBtnAdd(void* userdata);
|
||||
void onBtnAddCallback(std::string filename);
|
||||
|
||||
static void onBtnReload(void* userdata);
|
||||
static void onBtnRemove(void* userdata);
|
||||
static void onBtnApply(void* userdata);
|
||||
static void onBtnClear(void* userdata);
|
||||
static void onBtnShowLog(void* userdata);
|
||||
|
||||
void reloadFileList(bool keep_selection);
|
||||
void onFileListCommitCallback();
|
||||
void reloadLowerUI();
|
||||
void toggleSelectTool(bool toggle);
|
||||
LLUUID getCurrentSelectionIfValid();
|
||||
|
||||
private:
|
||||
// llsafehandle is deprecated.
|
||||
LLPointer<LLObjectSelection> mObjectSelection;
|
||||
|
||||
// since we use this to check if selection changed,
|
||||
// and since uuid seems like a safer way to check rather than
|
||||
// comparing llvovolumes, we might as well refer to this
|
||||
// when querying what is actually selected.
|
||||
LLUUID mLastSelectedObject;
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,989 @@
|
|||
|
||||
|
||||
// linden headers
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
#include "llcallbacklist.h"
|
||||
#include "llviewerobjectlist.h"
|
||||
#include "llvovolume.h"
|
||||
#include "llmeshrepository.h"
|
||||
#include "llvolumemgr.h"
|
||||
#include "pipeline.h"
|
||||
#include "llviewercontrol.h"
|
||||
|
||||
// STL headers
|
||||
#include <chrono>
|
||||
|
||||
// boost headers
|
||||
#include "fix_macros.h"
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
// local mesh headers
|
||||
#include "lllocalmesh.h"
|
||||
#include "llfloaterlocalmesh.h"
|
||||
|
||||
// local mesh importers
|
||||
#include "lllocalmeshimportdae.h"
|
||||
|
||||
|
||||
/*==========================================*/
|
||||
/* LLLocalMeshFace: aka submesh denoted by */
|
||||
/* material assignment. holds per-face - */
|
||||
/* values for indices, bounding box and */
|
||||
/* vtx pos, normals, uv coords, weights. */
|
||||
/*==========================================*/
|
||||
void LLLocalMeshFace::setFaceBoundingBox(LLVector4 data_in, bool initial_values)
|
||||
{
|
||||
if (initial_values)
|
||||
{
|
||||
mFaceBoundingBox.first = data_in;
|
||||
mFaceBoundingBox.second = data_in;
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t array_iter = 0; array_iter < 4; ++array_iter)
|
||||
{
|
||||
mFaceBoundingBox.first[array_iter] = std::min(mFaceBoundingBox.first[array_iter], data_in[array_iter]);
|
||||
mFaceBoundingBox.second[array_iter] = std::max(mFaceBoundingBox.second[array_iter], data_in[array_iter]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*==========================================*/
|
||||
/* LLLocalMeshObject: collection of faces */
|
||||
/* has object name, transform & skininfo, */
|
||||
/* volumeid and volumeparams for vobj. */
|
||||
/* when applied - fills vobj volume. */
|
||||
/*==========================================*/
|
||||
LLLocalMeshObject::LLLocalMeshObject(std::string name)
|
||||
{
|
||||
mObjectName = name;
|
||||
mSculptID.generate();
|
||||
mVolumeParams.setSculptID(mSculptID, LL_SCULPT_TYPE_MESH);
|
||||
}
|
||||
|
||||
LLLocalMeshObject::~LLLocalMeshObject()
|
||||
{
|
||||
}
|
||||
|
||||
void LLLocalMeshObject::computeObjectBoundingBox()
|
||||
{
|
||||
// for the purposes of a bounding box, we only care for LOD3
|
||||
// unless the user is misusing lower LODs in a weird way.
|
||||
// we've already checked that mFaces[3] isn't empty in the invoking function.
|
||||
|
||||
// check anyway
|
||||
if (mFaces[3].empty())
|
||||
{
|
||||
// why are we still here, just to suffer?
|
||||
return;
|
||||
}
|
||||
|
||||
auto& lod3_faces = mFaces[3];
|
||||
|
||||
// .first is min, .second is max
|
||||
|
||||
// set initial values[face 0] to compare against
|
||||
auto& init_values = lod3_faces[0]->getFaceBoundingBox();
|
||||
mObjectBoundingBox.first = init_values.first;
|
||||
mObjectBoundingBox.second = init_values.second;
|
||||
|
||||
// process any additional faces
|
||||
for (size_t face_iter = 1; face_iter < lod3_faces.size(); ++face_iter)
|
||||
{
|
||||
auto& current_bbox_min = lod3_faces[face_iter]->getFaceBoundingBox().first;
|
||||
auto& current_bbox_max = lod3_faces[face_iter]->getFaceBoundingBox().second;
|
||||
|
||||
for (size_t array_iter = 0; array_iter < 4; ++array_iter)
|
||||
{
|
||||
mObjectBoundingBox.first.mV[array_iter] = std::min(mObjectBoundingBox.first.mV[array_iter], current_bbox_min.mV[array_iter]);
|
||||
mObjectBoundingBox.second.mV[array_iter] = std::max(mObjectBoundingBox.second.mV[array_iter], current_bbox_max.mV[array_iter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshObject::computeObjectTransform()
|
||||
{
|
||||
// most things here were tactfully stolen from LLModel::normalizeVolumeFaces()
|
||||
|
||||
// translation of bounding box to origin
|
||||
mObjectTranslation = mObjectBoundingBox.first;
|
||||
mObjectTranslation += mObjectBoundingBox.second;
|
||||
mObjectTranslation *= -0.5f;
|
||||
|
||||
// actual bounding box size
|
||||
mObjectSize = mObjectBoundingBox.second;
|
||||
mObjectSize -= mObjectBoundingBox.first;
|
||||
for (int vec_iter = 0; vec_iter < 4; ++vec_iter)
|
||||
{
|
||||
// make sure it can be divided by
|
||||
if (fabs(mObjectSize.mV[vec_iter]) <= F_APPROXIMATELY_ZERO)
|
||||
{
|
||||
mObjectSize.mV[vec_iter] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// bounding box scale in a 1Mx3 cube
|
||||
mObjectScale.set(1.f, 1.f, 1.f);
|
||||
for (int vec_iter = 0; vec_iter < 4; ++vec_iter)
|
||||
{
|
||||
mObjectScale.mV[vec_iter] = mObjectScale.mV[vec_iter] / mObjectSize.mV[vec_iter];
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshObject::normalizeFaceValues(LLLocalMeshFileLOD lod_iter)
|
||||
{
|
||||
// NOTE: uv extents
|
||||
// doesn't actually seem necessary, UDIM support "just works".
|
||||
|
||||
auto& lod_faces = mFaces[lod_iter];
|
||||
|
||||
// if current lod isn't in use, skip.
|
||||
if (lod_faces.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// process current lod
|
||||
for (auto& current_face : lod_faces)
|
||||
{
|
||||
// first transform the bounding boxes
|
||||
auto& current_submesh_bbox = current_face->getFaceBoundingBox();
|
||||
current_submesh_bbox.first += mObjectTranslation;
|
||||
current_submesh_bbox.second += mObjectTranslation;
|
||||
for (int vec_iter = 0; vec_iter < 4; ++vec_iter)
|
||||
{
|
||||
current_submesh_bbox.first.mV[vec_iter] *= mObjectScale.mV[vec_iter];
|
||||
current_submesh_bbox.second.mV[vec_iter] *= mObjectScale.mV[vec_iter];
|
||||
}
|
||||
|
||||
// then transform the positions
|
||||
auto& current_face_positions = current_face->getPositions();
|
||||
for (auto& current_position : current_face_positions)
|
||||
{
|
||||
current_position += mObjectTranslation;
|
||||
for (int vec_iter = 0; vec_iter < 4; ++vec_iter)
|
||||
{
|
||||
current_position.mV[vec_iter] *= mObjectScale.mV[vec_iter];
|
||||
}
|
||||
}
|
||||
|
||||
// if we have no normals, or they somehow don't match pos - skip here.
|
||||
auto& current_face_normals = current_face->getNormals();
|
||||
if (current_face_normals.size() != current_face_positions.size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// and last, transform the normals
|
||||
for (auto& current_normal : current_face_normals)
|
||||
{
|
||||
if (current_normal.isExactlyZero())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int vec_iter = 0; vec_iter < 4; ++vec_iter)
|
||||
{
|
||||
current_normal.mV[vec_iter] *= mObjectSize.mV[vec_iter];
|
||||
}
|
||||
|
||||
current_normal.normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshObject::fillVolume(LLLocalMeshFileLOD lod)
|
||||
{
|
||||
// check if we have data for [lod]
|
||||
if (mFaces[lod].empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// generate face data
|
||||
std::vector<LLVolumeFace> new_faces;
|
||||
bool weight_attributes_found = false;
|
||||
|
||||
for (auto& current_submesh : mFaces[lod])
|
||||
{
|
||||
LLVolumeFace new_face;
|
||||
new_face.resizeVertices(current_submesh->getPositions().size());
|
||||
new_face.resizeIndices(current_submesh->getIndices().size());
|
||||
|
||||
// NOTE: uv extents
|
||||
// doesn't actually seem necessary, UDIM support "just works".
|
||||
|
||||
// position attribute
|
||||
for (size_t position_iter = 0; position_iter < current_submesh->getPositions().size(); ++position_iter)
|
||||
{
|
||||
new_face.mPositions[position_iter].loadua(current_submesh->getPositions()[position_iter].mV);
|
||||
}
|
||||
|
||||
// normal attribute
|
||||
for (size_t normal_iter = 0; normal_iter < current_submesh->getNormals().size(); ++normal_iter)
|
||||
{
|
||||
new_face.mNormals[normal_iter].loadua(current_submesh->getNormals()[normal_iter].mV);
|
||||
}
|
||||
|
||||
// uv attribute
|
||||
for (size_t uv_iter = 0; uv_iter < current_submesh->getUVs().size(); ++uv_iter)
|
||||
{
|
||||
new_face.mTexCoords[uv_iter] = current_submesh->getUVs()[uv_iter];
|
||||
}
|
||||
|
||||
// weight attribute
|
||||
if (!current_submesh->getSkin().empty())
|
||||
{
|
||||
weight_attributes_found = true;
|
||||
new_face.allocateWeights(current_submesh->getSkin().size());
|
||||
for (size_t weight_iter = 0; weight_iter < current_submesh->getSkin().size(); ++weight_iter)
|
||||
{
|
||||
auto current_local_weight = current_submesh->getSkin()[weight_iter];
|
||||
LLVector4 current_v4_weight;
|
||||
|
||||
for (int joint_iter = 0; joint_iter < 4; ++joint_iter)
|
||||
{
|
||||
auto jidx = current_local_weight.mJointIndices[joint_iter];
|
||||
if (jidx == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float cweight = current_local_weight.mJointWeights[joint_iter];
|
||||
float cjoint = (float)jidx;
|
||||
float combined = cweight + cjoint;
|
||||
current_v4_weight[joint_iter] = combined;
|
||||
}
|
||||
|
||||
new_face.mWeights[weight_iter].loadua(current_v4_weight.mV);
|
||||
}
|
||||
}
|
||||
|
||||
// transfer indices
|
||||
for (size_t index_iter = 0; index_iter < current_submesh->getNumIndices(); ++index_iter)
|
||||
{
|
||||
new_face.mIndices[index_iter] = current_submesh->getIndices()[index_iter];
|
||||
}
|
||||
|
||||
new_faces.push_back(new_face);
|
||||
}
|
||||
|
||||
// push data into relevant lod
|
||||
LLVolume* current_volume = LLPrimitive::getVolumeManager()->refVolume(mVolumeParams, lod);
|
||||
if (current_volume)
|
||||
{
|
||||
current_volume->copyFacesFrom(new_faces);
|
||||
current_volume->setMeshAssetLoaded(TRUE);
|
||||
LLPrimitive::getVolumeManager()->unrefVolume(current_volume);
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshObject::attachSkinInfo()
|
||||
{
|
||||
auto skinmap_seeker = gMeshRepo.mSkinMap.find(mSculptID);
|
||||
if (skinmap_seeker == gMeshRepo.mSkinMap.end())
|
||||
{
|
||||
gMeshRepo.mSkinMap[mSculptID] = mMeshSkinInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: seems necessary, not tested without.
|
||||
skinmap_seeker->second = mMeshSkinInfo;
|
||||
}
|
||||
}
|
||||
|
||||
bool LLLocalMeshObject::getIsRiggedObject()
|
||||
{
|
||||
bool result = false;
|
||||
auto& main_lod_faces = mFaces[LLLocalMeshFileLOD::LOCAL_LOD_HIGH];
|
||||
|
||||
// main lod isn't empty
|
||||
if (!main_lod_faces.empty())
|
||||
{
|
||||
auto& skin = main_lod_faces[0]->getSkin();
|
||||
if (!skin.empty())
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*==========================================*/
|
||||
/* LLLocalMeshFile: Single Unit */
|
||||
/* owns filenames [main and lods] */
|
||||
/* owns the loaded local mesh objects */
|
||||
/*==========================================*/
|
||||
LLLocalMeshFile::LLLocalMeshFile(std::string filename, bool try_lods)
|
||||
{
|
||||
// initialize safe defaults
|
||||
for (size_t lod_iter = 0; lod_iter < 4; ++lod_iter)
|
||||
{
|
||||
mFilenames[lod_iter].empty();
|
||||
mLastModified[lod_iter].clear();
|
||||
mLoadedSuccessfully[lod_iter] = false;
|
||||
}
|
||||
|
||||
mTryLODFiles = try_lods;
|
||||
mShortName.empty();
|
||||
mLoadingLog.clear();
|
||||
mExtension = LLLocalMeshFileExtension::EXTEN_NONE;
|
||||
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_NONE;
|
||||
mLocalMeshFileID.setNull();
|
||||
mLocalMeshFileNeedsUIUpdate = false;
|
||||
mLoadedObjectList.clear();
|
||||
mSavedObjectSculptIDs.clear();
|
||||
|
||||
pushLog("LLLocalMeshFile", "Initializing with filename: " + filename);
|
||||
|
||||
// check if main filename exists, just in case
|
||||
if (!boost::filesystem::exists(filename))
|
||||
{
|
||||
// filename provided doesn't exist, just stop.
|
||||
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ERROR;
|
||||
pushLog("LLLocalMeshFile", "Couldn't find filename: " + filename, true);
|
||||
return;
|
||||
}
|
||||
|
||||
mFilenames[3] = filename;
|
||||
mShortName = boost::filesystem::path(filename).filename().generic_string();
|
||||
|
||||
// check if we have a valid extension, can't switch with string can we?
|
||||
std::string exten_str = boost::filesystem::extension(filename);
|
||||
if (exten_str == ".dae")
|
||||
{
|
||||
mExtension = LLLocalMeshFileExtension::EXTEN_DAE;
|
||||
pushLog("LLLocalMeshFile", "Extension found: COLLADA");
|
||||
}
|
||||
// add more ifs for different types, in lieu of a better approach?
|
||||
|
||||
// if no extensions found, stop.
|
||||
if (mExtension == LLLocalMeshFileExtension::EXTEN_NONE)
|
||||
{
|
||||
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ERROR;
|
||||
pushLog("LLLocalMeshFile", "No valid file extension found.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
mLocalMeshFileID.generate();
|
||||
|
||||
// actually loads the files
|
||||
reloadLocalMeshObjects(true);
|
||||
}
|
||||
|
||||
LLLocalMeshFile::~LLLocalMeshFile()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LLLocalMeshFile::reloadLocalMeshObjects(bool initial_load)
|
||||
{
|
||||
// if we're already loading - nothing should be calling for a reload,
|
||||
// but just in case..
|
||||
if (mLocalMeshFileStatus == LLLocalMeshFileStatus::STATUS_LOADING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're reloading, may be a good time to clear the log
|
||||
if (!initial_load)
|
||||
{
|
||||
mLoadingLog.clear();
|
||||
|
||||
// before reloading (and regenerating sculpt ids for each object) save these ids
|
||||
// we'll need them to find the list of vobjects affected by each local object,
|
||||
// that so we can update vobjects with the new local objects.
|
||||
|
||||
// clear it first just in case
|
||||
mSavedObjectSculptIDs.clear();
|
||||
|
||||
for (auto& local_object : mLoadedObjectList)
|
||||
{
|
||||
auto id = local_object->getVolumeParams().getSculptID();
|
||||
mSavedObjectSculptIDs.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_LOADING;
|
||||
mLocalMeshFileNeedsUIUpdate = true;
|
||||
|
||||
// another recheck that mFilenames[3] main file is present,
|
||||
// in case the file got deleted and the user hits reload - it'll error out here.
|
||||
if (!boost::filesystem::exists(mFilenames[3]))
|
||||
{
|
||||
// filename provided doesn't exist, just stop.
|
||||
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ERROR;
|
||||
pushLog("LLLocalMeshFile", "Couldn't find filename: " + mFilenames[3], true);
|
||||
return;
|
||||
}
|
||||
|
||||
// check for lod filenames
|
||||
if (mTryLODFiles)
|
||||
{
|
||||
size_t dot_position = mFilenames[3].find_last_of(".");
|
||||
if (dot_position == mFilenames[3].npos)
|
||||
{
|
||||
// should theoretically never happen, we did check extension before,
|
||||
// but "should never happen" doesn't mean we shouldn't check.
|
||||
pushLog("LLLocalMeshFile", "Filename extension error, loading stopped.", true);
|
||||
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
pushLog("LLLocalMeshFile", "Seeking LOD files...");
|
||||
// up to LOD2, LOD3 being the highest is always done by this point.
|
||||
for (size_t lodfile_iter = 0; lodfile_iter < 3; ++lodfile_iter)
|
||||
{
|
||||
std::string current_lod_filename = mFilenames[3];
|
||||
std::string lod_string = "_LOD" + std::to_string(lodfile_iter);
|
||||
|
||||
current_lod_filename.insert(dot_position, lod_string);
|
||||
if (boost::filesystem::exists(current_lod_filename))
|
||||
{
|
||||
pushLog("LLLocalMeshFile", "LOD filename " + current_lod_filename + " found, adding.");
|
||||
mFilenames[lodfile_iter] = current_lod_filename;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
pushLog("LLLocalMeshFile", "LOD filename " + current_lod_filename + " not found, skipping.");
|
||||
mFilenames[lodfile_iter].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
pushLog("LLLocalMeshFile", "Skipping LOD 2-0 files, as specified.");
|
||||
}
|
||||
|
||||
// if we are here we can assume at least mFilenames[3] is present
|
||||
// here we'll define a lambda to call through std::async, accessible through mAsyncFuture.
|
||||
// the lamnda returns bool if change happened, individual lod logs, and their status.
|
||||
auto lambda_loadfiles = [&]() mutable -> LLLocalMeshLoaderReply
|
||||
{
|
||||
bool change_happened = false;
|
||||
std::vector<std::string> log;
|
||||
std::array<bool, 4> lod_success = {false, false, false, false};
|
||||
|
||||
// iterate over every lod
|
||||
// we're counting back because LOD3 is most likely to have showstopper problems,
|
||||
// so i'd rather not load other lods if LOD3 is found to be broken.
|
||||
// int rather than size_t because we're iterating over LODS 3 to 0, stopping at -1
|
||||
for (signed int lod_idx = 3; lod_idx >= 0; --lod_idx)
|
||||
{
|
||||
LLLocalMeshFileLOD current_lod = static_cast<LLLocalMeshFileLOD>(lod_idx);
|
||||
|
||||
// do we have a filename for this lod?
|
||||
if (mFilenames[lod_idx].empty())
|
||||
{
|
||||
log.push_back("[ LLLocalMeshFile ] File for LOD " + std::to_string(lod_idx) + " was not found, skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// has the file been modified since we last checked?
|
||||
bool file_modified = updateLastModified(current_lod);
|
||||
if (!file_modified)
|
||||
{
|
||||
log.push_back("[ LLLocalMeshFile ] File for LOD " + std::to_string(lod_idx) + " was not modified, skipping.");
|
||||
lod_success[lod_idx] = mLoadedSuccessfully[lod_idx];
|
||||
continue;
|
||||
}
|
||||
|
||||
// up until here, skipping loading a lod is fine, after here - it's a sign of an error.
|
||||
|
||||
// clear the old objects, if any.
|
||||
if (lod_idx == LLLocalMeshFileLOD::LOCAL_LOD_HIGH)
|
||||
{
|
||||
mLoadedObjectList.clear();
|
||||
}
|
||||
|
||||
log.push_back("[ LLLocalMeshFile ] Attempting to load file for LOD " + std::to_string(lod_idx));
|
||||
switch (mExtension)
|
||||
{
|
||||
case LLLocalMeshFileExtension::EXTEN_DAE:
|
||||
{
|
||||
// pass it over to dae loader
|
||||
LLLocalMeshImportDAE importer;
|
||||
auto importer_result = importer.loadFile(this, current_lod);
|
||||
lod_success[lod_idx] = importer_result.first;
|
||||
|
||||
// NOTE: if not success - do not indicate change as not to affect existing vobjects?
|
||||
if (lod_success[lod_idx])
|
||||
{
|
||||
change_happened = true;
|
||||
}
|
||||
|
||||
auto importer_log = importer_result.second;
|
||||
log.reserve(log.size() + importer_log.size());
|
||||
log.insert(log.end(), importer_log.begin(), importer_log.end());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
log.push_back("[ LLLocalMeshFile ] ERROR, Loader for LOD " + std::to_string(lod_idx) + " called with invalid extension.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// by this point, it's only false if there's an actual error loading the file.
|
||||
if (!lod_success[lod_idx])
|
||||
{
|
||||
log.push_back("[ LLLocalMeshFile ] ERROR, attempted and failed to load LOD " + std::to_string(lod_idx) + ", stopping.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// just in case, recheck if we actually ended up loading anything
|
||||
auto& object_list = getObjectVector();
|
||||
if (object_list.empty())
|
||||
{
|
||||
log.push_back("[ LLLocalMeshFile ] ERROR, no objects loaded, stopping.");
|
||||
|
||||
// seems like a pretty serious error, error out all the lods manually,
|
||||
// and set change happened to false.
|
||||
for (size_t lod_iter = 0; lod_iter < 4; ++lod_iter)
|
||||
{
|
||||
lod_success[lod_iter] = false;
|
||||
}
|
||||
|
||||
change_happened = false;
|
||||
}
|
||||
|
||||
LLLocalMeshLoaderReply result;
|
||||
result.mChanged = change_happened;
|
||||
result.mLog = log;
|
||||
result.mStatus = lod_success;
|
||||
return result;
|
||||
};
|
||||
|
||||
mAsyncFuture = std::async(std::launch::async, lambda_loadfiles);
|
||||
}
|
||||
|
||||
LLLocalMeshFile::LLLocalMeshFileStatus LLLocalMeshFile::reloadLocalMeshObjectsCheck()
|
||||
{
|
||||
// only invalid if no loading is happening.
|
||||
if (mAsyncFuture.valid() == false)
|
||||
{
|
||||
return mLocalMeshFileStatus;
|
||||
}
|
||||
|
||||
// actually check if the thread is done, if no - return that we're still loading.
|
||||
// _Is_ready() doesn't seem to actually do what it promises to, how punny.
|
||||
// so we'll wait for zero milliseconds instead to check if ready.
|
||||
auto timeout = std::chrono::milliseconds(0);
|
||||
if (mAsyncFuture.wait_for(timeout) != std::future_status::ready)
|
||||
{
|
||||
// thread is still working
|
||||
return mLocalMeshFileStatus;
|
||||
}
|
||||
else
|
||||
{
|
||||
// thread is done working
|
||||
reloadLocalMeshObjectsCallback();
|
||||
}
|
||||
|
||||
// by now we have either active or error status, this return will lead to ui refresh.
|
||||
return mLocalMeshFileStatus;
|
||||
}
|
||||
|
||||
void LLLocalMeshFile::reloadLocalMeshObjectsCallback()
|
||||
{
|
||||
LLLocalMeshLoaderReply reply = mAsyncFuture.get();
|
||||
mLoadingLog.reserve(mLoadingLog.size() + reply.mLog.size());
|
||||
mLoadingLog.insert(mLoadingLog.end(), reply.mLog.begin(), reply.mLog.end());
|
||||
mLoadedSuccessfully = reply.mStatus;
|
||||
|
||||
// if LOD3 is ok, we're technically fine.
|
||||
if (mLoadedSuccessfully[3])
|
||||
{
|
||||
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ACTIVE;
|
||||
if (reply.mChanged)
|
||||
{
|
||||
updateVObjects();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ERROR;
|
||||
}
|
||||
|
||||
mLocalMeshFileNeedsUIUpdate = true;
|
||||
}
|
||||
|
||||
bool LLLocalMeshFile::updateLastModified(LLLocalMeshFileLOD lod)
|
||||
{
|
||||
bool file_updated = false;
|
||||
LLSD current_last_modified = mLastModified[lod];
|
||||
std::string current_filename = mFilenames[lod];
|
||||
|
||||
#ifndef LL_WINDOWS
|
||||
const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(mFilename));
|
||||
#else
|
||||
const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(utf8str_to_utf16str(current_filename)));
|
||||
#endif
|
||||
|
||||
LLSD new_last_modified = asctime(localtime(&temp_time));
|
||||
|
||||
if (new_last_modified.asString() != current_last_modified.asString())
|
||||
{
|
||||
file_updated = true;
|
||||
mLastModified[lod] = new_last_modified;
|
||||
}
|
||||
|
||||
return file_updated;
|
||||
}
|
||||
|
||||
bool LLLocalMeshFile::notifyNeedsUIUpdate()
|
||||
{
|
||||
bool result = mLocalMeshFileNeedsUIUpdate;
|
||||
|
||||
if (mLocalMeshFileNeedsUIUpdate)
|
||||
{
|
||||
// ui update can only be needed once.
|
||||
mLocalMeshFileNeedsUIUpdate = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
LLLocalMeshFile::LLLocalMeshFileInfo LLLocalMeshFile::getFileInfo()
|
||||
{
|
||||
LLLocalMeshFileInfo result;
|
||||
|
||||
result.mName = mShortName;
|
||||
result.mStatus = mLocalMeshFileStatus;
|
||||
result.mLocalID = mLocalMeshFileID;
|
||||
result.mLODAvailability = mLoadedSuccessfully;
|
||||
result.mObjectList.clear();
|
||||
|
||||
if (mLocalMeshFileStatus == LLLocalMeshFileStatus::STATUS_ACTIVE)
|
||||
{
|
||||
// fill object list
|
||||
auto& vector_objects = getObjectVector();
|
||||
for (auto& current_object : vector_objects)
|
||||
{
|
||||
std::string object_name = current_object->getObjectName();
|
||||
result.mObjectList.push_back(object_name);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLLocalMeshFile::updateVObjects()
|
||||
{
|
||||
for (size_t object_iter = 0; object_iter < mSavedObjectSculptIDs.size(); ++object_iter)
|
||||
{
|
||||
auto local_obj_sculpt_id = mSavedObjectSculptIDs[object_iter];
|
||||
auto affected_vobject_ids = gObjectList.findMeshObjectsBySculptID(local_obj_sculpt_id);
|
||||
for (auto current_vobject_id : affected_vobject_ids)
|
||||
{
|
||||
auto target_object_ptr = static_cast<LLVOVolume*>(gObjectList.findObject(current_vobject_id));
|
||||
|
||||
if (!target_object_ptr->mIsLocalMesh)
|
||||
{
|
||||
// in case for some reason we got the id of an unrelated object?
|
||||
// should never get here.
|
||||
continue;
|
||||
}
|
||||
|
||||
bool using_scale = target_object_ptr->mIsLocalMeshUsingScale;
|
||||
applyToVObject(current_vobject_id, object_iter, using_scale);
|
||||
}
|
||||
|
||||
// also, remove old skin from
|
||||
gMeshRepo.mSkinMap.erase(local_obj_sculpt_id);
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshFile::applyToVObject(LLUUID viewer_object_id, int object_index, bool use_scale)
|
||||
{
|
||||
// get the actual vovolume
|
||||
auto target_object = static_cast<LLVOVolume*>(gObjectList.findObject(viewer_object_id));
|
||||
if (target_object == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check if object_index is sane
|
||||
if ((object_index < 0) || (object_index >= mLoadedObjectList.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto object_params = mLoadedObjectList[object_index]->getVolumeParams();
|
||||
target_object->setVolume(object_params, 3);
|
||||
target_object->mIsLocalMesh = true;
|
||||
target_object->mIsLocalMeshUsingScale = use_scale;
|
||||
|
||||
for (int lod_reverse_iter = 3; lod_reverse_iter >= 0; --lod_reverse_iter)
|
||||
{
|
||||
if (!mLoadedSuccessfully[lod_reverse_iter])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LLLocalMeshFileLOD current_lod = static_cast<LLLocalMeshFileLOD>(lod_reverse_iter);
|
||||
mLoadedObjectList[object_index]->fillVolume(current_lod);
|
||||
}
|
||||
|
||||
if (mLoadedObjectList[object_index]->getIsRiggedObject())
|
||||
{
|
||||
mLoadedObjectList[object_index]->attachSkinInfo();
|
||||
}
|
||||
|
||||
if ((!target_object->isAttachment()) && use_scale)
|
||||
{
|
||||
auto scale = mLoadedObjectList[object_index]->getObjectSize();
|
||||
scale *= 0.01; /* NOTE: magic number */
|
||||
target_object->setScale(LLVector3(scale), false);
|
||||
}
|
||||
|
||||
// force refresh (selected/edit mode won't let it redraw otherwise)
|
||||
gPipeline.markRebuild(target_object->mDrawable, LLDrawable::REBUILD_VOLUME, FALSE);
|
||||
// NOTE: this ^^ (or lod change) causes renderer crash on mesh with degenerate primitives.
|
||||
}
|
||||
|
||||
void LLLocalMeshFile::pushLog(std::string who, std::string what, bool is_error)
|
||||
{
|
||||
std::string log_msg = "[ " + who + " ] ";
|
||||
if (is_error)
|
||||
{
|
||||
log_msg += "[ ERROR ] ";
|
||||
}
|
||||
|
||||
log_msg += what;
|
||||
mLoadingLog.push_back(log_msg);
|
||||
}
|
||||
|
||||
|
||||
/*==========================================*/
|
||||
/* LLLocalMeshSystem: Main Manager Class */
|
||||
/* user facing manager class */
|
||||
/*==========================================*/
|
||||
LLLocalMeshSystem::LLLocalMeshSystem()
|
||||
{
|
||||
mLoadedFileList.clear();
|
||||
mFileAsyncsOngoing = false;
|
||||
mFloaterPtr = nullptr;
|
||||
}
|
||||
|
||||
LLLocalMeshSystem::~LLLocalMeshSystem()
|
||||
{
|
||||
/* clear files, triggers releasing donor objects
|
||||
and destructs any held localmesh objects. */
|
||||
mLoadedFileList.clear();
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::addFile(std::string filename, bool try_lods)
|
||||
{
|
||||
auto loaded_file = std::make_unique<LLLocalMeshFile>(filename, try_lods);
|
||||
mLoadedFileList.push_back(std::move(loaded_file));
|
||||
triggerFloaterRefresh();
|
||||
|
||||
triggerCheckFileAsyncStatus();
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::deleteFile(LLUUID local_file_id)
|
||||
{
|
||||
bool delete_done = false;
|
||||
for(auto iterator = mLoadedFileList.begin(); iterator != mLoadedFileList.end();)
|
||||
{
|
||||
auto current_file = iterator->get();
|
||||
auto current_info = current_file->getFileInfo();
|
||||
|
||||
if (current_info.mLocalID == local_file_id)
|
||||
{
|
||||
// check if the file is currently in a state of loading
|
||||
// even though the ui button will be disabled while loading -
|
||||
// this check still feels necessary.
|
||||
if (current_info.mStatus == LLLocalMeshFile::LLLocalMeshFileStatus::STATUS_LOADING)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// found the requested by id file, it's not in the process of loading, time to delete it.
|
||||
iterator = mLoadedFileList.erase(std::move(iterator));
|
||||
delete_done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
|
||||
if (delete_done)
|
||||
{
|
||||
triggerFloaterRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::reloadFile(LLUUID local_file_id)
|
||||
{
|
||||
bool reload_started = false;
|
||||
for (auto iterator = mLoadedFileList.begin(); iterator != mLoadedFileList.end(); ++iterator)
|
||||
{
|
||||
auto current_file = iterator->get();
|
||||
auto current_info = current_file->getFileInfo();
|
||||
|
||||
if (current_info.mLocalID == local_file_id)
|
||||
{
|
||||
// check if the file is currently in a state of loading
|
||||
// same deal as above, ui button will be disabled but checking regardless.
|
||||
if (current_info.mStatus == LLLocalMeshFile::LLLocalMeshFileStatus::STATUS_LOADING)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// found the requested by id file, it's not loading, let's make it loading.
|
||||
current_file->reloadLocalMeshObjects();
|
||||
reload_started = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reload_started)
|
||||
{
|
||||
triggerCheckFileAsyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::applyVObject(LLUUID viewer_object_id, LLUUID local_file_id, int object_index, bool use_scale)
|
||||
{
|
||||
for (auto& loaded_file : mLoadedFileList)
|
||||
{
|
||||
if (loaded_file->getFileID() == local_file_id)
|
||||
{
|
||||
loaded_file->applyToVObject(viewer_object_id, object_index, use_scale);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::clearVObject(LLUUID viewer_object_id)
|
||||
{
|
||||
auto target_object = (LLVOVolume*)gObjectList.findObject(viewer_object_id);
|
||||
if (!target_object)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target_object->mIsLocalMesh = false;
|
||||
|
||||
// get original params
|
||||
auto sculpt_params = (LLSculptParams*)target_object->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
|
||||
auto sculpt_id = sculpt_params->getSculptTexture();
|
||||
|
||||
LLVolumeParams volume_params;
|
||||
volume_params.setSculptID(sculpt_id, LL_SCULPT_TYPE_MESH);
|
||||
target_object->setVolume(volume_params, 3);
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::triggerCheckFileAsyncStatus()
|
||||
{
|
||||
// async already ongoing, just wait patiently.
|
||||
if (mFileAsyncsOngoing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// this is here so it's set off /manually/ rather than by a function called through doOnIdleOneTime
|
||||
mFileAsyncsOngoing = true;
|
||||
checkFileAsyncStatus();
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::checkFileAsyncStatus()
|
||||
{
|
||||
// in case of misuse.
|
||||
// use triggerCheckFileAsyncStatus to trigger this instead.
|
||||
// the reason is: this function calls itself through doOnIdleOneTime,
|
||||
// so triggerCheckFileAsyncStatus is used this function doesn't set mFileAsyncsOngoing to true
|
||||
if (!mFileAsyncsOngoing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool found_active_asyncs = false;
|
||||
bool need_ui_update = false;
|
||||
|
||||
for (auto& current_file : mLoadedFileList)
|
||||
{
|
||||
// reloadLocalMeshObjectsCheck actually pings the future to check if ready,
|
||||
// and if yes - sets proper status, so we're pinging all files.
|
||||
// and if the file isn't loading - it'll just safely return.
|
||||
auto file_status = current_file->reloadLocalMeshObjectsCheck();
|
||||
if (file_status == LLLocalMeshFile::LLLocalMeshFileStatus::STATUS_LOADING)
|
||||
{
|
||||
found_active_asyncs = true;
|
||||
}
|
||||
|
||||
if (current_file->notifyNeedsUIUpdate())
|
||||
{
|
||||
need_ui_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_active_asyncs)
|
||||
{
|
||||
// come back here next tick to ping checkFileAsyncStatus again
|
||||
doOnIdleOneTime(boost::bind(&LLLocalMeshSystem::checkFileAsyncStatus, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
// nothing is updating, reflect that in mFileAsyncsOngoing
|
||||
mFileAsyncsOngoing = false;
|
||||
}
|
||||
|
||||
if (need_ui_update)
|
||||
{
|
||||
triggerFloaterRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::registerFloaterPointer(LLFloaterLocalMesh* floater_ptr)
|
||||
{
|
||||
// not checking for nullptr here, we use this to both register and de-register a floater,
|
||||
// so nullptr is legal here.
|
||||
mFloaterPtr = floater_ptr;
|
||||
}
|
||||
|
||||
void LLLocalMeshSystem::triggerFloaterRefresh()
|
||||
{
|
||||
if (mFloaterPtr)
|
||||
{
|
||||
mFloaterPtr->reloadFileList(true);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<LLLocalMeshFile::LLLocalMeshFileInfo> LLLocalMeshSystem::getFileInfoVector()
|
||||
{
|
||||
std::vector<LLLocalMeshFile::LLLocalMeshFileInfo> result;
|
||||
|
||||
for (auto& current_file : mLoadedFileList)
|
||||
{
|
||||
result.push_back(current_file->getFileInfo());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> LLLocalMeshSystem::getFileLog(LLUUID local_file_id)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
for (auto& current_file : mLoadedFileList)
|
||||
{
|
||||
if (current_file->getFileID() == local_file_id)
|
||||
{
|
||||
result = current_file->getFileLog();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
#pragma once
|
||||
|
||||
// linden headers
|
||||
#include "llmodel.h" // LLMeshSkinInfo
|
||||
|
||||
// STL headers
|
||||
#include <future>
|
||||
|
||||
|
||||
class LLFloaterLocalMesh;
|
||||
|
||||
enum LLLocalMeshFileLOD
|
||||
{
|
||||
LOCAL_LOD_LOWEST,
|
||||
LOCAL_LOD_LOW,
|
||||
LOCAL_LOD_MEDIUM,
|
||||
LOCAL_LOD_HIGH
|
||||
};
|
||||
|
||||
|
||||
/*==========================================*/
|
||||
/* LLLocalMeshFace: aka submesh denoted by */
|
||||
/* material assignment. holds per-face */
|
||||
/* values for indices, bounding box and */
|
||||
/* vtx pos, normals, uv coords, weights. */
|
||||
/*==========================================*/
|
||||
class LLLocalMeshFace
|
||||
{
|
||||
public:
|
||||
struct LLLocalMeshSkinUnit
|
||||
{
|
||||
std::array<int, 4> mJointIndices;
|
||||
std::array<float, 4> mJointWeights;
|
||||
};
|
||||
|
||||
public:
|
||||
void setFaceBoundingBox(LLVector4 data_in, bool initial_values = false);
|
||||
|
||||
int getNumVerts() { return mPositions.size(); }
|
||||
int getNumIndices() { return mIndices.size(); }
|
||||
|
||||
std::vector<int>& getIndices() { return mIndices; };
|
||||
std::vector<LLVector4>& getPositions() { return mPositions; };
|
||||
std::vector<LLVector4>& getNormals() { return mNormals; };
|
||||
std::vector<LLVector2>& getUVs() { return mUVs; };
|
||||
std::vector<LLLocalMeshSkinUnit>& getSkin() { return mSkin; }
|
||||
std::pair<LLVector4, LLVector4>& getFaceBoundingBox() { return mFaceBoundingBox; }
|
||||
|
||||
private:
|
||||
std::vector<int> mIndices;
|
||||
std::vector<LLVector4> mPositions;
|
||||
std::vector<LLVector4> mNormals;
|
||||
std::vector<LLVector2> mUVs;
|
||||
std::vector<LLLocalMeshSkinUnit> mSkin;
|
||||
std::pair<LLVector4, LLVector4> mFaceBoundingBox;
|
||||
};
|
||||
|
||||
|
||||
/*==========================================*/
|
||||
/* LLLocalMeshObject: collection of faces */
|
||||
/* has object name, transform & skininfo, */
|
||||
/* volumeid and volumeparams for vobj. */
|
||||
/* when applied - fills vobj volume. */
|
||||
/*==========================================*/
|
||||
class LLLocalMeshObject
|
||||
{
|
||||
public:
|
||||
// life cycle management
|
||||
LLLocalMeshObject(std::string name);
|
||||
~LLLocalMeshObject();
|
||||
|
||||
public:
|
||||
// translation and scale
|
||||
void computeObjectBoundingBox();
|
||||
void computeObjectTransform();
|
||||
void normalizeFaceValues(LLLocalMeshFileLOD lod_iter);
|
||||
|
||||
// applying local object to viewer object
|
||||
void fillVolume(LLLocalMeshFileLOD lod);
|
||||
void attachSkinInfo();
|
||||
|
||||
// getters
|
||||
std::vector<std::unique_ptr<LLLocalMeshFace>>& getFaces(LLLocalMeshFileLOD lod) { return mFaces[lod]; };
|
||||
std::pair<LLVector4, LLVector4>& getObjectBoundingBox() { return mObjectBoundingBox; };
|
||||
std::string getObjectName() { return mObjectName; };
|
||||
LLVector4 getObjectTranslation() { return mObjectTranslation; };
|
||||
LLVector4 getObjectSize() { return mObjectSize; };
|
||||
LLVector4 getObjectScale() { return mObjectScale; };
|
||||
LLMeshSkinInfo& getObjectMeshSkinInfo() { return mMeshSkinInfo; };
|
||||
LLVolumeParams getVolumeParams() { return mVolumeParams; };
|
||||
bool getIsRiggedObject();
|
||||
|
||||
private:
|
||||
// internal data keeping
|
||||
std::array<std::vector<std::unique_ptr<LLLocalMeshFace>>, 4> mFaces;
|
||||
std::pair<LLVector4, LLVector4> mObjectBoundingBox;
|
||||
std::string mObjectName;
|
||||
LLVector4 mObjectTranslation;
|
||||
LLVector4 mObjectSize;
|
||||
LLVector4 mObjectScale;
|
||||
|
||||
// vovolume
|
||||
LLMeshSkinInfo mMeshSkinInfo;
|
||||
LLUUID mSculptID;
|
||||
LLVolumeParams mVolumeParams;
|
||||
};
|
||||
|
||||
|
||||
/*==========================================*/
|
||||
/* LLLocalMeshFile: Single Unit */
|
||||
/* owns filenames [main and lods] */
|
||||
/* owns the loaded local mesh objects */
|
||||
/*==========================================*/
|
||||
class LLLocalMeshFile
|
||||
{
|
||||
// class specific types
|
||||
public:
|
||||
enum LLLocalMeshFileStatus
|
||||
{
|
||||
STATUS_NONE,
|
||||
STATUS_LOADING,
|
||||
STATUS_ACTIVE,
|
||||
STATUS_ERROR
|
||||
};
|
||||
|
||||
// for future gltf support, possibly more.
|
||||
enum LLLocalMeshFileExtension
|
||||
{
|
||||
EXTEN_DAE,
|
||||
EXTEN_NONE
|
||||
};
|
||||
|
||||
struct LLLocalMeshFileInfo
|
||||
{
|
||||
std::string mName;
|
||||
LLLocalMeshFileStatus mStatus;
|
||||
LLUUID mLocalID;
|
||||
std::array<bool, 4> mLODAvailability;
|
||||
std::vector<std::string> mObjectList;
|
||||
};
|
||||
|
||||
struct LLLocalMeshLoaderReply
|
||||
{
|
||||
bool mChanged;
|
||||
std::vector<std::string> mLog;
|
||||
std::array<bool, 4> mStatus;
|
||||
};
|
||||
|
||||
public:
|
||||
// life cycle management
|
||||
LLLocalMeshFile(std::string filename, bool try_lods);
|
||||
~LLLocalMeshFile();
|
||||
|
||||
// disallowing copy
|
||||
LLLocalMeshFile(const LLLocalMeshFile& local_mesh_file) = delete;
|
||||
LLLocalMeshFile& operator=(const LLLocalMeshFile& local_mesh_file) = delete;
|
||||
|
||||
public:
|
||||
// file loading
|
||||
void reloadLocalMeshObjects(bool initial_load = false);
|
||||
LLLocalMeshFileStatus reloadLocalMeshObjectsCheck();
|
||||
void reloadLocalMeshObjectsCallback();
|
||||
bool updateLastModified(LLLocalMeshFileLOD lod);
|
||||
std::vector<std::unique_ptr<LLLocalMeshObject>>& getObjectVector() { return mLoadedObjectList; };
|
||||
|
||||
// info getters
|
||||
bool notifyNeedsUIUpdate();
|
||||
LLLocalMeshFileInfo getFileInfo();
|
||||
std::string getFilename(LLLocalMeshFileLOD lod) { return mFilenames[lod]; };
|
||||
LLUUID getFileID() { return mLocalMeshFileID; };
|
||||
std::vector<std::string> getFileLog() { return mLoadingLog; };
|
||||
|
||||
// viewer object
|
||||
void updateVObjects();
|
||||
void applyToVObject(LLUUID viewer_object_id, int object_index, bool use_scale);
|
||||
|
||||
// misc
|
||||
void pushLog(std::string who, std::string what, bool is_error = false);
|
||||
|
||||
private:
|
||||
std::array<std::string, 4> mFilenames;
|
||||
std::array<LLSD, 4> mLastModified;
|
||||
std::array<bool, 4> mLoadedSuccessfully;
|
||||
bool mTryLODFiles;
|
||||
std::string mShortName;
|
||||
std::vector<std::string> mLoadingLog;
|
||||
LLLocalMeshFileExtension mExtension;
|
||||
LLLocalMeshFileStatus mLocalMeshFileStatus;
|
||||
LLUUID mLocalMeshFileID;
|
||||
bool mLocalMeshFileNeedsUIUpdate;
|
||||
|
||||
std::future<LLLocalMeshLoaderReply> mAsyncFuture;
|
||||
std::vector<std::unique_ptr<LLLocalMeshObject>> mLoadedObjectList;
|
||||
std::vector<LLUUID> mSavedObjectSculptIDs;
|
||||
};
|
||||
|
||||
|
||||
/*=============================*/
|
||||
/* LLLocalMeshSystem: */
|
||||
/* user facing manager class. */
|
||||
/*=============================*/
|
||||
class LLLocalMeshSystem : public LLSingleton<LLLocalMeshSystem>
|
||||
{
|
||||
|
||||
public:
|
||||
// life cycle management
|
||||
LLSINGLETON(LLLocalMeshSystem);
|
||||
~LLLocalMeshSystem();
|
||||
|
||||
public:
|
||||
// file management
|
||||
void addFile(std::string filename, bool try_lods);
|
||||
void deleteFile(LLUUID local_file_id);
|
||||
void reloadFile(LLUUID local_file_id);
|
||||
|
||||
// viewer object management
|
||||
void applyVObject(LLUUID viewer_object_id, LLUUID local_file_id, int object_index, bool use_scale);
|
||||
void clearVObject(LLUUID viewer_object_id);
|
||||
|
||||
// high level async support
|
||||
void triggerCheckFileAsyncStatus();
|
||||
void checkFileAsyncStatus();
|
||||
|
||||
// floater two-way communication
|
||||
void registerFloaterPointer(LLFloaterLocalMesh* floater_ptr);
|
||||
void triggerFloaterRefresh();
|
||||
std::vector<LLLocalMeshFile::LLLocalMeshFileInfo> getFileInfoVector();
|
||||
std::vector<std::string> getFileLog(LLUUID local_file_id);
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<LLLocalMeshFile>> mLoadedFileList;
|
||||
bool mFileAsyncsOngoing;
|
||||
LLFloaterLocalMesh* mFloaterPtr;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* @file lllocalmeshimportdae.h
|
||||
* @author Vaalith Jinn
|
||||
* @brief Local Mesh Import DAE header
|
||||
*
|
||||
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2011, 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$
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// linden headers
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
|
||||
// formal declarations
|
||||
class LLLocalMeshObject;
|
||||
class LLLocalMeshFace;
|
||||
class LLLocalMeshFileData;
|
||||
|
||||
// collada dom magic
|
||||
#if LL_MSVC
|
||||
#pragma warning (disable : 4263)
|
||||
#pragma warning (disable : 4264)
|
||||
#endif
|
||||
#include "dom/domMesh.h"
|
||||
#if LL_MSVC
|
||||
#pragma warning (default : 4263)
|
||||
#pragma warning (default : 4264)
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
NOTE: basically everything here is just a refactor of lldaeloader
|
||||
in what is hopefully more modernized and easier to understand c++
|
||||
and more importantly - less dependent on the caller.
|
||||
|
||||
there's only so much of a wheel you can reinvent.
|
||||
|
||||
one important note: i explicitly excluded the parts that add
|
||||
additional faces (on too many verts) and additional objects (on too many faces)
|
||||
because that perpetuates bad habits in bad creators.
|
||||
|
||||
if someone wants to upload a mesh object with 400 faces -
|
||||
let them do it by hand, or eat cake, or something.
|
||||
*/
|
||||
|
||||
class LLLocalMeshImportDAE
|
||||
{
|
||||
public:
|
||||
// use default constructor/destructor if we can get away with it.
|
||||
|
||||
public:
|
||||
typedef std::pair<bool, std::vector<std::string>> loadFile_return;
|
||||
loadFile_return loadFile(LLLocalMeshFile* data, LLLocalMeshFileLOD lod);
|
||||
bool processObject(domMesh* current_mesh, LLLocalMeshObject* current_object);
|
||||
bool processSkin(daeDatabase* collada_db, daeElement* collada_document_root, domMesh* current_mesh, domSkin* current_skin, std::unique_ptr<LLLocalMeshObject>& current_object);
|
||||
void processSkeletonJoint(domNode* current_node, std::map<std::string, std::string>& joint_map, std::map<std::string, LLMatrix4>& joint_transforms);
|
||||
|
||||
bool readMesh_CommonElements(const domInputLocalOffset_Array& inputs,
|
||||
int& offset_position, int& offset_normals, int& offset_uvmap, int& index_stride,
|
||||
domSource*& source_position, domSource*& source_normals, domSource*& source_uvmap);
|
||||
|
||||
std::string getElementName(daeElement* element_current, int fallback_index);
|
||||
void pushLog(std::string who, std::string what);
|
||||
|
||||
// mesh data read functions, basically refactored from what's already in lldaeloader
|
||||
// consolidating them into a single mega function makes for very sketchy readability.
|
||||
bool readMesh_Triangle(LLLocalMeshFace* data_out, const domTrianglesRef& data_in);
|
||||
bool readMesh_Polylist(LLLocalMeshFace* data_out, const domPolylistRef& data_in);
|
||||
|
||||
// NOTE: polygon schema
|
||||
//bool readMesh_Polygons(LLLocalMeshFace* data_out, const domPolygonsRef& data_in);
|
||||
|
||||
private:
|
||||
LLLocalMeshFileLOD mLod;
|
||||
std::vector<std::string> mLoadingLog;
|
||||
};
|
||||
|
||||
|
|
@ -221,6 +221,9 @@
|
|||
#include "NACLfloaterexploresounds.h"
|
||||
#include "particleeditor.h"
|
||||
#include "quickprefs.h"
|
||||
#include "llfloaterlocalmesh.h" // local mesh
|
||||
|
||||
|
||||
// handle secondlife:///app/openfloater/{NAME} URLs
|
||||
class LLFloaterOpenHandler : public LLCommandHandler
|
||||
{
|
||||
|
|
@ -521,6 +524,7 @@ void LLViewerFloaterReg::registerFloaters()
|
|||
LLFloaterReg::add("script_recover", "floater_script_recover.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptRecover>);
|
||||
LLFloaterReg::add("sound_explorer", "floater_NACL_explore_sounds.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<NACLFloaterExploreSounds>);
|
||||
LLFloaterReg::add("vram_usage", "floater_fs_vram_usage.xml", static_cast<LLFloaterBuildFunc>( &LLFloaterReg::build< FSFloaterVRAMUsage >) );
|
||||
LLFloaterReg::add("local_mesh_floater", "llfloaterlocalmesh.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLocalMesh>); // local mesh
|
||||
|
||||
LLFloaterReg::registerControlVariables(); // Make sure visibility and rect controls get preserved when saving
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2449,6 +2449,24 @@ S32 LLViewerObjectList::findReferences(LLDrawable *drawablep) const
|
|||
return num_refs;
|
||||
}
|
||||
|
||||
std::vector<LLUUID> LLViewerObjectList::findMeshObjectsBySculptID(LLUUID target_sculpt_id)
|
||||
{
|
||||
std::vector<LLUUID> result;
|
||||
// getting IDs rather than object/vovobject pointers here because
|
||||
// of the extra safety if later calling them through findObject
|
||||
|
||||
for (auto current_object : mObjects)
|
||||
{
|
||||
if ((current_object->isMesh()) &&
|
||||
(current_object->getVolume()) &&
|
||||
(current_object->getVolume()->getParams().getSculptID() == target_sculpt_id))
|
||||
{
|
||||
result.push_back(current_object->getID());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLViewerObjectList::orphanize(LLViewerObject *childp, U32 parent_id, U32 ip, U32 port)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ public:
|
|||
void cleanupReferences(LLViewerObject *objectp);
|
||||
|
||||
S32 findReferences(LLDrawable *drawablep) const; // Find references to drawable in all objects, and return value.
|
||||
std::vector<LLUUID> findMeshObjectsBySculptID(LLUUID target_sculpt_id);
|
||||
|
||||
S32 getOrphanParentCount() const { return (S32) mOrphanParents.size(); }
|
||||
S32 getOrphanCount() const { return mNumOrphans; }
|
||||
|
|
|
|||
|
|
@ -252,6 +252,8 @@ LLVOVolume::LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *re
|
|||
mMDCImplCount = 0;
|
||||
mLastRiggingInfoLOD = -1;
|
||||
mResetDebugText = false;
|
||||
mIsLocalMesh = false;
|
||||
mIsLocalMeshUsingScale = false;
|
||||
}
|
||||
|
||||
LLVOVolume::~LLVOVolume()
|
||||
|
|
@ -345,6 +347,14 @@ U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys,
|
|||
bool enfore_strict_object_check = LLGridManager::instance().isInSecondLife() && fsEnforceStrictObjectCheck;
|
||||
// </FS:Ansariel>
|
||||
|
||||
// LOCAL MESH TEMP TODO
|
||||
// local mesh begin // TODO: check if necessary here, or can be granular.
|
||||
if (mIsLocalMesh == true)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// local mesh end
|
||||
|
||||
LLColor4U color;
|
||||
const S32 teDirtyBits = (TEM_CHANGE_TEXTURE|TEM_CHANGE_COLOR|TEM_CHANGE_MEDIA);
|
||||
const bool previously_volume_changed = mVolumeChanged;
|
||||
|
|
@ -4557,6 +4567,7 @@ U32 LLVOVolume::getHighLODTriangleCount()
|
|||
U32 LLVOVolume::getLODTriangleCount(S32 lod)
|
||||
{
|
||||
U32 ret = 0;
|
||||
return ret; // LOCAL MESH TEMP TODO
|
||||
|
||||
LLVolume* volume = getVolume();
|
||||
|
||||
|
|
|
|||
|
|
@ -316,6 +316,8 @@ public:
|
|||
//virtual
|
||||
void updateRiggingInfo();
|
||||
S32 mLastRiggingInfoLOD;
|
||||
bool mIsLocalMesh;
|
||||
bool mIsLocalMeshUsingScale;
|
||||
|
||||
// Functions that deal with media, or media navigation
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
|
||||
<floater
|
||||
positioning="centered"
|
||||
legacy_header_height="18"
|
||||
can_resize="true"
|
||||
can_dock="false"
|
||||
can_close="true"
|
||||
height="300"
|
||||
width="505"
|
||||
min_width="505"
|
||||
min_height="300"
|
||||
layout="topleft"
|
||||
save_rect="true"
|
||||
save_visibility="true"
|
||||
single_instance="false"
|
||||
save_dock_state="false"
|
||||
title="Local Mesh">
|
||||
|
||||
<scroll_list
|
||||
name="l_name_list"
|
||||
left="10"
|
||||
top="25"
|
||||
width="485"
|
||||
height="223"
|
||||
follows="left|top|right|bottom"
|
||||
column_padding="0"
|
||||
draw_heading="true"
|
||||
multi_select="true"
|
||||
search_column="1"
|
||||
visible="true">
|
||||
<column name="unit_status" label="Status" width="55" />
|
||||
<column name="unit_name" label="Name" dynamicwidth="true" />
|
||||
<column name="unit_lods" label="LODs" width="55" />
|
||||
<column name="unit_objects" label="Objects" width="55" />
|
||||
<column name="unit_id_HIDDEN" label="ID" width="0" />
|
||||
</scroll_list>
|
||||
|
||||
<text_editor
|
||||
name="text_log"
|
||||
max_length="65536"
|
||||
left="10"
|
||||
top="25"
|
||||
width="485"
|
||||
height="223"
|
||||
follows="left|top|right|bottom"
|
||||
type="string"
|
||||
embedded_items="false"
|
||||
font="SansSerif"
|
||||
ignore_tab="false"
|
||||
layout="topleft"
|
||||
parse_urls="false"
|
||||
spellcheck="false"
|
||||
read_only="true"
|
||||
word_wrap="false"
|
||||
visible="false">
|
||||
</text_editor>
|
||||
|
||||
<button
|
||||
height="18"
|
||||
label="Apply"
|
||||
layout="topleft"
|
||||
left="10"
|
||||
top="253"
|
||||
name="btn_apply"
|
||||
width="75"
|
||||
visible="true"/>
|
||||
<check_box
|
||||
height="18"
|
||||
label="Apply Scale"
|
||||
layout="topleft"
|
||||
left="90"
|
||||
top="253"
|
||||
name="chkbox_use_scale"
|
||||
width="75"
|
||||
visible="true"
|
||||
follows="bottom"/>
|
||||
<combo_box
|
||||
height="18"
|
||||
layout="topleft"
|
||||
follows="left|top|bottom"
|
||||
name="object_apply_list"
|
||||
left="180"
|
||||
top="253"
|
||||
width="315"
|
||||
visible="true">
|
||||
/>
|
||||
</combo_box>
|
||||
|
||||
<button
|
||||
height="18"
|
||||
label="Add"
|
||||
layout="topleft"
|
||||
left="10"
|
||||
top="275"
|
||||
name="btn_add"
|
||||
width="75"
|
||||
visible="true"/>
|
||||
<check_box
|
||||
height="18"
|
||||
label="Use LODs"
|
||||
layout="topleft"
|
||||
left="90"
|
||||
top="275"
|
||||
name="chkbox_use_lods"
|
||||
width="75"
|
||||
visible="true"
|
||||
follows="bottom"/>
|
||||
<button
|
||||
height="18"
|
||||
label="Remove"
|
||||
layout="topleft"
|
||||
left="180"
|
||||
top="275"
|
||||
name="btn_remove"
|
||||
width="75"
|
||||
visible="true"/>
|
||||
<button
|
||||
height="18"
|
||||
label="Reload"
|
||||
layout="topleft"
|
||||
left="260"
|
||||
top="275"
|
||||
name="btn_reload"
|
||||
width="75"
|
||||
visible="true"/>
|
||||
<button
|
||||
height="18"
|
||||
label="Clear"
|
||||
layout="topleft"
|
||||
left="340"
|
||||
top="275"
|
||||
name="btn_clear"
|
||||
width="75"
|
||||
visible="true"/>
|
||||
<button
|
||||
height="18"
|
||||
label="Show Log"
|
||||
layout="topleft"
|
||||
left="420"
|
||||
top="275"
|
||||
name="btn_showlog"
|
||||
width="75"
|
||||
visible="true"/>
|
||||
|
||||
</floater>
|
||||
|
|
@ -2215,6 +2215,16 @@
|
|||
parameter="" />
|
||||
</menu_item_call>
|
||||
</menu>
|
||||
|
||||
<!-- local mesh begin -->
|
||||
<menu_item_call
|
||||
enabled="true"
|
||||
label="Local Mesh"
|
||||
name="local_mesh">
|
||||
<menu_item_call.on_click function="Floater.Show" parameter="local_mesh_floater"/>
|
||||
</menu_item_call>
|
||||
<!-- local mesh end -->
|
||||
|
||||
<menu_item_separator/>
|
||||
<menu_item_call
|
||||
enabled="false"
|
||||
|
|
|
|||
Loading…
Reference in New Issue