Replace MAX_AGENT_ATTACHMENTS with sim based response Replace max groups with benefits based response Upload costs are now handled by sim response Removed no-longer-needed lleconomy files and classes Removed dead fields from hippolimits and hippogridmanager Also removed lame LL code that is redundant and silly On non SL grids, when values are not provided for benefits, they will be set to the values granting maximum liberty. Old standardized responses still work when benefits aren't implemented.
4061 lines
105 KiB
C++
4061 lines
105 KiB
C++
/**
|
|
* @file llmeshrepository.cpp
|
|
* @brief Mesh repository implementation.
|
|
*
|
|
* $LicenseInfo:firstyear=2005&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "apr_pools.h"
|
|
#include "apr_dso.h"
|
|
#include "llhttpstatuscodes.h"
|
|
#include "llmeshrepository.h"
|
|
|
|
#include "llagent.h"
|
|
#include "llappviewer.h"
|
|
#include "llbufferstream.h"
|
|
#include "llcallbacklist.h"
|
|
#include "lldatapacker.h"
|
|
#include "llfasttimer.h"
|
|
#include "llfloaterperms.h"
|
|
#include "llimagej2c.h"
|
|
#include "llhost.h"
|
|
#include "llnotificationsutil.h"
|
|
#include "llsd.h"
|
|
#include "llsdutil_math.h"
|
|
#include "llsdserialize.h"
|
|
#include "llthread.h"
|
|
#include "llvfile.h"
|
|
#include "llviewercontrol.h"
|
|
#include "llviewerinventory.h"
|
|
#include "llviewermenufile.h"
|
|
#include "llviewerobjectlist.h"
|
|
#include "llviewerregion.h"
|
|
#include "llviewertexturelist.h"
|
|
#include "llvolume.h"
|
|
#include "llvolumemgr.h"
|
|
#include "llvovolume.h"
|
|
#include "llworld.h"
|
|
#include "material_codes.h"
|
|
#include "pipeline.h"
|
|
#include "llinventorymodel.h"
|
|
#include "llfoldertype.h"
|
|
#include "llviewerparcelmgr.h"
|
|
#include "llassetuploadresponders.h"
|
|
#include "lluploadfloaterobservers.h"
|
|
#include "aicurl.h"
|
|
|
|
#include "boost/lexical_cast.hpp"
|
|
|
|
#ifndef LL_WINDOWS
|
|
#include "netdb.h"
|
|
#endif
|
|
|
|
#include <queue>
|
|
|
|
class AIHTTPTimeoutPolicy;
|
|
extern AIHTTPTimeoutPolicy meshHeaderResponder_timeout;
|
|
extern AIHTTPTimeoutPolicy meshLODResponder_timeout;
|
|
extern AIHTTPTimeoutPolicy meshSkinInfoResponder_timeout;
|
|
extern AIHTTPTimeoutPolicy meshDecompositionResponder_timeout;
|
|
extern AIHTTPTimeoutPolicy meshPhysicsShapeResponder_timeout;
|
|
extern AIHTTPTimeoutPolicy wholeModelFeeResponder_timeout;
|
|
extern AIHTTPTimeoutPolicy wholeModelUploadResponder_timeout;
|
|
|
|
LLMeshRepository gMeshRepo;
|
|
|
|
const U32 MAX_MESH_REQUESTS_PER_SECOND = 100;
|
|
|
|
// Maximum mesh version to support. Three least significant digits are reserved for the minor version,
|
|
// with major version changes indicating a format change that is not backwards compatible and should not
|
|
// be parsed by viewers that don't specifically support that version. For example, if the integer "1" is
|
|
// present, the version is 0.001. A viewer that can parse version 0.001 can also parse versions up to 0.999,
|
|
// but not 1.0 (integer 1000).
|
|
// See wiki at https://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format
|
|
const S32 MAX_MESH_VERSION = 999;
|
|
|
|
U32 LLMeshRepository::sBytesReceived = 0;
|
|
U32 LLMeshRepository::sHTTPRequestCount = 0;
|
|
U32 LLMeshRepository::sHTTPRetryCount = 0;
|
|
U32 LLMeshRepository::sLODProcessing = 0;
|
|
U32 LLMeshRepository::sLODPending = 0;
|
|
|
|
U32 LLMeshRepository::sCacheBytesRead = 0;
|
|
U32 LLMeshRepository::sCacheBytesWritten = 0;
|
|
U32 LLMeshRepository::sPeakKbps = 0;
|
|
|
|
const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5;
|
|
|
|
static S32 dump_num = 0;
|
|
std::string make_dump_name(std::string prefix, S32 num)
|
|
{
|
|
return prefix + boost::lexical_cast<std::string>(num) + std::string(".xml");
|
|
|
|
}
|
|
void dump_llsd_to_file(const LLSD& content, std::string filename);
|
|
LLSD llsd_from_file(std::string filename);
|
|
|
|
std::string header_lod[] =
|
|
{
|
|
"lowest_lod",
|
|
"low_lod",
|
|
"medium_lod",
|
|
"high_lod"
|
|
};
|
|
const char * const LOG_MESH = "Mesh";
|
|
|
|
|
|
//get the number of bytes resident in memory for given volume
|
|
U32 get_volume_memory_size(const LLVolume* volume)
|
|
{
|
|
U32 indices = 0;
|
|
U32 vertices = 0;
|
|
|
|
for (U32 i = 0; i < (U32)volume->getNumVolumeFaces(); ++i)
|
|
{
|
|
const LLVolumeFace& face = volume->getVolumeFace(i);
|
|
indices += face.mNumIndices;
|
|
vertices += face.mNumVertices;
|
|
}
|
|
|
|
|
|
return indices * 2 + vertices * 11 + sizeof(LLVolume) + sizeof(LLVolumeFace) * volume->getNumVolumeFaces();
|
|
}
|
|
|
|
void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, F32 scale = 1.f)
|
|
{
|
|
res.mPositions.clear();
|
|
res.mNormals.clear();
|
|
|
|
const F32* v = mesh.mVertexBase;
|
|
|
|
if (mesh.mIndexType == LLCDMeshData::INT_16)
|
|
{
|
|
U16* idx = (U16*) mesh.mIndexBase;
|
|
for (S32 j = 0; j < mesh.mNumTriangles; ++j)
|
|
{
|
|
F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes);
|
|
F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes);
|
|
F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes);
|
|
|
|
idx = (U16*) (((U8*)idx)+mesh.mIndexStrideBytes);
|
|
|
|
LLVector3 v0(mp0);
|
|
LLVector3 v1(mp1);
|
|
LLVector3 v2(mp2);
|
|
|
|
LLVector3 n = (v1-v0)%(v2-v0);
|
|
n.normalize();
|
|
|
|
res.mPositions.push_back(v0*scale);
|
|
res.mPositions.push_back(v1*scale);
|
|
res.mPositions.push_back(v2*scale);
|
|
|
|
res.mNormals.push_back(n);
|
|
res.mNormals.push_back(n);
|
|
res.mNormals.push_back(n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
U32* idx = (U32*) mesh.mIndexBase;
|
|
for (S32 j = 0; j < mesh.mNumTriangles; ++j)
|
|
{
|
|
F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes);
|
|
F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes);
|
|
F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes);
|
|
|
|
idx = (U32*) (((U8*)idx)+mesh.mIndexStrideBytes);
|
|
|
|
LLVector3 v0(mp0);
|
|
LLVector3 v1(mp1);
|
|
LLVector3 v2(mp2);
|
|
|
|
LLVector3 n = (v1-v0)%(v2-v0);
|
|
n.normalize();
|
|
|
|
res.mPositions.push_back(v0*scale);
|
|
res.mPositions.push_back(v1*scale);
|
|
res.mPositions.push_back(v2*scale);
|
|
|
|
res.mNormals.push_back(n);
|
|
res.mNormals.push_back(n);
|
|
res.mNormals.push_back(n);
|
|
}
|
|
}
|
|
}
|
|
|
|
LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMaterial& material)
|
|
{
|
|
LLPointer< LLViewerFetchedTexture > * ppTex = static_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData);
|
|
return ppTex ? (*ppTex).get() : NULL;
|
|
}
|
|
|
|
S32 LLMeshRepoThread::sActiveHeaderRequests = 0;
|
|
S32 LLMeshRepoThread::sActiveLODRequests = 0;
|
|
U32 LLMeshRepoThread::sMaxConcurrentRequests = 1;
|
|
|
|
class LLMeshHeaderResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
public:
|
|
LLVolumeParams mMeshParams;
|
|
bool mProcessed;
|
|
void retry();
|
|
|
|
LLMeshHeaderResponder(const LLVolumeParams& mesh_params)
|
|
: mMeshParams(mesh_params)
|
|
{
|
|
LLMeshRepoThread::incActiveHeaderRequests();
|
|
mProcessed = false;
|
|
}
|
|
|
|
~LLMeshHeaderResponder()
|
|
{
|
|
if (!LLApp::isQuitting())
|
|
{
|
|
if (!mProcessed)
|
|
{ //something went wrong, retry
|
|
LL_WARNS() << "Timeout or service unavailable, retrying." << LL_ENDL;
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
LLMutexLock lock(gMeshRepo.mThread->mMutex);
|
|
gMeshRepo.mThread->pushHeaderRequest(mMeshParams, 10.f);
|
|
}
|
|
|
|
LLMeshRepoThread::decActiveHeaderRequests();
|
|
}
|
|
}
|
|
|
|
|
|
virtual void completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer);
|
|
|
|
/*virtual*/ AICapabilityType capability_type(void) const { return cap_mesh; }
|
|
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshHeaderResponder_timeout; }
|
|
/*virtual*/ char const* getName(void) const { return "LLMeshHeaderResponder"; }
|
|
};
|
|
|
|
class LLMeshLODResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
public:
|
|
LLVolumeParams mMeshParams;
|
|
S32 mLOD;
|
|
U32 mRequestedBytes;
|
|
U32 mOffset;
|
|
bool mProcessed;
|
|
void retry();
|
|
|
|
LLMeshLODResponder(const LLVolumeParams& mesh_params, S32 lod, U32 offset, U32 requested_bytes)
|
|
: mMeshParams(mesh_params), mLOD(lod), mOffset(offset), mRequestedBytes(requested_bytes)
|
|
{
|
|
LLMeshRepoThread::incActiveLODRequests();
|
|
mProcessed = false;
|
|
}
|
|
|
|
~LLMeshLODResponder()
|
|
{
|
|
if (!LLApp::isExiting())
|
|
{
|
|
if (!mProcessed)
|
|
{
|
|
LL_WARNS() << "Killed without being processed, retrying." << LL_ENDL;
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD);
|
|
}
|
|
LLMeshRepoThread::decActiveLODRequests();
|
|
}
|
|
}
|
|
|
|
virtual void completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer);
|
|
|
|
/*virtual*/ AICapabilityType capability_type(void) const { return cap_mesh; }
|
|
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshLODResponder_timeout; }
|
|
/*virtual*/ char const* getName(void) const { return "LLMeshLODResponder"; }
|
|
};
|
|
|
|
class LLMeshSkinInfoResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
public:
|
|
LLUUID mMeshID;
|
|
U32 mRequestedBytes;
|
|
U32 mOffset;
|
|
bool mProcessed;
|
|
void retry();
|
|
|
|
LLMeshSkinInfoResponder(const LLUUID& id, U32 offset, U32 size)
|
|
: mMeshID(id), mRequestedBytes(size), mOffset(offset)
|
|
{
|
|
mProcessed = false;
|
|
}
|
|
|
|
~LLMeshSkinInfoResponder()
|
|
{
|
|
if (!LLApp::isQuitting() &&
|
|
!mProcessed &&
|
|
mMeshID.notNull())
|
|
{ // Something went wrong, retry
|
|
LL_WARNS() << "Timeout or service unavailable, retrying loadMeshSkinInfo() for " << mMeshID << LL_ENDL;
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
gMeshRepo.mThread->loadMeshSkinInfo(mMeshID);
|
|
}
|
|
}
|
|
|
|
virtual void completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer);
|
|
|
|
/*virtual*/ AICapabilityType capability_type(void) const { return cap_mesh; }
|
|
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshSkinInfoResponder_timeout; }
|
|
/*virtual*/ char const* getName(void) const { return "LLMeshSkinInfoResponder"; }
|
|
};
|
|
|
|
class LLMeshDecompositionResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
public:
|
|
LLUUID mMeshID;
|
|
U32 mRequestedBytes;
|
|
U32 mOffset;
|
|
bool mProcessed;
|
|
void retry();
|
|
|
|
LLMeshDecompositionResponder(const LLUUID& id, U32 offset, U32 size)
|
|
: mMeshID(id), mRequestedBytes(size), mOffset(offset)
|
|
{
|
|
mProcessed = false;
|
|
}
|
|
|
|
~LLMeshDecompositionResponder()
|
|
{
|
|
if (!LLApp::isQuitting() &&
|
|
!mProcessed &&
|
|
mMeshID.notNull())
|
|
{ // Something went wrong, retry
|
|
LL_WARNS() << "Timeout or service unavailable, retrying loadMeshDecomposition() for " << mMeshID << LL_ENDL;
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
gMeshRepo.mThread->loadMeshDecomposition(mMeshID);
|
|
}
|
|
}
|
|
|
|
virtual void completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer);
|
|
|
|
/*virtual*/ AICapabilityType capability_type(void) const { return cap_mesh; }
|
|
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshDecompositionResponder_timeout; }
|
|
/*virtual*/ char const* getName(void) const { return "LLMeshDecompositionResponder"; }
|
|
};
|
|
|
|
class LLMeshPhysicsShapeResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
public:
|
|
LLUUID mMeshID;
|
|
U32 mRequestedBytes;
|
|
U32 mOffset;
|
|
bool mProcessed;
|
|
void retry();
|
|
|
|
LLMeshPhysicsShapeResponder(const LLUUID& id, U32 offset, U32 size)
|
|
: mMeshID(id), mRequestedBytes(size), mOffset(offset)
|
|
{
|
|
mProcessed = false;
|
|
}
|
|
|
|
~LLMeshPhysicsShapeResponder()
|
|
{
|
|
if (!LLApp::isQuitting() &&
|
|
!mProcessed &&
|
|
mMeshID.notNull())
|
|
{ // Something went wrong, retry
|
|
LL_WARNS() << "Timeout or service unavailable, retrying loadMeshPhysicsShape() for " << mMeshID << LL_ENDL;
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID);
|
|
}
|
|
}
|
|
|
|
virtual void completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer);
|
|
|
|
/*virtual*/ AICapabilityType capability_type(void) const { return cap_mesh; }
|
|
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return meshPhysicsShapeResponder_timeout; }
|
|
/*virtual*/ char const* getName(void) const { return "LLMeshPhysicsShapeResponder"; }
|
|
};
|
|
|
|
void log_upload_error(S32 status, const LLSD& content, std::string stage, std::string model_name)
|
|
{
|
|
// Add notification popup.
|
|
LLSD args;
|
|
std::string message = content["error"]["message"];
|
|
std::string identifier = content["error"]["identifier"];
|
|
args["MESSAGE"] = message;
|
|
args["IDENTIFIER"] = identifier;
|
|
args["LABEL"] = model_name;
|
|
gMeshRepo.uploadError(args);
|
|
|
|
// Log details.
|
|
LL_WARNS() << "stage: " << stage << " http status: " << status << LL_ENDL;
|
|
if (content.has("error"))
|
|
{
|
|
const LLSD& err = content["error"];
|
|
LL_WARNS() << "err: " << err << LL_ENDL;
|
|
LL_WARNS() << "mesh upload failed, stage '" << stage
|
|
<< "' error '" << err["error"].asString()
|
|
<< "', message '" << err["message"].asString()
|
|
<< "', id '" << err["identifier"].asString()
|
|
<< "'" << LL_ENDL;
|
|
if (err.has("errors"))
|
|
{
|
|
S32 error_num = 0;
|
|
const LLSD& err_list = err["errors"];
|
|
for (LLSD::array_const_iterator it = err_list.beginArray();
|
|
it != err_list.endArray();
|
|
++it)
|
|
{
|
|
const LLSD& err_entry = *it;
|
|
LL_WARNS() << "error[" << error_num << "]:" << LL_ENDL;
|
|
for (LLSD::map_const_iterator map_it = err_entry.beginMap();
|
|
map_it != err_entry.endMap();
|
|
++map_it)
|
|
{
|
|
LL_WARNS() << "\t" << map_it->first << ": "
|
|
<< map_it->second << LL_ENDL;
|
|
}
|
|
error_num++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "bad mesh, no error information available" << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
class LLWholeModelFeeResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
std::string& mWholeModelUploadURL;
|
|
LLSD mModelData;
|
|
LLHandle<LLWholeModelFeeObserver> mObserverHandle;
|
|
public:
|
|
LLWholeModelFeeResponder(LLSD& model_data, LLHandle<LLWholeModelFeeObserver> observer_handle, std::string& url_out):
|
|
mWholeModelUploadURL(url_out),
|
|
mModelData(model_data),
|
|
mObserverHandle(observer_handle)
|
|
{
|
|
}
|
|
|
|
~LLWholeModelFeeResponder()
|
|
{
|
|
}
|
|
|
|
virtual void httpCompleted(void)
|
|
{
|
|
LLSD cc = mContent;
|
|
if (gSavedSettings.getS32("MeshUploadFakeErrors")&1)
|
|
{
|
|
cc = llsd_from_file("fake_upload_error.xml");
|
|
}
|
|
|
|
dump_llsd_to_file(cc,make_dump_name("whole_model_fee_response_",dump_num));
|
|
|
|
LLWholeModelFeeObserver* observer = mObserverHandle.get();
|
|
|
|
if (isGoodStatus(mStatus) &&
|
|
cc["state"].asString() == "upload")
|
|
{
|
|
mWholeModelUploadURL = cc["uploader"].asString();
|
|
|
|
if (observer)
|
|
{
|
|
cc["data"]["upload_price"] = cc["upload_price"];
|
|
observer->onModelPhysicsFeeReceived(cc["data"], mWholeModelUploadURL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "fee request failed" << LL_ENDL;
|
|
log_upload_error(mStatus,cc,"fee",mModelData["name"]);
|
|
mWholeModelUploadURL = "";
|
|
|
|
if (observer)
|
|
{
|
|
observer->setModelPhysicsFeeErrorStatus(mStatus, mReason);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return wholeModelFeeResponder_timeout; }
|
|
/*virtual*/ char const* getName(void) const { return "LLWholeModelFeeResponder"; }
|
|
};
|
|
|
|
class LLWholeModelUploadResponder : public LLHTTPClient::ResponderWithCompleted
|
|
{
|
|
LLSD mModelData;
|
|
LLHandle<LLWholeModelUploadObserver> mObserverHandle;
|
|
|
|
public:
|
|
LLWholeModelUploadResponder(LLSD& model_data, LLHandle<LLWholeModelUploadObserver> observer_handle):
|
|
mModelData(model_data),
|
|
mObserverHandle(observer_handle)
|
|
{
|
|
}
|
|
|
|
~LLWholeModelUploadResponder()
|
|
{
|
|
}
|
|
|
|
virtual void httpCompleted(void)
|
|
{
|
|
LLSD cc = mContent;
|
|
if (gSavedSettings.getS32("MeshUploadFakeErrors")&2)
|
|
{
|
|
cc = llsd_from_file("fake_upload_error.xml");
|
|
}
|
|
|
|
dump_llsd_to_file(cc,make_dump_name("whole_model_upload_response_",dump_num));
|
|
|
|
LLWholeModelUploadObserver* observer = mObserverHandle.get();
|
|
|
|
// requested "mesh" asset type isn't actually the type
|
|
// of the resultant object, fix it up here.
|
|
if (isGoodStatus(mStatus) &&
|
|
cc["state"].asString() == "complete")
|
|
{
|
|
mModelData["asset_type"] = "object";
|
|
gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData,cc));
|
|
|
|
if (observer)
|
|
{
|
|
doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "upload failed" << LL_ENDL;
|
|
std::string model_name = mModelData["name"].asString();
|
|
log_upload_error(mStatus,cc,"upload",model_name);
|
|
|
|
if (observer)
|
|
{
|
|
doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*virtual*/ AIHTTPTimeoutPolicy const& getHTTPTimeoutPolicy(void) const { return wholeModelUploadResponder_timeout; }
|
|
/*virtual*/ char const* getName(void) const { return "LLWholeModelUploadResponder"; }
|
|
};
|
|
|
|
LLMeshRepoThread::LLMeshRepoThread()
|
|
: LLThread("mesh repo")
|
|
{
|
|
mMutex = new LLMutex();
|
|
mHeaderMutex = new LLMutex();
|
|
mSignal = new LLCondition();
|
|
mSkinInfoQMutex = new LLMutex();
|
|
mDecompositionQMutex = new LLMutex();
|
|
}
|
|
|
|
LLMeshRepoThread::~LLMeshRepoThread()
|
|
{
|
|
delete mMutex;
|
|
mMutex = NULL;
|
|
delete mHeaderMutex;
|
|
mHeaderMutex = NULL;
|
|
delete mSignal;
|
|
mSignal = NULL;
|
|
delete mSkinInfoQMutex;
|
|
mSkinInfoQMutex = NULL;
|
|
delete mDecompositionQMutex;
|
|
mDecompositionQMutex = NULL;
|
|
}
|
|
|
|
bool LLMeshRepoThread::HeaderRequest::fetch(U32& count)
|
|
{
|
|
return gMeshRepo.mThread->fetchMeshHeader(this->mMeshParams, count);
|
|
}
|
|
|
|
void LLMeshRepoThread::LODRequest::preFetch()
|
|
{
|
|
--LLMeshRepository::sLODProcessing;
|
|
}
|
|
|
|
bool LLMeshRepoThread::LODRequest::fetch(U32& count)
|
|
{
|
|
if (!gMeshRepo.mThread->fetchMeshLOD(this->mMeshParams, this->mLOD, count))
|
|
{
|
|
gMeshRepo.mThread->mMutex->lock();
|
|
++LLMeshRepository::sLODProcessing;
|
|
gMeshRepo.mThread->mMutex->unlock();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void LLMeshRepoThread::runQueue(std::deque<std::pair<std::shared_ptr<MeshRequest>, F32> >& query, U32& count, S32& active_requests)
|
|
{
|
|
std::queue<std::pair<std::shared_ptr<MeshRequest>, F32> > incomplete;
|
|
while (!query.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && active_requests < (S32)sMaxConcurrentRequests)
|
|
{
|
|
if (mMutex)
|
|
{
|
|
mMutex->lock();
|
|
auto req = query.front().first;
|
|
F32 delay = query.front().second;
|
|
query.pop_front();
|
|
req->preFetch();
|
|
mMutex->unlock();
|
|
F32 remainder = delay - req->mTimer.getElapsedTimeF32();
|
|
if (remainder > 0.f)
|
|
{
|
|
//LL_INFOS() << req->mMeshParams.getSculptID() << " skipped. " << remainder << "s remaining" << LL_ENDL;
|
|
incomplete.push(std::make_pair(req, delay));
|
|
}
|
|
else if (!req->fetch(count))//failed, resubmit
|
|
{
|
|
LL_INFOS() << req->mMeshParams.getSculptID() << " fetch failed outright. Delaying for " << (delay ? delay : 15) << "s" << LL_ENDL;
|
|
req->mTimer.reset();
|
|
incomplete.push(std::make_pair(req, delay ? delay : 15));
|
|
}
|
|
else {
|
|
//LL_INFOS() << req->mMeshParams.getSculptID() << " fetch request created. " << std::hex << &(req->mMeshParams) << std::dec << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
if (!incomplete.empty())
|
|
{
|
|
mMutex->lock();
|
|
while (!incomplete.empty())
|
|
{
|
|
query.push_back(incomplete.front());
|
|
incomplete.pop();
|
|
}
|
|
mMutex->unlock();
|
|
}
|
|
}
|
|
|
|
void LLMeshRepoThread::runSet(uuid_set_t& set, std::function<bool(const LLUUID& mesh_id)> fn)
|
|
{
|
|
for (auto iter = set.begin(); iter != set.end();)
|
|
{
|
|
if (fn(*iter))
|
|
iter = set.erase(iter);
|
|
else ++iter;
|
|
}
|
|
}
|
|
|
|
void LLMeshRepoThread::run()
|
|
{
|
|
LLCDResult res = LLConvexDecomposition::initThread();
|
|
if (res != LLCD_OK)
|
|
{
|
|
LL_WARNS() << "convex decomposition unable to be loaded" << LL_ENDL;
|
|
}
|
|
|
|
mSignal->lock();
|
|
while (!LLApp::isQuitting())
|
|
{
|
|
if (!LLApp::isQuitting())
|
|
{
|
|
static U32 count = 0;
|
|
|
|
static F32 last_hundred = gFrameTimeSeconds;
|
|
|
|
if (gFrameTimeSeconds - last_hundred > 1.f)
|
|
{ //a second has gone by, clear count
|
|
last_hundred = gFrameTimeSeconds;
|
|
count = 0;
|
|
}
|
|
|
|
// NOTE: throttling intentionally favors LOD requests over header requests
|
|
runQueue(mLODReqQ, count, sActiveLODRequests);
|
|
runQueue(mHeaderReqQ, count, sActiveHeaderRequests);
|
|
|
|
// Protected by mSignal
|
|
runSet(mSkinRequests, std::bind(&LLMeshRepoThread::fetchMeshSkinInfo, this, std::placeholders::_1));
|
|
runSet(mDecompositionRequests, std::bind(&LLMeshRepoThread::fetchMeshDecomposition, this, std::placeholders::_1));
|
|
runSet(mPhysicsShapeRequests, std::bind(&LLMeshRepoThread::fetchMeshPhysicsShape, this, std::placeholders::_1));
|
|
|
|
}
|
|
|
|
mSignal->unlock();
|
|
ms_sleep(1000 / 60);
|
|
mSignal->lock();
|
|
|
|
//mSignal->wait();
|
|
}
|
|
|
|
if (mSignal->isLocked())
|
|
{ //make sure to let go of the mutex associated with the given signal before shutting down
|
|
mSignal->unlock();
|
|
}
|
|
|
|
res = LLConvexDecomposition::quitThread();
|
|
if (res != LLCD_OK)
|
|
{
|
|
LL_WARNS() << "convex decomposition unable to be quit" << LL_ENDL;
|
|
}
|
|
|
|
}
|
|
|
|
void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id)
|
|
{ //protected by mSignal, no locking needed here
|
|
mSkinRequests.insert(mesh_id);
|
|
}
|
|
|
|
void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id)
|
|
{ //protected by mSignal, no locking needed here
|
|
mDecompositionRequests.insert(mesh_id);
|
|
}
|
|
|
|
void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id)
|
|
{ //protected by mSignal, no locking needed here
|
|
mPhysicsShapeRequests.insert(mesh_id);
|
|
}
|
|
|
|
void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
|
|
{
|
|
if (!LLAppViewer::isQuitting())
|
|
{
|
|
loadMeshLOD(mesh_params, lod);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
|
|
{ //could be called from any thread
|
|
std::unique_lock<LLMutex> header_lock(*mHeaderMutex);
|
|
bool exists = mMeshHeader.find(mesh_params.getSculptID()) != mMeshHeader.end();
|
|
header_lock.unlock();
|
|
LLMutexLock lock(mMutex);
|
|
if (exists)
|
|
{
|
|
//if we have the header, request LOD byte range
|
|
gMeshRepo.mThread->pushLODRequest(mesh_params, lod, 0.f);
|
|
LLMeshRepository::sLODProcessing++;
|
|
}
|
|
else
|
|
{
|
|
pending_lod_map::iterator pending = mPendingLOD.find(mesh_params);
|
|
|
|
if (pending != mPendingLOD.end())
|
|
{ //append this lod request to existing header request
|
|
pending->second.push_back(lod);
|
|
llassert(pending->second.size() <= LLModel::NUM_LODS);
|
|
}
|
|
else
|
|
{ //if no header request is pending, fetch header
|
|
gMeshRepo.mThread->pushHeaderRequest(mesh_params, 0.f);
|
|
mPendingLOD[mesh_params].push_back(lod);
|
|
}
|
|
}
|
|
}
|
|
|
|
//static
|
|
std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id)
|
|
{
|
|
std::string http_url;
|
|
|
|
if (gAgent.getRegion())
|
|
{
|
|
http_url = gMeshRepo.mGetMeshCapability;
|
|
}
|
|
|
|
if (!http_url.empty())
|
|
{
|
|
http_url += "/?mesh_id=";
|
|
http_url += mesh_id.asString().c_str();
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Current region does not have GetMesh capability! Cannot load " << mesh_id << ".mesh" << LL_ENDL;
|
|
}
|
|
|
|
return http_url;
|
|
}
|
|
|
|
bool LLMeshRepoThread::getMeshHeaderInfo(const LLUUID& mesh_id, const char* block_name, MeshHeaderInfo& info)
|
|
{ //protected by mMutex
|
|
|
|
if (!mHeaderMutex)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LLMutexLock lock(mHeaderMutex);
|
|
|
|
if (mMeshHeader.find(mesh_id) == mMeshHeader.end())
|
|
{ //we have no header info for this mesh, do nothing
|
|
return false;
|
|
}
|
|
|
|
if ((info.mHeaderSize = mMeshHeaderSize[mesh_id]) > 0)
|
|
{
|
|
const LLSD& header = mMeshHeader[mesh_id];
|
|
const LLSD& block = header[block_name];
|
|
info.mVersion = header["version"].asInteger();
|
|
info.mOffset = info.mHeaderSize + block["offset"].asInteger();
|
|
info.mSize = block["size"].asInteger();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLMeshRepoThread::loadInfoFromVFS(const LLUUID& mesh_id, MeshHeaderInfo& info, boost::function<bool(const LLUUID&, U8*, S32)> fn)
|
|
{
|
|
//check VFS for mesh skin info
|
|
LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH);
|
|
if (file.getSize() >= info.mOffset + info.mSize)
|
|
{
|
|
LLMeshRepository::sCacheBytesRead += info.mSize;
|
|
|
|
file.seek(info.mOffset);
|
|
U8* buffer = new U8[info.mSize];
|
|
file.read(buffer, info.mSize);
|
|
|
|
//make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written)
|
|
bool zero = true;
|
|
for (S32 i = 0; i < llmin(info.mSize, S32(1024)) && zero; ++i)
|
|
{
|
|
zero = buffer[i] > 0 ? false : true;
|
|
}
|
|
|
|
if (!zero)
|
|
{ //attempt to parse
|
|
if (fn(mesh_id, buffer, info.mSize))
|
|
{
|
|
delete[] buffer;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
delete[] buffer;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
|
|
{
|
|
MeshHeaderInfo info;
|
|
if (!getMeshHeaderInfo(mesh_id, "skin", info))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (info.mHeaderSize > 0 && info.mVersion <= MAX_MESH_VERSION && info.mOffset >= 0 && info.mSize > 0)
|
|
{
|
|
//check VFS for mesh skin info
|
|
if (loadInfoFromVFS(mesh_id, info, boost::bind(&LLMeshRepoThread::skinInfoReceived, this, _1, _2, _3 )))
|
|
return true;
|
|
|
|
//reading from VFS failed for whatever reason, fetch from sim
|
|
AIHTTPHeaders headers("Accept", "application/octet-stream");
|
|
|
|
std::string http_url = constructUrl(mesh_id);
|
|
if (!http_url.empty())
|
|
{
|
|
if (!LLHTTPClient::getByteRange(http_url, headers, info.mOffset, info.mSize,
|
|
new LLMeshSkinInfoResponder(mesh_id, info.mOffset, info.mSize)))
|
|
return false;
|
|
LLMeshRepository::sHTTPRequestCount++;
|
|
}
|
|
}
|
|
|
|
//early out was not hit, effectively fetched
|
|
return true;
|
|
}
|
|
|
|
bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
|
|
{
|
|
MeshHeaderInfo info;
|
|
if (!getMeshHeaderInfo(mesh_id, "physics_convex", info))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (info.mHeaderSize > 0 && info.mVersion <= MAX_MESH_VERSION && info.mOffset >= 0 && info.mSize > 0)
|
|
{
|
|
if (loadInfoFromVFS(mesh_id, info, boost::bind(&LLMeshRepoThread::decompositionReceived, this, _1, _2, _3 )))
|
|
return true;
|
|
|
|
//reading from VFS failed for whatever reason, fetch from sim
|
|
AIHTTPHeaders headers("Accept", "application/octet-stream");
|
|
|
|
std::string http_url = constructUrl(mesh_id);
|
|
if (!http_url.empty())
|
|
{
|
|
if (!LLHTTPClient::getByteRange(http_url, headers, info.mOffset, info.mSize,
|
|
new LLMeshDecompositionResponder(mesh_id, info.mOffset, info.mSize)))
|
|
return false;
|
|
LLMeshRepository::sHTTPRequestCount++;
|
|
}
|
|
}
|
|
|
|
//early out was not hit, effectively fetched
|
|
return true;
|
|
}
|
|
|
|
bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
|
|
{
|
|
MeshHeaderInfo info;
|
|
if (!getMeshHeaderInfo(mesh_id, "physics_mesh", info))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (info.mHeaderSize > 0)
|
|
{
|
|
if (info.mVersion <= MAX_MESH_VERSION && info.mOffset >= 0 && info.mSize > 0)
|
|
{
|
|
if (loadInfoFromVFS(mesh_id, info, boost::bind(&LLMeshRepoThread::physicsShapeReceived, this, _1, _2, _3 )))
|
|
return true;
|
|
|
|
//reading from VFS failed for whatever reason, fetch from sim
|
|
AIHTTPHeaders headers("Accept", "application/octet-stream");
|
|
|
|
std::string http_url = constructUrl(mesh_id);
|
|
if (!http_url.empty())
|
|
{
|
|
if (!LLHTTPClient::getByteRange(http_url, headers, info.mOffset, info.mSize,
|
|
new LLMeshPhysicsShapeResponder(mesh_id, info.mOffset, info.mSize)))
|
|
return false;
|
|
LLMeshRepository::sHTTPRequestCount++;
|
|
}
|
|
}
|
|
else
|
|
{ //no physics shape whatsoever, report back NULL
|
|
physicsShapeReceived(mesh_id, NULL, 0);
|
|
}
|
|
}
|
|
|
|
//early out was not hit, effectively fetched
|
|
return true;
|
|
}
|
|
|
|
//static
|
|
void LLMeshRepoThread::incActiveLODRequests()
|
|
{
|
|
LLMutexLock lock(gMeshRepo.mThread->mMutex);
|
|
++LLMeshRepoThread::sActiveLODRequests;
|
|
}
|
|
|
|
//static
|
|
void LLMeshRepoThread::decActiveLODRequests()
|
|
{
|
|
LLMutexLock lock(gMeshRepo.mThread->mMutex);
|
|
--LLMeshRepoThread::sActiveLODRequests;
|
|
}
|
|
|
|
//static
|
|
void LLMeshRepoThread::incActiveHeaderRequests()
|
|
{
|
|
LLMutexLock lock(gMeshRepo.mThread->mMutex);
|
|
++LLMeshRepoThread::sActiveHeaderRequests;
|
|
}
|
|
|
|
//static
|
|
void LLMeshRepoThread::decActiveHeaderRequests()
|
|
{
|
|
LLMutexLock lock(gMeshRepo.mThread->mMutex);
|
|
--LLMeshRepoThread::sActiveHeaderRequests;
|
|
}
|
|
|
|
//return false if failed to get header
|
|
bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count)
|
|
{
|
|
{
|
|
//look for mesh in asset in vfs
|
|
LLVFile file(gVFS, mesh_params.getSculptID(), LLAssetType::AT_MESH);
|
|
|
|
S32 size = file.getSize();
|
|
|
|
if (size > 0)
|
|
{ //NOTE -- if the header size is ever more than 4KB, this will break
|
|
U8 buffer[4096];
|
|
S32 bytes = llmin(size, 4096);
|
|
LLMeshRepository::sCacheBytesRead += bytes;
|
|
file.read(buffer, bytes);
|
|
if (headerReceived(mesh_params, buffer, bytes))
|
|
{ //did not do an HTTP request, return false
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//either cache entry doesn't exist or is corrupt, request header from simulator
|
|
bool retval = true;
|
|
AIHTTPHeaders headers("Accept", "application/octet-stream");
|
|
|
|
std::string http_url = constructUrl(mesh_params.getSculptID());
|
|
if (!http_url.empty())
|
|
{
|
|
//grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits
|
|
//within the first 4KB
|
|
//NOTE -- this will break of headers ever exceed 4KB
|
|
retval = LLHTTPClient::getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params));
|
|
if (retval)
|
|
{
|
|
LLMeshRepository::sHTTPRequestCount++;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
//return false if failed to get mesh lod.
|
|
bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count)
|
|
{
|
|
LLUUID mesh_id = mesh_params.getSculptID();
|
|
MeshHeaderInfo info;
|
|
|
|
if (!getMeshHeaderInfo(mesh_id, header_lod[lod].c_str(), info))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (info.mHeaderSize > 0)
|
|
{
|
|
if(info.mVersion <= MAX_MESH_VERSION && info.mOffset >= 0 && info.mSize > 0)
|
|
{
|
|
if (loadInfoFromVFS(mesh_id, info, boost::bind(&LLMeshRepoThread::lodReceived, this, mesh_params, lod, _2, _3 )))
|
|
return true;
|
|
|
|
//reading from VFS failed for whatever reason, fetch from sim
|
|
AIHTTPHeaders headers("Accept", "application/octet-stream");
|
|
|
|
std::string http_url = constructUrl(mesh_id);
|
|
if (!http_url.empty())
|
|
{
|
|
count++;
|
|
if (!LLHTTPClient::getByteRange(constructUrl(mesh_id), headers, info.mOffset, info.mSize,
|
|
new LLMeshLODResponder(mesh_params, lod, info.mOffset, info.mSize)))
|
|
return false;
|
|
LLMeshRepository::sHTTPRequestCount++;
|
|
|
|
}
|
|
else
|
|
{
|
|
mUnavailableQ.push(LODRequest(mesh_params, lod));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mUnavailableQ.push(LODRequest(mesh_params, lod));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size)
|
|
{
|
|
LLSD header;
|
|
|
|
U32 header_size = 0;
|
|
if (data_size > 0)
|
|
{
|
|
std::string res_str((char*) data, data_size);
|
|
|
|
std::string deprecated_header("<? LLSD/Binary ?>");
|
|
|
|
if (res_str.substr(0, deprecated_header.size()) == deprecated_header)
|
|
{
|
|
res_str = res_str.substr(deprecated_header.size()+1, data_size);
|
|
header_size = deprecated_header.size()+1;
|
|
}
|
|
data_size = res_str.size();
|
|
|
|
std::istringstream stream(res_str);
|
|
|
|
if (!LLSDSerialize::fromBinary(header, stream, data_size))
|
|
{
|
|
LL_WARNS() << "Mesh header parse error. Not a valid mesh asset!" << LL_ENDL;
|
|
return false;
|
|
}
|
|
|
|
header_size += stream.tellg();
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS()
|
|
<< "Marking header as non-existent, will not retry." << LL_ENDL;
|
|
header["404"] = 1;
|
|
}
|
|
|
|
{
|
|
LLUUID mesh_id = mesh_params.getSculptID();
|
|
|
|
{
|
|
LLMutexLock lock(mHeaderMutex);
|
|
mMeshHeaderSize[mesh_id] = header_size;
|
|
mMeshHeader[mesh_id] = header;
|
|
}
|
|
|
|
LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time.
|
|
|
|
//check for pending requests
|
|
pending_lod_map::iterator iter = mPendingLOD.find(mesh_params);
|
|
if (iter != mPendingLOD.end())
|
|
{
|
|
for (U32 i = 0; i < iter->second.size(); ++i)
|
|
{
|
|
LLMeshRepository::sLODProcessing++;
|
|
gMeshRepo.mThread->pushLODRequest(mesh_params, iter->second[i], 0.f);
|
|
}
|
|
mPendingLOD.erase(iter);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size)
|
|
{
|
|
AIStateMachine::StateTimer timer("lodReceived");
|
|
LLPointer<LLVolume> volume = new LLVolume(mesh_params, LLVolumeLODGroup::getVolumeScaleFromDetail(lod));
|
|
std::string mesh_string((char*) data, data_size);
|
|
std::istringstream stream(mesh_string);
|
|
|
|
AIStateMachine::StateTimer timer2("unpackVolumeFaces");
|
|
if (volume->unpackVolumeFaces(stream, data_size))
|
|
{
|
|
AIStateMachine::StateTimer timer("getNumFaces");
|
|
if (volume->getNumFaces() > 0)
|
|
{
|
|
AIStateMachine::StateTimer timer("LoadedMesh");
|
|
LoadedMesh mesh(volume, mesh_params, lod);
|
|
{
|
|
AIStateMachine::StateTimer timer("LLMutexLock");
|
|
LLMutexLock lock(mMutex);
|
|
mLoadedQ.push(mesh);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size)
|
|
{
|
|
LLSD skin;
|
|
|
|
if (data_size > 0)
|
|
{
|
|
std::string res_str((char*) data, data_size);
|
|
|
|
std::istringstream stream(res_str);
|
|
|
|
if (!unzip_llsd(skin, stream, data_size))
|
|
{
|
|
LL_WARNS() << "Mesh skin info parse error. Not a valid mesh asset!" << LL_ENDL;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
{
|
|
LLMeshSkinInfo info(skin);
|
|
info.mMeshID = mesh_id;
|
|
|
|
//LL_INFOS() <<"info pelvis offset"<<info.mPelvisOffset<<LL_ENDL;
|
|
mSkinInfoQMutex->lock();
|
|
mSkinInfoQ.push(info);
|
|
mSkinInfoQMutex->unlock();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size)
|
|
{
|
|
LLSD decomp;
|
|
|
|
if (data_size > 0)
|
|
{
|
|
std::string res_str((char*) data, data_size);
|
|
|
|
std::istringstream stream(res_str);
|
|
|
|
if (!unzip_llsd(decomp, stream, data_size))
|
|
{
|
|
LL_WARNS() << "Mesh decomposition parse error. Not a valid mesh asset!" << LL_ENDL;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
{
|
|
LLModel::Decomposition* d = new LLModel::Decomposition(decomp);
|
|
d->mMeshID = mesh_id;
|
|
mDecompositionQMutex->lock();
|
|
mDecompositionQ.push(d);
|
|
mDecompositionQMutex->unlock();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size)
|
|
{
|
|
LLSD physics_shape;
|
|
|
|
LLModel::Decomposition* d = new LLModel::Decomposition();
|
|
d->mMeshID = mesh_id;
|
|
|
|
if (data == NULL)
|
|
{ //no data, no physics shape exists
|
|
d->mPhysicsShapeMesh.clear();
|
|
}
|
|
else
|
|
{
|
|
LLVolumeParams volume_params;
|
|
volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
|
|
volume_params.setSculptID(mesh_id, LL_SCULPT_TYPE_MESH);
|
|
LLPointer<LLVolume> volume = new LLVolume(volume_params,0);
|
|
std::string mesh_string((char*) data, data_size);
|
|
std::istringstream stream(mesh_string);
|
|
|
|
if (volume->unpackVolumeFaces(stream, data_size))
|
|
{
|
|
//load volume faces into decomposition buffer
|
|
S32 vertex_count = 0;
|
|
S32 index_count = 0;
|
|
|
|
for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i)
|
|
{
|
|
const LLVolumeFace& face = volume->getVolumeFace(i);
|
|
vertex_count += face.mNumVertices;
|
|
index_count += face.mNumIndices;
|
|
}
|
|
|
|
d->mPhysicsShapeMesh.clear();
|
|
|
|
std::vector<LLVector3>& pos = d->mPhysicsShapeMesh.mPositions;
|
|
std::vector<LLVector3>& norm = d->mPhysicsShapeMesh.mNormals;
|
|
|
|
for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i)
|
|
{
|
|
const LLVolumeFace& face = volume->getVolumeFace(i);
|
|
|
|
for (S32 i = 0; i < face.mNumIndices; ++i)
|
|
{
|
|
U16 idx = face.mIndices[i];
|
|
|
|
pos.push_back(LLVector3(face.mPositions[idx].getF32ptr()));
|
|
norm.push_back(LLVector3(face.mNormals[idx].getF32ptr()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mDecompositionQMutex->lock();
|
|
mDecompositionQ.push(d);
|
|
mDecompositionQMutex->unlock();
|
|
return true;
|
|
}
|
|
|
|
void LLMeshUploadThread::init(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures,
|
|
bool upload_skin, bool upload_joints, bool do_upload,
|
|
LLHandle<LLWholeModelFeeObserver> const& fee_observer, LLHandle<LLWholeModelUploadObserver> const& upload_observer)
|
|
{
|
|
mDoUpload = do_upload;
|
|
mFeeObserverHandle = fee_observer;
|
|
mUploadObserverHandle = upload_observer;
|
|
mInstanceList = data;
|
|
mUploadTextures = upload_textures;
|
|
mUploadSkin = upload_skin;
|
|
mUploadJoints = upload_joints;
|
|
mOrigin = gAgent.getPositionAgent();
|
|
mHost = gAgent.getRegionHost();
|
|
|
|
mWholeModelFeeCapability = gAgent.getRegion()->getCapability("NewFileAgentInventory");
|
|
|
|
mOrigin += gAgent.getAtAxis() * scale.magVec();
|
|
|
|
mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut") ;
|
|
}
|
|
|
|
LLMeshUploadThread::~LLMeshUploadThread()
|
|
{
|
|
|
|
}
|
|
|
|
LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread)
|
|
{
|
|
mStage = "single_hull";
|
|
mModel = mdl;
|
|
mDecompID = &mdl->mDecompID;
|
|
mBaseModel = base_model;
|
|
mThread = thread;
|
|
|
|
//copy out positions and indices
|
|
assignData(mdl) ;
|
|
|
|
mThread->mFinalDecomp = this;
|
|
mThread->mPhysicsComplete = false;
|
|
}
|
|
|
|
void LLMeshUploadThread::DecompRequest::completed()
|
|
{
|
|
if (mThread->mFinalDecomp == this)
|
|
{
|
|
mThread->mPhysicsComplete = true;
|
|
}
|
|
|
|
llassert(mHull.size() == 1);
|
|
|
|
mThread->mHullMap[mBaseModel] = mHull[0];
|
|
}
|
|
|
|
//called in the main thread.
|
|
void LLMeshUploadThread::preStart()
|
|
{
|
|
//build map of LLModel refs to instances for callbacks
|
|
for (instance_list::iterator iter = mInstanceList.begin(); iter != mInstanceList.end(); ++iter)
|
|
{
|
|
mInstance[iter->mModel].push_back(*iter);
|
|
}
|
|
}
|
|
|
|
AIMeshUpload::AIMeshUpload(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, std::string const& upload_url, bool do_upload,
|
|
LLHandle<LLWholeModelFeeObserver> const& fee_observer, LLHandle<LLWholeModelUploadObserver> const& upload_observer) :
|
|
#ifdef CWDEBUG
|
|
AIStateMachine(false),
|
|
#endif
|
|
mMeshUpload(new AIStateMachineThread<LLMeshUploadThread>(CWD_ONLY(false))), mWholeModelUploadURL(upload_url)
|
|
{
|
|
mMeshUpload->thread_impl().init(data, scale, upload_textures, upload_skin, upload_joints, do_upload, fee_observer, upload_observer);
|
|
}
|
|
|
|
char const* AIMeshUpload::state_str_impl(state_type run_state) const
|
|
{
|
|
switch (run_state)
|
|
{
|
|
AI_CASE_RETURN(AIMeshUpload_start);
|
|
AI_CASE_RETURN(AIMeshUpload_threadFinished);
|
|
AI_CASE_RETURN(AIMeshUpload_responderFinished);
|
|
}
|
|
return "UNKNOWN STATE";
|
|
}
|
|
|
|
void AIMeshUpload::initialize_impl()
|
|
{
|
|
mMeshUpload->thread_impl().preStart();
|
|
set_state(AIMeshUpload_start);
|
|
}
|
|
|
|
void AIMeshUpload::multiplex_impl(state_type run_state)
|
|
{
|
|
switch (run_state)
|
|
{
|
|
case AIMeshUpload_start:
|
|
mMeshUpload->run(this, AIMeshUpload_threadFinished);
|
|
idle(); // Wait till the thread finished.
|
|
break;
|
|
case AIMeshUpload_threadFinished:
|
|
mMeshUpload->thread_impl().postRequest(mWholeModelUploadURL, this);
|
|
idle(); // Wait till the responder finished.
|
|
break;
|
|
case AIMeshUpload_responderFinished:
|
|
finish();
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool LLMeshUploadThread::run()
|
|
{
|
|
generateHulls();
|
|
wholeModelToLLSD(mModelData, mDoUpload);
|
|
if (!mDoUpload)
|
|
{
|
|
++dump_num;
|
|
dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num));
|
|
}
|
|
else
|
|
{
|
|
mBody = mModelData["asset_resources"];
|
|
dump_llsd_to_file(mBody, make_dump_name("whole_model_body_", dump_num));
|
|
}
|
|
return true; // true = finish, false = abort.
|
|
}
|
|
|
|
void LLMeshUploadThread::postRequest(std::string& whole_model_upload_url, AIMeshUpload* state_machine)
|
|
{
|
|
if (mDoUpload)
|
|
{
|
|
LLHTTPClient::post(whole_model_upload_url, mBody,
|
|
new LLWholeModelUploadResponder(mModelData, mUploadObserverHandle)/*,*/
|
|
DEBUG_CURLIO_PARAM(debug_off), keep_alive, state_machine, AIMeshUpload_responderFinished);
|
|
}
|
|
else
|
|
{
|
|
LLHTTPClient::post(mWholeModelFeeCapability, mModelData,
|
|
new LLWholeModelFeeResponder(mModelData, mFeeObserverHandle, whole_model_upload_url)/*,*/
|
|
DEBUG_CURLIO_PARAM(debug_on), keep_alive, state_machine, AIMeshUpload_responderFinished);
|
|
}
|
|
}
|
|
|
|
void dump_llsd_to_file(const LLSD& content, std::string filename)
|
|
{
|
|
if (gSavedSettings.getBOOL("MeshUploadLogXML"))
|
|
{
|
|
std::ofstream of(filename.c_str());
|
|
LLSDSerialize::toPrettyXML(content,of);
|
|
}
|
|
}
|
|
|
|
LLSD llsd_from_file(std::string filename)
|
|
{
|
|
std::ifstream ifs(filename.c_str());
|
|
LLSD result;
|
|
LLSDSerialize::fromXML(result,ifs);
|
|
return result;
|
|
}
|
|
|
|
void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures)
|
|
{
|
|
LLSD result;
|
|
|
|
LLSD res;
|
|
result["folder_id"] = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT);
|
|
result["texture_folder_id"] = gInventory.findCategoryUUIDForType(LLFolderType::FT_TEXTURE);
|
|
result["asset_type"] = "mesh";
|
|
result["inventory_type"] = "object";
|
|
result["description"] = "(No Description)";
|
|
result["next_owner_mask"] = LLSD::Integer(LLFloaterPerms::getNextOwnerPerms("Uploads"));
|
|
result["group_mask"] = LLSD::Integer(LLFloaterPerms::getGroupPerms("Uploads"));
|
|
result["everyone_mask"] = LLSD::Integer(LLFloaterPerms::getEveryonePerms("Uploads"));
|
|
|
|
res["mesh_list"] = LLSD::emptyArray();
|
|
res["texture_list"] = LLSD::emptyArray();
|
|
res["instance_list"] = LLSD::emptyArray();
|
|
S32 mesh_num = 0;
|
|
S32 texture_num = 0;
|
|
|
|
std::set<LLViewerTexture* > textures;
|
|
std::map<LLViewerTexture*,S32> texture_index;
|
|
|
|
std::map<LLModel*,S32> mesh_index;
|
|
std::string model_name;
|
|
std::string model_metric;
|
|
|
|
S32 instance_num = 0;
|
|
|
|
for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter)
|
|
{
|
|
LLMeshUploadData data;
|
|
data.mBaseModel = iter->first;
|
|
|
|
if (data.mBaseModel->mSubmodelID)
|
|
{
|
|
// These are handled below to insure correct parenting order on creation
|
|
// due to map walking being based on model address (aka random)
|
|
continue;
|
|
}
|
|
LLModelInstance& first_instance = *(iter->second.begin());
|
|
for (S32 i = 0; i < 5; i++)
|
|
{
|
|
data.mModel[i] = first_instance.mLOD[i];
|
|
}
|
|
|
|
if (mesh_index.find(data.mBaseModel) == mesh_index.end())
|
|
{
|
|
// Have not seen this model before - create a new mesh_list entry for it.
|
|
if (model_name.empty())
|
|
{
|
|
model_name = data.mBaseModel->getName();
|
|
}
|
|
|
|
if (model_metric.empty())
|
|
{
|
|
model_metric = data.mBaseModel->getMetric();
|
|
}
|
|
|
|
std::stringstream ostr;
|
|
|
|
LLModel::Decomposition& decomp =
|
|
data.mModel[LLModel::LOD_PHYSICS].notNull() ?
|
|
data.mModel[LLModel::LOD_PHYSICS]->mPhysics :
|
|
data.mBaseModel->mPhysics;
|
|
|
|
decomp.mBaseHull = mHullMap[data.mBaseModel];
|
|
|
|
LLSD mesh_header = LLModel::writeModel(
|
|
ostr,
|
|
data.mModel[LLModel::LOD_PHYSICS],
|
|
data.mModel[LLModel::LOD_HIGH],
|
|
data.mModel[LLModel::LOD_MEDIUM],
|
|
data.mModel[LLModel::LOD_LOW],
|
|
data.mModel[LLModel::LOD_IMPOSTOR],
|
|
decomp,
|
|
mUploadSkin,
|
|
mUploadJoints,
|
|
FALSE,
|
|
FALSE,
|
|
data.mBaseModel->mSubmodelID);
|
|
|
|
data.mAssetData = ostr.str();
|
|
std::string str = ostr.str();
|
|
|
|
res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end());
|
|
mesh_index[data.mBaseModel] = mesh_num;
|
|
mesh_num++;
|
|
}
|
|
|
|
// For all instances that use this model
|
|
for (instance_list::iterator instance_iter = iter->second.begin();
|
|
instance_iter != iter->second.end();
|
|
++instance_iter)
|
|
{
|
|
|
|
LLModelInstance& instance = *instance_iter;
|
|
|
|
LLSD instance_entry;
|
|
|
|
for (S32 i = 0; i < 5; i++)
|
|
{
|
|
data.mModel[i] = instance.mLOD[i];
|
|
}
|
|
|
|
LLVector3 pos, scale;
|
|
LLQuaternion rot;
|
|
LLMatrix4 transformation = instance.mTransform;
|
|
decomposeMeshMatrix(transformation,pos,rot,scale);
|
|
instance_entry["position"] = ll_sd_from_vector3(pos);
|
|
instance_entry["rotation"] = ll_sd_from_quaternion(rot);
|
|
instance_entry["scale"] = ll_sd_from_vector3(scale);
|
|
|
|
instance_entry["material"] = LL_MCODE_WOOD;
|
|
instance_entry["physics_shape_type"] = data.mModel[LLModel::LOD_PHYSICS].notNull() ? (U8)(LLViewerObject::PHYSICS_SHAPE_PRIM) : (U8)(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL);
|
|
instance_entry["mesh"] = mesh_index[data.mBaseModel];
|
|
instance_entry["mesh_name"] = instance.mLabel;
|
|
|
|
instance_entry["face_list"] = LLSD::emptyArray();
|
|
|
|
// We want to be able to allow more than 8 materials...
|
|
//
|
|
S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ;
|
|
|
|
for (S32 face_num = 0; face_num < end; face_num++)
|
|
{
|
|
LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]];
|
|
LLSD face_entry = LLSD::emptyMap();
|
|
|
|
LLViewerFetchedTexture *texture = NULL;
|
|
|
|
if (material.mDiffuseMapFilename.size())
|
|
{
|
|
texture = FindViewerTexture(material);
|
|
}
|
|
|
|
if ((texture != NULL) &&
|
|
(textures.find(texture) == textures.end()))
|
|
{
|
|
textures.insert(texture);
|
|
}
|
|
|
|
std::stringstream texture_str;
|
|
if (texture != NULL && include_textures && mUploadTextures)
|
|
{
|
|
if(texture->hasSavedRawImage())
|
|
{
|
|
LLPointer<LLImageJ2C> upload_file =
|
|
LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage());
|
|
|
|
if (!upload_file.isNull() && upload_file->getDataSize())
|
|
{
|
|
texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (texture != NULL &&
|
|
mUploadTextures &&
|
|
texture_index.find(texture) == texture_index.end())
|
|
{
|
|
texture_index[texture] = texture_num;
|
|
std::string str = texture_str.str();
|
|
res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end());
|
|
texture_num++;
|
|
}
|
|
|
|
// Subset of TextureEntry fields.
|
|
if (texture != NULL && mUploadTextures)
|
|
{
|
|
face_entry["image"] = texture_index[texture];
|
|
face_entry["scales"] = 1.0;
|
|
face_entry["scalet"] = 1.0;
|
|
face_entry["offsets"] = 0.0;
|
|
face_entry["offsett"] = 0.0;
|
|
face_entry["imagerot"] = 0.0;
|
|
}
|
|
face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor);
|
|
face_entry["fullbright"] = material.mFullbright;
|
|
instance_entry["face_list"][face_num] = face_entry;
|
|
}
|
|
|
|
res["instance_list"][instance_num] = instance_entry;
|
|
instance_num++;
|
|
}
|
|
}
|
|
|
|
for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter)
|
|
{
|
|
LLMeshUploadData data;
|
|
data.mBaseModel = iter->first;
|
|
|
|
if (!data.mBaseModel->mSubmodelID)
|
|
{
|
|
// These were handled above already...
|
|
//
|
|
continue;
|
|
}
|
|
|
|
LLModelInstance& first_instance = *(iter->second.begin());
|
|
for (S32 i = 0; i < 5; i++)
|
|
{
|
|
data.mModel[i] = first_instance.mLOD[i];
|
|
}
|
|
|
|
if (mesh_index.find(data.mBaseModel) == mesh_index.end())
|
|
{
|
|
// Have not seen this model before - create a new mesh_list entry for it.
|
|
if (model_name.empty())
|
|
{
|
|
model_name = data.mBaseModel->getName();
|
|
}
|
|
|
|
if (model_metric.empty())
|
|
{
|
|
model_metric = data.mBaseModel->getMetric();
|
|
}
|
|
|
|
std::stringstream ostr;
|
|
|
|
LLModel::Decomposition& decomp =
|
|
data.mModel[LLModel::LOD_PHYSICS].notNull() ?
|
|
data.mModel[LLModel::LOD_PHYSICS]->mPhysics :
|
|
data.mBaseModel->mPhysics;
|
|
|
|
decomp.mBaseHull = mHullMap[data.mBaseModel];
|
|
|
|
LLSD mesh_header = LLModel::writeModel(
|
|
ostr,
|
|
data.mModel[LLModel::LOD_PHYSICS],
|
|
data.mModel[LLModel::LOD_HIGH],
|
|
data.mModel[LLModel::LOD_MEDIUM],
|
|
data.mModel[LLModel::LOD_LOW],
|
|
data.mModel[LLModel::LOD_IMPOSTOR],
|
|
decomp,
|
|
mUploadSkin,
|
|
mUploadJoints,
|
|
FALSE,
|
|
FALSE,
|
|
data.mBaseModel->mSubmodelID);
|
|
|
|
data.mAssetData = ostr.str();
|
|
std::string str = ostr.str();
|
|
|
|
res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end());
|
|
mesh_index[data.mBaseModel] = mesh_num;
|
|
mesh_num++;
|
|
}
|
|
|
|
// For all instances that use this model
|
|
for (instance_list::iterator instance_iter = iter->second.begin();
|
|
instance_iter != iter->second.end();
|
|
++instance_iter)
|
|
{
|
|
|
|
LLModelInstance& instance = *instance_iter;
|
|
|
|
LLSD instance_entry;
|
|
|
|
for (S32 i = 0; i < 5; i++)
|
|
{
|
|
data.mModel[i] = instance.mLOD[i];
|
|
}
|
|
|
|
LLVector3 pos, scale;
|
|
LLQuaternion rot;
|
|
LLMatrix4 transformation = instance.mTransform;
|
|
decomposeMeshMatrix(transformation,pos,rot,scale);
|
|
instance_entry["position"] = ll_sd_from_vector3(pos);
|
|
instance_entry["rotation"] = ll_sd_from_quaternion(rot);
|
|
instance_entry["scale"] = ll_sd_from_vector3(scale);
|
|
|
|
instance_entry["material"] = LL_MCODE_WOOD;
|
|
instance_entry["physics_shape_type"] = (U8)(LLViewerObject::PHYSICS_SHAPE_NONE);
|
|
instance_entry["mesh"] = mesh_index[data.mBaseModel];
|
|
|
|
instance_entry["face_list"] = LLSD::emptyArray();
|
|
|
|
// We want to be able to allow more than 8 materials...
|
|
//
|
|
S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ;
|
|
|
|
for (S32 face_num = 0; face_num < end; face_num++)
|
|
{
|
|
LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]];
|
|
LLSD face_entry = LLSD::emptyMap();
|
|
|
|
LLViewerFetchedTexture *texture = NULL;
|
|
|
|
if (material.mDiffuseMapFilename.size())
|
|
{
|
|
texture = FindViewerTexture(material);
|
|
}
|
|
|
|
if ((texture != NULL) &&
|
|
(textures.find(texture) == textures.end()))
|
|
{
|
|
textures.insert(texture);
|
|
}
|
|
|
|
std::stringstream texture_str;
|
|
if (texture != NULL && include_textures && mUploadTextures)
|
|
{
|
|
if(texture->hasSavedRawImage())
|
|
{
|
|
LLPointer<LLImageJ2C> upload_file =
|
|
LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage());
|
|
|
|
if (!upload_file.isNull() && upload_file->getDataSize())
|
|
{
|
|
texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (texture != NULL &&
|
|
mUploadTextures &&
|
|
texture_index.find(texture) == texture_index.end())
|
|
{
|
|
texture_index[texture] = texture_num;
|
|
std::string str = texture_str.str();
|
|
res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end());
|
|
texture_num++;
|
|
}
|
|
|
|
// Subset of TextureEntry fields.
|
|
if (texture != NULL && mUploadTextures)
|
|
{
|
|
face_entry["image"] = texture_index[texture];
|
|
face_entry["scales"] = 1.0;
|
|
face_entry["scalet"] = 1.0;
|
|
face_entry["offsets"] = 0.0;
|
|
face_entry["offsett"] = 0.0;
|
|
face_entry["imagerot"] = 0.0;
|
|
}
|
|
face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor);
|
|
face_entry["fullbright"] = material.mFullbright;
|
|
instance_entry["face_list"][face_num] = face_entry;
|
|
}
|
|
|
|
res["instance_list"][instance_num] = instance_entry;
|
|
instance_num++;
|
|
}
|
|
}
|
|
|
|
if (model_name.empty()) model_name = "mesh model";
|
|
result["name"] = model_name;
|
|
if (model_metric.empty()) model_metric = "MUT_Unspecified";
|
|
res["metric"] = model_metric;
|
|
result["asset_resources"] = res;
|
|
dump_llsd_to_file(result,make_dump_name("whole_model_",dump_num));
|
|
|
|
dest = result;
|
|
}
|
|
|
|
void LLMeshUploadThread::generateHulls()
|
|
{
|
|
bool has_valid_requests = false ;
|
|
|
|
for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter)
|
|
{
|
|
LLMeshUploadData data;
|
|
data.mBaseModel = iter->first;
|
|
|
|
LLModelInstance& instance = *(iter->second.begin());
|
|
|
|
for (S32 i = 0; i < 5; i++)
|
|
{
|
|
data.mModel[i] = instance.mLOD[i];
|
|
}
|
|
|
|
//queue up models for hull generation
|
|
LLModel* physics = NULL;
|
|
|
|
if (data.mModel[LLModel::LOD_PHYSICS].notNull())
|
|
{
|
|
physics = data.mModel[LLModel::LOD_PHYSICS];
|
|
}
|
|
else if (data.mModel[LLModel::LOD_LOW].notNull())
|
|
{
|
|
physics = data.mModel[LLModel::LOD_LOW];
|
|
}
|
|
else if (data.mModel[LLModel::LOD_MEDIUM].notNull())
|
|
{
|
|
physics = data.mModel[LLModel::LOD_MEDIUM];
|
|
}
|
|
else
|
|
{
|
|
physics = data.mModel[LLModel::LOD_HIGH];
|
|
}
|
|
|
|
llassert(physics != NULL);
|
|
|
|
DecompRequest* request = new DecompRequest(physics, data.mBaseModel, this);
|
|
if(request->isValid())
|
|
{
|
|
gMeshRepo.mDecompThread->submitRequest(request);
|
|
has_valid_requests = true ;
|
|
}
|
|
}
|
|
|
|
if(has_valid_requests)
|
|
{
|
|
while (!mPhysicsComplete)
|
|
{
|
|
apr_sleep(100);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LLMeshRepoThread::notifyLoadedMeshes()
|
|
{
|
|
if (!mMutex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (!mLoadedQ.empty())
|
|
{
|
|
mMutex->lock();
|
|
LoadedMesh mesh = mLoadedQ.front();
|
|
mLoadedQ.pop();
|
|
mMutex->unlock();
|
|
|
|
if (mesh.mVolume && mesh.mVolume->getNumVolumeFaces() > 0)
|
|
{
|
|
gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume);
|
|
}
|
|
else
|
|
{
|
|
gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams,
|
|
LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail()));
|
|
}
|
|
}
|
|
|
|
while (!mUnavailableQ.empty())
|
|
{
|
|
mMutex->lock();
|
|
LODRequest req = mUnavailableQ.front();
|
|
mUnavailableQ.pop();
|
|
mMutex->unlock();
|
|
|
|
gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD);
|
|
}
|
|
|
|
while (!mSkinInfoQ.empty())
|
|
{
|
|
mSkinInfoQMutex->lock();
|
|
auto req = mSkinInfoQ.front();
|
|
mSkinInfoQ.pop();
|
|
mSkinInfoQMutex->unlock();
|
|
|
|
gMeshRepo.notifySkinInfoReceived(req);
|
|
}
|
|
|
|
while (!mDecompositionQ.empty())
|
|
{
|
|
mDecompositionQMutex->lock();
|
|
auto req = mDecompositionQ.front();
|
|
mDecompositionQ.pop();
|
|
mDecompositionQMutex->unlock();
|
|
|
|
gMeshRepo.notifyDecompositionReceived(req);
|
|
}
|
|
}
|
|
|
|
S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
|
|
{ //only ever called from main thread
|
|
LLMutexLock lock(mHeaderMutex);
|
|
mesh_header_map::iterator iter = mMeshHeader.find(mesh_params.getSculptID());
|
|
|
|
if (iter != mMeshHeader.end())
|
|
{
|
|
LLSD& header = iter->second;
|
|
|
|
return LLMeshRepository::getActualMeshLOD(header, lod);
|
|
}
|
|
|
|
return lod;
|
|
}
|
|
|
|
//static
|
|
S32 LLMeshRepository::getActualMeshLOD(LLSD& header, S32 lod)
|
|
{
|
|
lod = llclamp(lod, 0, 3);
|
|
|
|
S32 version = header["version"].asInteger();
|
|
|
|
if (header.has("404") || version > MAX_MESH_VERSION)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (header[header_lod[lod]]["size"].asInteger() > 0)
|
|
{
|
|
return lod;
|
|
}
|
|
|
|
//search down to find the next available lower lod
|
|
for (S32 i = lod-1; i >= 0; --i)
|
|
{
|
|
if (header[header_lod[i]]["size"].asInteger() > 0)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
//search up to find then ext available higher lod
|
|
for (S32 i = lod+1; i < 4; ++i)
|
|
{
|
|
if (header[header_lod[i]]["size"].asInteger() > 0)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
//header exists and no good lod found, treat as 404
|
|
header["404"] = 1;
|
|
return -1;
|
|
}
|
|
|
|
void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header)
|
|
{
|
|
mThread->mMeshHeader[data.mUUID] = header;
|
|
|
|
// we cache the mesh for default parameters
|
|
LLVolumeParams volume_params;
|
|
volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
|
|
volume_params.setSculptID(data.mUUID, LL_SCULPT_TYPE_MESH);
|
|
|
|
for (U32 i = 0; i < 4; i++)
|
|
{
|
|
if (data.mModel[i].notNull())
|
|
{
|
|
LLPointer<LLVolume> volume = new LLVolume(volume_params, LLVolumeLODGroup::getVolumeScaleFromDetail(i));
|
|
volume->copyVolumeFaces(data.mModel[i]);
|
|
volume->setMeshAssetLoaded(TRUE);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void LLMeshLODResponder::retry()
|
|
{
|
|
AIStateMachine::StateTimer timer("loadMeshLOD");
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD);
|
|
}
|
|
|
|
void LLMeshLODResponder::completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer)
|
|
{
|
|
mProcessed = true;
|
|
|
|
// thread could have already be destroyed during logout
|
|
if( !gMeshRepo.mThread )
|
|
{
|
|
return;
|
|
}
|
|
|
|
S32 data_size = buffer->countAfter(channels.in(), NULL);
|
|
|
|
if (mStatus < 200 || mStatus >= 400)
|
|
{
|
|
LL_WARNS() << mStatus << ": " << mReason << LL_ENDL;
|
|
}
|
|
|
|
if (data_size < (S32)mRequestedBytes)
|
|
{
|
|
if (is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE)
|
|
{ //timeout or service unavailable, try again
|
|
LL_WARNS() << "Timeout or service unavailable, retrying." << LL_ENDL;
|
|
retry();
|
|
}
|
|
else
|
|
{
|
|
llassert(is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint
|
|
LL_WARNS() << "Unhandled status " << mStatus << LL_ENDL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
LLMeshRepository::sBytesReceived += mRequestedBytes;
|
|
|
|
U8* data = NULL;
|
|
|
|
if (data_size > 0)
|
|
{
|
|
AIStateMachine::StateTimer timer("readAfter");
|
|
data = new U8[data_size];
|
|
buffer->readAfter(channels.in(), NULL, data, data_size);
|
|
}
|
|
|
|
if (gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size))
|
|
{
|
|
AIStateMachine::StateTimer timer("FileOpen");
|
|
//good fetch from sim, write to VFS for caching
|
|
LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE);
|
|
|
|
S32 offset = mOffset;
|
|
S32 size = mRequestedBytes;
|
|
|
|
if (file.getSize() >= offset+size)
|
|
{
|
|
AIStateMachine::StateTimer timer("WriteData");
|
|
file.seek(offset);
|
|
file.write(data, size);
|
|
LLMeshRepository::sCacheBytesWritten += size;
|
|
}
|
|
}
|
|
|
|
delete [] data;
|
|
}
|
|
|
|
void LLMeshSkinInfoResponder::retry()
|
|
{
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
gMeshRepo.mThread->loadMeshSkinInfo(mMeshID);
|
|
}
|
|
|
|
void LLMeshSkinInfoResponder::completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer)
|
|
{
|
|
mProcessed = true;
|
|
|
|
// thread could have already be destroyed during logout
|
|
if( !gMeshRepo.mThread )
|
|
{
|
|
return;
|
|
}
|
|
|
|
S32 data_size = buffer->countAfter(channels.in(), NULL);
|
|
|
|
if (mStatus < 200 || mStatus >= 400)
|
|
{
|
|
LL_WARNS() << mStatus << ": " << mReason << LL_ENDL;
|
|
}
|
|
|
|
if (data_size < (S32)mRequestedBytes)
|
|
{
|
|
if (is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE)
|
|
{ //timeout or service unavailable, try again
|
|
LL_WARNS() << "Timeout or service unavailable, retrying loadMeshSkinInfo() for " << mMeshID << LL_ENDL;
|
|
retry();
|
|
}
|
|
else
|
|
{
|
|
llassert(is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint
|
|
LL_WARNS() << "Unhandled status " << mStatus << LL_ENDL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
LLMeshRepository::sBytesReceived += mRequestedBytes;
|
|
|
|
U8* data = NULL;
|
|
|
|
if (data_size > 0)
|
|
{
|
|
data = new U8[data_size];
|
|
buffer->readAfter(channels.in(), NULL, data, data_size);
|
|
}
|
|
|
|
if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size))
|
|
{
|
|
//good fetch from sim, write to VFS for caching
|
|
LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
|
|
|
|
S32 offset = mOffset;
|
|
S32 size = mRequestedBytes;
|
|
|
|
if (file.getSize() >= offset+size)
|
|
{
|
|
LLMeshRepository::sCacheBytesWritten += size;
|
|
file.seek(offset);
|
|
file.write(data, size);
|
|
}
|
|
}
|
|
|
|
delete [] data;
|
|
}
|
|
|
|
void LLMeshDecompositionResponder::retry()
|
|
{
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
gMeshRepo.mThread->loadMeshDecomposition(mMeshID);
|
|
}
|
|
|
|
void LLMeshDecompositionResponder::completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer)
|
|
{
|
|
mProcessed = true;
|
|
|
|
if( !gMeshRepo.mThread )
|
|
{
|
|
return;
|
|
}
|
|
|
|
S32 data_size = buffer->countAfter(channels.in(), NULL);
|
|
|
|
if (mStatus < 200 || mStatus >= 400)
|
|
{
|
|
LL_WARNS() << mStatus << ": " << mReason << LL_ENDL;
|
|
}
|
|
|
|
if (data_size < (S32)mRequestedBytes)
|
|
{
|
|
if (is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE)
|
|
{ //timeout or service unavailable, try again
|
|
LL_WARNS() << "Timeout or service unavailable, retrying loadMeshDecomposition() for " << mMeshID << LL_ENDL;
|
|
retry();
|
|
}
|
|
else
|
|
{
|
|
llassert(is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint
|
|
LL_WARNS() << "Unhandled status " << mStatus << LL_ENDL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
LLMeshRepository::sBytesReceived += mRequestedBytes;
|
|
|
|
U8* data = NULL;
|
|
|
|
if (data_size > 0)
|
|
{
|
|
data = new U8[data_size];
|
|
buffer->readAfter(channels.in(), NULL, data, data_size);
|
|
}
|
|
|
|
if (gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size))
|
|
{
|
|
//good fetch from sim, write to VFS for caching
|
|
LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
|
|
|
|
S32 offset = mOffset;
|
|
S32 size = mRequestedBytes;
|
|
|
|
if (file.getSize() >= offset+size)
|
|
{
|
|
LLMeshRepository::sCacheBytesWritten += size;
|
|
file.seek(offset);
|
|
file.write(data, size);
|
|
}
|
|
}
|
|
|
|
delete [] data;
|
|
}
|
|
|
|
void LLMeshPhysicsShapeResponder::retry()
|
|
{
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID);
|
|
}
|
|
void LLMeshPhysicsShapeResponder::completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer)
|
|
{
|
|
mProcessed = true;
|
|
|
|
// thread could have already be destroyed during logout
|
|
if( !gMeshRepo.mThread )
|
|
{
|
|
return;
|
|
}
|
|
|
|
S32 data_size = buffer->countAfter(channels.in(), NULL);
|
|
|
|
if (mStatus < 200 || mStatus >= 400)
|
|
{
|
|
LL_WARNS() << mStatus << ": " << mReason << LL_ENDL;
|
|
}
|
|
|
|
if (data_size < (S32)mRequestedBytes)
|
|
{
|
|
if (is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE)
|
|
{ //timeout or service unavailable, try again
|
|
LL_WARNS() << "Timeout or service unavailable, retrying loadMeshPhysicsShape() for " << mMeshID << LL_ENDL;
|
|
retry();
|
|
}
|
|
else
|
|
{
|
|
llassert(is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE); //intentionally trigger a breakpoint
|
|
LL_WARNS() << "Unhandled status " << mStatus << LL_ENDL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
LLMeshRepository::sBytesReceived += mRequestedBytes;
|
|
|
|
U8* data = NULL;
|
|
|
|
if (data_size > 0)
|
|
{
|
|
data = new U8[data_size];
|
|
buffer->readAfter(channels.in(), NULL, data, data_size);
|
|
}
|
|
|
|
if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size))
|
|
{
|
|
//good fetch from sim, write to VFS for caching
|
|
LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
|
|
|
|
S32 offset = mOffset;
|
|
S32 size = mRequestedBytes;
|
|
|
|
if (file.getSize() >= offset+size)
|
|
{
|
|
LLMeshRepository::sCacheBytesWritten += size;
|
|
file.seek(offset);
|
|
file.write(data, size);
|
|
}
|
|
}
|
|
|
|
delete [] data;
|
|
}
|
|
|
|
void LLMeshHeaderResponder::retry()
|
|
{
|
|
AIStateMachine::StateTimer timer("Retry");
|
|
LLMeshRepository::sHTTPRetryCount++;
|
|
LLMutexLock lock(gMeshRepo.mThread->mMutex);
|
|
gMeshRepo.mThread->pushHeaderRequest(mMeshParams, 10.f);
|
|
}
|
|
|
|
void LLMeshHeaderResponder::completedRaw(LLChannelDescriptors const& channels,
|
|
LLIOPipe::buffer_ptr_t const& buffer)
|
|
{
|
|
//LL_INFOS() << mMeshParams.getSculptID() << " Status: " << mStatus << LL_ENDL;
|
|
mProcessed = true;
|
|
|
|
// thread could have already be destroyed during logout
|
|
if( !gMeshRepo.mThread )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (mStatus < 200 || mStatus >= 400)
|
|
{
|
|
//llwarns
|
|
// << "Header responder failed with status: "
|
|
// << mStatus << ": " << mReason << LL_ENDL;
|
|
|
|
// 503 (service unavailable) or HTTP_INTERNAL_ERROR_*'s.
|
|
// can be due to server load and can be retried
|
|
|
|
// TODO*: Add maximum retry logic, exponential backoff
|
|
// and (somewhat more optional than the others) retries
|
|
// again after some set period of time
|
|
|
|
llassert(mStatus == HTTP_NOT_FOUND || mStatus == HTTP_SERVICE_UNAVAILABLE || mStatus == HTTP_REQUEST_TIME_OUT || is_internal_http_error_that_warrants_a_retry(mStatus));
|
|
|
|
if (is_internal_http_error_that_warrants_a_retry(mStatus) || mStatus == HTTP_SERVICE_UNAVAILABLE)
|
|
{ //retry
|
|
LL_WARNS() << "Timeout or service unavailable, retrying." << LL_ENDL;
|
|
retry();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Unhandled status: " << mStatus << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
S32 data_size = buffer->countAfter(channels.in(), NULL);
|
|
|
|
static const U32 BUFF_MAX_STATIC_SIZE = 16384; //If we exceed this size just bump the vector back to BUFF_MAX_STATIC_SIZE after we're done.
|
|
static std::vector<U8> data(BUFF_MAX_STATIC_SIZE);
|
|
if (data_size > (S32)data.size())
|
|
data.resize(data_size);
|
|
else
|
|
memset(&data[0] + data_size, 0, data.size() - data_size);
|
|
|
|
if (data_size > 0)
|
|
{
|
|
AIStateMachine::StateTimer timer("readAfter");
|
|
buffer->readAfter(channels.in(), NULL, &data[0], data_size);
|
|
}
|
|
|
|
LLMeshRepository::sBytesReceived += llmin(data_size, 4096);
|
|
|
|
AIStateMachine::StateTimer timer("headerReceived");
|
|
bool success = gMeshRepo.mThread->headerReceived(mMeshParams, &data[0], data_size);
|
|
|
|
llassert(success);
|
|
|
|
if (!success)
|
|
{
|
|
LL_WARNS()
|
|
<< "Unable to parse mesh header: "
|
|
<< mStatus << ": " << mReason << LL_ENDL;
|
|
}
|
|
else if (data_size > 0)
|
|
{
|
|
//header was successfully retrieved from sim, cache in vfs
|
|
LLUUID mesh_id = mMeshParams.getSculptID();
|
|
LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id];
|
|
|
|
S32 version = header["version"].asInteger();
|
|
|
|
if (version <= MAX_MESH_VERSION)
|
|
{
|
|
std::stringstream str;
|
|
|
|
S32 lod_bytes = 0;
|
|
|
|
for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i)
|
|
{ //figure out how many bytes we'll need to reserve in the file
|
|
std::string lod_name = header_lod[i];
|
|
lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger());
|
|
}
|
|
|
|
//just in case skin info or decomposition is at the end of the file (which it shouldn't be)
|
|
lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger());
|
|
lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger());
|
|
|
|
S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id];
|
|
S32 bytes = lod_bytes + header_bytes;
|
|
|
|
|
|
//it's possible for the remote asset to have more data than is needed for the local cache
|
|
//only allocate as much space in the VFS as is needed for the local cache
|
|
data_size = llmin(data_size, bytes);
|
|
|
|
AIStateMachine::StateTimer timer("FileOpen");
|
|
LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE);
|
|
if (file.getMaxSize() >= bytes || file.setMaxSize(bytes))
|
|
{
|
|
LLMeshRepository::sCacheBytesWritten += data_size;
|
|
|
|
AIStateMachine::StateTimer timer("WriteData");
|
|
S32 bytes_remaining = bytes;
|
|
while (bytes_remaining > 0)
|
|
{
|
|
const S32 bytes_to_write = llmin(bytes_remaining, data_size);
|
|
file.write(&data[0], bytes_to_write);
|
|
if (bytes_remaining == bytes && bytes_to_write < bytes_remaining)
|
|
{
|
|
memset(&data[0], 0, data.size());
|
|
}
|
|
bytes_remaining -= llmin(bytes_remaining, bytes_to_write);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.size() > BUFF_MAX_STATIC_SIZE)
|
|
{
|
|
std::vector<U8>().swap(data);
|
|
data.resize(BUFF_MAX_STATIC_SIZE);
|
|
}
|
|
}
|
|
|
|
|
|
LLMeshRepository::LLMeshRepository()
|
|
: mMeshMutex(NULL),
|
|
mMeshThreadCount(0),
|
|
mThread(NULL),
|
|
mDecompThread(nullptr)
|
|
{
|
|
|
|
}
|
|
|
|
void LLMeshRepository::init()
|
|
{
|
|
mMeshMutex = new LLMutex();
|
|
|
|
LLConvexDecomposition::getInstance()->initSystem();
|
|
|
|
mDecompThread = new LLPhysicsDecomp();
|
|
mDecompThread->start();
|
|
|
|
while (!mDecompThread->mInited)
|
|
{ //wait for physics decomp thread to init
|
|
apr_sleep(100);
|
|
}
|
|
|
|
|
|
|
|
mThread = new LLMeshRepoThread();
|
|
mThread->start();
|
|
}
|
|
|
|
void LLMeshRepository::shutdown()
|
|
{
|
|
LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL;
|
|
|
|
mThread->mSignal->signal();
|
|
|
|
while (!mThread->isStopped())
|
|
{
|
|
apr_sleep(10);
|
|
}
|
|
delete mThread;
|
|
mThread = NULL;
|
|
|
|
delete mMeshMutex;
|
|
mMeshMutex = NULL;
|
|
|
|
LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL;
|
|
|
|
if (mDecompThread)
|
|
{
|
|
mDecompThread->shutdown();
|
|
delete mDecompThread;
|
|
mDecompThread = NULL;
|
|
}
|
|
|
|
LLConvexDecomposition::quitSystem();
|
|
}
|
|
|
|
void LLMeshRepository::unregisterMesh(LLVOVolume* vobj)
|
|
{
|
|
for (auto& lod : mLoadingMeshes)
|
|
{
|
|
for (auto& param : lod)
|
|
{
|
|
vector_replace_with_last(param.second, vobj);
|
|
}
|
|
}
|
|
}
|
|
|
|
S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod)
|
|
{
|
|
if (detail < 0 || detail > 4)
|
|
{
|
|
return detail;
|
|
}
|
|
|
|
{
|
|
LLMutexLock lock(mMeshMutex);
|
|
//add volume to list of loading meshes
|
|
mesh_load_map::iterator iter = mLoadingMeshes[detail].find(mesh_params);
|
|
if (iter != mLoadingMeshes[detail].end())
|
|
{ //request pending for this mesh, append volume id to list
|
|
auto it = std::find(iter->second.begin(), iter->second.end(), vobj);
|
|
if (it == iter->second.end()) {
|
|
iter->second.push_back(vobj);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//first request for this mesh
|
|
mLoadingMeshes[detail][mesh_params].push_back(vobj);
|
|
mPendingRequests.push_back(LLMeshRepoThread::LODRequest(mesh_params, detail));
|
|
LLMeshRepository::sLODPending++;
|
|
}
|
|
}
|
|
|
|
//do a quick search to see if we can't display something while we wait for this mesh to load
|
|
LLVolume* volume = vobj->getVolume();
|
|
|
|
if (volume)
|
|
{
|
|
LLVolumeParams params = volume->getParams();
|
|
|
|
LLVolumeLODGroup* group = LLPrimitive::getVolumeManager()->getGroup(params);
|
|
|
|
if (group)
|
|
{
|
|
//first, see if last_lod is available (don't transition down to avoid funny popping a la SH-641)
|
|
if (last_lod >= 0)
|
|
{
|
|
LLVolume* lod = group->refLOD(last_lod);
|
|
if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0)
|
|
{
|
|
group->derefLOD(lod);
|
|
return last_lod;
|
|
}
|
|
group->derefLOD(lod);
|
|
}
|
|
|
|
//next, see what the next lowest LOD available might be
|
|
for (S32 i = detail-1; i >= 0; --i)
|
|
{
|
|
LLVolume* lod = group->refLOD(i);
|
|
if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0)
|
|
{
|
|
group->derefLOD(lod);
|
|
return i;
|
|
}
|
|
|
|
group->derefLOD(lod);
|
|
}
|
|
|
|
//no lower LOD is a available, is a higher lod available?
|
|
for (S32 i = detail+1; i < 4; ++i)
|
|
{
|
|
LLVolume* lod = group->refLOD(i);
|
|
if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0)
|
|
{
|
|
group->derefLOD(lod);
|
|
return i;
|
|
}
|
|
|
|
group->derefLOD(lod);
|
|
}
|
|
}
|
|
}
|
|
|
|
return detail;
|
|
}
|
|
|
|
void LLMeshRepository::notifyLoadedMeshes()
|
|
{ //called from main thread
|
|
static const LLCachedControl<U32> max_concurrent_requests("MeshMaxConcurrentRequests");
|
|
LLMeshRepoThread::sMaxConcurrentRequests = max_concurrent_requests;
|
|
|
|
//update inventory
|
|
if (!mInventoryQ.empty())
|
|
{
|
|
LLMutexLock lock(mMeshMutex);
|
|
while (!mInventoryQ.empty())
|
|
{
|
|
inventory_data& data = mInventoryQ.front();
|
|
|
|
LLAssetType::EType asset_type = LLAssetType::lookup(data.mPostData["asset_type"].asString());
|
|
LLInventoryType::EType inventory_type = LLInventoryType::lookup(data.mPostData["inventory_type"].asString());
|
|
|
|
// Handle addition of texture, if any.
|
|
if ( data.mResponse.has("new_texture_folder_id") )
|
|
{
|
|
const LLUUID& folder_id = data.mResponse["new_texture_folder_id"].asUUID();
|
|
|
|
if ( folder_id.notNull() )
|
|
{
|
|
LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TEXTURE);
|
|
|
|
std::string name;
|
|
// Check if the server built a different name for the texture folder
|
|
if ( data.mResponse.has("new_texture_folder_name") )
|
|
{
|
|
name = data.mResponse["new_texture_folder_name"].asString();
|
|
}
|
|
else
|
|
{
|
|
name = data.mPostData["name"].asString();
|
|
}
|
|
|
|
// Add the category to the internal representation
|
|
LLPointer<LLViewerInventoryCategory> cat =
|
|
new LLViewerInventoryCategory(folder_id, parent_id,
|
|
LLFolderType::FT_NONE, name, gAgent.getID());
|
|
cat->setVersion(LLViewerInventoryCategory::VERSION_UNKNOWN);
|
|
|
|
LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1);
|
|
gInventory.accountForUpdate(update);
|
|
gInventory.updateCategory(cat);
|
|
}
|
|
}
|
|
|
|
on_new_single_inventory_upload_complete(
|
|
asset_type,
|
|
inventory_type,
|
|
data.mPostData["asset_type"].asString(),
|
|
data.mPostData["folder_id"].asUUID(),
|
|
data.mPostData["name"],
|
|
data.mPostData["description"],
|
|
data.mResponse,
|
|
data.mResponse["upload_price"]);
|
|
//}
|
|
|
|
mInventoryQ.pop();
|
|
}
|
|
}
|
|
|
|
//call completed callbacks on finished decompositions
|
|
mDecompThread->notifyCompleted();
|
|
|
|
if (!mThread->mSignal->try_lock())
|
|
{ //curl thread is churning, wait for it to go idle
|
|
return;
|
|
}
|
|
mThread->mSignal->unlock();
|
|
|
|
static std::string region_name("never name a region this");
|
|
|
|
if (gAgent.getRegion())
|
|
{ //update capability url
|
|
if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived())
|
|
{
|
|
region_name = gAgent.getRegion()->getName();
|
|
const std::string mesh_cap(gAgent.getRegion()->getViewerAssetUrl());
|
|
mGetMeshCapability = !mesh_cap.empty() ? mesh_cap : gAgent.getRegion()->getCapability("GetMesh2");
|
|
if (mGetMeshCapability.empty())
|
|
{
|
|
mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh");
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
LLMutexLock lock1(mMeshMutex);
|
|
LLMutexLock lock2(mThread->mMutex);
|
|
|
|
//popup queued error messages from background threads
|
|
while (!mUploadErrorQ.empty())
|
|
{
|
|
LLNotificationsUtil::add("MeshUploadError", mUploadErrorQ.front());
|
|
mUploadErrorQ.pop();
|
|
}
|
|
|
|
S32 push_count = LLMeshRepoThread::sMaxConcurrentRequests-(LLMeshRepoThread::sActiveHeaderRequests+LLMeshRepoThread::sActiveLODRequests);
|
|
|
|
push_count = llmin(push_count, (S32)mPendingRequests.size());
|
|
|
|
if (push_count > 0)
|
|
{
|
|
//calculate "score" for pending requests
|
|
|
|
//create score map
|
|
boost::unordered_map<LLUUID, F32> score_map;
|
|
|
|
for (auto& lod : mLoadingMeshes)
|
|
{
|
|
for (auto& param : lod)
|
|
{
|
|
F32 max_score = 0.f;
|
|
for (auto& vobj : param.second)
|
|
{
|
|
if (LLDrawable* drawable = vobj->mDrawable)
|
|
{
|
|
F32 cur_score = drawable->getRadius() / llmax(drawable->mDistanceWRTCamera, 1.f);
|
|
max_score = llmax(max_score, cur_score);
|
|
}
|
|
}
|
|
|
|
score_map[param.first.getSculptID()] = max_score;
|
|
}
|
|
}
|
|
|
|
//set "score" for pending requests
|
|
for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter)
|
|
{
|
|
iter->mScore = score_map[iter->mMeshParams.getSculptID()];
|
|
}
|
|
|
|
//sort by "score"
|
|
std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count,
|
|
mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater());
|
|
|
|
while (!mPendingRequests.empty() && push_count > 0)
|
|
{
|
|
LLMeshRepoThread::LODRequest& request = mPendingRequests.front();
|
|
mThread->loadMeshLOD(request.mMeshParams, request.mLOD);
|
|
mPendingRequests.erase(mPendingRequests.begin());
|
|
LLMeshRepository::sLODPending--;
|
|
push_count--;
|
|
}
|
|
}
|
|
|
|
//send skin info requests
|
|
while (!mPendingSkinRequests.empty())
|
|
{
|
|
mThread->loadMeshSkinInfo(mPendingSkinRequests.front());
|
|
mPendingSkinRequests.pop();
|
|
}
|
|
|
|
//send decomposition requests
|
|
while (!mPendingDecompositionRequests.empty())
|
|
{
|
|
mThread->loadMeshDecomposition(mPendingDecompositionRequests.front());
|
|
mPendingDecompositionRequests.pop();
|
|
}
|
|
|
|
//send physics shapes decomposition requests
|
|
while (!mPendingPhysicsShapeRequests.empty())
|
|
{
|
|
mThread->loadMeshPhysicsShape(mPendingPhysicsShapeRequests.front());
|
|
mPendingPhysicsShapeRequests.pop();
|
|
}
|
|
|
|
mThread->notifyLoadedMeshes();
|
|
}
|
|
|
|
mThread->mSignal->signal();
|
|
}
|
|
|
|
void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo& info)
|
|
{
|
|
mSkinMap.insert_or_assign(info.mMeshID, info);
|
|
|
|
skin_load_map::iterator iter = mLoadingSkins.find(info.mMeshID);
|
|
if (iter != mLoadingSkins.end())
|
|
{
|
|
for (auto obj_id = iter->second.begin(); obj_id != iter->second.end(); ++obj_id)
|
|
{
|
|
LLVOVolume* vobj = (LLVOVolume*) gObjectList.findObject(*obj_id);
|
|
if (vobj)
|
|
{
|
|
vobj->notifyMeshLoaded();
|
|
}
|
|
}
|
|
mLoadingSkins.erase(info.mMeshID);
|
|
}
|
|
}
|
|
|
|
void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp)
|
|
{
|
|
decomposition_map::iterator iter = mDecompositionMap.find(decomp->mMeshID);
|
|
if (iter == mDecompositionMap.end())
|
|
{ //just insert decomp into map
|
|
mDecompositionMap[decomp->mMeshID] = decomp;
|
|
mLoadingDecompositions.erase(decomp->mMeshID);
|
|
}
|
|
else
|
|
{ //merge decomp with existing entry
|
|
iter->second->merge(decomp);
|
|
mLoadingDecompositions.erase(decomp->mMeshID);
|
|
delete decomp;
|
|
}
|
|
}
|
|
|
|
void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume)
|
|
{ //called from main thread
|
|
S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail());
|
|
|
|
//get list of objects waiting to be notified this mesh is loaded
|
|
mesh_load_map::iterator obj_iter = mLoadingMeshes[detail].find(mesh_params);
|
|
|
|
if (volume && obj_iter != mLoadingMeshes[detail].end())
|
|
{
|
|
//make sure target volume is still valid
|
|
if (volume->getNumVolumeFaces() <= 0)
|
|
{
|
|
LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_params.getSculptID()
|
|
<< LL_ENDL;
|
|
}
|
|
|
|
{ //update system volume
|
|
LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, detail);
|
|
if (sys_volume)
|
|
{
|
|
sys_volume->copyVolumeFaces(volume);
|
|
sys_volume->setMeshAssetLoaded(TRUE);
|
|
LLPrimitive::getVolumeManager()->unrefVolume(sys_volume);
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_params.getSculptID()
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
|
|
//notify waiting LLVOVolume instances that their requested mesh is available
|
|
for (auto& vobj : obj_iter->second)
|
|
{
|
|
vobj->notifyMeshLoaded();
|
|
}
|
|
|
|
mLoadingMeshes[detail].erase(mesh_params);
|
|
}
|
|
}
|
|
|
|
void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod)
|
|
{ //called from main thread
|
|
//get list of objects waiting to be notified this mesh is loaded
|
|
mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_params);
|
|
|
|
F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(lod);
|
|
|
|
if (obj_iter != mLoadingMeshes[lod].end())
|
|
{
|
|
for (auto& vobj : obj_iter->second)
|
|
{
|
|
LLVolume* obj_volume = vobj->getVolume();
|
|
if (obj_volume &&
|
|
obj_volume->getDetail() == detail &&
|
|
obj_volume->getParams() == mesh_params)
|
|
{ //should force volume to find most appropriate LOD
|
|
vobj->setVolume(obj_volume->getParams(), lod);
|
|
}
|
|
}
|
|
|
|
mLoadingMeshes[lod].erase(mesh_params);
|
|
}
|
|
}
|
|
|
|
S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
|
|
{
|
|
return mThread->getActualMeshLOD(mesh_params, lod);
|
|
}
|
|
|
|
const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const LLVOVolume* requesting_obj)
|
|
{
|
|
if (mesh_id.notNull())
|
|
{
|
|
const auto iter = mSkinMap.find(mesh_id);
|
|
if (iter != mSkinMap.cend())
|
|
{
|
|
return &(iter->second);
|
|
}
|
|
|
|
//no skin info known about given mesh, try to fetch it
|
|
{
|
|
LLMutexLock lock(mMeshMutex);
|
|
//add volume to list of loading meshes
|
|
skin_load_map::iterator iter = mLoadingSkins.find(mesh_id);
|
|
if (iter == mLoadingSkins.end())
|
|
{ //no request pending for this skin info
|
|
mPendingSkinRequests.push(mesh_id);
|
|
}
|
|
mLoadingSkins[mesh_id].insert(requesting_obj->getID());
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id)
|
|
{
|
|
if (mesh_id.notNull())
|
|
{
|
|
LLModel::Decomposition* decomp = NULL;
|
|
decomposition_map::iterator iter = mDecompositionMap.find(mesh_id);
|
|
if (iter != mDecompositionMap.end())
|
|
{
|
|
decomp = iter->second;
|
|
}
|
|
|
|
//decomposition block hasn't been fetched yet
|
|
if (!decomp || decomp->mPhysicsShapeMesh.empty())
|
|
{
|
|
LLMutexLock lock(mMeshMutex);
|
|
//add volume to list of loading meshes
|
|
auto iter = mLoadingPhysicsShapes.find(mesh_id);
|
|
if (iter == mLoadingPhysicsShapes.end())
|
|
{ //no request pending for this skin info
|
|
mLoadingPhysicsShapes.insert(mesh_id);
|
|
mPendingPhysicsShapeRequests.push(mesh_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id)
|
|
{
|
|
LLModel::Decomposition* ret = NULL;
|
|
|
|
if (mesh_id.notNull())
|
|
{
|
|
decomposition_map::iterator iter = mDecompositionMap.find(mesh_id);
|
|
if (iter != mDecompositionMap.end())
|
|
{
|
|
ret = iter->second;
|
|
}
|
|
|
|
//decomposition block hasn't been fetched yet
|
|
if (!ret || ret->mBaseHullMesh.empty())
|
|
{
|
|
LLMutexLock lock(mMeshMutex);
|
|
//add volume to list of loading meshes
|
|
auto iter = mLoadingDecompositions.find(mesh_id);
|
|
if (iter == mLoadingDecompositions.end())
|
|
{ //no request pending for this skin info
|
|
mLoadingDecompositions.insert(mesh_id);
|
|
mPendingDecompositionRequests.push(mesh_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void LLMeshRepository::buildHull(const LLVolumeParams& params, S32 detail)
|
|
{
|
|
LLVolume* volume = LLPrimitive::getVolumeManager()->refVolume(params, detail);
|
|
|
|
if (!volume->mHullPoints)
|
|
{
|
|
//all default params
|
|
//execute first stage
|
|
//set simplify mode to retain
|
|
//set retain percentage to zero
|
|
//run second stage
|
|
}
|
|
|
|
LLPrimitive::getVolumeManager()->unrefVolume(volume);
|
|
}
|
|
|
|
bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id)
|
|
{
|
|
LLSD mesh = mThread->getMeshHeader(mesh_id);
|
|
if (mesh.has("physics_mesh") && mesh["physics_mesh"].has("size") && (mesh["physics_mesh"]["size"].asInteger() > 0))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
LLModel::Decomposition* decomp = getDecomposition(mesh_id);
|
|
if (decomp && !decomp->mHull.empty())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
LLSD& LLMeshRepository::getMeshHeader(const LLUUID& mesh_id)
|
|
{
|
|
return mThread->getMeshHeader(mesh_id);
|
|
}
|
|
|
|
LLSD& LLMeshRepoThread::getMeshHeader(const LLUUID& mesh_id)
|
|
{
|
|
static LLSD dummy_ret;
|
|
if (mesh_id.notNull())
|
|
{
|
|
LLMutexLock lock(mHeaderMutex);
|
|
mesh_header_map::iterator iter = mMeshHeader.find(mesh_id);
|
|
if (iter != mMeshHeader.end() && mMeshHeaderSize[mesh_id] > 0)
|
|
{
|
|
return iter->second;
|
|
}
|
|
}
|
|
|
|
return dummy_ret;
|
|
}
|
|
|
|
|
|
void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3& scale, bool upload_textures,
|
|
bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
|
|
LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer)
|
|
{
|
|
if (do_upload && upload_url.empty())
|
|
{
|
|
LL_INFOS() << "unable to upload, fee request failed" << LL_ENDL;
|
|
return;
|
|
}
|
|
AIMeshUpload* thread = new AIMeshUpload(data, scale, upload_textures, upload_skin, upload_joints, upload_url,
|
|
do_upload, fee_observer, upload_observer);
|
|
thread->run(NULL, 0, false, true, &gMainThreadEngine);
|
|
}
|
|
|
|
S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod)
|
|
{
|
|
if (mThread && mesh_id.notNull() && LLPrimitive::NO_LOD != lod)
|
|
{
|
|
LLMutexLock lock(mThread->mHeaderMutex);
|
|
LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
|
|
if (iter != mThread->mMeshHeader.end() && mThread->mMeshHeaderSize[mesh_id] > 0)
|
|
{
|
|
LLSD& header = iter->second;
|
|
|
|
if (header.has("404"))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
S32 size = header[header_lod[lod]]["size"].asInteger();
|
|
return size;
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation,
|
|
LLVector3& result_pos,
|
|
LLQuaternion& result_rot,
|
|
LLVector3& result_scale)
|
|
{
|
|
// check for reflection
|
|
BOOL reflected = (transformation.determinant() < 0);
|
|
|
|
// compute position
|
|
LLVector3 position = LLVector3(0, 0, 0) * transformation;
|
|
|
|
// compute scale
|
|
LLVector3 x_transformed = LLVector3(1, 0, 0) * transformation - position;
|
|
LLVector3 y_transformed = LLVector3(0, 1, 0) * transformation - position;
|
|
LLVector3 z_transformed = LLVector3(0, 0, 1) * transformation - position;
|
|
F32 x_length = x_transformed.normalize();
|
|
F32 y_length = y_transformed.normalize();
|
|
F32 z_length = z_transformed.normalize();
|
|
LLVector3 scale = LLVector3(x_length, y_length, z_length);
|
|
|
|
// adjust for "reflected" geometry
|
|
LLVector3 x_transformed_reflected = x_transformed;
|
|
if (reflected)
|
|
{
|
|
x_transformed_reflected *= -1.0;
|
|
}
|
|
|
|
// compute rotation
|
|
LLMatrix3 rotation_matrix;
|
|
rotation_matrix.setRows(x_transformed_reflected, y_transformed, z_transformed);
|
|
LLQuaternion quat_rotation = rotation_matrix.quaternion();
|
|
quat_rotation.normalize(); // the rotation_matrix might not have been orthoginal. make it so here.
|
|
LLVector3 euler_rotation;
|
|
quat_rotation.getEulerAngles(&euler_rotation.mV[VX], &euler_rotation.mV[VY], &euler_rotation.mV[VZ]);
|
|
|
|
result_pos = position + mOrigin;
|
|
result_scale = scale;
|
|
result_rot = quat_rotation;
|
|
}
|
|
|
|
void LLMeshRepository::updateInventory(inventory_data data)
|
|
{
|
|
LLMutexLock lock(mMeshMutex);
|
|
dump_llsd_to_file(data.mPostData,make_dump_name("update_inventory_post_data_",dump_num));
|
|
dump_llsd_to_file(data.mResponse,make_dump_name("update_inventory_response_",dump_num));
|
|
mInventoryQ.push(data);
|
|
}
|
|
|
|
void LLMeshRepository::uploadError(LLSD& args)
|
|
{
|
|
LLMutexLock lock(mMeshMutex);
|
|
mUploadErrorQ.push(args);
|
|
}
|
|
|
|
F32 LLMeshRepository::getEstTrianglesMax(LLUUID mesh_id)
|
|
{
|
|
LLMeshCostData costs;
|
|
if (getCostData(mesh_id, costs))
|
|
{
|
|
return costs.getEstTrisMax();
|
|
}
|
|
else
|
|
{
|
|
return 0.f;
|
|
}
|
|
}
|
|
|
|
F32 LLMeshRepository::getEstTrianglesStreamingCost(LLUUID mesh_id)
|
|
{
|
|
LLMeshCostData costs;
|
|
if (getCostData(mesh_id, costs))
|
|
{
|
|
return costs.getEstTrisForStreamingCost();
|
|
}
|
|
else
|
|
{
|
|
return 0.f;
|
|
}
|
|
}
|
|
|
|
// FIXME replace with calc based on LLMeshCostData
|
|
F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value)
|
|
{
|
|
F32 result = 0.f;
|
|
if (mThread && mesh_id.notNull())
|
|
{
|
|
LLMutexLock lock(mThread->mHeaderMutex);
|
|
LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
|
|
if (iter != mThread->mMeshHeader.end() && mThread->mMeshHeaderSize[mesh_id] > 0)
|
|
{
|
|
result = getStreamingCostLegacy(iter->second, radius, bytes, bytes_visible, lod, unscaled_value);
|
|
}
|
|
}
|
|
if (result > 0.f)
|
|
{
|
|
LLMeshCostData data;
|
|
if (getCostData(mesh_id, data))
|
|
{
|
|
F32 ref_streaming_cost = data.getRadiusBasedStreamingCost(radius);
|
|
F32 ref_weighted_tris = data.getRadiusWeightedTris(radius);
|
|
if (!is_approx_equal(ref_streaming_cost,result))
|
|
{
|
|
LL_WARNS() << mesh_id << "streaming mismatch " << result << " " << ref_streaming_cost << LL_ENDL;
|
|
}
|
|
if (unscaled_value && !is_approx_equal(ref_weighted_tris,*unscaled_value))
|
|
{
|
|
LL_WARNS() << mesh_id << "weighted_tris mismatch " << *unscaled_value << " " << ref_weighted_tris << LL_ENDL;
|
|
}
|
|
if (bytes && (*bytes != data.getSizeTotal()))
|
|
{
|
|
LL_WARNS() << mesh_id << "bytes mismatch " << *bytes << " " << data.getSizeTotal() << LL_ENDL;
|
|
}
|
|
if (bytes_visible && (lod >=0) && (lod < 4) && (*bytes_visible != data.getSizeByLOD(lod)))
|
|
{
|
|
LL_WARNS() << mesh_id << "bytes_visible mismatch " << *bytes_visible << " " << data.getSizeByLOD(lod) << LL_ENDL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "getCostData failed!!!" << LL_ENDL;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// FIXME replace with calc based on LLMeshCostData
|
|
//static
|
|
F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value)
|
|
{
|
|
if (header.has("404")
|
|
|| !header.has("lowest_lod")
|
|
|| (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION))
|
|
{
|
|
return 0.f;
|
|
}
|
|
|
|
F32 max_distance = 512.f;
|
|
|
|
F32 dlowest = llmin(radius/0.03f, max_distance);
|
|
F32 dlow = llmin(radius/0.06f, max_distance);
|
|
F32 dmid = llmin(radius/0.24f, max_distance);
|
|
|
|
static const LLCachedControl<U32> mesh_meta_data_discount("MeshMetaDataDiscount");
|
|
static const LLCachedControl<U32> mesh_minimum_byte_size("MeshMinimumByteSize");
|
|
static const LLCachedControl<U32> mesh_bytes_per_triangle("MeshBytesPerTriangle");
|
|
static const LLCachedControl<U32> mesh_triangle_budget("MeshTriangleBudget");
|
|
|
|
F32 METADATA_DISCOUNT = (F32) mesh_meta_data_discount.get(); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead
|
|
F32 MINIMUM_SIZE = (F32) mesh_minimum_byte_size.get(); //make sure nothing is "free"
|
|
|
|
F32 bytes_per_triangle = (F32) mesh_bytes_per_triangle.get();
|
|
|
|
S32 bytes_lowest = header["lowest_lod"]["size"].asInteger();
|
|
S32 bytes_low = header["low_lod"]["size"].asInteger();
|
|
S32 bytes_mid = header["medium_lod"]["size"].asInteger();
|
|
S32 bytes_high = header["high_lod"]["size"].asInteger();
|
|
|
|
if (bytes_high == 0)
|
|
{
|
|
return 0.f;
|
|
}
|
|
|
|
if (bytes_mid == 0)
|
|
{
|
|
bytes_mid = bytes_high;
|
|
}
|
|
|
|
if (bytes_low == 0)
|
|
{
|
|
bytes_low = bytes_mid;
|
|
}
|
|
|
|
if (bytes_lowest == 0)
|
|
{
|
|
bytes_lowest = bytes_low;
|
|
}
|
|
|
|
F32 triangles_lowest = llmax((F32) bytes_lowest-METADATA_DISCOUNT, MINIMUM_SIZE)/bytes_per_triangle;
|
|
F32 triangles_low = llmax((F32) bytes_low-METADATA_DISCOUNT, MINIMUM_SIZE)/bytes_per_triangle;
|
|
F32 triangles_mid = llmax((F32) bytes_mid-METADATA_DISCOUNT, MINIMUM_SIZE)/bytes_per_triangle;
|
|
F32 triangles_high = llmax((F32) bytes_high-METADATA_DISCOUNT, MINIMUM_SIZE)/bytes_per_triangle;
|
|
|
|
if (bytes)
|
|
{
|
|
*bytes = 0;
|
|
*bytes += header["lowest_lod"]["size"].asInteger();
|
|
*bytes += header["low_lod"]["size"].asInteger();
|
|
*bytes += header["medium_lod"]["size"].asInteger();
|
|
*bytes += header["high_lod"]["size"].asInteger();
|
|
}
|
|
|
|
if (bytes_visible)
|
|
{
|
|
lod = LLMeshRepository::getActualMeshLOD(header, lod);
|
|
if (lod >= 0 && lod <= 3)
|
|
{
|
|
*bytes_visible = header[header_lod[lod]]["size"].asInteger();
|
|
}
|
|
}
|
|
|
|
F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559)
|
|
F32 min_area = 1.f;
|
|
|
|
F32 high_area = llmin(F_PI*dmid*dmid, max_area);
|
|
F32 mid_area = llmin(F_PI*dlow*dlow, max_area);
|
|
F32 low_area = llmin(F_PI*dlowest*dlowest, max_area);
|
|
F32 lowest_area = max_area;
|
|
|
|
lowest_area -= low_area;
|
|
low_area -= mid_area;
|
|
mid_area -= high_area;
|
|
|
|
high_area = llclamp(high_area, min_area, max_area);
|
|
mid_area = llclamp(mid_area, min_area, max_area);
|
|
low_area = llclamp(low_area, min_area, max_area);
|
|
lowest_area = llclamp(lowest_area, min_area, max_area);
|
|
|
|
F32 total_area = high_area + mid_area + low_area + lowest_area;
|
|
high_area /= total_area;
|
|
mid_area /= total_area;
|
|
low_area /= total_area;
|
|
lowest_area /= total_area;
|
|
|
|
F32 weighted_avg = triangles_high*high_area +
|
|
triangles_mid*mid_area +
|
|
triangles_low*low_area +
|
|
triangles_lowest*lowest_area;
|
|
|
|
if (unscaled_value)
|
|
{
|
|
*unscaled_value = weighted_avg;
|
|
}
|
|
|
|
return weighted_avg/mesh_triangle_budget*15000.f;
|
|
}
|
|
|
|
LLMeshCostData::LLMeshCostData()
|
|
{
|
|
mSizeByLOD.resize(4);
|
|
mEstTrisByLOD.resize(4);
|
|
|
|
std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0);
|
|
std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f);
|
|
}
|
|
|
|
bool LLMeshCostData::init(const LLSD& header)
|
|
{
|
|
mSizeByLOD.resize(4);
|
|
mEstTrisByLOD.resize(4);
|
|
|
|
std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0);
|
|
std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f);
|
|
|
|
S32 bytes_high = header["high_lod"]["size"].asInteger();
|
|
S32 bytes_med = header["medium_lod"]["size"].asInteger();
|
|
if (bytes_med == 0)
|
|
{
|
|
bytes_med = bytes_high;
|
|
}
|
|
S32 bytes_low = header["low_lod"]["size"].asInteger();
|
|
if (bytes_low == 0)
|
|
{
|
|
bytes_low = bytes_med;
|
|
}
|
|
S32 bytes_lowest = header["lowest_lod"]["size"].asInteger();
|
|
if (bytes_lowest == 0)
|
|
{
|
|
bytes_lowest = bytes_low;
|
|
}
|
|
mSizeByLOD[0] = bytes_lowest;
|
|
mSizeByLOD[1] = bytes_low;
|
|
mSizeByLOD[2] = bytes_med;
|
|
mSizeByLOD[3] = bytes_high;
|
|
|
|
F32 METADATA_DISCOUNT = (F32) gSavedSettings.getU32("MeshMetaDataDiscount"); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead
|
|
F32 MINIMUM_SIZE = (F32) gSavedSettings.getU32("MeshMinimumByteSize"); //make sure nothing is "free"
|
|
F32 bytes_per_triangle = (F32) gSavedSettings.getU32("MeshBytesPerTriangle");
|
|
|
|
for (S32 i=0; i<4; i++)
|
|
{
|
|
mEstTrisByLOD[i] = llmax((F32) mSizeByLOD[i]-METADATA_DISCOUNT, MINIMUM_SIZE)/bytes_per_triangle;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
S32 LLMeshCostData::getSizeByLOD(S32 lod)
|
|
{
|
|
if (llclamp(lod,0,3) != lod)
|
|
{
|
|
return 0;
|
|
}
|
|
return mSizeByLOD[lod];
|
|
}
|
|
|
|
S32 LLMeshCostData::getSizeTotal()
|
|
{
|
|
return mSizeByLOD[0] + mSizeByLOD[1] + mSizeByLOD[2] + mSizeByLOD[3];
|
|
}
|
|
|
|
F32 LLMeshCostData::getEstTrisByLOD(S32 lod)
|
|
{
|
|
if (llclamp(lod,0,3) != lod)
|
|
{
|
|
return 0.f;
|
|
}
|
|
return mEstTrisByLOD[lod];
|
|
}
|
|
|
|
F32 LLMeshCostData::getEstTrisMax()
|
|
{
|
|
return llmax(mEstTrisByLOD[0], mEstTrisByLOD[1], mEstTrisByLOD[2], mEstTrisByLOD[3]);
|
|
}
|
|
|
|
F32 LLMeshCostData::getRadiusWeightedTris(F32 radius)
|
|
{
|
|
F32 max_distance = 512.f;
|
|
|
|
F32 dlowest = llmin(radius/0.03f, max_distance);
|
|
F32 dlow = llmin(radius/0.06f, max_distance);
|
|
F32 dmid = llmin(radius/0.24f, max_distance);
|
|
|
|
F32 triangles_lowest = mEstTrisByLOD[0];
|
|
F32 triangles_low = mEstTrisByLOD[1];
|
|
F32 triangles_mid = mEstTrisByLOD[2];
|
|
F32 triangles_high = mEstTrisByLOD[3];
|
|
|
|
F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559)
|
|
F32 min_area = 1.f;
|
|
|
|
F32 high_area = llmin(F_PI*dmid*dmid, max_area);
|
|
F32 mid_area = llmin(F_PI*dlow*dlow, max_area);
|
|
F32 low_area = llmin(F_PI*dlowest*dlowest, max_area);
|
|
F32 lowest_area = max_area;
|
|
|
|
lowest_area -= low_area;
|
|
low_area -= mid_area;
|
|
mid_area -= high_area;
|
|
|
|
high_area = llclamp(high_area, min_area, max_area);
|
|
mid_area = llclamp(mid_area, min_area, max_area);
|
|
low_area = llclamp(low_area, min_area, max_area);
|
|
lowest_area = llclamp(lowest_area, min_area, max_area);
|
|
|
|
F32 total_area = high_area + mid_area + low_area + lowest_area;
|
|
high_area /= total_area;
|
|
mid_area /= total_area;
|
|
low_area /= total_area;
|
|
lowest_area /= total_area;
|
|
|
|
F32 weighted_avg = triangles_high*high_area +
|
|
triangles_mid*mid_area +
|
|
triangles_low*low_area +
|
|
triangles_lowest*lowest_area;
|
|
|
|
return weighted_avg;
|
|
}
|
|
|
|
F32 LLMeshCostData::getEstTrisForStreamingCost()
|
|
{
|
|
LL_DEBUGS("StreamingCost") << "tris_by_lod: "
|
|
<< mEstTrisByLOD[0] << ", "
|
|
<< mEstTrisByLOD[1] << ", "
|
|
<< mEstTrisByLOD[2] << ", "
|
|
<< mEstTrisByLOD[3] << LL_ENDL;
|
|
|
|
F32 charged_tris = mEstTrisByLOD[3];
|
|
F32 allowed_tris = mEstTrisByLOD[3];
|
|
const F32 ENFORCE_FLOOR = 64.0f;
|
|
for (S32 i=2; i>=0; i--)
|
|
{
|
|
// How many tris can we have in this LOD without affecting land impact?
|
|
// - normally an LOD should be at most half the size of the previous one.
|
|
// - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller.
|
|
allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,mEstTrisByLOD[i]);
|
|
F32 excess_tris = mEstTrisByLOD[i]-allowed_tris;
|
|
if (excess_tris>0.f)
|
|
{
|
|
LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris << LL_ENDL;
|
|
charged_tris += excess_tris;
|
|
}
|
|
}
|
|
return charged_tris;
|
|
}
|
|
|
|
F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius)
|
|
{
|
|
return getRadiusWeightedTris(radius)/gSavedSettings.getU32("MeshTriangleBudget")*15000.f;
|
|
}
|
|
|
|
F32 LLMeshCostData::getTriangleBasedStreamingCost()
|
|
{
|
|
F32 result = ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * getEstTrisForStreamingCost();
|
|
return result;
|
|
}
|
|
|
|
|
|
bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)
|
|
{
|
|
data = LLMeshCostData();
|
|
|
|
if (mThread && mesh_id.notNull())
|
|
{
|
|
LLMutexLock lock(mThread->mHeaderMutex);
|
|
LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
|
|
if (iter != mThread->mMeshHeader.end() && mThread->mMeshHeaderSize[mesh_id] > 0)
|
|
{
|
|
const LLSD& header = iter->second;
|
|
|
|
bool header_invalid = (header.has("404")
|
|
|| !header.has("lowest_lod")
|
|
|| (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION));
|
|
if (!header_invalid)
|
|
{
|
|
return getCostData(header, data);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLMeshRepository::getCostData(LLSD& header, LLMeshCostData& data)
|
|
{
|
|
data = LLMeshCostData();
|
|
|
|
if (!data.init(header))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
LLPhysicsDecomp::LLPhysicsDecomp()
|
|
: LLThread("Physics Decomp")
|
|
{
|
|
mInited = false;
|
|
mQuitting = false;
|
|
mDone = false;
|
|
|
|
mSignal = new LLCondition;
|
|
mMutex = new LLMutex;
|
|
}
|
|
|
|
LLPhysicsDecomp::~LLPhysicsDecomp()
|
|
{
|
|
shutdown();
|
|
|
|
delete mSignal;
|
|
mSignal = NULL;
|
|
delete mMutex;
|
|
mMutex = NULL;
|
|
}
|
|
|
|
void LLPhysicsDecomp::shutdown()
|
|
{
|
|
if (mSignal)
|
|
{
|
|
mQuitting = true;
|
|
mSignal->signal();
|
|
|
|
while (!isStopped())
|
|
{
|
|
apr_sleep(10);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLPhysicsDecomp::submitRequest(LLPhysicsDecomp::Request* request)
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
mRequestQ.push(request);
|
|
mSignal->signal();
|
|
}
|
|
|
|
//static
|
|
S32 LLPhysicsDecomp::llcdCallback(const char* status, S32 p1, S32 p2)
|
|
{
|
|
if (gMeshRepo.mDecompThread && gMeshRepo.mDecompThread->mCurRequest.notNull())
|
|
{
|
|
return gMeshRepo.mDecompThread->mCurRequest->statusCallback(status, p1, p2);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
bool needTriangles( LLConvexDecomposition *aDC )
|
|
{
|
|
if( !aDC )
|
|
return false;
|
|
|
|
LLCDParam const *pParams(0);
|
|
int nParams = aDC->getParameters( &pParams );
|
|
|
|
if( nParams <= 0 )
|
|
return false;
|
|
|
|
for( int i = 0; i < nParams; ++i )
|
|
{
|
|
if( pParams[i].mName && strcmp( "nd_AlwaysNeedTriangles", pParams[i].mName ) == 0 )
|
|
{
|
|
if( LLCDParam::LLCD_BOOLEAN == pParams[i].mType && pParams[i].mDefault.mBool )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based)
|
|
{
|
|
// <singu> HACD
|
|
if (vertex_based)
|
|
{
|
|
if (LLConvexDecomposition* pDeComp = LLConvexDecomposition::getInstance())
|
|
vertex_based = !needTriangles(pDeComp);
|
|
else
|
|
return;
|
|
}
|
|
// </singu>
|
|
|
|
mesh.mVertexBase = mCurRequest->mPositions[0].mV;
|
|
mesh.mVertexStrideBytes = 12;
|
|
mesh.mNumVertices = mCurRequest->mPositions.size();
|
|
|
|
if(!vertex_based)
|
|
{
|
|
mesh.mIndexType = LLCDMeshData::INT_16;
|
|
mesh.mIndexBase = &(mCurRequest->mIndices[0]);
|
|
mesh.mIndexStrideBytes = 6;
|
|
|
|
mesh.mNumTriangles = mCurRequest->mIndices.size()/3;
|
|
}
|
|
|
|
if ((vertex_based || mesh.mNumTriangles > 0) && mesh.mNumVertices > 2)
|
|
{
|
|
LLCDResult ret = LLCD_OK;
|
|
if (LLConvexDecomposition::getInstance() != NULL)
|
|
{
|
|
ret = LLConvexDecomposition::getInstance()->setMeshData(&mesh, vertex_based);
|
|
}
|
|
|
|
if (ret)
|
|
{
|
|
LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLPhysicsDecomp::doDecomposition()
|
|
{
|
|
LLCDMeshData mesh;
|
|
S32 stage = mStageID[mCurRequest->mStage];
|
|
|
|
if (LLConvexDecomposition::getInstance() == NULL)
|
|
{
|
|
// stub library. do nothing.
|
|
return;
|
|
}
|
|
|
|
//load data intoLLCD
|
|
if (stage == 0)
|
|
{
|
|
setMeshData(mesh, false);
|
|
}
|
|
|
|
//build parameter map
|
|
std::map<std::string, const LLCDParam*> param_map;
|
|
|
|
static const LLCDParam* params = NULL;
|
|
static S32 param_count = 0;
|
|
if (!params)
|
|
{
|
|
param_count = LLConvexDecomposition::getInstance()->getParameters(¶ms);
|
|
}
|
|
|
|
for (S32 i = 0; i < param_count; ++i)
|
|
{
|
|
param_map[params[i].mName] = params+i;
|
|
}
|
|
|
|
LLCDResult ret = LLCD_OK;
|
|
//set parameter values
|
|
for (decomp_params::iterator iter = mCurRequest->mParams.begin(); iter != mCurRequest->mParams.end(); ++iter)
|
|
{
|
|
const std::string& name = iter->first;
|
|
const LLSD& value = iter->second;
|
|
|
|
const LLCDParam* param = param_map[name];
|
|
|
|
if (param == NULL)
|
|
{ //couldn't find valid parameter
|
|
continue;
|
|
}
|
|
|
|
|
|
if (param->mType == LLCDParam::LLCD_FLOAT)
|
|
{
|
|
ret = LLConvexDecomposition::getInstance()->setParam(param->mName, (F32) value.asReal());
|
|
}
|
|
else if (param->mType == LLCDParam::LLCD_INTEGER ||
|
|
param->mType == LLCDParam::LLCD_ENUM)
|
|
{
|
|
ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asInteger());
|
|
}
|
|
else if (param->mType == LLCDParam::LLCD_BOOLEAN)
|
|
{
|
|
ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asBoolean());
|
|
}
|
|
}
|
|
|
|
mCurRequest->setStatusMessage("Executing.");
|
|
|
|
if (LLConvexDecomposition::getInstance() != NULL)
|
|
{
|
|
ret = LLConvexDecomposition::getInstance()->executeStage(stage);
|
|
}
|
|
|
|
if (ret)
|
|
{
|
|
LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "."
|
|
<< LL_ENDL;
|
|
LLMutexLock lock(mMutex);
|
|
|
|
mCurRequest->mHull.clear();
|
|
mCurRequest->mHullMesh.clear();
|
|
|
|
mCurRequest->setStatusMessage("FAIL");
|
|
|
|
completeCurrent();
|
|
}
|
|
else
|
|
{
|
|
mCurRequest->setStatusMessage("Reading results");
|
|
|
|
S32 num_hulls =0;
|
|
if (LLConvexDecomposition::getInstance() != NULL)
|
|
{
|
|
num_hulls = LLConvexDecomposition::getInstance()->getNumHullsFromStage(stage);
|
|
}
|
|
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
mCurRequest->mHull.clear();
|
|
mCurRequest->mHull.resize(num_hulls);
|
|
|
|
mCurRequest->mHullMesh.clear();
|
|
mCurRequest->mHullMesh.resize(num_hulls);
|
|
}
|
|
|
|
for (S32 i = 0; i < num_hulls; ++i)
|
|
{
|
|
std::vector<LLVector3> p;
|
|
LLCDHull hull;
|
|
// if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code
|
|
LLConvexDecomposition::getInstance()->getHullFromStage(stage, i, &hull);
|
|
|
|
const F32* v = hull.mVertexBase;
|
|
|
|
for (S32 j = 0; j < hull.mNumVertices; ++j)
|
|
{
|
|
LLVector3 vert(v[0], v[1], v[2]);
|
|
p.push_back(vert);
|
|
v = (F32*) (((U8*) v) + hull.mVertexStrideBytes);
|
|
}
|
|
|
|
LLCDMeshData mesh;
|
|
// if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code
|
|
LLConvexDecomposition::getInstance()->getMeshFromStage(stage, i, &mesh);
|
|
|
|
get_vertex_buffer_from_mesh(mesh, mCurRequest->mHullMesh[i]);
|
|
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
mCurRequest->mHull[i] = p;
|
|
}
|
|
}
|
|
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
mCurRequest->setStatusMessage("FAIL");
|
|
completeCurrent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLPhysicsDecomp::completeCurrent()
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
mCompletedQ.push(mCurRequest);
|
|
mCurRequest = NULL;
|
|
}
|
|
|
|
void LLPhysicsDecomp::notifyCompleted()
|
|
{
|
|
if (!mCompletedQ.empty())
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
while (!mCompletedQ.empty())
|
|
{
|
|
Request* req = mCompletedQ.front();
|
|
req->completed();
|
|
mCompletedQ.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void make_box(LLPhysicsDecomp::Request * request)
|
|
{
|
|
LLVector3 min,max;
|
|
min = request->mPositions[0];
|
|
max = min;
|
|
|
|
for (U32 i = 0; i < request->mPositions.size(); ++i)
|
|
{
|
|
update_min_max(min, max, request->mPositions[i]);
|
|
}
|
|
|
|
request->mHull.clear();
|
|
|
|
LLModel::hull box;
|
|
box.push_back(LLVector3(min[0],min[1],min[2]));
|
|
box.push_back(LLVector3(max[0],min[1],min[2]));
|
|
box.push_back(LLVector3(min[0],max[1],min[2]));
|
|
box.push_back(LLVector3(max[0],max[1],min[2]));
|
|
box.push_back(LLVector3(min[0],min[1],max[2]));
|
|
box.push_back(LLVector3(max[0],min[1],max[2]));
|
|
box.push_back(LLVector3(min[0],max[1],max[2]));
|
|
box.push_back(LLVector3(max[0],max[1],max[2]));
|
|
|
|
request->mHull.push_back(box);
|
|
}
|
|
|
|
|
|
void LLPhysicsDecomp::doDecompositionSingleHull()
|
|
{
|
|
LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance();
|
|
|
|
if (decomp == NULL)
|
|
{
|
|
//stub. do nothing.
|
|
return;
|
|
}
|
|
|
|
LLCDMeshData mesh;
|
|
|
|
setMeshData(mesh, true);
|
|
|
|
LLCDResult ret = decomp->buildSingleHull() ;
|
|
if(ret)
|
|
{
|
|
LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL;
|
|
make_box(mCurRequest);
|
|
}
|
|
else
|
|
{
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
mCurRequest->mHull.clear();
|
|
mCurRequest->mHull.resize(1);
|
|
mCurRequest->mHullMesh.clear();
|
|
}
|
|
|
|
std::vector<LLVector3> p;
|
|
LLCDHull hull;
|
|
|
|
// if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code
|
|
decomp->getSingleHull(&hull);
|
|
|
|
const F32* v = hull.mVertexBase;
|
|
|
|
for (S32 j = 0; j < hull.mNumVertices; ++j)
|
|
{
|
|
LLVector3 vert(v[0], v[1], v[2]);
|
|
p.push_back(vert);
|
|
v = (F32*) (((U8*) v) + hull.mVertexStrideBytes);
|
|
}
|
|
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
mCurRequest->mHull[0] = p;
|
|
}
|
|
}
|
|
|
|
{
|
|
completeCurrent();
|
|
|
|
}
|
|
}
|
|
|
|
#ifdef ND_HASCONVEXDECOMP_TRACER
|
|
|
|
class ndDecompTracer: public ndConvexDecompositionTracer
|
|
{
|
|
int mRefCount;
|
|
|
|
public:
|
|
ndDecompTracer()
|
|
: mRefCount(0)
|
|
{
|
|
}
|
|
|
|
virtual ~ndDecompTracer() { }
|
|
|
|
virtual void trace( char const *a_strMsg )
|
|
{
|
|
LL_INFOS() << a_strMsg << LL_ENDL;
|
|
}
|
|
|
|
virtual void startTraceData( char const *a_strWhat)
|
|
{
|
|
LL_INFOS() << a_strWhat << LL_ENDL;
|
|
}
|
|
|
|
virtual void traceData( char const *a_strData )
|
|
{
|
|
LL_INFOS() << a_strData << LL_ENDL;
|
|
}
|
|
|
|
virtual void endTraceData()
|
|
{
|
|
|
|
}
|
|
|
|
virtual int getLevel()
|
|
{
|
|
return eTraceFunctions;// | eTraceData;
|
|
}
|
|
|
|
virtual void addref()
|
|
{
|
|
++mRefCount;
|
|
}
|
|
|
|
virtual void release()
|
|
{
|
|
--mRefCount;
|
|
if( mRefCount == 0 )
|
|
delete this;
|
|
}
|
|
};
|
|
|
|
#endif
|
|
|
|
void LLPhysicsDecomp::run()
|
|
{
|
|
LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance();
|
|
if (decomp == NULL)
|
|
{
|
|
// stub library. Set init to true so the main thread
|
|
// doesn't wait for this to finish.
|
|
mInited = true;
|
|
return;
|
|
}
|
|
|
|
#ifdef ND_HASCONVEXDECOMP_TRACER
|
|
ndConvexDecompositionTracable *pTraceable = dynamic_cast< ndConvexDecompositionTracable* >( decomp );
|
|
|
|
if( pTraceable )
|
|
pTraceable->setTracer( new ndDecompTracer() );
|
|
#endif
|
|
|
|
decomp->initThread();
|
|
mInited = true;
|
|
|
|
static const LLCDStageData* stages = NULL;
|
|
static S32 num_stages = 0;
|
|
|
|
if (!stages)
|
|
{
|
|
num_stages = decomp->getStages(&stages);
|
|
}
|
|
|
|
for (S32 i = 0; i < num_stages; i++)
|
|
{
|
|
mStageID[stages[i].mName] = i;
|
|
}
|
|
|
|
mSignal->lock();
|
|
|
|
while (!mQuitting)
|
|
{
|
|
mSignal->wait();
|
|
while (!mQuitting && !mRequestQ.empty())
|
|
{
|
|
{
|
|
LLMutexLock lock(mMutex);
|
|
mCurRequest = mRequestQ.front();
|
|
mRequestQ.pop();
|
|
}
|
|
|
|
S32& id = *(mCurRequest->mDecompID);
|
|
if (id == -1)
|
|
{
|
|
decomp->genDecomposition(id);
|
|
}
|
|
decomp->bindDecomposition(id);
|
|
|
|
if (mCurRequest->mStage == "single_hull")
|
|
{
|
|
doDecompositionSingleHull();
|
|
}
|
|
else
|
|
{
|
|
doDecomposition();
|
|
}
|
|
}
|
|
}
|
|
|
|
decomp->quitThread();
|
|
|
|
if (mSignal->isLocked())
|
|
{ //let go of mSignal's associated mutex
|
|
mSignal->unlock();
|
|
}
|
|
|
|
mDone = true;
|
|
}
|
|
|
|
void LLPhysicsDecomp::Request::assignData(LLModel* mdl)
|
|
{
|
|
if (!mdl)
|
|
{
|
|
return ;
|
|
}
|
|
|
|
U16 index_offset = 0;
|
|
U16 tri[3] ;
|
|
|
|
mPositions.clear();
|
|
mIndices.clear();
|
|
mBBox[1] = LLVector3(F32_MIN, F32_MIN, F32_MIN) ;
|
|
mBBox[0] = LLVector3(F32_MAX, F32_MAX, F32_MAX) ;
|
|
|
|
//queue up vertex positions and indices
|
|
for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i)
|
|
{
|
|
const LLVolumeFace& face = mdl->getVolumeFace(i);
|
|
if (mPositions.size() + face.mNumVertices > 65535)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (U32 j = 0; j < (U32)face.mNumVertices; ++j)
|
|
{
|
|
mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr()));
|
|
for(U32 k = 0 ; k < 3 ; k++)
|
|
{
|
|
mBBox[0].mV[k] = llmin(mBBox[0].mV[k], mPositions[j].mV[k]) ;
|
|
mBBox[1].mV[k] = llmax(mBBox[1].mV[k], mPositions[j].mV[k]) ;
|
|
}
|
|
}
|
|
|
|
updateTriangleAreaThreshold() ;
|
|
|
|
for (U32 j = 0; j+2 < (U32)face.mNumIndices; j += 3)
|
|
{
|
|
tri[0] = face.mIndices[j] + index_offset ;
|
|
tri[1] = face.mIndices[j + 1] + index_offset ;
|
|
tri[2] = face.mIndices[j + 2] + index_offset ;
|
|
|
|
if(isValidTriangle(tri[0], tri[1], tri[2]))
|
|
{
|
|
mIndices.push_back(tri[0]);
|
|
mIndices.push_back(tri[1]);
|
|
mIndices.push_back(tri[2]);
|
|
}
|
|
}
|
|
|
|
index_offset += face.mNumVertices;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
void LLPhysicsDecomp::Request::updateTriangleAreaThreshold()
|
|
{
|
|
F32 range = mBBox[1].mV[0] - mBBox[0].mV[0] ;
|
|
range = llmin(range, mBBox[1].mV[1] - mBBox[0].mV[1]) ;
|
|
range = llmin(range, mBBox[1].mV[2] - mBBox[0].mV[2]) ;
|
|
|
|
mTriangleAreaThreshold = llmin(0.0002f, range * 0.000002f) ;
|
|
}
|
|
|
|
//check if the triangle area is large enough to qualify for a valid triangle
|
|
bool LLPhysicsDecomp::Request::isValidTriangle(U16 idx1, U16 idx2, U16 idx3)
|
|
{
|
|
LLVector3 a = mPositions[idx2] - mPositions[idx1] ;
|
|
LLVector3 b = mPositions[idx3] - mPositions[idx1] ;
|
|
F32 c = a * b ;
|
|
|
|
return ((a*a) * (b*b) - c * c) > mTriangleAreaThreshold ;
|
|
}
|
|
|
|
void LLPhysicsDecomp::Request::setStatusMessage(const std::string& msg)
|
|
{
|
|
mStatusMessage = msg;
|
|
}
|
|
|
|
void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp)
|
|
{
|
|
decomp.mMesh.resize(decomp.mHull.size());
|
|
|
|
for (U32 i = 0; i < decomp.mHull.size(); ++i)
|
|
{
|
|
LLCDHull hull;
|
|
hull.mNumVertices = decomp.mHull[i].size();
|
|
hull.mVertexBase = decomp.mHull[i][0].mV;
|
|
hull.mVertexStrideBytes = 12;
|
|
|
|
LLCDMeshData mesh;
|
|
LLCDResult res = LLCD_OK;
|
|
if (LLConvexDecomposition::getInstance() != NULL)
|
|
{
|
|
res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh);
|
|
}
|
|
if (res == LLCD_OK)
|
|
{
|
|
get_vertex_buffer_from_mesh(mesh, decomp.mMesh[i]);
|
|
}
|
|
}
|
|
|
|
if (!decomp.mBaseHull.empty() && decomp.mBaseHullMesh.empty())
|
|
{ //get mesh for base hull
|
|
LLCDHull hull;
|
|
hull.mNumVertices = decomp.mBaseHull.size();
|
|
hull.mVertexBase = decomp.mBaseHull[0].mV;
|
|
hull.mVertexStrideBytes = 12;
|
|
|
|
LLCDMeshData mesh;
|
|
LLCDResult res = LLCD_OK;
|
|
if (LLConvexDecomposition::getInstance() != NULL)
|
|
{
|
|
res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh);
|
|
}
|
|
if (res == LLCD_OK)
|
|
{
|
|
get_vertex_buffer_from_mesh(mesh, decomp.mBaseHullMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool LLMeshRepository::meshUploadEnabled()
|
|
{
|
|
LLViewerRegion *region = gAgent.getRegion();
|
|
static const LLCachedControl<bool> mesh_enabled("MeshEnabled");
|
|
if(mesh_enabled &&
|
|
region)
|
|
{
|
|
return region->meshUploadEnabled();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLMeshRepository::meshRezEnabled()
|
|
{
|
|
LLViewerRegion *region = gAgent.getRegion();
|
|
static const LLCachedControl<bool> mesh_enabled("MeshEnabled");
|
|
if(mesh_enabled &&
|
|
region)
|
|
{
|
|
return region->meshRezEnabled();
|
|
}
|
|
return false;
|
|
}
|