From 6b9a2d24cce8efaa72c2fd60655998844394312d Mon Sep 17 00:00:00 2001
From: Dave Parks <davep@lindenlab.com>
Date: Wed, 30 Mar 2011 18:38:22 -0500
Subject: [PATCH] SH-477 Better mesh streaming cost estimation.

---
 indra/llcommon/llsdserialize.cpp        |   5 -
 indra/llprimitive/llmodel.cpp           | 360 ++++++++++++------------
 indra/llprimitive/llmodel.h             | 103 +++----
 indra/newview/app_settings/settings.xml |   2 +-
 indra/newview/llfloatermodelpreview.cpp |  34 ++-
 indra/newview/llmeshrepository.cpp      | 109 ++++---
 indra/newview/llmeshrepository.h        |   7 +-
 indra/newview/llselectmgr.cpp           |  18 +-
 indra/newview/llselectmgr.h             |   4 +-
 indra/newview/llviewerobject.cpp        |   2 +-
 indra/newview/llviewerobject.h          |   2 +-
 indra/newview/llviewerwindow.cpp        |  21 +-
 indra/newview/llvovolume.cpp            |   6 +-
 indra/newview/llvovolume.h              |   2 +-
 14 files changed, 351 insertions(+), 324 deletions(-)

diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp
index f3cbfab77aa..5be5ecc492f 100644
--- a/indra/llcommon/llsdserialize.cpp
+++ b/indra/llcommon/llsdserialize.cpp
@@ -2095,11 +2095,6 @@ bool unzip_llsd(LLSD& data, std::istream& is, S32 size)
 
 	S32 ret = inflateInit(&strm);
 
-	if (ret != Z_OK)
-	{
-		llerrs << "WTF?" << llendl;
-	}
-	
 	do
 	{
 		strm.avail_out = CHUNK;
diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp
index 595f9aa3071..049cf6b71f1 100755
--- a/indra/llprimitive/llmodel.cpp
+++ b/indra/llprimitive/llmodel.cpp
@@ -1333,70 +1333,6 @@ std::string LLModel::getName() const
 		return mLabel;
 }
 
-//static 
-LLSD LLModel::writeModel(
-	std::string filename,
-	LLModel* physics,
-	LLModel* high,
-	LLModel* medium,
-	LLModel* low,
-	LLModel* impostor,
-	const convex_hull_decomposition& decomp,
-	BOOL upload_skin,
-	BOOL upload_joints,
-	BOOL nowrite)
-{
-	LLModel::hull dummy_hull;
-	return writeModel(
-		filename,
-		physics,
-		high,
-		medium,
-		low,
-		impostor,
-		decomp,
-		dummy_hull,
-		upload_skin,
-		upload_joints,
-		nowrite);
-}
-
-//static 
-LLSD LLModel::writeModel(
-	std::string filename,
-	LLModel* physics,
-	LLModel* high,
-	LLModel* medium,
-	LLModel* low,
-	LLModel* impostor,
-	const convex_hull_decomposition& decomp,
-	const hull& base_hull,
-	BOOL upload_skin,
-	BOOL upload_joints,
-	BOOL nowrite)
-{
-	std::ofstream os(
-		filename.c_str(),
-		std::ofstream::out | std::ofstream::binary);
-
-	LLSD header = writeModel(
-		os,
-		physics,
-		high,
-		medium,
-		low,
-		impostor,
-		decomp,
-		base_hull,
-		upload_skin,
-		upload_joints,
-		nowrite);
-
-	os.close();
-
-	return header;
-}
-
 //static
 LLSD LLModel::writeModel(
 	std::ostream& ostr,
@@ -1405,8 +1341,7 @@ LLSD LLModel::writeModel(
 	LLModel* medium,
 	LLModel* low,
 	LLModel* impostor,
-	const convex_hull_decomposition& decomp,
-	const hull& base_hull,
+	const LLModel::Decomposition& decomp,
 	BOOL upload_skin,
 	BOOL upload_joints,
 	BOOL nowrite)
@@ -1429,119 +1364,10 @@ LLSD LLModel::writeModel(
 		mdl["skin"] = high->mSkinInfo.asLLSD(upload_joints);
 	}
 
-	if (!decomp.empty() || !base_hull.empty())
+	if (!decomp.mBaseHull.empty() ||
+		!decomp.mHull.empty())		
 	{
-		//write decomposition block
-		// ["decomposition"]["HullList"] -- list of 8 bit integers, each entry represents a hull with specified number of points
-		// ["decomposition"]["PositionDomain"]["Min"/"Max"]
-		// ["decomposition"]["Position"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points
-		// ["decomposition"]["Hull"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points representing a single hull approximation of given shape
-		
-		
-		//get minimum and maximum
-		LLVector3 min;
-		
-		if (decomp.empty())
-		{
-			min = base_hull[0];
-		}
-		else
-		{
-			min = decomp[0][0];
-		}
-
-		LLVector3 max = min;
-
-		LLSD::Binary hulls(decomp.size());
-
-		U32 total = 0;
-
-		for (U32 i = 0; i < decomp.size(); ++i)
-		{
-			U32 size = decomp[i].size();
-			total += size;
-			hulls[i] = (U8) (size);
-
-			for (U32 j = 0; j < decomp[i].size(); ++j)
-			{
-				update_min_max(min, max, decomp[i][j]);
-			}
-
-		}
-
-		for (U32 i = 0; i < base_hull.size(); ++i)
-		{
-			update_min_max(min, max, base_hull[i]);	
-		}
-
-		mdl["decomposition"]["Min"] = min.getValue();
-		mdl["decomposition"]["Max"] = max.getValue();
-
-		if (!hulls.empty())
-		{
-			mdl["decomposition"]["HullList"] = hulls;
-		}
-
-		if (total > 0)
-		{
-			LLSD::Binary p(total*3*2);
-
-			LLVector3 range = max-min;
-
-			U32 vert_idx = 0;
-			for (U32 i = 0; i < decomp.size(); ++i)
-			{
-				for (U32 j = 0; j < decomp[i].size(); ++j)
-				{
-					for (U32 k = 0; k < 3; k++)
-					{
-						//convert to 16-bit normalized across domain
-						U16 val = (U16) (((decomp[i][j].mV[k]-min.mV[k])/range.mV[k])*65535);
-
-						U8* buff = (U8*) &val;
-						//write to binary buffer
-						p[vert_idx++] = buff[0];
-						p[vert_idx++] = buff[1];
-
-						if (vert_idx > p.size())
-						{
-							llerrs << "WTF?" << llendl;
-						}
-					}
-				}
-			}
-
-			mdl["decomposition"]["Position"] = p;
-		}
-
-		if (!base_hull.empty())
-		{
-			LLSD::Binary p(base_hull.size()*3*2);
-
-			LLVector3 range = max-min;
-
-			U32 vert_idx = 0;
-			for (U32 j = 0; j < base_hull.size(); ++j)
-			{
-				for (U32 k = 0; k < 3; k++)
-				{
-					//convert to 16-bit normalized across domain
-					U16 val = (U16) (((base_hull[j].mV[k]-min.mV[k])/range.mV[k])*65535);
-
-					U8* buff = (U8*) &val;
-					//write to binary buffer
-					p[vert_idx++] = buff[0];
-					p[vert_idx++] = buff[1];
-
-					if (vert_idx > p.size())
-					{
-						llerrs << "WTF?" << llendl;
-					}
-				}
-			}
-			
-			mdl["decomposition"]["Hull"] = p;
-		}
+		mdl["decomposition"] = decomp.asLLSD();
 	}
 
 	for (U32 idx = 0; idx < MODEL_NAMES_LENGTH; ++idx)
@@ -1906,7 +1732,11 @@ void LLModel::updateHullCenters()
 		mHullPoints += mPhysics.mHull[i].size();
 	}
 
-	mCenterOfHullCenters *= 1.f / mHullPoints;
+	if (mHullPoints > 0)
+	{
+		mCenterOfHullCenters *= 1.f / mHullPoints;
+		llassert(mPhysics.asLLSD().has("HullList"));
+	}
 }
 
 bool LLModel::loadModel(std::istream& is)
@@ -2182,19 +2012,33 @@ void LLModel::Decomposition::fromLLSD(LLSD& decomp)
 		max.setValue(decomp["Max"]);
 		range = max-min;
 
+		
 		for (U32 i = 0; i < hulls.size(); ++i)
 		{
 			U16 count = (hulls[i] == 0) ? 256 : hulls[i];
 			
+			std::set<U64> valid;
+
+			//must have at least 4 points
+			llassert(count > 3);
+
 			for (U32 j = 0; j < count; ++j)
 			{
+				U64 test = (U64) p[0] | ((U64) p[1] << 16) | ((U64) p[2] << 32);
+				//point must be unique
+				//llassert(valid.find(test) == valid.end());
+				valid.insert(test);
 				mHull[i].push_back(LLVector3(
 					(F32) p[0]/65535.f*range.mV[0]+min.mV[0],
 					(F32) p[1]/65535.f*range.mV[1]+min.mV[1],
 					(F32) p[2]/65535.f*range.mV[2]+min.mV[2]));
 				p += 3;
-			}		 
 
+
+			}
+
+			//each hull must contain at least 4 unique points
+			llassert(valid.size() > 3);
 		}
 	}
 
@@ -2208,8 +2052,17 @@ void LLModel::Decomposition::fromLLSD(LLSD& decomp)
 		LLVector3 max;
 		LLVector3 range;
 
