From c38855897e709314f0a7e650452f90e4ee3a48cd Mon Sep 17 00:00:00 2001 From: Seethe Date: Tue, 15 Mar 2022 08:26:43 +0200 Subject: [PATCH 01/11] local mesh beta --- indra/newview/CMakeLists.txt | 10 + indra/newview/llfloaterlocalmesh.cpp | 613 +++++++ indra/newview/llfloaterlocalmesh.h | 52 + indra/newview/lllocalmesh.cpp | 989 ++++++++++ indra/newview/lllocalmesh.h | 234 +++ indra/newview/lllocalmeshimportdae.cpp | 1620 +++++++++++++++++ indra/newview/lllocalmeshimportdae.h | 96 + indra/newview/llviewerfloaterreg.cpp | 4 + indra/newview/llviewerobjectlist.cpp | 18 + indra/newview/llviewerobjectlist.h | 1 + indra/newview/llvovolume.cpp | 11 + indra/newview/llvovolume.h | 2 + .../default/xui/en/llfloaterlocalmesh.xml | 145 ++ .../skins/default/xui/en/menu_viewer.xml | 10 + 14 files changed, 3805 insertions(+) create mode 100644 indra/newview/llfloaterlocalmesh.cpp create mode 100644 indra/newview/llfloaterlocalmesh.h create mode 100644 indra/newview/lllocalmesh.cpp create mode 100644 indra/newview/lllocalmesh.h create mode 100644 indra/newview/lllocalmeshimportdae.cpp create mode 100644 indra/newview/lllocalmeshimportdae.h create mode 100644 indra/newview/skins/default/xui/en/llfloaterlocalmesh.xml diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 2b37927565..a0e2a01612 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -222,6 +222,11 @@ set(viewer_SOURCE_FILES # [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 # [Legacy Bake] llagentwearablesfetch.h + # local mesh + lllocalmesh.h + llfloaterlocalmesh.h + lllocalmeshimportdae.h + llaccountingcostmanager.h llaisapi.h llagent.h diff --git a/indra/newview/llfloaterlocalmesh.cpp b/indra/newview/llfloaterlocalmesh.cpp new file mode 100644 index 0000000000..9d5d40217b --- /dev/null +++ b/indra/newview/llfloaterlocalmesh.cpp @@ -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& filenames); + +private: + LLFloaterLocalMesh* mParentFloater; +}; + +LLFloaterLocalMeshFilePicker::LLFloaterLocalMeshFilePicker(LLFloaterLocalMesh* parent_floater) + : LLFilePickerThread(LLFilePicker::FFLOAD_COLLADA) +{ + mParentFloater = parent_floater; +} + +void LLFloaterLocalMeshFilePicker::notify(const std::vector& 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("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("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("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("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("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("object_apply_list"); + if (!objectlist_combo_box) + { + return; + } + + // checkbox pointer + bool use_scale = false; + auto checkbox_use_scale = self->getChild("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("l_name_list"); + LLTextEditor* text_ctrl = self->getChild("text_log"); + LLButton* log_btn = self->getChild("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("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("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 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("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("btn_remove"); + if (btn_remove) + { + btn_remove->setEnabled(selected_file_loaded); + + } + + auto btn_reload = getChild("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("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("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; +} + + diff --git a/indra/newview/llfloaterlocalmesh.h b/indra/newview/llfloaterlocalmesh.h new file mode 100644 index 0000000000..f49568b644 --- /dev/null +++ b/indra/newview/llfloaterlocalmesh.h @@ -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 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; + +}; \ No newline at end of file diff --git a/indra/newview/lllocalmesh.cpp b/indra/newview/lllocalmesh.cpp new file mode 100644 index 0000000000..ac11827667 --- /dev/null +++ b/indra/newview/lllocalmesh.cpp @@ -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 + +// boost headers +#include "fix_macros.h" +#include + +// 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 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 log; + std::array 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(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(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(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(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(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 LLLocalMeshSystem::getFileInfoVector() +{ + std::vector result; + + for (auto& current_file : mLoadedFileList) + { + result.push_back(current_file->getFileInfo()); + } + + return result; +} + +std::vector LLLocalMeshSystem::getFileLog(LLUUID local_file_id) +{ + std::vector result; + + for (auto& current_file : mLoadedFileList) + { + if (current_file->getFileID() == local_file_id) + { + result = current_file->getFileLog(); + break; + } + } + + return result; +} + diff --git a/indra/newview/lllocalmesh.h b/indra/newview/lllocalmesh.h new file mode 100644 index 0000000000..b51a9e2838 --- /dev/null +++ b/indra/newview/lllocalmesh.h @@ -0,0 +1,234 @@ +#pragma once + +// linden headers +#include "llmodel.h" // LLMeshSkinInfo + +// STL headers +#include + + +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 mJointIndices; + std::array mJointWeights; + }; + + public: + void setFaceBoundingBox(LLVector4 data_in, bool initial_values = false); + + int getNumVerts() { return mPositions.size(); } + int getNumIndices() { return mIndices.size(); } + + std::vector& getIndices() { return mIndices; }; + std::vector& getPositions() { return mPositions; }; + std::vector& getNormals() { return mNormals; }; + std::vector& getUVs() { return mUVs; }; + std::vector& getSkin() { return mSkin; } + std::pair& getFaceBoundingBox() { return mFaceBoundingBox; } + + private: + std::vector mIndices; + std::vector mPositions; + std::vector mNormals; + std::vector mUVs; + std::vector mSkin; + std::pair 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>& getFaces(LLLocalMeshFileLOD lod) { return mFaces[lod]; }; + std::pair& 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>, 4> mFaces; + std::pair 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 mLODAvailability; + std::vector mObjectList; + }; + + struct LLLocalMeshLoaderReply + { + bool mChanged; + std::vector mLog; + std::array 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>& getObjectVector() { return mLoadedObjectList; }; + + // info getters + bool notifyNeedsUIUpdate(); + LLLocalMeshFileInfo getFileInfo(); + std::string getFilename(LLLocalMeshFileLOD lod) { return mFilenames[lod]; }; + LLUUID getFileID() { return mLocalMeshFileID; }; + std::vector 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 mFilenames; + std::array mLastModified; + std::array mLoadedSuccessfully; + bool mTryLODFiles; + std::string mShortName; + std::vector mLoadingLog; + LLLocalMeshFileExtension mExtension; + LLLocalMeshFileStatus mLocalMeshFileStatus; + LLUUID mLocalMeshFileID; + bool mLocalMeshFileNeedsUIUpdate; + + std::future mAsyncFuture; + std::vector> mLoadedObjectList; + std::vector mSavedObjectSculptIDs; +}; + + +/*=============================*/ +/* LLLocalMeshSystem: */ +/* user facing manager class. */ +/*=============================*/ +class LLLocalMeshSystem : public LLSingleton +{ + + 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 getFileInfoVector(); + std::vector getFileLog(LLUUID local_file_id); + + private: + std::vector> mLoadedFileList; + bool mFileAsyncsOngoing; + LLFloaterLocalMesh* mFloaterPtr; +}; \ No newline at end of file diff --git a/indra/newview/lllocalmeshimportdae.cpp b/indra/newview/lllocalmeshimportdae.cpp new file mode 100644 index 0000000000..e3d34a3d9d --- /dev/null +++ b/indra/newview/lllocalmeshimportdae.cpp @@ -0,0 +1,1620 @@ +/** + * @file lllocalmeshimportdae.cpp + * @author Vaalith Jinn + * @brief Local Mesh Import DAE source + * + * $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$ + */ + + /* precompiled headers */ +#include "llviewerprecompiledheaders.h" + +/* own header */ +#include "lllocalmesh.h" +#include "lllocalmeshimportdae.h" + +/* linden headers */ +#include "llmodelloader.h" +#include "llvoavatarself.h" + +/* dae headers*/ +#if LL_MSVC +#pragma warning (disable : 4263) +#pragma warning (disable : 4264) +#endif + +#include "dae.h" +#include "dom/domConstants.h" +#include "dom/domMesh.h" +#include "dom/domSkin.h" +#include "dom/domGeometry.h" +#include "dom/domInstance_controller.h" +#include "dom/domNode.h" + +#if LL_MSVC +#pragma warning (default : 4263) +#pragma warning (default : 4264) +#endif + + +LLLocalMeshImportDAE::loadFile_return LLLocalMeshImportDAE::loadFile(LLLocalMeshFile* data, LLLocalMeshFileLOD lod) +{ + pushLog("DAE Importer", "Starting"); + + // instantiate collada objects + DAE collada_core; + domCOLLADA* collada_dom = nullptr; + daeDocument* collada_document = nullptr; + daeElement* collada_document_root = nullptr; + daeDatabase* collada_db = nullptr; + std::string filename = data->getFilename(lod); + mLod = lod; + mLoadingLog.clear(); + + // open file and check if opened + collada_dom = collada_core.open(filename); + if (!collada_dom) + { + pushLog("DAE Importer", "Collada DOM instance could not initialize."); + return loadFile_return(false, mLoadingLog); + } + + collada_document = collada_core.getDoc(filename); + if (!collada_document) + { + pushLog("DAE Importer", "Collada file contained no collada document."); + return loadFile_return(false, mLoadingLog); + } + + collada_document_root = collada_document->getDomRoot(); + if (!collada_document_root) + { + pushLog("DAE Importer", "Collada document contained no root."); + return loadFile_return(false, mLoadingLog); + } + + // fill collada db + collada_db = collada_core.getDatabase(); + + // NOTE: + // do we need version, identity, unit scale, up direction? + // doesn't seem like we do, but this here would be the space for them if yes. + + size_t mesh_amount = collada_db->getElementCount(NULL, COLLADA_TYPE_MESH); + size_t skin_amount = collada_db->getElementCount(NULL, COLLADA_TYPE_SKIN); + + if (mesh_amount == 0) + { + pushLog("DAE Importer", "Collada document contained no MESH instances."); + return loadFile_return(false, mLoadingLog); + } + + std::vector mesh_usage_tracker; + + // [ iterate over OBJECTS ] + for (size_t mesh_index = 0; mesh_index < mesh_amount; ++mesh_index) + { + pushLog("DAE Importer", "Parsing object number " + std::to_string(mesh_index)); + + daeElement* mesh_current_elem = nullptr; + collada_db->getElement(&mesh_current_elem, mesh_index, NULL, COLLADA_TYPE_MESH); + domMesh* mesh_current = daeSafeCast(mesh_current_elem); + if (!mesh_current) + { + pushLog("DAE Importer", "Could not dereference the object, skipping."); + continue; + } + + // this WILL always return with some kind of a name, even a fallback one of "object_[idx]" + std::string object_name = getElementName(mesh_current_elem, mesh_index); + pushLog("DAE Importer", "Object name found: " + object_name); + + // we're filling up from the main file, so we're creating our objects + if (mLod == LLLocalMeshFileLOD::LOCAL_LOD_HIGH) + { + std::unique_ptr current_object = std::make_unique(object_name); + bool object_success = processObject(mesh_current, current_object.get()); + + if (object_success) + { + pushLog("DAE Importer", "Object loaded successfully."); + current_object->computeObjectBoundingBox(); + current_object->computeObjectTransform(); + current_object->normalizeFaceValues(mLod); + // normalizeFaceValues is necessary for skin calculations down below, + // but we also have to do it once per each lod so we'll call it foreach lod. + + auto& object_vector = data->getObjectVector(); + object_vector.push_back(std::move(current_object)); + mesh_usage_tracker.push_back(mesh_current); + } + + else + { + pushLog("DAE Importer", "Object loading failed, skipping this one."); + // should we error out here, or recover? + } + } + + // parsing a lower lod file, into objects made during LOD3 parsing + else + { + auto& object_vector = data->getObjectVector(); + if (object_vector.size() <= mesh_index) + { + pushLog("DAE Importer", "LOD" + std::to_string(mLod) + " is requesting an object that LOD3 did not have or failed to load, skipping."); + continue; // we try to recover, or should we just fail this? + } + + auto current_object_ptr = object_vector[mesh_index].get(); + if (!current_object_ptr) + { + pushLog("DAE Importer", "Bad object reference given, skipping."); + continue; // ditto + } + + bool object_success = processObject(mesh_current, current_object_ptr); + + if (object_success) + { + pushLog("DAE Importer", "Object loaded successfully."); + current_object_ptr->normalizeFaceValues(mLod); + } + + else + { + pushLog("DAE Importer", "Object loading failed."); + } + } + } + + // check if we managed to load any objects at all, if not - no point continuing. + if (data->getObjectVector().empty()) + { + pushLog("DAE Importer", "No objects have been successfully loaded, stopping."); + return loadFile_return(false, mLoadingLog); + } + + pushLog("DAE Importer", "Object and face parsing complete."); + + // [ iterate over SKINS ] + size_t skins_loaded = 0; + for (size_t skin_index = 0; skin_index < skin_amount; ++skin_index) + { + pushLog("DAE Importer", "Parsing skin number " + std::to_string(skin_index)); + + daeElement* skin_current_elem = nullptr; + collada_db->getElement(&skin_current_elem, skin_index, NULL, COLLADA_TYPE_SKIN); + domSkin* skin_current = daeSafeCast(skin_current_elem); + if (!skin_current) + { + pushLog("DAE Importer", "Skin pointer is null, skipping."); + continue; + } + + domGeometry* skin_current_geom = daeSafeCast(skin_current->getSource().getElement()); + if (!skin_current_geom) + { + pushLog("DAE Importer", "Skin doesn't seem to have associated geometry, skipping."); + continue; + } + + domMesh* skin_current_mesh = skin_current_geom->getMesh(); + if (!skin_current_mesh) + { + pushLog("DAE Importer", "Skin associated geometry continer has no mesh, skipping."); + continue; + } + + // find the associated lllocalmeshobject linked to this mesh + int current_object_iter = -1; + for (size_t mesh_tracker_iterator = 0; mesh_tracker_iterator < mesh_usage_tracker.size(); ++mesh_tracker_iterator) + { + auto iterable_mesh = mesh_usage_tracker[mesh_tracker_iterator]; + if (skin_current_mesh == iterable_mesh) + { + current_object_iter = mesh_tracker_iterator; + break; + } + } + + if (current_object_iter < 0) + { + pushLog("DAE Importer", "Skin associated mesh has no equivalent loaded object mesh, skipping."); + continue; + } + + auto& object_vector = data->getObjectVector(); + if (current_object_iter >= object_vector.size()) + { + pushLog("DAE Importer", "Requested object out of bounds, skipping."); + continue; + } + + auto& current_object = object_vector[current_object_iter]; + bool skin_success = processSkin(collada_db, collada_document_root, skin_current_mesh, skin_current, current_object); + if (skin_success) + { + ++skins_loaded; + pushLog("DAE Importer", "Skin idx " + std::to_string(skin_index) + " loading successful."); + } + else + { + pushLog("DAE Importer", "Skin idx " + std::to_string(skin_index) + " loading unsuccessful."); + } + + } + + if (skin_amount) + { + if (skins_loaded) + { + if (skins_loaded == skin_amount) + { + pushLog("DAE Importer", "All available skin data has been successfully loaded.."); + } + else + { + pushLog("DAE Importer", std::to_string(skin_amount) + " Skin data instances found, out of these - " + std::to_string(skins_loaded) + "loaded successfully."); + } + } + else + { + pushLog("DAE Importer", "Skinning data found, but all of it failed to load."); + } + } + else + { + pushLog("DAE Importer", "No skinning data found."); + } + + /* + this entire function silently, or maybe not so silently ignores certain failures, such as certain objects or skins not loading properly + does not halt the function. i'm not sure if it should necesarily? + + it is my hope that the log coupled with seeing what does load correctly - can help people diagnose their mesh better rather than + just a flat "loading error, nothing loaded" blanket errors. + */ + + return loadFile_return(true, mLoadingLog); +} + + +bool LLLocalMeshImportDAE::processObject(domMesh* current_mesh, LLLocalMeshObject* current_object) +{ + auto& object_faces = current_object->getFaces(mLod); + domTriangles_Array& triangle_array = current_mesh->getTriangles_array(); + domPolylist_Array& polylist_array = current_mesh->getPolylist_array(); +// domPolygons_Array& polygons_array = current_mesh->getPolygons_array(); // NOTE: polygon schema + + enum submesh_type + { + SUBMESH_TRIANGLE, + SUBMESH_POLYLIST, + SUBMESH_POLYGONS + }; + + const size_t total_faces_found + = triangle_array.getCount() + + polylist_array.getCount(); + //+ polygons_array.getCount(); // NOTE: polygon schema + + pushLog("DAE Importer", "Potentially " + std::to_string(total_faces_found) + " object faces found."); + + if (total_faces_found > 8) + { + pushLog("DAE Importer", "NOTE: object contains more than 8 faces, but ONLY up to 8 faces will be loaded."); + } + + bool submesh_failure_found = false; + bool stop_loading_additional_faces = false; + auto lambda_process_submesh = [&](int idx, submesh_type array_type) + { + // check for face overflow + if (object_faces.size() == 8) + { + pushLog("DAE Importer", "NOTE: reached the limit of 8 faces per object, ignoring the rest."); + stop_loading_additional_faces = true; + return; + } + + auto current_submesh = std::make_unique(); + bool submesh_load_success = false; + + /* in true khronos fashion, there's multiple ways to represent what a mesh object is made of, + typically blender does polygons, max does polylists, maya & ad fbx converter do triangles. */ + switch (array_type) + { + case submesh_type::SUBMESH_TRIANGLE: + { + pushLog("DAE Importer", "Attempting to load face idx " + std::to_string(idx) + " of type TRIANGLES."); + domTrianglesRef& current_triangle_set = triangle_array.get(idx); + submesh_load_success = readMesh_Triangle(current_submesh.get(), current_triangle_set); + break; + } + + case submesh_type::SUBMESH_POLYLIST: + { + pushLog("DAE Importer", "Attempting to load face idx " + std::to_string(idx) + " of type POLYLIST."); + domPolylistRef& current_polylist_set = polylist_array.get(idx); + submesh_load_success = readMesh_Polylist(current_submesh.get(), current_polylist_set); + break; + } + + // NOTE: polygon schema + /* + case submesh_type::SUBMESH_POLYGONS: + { + // polygons type schema is deprecated, no modern DCC exports that. + pushLog("DAE Importer", "Attempting to load face idx " + std::to_string(idx) + " of type POLYGONS."); + pushLog("DAE Importer", "POLYGONS type schema is deprecated."); + break; + } + */ + } + + if (submesh_load_success) + { + pushLog("DAE Importer", "Face idx " + std::to_string(idx) + " loaded successfully."); + object_faces.push_back(std::move(current_submesh)); + } + else + { + pushLog("DAE Importer", "Face idx " + std::to_string(idx) + " failed to load."); + submesh_failure_found = true; + } + }; + + for (size_t iter = 0; iter < triangle_array.getCount(); ++iter) + { + if (stop_loading_additional_faces) + { + break; + } + + lambda_process_submesh(iter, submesh_type::SUBMESH_TRIANGLE); + } + + for (size_t iter = 0; iter < polylist_array.getCount(); ++iter) + { + if (stop_loading_additional_faces) + { + break; + } + + lambda_process_submesh(iter, submesh_type::SUBMESH_POLYLIST); + } + + // NOTE: polygon schema + /* + for (size_t iter = 0; iter < polygons_array.getCount(); ++iter) + { + lambda_process_submesh(iter, submesh_type::SUBMESH_POLYGONS); + } + */ + + return !submesh_failure_found; +} + +// this function is a mess even after refactoring, omnissiah help whichever tech priest delves into this mess. +bool LLLocalMeshImportDAE::processSkin(daeDatabase* collada_db, daeElement* collada_document_root, domMesh* current_mesh, domSkin* current_skin, + std::unique_ptr& current_object) +{ + //=========================================================== + // get transforms, calc bind matrix, get SL joint names list + //=========================================================== + + pushLog("DAE Importer", "Preparing transformations and bind shape matrix."); + + // grab transformations + LLVector4 inverse_translation = current_object->getObjectTranslation() *= -1.f; // seems legit + LLVector4 objct_size = current_object->getObjectSize(); + + // this is basically the data_out but for skinning data + auto& skininfo = current_object->getObjectMeshSkinInfo(); + + // basically copy-pasted from linden magic + LLMatrix4 normalized_transformation, mesh_scale; + normalized_transformation.setTranslation(LLVector3(inverse_translation)); + mesh_scale.initScale(LLVector3(objct_size)); + mesh_scale *= normalized_transformation; + normalized_transformation = mesh_scale; + + glh::matrix4f inv_mat((F32*)normalized_transformation.mMatrix); + inv_mat = inv_mat.inverse(); + LLMatrix4 inverse_normalized_transformation(inv_mat.m); + + // bind shape matrix + domSkin::domBind_shape_matrix* skin_current_bind_matrix = current_skin->getBind_shape_matrix(); + if (skin_current_bind_matrix) + { + auto& bind_matrix_value = skin_current_bind_matrix->getValue(); + for (size_t matrix_i = 0; matrix_i < 4; matrix_i++) + { + for (size_t matrix_j = 0; matrix_j < 4; matrix_j++) + { + skininfo.mBindShapeMatrix.mMatrix[matrix_i][matrix_j] = bind_matrix_value[matrix_i + (matrix_j * 4)]; + } + } + + // matrix multiplication order matters, so this is as clean as it gets. + LLMatrix4 temp_transformation = normalized_transformation; + temp_transformation *= skininfo.mBindShapeMatrix; + skininfo.mBindShapeMatrix = temp_transformation; + } + + // setup joint map + JointMap joint_map = gAgentAvatarp->getJointAliases(); + + // unfortunately getSortedJointNames clears the ref vector, and we need two extra lists. + std::vector extra_names, more_extra_names; + gAgentAvatarp->getSortedJointNames(1, extra_names); + gAgentAvatarp->getSortedJointNames(2, more_extra_names); + extra_names.reserve(more_extra_names.size()); + extra_names.insert(extra_names.end(), more_extra_names.begin(), more_extra_names.end()); + + // add the extras to jointmap + for (auto extra_name : extra_names) + { + joint_map[extra_name] = extra_name; + } + + + //======================================================= + // find or re-create a skeleton, deal with joint offsets + //======================================================= + + pushLog("DAE Importer", "Preparing to process skeleton[s]..."); + + // try to find a regular skeleton + size_t skeleton_count = collada_db->getElementCount(NULL, "skeleton"); + bool use_alternative_joint_search = true; + JointTransformMap joint_transforms; + for (size_t skeleton_iterator = 0; skeleton_iterator < skeleton_count; ++skeleton_iterator) + { + daeElement* current_element = nullptr; + collada_db->getElement(¤t_element, skeleton_iterator, 0, "skeleton"); + auto current_skeleton = daeSafeCast(current_element); + if (!current_skeleton) + { + continue; + } + + auto current_skeleton_root = current_skeleton->getValue().getElement(); + if (!current_skeleton_root) + { + continue; + } + + // we found at least one proper sekeleton + use_alternative_joint_search = false; + pushLog("DAE Importer", "Skeleton data found, attempting to process.."); + + auto skeleton_nodes = current_skeleton_root->getChildrenByType(); + for (size_t skeleton_node_iterator = 0; skeleton_node_iterator < skeleton_nodes.getCount(); ++skeleton_node_iterator) + { + auto current_node = daeSafeCast(skeleton_nodes[skeleton_node_iterator]); + if (!current_node) + { + continue; + } + + // work with node here + processSkeletonJoint(current_node, joint_map, joint_transforms); + } + } + + // no skeletons found, recreate one from joints + if (use_alternative_joint_search) + { + pushLog("DAE Importer", "No conventional skeleton data found, attempting to recreate from joints..."); + daeElement* document_scene = collada_document_root->getDescendant("visual_scene"); + if (!document_scene) + { + // TODO: ERROR + // missing skeleton set? + } + else + { + auto scene_children = document_scene->getChildren(); + for (size_t scene_child_iterator = 0; scene_child_iterator < scene_children.getCount(); ++scene_child_iterator) + { + auto current_node = daeSafeCast(scene_children[scene_child_iterator]); + if (!current_node) + { + continue; + } + + // work with node here + processSkeletonJoint(current_node, joint_map, joint_transforms); + } + } + } + + + // jointlist processing + + // moved this lambda definition out of the loop below. + auto lambda_process_joint_name = [&skininfo, &joint_map](std::string joint_name) + { + // looking for internal joint name, otherwise use provided name? + // seems weird, but ok. + if (joint_map.find(joint_name) != joint_map.end()) + { + joint_name = joint_map[joint_name]; + skininfo.mJointNames.push_back(JointKey::construct(joint_name)); + skininfo.mJointNums.push_back(-1); + } + }; + + auto& list_of_jointinputs = current_skin->getJoints()->getInput_array(); + for (size_t joint_input_iterator = 0; joint_input_iterator < list_of_jointinputs.getCount(); ++joint_input_iterator) + { + + auto current_element = list_of_jointinputs[joint_input_iterator]->getSource().getElement(); + auto current_source = daeSafeCast(current_element); + if (!current_source) + { + pushLog("DAE Importer", "WARNING: Joint data number " + std::to_string(joint_input_iterator) + " could not be read, skipping."); + continue; + } + + std::string current_semantic = list_of_jointinputs[joint_input_iterator]->getSemantic(); + if (current_semantic.compare(COMMON_PROFILE_INPUT_JOINT) == 0) + { + // we got a list of the active joints this mesh uses + // names can be either as an array of names or ids + // define lambda for common code + // moved lambda out of the loop, see above. + + auto name_source = current_source->getName_array(); + if (name_source) + { + auto list_of_names = name_source->getValue(); + for (size_t joint_name_iter = 0; joint_name_iter < list_of_names.getCount(); ++joint_name_iter) + { + std::string current_name = list_of_names.get(joint_name_iter); + lambda_process_joint_name(current_name); + } + } + + else + { + auto id_source = current_source->getIDREF_array(); + if (!id_source) + { + pushLog("DAE Importer", "WARNING: Joint number " + std::to_string(joint_input_iterator) + " did not provide name or ID, skipping."); + continue; + } + + auto list_of_names = id_source->getValue(); + for (size_t joint_name_iter = 0; joint_name_iter < list_of_names.getCount(); ++joint_name_iter) + { + std::string current_name = list_of_names.get(joint_name_iter).getID(); + lambda_process_joint_name(current_name); + } + } + } + + else if (current_semantic.compare(COMMON_PROFILE_INPUT_INV_BIND_MATRIX) == 0) + { + // we got an inverse bind matrix + auto float_array = current_source->getFloat_array(); + if (!float_array) + { + pushLog("DAE Importer", "WARNING: Inverse bind matrix not found, attempting to skip."); + continue; + } + + auto& current_transform = float_array->getValue(); + for (size_t transform_matrix_iterator = 0; transform_matrix_iterator < (current_transform.getCount() / 16); ++transform_matrix_iterator) + { + LLMatrix4 current_matrix; + for (size_t matrix_pos_i = 0; matrix_pos_i < 4; matrix_pos_i++) + { + for (size_t matrix_pos_j = 0; matrix_pos_j < 4; matrix_pos_j++) + { + current_matrix.mMatrix[matrix_pos_i][matrix_pos_j] = current_transform + [(transform_matrix_iterator * 16) + matrix_pos_i + (matrix_pos_j * 4)]; + } + } + + skininfo.mInvBindMatrix.push_back(current_matrix); + } + } + } + + int jointname_number_iter = 0; + for (auto jointname_iterator = skininfo.mJointNames.begin(); jointname_iterator != skininfo.mJointNames.end(); ++jointname_iterator, ++jointname_number_iter) + { + std::string name_lookup = jointname_iterator->mName; + if (joint_map.find(name_lookup) == joint_map.end()) + { + pushLog("DAE Importer", "WARNING: Unknown joint named " + name_lookup + " found, skipping over it."); + continue; + } + + if (skininfo.mInvBindMatrix.size() <= jointname_number_iter) + { + // doesn't seem like a critical fail that should invalidate the entire skin, just break and move on? + pushLog("DAE Importer", "WARNING: Requesting out of bounds joint named " + name_lookup); + break; + } + + LLMatrix4 newinverse = skininfo.mInvBindMatrix[jointname_number_iter]; + auto joint_translation = joint_transforms[name_lookup].getTranslation(); + newinverse.setTranslation(joint_translation); + skininfo.mAlternateBindMatrix.push_back(newinverse); + } + + size_t bind_count = skininfo.mAlternateBindMatrix.size(); + if ((bind_count > 0) && (bind_count != skininfo.mJointNames.size())) + { + // different number of binds vs jointnames, i hestiate to fail the entire skinmap over this + // because it can just be a case of a few additional joints being ignored, unless i'm missing something? + pushLog("DAE Importer", "WARNING: " + std::to_string(skininfo.mJointNames.size()) + " joints were found, but " + std::to_string(bind_count) + " binds matrices were made."); + } + + //============================== + // transform vtx positions + //============================== + + // transform positions + auto raw_vertex_array = current_mesh->getVertices(); + if (!raw_vertex_array) + { + pushLog("DAE Importer", "ERROR: Failed to read the vertex array."); + return false; + } + + std::vector transformed_positions; + auto vertex_input_array = raw_vertex_array->getInput_array(); + + for (size_t vertex_input_iterator = 0; vertex_input_iterator < vertex_input_array.getCount(); ++vertex_input_iterator) + { + std::string current_semantic = vertex_input_array[vertex_input_iterator]->getSemantic(); + if (current_semantic.compare(COMMON_PROFILE_INPUT_POSITION) != 0) + { + // if what we got isn't a position array - skip. + continue; + } + + if (!transformed_positions.empty()) + { + // just in case we somehow got multiple valid position arrays + break; + } + + auto pos_source = daeSafeCast(vertex_input_array[vertex_input_iterator]->getSource().getElement()); + if (!pos_source) + { + // not a valid position array, no need to bother the user wit it though. + continue; + } + + auto pos_array = pos_source->getFloat_array(); + if (!pos_array) + { + continue; + } + + auto& vertex_positions = pos_array->getValue(); + for (size_t vtx_position_iterator = 0; vtx_position_iterator < vertex_positions.getCount(); vtx_position_iterator += 3) + { + if (vertex_positions.getCount() <= (vtx_position_iterator + 2)) + { + pushLog("DAE Importer", "ERROR: Position array request out of bound."); + break; + } + + LLVector3 temp_pos + ( + vertex_positions[vtx_position_iterator], + vertex_positions[vtx_position_iterator + 1], + vertex_positions[vtx_position_iterator + 2] + ); + + temp_pos = temp_pos * inverse_normalized_transformation; + + LLVector4 new_vector; + new_vector.set(temp_pos[0], temp_pos[1], temp_pos[2]); + transformed_positions.push_back(new_vector); + } + + } + + //============================== + // extract weights and limit to 4 + //============================== + auto current_weights = current_skin->getVertex_weights(); + if (!current_weights) + { + pushLog("DAE Importer", "ERROR: Failed to read the weights array, stopping."); + return false; + } + + auto weight_inputs = current_weights->getInput_array(); + domFloat_array* vertex_weights = nullptr; + + for (size_t weight_input_iter = 0; weight_input_iter < weight_inputs.getCount(); ++weight_input_iter) + { + std::string current_semantic = weight_inputs[weight_input_iter]->getSemantic(); + if (current_semantic.compare(COMMON_PROFILE_INPUT_WEIGHT) == 0) + { + auto weights_source = daeSafeCast(weight_inputs[weight_input_iter]->getSource().getElement()); + if (!weights_source) + { + continue; + } + + vertex_weights = weights_source->getFloat_array(); + break; + } + } + + if (!vertex_weights) + { + pushLog("DAE Importer", "ERROR: Failed to find valid weight data, stopping."); + return false; + } + + // vcount - in this case defines how many joints influence each vtx + // v - joint indices + + auto& weight_values = vertex_weights->getValue(); + auto& joint_influence_count = current_weights->getVcount()->getValue(); + auto& joint_weight_indices = current_weights->getV()->getValue(); + std::map > skinweight_data; + + size_t joint_weight_strider = 0; + for (size_t joint_iterator = 0; joint_iterator < joint_influence_count.getCount(); ++joint_iterator) + { + auto influencees_count = joint_influence_count[joint_iterator]; + LLModel::weight_list full_weight_list; + LLModel::weight_list sorted_weight_list; + + // extract all of the weights + for (size_t influence_iter = 0; influence_iter < influencees_count; ++influence_iter) + { + int joint_idx = joint_weight_indices[joint_weight_strider++]; + int weight_idx = joint_weight_indices[joint_weight_strider++]; + + if (joint_idx == -1) + { + continue; + } + + float weight_value = weight_values[weight_idx]; + full_weight_list.push_back(LLModel::JointWeight(joint_idx, weight_value)); + } + + // sort by large-to-small + std::sort(full_weight_list.begin(), full_weight_list.end(), LLModel::CompareWeightGreater()); + + + // limit to 4, and normalize the result + F32 total = 0.f; + + for (U32 i = 0; i < llmin((U32)4, (U32)full_weight_list.size()); ++i) + { //take up to 4 most significant weights + if (full_weight_list[i].mWeight > 0.f) + { + sorted_weight_list.push_back(full_weight_list[i]); + total += full_weight_list[i].mWeight; + } + } + + F32 scale = 1.f / total; + if (scale != 1.f) + { //normalize weights + for (U32 i = 0; i < sorted_weight_list.size(); ++i) + { + sorted_weight_list[i].mWeight *= scale; + } + } + + skinweight_data[transformed_positions[joint_iterator]] = sorted_weight_list; + } + + //============================== + // map weights+joints to corresponding vertices + //============================== + + // compare that allows for some deviation, needed below + auto soft_compare = [](LLVector4 vec_a, LLVector4 vec_b, float epsilon) -> bool + { + bool result = false; + + if ( (fabs(vec_a.mV[0] - vec_b.mV[0]) < epsilon) && + (fabs(vec_a.mV[1] - vec_b.mV[1]) < epsilon) && + (fabs(vec_a.mV[2] - vec_b.mV[2]) < epsilon) ) + { + result = true; + } + + return result; + }; + + auto& faces = current_object->getFaces(mLod); + for (auto& current_face : faces) + { + auto& positions = current_face->getPositions(); + auto& weights = current_face->getSkin(); + + for (auto& current_position : positions) + { + int found_iterator = -1; + + for (size_t internal_position_iter = 0; internal_position_iter < transformed_positions.size(); ++internal_position_iter) + { + auto& internal_position = transformed_positions[internal_position_iter]; + if (soft_compare(current_position, internal_position, F_ALMOST_ZERO)) + { + found_iterator = internal_position_iter; + break; + } + } + + if (found_iterator < 0) + { + continue; + } + + auto cjoints = skinweight_data[transformed_positions[found_iterator]]; + + LLLocalMeshFace::LLLocalMeshSkinUnit new_wght; + + // first init all joints to -1, in case below we get less than 4 influences. + for (size_t tjidx = 0; tjidx < 4; ++tjidx) + { + new_wght.mJointIndices[tjidx] = -1; + } + + /* + NOTE: LL uses a single float value to indicate joint num and weight; + for example: 7.55 means the vtx is affected by joint number 7, with the weight of 55. + that of course limits max weight to 0.999f as not to actually change joint number. + + since LL uses that format, that's how i choose to store it to avoid confusion further down the pipeline. + */ + for (size_t jidx = 0; jidx < cjoints.size(); ++jidx) + { + auto cjoint = cjoints[jidx]; + + new_wght.mJointIndices[jidx] = cjoint.mJointIdx; + new_wght.mJointWeights[jidx] = llclamp((F32)cjoint.mWeight, 0.f, 0.999f); + } + + weights.push_back(new_wght); + } + } + + return true; +} + +void LLLocalMeshImportDAE::processSkeletonJoint(domNode* current_node, std::map& joint_map, std::map& joint_transforms) +{ + // safety checks & name check + auto node_name = current_node->getName(); + if (!node_name) + { + return; + } + + auto jointmap_iter = joint_map.find(node_name); + if (jointmap_iter == joint_map.end()) + { + return; + } + + // begin actual joint work + domTranslate* current_transformation = nullptr; + + daeSIDResolver jointResolver_translation(current_node, "./translate"); + current_transformation = daeSafeCast(jointResolver_translation.getElement()); + + if (!current_transformation) + { + daeSIDResolver jointResolver_location(current_node, "./location"); + current_transformation = daeSafeCast(jointResolver_location.getElement()); + } + + if (!current_transformation) + { + daeElement* child_translate_element = current_node->getChild("translate"); + if (child_translate_element) + { + current_transformation = daeSafeCast(child_translate_element); + } + } + + if (!current_transformation) // no queries worked + { + daeSIDResolver jointResolver_matrix(current_node, "./matrix"); + auto joint_transform_matrix = daeSafeCast(jointResolver_matrix.getElement()); + if (joint_transform_matrix) + { + LLMatrix4 workingTransform; + domFloat4x4 domArray = joint_transform_matrix->getValue(); + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + workingTransform.mMatrix[i][j] = domArray[i + j * 4]; + } + } + LLVector3 trans = workingTransform.getTranslation(); + joint_transforms[node_name] = workingTransform; + } + } + + else // previous query worked + { + domFloat3 joint_transform = current_transformation->getValue(); + LLVector3 singleJointTranslation(joint_transform[0], joint_transform[1], joint_transform[2]); + LLMatrix4 workingtransform; + workingtransform.setTranslation(singleJointTranslation); + + joint_transforms[node_name] = workingtransform; + } + // end actual joint work + + // get children to work on + auto current_node_children = current_node->getChildren(); + for (size_t node_children_iter = 0; node_children_iter < current_node_children.getCount(); ++node_children_iter) + { + auto current_child_node = daeSafeCast(current_node_children[node_children_iter]); + if (!current_child_node) + { + continue; + } + + processSkeletonJoint(current_child_node, joint_map, joint_transforms); + } +} + + +bool LLLocalMeshImportDAE::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) +{ + index_stride = 0; + + for (size_t input_iterator = 0; input_iterator < inputs.getCount(); ++input_iterator) + { + index_stride = std::max((int)inputs[input_iterator]->getOffset(), index_stride); + std::string current_semantic = inputs[input_iterator]->getSemantic(); + + // vertex array + if (current_semantic.compare(COMMON_PROFILE_INPUT_VERTEX) == 0) + { + daeElementRef current_element = inputs[input_iterator]->getSource().getElement(); + domVertices* input_vertices = daeSafeCast(current_element); + if (!input_vertices) + { + pushLog("DAE Importer", "Collada file error, vertex array not found."); + return false; + } + + domInputLocal_Array& input_vertices_array = input_vertices->getInput_array(); + for (size_t vertex_input_iterator = 0; vertex_input_iterator < input_vertices_array.getCount(); ++vertex_input_iterator) + { + std::string current_vertex_semantic = input_vertices_array[vertex_input_iterator]->getSemantic(); + + // vertex positions array + if (current_vertex_semantic.compare(COMMON_PROFILE_INPUT_POSITION) == 0) + { + offset_position = inputs[input_iterator]->getOffset(); + daeElementRef current_element = input_vertices_array[vertex_input_iterator]->getSource().getElement(); + source_position = daeSafeCast(current_element); + } + + // vertex normal array + else if (current_vertex_semantic.compare(COMMON_PROFILE_INPUT_NORMAL) == 0) + { + offset_normals = inputs[input_iterator]->getOffset(); + daeElementRef current_element = input_vertices_array[vertex_input_iterator]->getSource().getElement(); + source_normals = daeSafeCast(current_element); + } + } + + } + + // normal array outside of vertex array + else if (current_semantic.compare(COMMON_PROFILE_INPUT_NORMAL) == 0) + { + offset_normals = inputs[input_iterator]->getOffset(); + daeElementRef current_element = inputs[input_iterator]->getSource().getElement(); + source_normals = daeSafeCast(current_element); + } + + // uv array + else if (current_semantic.compare(COMMON_PROFILE_INPUT_TEXCOORD) == 0) + { + offset_uvmap = inputs[input_iterator]->getOffset(); + daeElementRef current_element = inputs[input_iterator]->getSource().getElement(); + source_uvmap = daeSafeCast(current_element); + } + } + + index_stride += 1; + + if (!source_position) + { + pushLog("DAE Importer", "Collada file error, position array not found."); + return false; + } + + if (!source_position->getFloat_array()) + { + pushLog("DAE Importer", "Collada file error, position array is corrupt."); + return false; + } + + return true; +} + +std::string LLLocalMeshImportDAE::getElementName(daeElement* element_current, int fallback_index) +{ + std::string result; + + // why lambda - we first use it on the element itself, if no result - we use it on the element's parent. + auto lambda_ask_element = [](daeElement* element_current) -> std::string + { + std::string lambda_result; + + /* + the reason we want BOTH name and id is because collada dom is rather bad + at reading NAME from blender files, and is equally as bad at reading ID from anywhere else. + */ + + lambda_result = element_current->getAttribute("name"); + if (element_current->getID()) + { + // if we already have name, let's add a separator + if (!lambda_result.empty()) + { + lambda_result += " | "; + } + + lambda_result += element_current->getID(); + } + + return lambda_result; + }; + + if (element_current) + { + // first ask the given element + result = lambda_ask_element(element_current); + + // if got no name, ask parent + if (result.empty()) + { + auto parent = element_current->getParent(); + if (parent) + { + result = lambda_ask_element(parent); + } + } + } + + // in case all else fails, fallback naming + if (result.empty()) + { + result = "object_" + fallback_index; + } + + return result; +} + +void LLLocalMeshImportDAE::pushLog(std::string who, std::string what) +{ + std::string log_msg = "[ " + who + " ] "; + + log_msg += what; + mLoadingLog.push_back(log_msg); +} + + +bool LLLocalMeshImportDAE::readMesh_Triangle(LLLocalMeshFace* data_out, const domTrianglesRef& data_in) +{ + if (!data_out) + { + pushLog("DAE Importer", "LLLocalMeshFace pointer is NULL."); + return false; + } + + if (!data_in) + { + pushLog("DAE Importer", "domTrianglesRef is NULL."); + return false; + } + + const domInputLocalOffset_Array& inputs = data_in->getInput_array(); + + int offset_position = -1; + int offset_normals = -1; + int offset_uvmap = -1; + int index_stride = 0; + + domSource* source_position = nullptr; + domSource* source_normals = nullptr; + domSource* source_uvmap = nullptr; + + // success implies source_position is not null and has gettable float array. + bool element_success = readMesh_CommonElements(inputs, offset_position, offset_normals, offset_uvmap, + index_stride, source_position, source_normals, source_uvmap); + + if (!element_success) + { + pushLog("DAE Importer", "Collada file error, could not read array positions."); + return false; + } + + domListOfFloats empty; + domListOfFloats& serialized_values_position = source_position->getFloat_array()->getValue(); + domListOfFloats& serialized_values_normals = source_normals ? source_normals->getFloat_array()->getValue() : empty; + domListOfFloats& serialized_values_uvmap = source_uvmap ? source_uvmap->getFloat_array()->getValue() : empty; + domListOfUInts& triangle_list = data_in->getP()->getValue(); + /* + we don't get/need vcount with triangles since they're, uh, limited to 3 verts. + so P in this case is just an array of vtx indices linearly describing triangles + like [ 0 1 2 3 4 5 4 6 7 0 9 10 ] and so on. thanks, fancy collada book. + */ + + if (serialized_values_position.getCount() == 0) + { + pushLog("DAE Importer", "Collada file error, vertex position array is empty."); + return false; + } + + auto& list_indices = data_out->getIndices(); + auto& list_positions = data_out->getPositions(); + auto& list_normals = data_out->getNormals(); + auto& list_uvs = data_out->getUVs(); + + LLVector4 initial_bbox_values; + initial_bbox_values.set + ( + (float)serialized_values_position[0], + (float)serialized_values_position[1], + (float)serialized_values_position[2] + ); + data_out->setFaceBoundingBox(initial_bbox_values, true); + + // used to track verts of repeat pos but different uv/normal attributes. + std::vector repeat_map_position_iterable; + + struct repeat_map_data_struct + { + LLVector4 vtx_normal_data; + LLVector2 vtx_uv_data; + int vtx_index; + }; + + std::vector> repeat_map_data; + + // iterate per vertex + for (size_t triangle_iterator = 0; triangle_iterator < triangle_list.getCount(); triangle_iterator += index_stride) + { + LLVector4 attr_position; + LLVector4 attr_normal; + LLVector2 attr_uv; + + // position attribute + attr_position.set + ( + serialized_values_position[triangle_list[triangle_iterator + offset_position] * 3 + 0], + serialized_values_position[triangle_list[triangle_iterator + offset_position] * 3 + 1], + serialized_values_position[triangle_list[triangle_iterator + offset_position] * 3 + 2], + 0 + ); + + // normal attribute + if (source_normals) + { + attr_normal.set + ( + serialized_values_normals[triangle_list[triangle_iterator + offset_normals] * 3 + 0], + serialized_values_normals[triangle_list[triangle_iterator + offset_normals] * 3 + 1], + serialized_values_normals[triangle_list[triangle_iterator + offset_normals] * 3 + 2], + 0 + ); + } + + // uv attribute + if (source_uvmap) + { + attr_uv.set + ( + serialized_values_uvmap[triangle_list[triangle_iterator + offset_uvmap] * 2 + 0], + serialized_values_uvmap[triangle_list[triangle_iterator + offset_uvmap] * 2 + 1] + ); + } + + + // check if this vertex data already exists, + // if yes - just push in a new index that points where the original data is found. + bool repeat_found = false; + + // check if we've already seen a vertex with this position value + auto seeker_position = std::find(repeat_map_position_iterable.begin(), repeat_map_position_iterable.end(), attr_position); + if (seeker_position != repeat_map_position_iterable.end()) + { + // compare to check if you find one with matching normal and uv values + size_t seeker_index = std::distance(repeat_map_position_iterable.begin(), seeker_position); + for (auto repeat_vtx_data : repeat_map_data[seeker_index]) + { + if (repeat_vtx_data.vtx_normal_data != attr_normal) + { + continue; + } + + if (repeat_vtx_data.vtx_uv_data != attr_uv) + { + continue; + } + + // found a vertex whose position, normals and uvs match what we already have + int repeated_index = repeat_vtx_data.vtx_index; + int list_indices_size = list_indices.size(); + int triangle_check = list_indices_size % 3; + + if (((triangle_check < 1) || (list_indices[list_indices_size - 1] != repeated_index)) + && ((triangle_check < 2) || (list_indices[list_indices_size - 2] != repeated_index))) + { + repeat_found = true; + list_indices.push_back(repeated_index); + } + + break; + } + + } + + // the vertex data is actually new, let's push it in together with it's new index + if (!repeat_found) + { + // check if vertex data list + 1 would overflow 65535 size + if ((list_positions.size() + 1) >= 65535) + { + pushLog("DAE Importer", "Face error, too many vertices. Do NOT exceed 65535 vertics per face."); + return false; + } + + // update bounding box + data_out->setFaceBoundingBox(attr_position); + + // push back vertex data + list_positions.push_back(attr_position); + if (source_normals) + { + list_normals.push_back(attr_normal); + } + + if (source_uvmap) + { + list_uvs.push_back(attr_uv); + } + + // index is verts list - 1, push that index into indices + int current_index = list_positions.size() - 1; + list_indices.push_back(current_index); + + // track this position for future repeats + repeat_map_data_struct new_trackable; + new_trackable.vtx_normal_data = attr_normal; + new_trackable.vtx_uv_data = attr_uv; + new_trackable.vtx_index = current_index; + + if (seeker_position != repeat_map_position_iterable.end()) + { + int seeker_index = std::distance(repeat_map_position_iterable.begin(), seeker_position); + repeat_map_data[seeker_index].push_back(new_trackable); + } + else + { + repeat_map_position_iterable.push_back(attr_position); + std::vector new_map_data_vec; + new_map_data_vec.push_back(new_trackable); + repeat_map_data.push_back(new_map_data_vec); + } + } + + if (list_positions.empty()) + { + pushLog("DAE Importer", "Face error, no valid vertex positions found."); + return false; + } + + } + + return true; +} + +bool LLLocalMeshImportDAE::readMesh_Polylist(LLLocalMeshFace* data_out, const domPolylistRef& data_in) +{ + if (!data_out) + { + pushLog("DAE Importer", "LLLocalMeshFace pointer is NULL."); + return false; + } + + if (!data_in) + { + pushLog("DAE Importer", "domPolylistRef is NULL."); + return false; + } + + const domInputLocalOffset_Array& inputs = data_in->getInput_array(); + + int offset_position = -1; + int offset_normals = -1; + int offset_uvmap = -1; + int index_stride = 0; + + domSource* source_position = nullptr; + domSource* source_normals = nullptr; + domSource* source_uvmap = nullptr; + + // success implies source_position is not null and has gettable float array. + bool element_success = readMesh_CommonElements(inputs, offset_position, offset_normals, offset_uvmap, + index_stride, source_position, source_normals, source_uvmap); + + if (!element_success) + { + pushLog("DAE Importer", "Collada file error, could not read array positions."); + return false; + } + + domListOfFloats empty; + domListOfFloats& serialized_values_position = source_position->getFloat_array()->getValue(); + domListOfFloats& serialized_values_normals = source_normals ? source_normals->getFloat_array()->getValue() : empty; + domListOfFloats& serialized_values_uvmap = source_uvmap ? source_uvmap->getFloat_array()->getValue() : empty; + + // vcount in indicates the amount of vertices per prim, + // with the added benefit of getCount() of vcount gets us the number of primitives in this polylist. + domListOfUInts& list_primitives = data_in->getVcount()->getValue(); + + // P indicates the vertex index, so a prim 0 of [vcount containing 4 verts] means 4 first indices here. + domListOfUInts& vertex_indices = data_in->getP()->getValue(); + + + if (serialized_values_position.getCount() == 0) + { + pushLog("DAE Importer", "Collada file error, vertex position array is empty."); + return false; + } + + auto& list_indices = data_out->getIndices(); + auto& list_positions = data_out->getPositions(); + auto& list_normals = data_out->getNormals(); + auto& list_uvs = data_out->getUVs(); + + LLVector4 initial_bbox_values; + initial_bbox_values.set + ( + (float)serialized_values_position[0], + (float)serialized_values_position[1], + (float)serialized_values_position[2] + ); + data_out->setFaceBoundingBox(initial_bbox_values, true); + + // used to track verts of repeat pos but different uv/normal attributes. + std::vector repeat_map_position_iterable; + + struct repeat_map_data_struct + { + LLVector4 vtx_normal_data; + LLVector2 vtx_uv_data; + int vtx_index; + }; + + std::vector> repeat_map_data; + + // each time we sample an index vtx, this goes up by one, to keep an idx of where we are + int global_vtx_index = 0; + + // iterate per primtiive + for (size_t prim_iterator = 0; prim_iterator < list_primitives.getCount(); ++prim_iterator) + { + int first_idx = 0; + int last_idx = 0; + + size_t num_vertices = list_primitives[prim_iterator]; + + // iterate per vertex + for (size_t vtx_iter = 0; vtx_iter < num_vertices; ++vtx_iter) + { + int current_vtx_index = global_vtx_index; + global_vtx_index += index_stride; + + LLVector4 attr_position; + LLVector4 attr_normal; + LLVector2 attr_uv; + + // position attribute + attr_position.set + ( + serialized_values_position[vertex_indices[current_vtx_index + offset_position] * 3 + 0], + serialized_values_position[vertex_indices[current_vtx_index + offset_position] * 3 + 1], + serialized_values_position[vertex_indices[current_vtx_index + offset_position] * 3 + 2], + 0 + ); + + // normal attribute + if (source_normals) + { + attr_normal.set + ( + serialized_values_normals[vertex_indices[current_vtx_index + offset_normals] * 3 + 0], + serialized_values_normals[vertex_indices[current_vtx_index + offset_normals] * 3 + 1], + serialized_values_normals[vertex_indices[current_vtx_index + offset_normals] * 3 + 2], + 0 + ); + } + + // uv attribute + if (source_uvmap) + { + attr_uv.set + ( + serialized_values_uvmap[vertex_indices[current_vtx_index + offset_uvmap] * 2 + 0], + serialized_values_uvmap[vertex_indices[current_vtx_index + offset_uvmap] * 2 + 1] + ); + } + + // check if this vertex data already exists, + // if yes - just push in a new index that points where the original data is found. + bool repeat_found = false; + + // check if we've already seen a vertex with this position value + auto seeker_position = std::find(repeat_map_position_iterable.begin(), repeat_map_position_iterable.end(), attr_position); + if (seeker_position != repeat_map_position_iterable.end()) + { + // compare to check if you find one with matching normal and uv values + int seeker_index = std::distance(repeat_map_position_iterable.begin(), seeker_position); + for (auto repeat_vtx_data : repeat_map_data[seeker_index]) + { + if (repeat_vtx_data.vtx_normal_data != attr_normal) + { + continue; + } + + if (repeat_vtx_data.vtx_uv_data != attr_uv) + { + continue; + } + + // found a vertex whose position, normals and uvs match what we already have + repeat_found = true; + int repeated_index = repeat_vtx_data.vtx_index; + + if (vtx_iter == 0) + { + first_idx = repeated_index; + } + else if (vtx_iter == 1) + { + last_idx = repeated_index; + } + else + { + // copied basically verbatim off of daeloader, i could sit and figure out why + // this works, but i have to release this thing some time this century. + list_indices.push_back(first_idx); + list_indices.push_back(last_idx); + list_indices.push_back(repeated_index); + last_idx = repeated_index; + } + + break; + } + } + + if (!repeat_found) + { + // check if vertex data list + 1 would overflow 65535 size + if ((list_positions.size() + 1) >= 65535) + { + pushLog("DAE Importer", "Face error, too many vertices. Do NOT exceed 65535 vertics per face."); + return false; + } + + // update bounding box + data_out->setFaceBoundingBox(attr_position); + + // push back vertex data + list_positions.push_back(attr_position); + if (source_normals) + { + list_normals.push_back(attr_normal); + } + + if (source_uvmap) + { + list_uvs.push_back(attr_uv); + } + + // index is verts list - 1, push that index into indices + int current_index = list_positions.size() - 1; + + if (vtx_iter == 0) + { + first_idx = current_index; + } + else if (vtx_iter == 1) + { + last_idx = current_index; + } + else + { + list_indices.push_back(first_idx); + list_indices.push_back(last_idx); + list_indices.push_back(current_index); + last_idx = current_index; + } + + // track this position for future repeats + repeat_map_data_struct new_trackable; + new_trackable.vtx_normal_data = attr_normal; + new_trackable.vtx_uv_data = attr_uv; + new_trackable.vtx_index = current_index; + + if (seeker_position != repeat_map_position_iterable.end()) + { + int seeker_index = std::distance(repeat_map_position_iterable.begin(), seeker_position); + repeat_map_data[seeker_index].push_back(new_trackable); + } + else + { + repeat_map_position_iterable.push_back(attr_position); + std::vector new_map_data_vec; + new_map_data_vec.push_back(new_trackable); + repeat_map_data.push_back(new_map_data_vec); + } + } + } + + if (list_positions.empty()) + { + pushLog("DAE Importer", "Face error, no valid vertex positions found."); + return false; + } + } + + return true; +} + +//bool LLLocalMeshImportDAE::readMesh_Polygons(LLLocalMeshFace* data_out, const domPolygonsRef& data_in) +//{ + /* + i couldn't find any collada files of this type to test on + this type may have been deprecated? + */ + + // ok so.. in here vcount should be a number of polys, EACH poly should have it's own P (array of vtx indices) + + // return false // gotta return a thing + +//} \ No newline at end of file diff --git a/indra/newview/lllocalmeshimportdae.h b/indra/newview/lllocalmeshimportdae.h new file mode 100644 index 0000000000..bfa99ddde6 --- /dev/null +++ b/indra/newview/lllocalmeshimportdae.h @@ -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> 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& current_object); + void processSkeletonJoint(domNode* current_node, std::map& joint_map, std::map& 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 mLoadingLog; +}; + diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 9b19dd48a7..ff640eec30 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -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); LLFloaterReg::add("sound_explorer", "floater_NACL_explore_sounds.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("vram_usage", "floater_fs_vram_usage.xml", static_cast( &LLFloaterReg::build< FSFloaterVRAMUsage >) ); + LLFloaterReg::add("local_mesh_floater", "llfloaterlocalmesh.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); // local mesh LLFloaterReg::registerControlVariables(); // Make sure visibility and rect controls get preserved when saving } diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index f80a65ca86..3fc26e14b2 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -2449,6 +2449,24 @@ S32 LLViewerObjectList::findReferences(LLDrawable *drawablep) const return num_refs; } +std::vector LLViewerObjectList::findMeshObjectsBySculptID(LLUUID target_sculpt_id) +{ + std::vector 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) { diff --git a/indra/newview/llviewerobjectlist.h b/indra/newview/llviewerobjectlist.h index 7429497363..3f78670bcf 100644 --- a/indra/newview/llviewerobjectlist.h +++ b/indra/newview/llviewerobjectlist.h @@ -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 findMeshObjectsBySculptID(LLUUID target_sculpt_id); S32 getOrphanParentCount() const { return (S32) mOrphanParents.size(); } S32 getOrphanCount() const { return mNumOrphans; } diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 50a44d13d3..b9266d501f 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -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; // + // 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(); diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index 593057adbc..d1a4c69b6a 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -316,6 +316,8 @@ public: //virtual void updateRiggingInfo(); S32 mLastRiggingInfoLOD; + bool mIsLocalMesh; + bool mIsLocalMeshUsingScale; // Functions that deal with media, or media navigation diff --git a/indra/newview/skins/default/xui/en/llfloaterlocalmesh.xml b/indra/newview/skins/default/xui/en/llfloaterlocalmesh.xml new file mode 100644 index 0000000000..b992b12a4d --- /dev/null +++ b/indra/newview/skins/default/xui/en/llfloaterlocalmesh.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + +