diff options
Diffstat (limited to 'indra')
-rw-r--r-- | indra/llprimitive/llmodel.cpp | 90 | ||||
-rw-r--r-- | indra/llprimitive/llmodel.h | 6 | ||||
-rw-r--r-- | indra/newview/featuretable.txt | 3 | ||||
-rw-r--r-- | indra/newview/featuretable_xp.txt | 3 | ||||
-rw-r--r-- | indra/newview/llfloatermodelpreview.cpp | 20 | ||||
-rw-r--r-- | indra/newview/llfloatertools.cpp | 11 | ||||
-rw-r--r-- | indra/newview/llmeshrepository.cpp | 541 | ||||
-rw-r--r-- | indra/newview/llmeshrepository.h | 26 | ||||
-rw-r--r-- | indra/newview/llselectmgr.cpp | 65 | ||||
-rw-r--r-- | indra/newview/llselectmgr.h | 3 | ||||
-rw-r--r-- | indra/newview/llspatialpartition.cpp | 39 | ||||
-rw-r--r-- | indra/newview/llviewerobject.cpp | 54 | ||||
-rw-r--r-- | indra/newview/llviewerobject.h | 13 | ||||
-rw-r--r-- | indra/newview/llviewerobjectlist.cpp | 9 | ||||
-rw-r--r-- | indra/newview/llviewerobjectlist.h | 2 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/floater_tools.xml | 6 |
16 files changed, 699 insertions, 192 deletions
diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 1cada567e9..d6369b3387 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -1290,9 +1290,16 @@ LLModel* LLModel::loadModelFromDomMesh(domMesh *mesh) //static LLSD LLModel::writeModel(std::string filename, LLModel* physics, LLModel* high, LLModel* medium, LLModel* low, LLModel* impostor, LLModel::physics_shape& decomp, bool upload_skin, bool upload_joints, bool nowrite) { + LLModel::hull dummy_hull; + return writeModel(filename, physics, high, medium, low, impostor, decomp, dummy_hull, upload_skin, upload_joints, nowrite); +} + +//static +LLSD LLModel::writeModel(std::string filename, LLModel* physics, LLModel* high, LLModel* medium, LLModel* low, LLModel* impostor, LLModel::physics_shape& decomp, LLModel::hull& base_hull, bool upload_skin, bool upload_joints, bool nowrite) +{ std::ofstream os(filename.c_str(), std::ofstream::out | std::ofstream::binary); - LLSD header = writeModel(os, physics, high, medium, low, impostor, decomp, upload_skin, upload_joints, nowrite); + LLSD header = writeModel(os, physics, high, medium, low, impostor, decomp, base_hull, upload_skin, upload_joints, nowrite); os.close(); @@ -1300,7 +1307,7 @@ LLSD LLModel::writeModel(std::string filename, LLModel* physics, LLModel* high, } //static -LLSD LLModel::writeModel(std::ostream& ostr, LLModel* physics, LLModel* high, LLModel* medium, LLModel* low, LLModel* impostor, LLModel::physics_shape& decomp, bool upload_skin, bool upload_joints, bool nowrite) +LLSD LLModel::writeModel(std::ostream& ostr, LLModel* physics, LLModel* high, LLModel* medium, LLModel* low, LLModel* impostor, LLModel::physics_shape& decomp, LLModel::hull& base_hull, bool upload_skin, bool upload_joints, bool nowrite) { LLSD mdl; @@ -1359,15 +1366,27 @@ LLSD LLModel::writeModel(std::ostream& ostr, LLModel* physics, LLModel* high, LL } - if (!decomp.empty()) + if (!decomp.empty() || !base_hull.empty()) { //write decomposition block // ["decomposition"]["HullList"] -- list of 8 bit integers, each entry represents a hull with specified number of points // ["decomposition"]["PositionDomain"]["Min"/"Max"] // ["decomposition"]["Position"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points - + // ["decomposition"]["Hull"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points representing a single hull approximation of given shape + + //get minimum and maximum - LLVector3 min = decomp[0][0]; + LLVector3 min; + + if (decomp.empty()) + { + min = base_hull[0]; + } + else + { + min = decomp[0][0]; + } + LLVector3 max = min; LLSD::Binary hulls(decomp.size()); @@ -1386,23 +1405,64 @@ LLSD LLModel::writeModel(std::ostream& ostr, LLModel* physics, LLModel* high, LL } } + for (U32 i = 0; i < base_hull.size(); ++i) + { + update_min_max(min, max, base_hull[i]); + } + mdl["decomposition"]["Min"] = min.getValue(); mdl["decomposition"]["Max"] = max.getValue(); - mdl["decomposition"]["HullList"] = hulls; - - LLSD::Binary p(total*3*2); - LLVector3 range = max-min; + if (!hulls.empty()) + { + mdl["decomposition"]["HullList"] = hulls; + } - U32 vert_idx = 0; - for (U32 i = 0; i < decomp.size(); ++i) + if (total > 0) { - for (U32 j = 0; j < decomp[i].size(); ++j) + LLSD::Binary p(total*3*2); + + LLVector3 range = max-min; + + U32 vert_idx = 0; + for (U32 i = 0; i < decomp.size(); ++i) + { + for (U32 j = 0; j < decomp[i].size(); ++j) + { + for (U32 k = 0; k < 3; k++) + { + //convert to 16-bit normalized across domain + U16 val = (U16) (((decomp[i][j].mV[k]-min.mV[k])/range.mV[k])*65535); + + U8* buff = (U8*) &val; + //write to binary buffer + p[vert_idx++] = buff[0]; + p[vert_idx++] = buff[1]; + + if (vert_idx > p.size()) + { + llerrs << "WTF?" << llendl; + } + } + } + } + + mdl["decomposition"]["Position"] = p; + } + + if (!base_hull.empty()) + { + LLSD::Binary p(base_hull.size()*3*2); + + LLVector3 range = max-min; + + U32 vert_idx = 0; + for (U32 j = 0; j < base_hull.size(); ++j) { for (U32 k = 0; k < 3; k++) { //convert to 16-bit normalized across domain - U16 val = (U16) (((decomp[i][j].mV[k]-min.mV[k])/range.mV[k])*65535); + U16 val = (U16) (((base_hull[j].mV[k]-min.mV[k])/range.mV[k])*65535); U8* buff = (U8*) &val; //write to binary buffer @@ -1415,9 +1475,9 @@ LLSD LLModel::writeModel(std::ostream& ostr, LLModel* physics, LLModel* high, LL } } } + + mdl["decomposition"]["Hull"] = p; } - - mdl["decomposition"]["Position"] = p; } for (U32 idx = 0; idx < MODEL_NAMES_LENGTH; ++idx) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index ec21ef2fcd..d043015f74 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -57,11 +57,13 @@ public: //physics shape is a vector of convex hulls //each convex hull is a set of points - typedef std::vector<std::vector<LLVector3> > physics_shape; + typedef std::vector<LLVector3> hull; + typedef std::vector<hull> physics_shape; LLModel(LLVolumeParams& params, F32 detail); static LLSD writeModel(std::string filename, LLModel* physics, LLModel* high, LLModel* medium, LLModel* low, LLModel* imposotr, LLModel::physics_shape& physics_shape, bool upload_skin, bool upload_joints, bool nowrite = FALSE); - static LLSD writeModel(std::ostream& ostr, LLModel* physics, LLModel* high, LLModel* medium, LLModel* low, LLModel* imposotr, LLModel::physics_shape& physics_shape, bool upload_skin, bool upload_joints, bool nowrite = FALSE); + static LLSD writeModel(std::string filename, LLModel* physics, LLModel* high, LLModel* medium, LLModel* low, LLModel* imposotr, LLModel::physics_shape& physics_shape, LLModel::hull& base_hull, bool upload_skin, bool upload_joints, bool nowrite = FALSE); + static LLSD writeModel(std::ostream& ostr, LLModel* physics, LLModel* high, LLModel* medium, LLModel* low, LLModel* imposotr, LLModel::physics_shape& physics_shape, LLModel::hull& base_hull, bool upload_skin, bool upload_joints, bool nowrite = FALSE); static LLSD writeModelToStream(std::ostream& ostr, LLSD& mdl, BOOL nowrite = FALSE); static LLModel* loadModelFromAsset(std::string filename, S32 lod); static LLModel* loadModelFromDae(std::string filename); diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt index f3d6e73043..8b20086af4 100644 --- a/indra/newview/featuretable.txt +++ b/indra/newview/featuretable.txt @@ -1,4 +1,4 @@ -version 23 +version 24 // NOTE: This is mostly identical to featuretable_mac.txt with a few differences // Should be combined into one table @@ -454,6 +454,7 @@ RenderAvatarCloth 0 0 list ATI RenderUseStreamVBO 1 0 +RenderAvatarVP 1 0 /// Tweaked NVIDIA diff --git a/indra/newview/featuretable_xp.txt b/indra/newview/featuretable_xp.txt index 9b901022c4..4fa3087b9d 100644 --- a/indra/newview/featuretable_xp.txt +++ b/indra/newview/featuretable_xp.txt @@ -1,4 +1,4 @@ -version 23 +version 24 // NOTE: This is mostly identical to featuretable_mac.txt with a few differences // Should be combined into one table @@ -451,6 +451,7 @@ RenderAvatarCloth 0 0 list ATI RenderUseStreamVBO 1 0 +RenderAvatarVP 1 0 /// Tweaked NVIDIA diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 82d3a07345..deb2eff351 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -900,7 +900,8 @@ void LLFloaterModelPreview::showDecompFloater() // protected against stub by stage_count being 0 for stub above LLConvexDecomposition::getInstance()->registerCallback(j, LLPhysicsDecomp::llcdCallback); - llinfos << "Physics decomp stage " << j << " parameters:" << llendl; + llinfos << "Physics decomp stage " << stage[j].mName << " (" << j << ") parameters:" << llendl; + llinfos << "------------------------------------" << llendl; for (S32 i = 0; i < param_count; ++i) { @@ -912,11 +913,15 @@ void LLFloaterModelPreview::showDecompFloater() std::string name(param[i].mName ? param[i].mName : ""); std::string description(param[i].mDescription ? param[i].mDescription : ""); + std::string type = "unknown"; + llinfos << name << " - " << description << llendl; if (param[i].mType == LLCDParam::LLCD_FLOAT) { mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mFloat); + llinfos << "Type: float, Default: " << param[i].mDefault.mFloat << llendl; + LLSliderCtrl::Params p; p.name(name); p.label(name); @@ -935,6 +940,7 @@ void LLFloaterModelPreview::showDecompFloater() else if (param[i].mType == LLCDParam::LLCD_INTEGER) { mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); + llinfos << "Type: integer, Default: " << param[i].mDefault.mIntOrEnumValue << llendl; LLSliderCtrl::Params p; p.name(name); p.label(name); @@ -952,6 +958,8 @@ void LLFloaterModelPreview::showDecompFloater() else if (param[i].mType == LLCDParam::LLCD_BOOLEAN) { mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mBool); + llinfos << "Type: boolean, Default: " << (param[i].mDefault.mBool ? "True" : "False") << llendl; + LLCheckBoxCtrl::Params p; p.rect(LLRect(left, cur_y, right, cur_y-20)); p.name(name); @@ -967,6 +975,8 @@ void LLFloaterModelPreview::showDecompFloater() { S32 cur_x = left; mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); + llinfos << "Type: enum, Default: " << param[i].mDefault.mIntOrEnumValue << llendl; + { //add label LLTextBox::Params p; const LLFontGL* font = (LLFontGL*) p.font(); @@ -987,9 +997,13 @@ void LLFloaterModelPreview::showDecompFloater() p.label(name); p.tool_tip(description); + llinfos << "Accepted values: " << llendl; LLComboBox* combo_box = LLUICtrlFactory::create<LLComboBox>(p); for (S32 k = 0; k < param[i].mDetails.mEnumValues.mNumEnums; ++k) { + llinfos << param[i].mDetails.mEnumValues.mEnumsArray[k].mValue + << " - " << param[i].mDetails.mEnumValues.mEnumsArray[k].mName << llendl; + combo_box->add(param[i].mDetails.mEnumValues.mEnumsArray[k].mName, LLSD::Integer(param[i].mDetails.mEnumValues.mEnumsArray[k].mValue)); } @@ -997,8 +1011,12 @@ void LLFloaterModelPreview::showDecompFloater() combo_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); mDecompFloater->addChild(combo_box); cur_y += 30; + } + + llinfos << "----" << llendl; } + llinfos << "-----------------------------" << llendl; } } diff --git a/indra/newview/llfloatertools.cpp b/indra/newview/llfloatertools.cpp index 8a8177abde..e5c6fe7631 100644 --- a/indra/newview/llfloatertools.cpp +++ b/indra/newview/llfloatertools.cpp @@ -434,6 +434,10 @@ void LLFloaterTools::refresh() LLSelectMgr::getInstance()->getSelection()->getSelectedObjectCost(); F32 link_cost = LLSelectMgr::getInstance()->getSelection()->getSelectedLinksetCost(); + F32 obj_physics_cost = + LLSelectMgr::getInstance()->getSelection()->getSelectedPhysicsCost(); + F32 link_physics_cost = + LLSelectMgr::getInstance()->getSelection()->getSelectedLinksetPhysicsCost(); // Update the text for the counts childSetTextArg( @@ -443,11 +447,10 @@ void LLFloaterTools::refresh() childSetTextArg("object_count", "[COUNT]", object_count_string); // Update the text for the resource costs - childSetTextArg( - "linked_set_cost", - "[COST]", - llformat("%.1f", link_cost)); + childSetTextArg("linked_set_cost","[COST]",llformat("%.1f", link_cost)); childSetTextArg("object_cost", "[COST]", llformat("%.1f", obj_cost)); + childSetTextArg("linked_set_cost","[PHYSICS]",llformat("%.1f", link_physics_cost)); + childSetTextArg("object_cost", "[PHYSICS]", llformat("%.1f", obj_physics_cost)); // Display rendering cost if needed if (sShowObjectCost) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index eae5cf59f0..681748694c 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1069,43 +1069,93 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 LLMeshDecomposition* d = new LLMeshDecomposition(); d->mMeshID = mesh_id; - // updated for const-correctness. gcc is picky about this type of thing - Nyx - const LLSD::Binary& hulls = decomp["HullList"].asBinary(); - const LLSD::Binary& position = decomp["Position"].asBinary(); + if (decomp.has("HullList")) + { + // updated for const-correctness. gcc is picky about this type of thing - Nyx + const LLSD::Binary& hulls = decomp["HullList"].asBinary(); + const LLSD::Binary& position = decomp["Position"].asBinary(); + + U16* p = (U16*) &position[0]; - U16* p = (U16*) &position[0]; + d->mHull.resize(hulls.size()); - d->mHull.resize(hulls.size()); + LLVector3 min; + LLVector3 max; + LLVector3 range; - LLVector3 min; - LLVector3 max; - LLVector3 range; + min.setValue(decomp["Min"]); + max.setValue(decomp["Max"]); + range = max-min; - min.setValue(decomp["Min"]); - max.setValue(decomp["Max"]); - range = max-min; + for (U32 i = 0; i < hulls.size(); ++i) + { + U16 count = (hulls[i] == 0) ? 256 : hulls[i]; + + for (U32 j = 0; j < count; ++j) + { + d->mHull[i].push_back(LLVector3( + (F32) p[0]/65535.f*range.mV[0]+min.mV[0], + (F32) p[1]/65535.f*range.mV[1]+min.mV[1], + (F32) p[2]/65535.f*range.mV[2]+min.mV[2])); + p += 3; + } - for (U32 i = 0; i < hulls.size(); ++i) + } + + //get mesh for decomposition + for (U32 i = 0; i < d->mHull.size(); ++i) + { + LLCDHull hull; + hull.mNumVertices = d->mHull[i].size(); + hull.mVertexBase = d->mHull[i][0].mV; + hull.mVertexStrideBytes = 12; + + LLCDMeshData mesh; + LLCDResult res = LLCD_OK; + if (LLConvexDecomposition::getInstance() != NULL) + { + res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); + } + if (res != LLCD_OK) + { + llwarns << "could not get mesh from hull from convex decomposition lib." << llendl; + return false; + } + + + d->mMesh.push_back(get_vertex_buffer_from_mesh(mesh)); + } + } + + if (decomp.has("Hull")) { - U16 count = (hulls[i] == 0) ? 256 : hulls[i]; + const LLSD::Binary& position = decomp["Hull"].asBinary(); + + U16* p = (U16*) &position[0]; + + LLVector3 min; + LLVector3 max; + LLVector3 range; + + min.setValue(decomp["Min"]); + max.setValue(decomp["Max"]); + range = max-min; + + U16 count = position.size()/6; for (U32 j = 0; j < count; ++j) { - d->mHull[i].push_back(LLVector3( + d->mBaseHull.push_back(LLVector3( (F32) p[0]/65535.f*range.mV[0]+min.mV[0], (F32) p[1]/65535.f*range.mV[1]+min.mV[1], (F32) p[2]/65535.f*range.mV[2]+min.mV[2])); p += 3; } - - } - - //get mesh for decomposition - for (U32 i = 0; i < d->mHull.size(); ++i) - { + + //get mesh for decomposition LLCDHull hull; - hull.mNumVertices = d->mHull[i].size(); - hull.mVertexBase = d->mHull[i][0].mV; + hull.mNumVertices = d->mBaseHull.size(); + hull.mVertexBase = d->mBaseHull[0].mV; hull.mVertexStrideBytes = 12; LLCDMeshData mesh; @@ -1120,9 +1170,8 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 return false; } - - d->mMesh.push_back(get_vertex_buffer_from_mesh(mesh)); - } + d->mBaseHullMesh = get_vertex_buffer_from_mesh(mesh); + } mDecompositionQ.push(d); } @@ -1160,6 +1209,63 @@ LLMeshUploadThread::~LLMeshUploadThread() } +LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) +{ + mStage = "single_hull"; + mModel = mdl; + mBaseModel = base_model; + mThread = thread; + + //copy out positions and indices + if (mdl) + { + U16 index_offset = 0; + + mPositions.clear(); + mIndices.clear(); + + //queue up vertex positions and indices + for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = mdl->getVolumeFace(i); + if (mPositions.size() + face.mNumVertices > 65535) + { + continue; + } + + for (U32 j = 0; j < face.mNumVertices; ++j) + { + mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr())); + } + + for (U32 j = 0; j < face.mNumIndices; ++j) + { + mIndices.push_back(face.mIndices[j]+index_offset); + } + + index_offset += face.mNumVertices; + } + } + + mThread->mFinalDecomp = this; + mThread->mPhysicsComplete = false; +} + +void LLMeshUploadThread::DecompRequest::completed() +{ + if (mThread->mFinalDecomp == this) + { + mThread->mPhysicsComplete = true; + } + + if (mHull.size() != 1) + { + llerrs << "WTF?" << llendl; + } + + mThread->mHullMap[mBaseModel] = mHull[0]; +} + void LLMeshUploadThread::run() { mCurlRequest = new LLCurlRequest(); @@ -1202,8 +1308,31 @@ void LLMeshUploadThread::run() } } } - } + //queue up models for hull generation + LLModel* physics = NULL; + + if (data.mModel[LLModel::LOD_PHYSICS].notNull()) + { + physics = data.mModel[LLModel::LOD_PHYSICS]; + } + else if (data.mModel[LLModel::LOD_MEDIUM].notNull()) + { + physics = data.mModel[LLModel::LOD_MEDIUM]; + } + else + { + physics = data.mModel[LLModel::LOD_HIGH]; + } + + if (!physics) + { + llerrs << "WTF?" << llendl; + } + + DecompRequest* request = new DecompRequest(physics, data.mBaseModel, this); + gMeshRepo.mDecompThread->submitRequest(request); + } //upload textures bool done = false; @@ -2209,6 +2338,8 @@ void LLMeshUploadThread::sendCostRequest(LLMeshUploadData& data) data.mModel[LLModel::LOD_PHYSICS]->mPhysicsShape : data.mBaseModel->mPhysicsShape; + LLModel::hull dummy_hull; + LLSD header = LLModel::writeModel(ostr, data.mModel[LLModel::LOD_PHYSICS], data.mModel[LLModel::LOD_HIGH], @@ -2216,6 +2347,7 @@ void LLMeshUploadThread::sendCostRequest(LLMeshUploadData& data) data.mModel[LLModel::LOD_LOW], data.mModel[LLModel::LOD_IMPOSTOR], phys_shape, + dummy_hull, mUploadSkin, mUploadJoints, true); @@ -2295,6 +2427,8 @@ void LLMeshUploadThread::doUploadModel(LLMeshUploadData& data) data.mModel[LLModel::LOD_PHYSICS]->mPhysicsShape : data.mBaseModel->mPhysicsShape; + + LLModel::writeModel(ostr, data.mModel[LLModel::LOD_PHYSICS], data.mModel[LLModel::LOD_HIGH], @@ -2302,6 +2436,7 @@ void LLMeshUploadThread::doUploadModel(LLMeshUploadData& data) data.mModel[LLModel::LOD_LOW], data.mModel[LLModel::LOD_IMPOSTOR], phys_shape, + mHullMap[data.mBaseModel], mUploadSkin, mUploadJoints); @@ -2475,6 +2610,8 @@ LLSD LLMeshUploadThread::createObject(LLModelInstance& instance) perm.setNextOwnerBits(gAgent.getID(), LLUUID::null, TRUE, LLFloaterPerms::getNextOwnerPerms()); perm.setGroupBits(gAgent.getID(), LLUUID::null, TRUE, LLFloaterPerms::getGroupPerms()); perm.setEveryoneBits(gAgent.getID(), LLUUID::null, TRUE, LLFloaterPerms::getEveryonePerms()); + perm.setOwnerAndGroup(gAgent.getID(), gAgent.getID(), LLUUID::null, false); + perm.setCreator(gAgent.getID()); object_params["permissions"] = ll_create_sd_from_permissions(perm); @@ -2635,159 +2772,269 @@ S32 LLPhysicsDecomp::llcdCallback(const char* status, S32 p1, S32 p2) return 1; } -void LLPhysicsDecomp::run() +void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh) { - LLConvexDecomposition::initSystem(); - mInited = true; + mesh.mVertexBase = mCurRequest->mPositions[0].mV; + mesh.mVertexStrideBytes = 12; + mesh.mNumVertices = mCurRequest->mPositions.size(); - while (!mQuitting) + mesh.mIndexType = LLCDMeshData::INT_16; + mesh.mIndexBase = &(mCurRequest->mIndices[0]); + mesh.mIndexStrideBytes = 6; + + mesh.mNumTriangles = mCurRequest->mIndices.size()/3; + + LLCDResult ret = LLCD_OK; + if (LLConvexDecomposition::getInstance() != NULL) { - mSignal->wait(); - while (!mQuitting && !mRequestQ.empty()) - { - mCurRequest = mRequestQ.front(); - mRequestQ.pop(); + ret = LLConvexDecomposition::getInstance()->setMeshData(&mesh); + } - LLCDMeshData mesh; - S32 stage = mStageID[mCurRequest->mStage]; + if (ret) + { + llerrs << "Convex Decomposition thread valid but could not set mesh data" << llendl; + } +} - //load data intoLLCD - if (stage == 0) - { - mesh.mVertexBase = mCurRequest->mPositions[0].mV; - mesh.mVertexStrideBytes = 12; - mesh.mNumVertices = mCurRequest->mPositions.size(); +void LLPhysicsDecomp::doDecomposition() +{ + LLCDMeshData mesh; + S32 stage = mStageID[mCurRequest->mStage]; - mesh.mIndexType = LLCDMeshData::INT_16; - mesh.mIndexBase = &(mCurRequest->mIndices[0]); - mesh.mIndexStrideBytes = 6; - - mesh.mNumTriangles = mCurRequest->mIndices.size()/3; + //load data intoLLCD + if (stage == 0) + { + setMeshData(mesh); + } + + //build parameter map + std::map<std::string, const LLCDParam*> param_map; - LLCDResult ret = LLCD_OK; - if (LLConvexDecomposition::getInstance() != NULL) - { - ret = LLConvexDecomposition::getInstance()->setMeshData(&mesh); - } + const LLCDParam* params; + S32 param_count = LLConvexDecomposition::getInstance()->getParameters(¶ms); + + for (S32 i = 0; i < param_count; ++i) + { + param_map[params[i].mName] = params+i; + } - if (ret) - { - llerrs << "Convex Decomposition thread valid but could not set mesh data" << llendl; - } - } + //set parameter values + for (decomp_params::iterator iter = mCurRequest->mParams.begin(); iter != mCurRequest->mParams.end(); ++iter) + { + const std::string& name = iter->first; + const LLSD& value = iter->second; + const LLCDParam* param = param_map[name]; - //build parameter map - std::map<std::string, const LLCDParam*> param_map; + if (param == NULL) + { //couldn't find valid parameter + continue; + } - const LLCDParam* params; - S32 param_count = LLConvexDecomposition::getInstance()->getParameters(¶ms); - - for (S32 i = 0; i < param_count; ++i) - { - param_map[params[i].mName] = params+i; - } + U32 ret = LLCD_OK; - //set parameter values - for (decomp_params::iterator iter = mCurRequest->mParams.begin(); iter != mCurRequest->mParams.end(); ++iter) - { - const std::string& name = iter->first; - const LLSD& value = iter->second; + if (param->mType == LLCDParam::LLCD_FLOAT) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, (F32) value.asReal()); + } + else if (param->mType == LLCDParam::LLCD_INTEGER || + param->mType == LLCDParam::LLCD_ENUM) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asInteger()); + } + else if (param->mType == LLCDParam::LLCD_BOOLEAN) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asBoolean()); + } - const LLCDParam* param = param_map[name]; + if (ret) + { + llerrs << "WTF?" << llendl; + } + } - if (param == NULL) - { //couldn't find valid parameter - continue; - } + mCurRequest->setStatusMessage("Executing."); - U32 ret = LLCD_OK; + LLCDResult ret = LLCD_OK; + + if (LLConvexDecomposition::getInstance() != NULL) + { + ret = LLConvexDecomposition::getInstance()->executeStage(stage); + } - if (param->mType == LLCDParam::LLCD_FLOAT) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, (F32) value.asReal()); - } - else if (param->mType == LLCDParam::LLCD_INTEGER || - param->mType == LLCDParam::LLCD_ENUM) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asInteger()); - } - else if (param->mType == LLCDParam::LLCD_BOOLEAN) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asBoolean()); - } + if (ret) + { + llerrs << "Convex Decomposition thread valid but could not execute stage " << stage << llendl; + } - if (ret) - { - llerrs << "WTF?" << llendl; - } - } + mCurRequest->setStatusMessage("Reading results"); + + S32 num_hulls =0; + if (LLConvexDecomposition::getInstance() != NULL) + { + num_hulls = LLConvexDecomposition::getInstance()->getNumHullsFromStage(stage); + } + + mMutex->lock(); + mCurRequest->mHull.clear(); + mCurRequest->mHull.resize(num_hulls); - mCurRequest->setStatusMessage("Executing."); + mCurRequest->mHullMesh.clear(); + mCurRequest->mHullMesh.resize(num_hulls); + mMutex->unlock(); - LLCDResult ret = LLCD_OK; - - if (LLConvexDecomposition::getInstance() != NULL) - { - ret = LLConvexDecomposition::getInstance()->executeStage(stage); - } + for (S32 i = 0; i < num_hulls; ++i) + { + std::vector<LLVector3> p; + LLCDHull hull; + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + LLConvexDecomposition::getInstance()->getHullFromStage(stage, i, &hull); - if (ret) - { - llerrs << "Convex Decomposition thread valid but could not execute stage " << stage << llendl; - } + const F32* v = hull.mVertexBase; - mCurRequest->setStatusMessage("Reading results"); + for (S32 j = 0; j < hull.mNumVertices; ++j) + { + LLVector3 vert(v[0], v[1], v[2]); + p.push_back(vert); + v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); + } + + LLCDMeshData mesh; + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + LLConvexDecomposition::getInstance()->getMeshFromStage(stage, i, &mesh); - S32 num_hulls =0; - if (LLConvexDecomposition::getInstance() != NULL) - { - num_hulls = LLConvexDecomposition::getInstance()->getNumHullsFromStage(stage); - } + mCurRequest->mHullMesh[i] = get_vertex_buffer_from_mesh(mesh); + + mMutex->lock(); + mCurRequest->mHull[i] = p; + mMutex->unlock(); + } + + { + LLMutexLock lock(mMutex); + + mCurRequest->setStatusMessage("Done."); + mCurRequest->completed(); + + mCurRequest = NULL; + } +} + +void LLPhysicsDecomp::doDecompositionSingleHull() +{ + LLCDMeshData mesh; + + setMeshData(mesh); - mMutex->lock(); - mCurRequest->mHull.clear(); - mCurRequest->mHull.resize(num_hulls); + + //set all parameters to default + std::map<std::string, const LLCDParam*> param_map; - mCurRequest->mHullMesh.clear(); - mCurRequest->mHullMesh.resize(num_hulls); - mMutex->unlock(); + const LLCDParam* params; + S32 param_count = LLConvexDecomposition::getInstance()->getParameters(¶ms); + + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); - for (S32 i = 0; i < num_hulls; ++i) - { - std::vector<LLVector3> p; - LLCDHull hull; - // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code - LLConvexDecomposition::getInstance()->getHullFromStage(stage, i, &hull); + for (S32 i = 0; i < param_count; ++i) + { + decomp->setParam(params[i].mName, params[i].mDefault.mIntOrEnumValue); + } - const F32* v = hull.mVertexBase; + const S32 STAGE_DECOMPOSE = mStageID["Decompose"]; + const S32 STAGE_SIMPLIFY = mStageID["Simplify"]; + const S32 DECOMP_PREVIEW = 0; + const S32 SIMPLIFY_RETAIN = 0; + + decomp->setParam("Decompose Quality", DECOMP_PREVIEW); + decomp->setParam("Simplify Method", SIMPLIFY_RETAIN); + decomp->setParam("Retain%", 0.f); - for (S32 j = 0; j < hull.mNumVertices; ++j) - { - LLVector3 vert(v[0], v[1], v[2]); - p.push_back(vert); - v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); - } - - LLCDMeshData mesh; - // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code - LLConvexDecomposition::getInstance()->getMeshFromStage(stage, i, &mesh); + LLCDResult ret = LLCD_OK; + ret = decomp->executeStage(STAGE_DECOMPOSE); + + if (ret) + { + llerrs << "Could not execute decomposition stage when attempting to create single hull." << llendl; + } - mCurRequest->mHullMesh[i] = get_vertex_buffer_from_mesh(mesh); + ret = decomp->executeStage(STAGE_SIMPLIFY); + + if (ret) + { + llerrs << "Could not execute simiplification stage when attempting to create single hull." << llendl; + } + + S32 num_hulls =0; + if (LLConvexDecomposition::getInstance() != NULL) + { + num_hulls = LLConvexDecomposition::getInstance()->getNumHullsFromStage(STAGE_SIMPLIFY); + } + + mMutex->lock(); + mCurRequest->mHull.clear(); + mCurRequest->mHull.resize(num_hulls); + mCurRequest->mHullMesh.clear(); + mMutex->unlock(); + + for (S32 i = 0; i < num_hulls; ++i) + { + std::vector<LLVector3> p; + LLCDHull hull; + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + LLConvexDecomposition::getInstance()->getHullFromStage(STAGE_SIMPLIFY, i, &hull); + + const F32* v = hull.mVertexBase; + + for (S32 j = 0; j < hull.mNumVertices; ++j) + { + LLVector3 vert(v[0], v[1], v[2]); + p.push_back(vert); + v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); + } - mMutex->lock(); - mCurRequest->mHull[i] = p; - mMutex->unlock(); - } + mMutex->lock(); + mCurRequest->mHull[i] = p; + mMutex->unlock(); + } + + { + LLMutexLock lock(mMutex); + mCurRequest->completed(); + mCurRequest = NULL; + } +} + +void LLPhysicsDecomp::run() +{ + LLConvexDecomposition::initSystem(); + mInited = true; + + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); + + const LLCDStageData* stages; + S32 num_stages = decomp->getStages(&stages); + for (S32 i = 0; i < num_stages; i++) + { + mStageID[stages[i].mName] = i; + } + + while (!mQuitting) + { + mSignal->wait(); + while (!mQuitting && !mRequestQ.empty()) + { + mCurRequest = mRequestQ.front(); + mRequestQ.pop(); + + if (mCurRequest->mStage == "single_hull") { - LLMutexLock lock(mMutex); - - mCurRequest->setStatusMessage("Done."); - mCurRequest->completed(); - - mCurRequest = NULL; + doDecompositionSingleHull(); } + else + { + doDecomposition(); + } } } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index e7270cc47d..4f790227b1 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -144,8 +144,10 @@ public: LLUUID mMeshID; LLModel::physics_shape mHull; + LLModel::hull mBaseHull; std::vector<LLPointer<LLVertexBuffer> > mMesh; + LLPointer<LLVertexBuffer> mBaseHullMesh; }; class LLPhysicsDecomp : public LLThread @@ -189,6 +191,10 @@ public: static S32 llcdCallback(const char*, S32, S32); void cancel(); + void setMeshData(LLCDMeshData& mesh); + void doDecomposition(); + void doDecompositionSingleHull(); + virtual void run(); std::map<std::string, S32> mStageID; @@ -337,6 +343,26 @@ public: class LLMeshUploadThread : public LLThread { public: + class DecompRequest : public LLPhysicsDecomp::Request + { + public: + LLPointer<LLModel> mModel; + LLPointer<LLModel> mBaseModel; + + LLMeshUploadThread* mThread; + + DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread); + + S32 statusCallback(const char* status, S32 p1, S32 p2) { return 1; } + void completed(); + }; + + LLPointer<DecompRequest> mFinalDecomp; + bool mPhysicsComplete; + + typedef std::map<LLPointer<LLModel>, std::vector<LLVector3> > hull_map; + hull_map mHullMap; + typedef std::vector<LLModelInstance> instance_list; instance_list mInstanceList; diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 57e04a43a2..f819e9fcbc 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -6230,9 +6230,13 @@ F32 LLObjectSelection::getSelectedObjectCost() return cost; } -F32 LLObjectSelection::getSelectedObjectStreamingCost() +F32 LLObjectSelection::getSelectedLinksetCost() { + cleanupNodes(); F32 cost = 0.f; + + std::set<LLViewerObject*> me_roots; + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) { LLSelectNode* node = *iter; @@ -6240,16 +6244,26 @@ F32 LLObjectSelection::getSelectedObjectStreamingCost() if (object) { - cost += object->getStreamingCost(); + LLViewerObject* root = static_cast<LLViewerObject*>(object->getRoot()); + if (root) + { + if (me_roots.find(root) == me_roots.end()) + { + me_roots.insert(root); + cost += root->getLinksetCost(); + } + } } } return cost; } -U32 LLObjectSelection::getSelectedObjectTriangleCount() +F32 LLObjectSelection::getSelectedPhysicsCost() { - U32 count = 0; + cleanupNodes(); + F32 cost = 0.f; + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) { LLSelectNode* node = *iter; @@ -6257,14 +6271,14 @@ U32 LLObjectSelection::getSelectedObjectTriangleCount() if (object) { - count += object->getTriangleCount(); + cost += object->getPhysicsCost(); } } - return count; + return cost; } -F32 LLObjectSelection::getSelectedLinksetCost() +F32 LLObjectSelection::getSelectedLinksetPhysicsCost() { cleanupNodes(); F32 cost = 0.f; @@ -6284,7 +6298,7 @@ F32 LLObjectSelection::getSelectedLinksetCost() if (me_roots.find(root) == me_roots.end()) { me_roots.insert(root); - cost += root->getLinksetCost(); + cost += root->getLinksetPhysicsCost(); } } } @@ -6293,6 +6307,41 @@ F32 LLObjectSelection::getSelectedLinksetCost() return cost; } +F32 LLObjectSelection::getSelectedObjectStreamingCost() +{ + F32 cost = 0.f; + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + + if (object) + { + cost += object->getStreamingCost(); + } + } + + return cost; +} + +U32 LLObjectSelection::getSelectedObjectTriangleCount() +{ + U32 count = 0; + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + + if (object) + { + count += object->getTriangleCount(); + } + } + + return count; +} + + //----------------------------------------------------------------------------- // getTECount() //----------------------------------------------------------------------------- diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h index e598ffd47d..9a896bd5e2 100644 --- a/indra/newview/llselectmgr.h +++ b/indra/newview/llselectmgr.h @@ -288,6 +288,9 @@ public: S32 getObjectCount(BOOL mesh_adjust = FALSE); F32 getSelectedObjectCost(); F32 getSelectedLinksetCost(); + F32 getSelectedPhysicsCost(); + F32 getSelectedLinksetPhysicsCost(); + F32 getSelectedObjectStreamingCost(); U32 getSelectedObjectTriangleCount(); diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index 148c222014..b99829c3e4 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -2800,6 +2800,38 @@ S32 get_physics_detail(const LLVolumeParams& volume_params, const LLVector3& sca return detail; } +void renderMeshBaseHull(LLVOVolume* volume, U32 data_mask, LLColor4& color) +{ + LLUUID mesh_id = volume->getVolume()->getParams().getSculptID(); + const LLMeshDecomposition* decomp = gMeshRepo.getDecomposition(mesh_id); + + if (decomp) + { + gGL.pushMatrix(); + glMultMatrixf((F32*) volume->getRelativeXform().mMatrix); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLVertexBuffer* buff = decomp->mBaseHullMesh; + + buff->setBuffer(data_mask); + + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glColor3fv(color.mV); + buff->drawArrays(LLRender::TRIANGLES, 0, buff->getNumVerts()); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + { + LLGLEnable blend(GL_BLEND); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + glColor4fv(color.mV); + buff->drawArrays(LLRender::TRIANGLES, 0, buff->getNumVerts()); + } + gGL.popMatrix(); + } +} + void renderPhysicsShape(LLDrawable* drawable, LLVOVolume* volume) { U8 physics_type = volume->getPhysicsShapeType(); @@ -2875,6 +2907,13 @@ void renderPhysicsShape(LLDrawable* drawable, LLVOVolume* volume) gGL.popMatrix(); } } + else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::USER_CONVEX) + { + if (volume->isMesh()) + { + renderMeshBaseHull(volume, data_mask, color); + } + } else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::BOX) { gGL.pushMatrix(); diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 9b3f81e193..dcd208fea5 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -240,6 +240,8 @@ LLViewerObject::LLViewerObject(const LLUUID &id, const LLPCode pcode, LLViewerRe mClickAction(0), mObjectCost(0.f), mLinksetCost(0.f), + mPhysicsCost(0.f), + mLinksetPhysicsCost(0.f), mCostStale(true), mAttachmentItemID(LLUUID::null) { @@ -2931,6 +2933,28 @@ void LLViewerObject::setLinksetCost(F32 cost) } } +void LLViewerObject::setPhysicsCost(F32 cost) +{ + mPhysicsCost = cost; + mCostStale = false; + + if (isSelected()) + { + gFloaterTools->dirty(); + } +} + +void LLViewerObject::setLinksetPhysicsCost(F32 cost) +{ + mLinksetPhysicsCost = cost; + mCostStale = false; + + if (isSelected()) + { + gFloaterTools->dirty(); + } +} + F32 LLViewerObject::getObjectCost() { @@ -2942,27 +2966,45 @@ F32 LLViewerObject::getObjectCost() return mObjectCost; } -F32 LLViewerObject::getStreamingCost() +F32 LLViewerObject::getLinksetCost() { - return 0.f; + if (mCostStale) + { + gObjectList.updateObjectCost(this); + } + + return mLinksetCost; } -U32 LLViewerObject::getTriangleCount() +F32 LLViewerObject::getPhysicsCost() { - return 0; + if (mCostStale) + { + gObjectList.updateObjectCost(this); + } + + return mPhysicsCost; } -F32 LLViewerObject::getLinksetCost() +F32 LLViewerObject::getLinksetPhysicsCost() { if (mCostStale) { gObjectList.updateObjectCost(this); } - return mLinksetCost; + return mLinksetPhysicsCost; } +F32 LLViewerObject::getStreamingCost() +{ + return 0.f; +} +U32 LLViewerObject::getTriangleCount() +{ + return 0; +} void LLViewerObject::updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax) { diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index 094f01ab3d..3cc23bb085 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -334,14 +334,20 @@ public: virtual void setScale(const LLVector3 &scale, BOOL damped = FALSE); - void setObjectCost(F32 cost); - F32 getObjectCost(); virtual F32 getStreamingCost(); virtual U32 getTriangleCount(); + void setObjectCost(F32 cost); + F32 getObjectCost(); + void setLinksetCost(F32 cost); F32 getLinksetCost(); + void setPhysicsCost(F32 cost); + F32 getPhysicsCost(); + + void setLinksetPhysicsCost(F32 cost); + F32 getLinksetPhysicsCost(); void sendShapeUpdate(); @@ -699,6 +705,9 @@ protected: U8 mClickAction; F32 mObjectCost; //resource cost of this object or -1 if unknown F32 mLinksetCost; + F32 mPhysicsCost; + F32 mLinksetPhysicsCost; + bool mCostStale; static U32 sNumZombieObjects; // Objects which are dead, but not deleted diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index e6f1d8e728..97b102a6a3 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -728,7 +728,10 @@ public: F32 object_cost = content[iter->asString()]["resource_cost"].asReal(); - gObjectList.updateObjectCost(object_id, object_cost, link_cost); + F32 physics_cost = content[iter->asString()]["physics_cost"].asReal(); + F32 link_physics_cost = content[iter->asString()]["linked_set_physics_cost"].asReal(); + + gObjectList.updateObjectCost(object_id, object_cost, link_cost, physics_cost, link_physics_cost); } else { @@ -1187,7 +1190,7 @@ void LLViewerObjectList::updateObjectCost(LLViewerObject* object) mStaleObjectCost.insert(object->getID()); } -void LLViewerObjectList::updateObjectCost(LLUUID object_id, F32 object_cost, F32 link_cost) +void LLViewerObjectList::updateObjectCost(LLUUID object_id, F32 object_cost, F32 link_cost, F32 physics_cost, F32 link_physics_cost) { mPendingObjectCost.erase(object_id); @@ -1196,6 +1199,8 @@ void LLViewerObjectList::updateObjectCost(LLUUID object_id, F32 object_cost, F32 { object->setObjectCost(object_cost); object->setLinksetCost(link_cost); + object->setPhysicsCost(physics_cost); + object->setLinksetPhysicsCost(link_physics_cost); } } diff --git a/indra/newview/llviewerobjectlist.h b/indra/newview/llviewerobjectlist.h index 0f58e543ac..afa881ea58 100644 --- a/indra/newview/llviewerobjectlist.h +++ b/indra/newview/llviewerobjectlist.h @@ -92,7 +92,7 @@ public: void update(LLAgent &agent, LLWorld &world); void updateObjectCost(LLViewerObject* object); - void updateObjectCost(LLUUID object_id, F32 object_cost, F32 link_cost); + void updateObjectCost(LLUUID object_id, F32 object_cost, F32 link_cost, F32 physics_cost, F32 link_physics_cost); void onObjectCostFetchFailure(LLUUID object_id); void shiftObjects(const LLVector3 &offset); diff --git a/indra/newview/skins/default/xui/en/floater_tools.xml b/indra/newview/skins/default/xui/en/floater_tools.xml index 2e3349dbe8..7dca22fd5a 100644 --- a/indra/newview/skins/default/xui/en/floater_tools.xml +++ b/indra/newview/skins/default/xui/en/floater_tools.xml @@ -747,8 +747,9 @@ top_delta="0" right="-10" name="linked_set_cost" + tool_tip="Cost of currently selected linked sets as [prims],[physics complexity]" width="80"> - Cost: [COST] + Cost: [COST] / [PHYSICS] </text> <text text_color="LtGray_50" @@ -773,8 +774,9 @@ top_delta="0" right="-10" name="object_cost" + tool_tip="Cost of currently selected objects as [prims] / [physics complexity]" width="80"> - Cost: [COST] + Cost: [COST] / [PHYSICS] </text> <!-- <text --> <!-- text_color="LtGray_50" --> |