-		min.setValue(decomp["Min"]);
-		max.setValue(decomp["Max"]);
+		if (decomp.has("Min"))
+		{
+			min.setValue(decomp["Min"]);
+			max.setValue(decomp["Max"]);
+		}
+		else
+		{
+			min.set(-0.5f, -0.5f, -0.5f);
+			max.set(0.5f, 0.5f, 0.5f);
+		}
+
 		range = max-min;
 
 		U16 count = position.size()/6;
@@ -2229,7 +2082,145 @@ void LLModel::Decomposition::fromLLSD(LLSD& decomp)
 		//but contains no base hull
 		mBaseHullMesh.clear();;
 	}
+}
+
+LLSD LLModel::Decomposition::asLLSD() const
+{
+	LLSD ret;
+	
+	if (mBaseHull.empty() && mHull.empty())
+	{ //nothing to write
+		return ret;
+	}
+
+	//write decomposition block
+	// ["decomposition"]["HullList"] -- list of 8 bit integers, each entry represents a hull with specified number of points
+	// ["decomposition"]["PositionDomain"]["Min"/"Max"]
+	// ["decomposition"]["Position"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points
+	// ["decomposition"]["Hull"] -- list of 16-bit integers to be decoded to given domain, encoded 3D points representing a single hull approximation of given shape
+	
+	
+	//get minimum and maximum
+	LLVector3 min;
+	
+	if (mHull.empty())
+	{  
+		min = mBaseHull[0];
+	}
+	else
+	{
+		min = mHull[0][0];
+	}
+
+	LLVector3 max = min;
+
+	LLSD::Binary hulls(mHull.size());
+
+	U32 total = 0;
+
+	for (U32 i = 0; i < mHull.size(); ++i)
+	{
+		U32 size = mHull[i].size();
+		total += size;
+		hulls[i] = (U8) (size);
+
+		for (U32 j = 0; j < mHull[i].size(); ++j)
+		{
+			update_min_max(min, max, mHull[i][j]);
+		}
+	}
+
+	for (U32 i = 0; i < mBaseHull.size(); ++i)
+	{
+		update_min_max(min, max, mBaseHull[i]);	
+	}
 
+	ret["Min"] = min.getValue();
+	ret["Max"] = max.getValue();
+
+	if (!hulls.empty())
+	{
+		ret["HullList"] = hulls;
+	}
+
+	if (total > 0)
+	{
+		LLSD::Binary p(total*3*2);
+
+		LLVector3 range = max-min;
+
+		U32 vert_idx = 0;
+		
+		for (U32 i = 0; i < mHull.size(); ++i)
+		{
+			std::set<U64> valid;
+
+			llassert(!mHull[i].empty());
+
+			for (U32 j = 0; j < mHull[i].size(); ++j)
+			{
+				U64 test = 0;
+				for (U32 k = 0; k < 3; k++)
+				{
+					//convert to 16-bit normalized across domain
+					U16 val = (U16) (((mHull[i][j].mV[k]-min.mV[k])/range.mV[k])*65535);
+
+					switch (k)
+					{
+						case 0: test = test | (U64) val; break;
+						case 1: test = test | ((U64) val << 16); break;
+						case 2: test = test | ((U64) val << 32); break;
+					};
+
+					valid.insert(test);
+					
+					U8* buff = (U8*) &val;
+					//write to binary buffer
+					p[vert_idx++] = buff[0];
+					p[vert_idx++] = buff[1];
+
+					//makes sure we haven't run off the end of the array
+					llassert(vert_idx <= p.size());
+				}
+			}
+
+			//must have at least 4 unique points
+			llassert(valid.size() > 3);
+		}
+
+		ret["Position"] = p;
+	}
+
+	if (!mBaseHull.empty())
+	{
+		LLSD::Binary p(mBaseHull.size()*3*2);
+
+		LLVector3 range = max-min;
+
+		U32 vert_idx = 0;
+		for (U32 j = 0; j < mBaseHull.size(); ++j)
+		{
+			for (U32 k = 0; k < 3; k++)
+			{
+				//convert to 16-bit normalized across domain
+				U16 val = (U16) (((mBaseHull[j].mV[k]-min.mV[k])/range.mV[k])*65535);
+
+				U8* buff = (U8*) &val;
+				//write to binary buffer
+				p[vert_idx++] = buff[0];
+				p[vert_idx++] = buff[1];
+
+				if (vert_idx > p.size())
+				{
+					llerrs << "WTF?" << llendl;
+				}
+			}
+		}
+		
+		ret["Hull"] = p;
+	}
+
+	return ret;
 }
 
 void LLModel::Decomposition::merge(const LLModel::Decomposition* rhs)
