summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorBrad Payne (Vir Linden) <vir@lindenlab.com>2010-12-29 14:08:47 -0500
committerBrad Payne (Vir Linden) <vir@lindenlab.com>2010-12-29 14:08:47 -0500
commit93ee0b044f77ab07bb9df08bf52c9f4afa713caa (patch)
tree08490033f3628dadb0073bf2b07d5e7af0ec1798 /indra
parent28b628ab7b92243a6e40fcdf87f4e2c5b44150e7 (diff)
parent8204145ab1c4b8f8a509db15f61bbdd264532508 (diff)
merge
Diffstat (limited to 'indra')
-rw-r--r--[-rwxr-xr-x]indra/llcommon/llfasttimer_class.h0
-rw-r--r--indra/llcommon/llqueuedthread.h2
-rw-r--r--[-rwxr-xr-x]indra/llcommon/llthread.cpp0
-rw-r--r--indra/llmessage/llassetstorage.cpp4
-rwxr-xr-xindra/llprimitive/llmodel.cpp23
-rwxr-xr-xindra/llprimitive/llmodel.h3
-rw-r--r--indra/llui/llurlentry.cpp63
-rw-r--r--indra/llui/llurlentry.h12
-rw-r--r--indra/llui/llurlregistry.cpp1
-rw-r--r--indra/llui/tests/llurlentry_test.cpp149
-rw-r--r--indra/newview/CMakeLists.txt12
-rw-r--r--indra/newview/llagent.cpp3
-rw-r--r--[-rwxr-xr-x]indra/newview/llappearancemgr.cpp0
-rw-r--r--[-rwxr-xr-x]indra/newview/llappearancemgr.h0
-rw-r--r--indra/newview/llappviewer.cpp124
-rw-r--r--indra/newview/llappviewer.h4
-rw-r--r--[-rwxr-xr-x]indra/newview/llcallbacklist.cpp0
-rw-r--r--[-rwxr-xr-x]indra/newview/llcallbacklist.h0
-rwxr-xr-xindra/newview/llfloatermodelpreview.cpp8508
-rw-r--r--[-rwxr-xr-x]indra/newview/llfloatermodelpreview.h665
-rw-r--r--indra/newview/llfloatermodelwizard.cpp244
-rw-r--r--indra/newview/llfloatermodelwizard.h45
-rw-r--r--indra/newview/llinspecttoast.cpp11
-rwxr-xr-xindra/newview/llmeshrepository.cpp80
-rw-r--r--indra/newview/llmeshrepository.h16
-rw-r--r--indra/newview/llpanelavatar.cpp18
-rw-r--r--indra/newview/llremoteparcelrequest.cpp13
-rw-r--r--indra/newview/llsidepaneliteminfo.cpp9
-rw-r--r--indra/newview/llsidepaneliteminfo.h1
-rw-r--r--indra/newview/llsimplestat.h158
-rw-r--r--indra/newview/lltexturefetch.cpp669
-rw-r--r--indra/newview/lltexturefetch.h88
-rw-r--r--indra/newview/lltoastgroupnotifypanel.cpp1
-rw-r--r--indra/newview/llviewerassetstats.cpp618
-rw-r--r--indra/newview/llviewerassetstats.h334
-rw-r--r--indra/newview/llviewerassetstorage.cpp129
-rw-r--r--indra/newview/llviewerassetstorage.h11
-rw-r--r--indra/newview/llviewercontrol.cpp40
-rw-r--r--indra/newview/llviewerfloaterreg.cpp26
-rw-r--r--indra/newview/llviewerregion.cpp1
-rw-r--r--indra/newview/skins/default/xui/en/floater_model_preview.xml160
-rw-r--r--indra/newview/skins/default/xui/en/floater_model_wizard.xml854
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml23
-rw-r--r--indra/newview/skins/default/xui/en/panel_my_profile.xml18
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_advanced.xml2
-rw-r--r--indra/newview/tests/llsimplestat_test.cpp586
-rw-r--r--indra/newview/tests/llviewerassetstats_test.cpp990
47 files changed, 9708 insertions, 5010 deletions
diff --git a/indra/llcommon/llfasttimer_class.h b/indra/llcommon/llfasttimer_class.h
index 827747f0c6..827747f0c6 100755..100644
--- a/indra/llcommon/llfasttimer_class.h
+++ b/indra/llcommon/llfasttimer_class.h
diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h
index c75e0e2bbf..a53b22f6fc 100644
--- a/indra/llcommon/llqueuedthread.h
+++ b/indra/llcommon/llqueuedthread.h
@@ -179,7 +179,7 @@ public:
void waitOnPending();
void printQueueStats();
- S32 getPending();
+ virtual S32 getPending();
bool getThreaded() { return mThreaded ? true : false; }
// Request accessors
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index b4617c5453..b4617c5453 100755..100644
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp
index c5864b30c7..aada16cc9a 100644
--- a/indra/llmessage/llassetstorage.cpp
+++ b/indra/llmessage/llassetstorage.cpp
@@ -513,6 +513,10 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, LL
}
+//
+// *NOTE: Logic here is replicated in LLViewerAssetStorage::_queueDataRequest.
+// Changes here may need to be replicated in the viewer's derived class.
+//
void LLAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType atype,
LLGetAssetCallback callback,
void *user_data, BOOL duplicate,
diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp
index 2a8505e16e..28f152f49c 100755
--- a/indra/llprimitive/llmodel.cpp
+++ b/indra/llprimitive/llmodel.cpp
@@ -27,6 +27,7 @@
#include "linden_common.h"
#include "llmodel.h"
+#include "llconvexdecomposition.h"
#include "llsdserialize.h"
#include "llvector4a.h"
@@ -57,7 +58,15 @@ const int MODEL_NAMES_LENGTH = sizeof(model_names) / sizeof(std::string);
LLModel::LLModel(LLVolumeParams& params, F32 detail)
: LLVolume(params, detail), mNormalizedScale(1,1,1), mNormalizedTranslation(0,0,0)
{
+ mDecompID = -1;
+}
+LLModel::~LLModel()
+{
+ if (mDecompID >= 0)
+ {
+ LLConvexDecomposition::getInstance()->deleteDecomposition(mDecompID);
+ }
}
void load_face_from_dom_inputs(LLVolumeFace& face, const domInputLocalOffset_Array& inputs, U32 min_idx, U32 max_idx)
@@ -941,11 +950,10 @@ void LLModel::normalizeVolumeFaces()
LLVector4a size;
size.setSub(max, min);
- // To make the model fit within
- // the unit cube with only the largest
- // dimensions fitting on the surface of the cube,
- // calculate the largest extent on any axis
- F32 scale = 1.f/llmax(llmax(size[0], size[1]), size[2]);
+ // Compute scale as reciprocal of size
+ LLVector4a scale;
+ scale.splat(1.f);
+ scale.div(size);
for (U32 i = 0; i < mVolumeFaces.size(); ++i)
{
@@ -974,7 +982,10 @@ void LLModel::normalizeVolumeFaces()
// we would need to multiply the model
// by to get the original size of the
// model instead of the normalized size.
- mNormalizedScale = LLVector3(1,1,1) / scale;
+ LLVector4a normalized_scale;
+ normalized_scale.splat(1.f);
+ normalized_scale.div(scale);
+ mNormalizedScale.set(normalized_scale.getF32ptr());
mNormalizedTranslation.set(trans.getF32ptr());
mNormalizedTranslation *= -1.f;
}
diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h
index 9cc734ff59..ebf37904d4 100755
--- a/indra/llprimitive/llmodel.h
+++ b/indra/llprimitive/llmodel.h
@@ -56,6 +56,8 @@ public:
typedef std::vector<LLVector3> hull;
LLModel(LLVolumeParams& params, F32 detail);
+ ~LLModel();
+
static LLSD writeModel(
std::string filename,
LLModel* physics,
@@ -193,6 +195,7 @@ public:
LLVector3 mNormalizedTranslation;
// convex hull decomposition
+ S32 mDecompID;
convex_hull_decomposition mConvexHullDecomp;
void setConvexHullDecomposition(
const convex_hull_decomposition& decomp);
diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp
index e51f28e2e9..4f7b4be526 100644
--- a/indra/llui/llurlentry.cpp
+++ b/indra/llui/llurlentry.cpp
@@ -806,6 +806,69 @@ std::string LLUrlEntryPlace::getLocation(const std::string &url) const
}
//
+// LLUrlEntryRegion Describes secondlife:///app/region/REGION_NAME/X/Y/Z URLs, e.g.
+// secondlife:///app/region/Ahern/128/128/0
+//
+LLUrlEntryRegion::LLUrlEntryRegion()
+{
+ mPattern = boost::regex("secondlife:///app/region/[^/\\s]+(/\\d+)?(/\\d+)?(/\\d+)?/?",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_slurl.xml";
+ mTooltip = LLTrans::getString("TooltipSLURL");
+}
+
+std::string LLUrlEntryRegion::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ //
+ // we handle SLURLs in the following formats:
+ // - secondlife:///app/region/Place/X/Y/Z
+ // - secondlife:///app/region/Place/X/Y
+ // - secondlife:///app/region/Place/X
+ // - secondlife:///app/region/Place
+ //
+
+ LLSD path_array = LLURI(url).pathArray();
+ S32 path_parts = path_array.size();
+
+ if (path_parts < 3) // no region name
+ {
+ llwarns << "Failed to parse url [" << url << "]" << llendl;
+ return url;
+ }
+
+ std::string label = unescapeUrl(path_array[2]); // region name
+
+ if (path_parts > 3) // secondlife:///app/region/Place/X
+ {
+ std::string x = path_array[3];
+ label += " (" + x;
+
+ if (path_parts > 4) // secondlife:///app/region/Place/X/Y
+ {
+ std::string y = path_array[4];
+ label += "," + y;
+
+ if (path_parts > 5) // secondlife:///app/region/Place/X/Y/Z
+ {
+ std::string z = path_array[5];
+ label = label + "," + z;
+ }
+ }
+
+ label += ")";
+ }
+
+ return label;
+}
+
+std::string LLUrlEntryRegion::getLocation(const std::string &url) const
+{
+ LLSD path_array = LLURI(url).pathArray();
+ std::string region_name = unescapeUrl(path_array[2]);
+ return region_name;
+}
+
+//
// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g.,
// secondlife:///app/teleport/Ahern/50/50/50/
// x-grid-location-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/
diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h
index 43a667c390..1791739061 100644
--- a/indra/llui/llurlentry.h
+++ b/indra/llui/llurlentry.h
@@ -302,6 +302,18 @@ public:
};
///
+/// LLUrlEntryRegion Describes a Second Life location Url, e.g.,
+/// secondlife:///app/region/Ahern/128/128/0
+///
+class LLUrlEntryRegion : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryRegion();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+ /*virtual*/ std::string getLocation(const std::string &url) const;
+};
+
+///
/// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g.,
/// secondlife:///app/teleport/Ahern/50/50/50/
///
diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp
index 478b412d5e..523ee5d78c 100644
--- a/indra/llui/llurlregistry.cpp
+++ b/indra/llui/llurlregistry.cpp
@@ -54,6 +54,7 @@ LLUrlRegistry::LLUrlRegistry()
registerUrl(new LLUrlEntryGroup());
registerUrl(new LLUrlEntryParcel());
registerUrl(new LLUrlEntryTeleport());
+ registerUrl(new LLUrlEntryRegion());
registerUrl(new LLUrlEntryWorldMap());
registerUrl(new LLUrlEntryObjectIM());
registerUrl(new LLUrlEntryPlace());
diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp
index 59c0826ad7..8f0a48018f 100644
--- a/indra/llui/tests/llurlentry_test.cpp
+++ b/indra/llui/tests/llurlentry_test.cpp
@@ -103,6 +103,45 @@ namespace tut
ensure_equals(testname, url, expected);
}
+ void dummyCallback(const std::string &url, const std::string &label, const std::string& icon)
+ {
+ }
+
+ void testLabel(const std::string &testname, LLUrlEntryBase &entry,
+ const char *text, const std::string &expected)
+ {
+ boost::regex regex = entry.getPattern();
+ std::string label = "";
+ boost::cmatch result;
+ bool found = boost::regex_search(text, result, regex);
+ if (found)
+ {
+ S32 start = static_cast<U32>(result[0].first - text);
+ S32 end = static_cast<U32>(result[0].second - text);
+ std::string url = std::string(text+start, end-start);
+ label = entry.getLabel(url, boost::bind(dummyCallback, _1, _2, _3));
+ }
+ ensure_equals(testname, label, expected);
+ }
+
+ void testLocation(const std::string &testname, LLUrlEntryBase &entry,
+ const char *text, const std::string &expected)
+ {
+ boost::regex regex = entry.getPattern();
+ std::string location = "";
+ boost::cmatch result;
+ bool found = boost::regex_search(text, result, regex);
+ if (found)
+ {
+ S32 start = static_cast<U32>(result[0].first - text);
+ S32 end = static_cast<U32>(result[0].second - text);
+ std::string url = std::string(text+start, end-start);
+ location = entry.getLocation(url);
+ }
+ ensure_equals(testname, location, expected);
+ }
+
+
template<> template<>
void object::test<1>()
{
@@ -697,4 +736,114 @@ namespace tut
"<nolink>My Object</nolink>",
"My Object");
}
+
+ template<> template<>
+ void object::test<13>()
+ {
+ //
+ // test LLUrlEntryRegion - secondlife:///app/region/<location> URLs
+ //
+ LLUrlEntryRegion url;
+
+ // Regex tests.
+ testRegex("no valid region", url,
+ "secondlife:///app/region/",
+ "");
+
+ testRegex("invalid coords", url,
+ "secondlife:///app/region/Korea2/a/b/c",
+ "secondlife:///app/region/Korea2/"); // don't count invalid coords
+
+ testRegex("Ahern (50,50,50) [1]", url,
+ "secondlife:///app/region/Ahern/50/50/50/",
+ "secondlife:///app/region/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50,50) [2]", url,
+ "XXX secondlife:///app/region/Ahern/50/50/50/ XXX",
+ "secondlife:///app/region/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50,50) [3]", url,
+ "XXX secondlife:///app/region/Ahern/50/50/50 XXX",
+ "secondlife:///app/region/Ahern/50/50/50");
+
+ testRegex("Ahern (50,50,50) multicase", url,
+ "XXX secondlife:///app/region/Ahern/50/50/50/ XXX",
+ "secondlife:///app/region/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50) [1]", url,
+ "XXX secondlife:///app/region/Ahern/50/50/ XXX",
+ "secondlife:///app/region/Ahern/50/50/");
+
+ testRegex("Ahern (50,50) [2]", url,
+ "XXX secondlife:///app/region/Ahern/50/50 XXX",
+ "secondlife:///app/region/Ahern/50/50");
+
+ // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat
+ testRegex("Region with brackets", url,
+ "XXX secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30 XXX",
+ "secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30");
+
+ // DEV-35459: SLURLs and teleport Links not parsed properly
+ testRegex("Region with quote", url,
+ "XXX secondlife:///app/region/A'ksha%20Oasis/41/166/701 XXX",
+ "secondlife:///app/region/A%27ksha%20Oasis/41/166/701");
+
+ // Rendering tests.
+ testLabel("Render /app/region/Ahern/50/50/50/", url,
+ "secondlife:///app/region/Ahern/50/50/50/",
+ "Ahern (50,50,50)");
+
+ testLabel("Render /app/region/Ahern/50/50/50", url,
+ "secondlife:///app/region/Ahern/50/50/50",
+ "Ahern (50,50,50)");
+
+ testLabel("Render /app/region/Ahern/50/50/", url,
+ "secondlife:///app/region/Ahern/50/50/",
+ "Ahern (50,50)");
+
+ testLabel("Render /app/region/Ahern/50/50", url,
+ "secondlife:///app/region/Ahern/50/50",
+ "Ahern (50,50)");
+
+ testLabel("Render /app/region/Ahern/50/", url,
+ "secondlife:///app/region/Ahern/50/",
+ "Ahern (50)");
+
+ testLabel("Render /app/region/Ahern/50", url,
+ "secondlife:///app/region/Ahern/50",
+ "Ahern (50)");
+
+ testLabel("Render /app/region/Ahern/", url,
+ "secondlife:///app/region/Ahern/",
+ "Ahern");
+
+ testLabel("Render /app/region/Ahern/ within context", url,
+ "XXX secondlife:///app/region/Ahern/ XXX",
+ "Ahern");
+
+ testLabel("Render /app/region/Ahern", url,
+ "secondlife:///app/region/Ahern",
+ "Ahern");
+
+ testLabel("Render /app/region/Ahern within context", url,
+ "XXX secondlife:///app/region/Ahern XXX",
+ "Ahern");
+
+ testLabel("Render /app/region/Product%20Engine/", url,
+ "secondlife:///app/region/Product%20Engine/",
+ "Product Engine");
+
+ testLabel("Render /app/region/Product%20Engine", url,
+ "secondlife:///app/region/Product%20Engine",
+ "Product Engine");
+
+ // Location parsing texts.
+ testLocation("Location /app/region/Ahern/50/50/50/", url,
+ "secondlife:///app/region/Ahern/50/50/50/",
+ "Ahern");
+
+ testLocation("Location /app/region/Product%20Engine", url,
+ "secondlife:///app/region/Product%20Engine",
+ "Product Engine");
+ }
}
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index fe3e850a03..c6b1266206 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -493,6 +493,7 @@ set(viewer_SOURCE_FILES
llvectorperfoptions.cpp
llversioninfo.cpp
llviewchildren.cpp
+ llviewerassetstats.cpp
llviewerassetstorage.cpp
llviewerassettype.cpp
llviewerattachmenu.cpp
@@ -1032,6 +1033,7 @@ set(viewer_HEADER_FILES
llvectorperfoptions.h
llversioninfo.h
llviewchildren.h
+ llviewerassetstats.h
llviewerassetstorage.h
llviewerassettype.h
llviewerattachmenu.h
@@ -1990,6 +1992,16 @@ if (LL_TESTS)
"${test_libs}"
)
+ LL_ADD_INTEGRATION_TEST(llsimplestat
+ ""
+ "${test_libs}"
+ )
+
+ LL_ADD_INTEGRATION_TEST(llviewerassetstats
+ llviewerassetstats.cpp
+ "${test_libs}"
+ )
+
#ADD_VIEWER_BUILD_TEST(llmemoryview viewer)
#ADD_VIEWER_BUILD_TEST(llagentaccess viewer)
#ADD_VIEWER_BUILD_TEST(llworldmap viewer)
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 4a23081069..cc8491911d 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -637,6 +637,9 @@ void LLAgent::setRegion(LLViewerRegion *regionp)
// Update all of the regions.
LLWorld::getInstance()->updateAgentOffset(mAgentOriginGlobal);
}
+
+ // Pass new region along to metrics components that care about this level of detail.
+ LLAppViewer::metricsUpdateRegion(regionp->getHandle());
}
mRegionp = regionp;
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index 0e080e713b..0e080e713b 100755..100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h
index 4b1d95cf25..4b1d95cf25 100755..100644
--- a/indra/newview/llappearancemgr.h
+++ b/indra/newview/llappearancemgr.h
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 872f59a75d..55ecc9bd20 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -195,6 +195,7 @@
#include "llparcel.h"
#include "llavatariconctrl.h"
#include "llgroupiconctrl.h"
+#include "llviewerassetstats.h"
// Include for security api initialization
#include "llsecapi.h"
@@ -338,6 +339,14 @@ static std::string gWindowTitle;
LLAppViewer::LLUpdaterInfo *LLAppViewer::sUpdaterInfo = NULL ;
+//----------------------------------------------------------------------------
+// Metrics logging control constants
+//----------------------------------------------------------------------------
+static const F32 METRICS_INTERVAL_DEFAULT = 600.0;
+static const F32 METRICS_INTERVAL_QA = 30.0;
+static F32 app_metrics_interval = METRICS_INTERVAL_DEFAULT;
+static bool app_metrics_qa_mode = false;
+
void idle_afk_check()
{
// check idle timers
@@ -659,6 +668,21 @@ bool LLAppViewer::init()
// Called before threads are created.
LLCurl::initClass();
LLMachineID::init();
+
+ {
+ // Viewer metrics initialization
+ static LLCachedControl<bool> metrics_submode(gSavedSettings,
+ "QAModeMetrics",
+ false,
+ "Enables QA features (logging, faster cycling) for metrics collector");
+
+ if (metrics_submode)
+ {
+ app_metrics_qa_mode = true;
+ app_metrics_interval = METRICS_INTERVAL_QA;
+ }
+ LLViewerAssetStatsFF::init();
+ }
initThreads();
writeSystemInfo();
@@ -1238,6 +1262,7 @@ bool LLAppViewer::mainLoop()
break;
}
}
+ gMeshRepo.update() ;
if(!total_work_pending) //pause texture fetching threads if nothing to process.
{
@@ -1741,6 +1766,8 @@ bool LLAppViewer::cleanup()
LLWatchdog::getInstance()->cleanup();
+ LLViewerAssetStatsFF::cleanup();
+
llinfos << "Shutting down message system" << llendflush;
end_messaging_system();
@@ -1807,7 +1834,10 @@ bool LLAppViewer::initThreads()
// Image decoding
LLAppViewer::sImageDecodeThread = new LLImageDecodeThread(enable_threads && true);
LLAppViewer::sTextureCache = new LLTextureCache(enable_threads && true);
- LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(), sImageDecodeThread, enable_threads && true);
+ LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(),
+ sImageDecodeThread,
+ enable_threads && true,
+ app_metrics_qa_mode);
LLImage::initClass();
if (LLFastTimer::sLog || LLFastTimer::sMetricLog)
@@ -3078,6 +3108,9 @@ void LLAppViewer::requestQuit()
return;
}
+ // Try to send metrics back to the grid
+ metricsSend(!gDisconnected);
+
LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, TRUE);
effectp->setPositionGlobal(gAgent.getPositionGlobal());
effectp->setColor(LLColor4U(gAgent.getEffectColor()));
@@ -3859,6 +3892,11 @@ void LLAppViewer::idle()
llinfos << "Unknown object updates: " << gObjectList.mNumUnknownUpdates << llendl;
gObjectList.mNumUnknownUpdates = 0;
}
+
+ // ViewerMetrics FPS piggy-backing on the debug timer.
+ // The 5-second interval is nice for this purpose. If the object debug
+ // bit moves or is disabled, please give this a suitable home.
+ LLViewerAssetStatsFF::record_fps_main(gFPSClamped);
}
}
@@ -3901,6 +3939,18 @@ void LLAppViewer::idle()
gInventory.idleNotifyObservers();
}
+ // Metrics logging (LLViewerAssetStats, etc.)
+ {
+ static LLTimer report_interval;
+
+ // *TODO: Add configuration controls for this
+ if (report_interval.getElapsedTimeF32() >= app_metrics_interval)
+ {
+ metricsSend(! gDisconnected);
+ report_interval.reset();
+ }
+ }
+
if (gDisconnected)
{
return;
@@ -4808,3 +4858,75 @@ bool LLAppViewer::getMasterSystemAudioMute()
{
return gSavedSettings.getBOOL("MuteAudio");
}
+
+//----------------------------------------------------------------------------
+// Metrics-related methods (static and otherwise)
+//----------------------------------------------------------------------------
+
+/**
+ * LLViewerAssetStats collects data on a per-region (as defined by the agent's
+ * location) so we need to tell it about region changes which become a kind of
+ * hidden variable/global state in the collectors. For collectors not running
+ * on the main thread, we need to send a message to move the data over safely
+ * and cheaply (amortized over a run).
+ */
+void LLAppViewer::metricsUpdateRegion(U64 region_handle)
+{
+ if (0 != region_handle)
+ {
+ LLViewerAssetStatsFF::set_region_main(region_handle);
+ if (LLAppViewer::sTextureFetch)
+ {
+ // Send a region update message into 'thread1' to get the new region.
+ LLAppViewer::sTextureFetch->commandSetRegion(region_handle);
+ }
+ else
+ {
+ // No 'thread1', a.k.a. TextureFetch, so update directly
+ LLViewerAssetStatsFF::set_region_thread1(region_handle);
+ }
+ }
+}
+
+
+/**
+ * Attempts to start a multi-threaded metrics report to be sent back to
+ * the grid for consumption.
+ */
+void LLAppViewer::metricsSend(bool enable_reporting)
+{
+ if (! gViewerAssetStatsMain)
+ return;
+
+ if (LLAppViewer::sTextureFetch)
+ {
+ LLViewerRegion * regionp = gAgent.getRegion();
+
+ if (enable_reporting && regionp)
+ {
+ std::string caps_url = regionp->getCapability("ViewerMetrics");
+
+ // Make a copy of the main stats to send into another thread.
+ // Receiving thread takes ownership.
+ LLViewerAssetStats * main_stats(new LLViewerAssetStats(*gViewerAssetStatsMain));
+
+ // Send a report request into 'thread1' to get the rest of the data
+ // and provide some additional parameters while here.
+ LLAppViewer::sTextureFetch->commandSendMetrics(caps_url,
+ gAgentSessionID,
+ gAgentID,
+ main_stats);
+ main_stats = 0; // Ownership transferred
+ }
+ else
+ {
+ LLAppViewer::sTextureFetch->commandDataBreak();
+ }
+ }
+
+ // Reset even if we can't report. Rather than gather up a huge chunk of
+ // data, we'll keep to our sampling interval and retain the data
+ // resolution in time.
+ gViewerAssetStatsMain->reset();
+}
+
diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h
index 7c946b04a5..a18e6cbb02 100644
--- a/indra/newview/llappviewer.h
+++ b/indra/newview/llappviewer.h
@@ -169,6 +169,10 @@ public:
// mute/unmute the system's master audio
virtual void setMasterSystemAudioMute(bool mute);
virtual bool getMasterSystemAudioMute();
+
+ // Metrics policy helper statics.
+ static void metricsUpdateRegion(U64 region_handle);
+ static void metricsSend(bool enable_reporting);
protected:
virtual bool initWindow(); // Initialize the viewer's window.
diff --git a/indra/newview/llcallbacklist.cpp b/indra/newview/llcallbacklist.cpp
index 357a6582d1..357a6582d1 100755..100644
--- a/indra/newview/llcallbacklist.cpp
+++ b/indra/newview/llcallbacklist.cpp
diff --git a/indra/newview/llcallbacklist.h b/indra/newview/llcallbacklist.h
index 97f3bfd9ee..97f3bfd9ee 100755..100644
--- a/indra/newview/llcallbacklist.h
+++ b/indra/newview/llcallbacklist.h
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index f9648b0e3b..2f1ce25511 100755
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -1,4267 +1,4241 @@
-/**
- * @file llfloatermodelpreview.cpp
- * @brief LLFloaterModelPreview class implementation
- *
- * $LicenseInfo:firstyear=2004&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include "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 "llfilepicker.h"
-#include "llimagebmp.h"
-#include "llimagetga.h"
-#include "llimagejpeg.h"
-#include "llimagepng.h"
-
-#include "llagent.h"
-#include "llbutton.h"
-#include "llcombobox.h"
-#include "lldatapacker.h"
-#include "lldrawable.h"
-#include "lldrawpoolavatar.h"
-#include "llrender.h"
-#include "llface.h"
-#include "lleconomy.h"
-#include "llfocusmgr.h"
-#include "llfloaterperms.h"
-#include "lliconctrl.h"
-#include "llmatrix4a.h"
-#include "llmenubutton.h"
-#include "llmeshrepository.h"
-#include "llsdutil_math.h"
-#include "lltextbox.h"
-#include "lltoolmgr.h"
-#include "llui.h"
-#include "llvector4a.h"
-#include "llviewercamera.h"
-#include "llviewerwindow.h"
-#include "llvoavatar.h"
-#include "llvoavatarself.h"
-#include "pipeline.h"
-#include "lluictrlfactory.h"
-#include "llviewermenu.h"
-#include "llviewermenufile.h"
-#include "llviewerregion.h"
-#include "llviewertexturelist.h"
-#include "llstring.h"
-#include "llbutton.h"
-#include "llcheckboxctrl.h"
-#include "llsliderctrl.h"
-#include "llspinctrl.h"
-#include "lltoggleablemenu.h"
-#include "llvfile.h"
-#include "llvfs.h"
-#include "llcallbacklist.h"
-
-#include "glod/glod.h"
-
-//static
-S32 LLFloaterModelPreview::sUploadAmount = 10;
-LLFloaterModelPreview* LLFloaterModelPreview::sInstance = NULL;
-
-const S32 PREVIEW_BORDER_WIDTH = 2;
-const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH;
-const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE;
-const S32 PREF_BUTTON_HEIGHT = 16 + 7 + 16;
-const S32 PREVIEW_TEXTURE_HEIGHT = 300;
-const S32 NUM_LOD = 4;
-
-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."
-};
-
-
-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;
- }
- }
-
- 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 LLSD& key) :
-LLFloater(key)
-{
- sInstance = this;
- mLastMouseX = 0;
- mLastMouseY = 0;
- mGLName = 0;
-}
-
-//-----------------------------------------------------------------------------
-// postBuild()
-//-----------------------------------------------------------------------------
-BOOL LLFloaterModelPreview::postBuild()
-{
- if (!LLFloater::postBuild())
- {
- return FALSE;
- }
-
- setViewOption("show_textures", true);
-
- childSetAction("lod_browse", onBrowseLOD, this);
-
- childSetCommitCallback("crease_angle", onGenerateNormalsCommit, this);
- childSetCommitCallback("generate_normals", onGenerateNormalsCommit, this);
-
- childSetCommitCallback("lod_generate", onAutoFillCommit, this);
-
- childSetCommitCallback("lod_mode", onLODParamCommit, this);
- childSetCommitCallback("lod_error_threshold", onLODParamCommit, this);
- childSetCommitCallback("lod_triangle_limit", onLODParamCommit, this);
- childSetCommitCallback("build_operator", onLODParamCommit, this);
- childSetCommitCallback("queue_mode", onLODParamCommit, this);
- childSetCommitCallback("border_mode", onLODParamCommit, this);
- childSetCommitCallback("share_tolerance", onLODParamCommit, this);
-
- childSetTextArg("status", "[STATUS]", getString("status_idle"));
-
- //childSetLabelArg("ok_btn", "[AMOUNT]", llformat("%d",sUploadAmount));
- childSetAction("ok_btn", onUpload, this);
- childDisable("ok_btn");
-
- childSetAction("clear_materials", onClearMaterials, this);
-
- childSetCommitCallback("preview_lod_combo", onPreviewLODCommit, this);
-
- childSetCommitCallback("upload_skin", onUploadSkinCommit, this);
- childSetCommitCallback("upload_joints", onUploadJointsCommit, this);
-
- childSetCommitCallback("import_scale", onImportScaleCommit, this);
-
- childSetCommitCallback("lod_file_or_limit", refresh, this);
- childSetCommitCallback("physics_load_radio", refresh, this);
-
- childDisable("upload_skin");
- childDisable("upload_joints");
- childDisable("ok_btn");
-
- mViewOptionMenuButton = getChild<LLMenuButton>("options_gear_btn");
-
- mCommitCallbackRegistrar.add("ModelImport.ViewOption.Action", boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _2));
- mEnableCallbackRegistrar.add("ModelImport.ViewOption.Check", boost::bind(&LLFloaterModelPreview::isViewOptionChecked, this, _2));
- mEnableCallbackRegistrar.add("ModelImport.ViewOption.Enabled", boost::bind(&LLFloaterModelPreview::isViewOptionEnabled, this, _2));
-
-
-
- mViewOptionMenu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>("menu_model_import_gear_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
- mViewOptionMenuButton->setMenu(mViewOptionMenu, LLMenuButton::MP_BOTTOM_LEFT);
-
- initDecompControls();
-
- LLView* preview_panel = getChild<LLView>("preview_panel");
-
- mPreviewRect = preview_panel->getRect();
-
- mModelPreview = new LLModelPreview(512, 512, this);
- mModelPreview->setPreviewTarget(16.f);
-
- //set callbacks for left click on line editor rows
- for (U32 i = 0; i <= LLModel::LOD_HIGH; i++)
- {
- LLTextBox* text = getChild<LLTextBox>(lod_label_name[i]);
- if (text)
- {
- text->setMouseDownCallback(boost::bind(&LLModelPreview::setPreviewLOD, mModelPreview, i));
- }
-
- text = getChild<LLTextBox>(lod_triangles_name[i]);
- if (text)
- {
- text->setMouseDownCallback(boost::bind(&LLModelPreview::setPreviewLOD, mModelPreview, i));
- }
-
- text = getChild<LLTextBox>(lod_vertices_name[i]);
- if (text)
- {
- text->setMouseDownCallback(boost::bind(&LLModelPreview::setPreviewLOD, mModelPreview, i));
- }
-
- text = getChild<LLTextBox>(lod_status_name[i]);
- if (text)
- {
- text->setMouseDownCallback(boost::bind(&LLModelPreview::setPreviewLOD, mModelPreview, i));
- }
- }
-
- return TRUE;
-}
-
-//-----------------------------------------------------------------------------
-// LLFloaterModelPreview()
-//-----------------------------------------------------------------------------
-LLFloaterModelPreview::~LLFloaterModelPreview()
-{
- sInstance = NULL;
-
- if ( mModelPreview->containsRiggedAsset() )
- {
- gAgentAvatarp->resetJointPositions();
- }
-
- delete mModelPreview;
-
- if (mGLName)
- {
- LLImageGL::deleteTextures(1, &mGLName );
- }
-}
-
-void LLFloaterModelPreview::onViewOptionChecked(const LLSD& userdata)
-{
- mViewOption[userdata.asString()] = !mViewOption[userdata.asString()];
- mModelPreview->refresh();
-}
-
-bool LLFloaterModelPreview::isViewOptionChecked(const LLSD& userdata)
-{
- return mViewOption[userdata.asString()];
-}
-
-bool LLFloaterModelPreview::isViewOptionEnabled(const LLSD& userdata)
-{
- return !mViewOptionDisabled[userdata.asString()];
-}
-
-void LLFloaterModelPreview::setViewOptionEnabled(const std::string& option, bool enabled)
-{
- mViewOptionDisabled[option] = !enabled;
-}
-
-void LLFloaterModelPreview::enableViewOption(const std::string& option)
-{
- setViewOptionEnabled(option, true);
-}
-
-void LLFloaterModelPreview::disableViewOption(const std::string& option)
-{
- setViewOptionEnabled(option, false);
-}
-
-void LLFloaterModelPreview::setViewOption(const std::string& option, bool value)
-{
- mViewOption[option] = value;
-}
-
-void LLFloaterModelPreview::loadModel(S32 lod)
-{
- mModelPreview->mLoading = true;
-
- (new LLMeshFilePicker(mModelPreview, lod))->getFile();
-}
-
-//static
-void LLFloaterModelPreview::onImportScaleCommit(LLUICtrl*,void* userdata)
-{
- LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata;
-
- if (!fp->mModelPreview)
- {
- return;
- }
-
- fp->mModelPreview->calcResourceCost();
- 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;
-
- LLCtrlSelectionInterface* iface = fp->childGetSelectionInterface("preview_lod_combo");
- if (iface)
- {
- which_mode = iface->getFirstSelectedIndex();
- }
- which_mode = (NUM_LOD-1)-which_mode; // 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::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)
-{
- LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata;
- fp->mModelPreview->genLODs(fp->mModelPreview->mPreviewLOD);
- fp->mModelPreview->updateStatusMessages();
- fp->mModelPreview->refresh();
-}
-
-
-//-----------------------------------------------------------------------------
-// draw()
-//-----------------------------------------------------------------------------
-void LLFloaterModelPreview::draw()
-{
- LLFloater::draw();
- LLRect r = getRect();
-
- mModelPreview->update();
-
- if (!mModelPreview->mLoading)
- {
- 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 (mCurRequest.notNull())
- {
- childSetTextArg("status", "[STATUS]", mCurRequest->mStatusMessage);
- }
-
- U32 resource_cost = mModelPreview->mResourceCost*10;
-
- if (childGetValue("upload_textures").asBoolean())
- {
- resource_cost += mModelPreview->mTextureSet.size()*10;
- }
-
- childSetLabelArg("ok_btn", "[AMOUNT]", llformat("%d", resource_cost));
-
- if (mModelPreview)
- {
- gGL.color3f(1.f, 1.f, 1.f);
-
- gGL.getTexUnit(0)->bind(mModelPreview);
-
-
- LLView* preview_panel = getChild<LLView>("preview_panel");
-
- LLRect rect = preview_panel->getRect();
- if (rect != mPreviewRect)
- {
- mModelPreview->refresh();
- mPreviewRect = preview_panel->getRect();
- }
-
- gGL.begin( LLRender::QUADS );
- {
- gGL.texCoord2f(0.f, 1.f);
- gGL.vertex2i(mPreviewRect.mLeft, mPreviewRect.mTop);
- gGL.texCoord2f(0.f, 0.f);
- gGL.vertex2i(mPreviewRect.mLeft, mPreviewRect.mBottom);
- gGL.texCoord2f(1.f, 0.f);
- gGL.vertex2i(mPreviewRect.mRight, mPreviewRect.mBottom);
- gGL.texCoord2f(1.f, 1.f);
- gGL.vertex2i(mPreviewRect.mRight, mPreviewRect.mTop);
- }
- 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(FALSE);
- 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::setMousePositionLocal(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;
-}
-
-//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;
- sInstance->mDecompParams[param->mName] = ctrl->getValue();
- }
-}
-
-//static
-void LLFloaterModelPreview::onPhysicsStageExecute(LLUICtrl* ctrl, void* data)
-{
- LLCDStageData* stage = (LLCDStageData*) data;
-
- LLModel* mdl = NULL;
-
- if (sInstance)
- {
- if (sInstance->mCurRequest.notNull())
- {
- llinfos << "Decomposition request still pending." << llendl;
- return;
- }
-
- if (sInstance->mModelPreview)
- {
- S32 idx = sInstance->childGetValue("physics_layer").asInteger();
- if (idx >= 0 && idx < sInstance->mModelPreview->mModel[LLModel::LOD_PHYSICS].size())
- {
- mdl = sInstance->mModelPreview->mModel[LLModel::LOD_PHYSICS][idx];
- }
- }
- }
-
- if (mdl)
- {
- sInstance->mCurRequest = new DecompRequest(stage->mName, mdl);
- gMeshRepo.mDecompThread->submitRequest(sInstance->mCurRequest);
- }
-
- const std::string decompose("Decompose");
-
- if (decompose == stage->mName)
- { //hide decompose panel and show simplify panel
- sInstance->childSetVisible("physics step 2", false);
- sInstance->childSetVisible("physics step 3", true);
- }
-}
-
-//static
-void LLFloaterModelPreview::onPhysicsOptimize(LLUICtrl* ctrl, void *data)
-{
- //hide step 1 panel and show step 2 panel + info
- sInstance->childSetVisible("physics step 1", false);
- sInstance->childSetVisible("physics step 2", true);
- sInstance->childSetVisible("physics info", true);
-}
-
-//static
-void LLFloaterModelPreview::onPhysicsBrowse(LLUICtrl* ctrl, void* userdata)
-{
- sInstance->loadModel(LLModel::LOD_PHYSICS);
-}
-
-//static
-void LLFloaterModelPreview::onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata)
-{
- S32 which_mode = 3;
- LLCtrlSelectionInterface* iface = sInstance->childGetSelectionInterface("physics_lod_combo");
- if (iface)
- {
- which_mode = iface->getFirstSelectedIndex();
- }
-
- sInstance->mModelPreview->setPhysicsFromLOD(which_mode);
-}
-
-//static
-void LLFloaterModelPreview::onPhysicsDecomposeBack(LLUICtrl* ctrl, void* userdata)
-{
- //hide step 2 panel and info and show step 1 panel
- sInstance->childSetVisible("physics step 1", true);
- sInstance->childSetVisible("physics step 2", false);
- sInstance->childSetVisible("physics info", false);
-}
-
-//static
-void LLFloaterModelPreview::onPhysicsSimplifyBack(LLUICtrl* ctrl, void* userdata)
-{
- //hide step 3 panel and show step 2 panel
- sInstance->childSetVisible("physics step 3", false);
- sInstance->childSetVisible("physics step 2", true);
-}
-
-//static
-void LLFloaterModelPreview::onPhysicsStageCancel(LLUICtrl* ctrl, void*data)
-{
- if (sInstance && sInstance->mCurRequest.notNull())
- {
- sInstance->mCurRequest->mContinue = 0;
- }
-}
-
-void LLFloaterModelPreview::initDecompControls()
-{
- LLSD key;
-
- childSetCommitCallback("cancel_btn", onPhysicsStageCancel, NULL);
- childSetCommitCallback("physics_lod_combo", onPhysicsUseLOD, NULL);
- childSetCommitCallback("physics_browse", onPhysicsBrowse, NULL);
- childSetCommitCallback("physics_optimize", onPhysicsOptimize, NULL);
- childSetCommitCallback("decompose_back", onPhysicsDecomposeBack, NULL);
- childSetCommitCallback("simplify_back", onPhysicsSimplifyBack, NULL);
- childSetCommitCallback("physics_layer", refresh, 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(&param);
- }
-
- for (S32 j = stage_count-1; j >= 0; --j)
- {
- LLButton* button = getChild<LLButton>(stage[j].mName);
- if (button)
- {
- button->setCommitCallback(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;
-
- LLSliderCtrl* slider = getChild<LLSliderCtrl>(name);
- if (slider)
- {
- 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, (void*) &param[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;
-
- LLSliderCtrl* slider = getChild<LLSliderCtrl>(name);
- if (slider)
- {
- 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, (void*) &param[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<LLCheckBoxCtrl>(name);
- if (check_box)
- {
- check_box->setValue(param[i].mDefault.mBool);
- check_box->setCommitCallback(onPhysicsParamCommit, (void*) &param[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<LLComboBox>(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;
-
- combo_box->add(param[i].mDetails.mEnumValues.mEnumsArray[k].mName,
- LLSD::Integer(param[i].mDetails.mEnumValues.mEnumsArray[k].mValue));
- }
- combo_box->setValue(param[i].mDefault.mIntOrEnumValue);
- combo_box->setCommitCallback(onPhysicsParamCommit, (void*) &param[i]);
- }
-
- //llinfos << "----" << llendl;
- }
- //llinfos << "-----------------------------" << llendl;
- }
- }
-
- childSetCommitCallback("physics_layer", LLFloaterModelPreview::refresh, LLFloaterModelPreview::sInstance);
- childSetCommitCallback("physics_explode", LLFloaterModelPreview::onExplodeCommit, this);
-}
-
-//-----------------------------------------------------------------------------
-// onMouseCaptureLost()
-//-----------------------------------------------------------------------------
-// static
-void LLFloaterModelPreview::onMouseCaptureLostModelPreview(LLMouseHandler* handler)
-{
- gViewerWindow->showCursor();
-}
-
-//-----------------------------------------------------------------------------
-// LLModelLoader
-//-----------------------------------------------------------------------------
-LLModelLoader::LLModelLoader(std::string filename, S32 lod, LLModelPreview* preview)
-: LLThread("Model Loader"), mFilename(filename), mLod(lod), mPreview(preview), mState(STARTING), mFirstTransform(TRUE)
-{
- 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";
-}
-
-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()
-{
- DAE dae;
- domCOLLADA* dom = dae.open(mFilename);
-
- if (dom)
- {
- daeDatabase* db = dae.getDatabase();
-
- daeInt count = db->getElementCount(NULL, COLLADA_TYPE_MESH);
-
- daeDocument* doc = dae.getDoc(mFilename);
- if (!doc)
- {
- llwarns << "can't find internal doc" << llendl;
- return;
- }
-
- daeElement* root = doc->getDomRoot();
- if (!root)
- {
- llwarns << "document has no root" << llendl;
- return;
- }
-
- //get unit scale
- mTransform.setIdentity();
-
- domAsset::domUnit* unit = daeSafeCast<domAsset::domUnit>(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<domAsset::domUp_axis>(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<LLModel> model = LLModel::loadModelFromDomMesh(mesh);
-
- 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<domGeometry>(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();
-
- for (int i = 0; i < 4; i++)
- {
- for(int j = 0; j < 4; j++)
- {
- model->mBindShapeMatrix.mMatrix[i][j] = dom_value[i + j*4];
- }
- }
-
- LLMatrix4 trans = normalized_transformation;
- trans *= model->mBindShapeMatrix;
- model->mBindShapeMatrix = trans;
-
- }
-
-
- //The joint transfom map that we'll populate below
- std::map<std::string,LLMatrix4> jointTransforms;
- jointTransforms.clear();
-
- //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<domInstance_controller::domSkeleton>( pElement );
- bool missingSkeletonOrScene = false;
-
- //If no skeleton, do a breadth-first search to get at specific joints
- if ( !pSkeleton )
- {
- daeElement* pScene = root->getDescendant("visual_scene");
- if ( !pScene )
- {
- llwarns<<"No visual scene - unable to parse bone offsets "<<llendl;
- missingSkeletonOrScene = true;
- }
- else
- {
- //Get the children at this level
- daeTArray< daeSmartRef<daeElement> > 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<domNode>(children[i]);
- if ( isNodeAJoint( pNode ) )
- {
- processJointNode( pNode, jointTransforms );
- }
- }
- }
- }
- 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<std::string, std::string> :: 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).second.c_str() );
- //llwarns<<"Joint "<< str <<llendl;
-
- //Setup the resolver
- daeSIDResolver resolver( pSkeletonRootNode, str );
-
- //Look for the joint
- domNode* pJoint = daeSafeCast<domNode>( resolver.getElement() );
- if ( pJoint )
- {
- //Pull out the translate id and store it in the jointTranslations map
- daeSIDResolver jointResolver( pJoint, "./translate" );
- domTranslate* pTranslate = daeSafeCast<domTranslate>( jointResolver.getElement() );
-
- LLMatrix4 workingTransform;
-
- //Translation via SID
- if ( pTranslate )
- {
- extractTranslation( pTranslate, 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" <<llendl;
- missingSkeletonOrScene = true;
- }
- else
- {
- extractTranslationViaElement( pTranslateElement, workingTransform );
- }
- }
-
- //Store the joint transform w/respect to it's name.
- jointTransforms[(*jointIt).second.c_str()] = workingTransform;
- }
- }
-
- //If anything failed in regards to extracting the skeleton, joints or translation id,
- //mention it
- if ( missingSkeletonOrScene )
- {
- llwarns<< "Partial jointmap found in asset - did you mean to just have a partial map?" << llendl;
- }
- }//got skeleton?
- }
-
- if ( !missingSkeletonOrScene )
- {
- //Set the joint translations on the avatar
- //The joints are reset in the dtor
- const int jointCnt = mJointMap.size();
- std::map<std::string, std::string> :: const_iterator jointIt = mJointMap.begin();
- for ( int i=0; i<jointCnt; ++i, ++jointIt )
- {
- std::string lookingForJoint = (*jointIt).first.c_str();
- if ( jointTransforms.find( lookingForJoint ) != jointTransforms.end() )
- {
- LLMatrix4 jointTransform = jointTransforms[lookingForJoint];
- LLJoint* pJoint = gAgentAvatarp->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;
- }
- //Reposition the avatars pelvis (avPos+offset)
- //if ( lookingForJoint == "mPelvis" )
- //{
- // const LLVector3& pos = gAgentAvatarp->getCharacterPosition();
- // gAgentAvatarp->setPelvisOffset( true, jointTransform.getTranslation() );
- // gAgentAvatarp->setPosition( pos + jointTransform.getTranslation() );
- //}
- }
- }
- } //missingSkeletonOrScene
-
- domSkin::domJoints* joints = skin->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->mJointList
- daeElement* elem = input->getSource().getElement();
-
- domSource* source = daeSafeCast<domSource>(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->mJointList.push_back(name);
- model->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->mJointList.push_back(name);
- model->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<domSource>(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->mInvBindMatrix.push_back(mat);
- }
- }
- }
- }
- }
-
- //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<std::string> :: const_iterator jointIt = model->mJointList.begin();
- const int jointCnt = model->mJointList.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 ( jointTransforms.find( lookingForJoint ) != jointTransforms.end() )
- {
- LLMatrix4 jointTransform = jointTransforms[lookingForJoint];
- LLMatrix4 newInverse = model->mInvBindMatrix[i];
- newInverse.setTranslation( jointTransforms[lookingForJoint].getTranslation() );
- model->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<domSource>(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 << "WTF?" << 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<domSource>(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<LLModel::JointWeight> 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 transform;
- std::vector<LLImportMaterial> materials;
- materials.resize(model->getNumVolumeFaces());
- mScene[transform].push_back(LLModelInstance(model, transform, materials));
- stretch_extents(model, transform, mExtents[0], mExtents[1], mFirstTransform);
- }
- }
- }
- }
- }
- }
- }
-
- daeElement* scene = root->getDescendant("visual_scene");
-
- if (!scene)
- {
- llwarns << "document has no visual_scene" << llendl;
- setLoadState( ERROR_PARSING );
- return;
- }
-
- processElement(scene);
-
- doOnIdleOneTime(boost::bind(&LLModelPreview::loadModelCallback,mPreview,mLod));
- }
-}
-
-bool LLModelLoader::isNodeAJoint( domNode* pNode )
-{
- if ( pNode->getName() == NULL)
- {
- return false;
- }
-
- if ( mJointMap.find( pNode->getName() ) != mJointMap.end() )
- {
- return true;
- }
-
- return false;
-}
-
-void LLModelLoader::extractTranslation( domTranslate* pTranslate, LLMatrix4& transform )
-{
- domFloat3 jointTrans = pTranslate->getValue();
- LLVector3 singleJointTranslation( jointTrans[0], jointTrans[1], jointTrans[2] );
- transform.setTranslation( singleJointTranslation );
-}
-
-void LLModelLoader::extractTranslationViaElement( daeElement* pTranslateElement, LLMatrix4& transform )
-{
- domTranslate* pTranslateChild = dynamic_cast<domTranslate*>( pTranslateElement );
- domFloat3 translateChild = pTranslateChild->getValue();
- LLVector3 singleJointTranslation( translateChild[0], translateChild[1], translateChild[2] );
- transform.setTranslation( singleJointTranslation );
-}
-
-void LLModelLoader::processJointNode( domNode* pNode, std::map<std::string,LLMatrix4>& jointTransforms )
-{
- if (pNode->getName() == NULL)
- {
- llwarns << "nameless node, can't process" << llendl;
- return;
- }
-
- //llwarns<<"ProcessJointNode# Node:" <<pNode->getName()<<llendl;
-
- //1. handle the incoming node - extract out translation via SID or element
-
- LLMatrix4 workingTransform;
-
- //Pull out the translate id and store it in the jointTranslations map
- daeSIDResolver jointResolver( pNode, "./translate" );
- domTranslate* pTranslate = daeSafeCast<domTranslate>( jointResolver.getElement() );
-
- //Translation via SID was successful
- if ( pTranslate )
- {
- extractTranslation( pTranslate, 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" <<llendl;
- }
- else
- {
- extractTranslationViaElement( pTranslateElement, workingTransform );
- }
- }
-
- //Store the working transform relative to the nodes name.
- jointTransforms[ pNode->getName() ] = workingTransform;
-
- //2. handle the nodes children
-
- //Gather and handle the incoming nodes children
- daeTArray< daeSmartRef<daeElement> > childOfChild = pNode->getChildren();
- S32 childOfChildCount = childOfChild.getCount();
-
- for (S32 i = 0; i < childOfChildCount; ++i)
- {
- domNode* pChildNode = daeSafeCast<domNode>( childOfChild[i] );
- if ( pChildNode )
- {
- processJointNode( pChildNode, jointTransforms );
- }
- }
-}
-
-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)
-{
- LLMatrix4 saved_transform = mTransform;
-
- domTranslate* translate = daeSafeCast<domTranslate>(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<domRotate>(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<domScale>(element);
- if (scale)
- {
- domFloat3 dom_value = scale->getValue();
-
- LLMatrix4 scaling;
- scaling.initScale(LLVector3(dom_value[0], dom_value[1], dom_value[2]));
-
- scaling *= mTransform;
- mTransform = scaling;
- }
-
- domMatrix* matrix = daeSafeCast<domMatrix>(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<domInstance_geometry>(element);
- if (instance_geo)
- {
- domGeometry* geo = daeSafeCast<domGeometry>(instance_geo->getUrl().getElement());
- if (geo)
- {
- domMesh* mesh = daeSafeCast<domMesh>(geo->getDescendant(daeElement::matchType(domMesh::ID())));
- if (mesh)
- {
- LLModel* model = mModel[mesh];
- if (model)
- {
- LLMatrix4 transformation = mTransform;
-
- std::vector<LLImportMaterial> 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;
-
- mScene[transformation].push_back(LLModelInstance(model, transformation, materials));
-
- stretch_extents(model, transformation, mExtents[0], mExtents[1], mFirstTransform);
- }
- }
- }
- }
-
- domInstance_node* instance_node = daeSafeCast<domInstance_node>(element);
- if (instance_node)
- {
- daeElement* instance = instance_node->getUrl().getElement();
- if (instance)
- {
- processElement(instance);
- }
- }
-
- //process children
- daeTArray< daeSmartRef<daeElement> > children = element->getChildren();
- for (S32 i = 0; i < children.getCount(); i++)
- {
- processElement(children[i]);
- }
-
- domNode* node = daeSafeCast<domNode>(element);
- if (node)
- { //this element was a node, restore transform before processiing siblings
- mTransform = saved_transform;
- }
-}
-
-std::vector<LLImportMaterial> LLModelLoader::getMaterials(LLModel* model, domInstance_geometry* instance_geo)
-{
- std::vector<LLImportMaterial> materials;
- for (int i = 0; i < model->mMaterialList.size(); i++)
- {
- LLImportMaterial import_material;
-
- domInstance_material* instance_mat = NULL;
-
- domBind_material::domTechnique_common* technique =
- daeSafeCast<domBind_material::domTechnique_common>(instance_geo->getDescendant(daeElement::matchType(domBind_material::domTechnique_common::ID())));
-
- if (technique)
- {
- daeTArray< daeSmartRef<domInstance_material> > inst_materials = technique->getChildrenByType<domInstance_material>();
- 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<domMaterial>(instance_mat->getTarget().getElement());
- if (material)
- {
- domInstance_effect* instance_effect =
- daeSafeCast<domInstance_effect>(material->getDescendant(daeElement::matchType(domInstance_effect::ID())));
- if (instance_effect)
- {
- domEffect* effect = daeSafeCast<domEffect>(instance_effect->getUrl().getElement());
- if (effect)
- {
- domProfile_COMMON* profile =
- daeSafeCast<domProfile_COMMON>(effect->getDescendant(daeElement::matchType(domProfile_COMMON::ID())));
- if (profile)
- {
- import_material = profileToMaterial(profile);
- }
- }
- }
- }
- }
-
- materials.push_back(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<domCommon_color_or_texture_type_complexType::domTexture>(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<domImage>(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)
- {
- std::string filename = cdom::uriToNativePath(init->getValue().str());
-
- mat.mDiffuseMap = LLViewerTextureManager::getFetchedTextureFromUrl("file://" + filename, TRUE, LLViewerTexture::BOOST_PREVIEW);
- mat.mDiffuseMap->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, TRUE, FALSE, this->mPreview, NULL, FALSE);
-
- mat.mDiffuseMap->forceToSaveRawImage();
- mat.mDiffuseMapFilename = filename;
- mat.mDiffuseMapLabel = getElementLabel(material);
- }
- }
- }
- }
- }
- }
- }
-
- domCommon_color_or_texture_type_complexType::domColor* color =
- daeSafeCast<domCommon_color_or_texture_type_complexType::domColor>(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<domCommon_color_or_texture_type_complexType::domColor>(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(NULL)
-{
- 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;
- mGroup = 0;
- mBuildShareTolerance = 0.f;
- mBuildQueueMode = GLOD_QUEUE_GREEDY;
- mBuildBorderMode = GLOD_BORDER_UNLOCK;
- mBuildOperator = GLOD_OPERATOR_HALF_EDGE_COLLAPSE;
-
- mFMP = fmp;
-
- glodInit();
-}
-
-LLModelPreview::~LLModelPreview()
-{
- if (mModelLoader)
- {
- delete mModelLoader;
- mModelLoader = NULL;
- }
-
- //*HACK : *TODO : turn this back on when we understand why this crashes
- //glodShutdown();
-}
-
-U32 LLModelPreview::calcResourceCost()
-{
- assert_main_thread();
-
- rebuildUploadData();
-
- if ( mModelLoader->getLoadState() != LLModelLoader::ERROR_PARSING )
- {
- mFMP->childEnable("ok_btn");
- }
-
- U32 cost = 0;
- std::set<LLModel*> accounted;
- U32 num_points = 0;
- U32 num_hulls = 0;
-
- F32 debug_scale = mFMP->childGetValue("import_scale").asReal();
-
- F32 streaming_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::convex_hull_decomposition& decomp =
- instance.mLOD[LLModel::LOD_PHYSICS] ?
- instance.mLOD[LLModel::LOD_PHYSICS]->mConvexHullDecomp :
- instance.mModel->mConvexHullDecomp;
-
- LLSD ret = LLModel::writeModel(
- "",
- 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);
- cost += gMeshRepo.calcResourceCost(ret);
-
- num_hulls += decomp.size();
- for (U32 i = 0; i < decomp.size(); ++i)
- {
- num_points += decomp[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()*debug_scale;
-
- streaming_cost += LLMeshRepository::getStreamingCost(ret, radius);
- }
- }
-
- //mFMP->childSetTextArg(info_name[LLModel::LOD_PHYSICS], "[HULLS]", llformat("%d",num_hulls));
- //mFMP->childSetTextArg(info_name[LLModel::LOD_PHYSICS], "[POINTS]", llformat("%d",num_points));
- mFMP->childSetTextArg("streaming cost", "[COST]", llformat("%.3f", streaming_cost));
- F32 scale = mFMP->childGetValue("import_scale").asReal()*2.f;
- mFMP->childSetTextArg("import_dimensions", "[X]", llformat("%.3f", mPreviewScale[0]*scale));
- mFMP->childSetTextArg("import_dimensions", "[Y]", llformat("%.3f", mPreviewScale[1]*scale));
- mFMP->childSetTextArg("import_dimensions", "[Z]", llformat("%.3f", mPreviewScale[2]*scale));
-
- updateStatusMessages();
-
- return cost;
-}
-
-void LLModelPreview::rebuildUploadData()
-{
- assert_main_thread();
-
- mUploadData.clear();
- mTextureSet.clear();
-
- //fill uploaddata instance vectors from scene data
-
- std::string requested_name = mFMP->getChild<LLUICtrl>("description_form")->getValue().asString();
-
-
- LLSpinCtrl* scale_spinner = mFMP->getChild<LLSpinCtrl>("import_scale");
-
- if (!scale_spinner)
- {
- llerrs << "floater_model_preview.xml MUST contain import_scale spinner." << llendl;
- }
-
- F32 scale = scale_spinner->getValue().asReal();
-
- LLMatrix4 scale_mat;
- scale_mat.initScale(LLVector3(scale, scale, scale));
-
- F32 max_scale = 0.f;
-
- if ( mBaseScene.size() > 0 )
- {
- mFMP->childEnable("ok_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;
- }
-
- S32 idx = 0;
- for (idx = 0; idx < mBaseModel.size(); ++idx)
- { //find reference instance for this model
- if (mBaseModel[idx] == base_model)
- {
- break;
- }
- }
-
- for (U32 i = 0; i < LLModel::NUM_LODS; i++)
- { //fill LOD slots based on reference model index
- if (!mModel[i].empty())
- {
- 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/max_scale;
-
- scale_spinner->setMaxValue(max_import_scale);
-
- if (max_import_scale < scale)
- {
- scale_spinner->setValue(max_import_scale);
- }
-
- //refill "layer" combo in physics panel
- LLComboBox* combo_box = mFMP->getChild<LLComboBox>("physics_layer");
- if (combo_box)
- {
- S32 current = combo_box->getCurrentIndex();
- combo_box->removeall();
-
- for (S32 i = 0; i < mBaseModel.size(); ++i)
- {
- LLModel* mdl = mBaseModel[i];
- combo_box->add(mdl->mLabel, i);
- }
- combo_box->setCurrentByIndex(current);
- }
-}
-
-
-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)
-{
- assert_main_thread();
-
- LLMutexLock lock(this);
-
- if (mModelLoader)
- {
- delete mModelLoader;
- mModelLoader = NULL;
- }
-
- if (filename.empty())
- {
- if (mBaseModel.empty())
- {
- // this is the initial file picking. Close the whole floater
- // if we don't have a base model to show for high LOD.
- mFMP->closeFloater(false);
- }
-
- mLoading = false;
- return;
- }
-
- mLODFile[lod] = filename;
-
- if (lod == LLModel::LOD_HIGH)
- {
- clearGLODGroup();
- }
-
- mModelLoader = new LLModelLoader(filename, lod, this);
-
- mModelLoader->start();
-
- mFMP->childSetTextArg("status", "[STATUS]", mFMP->getString("status_reading_file"));
-
- setPreviewLOD(lod);
-
- if ( mModelLoader->getLoadState() == LLModelLoader::ERROR_PARSING )
- {
- mFMP->childDisable("ok_btn");
- }
-
- if (lod == mPreviewLOD)
- {
- mFMP->childSetText("lod_file", mLODFile[mPreviewLOD]);
- }
- else if (lod == LLModel::LOD_PHYSICS)
- {
- mFMP->childSetText("physics_file", mLODFile[lod]);
- }
-
- mFMP->openFloater();
-}
-
-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)
-{
- 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<LLPointer<LLModel>, U32>::iterator iter = mObject.begin(); iter != mObject.end(); ++iter)
- {
- glodDeleteObject(iter->second);
- stop_gloderror();
- }
- mObject.clear();
-
- glodDeleteGroup(mGroup);
- stop_gloderror();
- mGroup = 0;
- }
-}
-
-void LLModelPreview::loadModelCallback(S32 lod)
-{
- assert_main_thread();
-
- LLMutexLock lock(this);
- if (!mModelLoader)
- {
- return;
- }
-
- mModel[lod] = mModelLoader->mModelList;
- mScene[lod] = mModelLoader->mScene;
- mVertexBuffer[lod].clear();
-
- if (lod == LLModel::LOD_PHYSICS)
- {
- mPhysicsMesh.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;
- refresh();
-}
-
-void LLModelPreview::resetPreviewTarget()
-{
- mPreviewTarget = (mModelLoader->mExtents[0] + mModelLoader->mExtents[1]) * 0.5f;
- mPreviewScale = (mModelLoader->mExtents[1] - mModelLoader->mExtents[0]) * 0.5f;
- setPreviewTarget(mPreviewScale.magVec()*2.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();
-
- 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();
-
-}
-
-void LLModelPreview::consolidate()
-{
- std::map<LLImportMaterial, std::vector<LLModelInstance> > composite;
-
- LLMatrix4 identity;
-
- //bake out each node in current scene to composite
- for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
- { //for each transform in current scene
- LLMatrix4 mat = iter->first;
- glh::matrix4f inv_trans = glh::matrix4f((F32*) mat.mMatrix).inverse().transpose();
- LLMatrix4 norm_mat(inv_trans.m);
-
- for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
- { //for each instance with that transform
- LLModelInstance& source_instance = *model_iter;
- LLModel* source = source_instance.mModel;
-
- if (!validate_model(source))
- {
- llerrs << "Invalid model found!" << llendl;
- }
-
- for (S32 i = 0; i < source->getNumVolumeFaces(); ++i)
- { //for each face in instance
- const LLVolumeFace& src_face = source->getVolumeFace(i);
- LLImportMaterial& source_material = source_instance.mMaterial[i];
-
- //get model in composite that is composite for this material
- LLModel* model = NULL;
-
- if (composite.find(source_material) != composite.end())
- {
- model = composite[source_material].rbegin()->mModel;
- if (model->getVolumeFace(0).mNumVertices + src_face.mNumVertices > 65535)
- {
- model = NULL;
- }
- }
-
- if (model == NULL)
- { //no model found, make new model
- std::vector<LLImportMaterial> materials;
- materials.push_back(source_material);
- LLVolumeParams volume_params;
- volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
- model = new LLModel(volume_params, 0.f);
- model->mLabel = source->mLabel;
- model->setNumVolumeFaces(0);
- composite[source_material].push_back(LLModelInstance(model, identity, materials));
- }
-
- model->appendFace(src_face, source->mMaterialList[i], mat, norm_mat);
- }
- }
- }
-
-
- //condense composite into as few LLModel instances as possible
- LLModelLoader::model_list new_model;
- std::vector<LLModelInstance> instance_list;
-
- LLVolumeParams volume_params;
- volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
-
- std::vector<LLImportMaterial> empty_material;
- LLModelInstance cur_instance(new LLModel(volume_params, 0.f), identity, empty_material);
- cur_instance.mModel->setNumVolumeFaces(0);
-
- BOOL first_transform = TRUE;
-
- LLModelLoader::scene new_scene;
- LLVector3 min,max;
-
- for (std::map<LLImportMaterial, std::vector<LLModelInstance> >::iterator iter = composite.begin();
- iter != composite.end();
- ++iter)
- {
- std::map<LLImportMaterial, std::vector<LLModelInstance> >::iterator next_iter = iter; ++next_iter;
-
- for (std::vector<LLModelInstance>::iterator instance_iter = iter->second.begin();
- instance_iter != iter->second.end();
- ++instance_iter)
- {
- LLModel* source = instance_iter->mModel;
-
- if (instance_iter->mMaterial.size() != 1)
- {
- llerrs << "WTF?" << llendl;
- }
-
- if (source->getNumVolumeFaces() != 1)
- {
- llerrs << "WTF?" << llendl;
- }
-
- if (source->mMaterialList.size() != 1)
- {
- llerrs << "WTF?" << llendl;
- }
-
- cur_instance.mModel->addFace(source->getVolumeFace(0));
- cur_instance.mMaterial.push_back(instance_iter->mMaterial[0]);
- cur_instance.mModel->mMaterialList.push_back(source->mMaterialList[0]);
-
- BOOL last_model = FALSE;
-
- std::vector<LLModelInstance>::iterator next_instance = instance_iter; ++next_instance;
-
- if (next_iter == composite.end() &&
- next_instance == iter->second.end())
- {
- last_model = TRUE;
- }
-
- if (last_model || cur_instance.mModel->getNumVolumeFaces() >= MAX_MODEL_FACES)
- {
- cur_instance.mModel->mLabel = source->mLabel;
-
- cur_instance.mModel->optimizeVolumeFaces();
- cur_instance.mModel->normalizeVolumeFaces();
-
- if (!validate_model(cur_instance.mModel))
- {
- llerrs << "Invalid model detected." << llendl;
- }
-
- new_model.push_back(cur_instance.mModel);
-
- LLMatrix4 transformation = LLMatrix4();
-
- // adjust the transformation to compensate for mesh normalization
- LLVector3 mesh_scale_vector;
- LLVector3 mesh_translation_vector;
- cur_instance.mModel->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;
-
- cur_instance.mTransform = transformation;
-
- new_scene[transformation].push_back(cur_instance);
- stretch_extents(cur_instance.mModel, transformation, min, max, first_transform);
-
- if (!last_model)
- {
- cur_instance = LLModelInstance(new LLModel(volume_params, 0.f), identity, empty_material);
- cur_instance.mModel->setNumVolumeFaces(0);
- }
- }
- }
- }
-
- mScene[mPreviewLOD] = new_scene;
- mModel[mPreviewLOD] = new_model;
- mVertexBuffer[mPreviewLOD].clear();
-
- if (mPreviewLOD == LLModel::LOD_HIGH)
- {
- mBaseScene = new_scene;
- mBaseModel = new_model;
- clearGLODGroup();
- mVertexBuffer[5].clear();
- }
-
- mPreviewTarget = (min+max)*0.5f;
- mPreviewScale = (max-min)*0.5f;
- setPreviewTarget(mPreviewScale.magVec()*2.f);
-
- clearIncompatible(mPreviewLOD);
-
- mResourceCost = calcResourceCost();
- refresh();
-}
-
-void LLModelPreview::clearMaterials()
-{
- for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
- { //for each transform in current scene
- for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
- { //for each instance with that transform
- LLModelInstance& source_instance = *model_iter;
- LLModel* source = source_instance.mModel;
-
- for (S32 i = 0; i < source->getNumVolumeFaces(); ++i)
- { //for each face in instance
- LLImportMaterial& source_material = source_instance.mMaterial[i];
-
- //clear material info
- source_material.mDiffuseColor = LLColor4(1,1,1,1);
- source_material.mDiffuseMap = NULL;
- source_material.mDiffuseMapFilename.clear();
- source_material.mDiffuseMapLabel.clear();
- source_material.mFullbright = false;
- }
- }
- }
-
- mVertexBuffer[mPreviewLOD].clear();
-
- if (mPreviewLOD == LLModel::LOD_HIGH)
- {
- mBaseScene = mScene[mPreviewLOD];
- mBaseModel = mModel[mPreviewLOD];
- clearGLODGroup();
- mVertexBuffer[5].clear();
- }
-
- mResourceCost = calcResourceCost();
- refresh();
-}
-
-bool LLModelPreview::containsRiggedAsset( void )
-{
- //loop through the models and determine if any of them contained a rigged asset, and if so
- //return true.
- //This is used to cleanup the joint positions after a preview.
- for (LLModelLoader::model_list::iterator iter = mBaseModel.begin(); iter != mBaseModel.end(); ++iter)
- {
- LLModel* pModel = *iter;
- if ( pModel->mAlternateBindMatrix.size() > 0 )
- {
- return true;
- }
- }
- return false;
-}
-void LLModelPreview::genLODs(S32 which_lod)
-{
- if (mBaseModel.empty())
- {
- return;
- }
-
- if (which_lod == LLModel::LOD_PHYSICS)
- { //clear physics mesh map
- mPhysicsMesh.clear();
- }
-
- 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;
-
- LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode");
- if (iface)
- {
- lod_mode = iface->getFirstSelectedIndex();
- }
-
- F32 lod_error_threshold = mFMP->childGetValue("lod_error_threshold").asReal();
-
- if (lod_mode == 0)
- {
- lod_mode = GLOD_TRIANGLE_BUDGET;
- if (which_lod != -1)
- {
- limit = mFMP->childGetValue("lod_triangle_limit").asInteger();
- }
- }
- else
- {
- lod_mode = GLOD_ERROR_THRESHOLD;
- }
-
- U32 build_operator = 0;
-
- iface = mFMP->childGetSelectionInterface("build_operator");
- if (iface)
- {
- build_operator = iface->getFirstSelectedIndex();
- }
-
- if (build_operator == 0)
- {
- build_operator = GLOD_OPERATOR_HALF_EDGE_COLLAPSE;
- }
- else
- {
- build_operator = GLOD_OPERATOR_EDGE_COLLAPSE;
- }
-
- U32 queue_mode=0;
- iface = mFMP->childGetSelectionInterface("queue_mode");
- if (iface)
- {
- queue_mode = iface->getFirstSelectedIndex();
- }
-
- if (queue_mode == 0)
- {
- queue_mode = GLOD_QUEUE_GREEDY;
- }
- else if (queue_mode == 1)
- {
- queue_mode = GLOD_QUEUE_LAZY;
- }
- else
- {
- queue_mode = GLOD_QUEUE_INDEPENDENT;
- }
-
- U32 border_mode = 0;
-
- iface = mFMP->childGetSelectionInterface("border_mode");
- if (iface)
- {
- border_mode = iface->getFirstSelectedIndex();
- }
-
- if (border_mode == 0)
- {
- border_mode = GLOD_BORDER_UNLOCK;
- }
- else
- {
- border_mode = GLOD_BORDER_LOCK;
- }
-
- bool object_dirty = false;
- if (border_mode != mBuildBorderMode)
- {
- mBuildBorderMode = border_mode;
- object_dirty = true;
- }
-
- if (queue_mode != mBuildQueueMode)
- {
- mBuildQueueMode = queue_mode;
- object_dirty = true;
- }
-
- if (build_operator != mBuildOperator)
- {
- mBuildOperator = build_operator;
- object_dirty = true;
- }
-
- F32 share_tolerance = mFMP->childGetValue("share_tolerance").asReal();
- if (share_tolerance != mBuildShareTolerance)
- {
- mBuildShareTolerance = share_tolerance;
- object_dirty = true;
- }
-
- 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)
- {
- mVertexBuffer[5][mdl][i]->setBuffer(type_mask);
- 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();
- }
-
- glodObjectParameteri(mObject[mdl], GLOD_BUILD_OPERATOR, build_operator);
- stop_gloderror();
-
- glodObjectParameteri(mObject[mdl], GLOD_BUILD_QUEUE_MODE, queue_mode);
- stop_gloderror();
-
- glodObjectParameteri(mObject[mdl], GLOD_BUILD_BORDER_MODE, border_mode);
- stop_gloderror();
-
- glodObjectParameterf(mObject[mdl], GLOD_BUILD_SHARE_TOLERANCE, share_tolerance);
- 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 /= 3;
- }
- }
- else
- {
- triangle_count = limit;
- }
-
- mModel[lod].clear();
- mModel[lod].resize(mBaseModel.size());
- mVertexBuffer[lod].clear();
-
- U32 actual_tris = 0;
- U32 actual_verts = 0;
- U32 submeshes = 0;
-
- glodGroupParameteri(mGroup, GLOD_ADAPT_MODE, lod_mode);
- stop_gloderror();
-
- glodGroupParameteri(mGroup, GLOD_ERROR_MODE, GLOD_OBJECT_SPACE_ERROR);
- stop_gloderror();
-
- glodGroupParameteri(mGroup, GLOD_MAX_TRIANGLES, triangle_count);
- stop_gloderror();
-
- glodGroupParameterf(mGroup, GLOD_OBJECT_SPACE_ERROR_THRESHOLD, lod_error_threshold);
- 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)
- {
- LLPointer<LLVertexBuffer> 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<LLVector3> pos;
- LLStrider<LLVector3> norm;
- LLStrider<LLVector2> tc;
- LLStrider<U16> index;
-
- buff->getVertexStrider(pos);
- buff->getNormalStrider(norm);
- 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->mJointMap = base->mJointMap;
- target_model->mJointList = base->mJointList;
- target_model->mInvBindMatrix = base->mInvBindMatrix;
- target_model->mBindShapeMatrix = base->mBindShapeMatrix;
- target_model->mAlternateBindMatrix = base->mAlternateBindMatrix;
- //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<S32> tris[LLModel::NUM_LODS];
- std::vector<S32> verts[LLModel::NUM_LODS];
- std::vector<S32> submeshes[LLModel::NUM_LODS];
-
- //total triangle/vertex/submesh count for each lod
- S32 total_tris[LLModel::NUM_LODS];
- S32 total_verts[LLModel::NUM_LODS];
- S32 total_submeshes[LLModel::NUM_LODS];
-
- for (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];
- }
-
-
- 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())
- {
- for (U32 i = 0; i < verts[lod].size(); ++i)
- {
- S32 max_verts = i < verts[lod+1].size() ? verts[lod+1][i] : 0;
-
- if (max_verts > 0)
- {
- if (verts[lod][i] > max_verts)
- { //too many vertices in this lod
- message = "mesh_status_too_many_vertices";
- upload_status[lod] = 2;
- }
- }
- }
- }
- }
-
- LLIconCtrl* icon = mFMP->getChild<LLIconCtrl>(lod_icon_name[lod]);
- LLUIImagePtr img = LLUI::getUIImage(lod_status_image[upload_status[lod]]);
- icon->setVisible(true);
- icon->setImage(img);
-
- if (upload_status[lod] >= 2)
- {
- upload_ok = false;
- }
-
- if (lod == mPreviewLOD)
- {
- mFMP->childSetText("lod_status_message_text", mFMP->getString(message));
- icon = mFMP->getChild<LLIconCtrl>("lod_status_message_icon");
- icon->setImage(img);
- }
- }
-
- bool errorStateFromLoader = mModelLoader->getLoadState() == LLModelLoader::ERROR_PARSING ? true : false;
-
- if ( upload_ok && !errorStateFromLoader )
- {
- mFMP->childEnable("ok_btn");
- }
- else
- {
- mFMP->childDisable("ok_btn");
- }
-
- //add up physics triangles etc
- S32 start = 0;
- S32 end = mModel[LLModel::LOD_PHYSICS].size();
-
- S32 idx = mFMP->childGetValue("physics_layer").asInteger();
-
- if (idx >= 0 && idx < mModel[LLModel::LOD_PHYSICS].size())
- {
- start = idx;
- end = idx+1;
- }
-
- 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->mConvexHullDecomp;
-
- 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");
- fmp->setViewOption("show_physics", true);
- }
- }
- else
- {
- fmp->disableViewOption("show_physics");
- fmp->setViewOption("show_physics", false);
- }
- }
-
- const char* lod_controls[] =
- {
- "lod_mode",
- "lod_triangle_limit",
- "lod_error_tolerance",
- "build_operator_text",
- "queue_mode_text",
- "border_mode_text",
- "share_tolerance_text",
- "build_operator",
- "queue_mode",
- "border_mode",
- "share_tolerance"
- };
- 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*);
-
- //enable/disable controls based on radio groups
- if (mFMP->childGetValue("lod_from_file").asBoolean())
- {
- for (U32 i = 0; i < num_file_controls; ++i)
- {
- mFMP->childEnable(file_controls[i]);
- }
-
- for (U32 i = 0; i < num_lod_controls; ++i)
- {
- mFMP->childDisable(lod_controls[i]);
- }
-
-
- }
- else if (mFMP->childGetValue("lod_auto_generate").asBoolean())
- {
- for (U32 i = 0; i < num_file_controls; ++i)
- {
- mFMP->childDisable(file_controls[i]);
- }
-
- for (U32 i = 0; i < num_lod_controls; ++i)
- {
- mFMP->childEnable(lod_controls[i]);
- }
-
- //if (threshold)
- {
- U32 lod_mode = 0;
- LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode");
- if (iface)
- {
- lod_mode = iface->getFirstSelectedIndex();
- }
-
- LLSpinCtrl* threshold = mFMP->getChild<LLSpinCtrl>("lod_error_threshold");
- LLSpinCtrl* limit = mFMP->getChild<LLSpinCtrl>("lod_triangle_limit");
-
- limit->setMaxValue(mMaxTriangleLimit);
- limit->setValue(total_tris[mPreviewLOD]);
-
- if (lod_mode == 0)
- {
- limit->setVisible(true);
- threshold->setVisible(false);
-
- limit->setMaxValue(mMaxTriangleLimit);
- }
- else
- {
- limit->setVisible(false);
- threshold->setVisible(true);
- }
- }
- }
- else
- { // "None" is chosen
- for (U32 i = 0; i < num_file_controls; ++i)
- {
- mFMP->childDisable(file_controls[i]);
- }
-
- for (U32 i = 0; i < num_lod_controls; ++i)
- {
- mFMP->childDisable(lod_controls[i]);
- }
-
- if (!mModel[mPreviewLOD].empty())
- {
- mModel[mPreviewLOD].clear();
- mScene[mPreviewLOD].clear();
- mVertexBuffer[mPreviewLOD].clear();
-
- //this can cause phasing issues with the UI, so reenter this function and return
- updateStatusMessages();
- return;
- }
- }
-
- if (mFMP->childGetValue("physics_load_from_file").asBoolean())
- {
- mFMP->childDisable("physics_lod_combo");
- mFMP->childEnable("physics_file");
- mFMP->childEnable("physics_browse");
- }
- else
- {
- mFMP->childEnable("physics_lod_combo");
- mFMP->childDisable("physics_file");
- mFMP->childDisable("physics_browse");
- }
-}
-
-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<LLVector3> vertex_strider;
- LLStrider<LLVector3> normal_strider;
- LLStrider<LLVector2> tc_strider;
- LLStrider<U16> index_strider;
- LLStrider<LLVector4> weights_strider;
-
- vb->getVertexStrider(vertex_strider);
- vb->getNormalStrider(normal_strider);
- vb->getTexCoord0Strider(tc_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));
- LLVector4a::memcpyNonAliased16((F32*) tc_strider.get(), (F32*) vf.mTexCoords, num_vertices*2*sizeof(F32));
- 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);
- if (weight_list.size() > 4)
- {
- llerrs << "WTF?" << llendl;
- }
-
- 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();
- }
-
-}
-
-//-----------------------------------------------------------------------------
-// render()
-//-----------------------------------------------------------------------------
-BOOL LLModelPreview::render()
-{
- assert_main_thread();
-
- LLMutexLock lock(this);
- mNeedsUpdate = FALSE;
-
- bool edges = false;
- bool joint_positions = false;
- bool skin_weight = false;
- bool textures = false;
- bool physics = false;
-
- LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
- if (fmp)
- {
- edges = fmp->isViewOptionChecked("show_edges");
- joint_positions = fmp->isViewOptionChecked("show_joint_positions");
- skin_weight = fmp->isViewOptionChecked("show_skin_weight");
- textures = fmp->isViewOptionChecked("show_textures");
- physics = fmp->isViewOptionChecked("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.15f, 0.2f, 0.3f, 1.f);
-
- gl_rect_2d_simple( width, height );
-
- glMatrixMode(GL_PROJECTION);
- gGL.popMatrix();
-
- glMatrixMode(GL_MODELVIEW);
- gGL.popMatrix();
- }
-
- bool has_skin_weights = false;
- bool upload_skin = mFMP->childGetValue("upload_skin").asBoolean();
- bool upload_joints = mFMP->childGetValue("upload_joints").asBoolean();
-
- 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())
- {
- has_skin_weights = true;
- }
- }
- }
-
- if (has_skin_weights)
- { //model has skin weights, enable view options for skin weights and joint positions
- if (fmp)
- {
- fmp->enableViewOption("show_skin_weight");
- fmp->setViewOptionEnabled("show_joint_positions", skin_weight);
- }
- mFMP->childEnable("upload_skin");
- }
- else
- {
- mFMP->childDisable("upload_skin");
- if (fmp)
- {
- fmp->setViewOption("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;
- }
-
- 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::getInstance()->setAspect(aspect);
-
- LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom);
-
- LLVector3 offset = mCameraOffset;
- LLVector3 target_pos = mPreviewTarget+offset;
-
- F32 z_near = 0.001f;
- F32 z_far = mCameraDistance+mPreviewScale.magVec()+mCameraOffset.magVec();
-
- if (skin_weight)
- {
- target_pos = gAgentAvatarp->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;
- LLViewerCamera::getInstance()->setOriginAndLookAt(
- target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + offset) * av_rot), // camera
- LLVector3::z_axis, // up
- target_pos); // point of interest
-
-
- LLViewerCamera::getInstance()->setPerspective(FALSE, mOrigin.mX, mOrigin.mY, width, height, FALSE, z_near, z_far);
-
- stop_glerror();
-
- gGL.pushMatrix();
- const F32 BRIGHTNESS = 0.9f;
- gGL.color3f(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS);
-
- LLGLEnable normalize(GL_NORMALIZE);
-
- if (!mBaseModel.empty() && mVertexBuffer[5].empty())
- {
- genBuffers(-1, skin_weight);
- //genBuffers(3);
- //genLODs();
- }
-
- S32 physics_idx = mFMP->childGetValue("physics_layer").asInteger();
-
- if (!mModel[mPreviewLOD].empty())
- {
- bool regen = mVertexBuffer[mPreviewLOD].empty();
- if (!regen)
- {
- const std::vector<LLPointer<LLVertexBuffer> >& vb_vec = mVertexBuffer[mPreviewLOD].begin()->second;
- if (!vb_vec.empty())
- {
- const LLVertexBuffer* buff = vb_vec[0];
- regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight;
- }
- }
-
- 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(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0);
-
- if (textures)
- {
- glColor4fv(instance.mMaterial[i].mDiffuseColor.mV);
- if (i < instance.mMaterial.size() && instance.mMaterial[i].mDiffuseMap.notNull())
- {
- gGL.getTexUnit(0)->bind(instance.mMaterial[i].mDiffuseMap, true);
- if (instance.mMaterial[i].mDiffuseMap->getDiscardLevel() > -1)
- {
- mTextureSet.insert(instance.mMaterial[i].mDiffuseMap);
- }
- }
- }
- 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);
- LLGLEnable blend(GL_BLEND);
- gGL.blendFunc(LLRender::BF_ONE, LLRender::BF_ZERO);
-
- LLModel* physics_model = NULL;
-
- if (physics_idx >= 0 && physics_idx < mModel[LLModel::LOD_PHYSICS].size() )
- {
- physics_model = mModel[LLModel::LOD_PHYSICS][physics_idx];
- }
-
- 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);
-
- std::map<LLPointer<LLModel>, std::vector<LLPointer<LLVertexBuffer> > >::iterator iter =
- mPhysicsMesh.find(model);
- if (iter != mPhysicsMesh.end())
- {
- render_mesh = false;
- for (U32 i = 0; i < iter->second.size(); ++i)
- {
- if (explode > 0.f)
- {
- gGL.pushMatrix();
-
- LLVector3 offset = model->mHullCenter[i]-model->mCenterOfHullCenters;
- offset *= explode;
-
- gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]);
- }
-
- static std::vector<LLColor4U> hull_colors;
-
- if (i+1 >= hull_colors.size())
- {
- hull_colors.push_back(LLColor4U(rand()%128+127, rand()%128+127, rand()%128+127, 255));
- }
-
- LLVertexBuffer* buff = iter->second[i];
- if (buff)
- {
- buff->setBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL);
-
- glColor4ubv(hull_colors[i].mV);
- buff->drawArrays(LLRender::TRIANGLES, 0, buff->getNumVerts());
-
- if (edges)
- {
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
- glLineWidth(3.f);
- glColor4ub(hull_colors[i].mV[0]/2, hull_colors[i].mV[1]/2, hull_colors[i].mV[2]/2, 255);
- buff->drawArrays(LLRender::TRIANGLES, 0, buff->getNumVerts());
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
- glLineWidth(1.f);
- }
- }
-
- 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];
-
- buffer->setBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0);
-
- if (textures)
- {
- glColor4fv(instance.mMaterial[i].mDiffuseColor.mV);
- if (i < instance.mMaterial.size() && instance.mMaterial[i].mDiffuseMap.notNull())
- {
- gGL.getTexUnit(0)->bind(instance.mMaterial[i].mDiffuseMap, true);
- if (instance.mMaterial[i].mDiffuseMap->getDiscardLevel() > -1)
- {
- mTextureSet.insert(instance.mMaterial[i].mDiffuseMap);
- }
- }
- }
- 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 || model == physics_model)
- {
- if (model == physics_model)
- {
- glColor3f(1.f, 1.f, 0.f);
- }
-
- 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();
- }
-
- gGL.setSceneBlendType(LLRender::BT_ALPHA);
- }
- }
- else
- {
- LLVOAvatarSelf* avatar = gAgentAvatarp;
- target_pos = avatar->getPositionAgent();
-
- LLViewerCamera::getInstance()->setOriginAndLookAt(
- target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + offset) * av_rot), // camera
- LLVector3::z_axis, // up
- target_pos); // point of interest
-
- if (joint_positions)
- {
- avatar->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<LLVector3> position;
- buffer->getVertexStrider(position);
-
- LLStrider<LLVector4> weight;
- buffer->getWeight4Strider(weight);
-
- //quick 'n dirty software vertex skinning
-
- //build matrix palette
- LLMatrix4 mat[64];
- for (U32 j = 0; j < model->mJointList.size(); ++j)
- {
- LLJoint* joint = avatar->getJoint(model->mJointList[j]);
- if (joint)
- {
- mat[j] = model->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->mBindShapeMatrix;
- v = v * final_mat;
-
- position[j] = v;
- }
-
- buffer->setBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0);
- glColor4fv(instance.mMaterial[i].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<LLComboBox>("preview_lod_combo");
- combo_box->setCurrentByIndex((NUM_LOD-1)-mPreviewLOD); // combo box list of lods is in reverse order
- mFMP->childSetTextArg("lod_table_footer", "[DETAIL]", mFMP->getString(lod_name[mPreviewLOD]));
- mFMP->childSetText("lod_file", mLODFile[mPreviewLOD]);
-
- LLColor4 highlight_color = LLUIColorTable::instance().getColor("MeshImportTableHighlightColor");
- LLColor4 normal_color = LLUIColorTable::instance().getColor("MeshImportTableNormalColor");
-
- for (S32 i = 0; i <= LLModel::LOD_HIGH; ++i)
- {
- const LLColor4& color = (i == lod) ? highlight_color : normal_color;
-
- mFMP->childSetColor(lod_status_name[i], color);
- mFMP->childSetColor(lod_label_name[i], color);
- mFMP->childSetColor(lod_triangles_name[i], color);
- mFMP->childSetColor(lod_vertices_name[i], color);
- }
- }
- refresh();
- updateStatusMessages();
-}
-
-//static
-void LLFloaterModelPreview::onBrowseLOD(void* data)
-{
- assert_main_thread();
-
- LLFloaterModelPreview* mp = (LLFloaterModelPreview*) data;
- mp->loadModel(mp->mModelPreview->mPreviewLOD);
-}
-
-//static
-void LLFloaterModelPreview::onUpload(void* user_data)
-{
- assert_main_thread();
-
- LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data;
-
- mp->mModelPreview->rebuildUploadData();
-
- gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, mp->mModelPreview->mPreviewScale,
- mp->childGetValue("upload_textures").asBoolean(), mp->childGetValue("upload_skin"), mp->childGetValue("upload_joints"));
-
- mp->closeFloater(false);
-}
-
-
-//static
-void LLFloaterModelPreview::onClearMaterials(void* user_data)
-{
- LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data;
- mp->mModelPreview->clearMaterials();
-}
-
-//static
-void LLFloaterModelPreview::refresh(LLUICtrl* ctrl, void* user_data)
-{
- sInstance->mModelPreview->mDirty = true;
-}
-
-void LLFloaterModelPreview::updateResourceCost()
-{
- U32 cost = mModelPreview->mResourceCost;
- childSetLabelArg("ok_btn", "[AMOUNT]", llformat("%d",cost));
-}
-
-//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();
-}
-
-LLFloaterModelPreview::DecompRequest::DecompRequest(const std::string& stage, LLModel* mdl)
-{
- mStage = stage;
- mContinue = 1;
- mModel = mdl;
- mParams = sInstance->mDecompParams;
-
- //copy out positions and indices
- if (mdl)
- {
- U16 index_offset = 0;
-
- mPositions.clear();
- mIndices.clear();
-
- //queue up vertex positions and indices
- for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i)
- {
- const LLVolumeFace& face = mdl->getVolumeFace(i);
- if (mPositions.size() + face.mNumVertices > 65535)
- {
- continue;
- }
-
- for (U32 j = 0; j < face.mNumVertices; ++j)
- {
- mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr()));
- }
-
- for (U32 j = 0; j < face.mNumIndices; ++j)
- {
- mIndices.push_back(face.mIndices[j]+index_offset);
- }
-
- index_offset += face.mNumVertices;
- }
- }
-}
-
-S32 LLFloaterModelPreview::DecompRequest::statusCallback(const char* status, S32 p1, S32 p2)
-{
- setStatusMessage(llformat("%s: %d/%d", status, p1, p2));
- return mContinue;
-}
-
-void LLFloaterModelPreview::DecompRequest::completed()
-{
- mModel->setConvexHullDecomposition(mHull);
-
- if (sInstance)
- {
- if (sInstance->mModelPreview)
- {
- sInstance->mModelPreview->mPhysicsMesh[mModel] = mHullMesh;
- sInstance->mModelPreview->mDirty = true;
- LLFloaterModelPreview::sInstance->mModelPreview->refresh();
- }
-
- sInstance->mCurRequest = NULL;
- }
-}
+/**
+ * @file llfloatermodelpreview.cpp
+ * @brief LLFloaterModelPreview class implementation
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "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 "llfilepicker.h"
+#include "llimagebmp.h"
+#include "llimagetga.h"
+#include "llimagejpeg.h"
+#include "llimagepng.h"
+
+#include "llagent.h"
+#include "llbutton.h"
+#include "llcombobox.h"
+#include "lldatapacker.h"
+#include "lldrawable.h"
+#include "lldrawpoolavatar.h"
+#include "llrender.h"
+#include "llface.h"
+#include "lleconomy.h"
+#include "llfocusmgr.h"
+#include "llfloaterperms.h"
+#include "lliconctrl.h"
+#include "llmatrix4a.h"
+#include "llmenubutton.h"
+#include "llmeshrepository.h"
+#include "llsdutil_math.h"
+#include "lltextbox.h"
+#include "lltoolmgr.h"
+#include "llui.h"
+#include "llvector4a.h"
+#include "llviewercamera.h"
+#include "llviewerwindow.h"
+#include "llvoavatar.h"
+#include "llvoavatarself.h"
+#include "pipeline.h"
+#include "lluictrlfactory.h"
+#include "llviewermenu.h"
+#include "llviewermenufile.h"
+#include "llviewerregion.h"
+#include "llviewertexturelist.h"
+#include "llstring.h"
+#include "llbutton.h"
+#include "llcheckboxctrl.h"
+#include "llradiogroup.h"
+#include "llsliderctrl.h"
+#include "llspinctrl.h"
+#include "lltoggleablemenu.h"
+#include "llvfile.h"
+#include "llvfs.h"
+#include "llcallbacklist.h"
+
+#include "glod/glod.h"
+
+//static
+S32 LLFloaterModelPreview::sUploadAmount = 10;
+LLFloaterModelPreview* LLFloaterModelPreview::sInstance = NULL;
+
+const S32 PREVIEW_BORDER_WIDTH = 2;
+const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH;
+const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE;
+const S32 PREF_BUTTON_HEIGHT = 16 + 7 + 16;
+const S32 PREVIEW_TEXTURE_HEIGHT = 300;
+
+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."
+};
+
+
+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;
+ }
+ }
+
+ 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 LLSD& key) :
+LLFloater(key)
+{
+ sInstance = this;
+ mLastMouseX = 0;
+ mLastMouseY = 0;
+ mGLName = 0;
+ mStatusLock = new LLMutex(NULL);
+
+ mLODMode[LLModel::LOD_HIGH] = 0;
+ for (U32 i = 0; i < LLModel::LOD_HIGH; i++)
+ {
+ mLODMode[i] = 1;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// postBuild()
+//-----------------------------------------------------------------------------
+BOOL LLFloaterModelPreview::postBuild()
+{
+ if (!LLFloater::postBuild())
+ {
+ return FALSE;
+ }
+
+ setViewOption("show_textures", true);
+
+ childSetAction("lod_browse", onBrowseLOD, this);
+
+ childSetCommitCallback("crease_angle", onGenerateNormalsCommit, this);
+ childSetCommitCallback("generate_normals", onGenerateNormalsCommit, this);
+
+ childSetCommitCallback("lod_generate", onAutoFillCommit, this);
+
+ childSetCommitCallback("lod_mode", onLODParamCommit, this);
+ childSetCommitCallback("lod_error_threshold", onLODParamCommit, this);
+ childSetCommitCallback("lod_triangle_limit", onLODParamCommit, this);
+ childSetCommitCallback("build_operator", onLODParamCommit, this);
+ childSetCommitCallback("queue_mode", onLODParamCommit, this);
+ childSetCommitCallback("border_mode", onLODParamCommit, this);
+ childSetCommitCallback("share_tolerance", onLODParamCommit, this);
+
+ childSetTextArg("status", "[STATUS]", getString("status_idle"));
+
+ //childSetLabelArg("ok_btn", "[AMOUNT]", llformat("%d",sUploadAmount));
+ childSetAction("ok_btn", onUpload, this);
+ childDisable("ok_btn");
+
+ childSetAction("clear_materials", onClearMaterials, this);
+
+ childSetCommitCallback("preview_lod_combo", onPreviewLODCommit, this);
+
+ childSetCommitCallback("upload_skin", onUploadSkinCommit, this);
+ childSetCommitCallback("upload_joints", onUploadJointsCommit, this);
+
+ childSetCommitCallback("import_scale", onImportScaleCommit, this);
+
+ childSetCommitCallback("lod_file_or_limit", refresh, this);
+ childSetCommitCallback("physics_load_radio", refresh, this);
+ //childSetCommitCallback("physics_optimize", refresh, this);
+ //childSetCommitCallback("physics_use_hull", refresh, this);
+
+ childDisable("upload_skin");
+ childDisable("upload_joints");
+ childDisable("ok_btn");
+
+ mViewOptionMenuButton = getChild<LLMenuButton>("options_gear_btn");
+
+ mCommitCallbackRegistrar.add("ModelImport.ViewOption.Action", boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _2));
+ mEnableCallbackRegistrar.add("ModelImport.ViewOption.Check", boost::bind(&LLFloaterModelPreview::isViewOptionChecked, this, _2));
+ mEnableCallbackRegistrar.add("ModelImport.ViewOption.Enabled", boost::bind(&LLFloaterModelPreview::isViewOptionEnabled, this, _2));
+
+
+
+ mViewOptionMenu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>("menu_model_import_gear_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
+ mViewOptionMenuButton->setMenu(mViewOptionMenu, LLMenuButton::MP_BOTTOM_LEFT);
+
+ initDecompControls();
+
+ LLView* preview_panel = getChild<LLView>("preview_panel");
+
+ mPreviewRect = preview_panel->getRect();
+
+ mModelPreview = new LLModelPreview(512, 512, this);
+ mModelPreview->setPreviewTarget(16.f);
+
+ //set callbacks for left click on line editor rows
+ for (U32 i = 0; i <= LLModel::LOD_HIGH; i++)
+ {
+ LLTextBox* text = getChild<LLTextBox>(lod_label_name[i]);
+ if (text)
+ {
+ text->setMouseDownCallback(boost::bind(&LLModelPreview::setPreviewLOD, mModelPreview, i));
+ }
+
+ text = getChild<LLTextBox>(lod_triangles_name[i]);
+ if (text)
+ {
+ text->setMouseDownCallback(boost::bind(&LLModelPreview::setPreviewLOD, mModelPreview, i));
+ }
+
+ text = getChild<LLTextBox>(lod_vertices_name[i]);
+ if (text)
+ {
+ text->setMouseDownCallback(boost::bind(&LLModelPreview::setPreviewLOD, mModelPreview, i));
+ }
+
+ text = getChild<LLTextBox>(lod_status_name[i]);
+ if (text)
+ {
+ text->setMouseDownCallback(boost::bind(&LLModelPreview::setPreviewLOD, mModelPreview, i));
+ }
+ }
+
+ return TRUE;
+}
+
+//-----------------------------------------------------------------------------
+// LLFloaterModelPreview()
+//-----------------------------------------------------------------------------
+LLFloaterModelPreview::~LLFloaterModelPreview()
+{
+ sInstance = NULL;
+
+ if ( mModelPreview->containsRiggedAsset() )
+ {
+ gAgentAvatarp->resetJointPositions();
+ }
+
+ delete mModelPreview;
+
+ if (mGLName)
+ {
+ LLImageGL::deleteTextures(1, &mGLName );
+ }
+
+ delete mStatusLock;
+ mStatusLock = NULL;
+}
+
+void LLFloaterModelPreview::onViewOptionChecked(const LLSD& userdata)
+{
+ mViewOption[userdata.asString()] = !mViewOption[userdata.asString()];
+ mModelPreview->refresh();
+}
+
+bool LLFloaterModelPreview::isViewOptionChecked(const LLSD& userdata)
+{
+ return mViewOption[userdata.asString()];
+}
+
+bool LLFloaterModelPreview::isViewOptionEnabled(const LLSD& userdata)
+{
+ return !mViewOptionDisabled[userdata.asString()];
+}
+
+void LLFloaterModelPreview::setViewOptionEnabled(const std::string& option, bool enabled)
+{
+ mViewOptionDisabled[option] = !enabled;
+}
+
+void LLFloaterModelPreview::enableViewOption(const std::string& option)
+{
+ setViewOptionEnabled(option, true);
+}
+
+void LLFloaterModelPreview::disableViewOption(const std::string& option)
+{
+ setViewOptionEnabled(option, false);
+}
+
+void LLFloaterModelPreview::setViewOption(const std::string& option, bool value)
+{
+ mViewOption[option] = value;
+}
+
+void LLFloaterModelPreview::loadModel(S32 lod)
+{
+ mModelPreview->mLoading = true;
+
+ (new LLMeshFilePicker(mModelPreview, lod))->getFile();
+}
+
+//static
+void LLFloaterModelPreview::onImportScaleCommit(LLUICtrl*,void* userdata)
+{
+ LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata;
+
+ if (!fp->mModelPreview)
+ {
+ return;
+ }
+
+ fp->mModelPreview->calcResourceCost();
+ 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::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)
+{
+ LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata;
+ fp->mModelPreview->genLODs(fp->mModelPreview->mPreviewLOD);
+ fp->mModelPreview->updateStatusMessages();
+ fp->mModelPreview->refresh();
+}
+
+
+//-----------------------------------------------------------------------------
+// draw()
+//-----------------------------------------------------------------------------
+void LLFloaterModelPreview::draw()
+{
+ LLFloater::draw();
+ LLRect r = getRect();
+
+ mModelPreview->update();
+
+ if (!mModelPreview->mLoading)
+ {
+ 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 (!mCurRequest.empty())
+ {
+ LLMutexLock lock(mStatusLock);
+ childSetTextArg("status", "[STATUS]", mStatusMessage);
+ }
+
+ U32 resource_cost = mModelPreview->mResourceCost*10;
+
+ if (childGetValue("upload_textures").asBoolean())
+ {
+ resource_cost += mModelPreview->mTextureSet.size()*10;
+ }
+
+ childSetLabelArg("ok_btn", "[AMOUNT]", llformat("%d", resource_cost));
+
+ if (mModelPreview)
+ {
+ gGL.color3f(1.f, 1.f, 1.f);
+
+ gGL.getTexUnit(0)->bind(mModelPreview);
+
+
+ LLView* preview_panel = getChild<LLView>("preview_panel");
+
+ LLRect rect = preview_panel->getRect();
+ if (rect != mPreviewRect)
+ {
+ mModelPreview->refresh();
+ mPreviewRect = preview_panel->getRect();
+ }
+
+ gGL.begin( LLRender::QUADS );
+ {
+ gGL.texCoord2f(0.f, 1.f);
+ gGL.vertex2i(mPreviewRect.mLeft, mPreviewRect.mTop);
+ gGL.texCoord2f(0.f, 0.f);
+ gGL.vertex2i(mPreviewRect.mLeft, mPreviewRect.mBottom);
+ gGL.texCoord2f(1.f, 0.f);
+ gGL.vertex2i(mPreviewRect.mRight, mPreviewRect.mBottom);
+ gGL.texCoord2f(1.f, 1.f);
+ gGL.vertex2i(mPreviewRect.mRight, mPreviewRect.mTop);
+ }
+ 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(FALSE);
+ 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::setMousePositionLocal(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;
+}
+
+//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);
+ sInstance->mDecompParams[name] = ctrl->getValue();
+
+ if (name == "Simplify Method")
+ {
+ if (ctrl->getValue().asInteger() == 0)
+ {
+ sInstance->childSetVisible("Retain%", true);
+ sInstance->childSetVisible("Detail Scale", false);
+ }
+ else
+ {
+ sInstance->childSetVisible("Retain%", false);
+ sInstance->childSetVisible("Detail Scale", true);
+ }
+ }
+ }
+}
+
+//static
+void LLFloaterModelPreview::onPhysicsStageExecute(LLUICtrl* ctrl, void* data)
+{
+ LLCDStageData* stage = (LLCDStageData*) data;
+
+ 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->mName, mdl);
+ sInstance->mCurRequest.insert(request);
+ gMeshRepo.mDecompThread->submitRequest(request);
+ }
+ }
+ }
+}
+
+//static
+void LLFloaterModelPreview::onPhysicsBrowse(LLUICtrl* ctrl, void* userdata)
+{
+ sInstance->loadModel(LLModel::LOD_PHYSICS);
+}
+
+//static
+void LLFloaterModelPreview::onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata)
+{
+ S32 which_mode = 3;
+ LLCtrlSelectionInterface* iface = sInstance->childGetSelectionInterface("physics_lod_combo");
+ if (iface)
+ {
+ which_mode = iface->getFirstSelectedIndex();
+ }
+
+ sInstance->mModelPreview->setPhysicsFromLOD(which_mode);
+}
+
+//static
+void LLFloaterModelPreview::onPhysicsStageCancel(LLUICtrl* ctrl, void*data)
+{
+ if (sInstance)
+ {
+ for (std::set<LLPointer<DecompRequest> >::iterator iter = sInstance->mCurRequest.begin();
+ iter != sInstance->mCurRequest.end(); ++iter)
+ {
+ DecompRequest* req = *iter;
+ req->mContinue = 0;
+ }
+ }
+}
+
+void LLFloaterModelPreview::initDecompControls()
+{
+ LLSD key;
+
+ childSetCommitCallback("cancel_btn", 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(&param);
+ }
+
+ for (S32 j = stage_count-1; j >= 0; --j)
+ {
+ LLButton* button = getChild<LLButton>(stage[j].mName);
+ if (button)
+ {
+ button->setCommitCallback(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;
+
+ LLSliderCtrl* slider = getChild<LLSliderCtrl>(name);
+ if (slider)
+ {
+ 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, (void*) &param[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;
+
+ LLSliderCtrl* slider = getChild<LLSliderCtrl>(name);
+ if (slider)
+ {
+ 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, (void*) &param[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<LLCheckBoxCtrl>(name);
+ if (check_box)
+ {
+ check_box->setValue(param[i].mDefault.mBool);
+ check_box->setCommitCallback(onPhysicsParamCommit, (void*) &param[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<LLComboBox>(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;
+
+ combo_box->add(param[i].mDetails.mEnumValues.mEnumsArray[k].mName,
+ LLSD::Integer(param[i].mDetails.mEnumValues.mEnumsArray[k].mValue));
+ }
+ combo_box->setValue(param[i].mDefault.mIntOrEnumValue);
+ combo_box->setCommitCallback(onPhysicsParamCommit, (void*) &param[i]);
+ }
+
+ //llinfos << "----" << llendl;
+ }
+ //llinfos << "-----------------------------" << llendl;
+ }
+ }
+
+ childSetCommitCallback("physics_explode", LLFloaterModelPreview::onExplodeCommit, this);
+}
+
+//-----------------------------------------------------------------------------
+// onMouseCaptureLost()
+//-----------------------------------------------------------------------------
+// static
+void LLFloaterModelPreview::onMouseCaptureLostModelPreview(LLMouseHandler* handler)
+{
+ gViewerWindow->showCursor();
+}
+
+//-----------------------------------------------------------------------------
+// LLModelLoader
+//-----------------------------------------------------------------------------
+LLModelLoader::LLModelLoader(std::string filename, S32 lod, LLModelPreview* preview)
+: LLThread("Model Loader"), mFilename(filename), mLod(lod), mPreview(preview), mState(STARTING), mFirstTransform(TRUE)
+{
+ 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";
+}
+
+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()
+{
+ DAE dae;
+ domCOLLADA* dom = dae.open(mFilename);
+
+ if (dom)
+ {
+ daeDatabase* db = dae.getDatabase();
+
+ daeInt count = db->getElementCount(NULL, COLLADA_TYPE_MESH);
+
+ daeDocument* doc = dae.getDoc(mFilename);
+ if (!doc)
+ {
+ llwarns << "can't find internal doc" << llendl;
+ return;
+ }
+
+ daeElement* root = doc->getDomRoot();
+ if (!root)
+ {
+ llwarns << "document has no root" << llendl;
+ return;
+ }
+
+ //get unit scale
+ mTransform.setIdentity();
+
+ domAsset::domUnit* unit = daeSafeCast<domAsset::domUnit>(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<domAsset::domUp_axis>(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<LLModel> model = LLModel::loadModelFromDomMesh(mesh);
+
+ 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<domGeometry>(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();
+
+ for (int i = 0; i < 4; i++)
+ {
+ for(int j = 0; j < 4; j++)
+ {
+ model->mBindShapeMatrix.mMatrix[i][j] = dom_value[i + j*4];
+ }
+ }
+
+ LLMatrix4 trans = normalized_transformation;
+ trans *= model->mBindShapeMatrix;
+ model->mBindShapeMatrix = trans;
+
+ }
+
+
+ //The joint transfom map that we'll populate below
+ std::map<std::string,LLMatrix4> jointTransforms;
+ jointTransforms.clear();
+
+ //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<domInstance_controller::domSkeleton>( pElement );
+ bool missingSkeletonOrScene = false;
+
+ //If no skeleton, do a breadth-first search to get at specific joints
+ if ( !pSkeleton )
+ {
+ daeElement* pScene = root->getDescendant("visual_scene");
+ if ( !pScene )
+ {
+ llwarns<<"No visual scene - unable to parse bone offsets "<<llendl;
+ missingSkeletonOrScene = true;
+ }
+ else
+ {
+ //Get the children at this level
+ daeTArray< daeSmartRef<daeElement> > 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<domNode>(children[i]);
+ if ( isNodeAJoint( pNode ) )
+ {
+ processJointNode( pNode, jointTransforms );
+ }
+ }
+ }
+ }
+ 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<std::string, std::string> :: 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).second.c_str() );
+ //llwarns<<"Joint "<< str <<llendl;
+
+ //Setup the resolver
+ daeSIDResolver resolver( pSkeletonRootNode, str );
+
+ //Look for the joint
+ domNode* pJoint = daeSafeCast<domNode>( resolver.getElement() );
+ if ( pJoint )
+ {
+ //Pull out the translate id and store it in the jointTranslations map
+ daeSIDResolver jointResolver( pJoint, "./translate" );
+ domTranslate* pTranslate = daeSafeCast<domTranslate>( jointResolver.getElement() );
+
+ LLMatrix4 workingTransform;
+
+ //Translation via SID
+ if ( pTranslate )
+ {
+ extractTranslation( pTranslate, 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" <<llendl;
+ missingSkeletonOrScene = true;
+ }
+ else
+ {
+ extractTranslationViaElement( pTranslateElement, workingTransform );
+ }
+ }
+
+ //Store the joint transform w/respect to it's name.
+ jointTransforms[(*jointIt).second.c_str()] = workingTransform;
+ }
+ }
+
+ //If anything failed in regards to extracting the skeleton, joints or translation id,
+ //mention it
+ if ( missingSkeletonOrScene )
+ {
+ llwarns<< "Partial jointmap found in asset - did you mean to just have a partial map?" << llendl;
+ }
+ }//got skeleton?
+ }
+
+ if ( !missingSkeletonOrScene )
+ {
+ //Set the joint translations on the avatar
+ //The joints are reset in the dtor
+ const int jointCnt = mJointMap.size();
+ std::map<std::string, std::string> :: const_iterator jointIt = mJointMap.begin();
+ for ( int i=0; i<jointCnt; ++i, ++jointIt )
+ {
+ std::string lookingForJoint = (*jointIt).first.c_str();
+ if ( jointTransforms.find( lookingForJoint ) != jointTransforms.end() )
+ {
+ LLMatrix4 jointTransform = jointTransforms[lookingForJoint];
+ LLJoint* pJoint = gAgentAvatarp->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;
+ }
+ //Reposition the avatars pelvis (avPos+offset)
+ //if ( lookingForJoint == "mPelvis" )
+ //{
+ // const LLVector3& pos = gAgentAvatarp->getCharacterPosition();
+ // gAgentAvatarp->setPelvisOffset( true, jointTransform.getTranslation() );
+ // gAgentAvatarp->setPosition( pos + jointTransform.getTranslation() );
+ //}
+ }
+ }
+ } //missingSkeletonOrScene
+
+ domSkin::domJoints* joints = skin->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->mJointList
+ daeElement* elem = input->getSource().getElement();
+
+ domSource* source = daeSafeCast<domSource>(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->mJointList.push_back(name);
+ model->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->mJointList.push_back(name);
+ model->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<domSource>(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->mInvBindMatrix.push_back(mat);
+ }
+ }
+ }
+ }
+ }
+
+ //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<std::string> :: const_iterator jointIt = model->mJointList.begin();
+ const int jointCnt = model->mJointList.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 ( jointTransforms.find( lookingForJoint ) != jointTransforms.end() )
+ {
+ LLMatrix4 jointTransform = jointTransforms[lookingForJoint];
+ LLMatrix4 newInverse = model->mInvBindMatrix[i];
+ newInverse.setTranslation( jointTransforms[lookingForJoint].getTranslation() );
+ model->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<domSource>(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 << "WTF?" << 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<domSource>(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<LLModel::JointWeight> 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 transform;
+ std::vector<LLImportMaterial> materials;
+ materials.resize(model->getNumVolumeFaces());
+ mScene[transform].push_back(LLModelInstance(model, transform, materials));
+ stretch_extents(model, transform, mExtents[0], mExtents[1], mFirstTransform);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ daeElement* scene = root->getDescendant("visual_scene");
+
+ if (!scene)
+ {
+ llwarns << "document has no visual_scene" << llendl;
+ setLoadState( ERROR_PARSING );
+ return;
+ }
+
+ processElement(scene);
+
+ doOnIdleOneTime(boost::bind(&LLModelPreview::loadModelCallback,mPreview,mLod));
+ }
+}
+
+bool LLModelLoader::isNodeAJoint( domNode* pNode )
+{
+ if ( pNode->getName() == NULL)
+ {
+ return false;
+ }
+
+ if ( mJointMap.find( pNode->getName() ) != mJointMap.end() )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void LLModelLoader::extractTranslation( domTranslate* pTranslate, LLMatrix4& transform )
+{
+ domFloat3 jointTrans = pTranslate->getValue();
+ LLVector3 singleJointTranslation( jointTrans[0], jointTrans[1], jointTrans[2] );
+ transform.setTranslation( singleJointTranslation );
+}
+
+void LLModelLoader::extractTranslationViaElement( daeElement* pTranslateElement, LLMatrix4& transform )
+{
+ domTranslate* pTranslateChild = dynamic_cast<domTranslate*>( pTranslateElement );
+ domFloat3 translateChild = pTranslateChild->getValue();
+ LLVector3 singleJointTranslation( translateChild[0], translateChild[1], translateChild[2] );
+ transform.setTranslation( singleJointTranslation );
+}
+
+void LLModelLoader::processJointNode( domNode* pNode, std::map<std::string,LLMatrix4>& jointTransforms )
+{
+ if (pNode->getName() == NULL)
+ {
+ llwarns << "nameless node, can't process" << llendl;
+ return;
+ }
+
+ //llwarns<<"ProcessJointNode# Node:" <<pNode->getName()<<llendl;
+
+ //1. handle the incoming node - extract out translation via SID or element
+
+ LLMatrix4 workingTransform;
+
+ //Pull out the translate id and store it in the jointTranslations map
+ daeSIDResolver jointResolver( pNode, "./translate" );
+ domTranslate* pTranslate = daeSafeCast<domTranslate>( jointResolver.getElement() );
+
+ //Translation via SID was successful
+ if ( pTranslate )
+ {
+ extractTranslation( pTranslate, 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" <<llendl;
+ }
+ else
+ {
+ extractTranslationViaElement( pTranslateElement, workingTransform );
+ }
+ }
+
+ //Store the working transform relative to the nodes name.
+ jointTransforms[ pNode->getName() ] = workingTransform;
+
+ //2. handle the nodes children
+
+ //Gather and handle the incoming nodes children
+ daeTArray< daeSmartRef<daeElement> > childOfChild = pNode->getChildren();
+ S32 childOfChildCount = childOfChild.getCount();
+
+ for (S32 i = 0; i < childOfChildCount; ++i)
+ {
+ domNode* pChildNode = daeSafeCast<domNode>( childOfChild[i] );
+ if ( pChildNode )
+ {
+ processJointNode( pChildNode, jointTransforms );
+ }
+ }
+}
+
+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)
+{
+ LLMatrix4 saved_transform = mTransform;
+
+ domTranslate* translate = daeSafeCast<domTranslate>(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<domRotate>(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<domScale>(element);
+ if (scale)
+ {
+ domFloat3 dom_value = scale->getValue();
+
+ LLMatrix4 scaling;
+ scaling.initScale(LLVector3(dom_value[0], dom_value[1], dom_value[2]));
+
+ scaling *= mTransform;
+ mTransform = scaling;
+ }
+
+ domMatrix* matrix = daeSafeCast<domMatrix>(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<domInstance_geometry>(element);
+ if (instance_geo)
+ {
+ domGeometry* geo = daeSafeCast<domGeometry>(instance_geo->getUrl().getElement());
+ if (geo)
+ {
+ domMesh* mesh = daeSafeCast<domMesh>(geo->getDescendant(daeElement::matchType(domMesh::ID())));
+ if (mesh)
+ {
+ LLModel* model = mModel[mesh];
+ if (model)
+ {
+ LLMatrix4 transformation = mTransform;
+
+ std::vector<LLImportMaterial> 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;
+
+ mScene[transformation].push_back(LLModelInstance(model, transformation, materials));
+
+ stretch_extents(model, transformation, mExtents[0], mExtents[1], mFirstTransform);
+ }
+ }
+ }
+ }
+
+ domInstance_node* instance_node = daeSafeCast<domInstance_node>(element);
+ if (instance_node)
+ {
+ daeElement* instance = instance_node->getUrl().getElement();
+ if (instance)
+ {
+ processElement(instance);
+ }
+ }
+
+ //process children
+ daeTArray< daeSmartRef<daeElement> > children = element->getChildren();
+ for (S32 i = 0; i < children.getCount(); i++)
+ {
+ processElement(children[i]);
+ }
+
+ domNode* node = daeSafeCast<domNode>(element);
+ if (node)
+ { //this element was a node, restore transform before processiing siblings
+ mTransform = saved_transform;
+ }
+}
+
+std::vector<LLImportMaterial> LLModelLoader::getMaterials(LLModel* model, domInstance_geometry* instance_geo)
+{
+ std::vector<LLImportMaterial> materials;
+ for (int i = 0; i < model->mMaterialList.size(); i++)
+ {
+ LLImportMaterial import_material;
+
+ domInstance_material* instance_mat = NULL;
+
+ domBind_material::domTechnique_common* technique =
+ daeSafeCast<domBind_material::domTechnique_common>(instance_geo->getDescendant(daeElement::matchType(domBind_material::domTechnique_common::ID())));
+
+ if (technique)
+ {
+ daeTArray< daeSmartRef<domInstance_material> > inst_materials = technique->getChildrenByType<domInstance_material>();
+ 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<domMaterial>(instance_mat->getTarget().getElement());
+ if (material)
+ {
+ domInstance_effect* instance_effect =
+ daeSafeCast<domInstance_effect>(material->getDescendant(daeElement::matchType(domInstance_effect::ID())));
+ if (instance_effect)
+ {
+ domEffect* effect = daeSafeCast<domEffect>(instance_effect->getUrl().getElement());
+ if (effect)
+ {
+ domProfile_COMMON* profile =
+ daeSafeCast<domProfile_COMMON>(effect->getDescendant(daeElement::matchType(domProfile_COMMON::ID())));
+ if (profile)
+ {
+ import_material = profileToMaterial(profile);
+ }
+ }
+ }
+ }
+ }
+
+ materials.push_back(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<domCommon_color_or_texture_type_complexType::domTexture>(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<domImage>(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)
+ {
+ std::string filename = cdom::uriToNativePath(init->getValue().str());
+
+ mat.mDiffuseMap = LLViewerTextureManager::getFetchedTextureFromUrl("file://" + filename, TRUE, LLViewerTexture::BOOST_PREVIEW);
+ mat.mDiffuseMap->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, TRUE, FALSE, this->mPreview, NULL, FALSE);
+
+ mat.mDiffuseMap->forceToSaveRawImage();
+ mat.mDiffuseMapFilename = filename;
+ mat.mDiffuseMapLabel = getElementLabel(material);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ domCommon_color_or_texture_type_complexType::domColor* color =
+ daeSafeCast<domCommon_color_or_texture_type_complexType::domColor>(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<domCommon_color_or_texture_type_complexType::domColor>(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(NULL)
+{
+ 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;
+ mGroup = 0;
+ mBuildShareTolerance = 0.f;
+ mBuildQueueMode = GLOD_QUEUE_GREEDY;
+ mBuildBorderMode = GLOD_BORDER_UNLOCK;
+ mBuildOperator = GLOD_OPERATOR_HALF_EDGE_COLLAPSE;
+
+ mFMP = fmp;
+
+ glodInit();
+}
+
+LLModelPreview::~LLModelPreview()
+{
+ if (mModelLoader)
+ {
+ delete mModelLoader;
+ mModelLoader = NULL;
+ }
+
+ //*HACK : *TODO : turn this back on when we understand why this crashes
+ //glodShutdown();
+}
+
+U32 LLModelPreview::calcResourceCost()
+{
+ assert_main_thread();
+
+ rebuildUploadData();
+
+ if ( mModelLoader->getLoadState() != LLModelLoader::ERROR_PARSING )
+ {
+ mFMP->childEnable("ok_btn");
+ }
+
+ U32 cost = 0;
+ std::set<LLModel*> accounted;
+ U32 num_points = 0;
+ U32 num_hulls = 0;
+
+ F32 debug_scale = mFMP->childGetValue("import_scale").asReal();
+
+ F32 streaming_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::convex_hull_decomposition& decomp =
+ instance.mLOD[LLModel::LOD_PHYSICS] ?
+ instance.mLOD[LLModel::LOD_PHYSICS]->mConvexHullDecomp :
+ instance.mModel->mConvexHullDecomp;
+
+ LLSD ret = LLModel::writeModel(
+ "",
+ 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);
+ cost += gMeshRepo.calcResourceCost(ret);
+
+ num_hulls += decomp.size();
+ for (U32 i = 0; i < decomp.size(); ++i)
+ {
+ num_points += decomp[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()*debug_scale;
+
+ streaming_cost += LLMeshRepository::getStreamingCost(ret, radius);
+ }
+ }
+
+ //mFMP->childSetTextArg(info_name[LLModel::LOD_PHYSICS], "[HULLS]", llformat("%d",num_hulls));
+ //mFMP->childSetTextArg(info_name[LLModel::LOD_PHYSICS], "[POINTS]", llformat("%d",num_points));
+ mFMP->childSetTextArg("streaming cost", "[COST]", llformat("%.3f", streaming_cost));
+ F32 scale = mFMP->childGetValue("import_scale").asReal()*2.f;
+ mFMP->childSetTextArg("import_dimensions", "[X]", llformat("%.3f", mPreviewScale[0]*scale));
+ mFMP->childSetTextArg("import_dimensions", "[Y]", llformat("%.3f", mPreviewScale[1]*scale));
+ mFMP->childSetTextArg("import_dimensions", "[Z]", llformat("%.3f", mPreviewScale[2]*scale));
+
+ updateStatusMessages();
+
+ return cost;
+}
+
+void LLModelPreview::rebuildUploadData()
+{
+ assert_main_thread();
+
+ mUploadData.clear();
+ mTextureSet.clear();
+
+ //fill uploaddata instance vectors from scene data
+
+ std::string requested_name = mFMP->getChild<LLUICtrl>("description_form")->getValue().asString();
+
+
+ LLSpinCtrl* scale_spinner = mFMP->getChild<LLSpinCtrl>("import_scale");
+
+ if (!scale_spinner)
+ {
+ llerrs << "floater_model_preview.xml MUST contain import_scale spinner." << llendl;
+ }
+
+ F32 scale = scale_spinner->getValue().asReal();
+
+ LLMatrix4 scale_mat;
+ scale_mat.initScale(LLVector3(scale, scale, scale));
+
+ F32 max_scale = 0.f;
+
+ if ( mBaseScene.size() > 0 )
+ {
+ mFMP->childEnable("ok_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;
+ }
+
+ S32 idx = 0;
+ for (idx = 0; idx < mBaseModel.size(); ++idx)
+ { //find reference instance for this model
+ if (mBaseModel[idx] == base_model)
+ {
+ break;
+ }
+ }
+
+ for (U32 i = 0; i < LLModel::NUM_LODS; i++)
+ { //fill LOD slots based on reference model index
+ if (!mModel[i].empty())
+ {
+ 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/max_scale;
+
+ scale_spinner->setMaxValue(max_import_scale);
+
+ if (max_import_scale < scale)
+ {
+ scale_spinner->setValue(max_import_scale);
+ }
+
+}
+
+
+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)
+{
+ assert_main_thread();
+
+ LLMutexLock lock(this);
+
+ if (mModelLoader)
+ {
+ delete mModelLoader;
+ mModelLoader = NULL;
+ }
+
+ if (filename.empty())
+ {
+ if (mBaseModel.empty())
+ {
+ // this is the initial file picking. Close the whole floater
+ // if we don't have a base model to show for high LOD.
+ mFMP->closeFloater(false);
+ }
+
+ mLoading = false;
+ return;
+ }
+
+ mLODFile[lod] = filename;
+
+ if (lod == LLModel::LOD_HIGH)
+ {
+ clearGLODGroup();
+ }
+
+ mModelLoader = new LLModelLoader(filename, lod, this);
+
+ mModelLoader->start();
+
+ mFMP->childSetTextArg("status", "[STATUS]", mFMP->getString("status_reading_file"));
+
+ setPreviewLOD(lod);
+
+ if ( mModelLoader->getLoadState() == LLModelLoader::ERROR_PARSING )
+ {
+ mFMP->childDisable("ok_btn");
+ }
+
+ if (lod == mPreviewLOD)
+ {
+ mFMP->childSetText("lod_file", mLODFile[mPreviewLOD]);
+ }
+ else if (lod == LLModel::LOD_PHYSICS)
+ {
+ mFMP->childSetText("physics_file", mLODFile[lod]);
+ }
+
+ mFMP->openFloater();
+}
+
+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)
+{
+ 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<LLPointer<LLModel>, U32>::iterator iter = mObject.begin(); iter != mObject.end(); ++iter)
+ {
+ glodDeleteObject(iter->second);
+ stop_gloderror();
+ }
+ mObject.clear();
+
+ glodDeleteGroup(mGroup);
+ stop_gloderror();
+ mGroup = 0;
+ }
+}
+
+void LLModelPreview::loadModelCallback(S32 lod)
+{
+ assert_main_thread();
+
+ LLMutexLock lock(this);
+ if (!mModelLoader)
+ {
+ return;
+ }
+
+ mModel[lod] = mModelLoader->mModelList;
+ mScene[lod] = mModelLoader->mScene;
+ mVertexBuffer[lod].clear();
+
+ if (lod == LLModel::LOD_PHYSICS)
+ {
+ mPhysicsMesh.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;
+ refresh();
+}
+
+void LLModelPreview::resetPreviewTarget()
+{
+ mPreviewTarget = (mModelLoader->mExtents[0] + mModelLoader->mExtents[1]) * 0.5f;
+ mPreviewScale = (mModelLoader->mExtents[1] - mModelLoader->mExtents[0]) * 0.5f;
+ setPreviewTarget(mPreviewScale.magVec()*2.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();
+
+ 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();
+
+}
+
+void LLModelPreview::consolidate()
+{
+ std::map<LLImportMaterial, std::vector<LLModelInstance> > composite;
+
+ LLMatrix4 identity;
+
+ //bake out each node in current scene to composite
+ for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
+ { //for each transform in current scene
+ LLMatrix4 mat = iter->first;
+ glh::matrix4f inv_trans = glh::matrix4f((F32*) mat.mMatrix).inverse().transpose();
+ LLMatrix4 norm_mat(inv_trans.m);
+
+ for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
+ { //for each instance with that transform
+ LLModelInstance& source_instance = *model_iter;
+ LLModel* source = source_instance.mModel;
+
+ if (!validate_model(source))
+ {
+ llerrs << "Invalid model found!" << llendl;
+ }
+
+ for (S32 i = 0; i < source->getNumVolumeFaces(); ++i)
+ { //for each face in instance
+ const LLVolumeFace& src_face = source->getVolumeFace(i);
+ LLImportMaterial& source_material = source_instance.mMaterial[i];
+
+ //get model in composite that is composite for this material
+ LLModel* model = NULL;
+
+ if (composite.find(source_material) != composite.end())
+ {
+ model = composite[source_material].rbegin()->mModel;
+ if (model->getVolumeFace(0).mNumVertices + src_face.mNumVertices > 65535)
+ {
+ model = NULL;
+ }
+ }
+
+ if (model == NULL)
+ { //no model found, make new model
+ std::vector<LLImportMaterial> materials;
+ materials.push_back(source_material);
+ LLVolumeParams volume_params;
+ volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
+ model = new LLModel(volume_params, 0.f);
+ model->mLabel = source->mLabel;
+ model->setNumVolumeFaces(0);
+ composite[source_material].push_back(LLModelInstance(model, identity, materials));
+ }
+
+ model->appendFace(src_face, source->mMaterialList[i], mat, norm_mat);
+ }
+ }
+ }
+
+
+ //condense composite into as few LLModel instances as possible
+ LLModelLoader::model_list new_model;
+ std::vector<LLModelInstance> instance_list;
+
+ LLVolumeParams volume_params;
+ volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
+
+ std::vector<LLImportMaterial> empty_material;
+ LLModelInstance cur_instance(new LLModel(volume_params, 0.f), identity, empty_material);
+ cur_instance.mModel->setNumVolumeFaces(0);
+
+ BOOL first_transform = TRUE;
+
+ LLModelLoader::scene new_scene;
+ LLVector3 min,max;
+
+ for (std::map<LLImportMaterial, std::vector<LLModelInstance> >::iterator iter = composite.begin();
+ iter != composite.end();
+ ++iter)
+ {
+ std::map<LLImportMaterial, std::vector<LLModelInstance> >::iterator next_iter = iter; ++next_iter;
+
+ for (std::vector<LLModelInstance>::iterator instance_iter = iter->second.begin();
+ instance_iter != iter->second.end();
+ ++instance_iter)
+ {
+ LLModel* source = instance_iter->mModel;
+
+ if (instance_iter->mMaterial.size() != 1)
+ {
+ llerrs << "WTF?" << llendl;
+ }
+
+ if (source->getNumVolumeFaces() != 1)
+ {
+ llerrs << "WTF?" << llendl;
+ }
+
+ if (source->mMaterialList.size() != 1)
+ {
+ llerrs << "WTF?" << llendl;
+ }
+
+ cur_instance.mModel->addFace(source->getVolumeFace(0));
+ cur_instance.mMaterial.push_back(instance_iter->mMaterial[0]);
+ cur_instance.mModel->mMaterialList.push_back(source->mMaterialList[0]);
+
+ BOOL last_model = FALSE;
+
+ std::vector<LLModelInstance>::iterator next_instance = instance_iter; ++next_instance;
+
+ if (next_iter == composite.end() &&
+ next_instance == iter->second.end())
+ {
+ last_model = TRUE;
+ }
+
+ if (last_model || cur_instance.mModel->getNumVolumeFaces() >= MAX_MODEL_FACES)
+ {
+ cur_instance.mModel->mLabel = source->mLabel;
+
+ cur_instance.mModel->optimizeVolumeFaces();
+ cur_instance.mModel->normalizeVolumeFaces();
+
+ if (!validate_model(cur_instance.mModel))
+ {
+ llerrs << "Invalid model detected." << llendl;
+ }
+
+ new_model.push_back(cur_instance.mModel);
+
+ LLMatrix4 transformation = LLMatrix4();
+
+ // adjust the transformation to compensate for mesh normalization
+ LLVector3 mesh_scale_vector;
+ LLVector3 mesh_translation_vector;
+ cur_instance.mModel->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;
+
+ cur_instance.mTransform = transformation;
+
+ new_scene[transformation].push_back(cur_instance);
+ stretch_extents(cur_instance.mModel, transformation, min, max, first_transform);
+
+ if (!last_model)
+ {
+ cur_instance = LLModelInstance(new LLModel(volume_params, 0.f), identity, empty_material);
+ cur_instance.mModel->setNumVolumeFaces(0);
+ }
+ }
+ }
+ }
+
+ mScene[mPreviewLOD] = new_scene;
+ mModel[mPreviewLOD] = new_model;
+ mVertexBuffer[mPreviewLOD].clear();
+
+ if (mPreviewLOD == LLModel::LOD_HIGH)
+ {
+ mBaseScene = new_scene;
+ mBaseModel = new_model;
+ clearGLODGroup();
+ mVertexBuffer[5].clear();
+ }
+
+ mPreviewTarget = (min+max)*0.5f;
+ mPreviewScale = (max-min)*0.5f;
+ setPreviewTarget(mPreviewScale.magVec()*2.f);
+
+ clearIncompatible(mPreviewLOD);
+
+ mResourceCost = calcResourceCost();
+ refresh();
+}
+
+void LLModelPreview::clearMaterials()
+{
+ for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter)
+ { //for each transform in current scene
+ for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter)
+ { //for each instance with that transform
+ LLModelInstance& source_instance = *model_iter;
+ LLModel* source = source_instance.mModel;
+
+ for (S32 i = 0; i < source->getNumVolumeFaces(); ++i)
+ { //for each face in instance
+ LLImportMaterial& source_material = source_instance.mMaterial[i];
+
+ //clear material info
+ source_material.mDiffuseColor = LLColor4(1,1,1,1);
+ source_material.mDiffuseMap = NULL;
+ source_material.mDiffuseMapFilename.clear();
+ source_material.mDiffuseMapLabel.clear();
+ source_material.mFullbright = false;
+ }
+ }
+ }
+
+ mVertexBuffer[mPreviewLOD].clear();
+
+ if (mPreviewLOD == LLModel::LOD_HIGH)
+ {
+ mBaseScene = mScene[mPreviewLOD];
+ mBaseModel = mModel[mPreviewLOD];
+ clearGLODGroup();
+ mVertexBuffer[5].clear();
+ }
+
+ mResourceCost = calcResourceCost();
+ refresh();
+}
+
+bool LLModelPreview::containsRiggedAsset( void )
+{
+ //loop through the models and determine if any of them contained a rigged asset, and if so
+ //return true.
+ //This is used to cleanup the joint positions after a preview.
+ for (LLModelLoader::model_list::iterator iter = mBaseModel.begin(); iter != mBaseModel.end(); ++iter)
+ {
+ LLModel* pModel = *iter;
+ if ( pModel->mAlternateBindMatrix.size() > 0 )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+void LLModelPreview::genLODs(S32 which_lod, U32 decimation)
+{
+ if (mBaseModel.empty())
+ {
+ return;
+ }
+
+ if (which_lod == LLModel::LOD_PHYSICS)
+ { //clear physics mesh map
+ mPhysicsMesh.clear();
+ }
+
+ 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;
+
+ LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode");
+ if (iface)
+ {
+ lod_mode = iface->getFirstSelectedIndex();
+ }
+
+ F32 lod_error_threshold = mFMP->childGetValue("lod_error_threshold").asReal();
+
+ if (lod_mode == 0)
+ {
+ lod_mode = GLOD_TRIANGLE_BUDGET;
+ if (which_lod != -1)
+ {
+ limit = mFMP->childGetValue("lod_triangle_limit").asInteger();
+ }
+ }
+ else
+ {
+ lod_mode = GLOD_ERROR_THRESHOLD;
+ }
+
+ U32 build_operator = 0;
+
+ iface = mFMP->childGetSelectionInterface("build_operator");
+ if (iface)
+ {
+ build_operator = iface->getFirstSelectedIndex();
+ }
+
+ if (build_operator == 0)
+ {
+ build_operator = GLOD_OPERATOR_HALF_EDGE_COLLAPSE;
+ }
+ else
+ {
+ build_operator = GLOD_OPERATOR_EDGE_COLLAPSE;
+ }
+
+ U32 queue_mode=0;
+ iface = mFMP->childGetSelectionInterface("queue_mode");
+ if (iface)
+ {
+ queue_mode = iface->getFirstSelectedIndex();
+ }
+
+ if (queue_mode == 0)
+ {
+ queue_mode = GLOD_QUEUE_GREEDY;
+ }
+ else if (queue_mode == 1)
+ {
+ queue_mode = GLOD_QUEUE_LAZY;
+ }
+ else
+ {
+ queue_mode = GLOD_QUEUE_INDEPENDENT;
+ }
+
+ U32 border_mode = 0;
+
+ iface = mFMP->childGetSelectionInterface("border_mode");
+ if (iface)
+ {
+ border_mode = iface->getFirstSelectedIndex();
+ }
+
+ if (border_mode == 0)
+ {
+ border_mode = GLOD_BORDER_UNLOCK;
+ }
+ else
+ {
+ border_mode = GLOD_BORDER_LOCK;
+ }
+
+ bool object_dirty = false;
+ if (border_mode != mBuildBorderMode)
+ {
+ mBuildBorderMode = border_mode;
+ object_dirty = true;
+ }
+
+ if (queue_mode != mBuildQueueMode)
+ {
+ mBuildQueueMode = queue_mode;
+ object_dirty = true;
+ }
+
+ if (build_operator != mBuildOperator)
+ {
+ mBuildOperator = build_operator;
+ object_dirty = true;
+ }
+
+ F32 share_tolerance = mFMP->childGetValue("share_tolerance").asReal();
+ if (share_tolerance != mBuildShareTolerance)
+ {
+ mBuildShareTolerance = share_tolerance;
+ object_dirty = true;
+ }
+
+ 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)
+ {
+ mVertexBuffer[5][mdl][i]->setBuffer(type_mask);
+ 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();
+ }
+
+ glodObjectParameteri(mObject[mdl], GLOD_BUILD_OPERATOR, build_operator);
+ stop_gloderror();
+
+ glodObjectParameteri(mObject[mdl], GLOD_BUILD_QUEUE_MODE, queue_mode);
+ stop_gloderror();
+
+ glodObjectParameteri(mObject[mdl], GLOD_BUILD_BORDER_MODE, border_mode);
+ stop_gloderror();
+
+ glodObjectParameterf(mObject[mdl], GLOD_BUILD_SHARE_TOLERANCE, share_tolerance);
+ 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
+ {
+ triangle_count = limit;
+ }
+
+ mModel[lod].clear();
+ mModel[lod].resize(mBaseModel.size());
+ mVertexBuffer[lod].clear();
+
+ U32 actual_tris = 0;
+ U32 actual_verts = 0;
+ U32 submeshes = 0;
+
+ glodGroupParameteri(mGroup, GLOD_ADAPT_MODE, lod_mode);
+ stop_gloderror();
+
+ glodGroupParameteri(mGroup, GLOD_ERROR_MODE, GLOD_OBJECT_SPACE_ERROR);
+ stop_gloderror();
+
+ glodGroupParameteri(mGroup, GLOD_MAX_TRIANGLES, triangle_count);
+ stop_gloderror();
+
+ glodGroupParameterf(mGroup, GLOD_OBJECT_SPACE_ERROR_THRESHOLD, lod_error_threshold);
+ 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)
+ {
+ LLPointer<LLVertexBuffer> 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<LLVector3> pos;
+ LLStrider<LLVector3> norm;
+ LLStrider<LLVector2> tc;
+ LLStrider<U16> index;
+
+ buff->getVertexStrider(pos);
+ buff->getNormalStrider(norm);
+ 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->mJointMap = base->mJointMap;
+ target_model->mJointList = base->mJointList;
+ target_model->mInvBindMatrix = base->mInvBindMatrix;
+ target_model->mBindShapeMatrix = base->mBindShapeMatrix;
+ target_model->mAlternateBindMatrix = base->mAlternateBindMatrix;
+ //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<S32> tris[LLModel::NUM_LODS];
+ std::vector<S32> verts[LLModel::NUM_LODS];
+ std::vector<S32> submeshes[LLModel::NUM_LODS];
+
+ //total triangle/vertex/submesh count for each lod
+ S32 total_tris[LLModel::NUM_LODS];
+ S32 total_verts[LLModel::NUM_LODS];
+ S32 total_submeshes[LLModel::NUM_LODS];
+
+ for (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];
+ }
+
+
+ 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())
+ {
+ for (U32 i = 0; i < verts[lod].size(); ++i)
+ {
+ S32 max_verts = i < verts[lod+1].size() ? verts[lod+1][i] : 0;
+
+ if (max_verts > 0)
+ {
+ if (verts[lod][i] > max_verts)
+ { //too many vertices in this lod
+ message = "mesh_status_too_many_vertices";
+ upload_status[lod] = 2;
+ }
+ }
+ }
+ }
+ }
+
+ LLIconCtrl* icon = mFMP->getChild<LLIconCtrl>(lod_icon_name[lod]);
+ LLUIImagePtr img = LLUI::getUIImage(lod_status_image[upload_status[lod]]);
+ icon->setVisible(true);
+ icon->setImage(img);
+
+ if (upload_status[lod] >= 2)
+ {
+ upload_ok = false;
+ }
+
+ if (lod == mPreviewLOD)
+ {
+ mFMP->childSetText("lod_status_message_text", mFMP->getString(message));
+ icon = mFMP->getChild<LLIconCtrl>("lod_status_message_icon");
+ icon->setImage(img);
+ }
+ }
+
+ bool errorStateFromLoader = mModelLoader->getLoadState() == LLModelLoader::ERROR_PARSING ? true : false;
+
+ if ( upload_ok && !errorStateFromLoader )
+ {
+ mFMP->childEnable("ok_btn");
+ }
+ else
+ {
+ 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->mConvexHullDecomp;
+
+ 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");
+ fmp->setViewOption("show_physics", true);
+ }
+ }
+ else
+ {
+ fmp->disableViewOption("show_physics");
+ fmp->setViewOption("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;
+ //enable = enable && !use_hull && fmp->childGetValue("physics_optimize").asBoolean();
+
+ //enable/disable "analysis" UI
+ LLPanel* panel = fmp->getChild<LLPanel>("physics analysis");
+ LLView* child = panel->getFirstChild();
+ while (child)
+ {
+ child->setEnabled(enable);
+ child = panel->findNextSibling(child);
+ }
+
+ enable = phys_hulls > 0;
+ //enable/disable "simplification" UI
+ panel = fmp->getChild<LLPanel>("physics simplification");
+ child = panel->getFirstChild();
+ while (child)
+ {
+ child->setEnabled(enable);
+ child = panel->findNextSibling(child);
+ }
+ }
+
+ const char* lod_controls[] =
+ {
+ "lod_mode",
+ "lod_triangle_limit",
+ "lod_error_tolerance",
+ "build_operator_text",
+ "queue_mode_text",
+ "border_mode_text",
+ "share_tolerance_text",
+ "build_operator",
+ "queue_mode",
+ "border_mode",
+ "share_tolerance"
+ };
+ 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*);
+
+ if (fmp)
+ {
+ //enable/disable controls based on radio groups
+ if (mFMP->childGetValue("lod_from_file").asBoolean())
+ {
+ fmp->mLODMode[mPreviewLOD] = 0;
+ for (U32 i = 0; i < num_file_controls; ++i)
+ {
+ mFMP->childEnable(file_controls[i]);
+ }
+
+ for (U32 i = 0; i < num_lod_controls; ++i)
+ {
+ mFMP->childDisable(lod_controls[i]);
+ }
+ }
+ else if (mFMP->childGetValue("lod_none").asBoolean())
+ {
+ fmp->mLODMode[mPreviewLOD] = 2;
+ for (U32 i = 0; i < num_file_controls; ++i)
+ {
+ mFMP->childDisable(file_controls[i]);
+ }
+
+ for (U32 i = 0; i < num_lod_controls; ++i)
+ {
+ mFMP->childDisable(lod_controls[i]);
+ }
+
+ if (!mModel[mPreviewLOD].empty())
+ {
+ mModel[mPreviewLOD].clear();
+ mScene[mPreviewLOD].clear();
+ mVertexBuffer[mPreviewLOD].clear();
+
+ //this can cause phasing issues with the UI, so reenter this function and return
+ updateStatusMessages();
+ return;
+ }
+ }
+ else
+ { // auto generate, also the default case for wizard which has no radio selection
+ fmp->mLODMode[mPreviewLOD] = 1;
+
+ for (U32 i = 0; i < num_file_controls; ++i)
+ {
+ mFMP->childDisable(file_controls[i]);
+ }
+
+ for (U32 i = 0; i < num_lod_controls; ++i)
+ {
+ mFMP->childEnable(lod_controls[i]);
+ }
+
+ //if (threshold)
+ {
+ U32 lod_mode = 0;
+ LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode");
+ if (iface)
+ {
+ lod_mode = iface->getFirstSelectedIndex();
+ }
+
+ LLSpinCtrl* threshold = mFMP->getChild<LLSpinCtrl>("lod_error_threshold");
+ LLSpinCtrl* limit = mFMP->getChild<LLSpinCtrl>("lod_triangle_limit");
+
+ limit->setMaxValue(mMaxTriangleLimit);
+ limit->setValue(total_tris[mPreviewLOD]);
+
+ if (lod_mode == 0)
+ {
+ limit->setVisible(true);
+ threshold->setVisible(false);
+
+ limit->setMaxValue(mMaxTriangleLimit);
+ }
+ else
+ {
+ limit->setVisible(false);
+ threshold->setVisible(true);
+ }
+ }
+ }
+ }
+
+ if (mFMP->childGetValue("physics_load_from_file").asBoolean())
+ {
+ mFMP->childDisable("physics_lod_combo");
+ mFMP->childEnable("physics_file");
+ mFMP->childEnable("physics_browse");
+ }
+ else
+ {
+ mFMP->childEnable("physics_lod_combo");
+ mFMP->childDisable("physics_file");
+ mFMP->childDisable("physics_browse");
+ }
+}
+
+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<LLVector3> vertex_strider;
+ LLStrider<LLVector3> normal_strider;
+ LLStrider<LLVector2> tc_strider;
+ LLStrider<U16> index_strider;
+ LLStrider<LLVector4> weights_strider;
+
+ vb->getVertexStrider(vertex_strider);
+ vb->getNormalStrider(normal_strider);
+ vb->getTexCoord0Strider(tc_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));
+ LLVector4a::memcpyNonAliased16((F32*) tc_strider.get(), (F32*) vf.mTexCoords, num_vertices*2*sizeof(F32));
+ 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);
+ if (weight_list.size() > 4)
+ {
+ llerrs << "WTF?" << llendl;
+ }
+
+ 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();
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// render()
+//-----------------------------------------------------------------------------
+BOOL LLModelPreview::render()
+{
+ assert_main_thread();
+
+ LLMutexLock lock(this);
+ mNeedsUpdate = FALSE;
+
+ bool edges = false;
+ bool joint_positions = false;
+ bool skin_weight = false;
+ bool textures = false;
+ bool physics = false;
+
+ LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
+ if (fmp)
+ {
+ edges = fmp->isViewOptionChecked("show_edges");
+ joint_positions = fmp->isViewOptionChecked("show_joint_positions");
+ skin_weight = fmp->isViewOptionChecked("show_skin_weight");
+ textures = fmp->isViewOptionChecked("show_textures");
+ physics = fmp->isViewOptionChecked("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.15f, 0.2f, 0.3f, 1.f);
+
+ gl_rect_2d_simple( width, height );
+
+ glMatrixMode(GL_PROJECTION);
+ gGL.popMatrix();
+
+ glMatrixMode(GL_MODELVIEW);
+ gGL.popMatrix();
+ }
+
+ bool has_skin_weights = false;
+ bool upload_skin = mFMP->childGetValue("upload_skin").asBoolean();
+ bool upload_joints = mFMP->childGetValue("upload_joints").asBoolean();
+
+ 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())
+ {
+ has_skin_weights = true;
+ }
+ }
+ }
+
+ if (has_skin_weights)
+ { //model has skin weights, enable view options for skin weights and joint positions
+ if (fmp)
+ {
+ fmp->enableViewOption("show_skin_weight");
+ fmp->setViewOptionEnabled("show_joint_positions", skin_weight);
+ }
+ mFMP->childEnable("upload_skin");
+ }
+ else
+ {
+ mFMP->childDisable("upload_skin");
+ if (fmp)
+ {
+ fmp->setViewOption("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;
+ }
+
+ 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::getInstance()->setAspect(aspect);
+
+ LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom);
+
+ LLVector3 offset = mCameraOffset;
+ LLVector3 target_pos = mPreviewTarget+offset;
+
+ F32 z_near = 0.001f;
+ F32 z_far = mCameraDistance+mPreviewScale.magVec()+mCameraOffset.magVec();
+
+ if (skin_weight)
+ {
+ target_pos = gAgentAvatarp->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;
+ LLViewerCamera::getInstance()->setOriginAndLookAt(
+ target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + offset) * av_rot), // camera
+ LLVector3::z_axis, // up
+ target_pos); // point of interest
+
+
+ LLViewerCamera::getInstance()->setPerspective(FALSE, mOrigin.mX, mOrigin.mY, width, height, FALSE, z_near, z_far);
+
+ stop_glerror();
+
+ gGL.pushMatrix();
+ const F32 BRIGHTNESS = 0.9f;
+ gGL.color3f(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS);
+
+ LLGLEnable normalize(GL_NORMALIZE);
+
+ if (!mBaseModel.empty() && mVertexBuffer[5].empty())
+ {
+ genBuffers(-1, skin_weight);
+ //genBuffers(3);
+ //genLODs();
+ }
+
+ if (!mModel[mPreviewLOD].empty())
+ {
+ bool regen = mVertexBuffer[mPreviewLOD].empty();
+ if (!regen)
+ {
+ const std::vector<LLPointer<LLVertexBuffer> >& vb_vec = mVertexBuffer[mPreviewLOD].begin()->second;
+ if (!vb_vec.empty())
+ {
+ const LLVertexBuffer* buff = vb_vec[0];
+ regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight;
+ }
+ }
+
+ 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(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0);
+
+ if (textures)
+ {
+ glColor4fv(instance.mMaterial[i].mDiffuseColor.mV);
+ if (i < instance.mMaterial.size() && instance.mMaterial[i].mDiffuseMap.notNull())
+ {
+ gGL.getTexUnit(0)->bind(instance.mMaterial[i].mDiffuseMap, true);
+ if (instance.mMaterial[i].mDiffuseMap->getDiscardLevel() > -1)
+ {
+ mTextureSet.insert(instance.mMaterial[i].mDiffuseMap);
+ }
+ }
+ }
+ 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);
+ LLGLEnable blend(GL_BLEND);
+ gGL.blendFunc(LLRender::BF_ONE, LLRender::BF_ZERO);
+
+ 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);
+
+ std::map<LLPointer<LLModel>, std::vector<LLPointer<LLVertexBuffer> > >::iterator iter =
+ mPhysicsMesh.find(model);
+ if (iter != mPhysicsMesh.end())
+ { //render hull instead of mesh
+ render_mesh = false;
+ for (U32 i = 0; i < iter->second.size(); ++i)
+ {
+ if (explode > 0.f)
+ {
+ gGL.pushMatrix();
+
+ LLVector3 offset = model->mHullCenter[i]-model->mCenterOfHullCenters;
+ offset *= explode;
+
+ gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]);
+ }
+
+ static std::vector<LLColor4U> hull_colors;
+
+ if (i+1 >= hull_colors.size())
+ {
+ hull_colors.push_back(LLColor4U(rand()%128+127, rand()%128+127, rand()%128+127, 255));
+ }
+
+ LLVertexBuffer* buff = iter->second[i];
+ if (buff)
+ {
+ buff->setBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL);
+
+ glColor4ubv(hull_colors[i].mV);
+ buff->drawArrays(LLRender::TRIANGLES, 0, buff->getNumVerts());
+ }
+
+ 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];
+
+ buffer->setBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0);
+
+ buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts()-1, buffer->getNumIndices(), 0);
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ glColor4f(0.4f, 0.4f, 0.0f, 0.4f);
+
+ buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts()-1, buffer->getNumIndices(), 0);
+
+ glColor3f(1.f, 1.f, 0.f);
+
+ 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();
+ }
+
+ gGL.setSceneBlendType(LLRender::BT_ALPHA);
+ }
+ }
+ else
+ {
+ LLVOAvatarSelf* avatar = gAgentAvatarp;
+ target_pos = avatar->getPositionAgent();
+
+ LLViewerCamera::getInstance()->setOriginAndLookAt(
+ target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + offset) * av_rot), // camera
+ LLVector3::z_axis, // up
+ target_pos); // point of interest
+
+ if (joint_positions)
+ {
+ avatar->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<LLVector3> position;
+ buffer->getVertexStrider(position);
+
+ LLStrider<LLVector4> weight;
+ buffer->getWeight4Strider(weight);
+
+ //quick 'n dirty software vertex skinning
+
+ //build matrix palette
+ LLMatrix4 mat[64];
+ for (U32 j = 0; j < model->mJointList.size(); ++j)
+ {
+ LLJoint* joint = avatar->getJoint(model->mJointList[j]);
+ if (joint)
+ {
+ mat[j] = model->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->mBindShapeMatrix;
+ v = v * final_mat;
+
+ position[j] = v;
+ }
+
+ buffer->setBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0);
+ glColor4fv(instance.mMaterial[i].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<LLComboBox>("preview_lod_combo");
+ combo_box->setCurrentByIndex((NUM_LOD-1)-mPreviewLOD); // combo box list of lods is in reverse order
+ mFMP->childSetTextArg("lod_table_footer", "[DETAIL]", mFMP->getString(lod_name[mPreviewLOD]));
+ mFMP->childSetText("lod_file", mLODFile[mPreviewLOD]);
+
+ LLColor4 highlight_color = LLUIColorTable::instance().getColor("MeshImportTableHighlightColor");
+ LLColor4 normal_color = LLUIColorTable::instance().getColor("MeshImportTableNormalColor");
+
+ for (S32 i = 0; i <= LLModel::LOD_HIGH; ++i)
+ {
+ const LLColor4& color = (i == lod) ? highlight_color : normal_color;
+
+ mFMP->childSetColor(lod_status_name[i], color);
+ mFMP->childSetColor(lod_label_name[i], color);
+ mFMP->childSetColor(lod_triangles_name[i], color);
+ mFMP->childSetColor(lod_vertices_name[i], color);
+ }
+
+ LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance;
+ if (fmp)
+ {
+ LLRadioGroup* radio = fmp->getChild<LLRadioGroup>("lod_file_or_limit");
+ radio->selectNthItem(fmp->mLODMode[mPreviewLOD]);
+ }
+ }
+ refresh();
+ updateStatusMessages();
+}
+
+//static
+void LLFloaterModelPreview::onBrowseLOD(void* data)
+{
+ assert_main_thread();
+
+ LLFloaterModelPreview* mp = (LLFloaterModelPreview*) data;
+ mp->loadModel(mp->mModelPreview->mPreviewLOD);
+}
+
+//static
+void LLFloaterModelPreview::onUpload(void* user_data)
+{
+ assert_main_thread();
+
+ LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data;
+
+ mp->mModelPreview->rebuildUploadData();
+
+ gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, mp->mModelPreview->mPreviewScale,
+ mp->childGetValue("upload_textures").asBoolean(), mp->childGetValue("upload_skin"), mp->childGetValue("upload_joints"));
+
+ mp->closeFloater(false);
+}
+
+
+//static
+void LLFloaterModelPreview::onClearMaterials(void* user_data)
+{
+ LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data;
+ mp->mModelPreview->clearMaterials();
+}
+
+//static
+void LLFloaterModelPreview::refresh(LLUICtrl* ctrl, void* user_data)
+{
+ sInstance->mModelPreview->mDirty = true;
+}
+
+void LLFloaterModelPreview::updateResourceCost()
+{
+ U32 cost = mModelPreview->mResourceCost;
+ childSetLabelArg("ok_btn", "[AMOUNT]", llformat("%d",cost));
+}
+
+//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();
+}
+
+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
+ if (mdl)
+ {
+ U16 index_offset = 0;
+
+ mPositions.clear();
+ mIndices.clear();
+
+ //queue up vertex positions and indices
+ for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i)
+ {
+ const LLVolumeFace& face = mdl->getVolumeFace(i);
+ if (mPositions.size() + face.mNumVertices > 65535)
+ {
+ continue;
+ }
+
+ for (U32 j = 0; j < face.mNumVertices; ++j)
+ {
+ mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr()));
+ }
+
+ for (U32 j = 0; j < face.mNumIndices; ++j)
+ {
+ mIndices.push_back(face.mIndices[j]+index_offset);
+ }
+
+ index_offset += face.mNumVertices;
+ }
+ }
+}
+
+void LLFloaterModelPreview::setStatusMessage(const std::string& msg)
+{
+ LLMutexLock lock(mStatusLock);
+ mStatusMessage = msg;
+}
+
+S32 LLFloaterModelPreview::DecompRequest::statusCallback(const char* status, S32 p1, S32 p2)
+{
+ 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
+ mModel->setConvexHullDecomposition(mHull);
+
+ if (sInstance)
+ {
+ if (sInstance->mModelPreview)
+ {
+ sInstance->mModelPreview->mPhysicsMesh[mModel] = mHullMesh;
+ sInstance->mModelPreview->mDirty = true;
+ LLFloaterModelPreview::sInstance->mModelPreview->refresh();
+ }
+
+ sInstance->mCurRequest.erase(this);
+ }
+}
diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h
index ae4cf18fee..48263d493a 100755..100644
--- a/indra/newview/llfloatermodelpreview.h
+++ b/indra/newview/llfloatermodelpreview.h
@@ -1,328 +1,337 @@
-/**
- * @file llfloatermodelpreview.h
- * @brief LLFloaterModelPreview class definition
- *
- * $LicenseInfo:firstyear=2004&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#ifndef LL_LLFLOATERMODELPREVIEW_H
-#define LL_LLFLOATERMODELPREVIEW_H
-
-#include "llfloaternamedesc.h"
-
-#include "lldynamictexture.h"
-#include "llquaternion.h"
-#include "llmeshrepository.h"
-#include "llmodel.h"
-#include "llthread.h"
-#include "llviewermenufile.h"
-
-class LLComboBox;
-class LLJoint;
-class LLViewerJointMesh;
-class LLVOAvatar;
-class LLTextBox;
-class LLVertexBuffer;
-class LLModelPreview;
-class LLFloaterModelPreview;
-class daeElement;
-class domProfile_COMMON;
-class domInstance_geometry;
-class domNode;
-class domTranslate;
-class LLMenuButton;
-class LLToggleableMenu;
-
-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];
-
- std::map<daeElement*, LLPointer<LLModel> > mModel;
-
- typedef std::vector<LLPointer<LLModel> > model_list;
- model_list mModelList;
-
- typedef std::vector<LLModelInstance> model_instance_list;
-
- typedef std::map<LLMatrix4, model_instance_list > scene;
-
- scene mScene;
-
- typedef std::queue<LLPointer<LLModel> > model_queue;
-
- //queue of models that need a physics rep
- model_queue mPhysicsQ;
-
- LLModelLoader(std::string filename, S32 lod, LLModelPreview* preview);
-
- virtual void run();
-
- void processElement(daeElement* element);
- std::vector<LLImportMaterial> 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<std::string,LLMatrix4>& jointTransforms );
- void extractTranslation( domTranslate* pTranslate, LLMatrix4& transform );
- void extractTranslationViaElement( daeElement* pTranslateElement, LLMatrix4& transform );
-
- void setLoadState( U32 state ) { mState = state; }
- U32 getLoadState( void ) { return mState; }
-
- //map of avatar joints as named in COLLADA assets to internal joint names
- std::map<std::string, std::string> mJointMap;
-};
-
-class LLFloaterModelPreview : public LLFloater
-{
-public:
-
- class DecompRequest : public LLPhysicsDecomp::Request
- {
- public:
- S32 mContinue;
- LLPointer<LLModel> 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 LLSD& key);
- virtual ~LLFloaterModelPreview();
-
- virtual BOOL postBuild();
-
- 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);
-
- static void onMouseCaptureLostModelPreview(LLMouseHandler*);
- static void setUploadAmount(S32 amount) { sUploadAmount = amount; }
-
- static void onBrowseLOD(void* data);
-
- static void onUpload(void* data);
-
- static void onClearMaterials(void* data);
- static void onModelDecompositionComplete(LLModel* model, std::vector<LLPointer<LLVertexBuffer> >& physics_mesh);
-
- static void refresh(LLUICtrl* ctrl, void* data);
-
- void updateResourceCost();
-
- void loadModel(S32 lod);
-
- void onViewOptionChecked(const LLSD& userdata);
- bool isViewOptionChecked(const LLSD& userdata);
- bool isViewOptionEnabled(const LLSD& userdata);
- void setViewOptionEnabled(const std::string& option, bool enabled);
- void enableViewOption(const std::string& option);
- void disableViewOption(const std::string& option);
- void setViewOption(const std::string& option, bool value);
-
-protected:
- friend class LLModelPreview;
- friend class LLMeshFilePicker;
- friend class LLPhysicsDecomp;
-
- static void onImportScaleCommit(LLUICtrl*, void*);
- static void onUploadJointsCommit(LLUICtrl*,void*);
- static void onUploadSkinCommit(LLUICtrl*,void*);
-
- static void onPreviewLODCommit(LLUICtrl*,void*);
-
- static void onGenerateNormalsCommit(LLUICtrl*,void*);
-
- static void onAutoFillCommit(LLUICtrl*,void*);
- static void onLODParamCommit(LLUICtrl*,void*);
-
- static void onExplodeCommit(LLUICtrl*, void*);
-
- static void onPhysicsParamCommit(LLUICtrl* ctrl, void* userdata);
- static void onPhysicsStageExecute(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();
-
- LLModelPreview* mModelPreview;
-
- LLPhysicsDecomp::decomp_params mDecompParams;
-
- S32 mLastMouseX;
- S32 mLastMouseY;
- LLRect mPreviewRect;
- U32 mGLName;
- static S32 sUploadAmount;
-
- LLPointer<DecompRequest> mCurRequest;
-
- std::map<std::string, bool> mViewOption;
-
- //use "disabled" as false by default
- std::map<std::string, bool> mViewOptionDisabled;
-
- LLMenuButton* mViewOptionMenuButton;
- LLToggleableMenu* mViewOptionMenu;
-
-};
-
-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
-{
- 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);
- void loadModelCallback(S32 lod);
- void genLODs(S32 which_lod = -1);
- void generateNormals();
- void consolidate();
- void clearMaterials();
- U32 calcResourceCost();
- void rebuildUploadData();
- void clearIncompatible(S32 lod);
- void updateStatusMessages();
- bool containsRiggedAsset( void );
- void clearGLODGroup();
-
-
- static void textureLoadedCallback( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata );
-
- protected:
- friend class LLFloaterModelPreview;
- friend class LLFloaterModelWizard;
- 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;
-
- //GLOD object parameters (must rebuild object if these change)
- F32 mBuildShareTolerance;
- U32 mBuildQueueMode;
- U32 mBuildOperator;
- U32 mBuildBorderMode;
-
-
- 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<LLPointer<LLModel>, U32> mObject;
- U32 mMaxTriangleLimit;
- std::map<LLPointer<LLModel>, std::vector<LLPointer<LLVertexBuffer> > > mPhysicsMesh;
-
- LLMeshUploadThread::instance_list mUploadData;
- std::set<LLPointer<LLViewerFetchedTexture> > mTextureSet;
-
- //map of vertex buffers to models (one vertex buffer in vector per face in model
- std::map<LLModel*, std::vector<LLPointer<LLVertexBuffer> > > mVertexBuffer[LLModel::NUM_LODS+1];
-};
-
-
-#endif // LL_LLFLOATERMODELPREVIEW_H
+/**
+ * @file llfloatermodelpreview.h
+ * @brief LLFloaterModelPreview class definition
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFLOATERMODELPREVIEW_H
+#define LL_LLFLOATERMODELPREVIEW_H
+
+#include "llfloaternamedesc.h"
+
+#include "lldynamictexture.h"
+#include "llquaternion.h"
+#include "llmeshrepository.h"
+#include "llmodel.h"
+#include "llthread.h"
+#include "llviewermenufile.h"
+
+class LLComboBox;
+class LLJoint;
+class LLViewerJointMesh;
+class LLVOAvatar;
+class LLTextBox;
+class LLVertexBuffer;
+class LLModelPreview;
+class LLFloaterModelPreview;
+class daeElement;
+class domProfile_COMMON;
+class domInstance_geometry;
+class domNode;
+class domTranslate;
+class LLMenuButton;
+class LLToggleableMenu;
+
+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];
+
+ std::map<daeElement*, LLPointer<LLModel> > mModel;
+
+ typedef std::vector<LLPointer<LLModel> > model_list;
+ model_list mModelList;
+
+ typedef std::vector<LLModelInstance> model_instance_list;
+
+ typedef std::map<LLMatrix4, model_instance_list > scene;
+
+ scene mScene;
+
+ typedef std::queue<LLPointer<LLModel> > model_queue;
+
+ //queue of models that need a physics rep
+ model_queue mPhysicsQ;
+
+ LLModelLoader(std::string filename, S32 lod, LLModelPreview* preview);
+
+ virtual void run();
+
+ void processElement(daeElement* element);
+ std::vector<LLImportMaterial> 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<std::string,LLMatrix4>& jointTransforms );
+ void extractTranslation( domTranslate* pTranslate, LLMatrix4& transform );
+ void extractTranslationViaElement( daeElement* pTranslateElement, LLMatrix4& transform );
+
+ void setLoadState( U32 state ) { mState = state; }
+ U32 getLoadState( void ) { return mState; }
+
+ //map of avatar joints as named in COLLADA assets to internal joint names
+ std::map<std::string, std::string> mJointMap;
+};
+
+class LLFloaterModelPreview : public LLFloater
+{
+public:
+
+ class DecompRequest : public LLPhysicsDecomp::Request
+ {
+ public:
+ S32 mContinue;
+ LLPointer<LLModel> 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 LLSD& key);
+ virtual ~LLFloaterModelPreview();
+
+ virtual BOOL postBuild();
+
+ 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);
+
+ static void onMouseCaptureLostModelPreview(LLMouseHandler*);
+ static void setUploadAmount(S32 amount) { sUploadAmount = amount; }
+
+ static void onBrowseLOD(void* data);
+
+ static void onUpload(void* data);
+
+ static void onClearMaterials(void* data);
+ static void onModelDecompositionComplete(LLModel* model, std::vector<LLPointer<LLVertexBuffer> >& physics_mesh);
+
+ static void refresh(LLUICtrl* ctrl, void* data);
+
+ void updateResourceCost();
+
+ void loadModel(S32 lod);
+
+ void onViewOptionChecked(const LLSD& userdata);
+ bool isViewOptionChecked(const LLSD& userdata);
+ bool isViewOptionEnabled(const LLSD& userdata);
+ void setViewOptionEnabled(const std::string& option, bool enabled);
+ void enableViewOption(const std::string& option);
+ void disableViewOption(const std::string& option);
+ void setViewOption(const std::string& option, bool value);
+
+protected:
+ friend class LLModelPreview;
+ friend class LLMeshFilePicker;
+ friend class LLPhysicsDecomp;
+
+ static void onImportScaleCommit(LLUICtrl*, void*);
+ static void onUploadJointsCommit(LLUICtrl*,void*);
+ static void onUploadSkinCommit(LLUICtrl*,void*);
+
+ static void onPreviewLODCommit(LLUICtrl*,void*);
+
+ static void onGenerateNormalsCommit(LLUICtrl*,void*);
+
+ static void onAutoFillCommit(LLUICtrl*,void*);
+ static void onLODParamCommit(LLUICtrl*,void*);
+
+ static void onExplodeCommit(LLUICtrl*, void*);
+
+ static void onPhysicsParamCommit(LLUICtrl* ctrl, void* userdata);
+ static void onPhysicsStageExecute(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<LLPointer<DecompRequest> > mCurRequest;
+ std::string mStatusMessage;
+
+ std::map<std::string, bool> mViewOption;
+
+ //use "disabled" as false by default
+ std::map<std::string, bool> mViewOptionDisabled;
+
+ //store which lod mode each LOD is using
+ // 0 - load from file
+ // 1 - auto generate
+ // 2 - None
+ S32 mLODMode[4];
+
+ LLMenuButton* mViewOptionMenuButton;
+ LLToggleableMenu* mViewOptionMenu;
+ LLMutex* mStatusLock;
+};
+
+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
+{
+ 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);
+ void loadModelCallback(S32 lod);
+ void genLODs(S32 which_lod = -1, U32 decimation = 3);
+ void generateNormals();
+ void consolidate();
+ void clearMaterials();
+ U32 calcResourceCost();
+ void rebuildUploadData();
+ void clearIncompatible(S32 lod);
+ void updateStatusMessages();
+ bool containsRiggedAsset( void );
+ void clearGLODGroup();
+
+
+ static void textureLoadedCallback( BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, BOOL final, void* userdata );
+
+ protected:
+ friend class LLFloaterModelPreview;
+ friend class LLFloaterModelWizard;
+ 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;
+
+ //GLOD object parameters (must rebuild object if these change)
+ F32 mBuildShareTolerance;
+ U32 mBuildQueueMode;
+ U32 mBuildOperator;
+ U32 mBuildBorderMode;
+
+
+ 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<LLPointer<LLModel>, U32> mObject;
+ U32 mMaxTriangleLimit;
+ std::map<LLPointer<LLModel>, std::vector<LLPointer<LLVertexBuffer> > > mPhysicsMesh;
+
+ LLMeshUploadThread::instance_list mUploadData;
+ std::set<LLPointer<LLViewerFetchedTexture> > mTextureSet;
+
+ //map of vertex buffers to models (one vertex buffer in vector per face in model
+ std::map<LLModel*, std::vector<LLPointer<LLVertexBuffer> > > mVertexBuffer[LLModel::NUM_LODS+1];
+};
+
+
+#endif // LL_LLFLOATERMODELPREVIEW_H
diff --git a/indra/newview/llfloatermodelwizard.cpp b/indra/newview/llfloatermodelwizard.cpp
index 79c29ef017..aca5d67e60 100644
--- a/indra/newview/llfloatermodelwizard.cpp
+++ b/indra/newview/llfloatermodelwizard.cpp
@@ -28,19 +28,73 @@
#include "llviewerprecompiledheaders.h"
+#include "llbutton.h"
#include "lldrawable.h"
+#include "llcombobox.h"
#include "llfloater.h"
#include "llfloatermodelwizard.h"
#include "llfloatermodelpreview.h"
#include "llfloaterreg.h"
+#include "llslider.h"
+#include "lltoolmgr.h"
+#include "llviewerwindow.h"
+static const std::string stateNames[]={
+ "choose_file",
+ "optimize",
+ "physics",
+ "review",
+ "upload"};
LLFloaterModelWizard::LLFloaterModelWizard(const LLSD& key)
: LLFloater(key)
{
}
+void LLFloaterModelWizard::setState(int state)
+{
+ mState = state;
+ setButtons(state);
+
+ for(size_t t=0; t<LL_ARRAY_SIZE(stateNames); ++t)
+ {
+ LLView *view = getChild<LLView>(stateNames[t]+"_panel");
+ if (view)
+ {
+ view->setVisible(state == (int) t ? TRUE : FALSE);
+ }
+ }
+
+ if (state == OPTIMIZE)
+ {
+ mModelPreview->genLODs(-1);
+ }
+}
+
+void LLFloaterModelWizard::setButtons(int state)
+{
+ for(size_t i=0; i<LL_ARRAY_SIZE(stateNames); ++i)
+ {
+ LLButton *button = getChild<LLButton>(stateNames[i]+"_btn");
+
+ if (i < state)
+ {
+ button->setEnabled(TRUE);
+ button->setToggleState(FALSE);
+ }
+ else if (i == state)
+ {
+ button->setEnabled(TRUE);
+ button->setToggleState(TRUE);
+ }
+ else
+ {
+ button->setEnabled(FALSE);
+ }
+ }
+}
+
void LLFloaterModelWizard::loadModel()
{
mModelPreview->mLoading = TRUE;
@@ -48,6 +102,131 @@ void LLFloaterModelWizard::loadModel()
(new LLMeshFilePicker(mModelPreview, 3))->getFile();
}
+void LLFloaterModelWizard::onClickCancel()
+{
+ closeFloater();
+}
+
+void LLFloaterModelWizard::onClickBack()
+{
+ setState(llmax((int) CHOOSE_FILE, mState-1));
+}
+
+void LLFloaterModelWizard::onClickNext()
+{
+ setState(llmin((int) UPLOAD, mState+1));
+}
+
+bool LLFloaterModelWizard::onEnableNext()
+{
+ return true;
+}
+
+bool LLFloaterModelWizard::onEnableBack()
+{
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// handleMouseDown()
+//-----------------------------------------------------------------------------
+BOOL LLFloaterModelWizard::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 LLFloaterModelWizard::handleMouseUp(S32 x, S32 y, MASK mask)
+{
+ gFocusMgr.setMouseCapture(FALSE);
+ gViewerWindow->showCursor();
+ return LLFloater::handleMouseUp(x, y, mask);
+}
+
+//-----------------------------------------------------------------------------
+// handleHover()
+//-----------------------------------------------------------------------------
+BOOL LLFloaterModelWizard::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::setMousePositionLocal(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 LLFloaterModelWizard::handleScrollWheel(S32 x, S32 y, S32 clicks)
+{
+ if (mPreviewRect.pointInRect(x, y) && mModelPreview)
+ {
+ mModelPreview->zoom((F32)clicks * -0.2f);
+ mModelPreview->refresh();
+ }
+
+ return TRUE;
+}
+
BOOL LLFloaterModelWizard::postBuild()
{
@@ -56,7 +235,20 @@ BOOL LLFloaterModelWizard::postBuild()
childSetValue("import_scale", (F32) 0.67335826);
getChild<LLUICtrl>("browse")->setCommitCallback(boost::bind(&LLFloaterModelWizard::loadModel, this));
+ getChild<LLUICtrl>("cancel")->setCommitCallback(boost::bind(&LLFloaterModelWizard::onClickCancel, this));
+ getChild<LLUICtrl>("back")->setCommitCallback(boost::bind(&LLFloaterModelWizard::onClickBack, this));
+ getChild<LLUICtrl>("next")->setCommitCallback(boost::bind(&LLFloaterModelWizard::onClickNext, this));
+ childSetCommitCallback("preview_lod_combo", onPreviewLODCommit, this);
+ getChild<LLUICtrl>("accuracy_slider")->setCommitCallback(boost::bind(&LLFloaterModelWizard::onAccuracyPerformance, this, _2));
+
+ childSetAction("ok_btn", onUpload, this);
+ LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
+
+ enable_registrar.add("Next.OnEnable", boost::bind(&LLFloaterModelWizard::onEnableNext, this));
+ enable_registrar.add("Back.OnEnable", boost::bind(&LLFloaterModelWizard::onEnableBack, this));
+
+
mPreviewRect = preview_panel->getRect();
mModelPreview = new LLModelPreview(512, 512, this);
@@ -64,9 +256,55 @@ BOOL LLFloaterModelWizard::postBuild()
center();
+ setState(CHOOSE_FILE);
+
+ childSetTextArg("import_dimensions", "[X]", LLStringUtil::null);
+ childSetTextArg("import_dimensions", "[Y]", LLStringUtil::null);
+ childSetTextArg("import_dimensions", "[Z]", LLStringUtil::null);
+
return TRUE;
}
+void LLFloaterModelWizard::onUpload(void* user_data)
+{
+ LLFloaterModelWizard* mp = (LLFloaterModelWizard*) user_data;
+
+ mp->mModelPreview->rebuildUploadData();
+
+ gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, mp->mModelPreview->mPreviewScale,
+ mp->childGetValue("upload_textures").asBoolean(), mp->childGetValue("upload_skin"), mp->childGetValue("upload_joints"));
+
+ mp->closeFloater(false);
+}
+
+
+void LLFloaterModelWizard::onAccuracyPerformance(const LLSD& data)
+{
+ int val = (int) data.asInteger();
+
+ mModelPreview->genLODs(-1, NUM_LOD-val);
+
+ mModelPreview->refresh();
+}
+
+void LLFloaterModelWizard::onPreviewLODCommit(LLUICtrl* ctrl, void* userdata)
+{
+ LLFloaterModelWizard *fp =(LLFloaterModelWizard *)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);
+}
+
void LLFloaterModelWizard::draw()
{
LLFloater::draw();
@@ -74,14 +312,14 @@ void LLFloaterModelWizard::draw()
mModelPreview->update();
- if (mModelPreview && mModelPreview->mModelLoader)
+ if (mModelPreview)
{
gGL.color3f(1.f, 1.f, 1.f);
gGL.getTexUnit(0)->bind(mModelPreview);
-
- LLView* preview_panel = getChild<LLView>("preview_panel");
+ LLView *view = getChild<LLView>(stateNames[mState]+"_panel");
+ LLView* preview_panel = view->getChild<LLView>("preview_panel");
LLRect rect = preview_panel->getRect();
if (rect != mPreviewRect)
diff --git a/indra/newview/llfloatermodelwizard.h b/indra/newview/llfloatermodelwizard.h
index c766697d47..b7fd28aa9d 100644
--- a/indra/newview/llfloatermodelwizard.h
+++ b/indra/newview/llfloatermodelwizard.h
@@ -35,24 +35,43 @@ public:
virtual ~LLFloaterModelWizard() {};
/*virtual*/ BOOL postBuild();
void draw();
- void loadModel();
- //void onSave();
- //void onReset();
- //void onCancel();
- ///*virtual*/ void onOpen(const LLSD& key);
+ 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);
+
private:
-
+ enum EWizardState
+ {
+ CHOOSE_FILE = 0,
+ OPTIMIZE,
+ PHYSICS,
+ REVIEW,
+ UPLOAD
+ };
+
+ void setState(int state);
+ void setButtons(int state);
+ void onClickCancel();
+ void onClickBack();
+ void onClickNext();
+ bool onEnableNext();
+ bool onEnableBack();
+ void loadModel();
+ static void onPreviewLODCommit(LLUICtrl*,void*);
+ void onAccuracyPerformance(const LLSD& data);
+ static void onUpload(void* data);
+
LLModelPreview* mModelPreview;
LLRect mPreviewRect;
+ int mState;
+
+ S32 mLastMouseX;
+ S32 mLastMouseY;
+
+
};
-/*
-namespace LLFloaterDisplayNameUtil
-{
- // Register with LLFloaterReg
- void registerFloater();
-}
-*/
#endif
diff --git a/indra/newview/llinspecttoast.cpp b/indra/newview/llinspecttoast.cpp
index 58b3f0309f..d7b82667d1 100644
--- a/indra/newview/llinspecttoast.cpp
+++ b/indra/newview/llinspecttoast.cpp
@@ -46,6 +46,7 @@ public:
virtual ~LLInspectToast();
/*virtual*/ void onOpen(const LLSD& notification_id);
+ /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask);
private:
void onToastDestroy(LLToast * toast);
@@ -73,6 +74,7 @@ LLInspectToast::~LLInspectToast()
LLTransientFloaterMgr::getInstance()->removeControlView(this);
}
+// virtual
void LLInspectToast::onOpen(const LLSD& notification_id)
{
LLInspect::onOpen(notification_id);
@@ -103,6 +105,15 @@ void LLInspectToast::onOpen(const LLSD& notification_id)
LLUI::positionViewNearMouse(this);
}
+// virtual
+BOOL LLInspectToast::handleToolTip(S32 x, S32 y, MASK mask)
+{
+ // We don't like the way LLInspect handles tooltips
+ // (black tooltips look weird),
+ // so force using the default implementation (STORM-511).
+ return LLFloater::handleToolTip(x, y, mask);
+}
+
void LLInspectToast::onToastDestroy(LLToast * toast)
{
closeFloater(false);
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 4873eaeabd..dd2dcffc28 100755
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -1396,6 +1396,7 @@ LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_mod
{
mStage = "single_hull";
mModel = mdl;
+ mDecompID = &mdl->mDecompID;
mBaseModel = base_model;
mThread = thread;
@@ -1486,7 +1487,7 @@ void LLMeshUploadThread::run()
{
textures.insert(material_iter->mDiffuseMap);
- LLTextureUploadData data(material_iter->mDiffuseMap, material_iter->mDiffuseMapLabel);
+ LLTextureUploadData data(material_iter->mDiffuseMap.get(), material_iter->mDiffuseMapLabel);
uploadTexture(data);
}
}
@@ -2135,6 +2136,24 @@ void LLMeshRepository::shutdown()
LLConvexDecomposition::quitSystem();
}
+//called in the main thread.
+S32 LLMeshRepository::update()
+{
+ if(mUploadWaitList.empty())
+ {
+ return 0 ;
+ }
+
+ S32 size = mUploadWaitList.size() ;
+ for (S32 i = 0; i < size; ++i)
+ {
+ mUploads.push_back(mUploadWaitList[i]);
+ mUploadWaitList[i]->start() ;
+ }
+ mUploadWaitList.clear() ;
+
+ return size ;
+}
S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail)
{
@@ -2262,6 +2281,9 @@ void LLMeshRepository::notifyLoadedMeshes()
mInventoryQ.pop();
}
}
+
+ //call completed callbacks on finished decompositions
+ mDecompThread->notifyCompleted();
if (!mThread->mWaiting)
{ //curl thread is churning, wait for it to go idle
@@ -2654,8 +2676,7 @@ void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3
bool upload_skin, bool upload_joints)
{
LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, upload_skin, upload_joints);
- mUploads.push_back(thread);
- thread->start();
+ mUploadWaitList.push_back(thread);
}
S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod)
@@ -2742,7 +2763,7 @@ void LLMeshUploadThread::sendCostRequest(LLMeshUploadData& data)
void LLMeshUploadThread::sendCostRequest(LLTextureUploadData& data)
{
- if (data.mTexture.notNull() && data.mTexture->getDiscardLevel() >= 0)
+ if (data.mTexture && data.mTexture->getDiscardLevel() >= 0)
{
LLSD asset_resources = LLSD::emptyMap();
@@ -3229,9 +3250,8 @@ void LLPhysicsDecomp::doDecomposition()
mCurRequest->mHullMesh.clear();
mCurRequest->setStatusMessage("FAIL");
- mCurRequest->completed();
-
- mCurRequest = NULL;
+
+ completeCurrent();
}
else
{
@@ -3282,13 +3302,33 @@ void LLPhysicsDecomp::doDecomposition()
LLMutexLock lock(mMutex);
mCurRequest->setStatusMessage("FAIL");
- mCurRequest->completed();
-
- mCurRequest = NULL;
+ completeCurrent();
+ }
+ }
+}
+
+void LLPhysicsDecomp::completeCurrent()
+{
+ LLMutexLock lock(mMutex);
+ mCompletedQ.push(mCurRequest);
+ mCurRequest = NULL;
+}
+
+void LLPhysicsDecomp::notifyCompleted()
+{
+ if (!mCompletedQ.empty())
+ {
+ LLMutexLock lock(mMutex);
+ while (!mCompletedQ.empty())
+ {
+ Request* req = mCompletedQ.front();
+ req->completed();
+ mCompletedQ.pop();
}
}
}
+
void make_box(LLPhysicsDecomp::Request * request)
{
LLVector3 min,max;
@@ -3406,18 +3446,17 @@ void LLPhysicsDecomp::doDecompositionSingleHull()
{
- LLMutexLock lock(mMutex);
- mCurRequest->completed();
- mCurRequest = NULL;
+ completeCurrent();
+
}
}
+
void LLPhysicsDecomp::run()
{
- LLConvexDecomposition::getInstance()->initThread();
- mInited = true;
-
LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance();
+ decomp->initThread();
+ mInited = true;
static const LLCDStageData* stages = NULL;
static S32 num_stages = 0;
@@ -3443,6 +3482,13 @@ void LLPhysicsDecomp::run()
mRequestQ.pop();
}
+ S32& id = *(mCurRequest->mDecompID);
+ if (id == -1)
+ {
+ decomp->genDecomposition(id);
+ }
+ decomp->bindDecomposition(id);
+
if (mCurRequest->mStage == "single_hull")
{
doDecompositionSingleHull();
@@ -3454,7 +3500,7 @@ void LLPhysicsDecomp::run()
}
}
- LLConvexDecomposition::getInstance()->quitThread();
+ decomp->quitThread();
//delete mSignal;
delete mMutex;
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 8687ac750b..8c9892b28f 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -65,7 +65,7 @@ public:
class LLTextureUploadData
{
public:
- LLPointer<LLViewerFetchedTexture> mTexture;
+ LLViewerFetchedTexture* mTexture;
LLUUID mUUID;
std::string mRSVP;
std::string mLabel;
@@ -157,6 +157,7 @@ public:
{
public:
//input params
+ S32* mDecompID;
std::string mStage;
std::vector<LLVector3> mPositions;
std::vector<U16> mIndices;
@@ -167,8 +168,12 @@ public:
std::vector<LLPointer<LLVertexBuffer> > mHullMesh;
LLModel::convex_hull_decomposition mHull;
+ //status message callback, called from decomposition thread
virtual S32 statusCallback(const char* status, S32 p1, S32 p2) = 0;
+
+ //completed callback, called from the main thread
virtual void completed() = 0;
+
virtual void setStatusMessage(const std::string& msg);
};
@@ -193,6 +198,9 @@ public:
void doDecompositionSingleHull();
virtual void run();
+
+ void completeCurrent();
+ void notifyCompleted();
std::map<std::string, S32> mStageID;
@@ -201,6 +209,8 @@ public:
LLPointer<Request> mCurRequest;
+ std::queue<LLPointer<Request> > mCompletedQ;
+
};
class LLMeshRepoThread : public LLThread
@@ -399,7 +409,7 @@ public:
std::queue<LLTextureUploadData> mTextureQ;
std::queue<LLTextureUploadData> mConfirmedTextureQ;
- std::map<LLPointer<LLViewerFetchedTexture>, LLTextureUploadData> mTextureMap;
+ std::map<LLViewerFetchedTexture*, LLTextureUploadData> mTextureMap;
LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures,
bool upload_skin, bool upload_joints);
@@ -442,6 +452,7 @@ public:
void init();
void shutdown();
+ S32 update() ;
//mesh management functions
S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0);
@@ -505,6 +516,7 @@ public:
LLMeshRepoThread* mThread;
std::vector<LLMeshUploadThread*> mUploads;
+ std::vector<LLMeshUploadThread*> mUploadWaitList;
LLPhysicsDecomp* mDecompThread;
diff --git a/indra/newview/llpanelavatar.cpp b/indra/newview/llpanelavatar.cpp
index 1249d5d856..a9bcdef47c 100644
--- a/indra/newview/llpanelavatar.cpp
+++ b/indra/newview/llpanelavatar.cpp
@@ -341,10 +341,11 @@ LLPanelAvatarNotes::~LLPanelAvatarNotes()
if(getAvatarId().notNull())
{
LLAvatarTracker::instance().removeParticularFriendObserver(getAvatarId(), this);
- if(LLVoiceClient::instanceExists())
- {
- LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this);
- }
+ }
+
+ if(LLVoiceClient::instanceExists())
+ {
+ LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this);
}
}
@@ -758,10 +759,11 @@ LLPanelAvatarProfile::~LLPanelAvatarProfile()
if(getAvatarId().notNull())
{
LLAvatarTracker::instance().removeParticularFriendObserver(getAvatarId(), this);
- if(LLVoiceClient::instanceExists())
- {
- LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this);
- }
+ }
+
+ if(LLVoiceClient::instanceExists())
+ {
+ LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this);
}
}
diff --git a/indra/newview/llremoteparcelrequest.cpp b/indra/newview/llremoteparcelrequest.cpp
index 0dff087553..e5ef51bdd1 100644
--- a/indra/newview/llremoteparcelrequest.cpp
+++ b/indra/newview/llremoteparcelrequest.cpp
@@ -140,22 +140,25 @@ void LLRemoteParcelInfoProcessor::processParcelInfoReply(LLMessageSystem* msg, v
typedef std::vector<observer_multimap_t::iterator> deadlist_t;
deadlist_t dead_iters;
- observer_multimap_t::iterator oi;
- observer_multimap_t::iterator start = observers.lower_bound(parcel_data.parcel_id);
+ observer_multimap_t::iterator oi = observers.lower_bound(parcel_data.parcel_id);
observer_multimap_t::iterator end = observers.upper_bound(parcel_data.parcel_id);
- for (oi = start; oi != end; ++oi)
+ while (oi != end)
{
- LLRemoteParcelInfoObserver * observer = oi->second.get();
+ // increment the loop iterator now since it may become invalid below
+ observer_multimap_t::iterator cur_oi = oi++;
+
+ LLRemoteParcelInfoObserver * observer = cur_oi->second.get();
if(observer)
{
+ // may invalidate cur_oi if the observer removes itself
observer->processParcelInfo(parcel_data);
}
else
{
// the handle points to an expired observer, so don't keep it
// around anymore
- dead_iters.push_back(oi);
+ dead_iters.push_back(cur_oi);
}
}
diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp
index be797ea937..c8c6858b81 100644
--- a/indra/newview/llsidepaneliteminfo.cpp
+++ b/indra/newview/llsidepaneliteminfo.cpp
@@ -71,12 +71,12 @@ void LLItemPropertiesObserver::changed(U32 mask)
const std::set<LLUUID>& mChangedItemIDs = gInventory.getChangedIDs();
std::set<LLUUID>::const_iterator it;
- const LLUUID& object_id = mFloater->getObjectID();
+ const LLUUID& item_id = mFloater->getItemID();
for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++)
{
// set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288)
- if (*it == object_id)
+ if (*it == item_id)
{
// if there's a change we're interested in.
if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0)
@@ -196,6 +196,11 @@ const LLUUID& LLSidepanelItemInfo::getObjectID() const
return mObjectID;
}
+const LLUUID& LLSidepanelItemInfo::getItemID() const
+{
+ return mItemID;
+}
+
void LLSidepanelItemInfo::reset()
{
LLSidepanelInventorySubpanel::reset();
diff --git a/indra/newview/llsidepaneliteminfo.h b/indra/newview/llsidepaneliteminfo.h
index 6416e2cfe4..25be145f64 100644
--- a/indra/newview/llsidepaneliteminfo.h
+++ b/indra/newview/llsidepaneliteminfo.h
@@ -55,6 +55,7 @@ public:
void setEditMode(BOOL edit);
const LLUUID& getObjectID() const;
+ const LLUUID& getItemID() const;
protected:
/*virtual*/ void refresh();
diff --git a/indra/newview/llsimplestat.h b/indra/newview/llsimplestat.h
new file mode 100644
index 0000000000..a90e503adb
--- /dev/null
+++ b/indra/newview/llsimplestat.h
@@ -0,0 +1,158 @@
+/**
+ * @file llsimplestat.h
+ * @brief Runtime statistics accumulation.
+ *
+ * $LicenseInfo:firstyear=2010&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_SIMPLESTAT_H
+#define LL_SIMPLESTAT_H
+
+// History
+//
+// The original source for this code is the server repositories'
+// llcommon/llstat.h file. This particular code was added after the
+// viewer/server code schism but before the effort to convert common
+// code to libraries was complete. Rather than add to merge issues,
+// the needed code was cut'n'pasted into this new header as it isn't
+// too awful a burden. Post-modularization, we can look at removing
+// this redundancy.
+
+
+/**
+ * @class LLSimpleStatCounter
+ * @brief Just counts events.
+ *
+ * Really not needed but have a pattern in mind in the future.
+ * Interface limits what can be done at that's just fine.
+ *
+ * *TODO: Update/transfer unit tests
+ * Unit tests: indra/test/llcommon_llstat_tut.cpp
+ */
+class LLSimpleStatCounter
+{
+public:
+ inline LLSimpleStatCounter() { reset(); }
+ // Default destructor and assignment operator are valid
+
+ inline void reset() { mCount = 0; }
+
+ inline void merge(const LLSimpleStatCounter & src)
+ { mCount += src.mCount; }
+
+ inline U32 operator++() { return ++mCount; }
+
+ inline U32 getCount() const { return mCount; }
+
+protected:
+ U32 mCount;
+};
+
+
+/**
+ * @class LLSimpleStatMMM
+ * @brief Templated collector of min, max and mean data for stats.
+ *
+ * Fed a stream of data samples, keeps a running account of the
+ * min, max and mean seen since construction or the last reset()
+ * call. A freshly-constructed or reset instance returns counts
+ * and values of zero.
+ *
+ * Overflows and underflows (integer, inf or -inf) and NaN's
+ * are the caller's problem. As is loss of precision when
+ * the running sum's exponent (when parameterized by a floating
+ * point of some type) differs from a given data sample's.
+ *
+ * Unit tests: indra/test/llcommon_llstat_tut.cpp
+ */
+template <typename VALUE_T = F32>
+class LLSimpleStatMMM
+{
+public:
+ typedef VALUE_T Value;
+
+public:
+ LLSimpleStatMMM() { reset(); }
+ // Default destructor and assignment operator are valid
+
+ /**
+ * Resets the object returning all counts and derived
+ * values back to zero.
+ */
+ void reset()
+ {
+ mCount = 0;
+ mMin = Value(0);
+ mMax = Value(0);
+ mTotal = Value(0);
+ }
+
+ void record(Value v)
+ {
+ if (mCount)
+ {
+ mMin = llmin(mMin, v);
+ mMax = llmax(mMax, v);
+ }
+ else
+ {
+ mMin = v;
+ mMax = v;
+ }
+ mTotal += v;
+ ++mCount;
+ }
+
+ void merge(const LLSimpleStatMMM<VALUE_T> & src)
+ {
+ if (! mCount)
+ {
+ *this = src;
+ }
+ else if (src.mCount)
+ {
+ mMin = llmin(mMin, src.mMin);
+ mMax = llmax(mMax, src.mMax);
+ mCount += src.mCount;
+ mTotal += src.mTotal;
+ }
+ }
+
+ inline U32 getCount() const { return mCount; }
+ inline Value getMin() const { return mMin; }
+ inline Value getMax() const { return mMax; }
+ inline Value getMean() const { return mCount ? mTotal / mCount : mTotal; }
+
+protected:
+ U32 mCount;
+ Value mMin;
+ Value mMax;
+ Value mTotal;
+};
+
+#endif // LL_SIMPLESTAT_H
diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp
index 13fd51f473..4f63abb152 100644
--- a/indra/newview/lltexturefetch.cpp
+++ b/indra/newview/lltexturefetch.cpp
@@ -27,6 +27,7 @@
#include "llviewerprecompiledheaders.h"
#include <iostream>
+#include <map>
#include "llstl.h"
@@ -49,6 +50,7 @@
#include "llviewertexture.h"
#include "llviewerregion.h"
#include "llviewerstats.h"
+#include "llviewerassetstats.h"
#include "llworld.h"
//////////////////////////////////////////////////////////////////////////////
@@ -143,7 +145,7 @@ public:
/*virtual*/ bool deleteOK(); // called from update() (WORK THREAD)
~LLTextureFetchWorker();
- void relese() { --mActiveCount; }
+ // void relese() { --mActiveCount; }
S32 callbackHttpGet(const LLChannelDescriptors& channels,
const LLIOPipe::buffer_ptr_t& buffer,
@@ -161,9 +163,11 @@ public:
mGetReason = reason;
}
- void setCanUseHTTP(bool can_use_http) {mCanUseHTTP = can_use_http;}
- bool getCanUseHTTP()const {return mCanUseHTTP ;}
+ void setCanUseHTTP(bool can_use_http) { mCanUseHTTP = can_use_http; }
+ bool getCanUseHTTP() const { return mCanUseHTTP; }
+ LLTextureFetch & getFetcher() { return *mFetcher; }
+
protected:
LLTextureFetchWorker(LLTextureFetch* fetcher, const std::string& url, const LLUUID& id, const LLHost& host,
F32 priority, S32 discard, S32 size);
@@ -277,6 +281,8 @@ private:
S32 mLastPacket;
U16 mTotalPackets;
U8 mImageCodec;
+
+ LLViewerAssetStats::duration_t mMetricsStartTime;
};
//////////////////////////////////////////////////////////////////////////////
@@ -344,6 +350,18 @@ public:
}
mFetcher->removeFromHTTPQueue(mID, data_size);
+
+ if (worker->mMetricsStartTime)
+ {
+ LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE,
+ true,
+ LLImageBase::TYPE_AVATAR_BAKE == worker->mType,
+ LLViewerAssetStatsFF::get_timestamp() - worker->mMetricsStartTime);
+ worker->mMetricsStartTime = 0;
+ }
+ LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE,
+ true,
+ LLImageBase::TYPE_AVATAR_BAKE == worker->mType);
}
else
{
@@ -368,6 +386,229 @@ private:
//////////////////////////////////////////////////////////////////////////////
+// Cross-thread messaging for asset metrics.
+
+/**
+ * @brief Base class for cross-thread requests made of the fetcher
+ *
+ * I believe the intent of the LLQueuedThread class was to
+ * have these operations derived from LLQueuedThread::QueuedRequest
+ * but the texture fetcher has elected to manage the queue
+ * in its own manner. So these are free-standing objects which are
+ * managed in simple FIFO order on the mCommands queue of the
+ * LLTextureFetch object.
+ *
+ * What each represents is a simple command sent from an
+ * outside thread into the TextureFetch thread to be processed
+ * in order and in a timely fashion (though not an absolute
+ * higher priority than other operations of the thread).
+ * Each operation derives a new class from the base customizing
+ * members, constructors and the doWork() method to effect
+ * the command.
+ *
+ * The flow is one-directional. There are two global instances
+ * of the LLViewerAssetStats collector, one for the main program's
+ * thread pointed to by gViewerAssetStatsMain and one for the
+ * TextureFetch thread pointed to by gViewerAssetStatsThread1.
+ * Common operations has each thread recording metrics events
+ * into the respective collector unconcerned with locking and
+ * the state of any other thread. But when the agent moves into
+ * a different region or the metrics timer expires and a report
+ * needs to be sent back to the grid, messaging across threads
+ * is required to distribute data and perform global actions.
+ * In pseudo-UML, it looks like:
+ *
+ * Main Thread1
+ * . .
+ * . .
+ * +-----+ .
+ * | AM | .
+ * +--+--+ .
+ * +-------+ | .
+ * | Main | +--+--+ .
+ * | | | SRE |---. .
+ * | Stats | +-----+ \ .
+ * | | | \ (uuid) +-----+
+ * | Coll. | +--+--+ `-------->| SR |
+ * +-------+ | MSC | +--+--+
+ * | ^ +-----+ |
+ * | | (uuid) / . +-----+ (uuid)
+ * | `--------' . | MSC |---------.
+ * | . +-----+ |
+ * | +-----+ . v
+ * | | TE | . +-------+
+ * | +--+--+ . | Thd1 |
+ * | | . | |
+ * | +-----+ . | Stats |
+ * `--------->| RSC | . | |
+ * +--+--+ . | Coll. |
+ * | . +-------+
+ * +--+--+ . |
+ * | SME |---. . |
+ * +-----+ \ . |
+ * . \ (clone) +-----+ |
+ * . `-------->| SM | |
+ * . +--+--+ |
+ * . | |
+ * . +-----+ |
+ * . | RSC |<--------'
+ * . +-----+
+ * . |
+ * . +-----+
+ * . | CP |--> HTTP POST
+ * . +-----+
+ * . .
+ * . .
+ *
+ *
+ * Key:
+ *
+ * SRE - Set Region Enqueued. Enqueue a 'Set Region' command in
+ * the other thread providing the new UUID of the region.
+ * TFReqSetRegion carries the data.
+ * SR - Set Region. New region UUID is sent to the thread-local
+ * collector.
+ * SME - Send Metrics Enqueued. Enqueue a 'Send Metrics' command
+ * including an ownership transfer of a cloned LLViewerAssetStats.
+ * TFReqSendMetrics carries the data.
+ * SM - Send Metrics. Global metrics reporting operation. Takes
+ * the cloned stats from the command, merges it with the
+ * thread's local stats, converts to LLSD and sends it on
+ * to the grid.
+ * AM - Agent Moved. Agent has completed some sort of move to a
+ * new region.
+ * TE - Timer Expired. Metrics timer has expired (on the order
+ * of 10 minutes).
+ * CP - CURL Post
+ * MSC - Modify Stats Collector. State change in the thread-local
+ * collector. Typically a region change which affects the
+ * global pointers used to find the 'current stats'.
+ * RSC - Read Stats Collector. Extract collector data cloning it
+ * (i.e. deep copy) when necessary.
+ *
+ */
+class LLTextureFetch::TFRequest // : public LLQueuedThread::QueuedRequest
+{
+public:
+ // Default ctors and assignment operator are correct.
+
+ virtual ~TFRequest()
+ {}
+
+ // Patterned after QueuedRequest's method but expected behavior
+ // is different. Always expected to complete on the first call
+ // and work dispatcher will assume the same and delete the
+ // request after invocation.
+ virtual bool doWork(LLTextureFetch * fetcher) = 0;
+};
+
+namespace
+{
+
+/**
+ * @brief Implements a 'Set Region' cross-thread command.
+ *
+ * When an agent moves to a new region, subsequent metrics need
+ * to be binned into a new or existing stats collection in 1:1
+ * relationship with the region. We communicate this region
+ * change across the threads involved in the communication with
+ * this message.
+ *
+ * Corresponds to LLTextureFetch::commandSetRegion()
+ */
+class TFReqSetRegion : public LLTextureFetch::TFRequest
+{
+public:
+ TFReqSetRegion(U64 region_handle)
+ : LLTextureFetch::TFRequest(),
+ mRegionHandle(region_handle)
+ {}
+ TFReqSetRegion & operator=(const TFReqSetRegion &); // Not defined
+
+ virtual ~TFReqSetRegion()
+ {}
+
+ virtual bool doWork(LLTextureFetch * fetcher);
+
+public:
+ const U64 mRegionHandle;
+};
+
+
+/**
+ * @brief Implements a 'Send Metrics' cross-thread command.
+ *
+ * This is the big operation. The main thread gathers metrics
+ * for a period of minutes into LLViewerAssetStats and other
+ * objects then makes a snapshot of the data by cloning the
+ * collector. This command transfers the clone, along with a few
+ * additional arguments (UUIDs), handing ownership to the
+ * TextureFetch thread. It then merges its own data into the
+ * cloned copy, converts to LLSD and kicks off an HTTP POST of
+ * the resulting data to the currently active metrics collector.
+ *
+ * Corresponds to LLTextureFetch::commandSendMetrics()
+ */
+class TFReqSendMetrics : public LLTextureFetch::TFRequest
+{
+public:
+ /**
+ * Construct the 'Send Metrics' command to have the TextureFetch
+ * thread add and log metrics data.
+ *
+ * @param caps_url URL of a "ViewerMetrics" Caps target
+ * to receive the data. Does not have to
+ * be associated with a particular region.
+ *
+ * @param session_id UUID of the agent's session.
+ *
+ * @param agent_id UUID of the agent. (Being pure here...)
+ *
+ * @param main_stats Pointer to a clone of the main thread's
+ * LLViewerAssetStats data. Thread1 takes
+ * ownership of the copy and disposes of it
+ * when done.
+ */
+ TFReqSendMetrics(const std::string & caps_url,
+ const LLUUID & session_id,
+ const LLUUID & agent_id,
+ LLViewerAssetStats * main_stats)
+ : LLTextureFetch::TFRequest(),
+ mCapsURL(caps_url),
+ mSessionID(session_id),
+ mAgentID(agent_id),
+ mMainStats(main_stats)
+ {}
+ TFReqSendMetrics & operator=(const TFReqSendMetrics &); // Not defined
+
+ virtual ~TFReqSendMetrics();
+
+ virtual bool doWork(LLTextureFetch * fetcher);
+
+public:
+ const std::string mCapsURL;
+ const LLUUID mSessionID;
+ const LLUUID mAgentID;
+ LLViewerAssetStats * mMainStats;
+};
+
+/*
+ * Examines the merged viewer metrics report and if found to be too long,
+ * will attempt to truncate it in some reasonable fashion.
+ *
+ * @param max_regions Limit of regions allowed in report.
+ *
+ * @param metrics Full, merged viewer metrics report.
+ *
+ * @returns If data was truncated, returns true.
+ */
+bool truncate_viewer_metrics(int max_regions, LLSD & metrics);
+
+} // end of anonymous namespace
+
+
+//////////////////////////////////////////////////////////////////////////////
+
//static
const char* LLTextureFetchWorker::sStateDescs[] = {
"INVALID",
@@ -385,6 +626,9 @@ const char* LLTextureFetchWorker::sStateDescs[] = {
"DONE",
};
+// static
+volatile bool LLTextureFetch::svMetricsDataBreak(true); // Start with a data break
+
// called from MAIN THREAD
LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
@@ -434,7 +678,8 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher,
mFirstPacket(0),
mLastPacket(-1),
mTotalPackets(0),
- mImageCodec(IMG_CODEC_INVALID)
+ mImageCodec(IMG_CODEC_INVALID),
+ mMetricsStartTime(0)
{
mCanUseNET = mUrl.empty() ;
@@ -602,6 +847,7 @@ bool LLTextureFetchWorker::doWork(S32 param)
return true; // abort
}
}
+
if(mImagePriority < F_ALMOST_ZERO)
{
if (mState == INIT || mState == LOAD_FROM_NETWORK || mState == LOAD_FROM_SIMULATOR)
@@ -811,7 +1057,15 @@ bool LLTextureFetchWorker::doWork(S32 param)
mRequestedDiscard = mDesiredDiscard;
mSentRequest = QUEUED;
mFetcher->addToNetworkQueue(this);
+ if (! mMetricsStartTime)
+ {
+ mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ }
+ LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
+ false,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
+
return false;
}
else
@@ -820,6 +1074,12 @@ bool LLTextureFetchWorker::doWork(S32 param)
//llassert_always(mFetcher->mNetworkQueue.find(mID) != mFetcher->mNetworkQueue.end());
// Make certain this is in the network queue
//mFetcher->addToNetworkQueue(this);
+ //if (! mMetricsStartTime)
+ //{
+ // mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ //}
+ //LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE, false,
+ // LLImageBase::TYPE_AVATAR_BAKE == mType);
//setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
return false;
}
@@ -843,11 +1103,30 @@ bool LLTextureFetchWorker::doWork(S32 param)
}
setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority);
mState = DECODE_IMAGE;
- mWriteToCacheState = SHOULD_WRITE ;
+ mWriteToCacheState = SHOULD_WRITE;
+
+ if (mMetricsStartTime)
+ {
+ LLViewerAssetStatsFF::record_response_thread1(LLViewerAssetType::AT_TEXTURE,
+ false,
+ LLImageBase::TYPE_AVATAR_BAKE == mType,
+ LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime);
+ mMetricsStartTime = 0;
+ }
+ LLViewerAssetStatsFF::record_dequeue_thread1(LLViewerAssetType::AT_TEXTURE,
+ false,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
}
else
{
mFetcher->addToNetworkQueue(this); // failsafe
+ if (! mMetricsStartTime)
+ {
+ mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ }
+ LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
+ false,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority);
}
return false;
@@ -909,6 +1188,14 @@ bool LLTextureFetchWorker::doWork(S32 param)
mState = WAIT_HTTP_REQ;
mFetcher->addToHTTPQueue(mID);
+ if (! mMetricsStartTime)
+ {
+ mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ }
+ LLViewerAssetStatsFF::record_enqueue_thread1(LLViewerAssetType::AT_TEXTURE,
+ true,
+ LLImageBase::TYPE_AVATAR_BAKE == mType);
+
// Will call callbackHttpGet when curl request completes
std::vector<std::string> headers;
headers.push_back("Accept: image/x-j2c");
@@ -1523,7 +1810,7 @@ bool LLTextureFetchWorker::writeToCacheComplete()
//////////////////////////////////////////////////////////////////////////////
// public
-LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded)
+LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode)
: LLWorkerThread("TextureFetch", threaded),
mDebugCount(0),
mDebugPause(FALSE),
@@ -1535,8 +1822,10 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image
mImageDecodeThread(imagedecodethread),
mTextureBandwidth(0),
mHTTPTextureBits(0),
- mCurlGetRequest(NULL)
+ mCurlGetRequest(NULL),
+ mQAMode(qa_mode)
{
+ mCurlPOSTRequestCount = 0;
mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS");
mTextureInfo.setUpLogging(gSavedSettings.getBOOL("LogTextureDownloadsToViewerLog"), gSavedSettings.getBOOL("LogTextureDownloadsToSimulator"), gSavedSettings.getU32("TextureLoggingThreshold"));
}
@@ -1545,6 +1834,13 @@ LLTextureFetch::~LLTextureFetch()
{
clearDeleteList() ;
+ while (! mCommands.empty())
+ {
+ TFRequest * req(mCommands.front());
+ mCommands.erase(mCommands.begin());
+ delete req;
+ }
+
// ~LLQueuedThread() called here
}
@@ -1825,8 +2121,76 @@ bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority)
return res;
}
+// Replicates and expands upon the base class's
+// getPending() implementation. getPending() and
+// runCondition() replicate one another's logic to
+// an extent and are sometimes used for the same
+// function (deciding whether or not to sleep/pause
+// a thread). So the implementations need to stay
+// in step, at least until this can be refactored and
+// the redundancy eliminated.
+//
+// May be called from any thread
+
+//virtual
+S32 LLTextureFetch::getPending()
+{
+ S32 res;
+ lockData();
+ {
+ LLMutexLock lock(&mQueueMutex);
+
+ res = mRequestQueue.size();
+ res += mCurlPOSTRequestCount;
+ res += mCommands.size();
+ }
+ unlockData();
+ return res;
+}
+
+// virtual
+bool LLTextureFetch::runCondition()
+{
+ // Caller is holding the lock on LLThread's condition variable.
+
+ // LLQueuedThread, unlike its base class LLThread, makes this a
+ // private method which is unfortunate. I want to use it directly
+ // but I'm going to have to re-implement the logic here (or change
+ // declarations, which I don't want to do right now).
+ //
+ // Changes here may need to be reflected in getPending().
+
+ bool have_no_commands(false);
+ {
+ LLMutexLock lock(&mQueueMutex);
+
+ have_no_commands = mCommands.empty();
+ }
+
+ bool have_no_curl_requests(0 == mCurlPOSTRequestCount);
+
+ return ! (have_no_commands
+ && have_no_curl_requests
+ && (mRequestQueue.empty() && mIdleThread)); // From base class
+}
+
//////////////////////////////////////////////////////////////////////////////
+// MAIN THREAD (unthreaded envs), WORKER THREAD (threaded envs)
+void LLTextureFetch::commonUpdate()
+{
+ // Run a cross-thread command, if any.
+ cmdDoWork();
+
+ // Update Curl on same thread as mCurlGetRequest was constructed
+ S32 processed = mCurlGetRequest->process();
+ if (processed > 0)
+ {
+ lldebugs << "processed: " << processed << " messages." << llendl;
+ }
+}
+
+
// MAIN THREAD
//virtual
S32 LLTextureFetch::update(U32 max_time_ms)
@@ -1852,12 +2216,7 @@ S32 LLTextureFetch::update(U32 max_time_ms)
if (!mThreaded)
{
- // Update Curl on same thread as mCurlGetRequest was constructed
- S32 processed = mCurlGetRequest->process();
- if (processed > 0)
- {
- lldebugs << "processed: " << processed << " messages." << llendl;
- }
+ commonUpdate();
}
return res;
@@ -1912,12 +2271,7 @@ void LLTextureFetch::threadedUpdate()
}
process_timer.reset();
- // Update Curl on same thread as mCurlGetRequest was constructed
- S32 processed = mCurlGetRequest->process();
- if (processed > 0)
- {
- lldebugs << "processed: " << processed << " messages." << llendl;
- }
+ commonUpdate();
#if 0
const F32 INFO_TIME = 1.0f;
@@ -2367,3 +2721,280 @@ void LLTextureFetch::dump()
}
}
+//////////////////////////////////////////////////////////////////////////////
+
+// cross-thread command methods
+
+void LLTextureFetch::commandSetRegion(U64 region_handle)
+{
+ TFReqSetRegion * req = new TFReqSetRegion(region_handle);
+
+ cmdEnqueue(req);
+}
+
+void LLTextureFetch::commandSendMetrics(const std::string & caps_url,
+ const LLUUID & session_id,
+ const LLUUID & agent_id,
+ LLViewerAssetStats * main_stats)
+{
+ TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, main_stats);
+
+ cmdEnqueue(req);
+}
+
+void LLTextureFetch::commandDataBreak()
+{
+ // The pedantically correct way to implement this is to create a command
+ // request object in the above fashion and enqueue it. However, this is
+ // simple data of an advisorial not operational nature and this case
+ // of shared-write access is tolerable.
+
+ LLTextureFetch::svMetricsDataBreak = true;
+}
+
+void LLTextureFetch::cmdEnqueue(TFRequest * req)
+{
+ lockQueue();
+ mCommands.push_back(req);
+ unlockQueue();
+
+ unpause();
+}
+
+LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue()
+{
+ TFRequest * ret = 0;
+
+ lockQueue();
+ if (! mCommands.empty())
+ {
+ ret = mCommands.front();
+ mCommands.erase(mCommands.begin());
+ }
+ unlockQueue();
+
+ return ret;
+}
+
+void LLTextureFetch::cmdDoWork()
+{
+ if (mDebugPause)
+ {
+ return; // debug: don't do any work
+ }
+
+ TFRequest * req = cmdDequeue();
+ if (req)
+ {
+ // One request per pass should really be enough for this.
+ req->doWork(this);
+ delete req;
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+// Private (anonymous) class methods implementing the command scheme.
+
+namespace
+{
+
+/**
+ * Implements the 'Set Region' command.
+ *
+ * Thread: Thread1 (TextureFetch)
+ */
+bool
+TFReqSetRegion::doWork(LLTextureFetch *)
+{
+ LLViewerAssetStatsFF::set_region_thread1(mRegionHandle);
+
+ return true;
+}
+
+
+TFReqSendMetrics::~TFReqSendMetrics()
+{
+ delete mMainStats;
+ mMainStats = 0;
+}
+
+
+/**
+ * Implements the 'Send Metrics' command. Takes over
+ * ownership of the passed LLViewerAssetStats pointer.
+ *
+ * Thread: Thread1 (TextureFetch)
+ */
+bool
+TFReqSendMetrics::doWork(LLTextureFetch * fetcher)
+{
+ /*
+ * HTTP POST responder. Doesn't do much but tries to
+ * detect simple breaks in recording the metrics stream.
+ *
+ * The 'volatile' modifiers don't indicate signals,
+ * mmap'd memory or threads, really. They indicate that
+ * the referenced data is part of a pseudo-closure for
+ * this responder rather than being required for correct
+ * operation.
+ *
+ * We don't try very hard with the POST request. We give
+ * it one shot and that's more-or-less it. With a proper
+ * refactoring of the LLQueuedThread usage, these POSTs
+ * could be put in a request object and made more reliable.
+ */
+ class lcl_responder : public LLCurl::Responder
+ {
+ public:
+ lcl_responder(LLTextureFetch * fetcher,
+ S32 expected_sequence,
+ volatile const S32 & live_sequence,
+ volatile bool & reporting_break,
+ volatile bool & reporting_started)
+ : LLCurl::Responder(),
+ mFetcher(fetcher),
+ mExpectedSequence(expected_sequence),
+ mLiveSequence(live_sequence),
+ mReportingBreak(reporting_break),
+ mReportingStarted(reporting_started)
+ {
+ mFetcher->incrCurlPOSTCount();
+ }
+
+ ~lcl_responder()
+ {
+ mFetcher->decrCurlPOSTCount();
+ }
+
+ // virtual
+ void error(U32 status_num, const std::string & reason)
+ {
+ if (mLiveSequence == mExpectedSequence)
+ {
+ mReportingBreak = true;
+ }
+ LL_WARNS("Texture") << "Break in metrics stream due to POST failure to metrics collection service. Reason: "
+ << reason << LL_ENDL;
+ }
+
+ // virtual
+ void result(const LLSD & content)
+ {
+ if (mLiveSequence == mExpectedSequence)
+ {
+ mReportingBreak = false;
+ mReportingStarted = true;
+ }
+ }
+
+ private:
+ LLTextureFetch * mFetcher;
+ S32 mExpectedSequence;
+ volatile const S32 & mLiveSequence;
+ volatile bool & mReportingBreak;
+ volatile bool & mReportingStarted;
+
+ }; // class lcl_responder
+
+ if (! gViewerAssetStatsThread1)
+ return true;
+
+ static volatile bool reporting_started(false);
+ static volatile S32 report_sequence(0);
+
+ // We've taken over ownership of the stats copy at this
+ // point. Get a working reference to it for merging here
+ // but leave it in 'this'. Destructor will rid us of it.
+ LLViewerAssetStats & main_stats = *mMainStats;
+
+ // Merge existing stats into those from main, convert to LLSD
+ main_stats.merge(*gViewerAssetStatsThread1);
+ LLSD merged_llsd = main_stats.asLLSD(true);
+
+ // Add some additional meta fields to the content
+ merged_llsd["session_id"] = mSessionID;
+ merged_llsd["agent_id"] = mAgentID;
+ merged_llsd["message"] = "ViewerAssetMetrics"; // Identifies the type of metrics
+ merged_llsd["sequence"] = report_sequence; // Sequence number
+ merged_llsd["initial"] = ! reporting_started; // Initial data from viewer
+ merged_llsd["break"] = LLTextureFetch::svMetricsDataBreak; // Break in data prior to this report
+
+ // Update sequence number
+ if (S32_MAX == ++report_sequence)
+ report_sequence = 0;
+
+ // Limit the size of the stats report if necessary.
+ merged_llsd["truncated"] = truncate_viewer_metrics(10, merged_llsd);
+
+ if (! mCapsURL.empty())
+ {
+ LLCurlRequest::headers_t headers;
+ fetcher->getCurlRequest().post(mCapsURL,
+ headers,
+ merged_llsd,
+ new lcl_responder(fetcher,
+ report_sequence,
+ report_sequence,
+ LLTextureFetch::svMetricsDataBreak,
+ reporting_started));
+ }
+ else
+ {
+ LLTextureFetch::svMetricsDataBreak = true;
+ }
+
+ // In QA mode, Metrics submode, log the result for ease of testing
+ if (fetcher->isQAMode())
+ {
+ LL_INFOS("Textures") << merged_llsd << LL_ENDL;
+ }
+
+ gViewerAssetStatsThread1->reset();
+
+ return true;
+}
+
+
+bool
+truncate_viewer_metrics(int max_regions, LLSD & metrics)
+{
+ static const LLSD::String reg_tag("regions");
+ static const LLSD::String duration_tag("duration");
+
+ LLSD & reg_map(metrics[reg_tag]);
+ if (reg_map.size() <= max_regions)
+ {
+ return false;
+ }
+
+ // Build map of region hashes ordered by duration
+ typedef std::multimap<LLSD::Real, int> reg_ordered_list_t;
+ reg_ordered_list_t regions_by_duration;
+
+ int ind(0);
+ LLSD::array_const_iterator it_end(reg_map.endArray());
+ for (LLSD::array_const_iterator it(reg_map.beginArray()); it_end != it; ++it, ++ind)
+ {
+ LLSD::Real duration = (*it)[duration_tag].asReal();
+ regions_by_duration.insert(reg_ordered_list_t::value_type(duration, ind));
+ }
+
+ // Build a replacement regions array with the longest-persistence regions
+ LLSD new_region(LLSD::emptyArray());
+ reg_ordered_list_t::const_reverse_iterator it2_end(regions_by_duration.rend());
+ reg_ordered_list_t::const_reverse_iterator it2(regions_by_duration.rbegin());
+ for (int i(0); i < max_regions && it2_end != it2; ++i, ++it2)
+ {
+ new_region.append(reg_map[it2->second]);
+ }
+ reg_map = new_region;
+
+ return true;
+}
+
+} // end of anonymous namespace
+
+
+
diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h
index 796109df06..ad00a7ea36 100644
--- a/indra/newview/lltexturefetch.h
+++ b/indra/newview/lltexturefetch.h
@@ -33,6 +33,7 @@
#include "llworkerthread.h"
#include "llcurl.h"
#include "lltextureinfo.h"
+#include "llapr.h"
class LLViewerTexture;
class LLTextureFetchWorker;
@@ -40,6 +41,7 @@ class HTTPGetResponder;
class LLTextureCache;
class LLImageDecodeThread;
class LLHost;
+class LLViewerAssetStats;
// Interface class
class LLTextureFetch : public LLWorkerThread
@@ -48,9 +50,11 @@ class LLTextureFetch : public LLWorkerThread
friend class HTTPGetResponder;
public:
- LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded);
+ LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* imagedecodethread, bool threaded, bool qa_mode);
~LLTextureFetch();
+ class TFRequest;
+
/*virtual*/ S32 update(U32 max_time_ms);
void shutDownTextureCacheThread() ; //called in the main thread after the TextureCacheThread shuts down.
void shutDownImageDecodeThread() ; //called in the main thread after the ImageDecodeThread shuts down.
@@ -77,28 +81,77 @@ public:
S32 getNumHTTPRequests() ;
// Public for access by callbacks
+ S32 getPending();
void lockQueue() { mQueueMutex.lock(); }
void unlockQueue() { mQueueMutex.unlock(); }
LLTextureFetchWorker* getWorker(const LLUUID& id);
LLTextureFetchWorker* getWorkerAfterLock(const LLUUID& id);
LLTextureInfo* getTextureInfo() { return &mTextureInfo; }
-
+
+ // Commands available to other threads to control metrics gathering operations.
+ void commandSetRegion(U64 region_handle);
+ void commandSendMetrics(const std::string & caps_url,
+ const LLUUID & session_id,
+ const LLUUID & agent_id,
+ LLViewerAssetStats * main_stats);
+ void commandDataBreak();
+
+ LLCurlRequest & getCurlRequest() { return *mCurlGetRequest; }
+
+ bool isQAMode() const { return mQAMode; }
+
+ // Curl POST counter maintenance
+ inline void incrCurlPOSTCount() { mCurlPOSTRequestCount++; }
+ inline void decrCurlPOSTCount() { mCurlPOSTRequestCount--; }
+
protected:
void addToNetworkQueue(LLTextureFetchWorker* worker);
void removeFromNetworkQueue(LLTextureFetchWorker* worker, bool cancel);
void addToHTTPQueue(const LLUUID& id);
void removeFromHTTPQueue(const LLUUID& id, S32 received_size = 0);
void removeRequest(LLTextureFetchWorker* worker, bool cancel);
- // Called from worker thread (during doWork)
- void processCurlRequests();
+
+ // Overrides from the LLThread tree
+ bool runCondition();
private:
void sendRequestListToSimulators();
/*virtual*/ void startThread(void);
/*virtual*/ void endThread(void);
/*virtual*/ void threadedUpdate(void);
-
+ void commonUpdate();
+
+ // Metrics command helpers
+ /**
+ * Enqueues a command request at the end of the command queue
+ * and wakes up the thread as needed.
+ *
+ * Takes ownership of the TFRequest object.
+ *
+ * Method locks the command queue.
+ */
+ void cmdEnqueue(TFRequest *);
+
+ /**
+ * Returns the first TFRequest object in the command queue or
+ * NULL if none is present.
+ *
+ * Caller acquires ownership of the object and must dispose of it.
+ *
+ * Method locks the command queue.
+ */
+ TFRequest * cmdDequeue();
+
+ /**
+ * Processes the first command in the queue disposing of the
+ * request on completion. Successive calls are needed to perform
+ * additional commands.
+ *
+ * Method locks the command queue.
+ */
+ void cmdDoWork();
+
public:
LLUUID mDebugID;
S32 mDebugCount;
@@ -107,7 +160,7 @@ public:
S32 mBadPacketCount;
private:
- LLMutex mQueueMutex; //to protect mRequestMap only
+ LLMutex mQueueMutex; //to protect mRequestMap and mCommands only
LLMutex mNetworkQueueMutex; //to protect mNetworkQueue, mHTTPTextureQueue and mCancelQueue.
LLTextureCache* mTextureCache;
@@ -129,6 +182,29 @@ private:
LLTextureInfo mTextureInfo;
U32 mHTTPTextureBits;
+
+ // Out-of-band cross-thread command queue. This command queue
+ // is logically tied to LLQueuedThread's list of
+ // QueuedRequest instances and so must be covered by the
+ // same locks.
+ typedef std::vector<TFRequest *> command_queue_t;
+ command_queue_t mCommands;
+
+ // If true, modifies some behaviors that help with QA tasks.
+ const bool mQAMode;
+
+ // Count of POST requests outstanding. We maintain the count
+ // indirectly in the CURL request responder's ctor and dtor and
+ // use it when determining whether or not to sleep the thread. Can't
+ // use the LLCurl module's request counter as it isn't thread compatible.
+ // *NOTE: Don't mix Atomic and static, apr_initialize must be called first.
+ LLAtomic32<S32> mCurlPOSTRequestCount;
+
+public:
+ // A probabilistically-correct indicator that the current
+ // attempt to log metrics follows a break in the metrics stream
+ // reporting due to either startup or a problem POSTing data.
+ static volatile bool svMetricsDataBreak;
};
#endif // LL_LLTEXTUREFETCH_H
diff --git a/indra/newview/lltoastgroupnotifypanel.cpp b/indra/newview/lltoastgroupnotifypanel.cpp
index 563c27c4d7..75178a6ef8 100644
--- a/indra/newview/lltoastgroupnotifypanel.cpp
+++ b/indra/newview/lltoastgroupnotifypanel.cpp
@@ -77,6 +77,7 @@ LLToastGroupNotifyPanel::LLToastGroupNotifyPanel(LLNotificationPtr& notification
from << from_name << "/" << groupData.mName;
LLTextBox* pTitleText = getChild<LLTextBox>("title");
pTitleText->setValue(from.str());
+ pTitleText->setToolTip(from.str());
//message subject
const std::string& subject = payload["subject"].asString();
diff --git a/indra/newview/llviewerassetstats.cpp b/indra/newview/llviewerassetstats.cpp
new file mode 100644
index 0000000000..9d98302210
--- /dev/null
+++ b/indra/newview/llviewerassetstats.cpp
@@ -0,0 +1,618 @@
+/**
+ * @file llviewerassetstats.cpp
+ * @brief
+ *
+ * $LicenseInfo:firstyear=2010&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 "llviewerassetstats.h"
+#include "llregionhandle.h"
+
+#include "stdtypes.h"
+
+/*
+ * Classes and utility functions for per-thread and per-region
+ * asset and experiential metrics to be aggregated grid-wide.
+ *
+ * The basic metrics grouping is LLViewerAssetStats::PerRegionStats.
+ * This provides various counters and simple statistics for asset
+ * fetches binned into a few categories. One of these is maintained
+ * for each region encountered and the 'current' region is available
+ * as a simple reference. Each thread (presently two) interested
+ * in participating in these stats gets an instance of the
+ * LLViewerAssetStats class so that threads are completely
+ * independent.
+ *
+ * The idea of a current region is used for simplicity and speed
+ * of categorization. Each metrics event could have taken a
+ * region uuid argument resulting in a suitable lookup. Arguments
+ * against this design include:
+ *
+ * - Region uuid not trivially available to caller.
+ * - Cost (cpu, disruption in real work flow) too high.
+ * - Additional precision not really meaningful.
+ *
+ * By itself, the LLViewerAssetStats class is thread- and
+ * viewer-agnostic and can be used anywhere without assumptions
+ * of global pointers and other context. For the viewer,
+ * a set of free functions are provided in the namespace
+ * LLViewerAssetStatsFF which *do* implement viewer-native
+ * policies about per-thread globals and will do correct
+ * defensive tests of same.
+ *
+ * References
+ *
+ * Project:
+ * <TBD>
+ *
+ * Test Plan:
+ * <TBD>
+ *
+ * Jiras:
+ * <TBD>
+ *
+ * Unit Tests:
+ * indra/newview/tests/llviewerassetstats_test.cpp
+ *
+ */
+
+
+// ------------------------------------------------------
+// Global data definitions
+// ------------------------------------------------------
+LLViewerAssetStats * gViewerAssetStatsMain(0);
+LLViewerAssetStats * gViewerAssetStatsThread1(0);
+
+
+// ------------------------------------------------------
+// Local declarations
+// ------------------------------------------------------
+namespace
+{
+
+static LLViewerAssetStats::EViewerAssetCategories
+asset_type_to_category(const LLViewerAssetType::EType at, bool with_http, bool is_temp);
+
+}
+
+// ------------------------------------------------------
+// LLViewerAssetStats::PerRegionStats struct definition
+// ------------------------------------------------------
+void
+LLViewerAssetStats::PerRegionStats::reset()
+{
+ for (int i(0); i < LL_ARRAY_SIZE(mRequests); ++i)
+ {
+ mRequests[i].mEnqueued.reset();
+ mRequests[i].mDequeued.reset();
+ mRequests[i].mResponse.reset();
+ }
+ mFPS.reset();
+
+ mTotalTime = 0;
+ mStartTimestamp = LLViewerAssetStatsFF::get_timestamp();
+}
+
+
+void
+LLViewerAssetStats::PerRegionStats::merge(const LLViewerAssetStats::PerRegionStats & src)
+{
+ // mRegionHandle, mTotalTime, mStartTimestamp are left alone.
+
+ // mFPS
+ if (src.mFPS.getCount() && mFPS.getCount())
+ {
+ mFPS.merge(src.mFPS);
+ }
+
+ // Requests
+ for (int i = 0; i < LL_ARRAY_SIZE(mRequests); ++i)
+ {
+ mRequests[i].mEnqueued.merge(src.mRequests[i].mEnqueued);
+ mRequests[i].mDequeued.merge(src.mRequests[i].mDequeued);
+ mRequests[i].mResponse.merge(src.mRequests[i].mResponse);
+ }
+}
+
+
+void
+LLViewerAssetStats::PerRegionStats::accumulateTime(duration_t now)
+{
+ mTotalTime += (now - mStartTimestamp);
+ mStartTimestamp = now;
+}
+
+
+// ------------------------------------------------------
+// LLViewerAssetStats class definition
+// ------------------------------------------------------
+LLViewerAssetStats::LLViewerAssetStats()
+ : mRegionHandle(U64(0))
+{
+ reset();
+}
+
+
+LLViewerAssetStats::LLViewerAssetStats(const LLViewerAssetStats & src)
+ : mRegionHandle(src.mRegionHandle),
+ mResetTimestamp(src.mResetTimestamp)
+{
+ const PerRegionContainer::const_iterator it_end(src.mRegionStats.end());
+ for (PerRegionContainer::const_iterator it(src.mRegionStats.begin()); it_end != it; ++it)
+ {
+ mRegionStats[it->first] = new PerRegionStats(*it->second);
+ }
+ mCurRegionStats = mRegionStats[mRegionHandle];
+}
+
+
+void
+LLViewerAssetStats::reset()
+{
+ // Empty the map of all region stats
+ mRegionStats.clear();
+
+ // If we have a current stats, reset it, otherwise, as at construction,
+ // create a new one as we must always have a current stats block.
+ if (mCurRegionStats)
+ {
+ mCurRegionStats->reset();
+ }
+ else
+ {
+ mCurRegionStats = new PerRegionStats(mRegionHandle);
+ }
+
+ // And add reference to map
+ mRegionStats[mRegionHandle] = mCurRegionStats;
+
+ // Start timestamp consistent with per-region collector
+ mResetTimestamp = mCurRegionStats->mStartTimestamp;
+}
+
+
+void
+LLViewerAssetStats::setRegion(region_handle_t region_handle)
+{
+ if (region_handle == mRegionHandle)
+ {
+ // Already active, ignore.
+ return;
+ }
+
+ // Get duration for current set
+ const duration_t now = LLViewerAssetStatsFF::get_timestamp();
+ mCurRegionStats->accumulateTime(now);
+
+ // Prepare new set
+ PerRegionContainer::iterator new_stats = mRegionStats.find(region_handle);
+ if (mRegionStats.end() == new_stats)
+ {
+ // Haven't seen this region_id before, create a new block and make it current.
+ mCurRegionStats = new PerRegionStats(region_handle);
+ mRegionStats[region_handle] = mCurRegionStats;
+ }
+ else
+ {
+ mCurRegionStats = new_stats->second;
+ }
+ mCurRegionStats->mStartTimestamp = now;
+ mRegionHandle = region_handle;
+}
+
+
+void
+LLViewerAssetStats::recordGetEnqueued(LLViewerAssetType::EType at, bool with_http, bool is_temp)
+{
+ const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
+
+ ++(mCurRegionStats->mRequests[int(eac)].mEnqueued);
+}
+
+void
+LLViewerAssetStats::recordGetDequeued(LLViewerAssetType::EType at, bool with_http, bool is_temp)
+{
+ const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
+
+ ++(mCurRegionStats->mRequests[int(eac)].mDequeued);
+}
+
+void
+LLViewerAssetStats::recordGetServiced(LLViewerAssetType::EType at, bool with_http, bool is_temp, duration_t duration)
+{
+ const EViewerAssetCategories eac(asset_type_to_category(at, with_http, is_temp));
+
+ mCurRegionStats->mRequests[int(eac)].mResponse.record(duration);
+}
+
+void
+LLViewerAssetStats::recordFPS(F32 fps)
+{
+ mCurRegionStats->mFPS.record(fps);
+}
+
+LLSD
+LLViewerAssetStats::asLLSD(bool compact_output)
+{
+ // Top-level tags
+ static const LLSD::String tags[EVACCount] =
+ {
+ LLSD::String("get_texture_temp_http"),
+ LLSD::String("get_texture_temp_udp"),
+ LLSD::String("get_texture_non_temp_http"),
+ LLSD::String("get_texture_non_temp_udp"),
+ LLSD::String("get_wearable_udp"),
+ LLSD::String("get_sound_udp"),
+ LLSD::String("get_gesture_udp"),
+ LLSD::String("get_other")
+ };
+
+ // Stats Group Sub-tags.
+ static const LLSD::String enq_tag("enqueued");
+ static const LLSD::String deq_tag("dequeued");
+ static const LLSD::String rcnt_tag("resp_count");
+ static const LLSD::String rmin_tag("resp_min");
+ static const LLSD::String rmax_tag("resp_max");
+ static const LLSD::String rmean_tag("resp_mean");
+
+ // MMM Group Sub-tags.
+ static const LLSD::String cnt_tag("count");
+ static const LLSD::String min_tag("min");
+ static const LLSD::String max_tag("max");
+ static const LLSD::String mean_tag("mean");
+
+ const duration_t now = LLViewerAssetStatsFF::get_timestamp();
+ mCurRegionStats->accumulateTime(now);
+
+ LLSD regions = LLSD::emptyArray();
+ for (PerRegionContainer::iterator it = mRegionStats.begin();
+ mRegionStats.end() != it;
+ ++it)
+ {
+ if (0 == it->first)
+ {
+ // Never emit NULL UUID/handle in results.
+ continue;
+ }
+
+ PerRegionStats & stats = *it->second;
+
+ LLSD reg_stat = LLSD::emptyMap();
+
+ for (int i = 0; i < LL_ARRAY_SIZE(tags); ++i)
+ {
+ PerRegionStats::prs_group & group(stats.mRequests[i]);
+
+ if ((! compact_output) ||
+ group.mEnqueued.getCount() ||
+ group.mDequeued.getCount() ||
+ group.mResponse.getCount())
+ {
+ LLSD & slot = reg_stat[tags[i]];
+ slot = LLSD::emptyMap();
+ slot[enq_tag] = LLSD(S32(stats.mRequests[i].mEnqueued.getCount()));
+ slot[deq_tag] = LLSD(S32(stats.mRequests[i].mDequeued.getCount()));
+ slot[rcnt_tag] = LLSD(S32(stats.mRequests[i].mResponse.getCount()));
+ slot[rmin_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMin() * 1.0e-6));
+ slot[rmax_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMax() * 1.0e-6));
+ slot[rmean_tag] = LLSD(F64(stats.mRequests[i].mResponse.getMean() * 1.0e-6));
+ }
+ }
+
+ if ((! compact_output) || stats.mFPS.getCount())
+ {
+ LLSD & slot = reg_stat["fps"];
+ slot = LLSD::emptyMap();
+ slot[cnt_tag] = LLSD(S32(stats.mFPS.getCount()));
+ slot[min_tag] = LLSD(F64(stats.mFPS.getMin()));
+ slot[max_tag] = LLSD(F64(stats.mFPS.getMax()));
+ slot[mean_tag] = LLSD(F64(stats.mFPS.getMean()));
+ }
+
+ U32 grid_x(0), grid_y(0);
+ grid_from_region_handle(it->first, &grid_x, &grid_y);
+ reg_stat["grid_x"] = LLSD::Integer(grid_x);
+ reg_stat["grid_y"] = LLSD::Integer(grid_y);
+ reg_stat["duration"] = LLSD::Real(stats.mTotalTime * 1.0e-6);
+ regions.append(reg_stat);
+ }
+
+ LLSD ret = LLSD::emptyMap();
+ ret["regions"] = regions;
+ ret["duration"] = LLSD::Real((now - mResetTimestamp) * 1.0e-6);
+
+ return ret;
+}
+
+void
+LLViewerAssetStats::merge(const LLViewerAssetStats & src)
+{
+ // mRegionHandle, mCurRegionStats and mResetTimestamp are left untouched.
+ // Just merge the stats bodies
+
+ const PerRegionContainer::const_iterator it_end(src.mRegionStats.end());
+ for (PerRegionContainer::const_iterator it(src.mRegionStats.begin()); it_end != it; ++it)
+ {
+ PerRegionContainer::iterator dst(mRegionStats.find(it->first));
+ if (mRegionStats.end() == dst)
+ {
+ // Destination is missing data, just make a private copy
+ mRegionStats[it->first] = new PerRegionStats(*it->second);
+ }
+ else
+ {
+ dst->second->merge(*it->second);
+ }
+ }
+}
+
+
+// ------------------------------------------------------
+// Global free-function definitions (LLViewerAssetStatsFF namespace)
+// ------------------------------------------------------
+
+namespace LLViewerAssetStatsFF
+{
+
+//
+// Target thread is elaborated in the function name. This could
+// have been something 'templatey' like specializations iterated
+// over a set of constants but with so few, this is clearer I think.
+//
+// As for the threads themselves... rather than do fine-grained
+// locking as we gather statistics, this code creates a collector
+// for each thread, allocated and run independently. Logging
+// happens at relatively infrequent intervals and at that time
+// the data is sent to a single thread to be aggregated into
+// a single entity with locks, thread safety and other niceties.
+//
+// A particularly fussy implementation would distribute the
+// per-thread pointers across separate cache lines. But that should
+// be beyond current requirements.
+//
+
+// 'main' thread - initial program thread
+
+void
+set_region_main(LLViewerAssetStats::region_handle_t region_handle)
+{
+ if (! gViewerAssetStatsMain)
+ return;
+
+ gViewerAssetStatsMain->setRegion(region_handle);
+}
+
+void
+record_enqueue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp)
+{
+ if (! gViewerAssetStatsMain)
+ return;
+
+ gViewerAssetStatsMain->recordGetEnqueued(at, with_http, is_temp);
+}
+
+void
+record_dequeue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp)
+{
+ if (! gViewerAssetStatsMain)
+ return;
+
+ gViewerAssetStatsMain->recordGetDequeued(at, with_http, is_temp);
+}
+
+void
+record_response_main(LLViewerAssetType::EType at, bool with_http, bool is_temp, LLViewerAssetStats::duration_t duration)
+{
+ if (! gViewerAssetStatsMain)
+ return;
+
+ gViewerAssetStatsMain->recordGetServiced(at, with_http, is_temp, duration);
+}
+
+void
+record_fps_main(F32 fps)
+{
+ if (! gViewerAssetStatsMain)
+ return;
+
+ gViewerAssetStatsMain->recordFPS(fps);
+}
+
+
+// 'thread1' - should be for TextureFetch thread
+
+void
+set_region_thread1(LLViewerAssetStats::region_handle_t region_handle)
+{
+ if (! gViewerAssetStatsThread1)
+ return;
+
+ gViewerAssetStatsThread1->setRegion(region_handle);
+}
+
+void
+record_enqueue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp)
+{
+ if (! gViewerAssetStatsThread1)
+ return;
+
+ gViewerAssetStatsThread1->recordGetEnqueued(at, with_http, is_temp);
+}
+
+void
+record_dequeue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp)
+{
+ if (! gViewerAssetStatsThread1)
+ return;
+
+ gViewerAssetStatsThread1->recordGetDequeued(at, with_http, is_temp);
+}
+
+void
+record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp, LLViewerAssetStats::duration_t duration)
+{
+ if (! gViewerAssetStatsThread1)
+ return;
+
+ gViewerAssetStatsThread1->recordGetServiced(at, with_http, is_temp, duration);
+}
+
+
+void
+init()
+{
+ if (! gViewerAssetStatsMain)
+ {
+ gViewerAssetStatsMain = new LLViewerAssetStats();
+ }
+ if (! gViewerAssetStatsThread1)
+ {
+ gViewerAssetStatsThread1 = new LLViewerAssetStats();
+ }
+}
+
+void
+cleanup()
+{
+ delete gViewerAssetStatsMain;
+ gViewerAssetStatsMain = 0;
+
+ delete gViewerAssetStatsThread1;
+ gViewerAssetStatsThread1 = 0;
+}
+
+
+} // namespace LLViewerAssetStatsFF
+
+
+// ------------------------------------------------------
+// Local function definitions
+// ------------------------------------------------------
+
+namespace
+{
+
+LLViewerAssetStats::EViewerAssetCategories
+asset_type_to_category(const LLViewerAssetType::EType at, bool with_http, bool is_temp)
+{
+ // For statistical purposes, we divide GETs into several
+ // populations of asset fetches:
+ // - textures which are de-prioritized in the asset system
+ // - wearables (clothing, bodyparts) which directly affect
+ // user experiences when they log in
+ // - sounds
+ // - gestures
+ // - everything else.
+ //
+ llassert_always(50 == LLViewerAssetType::AT_COUNT);
+
+ // Multiple asset definitions are floating around so this requires some
+ // maintenance and attention.
+ static const LLViewerAssetStats::EViewerAssetCategories asset_to_bin_map[LLViewerAssetType::AT_COUNT] =
+ {
+ LLViewerAssetStats::EVACTextureTempHTTPGet, // (0) AT_TEXTURE
+ LLViewerAssetStats::EVACSoundUDPGet, // AT_SOUND
+ LLViewerAssetStats::EVACOtherGet, // AT_CALLINGCARD
+ LLViewerAssetStats::EVACOtherGet, // AT_LANDMARK
+ LLViewerAssetStats::EVACOtherGet, // AT_SCRIPT
+ LLViewerAssetStats::EVACWearableUDPGet, // AT_CLOTHING
+ LLViewerAssetStats::EVACOtherGet, // AT_OBJECT
+ LLViewerAssetStats::EVACOtherGet, // AT_NOTECARD
+ LLViewerAssetStats::EVACOtherGet, // AT_CATEGORY
+ LLViewerAssetStats::EVACOtherGet, // AT_ROOT_CATEGORY
+ LLViewerAssetStats::EVACOtherGet, // (10) AT_LSL_TEXT
+ LLViewerAssetStats::EVACOtherGet, // AT_LSL_BYTECODE
+ LLViewerAssetStats::EVACOtherGet, // AT_TEXTURE_TGA
+ LLViewerAssetStats::EVACWearableUDPGet, // AT_BODYPART
+ LLViewerAssetStats::EVACOtherGet, // AT_TRASH
+ LLViewerAssetStats::EVACOtherGet, // AT_SNAPSHOT_CATEGORY
+ LLViewerAssetStats::EVACOtherGet, // AT_LOST_AND_FOUND
+ LLViewerAssetStats::EVACSoundUDPGet, // AT_SOUND_WAV
+ LLViewerAssetStats::EVACOtherGet, // AT_IMAGE_TGA
+ LLViewerAssetStats::EVACOtherGet, // AT_IMAGE_JPEG
+ LLViewerAssetStats::EVACGestureUDPGet, // (20) AT_ANIMATION
+ LLViewerAssetStats::EVACGestureUDPGet, // AT_GESTURE
+ LLViewerAssetStats::EVACOtherGet, // AT_SIMSTATE
+ LLViewerAssetStats::EVACOtherGet, // AT_FAVORITE
+ LLViewerAssetStats::EVACOtherGet, // AT_LINK
+ LLViewerAssetStats::EVACOtherGet, // AT_LINK_FOLDER
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, // (30)
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, // (40)
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, //
+ LLViewerAssetStats::EVACOtherGet, // AT_MESH
+ // (50)
+ };
+
+ if (at < 0 || at >= LLViewerAssetType::AT_COUNT)
+ {
+ return LLViewerAssetStats::EVACOtherGet;
+ }
+ LLViewerAssetStats::EViewerAssetCategories ret(asset_to_bin_map[at]);
+ if (LLViewerAssetStats::EVACTextureTempHTTPGet == ret)
+ {
+ // Indexed with [is_temp][with_http]
+ static const LLViewerAssetStats::EViewerAssetCategories texture_bin_map[2][2] =
+ {
+ {
+ LLViewerAssetStats::EVACTextureNonTempUDPGet,
+ LLViewerAssetStats::EVACTextureNonTempHTTPGet,
+ },
+ {
+ LLViewerAssetStats::EVACTextureTempUDPGet,
+ LLViewerAssetStats::EVACTextureTempHTTPGet,
+ }
+ };
+
+ ret = texture_bin_map[is_temp][with_http];
+ }
+ return ret;
+}
+
+} // anonymous namespace
diff --git a/indra/newview/llviewerassetstats.h b/indra/newview/llviewerassetstats.h
new file mode 100644
index 0000000000..905ceefad5
--- /dev/null
+++ b/indra/newview/llviewerassetstats.h
@@ -0,0 +1,334 @@
+/**
+ * @file llviewerassetstats.h
+ * @brief Client-side collection of asset request statistics
+ *
+ * $LicenseInfo:firstyear=2010&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_LLVIEWERASSETSTATUS_H
+#define LL_LLVIEWERASSETSTATUS_H
+
+
+#include "linden_common.h"
+
+#include "llpointer.h"
+#include "llrefcount.h"
+#include "llviewerassettype.h"
+#include "llviewerassetstorage.h"
+#include "llsimplestat.h"
+#include "llsd.h"
+
+/**
+ * @class LLViewerAssetStats
+ * @brief Records performance aspects of asset access operations.
+ *
+ * This facility is derived from a very similar simulator-based
+ * one, LLSimAssetStats. It's function is to count asset access
+ * operations and characterize response times. Collected data
+ * are binned in several dimensions:
+ *
+ * - Asset types collapsed into a few aggregated categories
+ * - By simulator UUID
+ * - By transport mechanism (HTTP vs MessageSystem)
+ * - By persistence (temp vs non-temp)
+ *
+ * Statistics collected are fairly basic at this point:
+ *
+ * - Counts of enqueue and dequeue operations
+ * - Min/Max/Mean of asset transfer operations
+ *
+ * This collector differs from the simulator-based on in a
+ * number of ways:
+ *
+ * - The front-end/back-end distinction doesn't exist in viewer
+ * code
+ * - Multiple threads must be safely accomodated in the viewer
+ *
+ * Access to results is by conversion to an LLSD with some standardized
+ * key names. The intent of this structure is that it be emitted as
+ * standard syslog-based metrics formatting where it can be picked
+ * up by interested parties.
+ *
+ * For convenience, a set of free functions in namespace
+ * LLViewerAssetStatsFF is provided for conditional test-and-call
+ * operations.
+ */
+class LLViewerAssetStats
+{
+public:
+ enum EViewerAssetCategories
+ {
+ EVACTextureTempHTTPGet, //< Texture GETs - temp/baked, HTTP
+ EVACTextureTempUDPGet, //< Texture GETs - temp/baked, UDP
+ EVACTextureNonTempHTTPGet, //< Texture GETs - perm, HTTP
+ EVACTextureNonTempUDPGet, //< Texture GETs - perm, UDP
+ EVACWearableUDPGet, //< Wearable GETs
+ EVACSoundUDPGet, //< Sound GETs
+ EVACGestureUDPGet, //< Gesture GETs
+ EVACOtherGet, //< Other GETs
+
+ EVACCount // Must be last
+ };
+
+ /**
+ * Type for duration and other time values in the metrics. Selected
+ * for compatibility with the pre-existing timestamp on the texture
+ * fetcher class, LLTextureFetch.
+ */
+ typedef U64 duration_t;
+
+ /**
+ * Type for the region identifier used in stats. Currently uses
+ * the region handle's type (a U64) rather than the regions's LLUUID
+ * as the latter isn't available immediately.
+ */
+ typedef U64 region_handle_t;
+
+ /**
+ * @brief Collected data for a single region visited by the avatar.
+ *
+ * Fairly simple, for each asset bin enumerated above a count
+ * of enqueue and dequeue operations and simple stats on response
+ * times for completed requests.
+ */
+ class PerRegionStats : public LLRefCount
+ {
+ public:
+ PerRegionStats(const region_handle_t region_handle)
+ : LLRefCount(),
+ mRegionHandle(region_handle)
+ {
+ reset();
+ }
+
+ PerRegionStats(const PerRegionStats & src)
+ : LLRefCount(),
+ mRegionHandle(src.mRegionHandle),
+ mTotalTime(src.mTotalTime),
+ mStartTimestamp(src.mStartTimestamp),
+ mFPS(src.mFPS)
+ {
+ for (int i = 0; i < LL_ARRAY_SIZE(mRequests); ++i)
+ {
+ mRequests[i] = src.mRequests[i];
+ }
+ }
+
+ // Default assignment and destructor are correct.
+
+ void reset();
+
+ void merge(const PerRegionStats & src);
+
+ // Apply current running time to total and reset start point.
+ // Return current timestamp as a convenience.
+ void accumulateTime(duration_t now);
+
+ public:
+ region_handle_t mRegionHandle;
+ duration_t mTotalTime;
+ duration_t mStartTimestamp;
+ LLSimpleStatMMM<> mFPS;
+
+ struct prs_group
+ {
+ LLSimpleStatCounter mEnqueued;
+ LLSimpleStatCounter mDequeued;
+ LLSimpleStatMMM<duration_t> mResponse;
+ }
+ mRequests [EVACCount];
+ };
+
+public:
+ LLViewerAssetStats();
+ LLViewerAssetStats(const LLViewerAssetStats &);
+ // Default destructor is correct.
+ LLViewerAssetStats & operator=(const LLViewerAssetStats &); // Not defined
+
+ // Clear all metrics data. This leaves the currently-active region
+ // in place but with zero'd data for all metrics. All other regions
+ // are removed from the collection map.
+ void reset();
+
+ // Set hidden region argument and establish context for subsequent
+ // collection calls.
+ void setRegion(region_handle_t region_handle);
+
+ // Asset GET Requests
+ void recordGetEnqueued(LLViewerAssetType::EType at, bool with_http, bool is_temp);
+ void recordGetDequeued(LLViewerAssetType::EType at, bool with_http, bool is_temp);
+ void recordGetServiced(LLViewerAssetType::EType at, bool with_http, bool is_temp, duration_t duration);
+
+ // Frames-Per-Second Samples
+ void recordFPS(F32 fps);
+
+ // Merge a source instance into a destination instance. This is
+ // conceptually an 'operator+=()' method:
+ // - counts are added
+ // - minimums are min'd
+ // - maximums are max'd
+ // - other scalars are ignored ('this' wins)
+ //
+ void merge(const LLViewerAssetStats & src);
+
+ // Retrieve current metrics for all visited regions (NULL region UUID/handle excluded)
+ // Returned LLSD is structured as follows:
+ //
+ // &stats_group = {
+ // enqueued : int,
+ // dequeued : int,
+ // resp_count : int,
+ // resp_min : float,
+ // resp_max : float,
+ // resp_mean : float
+ // }
+ //
+ // &mmm_group = {
+ // count : int,
+ // min : float,
+ // max : float,
+ // mean : float
+ // }
+ //
+ // {
+ // duration: int
+ // regions: {
+ // $: { // Keys are strings of the region's handle in hex
+ // duration: : int,
+ // fps: : &mmm_group,
+ // get_texture_temp_http : &stats_group,
+ // get_texture_temp_udp : &stats_group,
+ // get_texture_non_temp_http : &stats_group,
+ // get_texture_non_temp_udp : &stats_group,
+ // get_wearable_udp : &stats_group,
+ // get_sound_udp : &stats_group,
+ // get_gesture_udp : &stats_group,
+ // get_other : &stats_group
+ // }
+ // }
+ // }
+ //
+ // @param compact_output If true, omits from conversion any mmm_block
+ // or stats_block that would contain all zero data.
+ // Useful for transmission when the receiver knows
+ // what is expected and will assume zero for missing
+ // blocks.
+ LLSD asLLSD(bool compact_output);
+
+protected:
+ typedef std::map<region_handle_t, LLPointer<PerRegionStats> > PerRegionContainer;
+
+ // Region of the currently-active region. Always valid but may
+ // be zero after construction or when explicitly set. Unchanged
+ // by a reset() call.
+ region_handle_t mRegionHandle;
+
+ // Pointer to metrics collection for currently-active region. Always
+ // valid and unchanged after reset() though contents will be changed.
+ // Always points to a collection contained in mRegionStats.
+ LLPointer<PerRegionStats> mCurRegionStats;
+
+ // Metrics data for all regions during one collection cycle
+ PerRegionContainer mRegionStats;
+
+ // Time of last reset
+ duration_t mResetTimestamp;
+};
+
+
+/**
+ * Global stats collectors one for each independent thread where
+ * assets and other statistics are gathered. The globals are
+ * expected to be created at startup time and then picked up by
+ * their respective threads afterwards. A set of free functions
+ * are provided to access methods behind the globals while both
+ * minimally disrupting visual flow and supplying a description
+ * of intent.
+ *
+ * Expected thread assignments:
+ *
+ * - Main: main() program execution thread
+ * - Thread1: TextureFetch worker thread
+ */
+extern LLViewerAssetStats * gViewerAssetStatsMain;
+
+extern LLViewerAssetStats * gViewerAssetStatsThread1;
+
+namespace LLViewerAssetStatsFF
+{
+/**
+ * @brief Allocation and deallocation of globals.
+ *
+ * init() should be called before threads are started that will access it though
+ * you'll likely get away with calling it afterwards. cleanup() should only be
+ * called after threads are shutdown to prevent races on the global pointers.
+ */
+void init();
+
+void cleanup();
+
+/**
+ * We have many timers, clocks etc. in the runtime. This is the
+ * canonical timestamp for these metrics which is compatible with
+ * the pre-existing timestamping in the texture fetcher.
+ */
+inline LLViewerAssetStats::duration_t get_timestamp()
+{
+ return LLTimer::getTotalTime();
+}
+
+/**
+ * Region context, event and duration loggers for the Main thread.
+ */
+void set_region_main(LLViewerAssetStats::region_handle_t region_handle);
+
+void record_enqueue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp);
+
+void record_dequeue_main(LLViewerAssetType::EType at, bool with_http, bool is_temp);
+
+void record_response_main(LLViewerAssetType::EType at, bool with_http, bool is_temp,
+ LLViewerAssetStats::duration_t duration);
+
+void record_fps_main(F32 fps);
+
+
+/**
+ * Region context, event and duration loggers for Thread 1.
+ */
+void set_region_thread1(LLViewerAssetStats::region_handle_t region_handle);
+
+void record_enqueue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp);
+
+void record_dequeue_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp);
+
+void record_response_thread1(LLViewerAssetType::EType at, bool with_http, bool is_temp,
+ LLViewerAssetStats::duration_t duration);
+
+} // namespace LLViewerAssetStatsFF
+
+#endif // LL_LLVIEWERASSETSTATUS_H
diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp
index 2e7ef0fec3..36c8b42a52 100644
--- a/indra/newview/llviewerassetstorage.cpp
+++ b/indra/newview/llviewerassetstorage.cpp
@@ -33,6 +33,61 @@
#include "message.h"
#include "llagent.h"
+#include "lltransfersourceasset.h"
+#include "lltransfertargetvfile.h"
+#include "llviewerassetstats.h"
+
+///----------------------------------------------------------------------------
+/// LLViewerAssetRequest
+///----------------------------------------------------------------------------
+
+/**
+ * @brief Local class to encapsulate asset fetch requests with a timestamp.
+ *
+ * Derived from the common LLAssetRequest class, this is currently used
+ * only for fetch/get operations and its only function is to wrap remote
+ * asset fetch requests so that they can be timed.
+ */
+class LLViewerAssetRequest : public LLAssetRequest
+{
+public:
+ LLViewerAssetRequest(const LLUUID &uuid, const LLAssetType::EType type)
+ : LLAssetRequest(uuid, type),
+ mMetricsStartTime(0)
+ {
+ }
+
+ LLViewerAssetRequest & operator=(const LLViewerAssetRequest &); // Not defined
+ // Default assignment operator valid
+
+ // virtual
+ ~LLViewerAssetRequest()
+ {
+ recordMetrics();
+ }
+
+protected:
+ void recordMetrics()
+ {
+ if (mMetricsStartTime)
+ {
+ // Okay, it appears this request was used for useful things. Record
+ // the expected dequeue and duration of request processing.
+ LLViewerAssetStatsFF::record_dequeue_main(mType, false, false);
+ LLViewerAssetStatsFF::record_response_main(mType, false, false,
+ (LLViewerAssetStatsFF::get_timestamp()
+ - mMetricsStartTime));
+ mMetricsStartTime = 0;
+ }
+ }
+
+public:
+ LLViewerAssetStats::duration_t mMetricsStartTime;
+};
+
+///----------------------------------------------------------------------------
+/// LLViewerAssetStorage
+///----------------------------------------------------------------------------
LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer,
LLVFS *vfs, LLVFS *static_vfs,
@@ -258,3 +313,77 @@ void LLViewerAssetStorage::storeAssetData(
}
}
}
+
+
+/**
+ * @brief Allocate and queue an asset fetch request for the viewer
+ *
+ * This is a nearly-verbatim copy of the base class's implementation
+ * with the following changes:
+ * - Use a locally-derived request class
+ * - Start timing for metrics when request is queued
+ *
+ * This is an unfortunate implementation choice but it's forced by
+ * current conditions. A refactoring that might clean up the layers
+ * of responsibility or introduce factories or more virtualization
+ * of methods would enable a more attractive solution.
+ *
+ * If LLAssetStorage::_queueDataRequest changes, this must change
+ * as well.
+ */
+
+// virtual
+void LLViewerAssetStorage::_queueDataRequest(
+ const LLUUID& uuid,
+ LLAssetType::EType atype,
+ LLGetAssetCallback callback,
+ void *user_data,
+ BOOL duplicate,
+ BOOL is_priority)
+{
+ if (mUpstreamHost.isOk())
+ {
+ // stash the callback info so we can find it after we get the response message
+ LLViewerAssetRequest *req = new LLViewerAssetRequest(uuid, atype);
+ req->mDownCallback = callback;
+ req->mUserData = user_data;
+ req->mIsPriority = is_priority;
+ if (!duplicate)
+ {
+ // Only collect metrics for non-duplicate requests. Others
+ // are piggy-backing and will artificially lower averages.
+ req->mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp();
+ }
+
+ mPendingDownloads.push_back(req);
+
+ if (!duplicate)
+ {
+ // send request message to our upstream data provider
+ // Create a new asset transfer.
+ LLTransferSourceParamsAsset spa;
+ spa.setAsset(uuid, atype);
+
+ // Set our destination file, and the completion callback.
+ LLTransferTargetParamsVFile tpvf;
+ tpvf.setAsset(uuid, atype);
+ tpvf.setCallback(downloadCompleteCallback, req);
+
+ llinfos << "Starting transfer for " << uuid << llendl;
+ LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(mUpstreamHost, LLTCT_ASSET);
+ ttcp->requestTransfer(spa, tpvf, 100.f + (is_priority ? 1.f : 0.f));
+
+ LLViewerAssetStatsFF::record_enqueue_main(atype, false, false);
+ }
+ }
+ else
+ {
+ // uh-oh, we shouldn't have gotten here
+ llwarns << "Attempt to move asset data request upstream w/o valid upstream provider" << llendl;
+ if (callback)
+ {
+ callback(mVFS, uuid, atype, user_data, LL_ERR_CIRCUIT_GONE, LL_EXSTAT_NO_UPSTREAM);
+ }
+ }
+}
+
diff --git a/indra/newview/llviewerassetstorage.h b/indra/newview/llviewerassetstorage.h
index 6346b79f03..ca9b9943fa 100644
--- a/indra/newview/llviewerassetstorage.h
+++ b/indra/newview/llviewerassetstorage.h
@@ -63,6 +63,17 @@ public:
bool is_priority = false,
bool user_waiting=FALSE,
F64 timeout=LL_ASSET_STORAGE_TIMEOUT);
+
+protected:
+ using LLAssetStorage::_queueDataRequest;
+
+ // virtual
+ void _queueDataRequest(const LLUUID& uuid,
+ LLAssetType::EType type,
+ void (*callback) (LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat),
+ void *user_data,
+ BOOL duplicate,
+ BOOL is_priority);
};
#endif
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index 8991c21518..dadda29416 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -128,6 +128,45 @@ static bool handleSetShaderChanged(const LLSD& newvalue)
return true;
}
+static bool handleRenderPerfTestChanged(const LLSD& newvalue)
+{
+ bool status = !newvalue.asBoolean();
+ if (!status)
+ {
+ gPipeline.clearRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY,
+ LLPipeline::RENDER_TYPE_GROUND,
+ LLPipeline::RENDER_TYPE_TERRAIN,
+ LLPipeline::RENDER_TYPE_GRASS,
+ LLPipeline::RENDER_TYPE_TREE,
+ LLPipeline::RENDER_TYPE_WATER,
+ LLPipeline::RENDER_TYPE_PASS_GRASS,
+ LLPipeline::RENDER_TYPE_HUD,
+ LLPipeline::RENDER_TYPE_PARTICLES,
+ LLPipeline::RENDER_TYPE_CLOUDS,
+ LLPipeline::RENDER_TYPE_HUD_PARTICLES,
+ LLPipeline::END_RENDER_TYPES);
+ gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, false);
+ }
+ else
+ {
+ gPipeline.setRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY,
+ LLPipeline::RENDER_TYPE_GROUND,
+ LLPipeline::RENDER_TYPE_TERRAIN,
+ LLPipeline::RENDER_TYPE_GRASS,
+ LLPipeline::RENDER_TYPE_TREE,
+ LLPipeline::RENDER_TYPE_WATER,
+ LLPipeline::RENDER_TYPE_PASS_GRASS,
+ LLPipeline::RENDER_TYPE_HUD,
+ LLPipeline::RENDER_TYPE_PARTICLES,
+ LLPipeline::RENDER_TYPE_CLOUDS,
+ LLPipeline::RENDER_TYPE_HUD_PARTICLES,
+ LLPipeline::END_RENDER_TYPES);
+ gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, true);
+ }
+
+ return true;
+}
+
bool handleRenderTransparentWaterChanged(const LLSD& newvalue)
{
LLWorld::getInstance()->updateWaterObjects();
@@ -565,6 +604,7 @@ void settings_setup_listeners()
gSavedSettings.getControl("RenderShadowDetail")->getSignal()->connect(boost::bind(&handleSetShaderChanged, _2));
gSavedSettings.getControl("RenderDeferredSSAO")->getSignal()->connect(boost::bind(&handleSetShaderChanged, _2));
gSavedSettings.getControl("RenderDeferredGI")->getSignal()->connect(boost::bind(&handleSetShaderChanged, _2));
+ gSavedSettings.getControl("RenderPerformanceTest")->getSignal()->connect(boost::bind(&handleRenderPerfTestChanged, _2));
gSavedSettings.getControl("TextureMemory")->getSignal()->connect(boost::bind(&handleVideoMemoryChanged, _2));
gSavedSettings.getControl("AuditTexture")->getSignal()->connect(boost::bind(&handleAuditTextureChanged, _2));
gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&handleChatFontSizeChanged, _2));
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 849ac7c830..8c06eb7f5d 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -123,9 +123,33 @@
#include "llsyswellwindow.h"
#include "llscriptfloater.h"
#include "llfloatermodelpreview.h"
+#include "llcommandhandler.h"
// *NOTE: Please add files in alphabetical order to keep merges easy.
+// handle secondlife:///app/floater/{NAME} URLs
+class LLFloaterOpenHandler : public LLCommandHandler
+{
+public:
+ // requires trusted browser to trigger
+ LLFloaterOpenHandler() : LLCommandHandler("floater", UNTRUSTED_THROTTLE) { }
+
+ bool handle(const LLSD& params, const LLSD& query_map,
+ LLMediaCtrl* web)
+ {
+ if (params.size() != 1)
+ {
+ return false;
+ }
+
+ const std::string floater_name = LLURI::unescape(params[0].asString());
+ LLFloaterReg::showInstance(floater_name);
+
+ return true;
+ }
+};
+
+LLFloaterOpenHandler gFloaterOpenHandler;
void LLViewerFloaterReg::registerFloaters()
{
@@ -252,7 +276,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("upload_image", "floater_image_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterImagePreview>, "upload");
LLFloaterReg::add("upload_sound", "floater_sound_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSoundPreview>, "upload");
LLFloaterReg::add("upload_model", "floater_model_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterModelPreview>, "upload");
- LLFloaterReg::add("upload_model_wizard", "floater_model_wizard.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterModelWizard>, "upload");
+ LLFloaterReg::add("upload_model_wizard", "floater_model_wizard.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterModelWizard>);
LLFloaterReg::add("voice_controls", "floater_voice_controls.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLCallFloater>);
LLFloaterReg::add("voice_effect", "floater_voice_effect.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterVoiceEffect>);
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index 75caf45175..a90c90a774 100644
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -1425,6 +1425,7 @@ void LLViewerRegion::setSeedCapability(const std::string& url)
capabilityNames.append("UpdateScriptTask");
capabilityNames.append("UploadBakedTexture");
capabilityNames.append("UploadObjectAsset");
+ capabilityNames.append("ViewerMetrics");
capabilityNames.append("ViewerStartAuction");
capabilityNames.append("ViewerStats");
capabilityNames.append("WebFetchInventoryDescendents");
diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml
index ee47e53db9..0fdcf486e7 100644
--- a/indra/newview/skins/default/xui/en/floater_model_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml
@@ -64,7 +64,7 @@
<text bottom_delta="25" left="25" width="100" follows="bottom|left">Upload Details</text>
<panel top_pad="5" border="true" left="15" width="290" height="70" follows="bottom|left"
- bevel_style="in" bg_alpha_color="0 0 0 0" bg_opaque_color="0 0 0 0.3">
+ bevel_style="none" bg_alpha_color="0 0 0 0" bg_opaque_color="0 0 0 0.3">
<text left="25" follows="bottom|left" width="140" height="15" name="streaming cost">
Resource Cost: [COST]
</text>
@@ -222,30 +222,25 @@
label="Physics"
name="physics_panel">
- <!-- PHYSICS STEP ONE-->
+ <!-- PHYSICS GEOMETRY-->
<panel
follows="top|left"
- name="physics step 1"
+ name="physics geometry"
left="0"
top="0"
- width="260"
- height="450"
- visible="true">
-
- <text follows="left|top" bottom="40" height="30" left="10" font="SansSerifBig">
- Step 1: Geometry
- </text>
-
- <radio_group follows="top|left" top_pad="5" width="240" height="70" name="physics_load_radio" value="physics_load_from_file">
- <radio_item bottom="50" name="physics_load_from_file" label="Load from file"/>
- <radio_item bottom="0" name="physics_use_lod" label="Use Level of Detail"/>
+ width="300"
+ height="80"
+ visible="true"
+ border="true"
+ bevel_style="none" bg_alpha_color="0 0 0 0" bg_opaque_color="0 0 0 0.3">
+
+ <radio_group follows="top|left" top="10" width="240" height="40" name="physics_load_radio" value="physics_load_from_file">
+ <radio_item bottom="0" name="physics_load_from_file" label="File:"/>
+ <radio_item bottom="23" name="physics_use_lod" label="Use Level of Detail:"/>
</radio_group>
- <line_editor follows="left|top" bottom_delta="-25" width="140" left="30" value="" name="physics_file" height="20"/>
- <button bottom_delta="3" name="physics_browse" label="Browse..." left_pad="5" follows="left|top" width="70" height="25"/>
-
- <combo_box left="30" bottom_delta="40" follows="left|top" height="18"
- name="physics_lod_combo" width="90" tool_tip="LOD to use for physics shape">
+ <combo_box left="180" top="10" follows="left|top" height="18"
+ name="physics_lod_combo" width="110" tool_tip="LOD to use for physics shape">
<combo_item name="physics_lowest">
Lowest
</combo_item>
@@ -260,120 +255,103 @@
</combo_item>
</combo_box>
- <!--
- <text follows="top|left" name="physics_triangles" top_pad="15" height="15" left="10">
- Triangles: [TRIANGLES]
- </text>
- <text follows="top|left" name="physics_hulls" top_pad="5" height="15">
- Hulls: [HULLS]
- </text>
- <text follows="top|left" name="physics_points" top_pad="5" height="15">
- Points: [POINTS]
- </text>
-
- <text follows="top|left" left="140" width="100" bottom_delta="-40" height="15">
- Layer:
- </text>
- <combo_box name="physics_layer" follows="top|left" width="100" height="20" top_pad="5"/>
- -->
+ <line_editor follows="left|top" top_pad="5" width="140" left="60" value="" name="physics_file" height="20"/>
+ <button left_pad="10" name="physics_browse" label="Browse..." follows="left|top" width="70" height="20"/>
- <button bottom="440" left="180" width="80" follows="bottom|right" label="Optimize" name="physics_optimize" height="20"/>
-
- </panel>
+ <!--
+ <check_box name="physics_optimize" follows="left|top" width="130" left="10" top_pad="5" height="20" label="Optimize"/>
+ <check_box name="physics_use_hull" follows="left|top" width="130" left_pad="5" height="20" label="Use Convex Hull"/>
+ -->
+ </panel>
- <!-- PHYSICS STEP 2-->
+ <!-- PHYSICS ANALYSIS-->
<panel
follows="top|left"
- name="physics step 2"
+ name="physics analysis"
+ top_pad="0"
left="0"
- top="0"
- width="260"
- height="450"
- visible="false">
+ width="300"
+ height="130"
+ visible="true"
+ border="true"
+ bevel_style="none" bg_alpha_color="0 0 0 0" bg_opaque_color="0 0 0 0.3">
<text follows="left|top" bottom="40" height="30" left="10" font="SansSerifBig">
- Step 2: Decompose
+ Step 1: Analysis
</text>
- <text top_pad="100" follows="top|left" height="15">
- Decompose Method:
+ <text top_pad="5" width="50" follows="top|left" height="15">
+ Method:
</text>
- <combo_box name="Method" follows="top|left" top_pad="5" height="20" width="100"/>
- <text top_pad="10" follows="top|left" height="15">
+ <combo_box name="Method" follows="top|left" left_pad="5" bottom_delta="2" height="20" width="80"/>
+ <text left="160" bottom_delta="-2" width="50" follows="top|left" height="15">
Quality:
</text>
- <combo_box name="Decompose Quality" follows="top|left" top_pad="5" height="20" width="100"/>
+ <combo_box name="Decompose Quality" bottom_delta="2" follows="top|left" left_pad="5" height="20" width="80"/>
- <check_box name="Simplify Coplanar" follows="top|left" top_pad="10" height="15" label="Simplify Coplaner"/>
- <check_box name="Close Holes (Slow)" follows="top|left" top_pad="10" height="15" label="Close Holes (slow)"/>
- <slider name="Cosine%" width="240" follows="top|left" top_pad="10" height="20" label="Cosine %"/>
+ <slider name="Smooth" left="10" width="280" follows="top|left" top_pad="10" height="20" label="Smooth:"/>
- <text follows="top|left" top_pad="20" height="30" width="260">
- "Note: Only models that have been Decomposed"
- "can be made Physical or used as Vehicles."
- </text>
-
- <button bottom="440" left="90" width="80" follows="bottom|right" label="&lt;&lt; Back" name="decompose_back" height="20"/>
- <button left_pad="5" width="90" follows="bottom|right" label="Decompose" name="Decompose" height="20"/>
+ <check_box name="Close Holes (Slow)" follows="top|left" top_pad="10" height="15" label="Close Holes (slow)"/>
+
+ <button left="200" bottom_delta="0" width="90" follows="top|left" label="Analyze" name="Decompose" height="20"/>
</panel>
- <!-- PHYSICS STEP 3 -->
- <panel
+ <!-- PHYSICS SIMPLIFICATION -->
+ <panel
follows="top|left"
- name="physics step 3"
+ name="physics simplification"
left="0"
- top="0"
- width="260"
- height="450"
- visible="false">
+ top_pad="0"
+ width="300"
+ height="150"
+ visible="true"
+ border="true"
+ bevel_style="none" bg_alpha_color="0 0 0 0" bg_opaque_color="0 0 0 0.3">
<text follows="left|top" bottom="40" height="30" left="10" font="SansSerifBig">
- Step 3: Simplify
+ Step 2: Simplification
</text>
- <text left="10" top_pad="100" height="15" width="240" follows="top|left">
- Simplify Method:
+ <text left="10" top_pad="5" height="15" width="140" follows="top|left">
+ Method:
</text>
- <combo_box top_pad="5" height="20" width="100" follows="top|left" name="Simplify Method"/>
+ <combo_box left_pad="5" height="20" width="120" follows="top|left" name="Simplify Method"/>
- <slider name="Combine Quality" label="Combine Quality:" label_width="100" width="260" follows="top|left" top_pad="10" height="20"/>
- <slider name="Detail Scale" label="Detail Scale:" label_width="100" width="260" follows="top|left" top_pad="10" height="20"/>
- <slider name="Retain%" label="Retain %:" label_width="100" width="260" follows="top|left" top_pad="10" height="20"/>
-
- <button bottom="440" left="90" width="80" follows="bottom|right" label="&lt;&lt; Back" name="simplify_back" height="20"/>
- <button left_pad="5" width="90" follows="bottom|right" label="Simplify" name="Simplify" height="20"/>
+ <slider left="10" name="Combine Quality" label="Passes:" label_width="120" width="270" follows="top|left" top_pad="10" height="20"/>
+ <slider name="Detail Scale" label="Detail Scale:" label_width="120" width="270" follows="top|left" top_pad="10" height="20"/>
+ <slider name="Retain%" label="Retain:" label_width="120" width="270" follows="top|left" bottom_delta="0" left_delta="0" visible="false" height="20"/>
+ <button left="190" width="90" follows="top|left" label="Simplify" name="Simplify" height="20"/>
</panel>
<!-- INFO PANEL -->
<panel
left="0"
- top="35"
- width="260"
+ top_pad="0"
+ width="300"
height="100"
follows="left|top"
name="physics info"
- visible="false">
+ visible="true"
+ border="true"
+ bevel_style="none" bg_alpha_color="0 0 0 0" bg_opaque_color="0 0 0 0.3">
- <text follows="top|left" name="physics_triangles" top_pad="15" height="15" left="10">
- Triangles: [TRIANGLES]
- </text>
- <text follows="top|left" name="physics_hulls" top_pad="5" height="15">
- Hulls: [HULLS]
+ <slider name="physics_explode" follows="top|left" top="10" left="10" label="Preview Spread:" min_val="0.0" max_val="3.0" height="20" width="280"/>
+
+ <text follows="top|left" name="physics_triangles" top_pad="10" height="15" left="10">
+ Triangles: [TRIANGLES]
</text>
<text follows="top|left" name="physics_points" top_pad="5" height="15">
- Points: [POINTS]
+ Vertices: [POINTS]
</text>
-
- <text follows="top|left" left="140" width="100" bottom_delta="-40" height="15">
- Layer:
+ <text follows="top|left" name="physics_hulls" top_pad="5" height="15">
+ Hulls: [HULLS]
</text>
- <combo_box name="physics_layer" follows="top|left" width="100" height="20" top_pad="5"/>
- <slider name="physics_explode" follows="top|left" top_pad="25" left="10" label="Explode" min_val="0.0" max_val="3.0" height="20" width="240"/>
+
</panel>
</panel>
diff --git a/indra/newview/skins/default/xui/en/floater_model_wizard.xml b/indra/newview/skins/default/xui/en/floater_model_wizard.xml
index 4a4b8075c8..0f8844519a 100644
--- a/indra/newview/skins/default/xui/en/floater_model_wizard.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_wizard.xml
@@ -1,230 +1,624 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
-<floater
- legacy_header_height="18"
- layout="topleft"
- name="Model Wizard"
- help_topic="model_wizard"
- bg_opaque_image_overlay="0.5 0.5 0.5 1"
- height="450"
- save_rect="true"
- title="UPLOAD MODEL WIZARD"
- width="530">
- <panel
- height="600">
- <button
- top="30"
- left="410"
- height="32"
- name="upload"
- enabled="false"
- label="5. Upload"
- border="false"
- image_unselected="model_wizard/middle_button_off.png"
- image_selected="model_wizard/middle_button_press.png"
- image_hover_unselected="model_wizard/middle_button_over.png"
- image_disabled="model_wizard/middle_button_disabled.png"
- image_disabled_selected="model_wizard/middle_button_disabled.png"
- width="110"/>
- <button
- top="30"
- left="310"
- height="32"
- tab_stop="false"
- name="review"
- label="4. Review"
- enabled="false"
- border="false"
- image_unselected="model_wizard/middle_button_off.png"
- image_selected="model_wizard/middle_button_press.png"
- image_hover_unselected="model_wizard/middle_button_over.png"
- image_disabled="model_wizard/middle_button_disabled.png"
- image_disabled_selected="model_wizard/middle_button_disabled.png"
- width="110"/>
- <button
- top="30"
- left="210"
- height="32"
- name="physics"
- label="3. Physics"
- tab_stop="false"
- enabled="false"
- border="false"
- image_unselected="model_wizard/middle_button_off.png"
- image_selected="model_wizard/middle_button_press.png"
- image_hover_unselected="model_wizard/middle_button_over.png"
- image_disabled="model_wizard/middle_button_disabled.png"
- image_disabled_selected="model_wizard/middle_button_disabled.png"
- width="110"/>
- <button
- top="30"
- left="115"
- name="optimize"
- label="2. Optimize"
- tab_stop="false"
- height="32"
- border="false"
- image_unselected="model_wizard/middle_button_off.png"
- image_selected="model_wizard/middle_button_press.png"
- image_hover_unselected="model_wizard/middle_button_over.png"
- image_disabled="model_wizard/middle_button_disabled.png"
- image_disabled_selected="model_wizard/middle_button_disabled.png"
- width="110"/>
- <button
- top="30"
- left="15"
- name="choose_file"
- enabled="false"
- label="1. Choose File"
- height="32"
- image_unselected="model_wizard/left_button_off.png"
- image_selected="model_wizard/left_button_press.png"
- image_hover_unselected="model_wizard/left_button_over.png"
- image_disabled="model_wizard/left_button_disabled.png"
- image_disabled_selected="model_wizard/left_button_disabled.png"
- width="110"/>
- <panel
- top_pad="20"
- height="20"
- width="500"
- bg_opaque_color="DkGray2"
- background_visible="true"
- background_opaque="true"
- left="20">
- <text
- width="200"
- left="10"
- top="2"
- height="10"
- font="SansSerifBig"
- layout="topleft"
- >
- Upload Model
- </text></panel>
- <text
- top_pad="14"
- width="460"
- height="20"
- font="SansSerifSmall"
- layout="topleft"
- word_wrap="true"
- left_delta="0">
- This wizard will help you import mesh models to Second Life. First specify a file containing the model you wish to import. Second Life supports COLLADA (.dae) files.
- </text>
-
- <panel
- top_delta="40"
- left="15"
- height="240"
- width="500"
- bg_opaque_color="DkGray2"
- background_visible="true"
- background_opaque="true">
-
- <text
- type="string"
- length="1"
- follows="left|top"
- top="10"
- height="10"
- layout="topleft"
- left_delta="10"
- name="Cache location"
- width="300">
- Filename:
- </text>
- <line_editor
- border_style="line"
- border_thickness="1"
- follows="left|top"
- font="SansSerifSmall"
- height="20"
- layout="topleft"
- left_delta="0"
- max_length="4096"
- name="lod_file"
- top_pad="5"
- width="220" />
- <button
- follows="left|top"
- height="23"
- label="Browse..."
- label_selected="Browse..."
- layout="topleft"
- left_pad="10"
- name="browse"
- top_delta="-1"
- width="75">
- </button>
- <text
- top_delta="-15"
- width="200"
- height="15"
- font="SansSerifSmall"
- layout="topleft"
- left_pad="24">
- Model Preview:
- </text>
-
- <!-- Placeholder panel for 3D preview render -->
-
- <panel
- left_delta="-2"
- top_pad="0"
- name="preview_panel"
- bevel_style="none"
- border_style="line"
- border="true"
- height="150"
- follows="all"
- width="150">
- </panel>
- <text
- top_pad="10"
- width="130"
- height="15"
- left="340"
- word_wrap="true"
- >
- Dimensions (meters):
- </text>
- <text
- top_pad="5"
- width="150"
- height="15"
- name="import_dimensions"
- left_delta="0">
- X: [X] | Y: [Y] | Z: [Z]
- </text>
-
- <text
- top="100"
- width="320"
- height="40"
- left="10"
- word_wrap="true"
- >
- Note:
-Advanced users familiar with 3d content creation tools may prefer to use the Advanced Mesh Import window.
- </text>
- </panel>
- <button
- top="410"
- right="-175"
- width="80"
- height="20"
- label="&lt;&lt; Back" />
- <button
- top="410"
- right="-92"
- width="80"
- height="20"
- label="Next &gt;&gt; " />
- <button
- top="410"
- right="-15"
- width="70"
- height="20"
- label="Cancel" />
- </panel>
- <spinner visible="false" left="10" height="20" follows="top|left" width="80" top_pad="-50" value="1.0" min_val="0.01" max_val="64.0" name="import_scale"/>
-</floater>
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ legacy_header_height="18"
+ layout="topleft"
+ name="Model Wizard"
+ help_topic="model_wizard"
+ bg_opaque_image_overlay="0.5 0.5 0.5 1"
+ height="450"
+ save_rect="true"
+ title="UPLOAD MODEL WIZARD"
+ width="530">
+ <button
+ top="30"
+ tab_stop="false"
+ left="410"
+ height="32"
+ name="upload_btn"
+ enabled="false"
+ label="5. Upload"
+ border="false"
+ image_unselected="model_wizard/middle_button_off.png"
+ image_selected="model_wizard/middle_button_press.png"
+ image_hover_unselected="model_wizard/middle_button_over.png"
+ image_disabled="model_wizard/middle_button_disabled.png"
+ image_disabled_selected="model_wizard/middle_button_disabled.png"
+ width="110"/>
+ <button
+ top="30"
+ left="310"
+ height="32"
+ tab_stop="false"
+ name="review_btn"
+ label="4. Review"
+ enabled="false"
+ border="false"
+ image_unselected="model_wizard/middle_button_off.png"
+ image_selected="model_wizard/middle_button_press.png"
+ image_hover_unselected="model_wizard/middle_button_over.png"
+ image_disabled="model_wizard/middle_button_disabled.png"
+ image_disabled_selected="model_wizard/middle_button_disabled.png"
+ width="110"/>
+ <button
+ top="30"
+ left="210"
+ height="32"
+ name="physics_btn"
+ label="3. Physics"
+ tab_stop="false"
+ enabled="false"
+ border="false"
+ image_unselected="model_wizard/middle_button_off.png"
+ image_selected="model_wizard/middle_button_press.png"
+ image_hover_unselected="model_wizard/middle_button_over.png"
+ image_disabled="model_wizard/middle_button_disabled.png"
+ image_disabled_selected="model_wizard/middle_button_disabled.png"
+ width="110"/>
+ <button
+ top="30"
+ left="115"
+ name="optimize_btn"
+ label="2. Optimize"
+ tab_stop="false"
+ height="32"
+ border="false"
+ image_unselected="model_wizard/middle_button_off.png"
+ image_selected="model_wizard/middle_button_press.png"
+ image_hover_unselected="model_wizard/middle_button_over.png"
+ image_disabled="model_wizard/middle_button_disabled.png"
+ image_disabled_selected="model_wizard/middle_button_disabled.png"
+ width="110"/>
+ <button
+ top="30"
+ left="15"
+ name="choose_file_btn"
+ tab_stop="false"
+ enabled="false"
+ label="1. Choose File"
+ height="32"
+ image_unselected="model_wizard/left_button_off.png"
+ image_selected="model_wizard/left_button_press.png"
+ image_hover_unselected="model_wizard/left_button_over.png"
+ image_disabled="model_wizard/left_button_disabled.png"
+ image_disabled_selected="model_wizard/left_button_disabled.png"
+ width="110"/>
+ <panel
+ height="388"
+ top_pad="0"
+ name="choose_file_panel"
+ visible="false"
+ width="530"
+ left="0">
+ <panel
+ height="20"
+ top_pad="20"
+ width="500"
+ name="header_panel"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true"
+ left="20">
+ <text
+ width="200"
+ left="10"
+ top="2"
+ name="header_text"
+ height="10"
+ font="SansSerifBig"
+ layout="topleft">
+ Upload Model
+ </text>
+ </panel>
+ <text
+ top_pad="14"
+ width="460"
+ height="20"
+ name="description"
+ font="SansSerifSmall"
+ layout="topleft"
+ word_wrap="true"
+ left_delta="0">
+ This wizard will help you import mesh models to Second Life. First specify a file containing the model you wish to import. Second Life supports COLLADA (.dae) files.
+ </text>
+ <panel
+ top_delta="40"
+ left="15"
+ height="245"
+ width="500"
+ name="content"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true">
+ <text
+ type="string"
+ length="1"
+ text_color="White"
+ follows="left|top"
+ top="10"
+ height="10"
+ layout="topleft"
+ left_delta="10"
+ name="Cache location"
+ width="300">
+ Filename:
+ </text>
+ <line_editor
+ border_style="line"
+ border_thickness="1"
+ follows="left|top"
+ font="SansSerifSmall"
+ height="20"
+ layout="topleft"
+ left_delta="0"
+ max_length="4096"
+ name="lod_file"
+ top_pad="5"
+ width="220" />
+ <button
+ follows="left|top"
+ height="23"
+ label="Browse..."
+ label_selected="Browse..."
+ layout="topleft"
+ left_pad="5"
+ name="browse"
+ top_delta="-1"
+ width="85">
+ </button>
+ <text
+ top_delta="-15"
+ width="200"
+ height="15"
+ font="SansSerifSmall"
+ layout="topleft"
+ text_color="White"
+ left_pad="19">
+ Model Preview:
+ </text>
+ <!-- Placeholder panel for 3D preview render -->
+ <panel
+ left_delta="0"
+ top_pad="0"
+ name="preview_panel"
+ bevel_style="none"
+ border_style="line"
+ border="true"
+ height="150"
+ follows="all"
+ width="150">
+ </panel>
+ <text
+ top_pad="10"
+ width="130"
+ height="15"
+ left="340"
+ text_color="White"
+ word_wrap="true">
+ Dimensions (meters):
+ </text>
+ <text
+ top_pad="2"
+ width="160"
+ height="15"
+ name="import_dimensions"
+ left_delta="0">
+ X: [X] |Y: [Y] |Z: [Z]
+ </text>
+ <text
+ top="100"
+ width="320"
+ height="15"
+ left="10"
+ text_color="White"
+ word_wrap="true">
+ Note:
+ </text>
+ <text
+ top_pad="0"
+ width="320"
+ height="40"
+ left="10"
+ word_wrap="true">
+Advanced users familiar with 3d content creation tools may prefer to use the [secondlife:///app/floater/upload_model Advanced Mesh Import Window] .
+ </text>
+ </panel>
+ </panel>
+
+
+ <panel
+ height="388"
+ top_delta="0"
+ name="optimize_panel"
+ visible="true"
+ width="530"
+ left="0">
+ <panel
+ height="20"
+ top_pad="20"
+ name="header_panel"
+ width="500"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true"
+ left="20">
+ <text
+ width="200"
+ left="10"
+ name="header_text"
+ top="2"
+ height="10"
+ font="SansSerifBig"
+ layout="topleft">
+ Optimize
+ </text>
+ </panel>
+ <text
+ top_pad="14"
+ width="460"
+ height="20"
+ font="SansSerifSmall"
+ layout="topleft"
+ name="description"
+ word_wrap="true"
+ left_delta="0">
+ This wizard is optimizing your model. This may take several minutes. To stop the process click the back button
+ </text>
+ <panel
+ top_delta="40"
+ visible="false"
+ left="15"
+ height="245"
+ width="500"
+ name="content"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true">
+ <text
+ top="20"
+ width="300"
+ height="12"
+ font="SansSerifBold"
+ left="112">Generating Level of Detail</text>
+ <progress_bar
+ name="optimize_progress_bar"
+ image_fill="model_wizard\progress_light.png"
+ color_bg="1 1 1 1"
+ color_bar="1 1 1 0.96"
+ follows="left|right|top"
+ width="260"
+ height="16"
+ image_bar="model_wizard\progress_bar_bg.png"
+ top_pad="14"
+ left="110"/>
+ <icon
+ top_pad="10"
+ left_delta="0"
+ width="13"
+ height="12"
+ image_name="model_wizard\check_mark.png"/>
+ <text
+ top_delta="0"
+ left_delta="18"
+ name="high_detail_text"
+ width="200"
+ height="14">Generate Level of Detail: High</text>
+ <icon
+ top_pad="10"
+ left_delta="-18"
+ width="13"
+ height="12"
+ image_name="model_wizard\check_mark.png"/>
+ <text
+ top_delta="0"
+ left_delta="18"
+ name="medium_detail_text"
+ width="200"
+ height="14">Generate Level of Detail: Medium</text>
+ <icon
+ top_pad="10"
+ left_delta="-18"
+ width="13"
+ height="12"
+ image_name="model_wizard\check_mark.png"/>
+ <text
+ top_delta="0"
+ left_delta="18"
+ name="low_detail_text"
+ width="200"
+ height="14">Generate Level of Detail: Low</text>
+ <icon
+ top_pad="10"
+ left_delta="-18"
+ width="13"
+ height="12"
+ image_name="model_wizard\check_mark.png"/>
+ <text
+ top_delta="0"
+ left_delta="18"
+ name="lowest_detail_text"
+ width="200"
+ height="14">Generate Level of Detail: Lowest</text>
+ </panel>
+ <panel
+ top_delta="0"
+ left_delta="0"
+ height="245"
+ width="500"
+ name="content2"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true">
+ <text top="10" left="10" width="85" text_color="White" follows="left|top" height="15" name="lod_label">
+ Model Preview:
+ </text>
+ <combo_box left_pad="5" top_delta="-2" follows="left|top" list_position="below" height="18"
+ name="preview_lod_combo" width="90" tool_tip="LOD to view in preview render">
+ <combo_item name="high">
+ High
+ </combo_item>
+ <combo_item name="medium">
+ Medium
+ </combo_item>
+ <combo_item name="lowest">
+ Lowest
+ </combo_item>
+ <combo_item name="low">
+ Low
+ </combo_item>
+ </combo_box>
+ <panel
+ left="10"
+ top_pad="5"
+ name="preview_panel"
+ bevel_style="none"
+ border_style="line"
+ border="true"
+ height="175"
+ follows="all"
+ width="175">
+ </panel>
+ <text top="35" left="220" text_color="White" font="SansSerifSmallBold" width="300" height="4">Performance</text>
+ <text top="55" left="200" halign="center" width="130" word_wrap="true" font="SansSerifSmall" height="80">Faster rendering but less detailed; lowers Resource (prim) cost.</text>
+ <text top="35" left="380" text_color="White" font="SansSerifSmallBold" width="300" height="4">Accuracy</text>
+ <text top="55" left="360" halign="center" width="130" word_wrap="true" font="SansSerifSmall" height="80">More detailed model but slower; increases Resource (prim) cost.</text>
+
+ <slider
+ follows="left|top"
+ height="20"
+ increment="1"
+ layout="topleft"
+ left="200"
+ max_val="2"
+ initial_value="1"
+ min_val="0"
+ name="accuracy_slider"
+ show_text="false"
+ top="105"
+ width="290" />
+ <text
+ font="SansSerifSmall"
+ top_pad="1"
+ width="300"
+ left_delta="6"
+ height="4">' ' '</text>
+ <text top_delta="25" width="100" text_color="White" left_delta="50" height="20">Resource Cost:</text>
+ <text top_delta="25" width="100" text_color="White" left_delta="0" height="20">Upload Fee:</text>
+ <text
+ top_pad="15"
+ width="130"
+ height="15"
+ left="10"
+ text_color="White"
+ word_wrap="true">
+ Dimensions (meters):
+ </text>
+ <text
+ top_pad="0"
+ width="160"
+ height="15"
+ name="import_dimensions"
+ left_delta="0">
+ X: [X] |Y: [Y] |Z: [Z]
+ </text>
+ </panel>
+ </panel>
+
+
+
+ <panel
+ height="388"
+ top_delta="0"
+ name="physics_panel"
+ visible="false"
+ width="530"
+ left="0">
+ <panel
+ height="20"
+ top_pad="20"
+ name="header_panel"
+ width="500"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true"
+ left="20">
+ <text
+ width="200"
+ left="10"
+ name="header_text"
+ top="2"
+ height="10"
+ font="SansSerifBig"
+ layout="topleft">
+ Physics
+ </text>
+ </panel>
+ <text
+ top_pad="14"
+ width="460"
+ height="20"
+ font="SansSerifSmall"
+ layout="topleft"
+ name="description"
+ word_wrap="true"
+ left_delta="0">
+ The wizard will create a physical shape, which determines how the object interacts with other objects and avatars. Set the slider to the detail level most appropriate for how your object will be used:
+ </text>
+ <panel
+ top_delta="40"
+ left="15"
+ height="245"
+ width="500"
+ name="content"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true"/>
+ </panel>
+
+
+ <panel
+ height="388"
+ top_delta="0"
+ name="review_panel"
+ visible="false"
+ width="530"
+ left="0">
+ <panel
+ height="20"
+ top_pad="20"
+ name="header_panel"
+ width="500"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true"
+ left="20">
+ <text
+ width="200"
+ left="10"
+ name="header_text"
+ top="2"
+ height="10"
+ font="SansSerifBig"
+ layout="topleft">
+ Review
+ </text>
+ </panel>
+ <text
+ top_pad="14"
+ width="460"
+ height="20"
+ font="SansSerifSmall"
+ layout="topleft"
+ name="description"
+ word_wrap="true"
+ left_delta="0">
+ Review the details below then click. Upload to upload your model. Your L$ balance will be charged when you click Upload.
+ </text>
+ <panel
+ top_delta="40"
+ left="15"
+ height="245"
+ width="500"
+ name="content"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true"/>
+ </panel>
+
+
+
+
+ <panel
+ height="388"
+ top_delta="0"
+ name="upload_panel"
+ visible="false"
+ width="530"
+ left="0">
+ <panel
+ height="20"
+ top_pad="20"
+ name="header_panel"
+ width="500"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true"
+ left="20">
+ <text
+ width="200"
+ left="10"
+ name="header_text"
+ top="2"
+ height="10"
+ font="SansSerifBig"
+ layout="topleft">
+ Upload Complete!
+ </text>
+ </panel>
+ <text
+ top_pad="14"
+ width="460"
+ height="20"
+ font="SansSerifSmall"
+ layout="topleft"
+ name="description"
+ word_wrap="true"
+ left_delta="0">
+ Congratulations! Your model has been sucessfully uploaded. You will find the model in the Objects folder in your inventory.
+ </text>
+ <panel
+ top_delta="40"
+ left="15"
+ height="245"
+ width="500"
+ name="content"
+ bg_opaque_color="DkGray2"
+ background_visible="true"
+ background_opaque="true">
+ <button top="10" follows="top|left" height="20" label="Upload"
+ left="15" width="80" name="ok_btn" tool_tip="Upload to simulator"/>
+ </panel>
+ </panel>
+
+
+
+ <button
+ top="412"
+ right="-245"
+ width="90"
+ height="22"
+ name="back"
+ label="&lt;&lt; Back" />
+ <button
+ top_delta="0"
+ right="-150"
+ width="90"
+ height="22"
+ name="next"
+ label="Next &gt;&gt; " />
+ <button
+ top_delta="0"
+ right="-15"
+ width="90"
+ height="22"
+ name="cancel"
+ label="Cancel" />
+ <spinner visible="false" left="10" height="20" follows="top|left" width="80" top_pad="-50" value="1.0" min_val="0.01" max_val="64.0" name="import_scale"/>
+
+ <string name="status_idle">Idle</string>
+ <string name="status_reading_file">Loading...</string>
+ <string name="status_generating_meshes">Generating Meshes...</string>
+ <string name="high">High</string>
+ <string name="medium">Medium</string>
+ <string name="low">Low</string>
+ <string name="lowest">Lowest</string>
+ <string name="mesh_status_good">Ship it!</string>
+ <string name="mesh_status_na">N/A</string>
+ <string name="mesh_status_none">None</string>
+ <string name="mesh_status_submesh_mismatch">Levels of detail have a different number of textureable faces.</string>
+ <string name="mesh_status_mesh_mismatch">Levels of detail have a different number of mesh instances.</string>
+ <string name="mesh_status_too_many_vertices">Level of detail has too many vertices.</string>
+ <string name="mesh_status_missing_lod">Missing required level of detail.</string>
+ <string name="layer_all">All</string>
+ <!-- Text to display in physics layer combo box for "all layers" -->
+
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 6c1e9a3082..df556691f0 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -1000,6 +1000,29 @@
parameter="perm_prefs" />
</menu_item_call>
</menu>
+ <menu_item_separator/>
+ <menu_item_call
+ enabled="false"
+ label="Undo"
+ name="Undo"
+ shortcut="control|Z">
+ <on_click
+ function="Edit.Undo"
+ userdata="" />
+ <on_enable
+ function="Edit.EnableUndo" />
+ </menu_item_call>
+ <menu_item_call
+ enabled="false"
+ label="Redo"
+ name="Redo"
+ shortcut="control|Y">
+ <on_click
+ function="Edit.Redo"
+ userdata="" />
+ <on_enable
+ function="Edit.EnableRedo" />
+ </menu_item_call>
</menu>
<menu
create_jump_keys="true"
diff --git a/indra/newview/skins/default/xui/en/panel_my_profile.xml b/indra/newview/skins/default/xui/en/panel_my_profile.xml
index 1b41f602cd..5b8abaca6f 100644
--- a/indra/newview/skins/default/xui/en/panel_my_profile.xml
+++ b/indra/newview/skins/default/xui/en/panel_my_profile.xml
@@ -185,7 +185,7 @@
</expandable_text>
</panel>
<text
- follows="left|top"
+ follows="left|top|right"
height="15"
font.style="BOLD"
font="SansSerifMedium"
@@ -200,7 +200,7 @@
use_ellipses="true"
/>
<text
- follows="left|top"
+ follows="left|top|right"
font.style="BOLD"
height="10"
layout="topleft"
@@ -213,7 +213,7 @@
<text_editor
allow_scroll="false"
bg_visible="false"
- follows="left|top"
+ follows="left|top|right"
h_pad="0"
height="15"
layout="topleft"
@@ -226,7 +226,7 @@
width="300"
word_wrap="true" />
<text
- follows="left|top"
+ follows="left|top|right"
font.style="BOLD"
height="15"
layout="topleft"
@@ -250,7 +250,7 @@
<text_editor
allow_scroll="false"
bg_visible="false"
- follows="left|top"
+ follows="left|top|right"
h_pad="0"
height="28"
layout="topleft"
@@ -266,7 +266,7 @@
Linden.
</text_editor>
<text
- follows="left|top"
+ follows="left|top|right"
font.style="BOLD"
height="15"
layout="topleft"
@@ -277,7 +277,7 @@
value="Partner:"
width="300" />
<panel
- follows="left|top"
+ follows="left|top|right"
height="15"
layout="topleft"
left="10"
@@ -285,7 +285,7 @@
top_pad="0"
width="300">
<text
- follows="left|top"
+ follows="left|top|right"
height="10"
initial_value="(retrieving)"
layout="topleft"
@@ -297,7 +297,7 @@
width="300" />
</panel>
<text
- follows="left|top"
+ follows="left|top|right"
font.style="BOLD"
height="13"
layout="topleft"
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml
index d6e4c56113..37aab059a9 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_advanced.xml
@@ -82,7 +82,7 @@
control_name="AllowMultipleViewers"
follows="top|left"
height="15"
- label="Allow Multiple Viewer"
+ label="Allow Multiple Viewers"
layout="topleft"
left="30"
name="allow_multiple_viewer_check"
diff --git a/indra/newview/tests/llsimplestat_test.cpp b/indra/newview/tests/llsimplestat_test.cpp
new file mode 100644
index 0000000000..60a8cac995
--- /dev/null
+++ b/indra/newview/tests/llsimplestat_test.cpp
@@ -0,0 +1,586 @@
+/**
+ * @file llsimplestats_test.cpp
+ * @date 2010-10-22
+ * @brief Test cases for some of llsimplestat.h
+ *
+ * $LicenseInfo:firstyear=2010&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 "linden_common.h"
+
+#include <tut/tut.hpp>
+
+#include "lltut.h"
+#include "../llsimplestat.h"
+#include "llsd.h"
+#include "llmath.h"
+
+// @brief Used as a pointer cast type to get access to LLSimpleStatCounter
+class TutStatCounter: public LLSimpleStatCounter
+{
+public:
+ TutStatCounter(); // Not defined
+ ~TutStatCounter(); // Not defined
+ void operator=(const TutStatCounter &); // Not defined
+
+ void setRawCount(U32 c) { mCount = c; }
+ U32 getRawCount() const { return mCount; }
+};
+
+
+namespace tut
+{
+ struct stat_counter_index
+ {};
+ typedef test_group<stat_counter_index> stat_counter_index_t;
+ typedef stat_counter_index_t::object stat_counter_index_object_t;
+ tut::stat_counter_index_t tut_stat_counter_index("stat_counter_test");
+
+ // Testing LLSimpleStatCounter's external interface
+ template<> template<>
+ void stat_counter_index_object_t::test<1>()
+ {
+ LLSimpleStatCounter c1;
+ ensure("Initialized counter is zero", (0 == c1.getCount()));
+
+ ensure("Counter increment return is 1", (1 == ++c1));
+ ensure("Counter increment return is 2", (2 == ++c1));
+
+ ensure("Current counter is 2", (2 == c1.getCount()));
+
+ c1.reset();
+ ensure("Counter is 0 after reset", (0 == c1.getCount()));
+
+ ensure("Counter increment return is 1", (1 == ++c1));
+ }
+
+ // Testing LLSimpleStatCounter's internal state
+ template<> template<>
+ void stat_counter_index_object_t::test<2>()
+ {
+ LLSimpleStatCounter c1;
+ TutStatCounter * tc1 = (TutStatCounter *) &c1;
+
+ ensure("Initialized private counter is zero", (0 == tc1->getRawCount()));
+
+ ++c1;
+ ++c1;
+
+ ensure("Current private counter is 2", (2 == tc1->getRawCount()));
+
+ c1.reset();
+ ensure("Raw counter is 0 after reset", (0 == tc1->getRawCount()));
+ }
+
+ // Testing LLSimpleStatCounter's wrapping behavior
+ template<> template<>
+ void stat_counter_index_object_t::test<3>()
+ {
+ LLSimpleStatCounter c1;
+ TutStatCounter * tc1 = (TutStatCounter *) &c1;
+
+ tc1->setRawCount(U32_MAX);
+ ensure("Initialized private counter is zero", (U32_MAX == c1.getCount()));
+
+ ensure("Increment of max value wraps to 0", (0 == ++c1));
+ }
+
+ // Testing LLSimpleStatMMM's external behavior
+ template<> template<>
+ void stat_counter_index_object_t::test<4>()
+ {
+ LLSimpleStatMMM<> m1;
+ typedef LLSimpleStatMMM<>::Value lcl_float;
+ lcl_float zero(0);
+
+ // Freshly-constructed
+ ensure("Constructed MMM<> has 0 count", (0 == m1.getCount()));
+ ensure("Constructed MMM<> has 0 min", (zero == m1.getMin()));
+ ensure("Constructed MMM<> has 0 max", (zero == m1.getMax()));
+ ensure("Constructed MMM<> has 0 mean no div-by-zero", (zero == m1.getMean()));
+
+ // Single insert
+ m1.record(1.0);
+ ensure("Single insert MMM<> has 1 count", (1 == m1.getCount()));
+ ensure("Single insert MMM<> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("Single insert MMM<> has 1.0 max", (1.0 == m1.getMax()));
+ ensure("Single insert MMM<> has 1.0 mean", (1.0 == m1.getMean()));
+
+ // Second insert
+ m1.record(3.0);
+ ensure("2nd insert MMM<> has 2 count", (2 == m1.getCount()));
+ ensure("2nd insert MMM<> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("2nd insert MMM<> has 3.0 max", (3.0 == m1.getMax()));
+ ensure_approximately_equals("2nd insert MMM<> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1);
+
+ // Third insert
+ m1.record(5.0);
+ ensure("3rd insert MMM<> has 3 count", (3 == m1.getCount()));
+ ensure("3rd insert MMM<> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("3rd insert MMM<> has 5.0 max", (5.0 == m1.getMax()));
+ ensure_approximately_equals("3rd insert MMM<> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1);
+
+ // Fourth insert
+ m1.record(1000000.0);
+ ensure("4th insert MMM<> has 4 count", (4 == m1.getCount()));
+ ensure("4th insert MMM<> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("4th insert MMM<> has 100000.0 max", (1000000.0 == m1.getMax()));
+ ensure_approximately_equals("4th insert MMM<> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1);
+
+ // Reset
+ m1.reset();
+ ensure("Reset MMM<> has 0 count", (0 == m1.getCount()));
+ ensure("Reset MMM<> has 0 min", (zero == m1.getMin()));
+ ensure("Reset MMM<> has 0 max", (zero == m1.getMax()));
+ ensure("Reset MMM<> has 0 mean no div-by-zero", (zero == m1.getMean()));
+ }
+
+ // Testing LLSimpleStatMMM's response to large values
+ template<> template<>
+ void stat_counter_index_object_t::test<5>()
+ {
+ LLSimpleStatMMM<> m1;
+ typedef LLSimpleStatMMM<>::Value lcl_float;
+ lcl_float zero(0);
+
+ // Insert overflowing values
+ const lcl_float bignum(F32_MAX / 2);
+
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(zero);
+
+ ensure("Overflowed MMM<> has 8 count", (8 == m1.getCount()));
+ ensure("Overflowed MMM<> has 0 min", (zero == m1.getMin()));
+ ensure("Overflowed MMM<> has huge max", (bignum == m1.getMax()));
+ ensure("Overflowed MMM<> has fetchable mean", (1.0 == m1.getMean() || true));
+ // We should be infinte but not interested in proving the IEEE standard here.
+ LLSD sd1(m1.getMean());
+ // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl;
+ ensure("Overflowed MMM<> produces LLSDable Real", (sd1.isReal()));
+ }
+
+ // Testing LLSimpleStatMMM<F32>'s external behavior
+ template<> template<>
+ void stat_counter_index_object_t::test<6>()
+ {
+ LLSimpleStatMMM<F32> m1;
+ typedef LLSimpleStatMMM<F32>::Value lcl_float;
+ lcl_float zero(0);
+
+ // Freshly-constructed
+ ensure("Constructed MMM<F32> has 0 count", (0 == m1.getCount()));
+ ensure("Constructed MMM<F32> has 0 min", (zero == m1.getMin()));
+ ensure("Constructed MMM<F32> has 0 max", (zero == m1.getMax()));
+ ensure("Constructed MMM<F32> has 0 mean no div-by-zero", (zero == m1.getMean()));
+
+ // Single insert
+ m1.record(1.0);
+ ensure("Single insert MMM<F32> has 1 count", (1 == m1.getCount()));
+ ensure("Single insert MMM<F32> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("Single insert MMM<F32> has 1.0 max", (1.0 == m1.getMax()));
+ ensure("Single insert MMM<F32> has 1.0 mean", (1.0 == m1.getMean()));
+
+ // Second insert
+ m1.record(3.0);
+ ensure("2nd insert MMM<F32> has 2 count", (2 == m1.getCount()));
+ ensure("2nd insert MMM<F32> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("2nd insert MMM<F32> has 3.0 max", (3.0 == m1.getMax()));
+ ensure_approximately_equals("2nd insert MMM<F32> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1);
+
+ // Third insert
+ m1.record(5.0);
+ ensure("3rd insert MMM<F32> has 3 count", (3 == m1.getCount()));
+ ensure("3rd insert MMM<F32> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("3rd insert MMM<F32> has 5.0 max", (5.0 == m1.getMax()));
+ ensure_approximately_equals("3rd insert MMM<F32> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1);
+
+ // Fourth insert
+ m1.record(1000000.0);
+ ensure("4th insert MMM<F32> has 4 count", (4 == m1.getCount()));
+ ensure("4th insert MMM<F32> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("4th insert MMM<F32> has 1000000.0 max", (1000000.0 == m1.getMax()));
+ ensure_approximately_equals("4th insert MMM<F32> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1);
+
+ // Reset
+ m1.reset();
+ ensure("Reset MMM<F32> has 0 count", (0 == m1.getCount()));
+ ensure("Reset MMM<F32> has 0 min", (zero == m1.getMin()));
+ ensure("Reset MMM<F32> has 0 max", (zero == m1.getMax()));
+ ensure("Reset MMM<F32> has 0 mean no div-by-zero", (zero == m1.getMean()));
+ }
+
+ // Testing LLSimpleStatMMM's response to large values
+ template<> template<>
+ void stat_counter_index_object_t::test<7>()
+ {
+ LLSimpleStatMMM<F32> m1;
+ typedef LLSimpleStatMMM<F32>::Value lcl_float;
+ lcl_float zero(0);
+
+ // Insert overflowing values
+ const lcl_float bignum(F32_MAX / 2);
+
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(zero);
+
+ ensure("Overflowed MMM<F32> has 8 count", (8 == m1.getCount()));
+ ensure("Overflowed MMM<F32> has 0 min", (zero == m1.getMin()));
+ ensure("Overflowed MMM<F32> has huge max", (bignum == m1.getMax()));
+ ensure("Overflowed MMM<F32> has fetchable mean", (1.0 == m1.getMean() || true));
+ // We should be infinte but not interested in proving the IEEE standard here.
+ LLSD sd1(m1.getMean());
+ // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl;
+ ensure("Overflowed MMM<F32> produces LLSDable Real", (sd1.isReal()));
+ }
+
+ // Testing LLSimpleStatMMM<F64>'s external behavior
+ template<> template<>
+ void stat_counter_index_object_t::test<8>()
+ {
+ LLSimpleStatMMM<F64> m1;
+ typedef LLSimpleStatMMM<F64>::Value lcl_float;
+ lcl_float zero(0);
+
+ // Freshly-constructed
+ ensure("Constructed MMM<F64> has 0 count", (0 == m1.getCount()));
+ ensure("Constructed MMM<F64> has 0 min", (zero == m1.getMin()));
+ ensure("Constructed MMM<F64> has 0 max", (zero == m1.getMax()));
+ ensure("Constructed MMM<F64> has 0 mean no div-by-zero", (zero == m1.getMean()));
+
+ // Single insert
+ m1.record(1.0);
+ ensure("Single insert MMM<F64> has 1 count", (1 == m1.getCount()));
+ ensure("Single insert MMM<F64> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("Single insert MMM<F64> has 1.0 max", (1.0 == m1.getMax()));
+ ensure("Single insert MMM<F64> has 1.0 mean", (1.0 == m1.getMean()));
+
+ // Second insert
+ m1.record(3.0);
+ ensure("2nd insert MMM<F64> has 2 count", (2 == m1.getCount()));
+ ensure("2nd insert MMM<F64> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("2nd insert MMM<F64> has 3.0 max", (3.0 == m1.getMax()));
+ ensure_approximately_equals("2nd insert MMM<F64> has 2.0 mean", m1.getMean(), lcl_float(2.0), 1);
+
+ // Third insert
+ m1.record(5.0);
+ ensure("3rd insert MMM<F64> has 3 count", (3 == m1.getCount()));
+ ensure("3rd insert MMM<F64> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("3rd insert MMM<F64> has 5.0 max", (5.0 == m1.getMax()));
+ ensure_approximately_equals("3rd insert MMM<F64> has 3.0 mean", m1.getMean(), lcl_float(3.0), 1);
+
+ // Fourth insert
+ m1.record(1000000.0);
+ ensure("4th insert MMM<F64> has 4 count", (4 == m1.getCount()));
+ ensure("4th insert MMM<F64> has 1.0 min", (1.0 == m1.getMin()));
+ ensure("4th insert MMM<F64> has 1000000.0 max", (1000000.0 == m1.getMax()));
+ ensure_approximately_equals("4th insert MMM<F64> has 250002.0 mean", m1.getMean(), lcl_float(250002.0), 1);
+
+ // Reset
+ m1.reset();
+ ensure("Reset MMM<F64> has 0 count", (0 == m1.getCount()));
+ ensure("Reset MMM<F64> has 0 min", (zero == m1.getMin()));
+ ensure("Reset MMM<F64> has 0 max", (zero == m1.getMax()));
+ ensure("Reset MMM<F64> has 0 mean no div-by-zero", (zero == m1.getMean()));
+ }
+
+ // Testing LLSimpleStatMMM's response to large values
+ template<> template<>
+ void stat_counter_index_object_t::test<9>()
+ {
+ LLSimpleStatMMM<F64> m1;
+ typedef LLSimpleStatMMM<F64>::Value lcl_float;
+ lcl_float zero(0);
+
+ // Insert overflowing values
+ const lcl_float bignum(F64_MAX / 2);
+
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(zero);
+
+ ensure("Overflowed MMM<F64> has 8 count", (8 == m1.getCount()));
+ ensure("Overflowed MMM<F64> has 0 min", (zero == m1.getMin()));
+ ensure("Overflowed MMM<F64> has huge max", (bignum == m1.getMax()));
+ ensure("Overflowed MMM<F64> has fetchable mean", (1.0 == m1.getMean() || true));
+ // We should be infinte but not interested in proving the IEEE standard here.
+ LLSD sd1(m1.getMean());
+ // std::cout << "Thingy: " << m1.getMean() << " and as LLSD: " << sd1 << std::endl;
+ ensure("Overflowed MMM<F64> produces LLSDable Real", (sd1.isReal()));
+ }
+
+ // Testing LLSimpleStatMMM<U64>'s external behavior
+ template<> template<>
+ void stat_counter_index_object_t::test<10>()
+ {
+ LLSimpleStatMMM<U64> m1;
+ typedef LLSimpleStatMMM<U64>::Value lcl_int;
+ lcl_int zero(0);
+
+ // Freshly-constructed
+ ensure("Constructed MMM<U64> has 0 count", (0 == m1.getCount()));
+ ensure("Constructed MMM<U64> has 0 min", (zero == m1.getMin()));
+ ensure("Constructed MMM<U64> has 0 max", (zero == m1.getMax()));
+ ensure("Constructed MMM<U64> has 0 mean no div-by-zero", (zero == m1.getMean()));
+
+ // Single insert
+ m1.record(1);
+ ensure("Single insert MMM<U64> has 1 count", (1 == m1.getCount()));
+ ensure("Single insert MMM<U64> has 1 min", (1 == m1.getMin()));
+ ensure("Single insert MMM<U64> has 1 max", (1 == m1.getMax()));
+ ensure("Single insert MMM<U64> has 1 mean", (1 == m1.getMean()));
+
+ // Second insert
+ m1.record(3);
+ ensure("2nd insert MMM<U64> has 2 count", (2 == m1.getCount()));
+ ensure("2nd insert MMM<U64> has 1 min", (1 == m1.getMin()));
+ ensure("2nd insert MMM<U64> has 3 max", (3 == m1.getMax()));
+ ensure("2nd insert MMM<U64> has 2 mean", (2 == m1.getMean()));
+
+ // Third insert
+ m1.record(5);
+ ensure("3rd insert MMM<U64> has 3 count", (3 == m1.getCount()));
+ ensure("3rd insert MMM<U64> has 1 min", (1 == m1.getMin()));
+ ensure("3rd insert MMM<U64> has 5 max", (5 == m1.getMax()));
+ ensure("3rd insert MMM<U64> has 3 mean", (3 == m1.getMean()));
+
+ // Fourth insert
+ m1.record(U64L(1000000000000));
+ ensure("4th insert MMM<U64> has 4 count", (4 == m1.getCount()));
+ ensure("4th insert MMM<U64> has 1 min", (1 == m1.getMin()));
+ ensure("4th insert MMM<U64> has 1000000000000ULL max", (U64L(1000000000000) == m1.getMax()));
+ ensure("4th insert MMM<U64> has 250000000002ULL mean", (U64L( 250000000002) == m1.getMean()));
+
+ // Reset
+ m1.reset();
+ ensure("Reset MMM<U64> has 0 count", (0 == m1.getCount()));
+ ensure("Reset MMM<U64> has 0 min", (zero == m1.getMin()));
+ ensure("Reset MMM<U64> has 0 max", (zero == m1.getMax()));
+ ensure("Reset MMM<U64> has 0 mean no div-by-zero", (zero == m1.getMean()));
+ }
+
+ // Testing LLSimpleStatMMM's response to large values
+ template<> template<>
+ void stat_counter_index_object_t::test<11>()
+ {
+ LLSimpleStatMMM<U64> m1;
+ typedef LLSimpleStatMMM<U64>::Value lcl_int;
+ lcl_int zero(0);
+
+ // Insert overflowing values
+ const lcl_int bignum(U64L(0xffffffffffffffff) / 2);
+
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(bignum);
+ m1.record(zero);
+
+ ensure("Overflowed MMM<U64> has 8 count", (8 == m1.getCount()));
+ ensure("Overflowed MMM<U64> has 0 min", (zero == m1.getMin()));
+ ensure("Overflowed MMM<U64> has huge max", (bignum == m1.getMax()));
+ ensure("Overflowed MMM<U64> has fetchable mean", (zero == m1.getMean() || true));
+ }
+
+ // Testing LLSimpleStatCounter's merge() method
+ template<> template<>
+ void stat_counter_index_object_t::test<12>()
+ {
+ LLSimpleStatCounter c1;
+ LLSimpleStatCounter c2;
+
+ ++c1;
+ ++c1;
+ ++c1;
+ ++c1;
+
+ ++c2;
+ ++c2;
+ c2.merge(c1);
+
+ ensure_equals("4 merged into 2 results in 6", 6, c2.getCount());
+
+ ensure_equals("Source of merge is undamaged", 4, c1.getCount());
+ }
+
+ // Testing LLSimpleStatMMM's merge() method
+ template<> template<>
+ void stat_counter_index_object_t::test<13>()
+ {
+ LLSimpleStatMMM<> m1;
+ LLSimpleStatMMM<> m2;
+
+ m1.record(3.5);
+ m1.record(4.5);
+ m1.record(5.5);
+ m1.record(6.5);
+
+ m2.record(5.0);
+ m2.record(7.0);
+ m2.record(9.0);
+
+ m2.merge(m1);
+
+ ensure_equals("Count after merge (p1)", 7, m2.getCount());
+ ensure_approximately_equals("Min after merge (p1)", F32(3.5), m2.getMin(), 22);
+ ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22);
+ ensure_approximately_equals("Mean after merge (p1)", F32(41.000/7.000), m2.getMean(), 22);
+
+
+ ensure_equals("Source count of merge is undamaged (p1)", 4, m1.getCount());
+ ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(3.5), m1.getMin(), 22);
+ ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(6.5), m1.getMax(), 22);
+ ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(5.0), m1.getMean(), 22);
+
+ m2.reset();
+
+ m2.record(-22.0);
+ m2.record(-1.0);
+ m2.record(30.0);
+
+ m2.merge(m1);
+
+ ensure_equals("Count after merge (p2)", 7, m2.getCount());
+ ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22);
+ ensure_approximately_equals("Max after merge (p2)", F32(30.0), m2.getMax(), 22);
+ ensure_approximately_equals("Mean after merge (p2)", F32(27.000/7.000), m2.getMean(), 22);
+
+ }
+
+ // Testing LLSimpleStatMMM's merge() method when src contributes nothing
+ template<> template<>
+ void stat_counter_index_object_t::test<14>()
+ {
+ LLSimpleStatMMM<> m1;
+ LLSimpleStatMMM<> m2;
+
+ m2.record(5.0);
+ m2.record(7.0);
+ m2.record(9.0);
+
+ m2.merge(m1);
+
+ ensure_equals("Count after merge (p1)", 3, m2.getCount());
+ ensure_approximately_equals("Min after merge (p1)", F32(5.0), m2.getMin(), 22);
+ ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22);
+ ensure_approximately_equals("Mean after merge (p1)", F32(7.000), m2.getMean(), 22);
+
+ ensure_equals("Source count of merge is undamaged (p1)", 0, m1.getCount());
+ ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(0), m1.getMin(), 22);
+ ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(0), m1.getMax(), 22);
+ ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(0), m1.getMean(), 22);
+
+ m2.reset();
+
+ m2.record(-22.0);
+ m2.record(-1.0);
+
+ m2.merge(m1);
+
+ ensure_equals("Count after merge (p2)", 2, m2.getCount());
+ ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22);
+ ensure_approximately_equals("Max after merge (p2)", F32(-1.0), m2.getMax(), 22);
+ ensure_approximately_equals("Mean after merge (p2)", F32(-11.5), m2.getMean(), 22);
+ }
+
+ // Testing LLSimpleStatMMM's merge() method when dst contributes nothing
+ template<> template<>
+ void stat_counter_index_object_t::test<15>()
+ {
+ LLSimpleStatMMM<> m1;
+ LLSimpleStatMMM<> m2;
+
+ m1.record(5.0);
+ m1.record(7.0);
+ m1.record(9.0);
+
+ m2.merge(m1);
+
+ ensure_equals("Count after merge (p1)", 3, m2.getCount());
+ ensure_approximately_equals("Min after merge (p1)", F32(5.0), m2.getMin(), 22);
+ ensure_approximately_equals("Max after merge (p1)", F32(9.0), m2.getMax(), 22);
+ ensure_approximately_equals("Mean after merge (p1)", F32(7.000), m2.getMean(), 22);
+
+ ensure_equals("Source count of merge is undamaged (p1)", 3, m1.getCount());
+ ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(5.0), m1.getMin(), 22);
+ ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(9.0), m1.getMax(), 22);
+ ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(7.0), m1.getMean(), 22);
+
+ m1.reset();
+ m2.reset();
+
+ m1.record(-22.0);
+ m1.record(-1.0);
+
+ m2.merge(m1);
+
+ ensure_equals("Count after merge (p2)", 2, m2.getCount());
+ ensure_approximately_equals("Min after merge (p2)", F32(-22.0), m2.getMin(), 22);
+ ensure_approximately_equals("Max after merge (p2)", F32(-1.0), m2.getMax(), 22);
+ ensure_approximately_equals("Mean after merge (p2)", F32(-11.5), m2.getMean(), 22);
+ }
+
+ // Testing LLSimpleStatMMM's merge() method when neither dst nor src contributes
+ template<> template<>
+ void stat_counter_index_object_t::test<16>()
+ {
+ LLSimpleStatMMM<> m1;
+ LLSimpleStatMMM<> m2;
+
+ m2.merge(m1);
+
+ ensure_equals("Count after merge (p1)", 0, m2.getCount());
+ ensure_approximately_equals("Min after merge (p1)", F32(0), m2.getMin(), 22);
+ ensure_approximately_equals("Max after merge (p1)", F32(0), m2.getMax(), 22);
+ ensure_approximately_equals("Mean after merge (p1)", F32(0), m2.getMean(), 22);
+
+ ensure_equals("Source count of merge is undamaged (p1)", 0, m1.getCount());
+ ensure_approximately_equals("Source min of merge is undamaged (p1)", F32(0), m1.getMin(), 22);
+ ensure_approximately_equals("Source max of merge is undamaged (p1)", F32(0), m1.getMax(), 22);
+ ensure_approximately_equals("Source mean of merge is undamaged (p1)", F32(0), m1.getMean(), 22);
+ }
+}
diff --git a/indra/newview/tests/llviewerassetstats_test.cpp b/indra/newview/tests/llviewerassetstats_test.cpp
new file mode 100644
index 0000000000..1bb4fb7c0c
--- /dev/null
+++ b/indra/newview/tests/llviewerassetstats_test.cpp
@@ -0,0 +1,990 @@
+/**
+ * @file llviewerassetstats_tut.cpp
+ * @date 2010-10-28
+ * @brief Test cases for some of newview/llviewerassetstats.cpp
+ *
+ * $LicenseInfo:firstyear=2010&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 "linden_common.h"
+
+#include <tut/tut.hpp>
+#include <iostream>
+
+#include "lltut.h"
+#include "../llviewerassetstats.h"
+#include "lluuid.h"
+#include "llsdutil.h"
+#include "llregionhandle.h"
+
+static const char * all_keys[] =
+{
+ "duration",
+ "fps",
+ "get_other",
+ "get_texture_temp_http",
+ "get_texture_temp_udp",
+ "get_texture_non_temp_http",
+ "get_texture_non_temp_udp",
+ "get_wearable_udp",
+ "get_sound_udp",
+ "get_gesture_udp"
+};
+
+static const char * resp_keys[] =
+{
+ "get_other",
+ "get_texture_temp_http",
+ "get_texture_temp_udp",
+ "get_texture_non_temp_http",
+ "get_texture_non_temp_udp",
+ "get_wearable_udp",
+ "get_sound_udp",
+ "get_gesture_udp"
+};
+
+static const char * sub_keys[] =
+{
+ "dequeued",
+ "enqueued",
+ "resp_count",
+ "resp_max",
+ "resp_min",
+ "resp_mean"
+};
+
+static const char * mmm_resp_keys[] =
+{
+ "fps"
+};
+
+static const char * mmm_sub_keys[] =
+{
+ "count",
+ "max",
+ "min",
+ "mean"
+};
+
+static const LLUUID region1("4e2d81a3-6263-6ffe-ad5c-8ce04bee07e8");
+static const LLUUID region2("68762cc8-b68b-4e45-854b-e830734f2d4a");
+static const U64 region1_handle(0x0000040000003f00ULL);
+static const U64 region2_handle(0x0000030000004200ULL);
+static const std::string region1_handle_str("0000040000003f00");
+static const std::string region2_handle_str("0000030000004200");
+
+#if 0
+static bool
+is_empty_map(const LLSD & sd)
+{
+ return sd.isMap() && 0 == sd.size();
+}
+
+static bool
+is_single_key_map(const LLSD & sd, const std::string & key)
+{
+ return sd.isMap() && 1 == sd.size() && sd.has(key);
+}
+#endif
+
+static bool
+is_double_key_map(const LLSD & sd, const std::string & key1, const std::string & key2)
+{
+ return sd.isMap() && 2 == sd.size() && sd.has(key1) && sd.has(key2);
+}
+
+static bool
+is_no_stats_map(const LLSD & sd)
+{
+ return is_double_key_map(sd, "duration", "regions");
+}
+
+static bool
+is_single_slot_array(const LLSD & sd, U64 region_handle)
+{
+ U32 grid_x(0), grid_y(0);
+ grid_from_region_handle(region_handle, &grid_x, &grid_y);
+
+ return (sd.isArray() &&
+ 1 == sd.size() &&
+ sd[0].has("grid_x") &&
+ sd[0].has("grid_y") &&
+ sd[0]["grid_x"].isInteger() &&
+ sd[0]["grid_y"].isInteger() &&
+ grid_x == sd[0]["grid_x"].asInteger() &&
+ grid_y == sd[0]["grid_y"].asInteger());
+}
+
+static bool
+is_double_slot_array(const LLSD & sd, U64 region_handle1, U64 region_handle2)
+{
+ U32 grid_x1(0), grid_y1(0);
+ U32 grid_x2(0), grid_y2(0);
+ grid_from_region_handle(region_handle1, &grid_x1, &grid_y1);
+ grid_from_region_handle(region_handle2, &grid_x2, &grid_y2);
+
+ return (sd.isArray() &&
+ 2 == sd.size() &&
+ sd[0].has("grid_x") &&
+ sd[0].has("grid_y") &&
+ sd[0]["grid_x"].isInteger() &&
+ sd[0]["grid_y"].isInteger() &&
+ sd[1].has("grid_x") &&
+ sd[1].has("grid_y") &&
+ sd[1]["grid_x"].isInteger() &&
+ sd[1]["grid_y"].isInteger() &&
+ ((grid_x1 == sd[0]["grid_x"].asInteger() &&
+ grid_y1 == sd[0]["grid_y"].asInteger() &&
+ grid_x2 == sd[1]["grid_x"].asInteger() &&
+ grid_y2 == sd[1]["grid_y"].asInteger()) ||
+ (grid_x1 == sd[1]["grid_x"].asInteger() &&
+ grid_y1 == sd[1]["grid_y"].asInteger() &&
+ grid_x2 == sd[0]["grid_x"].asInteger() &&
+ grid_y2 == sd[0]["grid_y"].asInteger())));
+}
+
+static LLSD
+get_region(const LLSD & sd, U64 region_handle1)
+{
+ U32 grid_x(0), grid_y(0);
+ grid_from_region_handle(region_handle1, &grid_x, &grid_y);
+
+ for (LLSD::array_const_iterator it(sd["regions"].beginArray());
+ sd["regions"].endArray() != it;
+ ++it)
+ {
+ if ((*it).has("grid_x") &&
+ (*it).has("grid_y") &&
+ (*it)["grid_x"].isInteger() &&
+ (*it)["grid_y"].isInteger() &&
+ (*it)["grid_x"].asInteger() == grid_x &&
+ (*it)["grid_y"].asInteger() == grid_y)
+ {
+ return *it;
+ }
+ }
+ return LLSD();
+}
+
+namespace tut
+{
+ struct tst_viewerassetstats_index
+ {};
+ typedef test_group<tst_viewerassetstats_index> tst_viewerassetstats_index_t;
+ typedef tst_viewerassetstats_index_t::object tst_viewerassetstats_index_object_t;
+ tut::tst_viewerassetstats_index_t tut_tst_viewerassetstats_index("tst_viewerassetstats_test");
+
+ // Testing free functions without global stats allocated
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<1>()
+ {
+ // Check that helpers aren't bothered by missing global stats
+ ensure("Global gViewerAssetStatsMain should be NULL", (NULL == gViewerAssetStatsMain));
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+
+ LLViewerAssetStatsFF::record_response_main(LLViewerAssetType::AT_GESTURE, false, false, 12300000ULL);
+ }
+
+ // Create a non-global instance and check the structure
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<2>()
+ {
+ ensure("Global gViewerAssetStatsMain should be NULL", (NULL == gViewerAssetStatsMain));
+
+ LLViewerAssetStats * it = new LLViewerAssetStats();
+
+ ensure("Global gViewerAssetStatsMain should still be NULL", (NULL == gViewerAssetStatsMain));
+
+ LLSD sd_full = it->asLLSD(false);
+
+ // Default (NULL) region ID doesn't produce LLSD results so should
+ // get an empty map back from output
+ ensure("Stat-less LLSD initially", is_no_stats_map(sd_full));
+
+ // Once the region is set, we will get a response even with no data collection
+ it->setRegion(region1_handle);
+ sd_full = it->asLLSD(false);
+ ensure("Correct single-key LLSD map root", is_double_key_map(sd_full, "duration", "regions"));
+ ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd_full["regions"], region1_handle));
+
+ LLSD sd = sd_full["regions"][0];
+
+ delete it;
+
+ // Check the structure of the LLSD
+ for (int i = 0; i < LL_ARRAY_SIZE(all_keys); ++i)
+ {
+ std::string line = llformat("Has '%s' key", all_keys[i]);
+ ensure(line, sd.has(all_keys[i]));
+ }
+
+ for (int i = 0; i < LL_ARRAY_SIZE(resp_keys); ++i)
+ {
+ for (int j = 0; j < LL_ARRAY_SIZE(sub_keys); ++j)
+ {
+ std::string line = llformat("Key '%s' has '%s' key", resp_keys[i], sub_keys[j]);
+ ensure(line, sd[resp_keys[i]].has(sub_keys[j]));
+ }
+ }
+
+ for (int i = 0; i < LL_ARRAY_SIZE(mmm_resp_keys); ++i)
+ {
+ for (int j = 0; j < LL_ARRAY_SIZE(mmm_sub_keys); ++j)
+ {
+ std::string line = llformat("Key '%s' has '%s' key", mmm_resp_keys[i], mmm_sub_keys[j]);
+ ensure(line, sd[mmm_resp_keys[i]].has(mmm_sub_keys[j]));
+ }
+ }
+ }
+
+ // Create a non-global instance and check some content
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<3>()
+ {
+ LLViewerAssetStats * it = new LLViewerAssetStats();
+ it->setRegion(region1_handle);
+
+ LLSD sd = it->asLLSD(false);
+ ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration"));
+ ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle));
+ sd = sd[0];
+
+ delete it;
+
+ // Check a few points on the tree for content
+ ensure("sd[get_texture_temp_http][dequeued] is 0", (0 == sd["get_texture_temp_http"]["dequeued"].asInteger()));
+ ensure("sd[get_sound_udp][resp_min] is 0", (0.0 == sd["get_sound_udp"]["resp_min"].asReal()));
+ }
+
+ // Create a global instance and verify free functions do something useful
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<4>()
+ {
+ gViewerAssetStatsMain = new LLViewerAssetStats();
+ LLViewerAssetStatsFF::set_region_main(region1_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false);
+
+ LLSD sd = gViewerAssetStatsMain->asLLSD(false);
+ ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration"));
+ ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle));
+ sd = sd["regions"][0];
+
+ // Check a few points on the tree for content
+ ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger()));
+ ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger()));
+ ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger()));
+
+ // Reset and check zeros...
+ // Reset leaves current region in place
+ gViewerAssetStatsMain->reset();
+ sd = gViewerAssetStatsMain->asLLSD(false)["regions"][region1_handle_str];
+
+ delete gViewerAssetStatsMain;
+ gViewerAssetStatsMain = NULL;
+
+ ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger()));
+ }
+
+ // Create two global instances and verify no interactions
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<5>()
+ {
+ gViewerAssetStatsThread1 = new LLViewerAssetStats();
+ gViewerAssetStatsMain = new LLViewerAssetStats();
+ LLViewerAssetStatsFF::set_region_main(region1_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false);
+
+ LLSD sd = gViewerAssetStatsThread1->asLLSD(false);
+ ensure("Other collector is empty", is_no_stats_map(sd));
+ sd = gViewerAssetStatsMain->asLLSD(false);
+ ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration"));
+ ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle));
+ sd = sd["regions"][0];
+
+ // Check a few points on the tree for content
+ ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger()));
+ ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger()));
+ ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger()));
+
+ // Reset and check zeros...
+ // Reset leaves current region in place
+ gViewerAssetStatsMain->reset();
+ sd = gViewerAssetStatsMain->asLLSD(false)["regions"][0];
+
+ delete gViewerAssetStatsMain;
+ gViewerAssetStatsMain = NULL;
+ delete gViewerAssetStatsThread1;
+ gViewerAssetStatsThread1 = NULL;
+
+ ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger()));
+ }
+
+ // Check multiple region collection
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<6>()
+ {
+ gViewerAssetStatsMain = new LLViewerAssetStats();
+
+ LLViewerAssetStatsFF::set_region_main(region1_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false);
+
+ LLViewerAssetStatsFF::set_region_main(region2_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+
+ LLSD sd = gViewerAssetStatsMain->asLLSD(false);
+
+ // std::cout << sd << std::endl;
+
+ ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions"));
+ ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle));
+ LLSD sd1 = get_region(sd, region1_handle);
+ LLSD sd2 = get_region(sd, region2_handle);
+ ensure("Region1 is present in results", sd1.isMap());
+ ensure("Region2 is present in results", sd2.isMap());
+
+ // Check a few points on the tree for content
+ ensure_equals("sd1[get_texture_non_temp_udp][enqueued] is 1", sd1["get_texture_non_temp_udp"]["enqueued"].asInteger(), 1);
+ ensure_equals("sd1[get_texture_temp_udp][enqueued] is 0", sd1["get_texture_temp_udp"]["enqueued"].asInteger(), 0);
+ ensure_equals("sd1[get_texture_non_temp_http][enqueued] is 0", sd1["get_texture_non_temp_http"]["enqueued"].asInteger(), 0);
+ ensure_equals("sd1[get_texture_temp_http][enqueued] is 0", sd1["get_texture_temp_http"]["enqueued"].asInteger(), 0);
+ ensure_equals("sd1[get_gesture_udp][dequeued] is 0", sd1["get_gesture_udp"]["dequeued"].asInteger(), 0);
+
+ // Check a few points on the tree for content
+ ensure("sd2[get_gesture_udp][enqueued] is 4", (4 == sd2["get_gesture_udp"]["enqueued"].asInteger()));
+ ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger()));
+ ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger()));
+
+ // Reset and check zeros...
+ // Reset leaves current region in place
+ gViewerAssetStatsMain->reset();
+ sd = gViewerAssetStatsMain->asLLSD(false);
+ ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration"));
+ ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle));
+ sd2 = sd["regions"][0];
+
+ delete gViewerAssetStatsMain;
+ gViewerAssetStatsMain = NULL;
+
+ ensure("sd2[get_texture_non_temp_udp][enqueued] is reset", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd2[get_gesture_udp][enqueued] is reset", (0 == sd2["get_gesture_udp"]["enqueued"].asInteger()));
+ }
+
+ // Check multiple region collection jumping back-and-forth between regions
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<7>()
+ {
+ gViewerAssetStatsMain = new LLViewerAssetStats();
+
+ LLViewerAssetStatsFF::set_region_main(region1_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false);
+
+ LLViewerAssetStatsFF::set_region_main(region2_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+
+ LLViewerAssetStatsFF::set_region_main(region1_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, true, true);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, true, true);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false);
+
+ LLViewerAssetStatsFF::set_region_main(region2_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_GESTURE, false, false);
+
+ LLSD sd = gViewerAssetStatsMain->asLLSD(false);
+
+ ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions"));
+ ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle));
+ LLSD sd1 = get_region(sd, region1_handle);
+ LLSD sd2 = get_region(sd, region2_handle);
+ ensure("Region1 is present in results", sd1.isMap());
+ ensure("Region2 is present in results", sd2.isMap());
+
+ // Check a few points on the tree for content
+ ensure("sd1[get_texture_non_temp_udp][enqueued] is 1", (1 == sd1["get_texture_non_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd1[get_texture_temp_udp][enqueued] is 0", (0 == sd1["get_texture_temp_udp"]["enqueued"].asInteger()));
+ ensure("sd1[get_texture_non_temp_http][enqueued] is 0", (0 == sd1["get_texture_non_temp_http"]["enqueued"].asInteger()));
+ ensure("sd1[get_texture_temp_http][enqueued] is 1", (1 == sd1["get_texture_temp_http"]["enqueued"].asInteger()));
+ ensure("sd1[get_gesture_udp][dequeued] is 0", (0 == sd1["get_gesture_udp"]["dequeued"].asInteger()));
+
+ // Check a few points on the tree for content
+ ensure("sd2[get_gesture_udp][enqueued] is 8", (8 == sd2["get_gesture_udp"]["enqueued"].asInteger()));
+ ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger()));
+ ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger()));
+
+ // Reset and check zeros...
+ // Reset leaves current region in place
+ gViewerAssetStatsMain->reset();
+ sd = gViewerAssetStatsMain->asLLSD(false);
+ ensure("Correct single-key LLSD map root", is_double_key_map(sd, "duration", "regions"));
+ ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle));
+ sd2 = get_region(sd, region2_handle);
+ ensure("Region2 is present in results", sd2.isMap());
+
+ delete gViewerAssetStatsMain;
+ gViewerAssetStatsMain = NULL;
+
+ ensure_equals("sd2[get_texture_non_temp_udp][enqueued] is reset", sd2["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0);
+ ensure_equals("sd2[get_gesture_udp][enqueued] is reset", sd2["get_gesture_udp"]["enqueued"].asInteger(), 0);
+ }
+
+ // Non-texture assets ignore transport and persistence flags
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<8>()
+ {
+ gViewerAssetStatsThread1 = new LLViewerAssetStats();
+ gViewerAssetStatsMain = new LLViewerAssetStats();
+ LLViewerAssetStatsFF::set_region_main(region1_handle);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_TEXTURE, false, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, false, true);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, false, true);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, true, false);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, true, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_BODYPART, true, true);
+ LLViewerAssetStatsFF::record_dequeue_main(LLViewerAssetType::AT_BODYPART, true, true);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, false, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, false, true);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, true, false);
+
+ LLViewerAssetStatsFF::record_enqueue_main(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ LLSD sd = gViewerAssetStatsThread1->asLLSD(false);
+ ensure("Other collector is empty", is_no_stats_map(sd));
+ sd = gViewerAssetStatsMain->asLLSD(false);
+ ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration"));
+ ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle));
+ sd = get_region(sd, region1_handle);
+ ensure("Region1 is present in results", sd.isMap());
+
+ // Check a few points on the tree for content
+ ensure("sd[get_gesture_udp][enqueued] is 0", (0 == sd["get_gesture_udp"]["enqueued"].asInteger()));
+ ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger()));
+
+ ensure("sd[get_wearable_udp][enqueued] is 4", (4 == sd["get_wearable_udp"]["enqueued"].asInteger()));
+ ensure("sd[get_wearable_udp][dequeued] is 4", (4 == sd["get_wearable_udp"]["dequeued"].asInteger()));
+
+ ensure("sd[get_other][enqueued] is 4", (4 == sd["get_other"]["enqueued"].asInteger()));
+ ensure("sd[get_other][dequeued] is 0", (0 == sd["get_other"]["dequeued"].asInteger()));
+
+ // Reset and check zeros...
+ // Reset leaves current region in place
+ gViewerAssetStatsMain->reset();
+ sd = get_region(gViewerAssetStatsMain->asLLSD(false), region1_handle);
+ ensure("Region1 is present in results", sd.isMap());
+
+ delete gViewerAssetStatsMain;
+ gViewerAssetStatsMain = NULL;
+ delete gViewerAssetStatsThread1;
+ gViewerAssetStatsThread1 = NULL;
+
+ ensure_equals("sd[get_texture_non_temp_udp][enqueued] is reset", sd["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0);
+ ensure_equals("sd[get_gesture_udp][dequeued] is reset", sd["get_gesture_udp"]["dequeued"].asInteger(), 0);
+ }
+
+
+ // LLViewerAssetStats::merge() basic functions work
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<9>()
+ {
+ LLViewerAssetStats s1;
+ LLViewerAssetStats s2;
+
+ s1.setRegion(region1_handle);
+ s2.setRegion(region1_handle);
+
+ s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 5000000);
+ s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 6000000);
+ s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 8000000);
+ s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 7000000);
+ s1.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 9000000);
+
+ s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 2000000);
+ s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 3000000);
+ s2.recordGetServiced(LLViewerAssetType::AT_TEXTURE, true, true, 4000000);
+
+ s2.merge(s1);
+
+ LLSD s2_llsd = get_region(s2.asLLSD(false), region1_handle);
+ ensure("Region1 is present in results", s2_llsd.isMap());
+
+ ensure_equals("count after merge", s2_llsd["get_texture_temp_http"]["resp_count"].asInteger(), 8);
+ ensure_approximately_equals("min after merge", s2_llsd["get_texture_temp_http"]["resp_min"].asReal(), 2.0, 22);
+ ensure_approximately_equals("max after merge", s2_llsd["get_texture_temp_http"]["resp_max"].asReal(), 9.0, 22);
+ ensure_approximately_equals("max after merge", s2_llsd["get_texture_temp_http"]["resp_mean"].asReal(), 5.5, 22);
+ }
+
+ // LLViewerAssetStats::merge() basic functions work without corrupting source data
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<10>()
+ {
+ LLViewerAssetStats s1;
+ LLViewerAssetStats s2;
+
+ s1.setRegion(region1_handle);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 23289200);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 282900);
+
+
+ s2.setRegion(region2_handle);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 6500000);
+ s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 10000);
+
+ {
+ s2.merge(s1);
+
+ LLSD src = s1.asLLSD(false);
+ LLSD dst = s2.asLLSD(false);
+
+ ensure_equals("merge src has single region", src["regions"].size(), 1);
+ ensure_equals("merge dst has dual regions", dst["regions"].size(), 2);
+
+ // Remove time stamps, they're a problem
+ src.erase("duration");
+ src["regions"][0].erase("duration");
+ dst.erase("duration");
+ dst["regions"][0].erase("duration");
+ dst["regions"][1].erase("duration");
+
+ LLSD s1_llsd = get_region(src, region1_handle);
+ ensure("Region1 is present in src", s1_llsd.isMap());
+ LLSD s2_llsd = get_region(dst, region1_handle);
+ ensure("Region1 is present in dst", s2_llsd.isMap());
+
+ ensure("result from src is in dst", llsd_equals(s1_llsd, s2_llsd));
+ }
+
+ s1.setRegion(region1_handle);
+ s2.setRegion(region1_handle);
+ s1.reset();
+ s2.reset();
+
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 23289200);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 282900);
+
+
+ s2.setRegion(region1_handle);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 6500000);
+ s2.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 10000);
+
+ {
+ s2.merge(s1);
+
+ LLSD src = s1.asLLSD(false);
+ LLSD dst = s2.asLLSD(false);
+
+ ensure_equals("merge src has single region (p2)", src["regions"].size(), 1);
+ ensure_equals("merge dst has single region (p2)", dst["regions"].size(), 1);
+
+ // Remove time stamps, they're a problem
+ src.erase("duration");
+ src["regions"][0].erase("duration");
+ dst.erase("duration");
+ dst["regions"][0].erase("duration");
+
+ LLSD s1_llsd = get_region(src, region1_handle);
+ ensure("Region1 is present in src", s1_llsd.isMap());
+ LLSD s2_llsd = get_region(dst, region1_handle);
+ ensure("Region1 is present in dst", s2_llsd.isMap());
+
+ ensure_equals("src counts okay (enq)", s1_llsd["get_other"]["enqueued"].asInteger(), 4);
+ ensure_equals("src counts okay (deq)", s1_llsd["get_other"]["dequeued"].asInteger(), 4);
+ ensure_equals("src resp counts okay", s1_llsd["get_other"]["resp_count"].asInteger(), 2);
+ ensure_approximately_equals("src respmin okay", s1_llsd["get_other"]["resp_min"].asReal(), 0.2829, 20);
+ ensure_approximately_equals("src respmax okay", s1_llsd["get_other"]["resp_max"].asReal(), 23.2892, 20);
+
+ ensure_equals("dst counts okay (enq)", s2_llsd["get_other"]["enqueued"].asInteger(), 12);
+ ensure_equals("src counts okay (deq)", s2_llsd["get_other"]["dequeued"].asInteger(), 11);
+ ensure_equals("dst resp counts okay", s2_llsd["get_other"]["resp_count"].asInteger(), 4);
+ ensure_approximately_equals("dst respmin okay", s2_llsd["get_other"]["resp_min"].asReal(), 0.010, 20);
+ ensure_approximately_equals("dst respmax okay", s2_llsd["get_other"]["resp_max"].asReal(), 23.2892, 20);
+ }
+ }
+
+
+ // Maximum merges are interesting when one side contributes nothing
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<11>()
+ {
+ LLViewerAssetStats s1;
+ LLViewerAssetStats s2;
+
+ s1.setRegion(region1_handle);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ // Want to test negative numbers here but have to work in U64
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0);
+
+ s2.setRegion(region1_handle);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ {
+ s2.merge(s1);
+
+ LLSD src = s1.asLLSD(false);
+ LLSD dst = s2.asLLSD(false);
+
+ ensure_equals("merge src has single region", src["regions"].size(), 1);
+ ensure_equals("merge dst has single region", dst["regions"].size(), 1);
+
+ // Remove time stamps, they're a problem
+ src.erase("duration");
+ src["regions"][0].erase("duration");
+ dst.erase("duration");
+ dst["regions"][0].erase("duration");
+
+ LLSD s2_llsd = get_region(dst, region1_handle);
+ ensure("Region1 is present in dst", s2_llsd.isMap());
+
+ ensure_equals("dst counts come from src only", s2_llsd["get_other"]["resp_count"].asInteger(), 3);
+
+ ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum",
+ s2_llsd["get_other"]["resp_max"].asReal(), F64(0.0), 20);
+ }
+
+ // Other way around
+ s1.setRegion(region1_handle);
+ s2.setRegion(region1_handle);
+ s1.reset();
+ s2.reset();
+
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ // Want to test negative numbers here but have to work in U64
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 0);
+
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ {
+ s1.merge(s2);
+
+ LLSD src = s2.asLLSD(false);
+ LLSD dst = s1.asLLSD(false);
+
+ ensure_equals("merge src has single region", src["regions"].size(), 1);
+ ensure_equals("merge dst has single region", dst["regions"].size(), 1);
+
+ // Remove time stamps, they're a problem
+ src.erase("duration");
+ src["regions"][0].erase("duration");
+ dst.erase("duration");
+ dst["regions"][0].erase("duration");
+
+ LLSD s2_llsd = get_region(dst, region1_handle);
+ ensure("Region1 is present in dst", s2_llsd.isMap());
+
+ ensure_equals("dst counts come from src only (flipped)", s2_llsd["get_other"]["resp_count"].asInteger(), 3);
+
+ ensure_approximately_equals("dst maximum with count 0 does not contribute to merged maximum (flipped)",
+ s2_llsd["get_other"]["resp_max"].asReal(), F64(0.0), 20);
+ }
+ }
+
+ // Minimum merges are interesting when one side contributes nothing
+ template<> template<>
+ void tst_viewerassetstats_index_object_t::test<12>()
+ {
+ LLViewerAssetStats s1;
+ LLViewerAssetStats s2;
+
+ s1.setRegion(region1_handle);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 3800000);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2700000);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2900000);
+
+ s2.setRegion(region1_handle);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ {
+ s2.merge(s1);
+
+ LLSD src = s1.asLLSD(false);
+ LLSD dst = s2.asLLSD(false);
+
+ ensure_equals("merge src has single region", src["regions"].size(), 1);
+ ensure_equals("merge dst has single region", dst["regions"].size(), 1);
+
+ // Remove time stamps, they're a problem
+ src.erase("duration");
+ src["regions"][0].erase("duration");
+ dst.erase("duration");
+ dst["regions"][0].erase("duration");
+
+ LLSD s2_llsd = get_region(dst, region1_handle);
+ ensure("Region1 is present in dst", s2_llsd.isMap());
+
+ ensure_equals("dst counts come from src only", s2_llsd["get_other"]["resp_count"].asInteger(), 3);
+
+ ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum",
+ s2_llsd["get_other"]["resp_min"].asReal(), F64(2.7), 20);
+ }
+
+ // Other way around
+ s1.setRegion(region1_handle);
+ s2.setRegion(region1_handle);
+ s1.reset();
+ s2.reset();
+
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s1.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 3800000);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2700000);
+ s1.recordGetServiced(LLViewerAssetType::AT_LSL_BYTECODE, true, true, 2900000);
+
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetEnqueued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+ s2.recordGetDequeued(LLViewerAssetType::AT_LSL_BYTECODE, true, true);
+
+ {
+ s1.merge(s2);
+
+ LLSD src = s2.asLLSD(false);
+ LLSD dst = s1.asLLSD(false);
+
+ ensure_equals("merge src has single region", src["regions"].size(), 1);
+ ensure_equals("merge dst has single region", dst["regions"].size(), 1);
+
+ // Remove time stamps, they're a problem
+ src.erase("duration");
+ src["regions"][0].erase("duration");
+ dst.erase("duration");
+ dst["regions"][0].erase("duration");
+
+ LLSD s2_llsd = get_region(dst, region1_handle);
+ ensure("Region1 is present in dst", s2_llsd.isMap());
+
+ ensure_equals("dst counts come from src only (flipped)", s2_llsd["get_other"]["resp_count"].asInteger(), 3);
+
+ ensure_approximately_equals("dst minimum with count 0 does not contribute to merged minimum (flipped)",
+ s2_llsd["get_other"]["resp_min"].asReal(), F64(2.7), 20);
+ }
+ }
+
+}