summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Payne (Vir Linden) <vir@lindenlab.com>2020-10-15 18:34:37 +0100
committerBrad Payne (Vir Linden) <vir@lindenlab.com>2020-10-15 18:34:37 +0100
commit96f4571f4c8c22c938f349ca1dc4728d49ba3353 (patch)
treec3960ccefec76acf2447f78413db8afdf7f8b9c2
parent00ab8eb6e6f1958a41ec9d6cfbdda0dd85f1dd8a (diff)
parenta2c8c8238cfb109e0da81363995e08e99173426f (diff)
Merge remote-tracking branch 'origin/master' into DRTVWR-517
-rwxr-xr-xdoc/contributions.txt3
-rw-r--r--indra/llcharacter/lljoint.cpp6
-rw-r--r--indra/llcharacter/lljoint.h6
-rw-r--r--indra/llprimitive/lldaeloader.cpp139
-rw-r--r--indra/llprimitive/lldaeloader.h2
-rw-r--r--indra/llprimitive/llmodelloader.cpp38
-rw-r--r--indra/llprimitive/llmodelloader.h18
-rw-r--r--indra/llui/llbutton.cpp27
-rw-r--r--indra/llui/llbutton.h1
-rw-r--r--indra/llui/llscrolllistctrl.cpp3
-rw-r--r--indra/llui/llscrolllistctrl.h1
-rw-r--r--indra/llui/lltabcontainer.cpp13
-rw-r--r--indra/llui/lltabcontainer.h8
-rw-r--r--indra/llui/lltextbase.cpp12
-rw-r--r--indra/llui/lltextbase.h2
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rw-r--r--indra/newview/app_settings/settings.xml6
-rw-r--r--indra/newview/app_settings/shaders/class1/objects/previewV.glsl1
-rw-r--r--indra/newview/lldynamictexture.cpp10
-rw-r--r--indra/newview/llfloatermodelpreview.cpp3918
-rw-r--r--indra/newview/llfloatermodelpreview.h270
-rw-r--r--indra/newview/llmodelpreview.cpp3564
-rw-r--r--indra/newview/llmodelpreview.h313
-rw-r--r--indra/newview/llviewermenufile.cpp2
-rw-r--r--indra/newview/llvoavatar.cpp21
-rw-r--r--indra/newview/llvoavatar.h2
-rw-r--r--indra/newview/pipeline.cpp6
-rw-r--r--indra/newview/skins/default/textures/containers/TabTop_Right_Flashing.pngbin0 -> 252 bytes
-rw-r--r--indra/newview/skins/default/textures/textures.xml1
-rw-r--r--indra/newview/skins/default/xui/en/floater_model_preview.xml591
-rw-r--r--indra/newview/skins/default/xui/en/floater_script_debug.xml3
32 files changed, 5183 insertions, 3808 deletions
diff --git a/doc/contributions.txt b/doc/contributions.txt
index e2b69a9fe4..07a96d8766 100755
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -261,6 +261,9 @@ Benjamin Bigdipper
Beq Janus
BUG-227094
Beth Walcher
+Beq Janus
+ SL-10288
+ SL-13583
Bezilon Kasei
Biancaluce Robbiani
CT-225
diff --git a/indra/llcharacter/lljoint.cpp b/indra/llcharacter/lljoint.cpp
index a685df5925..dee642310e 100644
--- a/indra/llcharacter/lljoint.cpp
+++ b/indra/llcharacter/lljoint.cpp
@@ -408,7 +408,7 @@ void showJointScaleOverrides( const LLJoint& joint, const std::string& note, con
bool LLJoint::aboveJointPosThreshold(const LLVector3& pos) const
{
LLVector3 diff = pos - getDefaultPosition();
- const F32 max_joint_pos_offset = 0.0001f; // 0.1 mm
+ const F32 max_joint_pos_offset = LL_JOINT_TRESHOLD_POS_OFFSET; // 0.1 mm
return diff.lengthSquared() > max_joint_pos_offset * max_joint_pos_offset;
}
@@ -511,7 +511,7 @@ void LLJoint::clearAttachmentPosOverrides()
// getAllAttachmentPosOverrides()
//--------------------------------------------------------------------
void LLJoint::getAllAttachmentPosOverrides(S32& num_pos_overrides,
- std::set<LLVector3>& distinct_pos_overrides)
+ std::set<LLVector3>& distinct_pos_overrides) const
{
num_pos_overrides = m_attachmentPosOverrides.count();
LLVector3OverrideMap::map_type::const_iterator it = m_attachmentPosOverrides.getMap().begin();
@@ -525,7 +525,7 @@ void LLJoint::getAllAttachmentPosOverrides(S32& num_pos_overrides,
// getAllAttachmentScaleOverrides()
//--------------------------------------------------------------------
void LLJoint::getAllAttachmentScaleOverrides(S32& num_scale_overrides,
- std::set<LLVector3>& distinct_scale_overrides)
+ std::set<LLVector3>& distinct_scale_overrides) const
{
num_scale_overrides = m_attachmentScaleOverrides.count();
LLVector3OverrideMap::map_type::const_iterator it = m_attachmentScaleOverrides.getMap().begin();
diff --git a/indra/llcharacter/lljoint.h b/indra/llcharacter/lljoint.h
index aa997a4cf7..1b646b641f 100644
--- a/indra/llcharacter/lljoint.h
+++ b/indra/llcharacter/lljoint.h
@@ -53,6 +53,8 @@ const U32 LL_FACE_JOINT_NUM = (LL_CHARACTER_MAX_ANIMATED_JOINTS-2);
const S32 LL_CHARACTER_MAX_PRIORITY = 7;
const F32 LL_MAX_PELVIS_OFFSET = 5.f;
+const F32 LL_JOINT_TRESHOLD_POS_OFFSET = 0.0001f; //0.1 mm
+
class LLVector3OverrideMap
{
public:
@@ -287,9 +289,9 @@ public:
void showAttachmentScaleOverrides(const std::string& av_info) const;
void getAllAttachmentPosOverrides(S32& num_pos_overrides,
- std::set<LLVector3>& distinct_pos_overrides);
+ std::set<LLVector3>& distinct_pos_overrides) const;
void getAllAttachmentScaleOverrides(S32& num_scale_overrides,
- std::set<LLVector3>& distinct_scale_overrides);
+ std::set<LLVector3>& distinct_scale_overrides) const;
// These are used in checks of whether a pos/scale override is considered significant.
bool aboveJointPosThreshold(const LLVector3& pos) const;
diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp
index 139f48fef8..d2aa3d8dc6 100644
--- a/indra/llprimitive/lldaeloader.cpp
+++ b/indra/llprimitive/lldaeloader.cpp
@@ -343,7 +343,7 @@ LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& fa
return LLModel::NO_ERRORS ;
}
-LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domPolylistRef& poly)
+LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domPolylistRef& poly, LLSD& log_msg)
{
domPRef p = poly->getP();
domListOfUInts& idx = p->getValue();
@@ -403,6 +403,7 @@ LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& fac
LLVolumeFace::VertexMapData::PointMap point_map;
U32 cur_idx = 0;
+ bool log_tc_msg = true;
for (U32 i = 0; i < vcount.getCount(); ++i)
{ //for each polygon
U32 first_index = 0;
@@ -426,8 +427,21 @@ LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& fac
if (tc_source)
{
- cv.mTexCoord.setVec(tc[idx[cur_idx+tc_offset]*2+0],
- tc[idx[cur_idx+tc_offset]*2+1]);
+ U64 idx_x = idx[cur_idx + tc_offset] * 2 + 0;
+ U64 idx_y = idx[cur_idx + tc_offset] * 2 + 1;
+
+ if (idx_y < tc.getCount())
+ {
+ cv.mTexCoord.setVec(tc[idx_x], tc[idx_y]);
+ }
+ else if (log_tc_msg)
+ {
+ log_tc_msg = false;
+ LL_WARNS() << "Texture coordinates data is not complete." << LL_ENDL;
+ LLSD args;
+ args["Message"] = "IncompleteTC";
+ log_msg.append(args);
+ }
}
if (norm_source)
@@ -1215,7 +1229,7 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do
for (S32 i = 0; i < childCount; ++i)
{
domNode* pNode = daeSafeCast<domNode>(children[i]);
- if ( isNodeAJoint( pNode ) )
+ if (pNode)
{
processJointNode( pNode, mJointList );
}
@@ -1470,6 +1484,12 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do
}
}
+ U32 bind_count = model->mSkinInfo.mAlternateBindMatrix.size();
+ if (bind_count > 0 && bind_count != jointCnt)
+ {
+ LL_WARNS("Mesh") << "Model " << model->mLabel << " has invalid joint bind matrix list." << LL_ENDL;
+ }
+
//grab raw position array
domVertices* verts = mesh->getVertices();
@@ -1834,59 +1854,61 @@ void LLDAELoader::processJointNode( domNode* pNode, JointTransformMap& jointTran
//LL_WARNS()<<"ProcessJointNode# Node:" <<pNode->getName()<<LL_ENDL;
//1. handle the incoming node - extract out translation via SID or element
+ if (isNodeAJoint(pNode))
+ {
+ LLMatrix4 workingTransform;
- LLMatrix4 workingTransform;
-
- //Pull out the translate id and store it in the jointTranslations map
- daeSIDResolver jointResolverA( pNode, "./translate" );
- domTranslate* pTranslateA = daeSafeCast<domTranslate>( jointResolverA.getElement() );
- daeSIDResolver jointResolverB( pNode, "./location" );
- domTranslate* pTranslateB = daeSafeCast<domTranslate>( jointResolverB.getElement() );
+ //Pull out the translate id and store it in the jointTranslations map
+ daeSIDResolver jointResolverA(pNode, "./translate");
+ domTranslate* pTranslateA = daeSafeCast<domTranslate>(jointResolverA.getElement());
+ daeSIDResolver jointResolverB(pNode, "./location");
+ domTranslate* pTranslateB = daeSafeCast<domTranslate>(jointResolverB.getElement());
- //Translation via SID was successful
- if ( pTranslateA )
- {
- extractTranslation( pTranslateA, workingTransform );
- }
- else
- if ( pTranslateB )
- {
- extractTranslation( pTranslateB, workingTransform );
- }
- else
- {
- //Translation via child from element
- daeElement* pTranslateElement = getChildFromElement( pNode, "translate" );
- if ( !pTranslateElement || pTranslateElement->typeID() != domTranslate::ID() )
- {
- //LL_WARNS()<< "The found element is not a translate node" <<LL_ENDL;
- daeSIDResolver jointResolver( pNode, "./matrix" );
- domMatrix* pMatrix = daeSafeCast<domMatrix>( jointResolver.getElement() );
- if ( pMatrix )
- {
- //LL_INFOS()<<"A matrix SID was however found!"<<LL_ENDL;
- domFloat4x4 domArray = pMatrix->getValue();
- for ( int i = 0; i < 4; i++ )
- {
- for( int j = 0; j < 4; j++ )
- {
- workingTransform.mMatrix[i][j] = domArray[i + j*4];
- }
- }
- }
- else
- {
- LL_WARNS()<< "The found element is not translate or matrix node - most likely a corrupt export!" <<LL_ENDL;
- }
- }
- else
- {
- extractTranslationViaElement( pTranslateElement, workingTransform );
- }
- }
+ //Translation via SID was successful
+ if (pTranslateA)
+ {
+ extractTranslation(pTranslateA, workingTransform);
+ }
+ else
+ if (pTranslateB)
+ {
+ extractTranslation(pTranslateB, workingTransform);
+ }
+ else
+ {
+ //Translation via child from element
+ daeElement* pTranslateElement = getChildFromElement(pNode, "translate");
+ if (!pTranslateElement || pTranslateElement->typeID() != domTranslate::ID())
+ {
+ //LL_WARNS()<< "The found element is not a translate node" <<LL_ENDL;
+ daeSIDResolver jointResolver(pNode, "./matrix");
+ domMatrix* pMatrix = daeSafeCast<domMatrix>(jointResolver.getElement());
+ if (pMatrix)
+ {
+ //LL_INFOS()<<"A matrix SID was however found!"<<LL_ENDL;
+ domFloat4x4 domArray = pMatrix->getValue();
+ for (int i = 0; i < 4; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ workingTransform.mMatrix[i][j] = domArray[i + j * 4];
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS() << "The found element is not translate or matrix node - most likely a corrupt export!" << LL_ENDL;
+ }
+ }
+ else
+ {
+ extractTranslationViaElement(pTranslateElement, workingTransform);
+ }
+ }
- //Store the working transform relative to the nodes name.
- jointTransforms[ pNode->getName() ] = workingTransform;
+ //Store the working transform relative to the nodes name.
+ jointTransforms[pNode->getName()] = workingTransform;
+ }
//2. handle the nodes children
@@ -2356,7 +2378,7 @@ LLColor4 LLDAELoader::getDaeColor(daeElement* element)
return value;
}
-bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh)
+bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh, LLSD& log_msg)
{
LLModel::EModelStatus status = LLModel::NO_ERRORS;
domTriangles_Array& tris = mesh->getTriangles_array();
@@ -2378,7 +2400,7 @@ bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh)
for (U32 i = 0; i < polys.getCount(); ++i)
{
domPolylistRef& poly = polys.get(i);
- status = load_face_from_dom_polylist(pModel->getVolumeFaces(), pModel->getMaterialList(), poly);
+ status = load_face_from_dom_polylist(pModel->getVolumeFaces(), pModel->getMaterialList(), poly, log_msg);
if(status != LLModel::NO_ERRORS)
{
@@ -2442,7 +2464,7 @@ bool LLDAELoader::loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& mo
// Get the whole set of volume faces
//
- addVolumeFacesFromDomMesh(ret, mesh);
+ addVolumeFacesFromDomMesh(ret, mesh, mWarningsArray);
U32 volume_faces = ret->getNumVolumeFaces();
@@ -2515,7 +2537,8 @@ bool LLDAELoader::createVolumeFacesFromDomMesh(LLModel* pModel, domMesh* mesh)
{
pModel->ClearFacesAndMaterials();
- addVolumeFacesFromDomMesh(pModel, mesh);
+ LLSD placeholder;
+ addVolumeFacesFromDomMesh(pModel, mesh, placeholder);
if (pModel->getNumVolumeFaces() > 0)
{
diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h
index 4e990dbe5e..2b211343e1 100644
--- a/indra/llprimitive/lldaeloader.h
+++ b/indra/llprimitive/lldaeloader.h
@@ -89,7 +89,7 @@ protected:
//Verify that a controller matches vertex counts
bool verifyController( domController* pController );
- static bool addVolumeFacesFromDomMesh(LLModel* model, domMesh* mesh);
+ static bool addVolumeFacesFromDomMesh(LLModel* model, domMesh* mesh, LLSD& log_msg);
static bool createVolumeFacesFromDomMesh(LLModel* model, domMesh *mesh);
static LLModel* loadModelFromDomMesh(domMesh* mesh);
diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp
index 4e468ff45f..5171621007 100644
--- a/indra/llprimitive/llmodelloader.cpp
+++ b/indra/llprimitive/llmodelloader.cpp
@@ -127,7 +127,7 @@ LLModelLoader::LLModelLoader(
, mStateCallback(state_cb)
, mOpaqueData(opaque_userdata)
, mRigValidJointUpload(true)
-, mLegacyRigValid(true)
+, mLegacyRigFlags(0)
, mNoNormalize(false)
, mNoOptimize(false)
, mCacheOnlyHitIfRigged(false)
@@ -136,6 +136,7 @@ LLModelLoader::LLModelLoader(
{
assert_main_thread();
sActiveLoaderList.push_back(this) ;
+ mWarningsArray = LLSD::emptyArray();
}
LLModelLoader::~LLModelLoader()
@@ -146,6 +147,7 @@ LLModelLoader::~LLModelLoader()
void LLModelLoader::run()
{
+ mWarningsArray.clear();
doLoadModel();
doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this));
}
@@ -387,7 +389,7 @@ void LLModelLoader::critiqueRigForUploadApplicability( const std::vector<std::st
//2. It is suitable for upload as standard av with just skin weights
bool isJointPositionUploadOK = isRigSuitableForJointPositionUpload( jointListFromAsset );
- bool isRigLegacyOK = isRigLegacy( jointListFromAsset );
+ U32 legacy_rig_flags = determineRigLegacyFlags( jointListFromAsset );
// It's OK that both could end up being true.
@@ -401,19 +403,16 @@ void LLModelLoader::critiqueRigForUploadApplicability( const std::vector<std::st
setRigValidForJointPositionUpload( false );
}
- if ( !isRigLegacyOK)
- {
- // This starts out true, becomes false if false for any loaded
- // mesh.
- setLegacyRigValid( false );
- }
+ legacy_rig_flags |= getLegacyRigFlags();
+ // This starts as 0, changes if any loaded mesh has issues
+ setLegacyRigFlags(legacy_rig_flags);
}
//-----------------------------------------------------------------------------
-// isRigLegacy()
+// determineRigLegacyFlags()
//-----------------------------------------------------------------------------
-bool LLModelLoader::isRigLegacy( const std::vector<std::string> &jointListFromAsset )
+U32 LLModelLoader::determineRigLegacyFlags( const std::vector<std::string> &jointListFromAsset )
{
//No joints in asset
if ( jointListFromAsset.size() == 0 )
@@ -426,7 +425,12 @@ bool LLModelLoader::isRigLegacy( const std::vector<std::string> &jointListFromAs
{
LL_WARNS() << "Rigged to " << jointListFromAsset.size() << " joints, max is " << mMaxJointsPerMesh << LL_ENDL;
LL_WARNS() << "Skinning disabled due to too many joints" << LL_ENDL;
- return false;
+ LLSD args;
+ args["Message"] = "TooManyJoint";
+ args["[JOINTS]"] = LLSD::Integer(jointListFromAsset.size());
+ args["[MAX]"] = LLSD::Integer(mMaxJointsPerMesh);
+ mWarningsArray.append(args);
+ return LEGACY_RIG_FLAG_TOO_MANY_JOINTS;
}
// Unknown joints in asset
@@ -437,16 +441,24 @@ bool LLModelLoader::isRigLegacy( const std::vector<std::string> &jointListFromAs
if (mJointMap.find(*it)==mJointMap.end())
{
LL_WARNS() << "Rigged to unrecognized joint name " << *it << LL_ENDL;
+ LLSD args;
+ args["Message"] = "UnrecognizedJoint";
+ args["[NAME]"] = *it;
+ mWarningsArray.append(args);
unknown_joint_count++;
}
}
if (unknown_joint_count>0)
{
LL_WARNS() << "Skinning disabled due to unknown joints" << LL_ENDL;
- return false;
+ LLSD args;
+ args["Message"] = "UnknownJoints";
+ args["[COUNT]"] = LLSD::Integer(unknown_joint_count);
+ mWarningsArray.append(args);
+ return LEGACY_RIG_FLAG_UNKNOWN_JOINT;
}
- return true;
+ return LEGACY_RIG_OK;
}
//-----------------------------------------------------------------------------
// isRigSuitableForJointPositionUpload()
diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h
index 643c45a6d8..fbc74554a0 100644
--- a/indra/llprimitive/llmodelloader.h
+++ b/indra/llprimitive/llmodelloader.h
@@ -42,6 +42,10 @@ typedef std::deque<std::string> JointNameSet;
const S32 SLM_SUPPORTED_VERSION = 3;
const S32 NUM_LOD = 4;
+const U32 LEGACY_RIG_OK = 0;
+const U32 LEGACY_RIG_FLAG_TOO_MANY_JOINTS = 1;
+const U32 LEGACY_RIG_FLAG_UNKNOWN_JOINT = 2;
+
class LLModelLoader : public LLThread
{
public:
@@ -166,7 +170,7 @@ public:
void critiqueRigForUploadApplicability( const std::vector<std::string> &jointListFromAsset );
//Determines if a rig is a legacy from the joint list
- bool isRigLegacy( const std::vector<std::string> &jointListFromAsset );
+ U32 determineRigLegacyFlags( const std::vector<std::string> &jointListFromAsset );
//Determines if a rig is suitable for upload
bool isRigSuitableForJointPositionUpload( const std::vector<std::string> &jointListFromAsset );
@@ -174,8 +178,9 @@ public:
const bool isRigValidForJointPositionUpload( void ) const { return mRigValidJointUpload; }
void setRigValidForJointPositionUpload( bool rigValid ) { mRigValidJointUpload = rigValid; }
- const bool isLegacyRigValid( void ) const { return mLegacyRigValid; }
- void setLegacyRigValid( bool rigValid ) { mLegacyRigValid = rigValid; }
+ const bool isLegacyRigValid(void) const { return mLegacyRigFlags == 0; }
+ U32 getLegacyRigFlags() const { return mLegacyRigFlags; }
+ void setLegacyRigFlags( U32 rigFlags ) { mLegacyRigFlags = rigFlags; }
//-----------------------------------------------------------------------------
// isNodeAJoint()
@@ -185,6 +190,9 @@ public:
return name != NULL && mJointMap.find(name) != mJointMap.end();
}
+ const LLSD logOut() const { return mWarningsArray; }
+ void clearLog() { mWarningsArray.clear(); }
+
protected:
LLModelLoader::load_callback_t mLoadCallback;
@@ -194,13 +202,15 @@ protected:
void* mOpaqueData;
bool mRigValidJointUpload;
- bool mLegacyRigValid;
+ U32 mLegacyRigFlags;
bool mNoNormalize;
bool mNoOptimize;
JointTransformMap mJointTransformMap;
+ LLSD mWarningsArray; // preview floater will pull logs from here
+
static std::list<LLModelLoader*> sActiveLoaderList;
static bool isAlive(LLModelLoader* loader) ;
};
diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp
index 27444b7f5b..9682c3bc10 100644
--- a/indra/llui/llbutton.cpp
+++ b/indra/llui/llbutton.cpp
@@ -643,7 +643,8 @@ void LLButton::draw()
LLColor4 highlighting_color = LLColor4::white;
LLColor4 glow_color = LLColor4::white;
LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA;
- LLUIImage* imagep = NULL;
+ LLUIImage* imagep = NULL;
+ LLUIImage* image_glow = NULL;
// Cancel sticking of color, if the button is pressed,
// or when a flashing of the previously selected button is ended
@@ -710,17 +711,18 @@ void LLButton::draw()
imagep = mImageDisabled;
}
+ image_glow = imagep;
+
if (mFlashing)
{
- // if button should flash and we have icon for flashing, use it as image for button
- if(flash && mImageFlash)
+ if (flash && mImageFlash)
{
- // setting flash to false to avoid its further influence on glow
- flash = false;
- imagep = mImageFlash;
+ // if button should flash and we have icon for flashing, use it as image for button
+ image_glow = mImageFlash;
}
- // else use usual flashing via flash_color
- else if (mFlashingTimer)
+
+ // provide fade-in and fade-out via flash_color
+ if (mFlashingTimer)
{
LLColor4 flash_color = mFlashBgColor.get();
use_glow_effect = TRUE;
@@ -734,6 +736,11 @@ void LLButton::draw()
{
glow_color = highlighting_color;
}
+ else
+ {
+ // will fade from highlight color
+ glow_color = flash_color;
+ }
}
}
@@ -806,7 +813,7 @@ void LLButton::draw()
if (mCurGlowStrength > 0.01f)
{
gGL.setSceneBlendType(glow_type);
- imagep->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % (mCurGlowStrength * alpha));
+ image_glow->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % (mCurGlowStrength * alpha));
gGL.setSceneBlendType(LLRender::BT_ALPHA);
}
}
@@ -817,7 +824,7 @@ void LLButton::draw()
if (mCurGlowStrength > 0.01f)
{
gGL.setSceneBlendType(glow_type);
- imagep->drawSolid(0, y, glow_color % (mCurGlowStrength * alpha));
+ image_glow->drawSolid(0, y, glow_color % (mCurGlowStrength * alpha));
gGL.setSceneBlendType(LLRender::BT_ALPHA);
}
}
diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h
index 7629ed1fea..572d36996c 100644
--- a/indra/llui/llbutton.h
+++ b/indra/llui/llbutton.h
@@ -205,6 +205,7 @@ public:
void setFlashing( bool b, bool force_flashing = false );
BOOL getFlashing() const { return mFlashing; }
LLFlashTimer* getFlashTimer() {return mFlashingTimer;}
+ void setFlashColor(const LLUIColor &color) { mFlashBgColor = color; };
void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; }
LLFontGL::HAlign getHAlign() const { return mHAlign; }
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index 763c3aeb81..367c6c3c5b 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -132,6 +132,7 @@ LLScrollListCtrl::Params::Params()
sort_ascending("sort_ascending", true),
mouse_wheel_opaque("mouse_wheel_opaque", false),
commit_on_keyboard_movement("commit_on_keyboard_movement", true),
+ commit_on_selection_change("commit_on_selection_change", false),
heading_height("heading_height"),
page_lines("page_lines", 0),
background_visible("background_visible"),
@@ -162,7 +163,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
mMaxSelectable(0),
mAllowKeyboardMovement(true),
mCommitOnKeyboardMovement(p.commit_on_keyboard_movement),
- mCommitOnSelectionChange(false),
+ mCommitOnSelectionChange(p.commit_on_selection_change),
mSelectionChanged(false),
mNeedsScroll(false),
mCanSelect(true),
diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h
index 43e1c0d707..8d00296183 100644
--- a/indra/llui/llscrolllistctrl.h
+++ b/indra/llui/llscrolllistctrl.h
@@ -97,6 +97,7 @@ public:
// behavioral flags
Optional<bool> multi_select,
commit_on_keyboard_movement,
+ commit_on_selection_change,
mouse_wheel_opaque;
// display flags
diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp
index 6521b883f8..e6b43da8e5 100644
--- a/indra/llui/lltabcontainer.cpp
+++ b/indra/llui/lltabcontainer.cpp
@@ -220,6 +220,8 @@ LLTabContainer::Params::Params()
last_tab("last_tab"),
use_custom_icon_ctrl("use_custom_icon_ctrl", false),
open_tabs_on_drag_and_drop("open_tabs_on_drag_and_drop", false),
+ enable_tabs_flashing("enable_tabs_flashing", false),
+ tabs_flashing_color("tabs_flashing_color"),
tab_icon_ctrl_pad("tab_icon_ctrl_pad", 0),
use_ellipses("use_ellipses"),
font_halign("halign")
@@ -259,6 +261,8 @@ LLTabContainer::LLTabContainer(const LLTabContainer::Params& p)
mCustomIconCtrlUsed(p.use_custom_icon_ctrl),
mOpenTabsOnDragAndDrop(p.open_tabs_on_drag_and_drop),
mTabIconCtrlPad(p.tab_icon_ctrl_pad),
+ mEnableTabsFlashing(p.enable_tabs_flashing),
+ mTabsFlashingColor(p.tabs_flashing_color),
mUseTabEllipses(p.use_ellipses)
{
static LLUICachedControl<S32> tabcntr_vert_tab_min_width ("UITabCntrVertTabMinWidth", 0);
@@ -280,6 +284,11 @@ LLTabContainer::LLTabContainer(const LLTabContainer::Params& p)
mMinTabWidth = tabcntr_vert_tab_min_width;
}
+ if (p.tabs_flashing_color.isProvided())
+ {
+ mEnableTabsFlashing = true;
+ }
+
initButtons( );
}
@@ -1102,6 +1111,10 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel)
p.pad_left( mLabelPadLeft );
p.pad_right(2);
}
+
+ // inits flash timer
+ p.button_flash_enable = mEnableTabsFlashing;
+ p.flash_color = mTabsFlashingColor;
// *TODO : It seems wrong not to use p in both cases considering the way p is initialized
if (mCustomIconCtrlUsed)
diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h
index 6bf963313c..8f8cedb1b9 100644
--- a/indra/llui/lltabcontainer.h
+++ b/indra/llui/lltabcontainer.h
@@ -109,6 +109,12 @@ public:
* Open tabs on hover in drag and drop situations
*/
Optional<bool> open_tabs_on_drag_and_drop;
+
+ /**
+ * Enable tab flashing
+ */
+ Optional<bool> enable_tabs_flashing;
+ Optional<LLUIColor> tabs_flashing_color;
/**
* Paddings for LLIconCtrl in case of LLCustomButtonIconCtrl usage(use_custom_icon_ctrl = true)
@@ -310,6 +316,8 @@ private:
bool mCustomIconCtrlUsed;
bool mOpenTabsOnDragAndDrop;
+ bool mEnableTabsFlashing;
+ LLUIColor mTabsFlashingColor;
S32 mTabIconCtrlPad;
bool mUseTabEllipses;
};
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index 30bf938591..ff72417867 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -2259,6 +2259,18 @@ void LLTextBase::needsReflow(S32 index)
mReflowIndex = llmin(mReflowIndex, index);
}
+S32 LLTextBase::removeFirstLine()
+{
+ if (!mLineInfoList.empty())
+ {
+ S32 length = getLineEnd(0);
+ deselect();
+ removeStringNoUndo(0, length);
+ return length;
+ }
+ return 0;
+}
+
void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params)
{
segment_vec_t segments;
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index 8687e7aa2a..4e966b7cef 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -404,6 +404,7 @@ public:
virtual void setText(const LLStringExplicit &utf8str , const LLStyle::Params& input_params = LLStyle::Params()); // uses default style
virtual std::string getText() const;
void setMaxTextLength(S32 length) { mMaxTextByteLength = length; }
+ S32 getMaxTextLength() { return mMaxTextByteLength; }
// wide-char versions
void setWText(const LLWString& text);
@@ -432,6 +433,7 @@ public:
S32 getLength() const { return getWText().length(); }
S32 getLineCount() const { return mLineInfoList.size(); }
+ S32 removeFirstLine(); // returns removed length
void addDocumentChild(LLView* view);
void removeDocumentChild(LLView* view);
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7d4ec7ac38..fa148f1719 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -401,6 +401,7 @@ set(viewer_SOURCE_FILES
llmenuoptionpathfindingrebakenavmesh.cpp
llmeshrepository.cpp
llmimetypes.cpp
+ llmodelpreview.cpp
llmorphview.cpp
llmoveview.cpp
llmutelist.cpp
@@ -1032,6 +1033,7 @@ set(viewer_HEADER_FILES
llmenuoptionpathfindingrebakenavmesh.h
llmeshrepository.h
llmimetypes.h
+ llmodelpreview.h
llmorphview.h
llmoveview.h
llmutelist.h
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index b03e20c456..e5a66bad38 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-6.4.10
+6.4.11
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 8d5a97d1cb..52dc4744f2 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -6708,7 +6708,7 @@
<integer>600</integer>
</map>
<key>MigrateCacheDirectory</key>
- <map>
+ <map>
<key>Comment</key>
<string>Check for old version of disk cache to migrate to current location</string>
<key>Persist</key>
@@ -7958,7 +7958,6 @@
<key>Value</key>
<integer>13</integer>
</map>
-
<key>PreviewAmbientColor</key>
<map>
<key>Comment</key>
@@ -7975,8 +7974,6 @@
<real>1.0</real>
</array>
</map>
-
-
<key>PreviewDiffuse0</key>
<map>
<key>Comment</key>
@@ -16607,3 +16604,4 @@
</map>
</llsd>
+
diff --git a/indra/newview/app_settings/shaders/class1/objects/previewV.glsl b/indra/newview/app_settings/shaders/class1/objects/previewV.glsl
index 88959266c8..4bb588335a 100644
--- a/indra/newview/app_settings/shaders/class1/objects/previewV.glsl
+++ b/indra/newview/app_settings/shaders/class1/objects/previewV.glsl
@@ -93,6 +93,5 @@ void main()
col.rgb += light_diffuse[1].rgb * calcDirectionalLight(norm, light_position[1].xyz);
col.rgb += light_diffuse[2].rgb*calcLocalLight(pos.xyz, norm, light_position[2], light_direction[2], light_attenuation[2].x, light_attenuation[2].z);
col.rgb += light_diffuse[3].rgb*calcLocalLight(pos.xyz, norm, light_position[3], light_direction[3], light_attenuation[3].x, light_attenuation[3].z);
-
vertex_color = col*color;
}
diff --git a/indra/newview/lldynamictexture.cpp b/indra/newview/lldynamictexture.cpp
index 1e8c57ac6a..89c20904c1 100644
--- a/indra/newview/lldynamictexture.cpp
+++ b/indra/newview/lldynamictexture.cpp
@@ -125,11 +125,11 @@ BOOL LLViewerDynamicTexture::render()
//-----------------------------------------------------------------------------
void LLViewerDynamicTexture::preRender(BOOL clear_depth)
{
- //only images up to 1024*1024 are supported
- llassert(mFullHeight <= 512);
- llassert(mFullWidth <= 512);
+ gPipeline.allocatePhysicsBuffer();
+ llassert(mFullWidth <= static_cast<S32>(gPipeline.mPhysicsDisplay.getWidth()));
+ llassert(mFullHeight <= static_cast<S32>(gPipeline.mPhysicsDisplay.getHeight()));
- if (gGLManager.mHasFramebufferObject && gPipeline.mBake.isComplete())
+ if (gGLManager.mHasFramebufferObject && gPipeline.mPhysicsDisplay.isComplete() && !gGLManager.mIsATI)
{ //using offscreen render target, just use the bottom left corner
mOrigin.set(0, 0);
}
@@ -216,7 +216,7 @@ BOOL LLViewerDynamicTexture::updateAllInstances()
return TRUE;
}
- bool use_fbo = gGLManager.mHasFramebufferObject && gPipeline.mBake.isComplete();
+ bool use_fbo = gGLManager.mHasFramebufferObject && gPipeline.mBake.isComplete() && !gGLManager.mIsATI;
if (use_fbo)
{
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index bc44e37c5a..b9c03f66a3 100644
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -27,7 +27,7 @@
#include "llviewerprecompiledheaders.h"
#include "llmodelloader.h"
-#include "lldaeloader.h"
+#include "llmodelpreview.h"
#include "llfloatermodelpreview.h"
@@ -40,15 +40,7 @@
#include "llagent.h"
#include "llbutton.h"
#include "llcombobox.h"
-#include "lldatapacker.h"
-#include "lldrawable.h"
-#include "llrender.h"
-#include "llface.h"
#include "llfocusmgr.h"
-#include "llfloaterperms.h"
-#include "lliconctrl.h"
-#include "llmatrix4a.h"
-#include "llmenubutton.h"
#include "llmeshrepository.h"
#include "llnotificationsutil.h"
#include "llsdutil_math.h"
@@ -56,44 +48,26 @@
#include "lltextbox.h"
#include "lltoolmgr.h"
#include "llui.h"
-#include "llvector4a.h"
-#include "llviewercamera.h"
#include "llviewerwindow.h"
-#include "llvoavatar.h"
-#include "llvoavatarself.h"
#include "pipeline.h"
-#include "lluictrlfactory.h"
#include "llviewercontrol.h"
-#include "llviewermenu.h"
-#include "llviewermenufile.h"
-#include "llviewerregion.h"
-#include "llviewertexturelist.h"
+#include "llviewermenufile.h" //LLFilePickerThread
#include "llstring.h"
#include "llbutton.h"
#include "llcheckboxctrl.h"
-#include "llradiogroup.h"
-#include "llsdserialize.h"
#include "llsliderctrl.h"
#include "llspinctrl.h"
-#include "lltoggleablemenu.h"
+#include "lltabcontainer.h"
#include "lltrans.h"
-#include "llvfile.h"
-#include "llvfs.h"
#include "llcallbacklist.h"
-#include "llviewerobjectlist.h"
-#include "llanimationstates.h"
+#include "llviewertexteditor.h"
#include "llviewernetwork.h"
-#include "llviewershadermgr.h"
-#include "glod/glod.h"
-#include <boost/algorithm/string.hpp>
//static
S32 LLFloaterModelPreview::sUploadAmount = 10;
LLFloaterModelPreview* LLFloaterModelPreview::sInstance = NULL;
-bool LLModelPreview::sIgnoreLoadedCallback = false;
-
// "Retain%" decomp parameter has values from 0.0 to 1.0 by 0.01
// But according to the UI spec for upload model floater, this parameter
// should be represented by Retain spinner with values from 1 to 100 by 1.
@@ -106,114 +80,20 @@ const double RETAIN_COEFFICIENT = 100;
// should be represented by Smooth combobox with only 10 values.
// So this const is used as a size of Smooth combobox list.
const S32 SMOOTH_VALUES_NUMBER = 10;
+const S32 PREVIEW_RENDER_SIZE = 1024;
+const F32 PREVIEW_CAMERA_DISTANCE = 16.f;
-// mCameraDistance
-// Also see: mCameraZoom
-const F32 MODEL_PREVIEW_CAMERA_DISTANCE = 16.f;
-
-void drawBoxOutline(const LLVector3& pos, const LLVector3& size);
-
-
-std::string lod_name[NUM_LOD+1] =
-{
- "lowest",
- "low",
- "medium",
- "high",
- "I went off the end of the lod_name array. Me so smart."
-};
-
-std::string lod_triangles_name[NUM_LOD+1] =
-{
- "lowest_triangles",
- "low_triangles",
- "medium_triangles",
- "high_triangles",
- "I went off the end of the lod_triangles_name array. Me so smart."
-};
-
-std::string lod_vertices_name[NUM_LOD+1] =
+class LLMeshFilePicker : public LLFilePickerThread
{
- "lowest_vertices",
- "low_vertices",
- "medium_vertices",
- "high_vertices",
- "I went off the end of the lod_vertices_name array. Me so smart."
-};
+public:
+ LLMeshFilePicker(LLModelPreview* mp, S32 lod);
+ virtual void notify(const std::vector<std::string>& filenames);
-std::string lod_status_name[NUM_LOD+1] =
-{
- "lowest_status",
- "low_status",
- "medium_status",
- "high_status",
- "I went off the end of the lod_status_name array. Me so smart."
+private:
+ LLModelPreview* mMP;
+ S32 mLOD;
};
-std::string lod_icon_name[NUM_LOD+1] =
-{
- "status_icon_lowest",
- "status_icon_low",
- "status_icon_medium",
- "status_icon_high",
- "I went off the end of the lod_status_name array. Me so smart."
-};
-
-std::string lod_status_image[NUM_LOD+1] =
-{
- "ModelImport_Status_Good",
- "ModelImport_Status_Warning",
- "ModelImport_Status_Error",
- "I went off the end of the lod_status_image array. Me so smart."
-};
-
-std::string lod_label_name[NUM_LOD+1] =
-{
- "lowest_label",
- "low_label",
- "medium_label",
- "high_label",
- "I went off the end of the lod_label_name array. Me so smart."
-};
-
-BOOL stop_gloderror()
-{
- GLuint error = glodGetError();
-
- if (error != GLOD_NO_ERROR)
- {
- LL_WARNS() << "GLOD error detected, cannot generate LOD: " << std::hex << error << LL_ENDL;
- return TRUE;
- }
-
- return FALSE;
-}
-
-LLViewerFetchedTexture* bindMaterialDiffuseTexture(const LLImportMaterial& material)
-{
- LLViewerFetchedTexture *texture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap(), FTT_DEFAULT, TRUE, LLGLTexture::BOOST_PREVIEW);
-
- if (texture)
- {
- if (texture->getDiscardLevel() > -1)
- {
- gGL.getTexUnit(0)->bind(texture, true);
- return texture;
- }
- }
-
- return NULL;
-}
-
-std::string stripSuffix(std::string name)
-{
- if ((name.find("_LOD") != -1) || (name.find("_PHYS") != -1))
- {
- return name.substr(0, name.rfind('_'));
- }
- return name;
-}
-
LLMeshFilePicker::LLMeshFilePicker(LLModelPreview* mp, S32 lod)
: LLFilePickerThread(LLFilePicker::FFLOAD_COLLADA)
{
@@ -234,37 +114,16 @@ void LLMeshFilePicker::notify(const std::vector<std::string>& filenames)
}
}
-void FindModel(LLModelLoader::scene& scene, const std::string& name_to_match, LLModel*& baseModelOut, LLMatrix4& matOut)
-{
- LLModelLoader::scene::iterator base_iter = scene.begin();
- bool found = false;
- while (!found && (base_iter != scene.end()))
- {
- matOut = base_iter->first;
-
- LLModelLoader::model_instance_list::iterator base_instance_iter = base_iter->second.begin();
- while (!found && (base_instance_iter != base_iter->second.end()))
- {
- LLModelInstance& base_instance = *base_instance_iter++;
- LLModel* base_model = base_instance.mModel;
-
- if (base_model && (base_model->mLabel == name_to_match))
- {
- baseModelOut = base_model;
- return;
- }
- }
- base_iter++;
- }
-}
-
//-----------------------------------------------------------------------------
// LLFloaterModelPreview()
//-----------------------------------------------------------------------------
LLFloaterModelPreview::LLFloaterModelPreview(const LLSD& key) :
LLFloaterModelUploadBase(key),
mUploadBtn(NULL),
-mCalculateBtn(NULL)
+mCalculateBtn(NULL),
+mUploadLogText(NULL),
+mTabContainer(NULL),
+mAvatarTabIndex(0)
{
sInstance = this;
mLastMouseX = 0;
@@ -307,10 +166,11 @@ BOOL LLFloaterModelPreview::postBuild()
getChild<LLSpinCtrl>("lod_triangle_limit_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLODParamCommit, this, lod, true));
}
- childSetCommitCallback("upload_skin", boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this), NULL);
- childSetCommitCallback("upload_joints", boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this), NULL);
- childSetCommitCallback("lock_scale_if_joint_position", boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this), NULL);
- childSetCommitCallback("upload_textures", boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this), NULL);
+ // Upload/avatar options, they need to refresh errors/notifications
+ childSetCommitCallback("upload_skin", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL);
+ childSetCommitCallback("upload_joints", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL);
+ childSetCommitCallback("lock_scale_if_joint_position", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL);
+ childSetCommitCallback("upload_textures", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL);
childSetTextArg("status", "[STATUS]", getString("status_idle"));
@@ -321,10 +181,6 @@ BOOL LLFloaterModelPreview::postBuild()
childSetCommitCallback("preview_lod_combo", onPreviewLODCommit, this);
- childSetCommitCallback("upload_skin", onUploadSkinCommit, this);
- childSetCommitCallback("upload_joints", onUploadJointsCommit, this);
- childSetCommitCallback("lock_scale_if_joint_position", onUploadJointsCommit, this);
-
childSetCommitCallback("import_scale", onImportScaleCommit, this);
childSetCommitCallback("pelvis_offset", onPelvisOffsetCommit, this);
@@ -333,13 +189,20 @@ BOOL LLFloaterModelPreview::postBuild()
getChild<LLCheckBoxCtrl>("show_edges")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1));
getChild<LLCheckBoxCtrl>("show_physics")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1));
getChild<LLCheckBoxCtrl>("show_textures")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1));
- getChild<LLCheckBoxCtrl>("show_skin_weight")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1));
+ getChild<LLCheckBoxCtrl>("show_skin_weight")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onShowSkinWeightChecked, this, _1));
+ getChild<LLCheckBoxCtrl>("show_joint_overrides")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1));
getChild<LLCheckBoxCtrl>("show_joint_positions")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1));
childDisable("upload_skin");
childDisable("upload_joints");
childDisable("lock_scale_if_joint_position");
+ childSetVisible("skin_too_many_joints", false);
+ childSetVisible("skin_unknown_joint", false);
+
+ childSetVisible("warning_title", false);
+ childSetVisible("warning_message", false);
+
initDecompControls();
LLView* preview_panel = getChild<LLView>("preview_panel");
@@ -395,6 +258,12 @@ BOOL LLFloaterModelPreview::postBuild()
mUploadBtn = getChild<LLButton>("ok_btn");
mCalculateBtn = getChild<LLButton>("calculate_btn");
+ mUploadLogText = getChild<LLViewerTextEditor>("log_text");
+ mTabContainer = getChild<LLTabContainer>("import_tab");
+
+ LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");
+ mAvatarTabIndex = mTabContainer->getIndexForPanel(panel);
+ panel->getChild<LLScrollListCtrl>("joints_list")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onJointListSelection, this));
if (LLConvexDecomposition::getInstance() != NULL)
{
@@ -411,6 +280,24 @@ BOOL LLFloaterModelPreview::postBuild()
}
//-----------------------------------------------------------------------------
+// reshape()
+//-----------------------------------------------------------------------------
+
+void LLFloaterModelPreview::reshape(S32 width, S32 height, BOOL called_from_parent)
+{
+ LLFloaterModelUploadBase::reshape(width, height, called_from_parent);
+
+ LLView* preview_panel = getChild<LLView>("preview_panel");
+ LLRect rect = preview_panel->getRect();
+
+ if (rect != mPreviewRect)
+ {
+ mModelPreview->refresh();
+ mPreviewRect = preview_panel->getRect();
+ }
+}
+
+//-----------------------------------------------------------------------------
// LLFloaterModelPreview()
//-----------------------------------------------------------------------------
LLFloaterModelPreview::~LLFloaterModelPreview()
@@ -433,18 +320,95 @@ void LLFloaterModelPreview::initModelPreview()
delete mModelPreview;
}
- mModelPreview = new LLModelPreview(512, 512, this );
- mModelPreview->setPreviewTarget(MODEL_PREVIEW_CAMERA_DISTANCE);
+ S32 tex_width = 512;
+ S32 tex_height = 512;
+
+ S32 max_width = llmin(PREVIEW_RENDER_SIZE, (S32)gPipeline.mScreenWidth);
+ S32 max_height = llmin(PREVIEW_RENDER_SIZE, (S32)gPipeline.mScreenHeight);
+
+ while ((tex_width << 1) < max_width)
+ {
+ tex_width <<= 1;
+ }
+ while ((tex_height << 1) < max_height)
+ {
+ tex_height <<= 1;
+ }
+
+ mModelPreview = new LLModelPreview(tex_width, tex_height, this);
+ mModelPreview->setPreviewTarget(PREVIEW_CAMERA_DISTANCE);
mModelPreview->setDetailsCallback(boost::bind(&LLFloaterModelPreview::setDetails, this, _1, _2, _3, _4, _5));
mModelPreview->setModelUpdatedCallback(boost::bind(&LLFloaterModelPreview::modelUpdated, this, _1));
}
+void LLFloaterModelPreview::onUploadOptionChecked(LLUICtrl* ctrl)
+{
+ if (mModelPreview)
+ {
+ auto name = ctrl->getName();
+ bool value = ctrl->getValue().asBoolean();
+ // update the option and notifications
+ // (this is a bit convoluted, because of the current structure of mModelPreview)
+ if (name == "upload_skin")
+ {
+ childSetValue("show_skin_weight", value);
+ mModelPreview->mViewOption["show_skin_weight"] = value;
+ if (!value)
+ {
+ mModelPreview->mViewOption["show_joint_overrides"] = false;
+ mModelPreview->mViewOption["show_joint_positions"] = false;
+ childSetValue("show_joint_overrides", false);
+ childSetValue("show_joint_positions", false);
+ }
+ }
+ else if (name == "upload_joints")
+ {
+ if (mModelPreview->mViewOption["show_skin_weight"])
+ {
+ childSetValue("show_joint_overrides", value);
+ mModelPreview->mViewOption["show_joint_overrides"] = value;
+ }
+ }
+ else if (name == "upload_textures")
+ {
+ childSetValue("show_textures", value);
+ mModelPreview->mViewOption["show_textures"] = value;
+ }
+ else if (name == "lock_scale_if_joint_position")
+ {
+ mModelPreview->mViewOption["lock_scale_if_joint_position"] = value;
+ }
+
+ mModelPreview->refresh(); // a 'dirty' flag for render
+ mModelPreview->resetPreviewTarget();
+ mModelPreview->clearBuffers();
+ mModelPreview->mDirty = true;
+ }
+ // set the button visible, it will be refreshed later
+ toggleCalculateButton(true);
+}
+
+void LLFloaterModelPreview::onShowSkinWeightChecked(LLUICtrl* ctrl)
+{
+ if (mModelPreview)
+ {
+ mModelPreview->mCameraOffset.clearVec();
+ onViewOptionChecked(ctrl);
+ }
+}
+
void LLFloaterModelPreview::onViewOptionChecked(LLUICtrl* ctrl)
{
if (mModelPreview)
{
- mModelPreview->mViewOption[ctrl->getName()] = !mModelPreview->mViewOption[ctrl->getName()];
-
+ auto name = ctrl->getName();
+ mModelPreview->mViewOption[name] = !mModelPreview->mViewOption[name];
+ if (name == "show_physics")
+ {
+ auto enabled = mModelPreview->mViewOption[name];
+ childSetEnabled("physics_explode", enabled);
+ childSetVisible("physics_explode", enabled);
+ }
mModelPreview->refresh();
}
}
@@ -479,6 +443,12 @@ void LLFloaterModelPreview::disableViewOption(const std::string& option)
setViewOptionEnabled(option, false);
}
+void LLFloaterModelPreview::loadHighLodModel()
+{
+ mModelPreview->mLookUpLodFiles = true;
+ loadModel(3);
+}
+
void LLFloaterModelPreview::loadModel(S32 lod)
{
mModelPreview->mLoading = true;
@@ -500,19 +470,14 @@ void LLFloaterModelPreview::loadModel(S32 lod, const std::string& file_name, boo
void LLFloaterModelPreview::onClickCalculateBtn()
{
+ clearLogTab();
+ addStringToLog("Calculating model data.", false);
mModelPreview->rebuildUploadData();
bool upload_skinweights = childGetValue("upload_skin").asBoolean();
bool upload_joint_positions = childGetValue("upload_joints").asBoolean();
bool lock_scale_if_joint_position = childGetValue("lock_scale_if_joint_position").asBoolean();
- if (upload_joint_positions)
- {
- // Diagnostic message showing list of joints for which joint offsets are defined.
- // FIXME - given time, would be much better to put this in the UI, in updateStatusMessages().
- mModelPreview->getPreviewAvatar()->showAttachmentOverrides();
- }
-
mUploadModelUrl.clear();
mModelPhysicsFee.clear();
@@ -526,6 +491,132 @@ void LLFloaterModelPreview::onClickCalculateBtn()
mUploadBtn->setEnabled(false);
}
+// Modified cell_params, make sure to clear values if you have to reuse cell_params outside of this function
+void add_row_to_list(LLScrollListCtrl *listp,
+ LLScrollListCell::Params &cell_params,
+ const LLSD &item_value,
+ const std::string &name,
+ const LLSD &vx,
+ const LLSD &vy,
+ const LLSD &vz)
+{
+ LLScrollListItem::Params item_params;
+ item_params.value = item_value;
+
+ cell_params.column = "model_name";
+ cell_params.value = name;
+
+ item_params.columns.add(cell_params);
+
+ cell_params.column = "axis_x";
+ cell_params.value = vx;
+ item_params.columns.add(cell_params);
+
+ cell_params.column = "axis_y";
+ cell_params.value = vy;
+ item_params.columns.add(cell_params);
+
+ cell_params.column = "axis_z";
+ cell_params.value = vz;
+
+ item_params.columns.add(cell_params);
+
+ listp->addRow(item_params);
+}
+
+void populate_list_with_overrides(LLScrollListCtrl *listp, const LLJointOverrideData &data, bool include_overrides)
+{
+ if (data.mModelsNoOverrides.empty() && data.mPosOverrides.empty())
+ {
+ return;
+ }
+
+ static const std::string no_override_placeholder = "-";
+
+ S32 count = 0;
+ LLScrollListCell::Params cell_params;
+ cell_params.font = LLFontGL::getFontSansSerif();
+ // Start out right justifying numeric displays
+ cell_params.font_halign = LLFontGL::HCENTER;
+
+ std::map<std::string, LLVector3>::const_iterator map_iter = data.mPosOverrides.begin();
+ std::map<std::string, LLVector3>::const_iterator map_end = data.mPosOverrides.end();
+ while (map_iter != map_end)
+ {
+ if (include_overrides)
+ {
+ add_row_to_list(listp,
+ cell_params,
+ LLSD::Integer(count),
+ map_iter->first,
+ LLSD::Real(map_iter->second.mV[VX]),
+ LLSD::Real(map_iter->second.mV[VY]),
+ LLSD::Real(map_iter->second.mV[VZ]));
+ }
+ else
+ {
+ add_row_to_list(listp,
+ cell_params,
+ LLSD::Integer(count),
+ map_iter->first,
+ no_override_placeholder,
+ no_override_placeholder,
+ no_override_placeholder);
+ }
+ count++;
+ map_iter++;
+ }
+
+ std::set<std::string>::const_iterator set_iter = data.mModelsNoOverrides.begin();
+ std::set<std::string>::const_iterator set_end = data.mModelsNoOverrides.end();
+ while (set_iter != set_end)
+ {
+ add_row_to_list(listp,
+ cell_params,
+ LLSD::Integer(count),
+ *set_iter,
+ no_override_placeholder,
+ no_override_placeholder,
+ no_override_placeholder);
+ count++;
+ set_iter++;
+ }
+}
+
+void LLFloaterModelPreview::onJointListSelection()
+{
+ S32 display_lod = mModelPreview->mPreviewLOD;
+ LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");
+ LLScrollListCtrl *joints_list = panel->getChild<LLScrollListCtrl>("joints_list");
+ LLScrollListCtrl *joints_pos = panel->getChild<LLScrollListCtrl>("pos_overrides_list");
+ LLScrollListCtrl *joints_scale = panel->getChild<LLScrollListCtrl>("scale_overrides_list");
+ LLTextBox *joint_pos_descr = panel->getChild<LLTextBox>("pos_overrides_descr");
+
+ joints_pos->deleteAllItems();
+ joints_scale->deleteAllItems();
+
+ LLScrollListItem *selected = joints_list->getFirstSelected();
+ if (selected)
+ {
+ std::string label = selected->getValue().asString();
+ LLJointOverrideData &data = mJointOverrides[display_lod][label];
+ bool upload_joint_positions = childGetValue("upload_joints").asBoolean();
+ populate_list_with_overrides(joints_pos, data, upload_joint_positions);
+
+ joint_pos_descr->setTextArg("[JOINT]", label);
+ mSelectedJointName = label;
+ }
+ else
+ {
+ // temporary value (shouldn't happen)
+ std::string label = "mPelvis";
+ joint_pos_descr->setTextArg("[JOINT]", label);
+ mSelectedJointName.clear();
+ }
+
+ // Note: We can make a version of renderBones() to highlight selected joint
+}
+
void LLFloaterModelPreview::onDescriptionKeystroke(LLUICtrl* ctrl)
{
// Workaround for SL-4186, server doesn't allow name changes after 'calculate' stage
@@ -570,33 +661,6 @@ void LLFloaterModelPreview::onPelvisOffsetCommit( LLUICtrl*, void* userdata )
}
//static
-void LLFloaterModelPreview::onUploadJointsCommit(LLUICtrl*,void* userdata)
-{
- LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata;
-
- if (!fp->mModelPreview)
- {
- return;
- }
-
- fp->mModelPreview->refresh();
-}
-
-//static
-void LLFloaterModelPreview::onUploadSkinCommit(LLUICtrl*,void* userdata)
-{
- LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata;
-
- if (!fp->mModelPreview)
- {
- return;
- }
- fp->mModelPreview->refresh();
- fp->mModelPreview->resetPreviewTarget();
- fp->mModelPreview->clearBuffers();
-}
-
-//static
void LLFloaterModelPreview::onPreviewLODCommit(LLUICtrl* ctrl, void* userdata)
{
LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata;
@@ -626,6 +690,7 @@ void LLFloaterModelPreview::onGenerateNormalsCommit(LLUICtrl* ctrl, void* userda
void LLFloaterModelPreview::toggleGenarateNormals()
{
bool enabled = childGetValue("gen_normals").asBoolean();
+ mModelPreview->mViewOption["gen_normals"] = enabled;
childSetEnabled("crease_angle", enabled);
if(enabled) {
mModelPreview->generateNormals();
@@ -669,6 +734,27 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit)
}
}
+void LLFloaterModelPreview::draw3dPreview()
+{
+ gGL.color3f(1.f, 1.f, 1.f);
+
+ gGL.getTexUnit(0)->bind(mModelPreview);
+
+ gGL.begin( LLRender::QUADS );
+ {
+ gGL.texCoord2f(0.f, 1.f);
+ gGL.vertex2i(mPreviewRect.mLeft+1, mPreviewRect.mTop-1);
+ gGL.texCoord2f(0.f, 0.f);
+ gGL.vertex2i(mPreviewRect.mLeft+1, mPreviewRect.mBottom+1);
+ gGL.texCoord2f(1.f, 0.f);
+ gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mBottom+1);
+ gGL.texCoord2f(1.f, 1.f);
+ gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mTop-1);
+ }
+ gGL.end();
+
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+}
//-----------------------------------------------------------------------------
// draw()
@@ -715,36 +801,9 @@ void LLFloaterModelPreview::draw()
childSetTextArg("prim_cost", "[PRIM_COST]", llformat("%d", mModelPreview->mResourceCost));
childSetTextArg("description_label", "[TEXTURES]", llformat("%d", mModelPreview->mTextureSet.size()));
- if (mModelPreview->lodsReady())
+ if (!isMinimized() && mModelPreview->lodsReady())
{
- gGL.color3f(1.f, 1.f, 1.f);
-
- gGL.getTexUnit(0)->bind(mModelPreview);
-
-
- LLView* preview_panel = getChild<LLView>("preview_panel");
-
- LLRect rect = preview_panel->getRect();
- if (rect != mPreviewRect)
- {
- mModelPreview->refresh();
- mPreviewRect = preview_panel->getRect();
- }
-
- gGL.begin( LLRender::QUADS );
- {
- gGL.texCoord2f(0.f, 1.f);
- gGL.vertex2i(mPreviewRect.mLeft, mPreviewRect.mTop-1);
- gGL.texCoord2f(0.f, 0.f);
- gGL.vertex2i(mPreviewRect.mLeft, mPreviewRect.mBottom);
- gGL.texCoord2f(1.f, 0.f);
- gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mBottom);
- gGL.texCoord2f(1.f, 1.f);
- gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mTop-1);
- }
- gGL.end();
-
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ draw3dPreview();
}
}
@@ -843,8 +902,11 @@ BOOL LLFloaterModelPreview::handleScrollWheel(S32 x, S32 y, S32 clicks)
mModelPreview->zoom((F32)clicks * -0.2f);
mModelPreview->refresh();
}
-
- return TRUE;
+ else
+ {
+ LLFloaterModelUploadBase::handleScrollWheel(x, y, clicks);
+ }
+ return TRUE;
}
/*virtual*/
@@ -1104,7 +1166,8 @@ void LLFloaterModelPreview::initDecompControls()
float max = param[i].mDetails.mRange.mHigh.mFloat;
float delta = param[i].mDetails.mRange.mDelta.mFloat;
- if ("Cosine%" == name)
+ bool is_smooth_cb = ("Cosine%" == name);
+ if (is_smooth_cb)
{
createSmoothComboBox(combo_box, min, max);
}
@@ -1115,10 +1178,8 @@ void LLFloaterModelPreview::initDecompControls()
std::string label = llformat("%.1f", value);
combo_box->add(label, value, ADD_BOTTOM, true);
}
- combo_box->setValue(param[i].mDefault.mFloat);
-
}
-
+ combo_box->setValue(is_smooth_cb ? 0: param[i].mDefault.mFloat);
combo_box->setCommitCallback(onPhysicsParamCommit, (void*) &param[i]);
}
}
@@ -1190,7 +1251,7 @@ void LLFloaterModelPreview::initDecompControls()
//LL_INFOS() << "-----------------------------" << LL_ENDL;
}
}
-
+ mDefaultDecompParams = mDecompParams;
childSetCommitCallback("physics_explode", LLFloaterModelPreview::onExplodeCommit, this);
}
@@ -1220,3089 +1281,279 @@ void LLFloaterModelPreview::onMouseCaptureLostModelPreview(LLMouseHandler* handl
}
//-----------------------------------------------------------------------------
-// LLModelPreview
+// addStringToLog()
//-----------------------------------------------------------------------------
-
-LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp)
-: LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, FALSE), LLMutex()
-, mLodsQuery()
-, mLodsWithParsingError()
-, mPelvisZOffset( 0.0f )
-, mLegacyRigValid( false )
-, mRigValidJointUpload( false )
-, mPhysicsSearchLOD( LLModel::LOD_PHYSICS )
-, mResetJoints( false )
-, mModelNoErrors( true )
-, mLastJointUpdate( false )
-{
- mNeedsUpdate = TRUE;
- mCameraDistance = 0.f;
- mCameraYaw = 0.f;
- mCameraPitch = 0.f;
- mCameraZoom = 1.f;
- mTextureName = 0;
- mPreviewLOD = 0;
- mModelLoader = NULL;
- mMaxTriangleLimit = 0;
- mDirty = false;
- mGenLOD = false;
- mLoading = false;
- mLoadState = LLModelLoader::STARTING;
- mGroup = 0;
- mLODFrozen = false;
- mBuildShareTolerance = 0.f;
- mBuildQueueMode = GLOD_QUEUE_GREEDY;
- mBuildBorderMode = GLOD_BORDER_UNLOCK;
- mBuildOperator = GLOD_OPERATOR_EDGE_COLLAPSE;
-
- for (U32 i = 0; i < LLModel::NUM_LODS; ++i)
- {
- mRequestedTriangleCount[i] = 0;
- mRequestedCreaseAngle[i] = -1.f;
- mRequestedLoDMode[i] = 0;
- mRequestedErrorThreshold[i] = 0.f;
- mRequestedBuildOperator[i] = 0;
- mRequestedQueueMode[i] = 0;
- mRequestedBorderMode[i] = 0;
- mRequestedShareTolerance[i] = 0.f;
- }
-
- mViewOption["show_textures"] = false;
-
- mFMP = fmp;
-
- mHasPivot = false;
- mModelPivot = LLVector3( 0.0f, 0.0f, 0.0f );
-
- glodInit();
-
- createPreviewAvatar();
-}
-
-LLModelPreview::~LLModelPreview()
-{
- // glod apparently has internal mem alignment issues that are angering
- // the heap-check code in windows, these should be hunted down in that
- // TP code, if possible
- //
- // kernel32.dll!HeapFree() + 0x14 bytes
- // msvcr100.dll!free(void * pBlock) Line 51 C
- // glod.dll!glodGetGroupParameteriv() + 0x119 bytes
- // glod.dll!glodShutdown() + 0x77 bytes
- //
- //glodShutdown();
- if(mModelLoader)
- {
- mModelLoader->shutdown();
- }
-}
-
-U32 LLModelPreview::calcResourceCost()
-{
- assert_main_thread();
-
- rebuildUploadData();
-
- //Upload skin is selected BUT check to see if the joints coming in from the asset were malformed.
- if ( mFMP && mFMP->childGetValue("upload_skin").asBoolean() )
- {
- bool uploadingJointPositions = mFMP->childGetValue("upload_joints").asBoolean();
- if ( uploadingJointPositions && !isRigValidForJointPositionUpload() )
- {
- mFMP->childDisable("ok_btn");
- }
- }
-
- std::set<LLModel*> accounted;
- U32 num_points = 0;
- U32 num_hulls = 0;
-
- F32 debug_scale = mFMP ? mFMP->childGetValue("import_scale").asReal() : 1.f;
- mPelvisZOffset = mFMP ? mFMP->childGetValue("pelvis_offset").asReal() : 3.0f;
-
- if ( mFMP && mFMP->childGetValue("upload_joints").asBoolean() )
- {
- // FIXME if preview avatar ever gets reused, this fake mesh ID stuff will fail.
- // see also call to addAttachmentPosOverride.
- LLUUID fake_mesh_id;
- fake_mesh_id.generate();
- getPreviewAvatar()->addPelvisFixup( mPelvisZOffset, fake_mesh_id );
- }
-
- F32 streaming_cost = 0.f;
- F32 physics_cost = 0.f;
- for (U32 i = 0; i < mUploadData.size(); ++i)
- {
- LLModelInstance& instance = mUploadData[i];
-
- if (accounted.find(instance.mModel) == accounted.end())
- {
- accounted.insert(instance.mModel);
-
- LLModel::Decomposition& decomp =
- instance.mLOD[LLModel::LOD_PHYSICS] ?
- instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics :
- instance.mModel->mPhysics;
-
- //update instance skin info for each lods pelvisZoffset
- for ( int j=0; j<LLModel::NUM_LODS; ++j )
- {
- if ( instance.mLOD[j] )
- {
- instance.mLOD[j]->mSkinInfo.mPelvisOffset = mPelvisZOffset;
- }
- }
-
- std::stringstream ostr;
- LLSD ret = LLModel::writeModel(ostr,
- instance.mLOD[4],
- instance.mLOD[3],
- instance.mLOD[2],
- instance.mLOD[1],
- instance.mLOD[0],
- decomp,
- mFMP->childGetValue("upload_skin").asBoolean(),
- mFMP->childGetValue("upload_joints").asBoolean(),
- mFMP->childGetValue("lock_scale_if_joint_position").asBoolean(),
- TRUE,
- FALSE,
- instance.mModel->mSubmodelID);
-
- num_hulls += decomp.mHull.size();
- for (U32 i = 0; i < decomp.mHull.size(); ++i)
- {
- num_points += decomp.mHull[i].size();
- }
-
- //calculate streaming cost
- LLMatrix4 transformation = instance.mTransform;
-
- LLVector3 position = LLVector3(0, 0, 0) * transformation;
-
- 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);
-
- F32 radius = scale.length()*0.5f*debug_scale;
-
- LLMeshCostData costs;
- if (gMeshRepo.getCostData(ret, costs))
- {
- streaming_cost += costs.getRadiusBasedStreamingCost(radius);
- }
- }
- }
-
- F32 scale = mFMP ? mFMP->childGetValue("import_scale").asReal()*2.f : 2.f;
-
- mDetailsSignal(mPreviewScale[0]*scale, mPreviewScale[1]*scale, mPreviewScale[2]*scale, streaming_cost, physics_cost);
-
- updateStatusMessages();
-
- return (U32) streaming_cost;
-}
-
-void LLFloaterModelPreview::setDetails(F32 x, F32 y, F32 z, F32 streaming_cost, F32 physics_cost)
-{
- assert_main_thread();
- childSetTextArg("import_dimensions", "[X]", llformat("%.3f", x));
- childSetTextArg("import_dimensions", "[Y]", llformat("%.3f", y));
- childSetTextArg("import_dimensions", "[Z]", llformat("%.3f", z));
-}
-
-void LLFloaterModelPreview::setPreviewLOD(S32 lod)
-{
- if (mModelPreview)
- {
- mModelPreview->setPreviewLOD(lod);
- }
-}
-
-
-void LLModelPreview::rebuildUploadData()
-{
- assert_main_thread();
-
- mUploadData.clear();
- mTextureSet.clear();
-
- //fill uploaddata instance vectors from scene data
-
- std::string requested_name = mFMP->getChild<LLUICtrl>("description_form")->getValue().asString();
-
- LLSpinCtrl* scale_spinner = mFMP->getChild<LLSpinCtrl>("import_scale");
-
- F32 scale = scale_spinner->getValue().asReal();
-
- LLMatrix4 scale_mat;
- scale_mat.initScale(LLVector3(scale, scale, scale));
-
- F32 max_scale = 0.f;
-
- BOOL importerDebug = gSavedSettings.getBOOL("ImporterDebug");
- BOOL legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching");
-
- for (LLModelLoader::scene::iterator iter = mBaseScene.begin(); iter != mBaseScene.end(); ++iter)
- { //for each transform in scene
- LLMatrix4 mat = iter->first;
-
- // compute position
- LLVector3 position = LLVector3(0, 0, 0) * mat;
-
- // compute scale
- LLVector3 x_transformed = LLVector3(1, 0, 0) * mat - position;
- LLVector3 y_transformed = LLVector3(0, 1, 0) * mat - position;
- LLVector3 z_transformed = LLVector3(0, 0, 1) * mat - position;
- F32 x_length = x_transformed.normalize();
- F32 y_length = y_transformed.normalize();
- F32 z_length = z_transformed.normalize();
-
- max_scale = llmax(llmax(llmax(max_scale, x_length), y_length), z_length);
-
- mat *= scale_mat;
-
- for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end();)
- { //for each instance with said transform applied
- LLModelInstance instance = *model_iter++;
-
- LLModel* base_model = instance.mModel;
-
- if (base_model && !requested_name.empty())
- {
- base_model->mRequestedLabel = requested_name;
- }
-
- for (int i = LLModel::NUM_LODS - 1; i >= LLModel::LOD_IMPOSTOR; i--)
- {
- LLModel* lod_model = NULL;
- if (!legacyMatching)
- {
- // Fill LOD slots by finding matching meshes by label with name extensions
- // in the appropriate scene for each LOD. This fixes all kinds of issues
- // where the indexed method below fails in spectacular fashion.
- // If you don't take the time to name your LOD and PHYS meshes
- // with the name of their corresponding mesh in the HIGH LOD,
- // then the indexed method will be attempted below.
-
- LLMatrix4 transform;
-
- std::string name_to_match = instance.mLabel;
- llassert(!name_to_match.empty());
-
- int extensionLOD;
- if (i != LLModel::LOD_PHYSICS || mModel[LLModel::LOD_PHYSICS].empty())
- {
- extensionLOD = i;
- }
- else
- {
- //Physics can be inherited from other LODs or loaded, so we need to adjust what extension we are searching for
- extensionLOD = mPhysicsSearchLOD;
- }
-
- std::string toAdd;
- switch (extensionLOD)
- {
- case LLModel::LOD_IMPOSTOR: toAdd = "_LOD0"; break;
- case LLModel::LOD_LOW: toAdd = "_LOD1"; break;
- case LLModel::LOD_MEDIUM: toAdd = "_LOD2"; break;
- case LLModel::LOD_PHYSICS: toAdd = "_PHYS"; break;
- case LLModel::LOD_HIGH: break;
- }
-
- if (name_to_match.find(toAdd) == -1)
- {
- name_to_match += toAdd;
- }
-
- FindModel(mScene[i], name_to_match, lod_model, transform);
-
- if (!lod_model && i != LLModel::LOD_PHYSICS)
- {
- if (importerDebug)
- {
- LL_INFOS() << "Search of" << name_to_match << " in LOD" << i << " list failed. Searching for alternative among LOD lists." << LL_ENDL;
- }
-
- int searchLOD = (i > LLModel::LOD_HIGH) ? LLModel::LOD_HIGH : i;
- while ((searchLOD <= LLModel::LOD_HIGH) && !lod_model)
- {
- std::string name_to_match = instance.mLabel;
- llassert(!name_to_match.empty());
-
- std::string toAdd;
- switch (searchLOD)
- {
- case LLModel::LOD_IMPOSTOR: toAdd = "_LOD0"; break;
- case LLModel::LOD_LOW: toAdd = "_LOD1"; break;
- case LLModel::LOD_MEDIUM: toAdd = "_LOD2"; break;
- case LLModel::LOD_PHYSICS: toAdd = "_PHYS"; break;
- case LLModel::LOD_HIGH: break;
- }
-
- if (name_to_match.find(toAdd) == -1)
- {
- name_to_match += toAdd;
- }
-
- // See if we can find an appropriately named model in LOD 'searchLOD'
- //
- FindModel(mScene[searchLOD], name_to_match, lod_model, transform);
- searchLOD++;
- }
- }
- }
- else
- {
- // Use old method of index-based association
- U32 idx = 0;
- for (idx = 0; idx < mBaseModel.size(); ++idx)
- {
- // find reference instance for this model
- if (mBaseModel[idx] == base_model)
- {
- if (importerDebug)
- {
- LL_INFOS() << "Attempting to use model index " << idx << " for LOD " << i << " of " << instance.mLabel << LL_ENDL;
- }
- break;
- }
- }
-
- // If the model list for the current LOD includes that index...
- //
- if (mModel[i].size() > idx)
- {
- // Assign that index from the model list for our LOD as the LOD model for this instance
- //
- lod_model = mModel[i][idx];
- if (importerDebug)
- {
- LL_INFOS() << "Indexed match of model index " << idx << " at LOD " << i << " to model named " << lod_model->mLabel << LL_ENDL;
- }
- }
- else if (importerDebug)
- {
- LL_INFOS() << "List of models does not include index " << idx << LL_ENDL;
- }
- }
-
- if (lod_model)
- {
- if (importerDebug)
- {
- if (i == LLModel::LOD_PHYSICS)
- {
- LL_INFOS() << "Assigning collision for " << instance.mLabel << " to match " << lod_model->mLabel << LL_ENDL;
- }
- else
- {
- LL_INFOS() << "Assigning LOD" << i << " for " << instance.mLabel << " to found match " << lod_model->mLabel << LL_ENDL;
- }
- }
- instance.mLOD[i] = lod_model;
- }
- else
- {
- if (i < LLModel::LOD_HIGH && !lodsReady())
- {
- // assign a placeholder from previous LOD until lod generation is complete.
- // Note: we might need to assign it regardless of conditions like named search does, to prevent crashes.
- instance.mLOD[i] = instance.mLOD[i + 1];
- }
- if (importerDebug)
- {
- LL_INFOS() << "List of models does not include " << instance.mLabel << LL_ENDL;
- }
- }
- }
-
- LLModel* high_lod_model = instance.mLOD[LLModel::LOD_HIGH];
- if (!high_lod_model)
- {
- setLoadState( LLModelLoader::ERROR_MATERIALS );
- mFMP->childDisable( "calculate_btn" );
- }
- else
- {
- for (U32 i = 0; i < LLModel::NUM_LODS-1; i++)
- {
- int refFaceCnt = 0;
- int modelFaceCnt = 0;
- llassert(instance.mLOD[i]);
- if (instance.mLOD[i] && !instance.mLOD[i]->matchMaterialOrder(high_lod_model, refFaceCnt, modelFaceCnt ) )
- {
- setLoadState( LLModelLoader::ERROR_MATERIALS );
- mFMP->childDisable( "calculate_btn" );
- }
- }
- LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) mFMP;
- bool upload_skinweights = fmp && fmp->childGetValue("upload_skin").asBoolean();
- if (upload_skinweights && high_lod_model->mSkinInfo.mJointNames.size() > 0)
- {
- LLQuaternion bind_rot = LLSkinningUtil::getUnscaledQuaternion(high_lod_model->mSkinInfo.mBindShapeMatrix);
- LLQuaternion identity;
- if (!bind_rot.isEqualEps(identity,0.01))
- {
- LL_WARNS() << "non-identity bind shape rot. mat is " << high_lod_model->mSkinInfo.mBindShapeMatrix
- << " bind_rot " << bind_rot << LL_ENDL;
- setLoadState( LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION );
- }
- }
- }
- instance.mTransform = mat;
- mUploadData.push_back(instance);
- }
- }
-
- for (U32 lod = 0; lod < LLModel::NUM_LODS-1; lod++)
- {
- // Search for models that are not included into upload data
- // If we found any, that means something we loaded is not a sub-model.
- for (U32 model_ind = 0; model_ind < mModel[lod].size(); ++model_ind)
- {
- bool found_model = false;
- for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
- {
- LLModelInstance& instance = *iter;
- if (instance.mLOD[lod] == mModel[lod][model_ind])
- {
- found_model = true;
- break;
- }
- }
- if (!found_model && mModel[lod][model_ind] && !mModel[lod][model_ind]->mSubmodelID)
- {
- if (importerDebug)
- {
- LL_INFOS() << "Model " << mModel[lod][model_ind]->mLabel << " was not used - mismatching lod models." << LL_ENDL;
- }
- setLoadState( LLModelLoader::ERROR_MATERIALS );
- mFMP->childDisable( "calculate_btn" );
- }
- }
- }
-
- F32 max_import_scale = (DEFAULT_MAX_PRIM_SCALE-0.1f)/max_scale;
-
- F32 max_axis = llmax(mPreviewScale.mV[0], mPreviewScale.mV[1]);
- max_axis = llmax(max_axis, mPreviewScale.mV[2]);
- max_axis *= 2.f;
-
- //clamp scale so that total imported model bounding box is smaller than 240m on a side
- max_import_scale = llmin(max_import_scale, 240.f/max_axis);
-
- scale_spinner->setMaxValue(max_import_scale);
-
- if (max_import_scale < scale)
- {
- scale_spinner->setValue(max_import_scale);
- }
-
-}
-
-void LLModelPreview::saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position)
+//static
+void LLFloaterModelPreview::addStringToLog(const std::string& message, const LLSD& args, bool flash, S32 lod)
{
- if (!mLODFile[LLModel::LOD_HIGH].empty())
- {
- std::string filename = mLODFile[LLModel::LOD_HIGH];
- std::string slm_filename;
-
- if (LLModelLoader::getSLMFilename(filename, slm_filename))
+ if (sInstance && sInstance->hasString(message))
+ {
+ std::string str;
+ switch (lod)
{
- saveUploadData(slm_filename, save_skinweights, save_joint_positions, lock_scale_if_joint_position);
- }
- }
-}
-
-void LLModelPreview::saveUploadData(const std::string& filename,
- bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position)
-{
-
- std::set<LLPointer<LLModel> > meshes;
- std::map<LLModel*, std::string> mesh_binary;
-
- LLModel::hull empty_hull;
-
- LLSD data;
-
- data["version"] = SLM_SUPPORTED_VERSION;
- if (!mBaseModel.empty())
- {
- data["name"] = mBaseModel[0]->getName();
- }
-
- S32 mesh_id = 0;
-
- //build list of unique models and initialize local id
- for (U32 i = 0; i < mUploadData.size(); ++i)
- {
- LLModelInstance& instance = mUploadData[i];
-
- if (meshes.find(instance.mModel) == meshes.end())
- {
- instance.mModel->mLocalID = mesh_id++;
- meshes.insert(instance.mModel);
-
- std::stringstream str;
- LLModel::Decomposition& decomp =
- instance.mLOD[LLModel::LOD_PHYSICS].notNull() ?
- instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics :
- instance.mModel->mPhysics;
-
- LLModel::writeModel(str,
- instance.mLOD[LLModel::LOD_PHYSICS],
- instance.mLOD[LLModel::LOD_HIGH],
- instance.mLOD[LLModel::LOD_MEDIUM],
- instance.mLOD[LLModel::LOD_LOW],
- instance.mLOD[LLModel::LOD_IMPOSTOR],
- decomp,
- save_skinweights,
- save_joint_positions,
- lock_scale_if_joint_position,
- FALSE, TRUE, instance.mModel->mSubmodelID);
-
- data["mesh"][instance.mModel->mLocalID] = str.str();
- }
-
- data["instance"][i] = instance.asLLSD();
- }
-
- llofstream out(filename.c_str(), std::ios_base::out | std::ios_base::binary);
- LLSDSerialize::toBinary(data, out);
- out.flush();
- out.close();
-}
-
-void LLModelPreview::clearModel(S32 lod)
-{
- if (lod < 0 || lod > LLModel::LOD_PHYSICS)
- {
- return;
- }
-
- mVertexBuffer[lod].clear();
- mModel[lod].clear();
- mScene[lod].clear();
+ case LLModel::LOD_IMPOSTOR: str = "LOD0 "; break;
+ case LLModel::LOD_LOW: str = "LOD1 "; break;
+ case LLModel::LOD_MEDIUM: str = "LOD2 "; break;
+ case LLModel::LOD_PHYSICS: str = "PHYS "; break;
+ case LLModel::LOD_HIGH: str = "LOD3 "; break;
+ default: break;
+ }
+
+ LLStringUtil::format_map_t args_msg;
+ LLSD::map_const_iterator iter = args.beginMap();
+ LLSD::map_const_iterator end = args.endMap();
+ for (; iter != end; ++iter)
+ {
+ args_msg[iter->first] = iter->second.asString();
+ }
+ str += sInstance->getString(message, args_msg);
+ sInstance->addStringToLogTab(str, flash);
+ }
}
-void LLModelPreview::getJointAliases( JointMap& joint_map)
+// static
+void LLFloaterModelPreview::addStringToLog(const std::string& str, bool flash)
{
- // Get all standard skeleton joints from the preview avatar.
- LLVOAvatar *av = getPreviewAvatar();
-
- //Joint names and aliases come from avatar_skeleton.xml
-
- joint_map = av->getJointAliases();
-
- std::vector<std::string> cv_names, attach_names;
- av->getSortedJointNames(1, cv_names);
- av->getSortedJointNames(2, attach_names);
- for (std::vector<std::string>::iterator it = cv_names.begin(); it != cv_names.end(); ++it)
- {
- joint_map[*it] = *it;
- }
- for (std::vector<std::string>::iterator it = attach_names.begin(); it != attach_names.end(); ++it)
+ if (sInstance)
{
- joint_map[*it] = *it;
+ sInstance->addStringToLogTab(str, flash);
}
}
-void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable_slm)
-{
- assert_main_thread();
-
- LLMutexLock lock(this);
-
- if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::NUM_LODS - 1)
- {
- LL_WARNS() << "Invalid level of detail: " << lod << LL_ENDL;
- assert(lod >= LLModel::LOD_IMPOSTOR && lod < LLModel::NUM_LODS);
- return;
- }
-
- // This triggers if you bring up the file picker and then hit CANCEL.
- // Just use the previous model (if any) and ignore that you brought up
- // the file picker.
-
- if (filename.empty())
- {
- if (mBaseModel.empty())
- {
- // this is the initial file picking. Close the whole floater
- // if we don't have a base model to show for high LOD.
- mFMP->closeFloater(false);
- }
- mLoading = false;
- return;
- }
-
- if (mModelLoader)
- {
- LL_WARNS() << "Incompleted model load operation pending." << LL_ENDL;
- return;
- }
-
- mLODFile[lod] = filename;
-
- if (lod == LLModel::LOD_HIGH)
- {
- clearGLODGroup();
- }
-
- std::map<std::string, std::string> joint_alias_map;
- getJointAliases(joint_alias_map);
-
- mModelLoader = new LLDAELoader(
- filename,
- lod,
- &LLModelPreview::loadedCallback,
- &LLModelPreview::lookupJointByName,
- &LLModelPreview::loadTextures,
- &LLModelPreview::stateChangedCallback,
- this,
- mJointTransformMap,
- mJointsFromNode,
- joint_alias_map,
- LLSkinningUtil::getMaxJointCount(),
- gSavedSettings.getU32("ImporterModelLimit"),
- gSavedSettings.getBOOL("ImporterPreprocessDAE"));
-
- if (force_disable_slm)
- {
- mModelLoader->mTrySLM = false;
- }
- else
- {
- // For MAINT-6647, we have set force_disable_slm to true,
- // which means this code path will never be taken. Trying to
- // re-use SLM files has never worked properly; in particular,
- // it tends to force the UI into strange checkbox options
- // which cannot be altered.
-
- //only try to load from slm if viewer is configured to do so and this is the
- //initial model load (not an LoD or physics shape)
- mModelLoader->mTrySLM = gSavedSettings.getBOOL("MeshImportUseSLM") && mUploadData.empty();
- }
- mModelLoader->start();
-
- mFMP->childSetTextArg("status", "[STATUS]", mFMP->getString("status_reading_file"));
-
- setPreviewLOD(lod);
-
- if ( getLoadState() >= LLModelLoader::ERROR_PARSING )
- {
- mFMP->childDisable("ok_btn");
- mFMP->childDisable( "calculate_btn" );
- }
-
- if (lod == mPreviewLOD)
- {
- mFMP->childSetValue("lod_file_" + lod_name[lod], mLODFile[lod]);
- }
- else if (lod == LLModel::LOD_PHYSICS)
- {
- mFMP->childSetValue("physics_file", mLODFile[lod]);
- }
-
- mFMP->openFloater();
-}
-
-void LLModelPreview::setPhysicsFromLOD(S32 lod)
-{
- assert_main_thread();
-
- if (lod >= 0 && lod <= 3)
- {
- mPhysicsSearchLOD = lod;
- mModel[LLModel::LOD_PHYSICS] = mModel[lod];
- mScene[LLModel::LOD_PHYSICS] = mScene[lod];
- mLODFile[LLModel::LOD_PHYSICS].clear();
- mFMP->childSetValue("physics_file", mLODFile[LLModel::LOD_PHYSICS]);
- mVertexBuffer[LLModel::LOD_PHYSICS].clear();
- rebuildUploadData();
- refresh();
- updateStatusMessages();
- }
-}
-
-void LLModelPreview::clearIncompatible(S32 lod)
-{
- //Don't discard models if specified model is the physic rep
- if ( lod == LLModel::LOD_PHYSICS )
- {
- return;
- }
-
- // at this point we don't care about sub-models,
- // different amount of sub-models means face count mismatch, not incompatibility
- U32 lod_size = countRootModels(mModel[lod]);
- for (U32 i = 0; i <= LLModel::LOD_HIGH; i++)
- { //clear out any entries that aren't compatible with this model
- if (i != lod)
- {
- if (countRootModels(mModel[i]) != lod_size)
- {
- mModel[i].clear();
- mScene[i].clear();
- mVertexBuffer[i].clear();
-
- if (i == LLModel::LOD_HIGH)
- {
- mBaseModel = mModel[lod];
- clearGLODGroup();
- mBaseScene = mScene[lod];
- mVertexBuffer[5].clear();
- }
- }
- }
- }
-}
-
-void LLModelPreview::clearGLODGroup()
-{
- if (mGroup)
- {
- for (std::map<LLPointer<LLModel>, U32>::iterator iter = mObject.begin(); iter != mObject.end(); ++iter)
- {
- glodDeleteObject(iter->second);
- stop_gloderror();
- }
- mObject.clear();
-
- glodDeleteGroup(mGroup);
- stop_gloderror();
- mGroup = 0;
- }
-}
-
-void LLModelPreview::loadModelCallback(S32 loaded_lod)
-{
- assert_main_thread();
-
- LLMutexLock lock(this);
- if (!mModelLoader)
- {
- mLoading = false ;
- return;
- }
- if(getLoadState() >= LLModelLoader::ERROR_PARSING)
- {
- mLoading = false ;
- mModelLoader = NULL;
- mLodsWithParsingError.push_back(loaded_lod);
- return ;
- }
-
- mLodsWithParsingError.erase(std::remove(mLodsWithParsingError.begin(), mLodsWithParsingError.end(), loaded_lod), mLodsWithParsingError.end());
- if(mLodsWithParsingError.empty())
- {
- mFMP->childEnable( "calculate_btn" );
- }
-
- // Copy determinations about rig so UI will reflect them
- //
- setRigValidForJointPositionUpload(mModelLoader->isRigValidForJointPositionUpload());
- setLegacyRigValid(mModelLoader->isLegacyRigValid());
-
- mModelLoader->loadTextures() ;
-
- if (loaded_lod == -1)
- { //populate all LoDs from model loader scene
- mBaseModel.clear();
- mBaseScene.clear();
-
- bool skin_weights = false;
- bool joint_positions = false;
- bool lock_scale_if_joint_position = false;
-
- for (S32 lod = 0; lod < LLModel::NUM_LODS; ++lod)
- { //for each LoD
-
- //clear scene and model info
- mScene[lod].clear();
- mModel[lod].clear();
- mVertexBuffer[lod].clear();
-
- if (mModelLoader->mScene.begin()->second[0].mLOD[lod].notNull())
- { //if this LoD exists in the loaded scene
-
- //copy scene to current LoD
- mScene[lod] = mModelLoader->mScene;
-
- //touch up copied scene to look like current LoD
- for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter)
- {
- LLModelLoader::model_instance_list& list = iter->second;
-
- for (LLModelLoader::model_instance_list::iterator list_iter = list.begin(); list_iter != list.end(); ++list_iter)
- {
- //override displayed model with current LoD
- list_iter->mModel = list_iter->mLOD[lod];
-
- if (!list_iter->mModel)
- {
- continue;
- }
-
- //add current model to current LoD's model list (LLModel::mLocalID makes a good vector index)
- S32 idx = list_iter->mModel->mLocalID;
-
- if (mModel[lod].size() <= idx)
- { //stretch model list to fit model at given index
- mModel[lod].resize(idx+1);
- }
-
- mModel[lod][idx] = list_iter->mModel;
- if (!list_iter->mModel->mSkinWeights.empty())
- {
- skin_weights = true;
-
- if (!list_iter->mModel->mSkinInfo.mAlternateBindMatrix.empty())
- {
- joint_positions = true;
- }
- if (list_iter->mModel->mSkinInfo.mLockScaleIfJointPosition)
- {
- lock_scale_if_joint_position = true;
- }
- }
- }
- }
- }
- }
-
- if (mFMP)
- {
- LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) mFMP;
-
- if (skin_weights)
- { //enable uploading/previewing of skin weights if present in .slm file
- fmp->enableViewOption("show_skin_weight");
- mViewOption["show_skin_weight"] = true;
- fmp->childSetValue("upload_skin", true);
- }
-
- if (joint_positions)
- {
- fmp->enableViewOption("show_joint_positions");
- mViewOption["show_joint_positions"] = true;
- fmp->childSetValue("upload_joints", true);
- }
-
- if (lock_scale_if_joint_position)
- {
- fmp->enableViewOption("lock_scale_if_joint_position");
- mViewOption["lock_scale_if_joint_position"] = true;
- fmp->childSetValue("lock_scale_if_joint_position", true);
- }
- }
-
- //copy high lod to base scene for LoD generation
- mBaseScene = mScene[LLModel::LOD_HIGH];
- mBaseModel = mModel[LLModel::LOD_HIGH];
-
- mDirty = true;
- resetPreviewTarget();
- }
- else
- { //only replace given LoD
- mModel[loaded_lod] = mModelLoader->mModelList;
- mScene[loaded_lod] = mModelLoader->mScene;
- mVertexBuffer[loaded_lod].clear();
-
- setPreviewLOD(loaded_lod);
-
- if (loaded_lod == LLModel::LOD_HIGH)
- { //save a copy of the highest LOD for automatic LOD manipulation
- if (mBaseModel.empty())
- { //first time we've loaded a model, auto-gen LoD
- mGenLOD = true;
- }
-
- mBaseModel = mModel[loaded_lod];
- clearGLODGroup();
-
- mBaseScene = mScene[loaded_lod];
- mVertexBuffer[5].clear();
- }
- else
- {
- BOOL importerDebug = gSavedSettings.getBOOL("ImporterDebug");
- BOOL legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching");
- if (!legacyMatching)
- {
- if (!mBaseModel.empty())
- {
- BOOL name_based = FALSE;
- BOOL has_submodels = FALSE;
- for (U32 idx = 0; idx < mBaseModel.size(); ++idx)
- {
- if (mBaseModel[idx]->mSubmodelID)
- { // don't do index-based renaming when the base model has submodels
- has_submodels = TRUE;
- if (importerDebug)
- {
- LL_INFOS() << "High LOD has submodels" << LL_ENDL;
- }
- break;
- }
- }
-
- for (U32 idx = 0; idx < mModel[loaded_lod].size(); ++idx)
- {
- std::string loaded_name = stripSuffix(mModel[loaded_lod][idx]->mLabel);
-
- LLModel* found_model = NULL;
- LLMatrix4 transform;
- FindModel(mBaseScene, loaded_name, found_model, transform);
- if (found_model)
- { // don't rename correctly named models (even if they are placed in a wrong order)
- name_based = TRUE;
- }
-
- if (mModel[loaded_lod][idx]->mSubmodelID)
- { // don't rename the models when loaded LOD model has submodels
- has_submodels = TRUE;
- }
- }
-
- if (importerDebug)
- {
- LL_INFOS() << "Loaded LOD " << loaded_lod << ": correct names" << (name_based ? "" : "NOT ") << "found; submodels " << (has_submodels ? "" : "NOT ") << "found" << LL_ENDL;
- }
-
- if (!name_based && !has_submodels)
- { // replace the name of the model loaded for any non-HIGH LOD to match the others (MAINT-5601)
- // this actually works like "ImporterLegacyMatching" for this particular LOD
- for (U32 idx = 0; idx < mModel[loaded_lod].size() && idx < mBaseModel.size(); ++idx)
- {
- std::string name = mBaseModel[idx]->mLabel;
- std::string loaded_name = stripSuffix(mModel[loaded_lod][idx]->mLabel);
-
- if (loaded_name != name)
- {
- switch (loaded_lod)
- {
- case LLModel::LOD_IMPOSTOR: name += "_LOD0"; break;
- case LLModel::LOD_LOW: name += "_LOD1"; break;
- case LLModel::LOD_MEDIUM: name += "_LOD2"; break;
- case LLModel::LOD_PHYSICS: name += "_PHYS"; break;
- case LLModel::LOD_HIGH: break;
- }
-
- if (importerDebug)
- {
- LL_WARNS() << "Loded model name " << mModel[loaded_lod][idx]->mLabel << " for LOD " << loaded_lod << " doesn't match the base model. Renaming to " << name << LL_ENDL;
- }
-
- mModel[loaded_lod][idx]->mLabel = name;
- }
- }
- }
- }
- }
- }
-
- clearIncompatible(loaded_lod);
-
- mDirty = true;
-
- if (loaded_lod == LLModel::LOD_HIGH)
- {
- resetPreviewTarget();
- }
- }
-
- mLoading = false;
- if (mFMP)
- {
- mFMP->getChild<LLCheckBoxCtrl>("confirm_checkbox")->set(FALSE);
- if (!mBaseModel.empty())
- {
- const std::string& model_name = mBaseModel[0]->getName();
- LLLineEditor* description_form = mFMP->getChild<LLLineEditor>("description_form");
- if (description_form->getText().empty())
- {
- description_form->setText(model_name);
- }
- }
- }
- refresh();
-
- mModelLoadedSignal();
-
- mModelLoader = NULL;
-}
-
-void LLModelPreview::resetPreviewTarget()
-{
- if ( mModelLoader )
- {
- mPreviewTarget = (mModelLoader->mExtents[0] + mModelLoader->mExtents[1]) * 0.5f;
- mPreviewScale = (mModelLoader->mExtents[1] - mModelLoader->mExtents[0]) * 0.5f;
- }
-
- setPreviewTarget(mPreviewScale.magVec()*10.f);
-}
-
-void LLModelPreview::generateNormals()
-{
- assert_main_thread();
-
- S32 which_lod = mPreviewLOD;
-
- if (which_lod > 4 || which_lod < 0 ||
- mModel[which_lod].empty())
- {
- return;
- }
-
- F32 angle_cutoff = mFMP->childGetValue("crease_angle").asReal();
-
- mRequestedCreaseAngle[which_lod] = angle_cutoff;
-
- angle_cutoff *= DEG_TO_RAD;
-
- if (which_lod == 3 && !mBaseModel.empty())
- {
- if(mBaseModelFacesCopy.empty())
- {
- mBaseModelFacesCopy.reserve(mBaseModel.size());
- for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it)
- {
- v_LLVolumeFace_t faces;
- (*it)->copyFacesTo(faces);
- mBaseModelFacesCopy.push_back(faces);
- }
- }
-
- for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it)
- {
- (*it)->generateNormals(angle_cutoff);
- }
-
- mVertexBuffer[5].clear();
- }
-
- bool perform_copy = mModelFacesCopy[which_lod].empty();
- if(perform_copy) {
- mModelFacesCopy[which_lod].reserve(mModel[which_lod].size());
- }
-
- for (LLModelLoader::model_list::iterator it = mModel[which_lod].begin(), itE = mModel[which_lod].end(); it != itE; ++it)
- {
- if(perform_copy)
- {
- v_LLVolumeFace_t faces;
- (*it)->copyFacesTo(faces);
- mModelFacesCopy[which_lod].push_back(faces);
- }
-
- (*it)->generateNormals(angle_cutoff);
- }
-
- mVertexBuffer[which_lod].clear();
- refresh();
- updateStatusMessages();
-}
-
-void LLModelPreview::restoreNormals()
+// static
+void LLFloaterModelPreview::addStringToLog(const std::ostringstream& strm, bool flash)
{
- S32 which_lod = mPreviewLOD;
-
- if (which_lod > 4 || which_lod < 0 ||
- mModel[which_lod].empty())
- {
- return;
- }
-
- if(!mBaseModelFacesCopy.empty())
- {
- llassert(mBaseModelFacesCopy.size() == mBaseModel.size());
-
- vv_LLVolumeFace_t::const_iterator itF = mBaseModelFacesCopy.begin();
- for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it, ++itF)
- {
- (*it)->copyFacesFrom((*itF));
- }
-
- mBaseModelFacesCopy.clear();
- }
-
- if(!mModelFacesCopy[which_lod].empty())
- {
- vv_LLVolumeFace_t::const_iterator itF = mModelFacesCopy[which_lod].begin();
- for (LLModelLoader::model_list::iterator it = mModel[which_lod].begin(), itE = mModel[which_lod].end(); it != itE; ++it, ++itF)
- {
- (*it)->copyFacesFrom((*itF));
- }
-
- mModelFacesCopy[which_lod].clear();
- }
-
- mVertexBuffer[which_lod].clear();
- refresh();
- updateStatusMessages();
+ if (sInstance)
+ {
+ sInstance->addStringToLogTab(strm.str(), flash);
+ }
}
-void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_limit)
+void LLFloaterModelPreview::clearAvatarTab()
{
- // Allow LoD from -1 to LLModel::LOD_PHYSICS
- if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1)
- {
- LL_WARNS() << "Invalid level of detail: " << which_lod << LL_ENDL;
- assert(which_lod >= -1 && which_lod < LLModel::NUM_LODS);
- return;
- }
-
- if (mBaseModel.empty())
- {
- return;
- }
-
- LLVertexBuffer::unbind();
-
- bool no_ff = LLGLSLShader::sNoFixedFunction;
- LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
- LLGLSLShader::sNoFixedFunction = false;
-
- if (shader)
- {
- shader->unbind();
- }
-
- stop_gloderror();
- static U32 cur_name = 1;
-
- S32 limit = -1;
-
- U32 triangle_count = 0;
-
- U32 instanced_triangle_count = 0;
-
- //get the triangle count for the whole scene
- for (LLModelLoader::scene::iterator iter = mBaseScene.begin(), endIter = mBaseScene.end(); iter != endIter; ++iter)
- {
- for (LLModelLoader::model_instance_list::iterator instance = iter->second.begin(), end_instance = iter->second.end(); instance != end_instance; ++instance)
- {
- LLModel* mdl = instance->mModel;
- if (mdl)
- {
- instanced_triangle_count += mdl->getNumTriangles();
- }
- }
- }
-
- //get the triangle count for the non-instanced set of models
- for (U32 i = 0; i < mBaseModel.size(); ++i)
- {
- triangle_count += mBaseModel[i]->getNumTriangles();
- }
-
- //get ratio of uninstanced triangles to instanced triangles
- F32 triangle_ratio = (F32) triangle_count / (F32) instanced_triangle_count;
-
- U32 base_triangle_count = triangle_count;
-
- U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;
-
- U32 lod_mode = 0;
-
- F32 lod_error_threshold = 0;
-
- // The LoD should be in range from Lowest to High
- if (which_lod > -1 && which_lod < NUM_LOD)
- {
- LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode_" + lod_name[which_lod]);
- if (iface)
- {
- lod_mode = iface->getFirstSelectedIndex();
- }
-
- lod_error_threshold = mFMP->childGetValue("lod_error_threshold_" + lod_name[which_lod]).asReal();
- }
-
- if (which_lod != -1)
- {
- mRequestedLoDMode[which_lod] = lod_mode;
- }
-
- if (lod_mode == 0)
- {
- lod_mode = GLOD_TRIANGLE_BUDGET;
-
- // The LoD should be in range from Lowest to High
- if (which_lod > -1 && which_lod < NUM_LOD)
- {
- limit = mFMP->childGetValue("lod_triangle_limit_" + lod_name[which_lod]).asInteger();
- //convert from "scene wide" to "non-instanced" triangle limit
- limit = (S32) ( (F32) limit*triangle_ratio );
- }
- }
- else
- {
- lod_mode = GLOD_ERROR_THRESHOLD;
- }
-
- bool object_dirty = false;
-
- if (mGroup == 0)
- {
- object_dirty = true;
- mGroup = cur_name++;
- glodNewGroup(mGroup);
- }
-
- if (object_dirty)
- {
- for (LLModelLoader::model_list::iterator iter = mBaseModel.begin(); iter != mBaseModel.end(); ++iter)
- { //build GLOD objects for each model in base model list
- LLModel* mdl = *iter;
-
- if (mObject[mdl] != 0)
- {
- glodDeleteObject(mObject[mdl]);
- }
-
- mObject[mdl] = cur_name++;
-
- glodNewObject(mObject[mdl], mGroup, GLOD_DISCRETE);
- stop_gloderror();
-
- if (iter == mBaseModel.begin() && !mdl->mSkinWeights.empty())
- { //regenerate vertex buffer for skinned models to prevent animation feedback during LOD generation
- mVertexBuffer[5].clear();
- }
-
- if (mVertexBuffer[5].empty())
- {
- genBuffers(5, false);
- }
-
- U32 tri_count = 0;
- for (U32 i = 0; i < mVertexBuffer[5][mdl].size(); ++i)
- {
- LLVertexBuffer* buff = mVertexBuffer[5][mdl][i];
- buff->setBuffer(type_mask & buff->getTypeMask());
-
- U32 num_indices = mVertexBuffer[5][mdl][i]->getNumIndices();
- if (num_indices > 2)
- {
- glodInsertElements(mObject[mdl], i, GL_TRIANGLES, num_indices, GL_UNSIGNED_SHORT, (U8*) mVertexBuffer[5][mdl][i]->getIndicesPointer(), 0, 0.f);
- }
- tri_count += num_indices/3;
- stop_gloderror();
- }
-
- glodBuildObject(mObject[mdl]);
- stop_gloderror();
- }
- }
-
-
- S32 start = LLModel::LOD_HIGH;
- S32 end = 0;
-
- if (which_lod != -1)
- {
- start = end = which_lod;
- }
-
- mMaxTriangleLimit = base_triangle_count;
-
- for (S32 lod = start; lod >= end; --lod)
- {
- if (which_lod == -1)
- {
- if (lod < start)
- {
- triangle_count /= decimation;
- }
- }
- else
- {
- if (enforce_tri_limit)
- {
- triangle_count = limit;
- }
- else
- {
- for (S32 j=LLModel::LOD_HIGH; j>which_lod; --j)
- {
- triangle_count /= decimation;
- }
- }
- }
-
- mModel[lod].clear();
- mModel[lod].resize(mBaseModel.size());
- mVertexBuffer[lod].clear();
-
- U32 actual_tris = 0;
- U32 actual_verts = 0;
- U32 submeshes = 0;
-
- mRequestedTriangleCount[lod] = (S32) ( (F32) triangle_count / triangle_ratio );
- mRequestedErrorThreshold[lod] = lod_error_threshold;
-
- glodGroupParameteri(mGroup, GLOD_ADAPT_MODE, lod_mode);
- stop_gloderror();
-
- glodGroupParameteri(mGroup, GLOD_ERROR_MODE, GLOD_OBJECT_SPACE_ERROR);
- stop_gloderror();
-
- glodGroupParameterf(mGroup, GLOD_OBJECT_SPACE_ERROR_THRESHOLD, lod_error_threshold);
- stop_gloderror();
-
- if (lod_mode != GLOD_TRIANGLE_BUDGET)
- {
- glodGroupParameteri(mGroup, GLOD_MAX_TRIANGLES, 0);
- }
- else
- {
- //SH-632: always add 1 to desired amount to avoid decimating below desired amount
- glodGroupParameteri(mGroup, GLOD_MAX_TRIANGLES, triangle_count+1);
- }
-
- stop_gloderror();
- glodAdaptGroup(mGroup);
- stop_gloderror();
-
- for (U32 mdl_idx = 0; mdl_idx < mBaseModel.size(); ++mdl_idx)
- {
- LLModel* base = mBaseModel[mdl_idx];
-
- GLint patch_count = 0;
- glodGetObjectParameteriv(mObject[base], GLOD_NUM_PATCHES, &patch_count);
- stop_gloderror();
-
- LLVolumeParams volume_params;
- volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
- mModel[lod][mdl_idx] = new LLModel(volume_params, 0.f);
-
- std::string name = base->mLabel;
-
- switch (lod)
- {
- case LLModel::LOD_IMPOSTOR: name += "_LOD0"; break;
- case LLModel::LOD_LOW: name += "_LOD1"; break;
- case LLModel::LOD_MEDIUM: name += "_LOD2"; break;
- case LLModel::LOD_PHYSICS: name += "_PHYS"; break;
- case LLModel::LOD_HIGH: break;
- }
-
- mModel[lod][mdl_idx]->mLabel = name;
- mModel[lod][mdl_idx]->mSubmodelID = base->mSubmodelID;
-
- GLint* sizes = new GLint[patch_count*2];
- glodGetObjectParameteriv(mObject[base], GLOD_PATCH_SIZES, sizes);
- stop_gloderror();
-
- GLint* names = new GLint[patch_count];
- glodGetObjectParameteriv(mObject[base], GLOD_PATCH_NAMES, names);
- stop_gloderror();
-
- mModel[lod][mdl_idx]->setNumVolumeFaces(patch_count);
+ LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");
+ LLScrollListCtrl *joints_list = panel->getChild<LLScrollListCtrl>("joints_list");
+ joints_list->deleteAllItems();
+ LLScrollListCtrl *joints_pos = panel->getChild<LLScrollListCtrl>("pos_overrides_list");
+ joints_pos->deleteAllItems(); mSelectedJointName.clear();
- LLModel* target_model = mModel[lod][mdl_idx];
-
- for (GLint i = 0; i < patch_count; ++i)
- {
- type_mask = mVertexBuffer[5][base][i]->getTypeMask();
-
- LLPointer<LLVertexBuffer> buff = new LLVertexBuffer(type_mask, 0);
-
- if (sizes[i*2+1] > 0 && sizes[i*2] > 0)
- {
- if (!buff->allocateBuffer(sizes[i * 2 + 1], sizes[i * 2], true))
- {
- // Todo: find a way to stop preview in this case instead of crashing
- LL_ERRS() << "Failed buffer allocation during preview LOD generation."
- << " Vertices: " << sizes[i * 2 + 1]
- << " Indices: " << sizes[i * 2] << LL_ENDL;
- }
- buff->setBuffer(type_mask);
- glodFillElements(mObject[base], names[i], GL_UNSIGNED_SHORT, (U8*) buff->getIndicesPointer());
- stop_gloderror();
- }
- else
- {
- // This face was eliminated or we failed to allocate buffer,
- // attempt to create a dummy triangle (one vertex, 3 indices, all 0)
- buff->allocateBuffer(1, 3, true);
- memset((U8*) buff->getMappedData(), 0, buff->getSize());
- memset((U8*) buff->getIndicesPointer(), 0, buff->getIndicesSize());
- }
-
- buff->validateRange(0, buff->getNumVerts()-1, buff->getNumIndices(), 0);
-
- LLStrider<LLVector3> pos;
- LLStrider<LLVector3> norm;
- LLStrider<LLVector2> tc;
- LLStrider<U16> index;
-
- buff->getVertexStrider(pos);
- if (type_mask & LLVertexBuffer::MAP_NORMAL)
- {
- buff->getNormalStrider(norm);
- }
- if (type_mask & LLVertexBuffer::MAP_TEXCOORD0)
- {
- buff->getTexCoord0Strider(tc);
- }
-
- buff->getIndexStrider(index);
-
- target_model->setVolumeFaceData(names[i], pos, norm, tc, index, buff->getNumVerts(), buff->getNumIndices());
- actual_tris += buff->getNumIndices()/3;
- actual_verts += buff->getNumVerts();
- ++submeshes;
-
- if (!validate_face(target_model->getVolumeFace(names[i])))
- {
- LL_ERRS() << "Invalid face generated during LOD generation." << LL_ENDL;
- }
- }
-
- //blind copy skin weights and just take closest skin weight to point on
- //decimated mesh for now (auto-generating LODs with skin weights is still a bit
- //of an open problem).
- target_model->mPosition = base->mPosition;
- target_model->mSkinWeights = base->mSkinWeights;
- target_model->mSkinInfo = base->mSkinInfo;
- //copy material list
- target_model->mMaterialList = base->mMaterialList;
-
- if (!validate_model(target_model))
- {
- LL_ERRS() << "Invalid model generated when creating LODs" << LL_ENDL;
- }
+ for (U32 i = 0; i < LLModel::NUM_LODS; ++i)
+ {
+ mJointOverrides[i].clear();
+ }
- delete [] sizes;
- delete [] names;
- }
+ LLTextBox *joint_total_descr = panel->getChild<LLTextBox>("conflicts_description");
+ joint_total_descr->setTextArg("[CONFLICTS]", llformat("%d", 0));
+ joint_total_descr->setTextArg("[JOINTS_COUNT]", llformat("%d", 0));
- //rebuild scene based on mBaseScene
- mScene[lod].clear();
- mScene[lod] = mBaseScene;
- for (U32 i = 0; i < mBaseModel.size(); ++i)
- {
- LLModel* mdl = mBaseModel[i];
- LLModel* target = mModel[lod][i];
- if (target)
- {
- for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter)
- {
- for (U32 j = 0; j < iter->second.size(); ++j)
- {
- if (iter->second[j].mModel == mdl)
- {
- iter->second[j].mModel = target;
- }
- }
- }
- }
- }
- }
-
- mResourceCost = calcResourceCost();
-
- LLVertexBuffer::unbind();
- LLGLSLShader::sNoFixedFunction = no_ff;
- if (shader)
- {
- shader->bind();
- }
+ LLTextBox *joint_pos_descr = panel->getChild<LLTextBox>("pos_overrides_descr");
+ joint_pos_descr->setTextArg("[JOINT]", std::string("mPelvis")); // Might be better to hide it
}
-void LLModelPreview::updateStatusMessages()
+void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides)
{
- assert_main_thread();
-
- //triangle/vertex/submesh count for each mesh asset for each lod
- std::vector<S32> tris[LLModel::NUM_LODS];
- std::vector<S32> verts[LLModel::NUM_LODS];
- std::vector<S32> submeshes[LLModel::NUM_LODS];
-
- //total triangle/vertex/submesh count for each lod
- S32 total_tris[LLModel::NUM_LODS];
- S32 total_verts[LLModel::NUM_LODS];
- S32 total_submeshes[LLModel::NUM_LODS];
-
- for (U32 i = 0; i < LLModel::NUM_LODS-1; i++)
+ S32 display_lod = mModelPreview->mPreviewLOD;
+ if (mModelPreview->mModel[display_lod].empty())
{
- total_tris[i] = 0;
- total_verts[i] = 0;
- total_submeshes[i] = 0;
+ mSelectedJointName.clear();
+ return;
}
- for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
- {
- LLModelInstance& instance = *iter;
-
- LLModel* model_high_lod = instance.mLOD[LLModel::LOD_HIGH];
- if (!model_high_lod)
- {
- setLoadState( LLModelLoader::ERROR_MATERIALS );
- mFMP->childDisable( "calculate_btn" );
- continue;
- }
-
- for (U32 i = 0; i < LLModel::NUM_LODS-1; i++)
- {
- LLModel* lod_model = instance.mLOD[i];
- if (!lod_model)
+ // Joints will be listed as long as they are listed in mAlternateBindMatrix
+ // even if they are for some reason identical to defaults.
+ // Todo: Are overrides always identical for all lods? They normally are, but there might be situations where they aren't.
+ if (mJointOverrides[display_lod].empty())
+ {
+ // populate map
+ for (LLModelLoader::scene::iterator iter = mModelPreview->mScene[display_lod].begin(); iter != mModelPreview->mScene[display_lod].end(); ++iter)
+ {
+ for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
{
- setLoadState( LLModelLoader::ERROR_MATERIALS );
- mFMP->childDisable( "calculate_btn" );
- }
- else
- {
- //for each model in the lod
- S32 cur_tris = 0;
- S32 cur_verts = 0;
- S32 cur_submeshes = lod_model->getNumVolumeFaces();
-
- for (S32 j = 0; j < cur_submeshes; ++j)
- { //for each submesh (face), add triangles and vertices to current total
- const LLVolumeFace& face = lod_model->getVolumeFace(j);
- cur_tris += face.mNumIndices/3;
- cur_verts += face.mNumVertices;
- }
-
- std::string instance_name = instance.mLabel;
-
- BOOL importerDebug = gSavedSettings.getBOOL("ImporterDebug");
- if (importerDebug)
+ LLModelInstance& instance = *model_iter;
+ LLModel* model = instance.mModel;
+ const LLMeshSkinInfo *skin = &model->mSkinInfo;
+ U32 joint_count = LLSkinningUtil::getMeshJointCount(skin);
+ U32 bind_count = highlight_overrides ? skin->mAlternateBindMatrix.size() : 0; // simply do not include overrides if data is not needed
+ if (bind_count > 0 && bind_count != joint_count)
{
- // Useful for debugging generalized complaints below about total submeshes which don't have enough
- // context to address exactly what needs to be fixed to move towards compliance with the rules.
- //
- LL_INFOS() << "Instance " << lod_model->mLabel << " LOD " << i << " Verts: " << cur_verts << LL_ENDL;
- LL_INFOS() << "Instance " << lod_model->mLabel << " LOD " << i << " Tris: " << cur_tris << LL_ENDL;
- LL_INFOS() << "Instance " << lod_model->mLabel << " LOD " << i << " Faces: " << cur_submeshes << LL_ENDL;
-
- LLModel::material_list::iterator mat_iter = lod_model->mMaterialList.begin();
- while (mat_iter != lod_model->mMaterialList.end())
+ std::ostringstream out;
+ out << "Invalid joint overrides for model " << model->getName();
+ out << ". Amount of joints " << joint_count;
+ out << ", is different from amount of overrides " << bind_count;
+ LL_INFOS() << out.str() << LL_ENDL;
+ addStringToLog(out.str(), true);
+ // Disable overrides for this model
+ bind_count = 0;
+ }
+ if (bind_count > 0)
+ {
+ for (U32 j = 0; j < joint_count; ++j)
{
- LL_INFOS() << "Instance " << lod_model->mLabel << " LOD " << i << " Material " << *(mat_iter) << LL_ENDL;
- mat_iter++;
+ const LLVector3& joint_pos = skin->mAlternateBindMatrix[j].getTranslation();
+ LLJointOverrideData &data = mJointOverrides[display_lod][skin->mJointNames[j]];
+
+ LLJoint* pJoint = LLModelPreview::lookupJointByName(skin->mJointNames[j], mModelPreview);
+ if (pJoint)
+ {
+ // see how voavatar uses aboveJointPosThreshold
+ if (pJoint->aboveJointPosThreshold(joint_pos))
+ {
+ // valid override
+ if (data.mPosOverrides.size() > 0
+ && (data.mPosOverrides.begin()->second - joint_pos).lengthSquared() > (LL_JOINT_TRESHOLD_POS_OFFSET * LL_JOINT_TRESHOLD_POS_OFFSET))
+ {
+ // File contains multiple meshes with conflicting joint offsets
+ // preview may be incorrect, upload result might wary (depends onto
+ // mesh_id that hasn't been generated yet).
+ data.mHasConflicts = true;
+ }
+ data.mPosOverrides[model->getName()] = joint_pos;
+ }
+ else
+ {
+ // default value, it won't be accounted for by avatar
+ data.mModelsNoOverrides.insert(model->getName());
+ }
+ }
}
}
-
- //add this model to the lod total
- total_tris[i] += cur_tris;
- total_verts[i] += cur_verts;
- total_submeshes[i] += cur_submeshes;
-
- //store this model's counts to asset data
- tris[i].push_back(cur_tris);
- verts[i].push_back(cur_verts);
- submeshes[i].push_back(cur_submeshes);
- }
- }
+ else
+ {
+ for (U32 j = 0; j < joint_count; ++j)
+ {
+ LLJointOverrideData &data = mJointOverrides[display_lod][skin->mJointNames[j]];
+ data.mModelsNoOverrides.insert(model->getName());
+ }
+ }
+ }
+ }
}
- if (mMaxTriangleLimit == 0)
- {
- mMaxTriangleLimit = total_tris[LLModel::LOD_HIGH];
- }
-
- bool has_degenerate = false;
-
- {//check for degenerate triangles in physics mesh
- U32 lod = LLModel::LOD_PHYSICS;
- const LLVector4a scale(0.5f);
- for (U32 i = 0; i < mModel[lod].size() && !has_degenerate; ++i)
- { //for each model in the lod
- if (mModel[lod][i] && mModel[lod][i]->mPhysics.mHull.empty())
- { //no decomp exists
- S32 cur_submeshes = mModel[lod][i]->getNumVolumeFaces();
- for (S32 j = 0; j < cur_submeshes && !has_degenerate; ++j)
- { //for each submesh (face), add triangles and vertices to current total
- LLVolumeFace& face = mModel[lod][i]->getVolumeFace(j);
- for (S32 k = 0; (k < face.mNumIndices) && !has_degenerate; )
- {
- U16 index_a = face.mIndices[k+0];
- U16 index_b = face.mIndices[k+1];
- U16 index_c = face.mIndices[k+2];
-
- LLVector4a v1; v1.setMul(face.mPositions[index_a], scale);
- LLVector4a v2; v2.setMul(face.mPositions[index_b], scale);
- LLVector4a v3; v3.setMul(face.mPositions[index_c], scale);
-
- if (ll_is_degenerate(v1,v2,v3))
- {
- has_degenerate = true;
- }
- else
- {
- k += 3;
- }
- }
- }
- }
- }
- }
-
- mFMP->childSetTextArg("submeshes_info", "[SUBMESHES]", llformat("%d", total_submeshes[LLModel::LOD_HIGH]));
-
- std::string mesh_status_na = mFMP->getString("mesh_status_na");
-
- S32 upload_status[LLModel::LOD_HIGH+1];
-
- mModelNoErrors = true;
-
- const U32 lod_high = LLModel::LOD_HIGH;
- U32 high_submodel_count = mModel[lod_high].size() - countRootModels(mModel[lod_high]);
-
- for (S32 lod = 0; lod <= lod_high; ++lod)
- {
- upload_status[lod] = 0;
-
- std::string message = "mesh_status_good";
-
- if (total_tris[lod] > 0)
- {
- mFMP->childSetValue(lod_triangles_name[lod], llformat("%d", total_tris[lod]));
- mFMP->childSetValue(lod_vertices_name[lod], llformat("%d", total_verts[lod]));
- }
- else
- {
- if (lod == lod_high)
- {
- upload_status[lod] = 2;
- message = "mesh_status_missing_lod";
- }
- else
- {
- for (S32 i = lod-1; i >= 0; --i)
- {
- if (total_tris[i] > 0)
- {
- upload_status[lod] = 2;
- message = "mesh_status_missing_lod";
- }
- }
- }
-
- mFMP->childSetValue(lod_triangles_name[lod], mesh_status_na);
- mFMP->childSetValue(lod_vertices_name[lod], mesh_status_na);
- }
-
- if (lod != lod_high)
- {
- if (total_submeshes[lod] && total_submeshes[lod] != total_submeshes[lod_high])
- { //number of submeshes is different
- message = "mesh_status_submesh_mismatch";
- upload_status[lod] = 2;
- }
- else if (mModel[lod].size() - countRootModels(mModel[lod]) != high_submodel_count)
- {//number of submodels is different, not all faces are matched correctly.
- message = "mesh_status_submesh_mismatch";
- upload_status[lod] = 2;
- // Note: Submodels in instance were loaded from higher LOD and as result face count
- // returns same value and total_submeshes[lod] is identical to high_lod one.
- }
- else if (!tris[lod].empty() && tris[lod].size() != tris[lod_high].size())
- { //number of meshes is different
- message = "mesh_status_mesh_mismatch";
- upload_status[lod] = 2;
- }
- else if (!verts[lod].empty())
- {
- S32 sum_verts_higher_lod = 0;
- S32 sum_verts_this_lod = 0;
- for (U32 i = 0; i < verts[lod].size(); ++i)
- {
- sum_verts_higher_lod += ((i < verts[lod+1].size()) ? verts[lod+1][i] : 0);
- sum_verts_this_lod += verts[lod][i];
- }
-
- if ((sum_verts_higher_lod > 0) &&
- (sum_verts_this_lod > sum_verts_higher_lod))
- {
- //too many vertices in this lod
- message = "mesh_status_too_many_vertices";
- upload_status[lod] = 1;
- }
- }
- }
-
- LLIconCtrl* icon = mFMP->getChild<LLIconCtrl>(lod_icon_name[lod]);
- LLUIImagePtr img = LLUI::getUIImage(lod_status_image[upload_status[lod]]);
- icon->setVisible(true);
- icon->setImage(img);
-
- if (upload_status[lod] >= 2)
- {
- mModelNoErrors = false;
- }
-
- if (lod == mPreviewLOD)
- {
- mFMP->childSetValue("lod_status_message_text", mFMP->getString(message));
- icon = mFMP->getChild<LLIconCtrl>("lod_status_message_icon");
- icon->setImage(img);
- }
-
- updateLodControls(lod);
- }
-
-
- //warn if hulls have more than 256 points in them
- BOOL physExceededVertexLimit = FALSE;
- for (U32 i = 0; mModelNoErrors && i < mModel[LLModel::LOD_PHYSICS].size(); ++i)
- {
- LLModel* mdl = mModel[LLModel::LOD_PHYSICS][i];
-
- if (mdl)
- {
- for (U32 j = 0; j < mdl->mPhysics.mHull.size(); ++j)
- {
- if (mdl->mPhysics.mHull[j].size() > 256)
- {
- physExceededVertexLimit = TRUE;
- LL_INFOS() << "Physical model " << mdl->mLabel << " exceeds vertex per hull limitations." << LL_ENDL;
- break;
- }
- }
- }
- }
- mFMP->childSetVisible("physics_status_message_text", physExceededVertexLimit);
- LLIconCtrl* physStatusIcon = mFMP->getChild<LLIconCtrl>("physics_status_message_icon");
- physStatusIcon->setVisible(physExceededVertexLimit);
- if (physExceededVertexLimit)
- {
- mFMP->childSetValue("physics_status_message_text", mFMP->getString("phys_status_vertex_limit_exceeded"));
- LLUIImagePtr img = LLUI::getUIImage("ModelImport_Status_Warning");
- physStatusIcon->setImage(img);
- }
-
- if (getLoadState() >= LLModelLoader::ERROR_PARSING)
- {
- mModelNoErrors = false;
- LL_INFOS() << "Loader returned errors, model can't be uploaded" << LL_ENDL;
- }
-
- bool uploadingSkin = mFMP->childGetValue("upload_skin").asBoolean();
- bool uploadingJointPositions = mFMP->childGetValue("upload_joints").asBoolean();
-
- if ( uploadingSkin )
- {
- if ( uploadingJointPositions && !isRigValidForJointPositionUpload() )
- {
- mModelNoErrors = false;
- LL_INFOS() << "Invalid rig, there might be issues with uploading Joint positions" << LL_ENDL;
- }
- }
-
- if(mModelNoErrors && mModelLoader)
- {
- if(!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean())
- {
- // Some textures are still loading, prevent upload until they are done
- mModelNoErrors = false;
- }
- }
-
- // Todo: investigate use of has_degenerate and include into mModelNoErrors upload blocking mechanics
- // current use of has_degenerate won't block upload permanently - later checks will restore the button
- if (!mModelNoErrors || has_degenerate)
- {
- mFMP->childDisable("ok_btn");
- }
+ LLPanel *panel = mTabContainer->getPanelByName("rigging_panel");
+ LLScrollListCtrl *joints_list = panel->getChild<LLScrollListCtrl>("joints_list");
- if (mModelNoErrors && mLodsWithParsingError.empty())
- {
- mFMP->childEnable("calculate_btn");
- }
- else
+ if (joints_list->isEmpty())
{
- mFMP->childDisable("calculate_btn");
- }
-
- //add up physics triangles etc
- S32 phys_tris = 0;
- S32 phys_hulls = 0;
- S32 phys_points = 0;
-
- //get the triangle count for the whole scene
- for (LLModelLoader::scene::iterator iter = mScene[LLModel::LOD_PHYSICS].begin(), endIter = mScene[LLModel::LOD_PHYSICS].end(); iter != endIter; ++iter)
- {
- for (LLModelLoader::model_instance_list::iterator instance = iter->second.begin(), end_instance = iter->second.end(); instance != end_instance; ++instance)
- {
- LLModel* model = instance->mModel;
- if (model)
- {
- S32 cur_submeshes = model->getNumVolumeFaces();
-
- LLModel::convex_hull_decomposition& decomp = model->mPhysics.mHull;
-
- if (!decomp.empty())
- {
- phys_hulls += decomp.size();
- for (U32 i = 0; i < decomp.size(); ++i)
- {
- phys_points += decomp[i].size();
- }
- }
- else
- { //choose physics shape OR decomposition, can't use both
- for (S32 j = 0; j < cur_submeshes; ++j)
- { //for each submesh (face), add triangles and vertices to current total
- const LLVolumeFace& face = model->getVolumeFace(j);
- phys_tris += face.mNumIndices/3;
- }
- }
- }
- }
- }
-
- if (phys_tris > 0)
- {
- mFMP->childSetTextArg("physics_triangles", "[TRIANGLES]", llformat("%d", phys_tris));
- }
- else
- {
- mFMP->childSetTextArg("physics_triangles", "[TRIANGLES]", mesh_status_na);
- }
-
- if (phys_hulls > 0)
- {
- mFMP->childSetTextArg("physics_hulls", "[HULLS]", llformat("%d", phys_hulls));
- mFMP->childSetTextArg("physics_points", "[POINTS]", llformat("%d", phys_points));
- }
- else
- {
- mFMP->childSetTextArg("physics_hulls", "[HULLS]", mesh_status_na);
- mFMP->childSetTextArg("physics_points", "[POINTS]", mesh_status_na);
- }
-
- LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
- if (fmp)
- {
- if (phys_tris > 0 || phys_hulls > 0)
- {
- if (!fmp->isViewOptionEnabled("show_physics"))
- {
- fmp->enableViewOption("show_physics");
- mViewOption["show_physics"] = true;
- fmp->childSetValue("show_physics", true);
- }
- }
- else
- {
- fmp->disableViewOption("show_physics");
- mViewOption["show_physics"] = false;
- fmp->childSetValue("show_physics", false);
-
- }
-
- //bool use_hull = fmp->childGetValue("physics_use_hull").asBoolean();
-
- //fmp->childSetEnabled("physics_optimize", !use_hull);
-
- bool enable = (phys_tris > 0 || phys_hulls > 0) && fmp->mCurRequest.empty();
- //enable = enable && !use_hull && fmp->childGetValue("physics_optimize").asBoolean();
-
- //enable/disable "analysis" UI
- LLPanel* panel = fmp->getChild<LLPanel>("physics analysis");
- LLView* child = panel->getFirstChild();
- while (child)
- {
- child->setEnabled(enable);
- child = panel->findNextSibling(child);
- }
-
- enable = phys_hulls > 0 && fmp->mCurRequest.empty();
- //enable/disable "simplification" UI
- panel = fmp->getChild<LLPanel>("physics simplification");
- child = panel->getFirstChild();
- while (child)
- {
- child->setEnabled(enable);
- child = panel->findNextSibling(child);
- }
-
- if (fmp->mCurRequest.empty())
- {
- fmp->childSetVisible("Simplify", true);
- fmp->childSetVisible("simplify_cancel", false);
- fmp->childSetVisible("Decompose", true);
- fmp->childSetVisible("decompose_cancel", false);
-
- if (phys_hulls > 0)
- {
- fmp->childEnable("Simplify");
- }
-
- if (phys_tris || phys_hulls > 0)
- {
- fmp->childEnable("Decompose");
- }
- }
- else
- {
- fmp->childEnable("simplify_cancel");
- fmp->childEnable("decompose_cancel");
- }
- }
-
-
- LLCtrlSelectionInterface* iface = fmp->childGetSelectionInterface("physics_lod_combo");
- S32 which_mode = 0;
- S32 file_mode = 1;
- if (iface)
- {
- which_mode = iface->getFirstSelectedIndex();
- file_mode = iface->getItemCount() - 1;
- }
-
- if (which_mode == file_mode)
- {
- mFMP->childEnable("physics_file");
- mFMP->childEnable("physics_browse");
- }
- else
- {
- mFMP->childDisable("physics_file");
- mFMP->childDisable("physics_browse");
- }
-
- LLSpinCtrl* crease = mFMP->getChild<LLSpinCtrl>("crease_angle");
-
- if (mRequestedCreaseAngle[mPreviewLOD] == -1.f)
- {
- mFMP->childSetColor("crease_label", LLColor4::grey);
- crease->forceSetValue(75.f);
- }
- else
- {
- mFMP->childSetColor("crease_label", LLColor4::white);
- crease->forceSetValue(mRequestedCreaseAngle[mPreviewLOD]);
- }
-
- mModelUpdatedSignal(true);
-
-}
-
-void LLModelPreview::updateLodControls(S32 lod)
-{
- if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::LOD_HIGH)
- {
- LL_WARNS() << "Invalid level of detail: " << lod << LL_ENDL;
- assert(lod >= LLModel::LOD_IMPOSTOR && lod <= LLModel::LOD_HIGH);
- return;
- }
-
- const char* lod_controls[] =
- {
- "lod_mode_",
- "lod_triangle_limit_",
- "lod_error_threshold_"
- };
- const U32 num_lod_controls = sizeof(lod_controls)/sizeof(char*);
-
- const char* file_controls[] =
- {
- "lod_browse_",
- "lod_file_",
- };
- const U32 num_file_controls = sizeof(file_controls)/sizeof(char*);
-
- LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
- if (!fmp) return;
-
- LLComboBox* lod_combo = mFMP->findChild<LLComboBox>("lod_source_" + lod_name[lod]);
- if (!lod_combo) return;
-
- S32 lod_mode = lod_combo->getCurrentIndex();
- if (lod_mode == LOD_FROM_FILE) // LoD from file
- {
- fmp->mLODMode[lod] = 0;
- for (U32 i = 0; i < num_file_controls; ++i)
- {
- mFMP->childSetVisible(file_controls[i] + lod_name[lod], true);
- }
-
- for (U32 i = 0; i < num_lod_controls; ++i)
- {
- mFMP->childSetVisible(lod_controls[i] + lod_name[lod], false);
- }
- }
- else if (lod_mode == USE_LOD_ABOVE) // use LoD above
- {
- fmp->mLODMode[lod] = 2;
- for (U32 i = 0; i < num_file_controls; ++i)
- {
- mFMP->childSetVisible(file_controls[i] + lod_name[lod], false);
- }
-
- for (U32 i = 0; i < num_lod_controls; ++i)
- {
- mFMP->childSetVisible(lod_controls[i] + lod_name[lod], false);
- }
-
- if (lod < LLModel::LOD_HIGH)
- {
- mModel[lod] = mModel[lod + 1];
- mScene[lod] = mScene[lod + 1];
- mVertexBuffer[lod].clear();
-
- // Also update lower LoD
- if (lod > LLModel::LOD_IMPOSTOR)
- {
- updateLodControls(lod - 1);
- }
- }
- }
- else // auto generate, the default case for all LoDs except High
- {
- fmp->mLODMode[lod] = 1;
-
- //don't actually regenerate lod when refreshing UI
- mLODFrozen = true;
-
- for (U32 i = 0; i < num_file_controls; ++i)
- {
- mFMP->getChildView(file_controls[i] + lod_name[lod])->setVisible(false);
- }
-
- for (U32 i = 0; i < num_lod_controls; ++i)
- {
- mFMP->getChildView(lod_controls[i] + lod_name[lod])->setVisible(true);
- }
-
-
- LLSpinCtrl* threshold = mFMP->getChild<LLSpinCtrl>("lod_error_threshold_" + lod_name[lod]);
- LLSpinCtrl* limit = mFMP->getChild<LLSpinCtrl>("lod_triangle_limit_" + lod_name[lod]);
-
- limit->setMaxValue(mMaxTriangleLimit);
- limit->forceSetValue(mRequestedTriangleCount[lod]);
-
- threshold->forceSetValue(mRequestedErrorThreshold[lod]);
-
- mFMP->getChild<LLComboBox>("lod_mode_" + lod_name[lod])->selectNthItem(mRequestedLoDMode[lod]);
-
- if (mRequestedLoDMode[lod] == 0)
- {
- limit->setVisible(true);
- threshold->setVisible(false);
-
- limit->setMaxValue(mMaxTriangleLimit);
- limit->setIncrement(mMaxTriangleLimit/32);
- }
- else
- {
- limit->setVisible(false);
- threshold->setVisible(true);
- }
-
- mLODFrozen = false;
- }
-}
-
-void LLModelPreview::setPreviewTarget(F32 distance)
-{
- mCameraDistance = distance;
- mCameraZoom = 1.f;
- mCameraPitch = 0.f;
- mCameraYaw = 0.f;
- mCameraOffset.clearVec();
-}
-
-void LLModelPreview::clearBuffers()
-{
- for (U32 i = 0; i < 6; i++)
- {
- mVertexBuffer[i].clear();
- }
-}
-
-void LLModelPreview::genBuffers(S32 lod, bool include_skin_weights)
-{
- U32 tri_count = 0;
- U32 vertex_count = 0;
- U32 mesh_count = 0;
-
-
- LLModelLoader::model_list* model = NULL;
-
- if (lod < 0 || lod > 4)
- {
- model = &mBaseModel;
- lod = 5;
- }
- else
- {
- model = &(mModel[lod]);
- }
-
- if (!mVertexBuffer[lod].empty())
- {
- mVertexBuffer[lod].clear();
- }
-
- mVertexBuffer[lod].clear();
-
- LLModelLoader::model_list::iterator base_iter = mBaseModel.begin();
-
- for (LLModelLoader::model_list::iterator iter = model->begin(); iter != model->end(); ++iter)
- {
- LLModel* mdl = *iter;
- if (!mdl)
- {
- continue;
- }
-
- LLModel* base_mdl = *base_iter;
- base_iter++;
-
- S32 num_faces = mdl->getNumVolumeFaces();
- for (S32 i = 0; i < num_faces; ++i)
- {
- const LLVolumeFace &vf = mdl->getVolumeFace(i);
- U32 num_vertices = vf.mNumVertices;
- U32 num_indices = vf.mNumIndices;
-
- if (!num_vertices || ! num_indices)
- {
- continue;
- }
-
- LLVertexBuffer* vb = NULL;
+ // Populate table
- bool skinned = include_skin_weights && !mdl->mSkinWeights.empty();
+ std::map<std::string, std::string> joint_alias_map;
+ mModelPreview->getJointAliases(joint_alias_map);
- U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0 ;
-
- if (skinned)
- {
- mask |= LLVertexBuffer::MAP_WEIGHT4;
- }
-
- vb = new LLVertexBuffer(mask, 0);
-
- if (!vb->allocateBuffer(num_vertices, num_indices, TRUE))
- {
- // We are likely to crash due this failure, if this happens, find a way to gracefully stop preview
- LL_WARNS() << "Failed to allocate Vertex Buffer for model preview "
- << num_vertices << " vertices and "
- << num_indices << " indices" << LL_ENDL;
- }
-
- LLStrider<LLVector3> vertex_strider;
- LLStrider<LLVector3> normal_strider;
- LLStrider<LLVector2> tc_strider;
- LLStrider<U16> index_strider;
- LLStrider<LLVector4> weights_strider;
-
- vb->getVertexStrider(vertex_strider);
- vb->getIndexStrider(index_strider);
-
- if (skinned)
- {
- vb->getWeight4Strider(weights_strider);
- }
-
- LLVector4a::memcpyNonAliased16((F32*) vertex_strider.get(), (F32*) vf.mPositions, num_vertices*4*sizeof(F32));
-
- if (vf.mTexCoords)
- {
- vb->getTexCoord0Strider(tc_strider);
- S32 tex_size = (num_vertices*2*sizeof(F32)+0xF) & ~0xF;
- LLVector4a::memcpyNonAliased16((F32*) tc_strider.get(), (F32*) vf.mTexCoords, tex_size);
- }
-
- if (vf.mNormals)
- {
- vb->getNormalStrider(normal_strider);
- LLVector4a::memcpyNonAliased16((F32*) normal_strider.get(), (F32*) vf.mNormals, num_vertices*4*sizeof(F32));
- }
-
- if (skinned)
- {
- for (U32 i = 0; i < num_vertices; i++)
- {
- //find closest weight to vf.mVertices[i].mPosition
- LLVector3 pos(vf.mPositions[i].getF32ptr());
-
- const LLModel::weight_list& weight_list = base_mdl->getJointInfluences(pos);
- llassert(weight_list.size()>0 && weight_list.size() <= 4); // LLModel::loadModel() should guarantee this
-
- LLVector4 w(0,0,0,0);
-
- for (U32 i = 0; i < weight_list.size(); ++i)
- {
- F32 wght = llclamp(weight_list[i].mWeight, 0.001f, 0.999f);
- F32 joint = (F32) weight_list[i].mJointIdx;
- w.mV[i] = joint + wght;
- llassert(w.mV[i]-(S32)w.mV[i]>0.0f); // because weights are non-zero, and range of wt values
- //should not cause floating point precision issues.
- }
-
- *(weights_strider++) = w;
- }
- }
-
- // build indices
- for (U32 i = 0; i < num_indices; i++)
- {
- *(index_strider++) = vf.mIndices[i];
- }
-
- mVertexBuffer[lod][mdl].push_back(vb);
+ S32 conflicts = 0;
+ joint_override_data_map_t::iterator joint_iter = mJointOverrides[display_lod].begin();
+ joint_override_data_map_t::iterator joint_end = mJointOverrides[display_lod].end();
+ while (joint_iter != joint_end)
+ {
+ const std::string& listName = joint_iter->first;
- vertex_count += num_vertices;
- tri_count += num_indices/3;
- ++mesh_count;
+ LLScrollListItem::Params item_params;
+ item_params.value(listName);
- }
- }
-}
+ LLScrollListCell::Params cell_params;
+ cell_params.font = LLFontGL::getFontSansSerif();
+ cell_params.value = listName;
+ if (joint_alias_map.find(listName) == joint_alias_map.end())
+ {
+ // Missing names
+ cell_params.color = LLColor4::red;
+ }
+ if (joint_iter->second.mHasConflicts)
+ {
+ // Conflicts
+ cell_params.color = LLColor4::orange;
+ conflicts++;
+ }
+ if (highlight_overrides && joint_iter->second.mPosOverrides.size() > 0)
+ {
+ cell_params.font.style = "BOLD";
+ }
-void LLModelPreview::update()
-{
- if (mGenLOD)
- {
- bool subscribe_for_generation = mLodsQuery.empty();
- mGenLOD = false;
- mDirty = true;
- mLodsQuery.clear();
+ item_params.columns.add(cell_params);
- for (S32 lod = LLModel::LOD_HIGH; lod >= 0; --lod)
- {
- // adding all lods into query for generation
- mLodsQuery.push_back(lod);
+ joints_list->addRow(item_params, ADD_BOTTOM);
+ joint_iter++;
}
-
- if (subscribe_for_generation)
+ joints_list->selectFirstItem();
+ LLScrollListItem *selected = joints_list->getFirstSelected();
+ if (selected)
{
- doOnIdleRepeating(lodQueryCallback);
+ mSelectedJointName = selected->getValue().asString();
}
- }
- if (mDirty && mLodsQuery.empty())
- {
- mDirty = false;
- mResourceCost = calcResourceCost();
- refresh();
- updateStatusMessages();
- }
-}
-
-//-----------------------------------------------------------------------------
-// createPreviewAvatar
-//-----------------------------------------------------------------------------
-void LLModelPreview::createPreviewAvatar( void )
-{
- mPreviewAvatar = (LLVOAvatar*)gObjectList.createObjectViewer( LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR );
- if ( mPreviewAvatar )
- {
- mPreviewAvatar->createDrawable( &gPipeline );
- mPreviewAvatar->mSpecialRenderMode = 1;
- mPreviewAvatar->startMotion( ANIM_AGENT_STAND );
- mPreviewAvatar->hideSkirt();
- }
- else
- {
- LL_INFOS() << "Failed to create preview avatar for upload model window" << LL_ENDL;
- }
-}
-
-//static
-U32 LLModelPreview::countRootModels(LLModelLoader::model_list models)
-{
- U32 root_models = 0;
- model_list::iterator model_iter = models.begin();
- while (model_iter != models.end())
- {
- LLModel* mdl = *model_iter;
- if (mdl && mdl->mSubmodelID == 0)
- {
- root_models++;
- }
- model_iter++;
- }
- return root_models;
-}
-
-void LLModelPreview::loadedCallback(
- LLModelLoader::scene& scene,
- LLModelLoader::model_list& model_list,
- S32 lod,
- void* opaque)
-{
- LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque);
- if (pPreview && !LLModelPreview::sIgnoreLoadedCallback)
- {
- pPreview->loadModelCallback(lod);
- }
-}
-
-void LLModelPreview::stateChangedCallback(U32 state,void* opaque)
-{
- LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque);
- if (pPreview)
- {
- pPreview->setLoadState(state);
- }
-}
-
-LLJoint* LLModelPreview::lookupJointByName(const std::string& str, void* opaque)
-{
- LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque);
- if (pPreview)
- {
- return pPreview->getPreviewAvatar()->getJoint(str);
- }
- return NULL;
-}
-
-U32 LLModelPreview::loadTextures(LLImportMaterial& material,void* opaque)
-{
- (void)opaque;
-
- if (material.mDiffuseMapFilename.size())
- {
- material.mOpaqueData = new LLPointer< LLViewerFetchedTexture >;
- LLPointer< LLViewerFetchedTexture >& tex = (*reinterpret_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData));
-
- tex = LLViewerTextureManager::getFetchedTextureFromUrl("file://" + material.mDiffuseMapFilename, FTT_LOCAL_FILE, TRUE, LLGLTexture::BOOST_PREVIEW);
- tex->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, TRUE, FALSE, opaque, NULL, FALSE);
- tex->forceToSaveRawImage(0, F32_MAX);
- material.setDiffuseMap(tex->getID()); // record tex ID
- return 1;
- }
-
- material.mOpaqueData = NULL;
- return 0;
+ LLTextBox *joint_conf_descr = panel->getChild<LLTextBox>("conflicts_description");
+ joint_conf_descr->setTextArg("[CONFLICTS]", llformat("%d", conflicts));
+ joint_conf_descr->setTextArg("[JOINTS_COUNT]", llformat("%d", mJointOverrides[display_lod].size()));
+ }
}
-void LLModelPreview::addEmptyFace( LLModel* pTarget )
-{
- U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;
-
- LLPointer<LLVertexBuffer> buff = new LLVertexBuffer(type_mask, 0);
-
- buff->allocateBuffer(1, 3, true);
- memset( (U8*) buff->getMappedData(), 0, buff->getSize() );
- memset( (U8*) buff->getIndicesPointer(), 0, buff->getIndicesSize() );
-
- buff->validateRange( 0, buff->getNumVerts()-1, buff->getNumIndices(), 0 );
-
- LLStrider<LLVector3> pos;
- LLStrider<LLVector3> norm;
- LLStrider<LLVector2> tc;
- LLStrider<U16> index;
-
- buff->getVertexStrider(pos);
-
- if ( type_mask & LLVertexBuffer::MAP_NORMAL )
- {
- buff->getNormalStrider(norm);
- }
- if ( type_mask & LLVertexBuffer::MAP_TEXCOORD0 )
- {
- buff->getTexCoord0Strider(tc);
- }
-
- buff->getIndexStrider(index);
-
- //resize face array
- int faceCnt = pTarget->getNumVolumeFaces();
- pTarget->setNumVolumeFaces( faceCnt+1 );
- pTarget->setVolumeFaceData( faceCnt+1, pos, norm, tc, index, buff->getNumVerts(), buff->getNumIndices() );
-
-}
-
//-----------------------------------------------------------------------------
-// render()
+// addStringToLogTab()
//-----------------------------------------------------------------------------
-BOOL LLModelPreview::render()
+void LLFloaterModelPreview::addStringToLogTab(const std::string& str, bool flash)
{
- assert_main_thread();
-
- LLMutexLock lock(this);
- mNeedsUpdate = FALSE;
-
- bool use_shaders = LLGLSLShader::sNoFixedFunction;
-
- bool edges = mViewOption["show_edges"];
- bool joint_positions = mViewOption["show_joint_positions"];
- bool skin_weight = mViewOption["show_skin_weight"];
- bool textures = mViewOption["show_textures"];
- bool physics = mViewOption["show_physics"];
-
- S32 width = getWidth();
- S32 height = getHeight();
-
- LLGLSUIDefault def; // GL_BLEND, GL_ALPHA_TEST, GL_CULL_FACE, depth test
- LLGLDisable no_blend(GL_BLEND);
- LLGLDepthTest depth(GL_FALSE); // SL-12781 disable z-buffer to render background color
- LLGLDisable fog(GL_FOG);
-
- {
- if (use_shaders)
- {
- gUIProgram.bind();
- }
- //clear background to grey
- gGL.matrixMode(LLRender::MM_PROJECTION);
- gGL.pushMatrix();
- gGL.loadIdentity();
- gGL.ortho(0.0f, width, 0.0f, height, -1.0f, 1.0f);
-
- gGL.matrixMode(LLRender::MM_MODELVIEW);
- gGL.pushMatrix();
- gGL.loadIdentity();
-
- gGL.color4f(0.169f, 0.169f, 0.169f, 1.f);
-
- gl_rect_2d_simple( width, height );
-
- gGL.matrixMode(LLRender::MM_PROJECTION);
- gGL.popMatrix();
-
- gGL.matrixMode(LLRender::MM_MODELVIEW);
- gGL.popMatrix();
- if (use_shaders)
- {
- gUIProgram.unbind();
- }
- }
-
- LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
-
- bool has_skin_weights = false;
- bool upload_skin = mFMP->childGetValue("upload_skin").asBoolean();
- bool upload_joints = mFMP->childGetValue("upload_joints").asBoolean();
-
- if ( upload_joints != mLastJointUpdate )
- {
- mLastJointUpdate = upload_joints;
- }
-
- for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
- {
- for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
- {
- LLModelInstance& instance = *model_iter;
- LLModel* model = instance.mModel;
- model->mPelvisOffset = mPelvisZOffset;
- if (!model->mSkinWeights.empty())
- {
- has_skin_weights = true;
- }
- }
- }
-
- if (has_skin_weights && lodsReady())
- { //model has skin weights, enable view options for skin weights and joint positions
- if (fmp && isLegacyRigValid() )
- {
- fmp->enableViewOption("show_skin_weight");
- fmp->setViewOptionEnabled("show_joint_positions", skin_weight);
- mFMP->childEnable("upload_skin");
- mFMP->childSetValue("show_skin_weight", skin_weight);
- }
- }
- else
- {
- mFMP->childDisable("upload_skin");
- if (fmp)
- {
- mViewOption["show_skin_weight"] = false;
- fmp->disableViewOption("show_skin_weight");
- fmp->disableViewOption("show_joint_positions");
-
- skin_weight = false;
- mFMP->childSetValue("show_skin_weight", false);
- fmp->setViewOptionEnabled("show_skin_weight", skin_weight);
- }
- }
-
- if (upload_skin && !has_skin_weights)
- { //can't upload skin weights if model has no skin weights
- mFMP->childSetValue("upload_skin", false);
- upload_skin = false;
- }
+ if (str.empty())
+ {
+ return;
+ }
- if (!upload_skin && upload_joints)
- { //can't upload joints if not uploading skin weights
- mFMP->childSetValue("upload_joints", false);
- upload_joints = false;
- }
+ LLWString text = utf8str_to_wstring(str);
+ S32 add_text_len = text.length() + 1; // newline
+ S32 editor_max_len = mUploadLogText->getMaxTextLength();
+ if (add_text_len > editor_max_len)
+ {
+ return;
+ }
- if (upload_skin && upload_joints)
+ // Make sure we have space for new string
+ S32 editor_text_len = mUploadLogText->getLength();
+ if (editor_max_len < (editor_text_len + add_text_len)
+ && mUploadLogText->getLineCount() <= 0)
{
- mFMP->childEnable("lock_scale_if_joint_position");
+ mUploadLogText->getTextBoundingRect();// forces a reflow() to fix line count
}
- else
+ while (editor_max_len < (editor_text_len + add_text_len))
{
- mFMP->childDisable("lock_scale_if_joint_position");
- mFMP->childSetValue("lock_scale_if_joint_position", false);
+ S32 shift = mUploadLogText->removeFirstLine();
+ if (shift > 0)
+ {
+ // removed a line
+ editor_text_len -= shift;
+ }
+ else
+ {
+ //nothing to remove?
+ LL_WARNS() << "Failed to clear log lines" << LL_ENDL;
+ break;
+ }
}
-
- //Only enable joint offsets if it passed the earlier critiquing
- if ( isRigValidForJointPositionUpload() )
- {
- mFMP->childSetEnabled("upload_joints", upload_skin);
- }
-
- F32 explode = mFMP->childGetValue("physics_explode").asReal();
-
- LLGLDepthTest gls_depth(GL_TRUE); // SL-12781 re-enable z-buffer for 3D model preview
-
- LLRect preview_rect;
-
- preview_rect = mFMP->getChildView("preview_panel")->getRect();
-
- F32 aspect = (F32) preview_rect.getWidth()/preview_rect.getHeight();
-
- LLViewerCamera::getInstance()->setAspect(aspect);
-
- LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom);
- LLVector3 offset = mCameraOffset;
- LLVector3 target_pos = mPreviewTarget+offset;
+ mUploadLogText->appendText(str, true);
- F32 z_near = 0.001f;
- F32 z_far = mCameraDistance*10.0f+mPreviewScale.magVec()+mCameraOffset.magVec();
-
- if (skin_weight)
- {
- target_pos = getPreviewAvatar()->getPositionAgent();
- z_near = 0.01f;
- z_far = 1024.f;
-
- //render avatar previews every frame
- refresh();
- }
-
- if (use_shaders)
- {
- gObjectPreviewProgram.bind();
- }
-
- gGL.loadIdentity();
- gPipeline.enableLightsPreview();
-
- LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) *
- LLQuaternion(mCameraYaw, LLVector3::z_axis);
-
- LLQuaternion av_rot = camera_rot;
- LLViewerCamera::getInstance()->setOriginAndLookAt(
- target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + offset) * av_rot), // camera
- LLVector3::z_axis, // up
- target_pos); // point of interest
-
-
- z_near = llclamp(z_far * 0.001f, 0.001f, 0.1f);
-
- LLViewerCamera::getInstance()->setPerspective(FALSE, mOrigin.mX, mOrigin.mY, width, height, FALSE, z_near, z_far);
-
- stop_glerror();
-
- gGL.pushMatrix();
- const F32 BRIGHTNESS = 0.9f;
- gGL.color3f(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS);
-
- const U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;
-
- LLGLEnable normalize(GL_NORMALIZE);
-
- if (!mBaseModel.empty() && mVertexBuffer[5].empty())
- {
- genBuffers(-1, skin_weight);
- //genBuffers(3);
- //genLODs();
- }
-
- if (!mModel[mPreviewLOD].empty())
- {
- mFMP->childEnable("reset_btn");
-
- bool regen = mVertexBuffer[mPreviewLOD].empty();
- if (!regen)
- {
- const std::vector<LLPointer<LLVertexBuffer> >& vb_vec = mVertexBuffer[mPreviewLOD].begin()->second;
- if (!vb_vec.empty())
- {
- const LLVertexBuffer* buff = vb_vec[0];
- regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight;
- }
- else
- {
- LL_INFOS() << "Vertex Buffer[" << mPreviewLOD << "]" << " is EMPTY!!!" << LL_ENDL;
- regen = TRUE;
- }
- }
-
- if (regen)
- {
- genBuffers(mPreviewLOD, skin_weight);
- }
-
- if (!skin_weight)
- {
- for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
- {
- LLModelInstance& instance = *iter;
-
- LLModel* model = instance.mLOD[mPreviewLOD];
-
- if (!model)
- {
- continue;
- }
-
- gGL.pushMatrix();
- LLMatrix4 mat = instance.mTransform;
-
- gGL.multMatrix((GLfloat*) mat.mMatrix);
-
-
- U32 num_models = mVertexBuffer[mPreviewLOD][model].size();
- for (U32 i = 0; i < num_models; ++i)
- {
- LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i];
-
- buffer->setBuffer(type_mask & buffer->getTypeMask());
-
- if (textures)
- {
- int materialCnt = instance.mModel->mMaterialList.size();
- if ( i < materialCnt )
- {
- const std::string& binding = instance.mModel->mMaterialList[i];
- const LLImportMaterial& material = instance.mMaterial[binding];
-
- gGL.diffuseColor4fv(material.mDiffuseColor.mV);
-
- // Find the tex for this material, bind it, and add it to our set
- //
- LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material);
- if (tex)
- {
- mTextureSet.insert(tex);
- }
- }
- }
- else
- {
- gGL.diffuseColor4f(1,1,1,1);
- }
-
- buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts()-1, buffer->getNumIndices(), 0);
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
- gGL.diffuseColor3f(0.4f, 0.4f, 0.4f);
-
- if (edges)
- {
- glLineWidth(3.f);
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
- buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts()-1, buffer->getNumIndices(), 0);
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
- glLineWidth(1.f);
- }
- }
- gGL.popMatrix();
- }
-
- if (physics)
- {
- glClear(GL_DEPTH_BUFFER_BIT);
-
- for (U32 pass = 0; pass < 2; pass++)
- {
- if (pass == 0)
- { //depth only pass
- gGL.setColorMask(false, false);
- }
- else
- {
- gGL.setColorMask(true, true);
- }
-
- //enable alpha blending on second pass but not first pass
- LLGLState blend(GL_BLEND, pass);
-
- gGL.blendFunc(LLRender::BF_SOURCE_ALPHA, LLRender::BF_ONE_MINUS_SOURCE_ALPHA);
-
- for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
- {
- LLModelInstance& instance = *iter;
-
- LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS];
-
- if (!model)
- {
- continue;
- }
-
- gGL.pushMatrix();
- LLMatrix4 mat = instance.mTransform;
-
- gGL.multMatrix((GLfloat*) mat.mMatrix);
-
-
- bool render_mesh = true;
-
- LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread;
- if (decomp)
- {
- LLMutexLock(decomp->mMutex);
-
- LLModel::Decomposition& physics = model->mPhysics;
-
- if (!physics.mHull.empty())
- {
- render_mesh = false;
-
- if (physics.mMesh.empty())
- { //build vertex buffer for physics mesh
- gMeshRepo.buildPhysicsMesh(physics);
- }
-
- if (!physics.mMesh.empty())
- { //render hull instead of mesh
- for (U32 i = 0; i < physics.mMesh.size(); ++i)
- {
- if (explode > 0.f)
- {
- gGL.pushMatrix();
-
- LLVector3 offset = model->mHullCenter[i]-model->mCenterOfHullCenters;
- offset *= explode;
-
- gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]);
- }
-
- static std::vector<LLColor4U> hull_colors;
-
- if (i+1 >= hull_colors.size())
- {
- hull_colors.push_back(LLColor4U(rand()%128+127, rand()%128+127, rand()%128+127, 128));
- }
-
- gGL.diffuseColor4ubv(hull_colors[i].mV);
- LLVertexBuffer::drawArrays(LLRender::TRIANGLES, physics.mMesh[i].mPositions, physics.mMesh[i].mNormals);
-
- if (explode > 0.f)
- {
- gGL.popMatrix();
- }
- }
- }
- }
- }
-
- if (render_mesh)
- {
- if (mVertexBuffer[LLModel::LOD_PHYSICS].empty())
- {
- genBuffers(LLModel::LOD_PHYSICS, false);
- }
-
- U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size();
- for (U32 i = 0; i < num_models; ++i)
- {
- LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i];
-
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
- gGL.diffuseColor4f(0.4f, 0.4f, 0.0f, 0.4f);
-
- buffer->setBuffer(type_mask & buffer->getTypeMask());
- buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts()-1, buffer->getNumIndices(), 0);
-
- gGL.diffuseColor3f(1.f, 1.f, 0.f);
-
- glLineWidth(2.f);
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
- buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts()-1, buffer->getNumIndices(), 0);
-
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
- glLineWidth(1.f);
- }
- }
-
- gGL.popMatrix();
- }
-
- glLineWidth(3.f);
- glPointSize(8.f);
- gPipeline.enableLightsFullbright();
- //show degenerate triangles
- LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS);
- LLGLDisable cull(GL_CULL_FACE);
- gGL.diffuseColor4f(1.f,0.f,0.f,1.f);
- const LLVector4a scale(0.5f);
-
- for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
- {
- LLModelInstance& instance = *iter;
-
- LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS];
-
- if (!model)
- {
- continue;
- }
-
- gGL.pushMatrix();
- LLMatrix4 mat = instance.mTransform;
-
- gGL.multMatrix((GLfloat*) mat.mMatrix);
-
-
- LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread;
- if (decomp)
- {
- LLMutexLock(decomp->mMutex);
-
- LLModel::Decomposition& physics = model->mPhysics;
-
- if (physics.mHull.empty())
- {
- if (mVertexBuffer[LLModel::LOD_PHYSICS].empty())
- {
- genBuffers(LLModel::LOD_PHYSICS, false);
- }
-
- for (U32 i = 0; i < mVertexBuffer[LLModel::LOD_PHYSICS][model].size(); ++i)
- {
- LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i];
-
- buffer->setBuffer(type_mask & buffer->getTypeMask());
-
- LLStrider<LLVector3> pos_strider;
- buffer->getVertexStrider(pos_strider, 0);
- LLVector4a* pos = (LLVector4a*) pos_strider.get();
-
- LLStrider<U16> idx;
- buffer->getIndexStrider(idx, 0);
-
- for (U32 i = 0; i < buffer->getNumIndices(); i += 3)
- {
- LLVector4a v1; v1.setMul(pos[*idx++], scale);
- LLVector4a v2; v2.setMul(pos[*idx++], scale);
- LLVector4a v3; v3.setMul(pos[*idx++], scale);
-
- if (ll_is_degenerate(v1,v2,v3))
- {
- buffer->draw(LLRender::LINE_LOOP, 3, i);
- buffer->draw(LLRender::POINTS, 3, i);
- }
- }
- }
- }
- }
-
- gGL.popMatrix();
- }
- glLineWidth(1.f);
- glPointSize(1.f);
- gPipeline.enableLightsPreview();
- gGL.setSceneBlendType(LLRender::BT_ALPHA);
- }
- }
- }
- else
- {
- target_pos = getPreviewAvatar()->getPositionAgent();
-
- LLViewerCamera::getInstance()->setOriginAndLookAt(
- target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + offset) * av_rot), // camera
- LLVector3::z_axis, // up
- target_pos); // point of interest
-
- for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
- {
- for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
- {
- LLModelInstance& instance = *model_iter;
- LLModel* model = instance.mModel;
-
- if (!model->mSkinWeights.empty())
- {
- for (U32 i = 0, e = mVertexBuffer[mPreviewLOD][model].size(); i < e; ++i)
- {
- LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i];
-
- const LLVolumeFace& face = model->getVolumeFace(i);
-
- LLStrider<LLVector3> position;
- buffer->getVertexStrider(position);
-
- LLStrider<LLVector4> weight;
- buffer->getWeight4Strider(weight);
-
- //quick 'n dirty software vertex skinning
-
- //build matrix palette
-
- LLMatrix4a mat[LL_MAX_JOINTS_PER_MESH_OBJECT];
- const LLMeshSkinInfo *skin = &model->mSkinInfo;
- U32 count = LLSkinningUtil::getMeshJointCount(skin);
- LLSkinningUtil::initSkinningMatrixPalette((LLMatrix4*)mat, count,
- skin, getPreviewAvatar());
- LLMatrix4a bind_shape_matrix;
- bind_shape_matrix.loadu(skin->mBindShapeMatrix);
- U32 max_joints = LLSkinningUtil::getMaxJointCount();
- for (U32 j = 0; j < buffer->getNumVerts(); ++j)
- {
- LLMatrix4a final_mat;
- F32 *wptr = weight[j].mV;
- LLSkinningUtil::getPerVertexSkinMatrix(wptr, mat, true, final_mat, max_joints);
-
- //VECTORIZE THIS
- LLVector4a& v = face.mPositions[j];
-
- LLVector4a t;
- LLVector4a dst;
- bind_shape_matrix.affineTransform(v, t);
- final_mat.affineTransform(t, dst);
-
- position[j][0] = dst[0];
- position[j][1] = dst[1];
- position[j][2] = dst[2];
- }
-
- llassert(model->mMaterialList.size() > i);
- const std::string& binding = instance.mModel->mMaterialList[i];
- const LLImportMaterial& material = instance.mMaterial[binding];
-
- buffer->setBuffer(type_mask & buffer->getTypeMask());
- gGL.diffuseColor4fv(material.mDiffuseColor.mV);
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-
- // Find the tex for this material, bind it, and add it to our set
- //
- LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material);
- if (tex)
- {
- mTextureSet.insert(tex);
- }
-
- buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0);
- gGL.diffuseColor3f(0.4f, 0.4f, 0.4f);
-
- if (edges)
- {
- glLineWidth(3.f);
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
- buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0);
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
- glLineWidth(1.f);
- }
- }
- }
- }
- }
-
- if (joint_positions)
- {
- LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
- if (shader)
- {
- gDebugProgram.bind();
- }
- getPreviewAvatar()->renderCollisionVolumes();
- getPreviewAvatar()->renderBones();
- if (shader)
- {
- shader->bind();
- }
- }
-
- }
- }
-
- if (use_shaders)
- {
- gObjectPreviewProgram.unbind();
- }
-
- gGL.popMatrix();
-
- return TRUE;
-}
-
-//-----------------------------------------------------------------------------
-// refresh()
-//-----------------------------------------------------------------------------
-void LLModelPreview::refresh()
-{
- mNeedsUpdate = TRUE;
-}
-
-//-----------------------------------------------------------------------------
-// rotate()
-//-----------------------------------------------------------------------------
-void LLModelPreview::rotate(F32 yaw_radians, F32 pitch_radians)
-{
- mCameraYaw = mCameraYaw + yaw_radians;
-
- mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f);
-}
-
-//-----------------------------------------------------------------------------
-// zoom()
-//-----------------------------------------------------------------------------
-void LLModelPreview::zoom(F32 zoom_amt)
-{
- F32 new_zoom = mCameraZoom+zoom_amt;
-
- mCameraZoom = llclamp(new_zoom, 1.f, 10.f);
+ if (flash)
+ {
+ LLPanel* panel = mTabContainer->getPanelByName("logs_panel");
+ if (mTabContainer->getCurrentPanel() != panel)
+ {
+ mTabContainer->setTabPanelFlashing(panel, true);
+ }
+ }
}
-void LLModelPreview::pan(F32 right, F32 up)
+void LLFloaterModelPreview::setDetails(F32 x, F32 y, F32 z, F32 streaming_cost, F32 physics_cost)
{
- mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * mCameraDistance / mCameraZoom, -1.f, 1.f);
- mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * mCameraDistance / mCameraZoom, -1.f, 1.f);
+ assert_main_thread();
+ childSetTextArg("import_dimensions", "[X]", llformat("%.3f", x));
+ childSetTextArg("import_dimensions", "[Y]", llformat("%.3f", y));
+ childSetTextArg("import_dimensions", "[Z]", llformat("%.3f", z));
}
-void LLModelPreview::setPreviewLOD(S32 lod)
+void LLFloaterModelPreview::setPreviewLOD(S32 lod)
{
- lod = llclamp(lod, 0, (S32) LLModel::LOD_HIGH);
-
- if (lod != mPreviewLOD)
+ if (mModelPreview)
{
- mPreviewLOD = lod;
-
- LLComboBox* combo_box = mFMP->getChild<LLComboBox>("preview_lod_combo");
- combo_box->setCurrentByIndex((NUM_LOD-1)-mPreviewLOD); // combo box list of lods is in reverse order
- mFMP->childSetValue("lod_file_" + lod_name[mPreviewLOD], mLODFile[mPreviewLOD]);
-
- LLComboBox* combo_box2 = mFMP->getChild<LLComboBox>("preview_lod_combo2");
- combo_box2->setCurrentByIndex((NUM_LOD-1)-mPreviewLOD); // combo box list of lods is in reverse order
-
- LLComboBox* combo_box3 = mFMP->getChild<LLComboBox>("preview_lod_combo3");
- combo_box3->setCurrentByIndex((NUM_LOD-1)-mPreviewLOD); // combo box list of lods is in reverse order
-
- LLColor4 highlight_color = LLUIColorTable::instance().getColor("MeshImportTableHighlightColor");
- LLColor4 normal_color = LLUIColorTable::instance().getColor("MeshImportTableNormalColor");
-
- for (S32 i = 0; i <= LLModel::LOD_HIGH; ++i)
- {
- const LLColor4& color = (i == lod) ? highlight_color : normal_color;
-
- mFMP->childSetColor(lod_status_name[i], color);
- mFMP->childSetColor(lod_label_name[i], color);
- mFMP->childSetColor(lod_triangles_name[i], color);
- mFMP->childSetColor(lod_vertices_name[i], color);
- }
+ mModelPreview->setPreviewLOD(lod);
}
- refresh();
- updateStatusMessages();
}
void LLFloaterModelPreview::onBrowseLOD(S32 lod)
@@ -4317,12 +1568,16 @@ void LLFloaterModelPreview::onReset(void* user_data)
{
assert_main_thread();
+
LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) user_data;
fmp->childDisable("reset_btn");
+ fmp->clearLogTab();
+ fmp->clearAvatarTab();
LLModelPreview* mp = fmp->mModelPreview;
std::string filename = mp->mLODFile[LLModel::LOD_HIGH];
fmp->resetDisplayOptions();
+ fmp->resetUploadOptions();
//reset model preview
fmp->initModelPreview();
@@ -4336,6 +1591,7 @@ void LLFloaterModelPreview::onUpload(void* user_data)
assert_main_thread();
LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data;
+ mp->clearLogTab();
mp->mUploadBtn->setEnabled(false);
@@ -4364,60 +1620,6 @@ void LLFloaterModelPreview::refresh()
sInstance->mModelPreview->mDirty = true;
}
-//static
-void LLModelPreview::textureLoadedCallback(
- BOOL success,
- LLViewerFetchedTexture *src_vi,
- LLImageRaw* src,
- LLImageRaw* src_aux,
- S32 discard_level,
- BOOL final,
- void* userdata )
-{
- LLModelPreview* preview = (LLModelPreview*) userdata;
- preview->refresh();
-
- if(final && preview->mModelLoader)
- {
- if(preview->mModelLoader->mNumOfFetchingTextures > 0)
- {
- preview->mModelLoader->mNumOfFetchingTextures-- ;
- }
- }
-}
-
-// static
-bool LLModelPreview::lodQueryCallback()
-{
- // not the best solution, but model preview belongs to floater
- // so it is an easy way to check that preview still exists.
- LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
- if (fmp && fmp->mModelPreview)
- {
- LLModelPreview* preview = fmp->mModelPreview;
- if (preview->mLodsQuery.size() > 0)
- {
- S32 lod = preview->mLodsQuery.back();
- preview->mLodsQuery.pop_back();
- preview->genLODs(lod);
-
- // return false to continue cycle
- return false;
- }
- }
- // nothing to process
- return true;
-}
-
-void LLModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit)
-{
- if (!mLODFrozen)
- {
- genLODs(lod, 3, enforce_tri_limit);
- refresh();
- }
-}
-
LLFloaterModelPreview::DecompRequest::DecompRequest(const std::string& stage, LLModel* mdl)
{
mStage = stage;
@@ -4430,6 +1632,26 @@ LLFloaterModelPreview::DecompRequest::DecompRequest(const std::string& stage, LL
assignData(mdl) ;
}
+void LLFloaterModelPreview::setCtrlLoadFromFile(S32 lod)
+{
+ if (lod == LLModel::LOD_PHYSICS)
+ {
+ LLComboBox* lod_combo = findChild<LLComboBox>("physics_lod_combo");
+ if (lod_combo)
+ {
+ lod_combo->setCurrentByIndex(5);
+ }
+ }
+ else
+ {
+ LLComboBox* lod_combo = findChild<LLComboBox>("lod_source_" + lod_name[lod]);
+ if (lod_combo)
+ {
+ lod_combo->setCurrentByIndex(0);
+ }
+ }
+}
+
void LLFloaterModelPreview::setStatusMessage(const std::string& msg)
{
LLMutexLock lock(mStatusLock);
@@ -4476,11 +1698,15 @@ void LLFloaterModelPreview::toggleCalculateButton(bool visible)
{
childSetTextArg("upload_fee", "[FEE]", tbd);
}
- childSetTextArg("price_breakdown", "[STREAMING]", tbd);
- childSetTextArg("price_breakdown", "[PHYSICS]", tbd);
- childSetTextArg("price_breakdown", "[INSTANCES]", tbd);
- childSetTextArg("price_breakdown", "[TEXTURES]", tbd);
- childSetTextArg("price_breakdown", "[MODEL]", tbd);
+ std::string dashes = hasString("--") ? getString("--") : "--";
+ childSetTextArg("price_breakdown", "[STREAMING]", dashes);
+ childSetTextArg("price_breakdown", "[PHYSICS]", dashes);
+ childSetTextArg("price_breakdown", "[INSTANCES]", dashes);
+ childSetTextArg("price_breakdown", "[TEXTURES]", dashes);
+ childSetTextArg("price_breakdown", "[MODEL]", dashes);
+ childSetTextArg("physics_breakdown", "[PCH]", dashes);
+ childSetTextArg("physics_breakdown", "[PM]", dashes);
+ childSetTextArg("physics_breakdown", "[PHU]", dashes);
}
}
@@ -4507,6 +1733,44 @@ void LLFloaterModelPreview::resetDisplayOptions()
}
}
+void LLFloaterModelPreview::resetUploadOptions()
+{
+ childSetValue("import_scale", 1);
+ childSetValue("pelvis_offset", 0);
+ childSetValue("physics_explode", 0);
+ childSetValue("physics_file", "");
+ childSetVisible("Retain%", false);
+ childSetVisible("Retain%_label", false);
+ childSetVisible("Detail Scale", true);
+ childSetVisible("Detail Scale label", true);
+
+ getChild<LLComboBox>("lod_source_" + lod_name[NUM_LOD - 1])->setCurrentByIndex(LLModelPreview::LOD_FROM_FILE);
+ for (S32 lod = 0; lod < NUM_LOD - 1; ++lod)
+ {
+ getChild<LLComboBox>("lod_source_" + lod_name[lod])->setCurrentByIndex(LLModelPreview::GENERATE);
+ childSetValue("lod_file_" + lod_name[lod], "");
+ }
+
+ for(auto& p : mDefaultDecompParams)
+ {
+ std::string ctrl_name(p.first);
+ LLUICtrl* ctrl = getChild<LLUICtrl>(ctrl_name);
+ if (ctrl)
+ {
+ ctrl->setValue(p.second);
+ }
+ }
+ getChild<LLComboBox>("physics_lod_combo")->setCurrentByIndex(0);
+ getChild<LLComboBox>("Cosine%")->setCurrentByIndex(0);
+}
+
+void LLFloaterModelPreview::clearLogTab()
+{
+ mUploadLogText->clear();
+ LLPanel* panel = mTabContainer->getPanelByName("logs_panel");
+ mTabContainer->setTabPanelFlashing(panel, false);
+}
+
void LLFloaterModelPreview::onModelPhysicsFeeReceived(const LLSD& result, std::string upload_url)
{
mModelPhysicsFee = result;
@@ -4530,6 +1794,16 @@ void LLFloaterModelPreview::handleModelPhysicsFeeReceived()
childSetTextArg("price_breakdown", "[INSTANCES]", llformat("%d", result["upload_price_breakdown"]["mesh_instance"].asInteger()));
childSetTextArg("price_breakdown", "[TEXTURES]", llformat("%d", result["upload_price_breakdown"]["texture"].asInteger()));
childSetTextArg("price_breakdown", "[MODEL]", llformat("%d", result["upload_price_breakdown"]["model"].asInteger()));
+
+ childSetTextArg("physics_breakdown", "[PCH]", llformat("%0.3f", result["model_physics_cost"]["hull"].asReal()));
+ childSetTextArg("physics_breakdown", "[PM]", llformat("%0.3f", result["model_physics_cost"]["mesh"].asReal()));
+ childSetTextArg("physics_breakdown", "[PHU]", llformat("%0.3f", result["model_physics_cost"]["decomposition"].asReal()));
+ childSetTextArg("streaming_breakdown", "[STR_TOTAL]", llformat("%d", result["streaming_cost"].asInteger()));
+ childSetTextArg("streaming_breakdown", "[STR_HIGH]", llformat("%d", result["streaming_params"]["high_lod"].asInteger()));
+ childSetTextArg("streaming_breakdown", "[STR_MED]", llformat("%d", result["streaming_params"]["medium_lod"].asInteger()));
+ childSetTextArg("streaming_breakdown", "[STR_LOW]", llformat("%d", result["streaming_params"]["low_lod"].asInteger()));
+ childSetTextArg("streaming_breakdown", "[STR_LOWEST]", llformat("%d", result["streaming_params"]["lowest_lod"].asInteger()));
+
childSetVisible("upload_fee", true);
childSetVisible("price_breakdown", true);
mUploadBtn->setEnabled(isModelUploadAllowed());
@@ -4537,7 +1811,11 @@ void LLFloaterModelPreview::handleModelPhysicsFeeReceived()
void LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(S32 status, const std::string& reason, const LLSD& result)
{
- LL_WARNS() << "LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(" << status << " : " << reason << ")" << LL_ENDL;
+ std::ostringstream out;
+ out << "LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(" << status;
+ out << " : " << reason << ")";
+ LL_WARNS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
doOnIdleOneTime(boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this, true));
if (result.has("upload_price"))
diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h
index 1c66570650..8a01b0c307 100644
--- a/indra/newview/llfloatermodelpreview.h
+++ b/indra/newview/llfloatermodelpreview.h
@@ -28,36 +28,26 @@
#define LL_LLFLOATERMODELPREVIEW_H
#include "llfloaternamedesc.h"
-
-#include "lldynamictexture.h"
-#include "llquaternion.h"
-#include "llmeshrepository.h"
-#include "llmodel.h"
-#include "llthread.h"
-#include "llviewermenufile.h"
#include "llfloatermodeluploadbase.h"
-
-#include "lldaeloader.h"
+#include "llmeshrepository.h"
class LLComboBox;
class LLJoint;
-class LLViewerJointMesh;
-class LLVOAvatar;
-class LLTextBox;
-class LLVertexBuffer;
+class LLMeshFilePicker;
class LLModelPreview;
-class LLFloaterModelPreview;
-class DAE;
-class daeElement;
-class domProfile_COMMON;
-class domInstance_geometry;
-class domNode;
-class domTranslate;
-class domController;
-class domSkin;
-class domMesh;
-class LLMenuButton;
-class LLToggleableMenu;
+class LLTabContainer;
+class LLViewerTextEditor;
+
+
+class LLJointOverrideData
+{
+public:
+ LLJointOverrideData() : mHasConflicts(false) {};
+ std::map<std::string, LLVector3> mPosOverrides; // models with overrides
+ std::set<std::string> mModelsNoOverrides; // models without defined overrides
+ bool mHasConflicts;
+};
+typedef std::map<std::string, LLJointOverrideData> joint_override_data_map_t;
class LLFloaterModelPreview : public LLFloaterModelUploadBase
{
@@ -80,6 +70,7 @@ public:
virtual ~LLFloaterModelPreview();
virtual BOOL postBuild();
+ /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
void initModelPreview();
@@ -93,6 +84,11 @@ public:
static void onMouseCaptureLostModelPreview(LLMouseHandler*);
static void setUploadAmount(S32 amount) { sUploadAmount = amount; }
+ static void addStringToLog(const std::string& message, const LLSD& args, bool flash, S32 lod = -1);
+ static void addStringToLog(const std::string& str, bool flash);
+ static void addStringToLog(const std::ostringstream& strm, bool flash);
+ void clearAvatarTab(); // clears table
+ void updateAvatarTab(bool highlight_overrides); // populates table and data as nessesary
void setDetails(F32 x, F32 y, F32 z, F32 streaming_cost, F32 physics_cost);
void setPreviewLOD(S32 lod);
@@ -107,13 +103,17 @@ public:
void loadModel(S32 lod);
void loadModel(S32 lod, const std::string& file_name, bool force_disable_slm = false);
+
+ void loadHighLodModel();
void onViewOptionChecked(LLUICtrl* ctrl);
+ void onUploadOptionChecked(LLUICtrl* ctrl);
bool isViewOptionChecked(const LLSD& userdata);
bool isViewOptionEnabled(const LLSD& userdata);
void setViewOptionEnabled(const std::string& option, bool enabled);
void enableViewOption(const std::string& option);
void disableViewOption(const std::string& option);
+ void onShowSkinWeightChecked(LLUICtrl* ctrl);
bool isModelLoading();
@@ -142,8 +142,6 @@ protected:
static void onImportScaleCommit(LLUICtrl*, void*);
static void onPelvisOffsetCommit(LLUICtrl*, void*);
- static void onUploadJointsCommit(LLUICtrl*,void*);
- static void onUploadSkinCommit(LLUICtrl*,void*);
static void onPreviewLODCommit(LLUICtrl*,void*);
@@ -154,6 +152,7 @@ protected:
static void onAutoFillCommit(LLUICtrl*,void*);
void onLODParamCommit(S32 lod, bool enforce_tri_limit);
+ void draw3dPreview();
static void onExplodeCommit(LLUICtrl*, void*);
@@ -175,11 +174,15 @@ protected:
// FIXME - this function and mStatusMessage have no visible effect, and the
// actual status messages are managed by directly manipulation of
// the UI element.
- void setStatusMessage(const std::string& msg);
+ void setStatusMessage(const std::string& msg);
+ void addStringToLogTab(const std::string& str, bool flash);
+
+ void setCtrlLoadFromFile(S32 lod);
LLModelPreview* mModelPreview;
LLPhysicsDecomp::decomp_params mDecompParams;
+ LLPhysicsDecomp::decomp_params mDefaultDecompParams;
S32 mLastMouseX;
S32 mLastMouseY;
@@ -203,223 +206,34 @@ protected:
LLSD mModelPhysicsFee;
private:
- void onClickCalculateBtn();
- void toggleCalculateButton();
+ void onClickCalculateBtn();
+ void onJointListSelection();
void onLoDSourceCommit(S32 lod);
void modelUpdated(bool calculate_visible);
// Toggles between "Calculate weights & fee" and "Upload" buttons.
+ void toggleCalculateButton();
void toggleCalculateButton(bool visible);
// resets display options of model preview to their defaults.
void resetDisplayOptions();
+ void resetUploadOptions();
+ void clearLogTab();
+
void createSmoothComboBox(LLComboBox* combo_box, float min, float max);
LLButton* mUploadBtn;
LLButton* mCalculateBtn;
-};
-
-class LLMeshFilePicker : public LLFilePickerThread
-{
-public:
- LLMeshFilePicker(LLModelPreview* mp, S32 lod);
- virtual void notify(const std::vector<std::string>& filenames);
-
-private:
- LLModelPreview* mMP;
- S32 mLOD;
-};
-
-
-class LLModelPreview : public LLViewerDynamicTexture, public LLMutex
-{
- typedef boost::signals2::signal<void (F32 x, F32 y, F32 z, F32 streaming_cost, F32 physics_cost)> details_signal_t;
- typedef boost::signals2::signal<void (void)> model_loaded_signal_t;
- typedef boost::signals2::signal<void (bool)> model_updated_signal_t;
-
-public:
-
- typedef enum
- {
- LOD_FROM_FILE = 0,
- GENERATE,
- USE_LOD_ABOVE,
- } eLoDMode;
-
-public:
- LLModelPreview(S32 width, S32 height, LLFloater* fmp);
- virtual ~LLModelPreview();
-
- void resetPreviewTarget();
- void setPreviewTarget(F32 distance);
- void setTexture(U32 name) { mTextureName = name; }
-
- void setPhysicsFromLOD(S32 lod);
- BOOL render();
- void update();
- void genBuffers(S32 lod, bool skinned);
- void clearBuffers();
- void refresh();
- void rotate(F32 yaw_radians, F32 pitch_radians);
- void zoom(F32 zoom_amt);
- void pan(F32 right, F32 up);
- virtual BOOL needsRender() { return mNeedsUpdate; }
- void setPreviewLOD(S32 lod);
- void clearModel(S32 lod);
- void getJointAliases(JointMap& joint_map);
- void loadModel(std::string filename, S32 lod, bool force_disable_slm = false);
- void loadModelCallback(S32 lod);
- bool lodsReady() { return !mGenLOD && mLodsQuery.empty(); }
- void queryLODs() { mGenLOD = true; };
- void genLODs(S32 which_lod = -1, U32 decimation = 3, bool enforce_tri_limit = false);
- void generateNormals();
- void restoreNormals();
- U32 calcResourceCost();
- void rebuildUploadData();
- void saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position);
- void saveUploadData(const std::string& filename, bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position);
- void clearIncompatible(S32 lod);
- void updateStatusMessages();
- void updateLodControls(S32 lod);
- void clearGLODGroup();
- void onLODParamCommit(S32 lod, bool enforce_tri_limit);
- void addEmptyFace( LLModel* pTarget );
-
- const bool getModelPivot( void ) const { return mHasPivot; }
- void setHasPivot( bool val ) { mHasPivot = val; }
- void setModelPivot( const LLVector3& pivot ) { mModelPivot = pivot; }
-
- //Is a rig valid so that it can be used as a criteria for allowing for uploading of joint positions
- //Accessors for joint position upload friendly rigs
- const bool isRigValidForJointPositionUpload( void ) const { return mRigValidJointUpload; }
- void setRigValidForJointPositionUpload( bool rigValid ) { mRigValidJointUpload = rigValid; }
-
- //Accessors for the legacy rigs
- const bool isLegacyRigValid( void ) const { return mLegacyRigValid; }
- void setLegacyRigValid( bool rigValid ) { mLegacyRigValid = rigValid; }
-
- static void textureLoadedCallback( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata );
- static bool lodQueryCallback();
-
- boost::signals2::connection setDetailsCallback( const details_signal_t::slot_type& cb ){ return mDetailsSignal.connect(cb); }
- boost::signals2::connection setModelLoadedCallback( const model_loaded_signal_t::slot_type& cb ){ return mModelLoadedSignal.connect(cb); }
- boost::signals2::connection setModelUpdatedCallback( const model_updated_signal_t::slot_type& cb ){ return mModelUpdatedSignal.connect(cb); }
-
- void setLoadState( U32 state ) { mLoadState = state; }
- U32 getLoadState() { return mLoadState; }
-
- static bool sIgnoreLoadedCallback;
- std::vector<S32> mLodsQuery;
- std::vector<S32> mLodsWithParsingError;
-
-protected:
-
- static void loadedCallback(LLModelLoader::scene& scene,LLModelLoader::model_list& model_list, S32 lod, void* opaque);
- static void stateChangedCallback(U32 state, void* opaque);
-
- static LLJoint* lookupJointByName(const std::string&, void* opaque);
- static U32 loadTextures(LLImportMaterial& material, void* opaque);
-
-private:
- //Utility function for controller vertex compare
- bool verifyCount( int expected, int result );
- //Creates the dummy avatar for the preview window
- void createPreviewAvatar( void );
- //Accessor for the dummy avatar
- LLVOAvatar* getPreviewAvatar( void ) { return mPreviewAvatar; }
- // Count amount of original models, excluding sub-models
- static U32 countRootModels(LLModelLoader::model_list models);
-
- protected:
- friend class LLModelLoader;
- friend class LLFloaterModelPreview;
- friend class LLFloaterModelPreview::DecompRequest;
- friend class LLPhysicsDecomp;
-
- LLFloater* mFMP;
-
- BOOL mNeedsUpdate;
- bool mDirty;
- bool mGenLOD;
- U32 mTextureName;
- F32 mCameraDistance;
- F32 mCameraYaw;
- F32 mCameraPitch;
- F32 mCameraZoom;
- LLVector3 mCameraOffset;
- LLVector3 mPreviewTarget;
- LLVector3 mPreviewScale;
- S32 mPreviewLOD;
- S32 mPhysicsSearchLOD;
- U32 mResourceCost;
- std::string mLODFile[LLModel::NUM_LODS];
- bool mLoading;
- U32 mLoadState;
- bool mResetJoints;
- bool mModelNoErrors;
-
- std::map<std::string, bool> mViewOption;
-
- //GLOD object parameters (must rebuild object if these change)
- bool mLODFrozen;
- F32 mBuildShareTolerance;
- U32 mBuildQueueMode;
- U32 mBuildOperator;
- U32 mBuildBorderMode;
- U32 mRequestedLoDMode[LLModel::NUM_LODS];
- S32 mRequestedTriangleCount[LLModel::NUM_LODS];
- F32 mRequestedErrorThreshold[LLModel::NUM_LODS];
- U32 mRequestedBuildOperator[LLModel::NUM_LODS];
- U32 mRequestedQueueMode[LLModel::NUM_LODS];
- U32 mRequestedBorderMode[LLModel::NUM_LODS];
- F32 mRequestedShareTolerance[LLModel::NUM_LODS];
- F32 mRequestedCreaseAngle[LLModel::NUM_LODS];
-
- LLModelLoader* mModelLoader;
-
- LLModelLoader::scene mScene[LLModel::NUM_LODS];
- LLModelLoader::scene mBaseScene;
-
- LLModelLoader::model_list mModel[LLModel::NUM_LODS];
- LLModelLoader::model_list mBaseModel;
-
- typedef std::vector<LLVolumeFace> v_LLVolumeFace_t;
- typedef std::vector<v_LLVolumeFace_t> vv_LLVolumeFace_t;
-
- vv_LLVolumeFace_t mModelFacesCopy[LLModel::NUM_LODS];
- vv_LLVolumeFace_t mBaseModelFacesCopy;
-
- U32 mGroup;
- std::map<LLPointer<LLModel>, U32> mObject;
- U32 mMaxTriangleLimit;
-
- LLMeshUploadThread::instance_list mUploadData;
- std::set<LLViewerFetchedTexture * > mTextureSet;
-
- //map of vertex buffers to models (one vertex buffer in vector per face in model
- std::map<LLModel*, std::vector<LLPointer<LLVertexBuffer> > > mVertexBuffer[LLModel::NUM_LODS+1];
-
- details_signal_t mDetailsSignal;
- model_loaded_signal_t mModelLoadedSignal;
- model_updated_signal_t mModelUpdatedSignal;
-
- LLVector3 mModelPivot;
- bool mHasPivot;
-
- float mPelvisZOffset;
-
- bool mRigValidJointUpload;
- bool mLegacyRigValid;
-
- bool mLastJointUpdate;
+ LLViewerTextEditor* mUploadLogText;
+ LLTabContainer* mTabContainer;
- JointNameSet mJointsFromNode;
- JointTransformMap mJointTransformMap;
+ S32 mAvatarTabIndex; // just to avoid any issues in case of xml changes
+ std::string mSelectedJointName;
- LLPointer<LLVOAvatar> mPreviewAvatar;
+ joint_override_data_map_t mJointOverrides[LLModel::NUM_LODS];
};
#endif // LL_LLFLOATERMODELPREVIEW_H
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
new file mode 100644
index 0000000000..d60d1d748e
--- /dev/null
+++ b/indra/newview/llmodelpreview.cpp
@@ -0,0 +1,3564 @@
+/**
+ * @file llmodelpreview.cpp
+ * @brief LLModelPreview class implementation
+ *
+ * $LicenseInfo:firstyear=2020&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2020, 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 "llmodelpreview.h"
+
+#include "llmodelloader.h"
+#include "lldaeloader.h"
+#include "llfloatermodelpreview.h"
+
+#include "llagent.h"
+#include "llanimationstates.h"
+#include "llcallbacklist.h"
+#include "lldatapacker.h"
+#include "lldrawable.h"
+#include "llface.h"
+#include "lliconctrl.h"
+#include "llmatrix4a.h"
+#include "llmeshrepository.h"
+#include "llrender.h"
+#include "llsdutil_math.h"
+#include "llskinningutil.h"
+#include "llstring.h"
+#include "llsdserialize.h"
+#include "lltoolmgr.h"
+#include "llui.h"
+#include "llvector4a.h"
+#include "llviewercamera.h"
+#include "llviewercontrol.h"
+#include "llviewerobjectlist.h"
+#include "llviewernetwork.h"
+#include "llviewershadermgr.h"
+#include "llviewertexteditor.h"
+#include "llviewertexturelist.h"
+#include "llvoavatar.h"
+#include "pipeline.h"
+
+// ui controls (from floater)
+#include "llbutton.h"
+#include "llcombobox.h"
+#include "llspinctrl.h"
+#include "lltabcontainer.h"
+#include "lltextbox.h"
+
+#include "glod/glod.h"
+#include <boost/algorithm/string.hpp>
+
+bool LLModelPreview::sIgnoreLoadedCallback = false;
+
+// Extra configurability, to be exposed later in xml (LLModelPreview probably
+// should become UI control at some point or get split into preview control)
+static const LLColor4 PREVIEW_CANVAS_COL(0.169f, 0.169f, 0.169f, 1.f);
+static const LLColor4 PREVIEW_EDGE_COL(0.4f, 0.4f, 0.4f, 1.0);
+static const LLColor4 PREVIEW_BASE_COL(1.f, 1.f, 1.f, 1.f);
+static const LLColor3 PREVIEW_BRIGHTNESS(0.9f, 0.9f, 0.9f);
+static const F32 PREVIEW_EDGE_WIDTH(1.f);
+static const LLColor4 PREVIEW_PSYH_EDGE_COL(0.f, 0.25f, 0.5f, 0.25f);
+static const LLColor4 PREVIEW_PSYH_FILL_COL(0.f, 0.5f, 1.0f, 0.5f);
+static const F32 PREVIEW_PSYH_EDGE_WIDTH(1.f);
+static const LLColor4 PREVIEW_DEG_EDGE_COL(1.f, 0.f, 0.f, 1.f);
+static const LLColor4 PREVIEW_DEG_FILL_COL(1.f, 0.f, 0.f, 0.5f);
+static const F32 PREVIEW_DEG_EDGE_WIDTH(3.f);
+static const F32 PREVIEW_DEG_POINT_SIZE(8.f);
+static const F32 PREVIEW_ZOOM_LIMIT(10.f);
+
+const F32 SKIN_WEIGHT_CAMERA_DISTANCE = 16.f;
+
+BOOL stop_gloderror()
+{
+ GLuint error = glodGetError();
+
+ if (error != GLOD_NO_ERROR)
+ {
+ LL_WARNS() << "GLOD error detected, cannot generate LOD: " << std::hex << error << LL_ENDL;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+LLViewerFetchedTexture* bindMaterialDiffuseTexture(const LLImportMaterial& material)
+{
+ LLViewerFetchedTexture *texture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap(), FTT_DEFAULT, TRUE, LLGLTexture::BOOST_PREVIEW);
+
+ if (texture)
+ {
+ if (texture->getDiscardLevel() > -1)
+ {
+ gGL.getTexUnit(0)->bind(texture, true);
+ return texture;
+ }
+ }
+
+ return NULL;
+}
+
+std::string stripSuffix(std::string name)
+{
+ if ((name.find("_LOD") != -1) || (name.find("_PHYS") != -1))
+ {
+ return name.substr(0, name.rfind('_'));
+ }
+ return name;
+}
+
+std::string getLodSuffix(S32 lod)
+{
+ std::string suffix;
+ switch (lod)
+ {
+ case LLModel::LOD_IMPOSTOR: suffix = "_LOD0"; break;
+ case LLModel::LOD_LOW: suffix = "_LOD1"; break;
+ case LLModel::LOD_MEDIUM: suffix = "_LOD2"; break;
+ case LLModel::LOD_PHYSICS: suffix = "_PHYS"; break;
+ case LLModel::LOD_HIGH: break;
+ }
+ return suffix;
+}
+
+void FindModel(LLModelLoader::scene& scene, const std::string& name_to_match, LLModel*& baseModelOut, LLMatrix4& matOut)
+{
+ LLModelLoader::scene::iterator base_iter = scene.begin();
+ bool found = false;
+ while (!found && (base_iter != scene.end()))
+ {
+ matOut = base_iter->first;
+
+ LLModelLoader::model_instance_list::iterator base_instance_iter = base_iter->second.begin();
+ while (!found && (base_instance_iter != base_iter->second.end()))
+ {
+ LLModelInstance& base_instance = *base_instance_iter++;
+ LLModel* base_model = base_instance.mModel;
+
+ if (base_model && (base_model->mLabel == name_to_match))
+ {
+ baseModelOut = base_model;
+ return;
+ }
+ }
+ base_iter++;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// LLModelPreview
+//-----------------------------------------------------------------------------
+
+LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp)
+ : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, FALSE), LLMutex()
+ , mLodsQuery()
+ , mLodsWithParsingError()
+ , mPelvisZOffset(0.0f)
+ , mLegacyRigFlags(U32_MAX)
+ , mRigValidJointUpload(false)
+ , mPhysicsSearchLOD(LLModel::LOD_PHYSICS)
+ , mResetJoints(false)
+ , mModelNoErrors(true)
+ , mLastJointUpdate(false)
+ , mFirstSkinUpdate(true)
+ , mHasDegenerate(false)
+ , mImporterDebug(LLCachedControl<bool>(gSavedSettings, "ImporterDebug", false))
+{
+ mNeedsUpdate = TRUE;
+ mCameraDistance = 0.f;
+ mCameraYaw = 0.f;
+ mCameraPitch = 0.f;
+ mCameraZoom = 1.f;
+ mTextureName = 0;
+ mPreviewLOD = 0;
+ mModelLoader = NULL;
+ mMaxTriangleLimit = 0;
+ mDirty = false;
+ mGenLOD = false;
+ mLoading = false;
+ mLookUpLodFiles = false;
+ mLoadState = LLModelLoader::STARTING;
+ mGroup = 0;
+ mLODFrozen = false;
+ mBuildShareTolerance = 0.f;
+ mBuildQueueMode = GLOD_QUEUE_GREEDY;
+ mBuildBorderMode = GLOD_BORDER_UNLOCK;
+ mBuildOperator = GLOD_OPERATOR_EDGE_COLLAPSE;
+
+ for (U32 i = 0; i < LLModel::NUM_LODS; ++i)
+ {
+ mRequestedTriangleCount[i] = 0;
+ mRequestedCreaseAngle[i] = -1.f;
+ mRequestedLoDMode[i] = 0;
+ mRequestedErrorThreshold[i] = 0.f;
+ mRequestedBuildOperator[i] = 0;
+ mRequestedQueueMode[i] = 0;
+ mRequestedBorderMode[i] = 0;
+ mRequestedShareTolerance[i] = 0.f;
+ }
+
+ mViewOption["show_textures"] = false;
+
+ mFMP = fmp;
+
+ mHasPivot = false;
+ mModelPivot = LLVector3(0.0f, 0.0f, 0.0f);
+
+ glodInit();
+
+ createPreviewAvatar();
+}
+
+LLModelPreview::~LLModelPreview()
+{
+ // glod apparently has internal mem alignment issues that are angering
+ // the heap-check code in windows, these should be hunted down in that
+ // TP code, if possible
+ //
+ // kernel32.dll!HeapFree() + 0x14 bytes
+ // msvcr100.dll!free(void * pBlock) Line 51 C
+ // glod.dll!glodGetGroupParameteriv() + 0x119 bytes
+ // glod.dll!glodShutdown() + 0x77 bytes
+ //
+ //glodShutdown();
+ if (mModelLoader)
+ {
+ mModelLoader->shutdown();
+ }
+}
+
+U32 LLModelPreview::calcResourceCost()
+{
+ assert_main_thread();
+
+ rebuildUploadData();
+
+ //Upload skin is selected BUT check to see if the joints coming in from the asset were malformed.
+ if (mFMP && mFMP->childGetValue("upload_skin").asBoolean())
+ {
+ bool uploadingJointPositions = mFMP->childGetValue("upload_joints").asBoolean();
+ if (uploadingJointPositions && !isRigValidForJointPositionUpload())
+ {
+ mFMP->childDisable("ok_btn");
+ }
+ }
+
+ std::set<LLModel*> accounted;
+ U32 num_points = 0;
+ U32 num_hulls = 0;
+
+ F32 debug_scale = mFMP ? mFMP->childGetValue("import_scale").asReal() : 1.f;
+ mPelvisZOffset = mFMP ? mFMP->childGetValue("pelvis_offset").asReal() : 3.0f;
+
+ if (mFMP && mFMP->childGetValue("upload_joints").asBoolean())
+ {
+ // FIXME if preview avatar ever gets reused, this fake mesh ID stuff will fail.
+ // see also call to addAttachmentPosOverride.
+ LLUUID fake_mesh_id;
+ fake_mesh_id.generate();
+ getPreviewAvatar()->addPelvisFixup(mPelvisZOffset, fake_mesh_id);
+ }
+
+ F32 streaming_cost = 0.f;
+ F32 physics_cost = 0.f;
+ for (U32 i = 0; i < mUploadData.size(); ++i)
+ {
+ LLModelInstance& instance = mUploadData[i];
+
+ if (accounted.find(instance.mModel) == accounted.end())
+ {
+ accounted.insert(instance.mModel);
+
+ LLModel::Decomposition& decomp =
+ instance.mLOD[LLModel::LOD_PHYSICS] ?
+ instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics :
+ instance.mModel->mPhysics;
+
+ //update instance skin info for each lods pelvisZoffset
+ for (int j = 0; j<LLModel::NUM_LODS; ++j)
+ {
+ if (instance.mLOD[j])
+ {
+ instance.mLOD[j]->mSkinInfo.mPelvisOffset = mPelvisZOffset;
+ }
+ }
+
+ std::stringstream ostr;
+ LLSD ret = LLModel::writeModel(ostr,
+ instance.mLOD[4],
+ instance.mLOD[3],
+ instance.mLOD[2],
+ instance.mLOD[1],
+ instance.mLOD[0],
+ decomp,
+ mFMP->childGetValue("upload_skin").asBoolean(),
+ mFMP->childGetValue("upload_joints").asBoolean(),
+ mFMP->childGetValue("lock_scale_if_joint_position").asBoolean(),
+ TRUE,
+ FALSE,
+ instance.mModel->mSubmodelID);
+
+ num_hulls += decomp.mHull.size();
+ for (U32 i = 0; i < decomp.mHull.size(); ++i)
+ {
+ num_points += decomp.mHull[i].size();
+ }
+
+ //calculate streaming cost
+ LLMatrix4 transformation = instance.mTransform;
+
+ LLVector3 position = LLVector3(0, 0, 0) * transformation;
+
+ 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);
+
+ F32 radius = scale.length()*0.5f*debug_scale;
+
+ LLMeshCostData costs;
+ if (gMeshRepo.getCostData(ret, costs))
+ {
+ streaming_cost += costs.getRadiusBasedStreamingCost(radius);
+ }
+ }
+ }
+
+ F32 scale = mFMP ? mFMP->childGetValue("import_scale").asReal()*2.f : 2.f;
+
+ mDetailsSignal(mPreviewScale[0] * scale, mPreviewScale[1] * scale, mPreviewScale[2] * scale, streaming_cost, physics_cost);
+
+ updateStatusMessages();
+
+ return (U32)streaming_cost;
+}
+
+void LLModelPreview::rebuildUploadData()
+{
+ assert_main_thread();
+
+ mUploadData.clear();
+ mTextureSet.clear();
+
+ //fill uploaddata instance vectors from scene data
+
+ std::string requested_name = mFMP->getChild<LLUICtrl>("description_form")->getValue().asString();
+
+ LLSpinCtrl* scale_spinner = mFMP->getChild<LLSpinCtrl>("import_scale");
+
+ F32 scale = scale_spinner->getValue().asReal();
+
+ LLMatrix4 scale_mat;
+ scale_mat.initScale(LLVector3(scale, scale, scale));
+
+ F32 max_scale = 0.f;
+
+ BOOL legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching");
+ U32 load_state = 0;
+
+ for (LLModelLoader::scene::iterator iter = mBaseScene.begin(); iter != mBaseScene.end(); ++iter)
+ { //for each transform in scene
+ LLMatrix4 mat = iter->first;
+
+ // compute position
+ LLVector3 position = LLVector3(0, 0, 0) * mat;
+
+ // compute scale
+ LLVector3 x_transformed = LLVector3(1, 0, 0) * mat - position;
+ LLVector3 y_transformed = LLVector3(0, 1, 0) * mat - position;
+ LLVector3 z_transformed = LLVector3(0, 0, 1) * mat - position;
+ F32 x_length = x_transformed.normalize();
+ F32 y_length = y_transformed.normalize();
+ F32 z_length = z_transformed.normalize();
+
+ max_scale = llmax(llmax(llmax(max_scale, x_length), y_length), z_length);
+
+ mat *= scale_mat;
+
+ for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end();)
+ { //for each instance with said transform applied
+ LLModelInstance instance = *model_iter++;
+
+ LLModel* base_model = instance.mModel;
+
+ if (base_model && !requested_name.empty())
+ {
+ base_model->mRequestedLabel = requested_name;
+ }
+
+ for (int i = LLModel::NUM_LODS - 1; i >= LLModel::LOD_IMPOSTOR; i--)
+ {
+ LLModel* lod_model = NULL;
+ if (!legacyMatching)
+ {
+ // Fill LOD slots by finding matching meshes by label with name extensions
+ // in the appropriate scene for each LOD. This fixes all kinds of issues
+ // where the indexed method below fails in spectacular fashion.
+ // If you don't take the time to name your LOD and PHYS meshes
+ // with the name of their corresponding mesh in the HIGH LOD,
+ // then the indexed method will be attempted below.
+
+ LLMatrix4 transform;
+
+ std::string name_to_match = instance.mLabel;
+ llassert(!name_to_match.empty());
+
+ int extensionLOD;
+ if (i != LLModel::LOD_PHYSICS || mModel[LLModel::LOD_PHYSICS].empty())
+ {
+ extensionLOD = i;
+ }
+ else
+ {
+ //Physics can be inherited from other LODs or loaded, so we need to adjust what extension we are searching for
+ extensionLOD = mPhysicsSearchLOD;
+ }
+
+ std::string toAdd = getLodSuffix(extensionLOD);
+
+ if (name_to_match.find(toAdd) == -1)
+ {
+ name_to_match += toAdd;
+ }
+
+ FindModel(mScene[i], name_to_match, lod_model, transform);
+
+ if (!lod_model && i != LLModel::LOD_PHYSICS)
+ {
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "Search of" << name_to_match;
+ out << " in LOD" << i;
+ out << " list failed. Searching for alternative among LOD lists.";
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+
+ int searchLOD = (i > LLModel::LOD_HIGH) ? LLModel::LOD_HIGH : i;
+ while ((searchLOD <= LLModel::LOD_HIGH) && !lod_model)
+ {
+ std::string name_to_match = instance.mLabel;
+ llassert(!name_to_match.empty());
+
+ std::string toAdd = getLodSuffix(searchLOD);
+
+ if (name_to_match.find(toAdd) == -1)
+ {
+ name_to_match += toAdd;
+ }
+
+ // See if we can find an appropriately named model in LOD 'searchLOD'
+ //
+ FindModel(mScene[searchLOD], name_to_match, lod_model, transform);
+ searchLOD++;
+ }
+ }
+ }
+ else
+ {
+ // Use old method of index-based association
+ U32 idx = 0;
+ for (idx = 0; idx < mBaseModel.size(); ++idx)
+ {
+ // find reference instance for this model
+ if (mBaseModel[idx] == base_model)
+ {
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "Attempting to use model index " << idx;
+ out << " for LOD" << i;
+ out << " of " << instance.mLabel;
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+ break;
+ }
+ }
+
+ // If the model list for the current LOD includes that index...
+ //
+ if (mModel[i].size() > idx)
+ {
+ // Assign that index from the model list for our LOD as the LOD model for this instance
+ //
+ lod_model = mModel[i][idx];
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "Indexed match of model index " << idx << " at LOD " << i << " to model named " << lod_model->mLabel;
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+ }
+ else if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "List of models does not include index " << idx;
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+ }
+
+ if (lod_model)
+ {
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ if (i == LLModel::LOD_PHYSICS)
+ {
+ out << "Assigning collision for " << instance.mLabel << " to match " << lod_model->mLabel;
+ }
+ else
+ {
+ out << "Assigning LOD" << i << " for " << instance.mLabel << " to found match " << lod_model->mLabel;
+ }
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+ instance.mLOD[i] = lod_model;
+ }
+ else
+ {
+ if (i < LLModel::LOD_HIGH && !lodsReady())
+ {
+ // assign a placeholder from previous LOD until lod generation is complete.
+ // Note: we might need to assign it regardless of conditions like named search does, to prevent crashes.
+ instance.mLOD[i] = instance.mLOD[i + 1];
+ }
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "List of models does not include " << instance.mLabel;
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+ }
+ }
+
+ LLModel* high_lod_model = instance.mLOD[LLModel::LOD_HIGH];
+ if (!high_lod_model)
+ {
+ LLFloaterModelPreview::addStringToLog("Model " + instance.mLabel + " has no High Lod (LOD3).", true);
+ load_state = LLModelLoader::ERROR_MATERIALS;
+ mFMP->childDisable("calculate_btn");
+ }
+ else
+ {
+ for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++)
+ {
+ int refFaceCnt = 0;
+ int modelFaceCnt = 0;
+ llassert(instance.mLOD[i]);
+ if (instance.mLOD[i] && !instance.mLOD[i]->matchMaterialOrder(high_lod_model, refFaceCnt, modelFaceCnt))
+ {
+ LLFloaterModelPreview::addStringToLog("Model " + instance.mLabel + " has mismatching materials between lods." , true);
+ load_state = LLModelLoader::ERROR_MATERIALS;
+ mFMP->childDisable("calculate_btn");
+ }
+ }
+ LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP;
+ bool upload_skinweights = fmp && fmp->childGetValue("upload_skin").asBoolean();
+ if (upload_skinweights && high_lod_model->mSkinInfo.mJointNames.size() > 0)
+ {
+ LLQuaternion bind_rot = LLSkinningUtil::getUnscaledQuaternion(high_lod_model->mSkinInfo.mBindShapeMatrix);
+ LLQuaternion identity;
+ if (!bind_rot.isEqualEps(identity, 0.01))
+ {
+ // Bind shape matrix is not in standard X-forward orientation.
+ // Might be good idea to only show this once. It can be spammy.
+ std::ostringstream out;
+ out << "non-identity bind shape rot. mat is ";
+ out << high_lod_model->mSkinInfo.mBindShapeMatrix;
+ out << " bind_rot ";
+ out << bind_rot;
+ LL_WARNS() << out.str() << LL_ENDL;
+
+ LLFloaterModelPreview::addStringToLog(out, getLoadState() != LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION);
+ load_state = LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION;
+ }
+ }
+ }
+ instance.mTransform = mat;
+ mUploadData.push_back(instance);
+ }
+ }
+
+ for (U32 lod = 0; lod < LLModel::NUM_LODS - 1; lod++)
+ {
+ // Search for models that are not included into upload data
+ // If we found any, that means something we loaded is not a sub-model.
+ for (U32 model_ind = 0; model_ind < mModel[lod].size(); ++model_ind)
+ {
+ bool found_model = false;
+ for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
+ {
+ LLModelInstance& instance = *iter;
+ if (instance.mLOD[lod] == mModel[lod][model_ind])
+ {
+ found_model = true;
+ break;
+ }
+ }
+ if (!found_model && mModel[lod][model_ind] && !mModel[lod][model_ind]->mSubmodelID)
+ {
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "Model " << mModel[lod][model_ind]->mLabel << " was not used - mismatching lod models.";
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, true);
+ }
+ load_state = LLModelLoader::ERROR_MATERIALS;
+ mFMP->childDisable("calculate_btn");
+ }
+ }
+ }
+
+ // Update state for notifications
+ if (load_state > 0)
+ {
+ // encountered issues
+ setLoadState(load_state);
+ }
+ else if (getLoadState() == LLModelLoader::ERROR_MATERIALS
+ || getLoadState() == LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION)
+ {
+ // This is only valid for these two error types because they are
+ // only used inside rebuildUploadData() and updateStatusMessages()
+ // updateStatusMessages() is called after rebuildUploadData()
+ setLoadState(LLModelLoader::DONE);
+ }
+
+ F32 max_import_scale = (DEFAULT_MAX_PRIM_SCALE - 0.1f) / max_scale;
+
+ F32 max_axis = llmax(mPreviewScale.mV[0], mPreviewScale.mV[1]);
+ max_axis = llmax(max_axis, mPreviewScale.mV[2]);
+ max_axis *= 2.f;
+
+ //clamp scale so that total imported model bounding box is smaller than 240m on a side
+ max_import_scale = llmin(max_import_scale, 240.f / max_axis);
+
+ scale_spinner->setMaxValue(max_import_scale);
+
+ if (max_import_scale < scale)
+ {
+ scale_spinner->setValue(max_import_scale);
+ }
+
+}
+
+void LLModelPreview::saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position)
+{
+ if (!mLODFile[LLModel::LOD_HIGH].empty())
+ {
+ std::string filename = mLODFile[LLModel::LOD_HIGH];
+ std::string slm_filename;
+
+ if (LLModelLoader::getSLMFilename(filename, slm_filename))
+ {
+ saveUploadData(slm_filename, save_skinweights, save_joint_positions, lock_scale_if_joint_position);
+ }
+ }
+}
+
+void LLModelPreview::saveUploadData(const std::string& filename,
+ bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position)
+{
+
+ std::set<LLPointer<LLModel> > meshes;
+ std::map<LLModel*, std::string> mesh_binary;
+
+ LLModel::hull empty_hull;
+
+ LLSD data;
+
+ data["version"] = SLM_SUPPORTED_VERSION;
+ if (!mBaseModel.empty())
+ {
+ data["name"] = mBaseModel[0]->getName();
+ }
+
+ S32 mesh_id = 0;
+
+ //build list of unique models and initialize local id
+ for (U32 i = 0; i < mUploadData.size(); ++i)
+ {
+ LLModelInstance& instance = mUploadData[i];
+
+ if (meshes.find(instance.mModel) == meshes.end())
+ {
+ instance.mModel->mLocalID = mesh_id++;
+ meshes.insert(instance.mModel);
+
+ std::stringstream str;
+ LLModel::Decomposition& decomp =
+ instance.mLOD[LLModel::LOD_PHYSICS].notNull() ?
+ instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics :
+ instance.mModel->mPhysics;
+
+ LLModel::writeModel(str,
+ instance.mLOD[LLModel::LOD_PHYSICS],
+ instance.mLOD[LLModel::LOD_HIGH],
+ instance.mLOD[LLModel::LOD_MEDIUM],
+ instance.mLOD[LLModel::LOD_LOW],
+ instance.mLOD[LLModel::LOD_IMPOSTOR],
+ decomp,
+ save_skinweights,
+ save_joint_positions,
+ lock_scale_if_joint_position,
+ FALSE, TRUE, instance.mModel->mSubmodelID);
+
+ data["mesh"][instance.mModel->mLocalID] = str.str();
+ }
+
+ data["instance"][i] = instance.asLLSD();
+ }
+
+ llofstream out(filename.c_str(), std::ios_base::out | std::ios_base::binary);
+ LLSDSerialize::toBinary(data, out);
+ out.flush();
+ out.close();
+}
+
+void LLModelPreview::clearModel(S32 lod)
+{
+ if (lod < 0 || lod > LLModel::LOD_PHYSICS)
+ {
+ return;
+ }
+
+ mVertexBuffer[lod].clear();
+ mModel[lod].clear();
+ mScene[lod].clear();
+}
+
+void LLModelPreview::getJointAliases(JointMap& joint_map)
+{
+ // Get all standard skeleton joints from the preview avatar.
+ LLVOAvatar *av = getPreviewAvatar();
+
+ //Joint names and aliases come from avatar_skeleton.xml
+
+ joint_map = av->getJointAliases();
+
+ std::vector<std::string> cv_names, attach_names;
+ av->getSortedJointNames(1, cv_names);
+ av->getSortedJointNames(2, attach_names);
+ for (std::vector<std::string>::iterator it = cv_names.begin(); it != cv_names.end(); ++it)
+ {
+ joint_map[*it] = *it;
+ }
+ for (std::vector<std::string>::iterator it = attach_names.begin(); it != attach_names.end(); ++it)
+ {
+ joint_map[*it] = *it;
+ }
+}
+
+void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable_slm)
+{
+ assert_main_thread();
+
+ LLMutexLock lock(this);
+
+ if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::NUM_LODS - 1)
+ {
+ std::ostringstream out;
+ out << "Invalid level of detail: ";
+ out << lod;
+ LL_WARNS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, true);
+ assert(lod >= LLModel::LOD_IMPOSTOR && lod < LLModel::NUM_LODS);
+ return;
+ }
+
+ // This triggers if you bring up the file picker and then hit CANCEL.
+ // Just use the previous model (if any) and ignore that you brought up
+ // the file picker.
+
+ if (filename.empty())
+ {
+ if (mBaseModel.empty())
+ {
+ // this is the initial file picking. Close the whole floater
+ // if we don't have a base model to show for high LOD.
+ mFMP->closeFloater(false);
+ }
+ mLoading = false;
+ return;
+ }
+
+ if (mModelLoader)
+ {
+ LL_WARNS() << "Incompleted model load operation pending." << LL_ENDL;
+ return;
+ }
+
+ mLODFile[lod] = filename;
+
+ if (lod == LLModel::LOD_HIGH)
+ {
+ clearGLODGroup();
+ }
+
+ std::map<std::string, std::string> joint_alias_map;
+ getJointAliases(joint_alias_map);
+
+ mModelLoader = new LLDAELoader(
+ filename,
+ lod,
+ &LLModelPreview::loadedCallback,
+ &LLModelPreview::lookupJointByName,
+ &LLModelPreview::loadTextures,
+ &LLModelPreview::stateChangedCallback,
+ this,
+ mJointTransformMap,
+ mJointsFromNode,
+ joint_alias_map,
+ LLSkinningUtil::getMaxJointCount(),
+ gSavedSettings.getU32("ImporterModelLimit"),
+ gSavedSettings.getBOOL("ImporterPreprocessDAE"));
+
+ if (force_disable_slm)
+ {
+ mModelLoader->mTrySLM = false;
+ }
+ else
+ {
+ // For MAINT-6647, we have set force_disable_slm to true,
+ // which means this code path will never be taken. Trying to
+ // re-use SLM files has never worked properly; in particular,
+ // it tends to force the UI into strange checkbox options
+ // which cannot be altered.
+
+ //only try to load from slm if viewer is configured to do so and this is the
+ //initial model load (not an LoD or physics shape)
+ mModelLoader->mTrySLM = gSavedSettings.getBOOL("MeshImportUseSLM") && mUploadData.empty();
+ }
+ mModelLoader->start();
+
+ mFMP->childSetTextArg("status", "[STATUS]", mFMP->getString("status_reading_file"));
+
+ setPreviewLOD(lod);
+
+ if (getLoadState() >= LLModelLoader::ERROR_PARSING)
+ {
+ mFMP->childDisable("ok_btn");
+ mFMP->childDisable("calculate_btn");
+ }
+
+ if (lod == mPreviewLOD)
+ {
+ mFMP->childSetValue("lod_file_" + lod_name[lod], mLODFile[lod]);
+ }
+ else if (lod == LLModel::LOD_PHYSICS)
+ {
+ mFMP->childSetValue("physics_file", mLODFile[lod]);
+ }
+
+ mFMP->openFloater();
+}
+
+void LLModelPreview::setPhysicsFromLOD(S32 lod)
+{
+ assert_main_thread();
+
+ if (lod >= 0 && lod <= 3)
+ {
+ mPhysicsSearchLOD = lod;
+ mModel[LLModel::LOD_PHYSICS] = mModel[lod];
+ mScene[LLModel::LOD_PHYSICS] = mScene[lod];
+ mLODFile[LLModel::LOD_PHYSICS].clear();
+ mFMP->childSetValue("physics_file", mLODFile[LLModel::LOD_PHYSICS]);
+ mVertexBuffer[LLModel::LOD_PHYSICS].clear();
+ rebuildUploadData();
+ refresh();
+ updateStatusMessages();
+ }
+}
+
+void LLModelPreview::clearIncompatible(S32 lod)
+{
+ //Don't discard models if specified model is the physic rep
+ if (lod == LLModel::LOD_PHYSICS)
+ {
+ return;
+ }
+
+ // at this point we don't care about sub-models,
+ // different amount of sub-models means face count mismatch, not incompatibility
+ U32 lod_size = countRootModels(mModel[lod]);
+ for (U32 i = 0; i <= LLModel::LOD_HIGH; i++)
+ { //clear out any entries that aren't compatible with this model
+ if (i != lod)
+ {
+ if (countRootModels(mModel[i]) != lod_size)
+ {
+ mModel[i].clear();
+ mScene[i].clear();
+ mVertexBuffer[i].clear();
+
+ if (i == LLModel::LOD_HIGH)
+ {
+ mBaseModel = mModel[lod];
+ clearGLODGroup();
+ mBaseScene = mScene[lod];
+ mVertexBuffer[5].clear();
+ }
+ }
+ }
+ }
+}
+
+void LLModelPreview::clearGLODGroup()
+{
+ if (mGroup)
+ {
+ for (std::map<LLPointer<LLModel>, U32>::iterator iter = mObject.begin(); iter != mObject.end(); ++iter)
+ {
+ glodDeleteObject(iter->second);
+ stop_gloderror();
+ }
+ mObject.clear();
+
+ glodDeleteGroup(mGroup);
+ stop_gloderror();
+ mGroup = 0;
+ }
+}
+
+void LLModelPreview::loadModelCallback(S32 loaded_lod)
+{
+ assert_main_thread();
+
+ LLMutexLock lock(this);
+ if (!mModelLoader)
+ {
+ mLoading = false;
+ return;
+ }
+ if (getLoadState() >= LLModelLoader::ERROR_PARSING)
+ {
+ mLoading = false;
+ mModelLoader = NULL;
+ mLodsWithParsingError.push_back(loaded_lod);
+ return;
+ }
+
+ mLodsWithParsingError.erase(std::remove(mLodsWithParsingError.begin(), mLodsWithParsingError.end(), loaded_lod), mLodsWithParsingError.end());
+ if (mLodsWithParsingError.empty())
+ {
+ mFMP->childEnable("calculate_btn");
+ }
+
+ // Copy determinations about rig so UI will reflect them
+ //
+ setRigValidForJointPositionUpload(mModelLoader->isRigValidForJointPositionUpload());
+ setLegacyRigFlags(mModelLoader->getLegacyRigFlags());
+
+ mModelLoader->loadTextures();
+
+ if (loaded_lod == -1)
+ { //populate all LoDs from model loader scene
+ mBaseModel.clear();
+ mBaseScene.clear();
+
+ bool skin_weights = false;
+ bool joint_overrides = false;
+ bool lock_scale_if_joint_position = false;
+
+ for (S32 lod = 0; lod < LLModel::NUM_LODS; ++lod)
+ { //for each LoD
+
+ //clear scene and model info
+ mScene[lod].clear();
+ mModel[lod].clear();
+ mVertexBuffer[lod].clear();
+
+ if (mModelLoader->mScene.begin()->second[0].mLOD[lod].notNull())
+ { //if this LoD exists in the loaded scene
+
+ //copy scene to current LoD
+ mScene[lod] = mModelLoader->mScene;
+
+ //touch up copied scene to look like current LoD
+ for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter)
+ {
+ LLModelLoader::model_instance_list& list = iter->second;
+
+ for (LLModelLoader::model_instance_list::iterator list_iter = list.begin(); list_iter != list.end(); ++list_iter)
+ {
+ //override displayed model with current LoD
+ list_iter->mModel = list_iter->mLOD[lod];
+
+ if (!list_iter->mModel)
+ {
+ continue;
+ }
+
+ //add current model to current LoD's model list (LLModel::mLocalID makes a good vector index)
+ S32 idx = list_iter->mModel->mLocalID;
+
+ if (mModel[lod].size() <= idx)
+ { //stretch model list to fit model at given index
+ mModel[lod].resize(idx + 1);
+ }
+
+ mModel[lod][idx] = list_iter->mModel;
+ if (!list_iter->mModel->mSkinWeights.empty())
+ {
+ skin_weights = true;
+
+ if (!list_iter->mModel->mSkinInfo.mAlternateBindMatrix.empty())
+ {
+ joint_overrides = true;
+ }
+ if (list_iter->mModel->mSkinInfo.mLockScaleIfJointPosition)
+ {
+ lock_scale_if_joint_position = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (mFMP)
+ {
+ LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP;
+
+ if (skin_weights)
+ { //enable uploading/previewing of skin weights if present in .slm file
+ fmp->enableViewOption("show_skin_weight");
+ mViewOption["show_skin_weight"] = true;
+ fmp->childSetValue("upload_skin", true);
+ }
+
+ if (joint_overrides)
+ {
+ fmp->enableViewOption("show_joint_overrides");
+ mViewOption["show_joint_overrides"] = true;
+ fmp->enableViewOption("show_joint_positions");
+ mViewOption["show_joint_positions"] = true;
+ fmp->childSetValue("upload_joints", true);
+ }
+ else
+ {
+ fmp->clearAvatarTab();
+ }
+
+ if (lock_scale_if_joint_position)
+ {
+ fmp->enableViewOption("lock_scale_if_joint_position");
+ mViewOption["lock_scale_if_joint_position"] = true;
+ fmp->childSetValue("lock_scale_if_joint_position", true);
+ }
+ }
+
+ //copy high lod to base scene for LoD generation
+ mBaseScene = mScene[LLModel::LOD_HIGH];
+ mBaseModel = mModel[LLModel::LOD_HIGH];
+
+ mDirty = true;
+ resetPreviewTarget();
+ }
+ else
+ { //only replace given LoD
+ mModel[loaded_lod] = mModelLoader->mModelList;
+ mScene[loaded_lod] = mModelLoader->mScene;
+ mVertexBuffer[loaded_lod].clear();
+
+ setPreviewLOD(loaded_lod);
+
+ if (loaded_lod == LLModel::LOD_HIGH)
+ { //save a copy of the highest LOD for automatic LOD manipulation
+ if (mBaseModel.empty())
+ { //first time we've loaded a model, auto-gen LoD
+ mGenLOD = true;
+ }
+
+ mBaseModel = mModel[loaded_lod];
+ clearGLODGroup();
+
+ mBaseScene = mScene[loaded_lod];
+ mVertexBuffer[5].clear();
+ }
+ else
+ {
+ BOOL legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching");
+ if (!legacyMatching)
+ {
+ if (!mBaseModel.empty())
+ {
+ BOOL name_based = FALSE;
+ BOOL has_submodels = FALSE;
+ for (U32 idx = 0; idx < mBaseModel.size(); ++idx)
+ {
+ if (mBaseModel[idx]->mSubmodelID)
+ { // don't do index-based renaming when the base model has submodels
+ has_submodels = TRUE;
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "High LOD has submodels";
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+ break;
+ }
+ }
+
+ for (U32 idx = 0; idx < mModel[loaded_lod].size(); ++idx)
+ {
+ std::string loaded_name = stripSuffix(mModel[loaded_lod][idx]->mLabel);
+
+ LLModel* found_model = NULL;
+ LLMatrix4 transform;
+ FindModel(mBaseScene, loaded_name, found_model, transform);
+ if (found_model)
+ { // don't rename correctly named models (even if they are placed in a wrong order)
+ name_based = TRUE;
+ }
+
+ if (mModel[loaded_lod][idx]->mSubmodelID)
+ { // don't rename the models when loaded LOD model has submodels
+ has_submodels = TRUE;
+ }
+ }
+
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "Loaded LOD " << loaded_lod << ": correct names" << (name_based ? "" : "NOT ") << "found; submodels " << (has_submodels ? "" : "NOT ") << "found";
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+
+ if (!name_based && !has_submodels)
+ { // replace the name of the model loaded for any non-HIGH LOD to match the others (MAINT-5601)
+ // this actually works like "ImporterLegacyMatching" for this particular LOD
+ for (U32 idx = 0; idx < mModel[loaded_lod].size() && idx < mBaseModel.size(); ++idx)
+ {
+ std::string name = mBaseModel[idx]->mLabel;
+ std::string loaded_name = stripSuffix(mModel[loaded_lod][idx]->mLabel);
+
+ if (loaded_name != name)
+ {
+ name += getLodSuffix(loaded_lod);
+
+ if (mImporterDebug)
+ {
+ std::ostringstream out;
+ out << "Loded model name " << mModel[loaded_lod][idx]->mLabel;
+ out << " for LOD " << loaded_lod;
+ out << " doesn't match the base model. Renaming to " << name;
+ LL_WARNS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ }
+
+ mModel[loaded_lod][idx]->mLabel = name;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ clearIncompatible(loaded_lod);
+
+ mDirty = true;
+
+ if (loaded_lod == LLModel::LOD_HIGH)
+ {
+ resetPreviewTarget();
+ }
+ }
+
+ mLoading = false;
+ if (mFMP)
+ {
+ if (!mBaseModel.empty())
+ {
+ const std::string& model_name = mBaseModel[0]->getName();
+ LLLineEditor* description_form = mFMP->getChild<LLLineEditor>("description_form");
+ if (description_form->getText().empty())
+ {
+ description_form->setText(model_name);
+ }
+ // Add info to log that loading is complete (purpose: separator between loading and other logs)
+ LLSD args;
+ args["MODEL_NAME"] = model_name; // Teoretically shouldn't be empty, but might be better idea to add filename here
+ LLFloaterModelPreview::addStringToLog("ModelLoaded", args, false, loaded_lod);
+ }
+ }
+ refresh();
+
+ mModelLoadedSignal();
+
+ mModelLoader = NULL;
+}
+
+void LLModelPreview::resetPreviewTarget()
+{
+ if (mModelLoader)
+ {
+ mPreviewTarget = (mModelLoader->mExtents[0] + mModelLoader->mExtents[1]) * 0.5f;
+ mPreviewScale = (mModelLoader->mExtents[1] - mModelLoader->mExtents[0]) * 0.5f;
+ }
+
+ setPreviewTarget(mPreviewScale.magVec()*10.f);
+}
+
+void LLModelPreview::generateNormals()
+{
+ assert_main_thread();
+
+ S32 which_lod = mPreviewLOD;
+
+ if (which_lod > 4 || which_lod < 0 ||
+ mModel[which_lod].empty())
+ {
+ return;
+ }
+
+ F32 angle_cutoff = mFMP->childGetValue("crease_angle").asReal();
+
+ mRequestedCreaseAngle[which_lod] = angle_cutoff;
+
+ angle_cutoff *= DEG_TO_RAD;
+
+ if (which_lod == 3 && !mBaseModel.empty())
+ {
+ if (mBaseModelFacesCopy.empty())
+ {
+ mBaseModelFacesCopy.reserve(mBaseModel.size());
+ for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it)
+ {
+ v_LLVolumeFace_t faces;
+ (*it)->copyFacesTo(faces);
+ mBaseModelFacesCopy.push_back(faces);
+ }
+ }
+
+ for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it)
+ {
+ (*it)->generateNormals(angle_cutoff);
+ }
+
+ mVertexBuffer[5].clear();
+ }
+
+ bool perform_copy = mModelFacesCopy[which_lod].empty();
+ if (perform_copy) {
+ mModelFacesCopy[which_lod].reserve(mModel[which_lod].size());
+ }
+
+ for (LLModelLoader::model_list::iterator it = mModel[which_lod].begin(), itE = mModel[which_lod].end(); it != itE; ++it)
+ {
+ if (perform_copy)
+ {
+ v_LLVolumeFace_t faces;
+ (*it)->copyFacesTo(faces);
+ mModelFacesCopy[which_lod].push_back(faces);
+ }
+
+ (*it)->generateNormals(angle_cutoff);
+ }
+
+ mVertexBuffer[which_lod].clear();
+ refresh();
+ updateStatusMessages();
+}
+
+void LLModelPreview::restoreNormals()
+{
+ S32 which_lod = mPreviewLOD;
+
+ if (which_lod > 4 || which_lod < 0 ||
+ mModel[which_lod].empty())
+ {
+ return;
+ }
+
+ if (!mBaseModelFacesCopy.empty())
+ {
+ llassert(mBaseModelFacesCopy.size() == mBaseModel.size());
+
+ vv_LLVolumeFace_t::const_iterator itF = mBaseModelFacesCopy.begin();
+ for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it, ++itF)
+ {
+ (*it)->copyFacesFrom((*itF));
+ }
+
+ mBaseModelFacesCopy.clear();
+ }
+
+ if (!mModelFacesCopy[which_lod].empty())
+ {
+ vv_LLVolumeFace_t::const_iterator itF = mModelFacesCopy[which_lod].begin();
+ for (LLModelLoader::model_list::iterator it = mModel[which_lod].begin(), itE = mModel[which_lod].end(); it != itE; ++it, ++itF)
+ {
+ (*it)->copyFacesFrom((*itF));
+ }
+
+ mModelFacesCopy[which_lod].clear();
+ }
+
+ mVertexBuffer[which_lod].clear();
+ refresh();
+ updateStatusMessages();
+}
+
+void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_limit)
+{
+ // Allow LoD from -1 to LLModel::LOD_PHYSICS
+ if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1)
+ {
+ std::ostringstream out;
+ out << "Invalid level of detail: " << which_lod;
+ LL_WARNS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ assert(which_lod >= -1 && which_lod < LLModel::NUM_LODS);
+ return;
+ }
+
+ if (mBaseModel.empty())
+ {
+ return;
+ }
+
+ LLVertexBuffer::unbind();
+
+ bool no_ff = LLGLSLShader::sNoFixedFunction;
+ LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
+ LLGLSLShader::sNoFixedFunction = false;
+
+ if (shader)
+ {
+ shader->unbind();
+ }
+
+ stop_gloderror();
+ static U32 cur_name = 1;
+
+ S32 limit = -1;
+
+ U32 triangle_count = 0;
+
+ U32 instanced_triangle_count = 0;
+
+ //get the triangle count for the whole scene
+ for (LLModelLoader::scene::iterator iter = mBaseScene.begin(), endIter = mBaseScene.end(); iter != endIter; ++iter)
+ {
+ for (LLModelLoader::model_instance_list::iterator instance = iter->second.begin(), end_instance = iter->second.end(); instance != end_instance; ++instance)
+ {
+ LLModel* mdl = instance->mModel;
+ if (mdl)
+ {
+ instanced_triangle_count += mdl->getNumTriangles();
+ }
+ }
+ }
+
+ //get the triangle count for the non-instanced set of models
+ for (U32 i = 0; i < mBaseModel.size(); ++i)
+ {
+ triangle_count += mBaseModel[i]->getNumTriangles();
+ }
+
+ //get ratio of uninstanced triangles to instanced triangles
+ F32 triangle_ratio = (F32)triangle_count / (F32)instanced_triangle_count;
+
+ U32 base_triangle_count = triangle_count;
+
+ U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;
+
+ U32 lod_mode = 0;
+
+ F32 lod_error_threshold = 0;
+
+ // The LoD should be in range from Lowest to High
+ if (which_lod > -1 && which_lod < NUM_LOD)
+ {
+ LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode_" + lod_name[which_lod]);
+ if (iface)
+ {
+ lod_mode = iface->getFirstSelectedIndex();
+ }
+
+ lod_error_threshold = mFMP->childGetValue("lod_error_threshold_" + lod_name[which_lod]).asReal();
+ }
+
+ if (which_lod != -1)
+ {
+ mRequestedLoDMode[which_lod] = lod_mode;
+ }
+
+ if (lod_mode == 0)
+ {
+ lod_mode = GLOD_TRIANGLE_BUDGET;
+
+ // The LoD should be in range from Lowest to High
+ if (which_lod > -1 && which_lod < NUM_LOD)
+ {
+ limit = mFMP->childGetValue("lod_triangle_limit_" + lod_name[which_lod]).asInteger();
+ //convert from "scene wide" to "non-instanced" triangle limit
+ limit = (S32)((F32)limit*triangle_ratio);
+ }
+ }
+ else
+ {
+ lod_mode = GLOD_ERROR_THRESHOLD;
+ }
+
+ bool object_dirty = false;
+
+ if (mGroup == 0)
+ {
+ object_dirty = true;
+ mGroup = cur_name++;
+ glodNewGroup(mGroup);
+ }
+
+ if (object_dirty)
+ {
+ for (LLModelLoader::model_list::iterator iter = mBaseModel.begin(); iter != mBaseModel.end(); ++iter)
+ { //build GLOD objects for each model in base model list
+ LLModel* mdl = *iter;
+
+ if (mObject[mdl] != 0)
+ {
+ glodDeleteObject(mObject[mdl]);
+ }
+
+ mObject[mdl] = cur_name++;
+
+ glodNewObject(mObject[mdl], mGroup, GLOD_DISCRETE);
+ stop_gloderror();
+
+ if (iter == mBaseModel.begin() && !mdl->mSkinWeights.empty())
+ { //regenerate vertex buffer for skinned models to prevent animation feedback during LOD generation
+ mVertexBuffer[5].clear();
+ }
+
+ if (mVertexBuffer[5].empty())
+ {
+ genBuffers(5, false);
+ }
+
+ U32 tri_count = 0;
+ for (U32 i = 0; i < mVertexBuffer[5][mdl].size(); ++i)
+ {
+ LLVertexBuffer* buff = mVertexBuffer[5][mdl][i];
+ buff->setBuffer(type_mask & buff->getTypeMask());
+
+ U32 num_indices = mVertexBuffer[5][mdl][i]->getNumIndices();
+ if (num_indices > 2)
+ {
+ glodInsertElements(mObject[mdl], i, GL_TRIANGLES, num_indices, GL_UNSIGNED_SHORT, (U8*)mVertexBuffer[5][mdl][i]->getIndicesPointer(), 0, 0.f);
+ }
+ tri_count += num_indices / 3;
+ stop_gloderror();
+ }
+
+ glodBuildObject(mObject[mdl]);
+ stop_gloderror();
+ }
+ }
+
+
+ S32 start = LLModel::LOD_HIGH;
+ S32 end = 0;
+
+ if (which_lod != -1)
+ {
+ start = end = which_lod;
+ }
+
+ mMaxTriangleLimit = base_triangle_count;
+
+ for (S32 lod = start; lod >= end; --lod)
+ {
+ if (which_lod == -1)
+ {
+ if (lod < start)
+ {
+ triangle_count /= decimation;
+ }
+ }
+ else
+ {
+ if (enforce_tri_limit)
+ {
+ triangle_count = limit;
+ }
+ else
+ {
+ for (S32 j = LLModel::LOD_HIGH; j>which_lod; --j)
+ {
+ triangle_count /= decimation;
+ }
+ }
+ }
+
+ mModel[lod].clear();
+ mModel[lod].resize(mBaseModel.size());
+ mVertexBuffer[lod].clear();
+
+ U32 actual_tris = 0;
+ U32 actual_verts = 0;
+ U32 submeshes = 0;
+
+ mRequestedTriangleCount[lod] = (S32)((F32)triangle_count / triangle_ratio);
+ mRequestedErrorThreshold[lod] = lod_error_threshold;
+
+ glodGroupParameteri(mGroup, GLOD_ADAPT_MODE, lod_mode);
+ stop_gloderror();
+
+ glodGroupParameteri(mGroup, GLOD_ERROR_MODE, GLOD_OBJECT_SPACE_ERROR);
+ stop_gloderror();
+
+ glodGroupParameterf(mGroup, GLOD_OBJECT_SPACE_ERROR_THRESHOLD, lod_error_threshold);
+ stop_gloderror();
+
+ if (lod_mode != GLOD_TRIANGLE_BUDGET)
+ {
+ glodGroupParameteri(mGroup, GLOD_MAX_TRIANGLES, 0);
+ }
+ else
+ {
+ //SH-632: always add 1 to desired amount to avoid decimating below desired amount
+ glodGroupParameteri(mGroup, GLOD_MAX_TRIANGLES, triangle_count + 1);
+ }
+
+ stop_gloderror();
+ glodAdaptGroup(mGroup);
+ stop_gloderror();
+
+ for (U32 mdl_idx = 0; mdl_idx < mBaseModel.size(); ++mdl_idx)
+ {
+ LLModel* base = mBaseModel[mdl_idx];
+
+ GLint patch_count = 0;
+ glodGetObjectParameteriv(mObject[base], GLOD_NUM_PATCHES, &patch_count);
+ stop_gloderror();
+
+ LLVolumeParams volume_params;
+ volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
+ mModel[lod][mdl_idx] = new LLModel(volume_params, 0.f);
+
+ std::string name = base->mLabel + getLodSuffix(lod);
+
+ mModel[lod][mdl_idx]->mLabel = name;
+ mModel[lod][mdl_idx]->mSubmodelID = base->mSubmodelID;
+
+ GLint* sizes = new GLint[patch_count * 2];
+ glodGetObjectParameteriv(mObject[base], GLOD_PATCH_SIZES, sizes);
+ stop_gloderror();
+
+ GLint* names = new GLint[patch_count];
+ glodGetObjectParameteriv(mObject[base], GLOD_PATCH_NAMES, names);
+ stop_gloderror();
+
+ mModel[lod][mdl_idx]->setNumVolumeFaces(patch_count);
+
+ LLModel* target_model = mModel[lod][mdl_idx];
+
+ for (GLint i = 0; i < patch_count; ++i)
+ {
+ type_mask = mVertexBuffer[5][base][i]->getTypeMask();
+
+ LLPointer<LLVertexBuffer> buff = new LLVertexBuffer(type_mask, 0);
+
+ if (sizes[i * 2 + 1] > 0 && sizes[i * 2] > 0)
+ {
+ if (!buff->allocateBuffer(sizes[i * 2 + 1], sizes[i * 2], true))
+ {
+ // Todo: find a way to stop preview in this case instead of crashing
+ LL_ERRS() << "Failed buffer allocation during preview LOD generation."
+ << " Vertices: " << sizes[i * 2 + 1]
+ << " Indices: " << sizes[i * 2] << LL_ENDL;
+ }
+ buff->setBuffer(type_mask);
+ glodFillElements(mObject[base], names[i], GL_UNSIGNED_SHORT, (U8*)buff->getIndicesPointer());
+ stop_gloderror();
+ }
+ else
+ {
+ // This face was eliminated or we failed to allocate buffer,
+ // attempt to create a dummy triangle (one vertex, 3 indices, all 0)
+ buff->allocateBuffer(1, 3, true);
+ memset((U8*)buff->getMappedData(), 0, buff->getSize());
+ memset((U8*)buff->getIndicesPointer(), 0, buff->getIndicesSize());
+ }
+
+ buff->validateRange(0, buff->getNumVerts() - 1, buff->getNumIndices(), 0);
+
+ LLStrider<LLVector3> pos;
+ LLStrider<LLVector3> norm;
+ LLStrider<LLVector2> tc;
+ LLStrider<U16> index;
+
+ buff->getVertexStrider(pos);
+ if (type_mask & LLVertexBuffer::MAP_NORMAL)
+ {
+ buff->getNormalStrider(norm);
+ }
+ if (type_mask & LLVertexBuffer::MAP_TEXCOORD0)
+ {
+ buff->getTexCoord0Strider(tc);
+ }
+
+ buff->getIndexStrider(index);
+
+ target_model->setVolumeFaceData(names[i], pos, norm, tc, index, buff->getNumVerts(), buff->getNumIndices());
+ actual_tris += buff->getNumIndices() / 3;
+ actual_verts += buff->getNumVerts();
+ ++submeshes;
+
+ if (!validate_face(target_model->getVolumeFace(names[i])))
+ {
+ LL_ERRS() << "Invalid face generated during LOD generation." << LL_ENDL;
+ }
+ }
+
+ //blind copy skin weights and just take closest skin weight to point on
+ //decimated mesh for now (auto-generating LODs with skin weights is still a bit
+ //of an open problem).
+ target_model->mPosition = base->mPosition;
+ target_model->mSkinWeights = base->mSkinWeights;
+ target_model->mSkinInfo = base->mSkinInfo;
+ //copy material list
+ target_model->mMaterialList = base->mMaterialList;
+
+ if (!validate_model(target_model))
+ {
+ LL_ERRS() << "Invalid model generated when creating LODs" << LL_ENDL;
+ }
+
+ delete[] sizes;
+ delete[] names;
+ }
+
+ //rebuild scene based on mBaseScene
+ mScene[lod].clear();
+ mScene[lod] = mBaseScene;
+
+ for (U32 i = 0; i < mBaseModel.size(); ++i)
+ {
+ LLModel* mdl = mBaseModel[i];
+ LLModel* target = mModel[lod][i];
+ if (target)
+ {
+ for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter)
+ {
+ for (U32 j = 0; j < iter->second.size(); ++j)
+ {
+ if (iter->second[j].mModel == mdl)
+ {
+ iter->second[j].mModel = target;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mResourceCost = calcResourceCost();
+
+ LLVertexBuffer::unbind();
+ LLGLSLShader::sNoFixedFunction = no_ff;
+ if (shader)
+ {
+ shader->bind();
+ }
+}
+
+void LLModelPreview::updateStatusMessages()
+{
+ // bit mask values for physics errors. used to prevent overwrite of single line status
+ // TODO: use this to provied multiline status
+ enum PhysicsError
+ {
+ NONE = 0,
+ NOHAVOK = 1,
+ DEGENERATE = 2,
+ TOOMANYHULLS = 4,
+ TOOMANYVERTSINHULL = 8
+ };
+
+ assert_main_thread();
+
+ U32 has_physics_error{ PhysicsError::NONE }; // physics error bitmap
+ //triangle/vertex/submesh count for each mesh asset for each lod
+ std::vector<S32> tris[LLModel::NUM_LODS];
+ std::vector<S32> verts[LLModel::NUM_LODS];
+ std::vector<S32> submeshes[LLModel::NUM_LODS];
+
+ //total triangle/vertex/submesh count for each lod
+ S32 total_tris[LLModel::NUM_LODS];
+ S32 total_verts[LLModel::NUM_LODS];
+ S32 total_submeshes[LLModel::NUM_LODS];
+
+ for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++)
+ {
+ total_tris[i] = 0;
+ total_verts[i] = 0;
+ total_submeshes[i] = 0;
+ }
+
+ for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
+ {
+ LLModelInstance& instance = *iter;
+
+ LLModel* model_high_lod = instance.mLOD[LLModel::LOD_HIGH];
+ if (!model_high_lod)
+ {
+ setLoadState(LLModelLoader::ERROR_MATERIALS);
+ mFMP->childDisable("calculate_btn");
+ continue;
+ }
+
+ for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++)
+ {
+ LLModel* lod_model = instance.mLOD[i];
+ if (!lod_model)
+ {
+ setLoadState(LLModelLoader::ERROR_MATERIALS);
+ mFMP->childDisable("calculate_btn");
+ }
+ else
+ {
+ //for each model in the lod
+ S32 cur_tris = 0;
+ S32 cur_verts = 0;
+ S32 cur_submeshes = lod_model->getNumVolumeFaces();
+
+ for (S32 j = 0; j < cur_submeshes; ++j)
+ { //for each submesh (face), add triangles and vertices to current total
+ const LLVolumeFace& face = lod_model->getVolumeFace(j);
+ cur_tris += face.mNumIndices / 3;
+ cur_verts += face.mNumVertices;
+ }
+
+ std::string instance_name = instance.mLabel;
+
+ if (mImporterDebug)
+ {
+ // Useful for debugging generalized complaints below about total submeshes which don't have enough
+ // context to address exactly what needs to be fixed to move towards compliance with the rules.
+ //
+ std::ostringstream out;
+ out << "Instance " << lod_model->mLabel << " LOD " << i << " Verts: " << cur_verts;
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+
+ out.str("");
+ out << "Instance " << lod_model->mLabel << " LOD " << i << " Tris: " << cur_tris;
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+
+ out.str("");
+ out << "Instance " << lod_model->mLabel << " LOD " << i << " Faces: " << cur_submeshes;
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+
+ out.str("");
+ LLModel::material_list::iterator mat_iter = lod_model->mMaterialList.begin();
+ while (mat_iter != lod_model->mMaterialList.end())
+ {
+ out << "Instance " << lod_model->mLabel << " LOD " << i << " Material " << *(mat_iter);
+ LL_INFOS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ out.str("");
+ mat_iter++;
+ }
+ }
+
+ //add this model to the lod total
+ total_tris[i] += cur_tris;
+ total_verts[i] += cur_verts;
+ total_submeshes[i] += cur_submeshes;
+
+ //store this model's counts to asset data
+ tris[i].push_back(cur_tris);
+ verts[i].push_back(cur_verts);
+ submeshes[i].push_back(cur_submeshes);
+ }
+ }
+ }
+
+ if (mMaxTriangleLimit == 0)
+ {
+ mMaxTriangleLimit = total_tris[LLModel::LOD_HIGH];
+ }
+
+ mHasDegenerate = false;
+ {//check for degenerate triangles in physics mesh
+ U32 lod = LLModel::LOD_PHYSICS;
+ const LLVector4a scale(0.5f);
+ for (U32 i = 0; i < mModel[lod].size() && !mHasDegenerate; ++i)
+ { //for each model in the lod
+ if (mModel[lod][i] && mModel[lod][i]->mPhysics.mHull.empty())
+ { //no decomp exists
+ S32 cur_submeshes = mModel[lod][i]->getNumVolumeFaces();
+ for (S32 j = 0; j < cur_submeshes && !mHasDegenerate; ++j)
+ { //for each submesh (face), add triangles and vertices to current total
+ LLVolumeFace& face = mModel[lod][i]->getVolumeFace(j);
+ for (S32 k = 0; (k < face.mNumIndices) && !mHasDegenerate;)
+ {
+ U16 index_a = face.mIndices[k + 0];
+ U16 index_b = face.mIndices[k + 1];
+ U16 index_c = face.mIndices[k + 2];
+
+ if (index_c == 0 && index_b == 0 && index_a == 0) // test in reverse as 3rd index is less likely to be 0 in a normal case
+ {
+ LL_DEBUGS("MeshValidation") << "Empty placeholder triangle (3 identical index 0 verts) ignored" << LL_ENDL;
+ }
+ else
+ {
+ LLVector4a v1; v1.setMul(face.mPositions[index_a], scale);
+ LLVector4a v2; v2.setMul(face.mPositions[index_b], scale);
+ LLVector4a v3; v3.setMul(face.mPositions[index_c], scale);
+ if (ll_is_degenerate(v1, v2, v3))
+ {
+ mHasDegenerate = true;
+ }
+ }
+ k += 3;
+ }
+ }
+ }
+ }
+ }
+
+ // flag degenerates here rather than deferring to a MAV error later
+ mFMP->childSetVisible("physics_status_message_text", mHasDegenerate); //display or clear
+ auto degenerateIcon = mFMP->getChild<LLIconCtrl>("physics_status_message_icon");
+ degenerateIcon->setVisible(mHasDegenerate);
+ if (mHasDegenerate)
+ {
+ has_physics_error |= PhysicsError::DEGENERATE;
+ mFMP->childSetValue("physics_status_message_text", mFMP->getString("phys_status_degenerate_triangles"));
+ LLUIImagePtr img = LLUI::getUIImage("ModelImport_Status_Error");
+ degenerateIcon->setImage(img);
+ }
+
+ mFMP->childSetTextArg("submeshes_info", "[SUBMESHES]", llformat("%d", total_submeshes[LLModel::LOD_HIGH]));
+
+ std::string mesh_status_na = mFMP->getString("mesh_status_na");
+
+ S32 upload_status[LLModel::LOD_HIGH + 1];
+
+ mModelNoErrors = true;
+
+ const U32 lod_high = LLModel::LOD_HIGH;
+ U32 high_submodel_count = mModel[lod_high].size() - countRootModels(mModel[lod_high]);
+
+ for (S32 lod = 0; lod <= lod_high; ++lod)
+ {
+ upload_status[lod] = 0;
+
+ std::string message = "mesh_status_good";
+
+ if (total_tris[lod] > 0)
+ {
+ mFMP->childSetValue(lod_triangles_name[lod], llformat("%d", total_tris[lod]));
+ mFMP->childSetValue(lod_vertices_name[lod], llformat("%d", total_verts[lod]));
+ }
+ else
+ {
+ if (lod == lod_high)
+ {
+ upload_status[lod] = 2;
+ message = "mesh_status_missing_lod";
+ }
+ else
+ {
+ for (S32 i = lod - 1; i >= 0; --i)
+ {
+ if (total_tris[i] > 0)
+ {
+ upload_status[lod] = 2;
+ message = "mesh_status_missing_lod";
+ }
+ }
+ }
+
+ mFMP->childSetValue(lod_triangles_name[lod], mesh_status_na);
+ mFMP->childSetValue(lod_vertices_name[lod], mesh_status_na);
+ }
+
+ if (lod != lod_high)
+ {
+ if (total_submeshes[lod] && total_submeshes[lod] != total_submeshes[lod_high])
+ { //number of submeshes is different
+ message = "mesh_status_submesh_mismatch";
+ upload_status[lod] = 2;
+ }
+ else if (mModel[lod].size() - countRootModels(mModel[lod]) != high_submodel_count)
+ {//number of submodels is different, not all faces are matched correctly.
+ message = "mesh_status_submesh_mismatch";
+ upload_status[lod] = 2;
+ // Note: Submodels in instance were loaded from higher LOD and as result face count
+ // returns same value and total_submeshes[lod] is identical to high_lod one.
+ }
+ else if (!tris[lod].empty() && tris[lod].size() != tris[lod_high].size())
+ { //number of meshes is different
+ message = "mesh_status_mesh_mismatch";
+ upload_status[lod] = 2;
+ }
+ else if (!verts[lod].empty())
+ {
+ S32 sum_verts_higher_lod = 0;
+ S32 sum_verts_this_lod = 0;
+ for (U32 i = 0; i < verts[lod].size(); ++i)
+ {
+ sum_verts_higher_lod += ((i < verts[lod + 1].size()) ? verts[lod + 1][i] : 0);
+ sum_verts_this_lod += verts[lod][i];
+ }
+
+ if ((sum_verts_higher_lod > 0) &&
+ (sum_verts_this_lod > sum_verts_higher_lod))
+ {
+ //too many vertices in this lod
+ message = "mesh_status_too_many_vertices";
+ upload_status[lod] = 1;
+ }
+ }
+ }
+
+ LLIconCtrl* icon = mFMP->getChild<LLIconCtrl>(lod_icon_name[lod]);
+ LLUIImagePtr img = LLUI::getUIImage(lod_status_image[upload_status[lod]]);
+ icon->setVisible(true);
+ icon->setImage(img);
+
+ if (upload_status[lod] >= 2)
+ {
+ mModelNoErrors = false;
+ }
+
+ if (lod == mPreviewLOD)
+ {
+ mFMP->childSetValue("lod_status_message_text", mFMP->getString(message));
+ icon = mFMP->getChild<LLIconCtrl>("lod_status_message_icon");
+ icon->setImage(img);
+ }
+
+ updateLodControls(lod);
+ }
+
+
+ //warn if hulls have more than 256 points in them
+ BOOL physExceededVertexLimit = FALSE;
+ for (U32 i = 0; mModelNoErrors && i < mModel[LLModel::LOD_PHYSICS].size(); ++i)
+ {
+ LLModel* mdl = mModel[LLModel::LOD_PHYSICS][i];
+
+ if (mdl)
+ {
+ for (U32 j = 0; j < mdl->mPhysics.mHull.size(); ++j)
+ {
+ if (mdl->mPhysics.mHull[j].size() > 256)
+ {
+ physExceededVertexLimit = TRUE;
+ LL_INFOS() << "Physical model " << mdl->mLabel << " exceeds vertex per hull limitations." << LL_ENDL;
+ break;
+ }
+ }
+ }
+ }
+
+ if (physExceededVertexLimit)
+ {
+ has_physics_error |= PhysicsError::TOOMANYVERTSINHULL;
+ }
+
+ if (!(has_physics_error & PhysicsError::DEGENERATE)){ // only update this field (incluides clearing it) if it is not already in use.
+ mFMP->childSetVisible("physics_status_message_text", physExceededVertexLimit);
+ LLIconCtrl* physStatusIcon = mFMP->getChild<LLIconCtrl>("physics_status_message_icon");
+ physStatusIcon->setVisible(physExceededVertexLimit);
+ if (physExceededVertexLimit)
+ {
+ mFMP->childSetValue("physics_status_message_text", mFMP->getString("phys_status_vertex_limit_exceeded"));
+ LLUIImagePtr img = LLUI::getUIImage("ModelImport_Status_Warning");
+ physStatusIcon->setImage(img);
+ }
+ }
+
+ if (getLoadState() >= LLModelLoader::ERROR_PARSING)
+ {
+ mModelNoErrors = false;
+ LL_INFOS() << "Loader returned errors, model can't be uploaded" << LL_ENDL;
+ }
+
+ bool uploadingSkin = mFMP->childGetValue("upload_skin").asBoolean();
+ bool uploadingJointPositions = mFMP->childGetValue("upload_joints").asBoolean();
+
+ if (uploadingSkin)
+ {
+ if (uploadingJointPositions && !isRigValidForJointPositionUpload())
+ {
+ mModelNoErrors = false;
+ LL_INFOS() << "Invalid rig, there might be issues with uploading Joint positions" << LL_ENDL;
+ }
+ }
+
+ if (mModelNoErrors && mModelLoader)
+ {
+ if (!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean())
+ {
+ // Some textures are still loading, prevent upload until they are done
+ mModelNoErrors = false;
+ }
+ }
+
+ if (!mModelNoErrors || mHasDegenerate)
+ {
+ mFMP->childDisable("ok_btn");
+ mFMP->childDisable("calculate_btn");
+ }
+ else
+ {
+ mFMP->childEnable("ok_btn");
+ mFMP->childEnable("calculate_btn");
+ }
+
+ if (mModelNoErrors && mLodsWithParsingError.empty())
+ {
+ mFMP->childEnable("calculate_btn");
+ }
+ else
+ {
+ mFMP->childDisable("calculate_btn");
+ }
+
+ //add up physics triangles etc
+ S32 phys_tris = 0;
+ S32 phys_hulls = 0;
+ S32 phys_points = 0;
+
+ //get the triangle count for the whole scene
+ for (LLModelLoader::scene::iterator iter = mScene[LLModel::LOD_PHYSICS].begin(), endIter = mScene[LLModel::LOD_PHYSICS].end(); iter != endIter; ++iter)
+ {
+ for (LLModelLoader::model_instance_list::iterator instance = iter->second.begin(), end_instance = iter->second.end(); instance != end_instance; ++instance)
+ {
+ LLModel* model = instance->mModel;
+ if (model)
+ {
+ S32 cur_submeshes = model->getNumVolumeFaces();
+
+ LLModel::convex_hull_decomposition& decomp = model->mPhysics.mHull;
+
+ if (!decomp.empty())
+ {
+ phys_hulls += decomp.size();
+ for (U32 i = 0; i < decomp.size(); ++i)
+ {
+ phys_points += decomp[i].size();
+ }
+ }
+ else
+ { //choose physics shape OR decomposition, can't use both
+ for (S32 j = 0; j < cur_submeshes; ++j)
+ { //for each submesh (face), add triangles and vertices to current total
+ const LLVolumeFace& face = model->getVolumeFace(j);
+ phys_tris += face.mNumIndices / 3;
+ }
+ }
+ }
+ }
+ }
+
+ if (phys_tris > 0)
+ {
+ mFMP->childSetTextArg("physics_triangles", "[TRIANGLES]", llformat("%d", phys_tris));
+ }
+ else
+ {
+ mFMP->childSetTextArg("physics_triangles", "[TRIANGLES]", mesh_status_na);
+ }
+
+ if (phys_hulls > 0)
+ {
+ mFMP->childSetTextArg("physics_hulls", "[HULLS]", llformat("%d", phys_hulls));
+ mFMP->childSetTextArg("physics_points", "[POINTS]", llformat("%d", phys_points));
+ }
+ else
+ {
+ mFMP->childSetTextArg("physics_hulls", "[HULLS]", mesh_status_na);
+ mFMP->childSetTextArg("physics_points", "[POINTS]", mesh_status_na);
+ }
+
+ LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
+ if (fmp)
+ {
+ if (phys_tris > 0 || phys_hulls > 0)
+ {
+ if (!fmp->isViewOptionEnabled("show_physics"))
+ {
+ fmp->enableViewOption("show_physics");
+ mViewOption["show_physics"] = true;
+ fmp->childSetValue("show_physics", true);
+ }
+ }
+ else
+ {
+ fmp->disableViewOption("show_physics");
+ mViewOption["show_physics"] = false;
+ fmp->childSetValue("show_physics", false);
+
+ }
+
+ //bool use_hull = fmp->childGetValue("physics_use_hull").asBoolean();
+
+ //fmp->childSetEnabled("physics_optimize", !use_hull);
+
+ bool enable = (phys_tris > 0 || phys_hulls > 0) && fmp->mCurRequest.empty();
+ //enable = enable && !use_hull && fmp->childGetValue("physics_optimize").asBoolean();
+
+ //enable/disable "analysis" UI
+ LLPanel* panel = fmp->getChild<LLPanel>("physics analysis");
+ LLView* child = panel->getFirstChild();
+ while (child)
+ {
+ child->setEnabled(enable);
+ child = panel->findNextSibling(child);
+ }
+
+ enable = phys_hulls > 0 && fmp->mCurRequest.empty();
+ //enable/disable "simplification" UI
+ panel = fmp->getChild<LLPanel>("physics simplification");
+ child = panel->getFirstChild();
+ while (child)
+ {
+ child->setEnabled(enable);
+ child = panel->findNextSibling(child);
+ }
+
+ if (fmp->mCurRequest.empty())
+ {
+ fmp->childSetVisible("Simplify", true);
+ fmp->childSetVisible("simplify_cancel", false);
+ fmp->childSetVisible("Decompose", true);
+ fmp->childSetVisible("decompose_cancel", false);
+
+ if (phys_hulls > 0)
+ {
+ fmp->childEnable("Simplify");
+ }
+
+ if (phys_tris || phys_hulls > 0)
+ {
+ fmp->childEnable("Decompose");
+ }
+ }
+ else
+ {
+ fmp->childEnable("simplify_cancel");
+ fmp->childEnable("decompose_cancel");
+ }
+ }
+
+
+ LLCtrlSelectionInterface* iface = fmp->childGetSelectionInterface("physics_lod_combo");
+ S32 which_mode = 0;
+ S32 file_mode = 1;
+ if (iface)
+ {
+ which_mode = iface->getFirstSelectedIndex();
+ file_mode = iface->getItemCount() - 1;
+ }
+
+ if (which_mode == file_mode)
+ {
+ mFMP->childEnable("physics_file");
+ mFMP->childEnable("physics_browse");
+ }
+ else
+ {
+ mFMP->childDisable("physics_file");
+ mFMP->childDisable("physics_browse");
+ }
+
+ LLSpinCtrl* crease = mFMP->getChild<LLSpinCtrl>("crease_angle");
+
+ if (mRequestedCreaseAngle[mPreviewLOD] == -1.f)
+ {
+ mFMP->childSetColor("crease_label", LLColor4::grey);
+ crease->forceSetValue(75.f);
+ }
+ else
+ {
+ mFMP->childSetColor("crease_label", LLColor4::white);
+ crease->forceSetValue(mRequestedCreaseAngle[mPreviewLOD]);
+ }
+
+ mModelUpdatedSignal(true);
+
+}
+
+void LLModelPreview::updateLodControls(S32 lod)
+{
+ if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::LOD_HIGH)
+ {
+ std::ostringstream out;
+ out << "Invalid level of detail: " << lod;
+ LL_WARNS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, false);
+ assert(lod >= LLModel::LOD_IMPOSTOR && lod <= LLModel::LOD_HIGH);
+ return;
+ }
+
+ const char* lod_controls[] =
+ {
+ "lod_mode_",
+ "lod_triangle_limit_",
+ "lod_error_threshold_"
+ };
+ const U32 num_lod_controls = sizeof(lod_controls) / sizeof(char*);
+
+ const char* file_controls[] =
+ {
+ "lod_browse_",
+ "lod_file_",
+ };
+ const U32 num_file_controls = sizeof(file_controls) / sizeof(char*);
+
+ LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
+ if (!fmp) return;
+
+ LLComboBox* lod_combo = mFMP->findChild<LLComboBox>("lod_source_" + lod_name[lod]);
+ if (!lod_combo) return;
+
+ S32 lod_mode = lod_combo->getCurrentIndex();
+ if (lod_mode == LOD_FROM_FILE) // LoD from file
+ {
+ fmp->mLODMode[lod] = 0;
+ for (U32 i = 0; i < num_file_controls; ++i)
+ {
+ mFMP->childSetVisible(file_controls[i] + lod_name[lod], true);
+ }
+
+ for (U32 i = 0; i < num_lod_controls; ++i)
+ {
+ mFMP->childSetVisible(lod_controls[i] + lod_name[lod], false);
+ }
+ }
+ else if (lod_mode == USE_LOD_ABOVE) // use LoD above
+ {
+ fmp->mLODMode[lod] = 2;
+ for (U32 i = 0; i < num_file_controls; ++i)
+ {
+ mFMP->childSetVisible(file_controls[i] + lod_name[lod], false);
+ }
+
+ for (U32 i = 0; i < num_lod_controls; ++i)
+ {
+ mFMP->childSetVisible(lod_controls[i] + lod_name[lod], false);
+ }
+
+ if (lod < LLModel::LOD_HIGH)
+ {
+ mModel[lod] = mModel[lod + 1];
+ mScene[lod] = mScene[lod + 1];
+ mVertexBuffer[lod].clear();
+
+ // Also update lower LoD
+ if (lod > LLModel::LOD_IMPOSTOR)
+ {
+ updateLodControls(lod - 1);
+ }
+ }
+ }
+ else // auto generate, the default case for all LoDs except High
+ {
+ fmp->mLODMode[lod] = 1;
+
+ //don't actually regenerate lod when refreshing UI
+ mLODFrozen = true;
+
+ for (U32 i = 0; i < num_file_controls; ++i)
+ {
+ mFMP->getChildView(file_controls[i] + lod_name[lod])->setVisible(false);
+ }
+
+ for (U32 i = 0; i < num_lod_controls; ++i)
+ {
+ mFMP->getChildView(lod_controls[i] + lod_name[lod])->setVisible(true);
+ }
+
+
+ LLSpinCtrl* threshold = mFMP->getChild<LLSpinCtrl>("lod_error_threshold_" + lod_name[lod]);
+ LLSpinCtrl* limit = mFMP->getChild<LLSpinCtrl>("lod_triangle_limit_" + lod_name[lod]);
+
+ limit->setMaxValue(mMaxTriangleLimit);
+ limit->forceSetValue(mRequestedTriangleCount[lod]);
+
+ threshold->forceSetValue(mRequestedErrorThreshold[lod]);
+
+ mFMP->getChild<LLComboBox>("lod_mode_" + lod_name[lod])->selectNthItem(mRequestedLoDMode[lod]);
+
+ if (mRequestedLoDMode[lod] == 0)
+ {
+ limit->setVisible(true);
+ threshold->setVisible(false);
+
+ limit->setMaxValue(mMaxTriangleLimit);
+ limit->setIncrement(mMaxTriangleLimit / 32);
+ }
+ else
+ {
+ limit->setVisible(false);
+ threshold->setVisible(true);
+ }
+
+ mLODFrozen = false;
+ }
+}
+
+void LLModelPreview::setPreviewTarget(F32 distance)
+{
+ mCameraDistance = distance;
+ mCameraZoom = 1.f;
+ mCameraPitch = 0.f;
+ mCameraYaw = 0.f;
+ mCameraOffset.clearVec();
+}
+
+void LLModelPreview::clearBuffers()
+{
+ for (U32 i = 0; i < 6; i++)
+ {
+ mVertexBuffer[i].clear();
+ }
+}
+
+void LLModelPreview::genBuffers(S32 lod, bool include_skin_weights)
+{
+ U32 tri_count = 0;
+ U32 vertex_count = 0;
+ U32 mesh_count = 0;
+
+
+ LLModelLoader::model_list* model = NULL;
+
+ if (lod < 0 || lod > 4)
+ {
+ model = &mBaseModel;
+ lod = 5;
+ }
+ else
+ {
+ model = &(mModel[lod]);
+ }
+
+ if (!mVertexBuffer[lod].empty())
+ {
+ mVertexBuffer[lod].clear();
+ }
+
+ mVertexBuffer[lod].clear();
+
+ LLModelLoader::model_list::iterator base_iter = mBaseModel.begin();
+
+ for (LLModelLoader::model_list::iterator iter = model->begin(); iter != model->end(); ++iter)
+ {
+ LLModel* mdl = *iter;
+ if (!mdl)
+ {
+ continue;
+ }
+
+ LLModel* base_mdl = *base_iter;
+ base_iter++;
+
+ S32 num_faces = mdl->getNumVolumeFaces();
+ for (S32 i = 0; i < num_faces; ++i)
+ {
+ const LLVolumeFace &vf = mdl->getVolumeFace(i);
+ U32 num_vertices = vf.mNumVertices;
+ U32 num_indices = vf.mNumIndices;
+
+ if (!num_vertices || !num_indices)
+ {
+ continue;
+ }
+
+ LLVertexBuffer* vb = NULL;
+
+ bool skinned = include_skin_weights && !mdl->mSkinWeights.empty();
+
+ U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;
+
+ if (skinned)
+ {
+ mask |= LLVertexBuffer::MAP_WEIGHT4;
+ }
+
+ vb = new LLVertexBuffer(mask, 0);
+
+ if (!vb->allocateBuffer(num_vertices, num_indices, TRUE))
+ {
+ // We are likely to crash due this failure, if this happens, find a way to gracefully stop preview
+ std::ostringstream out;
+ out << "Failed to allocate Vertex Buffer for model preview ";
+ out << num_vertices << " vertices and ";
+ out << num_indices << " indices";
+ LL_WARNS() << out.str() << LL_ENDL;
+ LLFloaterModelPreview::addStringToLog(out, true);
+ }
+
+ LLStrider<LLVector3> vertex_strider;
+ LLStrider<LLVector3> normal_strider;
+ LLStrider<LLVector2> tc_strider;
+ LLStrider<U16> index_strider;
+ LLStrider<LLVector4> weights_strider;
+
+ vb->getVertexStrider(vertex_strider);
+ vb->getIndexStrider(index_strider);
+
+ if (skinned)
+ {
+ vb->getWeight4Strider(weights_strider);
+ }
+
+ LLVector4a::memcpyNonAliased16((F32*)vertex_strider.get(), (F32*)vf.mPositions, num_vertices * 4 * sizeof(F32));
+
+ if (vf.mTexCoords)
+ {
+ vb->getTexCoord0Strider(tc_strider);
+ S32 tex_size = (num_vertices * 2 * sizeof(F32) + 0xF) & ~0xF;
+ LLVector4a::memcpyNonAliased16((F32*)tc_strider.get(), (F32*)vf.mTexCoords, tex_size);
+ }
+
+ if (vf.mNormals)
+ {
+ vb->getNormalStrider(normal_strider);
+ LLVector4a::memcpyNonAliased16((F32*)normal_strider.get(), (F32*)vf.mNormals, num_vertices * 4 * sizeof(F32));
+ }
+
+ if (skinned)
+ {
+ for (U32 i = 0; i < num_vertices; i++)
+ {
+ //find closest weight to vf.mVertices[i].mPosition
+ LLVector3 pos(vf.mPositions[i].getF32ptr());
+
+ const LLModel::weight_list& weight_list = base_mdl->getJointInfluences(pos);
+ llassert(weight_list.size()>0 && weight_list.size() <= 4); // LLModel::loadModel() should guarantee this
+
+ LLVector4 w(0, 0, 0, 0);
+
+ for (U32 i = 0; i < weight_list.size(); ++i)
+ {
+ F32 wght = llclamp(weight_list[i].mWeight, 0.001f, 0.999f);
+ F32 joint = (F32)weight_list[i].mJointIdx;
+ w.mV[i] = joint + wght;
+ llassert(w.mV[i] - (S32)w.mV[i]>0.0f); // because weights are non-zero, and range of wt values
+ //should not cause floating point precision issues.
+ }
+
+ *(weights_strider++) = w;
+ }
+ }
+
+ // build indices
+ for (U32 i = 0; i < num_indices; i++)
+ {
+ *(index_strider++) = vf.mIndices[i];
+ }
+
+ mVertexBuffer[lod][mdl].push_back(vb);
+
+ vertex_count += num_vertices;
+ tri_count += num_indices / 3;
+ ++mesh_count;
+
+ }
+ }
+}
+
+void LLModelPreview::update()
+{
+ if (mGenLOD)
+ {
+ bool subscribe_for_generation = mLodsQuery.empty();
+ mGenLOD = false;
+ mDirty = true;
+ mLodsQuery.clear();
+
+ for (S32 lod = LLModel::LOD_HIGH; lod >= 0; --lod)
+ {
+ // adding all lods into query for generation
+ mLodsQuery.push_back(lod);
+ }
+
+ if (subscribe_for_generation)
+ {
+ doOnIdleRepeating(lodQueryCallback);
+ }
+ }
+
+ if (mDirty && mLodsQuery.empty())
+ {
+ mDirty = false;
+ mResourceCost = calcResourceCost();
+ refresh();
+ updateStatusMessages();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// createPreviewAvatar
+//-----------------------------------------------------------------------------
+void LLModelPreview::createPreviewAvatar(void)
+{
+ mPreviewAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR);
+ if (mPreviewAvatar)
+ {
+ mPreviewAvatar->createDrawable(&gPipeline);
+ mPreviewAvatar->mSpecialRenderMode = 1;
+ mPreviewAvatar->startMotion(ANIM_AGENT_STAND);
+ mPreviewAvatar->hideSkirt();
+ }
+ else
+ {
+ LL_INFOS() << "Failed to create preview avatar for upload model window" << LL_ENDL;
+ }
+}
+
+//static
+U32 LLModelPreview::countRootModels(LLModelLoader::model_list models)
+{
+ U32 root_models = 0;
+ model_list::iterator model_iter = models.begin();
+ while (model_iter != models.end())
+ {
+ LLModel* mdl = *model_iter;
+ if (mdl && mdl->mSubmodelID == 0)
+ {
+ root_models++;
+ }
+ model_iter++;
+ }
+ return root_models;
+}
+
+void LLModelPreview::loadedCallback(
+ LLModelLoader::scene& scene,
+ LLModelLoader::model_list& model_list,
+ S32 lod,
+ void* opaque)
+{
+ LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque);
+ if (pPreview && !LLModelPreview::sIgnoreLoadedCallback)
+ {
+ // Load loader's warnings into floater's log tab
+ const LLSD out = pPreview->mModelLoader->logOut();
+ LLSD::array_const_iterator iter_out = out.beginArray();
+ LLSD::array_const_iterator end_out = out.endArray();
+ for (; iter_out != end_out; ++iter_out)
+ {
+ if (iter_out->has("Message"))
+ {
+ LLFloaterModelPreview::addStringToLog(iter_out->get("Message"), *iter_out, true, pPreview->mModelLoader->mLod);
+ }
+ }
+ pPreview->mModelLoader->clearLog();
+ pPreview->loadModelCallback(lod); // removes mModelLoader in some cases
+ if (pPreview->mLookUpLodFiles && (lod != LLModel::LOD_HIGH))
+ {
+ pPreview->lookupLODModelFiles(lod);
+ }
+ }
+
+}
+
+void LLModelPreview::lookupLODModelFiles(S32 lod)
+{
+ if (lod == LLModel::LOD_PHYSICS)
+ {
+ mLookUpLodFiles = false;
+ return;
+ }
+ S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS;
+
+ std::string lod_filename = mLODFile[LLModel::LOD_HIGH];
+ std::string ext = ".dae";
+ std::string::size_type i = lod_filename.rfind(ext);
+ if (i != std::string::npos)
+ {
+ lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext);
+ }
+ if (gDirUtilp->fileExists(lod_filename))
+ {
+ LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
+ if (fmp)
+ {
+ fmp->setCtrlLoadFromFile(next_lod);
+ }
+ loadModel(lod_filename, next_lod);
+ }
+ else
+ {
+ lookupLODModelFiles(next_lod);
+ }
+}
+
+void LLModelPreview::stateChangedCallback(U32 state, void* opaque)
+{
+ LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque);
+ if (pPreview)
+ {
+ pPreview->setLoadState(state);
+ }
+}
+
+LLJoint* LLModelPreview::lookupJointByName(const std::string& str, void* opaque)
+{
+ LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque);
+ if (pPreview)
+ {
+ return pPreview->getPreviewAvatar()->getJoint(str);
+ }
+ return NULL;
+}
+
+U32 LLModelPreview::loadTextures(LLImportMaterial& material, void* opaque)
+{
+ (void)opaque;
+
+ if (material.mDiffuseMapFilename.size())
+ {
+ material.mOpaqueData = new LLPointer< LLViewerFetchedTexture >;
+ LLPointer< LLViewerFetchedTexture >& tex = (*reinterpret_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData));
+
+ tex = LLViewerTextureManager::getFetchedTextureFromUrl("file://" + LLURI::unescape(material.mDiffuseMapFilename), FTT_LOCAL_FILE, TRUE, LLGLTexture::BOOST_PREVIEW);
+ tex->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, TRUE, FALSE, opaque, NULL, FALSE);
+ tex->forceToSaveRawImage(0, F32_MAX);
+ material.setDiffuseMap(tex->getID()); // record tex ID
+ return 1;
+ }
+
+ material.mOpaqueData = NULL;
+ return 0;
+}
+
+void LLModelPreview::addEmptyFace(LLModel* pTarget)
+{
+ U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;
+
+ LLPointer<LLVertexBuffer> buff = new LLVertexBuffer(type_mask, 0);
+
+ buff->allocateBuffer(1, 3, true);
+ memset((U8*)buff->getMappedData(), 0, buff->getSize());
+ memset((U8*)buff->getIndicesPointer(), 0, buff->getIndicesSize());
+
+ buff->validateRange(0, buff->getNumVerts() - 1, buff->getNumIndices(), 0);
+
+ LLStrider<LLVector3> pos;
+ LLStrider<LLVector3> norm;
+ LLStrider<LLVector2> tc;
+ LLStrider<U16> index;
+
+ buff->getVertexStrider(pos);
+
+ if (type_mask & LLVertexBuffer::MAP_NORMAL)
+ {
+ buff->getNormalStrider(norm);
+ }
+ if (type_mask & LLVertexBuffer::MAP_TEXCOORD0)
+ {
+ buff->getTexCoord0Strider(tc);
+ }
+
+ buff->getIndexStrider(index);
+
+ //resize face array
+ int faceCnt = pTarget->getNumVolumeFaces();
+ pTarget->setNumVolumeFaces(faceCnt + 1);
+ pTarget->setVolumeFaceData(faceCnt + 1, pos, norm, tc, index, buff->getNumVerts(), buff->getNumIndices());
+
+}
+
+//-----------------------------------------------------------------------------
+// render()
+//-----------------------------------------------------------------------------
+// Todo: we shouldn't be setting all those UI elements on render.
+// Note: Render happens each frame with skinned avatars
+BOOL LLModelPreview::render()
+{
+ assert_main_thread();
+
+ LLMutexLock lock(this);
+ mNeedsUpdate = FALSE;
+
+ bool use_shaders = LLGLSLShader::sNoFixedFunction;
+
+ bool edges = mViewOption["show_edges"];
+ bool joint_overrides = mViewOption["show_joint_overrides"];
+ bool joint_positions = mViewOption["show_joint_positions"];
+ bool skin_weight = mViewOption["show_skin_weight"];
+ bool textures = mViewOption["show_textures"];
+ bool physics = mViewOption["show_physics"];
+
+ S32 width = getWidth();
+ S32 height = getHeight();
+
+ LLGLSUIDefault def; // GL_BLEND, GL_ALPHA_TEST, GL_CULL_FACE, depth test
+ LLGLDisable no_blend(GL_BLEND);
+ LLGLEnable cull(GL_CULL_FACE);
+ LLGLDepthTest depth(GL_FALSE); // SL-12781 disable z-buffer to render background color
+ LLGLDisable fog(GL_FOG);
+
+ {
+ if (use_shaders)
+ {
+ gUIProgram.bind();
+ }
+ //clear background to grey
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.pushMatrix();
+ gGL.loadIdentity();
+ gGL.ortho(0.0f, width, 0.0f, height, -1.0f, 1.0f);
+
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.pushMatrix();
+ gGL.loadIdentity();
+
+ gGL.color4fv(PREVIEW_CANVAS_COL.mV);
+ gl_rect_2d_simple(width, height);
+
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.popMatrix();
+
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.popMatrix();
+ if (use_shaders)
+ {
+ gUIProgram.unbind();
+ }
+ }
+
+ LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
+
+ bool has_skin_weights = false;
+ bool upload_skin = mFMP->childGetValue("upload_skin").asBoolean();
+ bool upload_joints = mFMP->childGetValue("upload_joints").asBoolean();
+
+ if (upload_joints != mLastJointUpdate)
+ {
+ mLastJointUpdate = upload_joints;
+ if (fmp)
+ {
+ fmp->clearAvatarTab();
+ }
+ }
+
+ for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
+ {
+ for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
+ {
+ LLModelInstance& instance = *model_iter;
+ LLModel* model = instance.mModel;
+ model->mPelvisOffset = mPelvisZOffset;
+ if (!model->mSkinWeights.empty())
+ {
+ has_skin_weights = true;
+ }
+ }
+ }
+
+ if (has_skin_weights && lodsReady())
+ { //model has skin weights, enable view options for skin weights and joint positions
+ U32 flags = getLegacyRigFlags();
+ if (fmp)
+ {
+ if (flags == LEGACY_RIG_OK)
+ {
+ if (mFirstSkinUpdate)
+ {
+ // auto enable weight upload if weights are present
+ // (note: all these UI updates need to be somewhere that is not render)
+ mViewOption["show_skin_weight"] = true;
+ skin_weight = true;
+ fmp->childSetValue("upload_skin", true);
+ mFirstSkinUpdate = false;
+ }
+
+ fmp->enableViewOption("show_skin_weight");
+ fmp->setViewOptionEnabled("show_joint_overrides", skin_weight);
+ fmp->setViewOptionEnabled("show_joint_positions", skin_weight);
+ mFMP->childEnable("upload_skin");
+ mFMP->childSetValue("show_skin_weight", skin_weight);
+
+ }
+ else if ((flags & LEGACY_RIG_FLAG_TOO_MANY_JOINTS) > 0)
+ {
+ mFMP->childSetVisible("skin_too_many_joints", true);
+ }
+ else if ((flags & LEGACY_RIG_FLAG_UNKNOWN_JOINT) > 0)
+ {
+ mFMP->childSetVisible("skin_unknown_joint", true);
+ }
+ }
+ }
+ else
+ {
+ mFMP->childDisable("upload_skin");
+ if (fmp)
+ {
+ mViewOption["show_skin_weight"] = false;
+ fmp->disableViewOption("show_skin_weight");
+ fmp->disableViewOption("show_joint_overrides");
+ fmp->disableViewOption("show_joint_positions");
+
+ skin_weight = false;
+ mFMP->childSetValue("show_skin_weight", false);
+ fmp->setViewOptionEnabled("show_skin_weight", skin_weight);
+ }
+ }
+
+ if (upload_skin && !has_skin_weights)
+ { //can't upload skin weights if model has no skin weights
+ mFMP->childSetValue("upload_skin", false);
+ upload_skin = false;
+ }
+
+ if (!upload_skin && upload_joints)
+ { //can't upload joints if not uploading skin weights
+ mFMP->childSetValue("upload_joints", false);
+ upload_joints = false;
+ }
+
+ if (fmp)
+ {
+ if (upload_skin)
+ {
+ // will populate list of joints
+ fmp->updateAvatarTab(upload_joints);
+ }
+ else
+ {
+ fmp->clearAvatarTab();
+ }
+ }
+
+ if (upload_skin && upload_joints)
+ {
+ mFMP->childEnable("lock_scale_if_joint_position");
+ }
+ else
+ {
+ mFMP->childDisable("lock_scale_if_joint_position");
+ mFMP->childSetValue("lock_scale_if_joint_position", false);
+ }
+
+ //Only enable joint offsets if it passed the earlier critiquing
+ if (isRigValidForJointPositionUpload())
+ {
+ mFMP->childSetEnabled("upload_joints", upload_skin);
+ }
+
+ F32 explode = mFMP->childGetValue("physics_explode").asReal();
+
+ LLGLDepthTest gls_depth(GL_TRUE); // SL-12781 re-enable z-buffer for 3D model preview
+
+ LLRect preview_rect;
+
+ preview_rect = mFMP->getChildView("preview_panel")->getRect();
+
+ F32 aspect = (F32)preview_rect.getWidth() / preview_rect.getHeight();
+
+ LLViewerCamera::getInstance()->setAspect(aspect);
+
+ LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom);
+
+ LLVector3 offset = mCameraOffset;
+ LLVector3 target_pos = mPreviewTarget + offset;
+
+ F32 z_near = 0.001f;
+ F32 z_far = mCameraDistance*10.0f + mPreviewScale.magVec() + mCameraOffset.magVec();
+
+ if (skin_weight)
+ {
+ target_pos = getPreviewAvatar()->getPositionAgent() + offset;
+ z_near = 0.01f;
+ z_far = 1024.f;
+
+ //render avatar previews every frame
+ refresh();
+ }
+
+ if (use_shaders)
+ {
+ gObjectPreviewProgram.bind();
+ }
+
+ gGL.loadIdentity();
+ gPipeline.enableLightsPreview();
+
+ LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) *
+ LLQuaternion(mCameraYaw, LLVector3::z_axis);
+
+ LLQuaternion av_rot = camera_rot;
+ F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance;
+ LLViewerCamera::getInstance()->setOriginAndLookAt(
+ target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot), // camera
+ LLVector3::z_axis, // up
+ target_pos); // point of interest
+
+
+ z_near = llclamp(z_far * 0.001f, 0.001f, 0.1f);
+
+ LLViewerCamera::getInstance()->setPerspective(FALSE, mOrigin.mX, mOrigin.mY, width, height, FALSE, z_near, z_far);
+
+ stop_glerror();
+
+ gGL.pushMatrix();
+ gGL.color4fv(PREVIEW_EDGE_COL.mV);
+
+ const U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0;
+
+ LLGLEnable normalize(GL_NORMALIZE);
+
+ if (!mBaseModel.empty() && mVertexBuffer[5].empty())
+ {
+ genBuffers(-1, skin_weight);
+ //genBuffers(3);
+ //genLODs();
+ }
+
+ if (!mModel[mPreviewLOD].empty())
+ {
+ mFMP->childEnable("reset_btn");
+
+ bool regen = mVertexBuffer[mPreviewLOD].empty();
+ if (!regen)
+ {
+ const std::vector<LLPointer<LLVertexBuffer> >& vb_vec = mVertexBuffer[mPreviewLOD].begin()->second;
+ if (!vb_vec.empty())
+ {
+ const LLVertexBuffer* buff = vb_vec[0];
+ regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight;
+ }
+ else
+ {
+ LL_INFOS() << "Vertex Buffer[" << mPreviewLOD << "]" << " is EMPTY!!!" << LL_ENDL;
+ regen = TRUE;
+ }
+ }
+
+ if (regen)
+ {
+ genBuffers(mPreviewLOD, skin_weight);
+ }
+
+ if (!skin_weight)
+ {
+ for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
+ {
+ LLModelInstance& instance = *iter;
+
+ LLModel* model = instance.mLOD[mPreviewLOD];
+
+ if (!model)
+ {
+ continue;
+ }
+
+ gGL.pushMatrix();
+ LLMatrix4 mat = instance.mTransform;
+
+ gGL.multMatrix((GLfloat*)mat.mMatrix);
+
+
+ U32 num_models = mVertexBuffer[mPreviewLOD][model].size();
+ for (U32 i = 0; i < num_models; ++i)
+ {
+ LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i];
+
+ buffer->setBuffer(type_mask & buffer->getTypeMask());
+
+ if (textures)
+ {
+ int materialCnt = instance.mModel->mMaterialList.size();
+ if (i < materialCnt)
+ {
+ const std::string& binding = instance.mModel->mMaterialList[i];
+ const LLImportMaterial& material = instance.mMaterial[binding];
+
+ gGL.diffuseColor4fv(material.mDiffuseColor.mV);
+
+ // Find the tex for this material, bind it, and add it to our set
+ //
+ LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material);
+ if (tex)
+ {
+ mTextureSet.insert(tex);
+ }
+ }
+ }
+ else
+ {
+ gGL.diffuseColor4fv(PREVIEW_BASE_COL.mV);
+ }
+
+ buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0);
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV);
+ if (edges)
+ {
+ glLineWidth(PREVIEW_EDGE_WIDTH);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ glLineWidth(1.f);
+ }
+ }
+ gGL.popMatrix();
+ }
+
+ if (physics)
+ {
+ glClear(GL_DEPTH_BUFFER_BIT);
+
+ for (U32 pass = 0; pass < 2; pass++)
+ {
+ if (pass == 0)
+ { //depth only pass
+ gGL.setColorMask(false, false);
+ }
+ else
+ {
+ gGL.setColorMask(true, true);
+ }
+
+ //enable alpha blending on second pass but not first pass
+ LLGLState blend(GL_BLEND, pass);
+
+ gGL.blendFunc(LLRender::BF_SOURCE_ALPHA, LLRender::BF_ONE_MINUS_SOURCE_ALPHA);
+
+ for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
+ {
+ LLModelInstance& instance = *iter;
+
+ LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS];
+
+ if (!model)
+ {
+ continue;
+ }
+
+ gGL.pushMatrix();
+ LLMatrix4 mat = instance.mTransform;
+
+ gGL.multMatrix((GLfloat*)mat.mMatrix);
+
+
+ bool render_mesh = true;
+ LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread;
+ if (decomp)
+ {
+ LLMutexLock(decomp->mMutex);
+
+ LLModel::Decomposition& physics = model->mPhysics;
+
+ if (!physics.mHull.empty())
+ {
+ render_mesh = false;
+
+ if (physics.mMesh.empty())
+ { //build vertex buffer for physics mesh
+ gMeshRepo.buildPhysicsMesh(physics);
+ }
+
+ if (!physics.mMesh.empty())
+ { //render hull instead of mesh
+ for (U32 i = 0; i < physics.mMesh.size(); ++i)
+ {
+ if (explode > 0.f)
+ {
+ gGL.pushMatrix();
+
+ LLVector3 offset = model->mHullCenter[i] - model->mCenterOfHullCenters;
+ offset *= explode;
+
+ gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]);
+ }
+
+ static std::vector<LLColor4U> hull_colors;
+
+ if (i + 1 >= hull_colors.size())
+ {
+ hull_colors.push_back(LLColor4U(rand() % 128 + 127, rand() % 128 + 127, rand() % 128 + 127, 128));
+ }
+
+ gGL.diffuseColor4ubv(hull_colors[i].mV);
+ LLVertexBuffer::drawArrays(LLRender::TRIANGLES, physics.mMesh[i].mPositions, physics.mMesh[i].mNormals);
+
+ if (explode > 0.f)
+ {
+ gGL.popMatrix();
+ }
+ }
+ }
+ }
+ }
+
+ if (render_mesh)
+ {
+ if (mVertexBuffer[LLModel::LOD_PHYSICS].empty())
+ {
+ genBuffers(LLModel::LOD_PHYSICS, false);
+ }
+
+ U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size();
+ if (pass > 0){
+ for (U32 i = 0; i < num_models; ++i)
+ {
+ LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i];
+
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ gGL.diffuseColor4fv(PREVIEW_PSYH_FILL_COL.mV);
+
+ buffer->setBuffer(type_mask & buffer->getTypeMask());
+ buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0);
+
+ gGL.diffuseColor4fv(PREVIEW_PSYH_EDGE_COL.mV);
+ glLineWidth(PREVIEW_PSYH_EDGE_WIDTH);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0);
+
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ glLineWidth(1.f);
+ }
+ }
+ }
+ gGL.popMatrix();
+ }
+
+ // only do this if mDegenerate was set in the preceding mesh checks [Check this if the ordering ever breaks]
+ if (mHasDegenerate)
+ {
+ glLineWidth(PREVIEW_DEG_EDGE_WIDTH);
+ glPointSize(PREVIEW_DEG_POINT_SIZE);
+ gPipeline.enableLightsFullbright();
+ //show degenerate triangles
+ LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS);
+ LLGLDisable cull(GL_CULL_FACE);
+ gGL.diffuseColor4f(1.f, 0.f, 0.f, 1.f);
+ const LLVector4a scale(0.5f);
+
+ for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter)
+ {
+ LLModelInstance& instance = *iter;
+
+ LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS];
+
+ if (!model)
+ {
+ continue;
+ }
+
+ gGL.pushMatrix();
+ LLMatrix4 mat = instance.mTransform;
+
+ gGL.multMatrix((GLfloat*)mat.mMatrix);
+
+
+ LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread;
+ if (decomp)
+ {
+ LLMutexLock(decomp->mMutex);
+
+ LLModel::Decomposition& physics = model->mPhysics;
+
+ if (physics.mHull.empty())
+ {
+ if (mVertexBuffer[LLModel::LOD_PHYSICS].empty())
+ {
+ genBuffers(LLModel::LOD_PHYSICS, false);
+ }
+
+ U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size();
+ for (U32 v = 0; v < num_models; ++v)
+ {
+ LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][v];
+
+ buffer->setBuffer(type_mask & buffer->getTypeMask());
+
+ LLStrider<LLVector3> pos_strider;
+ buffer->getVertexStrider(pos_strider, 0);
+ LLVector4a* pos = (LLVector4a*)pos_strider.get();
+
+ LLStrider<U16> idx;
+ buffer->getIndexStrider(idx, 0);
+
+ for (U32 i = 0; i < buffer->getNumIndices(); i += 3)
+ {
+ LLVector4a v1; v1.setMul(pos[*idx++], scale);
+ LLVector4a v2; v2.setMul(pos[*idx++], scale);
+ LLVector4a v3; v3.setMul(pos[*idx++], scale);
+
+ if (ll_is_degenerate(v1, v2, v3))
+ {
+ buffer->draw(LLRender::LINE_LOOP, 3, i);
+ buffer->draw(LLRender::POINTS, 3, i);
+ }
+ }
+ }
+ }
+ }
+
+ gGL.popMatrix();
+ }
+ glLineWidth(1.f);
+ glPointSize(1.f);
+ gPipeline.enableLightsPreview();
+ gGL.setSceneBlendType(LLRender::BT_ALPHA);
+ }
+ }
+ }
+ }
+ else
+ {
+ target_pos = getPreviewAvatar()->getPositionAgent();
+ getPreviewAvatar()->clearAttachmentOverrides(); // removes pelvis fixup
+ LLUUID fake_mesh_id;
+ fake_mesh_id.generate();
+ getPreviewAvatar()->addPelvisFixup(mPelvisZOffset, fake_mesh_id);
+ bool pelvis_recalc = false;
+
+ LLViewerCamera::getInstance()->setOriginAndLookAt(
+ target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot), // camera
+ LLVector3::z_axis, // up
+ target_pos); // point of interest
+
+ for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
+ {
+ for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
+ {
+ LLModelInstance& instance = *model_iter;
+ LLModel* model = instance.mModel;
+
+ if (!model->mSkinWeights.empty())
+ {
+ const LLMeshSkinInfo *skin = &model->mSkinInfo;
+ LLSkinningUtil::initJointNums(&model->mSkinInfo, getPreviewAvatar());// inits skin->mJointNums if nessesary
+ U32 joint_count = LLSkinningUtil::getMeshJointCount(skin);
+ U32 bind_count = skin->mAlternateBindMatrix.size();
+
+ if (joint_overrides
+ && bind_count > 0
+ && joint_count == bind_count)
+ {
+ // mesh_id is used to determine which mesh gets to
+ // set the joint offset, in the event of a conflict. Since
+ // we don't know the mesh id yet, we can't guarantee that
+ // joint offsets will be applied with the same priority as
+ // in the uploaded model. If the file contains multiple
+ // meshes with conflicting joint offsets, preview may be
+ // incorrect.
+ LLUUID fake_mesh_id;
+ fake_mesh_id.generate();
+ for (U32 j = 0; j < joint_count; ++j)
+ {
+ LLJoint *joint = getPreviewAvatar()->getJoint(skin->mJointNums[j]);
+ if (joint)
+ {
+ const LLVector3& jointPos = skin->mAlternateBindMatrix[j].getTranslation();
+ if (joint->aboveJointPosThreshold(jointPos))
+ {
+ bool override_changed;
+ joint->addAttachmentPosOverride(jointPos, fake_mesh_id, "model", override_changed);
+
+ if (override_changed)
+ {
+ //If joint is a pelvis then handle old/new pelvis to foot values
+ if (joint->getName() == "mPelvis")// or skin->mJointNames[j]
+ {
+ pelvis_recalc = true;
+ }
+ }
+ if (skin->mLockScaleIfJointPosition)
+ {
+ // Note that unlike positions, there's no threshold check here,
+ // just a lock at the default value.
+ joint->addAttachmentScaleOverride(joint->getDefaultScale(), fake_mesh_id, "model");
+ }
+ }
+ }
+ }
+ }
+
+ for (U32 i = 0, e = mVertexBuffer[mPreviewLOD][model].size(); i < e; ++i)
+ {
+ LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i];
+
+ const LLVolumeFace& face = model->getVolumeFace(i);
+
+ LLStrider<LLVector3> position;
+ buffer->getVertexStrider(position);
+
+ LLStrider<LLVector4> weight;
+ buffer->getWeight4Strider(weight);
+
+ //quick 'n dirty software vertex skinning
+
+ //build matrix palette
+
+ LLMatrix4a mat[LL_MAX_JOINTS_PER_MESH_OBJECT];
+ LLSkinningUtil::initSkinningMatrixPalette((LLMatrix4*)mat, joint_count,
+ skin, getPreviewAvatar());
+
+ LLMatrix4a bind_shape_matrix;
+ bind_shape_matrix.loadu(skin->mBindShapeMatrix);
+ U32 max_joints = LLSkinningUtil::getMaxJointCount();
+ for (U32 j = 0; j < buffer->getNumVerts(); ++j)
+ {
+ LLMatrix4a final_mat;
+ F32 *wptr = weight[j].mV;
+ LLSkinningUtil::getPerVertexSkinMatrix(wptr, mat, true, final_mat, max_joints);
+
+ //VECTORIZE THIS
+ LLVector4a& v = face.mPositions[j];
+
+ LLVector4a t;
+ LLVector4a dst;
+ bind_shape_matrix.affineTransform(v, t);
+ final_mat.affineTransform(t, dst);
+
+ position[j][0] = dst[0];
+ position[j][1] = dst[1];
+ position[j][2] = dst[2];
+ }
+
+ llassert(model->mMaterialList.size() > i);
+ const std::string& binding = instance.mModel->mMaterialList[i];
+ const LLImportMaterial& material = instance.mMaterial[binding];
+
+ buffer->setBuffer(type_mask & buffer->getTypeMask());
+ gGL.diffuseColor4fv(material.mDiffuseColor.mV);
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+
+ // Find the tex for this material, bind it, and add it to our set
+ //
+ LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material);
+ if (tex)
+ {
+ mTextureSet.insert(tex);
+ }
+
+ buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0);
+
+ if (edges)
+ {
+ gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV);
+ glLineWidth(PREVIEW_EDGE_WIDTH);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0);
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ glLineWidth(1.f);
+ }
+ }
+ }
+ }
+ }
+
+ if (joint_positions)
+ {
+ LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
+ if (shader)
+ {
+ gDebugProgram.bind();
+ }
+ getPreviewAvatar()->renderCollisionVolumes();
+ if (fmp->mTabContainer->getCurrentPanelIndex() == fmp->mAvatarTabIndex)
+ {
+ getPreviewAvatar()->renderBones(fmp->mSelectedJointName);
+ }
+ else
+ {
+ getPreviewAvatar()->renderBones();
+ }
+ if (shader)
+ {
+ shader->bind();
+ }
+ }
+
+ if (pelvis_recalc)
+ {
+ // size/scale recalculation
+ getPreviewAvatar()->postPelvisSetRecalc();
+ }
+ }
+ }
+
+ if (use_shaders)
+ {
+ gObjectPreviewProgram.unbind();
+ }
+
+ gGL.popMatrix();
+
+ return TRUE;
+}
+
+//-----------------------------------------------------------------------------
+// refresh()
+//-----------------------------------------------------------------------------
+void LLModelPreview::refresh()
+{
+ mNeedsUpdate = TRUE;
+}
+
+//-----------------------------------------------------------------------------
+// rotate()
+//-----------------------------------------------------------------------------
+void LLModelPreview::rotate(F32 yaw_radians, F32 pitch_radians)
+{
+ mCameraYaw = mCameraYaw + yaw_radians;
+
+ mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f);
+}
+
+//-----------------------------------------------------------------------------
+// zoom()
+//-----------------------------------------------------------------------------
+void LLModelPreview::zoom(F32 zoom_amt)
+{
+ F32 new_zoom = mCameraZoom + zoom_amt;
+ // TODO: stop clamping in render
+ mCameraZoom = llclamp(new_zoom, 1.f, PREVIEW_ZOOM_LIMIT);
+}
+
+void LLModelPreview::pan(F32 right, F32 up)
+{
+ bool skin_weight = mViewOption["show_skin_weight"];
+ F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance;
+ mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * camera_distance / mCameraZoom, -1.f, 1.f);
+ mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * camera_distance / mCameraZoom, -1.f, 1.f);
+}
+
+void LLModelPreview::setPreviewLOD(S32 lod)
+{
+ lod = llclamp(lod, 0, (S32)LLModel::LOD_HIGH);
+
+ if (lod != mPreviewLOD)
+ {
+ mPreviewLOD = lod;
+
+ LLComboBox* combo_box = mFMP->getChild<LLComboBox>("preview_lod_combo");
+ combo_box->setCurrentByIndex((NUM_LOD - 1) - mPreviewLOD); // combo box list of lods is in reverse order
+ mFMP->childSetValue("lod_file_" + lod_name[mPreviewLOD], mLODFile[mPreviewLOD]);
+
+ LLColor4 highlight_color = LLUIColorTable::instance().getColor("MeshImportTableHighlightColor");
+ LLColor4 normal_color = LLUIColorTable::instance().getColor("MeshImportTableNormalColor");
+
+ for (S32 i = 0; i <= LLModel::LOD_HIGH; ++i)
+ {
+ const LLColor4& color = (i == lod) ? highlight_color : normal_color;
+
+ mFMP->childSetColor(lod_status_name[i], color);
+ mFMP->childSetColor(lod_label_name[i], color);
+ mFMP->childSetColor(lod_triangles_name[i], color);
+ mFMP->childSetColor(lod_vertices_name[i], color);
+ }
+
+ LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP;
+ if (fmp)
+ {
+ // make preview repopulate tab
+ fmp->clearAvatarTab();
+ }
+ }
+ refresh();
+ updateStatusMessages();
+}
+
+//static
+void LLModelPreview::textureLoadedCallback(
+ BOOL success,
+ LLViewerFetchedTexture *src_vi,
+ LLImageRaw* src,
+ LLImageRaw* src_aux,
+ S32 discard_level,
+ BOOL final,
+ void* userdata)
+{
+ LLModelPreview* preview = (LLModelPreview*)userdata;
+ preview->refresh();
+
+ if (final && preview->mModelLoader)
+ {
+ if (preview->mModelLoader->mNumOfFetchingTextures > 0)
+ {
+ preview->mModelLoader->mNumOfFetchingTextures--;
+ }
+ }
+}
+
+// static
+bool LLModelPreview::lodQueryCallback()
+{
+ // not the best solution, but model preview belongs to floater
+ // so it is an easy way to check that preview still exists.
+ LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
+ if (fmp && fmp->mModelPreview)
+ {
+ LLModelPreview* preview = fmp->mModelPreview;
+ if (preview->mLodsQuery.size() > 0)
+ {
+ S32 lod = preview->mLodsQuery.back();
+ preview->mLodsQuery.pop_back();
+ preview->genLODs(lod);
+
+ if (preview->mLookUpLodFiles && (lod == LLModel::LOD_HIGH))
+ {
+ preview->lookupLODModelFiles(LLModel::LOD_HIGH);
+ }
+
+ // return false to continue cycle
+ return false;
+ }
+ }
+ // nothing to process
+ return true;
+}
+
+void LLModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit)
+{
+ if (!mLODFrozen)
+ {
+ genLODs(lod, 3, enforce_tri_limit);
+ refresh();
+ }
+}
+
diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h
new file mode 100644
index 0000000000..3664a27a72
--- /dev/null
+++ b/indra/newview/llmodelpreview.h
@@ -0,0 +1,313 @@
+/**
+ * @file llmodelpreview.h
+ * @brief LLModelPreview class definition, class
+ * responsible for model preview inside LLFloaterModelPreview
+ *
+ * $LicenseInfo:firstyear=2020&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2020, 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$
+ */
+
+#ifndef LL_LLMODELPREVIEW_H
+#define LL_LLMODELPREVIEW_H
+
+#include "lldynamictexture.h"
+#include "llfloatermodelpreview.h"
+#include "llmeshrepository.h"
+#include "llmodelloader.h" //NUM_LOD
+#include "llmodel.h"
+
+class LLJoint;
+class LLVOAvatar;
+class LLTextBox;
+class LLVertexBuffer;
+class DAE;
+class daeElement;
+class domProfile_COMMON;
+class domInstance_geometry;
+class domNode;
+class domTranslate;
+class domController;
+class domSkin;
+class domMesh;
+
+// const strings needed by classes that use model preivew
+static const std::string lod_name[NUM_LOD + 1] =
+{
+ "lowest",
+ "low",
+ "medium",
+ "high",
+ "I went off the end of the lod_name array. Me so smart."
+};
+
+static const std::string lod_triangles_name[NUM_LOD + 1] =
+{
+ "lowest_triangles",
+ "low_triangles",
+ "medium_triangles",
+ "high_triangles",
+ "I went off the end of the lod_triangles_name array. Me so smart."
+};
+
+static const std::string lod_vertices_name[NUM_LOD + 1] =
+{
+ "lowest_vertices",
+ "low_vertices",
+ "medium_vertices",
+ "high_vertices",
+ "I went off the end of the lod_vertices_name array. Me so smart."
+};
+
+static const std::string lod_status_name[NUM_LOD + 1] =
+{
+ "lowest_status",
+ "low_status",
+ "medium_status",
+ "high_status",
+ "I went off the end of the lod_status_name array. Me so smart."
+};
+
+static const std::string lod_icon_name[NUM_LOD + 1] =
+{
+ "status_icon_lowest",
+ "status_icon_low",
+ "status_icon_medium",
+ "status_icon_high",
+ "I went off the end of the lod_status_name array. Me so smart."
+};
+
+static const std::string lod_status_image[NUM_LOD + 1] =
+{
+ "ModelImport_Status_Good",
+ "ModelImport_Status_Warning",
+ "ModelImport_Status_Error",
+ "I went off the end of the lod_status_image array. Me so smart."
+};
+
+static const std::string lod_label_name[NUM_LOD + 1] =
+{
+ "lowest_label",
+ "low_label",
+ "medium_label",
+ "high_label",
+ "I went off the end of the lod_label_name array. Me so smart."
+};
+
+class LLModelPreview : public LLViewerDynamicTexture, public LLMutex
+{
+ LOG_CLASS(LLModelPreview);
+
+ typedef boost::signals2::signal<void(F32 x, F32 y, F32 z, F32 streaming_cost, F32 physics_cost)> details_signal_t;
+ typedef boost::signals2::signal<void(void)> model_loaded_signal_t;
+ typedef boost::signals2::signal<void(bool)> model_updated_signal_t;
+
+public:
+
+ typedef enum
+ {
+ LOD_FROM_FILE = 0,
+ GENERATE,
+ USE_LOD_ABOVE,
+ } eLoDMode;
+
+public:
+ // Todo: model preview shouldn't need floater dependency, it
+ // should just expose data to floater, not control flaoter like it does
+ LLModelPreview(S32 width, S32 height, LLFloater* fmp);
+ virtual ~LLModelPreview();
+
+ void resetPreviewTarget();
+ void setPreviewTarget(F32 distance);
+ void setTexture(U32 name) { mTextureName = name; }
+
+ void setPhysicsFromLOD(S32 lod);
+ BOOL render();
+ void update();
+ void genBuffers(S32 lod, bool skinned);
+ void clearBuffers();
+ void refresh();
+ void rotate(F32 yaw_radians, F32 pitch_radians);
+ void zoom(F32 zoom_amt);
+ void pan(F32 right, F32 up);
+ virtual BOOL needsRender() { return mNeedsUpdate; }
+ void setPreviewLOD(S32 lod);
+ void clearModel(S32 lod);
+ void getJointAliases(JointMap& joint_map);
+ void loadModel(std::string filename, S32 lod, bool force_disable_slm = false);
+ void loadModelCallback(S32 lod);
+ bool lodsReady() { return !mGenLOD && mLodsQuery.empty(); }
+ void queryLODs() { mGenLOD = true; };
+ void genLODs(S32 which_lod = -1, U32 decimation = 3, bool enforce_tri_limit = false);
+ void generateNormals();
+ void restoreNormals();
+ U32 calcResourceCost();
+ void rebuildUploadData();
+ void saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position);
+ void saveUploadData(const std::string& filename, bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position);
+ void clearIncompatible(S32 lod);
+ void updateStatusMessages();
+ void updateLodControls(S32 lod);
+ void clearGLODGroup();
+ void onLODParamCommit(S32 lod, bool enforce_tri_limit);
+ void addEmptyFace(LLModel* pTarget);
+
+ const bool getModelPivot(void) const { return mHasPivot; }
+ void setHasPivot(bool val) { mHasPivot = val; }
+ void setModelPivot(const LLVector3& pivot) { mModelPivot = pivot; }
+
+ //Is a rig valid so that it can be used as a criteria for allowing for uploading of joint positions
+ //Accessors for joint position upload friendly rigs
+ const bool isRigValidForJointPositionUpload(void) const { return mRigValidJointUpload; }
+ void setRigValidForJointPositionUpload(bool rigValid) { mRigValidJointUpload = rigValid; }
+
+ //Accessors for the legacy rigs
+ const bool isLegacyRigValid(void) const { return mLegacyRigFlags == 0; }
+ U32 getLegacyRigFlags() const { return mLegacyRigFlags; }
+ void setLegacyRigFlags(U32 rigFlags) { mLegacyRigFlags = rigFlags; }
+
+ static void textureLoadedCallback(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata);
+ static bool lodQueryCallback();
+
+ boost::signals2::connection setDetailsCallback(const details_signal_t::slot_type& cb){ return mDetailsSignal.connect(cb); }
+ boost::signals2::connection setModelLoadedCallback(const model_loaded_signal_t::slot_type& cb){ return mModelLoadedSignal.connect(cb); }
+ boost::signals2::connection setModelUpdatedCallback(const model_updated_signal_t::slot_type& cb){ return mModelUpdatedSignal.connect(cb); }
+
+ void setLoadState(U32 state) { mLoadState = state; }
+ U32 getLoadState() { return mLoadState; }
+
+ static bool sIgnoreLoadedCallback;
+ std::vector<S32> mLodsQuery;
+ std::vector<S32> mLodsWithParsingError;
+ bool mHasDegenerate;
+
+protected:
+
+ static void loadedCallback(LLModelLoader::scene& scene, LLModelLoader::model_list& model_list, S32 lod, void* opaque);
+ static void stateChangedCallback(U32 state, void* opaque);
+
+ static LLJoint* lookupJointByName(const std::string&, void* opaque);
+ static U32 loadTextures(LLImportMaterial& material, void* opaque);
+
+ void lookupLODModelFiles(S32 lod);
+
+private:
+ //Utility function for controller vertex compare
+ bool verifyCount(int expected, int result);
+ //Creates the dummy avatar for the preview window
+ void createPreviewAvatar(void);
+ //Accessor for the dummy avatar
+ LLVOAvatar* getPreviewAvatar(void) { return mPreviewAvatar; }
+ // Count amount of original models, excluding sub-models
+ static U32 countRootModels(LLModelLoader::model_list models);
+
+protected:
+ friend class LLModelLoader;
+ friend class LLFloaterModelPreview;
+ friend class LLFloaterModelPreview::DecompRequest;
+ friend class LLPhysicsDecomp;
+
+ LLFloater* mFMP;
+
+ BOOL mNeedsUpdate;
+ bool mDirty;
+ bool mGenLOD;
+ U32 mTextureName;
+ F32 mCameraDistance;
+ F32 mCameraYaw;
+ F32 mCameraPitch;
+ F32 mCameraZoom;
+ LLVector3 mCameraOffset;
+ LLVector3 mPreviewTarget;
+ LLVector3 mPreviewScale;
+ S32 mPreviewLOD;
+ S32 mPhysicsSearchLOD;
+ U32 mResourceCost;
+ std::string mLODFile[LLModel::NUM_LODS];
+ bool mLoading;
+ U32 mLoadState;
+ bool mResetJoints;
+ bool mModelNoErrors;
+ bool mLookUpLodFiles;
+
+ std::map<std::string, bool> mViewOption;
+
+ //GLOD object parameters (must rebuild object if these change)
+ bool mLODFrozen;
+ F32 mBuildShareTolerance;
+ U32 mBuildQueueMode;
+ U32 mBuildOperator;
+ U32 mBuildBorderMode;
+ U32 mRequestedLoDMode[LLModel::NUM_LODS];
+ S32 mRequestedTriangleCount[LLModel::NUM_LODS];
+ F32 mRequestedErrorThreshold[LLModel::NUM_LODS];
+ U32 mRequestedBuildOperator[LLModel::NUM_LODS];
+ U32 mRequestedQueueMode[LLModel::NUM_LODS];
+ U32 mRequestedBorderMode[LLModel::NUM_LODS];
+ F32 mRequestedShareTolerance[LLModel::NUM_LODS];
+ F32 mRequestedCreaseAngle[LLModel::NUM_LODS];
+
+ LLModelLoader* mModelLoader;
+
+ LLModelLoader::scene mScene[LLModel::NUM_LODS];
+ LLModelLoader::scene mBaseScene;
+
+ LLModelLoader::model_list mModel[LLModel::NUM_LODS];
+ LLModelLoader::model_list mBaseModel;
+
+ typedef std::vector<LLVolumeFace> v_LLVolumeFace_t;
+ typedef std::vector<v_LLVolumeFace_t> vv_LLVolumeFace_t;
+
+ vv_LLVolumeFace_t mModelFacesCopy[LLModel::NUM_LODS];
+ vv_LLVolumeFace_t mBaseModelFacesCopy;
+
+ U32 mGroup;
+ std::map<LLPointer<LLModel>, U32> mObject;
+ U32 mMaxTriangleLimit;
+
+ LLMeshUploadThread::instance_list mUploadData;
+ std::set<LLViewerFetchedTexture * > mTextureSet;
+
+ //map of vertex buffers to models (one vertex buffer in vector per face in model
+ std::map<LLModel*, std::vector<LLPointer<LLVertexBuffer> > > mVertexBuffer[LLModel::NUM_LODS + 1];
+
+ details_signal_t mDetailsSignal;
+ model_loaded_signal_t mModelLoadedSignal;
+ model_updated_signal_t mModelUpdatedSignal;
+
+ LLVector3 mModelPivot;
+ bool mHasPivot;
+
+ float mPelvisZOffset;
+
+ bool mRigValidJointUpload;
+ U32 mLegacyRigFlags;
+
+ bool mLastJointUpdate;
+ bool mFirstSkinUpdate;
+
+ JointNameSet mJointsFromNode;
+ JointTransformMap mJointTransformMap;
+
+ LLPointer<LLVOAvatar> mPreviewAvatar;
+ LLCachedControl<bool> mImporterDebug;
+};
+
+#endif // LL_LLMODELPREVIEW_H
diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp
index cd48b1e8e7..d43442d69d 100644
--- a/indra/newview/llviewermenufile.cpp
+++ b/indra/newview/llviewermenufile.cpp
@@ -552,7 +552,7 @@ class LLFileUploadModel : public view_listener_t
LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) LLFloaterReg::getInstance("upload_model");
if (fmp && !fmp->isModelLoading())
{
- fmp->loadModel(3);
+ fmp->loadHighLodModel();
}
return TRUE;
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index cbadf4bcda..4e5f868c48 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -1586,13 +1586,16 @@ void LLVOAvatar::renderCollisionVolumes()
}
}
-void LLVOAvatar::renderBones()
+void LLVOAvatar::renderBones(const std::string &selected_joint)
{
LLGLEnable blend(GL_BLEND);
avatar_joint_list_t::iterator iter = mSkeleton.begin();
- avatar_joint_list_t::iterator end = mSkeleton.end();
+ avatar_joint_list_t::iterator end = mSkeleton.end();
+ // For selected joints
+ static LLVector3 SELECTED_COLOR_OCCLUDED(1.0f, 1.0f, 0.0f);
+ static LLVector3 SELECTED_COLOR_VISIBLE(0.5f, 0.5f, 0.5f);
// For bones with position overrides defined
static LLVector3 OVERRIDE_COLOR_OCCLUDED(1.0f, 0.0f, 0.0f);
static LLVector3 OVERRIDE_COLOR_VISIBLE(0.5f, 0.5f, 0.5f);
@@ -1619,7 +1622,18 @@ void LLVOAvatar::renderBones()
LLVector3 pos;
LLUUID mesh_id;
- if (jointp->hasAttachmentPosOverride(pos,mesh_id))
+ F32 sphere_scale = SPHERE_SCALEF;
+
+ // We are in render, so it is preferable to implement selection
+ // in a different way, but since this is for debug/preview, this
+ // is low priority
+ if (jointp->getName() == selected_joint)
+ {
+ sphere_scale *= 16;
+ occ_color = SELECTED_COLOR_OCCLUDED;
+ visible_color = SELECTED_COLOR_VISIBLE;
+ }
+ else if (jointp->hasAttachmentPosOverride(pos,mesh_id))
{
occ_color = OVERRIDE_COLOR_OCCLUDED;
visible_color = OVERRIDE_COLOR_VISIBLE;
@@ -1640,7 +1654,6 @@ void LLVOAvatar::renderBones()
LLVector3 begin_pos(0,0,0);
LLVector3 end_pos(jointp->getEnd());
- F32 sphere_scale = SPHERE_SCALEF;
gGL.pushMatrix();
gGL.multMatrix( &jointp->getXform()->getWorldMatrix().mMatrix[0][0] );
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index 8d23d955e3..8f9854fad3 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -477,7 +477,7 @@ public:
F32 getLastSkinTime() { return mLastSkinTime; }
U32 renderTransparent(BOOL first_pass);
void renderCollisionVolumes();
- void renderBones();
+ void renderBones(const std::string &selected_joint = std::string());
void renderJoints();
static void deleteCachedImages(bool clearAll=true);
static void destroyGL();
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 05f9f7800d..d850f3ced1 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -6563,7 +6563,7 @@ void LLPipeline::enableLightsPreview()
light->enable();
light->setPosition(light_pos);
light->setDiffuse(diffuse0);
- light->setAmbient(LLColor4::black);
+ light->setAmbient(ambient);
light->setSpecular(specular0);
light->setSpotExponent(0.f);
light->setSpotCutoff(180.f);
@@ -6574,7 +6574,7 @@ void LLPipeline::enableLightsPreview()
light->enable();
light->setPosition(light_pos);
light->setDiffuse(diffuse1);
- light->setAmbient(LLColor4::black);
+ light->setAmbient(ambient);
light->setSpecular(specular1);
light->setSpotExponent(0.f);
light->setSpotCutoff(180.f);
@@ -6584,7 +6584,7 @@ void LLPipeline::enableLightsPreview()
light->enable();
light->setPosition(light_pos);
light->setDiffuse(diffuse2);
- light->setAmbient(LLColor4::black);
+ light->setAmbient(ambient);
light->setSpecular(specular2);
light->setSpotExponent(0.f);
light->setSpotCutoff(180.f);
diff --git a/indra/newview/skins/default/textures/containers/TabTop_Right_Flashing.png b/indra/newview/skins/default/textures/containers/TabTop_Right_Flashing.png
new file mode 100644
index 0000000000..fd13bb699d
--- /dev/null
+++ b/indra/newview/skins/default/textures/containers/TabTop_Right_Flashing.png
Binary files differ
diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml
index 7325d836d2..a875c4e848 100644
--- a/indra/newview/skins/default/textures/textures.xml
+++ b/indra/newview/skins/default/textures/textures.xml
@@ -634,6 +634,7 @@ with the same filename but different name
<texture name="TabTop_Right_Off" file_name="containers/TabTop_Right_Off.png" preload="false" scale.left="8" scale.top="8" scale.right="62" scale.bottom="9" />
<texture name="TabTop_Right_Selected" file_name="containers/TabTop_Right_Selected.png" preload="false" scale.left="8" scale.top="8" scale.right="62" scale.bottom="9" />
+ <texture name="TabTop_Right_Flashing" file_name="containers/TabTop_Right_Flashing.png" preload="false" scale.left="8" scale.top="8" scale.right="62" scale.bottom="9" />
<texture name="TabTop_Middle_Off" file_name="containers/TabTop_Middle_Off.png" preload="false" scale.left="8" scale.top="8" scale.right="120" scale.bottom="9" />
<texture name="TabTop_Middle_Selected" file_name="containers/TabTop_Middle_Selected.png" preload="false" scale.left="8" scale.top="8" scale.right="96" scale.bottom="9" />
<texture name="TabTop_Left_Off" file_name="containers/TabTop_Left_Off.png" preload="false" scale.left="8" scale.top="8" scale.right="120" scale.bottom="9" />
diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml
index 5a86eb06fb..02a21764ce 100644
--- a/indra/newview/skins/default/xui/en/floater_model_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml
@@ -2,15 +2,16 @@
<floater
can_close="true"
can_drag_on_left="false"
- can_minimize="false"
- can_resize="false"
- height="480"
- min_height="480"
+ can_minimize="true"
+ can_resize="true"
+ height="625"
+ min_height="625"
width="980"
min_width="980"
name="Model Preview"
title="UPLOAD MODEL"
- help_topic="upload_model" >
+ help_topic="upload_model"
+ legacy_header_height="25">
<string name="status_idle"></string>
<string name="status_parse_error">Error: Dae parsing issue - see log for details.</string>
@@ -33,19 +34,27 @@
<string name="mesh_status_missing_lod">Missing required level of detail.</string>
<string name="mesh_status_invalid_material_list">LOD materials are not a subset of reference model.</string>
<string name="phys_status_vertex_limit_exceeded">Some physical hulls exceed vertex limitations.</string>
+ <string name="phys_status_degenerate_triangles">The physics mesh too dense remove the small thin triangles (see preview)</string>
<string name="layer_all">All</string> <!-- Text to display in physics layer combo box for "all layers" -->
<string name="decomposing">Analyzing...</string>
<string name="simplifying">Simplifying...</string>
<string name="tbd">TBD</string>
+
+ <!-- Warnings and info from model loader-->
+ <string name="TooManyJoint">Skinning disabled due to too many joints: [JOINTS], maximum: [MAX]</string>
+ <string name="UnrecognizedJoint">Rigged to unrecognized joint name [NAME]</string>
+ <string name="UnknownJoints">Skinning disabled due to [COUNT] unknown joints</string>
+ <string name="ModelLoaded">Model [MODEL_NAME] loaded</string>
+ <string name="IncompleteTC">Texture coordinates data is not complete.</string>
-<panel
- follows="top|left"
- height="455"
- layout="topleft"
- left="3"
- name="left_panel"
- top_pad="10"
- width="630">
+ <panel
+ follows="top|left"
+ height="595"
+ layout="topleft"
+ left="3"
+ name="left_panel"
+ top_pad="25"
+ width="635">
<panel
follows="all"
height="50"
@@ -76,12 +85,16 @@
</panel>
<tab_container
follows="top|left"
- top_pad="15"
+ top_pad="10"
left="0"
- height="300"
+ height="330"
width="635"
name="import_tab"
- tab_position="top">
+ tab_position="top"
+ enable_tabs_flashing="true"
+ tabs_flashing_color="MenuItemFlashBgColor">
+ <last_tab
+ tab_top_image_flash="TabTop_Right_Flashing"/> <!-- for log tab -->
<!-- LOD PANEL -->
<panel
help_topic="upload_model_lod"
@@ -92,12 +105,12 @@
<view_border
bevel_style="none"
follows="top|left"
- height="275"
+ height="306"
layout="topleft"
left="3"
name="lod_tab_border"
top_pad="0"
- width="629" />
+ width="628" />
<text
follows="left|top"
height="18"
@@ -688,7 +701,7 @@
left="10"
name="lod_tab_border"
top_pad="20"
- width="605" />
+ width="614" />
<check_box
follows="top|left"
height="15"
@@ -730,12 +743,12 @@
<view_border
bevel_style="none"
follows="top|left"
- height="275"
+ height="306"
layout="topleft"
left="3"
name="physics_tab_border"
top_pad="0"
- width="619"/>
+ width="628"/>
<panel
bg_alpha_color="0 0 0 0"
bg_opaque_color="0 0 0 0.3"
@@ -755,8 +768,9 @@
name="first_step_name"
text_color="White"
top_pad="0"
- width="210">
- Step 1: Level of Detail
+ width="210"
+ valign="center">
+ Step 1: Pick a physics model :
</text>
<combo_box
follows="left|top"
@@ -798,7 +812,7 @@
layout="topleft"
left="18"
name="physics_tab_border"
- top_pad="15"
+ top_pad="10"
width="589"/>
<panel
bg_alpha_color="0 0 0 0"
@@ -807,7 +821,7 @@
follows="top|left"
left="18"
name="physics analysis"
- top_pad="15"
+ top_pad="10"
visible="true"
width="589">
<text
@@ -819,7 +833,7 @@
name="method_label"
text_color="White"
top_pad="0">
- Step 2: Analyze
+ Step 2: Convert to hulls (optional)
</text>
<text
follows="top|left"
@@ -905,7 +919,7 @@
layout="topleft"
left="18"
name="physics_tab_border"
- top_pad="15"
+ top_pad="10"
width="589"/>
<panel
bg_alpha_color="0 0 0 0"
@@ -914,7 +928,7 @@
height="66"
left="18"
name="physics simplification"
- top_pad="15"
+ top_pad="10"
width="589">
<text
text_color="White"
@@ -1013,7 +1027,7 @@
layout="topleft"
left="18"
name="physics_tab_border"
- top_pad="15"
+ top_pad="10"
width="589"/>
<panel
bg_alpha_color="0 0 0 0"
@@ -1075,10 +1089,9 @@
follows="left|top"
height="19"
layout="topleft"
- left_pad="5"
- top_delta="0"
+ top_pad="5"
name="physics message"
- width="270">
+ width="589">
<icon
follows="left|top"
height="16"
@@ -1093,7 +1106,7 @@
layout="topleft"
left_pad="2"
name="physics_status_message_text"
- width="252"
+ width="573"
top_delta="3"/>
</panel>
</panel>
@@ -1105,12 +1118,12 @@
<view_border
bevel_style="none"
follows="top|left"
- height="275"
+ height="306"
layout="topleft"
left="3"
name="border"
top_pad="0"
- width="619"/>
+ width="628"/>
<text
follows="top|left"
height="16"
@@ -1157,75 +1170,211 @@
label_text.text_color="White"
left="20"
top_pad="20"/>
- <view_border
- bevel_style="none"
- follows="top|left"
- height="0"
- layout="topleft"
- name="border"
- top_pad="20"
- width="579"/>
- <text
- follows="top|left"
- height="15"
- left="20"
- name="include_label"
- text_color="White"
- top_pad="20"
- width="150">
- For avatar models only:
- </text>
- <check_box
- follows="top|left"
- height="15"
- label="Include skin weight"
- label_text.text_color="White"
- name="upload_skin"
- top_pad="15"/>
- <check_box
- follows="top|left"
- height="15"
- label="Include joint positions"
- label_text.text_color="White"
- name="upload_joints"
- top_pad="15"/>
- <check_box
- follows="top|left"
- height="15"
- label="Lock scale if joint position defined"
- label_text.text_color="White"
- name="lock_scale_if_joint_position"
- top_pad="15"/>
- <text
- follows="top|left"
- height="15"
- layout="topleft"
- left="220"
- name="pelvis_offset_label"
- text_color="White"
- top="134"
- width="200">
- Z offset (raise or lower avatar):
- </text>
- <spinner
- follows="top|left"
- height="20"
- min_val="-3.00"
- max_val="3.0"
- name="pelvis_offset"
- top_pad="10"
- value="0.0"
- width="80"/>
</panel>
+ <panel
+ label="Overrides"
+ layout="topleft"
+ name="rigging_panel"
+ title="Rigging">
+ <view_border
+ bevel_style="none"
+ follows="top|left"
+ height="306"
+ layout="topleft"
+ left="3"
+ name="avatar_tab_border"
+ top_pad="0"
+ width="628" />
+ <check_box
+ follows="top|left"
+ height="15"
+ label="Include skin weight"
+ label_text.text_color="White"
+ name="upload_skin"
+ top="8"
+ left="20"/>
+ <check_box
+ follows="top|left"
+ height="15"
+ label="Include joint positions"
+ label_text.text_color="White"
+ name="upload_joints"
+ left_delta="0"
+ top_pad="7"/>
+ <check_box
+ follows="top|left"
+ height="15"
+ label="Lock scale if joint position defined"
+ label_text.text_color="White"
+ name="lock_scale_if_joint_position"
+ top_pad="7"/>
+ <text
+ follows="top|left"
+ height="15"
+ layout="topleft"
+ left="220"
+ name="pelvis_offset_label"
+ text_color="White"
+ top="8"
+ width="200">
+ Z offset (raise or lower avatar):
+ </text>
+ <spinner
+ follows="top|left"
+ height="20"
+ min_val="-3.00"
+ max_val="3.0"
+ name="pelvis_offset"
+ top_pad="10"
+ value="0.0"
+ width="80"/>
+ <text
+ follows="top|left"
+ height="17"
+ left="425"
+ name="skin_too_many_joints"
+ text_color="Orange"
+ top="7"
+ width="195"
+ word_wrap="true">
+ Too many skinned joints
+ </text>
+ <text
+ follows="top|left"
+ height="32"
+ left="425"
+ name="skin_unknown_joint"
+ text_color="Orange"
+ top="8"
+ width="195"
+ word_wrap="true">
+ Model has an unknown joint(s)
+ </text>
+ <text
+ layout="topleft"
+ follows="top|left"
+ height="15"
+ left="20"
+ name="joints_descr"
+ top="73"
+ width="150">
+ Joints:
+ </text>
+ <scroll_list
+ layout="topleft"
+ follows="top|left"
+ name="joints_list"
+ column_padding="0"
+ draw_heading="false"
+ draw_stripes="false"
+ commit_on_selection_change="true"
+ heading_height="23"
+ height="199"
+ left_delta="0"
+ top_pad="0"
+ width="200"/>
+ <text
+ layout="topleft"
+ follows="top|left"
+ height="15"
+ left_delta="0"
+ name="conflicts_description"
+ top_pad="2"
+ width="200">
+ [CONFLICTS] conflicts in [JOINTS_COUNT] joints
+ </text>
+ <text
+ layout="topleft"
+ follows="top|left"
+ height="15"
+ left_pad="5"
+ name="pos_overrides_descr"
+ top="73"
+ width="300">
+ Position overrides for joint '[JOINT]':
+ </text>
+ <scroll_list
+ layout="topleft"
+ follows="top|left"
+ name="pos_overrides_list"
+ column_padding="0"
+ draw_heading="true"
+ draw_stripes="false"
+ heading_height="23"
+ height="100"
+ left_delta="0"
+ top_pad="0"
+ width="385">
+ <scroll_list.columns
+ label="Model"
+ name="model_name"
+ relative_width="0.49" />
+ <scroll_list.columns
+ label="X"
+ name="axis_x"
+ relative_width="0.17" />
+ <scroll_list.columns
+ label="Y"
+ name="axis_y"
+ relative_width="0.17" />
+ <scroll_list.columns
+ label="Z"
+ name="axis_z"
+ relative_width="0.17" />
+ </scroll_list>
+ </panel>
+ <panel
+ label="Log"
+ layout="topleft"
+ name="logs_panel"
+ title="Log">
+ <view_border
+ bevel_style="none"
+ follows="top|left"
+ height="289"
+ layout="topleft"
+ left="3"
+ name="log_tab_border"
+ top_pad="0"
+ width="628" />
+ <text_editor
+ type="string"
+ length="1"
+ embedded_items="false"
+ follows="top|left"
+ font="SansSerif"
+ ignore_tab="false"
+ layout="topleft"
+ height="289"
+ left="4"
+ top="0"
+ right="-1"
+ max_length="65536"
+ name="log_text"
+ parse_urls="true"
+ spellcheck="false"
+ read_only="true"
+ word_wrap="true">
+ </text_editor>
+ <check_box
+ control_name="ImporterDebug"
+ follows="top|left"
+ top_pad="9"
+ left="6"
+ width="70"
+ label="Enable detailed logging"
+ name="verbose_logging"/>
+ </panel>
</tab_container>
<panel
- follows="top|left"
- height="80"
- layout="top|left"
- left="0"
+ follows="top|left|bottom"
+ layout="topleft"
+ height="195"
+ left="4"
+ border="true"
name="weights_and_warning_panel"
top_pad="3"
- width="625">
+ width="629">
<button
follows="top|left"
label="Calculate weights &amp; fee"
@@ -1265,10 +1414,10 @@
label_color="White"
layout="topleft"
name="reset_btn"
- right="-2"
+ right="-5"
top="3"
height="20"
- width="275"/>
+ width="265"/>
<!-- ========== WEIGHTS ==========-->
<text
follows="top|left"
@@ -1287,7 +1436,7 @@
left_pad="0"
name="prim_weight"
top_delta="0"
- width="120"
+ width="130"
word_wrap="true">
Land impact: [EQ]
</text>
@@ -1297,7 +1446,7 @@
left_pad="0"
name="download_weight"
top_delta="0"
- width="100"
+ width="130"
word_wrap="true">
Download: [ST]
</text>
@@ -1307,7 +1456,7 @@
layout="topleft"
left_pad="0"
name="physics_weight"
- width="90"
+ width="130"
word_wrap="true">
Physics: [PH]
</text>
@@ -1317,19 +1466,150 @@
layout="topleft"
left_pad="0"
name="server_weight"
- width="83"
+ width="130"
word_wrap="true">
Server: [SIM]
</text>
- <!-- ========== NOTE MESSAGE ========== -->
+ <!-- =========== Cost breakdown ======== -->
+ <panel
+ border="true"
+ top_pad="5"
+ layout="topleft"
+ left="6"
+ name="price_breakdown_panel"
+ width="120"
+ height="100">
+ <text
+ layout="topleft"
+ left="3">
+ Price Breakdown
+ </text>
+ <view_border
+ bevel_style="none"
+ follows="top|left"
+ height="0"
+ layout="topleft"
+ left="3"
+ name="price_breakdown_border"
+ top_pad="5"
+ width="110"/>
+ <text
+ height="80"
+ top_pad="5"
+ layout="topleft"
+ left="3"
+ name="price_breakdown_labels"
+ width="70"
+ word_wrap="false">
+Download:
+Physics:
+Instances:
+Textures:
+Model:
+ </text>
+ <text
+ height="80"
+ top_delta="0"
+ layout="topleft"
+ halign="right"
+ left_pad="0"
+ name="price_breakdown"
+ width="40"
+ word_wrap="false">
+[STREAMING]
+[PHYSICS]
+[INSTANCES]
+[TEXTURES]
+[MODEL]
+ </text>
+ </panel>
+ <!--
+ Streaming breakdown numbers are available but not fully understood
+ uncommenting the following sections will display the numbers for debugging purposes
+ <text
+ height="80"
+ top_delta="0"
+ layout="topleft"
+ left="130"
+ name="streaming_breakdown_labels"
+ width="65"
+ word_wrap="true">
+Streaming/Download:
+High:
+Medium:
+Low:
+Lowest:
+ </text>
<text
+ height="80"
+ top_delta="0"
+ layout="topleft"
+ left_pad="0"
+ name="streaming_breakdown"
+ width="95"
+ word_wrap="true">
+[STR_TOTAL]
+[STR_HIGH]
+[STR_MED]
+[STR_LOW]
+[STR_LOWEST]
+ </text>-->
+ <panel
+ border="true"
+ layout="topleft"
+ left_pad="265"
+ name="physics_costs_panel"
+ width="120"
+ height="100">
+ <text
+ layout="topleft"
+ left="3">
+ Physics Costs
+ </text>
+ <view_border
+ bevel_style="none"
+ follows="top|left"
+ height="0"
+ layout="topleft"
+ left="3"
+ name="price_breakdown_border"
+ top_pad="5"
+ width="110"/>
+ <text
+ height="80"
+ top_pad="5"
+ layout="topleft"
+ left="5"
+ name="physics_breakdown_labels"
+ width="65">
+Base Hull:
+Mesh:
+Analysed:
+ </text>
+ <text
+ height="80"
+ top_delta="0"
+ layout="topleft"
+ left_pad="0"
+ name="physics_breakdown"
+ width="40"
+ halign="right"
+ word_wrap="false"
+ visible="true">
+[PCH]
+[PM]
+[PHU]
+ </text>-->
+ </panel>
+ <!-- ========== NOTE MESSAGE ========== -->
+ <text
font="SansSerif"
layout="topleft"
left="6"
name="warning_title"
- top_pad="10"
+ top_pad="5"
text_color="DrYellow"
- visible="false"
+ visible="true"
width="40">
NOTE:
</text>
@@ -1340,44 +1620,51 @@
left_pad="1"
name="warning_message"
parse_urls="true"
- top_delta="2"
+ top_delta="1"
wrap="true"
width="462"
- visible="false">
+ visible="true">
You dont have rights to upload mesh models. [[VURL] Find out how] to get certified.
+ </text>
+ <text
+ text_color="Yellow"
+ layout="topleft"
+ top_pad="-2"
+ left="6"
+ name="status">
+[STATUS]
</text>
- <text text_color="Yellow" layout="topleft" top_delta="20" left="6" name="status">[STATUS]</text>
-
</panel>
-</panel>
-
-<text
- follows="left|top"
- layout="topleft"
- left="640"
- name="lod_label"
- text_color="White"
- top="13"
- height="15"
- width="290">
- Preview:
- </text>
-<panel
- border="true"
- bevel_style="none"
- follows="top|left"
- name="preview_panel"
- top_pad="4"
- width="290"
- height="290"/>
-
-<panel
- follows="all"
- height="130"
- layout="topleft"
- name="right_panel"
- top_pad="5"
- width="340">
+ </panel>
+
+ <text
+ follows="left|top"
+ layout="topleft"
+ left="640"
+ name="lod_label"
+ text_color="White"
+ top="29"
+ height="15"
+ width="290">
+ Preview:
+ </text>
+ <panel
+ follows="all"
+ layout="topleft"
+ border="true"
+ bevel_style="none"
+ name="preview_panel"
+ top_pad="4"
+ width="325"
+ height="408"/>
+ <panel
+ follows="right|bottom"
+ can_resize="false"
+ height="140"
+ layout="topleft"
+ name="right_panel"
+ top_pad="5"
+ width="340">
<combo_box
top_pad="3"
follows="left|top"
@@ -1386,10 +1673,10 @@
name="preview_lod_combo"
width="150"
tool_tip="LOD to view in preview render">
- <combo_item name="high"> High </combo_item>
- <combo_item name="medium"> Medium </combo_item>
- <combo_item name="low"> Low </combo_item>
- <combo_item name="lowest"> Lowest </combo_item>
+ <combo_item name="high"> High </combo_item>
+ <combo_item name="medium"> Medium </combo_item>
+ <combo_item name="low"> Low </combo_item>
+ <combo_item name="lowest"> Lowest </combo_item>
</combo_box>
<text
follows="top|left"
@@ -1436,11 +1723,21 @@
</check_box>
<check_box
follows="top|left"
+ label="Joint position overrides"
+ label_text.text_color="White"
+ word_wrap="down"
+ width="130"
+ layout="topleft"
+ name="show_joint_overrides"
+ top_pad="8">
+ </check_box>
+ <check_box
+ follows="top|left"
label="Joints"
label_text.text_color="White"
layout="topleft"
name="show_joint_positions"
- top_pad="8">
+ top_pad="17">
</check_box>
<text
follows="top|left"
@@ -1460,5 +1757,5 @@
max_val="3.0"
height="20"
width="150"/>
-</panel>
+ </panel>
</floater>
diff --git a/indra/newview/skins/default/xui/en/floater_script_debug.xml b/indra/newview/skins/default/xui/en/floater_script_debug.xml
index cd88048d6b..6c49cfa1a8 100644
--- a/indra/newview/skins/default/xui/en/floater_script_debug.xml
+++ b/indra/newview/skins/default/xui/en/floater_script_debug.xml
@@ -17,5 +17,6 @@
name="Preview Tabs"
tab_position="bottom"
top="16"
- width="448" />
+ width="448"
+ enable_tabs_flashing="true"/>
</multi_floater>