@@ -2256,5 +2247,10 @@ void LLModel::Decomposition::merge(const LLModel::Decomposition* rhs)
 	{ //take physics shape mesh from rhs
 		mPhysicsShapeMesh = rhs->mPhysicsShapeMesh;
 	}
+
+	if (!mHull.empty())
+	{ //verify
+		llassert(asLLSD().has("HullList"));
+	}
 }
 
diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h
index e9450d29678..962e422a262 100755
--- a/indra/llprimitive/llmodel.h
+++ b/indra/llprimitive/llmodel.h
@@ -71,38 +71,53 @@ class LLModel : public LLVolume
 	
 	//convex_hull_decomposition is a vector of convex hulls
 	//each convex hull is a set of points
-	typedef  std::vector<std::vector<LLVector3> > convex_hull_decomposition;
+	typedef std::vector<std::vector<LLVector3> > convex_hull_decomposition;
 	typedef std::vector<LLVector3> hull;
 	
+	class PhysicsMesh
+	{
+	public:
+		std::vector<LLVector3> mPositions;
+		std::vector<LLVector3> mNormals;
+
+		void clear()
+		{
+			mPositions.clear();
+			mNormals.clear();
+		}
+
+		bool empty() const
+		{
+			return mPositions.empty();
+		}
+	};
+
+	class Decomposition
+	{
+	public:
+		Decomposition() { }
+		Decomposition(LLSD& data);
+		void fromLLSD(LLSD& data);
+		LLSD asLLSD() const;
+
+		void merge(const Decomposition* rhs);
+
+		LLUUID mMeshID;
+		LLModel::convex_hull_decomposition mHull;
+		LLModel::hull mBaseHull;
+
+		std::vector<LLModel::PhysicsMesh> mMesh;
+		LLModel::PhysicsMesh mBaseHullMesh;
+		LLModel::PhysicsMesh mPhysicsShapeMesh;
+	};
+
 	LLModel(LLVolumeParams& params, F32 detail);
 	~LLModel();
 
 	bool loadModel(std::istream& is);
 	bool loadSkinInfo(LLSD& header, std::istream& is);
 	bool loadDecomposition(LLSD& header, std::istream& is);
-	static LLSD writeModel(
-		std::string filename,
-		LLModel* physics,
-		LLModel* high,
-		LLModel* medium,
-		LLModel* low,
-		LLModel* imposotr,
-		const LLModel::convex_hull_decomposition& convex_hull_decomposition,
-		const LLModel::hull& base_hull,
-		BOOL upload_skin,
-		BOOL upload_joints,
-		BOOL nowrite = FALSE);
-	static LLSD writeModel(
-		std::string filename,
-		LLModel* physics,
-		LLModel* high,
-		LLModel* medium,
-		LLModel* low,
-		LLModel* imposotr,
-		const LLModel::convex_hull_decomposition& convex_hull_decomposition,
-		BOOL upload_skin,
-		BOOL upload_joints,
-		BOOL nowrite = FALSE);
+	
 	static LLSD writeModel(
 		std::ostream& ostr,
 		LLModel* physics,
@@ -110,11 +125,11 @@ class LLModel : public LLVolume
 		LLModel* medium,
 		LLModel* low,
 		LLModel* imposotr,
-		const LLModel::convex_hull_decomposition& convex_hull_decomposition,
-		const LLModel::hull& base_hull,
+		const LLModel::Decomposition& decomp,
 		BOOL upload_skin,
 		BOOL upload_joints,
 		BOOL nowrite = FALSE);
