diff options
Diffstat (limited to 'indra')
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(¶m); -	} -		 -	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*) ¶m[i]); -				} -			} -			else if (param[i].mType == LLCDParam::LLCD_INTEGER) -			{ -				mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); -				//llinfos << "Type: integer, Default: " << param[i].mDefault.mIntOrEnumValue << llendl; -				 -				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*) ¶m[i]); -				} -			} -			else if (param[i].mType == LLCDParam::LLCD_BOOLEAN) -			{ -				mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mBool); -				//llinfos << "Type: boolean, Default: " << (param[i].mDefault.mBool ? "True" : "False") << llendl; -					 -				LLCheckBoxCtrl* check_box = getChild<LLCheckBoxCtrl>(name); -				if (check_box) -				{ -					check_box->setValue(param[i].mDefault.mBool); -					check_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); -				} -			} -			else if (param[i].mType == LLCDParam::LLCD_ENUM) -			{ -				mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); -				//llinfos << "Type: enum, Default: " << param[i].mDefault.mIntOrEnumValue << llendl; -					 -				{ //plug into combo box -					 -					//llinfos << "Accepted values: " << llendl; -					LLComboBox* combo_box = getChild<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*) ¶m[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(¶m);
 +	}
 +
 +	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*) ¶m[i]);
 +				}
 +			}
 +			else if (param[i].mType == LLCDParam::LLCD_INTEGER)
 +			{
 +				mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue);
 +				//llinfos << "Type: integer, Default: " << param[i].mDefault.mIntOrEnumValue << llendl;
 +
 +				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*) ¶m[i]);
 +				}
 +			}
 +			else if (param[i].mType == LLCDParam::LLCD_BOOLEAN)
 +			{
 +				mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mBool);
 +				//llinfos << "Type: boolean, Default: " << (param[i].mDefault.mBool ? "True" : "False") << llendl;
 +
 +				LLCheckBoxCtrl* check_box = getChild<LLCheckBoxCtrl>(name);
 +				if (check_box)
 +				{
 +					check_box->setValue(param[i].mDefault.mBool);
 +					check_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]);
 +				}
 +			}
 +			else if (param[i].mType == LLCDParam::LLCD_ENUM)
 +			{
 +				mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue);
 +				//llinfos << "Type: enum, Default: " << param[i].mDefault.mIntOrEnumValue << llendl;
 +
 +				{ //plug into combo box
 +
 +					//llinfos << "Accepted values: " << llendl;
 +					LLComboBox* combo_box = getChild<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*) ¶m[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="<< 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="<< 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="<< Back" /> -		<button -		 top="410" -		 right="-92" -		 width="80" -		 height="20" -		 label="Next >> " /> -		<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="<< Back" />
 +	<button
 +	 top_delta="0"
 +	 right="-150"
 +	 width="90"
 +	 height="22"
 +	 name="next"
 +	 label="Next >> " />
 +	<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); +		} +	} + +}  | 
