1007 lines
29 KiB
C++
1007 lines
29 KiB
C++
/**
|
|
* @file vjlocalmesh.cpp
|
|
* @author Vaalith Jinn
|
|
* @brief Local Mesh main mechanism source
|
|
*
|
|
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
|
* Local Mesh contribution source code
|
|
* Copyright (C) 2022, Vaalith Jinn.
|
|
*
|
|
* 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
|
|
*
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
// 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"
|
|
#include "llmodelpreview.h"
|
|
|
|
// STL headers
|
|
#include <chrono>
|
|
|
|
// boost headers
|
|
#include "fix_macros.h"
|
|
#include <boost/filesystem.hpp>
|
|
|
|
// local mesh headers
|
|
#include "vjlocalmesh.h"
|
|
#include "vjfloaterlocalmesh.h"
|
|
|
|
// local mesh importers
|
|
#include "vjlocalmeshimportdae.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_view name):
|
|
mObjectName(name)
|
|
{
|
|
mSculptID.generate();
|
|
mVolumeParams.setSculptID(mSculptID, LL_SCULPT_TYPE_MESH);
|
|
}
|
|
|
|
LLLocalMeshObject::~LLLocalMeshObject() = default;
|
|
|
|
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(const LLMatrix4& scene_transform)
|
|
{
|
|
// 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];
|
|
}
|
|
mObjectSize = mObjectSize * scene_transform;
|
|
}
|
|
|
|
void LLLocalMeshObject::normalizeFaceValues(LLLocalMeshFileLOD lod_iter)
|
|
{
|
|
// NOTE: uv extents
|
|
// doesn't actually seem necessary, UDIM support "just works".
|
|
|
|
auto& lod_faces = mFaces[lod_iter];
|
|
|
|
// if current lod isn't in use, skip.
|
|
if (lod_faces.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// process current lod
|
|
for (auto& current_face : lod_faces)
|
|
{
|
|
// first transform the bounding boxes
|
|
auto& current_submesh_bbox = current_face->getFaceBoundingBox();
|
|
current_submesh_bbox.first += mObjectTranslation;
|
|
current_submesh_bbox.second += mObjectTranslation;
|
|
for (int vec_iter = 0; vec_iter < 4; ++vec_iter)
|
|
{
|
|
current_submesh_bbox.first.mV[vec_iter] *= mObjectScale.mV[vec_iter];
|
|
current_submesh_bbox.second.mV[vec_iter] *= mObjectScale.mV[vec_iter];
|
|
}
|
|
|
|
// then transform the positions
|
|
auto& current_face_positions = current_face->getPositions();
|
|
for (auto& current_position : current_face_positions)
|
|
{
|
|
current_position += mObjectTranslation;
|
|
for (int vec_iter = 0; vec_iter < 4; ++vec_iter)
|
|
{
|
|
current_position.mV[vec_iter] *= mObjectScale.mV[vec_iter];
|
|
}
|
|
}
|
|
|
|
// if we have no normals, or they somehow don't match pos - skip here.
|
|
auto& current_face_normals = current_face->getNormals();
|
|
if (current_face_normals.size() != current_face_positions.size())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// and last, transform the normals
|
|
for (auto& current_normal : current_face_normals)
|
|
{
|
|
if (current_normal.isExactlyZero())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int vec_iter = 0; vec_iter < 4; ++vec_iter)
|
|
{
|
|
current_normal.mV[vec_iter] *= mObjectSize.mV[vec_iter];
|
|
}
|
|
|
|
current_normal.normalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLLocalMeshObject::fillVolume(LLLocalMeshFileLOD lod)
|
|
{
|
|
// check if we have data for [lod]
|
|
if (mFaces[lod].empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// generate face data
|
|
std::vector<LLVolumeFace> new_faces;
|
|
bool weight_attributes_found = false;
|
|
|
|
for (auto& current_submesh : mFaces[lod])
|
|
{
|
|
LLVolumeFace new_face;
|
|
new_face.resizeVertices(current_submesh->getPositions().size());
|
|
new_face.resizeIndices(current_submesh->getIndices().size());
|
|
|
|
// NOTE: uv extents
|
|
// doesn't actually seem necessary, UDIM support "just works".
|
|
|
|
// position attribute
|
|
for (size_t position_iter = 0; position_iter < current_submesh->getPositions().size(); ++position_iter)
|
|
{
|
|
new_face.mPositions[position_iter].loadua(current_submesh->getPositions()[position_iter].mV);
|
|
}
|
|
|
|
// normal attribute
|
|
for (size_t normal_iter = 0; normal_iter < current_submesh->getNormals().size(); ++normal_iter)
|
|
{
|
|
new_face.mNormals[normal_iter].loadua(current_submesh->getNormals()[normal_iter].mV);
|
|
}
|
|
|
|
// uv attribute
|
|
for (size_t uv_iter = 0; uv_iter < current_submesh->getUVs().size(); ++uv_iter)
|
|
{
|
|
new_face.mTexCoords[uv_iter] = current_submesh->getUVs()[uv_iter];
|
|
}
|
|
|
|
// weight attribute
|
|
if (!current_submesh->getSkin().empty())
|
|
{
|
|
weight_attributes_found = true;
|
|
new_face.allocateWeights(current_submesh->getSkin().size());
|
|
for (size_t weight_iter = 0; weight_iter < current_submesh->getSkin().size(); ++weight_iter)
|
|
{
|
|
auto current_local_weight = current_submesh->getSkin()[weight_iter];
|
|
LLVector4 current_v4_weight;
|
|
|
|
for (int joint_iter = 0; joint_iter < 4; ++joint_iter)
|
|
{
|
|
auto jidx = current_local_weight.mJointIndices[joint_iter];
|
|
if (jidx == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float cweight = current_local_weight.mJointWeights[joint_iter];
|
|
float cjoint = (float)jidx;
|
|
float combined = cweight + cjoint;
|
|
current_v4_weight[joint_iter] = combined;
|
|
}
|
|
|
|
new_face.mWeights[weight_iter].loadua(current_v4_weight.mV);
|
|
}
|
|
}
|
|
|
|
// transfer indices
|
|
for (size_t index_iter = 0; index_iter < current_submesh->getNumIndices(); ++index_iter)
|
|
{
|
|
new_face.mIndices[index_iter] = current_submesh->getIndices()[index_iter];
|
|
}
|
|
|
|
new_faces.push_back(new_face);
|
|
}
|
|
|
|
// push data into relevant lod
|
|
LLVolume* current_volume = LLPrimitive::getVolumeManager()->refVolume(mVolumeParams, lod);
|
|
if (current_volume)
|
|
{
|
|
current_volume->copyFacesFrom(new_faces);
|
|
current_volume->setMeshAssetLoaded(TRUE);
|
|
LLPrimitive::getVolumeManager()->unrefVolume(current_volume);
|
|
}
|
|
}
|
|
|
|
void LLLocalMeshObject::attachSkinInfo()
|
|
{
|
|
auto skinmap_seeker = gMeshRepo.mSkinMap.find(mSculptID);
|
|
if (skinmap_seeker == gMeshRepo.mSkinMap.end())
|
|
{
|
|
gMeshRepo.mSkinMap[mSculptID] = mMeshSkinInfo;
|
|
}
|
|
else
|
|
{
|
|
// NOTE: seems necessary, not tested without.
|
|
skinmap_seeker->second = mMeshSkinInfo;
|
|
}
|
|
}
|
|
|
|
bool LLLocalMeshObject::getIsRiggedObject() const
|
|
{
|
|
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(const std::string& filename, bool try_lods)
|
|
{
|
|
// initialize safe defaults
|
|
for (size_t lod_iter = 0; lod_iter < LOCAL_NUM_LODS; ++lod_iter)
|
|
{
|
|
mFilenames[lod_iter].clear();
|
|
mLastModified[lod_iter].clear();
|
|
mLoadedSuccessfully[lod_iter] = false;
|
|
}
|
|
|
|
mTryLODFiles = try_lods;
|
|
mShortName.clear();
|
|
mLoadingLog.clear();
|
|
mExtension = LLLocalMeshFileExtension::EXTEN_NONE;
|
|
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_NONE;
|
|
mLocalMeshFileID.setNull();
|
|
mLocalMeshFileNeedsUIUpdate = false;
|
|
mLoadedObjectList.clear();
|
|
mSavedObjectSculptIDs.clear();
|
|
|
|
mShortName = std::string(boost::filesystem::path(filename).stem().string());
|
|
std::string stripSuffix(std::string);
|
|
auto base_lod_filename {stripSuffix(mShortName)};
|
|
pushLog("LLLocalMeshFile", "Initializing with base filename: " + base_lod_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;
|
|
}
|
|
|
|
// for the high lod we just store the filename fromthe file picker
|
|
mFilenames[LOCAL_LOD_HIGH] = filename;
|
|
|
|
// check if we have a valid extension, can't switch with string can we?
|
|
if (std::string exten_str = boost::filesystem::extension(filename); 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() = default;
|
|
|
|
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[LOCAL_LOD_HIGH]))
|
|
{
|
|
// filename provided doesn't exist, just stop.
|
|
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ERROR;
|
|
pushLog("LLLocalMeshFile", "Couldn't find filename: " + mFilenames[LOCAL_LOD_HIGH], true);
|
|
return;
|
|
}
|
|
|
|
// check for lod filenames
|
|
if (mTryLODFiles)
|
|
{
|
|
pushLog("LLLocalMeshFile", "Seeking LOD files...");
|
|
// up to LOD2, LOD3 being the highest is always done by this point.
|
|
for (S32 lodfile_iter = LOCAL_LOD_LOWEST; lodfile_iter < LOCAL_LOD_HIGH; ++lodfile_iter)
|
|
{
|
|
// lod filenames may be empty because this is first time through or because the lod didn't exist before.
|
|
if( mFilenames[lodfile_iter].empty() )
|
|
{
|
|
auto filepath { boost::filesystem::path(mFilenames[LOCAL_LOD_HIGH]).parent_path() };
|
|
std::string getLodSuffix(S32);
|
|
auto lod_suffix { getLodSuffix(lodfile_iter) };
|
|
auto extension { boost::filesystem::path(mFilenames[LOCAL_LOD_HIGH]).extension() };
|
|
|
|
boost::filesystem::path current_lod_filename = filepath / (mShortName + lod_suffix + extension.string());
|
|
if ( boost::filesystem::exists( current_lod_filename ) )
|
|
{
|
|
pushLog("LLLocalMeshFile", "LOD filename " + current_lod_filename.string() + " found, adding.");
|
|
mFilenames[lodfile_iter] = current_lod_filename.string();
|
|
}
|
|
|
|
else
|
|
{
|
|
pushLog("LLLocalMeshFile", "LOD filename " + current_lod_filename.string() + " not found, skipping.");
|
|
mFilenames[lodfile_iter].clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pushLog("LLLocalMeshFile", "Skipping LOD 2-0 files, as specified.");
|
|
}
|
|
|
|
// if we are here we can assume at least mFilenames[3] is present
|
|
// here we'll define a lambda to call through std::async, accessible through mAsyncFuture.
|
|
// the lamnda returns bool if change happened, individual lod logs, and their status.
|
|
auto lambda_loadfiles = [&]() mutable -> LLLocalMeshLoaderReply
|
|
{
|
|
bool change_happened = false;
|
|
std::vector<std::string> log;
|
|
std::array<bool, 4> lod_success = {false, false, false, false};
|
|
|
|
// iterate over every lod
|
|
// we're counting back because LOD3 is most likely to have showstopper problems,
|
|
// so i'd rather not load other lods if LOD3 is found to be broken.
|
|
// int rather than size_t because we're iterating over LODS 3 to 0, stopping at -1
|
|
for (signed int lod_idx = LOCAL_LOD_HIGH; lod_idx >= LOCAL_LOD_LOWEST; --lod_idx)
|
|
{
|
|
auto current_lod = static_cast<LLLocalMeshFileLOD>(lod_idx);
|
|
|
|
// do we have a filename for this lod?
|
|
if (mFilenames[lod_idx].empty())
|
|
{
|
|
log.push_back("[ LLLocalMeshFile ] File for LOD " + std::to_string(lod_idx) + " was not found, skipping.");
|
|
continue;
|
|
}
|
|
|
|
// has the file been modified since we last checked?
|
|
bool file_modified = updateLastModified(current_lod);
|
|
if (!file_modified)
|
|
{
|
|
log.push_back("[ LLLocalMeshFile ] File for LOD " + std::to_string(lod_idx) + " was not modified, skipping.");
|
|
lod_success[lod_idx] = mLoadedSuccessfully[lod_idx];
|
|
continue;
|
|
}
|
|
|
|
// up until here, skipping loading a lod is fine, after here - it's a sign of an error.
|
|
|
|
// clear the old objects, if any.
|
|
if (lod_idx == LLLocalMeshFileLOD::LOCAL_LOD_HIGH)
|
|
{
|
|
mLoadedObjectList.clear();
|
|
}
|
|
|
|
log.push_back("[ LLLocalMeshFile ] Attempting to load file for LOD " + std::to_string(lod_idx));
|
|
switch (mExtension)
|
|
{
|
|
case LLLocalMeshFileExtension::EXTEN_DAE:
|
|
{
|
|
// pass it over to dae loader
|
|
LLLocalMeshImportDAE importer;
|
|
auto importer_result = importer.loadFile(this, current_lod);
|
|
lod_success[lod_idx] = importer_result.first;
|
|
|
|
// NOTE: if not success - do not indicate change as not to affect existing vobjects?
|
|
if (lod_success[lod_idx])
|
|
{
|
|
change_happened = true;
|
|
}
|
|
|
|
auto importer_log = importer_result.second;
|
|
log.reserve(log.size() + importer_log.size());
|
|
log.insert(log.end(), importer_log.begin(), importer_log.end());
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
log.push_back("[ LLLocalMeshFile ] ERROR, Loader for LOD " + std::to_string(lod_idx) + " called with invalid extension.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// by this point, it's only false if there's an actual error loading the file.
|
|
if (!lod_success[lod_idx])
|
|
{
|
|
log.push_back("[ LLLocalMeshFile ] ERROR, attempted and failed to load LOD " + std::to_string(lod_idx) + ", stopping.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// just in case, recheck if we actually ended up loading anything
|
|
auto& object_list = getObjectVector();
|
|
if (object_list.empty())
|
|
{
|
|
log.push_back("[ LLLocalMeshFile ] ERROR, no objects loaded, stopping.");
|
|
|
|
// seems like a pretty serious error, error out all the lods manually,
|
|
// and set change happened to false.
|
|
for (size_t lod_iter = 0; lod_iter < 4; ++lod_iter)
|
|
{
|
|
lod_success[lod_iter] = false;
|
|
}
|
|
|
|
change_happened = false;
|
|
}
|
|
|
|
LLLocalMeshLoaderReply result;
|
|
result.mChanged = change_happened;
|
|
result.mLog = log;
|
|
result.mStatus = lod_success;
|
|
return result;
|
|
};
|
|
|
|
mAsyncFuture = std::async(std::launch::async, lambda_loadfiles);
|
|
}
|
|
|
|
LLLocalMeshFile::LLLocalMeshFileStatus LLLocalMeshFile::reloadLocalMeshObjectsCheck()
|
|
{
|
|
// only invalid if no loading is happening.
|
|
if (mAsyncFuture.valid() == false)
|
|
{
|
|
return mLocalMeshFileStatus;
|
|
}
|
|
|
|
// actually check if the thread is done, if no - return that we're still loading.
|
|
// _Is_ready() doesn't seem to actually do what it promises to, how punny.
|
|
// so we'll wait for zero milliseconds instead to check if ready.
|
|
auto timeout = std::chrono::milliseconds(0);
|
|
if (mAsyncFuture.wait_for(timeout) != std::future_status::ready)
|
|
{
|
|
// thread is still working
|
|
return mLocalMeshFileStatus;
|
|
}
|
|
else
|
|
{
|
|
// thread is done working
|
|
reloadLocalMeshObjectsCallback();
|
|
}
|
|
|
|
// by now we have either active or error status, this return will lead to ui refresh.
|
|
return mLocalMeshFileStatus;
|
|
}
|
|
|
|
void LLLocalMeshFile::reloadLocalMeshObjectsCallback()
|
|
{
|
|
LLLocalMeshLoaderReply reply = mAsyncFuture.get();
|
|
mLoadingLog.reserve(mLoadingLog.size() + reply.mLog.size());
|
|
mLoadingLog.insert(mLoadingLog.end(), reply.mLog.begin(), reply.mLog.end());
|
|
mLoadedSuccessfully = reply.mStatus;
|
|
|
|
// if LOD3 is ok, we're technically fine.
|
|
if (mLoadedSuccessfully[3])
|
|
{
|
|
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ACTIVE;
|
|
if (reply.mChanged)
|
|
{
|
|
updateVObjects();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mLocalMeshFileStatus = LLLocalMeshFileStatus::STATUS_ERROR;
|
|
}
|
|
|
|
mLocalMeshFileNeedsUIUpdate = true;
|
|
}
|
|
|
|
bool LLLocalMeshFile::updateLastModified(LLLocalMeshFileLOD lod)
|
|
{
|
|
bool file_updated = false;
|
|
LLSD current_last_modified = mLastModified[lod];
|
|
std::string current_filename = mFilenames[lod];
|
|
|
|
#ifndef LL_WINDOWS
|
|
const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(current_filename));
|
|
#else
|
|
const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(utf8str_to_utf16str(current_filename)));
|
|
#endif
|
|
|
|
|
|
if (LLSD new_last_modified = asctime(localtime(&temp_time)); new_last_modified.asString() != current_last_modified.asString())
|
|
{
|
|
file_updated = true;
|
|
mLastModified[lod] = new_last_modified;
|
|
}
|
|
|
|
return file_updated;
|
|
}
|
|
|
|
bool LLLocalMeshFile::notifyNeedsUIUpdate()
|
|
{
|
|
bool result = mLocalMeshFileNeedsUIUpdate;
|
|
|
|
if (mLocalMeshFileNeedsUIUpdate)
|
|
{
|
|
// ui update can only be needed once.
|
|
mLocalMeshFileNeedsUIUpdate = false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
LLLocalMeshFile::LLLocalMeshFileInfo LLLocalMeshFile::getFileInfo()
|
|
{
|
|
LLLocalMeshFileInfo result;
|
|
|
|
result.mName = mShortName;
|
|
result.mStatus = mLocalMeshFileStatus;
|
|
result.mLocalID = mLocalMeshFileID;
|
|
result.mLODAvailability = mLoadedSuccessfully;
|
|
result.mObjectList.clear();
|
|
|
|
if (mLocalMeshFileStatus == LLLocalMeshFileStatus::STATUS_ACTIVE)
|
|
{
|
|
// fill object list
|
|
auto& vector_objects = getObjectVector();
|
|
for (auto& current_object : vector_objects)
|
|
{
|
|
std::string object_name = current_object->getObjectName();
|
|
result.mObjectList.push_back(object_name);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void LLLocalMeshFile::updateVObjects()
|
|
{
|
|
for (size_t object_iter = 0; object_iter < mSavedObjectSculptIDs.size(); ++object_iter)
|
|
{
|
|
auto local_obj_sculpt_id = mSavedObjectSculptIDs[object_iter];
|
|
auto affected_vobject_ids = gObjectList.findMeshObjectsBySculptID(local_obj_sculpt_id);
|
|
for (auto current_vobject_id : affected_vobject_ids)
|
|
{
|
|
auto target_object_ptr = static_cast<LLVOVolume*>(gObjectList.findObject(current_vobject_id));
|
|
|
|
if (!target_object_ptr->mIsLocalMesh)
|
|
{
|
|
// in case for some reason we got the id of an unrelated object?
|
|
// should never get here.
|
|
continue;
|
|
}
|
|
|
|
bool using_scale = target_object_ptr->mIsLocalMeshUsingScale;
|
|
applyToVObject(current_vobject_id, object_iter, using_scale);
|
|
}
|
|
|
|
// also, remove old skin from
|
|
gMeshRepo.mSkinMap.erase(local_obj_sculpt_id);
|
|
}
|
|
}
|
|
|
|
void LLLocalMeshFile::applyToVObject(LLUUID viewer_object_id, int object_index, bool use_scale)
|
|
{
|
|
// get the actual vovolume
|
|
auto target_object = static_cast<LLVOVolume*>(gObjectList.findObject(viewer_object_id));
|
|
if (target_object == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// check if object_index is sane
|
|
if ((object_index < 0) || (object_index >= mLoadedObjectList.size()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto object_params = mLoadedObjectList[object_index]->getVolumeParams();
|
|
target_object->setVolume(object_params, 3);
|
|
target_object->mIsLocalMesh = true;
|
|
target_object->mIsLocalMeshUsingScale = use_scale;
|
|
|
|
for (int lod_reverse_iter = 3; lod_reverse_iter >= 0; --lod_reverse_iter)
|
|
{
|
|
if (!mLoadedSuccessfully[lod_reverse_iter])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto current_lod = static_cast<LLLocalMeshFileLOD>(lod_reverse_iter);
|
|
mLoadedObjectList[object_index]->fillVolume(current_lod);
|
|
}
|
|
|
|
if (mLoadedObjectList[object_index]->getIsRiggedObject())
|
|
{
|
|
mLoadedObjectList[object_index]->attachSkinInfo();
|
|
}
|
|
|
|
if ((!target_object->isAttachment()) && use_scale)
|
|
{
|
|
auto scale = mLoadedObjectList[object_index]->getObjectSize();
|
|
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(const std::string& who, const 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(const std::string& filename, bool try_lods)
|
|
{
|
|
auto loaded_file = std::make_unique<LLLocalMeshFile>(filename, try_lods);
|
|
mLoadedFileList.push_back(std::move(loaded_file));
|
|
triggerFloaterRefresh(false);
|
|
|
|
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(bool keep_selection)
|
|
{
|
|
if (mFloaterPtr)
|
|
{
|
|
mFloaterPtr->reloadFileList(keep_selection);
|
|
}
|
|
}
|
|
|
|
std::vector<LLLocalMeshFile::LLLocalMeshFileInfo> LLLocalMeshSystem::getFileInfoVector() const
|
|
{
|
|
std::vector<LLLocalMeshFile::LLLocalMeshFileInfo> result;
|
|
|
|
for (auto& current_file : mLoadedFileList)
|
|
{
|
|
result.push_back(current_file->getFileInfo());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::string> LLLocalMeshSystem::getFileLog(LLUUID local_file_id) const
|
|
{
|
|
std::vector<std::string> result;
|
|
|
|
for (auto& current_file : mLoadedFileList)
|
|
{
|
|
if (current_file->getFileID() == local_file_id)
|
|
{
|
|
result = current_file->getFileLog();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|