+
 	static LLSD writeModelToStream(
 		std::ostream& ostr,
 		LLSD& mdl,
@@ -220,42 +235,6 @@ class LLModel : public LLVolume
 	//ID for storing this model in a .slm file
 	S32 mLocalID;
 
-	class PhysicsMesh
-	{
-	public:
-		std::vector<LLVector3> mPositions;
-		std::vector<LLVector3> mNormals;
-
-		void clear()
-		{
-			mPositions.clear();
-			mNormals.clear();
-		}
-
-		bool empty() const
-		{
-			return mPositions.empty();
-		}
-	};
-
-	class Decomposition
-	{
-	public:
-		Decomposition() { }
-		Decomposition(LLSD& data);
-		void fromLLSD(LLSD& data);
-		
-		void merge(const Decomposition* rhs);
-
-		LLUUID mMeshID;
-		LLModel::convex_hull_decomposition mHull;
-		LLModel::hull mBaseHull;
-
-		std::vector<LLModel::PhysicsMesh> mMesh;
-		LLModel::PhysicsMesh mBaseHullMesh;
-		LLModel::PhysicsMesh mPhysicsShapeMesh;
-	};
-
 	Decomposition mPhysics;
 
 protected:
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index c11de57c42b..4c042435f25 100755
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -8974,7 +8974,7 @@
     <key>Type</key>
     <string>F32</string>
     <key>Value</key>
-    <real>2.0</real>
+    <real>3.0</real>
   </map>
   <key>MeshThreadCount</key>
   <map>
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index 541511e448c..243f301e9de 100755
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -1093,7 +1093,7 @@ LLModelLoader::LLModelLoader(std::string filename, S32 lod, LLModelPreview* prev
 	{
 		//only try to load from slm if viewer is configured to do so and this is the 
 		//initial model load (not an LoD or physics shape)
-		mTrySLM = gSavedSettings.getBOOL("MeshImportUseSLM") && mPreview->mBaseModel.empty();
+		mTrySLM = gSavedSettings.getBOOL("MeshImportUseSLM") && mPreview->mUploadData.empty();
 		mPreview->setLoadState(STARTING);
 	}
 	else
@@ -1889,6 +1889,14 @@ void LLModelLoader::loadModelCallback()
 	{ //wait until this thread is stopped before deleting self
 		apr_sleep(100);
 	}
+
+	//cleanup model loader
+	if (mPreview)
+	{
+		mPreview->mModelLoader = NULL;
+	}
+
+	delete this;
 }
 
 void LLModelLoader::handlePivotPoint( daeElement* pRoot )
@@ -2518,13 +2526,13 @@ U32 LLModelPreview::calcResourceCost()
 		{
 			accounted.insert(instance.mModel);
 
-			LLModel::convex_hull_decomposition& decomp =
+			LLModel::Decomposition& decomp =
 			instance.mLOD[LLModel::LOD_PHYSICS] ?
-			instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics.mHull :
-			instance.mModel->mPhysics.mHull;
+			instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics :
+			instance.mModel->mPhysics;
 
-			LLSD ret = LLModel::writeModel(
-										   "",
+			std::stringstream ostr;
+			LLSD ret = LLModel::writeModel(ostr,
 										   instance.mLOD[4],
 										   instance.mLOD[3],
 										   instance.mLOD[2],
@@ -2536,10 +2544,10 @@ U32 LLModelPreview::calcResourceCost()
 										   TRUE);
 			cost += gMeshRepo.calcResourceCost(ret);
 
-			num_hulls += decomp.size();
-			for (U32 i = 0; i < decomp.size(); ++i)
+			num_hulls += decomp.mHull.size();
+			for (U32 i = 0; i < decomp.mHull.size(); ++i)
 			{
-				num_points += decomp[i].size();
+				num_points += decomp.mHull[i].size();
 			}
 
 			//calculate streaming cost
@@ -2737,10 +2745,10 @@ void LLModelPreview::saveUploadData(const std::string& filename, bool save_skinw
 
 			std::stringstream str;
 
-			LLModel::convex_hull_decomposition& decomp =
+			LLModel::Decomposition& decomp =
 				instance.mLOD[LLModel::LOD_PHYSICS].notNull() ? 
-				instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics.mHull : 
-				instance.mModel->mPhysics.mHull;
+				instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics : 
+				instance.mModel->mPhysics;
 
 			LLModel::writeModel(str, 
 				instance.mLOD[LLModel::LOD_PHYSICS], 
@@ -2749,7 +2757,7 @@ void LLModelPreview::saveUploadData(const std::string& filename, bool save_skinw
 				instance.mLOD[LLModel::LOD_LOW], 
 				instance.mLOD[LLModel::LOD_IMPOSTOR], 
 				decomp, 
-				empty_hull, save_skinweights, save_joint_positions);
+				save_skinweights, save_joint_positions);
 
 			
 			data["mesh"][instance.mModel->mLocalID] = str.str();
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 752e4c87442..78943ef1b62 100755
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -1535,8 +1535,6 @@ void LLMeshRepoThread::notifyLoadedMeshes()
 
 S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) 
 { //only ever called from main thread
-	lod = llclamp(lod, 0, 3);
-
 	LLMutexLock lock(mHeaderMutex);
 	mesh_header_map::iterator iter = mMeshHeader.find(mesh_params.getSculptID());
 
@@ -1544,40 +1542,48 @@ S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo
 	{
 		LLSD& header = iter->second;
 
-		if (header.has("404"))
-		{
-			return -1;
-		}
+		return LLMeshRepository::getActualMeshLOD(header, lod);
+	}
 
-		if (header[header_lod[lod]]["size"].asInteger() > 0)
-		{
-			return lod;
-		}
+	return lod;
+}
+
+//static
+S32 LLMeshRepository::getActualMeshLOD(LLSD& header, S32 lod)
+{
+	lod = llclamp(lod, 0, 3);
+
+	if (header.has("404"))
+	{
+		return -1;
+	}
 
-		//search down to find the next available lower lod
-		for (S32 i = lod-1; i >= 0; --i)
+	if (header[header_lod[lod]]["size"].asInteger() > 0)
+	{
+		return lod;
+	}
+
+	//search down to find the next available lower lod
+	for (S32 i = lod-1; i >= 0; --i)
+	{
+		if (header[header_lod[i]]["size"].asInteger() > 0)
 		{
-			if (header[header_lod[i]]["size"].asInteger() > 0)
-			{
-				return i;
-			}
+			return i;
 		}
+	}
 
-		//search up to find then ext available higher lod
-		for (S32 i = lod+1; i < 4; ++i)
+	//search up to find then ext available higher lod
+	for (S32 i = lod+1; i < 4; ++i)
+	{
+		if (header[header_lod[i]]["size"].asInteger() > 0)
 		{
-			if (header[header_lod[i]]["size"].asInteger() > 0)
-			{
-				return i;
-			}
+			return i;
 		}
-
-		//header exists and no good lod found, treat as 404
-		header["404"] = 1;
-		return -1;
 	}
 
-	return lod;
+	//header exists and no good lod found, treat as 404
+	header["404"] = 1;
+	return -1;
 }
 
 U32 LLMeshRepoThread::getResourceCost(const LLUUID& mesh_id)
@@ -2514,12 +2520,12 @@ bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id)
 	return mesh.has("physics_shape") && mesh["physics_shape"].has("size") && (mesh["physics_shape"]["size"].asInteger() > 0);
 }
 
-const LLSD& LLMeshRepository::getMeshHeader(const LLUUID& mesh_id)
+LLSD& LLMeshRepository::getMeshHeader(const LLUUID& mesh_id)
 {
 	return mThread->getMeshHeader(mesh_id);
 }
 
-const LLSD& LLMeshRepoThread::getMeshHeader(const LLUUID& mesh_id)
+LLSD& LLMeshRepoThread::getMeshHeader(const LLUUID& mesh_id)
 {
 	static LLSD dummy_ret;
 	if (mesh_id.notNull())
@@ -2577,12 +2583,10 @@ void LLMeshUploadThread::sendCostRequest(LLMeshUploadData& data)
 	//write model file to memory buffer
 	std::stringstream ostr;
 
-	LLModel::convex_hull_decomposition& decomp =
+	LLModel::Decomposition& decomp =
 		data.mModel[LLModel::LOD_PHYSICS].notNull() ? 
-		data.mModel[LLModel::LOD_PHYSICS]->mPhysics.mHull : 
-		data.mBaseModel->mPhysics.mHull;
-
-	LLModel::hull dummy_hull;
+		data.mModel[LLModel::LOD_PHYSICS]->mPhysics : 
+		data.mBaseModel->mPhysics;
 
 	LLSD header = LLModel::writeModel(
 		ostr,
@@ -2592,7 +2596,6 @@ void LLMeshUploadThread::sendCostRequest(LLMeshUploadData& data)
 		data.mModel[LLModel::LOD_LOW],
 		data.mModel[LLModel::LOD_IMPOSTOR], 
 		decomp,
-		dummy_hull,
 		mUploadSkin,
 		mUploadJoints,
 		true);
@@ -2678,10 +2681,12 @@ void LLMeshUploadThread::doUploadModel(LLMeshUploadData& data)
 	{
 		std::stringstream ostr;
 
-		LLModel::convex_hull_decomposition& decomp =
+		LLModel::Decomposition& decomp =
 			data.mModel[LLModel::LOD_PHYSICS].notNull() ? 
-			data.mModel[LLModel::LOD_PHYSICS]->mPhysics.mHull : 
-			data.mBaseModel->mPhysics.mHull;
+			data.mModel[LLModel::LOD_PHYSICS]->mPhysics : 
+			data.mBaseModel->mPhysics;
+
+		decomp.mBaseHull = mHullMap[data.mBaseModel];
 
 		LLModel::writeModel(
 			ostr,  
@@ -2691,7 +2696,6 @@ void LLMeshUploadThread::doUploadModel(LLMeshUploadData& data)
 			data.mModel[LLModel::LOD_LOW],
 			data.mModel[LLModel::LOD_IMPOSTOR], 
 			decomp,
-			mHullMap[data.mBaseModel],
 			mUploadSkin,
 			mUploadJoints);
 
@@ -2939,17 +2943,36 @@ void LLMeshRepository::uploadError(LLSD& args)
 }
 
 //static
-F32 LLMeshRepository::getStreamingCost(const LLSD& header, F32 radius)
+F32 LLMeshRepository::getStreamingCost(LLSD& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod)
 {
-	F32 dlowest = llmin(radius/0.06f, 256.f);
-	F32 dlow = llmin(radius/0.24f, 256.f);
-	F32 dmid = llmin(radius/1.0f, 256.f);
+	F32 dlowest = llmin(radius/0.03f, 256.f);
+	F32 dlow = llmin(radius/0.06f, 256.f);
+	F32 dmid = llmin(radius/0.24f, 256.f);
 	
 	F32 bytes_lowest = header["lowest_lod"]["size"].asReal()/1024.f;
 	F32 bytes_low = header["low_lod"]["size"].asReal()/1024.f;
 	F32 bytes_mid = header["medium_lod"]["size"].asReal()/1024.f;
 	F32 bytes_high = header["high_lod"]["size"].asReal()/1024.f;
 
+	if (bytes)
+	{
+		*bytes = 0;
+		*bytes += header["lowest_lod"]["size"].asInteger();
+		*bytes += header["low_lod"]["size"].asInteger();
+		*bytes += header["medium_lod"]["size"].asInteger();
+		*bytes += header["high_lod"]["size"].asInteger();
+	}
+
+
+	if (bytes_visible)
+	{
+		lod = LLMeshRepository::getActualMeshLOD(header, lod);
+		if (lod >= 0 && lod <= 3)
+		{
+			*bytes_visible = header[header_lod[lod]]["size"].asInteger();
+		}
+	}
+
 	if (bytes_high == 0.f)
 	{
 		return 0.f;
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 31c2049c323..5983a282a23 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -318,7 +318,7 @@ class LLMeshRepoThread : public LLThread
 	bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size);
 	bool decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size);
 	bool physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size);
-	const LLSD& getMeshHeader(const LLUUID& mesh_id);
+	LLSD& getMeshHeader(const LLUUID& mesh_id);
 
 	void notifyLoadedMeshes();
 	S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod);
