From 697dd7e9298282590f8cf858a58335f70302532b Mon Sep 17 00:00:00 2001 From: Siana Gearz Date: Fri, 2 Dec 2011 09:30:12 +0100 Subject: [PATCH 1/2] Preparing to add mesh upload. --- indra/llcommon/llthread.cpp | 2 +- indra/llrender/llcubemap.cpp | 12 +- indra/newview/CMakeLists.txt | 4 + indra/newview/app_settings/settings.xml | 11 + indra/newview/llappviewer.cpp | 7 +- indra/newview/llassetuploadresponders.cpp | 921 ++- indra/newview/llassetuploadresponders.h | 48 +- indra/newview/llcallbacklist.cpp | 6 - indra/newview/lldrawpoolalpha.h | 1 - indra/newview/llfloatermodelpreview.cpp | 5833 +++++++++++++++++ indra/newview/llfloatermodelpreview.h | 474 ++ indra/newview/llfloatermodeluploadbase.cpp | 66 + indra/newview/llfloatermodeluploadbase.h | 69 + indra/newview/llmeshrepository.cpp | 93 +- indra/newview/llmeshrepository.h | 14 +- indra/newview/lluploadfloaterobservers.cpp | 63 + indra/newview/lluploadfloaterobservers.h | 112 + indra/newview/llviewermenu.cpp | 2 +- indra/newview/llviewermenufile.cpp | 273 +- indra/newview/llviewermenufile.h | 102 +- indra/newview/llviewerregion.cpp | 22 +- .../skins/default/xui/en-us/notifications.xml | 18 + 22 files changed, 7700 insertions(+), 453 deletions(-) create mode 100644 indra/newview/llfloatermodelpreview.cpp create mode 100644 indra/newview/llfloatermodelpreview.h create mode 100644 indra/newview/llfloatermodeluploadbase.cpp create mode 100644 indra/newview/llfloatermodeluploadbase.h create mode 100644 indra/newview/lluploadfloaterobservers.cpp create mode 100644 indra/newview/lluploadfloaterobservers.h diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 2fe048b4e..8ae458c1c 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -111,7 +111,7 @@ void *APR_THREAD_FUNC LLThread::staticRun(apr_thread_t *apr_threadp, void *datap // after the LLCurl::Multi::run() function exits and we actually // change this variable (which really SHOULD have been inside // the critical area of the mSignal lock)]. - llinfos << "LLThread::staticRun() Exiting: " << name << llendl; + lldebugs << "LLThread::staticRun() Exiting: " << name << llendl; return NULL; } diff --git a/indra/llrender/llcubemap.cpp b/indra/llrender/llcubemap.cpp index a9f026978..ed6f97f90 100644 --- a/indra/llrender/llcubemap.cpp +++ b/indra/llrender/llcubemap.cpp @@ -265,7 +265,7 @@ void LLCubeMap::setMatrix(S32 stage) if (mMatrixStage < 0) return; - //if (stage > 0) + if (stage > 0) { gGL.getTexUnit(stage)->activate(); } @@ -284,17 +284,17 @@ void LLCubeMap::setMatrix(S32 stage) gGL.loadMatrix((F32 *)trans.mMatrix); gGL.matrixMode(LLRender::MM_MODELVIEW); - /*if (stage > 0) + if (stage > 0) { gGL.getTexUnit(0)->activate(); - }*/ + } } void LLCubeMap::restoreMatrix() { if (mMatrixStage < 0) return; - //if (mMatrixStage > 0) + if (mMatrixStage > 0) { gGL.getTexUnit(mMatrixStage)->activate(); } @@ -302,10 +302,10 @@ void LLCubeMap::restoreMatrix() gGL.popMatrix(); gGL.matrixMode(LLRender::MM_MODELVIEW); - /*if (mMatrixStage > 0) + if (mMatrixStage > 0) { gGL.getTexUnit(0)->activate(); - }*/ + } } void LLCubeMap::setReflection (void) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index ce601470f..8abd55abf 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -217,6 +217,7 @@ set(viewer_SOURCE_FILES llfloatermap.cpp llfloatermemleak.cpp llfloatermessagelog.cpp + llfloatermodeluploadbase.cpp llfloatermute.cpp llfloaternamedesc.cpp llfloaternewim.cpp @@ -419,6 +420,7 @@ set(viewer_SOURCE_FILES lltrans.cpp lltranslate.cpp lluploaddialog.cpp + lluploadfloaterobservers.cpp llurl.cpp llurldispatcher.cpp llurlhistory.cpp @@ -701,6 +703,7 @@ set(viewer_HEADER_FILES llfloaterlandmark.h llfloatermap.h llfloatermemleak.h + llfloatermodeluploadbase.h llfloatermessagelog.h llfloatermute.h llfloaternamedesc.h @@ -907,6 +910,7 @@ set(viewer_HEADER_FILES lltranslate.h lluiconstants.h lluploaddialog.h + lluploadfloaterobservers.h llurl.h llurldispatcher.h llurlhistory.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index c55fc1362..a990baf7d 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8075,6 +8075,17 @@ Value 0 + MeshUploadTimeOut + + Comment + Maximum time in seconds for llcurl to execute a mesh uploading request + Persist + 1 + Type + S32 + Value + 600 + MeshUploadFakeErrors Comment diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 3a4bde499..cba1c2dcd 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4163,8 +4163,6 @@ static F32 CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; void LLAppViewer::idleNetwork() { pingMainloopTimeout("idleNetwork"); - LLError::LLCallStacks::clear() ; - llpushcallstacks ; gObjectList.mNumNewObjects = 0; S32 total_decoded = 0; @@ -4259,15 +4257,14 @@ void LLAppViewer::idleNetwork() { LLUUID this_region_id = agent_region->getRegionID(); bool this_region_alive = agent_region->isAlive(); - if ((mAgentRegionLastAlive && !this_region_alive) // newly dead - && (mAgentRegionLastID == this_region_id)) // same region + if (mAgentRegionLastAlive && !this_region_alive // newly dead + && mAgentRegionLastID == this_region_id) // same region { forceDisconnect(LLTrans::getString("AgentLostConnection")); } mAgentRegionLastID = this_region_id; mAgentRegionLastAlive = this_region_alive; } - llpushcallstacks ; } void LLAppViewer::disconnectViewer() diff --git a/indra/newview/llassetuploadresponders.cpp b/indra/newview/llassetuploadresponders.cpp index a121d5556..9dda0221a 100644 --- a/indra/newview/llassetuploadresponders.cpp +++ b/indra/newview/llassetuploadresponders.cpp @@ -34,12 +34,18 @@ #include "llassetuploadresponders.h" +// library includes +#include "lleconomy.h" +#include "llfocusmgr.h" +#include "llnotifications.h" +#include "llscrolllistctrl.h" +#include "llsdserialize.h" + // viewer includes #include "llagent.h" #include "llcompilequeue.h" #include "llfloaterbuycurrency.h" #include "llnotify.h" -#include "llinventorydefines.h" #include "llinventorymodel.h" #include "llinventoryview.h" #include "llpermissionsflags.h" @@ -56,28 +62,123 @@ #include "llviewermenufile.h" #include "llviewerwindow.h" #include "lltexlayer.h" - -// library includes -#include "lleconomy.h" -#include "llfocusmgr.h" -#include "llnotificationsutil.h" -#include "llscrolllistctrl.h" -#include "llsdserialize.h" - -#include "hippogridmanager.h" +#include "statemachine/aifilepicker.h" // When uploading multiple files, don't display any of them when uploading more than this number. static const S32 FILE_COUNT_DISPLAY_THRESHOLD = 5; void dialog_refresh_all(); +void on_new_single_inventory_upload_complete(LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type, + const std::string inventory_type_string, + const LLUUID& item_folder_id, + const std::string& item_name, + const std::string& item_description, + const LLSD& server_response, + S32 upload_price) +{ + if (upload_price > 0) + { + // this upload costed us L$, update our balance + // and display something saying that it cost L$ + LLStatusBar::sendMoneyBalanceRequest(); + + LLSD args; + args["AMOUNT"] = llformat("%d", upload_price); + LLNotifications::instance().add("UploadPayment", args); + } + + if (item_folder_id.notNull()) + { + U32 everyone_perms = PERM_NONE; + U32 group_perms = PERM_NONE; + U32 next_owner_perms = PERM_ALL; + if (server_response.has("new_next_owner_mask")) + { + // The server provided creation perms so use them. + // Do not assume we got the perms we asked for + // since the server may not have granted them all. + everyone_perms = server_response["new_everyone_mask"].asInteger(); + group_perms = server_response["new_group_mask"].asInteger(); + next_owner_perms = server_response["new_next_owner_mask"].asInteger(); + } + else + { + // The server doesn't provide creation perms + // so use old assumption-based perms. + if (inventory_type_string != "snapshot") + { + next_owner_perms = PERM_MOVE | PERM_TRANSFER; + } + } + + LLPermissions new_perms; + new_perms.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + new_perms.initMasks(PERM_ALL, PERM_ALL, everyone_perms, group_perms, + next_owner_perms); + + U32 inventory_item_flags = 0; + if (server_response.has("inventory_flags")) + { + inventory_item_flags = (U32) server_response["inventory_flags"].asInteger(); + if (inventory_item_flags != 0) + { + llinfos << "inventory_item_flags " << inventory_item_flags << llendl; + } + } + S32 creation_date_now = time_corrected(); + LLPointer item = new LLViewerInventoryItem( + server_response["new_inventory_item"].asUUID(), + item_folder_id, + new_perms, + server_response["new_asset"].asUUID(), + asset_type, + inventory_type, + item_name, + item_description, + LLSaleInfo::DEFAULT, + inventory_item_flags, + creation_date_now); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + + // Show the preview panel for textures and sounds to let + // user know that the image (or snapshot) arrived intact. + LLInventoryView* view = LLInventoryView::getActiveInventory(); + if (view) + { + LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); + + view->getPanel()->setSelection(server_response["new_inventory_item"].asUUID(), + TAKE_FOCUS_NO); + if ((LLAssetType::AT_TEXTURE == asset_type || LLAssetType::AT_SOUND == asset_type) + /* FIXME: && LLFilePicker::instance().getFileCount() <= FILE_COUNT_DISPLAY_THRESHOLD */) + { + view->getPanel()->openSelected(); + } + + // restore keyboard focus + gFocusMgr.setKeyboardFocus(focus); + } + } + else + { + llwarns << "Can't find a folder to put it in" << llendl; + } + + // remove the "Uploading..." message + LLUploadDialog::modalUploadFinished(); +} + LLAssetUploadResponder::LLAssetUploadResponder(const LLSD &post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type) - : LLHTTPClient::Responder(), - mPostData(post_data), - mVFileID(vfile_id), - mAssetType(asset_type) +: LLHTTPClient::Responder(), + mPostData(post_data), + mVFileID(vfile_id), + mAssetType(asset_type) { if (!gVFS->getExists(vfile_id, asset_type)) { @@ -91,10 +192,10 @@ LLAssetUploadResponder::LLAssetUploadResponder(const LLSD &post_data, LLAssetUploadResponder::LLAssetUploadResponder(const LLSD &post_data, const std::string& file_name, LLAssetType::EType asset_type) - : LLHTTPClient::Responder(), - mPostData(post_data), - mFileName(file_name), - mAssetType(asset_type) +: LLHTTPClient::Responder(), + mPostData(post_data), + mFileName(file_name), + mAssetType(asset_type) { } @@ -119,18 +220,17 @@ void LLAssetUploadResponder::error(U32 statusNum, const std::string& reason) args["FILE"] = (mFileName.empty() ? mVFileID.asString() : mFileName); args["REASON"] = "Error in upload request. Please visit " "http://secondlife.com/support for help fixing this problem."; - LLNotificationsUtil::add("CannotUploadReason", args); + LLNotifications::instance().add("CannotUploadReason", args); break; case 500: default: args["FILE"] = (mFileName.empty() ? mVFileID.asString() : mFileName); args["REASON"] = "The server is experiencing unexpected " "difficulties."; - LLNotificationsUtil::add("CannotUploadReason", args); + LLNotifications::instance().add("CannotUploadReason", args); break; } LLUploadDialog::modalUploadFinished(); - //AIFIXME? LLFilePicker::instance().reset(); // unlock file picker when bulk upload fails } //virtual @@ -182,14 +282,15 @@ void LLAssetUploadResponder::uploadFailure(const LLSD& content) // deal with L$ errors if (reason == "insufficient funds") { - LLFloaterBuyCurrency::buyCurrency("Uploading costs", LLGlobalEconomy::Singleton::getInstance()->getPriceUpload()); + S32 price = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); + LLFloaterBuyCurrency::buyCurrency("Uploading costs", price); } else { LLSD args; args["FILE"] = (mFileName.empty() ? mVFileID.asString() : mFileName); args["REASON"] = content["message"].asString(); - LLNotificationsUtil::add("CannotUploadReason", args); + LLNotifications::instance().add("CannotUploadReason", args); } } @@ -200,124 +301,70 @@ void LLAssetUploadResponder::uploadComplete(const LLSD& content) LLNewAgentInventoryResponder::LLNewAgentInventoryResponder(const LLSD& post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type) -: LLAssetUploadResponder(post_data, vfile_id, asset_type) +: LLAssetUploadResponder(post_data, vfile_id, asset_type) { } -LLNewAgentInventoryResponder::LLNewAgentInventoryResponder(const LLSD& post_data, const std::string& file_name, LLAssetType::EType asset_type) -: LLAssetUploadResponder(post_data, file_name, asset_type) +LLNewAgentInventoryResponder::LLNewAgentInventoryResponder(const LLSD& post_data, + const std::string& file_name, + LLAssetType::EType asset_type) +: LLAssetUploadResponder(post_data, file_name, asset_type) { } +// virtual +void LLNewAgentInventoryResponder::error(U32 statusNum, const std::string& reason) +{ + LLAssetUploadResponder::error(statusNum, reason); + //LLImportColladaAssetCache::getInstance()->assetUploaded(mVFileID, LLUUID(), FALSE); +} + +//virtual +void LLNewAgentInventoryResponder::uploadFailure(const LLSD& content) +{ + LLAssetUploadResponder::uploadFailure(content); + //LLImportColladaAssetCache::getInstance()->assetUploaded(mVFileID, content["new_asset"], FALSE); +} + //virtual void LLNewAgentInventoryResponder::uploadComplete(const LLSD& content) { lldebugs << "LLNewAgentInventoryResponder::result from capabilities" << llendl; - + //std::ostringstream llsdxml; //LLSDSerialize::toXML(content, llsdxml); //llinfos << "upload complete content:\n " << llsdxml.str() << llendl; LLAssetType::EType asset_type = LLAssetType::lookup(mPostData["asset_type"].asString()); LLInventoryType::EType inventory_type = LLInventoryType::lookup(mPostData["inventory_type"].asString()); - S32 expected_upload_cost = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); + S32 expected_upload_cost = 0; // Update L$ and ownership credit information // since it probably changed on the server if (asset_type == LLAssetType::AT_TEXTURE || asset_type == LLAssetType::AT_SOUND || - asset_type == LLAssetType::AT_ANIMATION) + asset_type == LLAssetType::AT_ANIMATION || + asset_type == LLAssetType::AT_MESH) { - LLStatusBar::sendMoneyBalanceRequest(); - - LLSD args; - args["AMOUNT"] = llformat("%d", expected_upload_cost); - args["CURRENCY"] = gHippoGridManager->getConnectedGrid()->getCurrencySymbol(); - LLNotificationsUtil::add("UploadPayment", args); + expected_upload_cost = LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); } - // Actually add the upload to viewer inventory llinfos << "Adding " << content["new_inventory_item"].asUUID() << " " << content["new_asset"].asUUID() << " to inventory." << llendl; - if(mPostData["folder_id"].asUUID().notNull()) - { - //std::ostringstream out; - //LLSDXMLFormatter *formatter = new LLSDXMLFormatter; - //formatter->format(mPostData, out, LLSDFormatter::OPTIONS_PRETTY); - //llinfos << "Post Data: " << out.str() << llendl; - - U32 everyone_perms = PERM_NONE; - U32 group_perms = PERM_NONE; - U32 next_owner_perms = PERM_ALL; - if(content.has("new_next_owner_mask")) - { - // This is a new sim that provides creation perms so use them. - // Do not assume we got the perms we asked for in mPostData - // since the sim may not have granted them all. - everyone_perms = content["new_everyone_mask"].asInteger(); - group_perms = content["new_group_mask"].asInteger(); - next_owner_perms = content["new_next_owner_mask"].asInteger(); - } - else - { - // This old sim doesn't provide creation perms so use old assumption-based perms. - if(mPostData["inventory_type"].asString() != "snapshot") - { - next_owner_perms = PERM_MOVE | PERM_TRANSFER; - } - } - LLPermissions new_perms; - new_perms.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - new_perms.initMasks(PERM_ALL, PERM_ALL, everyone_perms, group_perms, next_owner_perms); - S32 creation_date_now = time_corrected(); - LLPointer item - = new LLViewerInventoryItem(content["new_inventory_item"].asUUID(), - mPostData["folder_id"].asUUID(), - new_perms, - content["new_asset"].asUUID(), - asset_type, - inventory_type, - mPostData["name"].asString(), - mPostData["description"].asString(), - LLSaleInfo::DEFAULT, - LLInventoryItemFlags::II_FLAGS_NONE, - creation_date_now); - gInventory.updateItem(item); - gInventory.notifyObservers(); - - // Show the preview panel for textures and sounds to let - // user know that the image (or snapshot) arrived intact. - LLInventoryView* view = LLInventoryView::getActiveInventory(); - if(view) - { - LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); - - view->getPanel()->setSelection(content["new_inventory_item"].asUUID(), TAKE_FOCUS_NO); - if((LLAssetType::AT_TEXTURE == asset_type || LLAssetType::AT_SOUND == asset_type) - /* AIFIXME: && LLFilePicker::instance().getFileCount() <= FILE_COUNT_DISPLAY_THRESHOLD */) - { - view->getPanel()->openSelected(); - } - //LLInventoryView::dumpSelectionInformation((void*)view); - // restore keyboard focus - gFocusMgr.setKeyboardFocus(focus); - } - } - else - { - llwarns << "Can't find a folder to put it in" << llendl; - } - - // remove the "Uploading..." message - LLUploadDialog::modalUploadFinished(); - -#if 0 // AIFIXME: This needs to be done in some other way. - // *FIX: This is a pretty big hack. What this does is check the - // file picker if there are any more pending uploads. If so, - // upload that file. - std::string next_file = LLFilePicker::instance().getNextFile(); - if(!next_file.empty()) + on_new_single_inventory_upload_complete(asset_type, inventory_type, + mPostData["asset_type"].asString(), + mPostData["folder_id"].asUUID(), + mPostData["name"], + mPostData["description"], + content, expected_upload_cost); + + // continue uploading for bulk uploads + + if (!gUploadQueue.empty()) { + std::string next_file = gUploadQueue.front(); + gUploadQueue.pop_front(); + if (next_file.empty()) return; std::string name = gDirUtilp->getBaseFileName(next_file, true); std::string asset_name = name; @@ -326,36 +373,46 @@ void LLNewAgentInventoryResponder::uploadComplete(const LLSD& content) LLStringUtil::stripNonprintable(asset_name); LLStringUtil::trim(asset_name); - // Continuing the horrible hack above, we need to extract the originally requested permissions data, if any, - // and use them for each next file to be uploaded. Note the requested perms are not the same as the - // granted ones found in the given "content" structure but can still be found in mPostData. -MG - U32 everyone_perms = mPostData.has("everyone_mask") ? mPostData.get("everyone_mask" ).asInteger() : PERM_NONE; - U32 group_perms = mPostData.has("group_mask") ? mPostData.get("group_mask" ).asInteger() : PERM_NONE; - U32 next_owner_perms = mPostData.has("next_owner_mask") ? mPostData.get("next_owner_mask").asInteger() : PERM_NONE; + // We need to extract the originally requested permissions data, if any, + // and use them for each next file to be uploaded. Note the requested + // perms are not the same as the granted ones found in the given + // "content" structure but can still be found in mPostData. -MG + U32 everyone_perms = mPostData.has("everyone_mask") ? + mPostData.get("everyone_mask").asInteger() : + PERM_NONE; + + U32 group_perms = mPostData.has("group_mask") ? + mPostData.get("group_mask").asInteger() : + PERM_NONE; + + U32 next_owner_perms = mPostData.has("next_owner_mask") ? + mPostData.get("next_owner_mask").asInteger() : + PERM_NONE; + std::string display_name = LLStringUtil::null; LLAssetStorage::LLStoreAssetCallback callback = NULL; void *userdata = NULL; - upload_new_resource(next_file, asset_name, asset_name, - 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - next_owner_perms, group_perms, - everyone_perms, display_name, - callback, expected_upload_cost, userdata); + upload_new_resource(next_file, asset_name, asset_name, 0, + LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + next_owner_perms, group_perms, everyone_perms, + display_name, callback, expected_upload_cost, + userdata); } -#endif } LLSendTexLayerResponder::LLSendTexLayerResponder(const LLSD& post_data, - const LLUUID& vfile_id, - LLAssetType::EType asset_type, - LLBakedUploadData * baked_upload_data) - : LLAssetUploadResponder(post_data, vfile_id, asset_type), - mBakedUploadData(baked_upload_data) + const LLUUID& vfile_id, + LLAssetType::EType asset_type, + LLBakedUploadData * baked_upload_data) +: LLAssetUploadResponder(post_data, vfile_id, asset_type), + mBakedUploadData(baked_upload_data) { } LLSendTexLayerResponder::~LLSendTexLayerResponder() { - // mBakedUploadData is normally deleted by calls to LLTexLayerSetBuffer::onTextureUploadComplete() below + // mBakedUploadData is normally deleted by calls to + // LLTexLayerSetBuffer::onTextureUploadComplete() below if (mBakedUploadData) { // ...but delete it in the case where uploadComplete() is never called delete mBakedUploadData; @@ -373,15 +430,18 @@ void LLSendTexLayerResponder::uploadComplete(const LLSD& content) LLUUID new_id = content["new_asset"]; llinfos << "LLSendTexLayerResponder::result from capabilities: " << result << llendl; - if (result == "complete" - && mBakedUploadData != NULL) + if (result == "complete" && mBakedUploadData != NULL) { // Invoke - LLTexLayerSetBuffer::onTextureUploadComplete(new_id, (void*) mBakedUploadData, 0, LL_EXSTAT_NONE); + LLTexLayerSetBuffer::onTextureUploadComplete(new_id, + (void*)mBakedUploadData, + 0, LL_EXSTAT_NONE); mBakedUploadData = NULL; // deleted in onTextureUploadComplete() } else { // Invoke the original callback with an error result - LLTexLayerSetBuffer::onTextureUploadComplete(new_id, (void*) mBakedUploadData, -1, LL_EXSTAT_NONE); + LLTexLayerSetBuffer::onTextureUploadComplete(new_id, + (void*)mBakedUploadData, + -1, LL_EXSTAT_NONE); mBakedUploadData = NULL; // deleted in onTextureUploadComplete() } } @@ -391,7 +451,9 @@ void LLSendTexLayerResponder::error(U32 statusNum, const std::string& reason) llinfos << "status: " << statusNum << " reason: " << reason << llendl; // Invoke the original callback with an error result - LLTexLayerSetBuffer::onTextureUploadComplete(LLUUID(), (void*) mBakedUploadData, -1, LL_EXSTAT_NONE); + LLTexLayerSetBuffer::onTextureUploadComplete(LLUUID(), + (void*)mBakedUploadData, + -1, LL_EXSTAT_NONE); mBakedUploadData = NULL; // deleted in onTextureUploadComplete() } @@ -436,68 +498,64 @@ void LLUpdateAgentInventoryResponder::uploadComplete(const LLSD& content) switch(inventory_type) { case LLInventoryType::IT_NOTECARD: + { + // Update the UI with the new asset. + LLPreviewNotecard* nc; + nc = (LLPreviewNotecard*)LLPreview::find(new_item->getUUID()); + if (nc) { - - // Update the UI with the new asset. - LLPreviewNotecard* nc; - nc = (LLPreviewNotecard*)LLPreview::find(new_item->getUUID()); - if(nc) + // *HACK: we have to delete the asset in the VFS so + // that the viewer will redownload it. This is only + // really necessary if the asset had to be modified by + // the uploader, so this can be optimized away in some + // cases. A better design is to have a new uuid if the + // script actually changed the asset. + if (nc->hasEmbeddedInventory()) { - // *HACK: we have to delete the asset in the VFS so - // that the viewer will redownload it. This is only - // really necessary if the asset had to be modified by - // the uploader, so this can be optimized away in some - // cases. A better design is to have a new uuid if the - // script actually changed the asset. - if(nc->hasEmbeddedInventory()) - { - gVFS->removeFile( - content["new_asset"].asUUID(), - LLAssetType::AT_NOTECARD); - } - nc->refreshFromInventory(); + gVFS->removeFile(content["new_asset"].asUUID(), + LLAssetType::AT_NOTECARD); } + nc->refreshFromInventory(); } break; + } case LLInventoryType::IT_LSL: + { + // Find our window and close it if requested. + LLPreviewLSL* preview = (LLPreviewLSL*)LLPreview::find(item_id); + if (preview) { - // Find our window and close it if requested. - LLPreviewLSL* preview = (LLPreviewLSL*)LLPreview::find(item_id); - if (preview) + // Bytecode save completed + if (content["compiled"]) { - // Bytecode save completed - if (content["compiled"]) - { - preview->callbackLSLCompileSucceeded(); - } - else - { - preview->callbackLSLCompileFailed(content["errors"]); - } + preview->callbackLSLCompileSucceeded(); + } + else + { + preview->callbackLSLCompileFailed(content["errors"]); } } break; - + } case LLInventoryType::IT_GESTURE: + { + // If this gesture is active, then we need to update the in-memory + // active map with the new pointer. + if (LLGestureMgr::getInstance()->isGestureActive(item_id)) { - // If this gesture is active, then we need to update the in-memory - // active map with the new pointer. - if (LLGestureMgr::instance().isGestureActive(item_id)) - { - LLUUID asset_id = new_item->getAssetUUID(); - LLGestureMgr::instance().replaceGesture(item_id, asset_id); - gInventory.notifyObservers(); - } + LLUUID asset_id = new_item->getAssetUUID(); + LLGestureMgr::getInstance()->replaceGesture(item_id, asset_id); + gInventory.notifyObservers(); + } - //gesture will have a new asset_id - LLPreviewGesture* previewp = (LLPreviewGesture*)LLPreview::find(item_id); - if(previewp) - { - previewp->onUpdateSucceeded(); - } - + //gesture will have a new asset_id + LLPreviewGesture* previewp = (LLPreviewGesture*)LLPreview::find(item_id); + if (previewp) + { + previewp->onUpdateSucceeded(); } break; + } case LLInventoryType::IT_WEARABLE: default: break; @@ -506,16 +564,16 @@ void LLUpdateAgentInventoryResponder::uploadComplete(const LLSD& content) LLUpdateTaskInventoryResponder::LLUpdateTaskInventoryResponder(const LLSD& post_data, - const LLUUID& vfile_id, - LLAssetType::EType asset_type) -: LLAssetUploadResponder(post_data, vfile_id, asset_type) + const LLUUID& vfile_id, + LLAssetType::EType asset_type) +: LLAssetUploadResponder(post_data, vfile_id, asset_type) { } LLUpdateTaskInventoryResponder::LLUpdateTaskInventoryResponder(const LLSD& post_data, const std::string& file_name, LLAssetType::EType asset_type) -: LLAssetUploadResponder(post_data, file_name, asset_type) +: LLAssetUploadResponder(post_data, file_name, asset_type) { } @@ -523,7 +581,7 @@ LLUpdateTaskInventoryResponder::LLUpdateTaskInventoryResponder(const LLSD& post_ const std::string& file_name, const LLUUID& queue_id, LLAssetType::EType asset_type) -: LLAssetUploadResponder(post_data, file_name, asset_type), mQueueId(queue_id) +: LLAssetUploadResponder(post_data, file_name, asset_type), mQueueId(queue_id) { } @@ -539,63 +597,464 @@ void LLUpdateTaskInventoryResponder::uploadComplete(const LLSD& content) switch(mAssetType) { case LLAssetType::AT_NOTECARD: + { + // Update the UI with the new asset. + LLPreviewNotecard* nc; + nc = (LLPreviewNotecard*)LLPreview::find(item_id); + if (nc) { - // Update the UI with the new asset. - LLPreviewNotecard* nc; - nc = (LLPreviewNotecard*)LLPreview::find(item_id); - if(nc) + // *HACK: we have to delete the asset in the VFS so + // that the viewer will redownload it. This is only + // really necessary if the asset had to be modified by + // the uploader, so this can be optimized away in some + // cases. A better design is to have a new uuid if the + // script actually changed the asset. + if (nc->hasEmbeddedInventory()) { - // *HACK: we have to delete the asset in the VFS so - // that the viewer will redownload it. This is only - // really necessary if the asset had to be modified by - // the uploader, so this can be optimized away in some - // cases. A better design is to have a new uuid if the - // script actually changed the asset. - if(nc->hasEmbeddedInventory()) - { - gVFS->removeFile( - content["new_asset"].asUUID(), - LLAssetType::AT_NOTECARD); - } - - nc->setAssetId(content["new_asset"].asUUID()); - nc->refreshFromInventory(); + gVFS->removeFile(content["new_asset"].asUUID(), + LLAssetType::AT_NOTECARD); } + nc->setAssetId(content["new_asset"].asUUID()); + nc->refreshFromInventory(); } break; + } case LLAssetType::AT_LSL_TEXT: + { + if (mQueueId.notNull()) { - if(mQueueId.notNull()) + LLFloaterCompileQueue* queue = + (LLFloaterCompileQueue*) LLFloaterScriptQueue::findInstance(mQueueId); + if (NULL != queue) { - LLFloaterCompileQueue* queue = - (LLFloaterCompileQueue*) LLFloaterScriptQueue::findInstance(mQueueId); - if(NULL != queue) - { - queue->removeItemByItemID(item_id); - } + queue->removeItemByItemID(item_id); } - else + } + else + { + LLLiveLSLEditor* preview = LLLiveLSLEditor::find(item_id, task_id); + if (preview) { - LLLiveLSLEditor* preview = LLLiveLSLEditor::find(item_id, task_id); - if (preview) + // Bytecode save completed + if (content["compiled"]) { - // Bytecode save completed - if (content["compiled"]) - { - preview->callbackLSLCompileSucceeded( - task_id, - item_id, - mPostData["is_script_running"]); - } - else - { - preview->callbackLSLCompileFailed(content["errors"]); - } + preview->callbackLSLCompileSucceeded(task_id, + item_id, + mPostData["is_script_running"]); + } + else + { + preview->callbackLSLCompileFailed(content["errors"]); } } } break; - default: - break; + } + default: + break; + } +} + +///////////////////////////////////////////////////// +// LLNewAgentInventoryVariablePriceResponder::Impl // +///////////////////////////////////////////////////// +class LLNewAgentInventoryVariablePriceResponder::Impl +{ +public: + Impl(const LLUUID& vfile_id, + LLAssetType::EType asset_type, + const LLSD& inventory_data) + : mVFileID(vfile_id), + mAssetType(asset_type), + mInventoryData(inventory_data), + mFileName("") + { + if (!gVFS->getExists(vfile_id, asset_type)) + { + llwarns << "LLAssetUploadResponder called with nonexistant " + << "vfile_id " << vfile_id << llendl; + mVFileID.setNull(); + mAssetType = LLAssetType::AT_NONE; + } + } + + Impl(const std::string& file_name, + LLAssetType::EType asset_type, + const LLSD& inventory_data) + : mFileName(file_name), + mAssetType(asset_type), + mInventoryData(inventory_data) + { + mVFileID.setNull(); + } + + std::string getFilenameOrIDString() const + { + return (mFileName.empty() ? mVFileID.asString() : mFileName); + } + + LLUUID getVFileID() const + { + return mVFileID; + } + + std::string getFilename() const + { + return mFileName; + } + + LLAssetType::EType getAssetType() const + { + return mAssetType; + } + + LLInventoryType::EType getInventoryType() const + { + return LLInventoryType::lookup( + mInventoryData["inventory_type"].asString()); + } + + std::string getInventoryTypeString() const + { + return mInventoryData["inventory_type"].asString(); + } + + LLUUID getFolderID() const + { + return mInventoryData["folder_id"].asUUID(); + } + + std::string getItemName() const + { + return mInventoryData["name"].asString(); + } + + std::string getItemDescription() const + { + return mInventoryData["description"].asString(); + } + + void displayCannotUploadReason(const std::string& reason) + { + LLSD args; + args["FILE"] = getFilenameOrIDString(); + args["REASON"] = reason; + + LLNotifications::instance().add("CannotUploadReason", args); + LLUploadDialog::modalUploadFinished(); + } + + void onApplicationLevelError(const LLSD& error) + { + static const std::string _IDENTIFIER = "identifier"; + + static const std::string _INSUFFICIENT_FUNDS = + "NewAgentInventory_InsufficientLindenDollarBalance"; + static const std::string _MISSING_REQUIRED_PARAMETER = + "NewAgentInventory_MissingRequiredParamater"; + static const std::string _INVALID_REQUEST_BODY = + "NewAgentInventory_InvalidRequestBody"; + static const std::string _RESOURCE_COST_DIFFERS = + "NewAgentInventory_ResourceCostDiffers"; + + static const std::string _MISSING_PARAMETER = "missing_parameter"; + static const std::string _INVALID_PARAMETER = "invalid_parameter"; + static const std::string _MISSING_RESOURCE = "missing_resource"; + static const std::string _INVALID_RESOURCE = "invalid_resource"; + + // TODO* Add the other error_identifiers + + std::string error_identifier = error[_IDENTIFIER].asString(); + + // TODO*: Pull these user visible strings from an xml file + // to be localized + if (_INSUFFICIENT_FUNDS == error_identifier) + { + displayCannotUploadReason("You do not have a sufficient L$ balance to complete this upload."); + } + else if (_MISSING_REQUIRED_PARAMETER == error_identifier) + { + // Missing parameters + if (error.has(_MISSING_PARAMETER)) + { + std::string message = "Upload request was missing required parameter '[P]'"; + LLStringUtil::replaceString(message, "[P]", + error[_MISSING_PARAMETER].asString()); + + displayCannotUploadReason(message); + } + else + { + std::string message = "Upload request was missing a required parameter"; + displayCannotUploadReason(message); + } + } + else if (_INVALID_REQUEST_BODY == error_identifier) + { + // Invalid request body, check to see if + // a particular parameter was invalid + if (error.has(_INVALID_PARAMETER)) + { + std::string message = "Upload parameter '[P]' is invalid."; + LLStringUtil::replaceString(message, "[P]", + error[_INVALID_PARAMETER].asString()); + + // See if the server also responds with what resource + // is missing. + if (error.has(_MISSING_RESOURCE)) + { + message += "\nMissing resource '[R]'."; + + LLStringUtil::replaceString(message, "[R]", + error[_MISSING_RESOURCE].asString()); + } + else if (error.has(_INVALID_RESOURCE)) + { + message += "\nInvalid resource '[R]'."; + + LLStringUtil::replaceString(message, "[R]", + error[_INVALID_RESOURCE].asString()); + } + + displayCannotUploadReason(message); + } + else + { + std::string message = "Upload request was malformed"; + displayCannotUploadReason(message); + } + } + else if (_RESOURCE_COST_DIFFERS == error_identifier) + { + displayCannotUploadReason("The resource cost associated with this upload is not consistent with the server."); + } + else + { + displayCannotUploadReason("Unknown Error"); + } + } + + void onTransportError() + { + displayCannotUploadReason("The server is experiencing unexpected difficulties."); + } + + void onTransportError(const LLSD& error) + { + static const std::string _IDENTIFIER = "identifier"; + + static const std::string _SERVER_ERROR_AFTER_CHARGE = + "NewAgentInventory_ServerErrorAfterCharge"; + + std::string error_identifier = error[_IDENTIFIER].asString(); + + // TODO*: Pull the user visible strings from an xml file + // to be localized + + if (_SERVER_ERROR_AFTER_CHARGE == error_identifier) + { + displayCannotUploadReason("The server is experiencing unexpected difficulties. You may have been charged for the upload."); + } + else + { + displayCannotUploadReason("The server is experiencing unexpected difficulties."); + } + } + + bool uploadConfirmationCallback(const LLSD& notification, + const LLSD& response, + boost::intrusive_ptr responder) + { + S32 option = LLNotification::getSelectedOption(notification, response); + std::string confirmation_url = notification["payload"]["confirmation_url"].asString(); + + // Yay! We are confirming or cancelling our upload + if (option == 0) + { + confirmUpload(confirmation_url, responder); + } + + return false; + } + + void confirmUpload(const std::string& confirmation_url, + boost::intrusive_ptr responder) + { + if (getFilename().empty()) + { + // we have no filename, use virtual file ID instead + LLHTTPClient::postFile(confirmation_url, getVFileID(), + getAssetType(), responder); + } + else + { + LLHTTPClient::postFile(confirmation_url, getFilename(), responder); + } + } + +private: + std::string mFileName; + + LLSD mInventoryData; + LLAssetType::EType mAssetType; + LLUUID mVFileID; +}; + +/////////////////////////////////////////////// +// LLNewAgentInventoryVariablePriceResponder // +/////////////////////////////////////////////// +LLNewAgentInventoryVariablePriceResponder::LLNewAgentInventoryVariablePriceResponder(const LLUUID& vfile_id, + LLAssetType::EType asset_type, + const LLSD& inventory_info) +{ + mImpl = new Impl(vfile_id, asset_type, inventory_info); +} + +LLNewAgentInventoryVariablePriceResponder::LLNewAgentInventoryVariablePriceResponder(const std::string& file_name, + LLAssetType::EType asset_type, + const LLSD& inventory_info) +{ + mImpl = new Impl(file_name, asset_type, inventory_info); +} + +LLNewAgentInventoryVariablePriceResponder::~LLNewAgentInventoryVariablePriceResponder() +{ + delete mImpl; +} + +void LLNewAgentInventoryVariablePriceResponder::errorWithContent(U32 statusNum, + const std::string& reason, + const LLSD& content) +{ + LL_DEBUGS("Upload") << "LLNewAgentInventoryVariablePrice::error " + << statusNum << " reason: " << reason << LL_ENDL; + + if (content.has("error")) + { + static const std::string _ERROR = "error"; + mImpl->onTransportError(content[_ERROR]); + } + else + { + mImpl->onTransportError(); + } +} + +void LLNewAgentInventoryVariablePriceResponder::result(const LLSD& content) +{ + // Parse out application level errors and the appropriate + // responses for them + static const std::string _ERROR = "error"; + static const std::string _STATE = "state"; + + static const std::string _COMPLETE = "complete"; + static const std::string _CONFIRM_UPLOAD = "confirm_upload"; + + static const std::string _UPLOAD_PRICE = "upload_price"; + static const std::string _RESOURCE_COST = "resource_cost"; + static const std::string _RSVP = "rsvp"; + + // Check for application level errors + if (content.has(_ERROR)) + { + onApplicationLevelError(content[_ERROR]); + return; + } + + std::string state = content[_STATE]; + LLAssetType::EType asset_type = mImpl->getAssetType(); + + if (_COMPLETE == state) + { + // rename file in VFS with new asset id + if (mImpl->getFilename().empty()) + { + // rename the file in the VFS to the actual asset id + // llinfos << "Changing uploaded asset UUID to " << content["new_asset"].asUUID() << llendl; + gVFS->renameFile(mImpl->getVFileID(), asset_type, + content["new_asset"].asUUID(), asset_type); + } + + on_new_single_inventory_upload_complete(asset_type, + mImpl->getInventoryType(), + mImpl->getInventoryTypeString(), + mImpl->getFolderID(), + mImpl->getItemName(), + mImpl->getItemDescription(), + content, + content[_UPLOAD_PRICE].asInteger()); + + // TODO* Add bulk (serial) uploading or add + // a super class of this that does so + } + else if (_CONFIRM_UPLOAD == state) + { + showConfirmationDialog(content[_UPLOAD_PRICE].asInteger(), + content[_RESOURCE_COST].asInteger(), + content[_RSVP].asString()); + } + else + { + onApplicationLevelError(""); + } +} + +void LLNewAgentInventoryVariablePriceResponder::onApplicationLevelError(const LLSD& error) +{ + mImpl->onApplicationLevelError(error); +} + +void LLNewAgentInventoryVariablePriceResponder::showConfirmationDialog(S32 upload_price, + S32 resource_cost, + const std::string& confirmation_url) +{ + if (0 == upload_price) + { + // don't show confirmation dialog for free uploads, I mean, + // they're free! + + // The creating of a new instrusive_ptr(this) + // creates a new boost::intrusive_ptr + // which is a copy of this. This code is required because + // 'this' is always of type Class* and not the intrusive_ptr, + // and thus, a reference to 'this' is not registered + // by using just plain 'this'. + + // Since LLNewAgentInventoryVariablePriceResponder is a + // reference counted class, it is possible (since the + // reference to a plain 'this' would be missed here) that, + // when using plain ol' 'this', that this object + // would be deleted before the callback is triggered + // and cause sadness. + mImpl->confirmUpload(confirmation_url, + boost::intrusive_ptr(this)); + } + else + { + LLSD substitutions; + LLSD payload; + + substitutions["PRICE"] = upload_price; + + payload["confirmation_url"] = confirmation_url; + + // The creating of a new instrusive_ptr(this) + // creates a new boost::intrusive_ptr + // which is a copy of this. This code is required because + // 'this' is always of type Class* and not the intrusive_ptr, + // and thus, a reference to 'this' is not registered + // by using just plain 'this'. + + // Since LLNewAgentInventoryVariablePriceResponder is a + // reference counted class, it is possible (since the + // reference to a plain 'this' would be missed here) that, + // when using plain ol' 'this', that this object + // would be deleted before the callback is triggered + // and cause sadness. + LLNotifications::instance().add("UploadCostConfirmation", + substitutions, payload, + boost::bind(&LLNewAgentInventoryVariablePriceResponder::Impl::uploadConfirmationCallback, + mImpl, _1, _2, + boost::intrusive_ptr(this))); } } diff --git a/indra/newview/llassetuploadresponders.h b/indra/newview/llassetuploadresponders.h index c08f2999f..2643bc52a 100644 --- a/indra/newview/llassetuploadresponders.h +++ b/indra/newview/llassetuploadresponders.h @@ -34,6 +34,16 @@ #define LL_LLASSETUPLOADRESPONDER_H #include "llhttpclient.h" +#include "llinventory.h" + +void on_new_single_inventory_upload_complete(LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type, + const std::string inventory_type_string, + const LLUUID& item_folder_id, + const std::string& item_name, + const std::string& item_description, + const LLSD& server_response, + S32 upload_price); // Abstract class for supporting asset upload // via capabilities @@ -66,14 +76,50 @@ public: LLNewAgentInventoryResponder(const LLSD& post_data, const LLUUID& vfile_id, LLAssetType::EType asset_type); - LLNewAgentInventoryResponder(const LLSD& post_data, const std::string& file_name, + LLNewAgentInventoryResponder(const LLSD& post_data, + const std::string& file_name, LLAssetType::EType asset_type); + virtual void error(U32 statusNum, const std::string& reason); virtual void uploadComplete(const LLSD& content); + virtual void uploadFailure(const LLSD& content); +}; + +// A base class which goes through and performs some default +// actions for variable price uploads. If more specific actions +// are needed (such as different confirmation messages, etc.) +// the functions onApplicationLevelError and showConfirmationDialog. +class LLNewAgentInventoryVariablePriceResponder : + public LLHTTPClient::Responder +{ +public: + LLNewAgentInventoryVariablePriceResponder(const LLUUID& vfile_id, + LLAssetType::EType asset_type, + const LLSD& inventory_info); + + LLNewAgentInventoryVariablePriceResponder(const std::string& file_name, + LLAssetType::EType asset_type, + const LLSD& inventory_info); + virtual ~LLNewAgentInventoryVariablePriceResponder(); + + void errorWithContent(U32 statusNum, + const std::string& reason, + const LLSD& content); + void result(const LLSD& content); + + virtual void onApplicationLevelError(const LLSD& error); + virtual void showConfirmationDialog(S32 upload_price, + S32 resource_cost, + const std::string& confirmation_url); + +private: + class Impl; + Impl* mImpl; }; class LLBakedUploadData; class LLSendTexLayerResponder : public LLAssetUploadResponder { + LOG_CLASS(LLSendTexLayerResponder); public: LLSendTexLayerResponder(const LLSD& post_data, const LLUUID& vfile_id, diff --git a/indra/newview/llcallbacklist.cpp b/indra/newview/llcallbacklist.cpp index 1bfd2cb9b..d59394083 100644 --- a/indra/newview/llcallbacklist.cpp +++ b/indra/newview/llcallbacklist.cpp @@ -180,12 +180,6 @@ private: bool_func_t mCallable; }; -void doOnIdleRepeating(bool_func_t callable) -{ - OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); -} - #ifdef _DEBUG void test1(void *data) diff --git a/indra/newview/lldrawpoolalpha.h b/indra/newview/lldrawpoolalpha.h index c3aca964d..3a5affca0 100644 --- a/indra/newview/lldrawpoolalpha.h +++ b/indra/newview/lldrawpoolalpha.h @@ -72,7 +72,6 @@ public: virtual void render(S32 pass = 0); /*virtual*/ void prerender(); - void renderGroupAlpha(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture = TRUE); void renderAlpha(U32 mask); void renderAlphaHighlight(U32 mask); diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp new file mode 100644 index 000000000..424b0eee8 --- /dev/null +++ b/indra/newview/llfloatermodelpreview.cpp @@ -0,0 +1,5833 @@ +/** + * @file llfloatermodelpreview.cpp + * @brief LLFloaterModelPreview class implementation + * + * $LicenseInfo:firstyear=2004&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include + +#include "dae.h" +//#include "dom.h" +#include "dom/domAsset.h" +#include "dom/domBind_material.h" +#include "dom/domCOLLADA.h" +#include "dom/domConstants.h" +#include "dom/domController.h" +#include "dom/domEffect.h" +#include "dom/domGeometry.h" +#include "dom/domInstance_geometry.h" +#include "dom/domInstance_material.h" +#include "dom/domInstance_node.h" +#include "dom/domInstance_effect.h" +#include "dom/domMaterial.h" +#include "dom/domMatrix.h" +#include "dom/domNode.h" +#include "dom/domProfile_COMMON.h" +#include "dom/domRotate.h" +#include "dom/domScale.h" +#include "dom/domTranslate.h" +#include "dom/domVisual_scene.h" + +#include "llfloatermodelpreview.h" + +#include "llanimationstates.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfocusmgr.h" +#include "lliconctrl.h" +#include "llimagegl.h" +#include "lljoint.h" +#include "llmatrix4a.h" +#include "llnotifications.h" +#include "llrender.h" +#include "llsdserialize.h" +#include "llsdutil_math.h" +#include "llsliderctrl.h" +#include "llspinctrl.h" +#include "lltextbox.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llvector4a.h" + +#include "glod/glod.h" + +#include "llagent.h" +#include "llcallbacklist.h" +#include "lldrawable.h" +#include "lldrawpoolavatar.h" +#include "llface.h" +#include "llfilepicker.h" +#include "lltoolmgr.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewermenu.h" +#include "llviewernetwork.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewertexturelist.h" +#include "llviewerwindow.h" +#include "llvoavatar.h" +#include "pipeline.h" + +#include "hippogridmanager.h" + +const S32 SLM_SUPPORTED_VERSION = 3; + +//static +S32 LLFloaterModelPreview::sUploadAmount = 10; +LLFloaterModelPreview* LLFloaterModelPreview::sInstance = NULL; +std::list LLModelLoader::sActiveLoaderList; + +// "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. +// To achieve this, RETAIN_COEFFICIENT is used while creating spinner +// and when value is requested from spinner. +const double RETAIN_COEFFICIENT = 100; + +// "Cosine%" decomp parameter has values from 0.9 to 1 by 0.001 +// But according to the UI spec for upload model floater, this parameter +// 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; + +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] = +{ + "lowest_vertices", + "low_vertices", + "medium_vertices", + "high_vertices", + "I went off the end of the lod_vertices_name array. Me so smart." +}; + +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." +}; + +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." +}; + +std::string colladaVersion[VERSIONTYPE_COUNT + 1] = +{ + "1.4.0", + "1.4.1", + "Unsupported" +}; + +#define LL_DEGENERACY_TOLERANCE 1e-7f + +inline F32 dot3fpu(const LLVector4a& a, const LLVector4a& b) +{ + volatile F32 p0 = a[0] * b[0]; + volatile F32 p1 = a[1] * b[1]; + volatile F32 p2 = a[2] * b[2]; + return p0 + p1 + p2; +} + +bool ll_is_degenerate(const LLVector4a& a, + const LLVector4a& b, + const LLVector4a& c, + F32 tolerance = LL_DEGENERACY_TOLERANCE) +{ + // small area check + { + LLVector4a edge1; edge1.setSub(a, b); + LLVector4a edge2; edge2.setSub(a, c); + ////////////////////////////////////////////////////////////////////////// + /// Linden Modified + ////////////////////////////////////////////////////////////////////////// + + // If no one edge is more than 10x longer than any other edge, we weaken + // the tolerance by a factor of 1e-4f. + + LLVector4a edge3; edge3.setSub(c, b); + const F32 len1sq = edge1.dot3(edge1).getF32(); + const F32 len2sq = edge2.dot3(edge2).getF32(); + const F32 len3sq = edge3.dot3(edge3).getF32(); + bool abOK = (len1sq <= 100.f * len2sq) && (len1sq <= 100.f * len3sq); + bool acOK = (len2sq <= 100.f * len1sq) && (len1sq <= 100.f * len3sq); + bool cbOK = (len3sq <= 100.f * len1sq) && (len1sq <= 100.f * len2sq); + if (abOK && acOK && cbOK) + { + tolerance *= 1e-4f; + } + + ////////////////////////////////////////////////////////////////////////// + /// End Modified + ////////////////////////////////////////////////////////////////////////// + + LLVector4a cross; cross.setCross3(edge1, edge2); + + LLVector4a edge1b; edge1b.setSub(b, a); + LLVector4a edge2b; edge2b.setSub(b, c); + LLVector4a crossb; crossb.setCross3(edge1b, edge2b); + + if ((cross.dot3(cross).getF32() < tolerance) || (crossb.dot3(crossb).getF32() < tolerance)) + { + return true; + } + } + + // point triangle distance check + { + LLVector4a Q; Q.setSub(a, b); + LLVector4a R; R.setSub(c, b); + + const F32 QQ = dot3fpu(Q, Q); + const F32 RR = dot3fpu(R, R); + const F32 QR = dot3fpu(R, Q); + + volatile F32 QQRR = QQ * RR; + volatile F32 QRQR = QR * QR; + F32 Det = (QQRR - QRQR); + + if (Det == 0.0f) + { + return true; + } + } + + return false; +} + +bool validate_face(const LLVolumeFace& face) +{ + for (U32 i = 0; i < face.mNumIndices; ++i) + { + if (face.mIndices[i] >= face.mNumVertices) + { + llwarns << "Face has invalid index." << llendl; + return false; + } + } + + if (face.mNumIndices % 3 != 0 || face.mNumIndices == 0) + { + llwarns << "Face has invalid number of indices." << llendl; + return false; + } + + /*const LLVector4a scale(0.5f); + + for (U32 i = 0; i < face.mNumIndices; i+=3) + { + U16 idx1 = face.mIndices[i]; + U16 idx2 = face.mIndices[i + 1]; + U16 idx3 = face.mIndices[i+2]; + + LLVector4a v1; v1.setMul(face.mPositions[idx1], scale); + LLVector4a v2; v2.setMul(face.mPositions[idx2], scale); + LLVector4a v3; v3.setMul(face.mPositions[idx3], scale); + + if (ll_is_degenerate(v1,v2,v3)) + { + llwarns << "Degenerate face found!" << llendl; + return false; + } + }*/ + + return true; +} + +bool validate_model(const LLModel* mdl) +{ + if (mdl->getNumVolumeFaces() == 0) + { + llwarns << "Model has no faces!" << llendl; + return false; + } + + for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i) + { + if (mdl->getVolumeFace(i).mNumVertices == 0) + { + llwarns << "Face has no vertices." << llendl; + return false; + } + + if (mdl->getVolumeFace(i).mNumIndices == 0) + { + llwarns << "Face has no indices." << llendl; + return false; + } + + if (!validate_face(mdl->getVolumeFace(i))) + { + return false; + } + } + + return true; +} + +BOOL stop_gloderror() +{ + GLuint error = glodGetError(); + + if (error != GLOD_NO_ERROR) + { + llwarns << "GLOD error detected, cannot generate LOD: " << std::hex + << error << llendl; + return TRUE; + } + + return FALSE; +} + +LLMeshFilePicker::LLMeshFilePicker(LLModelPreview* mp, S32 lod) +: LLFilePickerThread(LLFilePicker::FFLOAD_COLLADA) +{ + mMP = mp; + mLOD = lod; +} + +void LLMeshFilePicker::notify(const std::string& filename) +{ + mMP->loadModel(mFile, mLOD); +} + +//----------------------------------------------------------------------------- +// LLFloaterModelPreview() +//----------------------------------------------------------------------------- +LLFloaterModelPreview::LLFloaterModelPreview(const std::string& name) +: LLFloaterModelUploadBase(name), + mUploadBtn(NULL), + mCalculateBtn(NULL) +{ + sInstance = this; + mLastMouseX = 0; + mLastMouseY = 0; + mGLName = 0; + mStatusLock = new LLMutex(); + mModelPreview = NULL; + + mLODMode[LLModel::LOD_HIGH] = 0; + for (U32 i = 0; i < LLModel::LOD_HIGH; i++) + { + mLODMode[i] = 1; + } + + LLUICtrlFactory::getInstance()->buildFloater(this, "floater_model_preview.xml"); +} + +//----------------------------------------------------------------------------- +// postBuild() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelPreview::postBuild() +{ + if (!LLFloater::postBuild()) + { + return FALSE; + } + + childSetCommitCallback("cancel_btn", onCancel, this); + childSetCommitCallback("crease_angle", onGenerateNormalsCommit, this); + childSetCommitCallback("gen_normals", toggleGenerateNormals, this); + + childSetCommitCallback("lod_generate", onAutoFillCommit, this); + + for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod) + { + LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[lod]); + lod_source_combo->setCommitCallback(onLoDSourceCommit); + lod_source_combo->setCallbackUserData((void*)lod); + lod_source_combo->setCurrentByIndex(mLODMode[lod]); + + childSetAction("lod_browse_" + lod_name[lod], onBrowseLOD, (void*)lod); + childSetCommitCallback("lod_mode_" + lod_name[lod], onLODParamCommit, (void*)lod); + childSetCommitCallback("lod_error_threshold_" + lod_name[lod], onLODParamCommit, (void*)lod); + childSetCommitCallback("lod_triangle_limit_" + lod_name[lod], onLODParamCommitEnforceTriLimit, (void*)lod); + } + + childSetCommitCallback("upload_skin", toggleCalculateButtonCallBack, this); + childSetCommitCallback("upload_joints", toggleCalculateButtonCallBack, this); + childSetCommitCallback("upload_textures", toggleCalculateButtonCallBack, this); + + childSetTextArg("status", "[STATUS]", getString("status_idle")); + + childSetAction("ok_btn", onUpload, this); + childDisable("ok_btn"); + + childSetAction("reset_btn", onReset, this); + + childSetCommitCallback("preview_lod_combo", onPreviewLODCommit, this); + + childSetCommitCallback("upload_skin", onUploadSkinCommit, this); + childSetCommitCallback("upload_joints", onUploadJointsCommit, this); + + childSetCommitCallback("import_scale", onImportScaleCommit, this); + childSetCommitCallback("pelvis_offset", onPelvisOffsetCommit, this); + + childSetCommitCallback("show_edges", onViewOptionChecked, this); + childSetCommitCallback("show_physics", onViewOptionChecked, this); + childSetCommitCallback("show_textures", onViewOptionChecked, this); + childSetCommitCallback("show_skin_weight", onViewOptionChecked, this); + childSetCommitCallback("show_joint_positions", onViewOptionChecked, this); + + childDisable("upload_skin"); + childDisable("upload_joints"); + + initDecompControls(); + + LLView* preview_panel = getChild("preview_panel"); + + mPreviewRect = preview_panel->getRect(); + + initModelPreview(); + + std::string validate_url; + if (gHippoGridManager->getCurrentGrid()->isSecondLife()) + { + if (LLViewerLogin::getInstance()->isInProductionGrid()) + { + validate_url = "http://secondlife.com/my/account/mesh.php"; + } + else + { + validate_url = "http://secondlife.aditi.lindenlab.com/my/account/mesh.php"; + } + } + else + { + // Let's point to a known valid website page for OpenSim grids... + validate_url = gHippoGridManager->getCurrentGrid()->getLoginUri(); + } + getChild("warning_message")->setTextArg("[VURL]", validate_url); + + mUploadBtn = getChild("ok_btn"); + mCalculateBtn = getChild("calculate_btn"); + childSetAction("calculate_btn", onClickCalculateBtn, this); + + toggleCalculateButton(true); + + return TRUE; +} + +//----------------------------------------------------------------------------- +// LLFloaterModelPreview() +//----------------------------------------------------------------------------- +LLFloaterModelPreview::~LLFloaterModelPreview() +{ + sInstance = NULL; + + if (mModelPreview) + { + delete mModelPreview; + } + + if (mGLName) + { + LLImageGL::deleteTextures(1, &mGLName); + } + + delete mStatusLock; + mStatusLock = NULL; +} + +void LLFloaterModelPreview::initModelPreview() +{ + if (mModelPreview) + { + delete mModelPreview; + } + + mModelPreview = new LLModelPreview(512, 512, this); + mModelPreview->setPreviewTarget(16.f); + mModelPreview->setDetailsCallback(boost::bind(&LLFloaterModelPreview::setDetails, this, _1, _2, _3, _4, _5)); + mModelPreview->setModelUpdatedCallback(boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this, _1)); +} + +void LLFloaterModelPreview::onViewOptionChecked(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* self = (LLFloaterModelPreview*)userdata; + if (self && self->mModelPreview) + { + self->mModelPreview->mViewOption[ctrl->getName()] = !self->mModelPreview->mViewOption[ctrl->getName()]; + + self->mModelPreview->refresh(); + } +} + +bool LLFloaterModelPreview::isViewOptionChecked(const LLSD& userdata) +{ + if (mModelPreview) + { + return mModelPreview->mViewOption[userdata.asString()]; + } + + return false; +} + +bool LLFloaterModelPreview::isViewOptionEnabled(const LLSD& userdata) +{ + return childIsEnabled(userdata.asString()); +} + +void LLFloaterModelPreview::setViewOptionEnabled(const std::string& option, bool enabled) +{ + childSetEnabled(option, enabled); +} + +void LLFloaterModelPreview::enableViewOption(const std::string& option) +{ + setViewOptionEnabled(option, true); +} + +void LLFloaterModelPreview::disableViewOption(const std::string& option) +{ + setViewOptionEnabled(option, false); +} + +void LLFloaterModelPreview::loadModel(S32 lod) +{ + mModelPreview->mLoading = true; + + (new LLMeshFilePicker(mModelPreview, lod))->getFile(); +} + +void LLFloaterModelPreview::loadModel(S32 lod, const std::string& file_name, bool force_disable_slm) +{ + mModelPreview->mLoading = true; + + mModelPreview->loadModel(file_name, lod, force_disable_slm); +} + +//static +void LLFloaterModelPreview::onClickCalculateBtn(void* userdata) +{ + LLFloaterModelPreview *self = (LLFloaterModelPreview *)userdata; + if (!self || !self->mModelPreview) return; + + self->mModelPreview->rebuildUploadData(); + + bool upload_skinweights = self->childGetValue("upload_skin").asBoolean(); + bool upload_joint_positions = self->childGetValue("upload_joints").asBoolean(); + + self->mUploadModelUrl.clear(); + + gMeshRepo.uploadModel(self->mModelPreview->mUploadData, + self->mModelPreview->mPreviewScale, + self->childGetValue("upload_textures").asBoolean(), + upload_skinweights, + upload_joint_positions, + self->mUploadModelUrl, + false, + self->getWholeModelFeeObserverHandle()); + + self->toggleCalculateButton(false); + self->mUploadBtn->setEnabled(false); +} + +//static +void LLFloaterModelPreview::onImportScaleCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview *fp = (LLFloaterModelPreview *)userdata; + + if (!fp->mModelPreview) + { + return; + } + + fp->mModelPreview->mDirty = true; + + fp->toggleCalculateButton(true); + + fp->mModelPreview->refresh(); +} + +//static +void LLFloaterModelPreview::onPelvisOffsetCommit(LLUICtrl*, void* userdata) +{ + LLFloaterModelPreview *fp =(LLFloaterModelPreview*)userdata; + + if (!fp->mModelPreview) + { + return; + } + + fp->mModelPreview->mDirty = true; + + fp->toggleCalculateButton(true); + + fp->mModelPreview->refresh(); +} + +//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; + + if (!fp->mModelPreview) + { + return; + } + + S32 which_mode = 0; + + LLComboBox* combo = (LLComboBox*) ctrl; + + which_mode = NUM_LOD - 1 - combo->getFirstSelectedIndex(); // combo box list of lods is in reverse order + + fp->mModelPreview->setPreviewLOD(which_mode); +} + +//static +void LLFloaterModelPreview::onGenerateNormalsCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata; + + fp->mModelPreview->generateNormals(); +} + +//static +void LLFloaterModelPreview::toggleGenerateNormals(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata; + + bool enabled = fp->childGetValue("gen_normals").asBoolean(); + fp->childSetEnabled("crease_angle", enabled); +} + +//static +void LLFloaterModelPreview::onExplodeCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* fp = LLFloaterModelPreview::sInstance; + + fp->mModelPreview->refresh(); +} + +//static +void LLFloaterModelPreview::onAutoFillCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata; + + fp->mModelPreview->genLODs(); +} + +//static +void LLFloaterModelPreview::onLODParamCommit(LLUICtrl* ctrl, void* userdata) +{ + S32 lod = (S32)userdata; + if (sInstance) + { + sInstance->mModelPreview->onLODParamCommit(lod, false); + } +} + +//static +void LLFloaterModelPreview::onLODParamCommitEnforceTriLimit(LLUICtrl* ctrl, void* userdata) +{ + S32 lod = (S32)userdata; + if (sInstance) + { + sInstance->mModelPreview->onLODParamCommit(lod, true); + } +} + +//----------------------------------------------------------------------------- +// draw() +//----------------------------------------------------------------------------- +void LLFloaterModelPreview::draw() +{ + LLFloater::draw(); + LLRect r = getRect(); + + mModelPreview->update(); + + if (!mModelPreview->mLoading) + { + if (mModelPreview->getLoadState() > LLModelLoader::ERROR_PARSING) + { + childSetTextArg("status", "[STATUS]", + getString(LLModel::getStatusString(mModelPreview->getLoadState() - LLModelLoader::ERROR_PARSING))); + } + else + if (mModelPreview->getLoadState() == LLModelLoader::ERROR_PARSING) + { + childSetTextArg("status", "[STATUS]", + getString("status_parse_error")); + toggleCalculateButton(false); + } + else + { + childSetTextArg("status", "[STATUS]", + getString("status_idle")); + } + } + + childSetTextArg("prim_cost", "[PRIM_COST]", + llformat("%d", mModelPreview->mResourceCost)); + childSetTextArg("description_label", "[TEXTURES]", + llformat("%d", mModelPreview->mTextureSet.size())); + + if (mModelPreview) + { + gGL.color3f(1.f, 1.f, 1.f); + + gGL.getTexUnit(0)->bind(mModelPreview); + + LLView* preview_panel = getChild("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); + } +} + +//----------------------------------------------------------------------------- +// handleMouseDown() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelPreview::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (mPreviewRect.pointInRect(x, y)) + { + bringToFront(x, y); + gFocusMgr.setMouseCapture(this); + gViewerWindow->hideCursor(); + mLastMouseX = x; + mLastMouseY = y; + return TRUE; + } + + return LLFloater::handleMouseDown(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleMouseUp() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelPreview::handleMouseUp(S32 x, S32 y, MASK mask) +{ + gFocusMgr.setMouseCapture(NULL); + gViewerWindow->showCursor(); + return LLFloater::handleMouseUp(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleHover() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelPreview::handleHover(S32 x, S32 y, MASK mask) +{ + MASK local_mask = mask & ~MASK_ALT; + + if (mModelPreview && hasMouseCapture()) + { + if (local_mask == MASK_PAN) + { + // pan here + mModelPreview->pan((F32)(x - mLastMouseX) * -0.005f, + (F32)(y - mLastMouseY) * -0.005f); + } + else if (local_mask == MASK_ORBIT) + { + F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; + F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f; + + mModelPreview->rotate(yaw_radians, pitch_radians); + } + else + { + + F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; + F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f; + + mModelPreview->rotate(yaw_radians, 0.f); + mModelPreview->zoom(zoom_amt); + } + + mModelPreview->refresh(); + + LLUI::setCursorPositionLocal(this, mLastMouseX, mLastMouseY); + } + + if (!mPreviewRect.pointInRect(x, y) || !mModelPreview) + { + return LLFloater::handleHover(x, y, mask); + } + else if (local_mask == MASK_ORBIT) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); + } + else if (local_mask == MASK_PAN) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); + } + else + { + gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); + } + + return TRUE; +} + +//----------------------------------------------------------------------------- +// handleScrollWheel() +//----------------------------------------------------------------------------- +BOOL LLFloaterModelPreview::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if (mPreviewRect.pointInRect(x, y) && mModelPreview) + { + mModelPreview->zoom((F32)clicks * -0.2f); + mModelPreview->refresh(); + } + + return TRUE; +} + +/*virtual*/ +void LLFloaterModelPreview::onOpen(const LLSD& key) +{ + requestAgentUploadPermissions(); +} + +//static +void LLFloaterModelPreview::onPhysicsParamCommit(LLUICtrl* ctrl, void* data) +{ + if (LLConvexDecomposition::getInstance() == NULL) + { + llinfos << "Convex decomposition tool is a stub on this platform. cannot get decomp." << llendl; + return; + } + + if (sInstance) + { + LLCDParam* param = (LLCDParam*) data; + std::string name(param->mName); + + LLSD value = ctrl->getValue(); + + if ("Retain%" == name) + { + value = ctrl->getValue().asReal() / RETAIN_COEFFICIENT; + } + + sInstance->mDecompParams[name] = value; + + if (name == "Simplify Method") + { + bool show_retain = false; + bool show_detail = true; + + if (ctrl->getValue().asInteger() == 0) + { + show_retain = true; + show_detail = false; + } + + sInstance->childSetVisible("Retain%", show_retain); + sInstance->childSetVisible("Retain%_label", show_retain); + + sInstance->childSetVisible("Detail Scale", show_detail); + sInstance->childSetVisible("Detail Scale label", show_detail); + } + } +} + +//static +void LLFloaterModelPreview::onPhysicsStageExecute(LLUICtrl* ctrl, void* data) +{ + LLCDStageData* stage_data = (LLCDStageData*) data; + std::string stage = stage_data->mName; + + if (sInstance) + { + if (!sInstance->mCurRequest.empty()) + { + llinfos << "Decomposition request still pending." << llendl; + return; + } + + if (sInstance->mModelPreview) + { + for (S32 i = 0; i < sInstance->mModelPreview->mModel[LLModel::LOD_PHYSICS].size(); ++i) + { + LLModel* mdl = sInstance->mModelPreview->mModel[LLModel::LOD_PHYSICS][i]; + DecompRequest* request = new DecompRequest(stage, mdl); + sInstance->mCurRequest.insert(request); + gMeshRepo.mDecompThread->submitRequest(request); + } + } + + if (stage == "Decompose") + { + sInstance->setStatusMessage(sInstance->getString("decomposing")); + sInstance->childSetVisible("Decompose", false); + sInstance->childSetVisible("decompose_cancel", true); + sInstance->childDisable("Simplify"); + } + else if (stage == "Simplify") + { + sInstance->setStatusMessage(sInstance->getString("simplifying")); + sInstance->childSetVisible("Simplify", false); + sInstance->childSetVisible("simplify_cancel", true); + sInstance->childDisable("Decompose"); + } + } +} + +//static +void LLFloaterModelPreview::onPhysicsBrowse(LLUICtrl* ctrl, void* userdata) +{ + sInstance->loadModel(LLModel::LOD_PHYSICS); +} + +//static +void LLFloaterModelPreview::onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata) +{ + S32 num_modes = 4; + S32 which_mode = 3; + static S32 previous_mode = which_mode; + + LLCtrlSelectionInterface* iface = sInstance->childGetSelectionInterface("physics_lod_combo"); + if (iface) + { + which_mode = iface->getFirstSelectedIndex(); + } + + S32 file_mode = iface->getItemCount() - 1; + bool file_browse = which_mode == file_mode; + bool lod_to_file = file_browse && (previous_mode != file_mode); + bool file_to_lod = !file_browse && (previous_mode == file_mode); + + if (!lod_to_file) + { + which_mode = num_modes - which_mode; + sInstance->mModelPreview->setPhysicsFromLOD(which_mode); + } + + if (lod_to_file || file_to_lod) + { + LLModelPreview *model_preview = sInstance->mModelPreview; + if (model_preview) + { + model_preview->refresh(); + model_preview->updateStatusMessages(); + } + } + + previous_mode = which_mode; +} + +//static +void LLFloaterModelPreview::onCancel(LLUICtrl* ctrl, void* data) +{ + if (sInstance) + { + sInstance->close(); + } +} + +//static +void LLFloaterModelPreview::onPhysicsStageCancel(LLUICtrl* ctrl, void*data) +{ + if (sInstance) + { + for (std::set >::iterator iter = sInstance->mCurRequest.begin(); + iter != sInstance->mCurRequest.end(); ++iter) + { + DecompRequest* req = *iter; + req->mContinue = 0; + } + + sInstance->mCurRequest.clear(); + + if (sInstance->mModelPreview) + { + sInstance->mModelPreview->updateStatusMessages(); + } + } +} + +void LLFloaterModelPreview::initDecompControls() +{ + LLSD key; + + childSetCommitCallback("simplify_cancel", onPhysicsStageCancel, NULL); + childSetCommitCallback("decompose_cancel", onPhysicsStageCancel, NULL); + + childSetCommitCallback("physics_lod_combo", onPhysicsUseLOD, NULL); + childSetCommitCallback("physics_browse", onPhysicsBrowse, NULL); + + static const LLCDStageData* stage = NULL; + static S32 stage_count = 0; + + if (!stage && LLConvexDecomposition::getInstance() != NULL) + { + stage_count = LLConvexDecomposition::getInstance()->getStages(&stage); + } + + static const LLCDParam* param = NULL; + static S32 param_count = 0; + if (!param && LLConvexDecomposition::getInstance() != NULL) + { + param_count = LLConvexDecomposition::getInstance()->getParameters(¶m); + } + + for (S32 j = stage_count - 1; j >= 0; --j) + { + childSetCommitCallback(stage[j].mName, onPhysicsStageExecute, (void*) &stage[j]); + + gMeshRepo.mDecompThread->mStageID[stage[j].mName] = j; + // protected against stub by stage_count being 0 for stub above + LLConvexDecomposition::getInstance()->registerCallback(j, LLPhysicsDecomp::llcdCallback); + + //llinfos << "Physics decomp stage " << stage[j].mName << " (" << j << ") parameters:" << llendl; + //llinfos << "------------------------------------" << llendl; + + for (S32 i = 0; i < param_count; ++i) + { + if (param[i].mStage != j) + { + continue; + } + + 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; + + LLUICtrl* ctrl = getChild(name); + if (LLSliderCtrl* slider = dynamic_cast(ctrl)) + { + slider->setMinValue(param[i].mDetails.mRange.mLow.mFloat); + slider->setMaxValue(param[i].mDetails.mRange.mHigh.mFloat); + slider->setIncrement(param[i].mDetails.mRange.mDelta.mFloat); + slider->setValue(param[i].mDefault.mFloat); + slider->setCommitCallback(onPhysicsParamCommit); + slider->setCallbackUserData((void*) ¶m[i]); + } + else if (LLSpinCtrl* spinner = dynamic_cast(ctrl)) + { + bool is_retain_ctrl = "Retain%" == name; + double coefficient = is_retain_ctrl ? RETAIN_COEFFICIENT : 1.f; + + spinner->setMinValue(param[i].mDetails.mRange.mLow.mFloat * coefficient); + spinner->setMaxValue(param[i].mDetails.mRange.mHigh.mFloat * coefficient); + spinner->setIncrement(param[i].mDetails.mRange.mDelta.mFloat * coefficient); + spinner->setValue(param[i].mDefault.mFloat * coefficient); + spinner->setCommitCallback(onPhysicsParamCommit); + spinner->setCallbackUserData((void*) ¶m[i]); + } + else if (LLComboBox* combo_box = dynamic_cast(ctrl)) + { + float min = param[i].mDetails.mRange.mLow.mFloat; + float max = param[i].mDetails.mRange.mHigh.mFloat; + float delta = param[i].mDetails.mRange.mDelta.mFloat; + + if ("Cosine%" == name) + { + createSmoothComboBox(combo_box, min, max); + } + else + { + for (float value = min; value <= max; value += delta) + { + std::string label = llformat("%.1f", value); + combo_box->add(label, value, ADD_BOTTOM, true); + } + combo_box->setValue(param[i].mDefault.mFloat); + } + + combo_box->setCommitCallback(onPhysicsParamCommit); + combo_box->setCallbackUserData((void*) ¶m[i]); + } + } + 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; + + LLUICtrl* ctrl = getChild(name); + if (LLSliderCtrl* slider = dynamic_cast(ctrl)) + { + slider->setMinValue(param[i].mDetails.mRange.mLow.mIntOrEnumValue); + slider->setMaxValue(param[i].mDetails.mRange.mHigh.mIntOrEnumValue); + slider->setIncrement(param[i].mDetails.mRange.mDelta.mIntOrEnumValue); + slider->setValue(param[i].mDefault.mIntOrEnumValue); + slider->setCommitCallback(onPhysicsParamCommit); + slider->setCallbackUserData((void*) ¶m[i]); + } + else if (LLComboBox* combo_box = dynamic_cast(ctrl)) + { + for (S32 k = param[i].mDetails.mRange.mLow.mIntOrEnumValue; + k <= param[i].mDetails.mRange.mHigh.mIntOrEnumValue; + k += param[i].mDetails.mRange.mDelta.mIntOrEnumValue) + { + std::string name = llformat("%.1d", k); + combo_box->add(name, k, ADD_BOTTOM, true); + } + combo_box->setValue(param[i].mDefault.mIntOrEnumValue); + combo_box->setCommitCallback(onPhysicsParamCommit); + combo_box->setCallbackUserData((void*) ¶m[i]); + } + } + 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* check_box = getChild(name); + if (check_box) + { + check_box->setValue(param[i].mDefault.mBool); + check_box->setCommitCallback(onPhysicsParamCommit); + check_box->setCallbackUserData((void*) ¶m[i]); + } + } + else if (param[i].mType == LLCDParam::LLCD_ENUM) + { + mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); + //llinfos << "Type: enum, Default: " << param[i].mDefault.mIntOrEnumValue << llendl; + + { //plug into combo box + + //llinfos << "Accepted values: " << llendl; + LLComboBox* combo_box = getChild(name); + 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; + + std::string name(param[i].mDetails.mEnumValues.mEnumsArray[k].mName); + combo_box->add(name, + LLSD::Integer(param[i].mDetails.mEnumValues.mEnumsArray[k].mValue)); + } + combo_box->setValue(param[i].mDefault.mIntOrEnumValue); + combo_box->setCommitCallback(onPhysicsParamCommit); + combo_box->setCallbackUserData((void*) ¶m[i]); + } + + //llinfos << "----" << llendl; + } + //llinfos << "-----------------------------" << llendl; + } + } + + childSetCommitCallback("physics_explode", LLFloaterModelPreview::onExplodeCommit, this); +} + +void LLFloaterModelPreview::createSmoothComboBox(LLComboBox* combo_box, float min, float max) +{ + float delta = (max - min) / SMOOTH_VALUES_NUMBER; + int ilabel = 0; + + combo_box->add("0 (none)", ADD_BOTTOM, true); + + for (float value = min + delta; value < max; value += delta) + { + std::string label = (++ilabel == SMOOTH_VALUES_NUMBER) ? "10 (max)" : llformat("%.1d", ilabel); + combo_box->add(label, value, ADD_BOTTOM, true); + } +} + +//----------------------------------------------------------------------------- +// onMouseCaptureLost() +//----------------------------------------------------------------------------- +// static +void LLFloaterModelPreview::onMouseCaptureLostModelPreview(LLMouseHandler* handler) +{ + gViewerWindow->showCursor(); +} + +//----------------------------------------------------------------------------- +// LLModelLoader +//----------------------------------------------------------------------------- +LLModelLoader::LLModelLoader(std::string filename, + S32 lod, LLModelPreview* preview, + JointTransformMap& jointMap, + std::deque& jointsFromNodes) +: mJointList(jointMap), + mJointsFromNode(jointsFromNodes), + LLThread("Model Loader"), + mFilename(filename), + mLod(lod), + mPreview(preview), + mFirstTransform(TRUE), + mNumOfFetchingTextures(0) +{ + mJointMap["mPelvis"] = "mPelvis"; + mJointMap["mTorso"] = "mTorso"; + mJointMap["mChest"] = "mChest"; + mJointMap["mNeck"] = "mNeck"; + mJointMap["mHead"] = "mHead"; + mJointMap["mSkull"] = "mSkull"; + mJointMap["mEyeRight"] = "mEyeRight"; + mJointMap["mEyeLeft"] = "mEyeLeft"; + mJointMap["mCollarLeft"] = "mCollarLeft"; + mJointMap["mShoulderLeft"] = "mShoulderLeft"; + mJointMap["mElbowLeft"] = "mElbowLeft"; + mJointMap["mWristLeft"] = "mWristLeft"; + mJointMap["mCollarRight"] = "mCollarRight"; + mJointMap["mShoulderRight"] = "mShoulderRight"; + mJointMap["mElbowRight"] = "mElbowRight"; + mJointMap["mWristRight"] = "mWristRight"; + mJointMap["mHipRight"] = "mHipRight"; + mJointMap["mKneeRight"] = "mKneeRight"; + mJointMap["mAnkleRight"] = "mAnkleRight"; + mJointMap["mFootRight"] = "mFootRight"; + mJointMap["mToeRight"] = "mToeRight"; + mJointMap["mHipLeft"] = "mHipLeft"; + mJointMap["mKneeLeft"] = "mKneeLeft"; + mJointMap["mAnkleLeft"] = "mAnkleLeft"; + mJointMap["mFootLeft"] = "mFootLeft"; + mJointMap["mToeLeft"] = "mToeLeft"; + + mJointMap["avatar_mPelvis"] = "mPelvis"; + mJointMap["avatar_mTorso"] = "mTorso"; + mJointMap["avatar_mChest"] = "mChest"; + mJointMap["avatar_mNeck"] = "mNeck"; + mJointMap["avatar_mHead"] = "mHead"; + mJointMap["avatar_mSkull"] = "mSkull"; + mJointMap["avatar_mEyeRight"] = "mEyeRight"; + mJointMap["avatar_mEyeLeft"] = "mEyeLeft"; + mJointMap["avatar_mCollarLeft"] = "mCollarLeft"; + mJointMap["avatar_mShoulderLeft"] = "mShoulderLeft"; + mJointMap["avatar_mElbowLeft"] = "mElbowLeft"; + mJointMap["avatar_mWristLeft"] = "mWristLeft"; + mJointMap["avatar_mCollarRight"] = "mCollarRight"; + mJointMap["avatar_mShoulderRight"] = "mShoulderRight"; + mJointMap["avatar_mElbowRight"] = "mElbowRight"; + mJointMap["avatar_mWristRight"] = "mWristRight"; + mJointMap["avatar_mHipRight"] = "mHipRight"; + mJointMap["avatar_mKneeRight"] = "mKneeRight"; + mJointMap["avatar_mAnkleRight"] = "mAnkleRight"; + mJointMap["avatar_mFootRight"] = "mFootRight"; + mJointMap["avatar_mToeRight"] = "mToeRight"; + mJointMap["avatar_mHipLeft"] = "mHipLeft"; + mJointMap["avatar_mKneeLeft"] = "mKneeLeft"; + mJointMap["avatar_mAnkleLeft"] = "mAnkleLeft"; + mJointMap["avatar_mFootLeft"] = "mFootLeft"; + mJointMap["avatar_mToeLeft"] = "mToeLeft"; + + mJointMap["hip"] = "mPelvis"; + mJointMap["abdomen"] = "mTorso"; + mJointMap["chest"] = "mChest"; + mJointMap["neck"] = "mNeck"; + mJointMap["head"] = "mHead"; + mJointMap["figureHair"] = "mSkull"; + mJointMap["lCollar"] = "mCollarLeft"; + mJointMap["lShldr"] = "mShoulderLeft"; + mJointMap["lForeArm"] = "mElbowLeft"; + mJointMap["lHand"] = "mWristLeft"; + mJointMap["rCollar"] = "mCollarRight"; + mJointMap["rShldr"] = "mShoulderRight"; + mJointMap["rForeArm"] = "mElbowRight"; + mJointMap["rHand"] = "mWristRight"; + mJointMap["rThigh"] = "mHipRight"; + mJointMap["rShin"] = "mKneeRight"; + mJointMap["rFoot"] = "mFootRight"; + mJointMap["lThigh"] = "mHipLeft"; + mJointMap["lShin"] = "mKneeLeft"; + mJointMap["lFoot"] = "mFootLeft"; + + if (mPreview) + { + //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) + mTrySLM = gSavedSettings.getBOOL("MeshImportUseSLM") && mPreview->mUploadData.empty(); + mPreview->setLoadState(STARTING); + } + else + { + mTrySLM = false; + } + + assert_main_thread(); + sActiveLoaderList.push_back(this); +} + +LLModelLoader::~LLModelLoader() +{ + assert_main_thread(); + sActiveLoaderList.remove(this); +} + +void stretch_extents(LLModel* model, LLMatrix4a& mat, LLVector4a& min, LLVector4a& max, BOOL& first_transform) +{ + LLVector4a box[] = + { + LLVector4a(-1, 1,-1), + LLVector4a(-1, 1, 1), + LLVector4a(-1,-1,-1), + LLVector4a(-1,-1, 1), + LLVector4a(1, 1,-1), + LLVector4a(1, 1, 1), + LLVector4a(1,-1,-1), + LLVector4a(1,-1, 1), + }; + + for (S32 j = 0; j < model->getNumVolumeFaces(); ++j) + { + const LLVolumeFace& face = model->getVolumeFace(j); + + LLVector4a center; + center.setAdd(face.mExtents[0], face.mExtents[1]); + center.mul(0.5f); + LLVector4a size; + size.setSub(face.mExtents[1],face.mExtents[0]); + size.mul(0.5f); + + for (U32 i = 0; i < 8; i++) + { + LLVector4a t; + t.setMul(size, box[i]); + t.add(center); + + LLVector4a v; + + mat.affineTransform(t, v); + + if (first_transform) + { + first_transform = FALSE; + min = max = v; + } + else + { + update_min_max(min, max, v); + } + } + } +} + +void stretch_extents(LLModel* model, LLMatrix4& mat, LLVector3& min, LLVector3& max, BOOL& first_transform) +{ + LLVector4a mina, maxa; + LLMatrix4a mata; + + mata.loadu(mat); + mina.load3(min.mV); + maxa.load3(max.mV); + + stretch_extents(model, mata, mina, maxa, first_transform); + + min.set(mina.getF32ptr()); + max.set(maxa.getF32ptr()); +} + +void LLModelLoader::run() +{ + doLoadModel(); + doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this)); +} + +bool LLModelLoader::doLoadModel() +{ + //first, look for a .slm file of the same name that was modified later + //than the .dae + + if (mTrySLM) + { + std::string filename = mFilename; + + std::string::size_type i = filename.rfind("."); + if (i != std::string::npos) + { + filename.replace(i, filename.size() - 1, ".slm"); + llstat slm_status; + if (LLFile::stat(filename, &slm_status) == 0) + { //slm file exists + llstat dae_status; + if (LLFile::stat(mFilename, &dae_status) != 0 || + dae_status.st_mtime < slm_status.st_mtime) + { + if (loadFromSLM(filename)) + { //slm successfully loaded, if this fails, fall through and + //try loading from dae + + mLod = -1; //successfully loading from an slm implicitly sets all + //LoDs + return true; + } + } + } + } + } + + //no suitable slm exists, load from the .dae file + DAE dae; + domCOLLADA* dom = dae.open(mFilename); + + if (!dom) + { + llinfos<<" Error with dae - traditionally indicates a corrupt file."<getVersion(); + //0=1.4 + //1=1.4.1 + //2=Currently unsupported, however may work + if (docVersion > 1) + { + docVersion = VERSIONTYPE_COUNT; + } + llinfos<<"Dae version "<getElementCount(NULL, COLLADA_TYPE_MESH); + + daeDocument* doc = dae.getDoc(mFilename); + if (!doc) + { + llwarns << "can't find internal doc" << llendl; + return false; + } + + daeElement* root = doc->getDomRoot(); + if (!root) + { + llwarns << "document has no root" << llendl; + return false; + } + + //Verify some basic properties of the dae + //1. Basic validity check on controller + U32 controllerCount = (int) db->getElementCount(NULL, "controller"); + bool result = false; + for (int i = 0; i < controllerCount; ++i) + { + domController* pController = NULL; + db->getElement((daeElement**) &pController, i , NULL, "controller"); + result = mPreview->verifyController(pController); + if (!result) + { + setLoadState(ERROR_PARSING); + return true; + } + } + + //get unit scale + mTransform.setIdentity(); + + domAsset::domUnit* unit = daeSafeCast(root->getDescendant(daeElement::matchType(domAsset::domUnit::ID()))); + + if (unit) + { + F32 meter = unit->getMeter(); + mTransform.mMatrix[0][0] = meter; + mTransform.mMatrix[1][1] = meter; + mTransform.mMatrix[2][2] = meter; + } + + //get up axis rotation + LLMatrix4 rotation; + + domUpAxisType up = UPAXISTYPE_Y_UP; // default is Y_UP + domAsset::domUp_axis* up_axis = + daeSafeCast(root->getDescendant(daeElement::matchType(domAsset::domUp_axis::ID()))); + + if (up_axis) + { + up = up_axis->getValue(); + } + + if (up == UPAXISTYPE_X_UP) + { + rotation.initRotation(0.0f, 90.0f * DEG_TO_RAD, 0.0f); + } + else if (up == UPAXISTYPE_Y_UP) + { + rotation.initRotation(90.0f * DEG_TO_RAD, 0.0f, 0.0f); + } + + rotation *= mTransform; + mTransform = rotation; + + for (daeInt idx = 0; idx < count; ++idx) + { //build map of domEntities to LLModel + domMesh* mesh = NULL; + db->getElement((daeElement**) &mesh, idx, NULL, COLLADA_TYPE_MESH); + + if (mesh) + { + LLPointer model = LLModel::loadModelFromDomMesh(mesh); + + if (model->getStatus() != LLModel::NO_ERRORS) + { + setLoadState(ERROR_PARSING + model->getStatus()); + return false; //abort + } + + if (model.notNull() && validate_model(model)) + { + mModelList.push_back(model); + mModel[mesh] = model; + } + } + } + + count = db->getElementCount(NULL, COLLADA_TYPE_SKIN); + for (daeInt idx = 0; idx < count; ++idx) + { //add skinned meshes as instances + domSkin* skin = NULL; + db->getElement((daeElement**) &skin, idx, NULL, COLLADA_TYPE_SKIN); + + if (skin) + { + domGeometry* geom = daeSafeCast(skin->getSource().getElement()); + + if (geom) + { + domMesh* mesh = geom->getMesh(); + if (mesh) + { + LLModel* model = mModel[mesh]; + if (model) + { + LLVector3 mesh_scale_vector; + LLVector3 mesh_translation_vector; + model->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + + LLMatrix4 normalized_transformation; + normalized_transformation.setTranslation(mesh_translation_vector); + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= normalized_transformation; + normalized_transformation = mesh_scale; + + glh::matrix4f inv_mat((F32*) normalized_transformation.mMatrix); + inv_mat = inv_mat.inverse(); + LLMatrix4 inverse_normalized_transformation(inv_mat.m); + + domSkin::domBind_shape_matrix* bind_mat = skin->getBind_shape_matrix(); + + if (bind_mat) + { //get bind shape matrix + domFloat4x4& dom_value = bind_mat->getValue(); + + LLMeshSkinInfo& skin_info = model->mSkinInfo; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + skin_info.mBindShapeMatrix.mMatrix[i][j] = dom_value[i + j*4]; + } + } + + LLMatrix4 trans = normalized_transformation; + trans *= skin_info.mBindShapeMatrix; + skin_info.mBindShapeMatrix = trans; + } + + //Some collada setup for accessing the skeleton + daeElement* pElement = 0; + dae.getDatabase()->getElement(&pElement, 0, 0, "skeleton"); + + //Try to get at the skeletal instance controller + domInstance_controller::domSkeleton* pSkeleton = daeSafeCast(pElement); + bool missingSkeletonOrScene = false; + + //If no skeleton, do a breadth-first search to get at specific joints + bool rootNode = false; + bool skeletonWithNoRootNode = false; + + //Need to test for a skeleton that does not have a root node + //This occurs when your instance controller does not have an associated scene + if (pSkeleton) + { + daeElement* pSkeletonRootNode = pSkeleton->getValue().getElement(); + if (pSkeletonRootNode) + { + rootNode = true; + } + else + { + skeletonWithNoRootNode = true; + } + + } + if (!pSkeleton || !rootNode) + { + daeElement* pScene = root->getDescendant("visual_scene"); + if (!pScene) + { + llwarns<<"No visual scene - unable to parse bone offsets "< > children = pScene->getChildren(); + S32 childCount = children.getCount(); + + //Process any children that are joints + //Not all children are joints, some code be ambient lights, cameras, geometry etc.. + for (S32 i = 0; i < childCount; ++i) + { + domNode* pNode = daeSafeCast(children[i]); + if (isNodeAJoint(pNode)) + { + processJointNode(pNode, mJointList); + } + } + } + } + else + //Has Skeleton + { + //Get the root node of the skeleton + daeElement* pSkeletonRootNode = pSkeleton->getValue().getElement(); + if (pSkeletonRootNode) + { + //Once we have the root node - start acccessing it's joint components + const int jointCnt = mJointMap.size(); + std::map :: const_iterator jointIt = mJointMap.begin(); + + //Loop over all the possible joints within the .dae - using the allowed joint list in the ctor. + for (int i = 0; i < jointCnt; ++i, ++jointIt) + { + //Build a joint for the resolver to work with + char str[64]={0}; + sprintf(str,"./%s",(*jointIt).first.c_str()); + //llwarns<<"Joint "<< str <(resolver.getElement()); + if (pJoint) + { + //Pull out the translate id and store it in the jointTranslations map + daeSIDResolver jointResolverA(pJoint, "./translate"); + domTranslate* pTranslateA = daeSafeCast(jointResolverA.getElement()); + daeSIDResolver jointResolverB(pJoint, "./location"); + domTranslate* pTranslateB = daeSafeCast(jointResolverB.getElement()); + + LLMatrix4 workingTransform; + + //Translation via SID + if (pTranslateA) + { + extractTranslation(pTranslateA, workingTransform); + } + else + if (pTranslateB) + { + extractTranslation(pTranslateB, workingTransform); + } + else + { + //Translation via child from element + daeElement* pTranslateElement = getChildFromElement(pJoint, "translate"); + if (pTranslateElement && pTranslateElement->typeID() != domTranslate::ID()) + { + llwarns<< "The found element is not a translate node" <getJoints(); + + domInputLocal_Array& joint_input = joints->getInput_array(); + + for (size_t i = 0; i < joint_input.getCount(); ++i) + { + domInputLocal* input = joint_input.get(i); + xsNMTOKEN semantic = input->getSemantic(); + + if (strcmp(semantic, COMMON_PROFILE_INPUT_JOINT) == 0) + { //found joint source, fill model->mJointMap and model->mSkinInfo.mJointNames + daeElement* elem = input->getSource().getElement(); + + domSource* source = daeSafeCast(elem); + if (source) + { + domName_array* names_source = source->getName_array(); + + if (names_source) + { + domListOfNames &names = names_source->getValue(); + + for (size_t j = 0; j < names.getCount(); ++j) + { + std::string name(names.get(j)); + if (mJointMap.find(name) != mJointMap.end()) + { + name = mJointMap[name]; + } + model->mSkinInfo.mJointNames.push_back(name); + model->mSkinInfo.mJointMap[name] = j; + } + } + else + { + domIDREF_array* names_source = source->getIDREF_array(); + if (names_source) + { + xsIDREFS& names = names_source->getValue(); + + for (size_t j = 0; j < names.getCount(); ++j) + { + std::string name(names.get(j).getID()); + if (mJointMap.find(name) != mJointMap.end()) + { + name = mJointMap[name]; + } + model->mSkinInfo.mJointNames.push_back(name); + model->mSkinInfo.mJointMap[name] = j; + } + } + } + } + } + else if (strcmp(semantic, COMMON_PROFILE_INPUT_INV_BIND_MATRIX) == 0) + { //found inv_bind_matrix array, fill model->mInvBindMatrix + domSource* source = daeSafeCast(input->getSource().getElement()); + if (source) + { + domFloat_array* t = source->getFloat_array(); + if (t) + { + domListOfFloats& transform = t->getValue(); + S32 count = transform.getCount()/16; + + for (S32 k = 0; k < count; ++k) + { + LLMatrix4 mat; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + mat.mMatrix[i][j] = transform[k*16 + i + j*4]; + } + } + + model->mSkinInfo.mInvBindMatrix.push_back(mat); + } + } + } + } + } + + //Now that we've parsed the joint array, let's determine if we have a full rig + //(which means we have all the joint sthat are required for an avatar versus + //a skinned asset attached to a node in a file that contains an entire skeleton, + //but does not use the skeleton). + buildJointToNodeMappingFromScene(root); + mPreview->critiqueRigForUploadApplicability(model->mSkinInfo.mJointNames); + + if (!missingSkeletonOrScene) + { + //Set the joint translations on the avatar - if it's a full mapping + //The joints are reset in the dtor + if (mPreview->getRigWithSceneParity()) + { + std::map :: const_iterator masterJointIt = mJointMap.begin(); + std::map :: const_iterator masterJointItEnd = mJointMap.end(); + for ( ;masterJointIt!=masterJointItEnd;++masterJointIt) + { + std::string lookingForJoint = (*masterJointIt).first.c_str(); + + if (mJointList.find(lookingForJoint) != mJointList.end()) + { + //llinfos<<"joint "<getPreviewAvatar()->getJoint(lookingForJoint); + if (pJoint) + { + pJoint->storeCurrentXform(jointTransform.getTranslation()); + } + else + { + //Most likely an error in the asset. + llwarns << "Tried to apply joint position from .dae, but it did not exist in the avatar rig." + << llendl; + } + } + } + } + } //missingSkeletonOrScene + + //We need to construct the alternate bind matrix (which contains the new joint positions) + //in the same order as they were stored in the joint buffer. The joints associated + //with the skeleton are not stored in the same order as they are in the exported joint buffer. + //This remaps the skeletal joints to be in the same order as the joints stored in the model. + std::vector :: const_iterator jointIt = model->mSkinInfo.mJointNames.begin(); + const int jointCnt = model->mSkinInfo.mJointNames.size(); + for (int i = 0; i < jointCnt; ++i, ++jointIt) + { + std::string lookingForJoint = (*jointIt).c_str(); + //Look for the joint xform that we extracted from the skeleton, using the jointIt as the key + //and store it in the alternate bind matrix + if (mJointList.find(lookingForJoint) != mJointList.end()) + { + LLMatrix4 jointTransform = mJointList[lookingForJoint]; + LLMatrix4 newInverse = model->mSkinInfo.mInvBindMatrix[i]; + newInverse.setTranslation(mJointList[lookingForJoint].getTranslation()); + model->mSkinInfo.mAlternateBindMatrix.push_back(newInverse); + } + else + { + llwarns << "Possibly misnamed/missing joint [" + << lookingForJoint.c_str() << " ] " + << llendl; + } + } + + //grab raw position array + + domVertices* verts = mesh->getVertices(); + if (verts) + { + domInputLocal_Array& inputs = verts->getInput_array(); + for (size_t i = 0; + i < inputs.getCount() && model->mPosition.empty(); + ++i) + { + if (strcmp(inputs[i]->getSemantic(), COMMON_PROFILE_INPUT_POSITION) == 0) + { + domSource* pos_source = daeSafeCast(inputs[i]->getSource().getElement()); + if (pos_source) + { + domFloat_array* pos_array = pos_source->getFloat_array(); + if (pos_array) + { + domListOfFloats& pos = pos_array->getValue(); + + for (size_t j = 0; j < pos.getCount(); j += 3) + { + if (pos.getCount() <= j+2) + { + llerrs << "Invalid position array size." << llendl; + } + + LLVector3 v(pos[j], pos[j + 1], pos[j+2]); + + //transform from COLLADA space to volume space + v = v * inverse_normalized_transformation; + + model->mPosition.push_back(v); + } + } + } + } + } + } + + //grab skin weights array + domSkin::domVertex_weights* weights = skin->getVertex_weights(); + if (weights) + { + domInputLocalOffset_Array& inputs = weights->getInput_array(); + domFloat_array* vertex_weights = NULL; + for (size_t i = 0; i < inputs.getCount(); ++i) + { + if (strcmp(inputs[i]->getSemantic(), COMMON_PROFILE_INPUT_WEIGHT) == 0) + { + domSource* weight_source = daeSafeCast(inputs[i]->getSource().getElement()); + if (weight_source) + { + vertex_weights = weight_source->getFloat_array(); + } + } + } + + if (vertex_weights) + { + domListOfFloats& w = vertex_weights->getValue(); + domListOfUInts& vcount = weights->getVcount()->getValue(); + domListOfInts& v = weights->getV()->getValue(); + + U32 c_idx = 0; + for (size_t vc_idx = 0; vc_idx < vcount.getCount(); ++vc_idx) + { //for each vertex + daeUInt count = vcount[vc_idx]; + + //create list of weights that influence this vertex + LLModel::weight_list weight_list; + + for (daeUInt i = 0; i < count; ++i) + { //for each weight + daeInt joint_idx = v[c_idx++]; + daeInt weight_idx = v[c_idx++]; + + if (joint_idx == -1) + { + //ignore bindings to bind_shape_matrix + continue; + } + + F32 weight_value = w[weight_idx]; + + weight_list.push_back(LLModel::JointWeight(joint_idx, weight_value)); + } + + //sort by joint weight + std::sort(weight_list.begin(), + weight_list.end(), + LLModel::CompareWeightGreater()); + + std::vector wght; + + F32 total = 0.f; + + for (U32 i = 0; + i < llmin((U32) 4, (U32) weight_list.size()); + ++i) + { //take up to 4 most significant weights + if (weight_list[i].mWeight > 0.f) + { + wght.push_back(weight_list[i]); + total += weight_list[i].mWeight; + } + } + + F32 scale = 1.f/total; + if (scale != 1.f) + { //normalize weights + for (U32 i = 0; i < wght.size(); ++i) + { + wght[i].mWeight *= scale; + } + } + + model->mSkinWeights[model->mPosition[vc_idx]] = wght; + } + + //add instance to scene for this model + + LLMatrix4 transformation = mTransform; + // adjust the transformation to compensate for mesh normalization + + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transformation; + transformation = mesh_translation; + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transformation; + transformation = mesh_scale; + + std::map materials; + for (U32 i = 0; i < model->mMaterialList.size(); ++i) + { + materials[model->mMaterialList[i]] = LLImportMaterial(); + } + mScene[transformation].push_back(LLModelInstance(model, + model->mLabel, + transformation, + materials)); + stretch_extents(model, transformation, + mExtents[0], mExtents[1], + mFirstTransform); + } + } + } + } + } + } + } + + daeElement* scene = root->getDescendant("visual_scene"); + + if (!scene) + { + llwarns << "document has no visual_scene" << llendl; + setLoadState(ERROR_PARSING); + return true; + } + + setLoadState(DONE); + + bool badElement = false; + + processElement(scene, badElement); + + if (badElement) + { + setLoadState(ERROR_PARSING); + } + + return true; +} + +void LLModelLoader::setLoadState(U32 state) +{ + if (mPreview) + { + mPreview->setLoadState(state); + } +} + +bool LLModelLoader::loadFromSLM(const std::string& filename) +{ + //only need to populate mScene with data from slm + llstat stat; + + if (LLFile::stat(filename, &stat)) + { //file does not exist + return false; + } + + S32 file_size = (S32) stat.st_size; + + llifstream ifstream(filename, std::ifstream::in | std::ifstream::binary); + LLSD data; + LLSDSerialize::fromBinary(data, ifstream, file_size); + ifstream.close(); + + //build model list for each LoD + model_list model[LLModel::NUM_LODS]; + + if (data["version"].asInteger() != SLM_SUPPORTED_VERSION) + { //unsupported version + return false; + } + + LLSD& mesh = data["mesh"]; + + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + + for (S32 lod = 0; lod < LLModel::NUM_LODS; ++lod) + { + for (U32 i = 0; i < mesh.size(); ++i) + { + std::stringstream str(mesh[i].asString()); + LLPointer loaded_model = new LLModel(volume_params, (F32) lod); + if (loaded_model->loadModel(str)) + { + loaded_model->mLocalID = i; + model[lod].push_back(loaded_model); + + if (lod == LLModel::LOD_HIGH && !loaded_model->mSkinInfo.mJointNames.empty()) + { + //check to see if rig is valid + mPreview->critiqueRigForUploadApplicability(loaded_model->mSkinInfo.mJointNames); + } + } + } + } + + if (model[LLModel::LOD_HIGH].empty()) + { //failed to load high lod + return false; + } + + // Set name. + std::string name = data["name"]; + if (!name.empty()) + { + model[LLModel::LOD_HIGH][0]->mLabel = name; + } + + //load instance list + model_instance_list instance_list; + + LLSD& instance = data["instance"]; + + for (U32 i = 0; i < instance.size(); ++i) + { + //deserialize instance list + instance_list.push_back(LLModelInstance(instance[i])); + + //match up model instance pointers + S32 idx = instance_list[i].mLocalMeshID; + + for (U32 lod = 0; lod < LLModel::NUM_LODS; ++lod) + { + if (!model[lod].empty()) + { + instance_list[i].mLOD[lod] = model[lod][idx]; + } + } + + instance_list[i].mModel = model[LLModel::LOD_HIGH][idx]; + } + + //convert instance_list to mScene + mFirstTransform = TRUE; + for (U32 i = 0; i < instance_list.size(); ++i) + { + LLModelInstance& cur_instance = instance_list[i]; + mScene[cur_instance.mTransform].push_back(cur_instance); + stretch_extents(cur_instance.mModel, cur_instance.mTransform, mExtents[0], mExtents[1], mFirstTransform); + } + + setLoadState(DONE); + + return true; +} + +//static +bool LLModelLoader::isAlive(LLModelLoader* loader) +{ + if (!loader) + { + return false; + } + + std::list::iterator iter = sActiveLoaderList.begin(); + for ( ; iter != sActiveLoaderList.end() && (*iter) != loader; ++iter); + + return *iter == loader; +} + +void LLModelLoader::loadModelCallback() +{ + assert_main_thread(); + + if (mPreview) + { + mPreview->loadModelCallback(mLod); + } + + while (!isStopped()) + { //wait until this thread is stopped before deleting self + apr_sleep(100); + } + + //doubel check if "this" is valid before deleting it, in case it is aborted during running. + if (!isAlive(this)) + { + return; + } + + //cleanup model loader + if (mPreview) + { + mPreview->mModelLoader = NULL; + } + + delete this; +} + +//----------------------------------------------------------------------------- +// buildJointToNodeMappingFromScene() +//----------------------------------------------------------------------------- +void LLModelLoader::buildJointToNodeMappingFromScene(daeElement* pRoot) +{ + daeElement* pScene = pRoot->getDescendant("visual_scene"); + if (pScene) + { + daeTArray< daeSmartRef > children = pScene->getChildren(); + S32 childCount = children.getCount(); + for (S32 i = 0; i < childCount; ++i) + { + domNode* pNode = daeSafeCast(children[i]); + processJointToNodeMapping(pNode); + } + } +} + +//----------------------------------------------------------------------------- +// processJointToNodeMapping() +//----------------------------------------------------------------------------- +void LLModelLoader::processJointToNodeMapping(domNode* pNode) +{ + if (isNodeAJoint(pNode)) + { + //1.Store the parent + std::string nodeName = pNode->getName(); + if (!nodeName.empty()) + { + mJointsFromNode.push_front(pNode->getName()); + } + //2. Handle the kiddo's + processChildJoints(pNode); + } + else + { + //Determine if the're any children wrt to this failed node. + //This occurs when an armature is exported and ends up being what essentially amounts to + //as the root for the visual_scene + if (pNode) + { + processChildJoints(pNode); + } + else + { + llinfos<<"Node is NULL"< > childOfChild = pParentNode->getChildren(); + S32 childOfChildCount = childOfChild.getCount(); + for (S32 i = 0; i < childOfChildCount; ++i) + { + domNode* pChildNode = daeSafeCast(childOfChild[i]); + if (pChildNode) + { + processJointToNodeMapping(pChildNode); + } + } +} + +//----------------------------------------------------------------------------- +// critiqueRigForUploadApplicability() +//----------------------------------------------------------------------------- +void LLModelPreview::critiqueRigForUploadApplicability(const std::vector &jointListFromAsset) +{ + critiqueJointToNodeMappingFromScene(); + + //Determines the following use cases for a rig: + //1. It is suitable for upload with skin weights & joint positions, or + //2. It is suitable for upload as standard av with just skin weights + + bool isJointPositionUploadOK = isRigSuitableForJointPositionUpload(jointListFromAsset); + bool isRigLegacyOK = isRigLegacy(jointListFromAsset); + + //It's OK that both could end up being true, both default to false + if (isJointPositionUploadOK) + { + setRigValidForJointPositionUpload(true); + } + + if (isRigLegacyOK) + { + setLegacyRigValid(true); + } + +} + +//----------------------------------------------------------------------------- +// critiqueJointToNodeMappingFromScene() +//----------------------------------------------------------------------------- +void LLModelPreview::critiqueJointToNodeMappingFromScene(void) +{ + //Do the actual nodes back the joint listing from the dae? + //if yes then this is a fully rigged asset, otherwise it's just a partial rig + + std::deque::iterator jointsFromNodeIt = mJointsFromNode.begin(); + std::deque::iterator jointsFromNodeEndIt = mJointsFromNode.end(); + bool result = true; + + if (!mJointsFromNode.empty()) + { + for ( ;jointsFromNodeIt!=jointsFromNodeEndIt;++jointsFromNodeIt) + { + std::string name = *jointsFromNodeIt; + if (mJointTransformMap.find(name) != mJointTransformMap.end()) + { + continue; + } + else + { + llinfos<<"critiqueJointToNodeMappingFromScene is missing a: "< &jointListFromAsset) +{ + //No joints in asset + if (jointListFromAsset.size() == 0) + { + return false; + } + + bool result = false; + + std::deque :: const_iterator masterJointIt = mMasterLegacyJointList.begin(); + std::deque :: const_iterator masterJointEndIt = mMasterLegacyJointList.end(); + + std::vector :: const_iterator modelJointIt = jointListFromAsset.begin(); + std::vector :: const_iterator modelJointItEnd = jointListFromAsset.end(); + + for ( ;masterJointIt!=masterJointEndIt;++masterJointIt) + { + result = false; + modelJointIt = jointListFromAsset.begin(); + + for ( ;modelJointIt!=modelJointItEnd; ++modelJointIt) + { + if (*masterJointIt == *modelJointIt) + { + result = true; + break; + } + } + if (!result) + { + llinfos<<" Asset did not contain the joint (if you're u/l a fully rigged asset w/joint positions - it is required)." << *masterJointIt<< llendl; + break; + } + } + return result; +} + +//----------------------------------------------------------------------------- +// isRigSuitableForJointPositionUpload() +//----------------------------------------------------------------------------- +bool LLModelPreview::isRigSuitableForJointPositionUpload(const std::vector &jointListFromAsset) +{ + bool result = false; + + std::deque :: const_iterator masterJointIt = mMasterJointList.begin(); + std::deque :: const_iterator masterJointEndIt = mMasterJointList.end(); + + std::vector :: const_iterator modelJointIt = jointListFromAsset.begin(); + std::vector :: const_iterator modelJointItEnd = jointListFromAsset.end(); + + for ( ;masterJointIt!=masterJointEndIt;++masterJointIt) + { + result = false; + modelJointIt = jointListFromAsset.begin(); + + for ( ;modelJointIt!=modelJointItEnd; ++modelJointIt) + { + if (*masterJointIt == *modelJointIt) + { + result = true; + break; + } + } + if (!result) + { + llinfos<<" Asset did not contain the joint (if you're u/l a fully rigged asset w/joint positions - it is required)." << *masterJointIt<< llendl; + break; + } + } + return result; +} + +//called in the main thread +void LLModelLoader::loadTextures() +{ + BOOL is_paused = isPaused(); + pause(); //pause the loader + + for (scene::iterator iter = mScene.begin(); iter != mScene.end(); ++iter) + { + for (U32 i = 0; i < iter->second.size(); i++) + { + for (std::map::iterator j = iter->second[i].mMaterial.begin(); + j != iter->second[i].mMaterial.end(); ++j) + { + LLImportMaterial& material = j->second; + + if (!material.mDiffuseMapFilename.empty()) + { + material.mDiffuseMap = + LLViewerTextureManager::getFetchedTextureFromUrl("file://" + material.mDiffuseMapFilename, TRUE, LLViewerTexture::BOOST_PREVIEW); + material.mDiffuseMap->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, TRUE, FALSE, mPreview, NULL, FALSE); + material.mDiffuseMap->forceToSaveRawImage(0, F32_MAX); + mNumOfFetchingTextures++; + } + } + } + } + + if (!is_paused) + { + unpause(); + } +} + +//----------------------------------------------------------------------------- +// isNodeAJoint() +//----------------------------------------------------------------------------- +bool LLModelLoader::isNodeAJoint(domNode* pNode) +{ + if (!pNode) + { + llinfos<<"Created node is NULL"<getName() == NULL) + { + llinfos<<"Parsed node has no name "<getId()) + { + llinfos<<"Parsed node ID: "<getId()<getName()) != mJointMap.end()) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// verifyCount +//----------------------------------------------------------------------------- +bool LLModelPreview::verifyCount(int expected, int result) +{ + if (expected != result) + { + llinfos<< "Error: (expected/got)"<getSkin(); + + if (pSkin) + { + xsAnyURI & uri = pSkin->getSource(); + domElement* pElement = uri.getElement(); + + if (!pElement) + { + llinfos<<"Can't resolve skin source"<getTypeName(); + if (stricmp(type_str, "geometry") == 0) + { + //Skin is reference directly by geometry and get the vertex count from skin + domSkin::domVertex_weights* pVertexWeights = pSkin->getVertex_weights(); + U32 vertexWeightsCount = pVertexWeights->getCount(); + domGeometry* pGeometry = (domGeometry*) (domElement*) uri.getElement(); + domMesh* pMesh = pGeometry->getMesh(); + + if (pMesh) + { + //Get vertex count from geometry + domVertices* pVertices = pMesh->getVertices(); + if (!pVertices) + { + llinfos<<"No vertices!"<getInput_array()[0]->getSource(); + domSource* pSource = (domSource*) (domElement*) src.getElement(); + U32 verticesCount = pSource->getTechnique_common()->getAccessor()->getCount(); + result = verifyCount(verticesCount, vertexWeightsCount); + if (!result) + { + return result; + } + } + } + + U32 vcountCount = (U32) pVertexWeights->getVcount()->getValue().getCount(); + result = verifyCount(vcountCount, vertexWeightsCount); + if (!result) + { + return result; + } + + domInputLocalOffset_Array& inputs = pVertexWeights->getInput_array(); + U32 sum = 0; + for (size_t i = 0; i < vcountCount; i++) + { + sum += pVertexWeights->getVcount()->getValue()[i]; + } + result = verifyCount(sum * inputs.getCount(), (domInt) pVertexWeights->getV()->getValue().getCount()); + } + } + + return result; +} + +//----------------------------------------------------------------------------- +// extractTranslation() +//----------------------------------------------------------------------------- +void LLModelLoader::extractTranslation(domTranslate* pTranslate, LLMatrix4& transform) +{ + domFloat3 jointTrans = pTranslate->getValue(); + LLVector3 singleJointTranslation(jointTrans[0], jointTrans[1], jointTrans[2]); + transform.setTranslation(singleJointTranslation); +} + +//----------------------------------------------------------------------------- +// extractTranslationViaElement() +//----------------------------------------------------------------------------- +void LLModelLoader::extractTranslationViaElement(daeElement* pTranslateElement, LLMatrix4& transform) +{ + if (pTranslateElement) + { + domTranslate* pTranslateChild = dynamic_cast(pTranslateElement); + domFloat3 translateChild = pTranslateChild->getValue(); + LLVector3 singleJointTranslation(translateChild[0], translateChild[1], translateChild[2]); + transform.setTranslation(singleJointTranslation); + } +} + +//----------------------------------------------------------------------------- +// extractTranslationViaSID() +//----------------------------------------------------------------------------- +void LLModelLoader::extractTranslationViaSID(daeElement* pElement, LLMatrix4& transform) +{ + if (pElement) + { + daeSIDResolver resolver(pElement, "./transform"); + domMatrix* pMatrix = daeSafeCast(resolver.getElement()); + //We are only extracting out the translational component atm + LLMatrix4 workingTransform; + if (pMatrix) + { + 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]; + } + } + LLVector3 trans = workingTransform.getTranslation(); + transform.setTranslation(trans); + } + } + else + { + llwarns<<"Element is nonexistent - empty/unsupported node."<getName() == NULL) + { + llwarns << "nameless node, can't process" << llendl; + return; + } + + //llwarns<<"ProcessJointNode# Node:" <getName()<(jointResolverA.getElement()); + daeSIDResolver jointResolverB(pNode, "./location"); + domTranslate* pTranslateB = daeSafeCast(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()) + { + //llwarns<< "The found element is not a translate node" <(jointResolver.getElement()); + if (pMatrix) + { + //llinfos<<"A matrix SID was however found!"<getValue(); + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + workingTransform.mMatrix[i][j] = domArray[i + j*4]; + } + } + } + else + { + llwarns<< "The found element is not translate or matrix node - most likely a corrupt export!" <getName() ] = workingTransform; + + //2. handle the nodes children + + //Gather and handle the incoming nodes children + daeTArray< daeSmartRef > childOfChild = pNode->getChildren(); + S32 childOfChildCount = childOfChild.getCount(); + + for (S32 i = 0; i < childOfChildCount; ++i) + { + domNode* pChildNode = daeSafeCast(childOfChild[i]); + if (pChildNode) + { + processJointNode(pChildNode, jointTransforms); + } + } +} + +//----------------------------------------------------------------------------- +// getChildFromElement() +//----------------------------------------------------------------------------- +daeElement* LLModelLoader::getChildFromElement(daeElement* pElement, std::string const & name) +{ + daeElement* pChildOfElement = pElement->getChild(name.c_str()); + if (pChildOfElement) + { + return pChildOfElement; + } + llwarns<< "Could not find a child [" << name << "] for the element: \"" << pElement->getAttribute("id") << "\"" << llendl; + return NULL; +} + +void LLModelLoader::processElement(daeElement* element, bool& badElement) +{ + LLMatrix4 saved_transform = mTransform; + + domTranslate* translate = daeSafeCast(element); + if (translate) + { + domFloat3 dom_value = translate->getValue(); + + LLMatrix4 translation; + translation.setTranslation(LLVector3(dom_value[0], dom_value[1], dom_value[2])); + + translation *= mTransform; + mTransform = translation; + } + + domRotate* rotate = daeSafeCast(element); + if (rotate) + { + domFloat4 dom_value = rotate->getValue(); + + LLMatrix4 rotation; + rotation.initRotTrans(dom_value[3] * DEG_TO_RAD, LLVector3(dom_value[0], dom_value[1], dom_value[2]), LLVector3(0, 0, 0)); + + rotation *= mTransform; + mTransform = rotation; + } + + domScale* scale = daeSafeCast(element); + if (scale) + { + domFloat3 dom_value = scale->getValue(); + + LLVector3 scale_vector = LLVector3(dom_value[0], dom_value[1], dom_value[2]); + scale_vector.abs(); // Set all values positive, since we don't currently support mirrored meshes + LLMatrix4 scaling; + scaling.initScale(scale_vector); + + scaling *= mTransform; + mTransform = scaling; + } + + domMatrix* matrix = daeSafeCast(element); + if (matrix) + { + domFloat4x4 dom_value = matrix->getValue(); + + LLMatrix4 matrix_transform; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + matrix_transform.mMatrix[i][j] = dom_value[i + j*4]; + } + } + + matrix_transform *= mTransform; + mTransform = matrix_transform; + } + + domInstance_geometry* instance_geo = daeSafeCast(element); + if (instance_geo) + { + domGeometry* geo = daeSafeCast(instance_geo->getUrl().getElement()); + if (geo) + { + domMesh* mesh = daeSafeCast(geo->getDescendant(daeElement::matchType(domMesh::ID()))); + if (mesh) + { + LLModel* model = mModel[mesh]; + if (model) + { + LLMatrix4 transformation = mTransform; + + if (mTransform.determinant() < 0) + { //negative scales are not supported + llinfos << "Negative scale detected, unsupported transform. domInstance_geometry: " << LLModel::getElementLabel(instance_geo) << llendl; + badElement = true; + } + + std::map materials = getMaterials(model, instance_geo); + + // adjust the transformation to compensate for mesh normalization + LLVector3 mesh_scale_vector; + LLVector3 mesh_translation_vector; + model->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector); + + LLMatrix4 mesh_translation; + mesh_translation.setTranslation(mesh_translation_vector); + mesh_translation *= transformation; + transformation = mesh_translation; + + LLMatrix4 mesh_scale; + mesh_scale.initScale(mesh_scale_vector); + mesh_scale *= transformation; + transformation = mesh_scale; + + std::string label = getElementLabel(instance_geo); + mScene[transformation].push_back(LLModelInstance(model, label, transformation, materials)); + + stretch_extents(model, transformation, mExtents[0], mExtents[1], mFirstTransform); + } + } + } + else + { + llinfos<<"Unable to resolve geometry URL."<(element); + if (instance_node) + { + daeElement* instance = instance_node->getUrl().getElement(); + if (instance) + { + processElement(instance,badElement); + } + } + + //process children + daeTArray< daeSmartRef > children = element->getChildren(); + int childCount = children.getCount(); + for (S32 i = 0; i < childCount; i++) + { + processElement(children[i],badElement); + } + + domNode* node = daeSafeCast(element); + if (node) + { //this element was a node, restore transform before processiing siblings + mTransform = saved_transform; + } +} + +std::map LLModelLoader::getMaterials(LLModel* model, domInstance_geometry* instance_geo) +{ + std::map materials; + for (int i = 0; i < model->mMaterialList.size(); i++) + { + LLImportMaterial import_material; + + domInstance_material* instance_mat = NULL; + + domBind_material::domTechnique_common* technique = + daeSafeCast(instance_geo->getDescendant(daeElement::matchType(domBind_material::domTechnique_common::ID()))); + + if (technique) + { + daeTArray< daeSmartRef > inst_materials = technique->getChildrenByType(); + for (int j = 0; j < inst_materials.getCount(); j++) + { + std::string symbol(inst_materials[j]->getSymbol()); + + if (symbol == model->mMaterialList[i]) // found the binding + { + instance_mat = inst_materials[j]; + } + } + } + + if (instance_mat) + { + domMaterial* material = daeSafeCast(instance_mat->getTarget().getElement()); + if (material) + { + domInstance_effect* instance_effect = + daeSafeCast(material->getDescendant(daeElement::matchType(domInstance_effect::ID()))); + if (instance_effect) + { + domEffect* effect = daeSafeCast(instance_effect->getUrl().getElement()); + if (effect) + { + domProfile_COMMON* profile = + daeSafeCast(effect->getDescendant(daeElement::matchType(domProfile_COMMON::ID()))); + if (profile) + { + import_material = profileToMaterial(profile); + } + } + } + } + } + + import_material.mBinding = model->mMaterialList[i]; + materials[model->mMaterialList[i]] = import_material; + } + + return materials; +} + +LLImportMaterial LLModelLoader::profileToMaterial(domProfile_COMMON* material) +{ + LLImportMaterial mat; + mat.mFullbright = FALSE; + + daeElement* diffuse = material->getDescendant("diffuse"); + if (diffuse) + { + domCommon_color_or_texture_type_complexType::domTexture* texture = + daeSafeCast(diffuse->getDescendant("texture")); + if (texture) + { + domCommon_newparam_type_Array newparams = material->getNewparam_array(); + for (S32 i = 0; i < newparams.getCount(); i++) + { + domFx_surface_common* surface = newparams[i]->getSurface(); + if (surface) + { + domFx_surface_init_common* init = surface->getFx_surface_init_common(); + if (init) + { + domFx_surface_init_from_common_Array init_from = init->getInit_from_array(); + + if (init_from.getCount() > i) + { + domImage* image = daeSafeCast(init_from[i]->getValue().getElement()); + if (image) + { + // we only support init_from now - embedded data will come later + domImage::domInit_from* init = image->getInit_from(); + if (init) + { + mat.mDiffuseMapFilename = cdom::uriToNativePath(init->getValue().str()); + mat.mDiffuseMapLabel = getElementLabel(material); + } + } + } + } + } + } + } + + domCommon_color_or_texture_type_complexType::domColor* color = + daeSafeCast(diffuse->getDescendant("color")); + if (color) + { + domFx_color_common domfx_color = color->getValue(); + LLColor4 value = LLColor4(domfx_color[0], domfx_color[1], domfx_color[2], domfx_color[3]); + mat.mDiffuseColor = value; + } + } + + daeElement* emission = material->getDescendant("emission"); + if (emission) + { + LLColor4 emission_color = getDaeColor(emission); + if (((emission_color[0] + emission_color[1] + emission_color[2]) / 3.0) > 0.25) + { + mat.mFullbright = TRUE; + } + } + + return mat; +} + +// try to get a decent label for this element +std::string LLModelLoader::getElementLabel(daeElement *element) +{ + // if we have a name attribute, use it + std::string name = element->getAttribute("name"); + if (name.length()) + { + return name; + } + + // if we have an ID attribute, use it + if (element->getID()) + { + return std::string(element->getID()); + } + + // if we have a parent, use it + daeElement* parent = element->getParent(); + if (parent) + { + // if parent has a name, use it + std::string name = parent->getAttribute("name"); + if (name.length()) + { + return name; + } + + // if parent has an ID, use it + if (parent->getID()) + { + return std::string(parent->getID()); + } + } + + // try to use our type + daeString element_name = element->getElementName(); + if (element_name) + { + return std::string(element_name); + } + + // if all else fails, use "object" + return std::string("object"); +} + +LLColor4 LLModelLoader::getDaeColor(daeElement* element) +{ + LLColor4 value; + domCommon_color_or_texture_type_complexType::domColor* color = + daeSafeCast(element->getDescendant("color")); + if (color) + { + domFx_color_common domfx_color = color->getValue(); + value = LLColor4(domfx_color[0], domfx_color[1], domfx_color[2], domfx_color[3]); + } + + return value; +} + +//----------------------------------------------------------------------------- +// LLModelPreview +//----------------------------------------------------------------------------- + +LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) +: LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, FALSE), + LLMutex(), + mPelvisZOffset(0.0f), + mLegacyRigValid(false), + mRigValidJointUpload(false), + mResetJoints(false), + mRigParityWithScene(false), + 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(); + + //move into joint mapper class + //1. joints for joint offset verification + mMasterJointList.push_front("mPelvis"); + mMasterJointList.push_front("mTorso"); + mMasterJointList.push_front("mChest"); + mMasterJointList.push_front("mNeck"); + mMasterJointList.push_front("mHead"); + mMasterJointList.push_front("mCollarLeft"); + mMasterJointList.push_front("mShoulderLeft"); + mMasterJointList.push_front("mElbowLeft"); + mMasterJointList.push_front("mWristLeft"); + mMasterJointList.push_front("mCollarRight"); + mMasterJointList.push_front("mShoulderRight"); + mMasterJointList.push_front("mElbowRight"); + mMasterJointList.push_front("mWristRight"); + mMasterJointList.push_front("mHipRight"); + mMasterJointList.push_front("mKneeRight"); + mMasterJointList.push_front("mFootRight"); + mMasterJointList.push_front("mHipLeft"); + mMasterJointList.push_front("mKneeLeft"); + mMasterJointList.push_front("mFootLeft"); + //2. legacy joint list - used to verify rigs that will not be using joint offsets + mMasterLegacyJointList.push_front("mPelvis"); + mMasterLegacyJointList.push_front("mTorso"); + mMasterLegacyJointList.push_front("mChest"); + mMasterLegacyJointList.push_front("mNeck"); + mMasterLegacyJointList.push_front("mHead"); + mMasterLegacyJointList.push_front("mHipRight"); + mMasterLegacyJointList.push_front("mKneeRight"); + mMasterLegacyJointList.push_front("mFootRight"); + mMasterLegacyJointList.push_front("mHipLeft"); + mMasterLegacyJointList.push_front("mKneeLeft"); + mMasterLegacyJointList.push_front("mFootLeft"); + + createPreviewAvatar(); +} + +LLModelPreview::~LLModelPreview() +{ + if (mModelLoader) + { + mModelLoader->mPreview = NULL; + mModelLoader = NULL; + } + //*HACK : *TODO : turn this back on when we understand why this crashes + //glodShutdown(); +} + +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 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()) + { + getPreviewAvatar()->setPelvisOffset(mPelvisZOffset); + } + + 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(), + TRUE); + + 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; + + streaming_cost += LLMeshRepository::getStreamingCost(ret, 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 LLModelPreview::rebuildUploadData() +{ + assert_main_thread(); + + mUploadData.clear(); + mTextureSet.clear(); + + //fill uploaddata instance vectors from scene data + + std::string requested_name = mFMP->getChild("description_form")->getValue().asString(); + + std::string metric = mFMP->getChild("model_category_combo")->getValue().asString(); + + LLSpinCtrl* scale_spinner = mFMP->getChild("import_scale"); + + F32 scale = scale_spinner->getValue().asReal(); + + LLMatrix4 scale_mat; + scale_mat.initScale(LLVector3(scale, scale, scale)); + + F32 max_scale = 0.f; + + //reorder materials to match mBaseModel + for (U32 i = 0; i < LLModel::NUM_LODS; i++) + { + if (mBaseModel.size() == mModel[i].size()) + { + for (U32 j = 0; j < mBaseModel.size(); ++j) + { + + int refFaceCnt = 0; + int modelFaceCnt = 0; + + if (!mModel[i][j]->matchMaterialOrder(mBaseModel[j], refFaceCnt, modelFaceCnt)) + { + mFMP->childDisable("calculate_btn"); + } + } + } + } + + 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(); ++model_iter) + { //for each instance with said transform applied + LLModelInstance instance = *model_iter; + + LLModel* base_model = instance.mModel; + + if (base_model) + { + base_model->mRequestedLabel = requested_name; + base_model->mMetric = metric; + } + + S32 idx = 0; + for (idx = 0; idx < mBaseModel.size(); ++idx) + { //find reference instance for this model + if (mBaseModel[idx] == base_model) + { + break; + } + } + + if (idx < mBaseModel.size()) + { + for (U32 i = 0; i < LLModel::NUM_LODS; i++) + { //fill LOD slots based on reference model index + if (mModel[i].size() > idx) + { + instance.mLOD[i] = mModel[i][idx]; + } + else + { + instance.mLOD[i] = NULL; + } + } + } + instance.mTransform = mat; + mUploadData.push_back(instance); + } + } + + 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) +{ + if (!mLODFile[LLModel::LOD_HIGH].empty()) + { + std::string filename = mLODFile[LLModel::LOD_HIGH]; + + std::string::size_type i = filename.rfind("."); + if (i != std::string::npos) + { + filename.replace(i, filename.size() - 1, ".slm"); + saveUploadData(filename, save_skinweights, save_joint_positions); + } + } +} + +void LLModelPreview::saveUploadData(const std::string& filename, bool save_skinweights, bool save_joint_positions) +{ + if (!gSavedSettings.getBOOL("MeshImportUseSLM")) + { + return; + } + + std::set > meshes; + std::map 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, FALSE, TRUE); + + data["mesh"][instance.mModel->mLocalID] = str.str(); + } + + data["instance"][i] = instance.asLLSD(); + } + + llofstream out(filename, 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::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) + { + llwarns << "Invalid level of detail: " << lod << llendl; + 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->close(); + mLoading = false; + } + return; + } + + if (mModelLoader) + { + llwarns << "Incompleted model load operation pending." << llendl; + return; + } + + mLODFile[lod] = filename; + + if (lod == LLModel::LOD_HIGH) + { + clearGLODGroup(); + } + + mModelLoader = new LLModelLoader(filename, lod, this, mJointTransformMap, mJointsFromNode); + + if (force_disable_slm) + { + mModelLoader->mTrySLM = false; + } + + 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->childSetText("lod_file_" + lod_name[lod], mLODFile[lod]); + } + else if (lod == LLModel::LOD_PHYSICS) + { + mFMP->childSetText("physics_file", mLODFile[lod]); + } + + mFMP->open(); +} + +void LLModelPreview::setPhysicsFromLOD(S32 lod) +{ + assert_main_thread(); + + if (lod >= 0 && lod <= 3) + { + mModel[LLModel::LOD_PHYSICS] = mModel[lod]; + mScene[LLModel::LOD_PHYSICS] = mScene[lod]; + mLODFile[LLModel::LOD_PHYSICS].clear(); + mFMP->childSetText("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; + } + + for (U32 i = 0; i <= LLModel::LOD_HIGH; i++) + { //clear out any entries that aren't compatible with this model + if (i != lod) + { + if (mModel[i].size() != mModel[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, 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 lod) +{ + assert_main_thread(); + + LLMutexLock lock(this); + if (!mModelLoader) + { + mLoading = false; + return; + } + if (getLoadState() >= LLModelLoader::ERROR_PARSING) + { + mLoading = false; + return; + } + + mModelLoader->loadTextures(); + + if (lod == -1) + { //populate all LoDs from model loader scene + mBaseModel.clear(); + mBaseScene.clear(); + + bool skin_weights = false; + bool joint_positions = 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]; + + //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 (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); + } + } + + //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[lod] = mModelLoader->mModelList; + mScene[lod] = mModelLoader->mScene; + mVertexBuffer[lod].clear(); + + setPreviewLOD(lod); + + if (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[lod]; + clearGLODGroup(); + + mBaseScene = mScene[lod]; + mVertexBuffer[5].clear(); + } + + clearIncompatible(lod); + + mDirty = true; + + if (lod == LLModel::LOD_HIGH) + { + resetPreviewTarget(); + } + } + + mLoading = false; + if (mFMP) + { + mFMP->getChild("confirm_checkbox")->set(FALSE); + if (!mBaseModel.empty()) + { + if (mFMP->getChild("description_form")->getValue().asString().empty()) + { + const std::string& model_name = mBaseModel[0]->getName(); + mFMP->getChild("description_form")->setValue(model_name); + } + } + } + refresh(); + + mModelLoadedSignal(); +} + +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()) + { + for (LLModelLoader::model_list::iterator iter = mBaseModel.begin(); iter != mBaseModel.end(); ++iter) + { + (*iter)->generateNormals(angle_cutoff); + } + + mVertexBuffer[5].clear(); + } + + for (LLModelLoader::model_list::iterator iter = mModel[which_lod].begin(); iter != mModel[which_lod].end(); ++iter) + { + (*iter)->generateNormals(angle_cutoff); + } + + 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) + { + llwarns << "Invalid level of detail: " << which_lod << llendl; + assert(which_lod >= -1 && which_lod < LLModel::NUM_LODS); + return; + } + + if (mBaseModel.empty()) + { + return; + } + + LLVertexBuffer::unbind(); + + stop_gloderror(); + static U32 cur_name = 1; + + S32 limit = -1; + + U32 triangle_count = 0; + + for (LLModelLoader::model_list::iterator iter = mBaseModel.begin(); iter != mBaseModel.end(); ++iter) + { + LLModel* mdl = *iter; + for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i) + { + triangle_count += mdl->getVolumeFace(i).mNumIndices/3; + } + } + + 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(); + } + } + 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, 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] = triangle_count; + 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); + + 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 buff = new LLVertexBuffer(type_mask, 0); + + if (sizes[i*2 + 1] > 0 && sizes[i*2] > 0) + { + buff->allocateBuffer(sizes[i*2 + 1], sizes[i*2], true); + buff->setBuffer(type_mask); + glodFillElements(mObject[base], names[i], GL_UNSIGNED_SHORT, buff->getIndicesPointer()); + stop_gloderror(); + } + else + { //this face was eliminated, create a dummy triangle (one vertex, 3 indices, all 0) + buff->allocateBuffer(1, 3, true); + memset(buff->getMappedData(), 0, buff->getSize()); + memset(buff->getIndicesPointer(), 0, buff->getIndicesSize()); + } + + buff->validateRange(0, buff->getNumVerts() - 1, buff->getNumIndices(), 0); + + LLStrider pos; + LLStrider norm; + LLStrider tc; + LLStrider 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]))) + { + llerrs << "Invalid face generated during LOD generation." << llendl; + } + } + + //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)) + { + llerrs << "Invalid model generated when creating LODs" << llendl; + } + + 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(); + + /*if (which_lod == -1 && mScene[LLModel::LOD_PHYSICS].empty()) + { //build physics scene + mScene[LLModel::LOD_PHYSICS] = mScene[LLModel::LOD_LOW]; + mModel[LLModel::LOD_PHYSICS] = mModel[LLModel::LOD_LOW]; + + for (U32 i = 1; i < mModel[LLModel::LOD_PHYSICS].size(); ++i) + { + mPhysicsQ.push(mModel[LLModel::LOD_PHYSICS][i]); + } + }*/ +} + +void LLModelPreview::updateStatusMessages() +{ + assert_main_thread(); + + //triangle/vertex/submesh count for each mesh asset for each lod + std::vector tris[LLModel::NUM_LODS]; + std::vector verts[LLModel::NUM_LODS]; + std::vector 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 (S32 lod = 0; lod < LLModel::NUM_LODS; ++lod) + { + //initialize total for this lod to 0 + total_tris[lod] = total_verts[lod] = total_submeshes[lod] = 0; + + for (U32 i = 0; i < mModel[lod].size(); ++i) + { //for each model in the lod + S32 cur_tris = 0; + S32 cur_verts = 0; + S32 cur_submeshes = mModel[lod][i]->getNumVolumeFaces(); + + for (S32 j = 0; j < cur_submeshes; ++j) + { //for each submesh (face), add triangles and vertices to current total + const LLVolumeFace& face = mModel[lod][i]->getVolumeFace(j); + cur_tris += face.mNumIndices/3; + cur_verts += face.mNumVertices; + } + + //add this model to the lod total + total_tris[lod] += cur_tris; + total_verts[lod] += cur_verts; + total_submeshes[lod] += cur_submeshes; + + //store this model's counts to asset data + tris[lod].push_back(cur_tris); + verts[lod].push_back(cur_verts); + submeshes[lod].push_back(cur_submeshes); + } + } + + 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]->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 + const LLVolumeFace& face = mModel[lod][i]->getVolumeFace(j); + for (S32 k = 0; k < face.mNumIndices && !has_degenerate; ) + { + LLVector4a v1; v1.setMul(face.mPositions[face.mIndices[k++]], scale); + LLVector4a v2; v2.setMul(face.mPositions[face.mIndices[k++]], scale); + LLVector4a v3; v3.setMul(face.mPositions[face.mIndices[k++]], scale); + + if (ll_is_degenerate(v1, v2, v3)) + { + has_degenerate = true; + } + } + } + } + } + } + + 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]; + + bool upload_ok = true; + + for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod) + { + upload_status[lod] = 0; + + std::string message = "mesh_status_good"; + + if (total_tris[lod] > 0) + { + mFMP->childSetText(lod_triangles_name[lod], llformat("%d", total_tris[lod])); + mFMP->childSetText(lod_vertices_name[lod], llformat("%d", total_verts[lod])); + } + else + { + if (lod == LLModel::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->childSetText(lod_triangles_name[lod], mesh_status_na); + mFMP->childSetText(lod_vertices_name[lod], mesh_status_na); + } + + const U32 lod_high = LLModel::LOD_HIGH; + + 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 (!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] = 2; + } + } + } + + std::string img = lod_status_image[upload_status[lod]]; + LLIconCtrl* icon = mFMP->getChild(lod_icon_name[lod]); + icon->setVisible(true); + icon->setImage(img); + + if (upload_status[lod] >= 2) + { + upload_ok = false; + } + + if (lod == mPreviewLOD) + { + mFMP->childSetText("lod_status_message_text", mFMP->getString(message)); + icon = mFMP->getChild("lod_status_message_icon"); + icon->setImage(img); + } + + updateLodControls(lod); + } + + //make sure no hulls have more than 256 points in them + for (U32 i = 0; upload_ok && i < mModel[LLModel::LOD_PHYSICS].size(); ++i) + { + LLModel* mdl = mModel[LLModel::LOD_PHYSICS][i]; + + for (U32 j = 0; upload_ok && j < mdl->mPhysics.mHull.size(); ++j) + { + if (mdl->mPhysics.mHull[j].size() > 256) + { + upload_ok = false; + } + } + } + + bool errorStateFromLoader = getLoadState() >= LLModelLoader::ERROR_PARSING ? true : false; + + bool skinAndRigOk = true; + bool uploadingSkin = mFMP->childGetValue("upload_skin").asBoolean(); + bool uploadingJointPositions = mFMP->childGetValue("upload_joints").asBoolean(); + + if (uploadingSkin) + { + if (uploadingJointPositions && !isRigValidForJointPositionUpload()) + { + skinAndRigOk = false; + } + } + + if (upload_ok && mModelLoader) + { + if (!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean()) + { + upload_ok = false; + } + } + + if (!upload_ok || errorStateFromLoader || !skinAndRigOk || has_degenerate) + { + mFMP->childDisable("ok_btn"); + } + + //add up physics triangles etc + S32 start = 0; + S32 end = mModel[LLModel::LOD_PHYSICS].size(); + + S32 phys_tris = 0; + S32 phys_hulls = 0; + S32 phys_points = 0; + + for (S32 i = start; i < end; ++i) + { //add up hulls and points and triangles for selected mesh(es) + LLModel* model = mModel[LLModel::LOD_PHYSICS][i]; + 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("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("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"); + } + } + + if (mFMP->childGetValue("physics_lod_combo").asString() == "From file") + { + mFMP->childEnable("physics_file"); + mFMP->childEnable("physics_browse"); + } + else + { + mFMP->childDisable("physics_file"); + mFMP->childDisable("physics_browse"); + } + + LLSpinCtrl* crease = mFMP->getChild("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) + { + llwarns << "Invalid level of detail: " << lod << llendl; + 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->getChild("lod_source_" + lod_name[lod], TRUE, FALSE); + if (!lod_combo) return; + + S32 lod_mode = lod_combo->getCurrentIndex(); + if (lod_mode == 0) // LoD from file + { + fmp->mLODMode[lod] = 0; + for (U32 i = 0; i < num_file_controls; ++i) + { + mFMP->childShow(file_controls[i] + lod_name[lod]); + } + + for (U32 i = 0; i < num_lod_controls; ++i) + { + mFMP->childHide(lod_controls[i] + lod_name[lod]); + } + } + else if (lod_mode == 2) // use LoD above + { + fmp->mLODMode[lod] = 2; + for (U32 i = 0; i < num_file_controls; ++i) + { + mFMP->childHide(file_controls[i] + lod_name[lod]); + } + + for (U32 i = 0; i < num_lod_controls; ++i) + { + mFMP->childHide(lod_controls[i] + lod_name[lod]); + } + + 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->childHide(file_controls[i] + lod_name[lod]); + } + + for (U32 i = 0; i < num_lod_controls; ++i) + { + mFMP->childShow(lod_controls[i] + lod_name[lod]); + } + + LLSpinCtrl* threshold = mFMP->getChild("lod_error_threshold_" + lod_name[lod]); + LLSpinCtrl* limit = mFMP->getChild("lod_triangle_limit_" + lod_name[lod]); + + limit->setMaxValue(mMaxTriangleLimit); + limit->forceSetValue(mRequestedTriangleCount[lod]); + + threshold->forceSetValue(mRequestedErrorThreshold[lod]); + + mFMP->getChild("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++; + + for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++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); + + vb->allocateBuffer(num_vertices, num_indices, TRUE); + + LLStrider vertex_strider; + LLStrider normal_strider; + LLStrider tc_strider; + LLStrider index_strider; + LLStrider 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); + LLVector4a::memcpyNonAliased16((F32*) tc_strider.get(), (F32*) vf.mTexCoords, num_vertices*2*sizeof(F32)); + } + + 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); + + LLVector4 w(0,0,0,0); + + for (U32 i = 0; i < weight_list.size(); ++i) + { + F32 wght = llmin(weight_list[i].mWeight, 0.999999f); + F32 joint = (F32) weight_list[i].mJointIdx; + w.mV[i] = joint + wght; + } + + *(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 (mDirty) + { + mDirty = false; + mResourceCost = calcResourceCost(); + refresh(); + updateStatusMessages(); + } + + if (mGenLOD) + { + mGenLOD = false; + genLODs(); + refresh(); + updateStatusMessages(); + } +} + +//----------------------------------------------------------------------------- +// getTranslationForJointOffset() +//----------------------------------------------------------------------------- +LLVector3 LLModelPreview::getTranslationForJointOffset(std::string joint) +{ + LLMatrix4 jointTransform; + if (mJointTransformMap.find(joint) != mJointTransformMap.end()) + { + jointTransform = mJointTransformMap[joint]; + return jointTransform.getTranslation(); + } + return LLVector3(0.0f,0.0f,0.0f); +} + +//----------------------------------------------------------------------------- +// createPreviewAvatar +//----------------------------------------------------------------------------- +void LLModelPreview::createPreviewAvatar(void) +{ + mPreviewAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, + gAgent.getRegion()); + if (mPreviewAvatar) + { + mPreviewAvatar->createDrawable(&gPipeline); + mPreviewAvatar->mIsDummy = TRUE; + mPreviewAvatar->mSpecialRenderMode = 1; + mPreviewAvatar->setPositionAgent(LLVector3::zero); + mPreviewAvatar->slamPosition(); + mPreviewAvatar->updateJointLODs(); + mPreviewAvatar->updateGeometry(mPreviewAvatar->mDrawable); + mPreviewAvatar->startMotion(ANIM_AGENT_STAND); + mPreviewAvatar->hideSkirt(); + } + else + { + llinfos<<"Failed to create preview avatar for upload model window"< buff = new LLVertexBuffer(type_mask, 0); + + buff->allocateBuffer(1, 3, true); + memset(buff->getMappedData(), 0, buff->getSize()); + memset(buff->getIndicesPointer(), 0, buff->getIndicesSize()); + + buff->validateRange(0, buff->getNumVerts() - 1, buff->getNumIndices(), 0); + + LLStrider pos; + LLStrider norm; + LLStrider tc; + LLStrider 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() +//----------------------------------------------------------------------------- +BOOL LLModelPreview::render() +{ + assert_main_thread(); + + LLMutexLock lock(this); + mNeedsUpdate = FALSE; + + 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; + LLGLDisable no_blend(GL_BLEND); + LLGLEnable cull(GL_CULL_FACE); + LLGLDepthTest depth(GL_TRUE); + LLGLDisable fog(GL_FOG); + + { + //clear background to blue + glMatrixMode(GL_PROJECTION); + gGL.pushMatrix(); + glLoadIdentity(); + glOrtho(0.0f, width, 0.0f, height, -1.0f, 1.0f); + + glMatrixMode(GL_MODELVIEW); + gGL.pushMatrix(); + glLoadIdentity(); + + gGL.color4f(0.169f, 0.169f, 0.169f, 1.f); + + gl_rect_2d_simple(width, height); + + glMatrixMode(GL_PROJECTION); + gGL.popMatrix(); + + glMatrixMode(GL_MODELVIEW); + gGL.popMatrix(); + } + + 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(); + + bool resetJoints = false; + if (upload_joints != mLastJointUpdate) + { + if (mLastJointUpdate) + { + resetJoints = true; + } + + 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) + { //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"); + } + } + 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; + } + + 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; + } + + //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(); + + glClear(GL_DEPTH_BUFFER_BIT); + + LLRect preview_rect = mFMP->getChildView("preview_panel")->getRect(); + F32 aspect = (F32) preview_rect.getWidth()/preview_rect.getHeight(); + + LLViewerCamera* camera = LLViewerCamera::getInstance(); + camera->setAspect(aspect); + + camera->setView(camera->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(); + z_near = 0.01f; + z_far = 1024.f; + mCameraDistance = 16.f; + + //render avatar previews every frame + refresh(); + } + + glLoadIdentity(); + gPipeline.enableLightsPreview(); + + LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * + LLQuaternion(mCameraYaw, LLVector3::z_axis); + + LLQuaternion av_rot = camera_rot; + camera->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); + + camera->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 >& vb_vec = mVertexBuffer[mPreviewLOD].begin()->second; + if (!vb_vec.empty()) + { + const LLVertexBuffer* buff = vb_vec[0]; + regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight; + } + } + + //make sure material lists all match + for (U32 i = 0; i < LLModel::NUM_LODS; i++) + { + if (mBaseModel.size() == mModel[i].size()) + { + for (U32 j = 0; j < mBaseModel.size(); ++j) + { + int refFaceCnt = 0; + int modelFaceCnt = 0; + + if (!mModel[i][j]->matchMaterialOrder(mBaseModel[j], refFaceCnt, modelFaceCnt)) + { + mFMP->childDisable("calculate_btn"); + } + } + } + } + + 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; + + glMultMatrixf((GLfloat*) mat.mMatrix); + + for (U32 i = 0; i < mVertexBuffer[mPreviewLOD][model].size(); ++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]; + + glColor4fv(material.mDiffuseColor.mV); + if (material.mDiffuseMap.notNull()) + { + if (material.mDiffuseMap->getDiscardLevel() > -1) + { + gGL.getTexUnit(0)->bind(material.mDiffuseMap, true); + mTextureSet.insert(material.mDiffuseMap.get()); + } + } + } + } + else + { + glColor4f(1,1,1,1); + } + + buffer->drawRange(LLRender::TRIANGLES, 0, + buffer->getNumVerts() - 1, + buffer->getNumIndices(), 0); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + glColor3f(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 i = 0; i < 2; i++) + { + if (i == 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, i); + + 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; + + glMultMatrixf((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 hull_colors; + + if (i + 1 >= hull_colors.size()) + { + hull_colors.push_back(LLColor4U(rand()%128 + 127, rand()%128 + 127, rand()%128 + 127, 128)); + } + + glColor4ubv(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); + } + for (U32 i = 0; i < mVertexBuffer[LLModel::LOD_PHYSICS][model].size(); ++i) + { + LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i]; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + glColor4f(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); + + glColor3f(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(LLColor4::white); + //show degenerate triangles + LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); + LLGLDisable cull(GL_CULL_FACE); + glColor4f(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; + + glMultMatrixf((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 pos_strider; + buffer->getVertexStrider(pos_strider, 0); + LLVector4a* pos = (LLVector4a*) pos_strider.get(); + + LLStrider 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(); + + camera->setOriginAndLookAt(target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + offset) * av_rot), // camera + LLVector3::z_axis, // up + target_pos); // point of interest + if (joint_positions) + { + getPreviewAvatar()->renderCollisionVolumes(); + } + + 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; i < mVertexBuffer[mPreviewLOD][model].size(); ++i) + { + LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; + + const LLVolumeFace& face = model->getVolumeFace(i); + + LLStrider position; + buffer->getVertexStrider(position); + + LLStrider weight; + buffer->getWeight4Strider(weight); + + //quick 'n dirty software vertex skinning + + //build matrix palette + + LLMatrix4 mat[64]; + for (U32 j = 0; j < model->mSkinInfo.mJointNames.size(); ++j) + { + LLJoint* joint = getPreviewAvatar()->getJoint(model->mSkinInfo.mJointNames[j]); + if (joint) + { + mat[j] = model->mSkinInfo.mInvBindMatrix[j]; + mat[j] *= joint->getWorldMatrix(); + } + } + + for (U32 j = 0; j < buffer->getRequestedVerts(); ++j) + { + LLMatrix4 final_mat; + final_mat.mMatrix[0][0] = final_mat.mMatrix[1][1] = final_mat.mMatrix[2][2] = final_mat.mMatrix[3][3] = 0.f; + + LLVector4 wght; + S32 idx[4]; + + F32 scale = 0.f; + for (U32 k = 0; k < 4; k++) + { + F32 w = weight[j].mV[k]; + + idx[k] = (S32) floorf(w); + wght.mV[k] = w - floorf(w); + scale += wght.mV[k]; + } + + wght *= 1.f/scale; + + for (U32 k = 0; k < 4; k++) + { + F32* src = (F32*) mat[idx[k]].mMatrix; + F32* dst = (F32*) final_mat.mMatrix; + + F32 w = wght.mV[k]; + + for (U32 l = 0; l < 16; l++) + { + dst[l] += src[l]*w; + } + } + + //VECTORIZE THIS + LLVector3 v(face.mPositions[j].getF32ptr()); + + v = v * model->mSkinInfo.mBindShapeMatrix; + v = v * final_mat; + + position[j] = v; + } + + const std::string& binding = instance.mModel->mMaterialList[i]; + const LLImportMaterial& material = instance.mMaterial[binding]; + buffer->setBuffer(type_mask & buffer->getTypeMask()); + glColor4fv(material.mDiffuseColor.mV); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0); + glColor3f(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); + } + } + } + } + } + } + } + + 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); +} + +void LLModelPreview::pan(F32 right, F32 up) +{ + 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); +} + +void LLModelPreview::setPreviewLOD(S32 lod) +{ + lod = llclamp(lod, 0, (S32) LLModel::LOD_HIGH); + + if (lod != mPreviewLOD) + { + mPreviewLOD = lod; + + LLComboBox* combo_box = mFMP->getChild("preview_lod_combo"); + // combo box list of lods is in reverse order + combo_box->setCurrentByIndex(NUM_LOD - 1 - mPreviewLOD); + mFMP->childSetText("lod_file_" + lod_name[mPreviewLOD], mLODFile[mPreviewLOD]); + + LLColor4 highlight_color = gColors.getColor("MeshImportTableHighlightColor"); + LLColor4 normal_color = gColors.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); + } + } + refresh(); + updateStatusMessages(); +} + +//static +void LLFloaterModelPreview::onBrowseLOD(void* user_data) +{ + assert_main_thread(); + + S32 lod = (S32)user_data; + + if (sInstance) + { + sInstance->loadModel(lod); + } +} + +//static +void LLFloaterModelPreview::onReset(void* user_data) +{ + assert_main_thread(); + + LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) user_data; + fmp->childDisable("reset_btn"); + LLModelPreview* mp = fmp->mModelPreview; + std::string filename = mp->mLODFile[3]; + + fmp->resetDisplayOptions(); + //reset model preview + fmp->initModelPreview(); + + mp = fmp->mModelPreview; + mp->loadModel(filename,3,true); +} + +//static +void LLFloaterModelPreview::onUpload(void* user_data) +{ + assert_main_thread(); + + LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data; + + mp->mUploadBtn->setEnabled(false); + + mp->mModelPreview->rebuildUploadData(); + + bool upload_skinweights = mp->childGetValue("upload_skin").asBoolean(); + bool upload_joint_positions = mp->childGetValue("upload_joints").asBoolean(); + + mp->mModelPreview->saveUploadData(upload_skinweights, upload_joint_positions); + + gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, + mp->mModelPreview->mPreviewScale, + mp->childGetValue("upload_textures").asBoolean(), + upload_skinweights, + upload_joint_positions, + mp->mUploadModelUrl, + true, + LLHandle(), + mp->getWholeModelUploadObserverHandle()); +} + +void LLFloaterModelPreview::refresh() +{ + sInstance->toggleCalculateButton(true); + 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--; + } + } +} + +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; + mContinue = 1; + mModel = mdl; + mDecompID = &mdl->mDecompID; + mParams = sInstance->mDecompParams; + + //copy out positions and indices + assignData(mdl); +} + +void LLFloaterModelPreview::setStatusMessage(const std::string& msg) +{ + LLMutexLock lock(mStatusLock); + mStatusMessage = msg; +} + +//static +void LLFloaterModelPreview::toggleCalculateButtonCallBack(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* self = (LLFloaterModelPreview*)userdata; + if (self) + { + self->toggleCalculateButton(true); + } +} + +void LLFloaterModelPreview::toggleCalculateButton(bool visible) +{ + mCalculateBtn->setVisible(visible); + + bool uploadingSkin = childGetValue("upload_skin").asBoolean(); + bool uploadingJointPositions = childGetValue("upload_joints").asBoolean(); + if (uploadingSkin) + { + //Disable the calculate button *if* the rig is invalid - which is determined during the critiquing process + if (uploadingJointPositions && !mModelPreview->isRigValidForJointPositionUpload()) + { + mCalculateBtn->setVisible(false); + } + } + + mUploadBtn->setVisible(!visible); + mUploadBtn->setEnabled(mHasUploadPerm && !mUploadModelUrl.empty()); + + if (visible) + { + std::string tbd = getString("tbd"); + childSetTextArg("prim_weight", "[EQ]", tbd); + childSetTextArg("download_weight", "[ST]", tbd); + childSetTextArg("server_weight", "[SIM]", tbd); + childSetTextArg("physics_weight", "[PH]", tbd); + 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); + } +} + +//static +void LLFloaterModelPreview::onLoDSourceCommit(LLUICtrl* ctrl, void* userdata) +{ + S32 lod = (S32)userdata; + if (sInstance) + { + sInstance->mModelPreview->updateLodControls(lod); + sInstance->refresh(); + } +} + +void LLFloaterModelPreview::resetDisplayOptions() +{ + std::map::iterator option_it = mModelPreview->mViewOption.begin(); + + for ( ;option_it != mModelPreview->mViewOption.end(); ++option_it) + { + LLUICtrl* ctrl = getChild(option_it->first); + ctrl->setValue(false); + } +} + +void LLFloaterModelPreview::onModelPhysicsFeeReceived(const LLSD& result, std::string upload_url) +{ + mModelPhysicsFee = result; + mModelPhysicsFee["url"] = upload_url; + + doOnIdleOneTime(boost::bind(&LLFloaterModelPreview::handleModelPhysicsFeeReceived,this)); +} + +void LLFloaterModelPreview::handleModelPhysicsFeeReceived() +{ + const LLSD& result = mModelPhysicsFee; + mUploadModelUrl = result["url"].asString(); + + childSetTextArg("prim_weight", "[EQ]", llformat("%0.3f", result["resource_cost"].asReal())); + childSetTextArg("download_weight", "[ST]", llformat("%0.3f", result["model_streaming_cost"].asReal())); + childSetTextArg("server_weight", "[SIM]", llformat("%0.3f", result["simulation_cost"].asReal())); + childSetTextArg("physics_weight", "[PH]", llformat("%0.3f", result["physics_cost"].asReal())); + childSetTextArg("upload_fee", "[FEE]", llformat("%d", result["upload_price"].asInteger())); + childSetTextArg("price_breakdown", "[STREAMING]", llformat("%d", result["upload_price_breakdown"]["mesh_streaming"].asInteger())); + childSetTextArg("price_breakdown", "[PHYSICS]", llformat("%d", result["upload_price_breakdown"]["mesh_physics"].asInteger())); + 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())); + childSetVisible("upload_fee", true); + childSetVisible("price_breakdown", true); + mUploadBtn->setEnabled(mHasUploadPerm && !mUploadModelUrl.empty()); +} + +void LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(U32 status, const std::string& reason) +{ + llwarns << "LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(" << status << " : " << reason << ")" << llendl; + doOnIdleOneTime(boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this, true)); +} + +/*virtual*/ +void LLFloaterModelPreview::onModelUploadSuccess() +{ + assert_main_thread(); + close(); +} + +/*virtual*/ +void LLFloaterModelPreview::onModelUploadFailure() +{ + assert_main_thread(); + toggleCalculateButton(true); + mUploadBtn->setEnabled(true); +} + +S32 LLFloaterModelPreview::DecompRequest::statusCallback(const char* status, S32 p1, S32 p2) +{ + if (mContinue) + { + setStatusMessage(llformat("%s: %d/%d", status, p1, p2)); + if (LLFloaterModelPreview::sInstance) + { + LLFloaterModelPreview::sInstance->setStatusMessage(mStatusMessage); + } + } + + return mContinue; +} + +void LLFloaterModelPreview::DecompRequest::completed() +{ //called from the main thread + if (mContinue) + { + mModel->setConvexHullDecomposition(mHull); + + if (sInstance) + { + if (mContinue) + { + if (sInstance->mModelPreview) + { + sInstance->mModelPreview->mDirty = true; + LLFloaterModelPreview::sInstance->mModelPreview->refresh(); + } + } + + sInstance->mCurRequest.erase(this); + } + } + else if (sInstance) + { + llassert(sInstance->mCurRequest.find(this) == sInstance->mCurRequest.end()); + } +} + +void dump_llsd_to_file(const LLSD& content, std::string filename); + +void LLFloaterModelPreview::onPermissionsReceived(const LLSD& result) +{ + dump_llsd_to_file(result,"perm_received.xml"); + std::string upload_status = result["mesh_upload_status"].asString(); + // BAP HACK: handle "" for case that MeshUploadFlag cap is broken. + mHasUploadPerm = (("" == upload_status) || ("valid" == upload_status)); + + //mUploadBtn->setEnabled(mHasUploadPerm); + mUploadBtn->setEnabled(mHasUploadPerm && !mUploadModelUrl.empty()); + getChild("warning_title")->setVisible(!mHasUploadPerm); + getChild("warning_message")->setVisible(!mHasUploadPerm); +} + +void LLFloaterModelPreview::setPermissonsErrorStatus(U32 status, const std::string& reason) +{ + llwarns << "LLFloaterModelPreview::setPermissonsErrorStatus(" << status + << " : " << reason << ")" << llendl; + + LLNotifications::instance().add("MeshUploadPermError"); +} diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h new file mode 100644 index 000000000..e8e21e77f --- /dev/null +++ b/indra/newview/llfloatermodelpreview.h @@ -0,0 +1,474 @@ +/** + * @file llfloatermodelpreview.h + * @brief LLFloaterModelPreview class definition + * + * $LicenseInfo:firstyear=2004&license=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMODELPREVIEW_H +#define LL_LLFLOATERMODELPREVIEW_H + +#include "llmodel.h" +#include "llquaternion.h" +#include "llthread.h" + +#include "lldynamictexture.h" +#include "llfloatermodeluploadbase.h" +#include "llmeshrepository.h" +#include "llviewermenufile.h" + +class LLComboBox; +class LLVOAvatar; +class LLVertexBuffer; +class LLModelPreview; +class LLFloaterModelPreview; +class daeElement; +class domProfile_COMMON; +class domInstance_geometry; +class domNode; +class domTranslate; +class domController; + +typedef std::map JointTransformMap; +typedef std::map:: iterator JointTransformMapIt; + +const S32 NUM_LOD = 4; + +class LLModelLoader : public LLThread +{ +public: + typedef enum + { + STARTING = 0, + READING_FILE, + CREATING_FACES, + GENERATING_VERTEX_BUFFERS, + GENERATING_LOD, + DONE, + ERROR_PARSING //basically loading failed + } eLoadState; + + U32 mState; + std::string mFilename; + S32 mLod; + LLModelPreview* mPreview; + LLMatrix4 mTransform; + BOOL mFirstTransform; + LLVector3 mExtents[2]; + bool mTrySLM; + + std::map > mModel; + + typedef std::vector > model_list; + model_list mModelList; + + typedef std::vector model_instance_list; + + typedef std::map scene; + + scene mScene; + + typedef std::queue > model_queue; + + //queue of models that need a physics rep + model_queue mPhysicsQ; + + LLModelLoader(std::string filename, S32 lod, LLModelPreview* preview, JointTransformMap& jointMap, + std::deque& jointsFromNodes); + ~LLModelLoader() ; + + virtual void run(); + bool doLoadModel(); + bool loadFromSLM(const std::string& filename); + void loadModelCallback(); + + void loadTextures() ; //called in the main thread. + void processElement(daeElement* element, bool& badElement); + std::map getMaterials(LLModel* model, domInstance_geometry* instance_geo); + LLImportMaterial profileToMaterial(domProfile_COMMON* material); + std::string getElementLabel(daeElement *element); + LLColor4 getDaeColor(daeElement* element); + + daeElement* getChildFromElement(daeElement* pElement, std::string const & name); + + bool isNodeAJoint(domNode* pNode); + void processJointNode(domNode* pNode, std::map& jointTransforms); + void extractTranslation(domTranslate* pTranslate, LLMatrix4& transform); + void extractTranslationViaElement(daeElement* pTranslateElement, LLMatrix4& transform); + void extractTranslationViaSID(daeElement* pElement, LLMatrix4& transform); + + void setLoadState(U32 state); + + void buildJointToNodeMappingFromScene(daeElement* pRoot); + void processJointToNodeMapping(domNode* pNode); + void processChildJoints(domNode* pParentNode); + + //map of avatar joints as named in COLLADA assets to internal joint names + std::map mJointMap; + JointTransformMap& mJointList; + std::deque& mJointsFromNode; + + S32 mNumOfFetchingTextures ; //updated in the main thread + bool areTexturesReady() { return !mNumOfFetchingTextures; } //called in the main thread. + +private: + static std::list sActiveLoaderList; + static bool isAlive(LLModelLoader* loader) ; +}; + +class LLFloaterModelPreview : public LLFloaterModelUploadBase +{ +public: + + class DecompRequest : public LLPhysicsDecomp::Request + { + public: + S32 mContinue; + LLPointer mModel; + + DecompRequest(const std::string& stage, LLModel* mdl); + virtual S32 statusCallback(const char* status, S32 p1, S32 p2); + virtual void completed(); + + }; + static LLFloaterModelPreview* sInstance; + + LLFloaterModelPreview(const std::string& name); + virtual ~LLFloaterModelPreview(); + + virtual BOOL postBuild(); + + void initModelPreview(); + + BOOL handleMouseDown(S32 x, S32 y, MASK mask); + BOOL handleMouseUp(S32 x, S32 y, MASK mask); + BOOL handleHover(S32 x, S32 y, MASK mask); + BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + + /*virtual*/ void onOpen(const LLSD& key); + + static void onMouseCaptureLostModelPreview(LLMouseHandler*); + static void setUploadAmount(S32 amount) { sUploadAmount = amount; } + + void setDetails(F32 x, F32 y, F32 z, F32 streaming_cost, F32 physics_cost); + + static void onBrowseLOD(void* user_data); + + static void onReset(void* data); + + static void onUpload(void* data); + + void refresh(); + + void loadModel(S32 lod); + void loadModel(S32 lod, const std::string& file_name, bool force_disable_slm = false); + + 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); + + // shows warning message if agent has no permissions to upload model + /*virtual*/ void onPermissionsReceived(const LLSD& result); + + // called when error occurs during permissions request + /*virtual*/ void setPermissonsErrorStatus(U32 status, const std::string& reason); + + /*virtual*/ void onModelPhysicsFeeReceived(const LLSD& result, std::string upload_url); + void handleModelPhysicsFeeReceived(); + /*virtual*/ void setModelPhysicsFeeErrorStatus(U32 status, const std::string& reason); + + /*virtual*/ void onModelUploadSuccess(); + + /*virtual*/ void onModelUploadFailure(); + +protected: + friend class LLModelPreview; + friend class LLMeshFilePicker; + friend class LLPhysicsDecomp; + + static void onImportScaleCommit(LLUICtrl* ctrl, void*); + static void onPelvisOffsetCommit(LLUICtrl* ctrl, void*); + static void onUploadJointsCommit(LLUICtrl* ctrl, void*); + static void onUploadSkinCommit(LLUICtrl* ctrl, void*); + + static void onPreviewLODCommit(LLUICtrl* ctrl, void*); + + static void onGenerateNormalsCommit(LLUICtrl* ctrl, void*); + + static void toggleGenerateNormals(LLUICtrl* ctrl, void*); + + static void onAutoFillCommit(LLUICtrl* ctrl, void*); + + static void toggleCalculateButtonCallBack(LLUICtrl* ctrl, void* userdata); + + static void onClickCalculateBtn(void* userdata); + + static void onLoDSourceCommit(LLUICtrl* ctrl, void* userdata); + + static void onViewOptionChecked(LLUICtrl* ctrl, void* userdata); + + static void onLODParamCommit(LLUICtrl* ctrl, void* userdata); + static void onLODParamCommitEnforceTriLimit(LLUICtrl* ctrl, void* userdata); + + static void onExplodeCommit(LLUICtrl* ctrl, void*); + + static void onPhysicsParamCommit(LLUICtrl* ctrl, void* userdata); + static void onPhysicsStageExecute(LLUICtrl* ctrl, void* userdata); + static void onCancel(LLUICtrl* ctrl, void* userdata); + static void onPhysicsStageCancel(LLUICtrl* ctrl, void* userdata); + + static void onPhysicsBrowse(LLUICtrl* ctrl, void* userdata); + static void onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata); + static void onPhysicsOptimize(LLUICtrl* ctrl, void* userdata); + static void onPhysicsDecomposeBack(LLUICtrl* ctrl, void* userdata); + static void onPhysicsSimplifyBack(LLUICtrl* ctrl, void* userdata); + + void draw(); + + void initDecompControls(); + + void setStatusMessage(const std::string& msg); + + LLModelPreview* mModelPreview; + + LLPhysicsDecomp::decomp_params mDecompParams; + + S32 mLastMouseX; + S32 mLastMouseY; + LLRect mPreviewRect; + U32 mGLName; + static S32 sUploadAmount; + + std::set > mCurRequest; + std::string mStatusMessage; + + //use "disabled" as false by default + std::map mViewOptionDisabled; + + //store which lod mode each LOD is using + // 0 - load from file + // 1 - auto generate + // 2 - use LoD above + S32 mLODMode[4]; + + LLMutex* mStatusLock; + + LLSD mModelPhysicsFee; + +private: + // Toggles between "Calculate weights & fee" and "Upload" buttons. + void toggleCalculateButton(bool visible); + + // resets display options of model preview to their defaults. + void resetDisplayOptions(); + + 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::string& filename); + +private: + LLModelPreview* mMP; + S32 mLOD; +}; + +class LLModelPreview : public LLViewerDynamicTexture, public LLMutex +{ + typedef boost::signals2::signal details_signal_t; + typedef boost::signals2::signal model_loaded_signal_t; + typedef boost::signals2::signal model_updated_signal_t; + +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 loadModel(std::string filename, S32 lod, bool force_disable_slm = false); + void loadModelCallback(S32 lod); + void genLODs(S32 which_lod = -1, U32 decimation = 3, bool enforce_tri_limit = false); + void generateNormals(); + U32 calcResourceCost(); + void rebuildUploadData(); + void saveUploadData(bool save_skinweights, bool save_joint_poisitions); + void saveUploadData(const std::string& filename, bool save_skinweights, bool save_joint_poisitions); + 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; } + + //Determines the viability of an asset to be used as an avatar rig (w or w/o joint upload caps) + void critiqueRigForUploadApplicability(const std::vector &jointListFromAsset); + void critiqueJointToNodeMappingFromScene(void); + //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; } + bool isRigSuitableForJointPositionUpload(const std::vector &jointListFromAsset); + //Determines if a rig is a legacy from the joint list + bool isRigLegacy(const std::vector &jointListFromAsset); + //Accessors for the legacy rigs + const bool isLegacyRigValid(void) const { return mLegacyRigValid; } + void setLegacyRigValid(bool rigValid) { mLegacyRigValid = rigValid; } + //Verify that a controller matches vertex counts + bool verifyController(domController* pController); + + static void textureLoadedCallback(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata); + + 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; } + void setRigWithSceneParity(bool state) { mRigParityWithScene = state; } + const bool getRigWithSceneParity(void) const { return mRigParityWithScene; } + + LLVector3 getTranslationForJointOffset(std::string joint); + +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; } + + 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; + U32 mResourceCost; + std::string mLODFile[LLModel::NUM_LODS]; + bool mLoading; + U32 mLoadState; + bool mResetJoints; + bool mRigParityWithScene; + + std::map 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; + + U32 mGroup; + std::map, U32> mObject; + U32 mMaxTriangleLimit; + + LLMeshUploadThread::instance_list mUploadData; + std::set mTextureSet; + + //map of vertex buffers to models (one vertex buffer in vector per face in model + std::map > > 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; + + std::deque mMasterJointList; + std::deque mMasterLegacyJointList; + std::deque mJointsFromNode; + JointTransformMap mJointTransformMap; + LLPointer mPreviewAvatar; +}; + +#endif // LL_LLFLOATERMODELPREVIEW_H diff --git a/indra/newview/llfloatermodeluploadbase.cpp b/indra/newview/llfloatermodeluploadbase.cpp new file mode 100644 index 000000000..70466afd0 --- /dev/null +++ b/indra/newview/llfloatermodeluploadbase.cpp @@ -0,0 +1,66 @@ +/** + * @file llfloatermodeluploadbase.cpp + * @brief LLFloaterUploadModelBase class definition + * + * $LicenseInfo:firstyear=2011&license=viewergpl$ + * + * Copyright (c) 2011, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatermodeluploadbase.h" + +#include "llnotifications.h" + +#include "llagent.h" +#include "llviewerregion.h" + +LLFloaterModelUploadBase::LLFloaterModelUploadBase(const LLSD& key) +: LLFloater(key), + mHasUploadPerm(false) +{ +} + +void LLFloaterModelUploadBase::requestAgentUploadPermissions() +{ + std::string capability = "MeshUploadFlag"; + std::string url = gAgent.getRegion()->getCapability(capability); + + if (!url.empty()) + { + llinfos<< typeid(*this).name() <<"::requestAgentUploadPermissions() requesting for upload model permissions from: "<< url <getNumVolumeFaces(); + return indices * 2 + vertices * 11 + sizeof(LLVolume) + sizeof(LLVolumeFace) * volume->getNumVolumeFaces(); } void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, F32 scale = 1.f) @@ -492,7 +493,7 @@ void LLMeshRepoThread::run() static F32 last_hundred = gFrameTimeSeconds; if (gFrameTimeSeconds - last_hundred > 1.f) - { //a second has gone by, clear count + { //a second has gone by, clear count last_hundred = gFrameTimeSeconds; count = 0; } @@ -527,7 +528,7 @@ void LLMeshRepoThread::run() } } - { //mSkinRequests is protected by mSignal + { //mSkinRequests is protected by mSignal std::set incomplete; for (std::set::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter) { @@ -540,7 +541,7 @@ void LLMeshRepoThread::run() mSkinRequests = incomplete; } - { //mDecompositionRequests is protected by mSignal + { //mDecompositionRequests is protected by mSignal std::set incomplete; for (std::set::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter) { @@ -553,7 +554,7 @@ void LLMeshRepoThread::run() mDecompositionRequests = incomplete; } - { //mPhysicsShapeRequests is protected by mSignal + { //mPhysicsShapeRequests is protected by mSignal std::set incomplete; for (std::set::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter) { @@ -571,7 +572,7 @@ void LLMeshRepoThread::run() } if (mSignal->isLocked()) - { //make sure to let go of the mutex associated with the given signal before shutting down + { //make sure to let go of the mutex associated with the given signal before shutting down mSignal->unlock(); } @@ -622,12 +623,12 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) pending_lod_map::iterator pending = mPendingLOD.find(mesh_params); if (pending != mPendingLOD.end()) - { //append this lod request to existing header request + { //append this lod request to existing header request pending->second.push_back(lod); llassert(pending->second.size() <= LLModel::NUM_LODS) } else - { //if no header request is pending, fetch header + { //if no header request is pending, fetch header LLMutexLock lock(mMutex); mHeaderReqQ.push(req); mPendingLOD[mesh_params].push_back(lod); @@ -659,11 +660,11 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) } bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) -{ //protected by mMutex +{ //protected by mMutex mHeaderMutex->lock(); if (mMeshHeader.find(mesh_id) == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing + { //we have no header info for this mesh, do nothing mHeaderMutex->unlock(); return false; } @@ -697,7 +698,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } if (!zero) - { //attempt to parse + { //attempt to parse if (skinInfoReceived(mesh_id, buffer, size)) { delete[] buffer; @@ -732,11 +733,11 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) -{ //protected by mMutex +{ //protected by mMutex mHeaderMutex->lock(); if (mMeshHeader.find(mesh_id) == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing + { //we have no header info for this mesh, do nothing mHeaderMutex->unlock(); return false; } @@ -770,7 +771,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } if (!zero) - { //attempt to parse + { //attempt to parse if (decompositionReceived(mesh_id, buffer, size)) { delete[] buffer; @@ -805,11 +806,11 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) -{ //protected by mMutex +{ //protected by mMutex mHeaderMutex->lock(); if (mMeshHeader.find(mesh_id) == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing + { //we have no header info for this mesh, do nothing mHeaderMutex->unlock(); return false; } @@ -843,7 +844,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } if (!zero) - { //attempt to parse + { //attempt to parse if (physicsShapeReceived(mesh_id, buffer, size)) { delete[] buffer; @@ -868,7 +869,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } } else - { //no physics shape whatsoever, report back NULL + { //no physics shape whatsoever, report back NULL physicsShapeReceived(mesh_id, NULL, 0); } } @@ -898,7 +899,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) LLMeshRepository::sCacheBytesRead += bytes; file.read(buffer, bytes); if (headerReceived(mesh_params, buffer, bytes)) - { //did not do an HTTP request, return false + { //did not do an HTTP request, return false return false; } } @@ -924,7 +925,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) } bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ //protected by mMutex +{ //protected by mMutex mHeaderMutex->lock(); bool retval = false; @@ -960,7 +961,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) } if (!zero) - { //attempt to parse + { //attempt to parse if (lodReceived(mesh_params, lod, buffer, size)) { delete[] buffer; @@ -1144,7 +1145,7 @@ bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 d->mMeshID = mesh_id; if (data == NULL) - { //no data, no physics shape exists + { //no data, no physics shape exists d->mPhysicsShapeMesh.clear(); } else @@ -1258,7 +1259,8 @@ void LLMeshUploadThread::DecompRequest::completed() void LLMeshUploadThread::preStart() { //build map of LLModel refs to instances for callbacks - for (instance_list::iterator iter = mInstanceList.begin(); iter != mInstanceList.end(); ++iter) + for (instance_list::iterator iter = mInstanceList.begin(); + iter != mInstanceList.end(); ++iter) { mInstance[iter->mModel].push_back(*iter); } @@ -1620,7 +1622,7 @@ void LLMeshRepoThread::notifyLoadedMeshes() else { gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, - LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); + LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); } } @@ -1648,7 +1650,7 @@ void LLMeshRepoThread::notifyLoadedMeshes() } S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ //only ever called from main thread +{ //only ever called from main thread LLMutexLock lock(mHeaderMutex); mesh_header_map::iterator iter = mMeshHeader.find(mesh_params.getSculptID()); @@ -1716,7 +1718,8 @@ void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header) { if (data.mModel[i].notNull()) { - LLPointer volume = new LLVolume(volume_params, LLVolumeLODGroup::getVolumeScaleFromDetail(i)); + LLPointer volume = new LLVolume(volume_params, + LLVolumeLODGroup::getVolumeScaleFromDetail(i)); volume->copyVolumeFaces(data.mModel[i]); volume->setMeshAssetLoaded(TRUE); } @@ -1741,7 +1744,7 @@ void LLMeshLODResponder::completedRaw(U32 status, const std::string& reason, if (data_size < (S32)mRequestedBytes) { if (status == 499 || status == 503) - { //timeout or service unavailable, try again + { //timeout or service unavailable, try again LLMeshRepository::sHTTPRetryCount++; gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD); } @@ -1795,7 +1798,7 @@ void LLMeshSkinInfoResponder::completedRaw(U32 status, const std::string& reason if (data_size < (S32)mRequestedBytes) { if (status == 499 || status == 503) - { //timeout or service unavailable, try again + { //timeout or service unavailable, try again LLMeshRepository::sHTTPRetryCount++; gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); } @@ -1849,7 +1852,7 @@ void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& r if (data_size < (S32)mRequestedBytes) { if (status == 499 || status == 503) - { //timeout or service unavailable, try again + { //timeout or service unavailable, try again LLMeshRepository::sHTTPRetryCount++; gMeshRepo.mThread->loadMeshDecomposition(mMeshID); } @@ -1903,7 +1906,7 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re if (data_size < (S32)mRequestedBytes) { if (status == 499 || status == 503) - { //timeout or service unavailable, try again + { //timeout or service unavailable, try again LLMeshRepository::sHTTPRetryCount++; gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); } @@ -1961,7 +1964,7 @@ void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, // and (somewhat more optional than the others) retries // again after some set period of time if (status == 503 || status == 499) - { //retry + { //retry LLMeshRepository::sHTTPRetryCount++; LLMeshRepoThread::HeaderRequest req(mMeshParams); LLMutexLock lock(gMeshRepo.mThread->mMutex); @@ -2071,7 +2074,7 @@ void LLMeshRepository::init() mDecompThread->start(); while (!mDecompThread->mInited) - { //wait for physics decomp thread to init + { //wait for physics decomp thread to init apr_sleep(100); } @@ -2169,7 +2172,7 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para //add volume to list of loading meshes mesh_load_map::iterator iter = mLoadingMeshes[detail].find(mesh_params); if (iter != mLoadingMeshes[detail].end()) - { //request pending for this mesh, append volume id to list + { //request pending for this mesh, append volume id to list iter->second.insert(vobj->getID()); } else @@ -2464,11 +2467,11 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom { decomposition_map::iterator iter = mDecompositionMap.find(decomp->mMeshID); if (iter == mDecompositionMap.end()) - { //just insert decomp into map + { //just insert decomp into map mDecompositionMap[decomp->mMeshID] = decomp; } else - { //merge decomp with existing entry + { //merge decomp with existing entry iter->second->merge(decomp); delete decomp; } @@ -2477,7 +2480,7 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom } void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) -{ //called from main thread +{ //called from main thread S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail()); //get list of objects waiting to be notified this mesh is loaded @@ -2520,7 +2523,7 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol } void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod) -{ //called from main thread +{ //called from main thread //get list of objects waiting to be notified this mesh is loaded mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_params); @@ -2538,7 +2541,7 @@ void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, if (obj_volume && obj_volume->getDetail() == detail && obj_volume->getParams() == mesh_params) - { //should force volume to find most appropriate LOD + { //should force volume to find most appropriate LOD vobj->setVolume(obj_volume->getParams(), lod); } } @@ -2597,7 +2600,7 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) //add volume to list of loading meshes std::set::iterator iter = mLoadingPhysicsShapes.find(mesh_id); if (iter == mLoadingPhysicsShapes.end()) - { //no request pending for this skin info + { //no request pending for this skin info mLoadingPhysicsShapes.insert(mesh_id); mPendingPhysicsShapeRequests.push(mesh_id); } @@ -2625,7 +2628,7 @@ LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id //add volume to list of loading meshes std::set::iterator iter = mLoadingDecompositions.find(mesh_id); if (iter == mLoadingDecompositions.end()) - { //no request pending for this skin info + { //no request pending for this skin info mLoadingDecompositions.insert(mesh_id); mPendingDecompositionRequests.push(mesh_id); } @@ -2918,7 +2921,7 @@ F32 LLMeshRepository::getStreamingCost(LLSD& header, F32 radius, S32* bytes, S32 LLPhysicsDecomp::LLPhysicsDecomp() -: LLThread("Physics Decomp") +: LLThread("Physics Decomp") { mInited = false; mQuitting = false; @@ -3044,7 +3047,7 @@ void LLPhysicsDecomp::doDecomposition() const LLCDParam* param = param_map[name]; if (param == NULL) - { //couldn't find valid parameter + { //couldn't find valid parameter continue; } @@ -3055,7 +3058,7 @@ void LLPhysicsDecomp::doDecomposition() ret = LLConvexDecomposition::getInstance()->setParam(param->mName, (F32) value.asReal()); } else if (param->mType == LLCDParam::LLCD_INTEGER || - param->mType == LLCDParam::LLCD_ENUM) + param->mType == LLCDParam::LLCD_ENUM) { ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asInteger()); } @@ -3260,6 +3263,7 @@ void LLPhysicsDecomp::doDecompositionSingleHull() { decomp->setParam(params[i].mName, params[i].mDefault.mIntOrEnumValue); } +#endif const S32 STAGE_DECOMPOSE = mStageID["Decompose"]; const S32 STAGE_SIMPLIFY = mStageID["Simplify"]; @@ -3565,7 +3569,7 @@ void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp) } if (!decomp.mBaseHull.empty() && decomp.mBaseHullMesh.empty()) - { //get mesh for base hull + { //get mesh for base hull LLCDHull hull; hull.mNumVertices = decomp.mBaseHull.size(); hull.mVertexBase = decomp.mBaseHull[0].mV; @@ -3608,5 +3612,4 @@ bool LLMeshRepository::meshRezEnabled() } return false; } -#endif //MESH_ENABLED diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index e23a5981e..eca3dbbd0 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -128,7 +128,7 @@ public: LLImportMaterial() : mFullbright(false) { - mDiffuseColor.set(1,1,1,1); + mDiffuseColor.set(1, 1, 1, 1); } LLImportMaterial(LLSD& data); @@ -246,10 +246,10 @@ public: static S32 sActiveLODRequests; static U32 sMaxConcurrentRequests; - LLCurlRequest* mCurlRequest; - LLMutex* mMutex; - LLMutex* mHeaderMutex; - LLCondition* mSignal; + LLCurlRequest* mCurlRequest; + LLMutex* mMutex; + LLMutex* mHeaderMutex; + LLCondition* mSignal; bool mWaiting; @@ -464,6 +464,9 @@ private: class LLMeshRepository { +private: + S32 mMeshUploadTimeOut ; //maximum time in seconds to execute an uploading request. + public: //metrics @@ -587,4 +590,3 @@ public: extern LLMeshRepository gMeshRepo; #endif - diff --git a/indra/newview/lluploadfloaterobservers.cpp b/indra/newview/lluploadfloaterobservers.cpp new file mode 100644 index 000000000..c8aef6585 --- /dev/null +++ b/indra/newview/lluploadfloaterobservers.cpp @@ -0,0 +1,63 @@ +/** + * @file lluploadfloaterobservers.cpp + * @brief LLUploadModelPremissionsResponder definition + * + * $LicenseInfo:firstyear=2011&license=viewergpl$ + * + * Copyright (c) 2011, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lluploadfloaterobservers.h" + +LLUploadModelPremissionsResponder::LLUploadModelPremissionsResponder(const LLHandle& observer) +: mObserverHandle(observer) +{ +} + +void LLUploadModelPremissionsResponder::error(U32 status, const std::string& reason) +{ + llwarns << "LLUploadModelPremissionsResponder::error(" + << status << ": " << reason << ")" << llendl; + + LLUploadPermissionsObserver* observer = mObserverHandle.get(); + + if (observer) + { + observer->setPermissonsErrorStatus(status, reason); + } +} + +void LLUploadModelPremissionsResponder::result(const LLSD& content) +{ + LLUploadPermissionsObserver* observer = mObserverHandle.get(); + + if (observer) + { + observer->onPermissionsReceived(content); + } +} diff --git a/indra/newview/lluploadfloaterobservers.h b/indra/newview/lluploadfloaterobservers.h new file mode 100644 index 000000000..0301f2640 --- /dev/null +++ b/indra/newview/lluploadfloaterobservers.h @@ -0,0 +1,112 @@ +/** + * @file lluploadfloaterobservers.h + * @brief LLUploadModelPremissionsResponder declaration + * + * $LicenseInfo:firstyear=2011&license=viewergpl$ + * + * Copyright (c) 2011, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLUPLOADFLOATEROBSERVERS_H +#define LL_LLUPLOADFLOATEROBSERVERS_H + +#include "llfloater.h" +#include "llhttpclient.h" +#include "llui.h" + +class LLUploadPermissionsObserver +{ +public: + + LLUploadPermissionsObserver() { mUploadPermObserverHandle.bind(this); } + virtual ~LLUploadPermissionsObserver() {} + + virtual void onPermissionsReceived(const LLSD& result) = 0; + virtual void setPermissonsErrorStatus(U32 status, const std::string& reason) = 0; + + LLHandle getPermObserverHandle() const + { + return mUploadPermObserverHandle; + } + +protected: + LLRootHandle mUploadPermObserverHandle; +}; + +class LLWholeModelFeeObserver +{ +public: + LLWholeModelFeeObserver() { mWholeModelFeeObserverHandle.bind(this); } + virtual ~LLWholeModelFeeObserver() {} + + virtual void onModelPhysicsFeeReceived(const LLSD& result, std::string upload_url) = 0; + virtual void setModelPhysicsFeeErrorStatus(U32 status, const std::string& reason) = 0; + + LLHandle getWholeModelFeeObserverHandle() const + { + return mWholeModelFeeObserverHandle; + } + +protected: + LLRootHandle mWholeModelFeeObserverHandle; +}; + + +class LLWholeModelUploadObserver +{ +public: + LLWholeModelUploadObserver() { mWholeModelUploadObserverHandle.bind(this); } + virtual ~LLWholeModelUploadObserver() {} + + virtual void onModelUploadSuccess() = 0; + + virtual void onModelUploadFailure() = 0; + + LLHandle getWholeModelUploadObserverHandle() const + { + return mWholeModelUploadObserverHandle; + } + +protected: + LLRootHandle mWholeModelUploadObserverHandle; +}; + + +class LLUploadModelPremissionsResponder : public LLHTTPClient::Responder +{ +public: + + LLUploadModelPremissionsResponder(const LLHandle& observer); + + void error(U32 status, const std::string& reason); + + void result(const LLSD& content); + +private: + LLHandle mObserverHandle; +}; + +#endif /* LL_LLUPLOADFLOATEROBSERVERS_H */ diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index aff062881..7f9e7158e 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -8996,7 +8996,7 @@ static void handle_load_from_xml_continued(AIFilePicker* filepicker) void handle_web_browser_test(void*) { - LLWeb::loadURL("http://secondlife.com/app/search/slurls.html"); + LLFloaterMediaBrowser::showInstance("http://secondlife.com/app/search/slurls.html"); } void handle_buy_currency_test(void*) diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 7c449c251..9c08adf8e 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -32,11 +32,34 @@ #include "llviewerprecompiledheaders.h" +// system libraries +#include + #include "llviewermenufile.h" +// linden libraries +#include "lleconomy.h" +#include "llhttpclient.h" +#include "llimage.h" +#include "llmemberlistener.h" +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include "llstring.h" +#include "lltransactiontypes.h" +#include "lluictrlfactory.h" +#include "lluuid.h" +#include "llvorbisencode.h" +#include "message.h" + // project includes #include "llagent.h" #include "llagentcamera.h" +#include "llappviewer.h" +#include "llassetuploadresponders.h" +#ifdef MESH_UPLOAD +#include "llfloatermodelpreview.h" +#endif #include "llimagejpeg.h" #include "llimagepng.h" @@ -62,7 +85,6 @@ #include "llviewerregion.h" #include "llviewerstats.h" #include "llviewerwindow.h" -#include "llappviewer.h" #include "lluploaddialog.h" // #include "llselectmgr.h" @@ -72,23 +94,9 @@ #include "lllocalinventory.h" // +#include "importtracker.h" -// linden libraries -#include "llassetuploadresponders.h" -#include "lleconomy.h" -#include "llhttpclient.h" -#include "llnotificationsutil.h" -#include "llmemberlistener.h" -#include "llsdserialize.h" -#include "llstring.h" -#include "lltransactiontypes.h" -#include "lluuid.h" -#include "llvorbisencode.h" - -// system libraries -#include - -//#include "importtracker.h" +std::deque gUploadQueue; typedef LLMemberListener view_listener_t; @@ -97,7 +105,8 @@ class LLFileEnableSaveAs : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) { - bool new_value = gFloaterView->getFrontmost() && gFloaterView->getFrontmost()->canSaveAs(); + bool new_value = gFloaterView->getFrontmost() && + gFloaterView->getFrontmost()->canSaveAs(); gMenuHolder->findControl(userdata["control"].asString())->setValue(new_value); return true; } @@ -107,7 +116,8 @@ class LLFileEnableUpload : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) { - bool new_value = gStatusBar && LLGlobalEconomy::Singleton::getInstance() && (gStatusBar->getBalance() >= LLGlobalEconomy::Singleton::getInstance()->getPriceUpload()); + bool new_value = gStatusBar && LLGlobalEconomy::Singleton::getInstance() && + gStatusBar->getBalance() >= LLGlobalEconomy::Singleton::getInstance()->getPriceUpload(); gMenuHolder->findControl(userdata["control"].asString())->setValue(new_value); return true; } @@ -207,7 +217,7 @@ bool AIFileUpload::is_valid(std::string const& filename, ELoadFilter type) // No extension LLSD args; args["FILE"] = short_name; - LLNotificationsUtil::add("NoFileExtension", args); + LLNotifications::instance().add("NoFileExtension", args); return false; } else @@ -250,7 +260,7 @@ bool AIFileUpload::is_valid(std::string const& filename, ELoadFilter type) LLSD args; args["EXTENSION"] = ext; args["VALIDS"] = valid_extensions; - LLNotificationsUtil::add("InvalidFileExtension", args); + LLNotifications::instance().add("InvalidFileExtension", args); return false; } }//end else (non-null extension) @@ -355,7 +365,7 @@ class LLFileUploadBulk : public view_listener_t if(expected_upload_cost) msg.append(llformat("\nWARNING: Each upload costs L$%d if it's not temporary.",expected_upload_cost)); args["MESSAGE"] = msg; - LLNotificationsUtil::add("GenericAlertYesNoCancel", args, LLSD(), onConfirmBulkUploadTemp); + LLNotifications::instance().add("GenericAlertYesNoCancel", args, LLSD(), onConfirmBulkUploadTemp); return true; } @@ -640,6 +650,7 @@ void upload_new_resource(const std::string& src_filename, std::string name, LLSD args; std::string exten = gDirUtilp->getExtension(src_filename); + U32 codec = LLImageBase::getCodecFromExtension(exten); LLAssetType::EType asset_type = LLAssetType::AT_NONE; std::string error_message; @@ -657,12 +668,11 @@ void upload_new_resource(const std::string& src_filename, std::string name, upload_error(error_message, "NofileExtension", filename, args); return; } - else if( exten == "bmp") + else if (codec != IMG_CODEC_INVALID) { + // It's an image file, the upload procedure is the same for all asset_type = LLAssetType::AT_TEXTURE; - if (!LLViewerTextureList::createUploadFile(src_filename, - filename, - IMG_CODEC_BMP )) + if (!LLViewerTextureList::createUploadFile(src_filename, filename, codec)) { error_message = llformat( "Problem with file %s:\n\n%s\n", src_filename.c_str(), LLImage::getLastError().c_str()); @@ -672,51 +682,6 @@ void upload_new_resource(const std::string& src_filename, std::string name, return; } } - else if( exten == "tga") - { - asset_type = LLAssetType::AT_TEXTURE; - if (!LLViewerTextureList::createUploadFile(src_filename, - filename, - IMG_CODEC_TGA )) - { - error_message = llformat("Problem with file %s:\n\n%s\n", - src_filename.c_str(), LLImage::getLastError().c_str()); - args["FILE"] = src_filename; - args["ERROR"] = LLImage::getLastError(); - upload_error(error_message, "ProblemWithFile", filename, args); - return; - } - } - else if( exten == "jpg" || exten == "jpeg") - { - asset_type = LLAssetType::AT_TEXTURE; - if (!LLViewerTextureList::createUploadFile(src_filename, - filename, - IMG_CODEC_JPEG )) - { - error_message = llformat("Problem with file %s:\n\n%s\n", - src_filename.c_str(), LLImage::getLastError().c_str()); - args["FILE"] = src_filename; - args["ERROR"] = LLImage::getLastError(); - upload_error(error_message, "ProblemWithFile", filename, args); - return; - } - } - else if( exten == "png") - { - asset_type = LLAssetType::AT_TEXTURE; - if (!LLViewerTextureList::createUploadFile(src_filename, - filename, - IMG_CODEC_PNG )) - { - error_message = llformat("Problem with file %s:\n\n%s\n", - src_filename.c_str(), LLImage::getLastError().c_str()); - args["FILE"] = src_filename; - args["ERROR"] = LLImage::getLastError(); - upload_error(error_message, "ProblemWithFile", filename, args); - return; - } - } else if(exten == "wav") { asset_type = LLAssetType::AT_SOUND; // tag it as audio @@ -1122,6 +1087,52 @@ void upload_done_callback(const LLUUID& uuid, void* user_data, S32 result, LLExt data = NULL; } +static LLAssetID upload_new_resource_prep(const LLTransactionID& tid, + LLAssetType::EType asset_type, + LLInventoryType::EType& inventory_type, + std::string& name, + const std::string& display_name, + std::string& description) +{ + LLAssetID uuid = generate_asset_id_for_new_upload(tid); + + increase_new_upload_stats(asset_type); + + assign_defaults_and_show_upload_message(asset_type, + inventory_type, + name, + display_name, + description); + + return uuid; +} + +LLSD generate_new_resource_upload_capability_body(LLAssetType::EType asset_type, + const std::string& name, + const std::string& desc, + LLFolderType::EType destination_folder_type, + LLInventoryType::EType inv_type, + U32 next_owner_perms, + U32 group_perms, + U32 everyone_perms) +{ + LLSD body; + + body["folder_id"] = gInventory.findCategoryUUIDForType(destination_folder_type == LLFolderType::FT_NONE ? + LLFolderType::assetTypeToFolderType(asset_type) : + destination_folder_type); + + body["asset_type"] = LLAssetType::lookup(asset_type); + body["inventory_type"] = LLInventoryType::lookup(inv_type); + body["name"] = name; + body["description"] = desc; + body["next_owner_mask"] = LLSD::Integer(next_owner_perms); + body["group_mask"] = LLSD::Integer(group_perms); + body["everyone_mask"] = LLSD::Integer(everyone_perms); + + return body; +} + void upload_new_resource(const LLTransactionID &tid, LLAssetType::EType asset_type, std::string name, std::string desc, S32 compression_info, @@ -1140,51 +1151,20 @@ void upload_new_resource(const LLTransactionID &tid, LLAssetType::EType asset_ty return ; } - LLAssetID uuid = tid.makeAssetID(gAgent.getSecureSessionID()); - - if( LLAssetType::AT_SOUND == asset_type ) - { - LLViewerStats::getInstance()->incStat(LLViewerStats::ST_UPLOAD_SOUND_COUNT ); - } - else - if( LLAssetType::AT_TEXTURE == asset_type ) - { - LLViewerStats::getInstance()->incStat(LLViewerStats::ST_UPLOAD_TEXTURE_COUNT ); - } - else - if( LLAssetType::AT_ANIMATION == asset_type) - { - LLViewerStats::getInstance()->incStat(LLViewerStats::ST_UPLOAD_ANIM_COUNT ); - } - if(LLInventoryType::IT_NONE == inv_type) - { - inv_type = LLInventoryType::defaultForAssetType(asset_type); - } - LLStringUtil::stripNonprintable(name); - LLStringUtil::stripNonprintable(desc); - if(name.empty()) - { - name = "(No Name)"; - } - if(desc.empty()) - { - desc = "(No Description)"; - } - - // At this point, we're ready for the upload. - std::string upload_message = "Uploading...\n\n"; - upload_message.append(display_name); - LLUploadDialog::modalUploadDialog(upload_message); + LLAssetID uuid = upload_new_resource_prep(tid, asset_type, inv_type, + name, display_name, desc); + llinfos << "*** Uploading: " + << "\nType: " << LLAssetType::lookup(asset_type) + << "\nUUID: " << uuid + << "\nName: " << name + << "\nDesc: " << desc + << "\nFolder: " + << gInventory.findCategoryUUIDForType(destination_folder_type == LLFolderType::FT_NONE ? + LLFolderType::assetTypeToFolderType(asset_type) : + destination_folder_type) + << "\nExpected Upload Cost: " << expected_upload_cost << llendl; - llinfos << "*** Uploading: " << llendl; - llinfos << "Type: " << LLAssetType::lookup(asset_type) << llendl; - llinfos << "UUID: " << uuid << llendl; - llinfos << "Name: " << name << llendl; - llinfos << "Desc: " << desc << llendl; - llinfos << "Expected Upload Cost: " << expected_upload_cost << llendl; - lldebugs << "Folder: " << gInventory.findCategoryUUIDForType((destination_folder_type == LLFolderType::FT_NONE) ? LLFolderType::assetTypeToFolderType(asset_type) : destination_folder_type) << llendl; - lldebugs << "Asset Type: " << LLAssetType::lookup(asset_type) << llendl; std::string url = gAgent.getRegion()->getCapability("NewFileAgentInventory"); // BOOL temporary = gSavedSettings.getBOOL("TemporaryUpload"); @@ -1204,11 +1184,8 @@ void upload_new_resource(const LLTransactionID &tid, LLAssetType::EType asset_ty body["everyone_mask"] = LLSD::Integer(everyone_perms); body["expected_upload_cost"] = LLSD::Integer(expected_upload_cost); - //std::ostringstream llsdxml; - //LLSDSerialize::toPrettyXML(body, llsdxml); - //llinfos << "posting body to capability: " << llsdxml.str() << llendl; - - LLHTTPClient::post(url, body, new LLNewAgentInventoryResponder(body, uuid, asset_type)); + LLHTTPClient::post(url, body, + new LLNewAgentInventoryResponder(body, uuid, asset_type)); } else { @@ -1259,6 +1236,64 @@ void upload_new_resource(const LLTransactionID &tid, LLAssetType::EType asset_ty } } +LLAssetID generate_asset_id_for_new_upload(const LLTransactionID& tid) +{ + LLAssetID uuid; + + if (gDisconnected) + { + uuid.setNull(); + } + else + { + uuid = tid.makeAssetID(gAgent.getSecureSessionID()); + } + + return uuid; +} + +void increase_new_upload_stats(LLAssetType::EType asset_type) +{ + if (LLAssetType::AT_SOUND == asset_type) + { + LLViewerStats::getInstance()->incStat(LLViewerStats::ST_UPLOAD_SOUND_COUNT); + } + else if (LLAssetType::AT_TEXTURE == asset_type) + { + LLViewerStats::getInstance()->incStat(LLViewerStats::ST_UPLOAD_TEXTURE_COUNT); + } + else if (LLAssetType::AT_ANIMATION == asset_type) + { + LLViewerStats::getInstance()->incStat(LLViewerStats::ST_UPLOAD_ANIM_COUNT); + } +} + +void assign_defaults_and_show_upload_message(LLAssetType::EType asset_type, + LLInventoryType::EType& inventory_type, + std::string& name, + const std::string& display_name, + std::string& description) +{ + if (LLInventoryType::IT_NONE == inventory_type) + { + inventory_type = LLInventoryType::defaultForAssetType(asset_type); + } + LLStringUtil::stripNonprintable(name); + LLStringUtil::stripNonprintable(description); + if (name.empty()) + { + name = "(No Name)"; + } + if (description.empty()) + { + description = "(No Description)"; + } + + // At this point, we're ready for the upload. + std::string upload_message = "Uploading...\n\n"; + upload_message.append(display_name); + LLUploadDialog::modalUploadDialog(upload_message); +} void init_menu_file() { diff --git a/indra/newview/llviewermenufile.h b/indra/newview/llviewermenufile.h index 8a058ff98..85bd7e166 100644 --- a/indra/newview/llviewermenufile.h +++ b/indra/newview/llviewermenufile.h @@ -38,6 +38,8 @@ // #include "llviewerinventory.h" +#include "statemachine/aifilepicker.h" + class NewResourceItemCallback : public LLInventoryCallback { void fire(const LLUUID& inv_item); @@ -46,36 +48,84 @@ class NewResourceItemCallback : public LLInventoryCallback class LLTransactionID; +extern std::deque gUploadQueue; void init_menu_file(); -void upload_new_resource(const std::string& src_filename, - std::string name, - std::string desc, - S32 compression_info, - LLFolderType::EType destination_folder_type, - LLInventoryType::EType inv_type, - U32 next_owner_perms, - U32 group_perms, - U32 everyone_perms, - const std::string& display_name, - LLAssetStorage::LLStoreAssetCallback callback, - S32 expected_upload_cost, - void *userdata); +void upload_new_resource(const std::string& src_filename, + std::string name, + std::string desc, + S32 compression_info, + LLFolderType::EType destination_folder_type, + LLInventoryType::EType inv_type, + U32 next_owner_perms, + U32 group_perms, + U32 everyone_perms, + const std::string& display_name, + LLAssetStorage::LLStoreAssetCallback callback, + S32 expected_upload_cost, + void *userdata); void upload_new_resource(const LLTransactionID &tid, - LLAssetType::EType type, - std::string name, - std::string desc, - S32 compression_info, - LLFolderType::EType destination_folder_type, - LLInventoryType::EType inv_type, - U32 next_owner_perms, - U32 group_perms, - U32 everyone_perms, - const std::string& display_name, - LLAssetStorage::LLStoreAssetCallback callback, - S32 expected_upload_cost, - void *userdata); + LLAssetType::EType type, + std::string name, + std::string desc, + S32 compression_info, + LLFolderType::EType destination_folder_type, + LLInventoryType::EType inv_type, + U32 next_owner_perms, + U32 group_perms, + U32 everyone_perms, + const std::string& display_name, + LLAssetStorage::LLStoreAssetCallback callback, + S32 expected_upload_cost, + void *userdata); + +LLAssetID generate_asset_id_for_new_upload(const LLTransactionID& tid); + +void increase_new_upload_stats(LLAssetType::EType asset_type); + +void assign_defaults_and_show_upload_message(LLAssetType::EType asset_type, + LLInventoryType::EType& inventory_type, + std::string& name, + const std::string& display_name, + std::string& description); + +LLSD generate_new_resource_upload_capability_body(LLAssetType::EType asset_type, + const std::string& name, + const std::string& desc, + LLFolderType::EType destination_folder_type, + LLInventoryType::EType inv_type, + U32 next_owner_perms, + U32 group_perms, + U32 everyone_perms); + + +class LLFilePickerThread : public LLThread +{ //multi-threaded file picker (runs system specific file picker in background and calls "notify" from main thread) +public: + + static std::queue sDeadQ; + static LLMutex* sMutex; + + static void initClass(); + static void cleanupClass(); + static void clearDead(); + + std::string mFile; + + ELoadFilter mFilter; + + LLFilePickerThread(ELoadFilter filter) + : LLThread("file picker"), mFilter(filter) + { + } + + void getFile(); + + virtual void run(); + + virtual void notify(const std::string& filename) = 0; +}; #endif diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 060d5df69..9d7d2d009 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1732,17 +1732,29 @@ std::string LLViewerRegion::getDescription() const return stringize(*this); } -#if MESH_ENABLED bool LLViewerRegion::meshUploadEnabled() const { - return (mSimulatorFeatures.has("MeshUploadEnabled") && - mSimulatorFeatures["MeshUploadEnabled"].asBoolean()); + if (getCapability("SimulatorFeatures").empty()) + { + return !getCapability("MeshUploadFlag").empty(); + } + else + { + return (mSimulatorFeatures.has("MeshUploadEnabled") && + mSimulatorFeatures["MeshUploadEnabled"].asBoolean()); + } } bool LLViewerRegion::meshRezEnabled() const { - return (mSimulatorFeatures.has("MeshRezEnabled") && + if (getCapability("SimulatorFeatures").empty()) + { + return !getCapability("GetMesh").empty(); + } + else + { + return (mSimulatorFeatures.has("MeshRezEnabled") && mSimulatorFeatures["MeshRezEnabled"].asBoolean()); + } } -#endif //MESH_ENABLED diff --git a/indra/newview/skins/default/xui/en-us/notifications.xml b/indra/newview/skins/default/xui/en-us/notifications.xml index 2293cdb91..93b2a1e80 100644 --- a/indra/newview/skins/default/xui/en-us/notifications.xml +++ b/indra/newview/skins/default/xui/en-us/notifications.xml @@ -1607,6 +1607,17 @@ Unable to upload [FILE] due to the following reason: [REASON] Please try again later. + +This upload will cost L$[PRICE], do you wish to continue with the upload? + + + + + Could not get region capability '[CAPABILITY]'. + + - Your CPU speed does not meet the minimum requirements. From d63df79b85d898ec948d74a4d2fb47bfcab7a35c Mon Sep 17 00:00:00 2001 From: Siana Gearz Date: Sat, 3 Dec 2011 03:51:15 +0100 Subject: [PATCH 2/2] Adding Neck and Root attachment points --- indra/newview/character/avatar_lad.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/indra/newview/character/avatar_lad.xml b/indra/newview/character/avatar_lad.xml index c018f19a8..4ad68ee8d 100755 --- a/indra/newview/character/avatar_lad.xml +++ b/indra/newview/character/avatar_lad.xml @@ -394,6 +394,26 @@ max_attachment_offset="2.0" visible_in_first_person="true" /> + + + +