@@ -434,7 +434,7 @@ class LLMeshRepository
 	static U32 sCacheBytesWritten;
 	static U32 sPeakKbps;
 	
-	static F32 getStreamingCost(const LLSD& header, F32 radius);
+	static F32 getStreamingCost(LLSD& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1);
 
 	LLMeshRepository();
 
@@ -452,6 +452,7 @@ class LLMeshRepository
 	void notifyDecompositionReceived(LLModel::Decomposition* info);
 
 	S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod);
+	static S32 getActualMeshLOD(LLSD& header, S32 lod);
 	U32 calcResourceCost(LLSD& header);
 	U32 getResourceCost(const LLUUID& mesh_params);
 	const LLMeshSkinInfo* getSkinInfo(const LLUUID& mesh_id);
@@ -462,7 +463,7 @@ class LLMeshRepository
 	void buildHull(const LLVolumeParams& params, S32 detail);
 	void buildPhysicsMesh(LLModel::Decomposition& decomp);
 
-	const LLSD& getMeshHeader(const LLUUID& mesh_id);
+	LLSD& getMeshHeader(const LLUUID& mesh_id);
 
 	void uploadModel(std::vector<LLModelInstance>& data, LLVector3& scale, bool upload_textures,
 			bool upload_skin, bool upload_joints);
diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp
index b139ba361e4..78ffc7908df 100644
--- a/indra/newview/llselectmgr.cpp
+++ b/indra/newview/llselectmgr.cpp
@@ -6374,7 +6374,7 @@ BOOL LLObjectSelection::isEmpty() const
 //-----------------------------------------------------------------------------
 // getObjectCount() - returns number of non null objects
 //-----------------------------------------------------------------------------
-S32 LLObjectSelection::getObjectCount(BOOL mesh_adjust)
+S32 LLObjectSelection::getObjectCount()
 {
 	cleanupNodes();
 	S32 count = mList.size();
@@ -6478,7 +6478,7 @@ F32 LLObjectSelection::getSelectedLinksetPhysicsCost()
 	return cost;
 }
 
-F32 LLObjectSelection::getSelectedObjectStreamingCost()
+F32 LLObjectSelection::getSelectedObjectStreamingCost(S32* total_bytes, S32* visible_bytes)
 {
 	F32 cost = 0.f;
 	for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter)
@@ -6488,7 +6488,19 @@ F32 LLObjectSelection::getSelectedObjectStreamingCost()
 		
 		if (object)
 		{
-			cost += object->getStreamingCost();
+			S32 bytes = 0;
+			S32 visible = 0;
+			cost += object->getStreamingCost(&bytes, &visible);
+
+			if (total_bytes)
+			{
+				*total_bytes += bytes;
+			}
+
+			if (visible_bytes)
+			{
+				*visible_bytes += visible;
+			}
 		}
 	}
 
diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h
index 031898d7c55..166616e13e0 100644
--- a/indra/newview/llselectmgr.h
+++ b/indra/newview/llselectmgr.h
@@ -278,14 +278,14 @@ class LLObjectSelection : public LLRefCount
 	LLSelectNode* findNode(LLViewerObject* objectp);
 
 	// count members
-	S32 getObjectCount(BOOL mesh_adjust = FALSE);
+	S32 getObjectCount();
 	F32 getSelectedObjectCost();
 	F32 getSelectedLinksetCost();
 	F32 getSelectedPhysicsCost();
 	F32 getSelectedLinksetPhysicsCost();
 	S32 getSelectedObjectRenderCost();
 	
-	F32 getSelectedObjectStreamingCost();
+	F32 getSelectedObjectStreamingCost(S32* total_bytes = NULL, S32* visible_bytes = NULL);
 	U32 getSelectedObjectTriangleCount();
 
 	S32 getTECount();
diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index bcdc774c5e1..70b18090336 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -3134,7 +3134,7 @@ F32 LLViewerObject::getLinksetPhysicsCost()
 	return mLinksetPhysicsCost;
 }
 
-F32 LLViewerObject::getStreamingCost()
+F32 LLViewerObject::getStreamingCost(S32* bytes, S32* visible_bytes)
 {
 	return 0.f;
 }
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index 2b2b7bd59da..44f46b8c0f8 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -326,7 +326,7 @@ class LLViewerObject : public LLPrimitive, public LLRefCount, public LLGLUpdate
 	
 	virtual void setScale(const LLVector3 &scale, BOOL damped = FALSE);
 
-	virtual F32 getStreamingCost();
+	virtual F32 getStreamingCost(S32* bytes = NULL, S32* visible_bytes = NULL);
 	virtual U32 getTriangleCount();
 
 	void setObjectCost(F32 cost);
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 39ef56d156e..141ade80796 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -493,6 +493,10 @@ class LLDebugText
 			{
 				F32 cost = 0.f;
 				S32 count = 0;
+				S32 object_count = 0;
+				S32 total_bytes = 0;
+				S32 visible_bytes = 0;
+
 				const char* label = "Region";
 				if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 0)
 				{ //region
@@ -506,8 +510,13 @@ class LLDebugText
 								object->getRegion() == region &&
 								object->getVolume())
 							{
-								cost += object->getStreamingCost();
+								object_count++;
+								S32 bytes = 0;	
+								S32 visible = 0;
+								cost += object->getStreamingCost(&bytes, &visible);
 								count += object->getTriangleCount();
+								total_bytes += bytes;
+								visible_bytes += visible;
 							}
 						}
 					}
@@ -515,12 +524,16 @@ class LLDebugText
 				else
 				{
 					label = "Selection";
-					cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectStreamingCost();
+					cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectStreamingCost(&total_bytes, &visible_bytes);
 					count = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectTriangleCount();
+					object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount();
 				}
 					
-				addText(xpos,ypos, llformat("%s streaming cost: %.1f (%.1f KTris)",
-							label, cost, count/1000.f));
+				addText(xpos,ypos, llformat("%s streaming cost: %.1f", label, cost));
+				ypos += y_inc;
+
+				addText(xpos, ypos, llformat("    %.1f KTris, %.1f/%.1f KB, %d objects",
+										count/1024.f, visible_bytes/1024.f, total_bytes/1024.f, object_count));
 				ypos += y_inc;
 			
 			}
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 98e5e4c6de1..7c4a4c13ba3 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -3082,15 +3082,15 @@ U32 LLVOVolume::getRenderCost(std::set<LLUUID> &textures) const
 
 }
 
-F32 LLVOVolume::getStreamingCost()
+F32 LLVOVolume::getStreamingCost(S32* bytes, S32* visible_bytes)
 {
 	if (isMesh())
 	{	
-		const LLSD& header = gMeshRepo.getMeshHeader(getVolume()->getParams().getSculptID());
+		LLSD& header = gMeshRepo.getMeshHeader(getVolume()->getParams().getSculptID());
 
 		F32 radius = getScale().length();
 		
-		return LLMeshRepository::getStreamingCost(header, radius);
+		return LLMeshRepository::getStreamingCost(header, radius, bytes, visible_bytes, mLOD);
 	}
 		
 	return 0.f;
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index b09243055c1..029811886d0 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -130,7 +130,7 @@ class LLVOVolume : public LLViewerObject
 	const LLMatrix3&	getRelativeXformInvTrans() const		{ return mRelativeXformInvTrans; }
 	/*virtual*/	const LLMatrix4	getRenderMatrix() const;
 				U32 	getRenderCost(std::set<LLUUID> &textures) const;
-	/*virtual*/	F32		getStreamingCost();
+	/*virtual*/	F32		getStreamingCost(S32* bytes = NULL, S32* visible_bytes = NULL);
 	/*virtual*/ U32		getTriangleCount();
 	/*virtual*/ BOOL lineSegmentIntersect(const LLVector3& start, const LLVector3& end, 
 										  S32 face = -1,                        // which face to check, -1 = ALL_SIDES
-- 
GitLab