diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 1459b9ada2785be76b7a8183123832be4895a2b1..5863310162c82713d9702f97c0b2439e11617b56 100755
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -37,6 +37,7 @@ set(llcommon_SOURCE_FILES
     llbase32.cpp
     llbase64.cpp
     llbitpack.cpp
+    llcallbacklist.cpp
     llcommon.cpp
     llcommonutils.cpp
     llcoros.cpp
@@ -129,6 +130,7 @@ set(llcommon_HEADER_FILES
     llbase64.h
     llbitpack.h
     llboost.h
+    llcallbacklist.h
     llcommon.h
     llcommonutils.h
     llcoros.h
diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..541ff75ee4d834382d61dba0a7f2522084921708
--- /dev/null
+++ b/indra/llcommon/llcallbacklist.cpp
@@ -0,0 +1,230 @@
+/** 
+ * @file llcallbacklist.cpp
+ * @brief A simple list of callback functions to call.
+ *
+ * $LicenseInfo:firstyear=2001&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 "llcallbacklist.h"
+#include "lleventtimer.h"
+#include "llerrorlegacy.h"
+
+// Globals
+//
+LLCallbackList gIdleCallbacks;
+
+//
+// Member functions
+//
+
+LLCallbackList::LLCallbackList()
+{
+	// nothing
+}
+
+LLCallbackList::~LLCallbackList()
+{
+}
+
+
+void LLCallbackList::addFunction( callback_t func, void *data)
+{
+	if (!func)
+	{
+		return;
+	}
+
+	// only add one callback per func/data pair
+	//
+	if (containsFunction(func))
+	{
+		return;
+	}
+	
+	callback_pair_t t(func, data);
+	mCallbackList.push_back(t);
+}
+
+bool LLCallbackList::containsFunction( callback_t func, void *data)
+{
+	callback_pair_t t(func, data);
+	callback_list_t::iterator iter = find(func,data);
+	if (iter != mCallbackList.end())
+	{
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+
+bool LLCallbackList::deleteFunction( callback_t func, void *data)
+{
+	callback_list_t::iterator iter = find(func,data);
+	if (iter != mCallbackList.end())
+	{
+		mCallbackList.erase(iter);
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+inline 
+LLCallbackList::callback_list_t::iterator
+LLCallbackList::find(callback_t func, void *data)
+{
+	callback_pair_t t(func, data);
+	return std::find(mCallbackList.begin(), mCallbackList.end(), t);
+}
+
+void LLCallbackList::deleteAllFunctions()
+{
+	mCallbackList.clear();
+}
+
+
+void LLCallbackList::callFunctions()
+{
+	for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end();  )
+	{
+		callback_list_t::iterator curiter = iter++;
+		curiter->first(curiter->second);
+	}
+}
+
+// Shim class to allow arbitrary boost::bind
+// expressions to be run as one-time idle callbacks.
+class OnIdleCallbackOneTime
+{
+public:
+	OnIdleCallbackOneTime(nullary_func_t callable):
+		mCallable(callable)
+	{
+	}
+	static void onIdle(void *data)
+	{
+		gIdleCallbacks.deleteFunction(onIdle, data);
+		OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data);
+		self->call();
+		delete self;
+	}
+	void call()
+	{
+		mCallable();
+	}
+private:
+	nullary_func_t mCallable;
+};
+
+void doOnIdleOneTime(nullary_func_t callable)
+{
+	OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable);
+	gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor);
+}
+
+// Shim class to allow generic boost functions to be run as
+// recurring idle callbacks.  Callable should return true when done,
+// false to continue getting called.
+class OnIdleCallbackRepeating
+{
+public:
+	OnIdleCallbackRepeating(bool_func_t callable):
+		mCallable(callable)
+	{
+	}
+	// Will keep getting called until the callable returns true.
+	static void onIdle(void *data)
+	{
+		OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data);
+		bool done = self->call();
+		if (done)
+		{
+			gIdleCallbacks.deleteFunction(onIdle, data);
+			delete self;
+		}
+	}
+	bool call()
+	{
+		return mCallable();
+	}
+private:
+	bool_func_t mCallable;
+};
+
+void doOnIdleRepeating(bool_func_t callable)
+{
+	OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable);
+	gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor);
+}
+
+class NullaryFuncEventTimer: public LLEventTimer
+{
+public:
+	NullaryFuncEventTimer(nullary_func_t callable, F32 seconds):
+		LLEventTimer(seconds),
+		mCallable(callable)
+	{
+	}
+
+private:
+	BOOL tick()
+	{
+		mCallable();
+		return TRUE;
+	}
+
+	nullary_func_t mCallable;
+};
+
+// Call a given callable once after specified interval.
+void doAfterInterval(nullary_func_t callable, F32 seconds)
+{
+	new NullaryFuncEventTimer(callable, seconds);
+}
+
+class BoolFuncEventTimer: public LLEventTimer
+{
+public:
+	BoolFuncEventTimer(bool_func_t callable, F32 seconds):
+		LLEventTimer(seconds),
+		mCallable(callable)
+	{
+	}
+private:
+	BOOL tick()
+	{
+		return mCallable();
+	}
+
+	bool_func_t mCallable;
+};
+
+// Call a given callable every specified number of seconds, until it returns true.
+void doPeriodically(bool_func_t callable, F32 seconds)
+{
+	new BoolFuncEventTimer(callable, seconds);
+}
diff --git a/indra/newview/llcallbacklist.h b/indra/llcommon/llcallbacklist.h
old mode 100755
new mode 100644
similarity index 76%
rename from indra/newview/llcallbacklist.h
rename to indra/llcommon/llcallbacklist.h
index 0516c9cdb44e663d47d123b555a7550ce0c774c3..89716cd74ca985696fbc785d5b99b22a704ae91d
--- a/indra/newview/llcallbacklist.h
+++ b/indra/llcommon/llcallbacklist.h
@@ -28,27 +28,34 @@
 #define LL_LLCALLBACKLIST_H
 
 #include "llstl.h"
+#include <boost/function.hpp>
+#include <list>
 
 class LLCallbackList
 {
 public:
 	typedef void (*callback_t)(void*);
+
+	typedef std::pair< callback_t,void* >	callback_pair_t;
+	// NOTE: It is confirmed that we DEPEND on the order provided by using a list :(
+	//
+	typedef std::list< callback_pair_t >	callback_list_t; 
 	
 	LLCallbackList();
 	~LLCallbackList();
 
-	void addFunction( callback_t func, void *data = NULL );		// register a callback, which will be called as func(data)
-	BOOL containsFunction( callback_t func, void *data = NULL );	// true if list already contains the function/data pair
-	BOOL deleteFunction( callback_t func, void *data = NULL );		// removes the first instance of this function/data pair from the list, false if not found
-	void callFunctions();												// calls all functions
+	void addFunction( callback_t func, void *data = NULL );			// register a callback, which will be called as func(data)
+	bool containsFunction( callback_t func, void *data = NULL );	// true if list already contains the function/data pair
+	bool deleteFunction( callback_t func, void *data = NULL );		// removes the first instance of this function/data pair from the list, false if not found
+	void callFunctions();														// calls all functions
 	void deleteAllFunctions();
 
 	static void test();
 
 protected:
-	// Use a list so that the callbacks are ordered in case that matters
-	typedef std::pair<callback_t,void*> callback_pair_t;
-	typedef std::list<callback_pair_t > callback_list_t;
+
+	inline callback_list_t::iterator find(callback_t func, void *data);
+
 	callback_list_t	mCallbackList;
 };
 
diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h
index fd09eb9debdcffcc298d87cd5c6feac7e25a05f4..0fb257aab1dbe6d89340c5831045928f6779e782 100755
--- a/indra/llcommon/llmemory.h
+++ b/indra/llcommon/llmemory.h
@@ -28,6 +28,7 @@
 
 #include "linden_common.h"
 #include "llunits.h"
+#include "stdtypes.h"
 #if !LL_WINDOWS
 #include <stdint.h>
 #endif
@@ -59,7 +60,7 @@ class LLMutex ;
 LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment);
 
 #ifdef SHOW_ASSERT
-#define ll_assert_aligned(ptr,alignment) ll_assert_aligned_func(reinterpret_cast<uintptr_t>(ptr),((U32)alignment))
+#define ll_assert_aligned(ptr,alignment) ll_assert_aligned_func(uintptr_t(ptr),((U32)alignment))
 #else
 #define ll_assert_aligned(ptr,alignment)
 #endif
@@ -69,13 +70,13 @@ LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment);
 template <typename T> T* LL_NEXT_ALIGNED_ADDRESS(T* address) 
 { 
 	return reinterpret_cast<T*>(
-		(reinterpret_cast<uintptr_t>(address) + 0xF) & ~0xF);
+		(uintptr_t(address) + 0xF) & ~0xF);
 }
 
 template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address) 
 { 
 	return reinterpret_cast<T*>(
-		(reinterpret_cast<uintptr_t>(address) + 0x3F) & ~0x3F);
+		(uintptr_t(address) + 0x3F) & ~0x3F);
 }
 
 #if LL_LINUX || LL_DARWIN
diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h
index 3659a319a2efd3e90d267a800ac05e24950ce901..ea535cee86d1783be719df606b9dea747694e7cd 100644
--- a/indra/llcommon/llmutex.h
+++ b/indra/llcommon/llmutex.h
@@ -33,6 +33,10 @@
 
 #define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
 
+#if MUTEX_DEBUG
+#include <map>
+#endif
+
 struct apr_thread_mutex_t;
 struct apr_pool_t;
 struct apr_thread_cond_t;
diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h
index 02f10fa2ba470d19ed5b743fa327438e970954bf..0435cb8a08d9515afda8ea2316719b86822c2234 100755
--- a/indra/llcommon/llstl.h
+++ b/indra/llcommon/llstl.h
@@ -27,6 +27,7 @@
 #ifndef LL_LLSTL_H
 #define LL_LLSTL_H
 
+#include "stdtypes.h"
 #include <functional>
 #include <algorithm>
 #include <map>
@@ -272,6 +273,7 @@ inline T get_if_there(const std::map<K,T>& inmap, const K& key, T default_value)
 	}
 };
 
+// Useful for replacing the removeObj() functionality of LLDynamicArray
 // Example:
 //  for (std::vector<T>::iterator iter = mList.begin(); iter != mList.end(); )
 //  {
@@ -530,7 +532,7 @@ bool before(const std::type_info* lhs, const std::type_info* rhs)
     return strcmp(lhs->name(), rhs->name()) < 0;
 #else  // not Linux, or gcc 4.4+
     // Just use before(), as we normally would
-    return lhs->before(*rhs);
+    return lhs->before(*rhs) ? true : false;
 #endif
 }
 
diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp
index 41ee3941ac77a6e4ab75de7e1ca3f600289dbdc7..edf5a1b75c009c12af065103faeb1ff29641b571 100755
--- a/indra/llmath/llvolume.cpp
+++ b/indra/llmath/llvolume.cpp
@@ -6072,7 +6072,7 @@ void LLVolumeFace::pushVertex(const LLVector4a& pos, const LLVector4a& norm, con
 
 	if (new_verts > mNumAllocatedVertices)
 	{ 
-		//double buffer size on expansion
+		// double buffer size on expansion
 		new_verts *= 2;
 
 		S32 new_tc_size = ((new_verts*8)+0xF) & ~0xF;
@@ -6088,18 +6088,21 @@ void LLVolumeFace::pushVertex(const LLVector4a& pos, const LLVector4a& norm, con
 		mNormals = mPositions+new_verts;
 		mTexCoords = (LLVector2*) (mNormals+new_verts);
 
-	//positions
-		LLVector4a::memcpyNonAliased16((F32*) mPositions, (F32*) old_buf, old_vsize);
-	
-	//normals
-		LLVector4a::memcpyNonAliased16((F32*) mNormals, (F32*) (old_buf+mNumVertices), old_vsize);
+		if (old_buf != NULL)
+		{
+			// copy old positions into new buffer
+			LLVector4a::memcpyNonAliased16((F32*)mPositions, (F32*)old_buf, old_vsize);
 
-	//tex coords
-		LLVector4a::memcpyNonAliased16((F32*) mTexCoords, (F32*) (old_buf+mNumVertices*2), old_tc_size);
+			// normals
+			LLVector4a::memcpyNonAliased16((F32*)mNormals, (F32*)(old_buf + mNumVertices), old_vsize);
 
-	//just clear tangents
-	ll_aligned_free_16(mTangents);
-	mTangents = NULL;
+			// tex coords
+			LLVector4a::memcpyNonAliased16((F32*)mTexCoords, (F32*)(old_buf + mNumVertices * 2), old_tc_size);
+		}
+
+		// just clear tangents
+		ll_aligned_free_16(mTangents);
+		mTangents = NULL;
 		ll_aligned_free<64>(old_buf);
 
 		mNumAllocatedVertices = new_verts;
diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h
index e1161682b51c77ee4eb497f4a73346a68623e775..1da2d0c6b15f9ea47bed4f9c8b64b693625ddb3d 100755
--- a/indra/llmath/llvolume.h
+++ b/indra/llmath/llvolume.h
@@ -972,6 +972,7 @@ class LLVolume : public LLRefCount
 	~LLVolume(); // use unref
 
 public:
+	typedef std::vector<LLVolumeFace> face_list_t;
 		
 	struct FaceParams
 	{
@@ -1045,6 +1046,10 @@ class LLVolume : public LLRefCount
 																				// conversion if *(LLVolume*) to LLVolume&
 	const LLVolumeFace &getVolumeFace(const S32 f) const {return mVolumeFaces[f];} // DO NOT DELETE VOLUME WHILE USING THIS REFERENCE, OR HOLD A POINTER TO THIS VOLUMEFACE
 	
+	LLVolumeFace &getVolumeFace(const S32 f) {return mVolumeFaces[f];} // DO NOT DELETE VOLUME WHILE USING THIS REFERENCE, OR HOLD A POINTER TO THIS VOLUMEFACE
+
+	face_list_t& getVolumeFaces() { return mVolumeFaces; }
+
 	U32					mFaceMask;			// bit array of which faces exist in this volume
 	LLVector3			mLODScaleBias;		// vector for biasing LOD based on scale
 	
@@ -1084,7 +1089,6 @@ class LLVolume : public LLRefCount
 	
 	
 	BOOL mGenerateSingleFace;
-	typedef std::vector<LLVolumeFace> face_list_t;
 	face_list_t mVolumeFaces;
 
 public:
diff --git a/indra/llmath/m4math.cpp b/indra/llmath/m4math.cpp
index 6a1b4143cfd64e9474f51e94645d54e2c7cda146..d89c482804d760ec49504fb713b959d590bddca4 100755
--- a/indra/llmath/m4math.cpp
+++ b/indra/llmath/m4math.cpp
@@ -274,6 +274,19 @@ const LLMatrix4&	LLMatrix4::invert(void)
 	return *this;
 }
 
+// Convenience func for simplifying comparison-heavy code by
+// intentionally stomping values in [-FLT_EPS,FLT_EPS] to 0.0f
+//
+void LLMatrix4::condition(void)
+{
+	U32 i;
+	U32 j;
+	for (i = 0; i < 3;i++)
+		for (j = 0; j < 3;j++)
+			mMatrix[i][j] = ((mMatrix[i][j] > -FLT_EPSILON)
+							  && (mMatrix[i][j] < FLT_EPSILON)) ? 0.0f : mMatrix[i][j];
+}
+
 LLVector4 LLMatrix4::getFwdRow4() const
 {
 	return LLVector4(mMatrix[VX][VX], mMatrix[VX][VY], mMatrix[VX][VZ], mMatrix[VX][VW]);
diff --git a/indra/llmath/m4math.h b/indra/llmath/m4math.h
index a7dce10397839e4ea21c88d59f4689d29f9193ed..a77c5bc76d11111149559b0f3552823ac9164f9f 100755
--- a/indra/llmath/m4math.h
+++ b/indra/llmath/m4math.h
@@ -180,6 +180,11 @@ class LLMatrix4
 	const LLMatrix4& setTranslation(const LLVector4 &translation);
 	const LLMatrix4& setTranslation(const LLVector3 &translation);
 
+	// Convenience func for simplifying comparison-heavy code by
+	// intentionally stomping values [-FLT_EPS,FLT_EPS] to 0.0
+	//
+	void condition(void);
+
 	///////////////////////////
 	//
 	// Get properties of a matrix
diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt
index d1475cf7342a2fb86751c6254c4c7952f4acad2b..75ce624a58d4d5576055cf7d78864fb58e6fff81 100755
--- a/indra/llprimitive/CMakeLists.txt
+++ b/indra/llprimitive/CMakeLists.txt
@@ -8,6 +8,7 @@ include(LLMath)
 include(LLMessage)
 include(LLXML)
 include(LLPhysicsExtensions)
+include(LLCharacter)
 
 include_directories(
     ${LLCOMMON_INCLUDE_DIRS}
@@ -16,6 +17,7 @@ include_directories(
     ${LLXML_INCLUDE_DIRS}
     ${LIBS_PREBUILT_DIR}/include/collada
     ${LIBS_PREBUILT_DIR}/include/collada/1.4
+    ${LLCHARACTER_INCLUDE_DIRS}
     )
 include_directories(SYSTEM
     ${LLCOMMON_SYSTEM_INCLUDE_DIRS}
@@ -24,11 +26,13 @@ include_directories(SYSTEM
     )
 
 set(llprimitive_SOURCE_FILES
+    lldaeloader.cpp
     llmaterialid.cpp
     llmaterial.cpp
     llmaterialtable.cpp
     llmediaentry.cpp
     llmodel.cpp
+    llmodelloader.cpp
     llprimitive.cpp
     llprimtexturelist.cpp
     lltextureanim.cpp
@@ -40,16 +44,17 @@ set(llprimitive_SOURCE_FILES
 
 set(llprimitive_HEADER_FILES
     CMakeLists.txt
-
+    lldaeloader.h
     legacy_object_types.h
-    lllslconstants.h
     llmaterial.h
     llmaterialid.h
     llmaterialtable.h
     llmediaentry.h
     llmodel.h
+    llmodelloader.h
     llprimitive.h
     llprimtexturelist.h
+    lllslconstants.h
     lltextureanim.h
     lltextureentry.h
     lltreeparams.h
@@ -72,6 +77,7 @@ target_link_libraries(llprimitive
     ${LLMESSAGE_LIBRARIES}
     ${LLXML_LIBRARIES}
     ${LLPHYSICSEXTENSIONS_LIBRARIES}
+    ${LLCHARACTER_LIBRARIES}
     )
 
 
diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c1b74b1fd7ee5cd96a5b2de9f07218b4dd2dcb62
--- /dev/null
+++ b/indra/llprimitive/lldaeloader.cpp
@@ -0,0 +1,2436 @@
+/**
+ * @file lldaeloader.cpp
+ * @brief LLDAELoader class implementation
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2013, 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$
+ */
+
+#if LL_MSVC
+#pragma warning (disable : 4263)
+#pragma warning (disable : 4264)
+#endif
+#include "dae.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"
+#if LL_MSVC
+#pragma warning (default : 4263)
+#pragma warning (default : 4264)
+#endif
+
+#include <boost/lexical_cast.hpp>
+
+#include "lldaeloader.h"
+#include "llsdserialize.h"
+#include "lljoint.h"
+
+#include "glh/glh_linear.h"
+#include "llmatrix4a.h"
+
+std::string colladaVersion[VERSIONTYPE_COUNT+1] = 
+{
+	"1.4.0",
+	"1.4.1",
+	"Unsupported"
+};
+
+static const std::string lod_suffix[LLModel::NUM_LODS] =
+{
+	"_LOD0",
+	"_LOD1",
+	"_LOD2",
+	"",
+	"_PHYS",
+};
+
+const U32 LIMIT_MATERIALS_OUTPUT = 12;
+
+bool get_dom_sources(const domInputLocalOffset_Array& inputs, S32& pos_offset, S32& tc_offset, S32& norm_offset, S32 &idx_stride,
+	domSource* &pos_source, domSource* &tc_source, domSource* &norm_source)
+{
+	idx_stride = 0;
+
+	for (U32 j = 0; j < inputs.getCount(); ++j)
+	{
+		idx_stride = llmax((S32) inputs[j]->getOffset(), idx_stride);
+
+		if (strcmp(COMMON_PROFILE_INPUT_VERTEX, inputs[j]->getSemantic()) == 0)
+		{ //found vertex array
+			const domURIFragmentType& uri = inputs[j]->getSource();
+			daeElementRef elem = uri.getElement();
+			domVertices* vertices = (domVertices*) elem.cast();
+			if ( !vertices )
+			{
+				return false;
+			}
+
+			domInputLocal_Array& v_inp = vertices->getInput_array();
+
+
+			for (U32 k = 0; k < v_inp.getCount(); ++k)
+			{
+				if (strcmp(COMMON_PROFILE_INPUT_POSITION, v_inp[k]->getSemantic()) == 0)
+				{
+					pos_offset = inputs[j]->getOffset();
+
+					const domURIFragmentType& uri = v_inp[k]->getSource();
+					daeElementRef elem = uri.getElement();
+					pos_source = (domSource*) elem.cast();
+				}
+
+				if (strcmp(COMMON_PROFILE_INPUT_NORMAL, v_inp[k]->getSemantic()) == 0)
+				{
+					norm_offset = inputs[j]->getOffset();
+
+					const domURIFragmentType& uri = v_inp[k]->getSource();
+					daeElementRef elem = uri.getElement();
+					norm_source = (domSource*) elem.cast();
+				}
+			}
+		}
+
+		if (strcmp(COMMON_PROFILE_INPUT_NORMAL, inputs[j]->getSemantic()) == 0)
+		{
+			//found normal array for this triangle list
+			norm_offset = inputs[j]->getOffset();
+			const domURIFragmentType& uri = inputs[j]->getSource();
+			daeElementRef elem = uri.getElement();
+			norm_source = (domSource*) elem.cast();
+		}
+		else if (strcmp(COMMON_PROFILE_INPUT_TEXCOORD, inputs[j]->getSemantic()) == 0)
+		{ //found texCoords
+			tc_offset = inputs[j]->getOffset();
+			const domURIFragmentType& uri = inputs[j]->getSource();
+			daeElementRef elem = uri.getElement();
+			tc_source = (domSource*) elem.cast();
+		}
+	}
+
+	idx_stride += 1;
+
+	return true;
+}
+
+LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domTrianglesRef& tri)
+{
+	LLVolumeFace face;
+	std::vector<LLVolumeFace::VertexData> verts;
+	std::vector<U16> indices;
+	
+	const domInputLocalOffset_Array& inputs = tri->getInput_array();
+
+	S32 pos_offset = -1;
+	S32 tc_offset = -1;
+	S32 norm_offset = -1;
+
+	domSource* pos_source = NULL;
+	domSource* tc_source = NULL;
+	domSource* norm_source = NULL;
+
+	S32 idx_stride = 0;
+
+	if ( !get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source))
+	{
+		return LLModel::BAD_ELEMENT;
+	}
+
+	if (!pos_source || !pos_source->getFloat_array())
+	{
+		LL_WARNS() << "Unable to process mesh without position data; invalid model;  invalid model." << LL_ENDL;
+		return LLModel::BAD_ELEMENT;
+	}
+
+	domPRef p = tri->getP();
+	domListOfUInts& idx = p->getValue();
+	
+	domListOfFloats  dummy ;
+	domListOfFloats& v = pos_source ? pos_source->getFloat_array()->getValue() : dummy ;
+	domListOfFloats& tc = tc_source ? tc_source->getFloat_array()->getValue() : dummy ;
+	domListOfFloats& n = norm_source ? norm_source->getFloat_array()->getValue() : dummy ;
+
+	if (pos_source)
+	{
+		if(v.getCount() == 0)
+		{
+			return LLModel::BAD_ELEMENT;
+		}
+
+		face.mExtents[0].set(v[0], v[1], v[2]);
+		face.mExtents[1].set(v[0], v[1], v[2]);
+	}
+	
+	LLVolumeFace::VertexMapData::PointMap point_map;
+	
+	for (U32 i = 0; i < idx.getCount(); i += idx_stride)
+	{
+		LLVolumeFace::VertexData cv;
+		if (pos_source)
+		{
+			cv.setPosition(LLVector4a(v[idx[i+pos_offset]*3+0],
+								v[idx[i+pos_offset]*3+1],
+								v[idx[i+pos_offset]*3+2]));
+		}
+
+		if (tc_source)
+		{
+			cv.mTexCoord.setVec(tc[idx[i+tc_offset]*2+0],
+								tc[idx[i+tc_offset]*2+1]);
+		}
+		
+		if (norm_source)
+		{
+			cv.setNormal(LLVector4a(n[idx[i+norm_offset]*3+0],
+								n[idx[i+norm_offset]*3+1],
+								n[idx[i+norm_offset]*3+2]));
+		}
+		
+		BOOL found = FALSE;
+			
+		LLVolumeFace::VertexMapData::PointMap::iterator point_iter;
+		point_iter = point_map.find(LLVector3(cv.getPosition().getF32ptr()));
+		
+		if (point_iter != point_map.end())
+		{
+			for (U32 j = 0; j < point_iter->second.size(); ++j)
+			{
+				// We have a matching loc
+				//
+				if ((point_iter->second)[j] == cv)
+				{
+					U16 shared_index	= (point_iter->second)[j].mIndex;
+
+					// Don't share verts within the same tri, degenerate
+					//
+					if ((indices.size() % 3) && (indices[indices.size()-1] != shared_index))
+					{
+						found = true;
+						indices.push_back(shared_index);
+					}
+					break;
+				}
+			}
+		}
+
+		if (!found)
+		{
+			update_min_max(face.mExtents[0], face.mExtents[1], cv.getPosition());
+			verts.push_back(cv);
+			if (verts.size() >= 65535)
+			{
+				//llerrs << "Attempted to write model exceeding 16-bit index buffer limitation." << LL_ENDL;
+				return LLModel::VERTEX_NUMBER_OVERFLOW ;
+			}
+			U16 index = (U16) (verts.size()-1);
+			indices.push_back(index);
+
+			LLVolumeFace::VertexMapData d;
+			d.setPosition(cv.getPosition());
+			d.mTexCoord = cv.mTexCoord;
+			d.setNormal(cv.getNormal());
+			d.mIndex = index;
+			if (point_iter != point_map.end())
+			{
+				point_iter->second.push_back(d);
+			}
+			else
+			{
+				point_map[LLVector3(d.getPosition().getF32ptr())].push_back(d);
+			}
+		}
+
+		if (indices.size()%3 == 0 && verts.size() >= 65532)
+		{
+			std::string material;
+
+			if (tri->getMaterial())
+			{
+				material = std::string(tri->getMaterial());
+			}
+
+			materials.push_back(material);
+			face_list.push_back(face);
+			face_list.rbegin()->fillFromLegacyData(verts, indices);
+			LLVolumeFace& new_face = *face_list.rbegin();
+			if (!norm_source)
+			{
+				//ll_aligned_free_16(new_face.mNormals);
+				new_face.mNormals = NULL;
+			}
+
+			if (!tc_source)
+			{
+				//ll_aligned_free_16(new_face.mTexCoords);
+				new_face.mTexCoords = NULL;
+			}
+
+			face = LLVolumeFace();
+			point_map.clear();
+		}
+	}
+
+	if (!verts.empty())
+	{
+		std::string material;
+
+		if (tri->getMaterial())
+		{
+			material = std::string(tri->getMaterial());
+		}
+		
+		materials.push_back(material);
+		face_list.push_back(face);
+
+		face_list.rbegin()->fillFromLegacyData(verts, indices);
+		LLVolumeFace& new_face = *face_list.rbegin();
+		if (!norm_source)
+		{
+			//ll_aligned_free_16(new_face.mNormals);
+			new_face.mNormals = NULL;
+		}
+
+		if (!tc_source)
+		{
+			//ll_aligned_free_16(new_face.mTexCoords);
+			new_face.mTexCoords = NULL;
+		}
+	}
+
+	return LLModel::NO_ERRORS ;
+}
+
+LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domPolylistRef& poly)
+{
+	domPRef p = poly->getP();
+	domListOfUInts& idx = p->getValue();
+
+	if (idx.getCount() == 0)
+	{
+		return LLModel::NO_ERRORS ;
+	}
+
+	const domInputLocalOffset_Array& inputs = poly->getInput_array();
+
+
+	domListOfUInts& vcount = poly->getVcount()->getValue();
+	
+	S32 pos_offset = -1;
+	S32 tc_offset = -1;
+	S32 norm_offset = -1;
+
+	domSource* pos_source = NULL;
+	domSource* tc_source = NULL;
+	domSource* norm_source = NULL;
+
+	S32 idx_stride = 0;
+
+	if (!get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source))
+	{
+		return LLModel::BAD_ELEMENT;
+	}
+
+	LLVolumeFace face;
+
+	std::vector<U16> indices;
+	std::vector<LLVolumeFace::VertexData> verts;
+
+	domListOfFloats v;
+	domListOfFloats tc;
+	domListOfFloats n;
+
+	if (pos_source)
+	{
+		v = pos_source->getFloat_array()->getValue();
+		face.mExtents[0].set(v[0], v[1], v[2]);
+		face.mExtents[1].set(v[0], v[1], v[2]);
+	}
+
+	if (tc_source)
+	{
+		tc = tc_source->getFloat_array()->getValue();
+	}
+
+	if (norm_source)
+	{
+		n = norm_source->getFloat_array()->getValue();
+	}
+	
+	LLVolumeFace::VertexMapData::PointMap point_map;
+
+	U32 cur_idx = 0;
+	for (U32 i = 0; i < vcount.getCount(); ++i)
+	{ //for each polygon
+		U32 first_index = 0;
+		U32 last_index = 0;
+		for (U32 j = 0; j < vcount[i]; ++j)
+		{ //for each vertex
+
+			LLVolumeFace::VertexData cv;
+
+			if (pos_source)
+			{
+				cv.getPosition().set(v[idx[cur_idx+pos_offset]*3+0],
+									v[idx[cur_idx+pos_offset]*3+1],
+									v[idx[cur_idx+pos_offset]*3+2]);
+				if (!cv.getPosition().isFinite3())
+				{
+					LL_WARNS() << "Found NaN while loading position data from DAE-Model, invalid model." << LL_ENDL;
+					return LLModel::BAD_ELEMENT;
+				}
+			}
+
+			if (tc_source)
+			{
+				cv.mTexCoord.setVec(tc[idx[cur_idx+tc_offset]*2+0],
+									tc[idx[cur_idx+tc_offset]*2+1]);
+			}
+			
+			if (norm_source)
+			{
+				cv.getNormal().set(n[idx[cur_idx+norm_offset]*3+0],
+									n[idx[cur_idx+norm_offset]*3+1],
+									n[idx[cur_idx+norm_offset]*3+2]);
+
+				if (!cv.getNormal().isFinite3())
+				{
+					LL_WARNS() << "Found NaN while loading normals from DAE-Model, invalid model." << LL_ENDL;
+					return LLModel::BAD_ELEMENT;
+				}
+			}
+			
+			cur_idx += idx_stride;
+			
+			BOOL found = FALSE;
+				
+			LLVolumeFace::VertexMapData::PointMap::iterator point_iter;
+			LLVector3 pos3(cv.getPosition().getF32ptr());
+			point_iter = point_map.find(pos3);
+			
+			if (point_iter != point_map.end())
+			{
+				for (U32 k = 0; k < point_iter->second.size(); ++k)
+				{
+					if ((point_iter->second)[k] == cv)
+					{
+						found = TRUE;
+						U32 index = (point_iter->second)[k].mIndex;
+						if (j == 0)
+						{
+							first_index = index;
+						}
+						else if (j == 1)
+						{
+							last_index = index;
+						}
+						else
+						{
+							// if these are the same, we have a very, very skinny triangle (coincident verts on one or more edges)
+							//
+							llassert((first_index != last_index) && (last_index != index) && (first_index != index));
+							indices.push_back(first_index);
+							indices.push_back(last_index);
+							indices.push_back(index);
+							last_index = index;
+						}
+
+						break;
+					}
+				}
+			}
+
+			if (!found)
+			{
+				update_min_max(face.mExtents[0], face.mExtents[1], cv.getPosition());
+				verts.push_back(cv);
+				if (verts.size() >= 65535)
+				{
+					//llerrs << "Attempted to write model exceeding 16-bit index buffer limitation." << LL_ENDL;
+					return LLModel::VERTEX_NUMBER_OVERFLOW ;
+				}
+				U16 index = (U16) (verts.size()-1);
+			
+				if (j == 0)
+				{
+					first_index = index;
+				}
+				else if (j == 1)
+				{
+					last_index = index;
+				}
+				else
+				{
+					// detect very skinny degenerate triangles with collapsed edges
+					//
+					llassert((first_index != last_index) && (last_index != index) && (first_index != index));
+					indices.push_back(first_index);
+					indices.push_back(last_index);
+					indices.push_back(index);
+					last_index = index;
+				}	
+
+				LLVolumeFace::VertexMapData d;
+				d.setPosition(cv.getPosition());
+				d.mTexCoord = cv.mTexCoord;
+				d.setNormal(cv.getNormal());
+				d.mIndex = index;
+				if (point_iter != point_map.end())
+				{
+					point_iter->second.push_back(d);
+				}
+				else
+				{
+					point_map[pos3].push_back(d);
+				}
+			}
+
+			if (indices.size()%3 == 0 && indices.size() >= 65532)
+			{
+				std::string material;
+
+				if (poly->getMaterial())
+				{
+					material = std::string(poly->getMaterial());
+				}
+
+				materials.push_back(material);
+				face_list.push_back(face);
+				face_list.rbegin()->fillFromLegacyData(verts, indices);
+				LLVolumeFace& new_face = *face_list.rbegin();
+				if (!norm_source)
+				{
+					//ll_aligned_free_16(new_face.mNormals);
+					new_face.mNormals = NULL;
+				}
+
+				if (!tc_source)
+				{
+					//ll_aligned_free_16(new_face.mTexCoords);
+					new_face.mTexCoords = NULL;
+				}
+
+				face = LLVolumeFace();
+				verts.clear();
+				indices.clear();
+				point_map.clear();
+			}
+		}
+	}
+
+	if (!verts.empty())
+	{
+		std::string material;
+
+		if (poly->getMaterial())
+		{
+			material = std::string(poly->getMaterial());
+		}
+	
+		materials.push_back(material);
+		face_list.push_back(face);
+		face_list.rbegin()->fillFromLegacyData(verts, indices);
+
+		LLVolumeFace& new_face = *face_list.rbegin();
+		if (!norm_source)
+		{
+			//ll_aligned_free_16(new_face.mNormals);
+			new_face.mNormals = NULL;
+		}
+
+		if (!tc_source)
+		{
+			//ll_aligned_free_16(new_face.mTexCoords);
+			new_face.mTexCoords = NULL;
+		}
+	}
+
+	return LLModel::NO_ERRORS ;
+}
+
+LLModel::EModelStatus load_face_from_dom_polygons(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domPolygonsRef& poly)
+{
+	LLVolumeFace face;
+	std::vector<U16> indices;
+	std::vector<LLVolumeFace::VertexData> verts;
+
+	const domInputLocalOffset_Array& inputs = poly->getInput_array();
+
+	S32 v_offset = -1;
+	S32 n_offset = -1;
+	S32 t_offset = -1;
+
+	domListOfFloats* v = NULL;
+	domListOfFloats* n = NULL;
+	domListOfFloats* t = NULL;
+	
+	U32 stride = 0;
+	for (U32 i = 0; i < inputs.getCount(); ++i)
+	{
+		stride = llmax((U32) inputs[i]->getOffset()+1, stride);
+
+		if (strcmp(COMMON_PROFILE_INPUT_VERTEX, inputs[i]->getSemantic()) == 0)
+		{ //found vertex array
+			v_offset = inputs[i]->getOffset();
+
+			const domURIFragmentType& uri = inputs[i]->getSource();
+			daeElementRef elem = uri.getElement();
+			domVertices* vertices = (domVertices*) elem.cast();
+			if (!vertices)
+			{
+				return LLModel::BAD_ELEMENT;
+			}
+			domInputLocal_Array& v_inp = vertices->getInput_array();
+
+			for (U32 k = 0; k < v_inp.getCount(); ++k)
+			{
+				if (strcmp(COMMON_PROFILE_INPUT_POSITION, v_inp[k]->getSemantic()) == 0)
+				{
+					const domURIFragmentType& uri = v_inp[k]->getSource();
+					daeElementRef elem = uri.getElement();
+					domSource* src = (domSource*) elem.cast();
+					if (!src)
+					{
+						return LLModel::BAD_ELEMENT;
+					}
+					v = &(src->getFloat_array()->getValue());
+				}
+			}
+		}
+		else if (strcmp(COMMON_PROFILE_INPUT_NORMAL, inputs[i]->getSemantic()) == 0)
+		{
+			n_offset = inputs[i]->getOffset();
+			//found normal array for this triangle list
+			const domURIFragmentType& uri = inputs[i]->getSource();
+			daeElementRef elem = uri.getElement();
+			domSource* src = (domSource*) elem.cast();
+			if (!src)
+			{
+				return LLModel::BAD_ELEMENT;
+			}
+			n = &(src->getFloat_array()->getValue());
+		}
+		else if (strcmp(COMMON_PROFILE_INPUT_TEXCOORD, inputs[i]->getSemantic()) == 0 && inputs[i]->getSet() == 0)
+		{ //found texCoords
+			t_offset = inputs[i]->getOffset();
+			const domURIFragmentType& uri = inputs[i]->getSource();
+			daeElementRef elem = uri.getElement();
+			domSource* src = (domSource*) elem.cast();
+			if (!src)
+			{
+				return LLModel::BAD_ELEMENT;
+			}
+			t = &(src->getFloat_array()->getValue());
+		}
+	}
+
+	domP_Array& ps = poly->getP_array();
+
+	//make a triangle list in <verts>
+	for (U32 i = 0; i < ps.getCount(); ++i)
+	{ //for each polygon
+		domListOfUInts& idx = ps[i]->getValue();
+		for (U32 j = 0; j < idx.getCount()/stride; ++j)
+		{ //for each vertex
+			if (j > 2)
+			{
+				U32 size = verts.size();
+				LLVolumeFace::VertexData v0 = verts[size-3];
+				LLVolumeFace::VertexData v1 = verts[size-1];
+
+				verts.push_back(v0);
+				verts.push_back(v1);
+			}
+
+			LLVolumeFace::VertexData vert;
+
+
+			if (v)
+			{
+				U32 v_idx = idx[j*stride+v_offset]*3;
+				v_idx = llclamp(v_idx, (U32) 0, (U32) v->getCount());
+				vert.getPosition().set(v->get(v_idx),
+								v->get(v_idx+1),
+								v->get(v_idx+2));
+			}
+			
+			//bounds check n and t lookups because some FBX to DAE converters
+			//use negative indices and empty arrays to indicate data does not exist
+			//for a particular channel
+			if (n && n->getCount() > 0)
+			{
+				U32 n_idx = idx[j*stride+n_offset]*3;
+				n_idx = llclamp(n_idx, (U32) 0, (U32) n->getCount());
+				vert.getNormal().set(n->get(n_idx),
+								n->get(n_idx+1),
+								n->get(n_idx+2));
+			}
+			else
+			{
+				vert.getNormal().clear();
+			}
+
+			
+			if (t && t->getCount() > 0)
+			{
+				U32 t_idx = idx[j*stride+t_offset]*2;
+				t_idx = llclamp(t_idx, (U32) 0, (U32) t->getCount());
+				vert.mTexCoord.setVec(t->get(t_idx),
+								t->get(t_idx+1));								
+			}
+			else
+			{
+				vert.mTexCoord.clear();
+			}
+
+						
+			verts.push_back(vert);
+		}
+	}
+
+	if (verts.empty())
+	{
+		return LLModel::NO_ERRORS;
+	}
+
+	face.mExtents[0] = verts[0].getPosition();
+	face.mExtents[1] = verts[0].getPosition();
+	
+	//create a map of unique vertices to indices
+	std::map<LLVolumeFace::VertexData, U32> vert_idx;
+
+	U32 cur_idx = 0;
+	for (U32 i = 0; i < verts.size(); ++i)
+	{
+		std::map<LLVolumeFace::VertexData, U32>::iterator iter = vert_idx.find(verts[i]);
+		if (iter == vert_idx.end())
+		{
+			vert_idx[verts[i]] = cur_idx++;
+		}
+	}
+
+	//build vertex array from map
+	std::vector<LLVolumeFace::VertexData> new_verts;
+	new_verts.resize(vert_idx.size());
+
+	for (std::map<LLVolumeFace::VertexData, U32>::iterator iter = vert_idx.begin(); iter != vert_idx.end(); ++iter)
+	{
+		new_verts[iter->second] = iter->first;
+		update_min_max(face.mExtents[0], face.mExtents[1], iter->first.getPosition());
+	}
+
+	//build index array from map
+	indices.resize(verts.size());
+
+	for (U32 i = 0; i < verts.size(); ++i)
+	{
+		indices[i] = vert_idx[verts[i]];
+		llassert(!i || (indices[i-1] != indices[i]));
+	}
+
+	// DEBUG just build an expanded triangle list
+	/*for (U32 i = 0; i < verts.size(); ++i)
+	{
+		indices.push_back((U16) i);
+		update_min_max(face.mExtents[0], face.mExtents[1], verts[i].getPosition());
+	}*/
+
+    if (!new_verts.empty())
+	{
+		std::string material;
+
+		if (poly->getMaterial())
+		{
+			material = std::string(poly->getMaterial());
+		}
+
+		materials.push_back(material);
+		face_list.push_back(face);
+		face_list.rbegin()->fillFromLegacyData(new_verts, indices);
+
+		LLVolumeFace& new_face = *face_list.rbegin();
+		if (!n)
+		{
+			//ll_aligned_free_16(new_face.mNormals);
+			new_face.mNormals = NULL;
+		}
+
+		if (!t)
+		{
+			//ll_aligned_free_16(new_face.mTexCoords);
+			new_face.mTexCoords = NULL;
+		}
+	}
+
+	return LLModel::NO_ERRORS ;
+}
+
+//-----------------------------------------------------------------------------
+// LLDAELoader
+//-----------------------------------------------------------------------------
+LLDAELoader::LLDAELoader(
+	std::string				filename,
+	S32						lod,
+	load_callback_t		load_cb,
+	joint_lookup_func_t	joint_lookup_func,
+	texture_load_func_t	texture_load_func,
+	state_callback_t		state_cb,
+	void*						opaque_userdata,
+	JointTransformMap&	jointMap,
+	JointSet&				jointsFromNodes,
+	U32					modelLimit)
+: LLModelLoader(
+		filename,
+		lod,
+		load_cb,
+		joint_lookup_func,
+		texture_load_func,
+		state_cb,
+		opaque_userdata,
+		jointMap,
+		jointsFromNodes),
+mGeneratedModelLimit(modelLimit)
+{
+}
+
+LLDAELoader::~LLDAELoader()
+{
+}
+
+struct ModelSort
+{
+	bool operator()(const LLPointer< LLModel >& lhs, const LLPointer< LLModel >& rhs)
+	{
+        if (lhs->mSubmodelID < rhs->mSubmodelID)
+        {
+            return true;
+        }
+		return LLStringUtil::compareInsensitive(lhs->mLabel, rhs->mLabel) < 0;
+	}
+};
+
+bool LLDAELoader::OpenFile(const std::string& filename)
+{
+	//no suitable slm exists, load from the .dae file
+	DAE dae;
+	domCOLLADA* dom = dae.open(filename);
+	
+	if (!dom)
+	{
+		LL_INFOS() <<" Error with dae - traditionally indicates a corrupt file."<<LL_ENDL;
+		setLoadState( ERROR_PARSING );
+		return false;
+	}
+	//Dom version
+	daeString domVersion = dae.getDomVersion();
+	std::string sldom(domVersion);
+	LL_INFOS()<<"Collada Importer Version: "<<sldom<<LL_ENDL;
+	//Dae version
+	domVersionType docVersion = dom->getVersion();
+	//0=1.4
+	//1=1.4.1
+	//2=Currently unsupported, however may work
+	if (docVersion > 1 ) 
+	{ 
+		docVersion = VERSIONTYPE_COUNT;
+	}
+	LL_INFOS()<<"Dae version "<<colladaVersion[docVersion]<<LL_ENDL;
+	
+	
+	daeDatabase* db = dae.getDatabase();
+	
+	daeInt count = db->getElementCount(NULL, COLLADA_TYPE_MESH);
+	
+	daeDocument* doc = dae.getDoc(mFilename);
+	if (!doc)
+	{
+		LL_WARNS() << "can't find internal doc" << LL_ENDL;
+		return false;
+	}
+	
+	daeElement* root = doc->getDomRoot();
+	if (!root)
+	{
+		LL_WARNS() << "document has no root" << LL_ENDL;
+		return false;
+	}
+	
+	//Verify some basic properties of the dae
+	//1. Basic validity check on controller 
+	U32 controllerCount = (int) db->getElementCount( NULL, "controller" );
+	bool result = false;
+	for ( int i=0; i<controllerCount; ++i )
+	{
+		domController* pController = NULL;
+		db->getElement( (daeElement**) &pController, i , NULL, "controller" );
+		result = verifyController( pController );
+		if (!result)
+		{
+			LL_INFOS() << "Could not verify controller" << LL_ENDL;
+			setLoadState( ERROR_PARSING );
+			return true;
+		}
+	}
+
+
+	//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;
+
+	mTransform.condition();	
+
+	U32 submodel_limit = count > 0 ? mGeneratedModelLimit/count : 0;
+	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)
+		{
+
+			std::vector<LLModel*> models;
+
+			loadModelsFromDomMesh(mesh, models, submodel_limit);
+
+			std::vector<LLModel*>::iterator i;
+			i = models.begin();
+			while (i != models.end())
+			{
+				LLModel* mdl = *i;
+				if(mdl->getStatus() != LLModel::NO_ERRORS)
+				{
+					setLoadState(ERROR_MODEL + mdl->getStatus()) ;
+					return false; //abort
+				}
+
+				if (mdl && validate_model(mdl))
+				{
+					mModelList.push_back(mdl);
+					mModelsMap[mesh].push_back(mdl);
+				}
+				i++;
+			}
+		}
+	}
+
+	std::sort(mModelList.begin(), mModelList.end(), ModelSort());
+
+	model_list::iterator model_iter = mModelList.begin();
+	while (model_iter != mModelList.end())
+	{
+		LLModel* mdl = *model_iter;
+		U32 material_count = mdl->mMaterialList.size();
+		LL_INFOS() << "Importing " << mdl->mLabel << " model with " << material_count << " material references" << LL_ENDL;
+		std::vector<std::string>::iterator mat_iter = mdl->mMaterialList.begin();
+		std::vector<std::string>::iterator end_iter = material_count > LIMIT_MATERIALS_OUTPUT
+														? mat_iter + LIMIT_MATERIALS_OUTPUT
+														: mdl->mMaterialList.end();
+		while (mat_iter != end_iter)
+		{
+			LL_INFOS() << mdl->mLabel << " references " << (*mat_iter) << LL_ENDL;
+			mat_iter++;
+		}
+		model_iter++;
+	}
+
+	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)
+				{
+					std::vector< LLPointer< LLModel > >::iterator i = mModelsMap[mesh].begin();
+					while (i != mModelsMap[mesh].end())
+					{
+						LLPointer<LLModel> mdl = *i;
+						LLDAELoader::processDomModel(mdl, &dae, root, mesh, skin);
+						i++;
+					}
+				}
+			}
+		}
+	}
+
+	LL_INFOS()<< "Collada skins processed: " << count <<LL_ENDL;
+
+	daeElement* scene = root->getDescendant("visual_scene");
+	
+	if (!scene)
+	{
+		LL_WARNS() << "document has no visual_scene" << LL_ENDL;
+		setLoadState( ERROR_PARSING );
+		return true;
+	}
+	
+	setLoadState( DONE );
+
+	bool badElement = false;
+	
+	processElement( scene, badElement, &dae );
+	
+	if ( badElement )
+	{
+		LL_INFOS()<<"Scene could not be parsed"<<LL_ENDL;
+		setLoadState( ERROR_PARSING );
+	}
+	
+	return true;
+}
+
+void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, domMesh* mesh, domSkin* skin)
+{
+	llassert(model && dae && mesh && skin);
+
+	if (model)
+	{
+		LLVector3 mesh_scale_vector;
+		LLVector3 mesh_translation_vector;
+		model->getNormalizedScaleTranslation(mesh_scale_vector, mesh_translation_vector);
+
+		LLMatrix4 normalized_transformation;
+		normalized_transformation.setTranslation(mesh_translation_vector);
+
+		LLMatrix4 mesh_scale;
+		mesh_scale.initScale(mesh_scale_vector);
+		mesh_scale *= normalized_transformation;
+		normalized_transformation = mesh_scale;
+
+		glh::matrix4f inv_mat((F32*) normalized_transformation.mMatrix);
+		inv_mat = inv_mat.inverse();
+		LLMatrix4 inverse_normalized_transformation(inv_mat.m);
+
+		domSkin::domBind_shape_matrix* bind_mat = skin->getBind_shape_matrix();
+
+		if (bind_mat)
+		{ //get bind shape matrix
+			domFloat4x4& dom_value = bind_mat->getValue();
+
+			LLMeshSkinInfo& skin_info = model->mSkinInfo;
+
+			for (int i = 0; i < 4; i++)
+			{
+				for(int j = 0; j < 4; j++)
+				{
+					skin_info.mBindShapeMatrix.mMatrix[i][j] = dom_value[i + j*4];
+				}
+			}
+
+			LLMatrix4 trans = normalized_transformation;
+			trans *= skin_info.mBindShapeMatrix;
+			skin_info.mBindShapeMatrix = trans;							
+		}
+
+
+		//Some collada setup for accessing the skeleton
+		daeElement* pElement = 0;
+		dae->getDatabase()->getElement( &pElement, 0, 0, "skeleton" );
+
+		//Try to get at the skeletal instance controller
+		domInstance_controller::domSkeleton* pSkeleton = daeSafeCast<domInstance_controller::domSkeleton>( pElement );
+		bool missingSkeletonOrScene = false;
+
+		//If no skeleton, do a breadth-first search to get at specific joints
+		bool rootNode = false;
+
+		//Need to test for a skeleton that does not have a root node
+		//This occurs when your instance controller does not have an associated scene 
+		if ( pSkeleton )
+		{
+			daeElement* pSkeletonRootNode = pSkeleton->getValue().getElement();
+			if ( pSkeletonRootNode )
+			{
+				rootNode = true;
+			}
+
+		}
+		if ( !pSkeleton || !rootNode )
+		{
+			daeElement* pScene = root->getDescendant("visual_scene");
+			if ( !pScene )
+			{
+				LL_WARNS()<<"No visual scene - unable to parse bone offsets "<<LL_ENDL;
+				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, mJointList );
+					}
+				}
+			}
+		}
+		else
+			//Has Skeleton
+		{
+			//Get the root node of the skeleton
+			daeElement* pSkeletonRootNode = pSkeleton->getValue().getElement();
+			if ( pSkeletonRootNode )
+			{
+				//Once we have the root node - start acccessing it's joint components
+				const int jointCnt = mJointMap.size();
+				JointMap :: const_iterator jointIt = mJointMap.begin();
+
+				//Loop over all the possible joints within the .dae - using the allowed joint list in the ctor.
+				for ( int i=0; i<jointCnt; ++i, ++jointIt )
+				{
+					//Build a joint for the resolver to work with
+					char str[64]={0};
+					sprintf(str,"./%s",(*jointIt).first.c_str() );
+					//LL_WARNS()<<"Joint "<< str <<LL_ENDL;
+
+					//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 jointResolverA( pJoint, "./translate" );
+						domTranslate* pTranslateA = daeSafeCast<domTranslate>( jointResolverA.getElement() );
+						daeSIDResolver jointResolverB( pJoint, "./location" );
+						domTranslate* pTranslateB = daeSafeCast<domTranslate>( jointResolverB.getElement() );
+
+						LLMatrix4 workingTransform;
+
+						//Translation via SID
+						if ( pTranslateA )
+						{
+							extractTranslation( pTranslateA, workingTransform );
+						}
+						else
+							if ( pTranslateB )
+							{
+								extractTranslation( pTranslateB, workingTransform );
+							}
+							else
+							{
+								//Translation via child from element
+								daeElement* pTranslateElement = getChildFromElement( pJoint, "translate" );
+								if ( pTranslateElement && pTranslateElement->typeID() != domTranslate::ID() )
+								{
+									LL_WARNS()<< "The found element is not a translate node" <<LL_ENDL;
+									missingSkeletonOrScene = true;
+								}
+								else
+									if ( pTranslateElement )
+									{
+										extractTranslationViaElement( pTranslateElement, workingTransform );
+									}
+									else
+									{
+										extractTranslationViaSID( pJoint, workingTransform );
+									}
+
+							}
+
+							//Store the joint transform w/respect to it's name.
+							mJointList[(*jointIt).second.c_str()] = workingTransform;
+					}
+				}
+
+				//If anything failed in regards to extracting the skeleton, joints or translation id,
+				//mention it
+				if ( missingSkeletonOrScene  )
+				{
+					LL_WARNS()<< "Partial jointmap found in asset - did you mean to just have a partial map?" << LL_ENDL;
+				}
+			}//got skeleton?
+		}
+
+
+		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->mSkinInfo.mJointNames
+				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->mSkinInfo.mJointNames.push_back(name);
+							model->mSkinInfo.mJointMap[name] = j;
+						}
+					}
+					else
+					{
+						domIDREF_array* names_source = source->getIDREF_array();
+						if (names_source)
+						{
+							xsIDREFS& names = names_source->getValue();
+
+							for (size_t j = 0; j < names.getCount(); ++j)
+							{
+								std::string name(names.get(j).getID());
+								if (mJointMap.find(name) != mJointMap.end())
+								{
+									name = mJointMap[name];
+								}
+								model->mSkinInfo.mJointNames.push_back(name);
+								model->mSkinInfo.mJointMap[name] = j;
+							}
+						}
+					}
+				}
+			}
+			else if (strcmp(semantic, COMMON_PROFILE_INPUT_INV_BIND_MATRIX) == 0)
+			{ //found inv_bind_matrix array, fill model->mInvBindMatrix
+				domSource* source = daeSafeCast<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->mSkinInfo.mInvBindMatrix.push_back(mat);											
+						}
+					}
+				}
+			}
+		}
+
+		//Now that we've parsed the joint array, let's determine if we have a full rig
+		//(which means we have all the joint sthat are required for an avatar versus
+		//a skinned asset attached to a node in a file that contains an entire skeleton,
+		//but does not use the skeleton).						
+		buildJointToNodeMappingFromScene( root );
+		critiqueRigForUploadApplicability( model->mSkinInfo.mJointNames );
+
+		if ( !missingSkeletonOrScene )
+		{
+			//Set the joint translations on the avatar - if it's a full mapping
+			//The joints are reset in the dtor
+			if ( getRigWithSceneParity() )
+			{	
+				JointMap :: const_iterator masterJointIt = mJointMap.begin();
+				JointMap :: const_iterator masterJointItEnd = mJointMap.end();
+				for (;masterJointIt!=masterJointItEnd;++masterJointIt )
+				{
+					std::string lookingForJoint = (*masterJointIt).first.c_str();
+
+					if ( mJointList.find( lookingForJoint ) != mJointList.end() )
+					{
+						//LL_INFOS()<<"joint "<<lookingForJoint.c_str()<<LL_ENDL;
+						LLMatrix4 jointTransform = mJointList[lookingForJoint];
+						LLJoint* pJoint = mJointLookupFunc(lookingForJoint,mOpaqueData);
+						if ( pJoint )
+						{   
+							LLUUID fake_mesh_id;
+							fake_mesh_id.generate();
+							pJoint->addAttachmentPosOverride( jointTransform.getTranslation(), fake_mesh_id, "");
+						}
+						else
+						{
+							//Most likely an error in the asset.
+							LL_WARNS()<<"Tried to apply joint position from .dae, but it did not exist in the avatar rig." << LL_ENDL;
+						}
+					}
+				}
+			}
+		} //missingSkeletonOrScene
+
+		//We need to construct the alternate bind matrix (which contains the new joint positions)
+		//in the same order as they were stored in the joint buffer. The joints associated
+		//with the skeleton are not stored in the same order as they are in the exported joint buffer.
+		//This remaps the skeletal joints to be in the same order as the joints stored in the model.
+		std::vector<std::string> :: const_iterator jointIt  = model->mSkinInfo.mJointNames.begin();
+		const int jointCnt = model->mSkinInfo.mJointNames.size();
+		for ( int i=0; i<jointCnt; ++i, ++jointIt )
+		{
+			std::string lookingForJoint = (*jointIt).c_str();
+			//Look for the joint xform that we extracted from the skeleton, using the jointIt as the key
+			//and store it in the alternate bind matrix
+			if ( mJointList.find( lookingForJoint ) != mJointList.end() )
+			{
+				LLMatrix4 jointTransform = mJointList[lookingForJoint];
+				LLMatrix4 newInverse = model->mSkinInfo.mInvBindMatrix[i];
+				newInverse.setTranslation( mJointList[lookingForJoint].getTranslation() );
+				model->mSkinInfo.mAlternateBindMatrix.push_back( newInverse );
+			}
+			else
+			{
+				LL_WARNS()<<"Possibly misnamed/missing joint [" <<lookingForJoint.c_str()<<" ] "<<LL_ENDL;
+			}
+		}
+
+		//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)
+								{
+									LL_ERRS() << "Invalid position array size." << LL_ENDL;
+								}
+
+								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 transformation;
+		transformation.initScale(mesh_scale_vector);
+		transformation.setTranslation(mesh_translation_vector);
+		transformation *= mTransform;
+
+		std::map<std::string, LLImportMaterial> materials;
+		for (U32 i = 0; i < model->mMaterialList.size(); ++i)
+		{
+			materials[model->mMaterialList[i]] = LLImportMaterial();
+		}
+		mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials));
+		stretch_extents(model, transformation, mExtents[0], mExtents[1], mFirstTransform);
+	}
+}
+
+//-----------------------------------------------------------------------------
+// buildJointToNodeMappingFromScene()
+//-----------------------------------------------------------------------------
+void LLDAELoader::buildJointToNodeMappingFromScene( daeElement* pRoot )
+{
+	daeElement* pScene = pRoot->getDescendant("visual_scene");
+	if ( pScene )
+	{
+		daeTArray< daeSmartRef<daeElement> > children = pScene->getChildren();
+		S32 childCount = children.getCount();
+		for (S32 i = 0; i < childCount; ++i)
+		{
+			domNode* pNode = daeSafeCast<domNode>(children[i]);
+			processJointToNodeMapping( pNode );			
+		}
+	}
+}
+//-----------------------------------------------------------------------------
+// processJointToNodeMapping()
+//-----------------------------------------------------------------------------
+void LLDAELoader::processJointToNodeMapping( domNode* pNode )
+{
+	if ( isNodeAJoint( pNode ) )
+	{
+		//1.Store the parent
+		std::string nodeName = pNode->getName();
+		if ( !nodeName.empty() )
+		{
+			mJointsFromNode.push_front( pNode->getName() );
+		}
+		//2. Handle the kiddo's
+		processChildJoints( pNode );
+	}
+	else
+	{
+		//Determine if the're any children wrt to this failed node.
+		//This occurs when an armature is exported and ends up being what essentially amounts to
+		//as the root for the visual_scene
+		if ( pNode ) 
+		{
+			processChildJoints( pNode );
+		}
+		else 
+		{
+			LL_INFOS()<<"Node is NULL"<<LL_ENDL;
+		}
+
+	}
+}
+//-----------------------------------------------------------------------------
+// processChildJoint()
+//-----------------------------------------------------------------------------
+void LLDAELoader::processChildJoints( domNode* pParentNode )
+{	
+	daeTArray< daeSmartRef<daeElement> > childOfChild = pParentNode->getChildren();
+	S32 childOfChildCount = childOfChild.getCount();
+	for (S32 i = 0; i < childOfChildCount; ++i)
+	{
+		domNode* pChildNode = daeSafeCast<domNode>( childOfChild[i] );
+		if ( pChildNode )
+		{
+			processJointToNodeMapping( pChildNode );
+		}
+	}
+}
+
+//-----------------------------------------------------------------------------
+// isNodeAJoint()
+//-----------------------------------------------------------------------------
+bool LLDAELoader::isNodeAJoint( domNode* pNode )
+{
+	if ( !pNode )
+	{
+		LL_INFOS()<<"Created node is NULL"<<LL_ENDL;
+		return false;
+	}
+	
+	return LLModelLoader::isNodeAJoint(pNode->getName());
+}
+//-----------------------------------------------------------------------------
+// verifyCount
+//-----------------------------------------------------------------------------
+bool LLDAELoader::verifyCount( int expected, int result )
+{
+	if ( expected != result )
+	{
+		LL_INFOS()<< "Error: (expected/got)"<<expected<<"/"<<result<<"verts"<<LL_ENDL;
+		return false;
+	}
+	return true;
+}
+//-----------------------------------------------------------------------------
+// verifyController
+//-----------------------------------------------------------------------------
+bool LLDAELoader::verifyController( domController* pController )
+{	
+
+	bool result = true;
+
+	domSkin* pSkin = pController->getSkin();
+
+	if ( pSkin )
+	{
+		xsAnyURI & uri = pSkin->getSource();
+		domElement* pElement = uri.getElement();
+
+		if ( !pElement )
+		{
+			LL_INFOS()<<"Can't resolve skin source"<<LL_ENDL;
+			return false;
+		}
+
+		daeString type_str = pElement->getTypeName();
+		if ( stricmp(type_str, "geometry") == 0 )
+		{	
+			//Skin is reference directly by geometry and get the vertex count from skin
+			domSkin::domVertex_weights* pVertexWeights = pSkin->getVertex_weights();
+			U32 vertexWeightsCount = pVertexWeights->getCount();
+			domGeometry* pGeometry = (domGeometry*) (domElement*) uri.getElement();
+			domMesh* pMesh = pGeometry->getMesh();				
+			
+			if ( pMesh )
+			{
+				//Get vertex count from geometry
+				domVertices* pVertices = pMesh->getVertices();
+				if ( !pVertices )
+				{ 
+					LL_INFOS()<<"No vertices!"<<LL_ENDL;
+					return false;
+				}
+
+				if ( pVertices )
+				{
+					xsAnyURI src = pVertices->getInput_array()[0]->getSource();
+					domSource* pSource = (domSource*) (domElement*) src.getElement();
+					U32 verticesCount = pSource->getTechnique_common()->getAccessor()->getCount();
+					result = verifyCount( verticesCount, vertexWeightsCount );
+					if ( !result )
+					{
+						return result;
+					}
+				}
+			}	
+
+			U32 vcountCount = (U32) pVertexWeights->getVcount()->getValue().getCount();
+			result = verifyCount( vcountCount, vertexWeightsCount );	
+			if ( !result )
+			{
+				return result;
+			}
+
+			domInputLocalOffset_Array& inputs = pVertexWeights->getInput_array();
+			U32 sum = 0;
+			for (size_t i=0; i<vcountCount; i++)
+			{
+				sum += pVertexWeights->getVcount()->getValue()[i];
+			}
+			result = verifyCount( sum * inputs.getCount(), (domInt) pVertexWeights->getV()->getValue().getCount() );
+		}
+	}
+	
+	return result;
+}
+
+//-----------------------------------------------------------------------------
+// extractTranslation()
+//-----------------------------------------------------------------------------
+void LLDAELoader::extractTranslation( domTranslate* pTranslate, LLMatrix4& transform )
+{
+	domFloat3 jointTrans = pTranslate->getValue();
+	LLVector3 singleJointTranslation( jointTrans[0], jointTrans[1], jointTrans[2] );
+	transform.setTranslation( singleJointTranslation );
+}
+//-----------------------------------------------------------------------------
+// extractTranslationViaElement()
+//-----------------------------------------------------------------------------
+void LLDAELoader::extractTranslationViaElement( daeElement* pTranslateElement, LLMatrix4& transform )
+{
+	if ( pTranslateElement )
+	{
+		domTranslate* pTranslateChild = dynamic_cast<domTranslate*>( pTranslateElement );
+		domFloat3 translateChild = pTranslateChild->getValue();
+		LLVector3 singleJointTranslation( translateChild[0], translateChild[1], translateChild[2] );
+		transform.setTranslation( singleJointTranslation );
+	}	
+}
+//-----------------------------------------------------------------------------
+// extractTranslationViaSID()
+//-----------------------------------------------------------------------------
+void LLDAELoader::extractTranslationViaSID( daeElement* pElement, LLMatrix4& transform )
+{
+	if ( pElement )
+	{	
+		daeSIDResolver resolver( pElement, "./transform" );
+		domMatrix* pMatrix = daeSafeCast<domMatrix>( resolver.getElement() );
+		//We are only extracting out the translational component atm
+		LLMatrix4 workingTransform;
+		if ( pMatrix )
+		{
+			domFloat4x4 domArray = pMatrix->getValue();									
+			for ( int i = 0; i < 4; i++ )
+			{
+				for( int j = 0; j < 4; j++ )
+				{
+					workingTransform.mMatrix[i][j] = domArray[i + j*4];
+				}
+			}
+			LLVector3 trans = workingTransform.getTranslation();
+			transform.setTranslation( trans );	
+		}
+	}
+	else
+	{
+		LL_WARNS()<<"Element is nonexistent - empty/unsupported node."<<LL_ENDL;
+	}
+}
+//-----------------------------------------------------------------------------
+// processJointNode()
+//-----------------------------------------------------------------------------
+void LLDAELoader::processJointNode( domNode* pNode, JointTransformMap& jointTransforms )
+{
+	if (pNode->getName() == NULL)
+	{
+		LL_WARNS() << "nameless node, can't process" << LL_ENDL;
+		return;
+	}
+
+	//LL_WARNS()<<"ProcessJointNode# Node:" <<pNode->getName()<<LL_ENDL;
+
+	//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 jointResolverA( pNode, "./translate" );
+	domTranslate* pTranslateA = daeSafeCast<domTranslate>( jointResolverA.getElement() );
+	daeSIDResolver jointResolverB( pNode, "./location" );
+	domTranslate* pTranslateB = daeSafeCast<domTranslate>( jointResolverB.getElement() );
+
+	//Translation via SID was successful
+	if ( pTranslateA )
+	{
+		extractTranslation( pTranslateA, workingTransform );
+	}
+	else
+	if ( pTranslateB )
+	{
+		extractTranslation( pTranslateB, workingTransform );
+	}
+	else
+	{
+		//Translation via child from element
+		daeElement* pTranslateElement = getChildFromElement( pNode, "translate" );
+		if ( !pTranslateElement || pTranslateElement->typeID() != domTranslate::ID() )
+		{
+			//LL_WARNS()<< "The found element is not a translate node" <<LL_ENDL;
+			daeSIDResolver jointResolver( pNode, "./matrix" );
+			domMatrix* pMatrix = daeSafeCast<domMatrix>( jointResolver.getElement() );
+			if ( pMatrix )
+			{
+				//LL_INFOS()<<"A matrix SID was however found!"<<LL_ENDL;
+				domFloat4x4 domArray = pMatrix->getValue();									
+				for ( int i = 0; i < 4; i++ )
+				{
+					for( int j = 0; j < 4; j++ )
+					{
+						workingTransform.mMatrix[i][j] = domArray[i + j*4];
+					}
+				}
+			}
+			else
+			{
+				LL_WARNS()<< "The found element is not translate or matrix node - most likely a corrupt export!" <<LL_ENDL;
+			}
+		}
+		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 );
+		}
+	}
+}
+//-----------------------------------------------------------------------------
+// getChildFromElement()
+//-----------------------------------------------------------------------------
+daeElement* LLDAELoader::getChildFromElement( daeElement* pElement, std::string const & name )
+{
+    daeElement* pChildOfElement = pElement->getChild( name.c_str() );
+	if ( pChildOfElement )
+	{
+		return pChildOfElement;
+	}
+	LL_WARNS()<< "Could not find a child [" << name << "] for the element: \"" << pElement->getAttribute("id") << "\"" << LL_ENDL;
+    return NULL;
+}
+
+void LLDAELoader::processElement( daeElement* element, bool& badElement, DAE* dae )
+{
+	LLMatrix4 saved_transform;
+	bool pushed_mat = false;
+
+	domNode* node = daeSafeCast<domNode>(element);
+	if (node)
+	{
+		pushed_mat = true;
+		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;
+		mTransform.condition();
+	}
+
+	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;
+		mTransform.condition();
+	}
+
+	domScale* scale = daeSafeCast<domScale>(element);
+	if (scale)
+	{
+		domFloat3 dom_value = scale->getValue();
+
+
+		LLVector3 scale_vector = LLVector3(dom_value[0], dom_value[1], dom_value[2]);
+		scale_vector.abs(); // Set all values positive, since we don't currently support mirrored meshes
+		LLMatrix4 scaling;
+		scaling.initScale(scale_vector);
+
+		scaling *= mTransform;
+		mTransform = scaling;
+		mTransform.condition();
+	}
+
+	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;
+		mTransform.condition();
+	}
+
+	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)
+			{
+
+				std::vector< LLPointer< LLModel > >::iterator i = mModelsMap[mesh].begin();				
+				while (i != mModelsMap[mesh].end())
+				{
+					LLModel* model = *i;
+
+					LLMatrix4 transformation = mTransform;
+				
+					if (mTransform.determinant() < 0)
+					{ //negative scales are not supported
+						LL_INFOS() << "Negative scale detected, unsupported transform.  domInstance_geometry: " << getElementLabel(instance_geo) << LL_ENDL;
+						badElement = true;
+					}
+
+					LLModelLoader::material_map materials = getMaterials(model, instance_geo, dae);
+
+					// 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;
+
+					if (transformation.determinant() < 0)
+					{ //negative scales are not supported
+						LL_INFOS() << "Negative scale detected, unsupported post-normalization transform.  domInstance_geometry: " << getElementLabel(instance_geo) << LL_ENDL;
+						badElement = true;
+					}
+
+					std::string label;
+					
+					if (model->mLabel.empty())
+					{
+						label = getLodlessLabel(instance_geo);
+
+						llassert(!label.empty());
+
+						if (model->mSubmodelID)
+						{
+							label += (char)((int)'a' + model->mSubmodelID);
+						}
+
+						model->mLabel = label + lod_suffix[mLod];
+					}
+					else
+					{
+						// Don't change model's name if possible, it will play havoc with scenes that already use said model.
+						size_t ext_pos = getSuffixPosition(model->mLabel);
+						if (ext_pos != -1)
+						{
+							label = model->mLabel.substr(0, ext_pos);
+						}
+						else
+						{
+							label = model->mLabel;
+						}
+					}
+
+					mScene[transformation].push_back(LLModelInstance(model, label, transformation, materials));
+					stretch_extents(model, transformation, mExtents[0], mExtents[1], mFirstTransform);
+					i++;
+				}
+			}
+		}
+		else 
+		{
+			LL_INFOS()<<"Unable to resolve geometry URL."<<LL_ENDL;
+			badElement = true;			
+		}
+
+	}	
+
+	domInstance_node* instance_node = daeSafeCast<domInstance_node>(element);
+	if (instance_node)
+	{
+		daeElement* instance = instance_node->getUrl().getElement();
+		if (instance)
+		{
+			processElement(instance,badElement, dae);
+		}
+	}
+
+	//process children
+	daeTArray< daeSmartRef<daeElement> > children = element->getChildren();
+	int childCount = children.getCount();
+	for (S32 i = 0; i < childCount; i++)
+	{
+		processElement(children[i],badElement, dae);
+	}
+
+	if (pushed_mat)
+	{ //this element was a node, restore transform before processiing siblings
+		mTransform = saved_transform;
+	}
+}
+
+std::map<std::string, LLImportMaterial> LLDAELoader::getMaterials(LLModel* model, domInstance_geometry* instance_geo, DAE* dae)
+{
+	std::map<std::string, 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];
+					break;
+				}
+			}
+		}
+
+		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, dae);
+						}
+					}
+				}
+			}
+		}
+
+		import_material.mBinding = model->mMaterialList[i];
+		materials[model->mMaterialList[i]] = import_material;
+	}
+
+	return materials;
+}
+
+LLImportMaterial LLDAELoader::profileToMaterial(domProfile_COMMON* material, DAE* dae)
+{
+	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();
+			if (newparams.getCount())
+			{
+
+				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)
+									{									
+										mat.mDiffuseMapFilename = cdom::uriToNativePath(init->getValue().str());
+										mat.mDiffuseMapLabel = getElementLabel(material);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			else if (texture->getTexture())
+			{
+				domImage* image = NULL;
+				dae->getDatabase()->getElement((daeElement**) &image, 0, texture->getTexture(), COLLADA_TYPE_IMAGE);
+				if (image)
+				{
+					// we only support init_from now - embedded data will come later
+					domImage::domInit_from* init = image->getInit_from();
+					if (init)
+					{
+						std::string image_path_value = cdom::uriToNativePath(init->getValue().str());
+
+#if LL_WINDOWS
+						// Work-around DOM tendency to resort to UNC names which are only confusing for downstream...
+						//
+						std::string::iterator i = image_path_value.begin();
+						while (*i == '\\')
+							i++;
+						mat.mDiffuseMapFilename.assign(i, image_path_value.end());
+#else
+						mat.mDiffuseMapFilename = image_path_value;
+#endif
+						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 LLDAELoader::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();
+	std::string index_string;
+	if (parent)
+	{
+		// retrieve index to distinguish items inside same parent
+		size_t ind = 0;
+		parent->getChildren().find(element, ind);
+		index_string = "_" + boost::lexical_cast<std::string>(ind);
+
+		// if parent has a name or ID, use it
+		std::string name = parent->getAttribute("name");
+		if (!name.length())
+		{
+			name = std::string(parent->getID());
+		}
+
+		if (name.length())
+		{
+			// make sure that index won't mix up with pre-named lod extensions
+			size_t ext_pos = getSuffixPosition(name);
+
+			if (ext_pos == -1)
+			{
+				return name + index_string;
+			}
+			else
+			{
+				return name.insert(ext_pos, index_string);
+			}
+		}
+	}
+
+	// try to use our type
+	daeString element_name = element->getElementName();
+	if (element_name)
+	{
+		return std::string(element_name) + index_string;
+	}
+
+	// if all else fails, use "object"
+	return std::string("object") + index_string;
+}
+
+// static
+size_t LLDAELoader::getSuffixPosition(std::string label)
+{
+	if ((label.find("_LOD") != -1) || (label.find("_PHYS") != -1))
+	{
+		return label.rfind('_');
+	}
+	return -1;
+}
+
+// static
+std::string LLDAELoader::getLodlessLabel(daeElement *element)
+{
+	std::string label = getElementLabel(element);
+	size_t ext_pos = getSuffixPosition(label);
+	if (ext_pos != -1)
+	{
+		return label.substr(0, ext_pos);
+	}
+	return label;
+}
+
+LLColor4 LLDAELoader::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;
+}
+
+bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh)
+{
+	LLModel::EModelStatus status = LLModel::NO_ERRORS;
+	domTriangles_Array& tris = mesh->getTriangles_array();
+
+	for (U32 i = 0; i < tris.getCount(); ++i)
+	{
+		domTrianglesRef& tri = tris.get(i);
+
+		status = load_face_from_dom_triangles(pModel->getVolumeFaces(), pModel->getMaterialList(), tri);
+		pModel->mStatus = status;
+		if(status != LLModel::NO_ERRORS)
+		{
+			pModel->ClearFacesAndMaterials();
+			return false;
+		}
+	}
+
+	domPolylist_Array& polys = mesh->getPolylist_array();
+	for (U32 i = 0; i < polys.getCount(); ++i)
+	{
+		domPolylistRef& poly = polys.get(i);
+		status = load_face_from_dom_polylist(pModel->getVolumeFaces(), pModel->getMaterialList(), poly);
+
+		if(status != LLModel::NO_ERRORS)
+		{
+			pModel->ClearFacesAndMaterials();
+			return false;
+		}
+	}
+
+	domPolygons_Array& polygons = mesh->getPolygons_array();
+
+	for (U32 i = 0; i < polygons.getCount(); ++i)
+	{
+		domPolygonsRef& poly = polygons.get(i);
+		status = load_face_from_dom_polygons(pModel->getVolumeFaces(), pModel->getMaterialList(), poly);
+
+		if(status != LLModel::NO_ERRORS)
+		{
+			pModel->ClearFacesAndMaterials();
+			return false;
+		}
+	}
+
+	return (status == LLModel::NO_ERRORS);
+}
+
+//static 
+LLModel* LLDAELoader::loadModelFromDomMesh(domMesh *mesh)
+{
+	LLVolumeParams volume_params;
+	volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
+	LLModel* ret = new LLModel(volume_params, 0.f); 
+	createVolumeFacesFromDomMesh(ret, mesh);
+    if (ret->mLabel.empty())
+    {
+	    ret->mLabel = getElementLabel(mesh);
+    }
+    return ret;
+}
+
+//static diff version supports creating multiple models when material counts spill
+// over the 8 face server-side limit
+//
+bool LLDAELoader::loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& models_out, U32 submodel_limit)
+{
+
+	LLVolumeParams volume_params;
+	volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
+
+	models_out.clear();
+
+	LLModel* ret = new LLModel(volume_params, 0.f);
+
+	std::string model_name = getLodlessLabel(mesh);
+	ret->mLabel = model_name + lod_suffix[mLod];
+
+	llassert(!ret->mLabel.empty());
+
+	// Like a monkey, ready to be shot into space
+	//
+	ret->ClearFacesAndMaterials();
+
+	// Get the whole set of volume faces
+	//
+	addVolumeFacesFromDomMesh(ret, mesh);
+
+	U32 volume_faces = ret->getNumVolumeFaces();
+
+	// Side-steps all manner of issues when splitting models
+	// and matching lower LOD materials to base models
+	//
+	ret->sortVolumeFacesByMaterialName();
+
+	bool normalized = false;
+
+    int submodelID = 0;
+
+	// remove all faces that definitely won't fit into one model and submodel limit
+	U32 face_limit = (submodel_limit + 1) * LL_SCULPT_MESH_MAX_FACES;
+	if (face_limit < volume_faces)
+	{
+		ret->setNumVolumeFaces(face_limit);
+	}
+
+	LLVolume::face_list_t remainder;
+	do 
+	{
+		// Insure we do this once with the whole gang and not per-model
+		//
+		if (!normalized && !mNoNormalize)
+		{			
+			normalized = true;
+			ret->normalizeVolumeFaces();
+		}
+
+		ret->trimVolumeFacesToSize(LL_SCULPT_MESH_MAX_FACES, &remainder);
+
+		if (!mNoOptimize)
+		{
+			ret->optimizeVolumeFaces();
+		}
+
+		volume_faces = remainder.size();
+
+		models_out.push_back(ret);
+
+		// If we have left-over volume faces, create another model
+		// to absorb them...
+		//
+		if (volume_faces)
+		{
+			LLModel* next = new LLModel(volume_params, 0.f);
+			next->mSubmodelID = ++submodelID;
+			next->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod];
+			next->getVolumeFaces() = remainder;
+			next->mNormalizedScale = ret->mNormalizedScale;
+			next->mNormalizedTranslation = ret->mNormalizedTranslation;
+			if ( ret->mMaterialList.size() > LL_SCULPT_MESH_MAX_FACES)
+			{
+				next->mMaterialList.assign(ret->mMaterialList.begin() + LL_SCULPT_MESH_MAX_FACES, ret->mMaterialList.end());
+			}
+			ret = next;
+		}
+
+		remainder.clear();
+
+	} while (volume_faces);	
+
+	return true;
+}
+
+bool LLDAELoader::createVolumeFacesFromDomMesh(LLModel* pModel, domMesh* mesh)
+{
+	if (mesh)
+	{
+		pModel->ClearFacesAndMaterials();
+
+		addVolumeFacesFromDomMesh(pModel, mesh);
+
+		if (pModel->getNumVolumeFaces() > 0)
+		{
+			pModel->normalizeVolumeFaces();
+			pModel->optimizeVolumeFaces();
+
+			if (pModel->getNumVolumeFaces() > 0)
+			{
+				return true;
+			}
+		}
+	}
+	else
+	{	
+		LL_WARNS() << "no mesh found" << LL_ENDL;
+	}
+
+	return false;
+}
diff --git a/indra/llprimitive/lldaeloader.h b/indra/llprimitive/lldaeloader.h
new file mode 100644
index 0000000000000000000000000000000000000000..7d91a6063b4cad0e4a2ed78443ce288012f3ee3d
--- /dev/null
+++ b/indra/llprimitive/lldaeloader.h
@@ -0,0 +1,107 @@
+/**
+ * @file lldaeloader.h
+ * @brief LLDAELoader class definition
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2013, 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_LLDAELOADER_H
+#define LL_LLDAELOADER_H
+
+#include "llmodelloader.h"
+
+class DAE;
+class daeElement;
+class domProfile_COMMON;
+class domInstance_geometry;
+class domNode;
+class domTranslate;
+class domController;
+class domSkin;
+class domMesh;
+
+class LLDAELoader : public LLModelLoader
+{
+public:
+	typedef std::map<std::string, LLImportMaterial>							material_map;
+	typedef std::map<daeElement*, std::vector<LLPointer<LLModel> > >	dae_model_map;
+	dae_model_map	mModelsMap;
+
+	LLDAELoader(
+		std::string									filename,
+		S32											lod, 
+		LLModelLoader::load_callback_t		load_cb,
+		LLModelLoader::joint_lookup_func_t	joint_lookup_func,
+		LLModelLoader::texture_load_func_t	texture_load_func,
+		LLModelLoader::state_callback_t		state_cb,
+		void*											opaque_userdata,
+		JointTransformMap&						jointMap,
+		JointSet&									jointsFromNodes,
+		U32									modelLimit);
+	virtual ~LLDAELoader() ;
+
+	virtual bool OpenFile(const std::string& filename);
+
+protected:
+
+	void processElement(daeElement* element, bool& badElement, DAE* dae);
+	void processDomModel(LLModel* model, DAE* dae, daeElement* pRoot, domMesh* mesh, domSkin* skin);
+
+	material_map getMaterials(LLModel* model, domInstance_geometry* instance_geo, DAE* dae);
+	LLImportMaterial profileToMaterial(domProfile_COMMON* material, DAE* dae);	
+	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 extractTranslationViaSID( daeElement* pElement, LLMatrix4& transform );
+	void buildJointToNodeMappingFromScene( daeElement* pRoot );
+	void processJointToNodeMapping( domNode* pNode );
+	void processChildJoints( domNode* pParentNode );
+
+	bool verifyCount( int expected, int result );
+
+	//Verify that a controller matches vertex counts
+	bool verifyController( domController* pController );
+
+	static bool addVolumeFacesFromDomMesh(LLModel* model, domMesh* mesh);
+	static bool createVolumeFacesFromDomMesh(LLModel* model, domMesh *mesh);
+
+	static LLModel* loadModelFromDomMesh(domMesh* mesh);
+
+	// Loads a mesh breaking it into one or more models as necessary
+	// to get around volume face limitations while retaining >8 materials
+	//
+	bool loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& models_out, U32 submodel_limit);
+
+	static std::string getElementLabel(daeElement *element);
+	static size_t getSuffixPosition(std::string label);
+	static std::string getLodlessLabel(daeElement *element);
+
+private:
+	U32 mGeneratedModelLimit; // Attempt to limit amount of generated submodels
+
+};
+#endif  // LL_LLDAELLOADER_H
diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp
index ed903146ef3352887d0886451df58f04877af99d..e494c55250145c74704fbc35690bb67b465c78e4 100755
--- a/indra/llprimitive/llmodel.cpp
+++ b/indra/llprimitive/llmodel.cpp
@@ -31,867 +31,38 @@
 #include "llconvexdecomposition.h"
 #include "llsdserialize.h"
 #include "llvector4a.h"
-#if LL_MSVC
-#pragma warning (disable : 4263)
-#pragma warning (disable : 4264)
-#endif
-#include "dae.h"
-#include "dae/daeErrorHandler.h"
-#include "dom/domConstants.h"
-#include "dom/domMesh.h"
-#if LL_MSVC
-#pragma warning (default : 4263)
-#pragma warning (default : 4264)
-#endif
-
-#ifdef LL_USESYSTEMLIBS
-# include <zlib.h>
-#else
-# include "zlib/zlib.h"
-#endif
-
-
-
-std::string model_names[] =
-{
-	"lowest_lod",
-	"low_lod",
-	"medium_lod",
-	"high_lod",
-	"physics_mesh"
-};
-
-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)
-	, mPelvisOffset( 0.0f ), mStatus(NO_ERRORS)
-{
-	mDecompID = -1;
-	mLocalID = -1;
-}
-
-LLModel::~LLModel()
-{
-	if (mDecompID >= 0)
-	{
-		LLConvexDecomposition::getInstance()->deleteDecomposition(mDecompID);
-	}
-}
-
-
-bool get_dom_sources(const domInputLocalOffset_Array& inputs, S32& pos_offset, S32& tc_offset, S32& norm_offset, S32 &idx_stride,
-					 domSource* &pos_source, domSource* &tc_source, domSource* &norm_source)
-{
-	idx_stride = 0;
-
-	for (U32 j = 0; j < inputs.getCount(); ++j)
-	{
-		idx_stride = llmax((S32) inputs[j]->getOffset(), idx_stride);
-
-		if (strcmp(COMMON_PROFILE_INPUT_VERTEX, inputs[j]->getSemantic()) == 0)
-		{ //found vertex array
-			const domURIFragmentType& uri = inputs[j]->getSource();
-			daeElementRef elem = uri.getElement();
-			domVertices* vertices = (domVertices*) elem.cast();
-			if ( !vertices )
-			{
-				return false;
-			}
-				
-			domInputLocal_Array& v_inp = vertices->getInput_array();
-			
-			
-			for (U32 k = 0; k < v_inp.getCount(); ++k)
-			{
-				if (strcmp(COMMON_PROFILE_INPUT_POSITION, v_inp[k]->getSemantic()) == 0)
-				{
-					pos_offset = inputs[j]->getOffset();
-
-					const domURIFragmentType& uri = v_inp[k]->getSource();
-					daeElementRef elem = uri.getElement();
-					pos_source = (domSource*) elem.cast();
-				}
-				
-				if (strcmp(COMMON_PROFILE_INPUT_NORMAL, v_inp[k]->getSemantic()) == 0)
-				{
-					norm_offset = inputs[j]->getOffset();
-
-					const domURIFragmentType& uri = v_inp[k]->getSource();
-					daeElementRef elem = uri.getElement();
-					norm_source = (domSource*) elem.cast();
-				}
-			}
-		}
-
-		if (strcmp(COMMON_PROFILE_INPUT_NORMAL, inputs[j]->getSemantic()) == 0)
-		{
-			//found normal array for this triangle list
-			norm_offset = inputs[j]->getOffset();
-			const domURIFragmentType& uri = inputs[j]->getSource();
-			daeElementRef elem = uri.getElement();
-			norm_source = (domSource*) elem.cast();
-		}
-		else if (strcmp(COMMON_PROFILE_INPUT_TEXCOORD, inputs[j]->getSemantic()) == 0)
-		{ //found texCoords
-			tc_offset = inputs[j]->getOffset();
-			const domURIFragmentType& uri = inputs[j]->getSource();
-			daeElementRef elem = uri.getElement();
-			tc_source = (domSource*) elem.cast();
-		}
-	}
-
-	idx_stride += 1;
-	
-	return true;
-}
-
-LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domTrianglesRef& tri)
-{
-	LLVolumeFace face;
-	std::vector<LLVolumeFace::VertexData> verts;
-	std::vector<U16> indices;
-	
-	const domInputLocalOffset_Array& inputs = tri->getInput_array();
-
-	S32 pos_offset = -1;
-	S32 tc_offset = -1;
-	S32 norm_offset = -1;
-
-	domSource* pos_source = NULL;
-	domSource* tc_source = NULL;
-	domSource* norm_source = NULL;
-
-	S32 idx_stride = 0;
-
-	if ( !get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source) || !pos_source )
-	{
-		LL_WARNS() << "Could not find dom sources for basic geo data; invalid model." << LL_ENDL;
-		return LLModel::BAD_ELEMENT;
-	}
-
-	if (!pos_source)
-	{
-		LL_WARNS() << "Unable to process mesh without position data; invalid model;  invalid model." << LL_ENDL;
-		return LLModel::BAD_ELEMENT;
-	}
-	
-	domPRef p = tri->getP();
-	domListOfUInts& idx = p->getValue();
-	
-	domListOfFloats  dummy ;
-	domListOfFloats& v = (pos_source && pos_source->getFloat_array()) ? pos_source->getFloat_array()->getValue() : dummy ;
-	domListOfFloats& tc = (tc_source && tc_source->getFloat_array()) ? tc_source->getFloat_array()->getValue() : dummy ;
-	domListOfFloats& n = (norm_source && norm_source->getFloat_array()) ? norm_source->getFloat_array()->getValue() : dummy ;
-
-	LLVolumeFace::VertexMapData::PointMap point_map;
-		
-	U32 index_count  = idx.getCount();
-	U32 vertex_count = (pos_source &&  pos_source->getFloat_array())	? v.getCount()	: 0;
-	U32 tc_count     = (tc_source && tc_source->getFloat_array()) 		? tc.getCount()	: 0;
-	U32 norm_count   = (norm_source && norm_source->getFloat_array()) 	? n.getCount(): 0;
-
-	if (vertex_count == 0)
-	{
-		LL_WARNS() << "Unable to process mesh with empty position array; invalid model." << LL_ENDL;
-		return LLModel::BAD_ELEMENT;
-	}
-
-	face.mExtents[0].set(v[0], v[1], v[2]);
-	face.mExtents[1].set(v[0], v[1], v[2]);
-	
-	for (U32 i = 0; i < index_count; i += idx_stride)
-	{
-		LLVolumeFace::VertexData cv;
-		if (pos_source)
-		{
-			// guard against model data specifiying out of range indices or verts
-			//
-			if (((i + pos_offset) > index_count)
-			 || ((idx[i+pos_offset]*3+2) > vertex_count))
-			{
-				LL_WARNS() << "Out of range index data; invalid model." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-
-			cv.setPosition(LLVector4a(v[idx[i+pos_offset]*3+0],
-								v[idx[i+pos_offset]*3+1],
-								v[idx[i+pos_offset]*3+2]));
-
-			if (!cv.getPosition().isFinite3())
-			{
-				LL_WARNS() << "Nan positional data, invalid model." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-		}
-
-		if (tc_source)
-		{
-			// guard against model data specifiying out of range indices or tcs
-			//
-
-			if (((i + tc_offset) > index_count)
-			 || ((idx[i+tc_offset]*2+1) > tc_count))
-			{
-				LL_WARNS() << "Out of range TC indices." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-
-			cv.mTexCoord.setVec(tc[idx[i+tc_offset]*2+0],
-								tc[idx[i+tc_offset]*2+1]);
-
-			if (!cv.mTexCoord.isFinite())
-			{
-				LL_WARNS() << "Found NaN while loading tex coords from DAE-Model, invalid model." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-		}
-		
-		if (norm_source)
-		{
-			// guard against model data specifiying out of range indices or norms
-			//
-			if (((i + norm_offset) > index_count)
-				|| ((idx[i+norm_offset]*3+2) > norm_count))
-			{
-				LL_WARNS() << "Found out of range norm indices, invalid model." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-
-			cv.setNormal(LLVector4a(n[idx[i+norm_offset]*3+0],
-								n[idx[i+norm_offset]*3+1],
-								n[idx[i+norm_offset]*3+2]));
-
-			if (!cv.getNormal().isFinite3())
-			{
-				LL_WARNS() << "Found NaN while loading normals from DAE-Model, invalid model." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-		}
-		
-		BOOL found = FALSE;
-			
-		LLVolumeFace::VertexMapData::PointMap::iterator point_iter;
-		point_iter = point_map.find(LLVector3(cv.getPosition().getF32ptr()));
-		
-		if (point_iter != point_map.end())
-		{
-			for (U32 j = 0; j < point_iter->second.size(); ++j)
-			{
-				if ((point_iter->second)[j] == cv)
-				{
-					found = TRUE;
-					indices.push_back((point_iter->second)[j].mIndex);
-					break;
-				}
-			}
-		}
-
-		if (!found)
-		{
-			update_min_max(face.mExtents[0], face.mExtents[1], cv.getPosition());
-			verts.push_back(cv);
-			if (verts.size() >= 65535)
-			{
-				//LL_ERRS() << "Attempted to write model exceeding 16-bit index buffer limitation." << LL_ENDL;
-				return LLModel::VERTEX_NUMBER_OVERFLOW ;
-			}
-			U16 index = (U16) (verts.size()-1);
-			indices.push_back(index);
-
-			LLVolumeFace::VertexMapData d;
-			d.setPosition(cv.getPosition());
-			d.mTexCoord = cv.mTexCoord;
-			d.setNormal(cv.getNormal());
-			d.mIndex = index;
-			if (point_iter != point_map.end())
-			{
-				point_iter->second.push_back(d);
-			}
-			else
-			{
-				point_map[LLVector3(d.getPosition().getF32ptr())].push_back(d);
-			}
-		}
-
-		if (indices.size()%3 == 0 && verts.size() >= 65532)
-		{
-			std::string material;
-
-			if (tri->getMaterial())
-			{
-				material = std::string(tri->getMaterial());
-			}
-
-			materials.push_back(material);
-			face_list.push_back(face);
-			face_list.rbegin()->fillFromLegacyData(verts, indices);
-			LLVolumeFace& new_face = *face_list.rbegin();
-			if (!norm_source)
-			{
-				//ll_aligned_free_16(new_face.mNormals);
-				new_face.mNormals = NULL;
-			}
-
-			if (!tc_source)
-			{
-				//ll_aligned_free_16(new_face.mTexCoords);
-				new_face.mTexCoords = NULL;
-			}
-
-			face = LLVolumeFace();
-			point_map.clear();
-		}
-	}
-
-	if (!verts.empty())
-	{
-		std::string material;
-
-		if (tri->getMaterial())
-		{
-			material = std::string(tri->getMaterial());
-		}
-		
-		materials.push_back(material);
-		face_list.push_back(face);
-
-		face_list.rbegin()->fillFromLegacyData(verts, indices);
-		LLVolumeFace& new_face = *face_list.rbegin();
-		if (!norm_source)
-		{
-			//ll_aligned_free_16(new_face.mNormals);
-			new_face.mNormals = NULL;
-		}
-
-		if (!tc_source)
-		{
-			//ll_aligned_free_16(new_face.mTexCoords);
-			new_face.mTexCoords = NULL;
-		}
-	}
-
-	return LLModel::NO_ERRORS ;
-}
-
-LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domPolylistRef& poly)
-{
-	domPRef p = poly->getP();
-	domListOfUInts& idx = p->getValue();
-
-	if (idx.getCount() == 0)
-	{
-		return LLModel::NO_ERRORS ;
-	}
-
-	const domInputLocalOffset_Array& inputs = poly->getInput_array();
-
-
-	domListOfUInts& vcount = poly->getVcount()->getValue();
-	
-	S32 pos_offset = -1;
-	S32 tc_offset = -1;
-	S32 norm_offset = -1;
-
-	domSource* pos_source = NULL;
-	domSource* tc_source = NULL;
-	domSource* norm_source = NULL;
-
-	S32 idx_stride = 0;
-
-	if (!get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source))
-	{
-		LL_WARNS() << "Could not get DOM sources for basic geo data, invalid model." << LL_ENDL;
-		return LLModel::BAD_ELEMENT;
-	}
-
-	LLVolumeFace face;
-
-	std::vector<U16> indices;
-	std::vector<LLVolumeFace::VertexData> verts;
-
-	domListOfFloats v;
-	domListOfFloats tc;
-	domListOfFloats n;
-
-	if (pos_source)
-	{
-		v = pos_source->getFloat_array()->getValue();
-		face.mExtents[0].set(v[0], v[1], v[2]);
-		face.mExtents[1].set(v[0], v[1], v[2]);
-	}
 
-	if (tc_source)
-	{
-		tc = tc_source->getFloat_array()->getValue();
-	}
-
-	if (norm_source)
-	{
-		n = norm_source->getFloat_array()->getValue();
-	}
-	
-	LLVolumeFace::VertexMapData::PointMap point_map;
-
-	U32 index_count  = idx.getCount();
-	U32 vertex_count = pos_source  ? v.getCount()  : 0;
-	U32 tc_count     = tc_source   ? tc.getCount() : 0;
-	U32 norm_count   = norm_source ? n.getCount()  : 0;
-
-	U32 cur_idx = 0;
-	for (U32 i = 0; i < vcount.getCount(); ++i)
-	{ //for each polygon
-		U32 first_index = 0;
-		U32 last_index = 0;
-		for (U32 j = 0; j < vcount[i]; ++j)
-		{ //for each vertex
-
-			LLVolumeFace::VertexData cv;
-
-			if (pos_source)
-			{
-				// guard against model data specifiying out of range indices or verts
-				//
-				if (((cur_idx + pos_offset) > index_count)
-				 || ((idx[cur_idx+pos_offset]*3+2) > vertex_count))
-				{
-					LL_WARNS() << "Out of range position indices, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-
-				cv.getPosition().set(v[idx[cur_idx+pos_offset]*3+0],
-									v[idx[cur_idx+pos_offset]*3+1],
-									v[idx[cur_idx+pos_offset]*3+2]);
-
-				if (!cv.getPosition().isFinite3())
-				{
-					LL_WARNS() << "Found NaN while loading positions from DAE-Model, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-
-			}
-
-			if (tc_source)
-			{
-				// guard against model data specifiying out of range indices or tcs
-				//
-				if (((cur_idx + tc_offset) > index_count)
-				 || ((idx[cur_idx+tc_offset]*2+1) > tc_count))
-				{
-					LL_WARNS() << "Out of range TC indices, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-
-				cv.mTexCoord.setVec(tc[idx[cur_idx+tc_offset]*2+0],
-									tc[idx[cur_idx+tc_offset]*2+1]);
-
-				if (!cv.mTexCoord.isFinite())
-				{
-					LL_WARNS() << "Found NaN while loading tex coords from DAE-Model, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-			}
-			
-			if (norm_source)
-			{
-				// guard against model data specifiying out of range indices or norms
-				//
-				if (((cur_idx + norm_offset) > index_count)
-				 || ((idx[cur_idx+norm_offset]*3+2) > norm_count))
-				{
-					LL_WARNS() << "Out of range norm indices, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-
-				cv.getNormal().set(n[idx[cur_idx+norm_offset]*3+0],
-									n[idx[cur_idx+norm_offset]*3+1],
-									n[idx[cur_idx+norm_offset]*3+2]);
-
-				if (!cv.getNormal().isFinite3())
-				{
-					LL_WARNS() << "Found NaN while loading normals from DAE-Model, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-			}
-			
-			cur_idx += idx_stride;
-			
-			BOOL found = FALSE;
-				
-			LLVolumeFace::VertexMapData::PointMap::iterator point_iter;
-			LLVector3 pos3(cv.getPosition().getF32ptr());
-			point_iter = point_map.find(pos3);
-			
-			if (point_iter != point_map.end())
-			{
-				for (U32 k = 0; k < point_iter->second.size(); ++k)
-				{
-					if ((point_iter->second)[k] == cv)
-					{
-						found = TRUE;
-						U32 index = (point_iter->second)[k].mIndex;
-						if (j == 0)
-						{
-							first_index = index;
-						}
-						else if (j == 1)
-						{
-							last_index = index;
-						}
-						else
-						{
-							indices.push_back(first_index);
-							indices.push_back(last_index);
-							indices.push_back(index);
-							last_index = index;
-						}
-
-						break;
-					}
-				}
-			}
-
-			if (!found)
-			{
-				update_min_max(face.mExtents[0], face.mExtents[1], cv.getPosition());
-				verts.push_back(cv);
-				if (verts.size() >= 65535)
-				{
-					//LL_ERRS() << "Attempted to write model exceeding 16-bit index buffer limitation." << LL_ENDL;
-					return LLModel::VERTEX_NUMBER_OVERFLOW ;
-				}
-				U16 index = (U16) (verts.size()-1);
-			
-				if (j == 0)
-				{
-					first_index = index;
-				}
-				else if (j == 1)
-				{
-					last_index = index;
-				}
-				else
-				{
-					indices.push_back(first_index);
-					indices.push_back(last_index);
-					indices.push_back(index);
-					last_index = index;
-				}	
-
-				LLVolumeFace::VertexMapData d;
-				d.setPosition(cv.getPosition());
-				d.mTexCoord = cv.mTexCoord;
-				d.setNormal(cv.getNormal());
-				d.mIndex = index;
-				if (point_iter != point_map.end())
-				{
-					point_iter->second.push_back(d);
-				}
-				else
-				{
-					point_map[pos3].push_back(d);
-				}
-			}
-
-			if (indices.size()%3 == 0 && indices.size() >= 65532)
-			{
-				std::string material;
-
-				if (poly->getMaterial())
-				{
-					material = std::string(poly->getMaterial());
-				}
-
-				materials.push_back(material);
-				face_list.push_back(face);
-				face_list.rbegin()->fillFromLegacyData(verts, indices);
-				LLVolumeFace& new_face = *face_list.rbegin();
-				if (!norm_source)
-				{
-					//ll_aligned_free_16(new_face.mNormals);
-					new_face.mNormals = NULL;
-				}
-
-				if (!tc_source)
-				{
-					//ll_aligned_free_16(new_face.mTexCoords);
-					new_face.mTexCoords = NULL;
-				}
-
-				face = LLVolumeFace();
-				verts.clear();
-				indices.clear();
-				point_map.clear();
-			}
-		}
-	}
-
-	if (!verts.empty())
-	{
-		std::string material;
-
-		if (poly->getMaterial())
-		{
-			material = std::string(poly->getMaterial());
-		}
-	
-		materials.push_back(material);
-		face_list.push_back(face);
-		face_list.rbegin()->fillFromLegacyData(verts, indices);
-
-		LLVolumeFace& new_face = *face_list.rbegin();
-		if (!norm_source)
-		{
-			//ll_aligned_free_16(new_face.mNormals);
-			new_face.mNormals = NULL;
-		}
-
-		if (!tc_source)
-		{
-			//ll_aligned_free_16(new_face.mTexCoords);
-			new_face.mTexCoords = NULL;
-		}
-	}
-
-	return LLModel::NO_ERRORS ;
-}
-
-LLModel::EModelStatus load_face_from_dom_polygons(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domPolygonsRef& poly)
-{
-	LLVolumeFace face;
-	std::vector<U16> indices;
-	std::vector<LLVolumeFace::VertexData> verts;
-
-	const domInputLocalOffset_Array& inputs = poly->getInput_array();
-
-	S32 v_offset = -1;
-	S32 n_offset = -1;
-	S32 t_offset = -1;
-
-	domListOfFloats* v = NULL;
-	domListOfFloats* n = NULL;
-	domListOfFloats* t = NULL;
-	
-	U32 stride = 0;
-	for (U32 i = 0; i < inputs.getCount(); ++i)
-	{
-		stride = llmax((U32) inputs[i]->getOffset()+1, stride);
-
-		if (strcmp(COMMON_PROFILE_INPUT_VERTEX, inputs[i]->getSemantic()) == 0)
-		{ //found vertex array
-			v_offset = inputs[i]->getOffset();
-
-			const domURIFragmentType& uri = inputs[i]->getSource();
-			daeElementRef elem = uri.getElement();
-			domVertices* vertices = (domVertices*) elem.cast();
-			if (!vertices)
-			{
-				LL_WARNS() << "Could not find vertex source, invalid model." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-			domInputLocal_Array& v_inp = vertices->getInput_array();
-
-			for (U32 k = 0; k < v_inp.getCount(); ++k)
-			{
-				if (strcmp(COMMON_PROFILE_INPUT_POSITION, v_inp[k]->getSemantic()) == 0)
-				{
-					const domURIFragmentType& uri = v_inp[k]->getSource();
-					daeElementRef elem = uri.getElement();
-					domSource* src = (domSource*) elem.cast();
-					if (!src)
-					{
-						LL_WARNS() << "Could not find DOM source, invalid model." << LL_ENDL;
-						return LLModel::BAD_ELEMENT;
-					}
-					v = &(src->getFloat_array()->getValue());
-				}
-			}
-		}
-		else if (strcmp(COMMON_PROFILE_INPUT_NORMAL, inputs[i]->getSemantic()) == 0)
-		{
-			n_offset = inputs[i]->getOffset();
-			//found normal array for this triangle list
-			const domURIFragmentType& uri = inputs[i]->getSource();
-			daeElementRef elem = uri.getElement();
-			domSource* src = (domSource*) elem.cast();
-			if (!src)
-			{
-				LL_WARNS() << "Could not find DOM source, invalid model." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-			n = &(src->getFloat_array()->getValue());
-		}
-		else if (strcmp(COMMON_PROFILE_INPUT_TEXCOORD, inputs[i]->getSemantic()) == 0 && inputs[i]->getSet() == 0)
-		{ //found texCoords
-			t_offset = inputs[i]->getOffset();
-			const domURIFragmentType& uri = inputs[i]->getSource();
-			daeElementRef elem = uri.getElement();
-			domSource* src = (domSource*) elem.cast();
-			if (!src)
-			{
-				LL_WARNS() << "Could not find DOM source, invalid model." << LL_ENDL;
-				return LLModel::BAD_ELEMENT;
-			}
-			t = &(src->getFloat_array()->getValue());
-		}
-	}
-
-	domP_Array& ps = poly->getP_array();
-
-	//make a triangle list in <verts>
-	for (U32 i = 0; i < ps.getCount(); ++i)
-	{ //for each polygon
-		domListOfUInts& idx = ps[i]->getValue();
-		for (U32 j = 0; j < idx.getCount()/stride; ++j)
-		{ //for each vertex
-			if (j > 2)
-			{
-				U32 size = verts.size();
-				LLVolumeFace::VertexData v0 = verts[size-3];
-				LLVolumeFace::VertexData v1 = verts[size-1];
-
-				verts.push_back(v0);
-				verts.push_back(v1);
-			}
-
-			LLVolumeFace::VertexData vert;
-
-
-			if (v)
-			{
-				U32 v_idx = idx[j*stride+v_offset]*3;
-				v_idx = llclamp(v_idx, (U32) 0, (U32) v->getCount());
-				vert.getPosition().set(v->get(v_idx),
-								v->get(v_idx+1),
-								v->get(v_idx+2));
-
-				if (!vert.getPosition().isFinite3())
-				{
-					LL_WARNS() << "Found NaN while loading position data from DAE-Model, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-			}
-			
-			//bounds check n and t lookups because some FBX to DAE converters
-			//use negative indices and empty arrays to indicate data does not exist
-			//for a particular channel
-			if (n && n->getCount() > 0)
-			{
-				U32 n_idx = idx[j*stride+n_offset]*3;
-				n_idx = llclamp(n_idx, (U32) 0, (U32) n->getCount());
-				vert.getNormal().set(n->get(n_idx),
-								n->get(n_idx+1),
-								n->get(n_idx+2));
-
-				if (!vert.getNormal().isFinite3())
-				{
-					LL_WARNS() << "Found NaN while loading normals from DAE-Model, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-			}
-			else
-			{
-				vert.getNormal().clear();
-			}
-
-			
-			if (t && t->getCount() > 0)
-			{
-				U32 t_idx = idx[j*stride+t_offset]*2;
-				t_idx = llclamp(t_idx, (U32) 0, (U32) t->getCount());
-				vert.mTexCoord.setVec(t->get(t_idx),
-								t->get(t_idx+1));								
-
-				if (!vert.mTexCoord.isFinite())
-				{
-					LL_WARNS() << "Found NaN while loading tex coords from DAE-Model, invalid model." << LL_ENDL;
-					return LLModel::BAD_ELEMENT;
-				}
-			}
-			else
-			{
-				vert.mTexCoord.clear();
-			}
-
-						
-			verts.push_back(vert);
-		}
-	}
-
-	if (verts.empty())
-	{
-		return LLModel::NO_ERRORS;
-	}
-
-	face.mExtents[0] = verts[0].getPosition();
-	face.mExtents[1] = verts[0].getPosition();
-	
-	//create a map of unique vertices to indices
-	std::map<LLVolumeFace::VertexData, U32> vert_idx;
-
-	U32 cur_idx = 0;
-	for (U32 i = 0; i < verts.size(); ++i)
-	{
-		std::map<LLVolumeFace::VertexData, U32>::iterator iter = vert_idx.find(verts[i]);
-		if (iter == vert_idx.end())
-		{
-			vert_idx[verts[i]] = cur_idx++;
-		}
-	}
-
-	//build vertex array from map
-	std::vector<LLVolumeFace::VertexData> new_verts;
-	new_verts.resize(vert_idx.size());
-
-	for (std::map<LLVolumeFace::VertexData, U32>::iterator iter = vert_idx.begin(); iter != vert_idx.end(); ++iter)
-	{
-		new_verts[iter->second] = iter->first;
-		update_min_max(face.mExtents[0], face.mExtents[1], iter->first.getPosition());
-	}
-
-	//build index array from map
-	indices.resize(verts.size());
-
-	for (U32 i = 0; i < verts.size(); ++i)
-	{
-		indices[i] = vert_idx[verts[i]];
-	}
-
-	// DEBUG just build an expanded triangle list
-	/*for (U32 i = 0; i < verts.size(); ++i)
-	{
-		indices.push_back((U16) i);
-		update_min_max(face.mExtents[0], face.mExtents[1], verts[i].getPosition());
-	}*/
-
-    if (!new_verts.empty())
-	{
-		std::string material;
+#ifdef LL_USESYSTEMLIBS
+# include <zlib.h>
+#else
+# include "zlib/zlib.h"
+#endif
 
-		if (poly->getMaterial())
-		{
-			material = std::string(poly->getMaterial());
-		}
+std::string model_names[] =
+{
+	"lowest_lod",
+	"low_lod",
+	"medium_lod",
+	"high_lod",
+	"physics_mesh"
+};
 
-		materials.push_back(material);
-		face_list.push_back(face);
-		face_list.rbegin()->fillFromLegacyData(new_verts, indices);
+const int MODEL_NAMES_LENGTH = sizeof(model_names) / sizeof(std::string);
 
-		LLVolumeFace& new_face = *face_list.rbegin();
-		if (!n)
-		{
-			//ll_aligned_free_16(new_face.mNormals);
-			new_face.mNormals = NULL;
-		}
+LLModel::LLModel(LLVolumeParams& params, F32 detail)
+	: LLVolume(params, detail), mNormalizedScale(1,1,1), mNormalizedTranslation(0,0,0)
+	, mPelvisOffset( 0.0f ), mStatus(NO_ERRORS), mSubmodelID(0)
+{
+	mDecompID = -1;
+	mLocalID = -1;
+}
 
-		if (!t)
-		{
-			//ll_aligned_free_16(new_face.mTexCoords);
-			new_face.mTexCoords = NULL;
-		}
+LLModel::~LLModel()
+{
+	if (mDecompID >= 0)
+	{
+		LLConvexDecomposition::getInstance()->deleteDecomposition(mDecompID);
 	}
-
-	return LLModel::NO_ERRORS ;
 }
 
 //static
@@ -903,115 +74,103 @@ std::string LLModel::getStatusString(U32 status)
 	{
 		if(status_strings[status] == std::string())
 		{
-			LL_ERRS() << "No valid status string for this status: " << (U32)status << LL_ENDL ;
+			//LL_ERRS() << "No valid status string for this status: " << (U32)status << LL_ENDL();
 		}
 		return status_strings[status] ;
 	}
 
-	LL_ERRS() << "Invalid model status: " << (U32)status << LL_ENDL ;
+	//LL_ERRS() << "Invalid model status: " << (U32)status << LL_ENDL();
 
 	return std::string() ;
 }
 
-void LLModel::addVolumeFacesFromDomMesh(domMesh* mesh)
+
+void LLModel::offsetMesh( const LLVector3& pivotPoint )
 {
-	domTriangles_Array& tris = mesh->getTriangles_array();
-		
-	for (U32 i = 0; i < tris.getCount(); ++i)
+	LLVector4a pivot( pivotPoint[VX], pivotPoint[VY], pivotPoint[VZ] );
+	
+	for (std::vector<LLVolumeFace>::iterator faceIt = mVolumeFaces.begin(); faceIt != mVolumeFaces.end(); )
 	{
-		domTrianglesRef& tri = tris.get(i);
-
-		mStatus = load_face_from_dom_triangles(mVolumeFaces, mMaterialList, tri);
+		std::vector<LLVolumeFace>:: iterator currentFaceIt = faceIt++;
+		LLVolumeFace& face = *currentFaceIt;
+		LLVector4a *pos = (LLVector4a*) face.mPositions;
 		
-		if(mStatus != NO_ERRORS)
+		for (U32 i=0; i<face.mNumVertices; ++i )
 		{
-			mVolumeFaces.clear() ;
-			mMaterialList.clear() ;
-			return ; //abort
+			pos[i].add( pivot );
 		}
 	}
+}
 
-	domPolylist_Array& polys = mesh->getPolylist_array();
-	for (U32 i = 0; i < polys.getCount(); ++i)
-	{
-		domPolylistRef& poly = polys.get(i);
-		mStatus = load_face_from_dom_polylist(mVolumeFaces, mMaterialList, poly);
-
-		if(mStatus != NO_ERRORS)
-		{
-			mVolumeFaces.clear() ;
-			mMaterialList.clear() ;
-			return ; //abort
-		}
-	}
-	
-	domPolygons_Array& polygons = mesh->getPolygons_array();
-	
-	for (U32 i = 0; i < polygons.getCount(); ++i)
+void LLModel::optimizeVolumeFaces()
+{
+	for (U32 i = 0; i < getNumVolumeFaces(); ++i)
 	{
-		domPolygonsRef& poly = polygons.get(i);
-		mStatus = load_face_from_dom_polygons(mVolumeFaces, mMaterialList, poly);
-
-		if(mStatus != NO_ERRORS)
-		{
-			mVolumeFaces.clear() ;
-			mMaterialList.clear() ;
-			return ; //abort
-		}
+		mVolumeFaces[i].optimize();
 	}
- 
 }
 
-BOOL LLModel::createVolumeFacesFromDomMesh(domMesh* mesh)
+struct MaterialBinding
+{
+	int				index;
+	std::string		matName;
+};
+
+struct MaterialSort
 {
-	if (mesh)
+	bool operator()(const MaterialBinding& lhs, const MaterialBinding& rhs)
 	{
-		mVolumeFaces.clear();
-		mMaterialList.clear();
+		return LLStringUtil::compareInsensitive(lhs.matName, rhs.matName) < 0;
+	}
+};
 
-		addVolumeFacesFromDomMesh(mesh);
-		
-		if (getNumVolumeFaces() > 0)
+void LLModel::sortVolumeFacesByMaterialName()
+{
+	std::vector<MaterialBinding> bindings;
+	bindings.resize(mVolumeFaces.size());
+
+	for (int i = 0; i < bindings.size(); i++)
+	{
+		bindings[i].index = i;
+		if(i < mMaterialList.size())
 		{
-			normalizeVolumeFaces();
-			optimizeVolumeFaces();
-			
-			if (getNumVolumeFaces() > 0)
-			{
-				return TRUE;
-			}
+			bindings[i].matName = mMaterialList[i];
 		}
 	}
-	else
-	{	
-		LL_WARNS() << "no mesh found" << LL_ENDL;
-	}
-	
-	return FALSE;
-}
+	std::sort(bindings.begin(), bindings.end(), MaterialSort());
+	std::vector< LLVolumeFace > new_faces;
 
-void LLModel::offsetMesh( const LLVector3& pivotPoint )
-{
-	LLVector4a pivot( pivotPoint[VX], pivotPoint[VY], pivotPoint[VZ] );
-	
-	for (std::vector<LLVolumeFace>::iterator faceIt = mVolumeFaces.begin(); faceIt != mVolumeFaces.end(); )
+	// remap the faces to be in the same order the mats now are...
+	//
+	new_faces.resize(bindings.size());
+	for (int i = 0; i < bindings.size(); i++)
 	{
-		std::vector<LLVolumeFace>:: iterator currentFaceIt = faceIt++;
-		LLVolumeFace& face = *currentFaceIt;
-		LLVector4a *pos = (LLVector4a*) face.mPositions;
-		
-		for (U32 i=0; i<face.mNumVertices; ++i )
+		new_faces[i] = mVolumeFaces[bindings[i].index];
+		if(i < mMaterialList.size())
 		{
-			pos[i].add( pivot );
+			mMaterialList[i] = bindings[i].matName;
 		}
 	}
+
+	mVolumeFaces = new_faces;	
 }
 
-void LLModel::optimizeVolumeFaces()
+void LLModel::trimVolumeFacesToSize(U32 new_count, LLVolume::face_list_t* remainder)
 {
-	for (U32 i = 0; i < getNumVolumeFaces(); ++i)
+	llassert(new_count <= LL_SCULPT_MESH_MAX_FACES);
+
+	if (new_count && (getNumVolumeFaces() > new_count))
 	{
-		mVolumeFaces[i].optimize();
+		// Copy out remaining volume faces for alternative handling, if provided
+		//
+		if (remainder)
+		{
+			(*remainder).assign(mVolumeFaces.begin() + new_count, mVolumeFaces.end());
+		}		
+
+		// Trim down to the final set of volume faces (now stuffed to the gills!)
+		//
+		mVolumeFaces.resize(new_count);
 	}
 }
 
@@ -1025,11 +184,6 @@ void LLModel::optimizeVolumeFaces()
 // within the unit cube.
 void LLModel::normalizeVolumeFaces()
 {
-
-	// ensure we don't have too many faces
-	if (mVolumeFaces.size() > LL_SCULPT_MESH_MAX_FACES)
-		mVolumeFaces.resize(LL_SCULPT_MESH_MAX_FACES);
-	
 	if (!mVolumeFaces.empty())
 	{
 		LLVector4a min, max;
@@ -1496,68 +650,10 @@ void LLModel::generateNormals(F32 angle_cutoff)
 	}
 }
 
-//static
-std::string LLModel::getElementLabel(daeElement *element)
-{ // try to get a decent label for this 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");
-}
-
-//static 
-LLModel* LLModel::loadModelFromDomMesh(domMesh *mesh)
-{
-	LLVolumeParams volume_params;
-	volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
-	LLModel* ret = new LLModel(volume_params, 0.f); 
-	ret->createVolumeFacesFromDomMesh(mesh);
-	ret->mLabel = getElementLabel(mesh);
-	return ret;
-}
 
 std::string LLModel::getName() const
 {
-	if (!mRequestedLabel.empty())
-		return mRequestedLabel;
-	else
-		return mLabel;
+    return mRequestedLabel.empty() ? mLabel : mRequestedLabel;
 }
 
 //static
@@ -1572,7 +668,8 @@ LLSD LLModel::writeModel(
 	BOOL upload_skin,
 	BOOL upload_joints,
 	BOOL nowrite,
-	BOOL as_slm)
+	BOOL as_slm,
+	int submodel_id)
 {
 	LLSD mdl;
 
@@ -1601,6 +698,14 @@ LLSD LLModel::writeModel(
 			model[LLModel::LOD_PHYSICS] = NULL;
 		}
 	}
+	else if (submodel_id)
+	{
+		const LLModel::Decomposition fake_decomp;
+		mdl["secondary"] = true;
+        mdl["submodel_id"] = submodel_id;
+		mdl["physics_convex"] = fake_decomp.asLLSD();
+		model[LLModel::LOD_PHYSICS] = NULL;
+	}
 
 	if (as_slm)
 	{ //save material list names
@@ -1612,7 +717,7 @@ LLSD LLModel::writeModel(
 
 	for (U32 idx = 0; idx < MODEL_NAMES_LENGTH; ++idx)
 	{
-		if (model[idx] && model[idx]->getNumVolumeFaces() > 0)
+		if (model[idx] && (model[idx]->getNumVolumeFaces() > 0) && model[idx]->getVolumeFace(0).mPositions != NULL)
 		{
 			LLVector3 min_pos = LLVector3(model[idx]->getVolumeFace(0).mPositions[0].getF32ptr());
 			LLVector3 max_pos = min_pos;
@@ -1845,6 +950,11 @@ LLSD LLModel::writeModelToStream(std::ostream& ostr, LLSD& mdl, BOOL nowrite, BO
 		}
 	}
 
+        if (mdl.has("submodel_id"))
+        { //write out submodel id
+        header["submodel_id"] = (LLSD::Integer)mdl["submodel_id"];
+        }
+
 	std::string out[MODEL_NAMES_LENGTH];
 
 	for (S32 i = 0; i < MODEL_NAMES_LENGTH; i++)
@@ -2028,7 +1138,9 @@ bool LLModel::loadModel(std::istream& is)
 		}
 	}
 
-	static const std::string nm[] = 
+	mSubmodelID = header.has("submodel_id") ? header["submodel_id"].asInteger() : false;
+
+	static const std::string lod_name[] = 
 	{
 		"lowest_lod",
 		"low_lod",
@@ -2041,8 +1153,8 @@ bool LLModel::loadModel(std::istream& is)
 
 	S32 lod = llclamp((S32) mDetail, 0, MODEL_LODS);
 
-	if (header[nm[lod]]["offset"].asInteger() == -1 || 
-		header[nm[lod]]["size"].asInteger() == 0 )
+	if (header[lod_name[lod]]["offset"].asInteger() == -1 || 
+		header[lod_name[lod]]["size"].asInteger() == 0 )
 	{ //cannot load requested LOD
 		LL_WARNS() << "LoD data is invalid!" << LL_ENDL;
 		return false;
@@ -2051,23 +1163,23 @@ bool LLModel::loadModel(std::istream& is)
 	bool has_skin = header["skin"]["offset"].asInteger() >=0 &&
 					header["skin"]["size"].asInteger() > 0;
 
-	if (lod == LLModel::LOD_HIGH)
+	if ((lod == LLModel::LOD_HIGH) && !mSubmodelID)
 	{ //try to load skin info and decomp info
 		std::ios::pos_type cur_pos = is.tellg();
 		loadSkinInfo(header, is);
 		is.seekg(cur_pos);
 	}
 
-	if (lod == LLModel::LOD_HIGH || lod == LLModel::LOD_PHYSICS)
+	if ((lod == LLModel::LOD_HIGH || lod == LLModel::LOD_PHYSICS) && !mSubmodelID)
 	{
 		std::ios::pos_type cur_pos = is.tellg();
 		loadDecomposition(header, is);
 		is.seekg(cur_pos);
 	}
 
-	is.seekg(header[nm[lod]]["offset"].asInteger(), std::ios_base::cur);
+	is.seekg(header[lod_name[lod]]["offset"].asInteger(), std::ios_base::cur);
 
-	if (unpackVolumeFaces(is, header[nm[lod]]["size"].asInteger()))
+	if (unpackVolumeFaces(is, header[lod_name[lod]]["size"].asInteger()))
 	{
 		if (has_skin)
 		{ 
@@ -2133,8 +1245,10 @@ bool LLModel::isMaterialListSubset( LLModel* ref )
 				break;
 			}										
 		}
+
 		if (!foundRef)
 		{
+            LL_INFOS() << "Could not find material " << mMaterialList[src] << " in reference model " << ref->mLabel << LL_ENDL;
 			return false;
 		}
 	}
@@ -2185,41 +1299,42 @@ bool LLModel::matchMaterialOrder(LLModel* ref, int& refFaceCnt, int& modelFaceCn
 	for (U32 i = 0; i < mMaterialList.size(); i++)
 	{
 		index_map[ref->mMaterialList[i]] = i;
-		if (!reorder)
-		{ //if any material name does not match reference, we need to reorder
-			reorder = ref->mMaterialList[i] != mMaterialList[i];
-		}
+		//if any material name does not match reference, we need to reorder
+		reorder |= ref->mMaterialList[i] != mMaterialList[i];
 		base_mat.insert(ref->mMaterialList[i]);
 		cur_mat.insert(mMaterialList[i]);
 	}
 
 
-	if (reorder && 
-		base_mat == cur_mat) //don't reorder if material name sets don't match
+	if (reorder &&  (base_mat == cur_mat)) //don't reorder if material name sets don't match
 	{
 		std::vector<LLVolumeFace> new_face_list;
-		new_face_list.resize(mVolumeFaces.size());
+		new_face_list.resize(mMaterialList.size());
 
 		std::vector<std::string> new_material_list;
-		new_material_list.resize(mVolumeFaces.size());
+		new_material_list.resize(mMaterialList.size());
 
 		//rebuild face list so materials have the same order 
 		//as the reference model
 		for (U32 i = 0; i < mMaterialList.size(); ++i)
 		{ 
 			U32 ref_idx = index_map[mMaterialList[i]];
-			new_face_list[ref_idx] = mVolumeFaces[i];
 
+			if (i < mVolumeFaces.size())
+			{
+				new_face_list[ref_idx] = mVolumeFaces[i];
+			}
 			new_material_list[ref_idx] = mMaterialList[i];
 		}
 
 		llassert(new_material_list == ref->mMaterialList);
 		
 		mVolumeFaces = new_face_list;
-	}
 
-	//override material list with reference model ordering
-	mMaterialList = ref->mMaterialList;
+		//override material list with reference model ordering
+		mMaterialList = ref->mMaterialList;
+	}
+	
 	return true;
 }
 
@@ -2250,7 +1365,7 @@ bool LLModel::loadDecomposition(LLSD& header, std::istream& is)
 	S32 offset = header["physics_convex"]["offset"].asInteger();
 	S32 size = header["physics_convex"]["size"].asInteger();
 
-	if (offset >= 0 && size > 0)
+	if (offset >= 0 && size > 0 && !mSubmodelID)
 	{
 		is.seekg(offset, std::ios_base::cur);
 
@@ -2658,3 +1773,227 @@ void LLModel::Decomposition::merge(const LLModel::Decomposition* rhs)
 	}
 }
 
+bool ll_is_degenerate(const LLVector4a& a, const LLVector4a& b, const LLVector4a& c, F32 tolerance)
+{
+	// small area check
+	{
+		LLVector4a edge1; edge1.setSub( a, b );
+		LLVector4a edge2; edge2.setSub( a, c );
+		//////////////////////////////////////////////////////////////////////////
+		/// Linden Modified
+		//////////////////////////////////////////////////////////////////////////
+
+		// If no one edge is more than 10x longer than any other edge, we weaken
+		// the tolerance by a factor of 1e-4f.
+
+		LLVector4a edge3; edge3.setSub( c, b );
+		const F32 len1sq = edge1.dot3(edge1).getF32();
+		const F32 len2sq = edge2.dot3(edge2).getF32();
+		const F32 len3sq = edge3.dot3(edge3).getF32();
+		bool abOK = (len1sq <= 100.f * len2sq) && (len1sq <= 100.f * len3sq);
+		bool acOK = (len2sq <= 100.f * len1sq) && (len1sq <= 100.f * len3sq);
+		bool cbOK = (len3sq <= 100.f * len1sq) && (len1sq <= 100.f * len2sq);
+		if ( abOK && acOK && cbOK )
+		{
+			tolerance *= 1e-4f;
+		}
+
+		//////////////////////////////////////////////////////////////////////////
+		/// End Modified
+		//////////////////////////////////////////////////////////////////////////
+
+		LLVector4a cross; cross.setCross3( edge1, edge2 );
+
+		LLVector4a edge1b; edge1b.setSub( b, a );
+		LLVector4a edge2b; edge2b.setSub( b, c );
+		LLVector4a crossb; crossb.setCross3( edge1b, edge2b );
+
+		if ( ( cross.dot3(cross).getF32() < tolerance ) || ( crossb.dot3(crossb).getF32() < tolerance ))
+		{
+			return true;
+		}
+	}
+
+	// point triangle distance check
+	{
+		LLVector4a Q; Q.setSub(a, b);
+		LLVector4a R; R.setSub(c, b);
+
+		const F32 QQ = dot3fpu(Q, Q);
+		const F32 RR = dot3fpu(R, R);
+		const F32 QR = dot3fpu(R, Q);
+
+		volatile F32 QQRR = QQ * RR;
+		volatile F32 QRQR = QR * QR;
+		F32 Det = (QQRR - QRQR);
+
+		if( Det == 0.0f )
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+bool validate_face(const LLVolumeFace& face)
+{
+	for (U32 i = 0; i < face.mNumIndices; ++i)
+	{
+		if (face.mIndices[i] >= face.mNumVertices)
+		{
+			LL_WARNS() << "Face has invalid index." << LL_ENDL;
+			return false;
+		}
+	}
+
+	if (face.mNumIndices % 3 != 0 || face.mNumIndices == 0)
+	{
+		LL_WARNS() << "Face has invalid number of indices." << LL_ENDL;
+		return false;
+	}
+
+	/*const LLVector4a scale(0.5f);
+
+	for (U32 i = 0; i < face.mNumIndices; i+=3)
+	{
+		U16 idx1 = face.mIndices[i];
+		U16 idx2 = face.mIndices[i+1];
+		U16 idx3 = face.mIndices[i+2];
+
+		LLVector4a v1; v1.setMul(face.mPositions[idx1], scale);
+		LLVector4a v2; v2.setMul(face.mPositions[idx2], scale);
+		LLVector4a v3; v3.setMul(face.mPositions[idx3], scale);
+
+		if (ll_is_degenerate(v1,v2,v3))
+		{
+			llwarns << "Degenerate face found!" << LL_ENDL;
+			return false;
+		}
+	}*/
+
+	return true;
+}
+
+bool validate_model(const LLModel* mdl)
+{
+	if (mdl->getNumVolumeFaces() == 0)
+	{
+		LL_WARNS() << "Model has no faces!" << LL_ENDL;
+		return false;
+	}
+
+	for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i)
+	{
+		if (mdl->getVolumeFace(i).mNumVertices == 0)
+		{
+			LL_WARNS() << "Face has no vertices." << LL_ENDL;
+			return false;
+		}
+
+		if (mdl->getVolumeFace(i).mNumIndices == 0)
+		{
+			LL_WARNS() << "Face has no indices." << LL_ENDL;
+			return false;
+		}
+
+		if (!validate_face(mdl->getVolumeFace(i)))
+		{
+			return false;
+		}
+	}
+
+	return true;
+}
+
+LLModelInstance::LLModelInstance(LLSD& data)
+	: LLModelInstanceBase()
+{
+	mLocalMeshID = data["mesh_id"].asInteger();
+	mLabel = data["label"].asString();
+	mTransform.setValue(data["transform"]);
+
+	for (U32 i = 0; i < data["material"].size(); ++i)
+	{
+		LLImportMaterial mat(data["material"][i]);
+		mMaterial[mat.mBinding] = mat;
+	}
+}
+
+
+LLSD LLModelInstance::asLLSD()
+{	
+	LLSD ret;
+
+	ret["mesh_id"] = mModel->mLocalID;
+	ret["label"] = mLabel;
+	ret["transform"] = mTransform.getValue();
+
+	U32 i = 0;
+	for (std::map<std::string, LLImportMaterial>::iterator iter = mMaterial.begin(); iter != mMaterial.end(); ++iter)
+	{
+		ret["material"][i++] = iter->second.asLLSD();
+	}
+
+	return ret;
+}
+
+
+LLImportMaterial::~LLImportMaterial()
+{
+}
+
+LLImportMaterial::LLImportMaterial(LLSD& data)
+{
+	mDiffuseMapFilename = data["diffuse"]["filename"].asString();
+	mDiffuseMapLabel = data["diffuse"]["label"].asString();
+	mDiffuseColor.setValue(data["diffuse"]["color"]);
+	mFullbright = data["fullbright"].asBoolean();
+	mBinding = data["binding"].asString();
+}
+
+
+LLSD LLImportMaterial::asLLSD()
+{
+	LLSD ret;
+
+	ret["diffuse"]["filename"] = mDiffuseMapFilename;
+	ret["diffuse"]["label"] = mDiffuseMapLabel;
+	ret["diffuse"]["color"] = mDiffuseColor.getValue();
+	ret["fullbright"] = mFullbright;
+	ret["binding"] = mBinding;
+
+	return ret;
+}
+
+bool LLImportMaterial::operator<(const LLImportMaterial &rhs) const
+{
+
+	if (mDiffuseMapID != rhs.mDiffuseMapID)
+	{
+		return mDiffuseMapID < rhs.mDiffuseMapID;
+	}
+
+	if (mDiffuseMapFilename != rhs.mDiffuseMapFilename)
+	{
+		return mDiffuseMapFilename < rhs.mDiffuseMapFilename;
+	}
+
+	if (mDiffuseMapLabel != rhs.mDiffuseMapLabel)
+	{
+		return mDiffuseMapLabel < rhs.mDiffuseMapLabel;
+	}
+
+	if (mDiffuseColor != rhs.mDiffuseColor)
+	{
+		return mDiffuseColor < rhs.mDiffuseColor;
+	}
+
+	if (mBinding != rhs.mBinding)
+	{
+		return mBinding < rhs.mBinding;
+	}
+
+	return mFullbright < rhs.mFullbright;
+}
+
diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h
index aaafc55258d51fac20b3016b7dea88c9d470fcb7..ae602c09df55b8098f0e3479e1e85e49f33c5b99 100755
--- a/indra/llprimitive/llmodel.h
+++ b/indra/llprimitive/llmodel.h
@@ -31,6 +31,7 @@
 #include "llvolume.h"
 #include "v4math.h"
 #include "m4math.h"
+#include <queue>
 
 class daeElement;
 class domMesh;
@@ -138,15 +139,16 @@ class LLModel : public LLVolume
 		BOOL upload_skin,
 		BOOL upload_joints,
 		BOOL nowrite = FALSE,
-		BOOL as_slm = FALSE);
+		BOOL as_slm = FALSE,
+		int submodel_id = 0);
 
 	static LLSD writeModelToStream(
 		std::ostream& ostr,
 		LLSD& mdl,
 		BOOL nowrite = FALSE, BOOL as_slm = FALSE);
+	
+	void ClearFacesAndMaterials() { mVolumeFaces.clear(); mMaterialList.clear(); }
 
-	static LLModel* loadModelFromDomMesh(domMesh* mesh);
-	static std::string getElementLabel(daeElement* element);
 	std::string getName() const;
 	std::string getMetric() const {return mMetric;}
 	EModelStatus getStatus() const {return mStatus;}
@@ -169,20 +171,25 @@ class LLModel : public LLVolume
 
 	void addFace(const LLVolumeFace& face);
 
+	void sortVolumeFacesByMaterialName();
 	void normalizeVolumeFaces();
+	void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL);
 	void optimizeVolumeFaces();
 	void offsetMesh( const LLVector3& pivotPoint );
 	void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out);
 	LLVector3 getTransformedCenter(const LLMatrix4& mat);
-
+	
 	//reorder face list based on mMaterialList in this and reference so 
 	//order matches that of reference (material ordering touchup)
 	bool matchMaterialOrder(LLModel* ref, int& refFaceCnt, int& modelFaceCnt );
 	bool isMaterialListSubset( LLModel* ref );
 	bool needToAddFaces( LLModel* ref, int& refFaceCnt, int& modelFaceCnt );
 	
-	
-	std::vector<std::string> mMaterialList;
+	typedef std::vector<std::string> material_list;
+
+	material_list mMaterialList;
+
+	material_list& getMaterialList() { return mMaterialList; }
 
 	//data used for skin weights
 	class JointWeight
@@ -275,9 +282,115 @@ class LLModel : public LLVolume
 	Decomposition mPhysics;
 
 	EModelStatus mStatus ;
+
+	int mSubmodelID;
+};
+
+typedef std::vector<LLPointer<LLModel> >	model_list;
+typedef std::queue<LLPointer<LLModel> >	model_queue;
+
+class LLModelMaterialBase
+{
+public:	
+	std::string mDiffuseMapFilename;
+	std::string mDiffuseMapLabel;
+	std::string mBinding;
+	LLColor4		mDiffuseColor;
+	bool			mFullbright;
+
+	LLModelMaterialBase() 
+		: mFullbright(false) 
+	{ 
+		mDiffuseColor.set(1,1,1,1);
+	}
+};
+
+class LLImportMaterial : public LLModelMaterialBase
+{
+public:
+    friend class LLMeshUploadThread;
+    friend class LLModelPreview;
+    
+    bool operator<(const LLImportMaterial &params) const;
+    
+    LLImportMaterial() : LLModelMaterialBase()
+    {
+        mDiffuseColor.set(1,1,1,1);
+    }
+    
+    LLImportMaterial(LLSD& data);
+    virtual ~LLImportMaterial();
+    
+    LLSD asLLSD();
+    
+    const LLUUID&	getDiffuseMap() const					{ return mDiffuseMapID;		}
+    void				setDiffuseMap(const LLUUID& texId)	{ mDiffuseMapID = texId;	}
+    
 protected:
-	void addVolumeFacesFromDomMesh(domMesh* mesh);
-	virtual BOOL createVolumeFacesFromDomMesh(domMesh *mesh);
+    
+    LLUUID		mDiffuseMapID;
+    void*			mOpaqueData;	// allow refs to viewer/platform-specific structs for each material
+    // currently only stores an LLPointer< LLViewerFetchedTexture > > to
+    // maintain refs to textures associated with each material for free
+    // ref counting.
 };
 
+typedef std::map<std::string, LLImportMaterial> material_map;
+
+class LLModelInstanceBase
+{
+public:
+	LLPointer<LLModel> mModel;
+	LLPointer<LLModel> mLOD[5];
+	LLUUID mMeshID;
+
+	LLMatrix4 mTransform;
+	material_map mMaterial;
+
+	LLModelInstanceBase(LLModel* model, LLMatrix4& transform, material_map& materials)
+		: mModel(model), mTransform(transform), mMaterial(materials)
+	{
+	}
+
+	LLModelInstanceBase()
+		: mModel(NULL)
+	{
+	}
+};
+
+typedef std::vector<LLModelInstanceBase> model_instance_list;
+
+class LLModelInstance : public LLModelInstanceBase
+{
+public:
+	std::string mLabel;
+	LLUUID mMeshID;
+	S32 mLocalMeshID;
+
+	LLModelInstance(LLModel* model, const std::string& label, LLMatrix4& transform, material_map& materials)
+		: LLModelInstanceBase(model, transform, materials), mLabel(label)
+	{
+		mLocalMeshID = -1;
+	}
+
+	LLModelInstance(LLSD& data);
+
+	LLSD asLLSD();
+};
+
+#define LL_DEGENERACY_TOLERANCE  1e-7f
+
+inline F32 dot3fpu(const LLVector4a& a, const LLVector4a& b)
+{
+	volatile F32 p0 = a[0] * b[0];
+	volatile F32 p1 = a[1] * b[1];
+	volatile F32 p2 = a[2] * b[2];
+	return p0 + p1 + p2;
+}
+
+bool ll_is_degenerate(const LLVector4a& a, const LLVector4a& b, const LLVector4a& c, F32 tolerance = LL_DEGENERACY_TOLERANCE);
+
+bool validate_face(const LLVolumeFace& face);
+bool validate_model(const LLModel* mdl);
+
 #endif //LL_LLMODEL_H
diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..81d92d151b1e911055935100bbb003c8d618dced
--- /dev/null
+++ b/indra/llprimitive/llmodelloader.cpp
@@ -0,0 +1,640 @@
+/**
+ * @file llmodelloader.cpp
+ * @brief LLModelLoader 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 "llmodelloader.h"
+#include "llsdserialize.h"
+#include "lljoint.h"
+#include "llcallbacklist.h"
+
+#include "glh/glh_linear.h"
+#include "llmatrix4a.h"
+#include <boost/bind.hpp>
+
+std::list<LLModelLoader*> LLModelLoader::sActiveLoaderList;
+
+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());
+}
+
+//-----------------------------------------------------------------------------
+// LLModelLoader
+//-----------------------------------------------------------------------------
+LLModelLoader::LLModelLoader(
+	std::string				filename,
+	S32						lod,
+	load_callback_t		load_cb,
+	joint_lookup_func_t	joint_lookup_func,
+	texture_load_func_t	texture_load_func,
+	state_callback_t		state_cb,
+	void*						opaque_userdata,
+	JointTransformMap&	jointMap,
+	JointSet&				jointsFromNodes )
+: mJointList( jointMap )
+, mJointsFromNode( jointsFromNodes )
+, LLThread("Model Loader")
+, mFilename(filename)
+, mLod(lod)
+, mFirstTransform(TRUE)
+, mNumOfFetchingTextures(0)
+, mLoadCallback(load_cb)
+, mJointLookupFunc(joint_lookup_func)
+, mTextureLoadFunc(texture_load_func)
+, mStateCallback(state_cb)
+, mOpaqueData(opaque_userdata)
+, mNoNormalize(false)
+, mNoOptimize(false)
+, mCacheOnlyHitIfRigged(false)
+{
+	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";
+
+	//move into joint mapper class
+	//1. joints for joint offset verification
+	mMasterJointList.push_front("mPelvis");
+	mMasterJointList.push_front("mTorso");
+	mMasterJointList.push_front("mChest");
+	mMasterJointList.push_front("mNeck");
+	mMasterJointList.push_front("mHead");
+	mMasterJointList.push_front("mCollarLeft");
+	mMasterJointList.push_front("mShoulderLeft");
+	mMasterJointList.push_front("mElbowLeft");
+	mMasterJointList.push_front("mWristLeft");
+	mMasterJointList.push_front("mCollarRight");
+	mMasterJointList.push_front("mShoulderRight");
+	mMasterJointList.push_front("mElbowRight");
+	mMasterJointList.push_front("mWristRight");
+	mMasterJointList.push_front("mHipRight");
+	mMasterJointList.push_front("mKneeRight");
+	mMasterJointList.push_front("mFootRight");
+	mMasterJointList.push_front("mHipLeft");
+	mMasterJointList.push_front("mKneeLeft");
+	mMasterJointList.push_front("mFootLeft");
+	
+	//2. legacy joint list - used to verify rigs that will not be using joint offsets
+	mMasterLegacyJointList.push_front("mPelvis");
+	mMasterLegacyJointList.push_front("mTorso");
+	mMasterLegacyJointList.push_front("mChest");
+	mMasterLegacyJointList.push_front("mNeck");
+	mMasterLegacyJointList.push_front("mHead");
+	mMasterLegacyJointList.push_front("mHipRight");
+	mMasterLegacyJointList.push_front("mKneeRight");
+	mMasterLegacyJointList.push_front("mFootRight");
+	mMasterLegacyJointList.push_front("mHipLeft");
+	mMasterLegacyJointList.push_front("mKneeLeft");
+	mMasterLegacyJointList.push_front("mFootLeft");
+
+	assert_main_thread();
+	sActiveLoaderList.push_back(this) ;
+}
+
+LLModelLoader::~LLModelLoader()
+{
+	assert_main_thread();
+	sActiveLoaderList.remove(this);
+}
+
+void LLModelLoader::run()
+{
+	doLoadModel();
+	doOnIdleOneTime(boost::bind(&LLModelLoader::loadModelCallback,this));
+}
+
+bool LLModelLoader::doLoadModel()
+{
+	//first, look for a .slm file of the same name that was modified later
+	//than the .dae
+
+	if (mTrySLM)
+	{
+		std::string filename = mFilename;
+			
+		std::string::size_type i = filename.rfind(".");
+		if (i != std::string::npos)
+		{
+			filename.replace(i, filename.size()-1, ".slm");
+			llstat slm_status;
+			if (LLFile::stat(filename, &slm_status) == 0)
+			{ //slm file exists
+				llstat dae_status;
+				if (LLFile::stat(mFilename, &dae_status) != 0 ||
+					dae_status.st_mtime < slm_status.st_mtime)
+				{
+					if (loadFromSLM(filename))
+					{ //slm successfully loaded, if this fails, fall through and
+						//try loading from dae
+
+						mLod = -1; //successfully loading from an slm implicitly sets all 
+									//LoDs
+						return true;
+					}
+				}
+			}	
+		}
+	}
+
+	return OpenFile(mFilename);
+}
+
+void LLModelLoader::setLoadState(U32 state)
+{
+	mStateCallback(state, mOpaqueData);
+}
+
+bool LLModelLoader::loadFromSLM(const std::string& filename)
+{ 
+	//only need to populate mScene with data from slm
+	llstat stat;
+
+	if (LLFile::stat(filename, &stat))
+	{ //file does not exist
+		return false;
+	}
+
+	S32 file_size = (S32) stat.st_size;
+	
+	llifstream ifstream(filename.c_str(), std::ifstream::in | std::ifstream::binary);
+	LLSD data;
+	LLSDSerialize::fromBinary(data, ifstream, file_size);
+	ifstream.close();
+
+	//build model list for each LoD
+	model_list model[LLModel::NUM_LODS];
+
+	if (data["version"].asInteger() != SLM_SUPPORTED_VERSION)
+	{  //unsupported version
+		return false;
+	}
+
+	LLSD& mesh = data["mesh"];
+
+	LLVolumeParams volume_params;
+	volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE);
+
+	for (S32 lod = 0; lod < LLModel::NUM_LODS; ++lod)
+	{
+		for (U32 i = 0; i < mesh.size(); ++i)
+		{
+			std::stringstream str(mesh[i].asString());
+			LLPointer<LLModel> loaded_model = new LLModel(volume_params, (F32) lod);
+			if (loaded_model->loadModel(str))
+			{
+				loaded_model->mLocalID = i;
+				model[lod].push_back(loaded_model);
+
+				if (lod == LLModel::LOD_HIGH)
+				{
+					if (!loaded_model->mSkinInfo.mJointNames.empty())
+					{ 
+						//check to see if rig is valid					
+						critiqueRigForUploadApplicability( loaded_model->mSkinInfo.mJointNames );					
+					}
+					else if (mCacheOnlyHitIfRigged)
+					{
+						return false;
+					}
+				}
+			}
+		}
+	}	
+
+	if (model[LLModel::LOD_HIGH].empty())
+	{ //failed to load high lod
+		return false;
+	}
+
+	//load instance list
+	model_instance_list instance_list;
+
+	LLSD& instance = data["instance"];
+
+	for (U32 i = 0; i < instance.size(); ++i)
+	{
+		//deserialize instance list
+		instance_list.push_back(LLModelInstance(instance[i]));
+
+		//match up model instance pointers
+		S32 idx = instance_list[i].mLocalMeshID;
+		std::string instance_label = instance_list[i].mLabel;
+
+		for (U32 lod = 0; lod < LLModel::NUM_LODS; ++lod)
+		{
+			if (!model[lod].empty())
+			{
+				if (idx >= model[lod].size())
+				{
+					if (model[lod].size())
+					{
+						instance_list[i].mLOD[lod] = model[lod][0];
+					}
+					else
+					{
+						instance_list[i].mLOD[lod] = NULL;
+					}					
+					continue;
+				}
+
+				if (model[lod][idx]
+					&& model[lod][idx]->mLabel.empty()
+					&& !instance_label.empty())
+				{
+					// restore model names
+					std::string name = instance_label;
+					switch (lod)
+					{
+					case LLModel::LOD_IMPOSTOR: name += "_LOD0"; break;
+					case LLModel::LOD_LOW:      name += "_LOD1"; break;
+					case LLModel::LOD_MEDIUM:   name += "_LOD2"; break;
+					case LLModel::LOD_PHYSICS:  name += "_PHYS"; break;
+					case LLModel::LOD_HIGH:                      break;
+					}
+					model[lod][idx]->mLabel = name;
+				}
+
+				instance_list[i].mLOD[lod] = model[lod][idx];
+			}
+		}
+
+		if (!instance_list[i].mModel)
+			instance_list[i].mModel = model[LLModel::LOD_HIGH][idx];
+	}
+
+	// Set name for UI to use
+	std::string name = data["name"];
+	if (!name.empty())
+	{
+		model[LLModel::LOD_HIGH][0]->mRequestedLabel = name;
+	}
+
+
+	//convert instance_list to mScene
+	mFirstTransform = TRUE;
+	for (U32 i = 0; i < instance_list.size(); ++i)
+	{
+		LLModelInstance& cur_instance = instance_list[i];
+		mScene[cur_instance.mTransform].push_back(cur_instance);
+		stretch_extents(cur_instance.mModel, cur_instance.mTransform, mExtents[0], mExtents[1], mFirstTransform);
+	}
+	
+	setLoadState( DONE );
+
+	return true;
+}
+
+//static
+bool LLModelLoader::isAlive(LLModelLoader* loader)
+{
+	if(!loader)
+	{
+		return false ;
+	}
+
+	std::list<LLModelLoader*>::iterator iter = sActiveLoaderList.begin() ;
+	for(; iter != sActiveLoaderList.end() && (*iter) != loader; ++iter) ;
+	
+	return *iter == loader ;
+}
+
+void LLModelLoader::loadModelCallback()
+{
+	mLoadCallback(mScene,mModelList,mLod, mOpaqueData);
+
+	while (!isStopped())
+	{ //wait until this thread is stopped before deleting self
+		apr_sleep(100);
+	}
+
+	//double check if "this" is valid before deleting it, in case it is aborted during running.
+	if(!isAlive(this))
+	{
+		return ;
+	}
+
+	delete this;
+}
+
+//-----------------------------------------------------------------------------
+// critiqueRigForUploadApplicability()
+//-----------------------------------------------------------------------------
+void LLModelLoader::critiqueRigForUploadApplicability( const std::vector<std::string> &jointListFromAsset )
+{
+	critiqueJointToNodeMappingFromScene();
+	
+	//Determines the following use cases for a rig:
+	//1. It is suitable for upload with skin weights & joint positions, or
+	//2. It is suitable for upload as standard av with just skin weights
+	
+	bool isJointPositionUploadOK = isRigSuitableForJointPositionUpload( jointListFromAsset );
+	bool isRigLegacyOK			 = isRigLegacy( jointListFromAsset );
+
+	//It's OK that both could end up being true, both default to false
+	if ( isJointPositionUploadOK )
+	{
+		setRigValidForJointPositionUpload( true );
+	}
+
+	if ( isRigLegacyOK) 
+	{	
+		setLegacyRigValid( true );
+	}
+
+}
+//-----------------------------------------------------------------------------
+// critiqueJointToNodeMappingFromScene()
+//-----------------------------------------------------------------------------
+void LLModelLoader::critiqueJointToNodeMappingFromScene( void  )
+{
+	//Do the actual nodes back the joint listing from the dae?
+	//if yes then this is a fully rigged asset, otherwise it's just a partial rig
+	
+	JointSet::iterator jointsFromNodeIt = mJointsFromNode.begin();
+	JointSet::iterator jointsFromNodeEndIt = mJointsFromNode.end();
+	bool result = true;
+
+	if ( !mJointsFromNode.empty() )
+	{
+		for ( ;jointsFromNodeIt!=jointsFromNodeEndIt;++jointsFromNodeIt )
+		{
+			std::string name = *jointsFromNodeIt;
+			if ( mJointTransformMap.find( name ) != mJointTransformMap.end() )
+			{
+				continue;
+			}
+			else
+			{
+				LL_INFOS() <<"critiqueJointToNodeMappingFromScene is missing a: " << name << LL_ENDL;
+				result = false;				
+			}
+		}
+	}
+	else
+	{
+		result = false;
+	}
+
+	//Determines the following use cases for a rig:
+	//1. Full av rig  w/1-1 mapping from the scene and joint array
+	//2. Partial rig but w/o parity between the scene and joint array
+	if ( result )
+	{		
+		setRigWithSceneParity( true );
+	}	
+}
+//-----------------------------------------------------------------------------
+// isRigLegacy()
+//-----------------------------------------------------------------------------
+bool LLModelLoader::isRigLegacy( const std::vector<std::string> &jointListFromAsset )
+{
+	//No joints in asset
+	if ( jointListFromAsset.size() == 0 )
+	{
+		return false;
+	}
+
+	bool result = false;
+
+	JointSet :: const_iterator masterJointIt = mMasterLegacyJointList.begin();	
+	JointSet :: const_iterator masterJointEndIt = mMasterLegacyJointList.end();
+	
+	std::vector<std::string> :: const_iterator modelJointIt = jointListFromAsset.begin();	
+	std::vector<std::string> :: const_iterator modelJointItEnd = jointListFromAsset.end();
+	
+	for ( ;masterJointIt!=masterJointEndIt;++masterJointIt )
+	{
+		result = false;
+		modelJointIt = jointListFromAsset.begin();
+
+		for ( ;modelJointIt!=modelJointItEnd; ++modelJointIt )
+		{
+			if ( *masterJointIt == *modelJointIt )
+			{
+				result = true;
+				break;
+			}			
+		}		
+		if ( !result )
+		{
+			LL_INFOS() <<" Asset did not contain the joint (if you're u/l a fully rigged asset w/joint positions - it is required)." << *masterJointIt<< LL_ENDL;
+			break;
+		}
+	}	
+	return result;
+}
+//-----------------------------------------------------------------------------
+// isRigSuitableForJointPositionUpload()
+//-----------------------------------------------------------------------------
+bool LLModelLoader::isRigSuitableForJointPositionUpload( const std::vector<std::string> &jointListFromAsset )
+{
+	bool result = false;
+
+	JointSet :: const_iterator masterJointIt = mMasterJointList.begin();	
+	JointSet :: const_iterator masterJointEndIt = mMasterJointList.end();
+	
+	std::vector<std::string> :: const_iterator modelJointIt = jointListFromAsset.begin();	
+	std::vector<std::string> :: const_iterator modelJointItEnd = jointListFromAsset.end();
+	
+	for ( ;masterJointIt!=masterJointEndIt;++masterJointIt )
+	{
+		result = false;
+		modelJointIt = jointListFromAsset.begin();
+
+		for ( ;modelJointIt!=modelJointItEnd; ++modelJointIt )
+		{
+			if ( *masterJointIt == *modelJointIt )
+			{
+				result = true;
+				break;
+			}			
+		}		
+		if ( !result )
+		{
+			LL_INFOS() <<" Asset did not contain the joint (if you're u/l a fully rigged asset w/joint positions - it is required)." << *masterJointIt<< LL_ENDL;
+			break;
+		}
+	}	
+	return result;
+}
+
+
+//called in the main thread
+void LLModelLoader::loadTextures()
+{
+	BOOL is_paused = isPaused() ;
+	pause() ; //pause the loader 
+
+	for(scene::iterator iter = mScene.begin(); iter != mScene.end(); ++iter)
+	{
+		for(U32 i = 0 ; i < iter->second.size(); i++)
+		{
+			for(std::map<std::string, LLImportMaterial>::iterator j = iter->second[i].mMaterial.begin();
+				j != iter->second[i].mMaterial.end(); ++j)
+			{
+				LLImportMaterial& material = j->second;
+
+				if(!material.mDiffuseMapFilename.empty())
+				{
+					mNumOfFetchingTextures += mTextureLoadFunc(material, mOpaqueData);					
+				}
+			}
+		}
+	}
+
+	if(!is_paused)
+	{
+		unpause() ;
+	}
+}
diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h
new file mode 100644
index 0000000000000000000000000000000000000000..bb4d06dca3c5f9a43de3b322d77d3f535c88207d
--- /dev/null
+++ b/indra/llprimitive/llmodelloader.h
@@ -0,0 +1,212 @@
+/**
+ * @file llmodelloader.h
+ * @brief LLModelLoader 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_LLMODELLOADER_H
+#define LL_LLMODELLOADER_H
+
+#include "llmodel.h"
+#include "llthread.h"
+#include <boost/function.hpp>
+#include <list>
+
+class LLJoint;
+
+typedef std::map<std::string, LLMatrix4>					JointTransformMap;
+typedef std::map<std::string, LLMatrix4>:: iterator	JointTransformMapIt;
+typedef std::map<std::string, std::string>				JointMap;
+typedef std::deque<std::string>								JointSet;
+
+const S32 SLM_SUPPORTED_VERSION	= 3;
+const S32 NUM_LOD						= 4;
+
+class LLModelLoader : public LLThread
+{
+public:
+
+	typedef std::map<std::string, LLImportMaterial>			material_map;
+	typedef std::vector<LLPointer<LLModel > >					model_list;	
+	typedef std::vector<LLModelInstance>						model_instance_list;	
+	typedef std::map<LLMatrix4, model_instance_list >		scene;
+
+	// Callback with loaded model data and loaded LoD
+	// 
+	typedef boost::function<void (scene&,model_list&,S32,void*) >		load_callback_t;
+
+	// Function to provide joint lookup by name
+	// (within preview avi skeleton, for example)
+	//
+	typedef boost::function<LLJoint* (const std::string&,void*) >		joint_lookup_func_t;
+
+	// Func to load and associate material with all it's textures,
+	// returned value is the number of textures loaded
+	// intentionally non-const so func can modify material to
+	// store platform-specific data
+	//
+	typedef boost::function<U32 (LLImportMaterial&,void*) >				texture_load_func_t;
+
+	// Callback to inform client of state changes
+	// during loading process (errors will be reported
+	// as state changes here as well)
+	//
+	typedef boost::function<void (U32,void*) >								state_callback_t;
+
+	typedef enum
+	{
+		STARTING = 0,
+		READING_FILE,
+		CREATING_FACES,
+		GENERATING_VERTEX_BUFFERS,
+		GENERATING_LOD,
+		DONE,
+		ERROR_PARSING, //basically loading failed
+		ERROR_MATERIALS,
+		ERROR_PASSWORD_REQUIRED,
+		ERROR_NEED_MORE_MEMORY,
+		ERROR_INVALID_FILE,
+		ERROR_LOADER_SETUP,
+		ERROR_INVALID_PARAMETERS,
+		ERROR_OUT_OF_RANGE,
+		ERROR_FILE_VERSION_INVALID,
+		ERROR_MODEL // this error should always be last in this list, error code is passed as ERROR_MODEL+error_code
+	} eLoadState;
+
+	U32 mState;
+	std::string mFilename;
+	
+	S32 mLod;
+	
+	LLMatrix4 mTransform;
+	BOOL mFirstTransform;
+	LLVector3 mExtents[2];
+	
+	bool mTrySLM;
+	bool mCacheOnlyHitIfRigged; // ignore cached SLM if it does not contain rig info (and we want rig info)
+
+	model_list		mModelList;
+	scene				mScene;
+
+	typedef std::queue<LLPointer<LLModel> > model_queue;
+
+	//queue of models that need a physics rep
+	model_queue mPhysicsQ;
+
+	//map of avatar joints as named in COLLADA assets to internal joint names
+	JointMap			mJointMap;
+	JointTransformMap&	mJointList;	
+	JointSet&			mJointsFromNode;
+
+	LLModelLoader(
+		std::string									filename,
+		S32											lod, 
+		LLModelLoader::load_callback_t		load_cb,
+		LLModelLoader::joint_lookup_func_t	joint_lookup_func,
+		LLModelLoader::texture_load_func_t	texture_load_func,
+		LLModelLoader::state_callback_t		state_cb,
+		void*											opaque_userdata,
+		JointTransformMap&						jointMap,
+		JointSet&									jointsFromNodes);
+	virtual ~LLModelLoader() ;
+
+	virtual void setNoNormalize() { mNoNormalize = true; }
+	virtual void setNoOptimize() { mNoOptimize = true; }
+
+	virtual void run();
+	
+	// Will try SLM or derived class OpenFile as appropriate
+	//
+	virtual bool doLoadModel();
+
+	// Derived classes need to provide their parsing of files here
+	//
+	virtual bool OpenFile(const std::string& filename) = 0;
+
+	bool loadFromSLM(const std::string& filename);
+	
+	void loadModelCallback();
+	void loadTextures() ; //called in the main thread.
+	void setLoadState(U32 state);
+
+	
+
+	S32 mNumOfFetchingTextures ; //updated in the main thread
+	bool areTexturesReady() { return !mNumOfFetchingTextures; } //called in the main thread.
+
+	bool verifyCount( int expected, int result );
+
+	//Determines the viability of an asset to be used as an avatar rig (w or w/o joint upload caps)
+	void critiqueRigForUploadApplicability( const std::vector<std::string> &jointListFromAsset );
+	void critiqueJointToNodeMappingFromScene( void  );
+
+	//Determines if a rig is a legacy from the joint list
+	bool isRigLegacy( const std::vector<std::string> &jointListFromAsset );
+
+	//Determines if a rig is suitable for upload
+	bool isRigSuitableForJointPositionUpload( const std::vector<std::string> &jointListFromAsset );
+
+	void setRigWithSceneParity( bool state ) { mRigParityWithScene = state; }
+	const bool getRigWithSceneParity( void ) const { return mRigParityWithScene; }
+
+	const bool isRigValidForJointPositionUpload( void ) const { return mRigValidJointUpload; }
+	void setRigValidForJointPositionUpload( bool rigValid ) { mRigValidJointUpload = rigValid; }
+
+	const bool isLegacyRigValid( void ) const { return mLegacyRigValid; }
+	void setLegacyRigValid( bool rigValid ) { mLegacyRigValid = rigValid; }		
+
+	//-----------------------------------------------------------------------------
+	// isNodeAJoint()
+	//-----------------------------------------------------------------------------
+	bool isNodeAJoint(const char* name)
+	{
+		return mJointMap.find(name) != mJointMap.end();
+	}
+
+protected:
+
+	LLModelLoader::load_callback_t		mLoadCallback;
+	LLModelLoader::joint_lookup_func_t	mJointLookupFunc;
+	LLModelLoader::texture_load_func_t	mTextureLoadFunc;
+	LLModelLoader::state_callback_t		mStateCallback;
+	void*											mOpaqueData;
+
+	bool		mRigParityWithScene;
+	bool		mRigValidJointUpload;
+	bool		mLegacyRigValid;
+
+	bool		mNoNormalize;
+	bool		mNoOptimize;
+
+	JointSet				mMasterJointList;
+	JointSet				mMasterLegacyJointList;
+	JointTransformMap	mJointTransformMap;
+
+	static std::list<LLModelLoader*> sActiveLoaderList;
+	static bool isAlive(LLModelLoader* loader) ;
+};
+class LLMatrix4a;
+void stretch_extents(LLModel* model, LLMatrix4a& mat, LLVector4a& min, LLVector4a& max, BOOL& first_transform);
+void stretch_extents(LLModel* model, LLMatrix4& mat, LLVector3& min, LLVector3& max, BOOL& first_transform);
+
+#endif  // LL_LLMODELLOADER_H
diff --git a/indra/llprimitive/lltextureentry.h b/indra/llprimitive/lltextureentry.h
index 3fe23d6c9f282bd49a6fb57cfa4ea29bf242f94f..81e2e8a5a28708b7f735a233503279d4e9ce702d 100755
--- a/indra/llprimitive/lltextureentry.h
+++ b/indra/llprimitive/lltextureentry.h
@@ -89,6 +89,10 @@ class LLTextureEntry
 
 	bool operator==(const LLTextureEntry &rhs) const;
 	bool operator!=(const LLTextureEntry &rhs) const;
+	
+	// Added to allow use with std::map
+	//
+	bool operator <(const LLTextureEntry &rhs) const;
 
 	LLSD asLLSD() const;
 	void asLLSD(LLSD& sd) const;
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 536c7740bbe80622994a10421d210ca7477e19e1..16877c345e82b94ab7725f1f083cd02505b50729 100755
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -140,7 +140,6 @@ set(viewer_SOURCE_FILES
     llbreadcrumbview.cpp
     llbrowsernotification.cpp
     llbuycurrencyhtml.cpp
-    llcallbacklist.cpp
     llcallingcard.cpp
     llcapabilitylistener.cpp
     llcaphttpsender.cpp
@@ -756,7 +755,6 @@ set(viewer_HEADER_FILES
     llbox.h
     llbreadcrumbview.h
     llbuycurrencyhtml.h
-    llcallbacklist.h
     llcallingcard.h
     llcapabilitylistener.h
     llcapabilityprovider.h
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 96fbc8323ba7013e601c297c265cea1e3d35255e..f05836742ae4ff24de3c8a9de84c5a5bee0b6c23 100755
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -2,6 +2,39 @@
 <llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:noNamespaceSchemaLocation="llsd.xsd">
 <map>
+  <key>ImporterDebug</key>
+  <map>
+    <key>Comment</key>
+    <string>Enable debug output to more precisely identify sources of import errors. Warning: the output can slow down import on many machines.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>0</integer>
+  </map>
+  <key>ImporterLegacyMatching</key>
+  <map>
+    <key>Comment</key>
+    <string>Enable index based model matching.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>0</integer>
+  </map>
+  <key>ImporterModelLimit</key>
+  <map>
+    <key>Comment</key>
+    <string>Limits amount of importer generated models for dae files</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>U32</string>
+    <key>Value</key>
+    <integer>768</integer>
+  </map>
     <key>IMShowTime</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llcallbacklist.cpp b/indra/newview/llcallbacklist.cpp
old mode 100755
new mode 100644
diff --git a/indra/newview/llcommandhandler.cpp b/indra/newview/llcommandhandler.cpp
index 19dba3f9173a0a210736d92f620a96f5ad6283c7..5ea7efc0454dbd26281f9d4bace2bf44dbbdd28e 100755
--- a/indra/newview/llcommandhandler.cpp
+++ b/indra/newview/llcommandhandler.cpp
@@ -30,6 +30,7 @@
 #include "llcommandhandler.h"
 #include "llnotificationsutil.h"
 #include "llcommanddispatcherlistener.h"
+#include "llstartup.h"
 #include "stringize.h"
 
 // system includes
@@ -116,7 +117,11 @@ bool LLCommandHandlerRegistry::dispatch(const std::string& cmd,
 			LL_WARNS_ONCE("SLURL") << "Blocked SLURL command from untrusted browser" << LL_ENDL;
 			if (! slurl_blocked)
 			{
-				LLNotificationsUtil::add("BlockedSLURL");
+				if (LLStartUp::getStartupState() >= STATE_BROWSER_INIT)
+				{
+					// Note: commands can arrive before we initialize everything we need for Notification.
+					LLNotificationsUtil::add("BlockedSLURL");
+				}
 				slurl_blocked = true;
 			}
 			return true;
@@ -138,7 +143,10 @@ bool LLCommandHandlerRegistry::dispatch(const std::string& cmd,
 				LL_WARNS_ONCE("SLURL") << "Throttled SLURL command from untrusted browser" << LL_ENDL;
 				if (! slurl_throttled)
 				{
-					LLNotificationsUtil::add("ThrottledSLURL");
+					if (LLStartUp::getStartupState() >= STATE_BROWSER_INIT)
+					{
+						LLNotificationsUtil::add("ThrottledSLURL");
+					}
 					slurl_throttled = true;
 				}
 				return true;
diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp
index 821d58a9b282c6fe41bf009905588418379224dc..f828b56f7f587099f82a574740acc58cf82c2485 100755
--- a/indra/newview/lldrawpoolavatar.cpp
+++ b/indra/newview/lldrawpoolavatar.cpp
@@ -1600,6 +1600,14 @@ void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace*
 		for (U32 j = 0; j < count; ++j)
 		{
 			LLJoint* joint = avatar->getJoint(skin->mJointNames[j]);
+			if (!joint)
+			{
+				joint = avatar->getJoint("mPelvis");
+			}
+			if (!joint)
+			{
+				LL_DEBUGS("Avatar") << "Failed to find " << skin->mJointNames[j] << LL_ENDL;
+			}
 			if (joint)
 			{
 				mat[j] = skin->mInvBindMatrix[j];
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 9a0bd9d1bc53977a7905bb65481894d805d68c56..55b94aa1412c51ad3eabab0769fcbb90b7f999f4 100755
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -493,6 +493,12 @@ void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res,
 	}
 }
 
+LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMaterial& material)
+{
+	LLPointer< LLViewerFetchedTexture > * ppTex = static_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData);
+	return ppTex ? (*ppTex).get() : NULL;
+}
+
 volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0;
 volatile S32 LLMeshRepoThread::sActiveLODRequests = 0;
 U32	LLMeshRepoThread::sMaxConcurrentRequests = 1;
@@ -587,16 +593,16 @@ class LLMeshLODHandler : public LLMeshHandlerBase
 	LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes)
 		: LLMeshHandlerBase(offset, requested_bytes),
 		  mLOD(lod)
-		{
+	{
 			mMeshParams = mesh_params;
 			LLMeshRepoThread::incActiveLODRequests();
 		}
 	virtual ~LLMeshLODHandler();
-
+	
 protected:
 	LLMeshLODHandler(const LLMeshLODHandler &);					// Not defined
 	void operator=(const LLMeshLODHandler &);					// Not defined
-
+	
 public:
 	virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size);
 	virtual void processFailure(LLCore::HttpStatus status);
@@ -796,7 +802,7 @@ LLMeshRepoThread::LLMeshRepoThread()
 	mHttpLargePolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_LARGE_MESH);
 }
 
-			
+
 LLMeshRepoThread::~LLMeshRepoThread()
 {
 	LL_INFOS(LOG_MESH) << "Small GETs issued:  " << LLMeshRepository::sHTTPRequestCount
@@ -865,16 +871,16 @@ void LLMeshRepoThread::run()
 		{
 			break;
 		}
-
+		
 		if (! mHttpRequestSet.empty())
 		{
 			// Dispatch all HttpHandler notifications
 			mHttpRequest->update(0L);
 		}
 		sRequestWaterLevel = mHttpRequestSet.size();			// Stats data update
-
-		// NOTE: order of queue processing intentionally favors LOD requests over header requests
 			
+		// NOTE: order of queue processing intentionally favors LOD requests over header requests
+
 		while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater)
 		{
 			if (! mMutex)
@@ -938,7 +944,7 @@ void LLMeshRepoThread::run()
 					mSkinRequests.erase(iter);
 					mMutex->unlock();
 
-					if (!fetchMeshSkinInfo(mesh_id))
+					if (! fetchMeshSkinInfo(mesh_id))
 					{
 						incomplete.insert(mesh_id);
 					}
@@ -967,7 +973,7 @@ void LLMeshRepoThread::run()
 					mDecompositionRequests.erase(iter);
 					mMutex->unlock();
 					
-					if (!fetchMeshDecomposition(mesh_id))
+					if (! fetchMeshDecomposition(mesh_id))
 					{
 						incomplete.insert(mesh_id);
 					}
@@ -993,7 +999,7 @@ void LLMeshRepoThread::run()
 					mPhysicsShapeRequests.erase(iter);
 					mMutex->unlock();
 					
-					if (!fetchMeshPhysicsShape(mesh_id))
+					if (! fetchMeshPhysicsShape(mesh_id))
 					{
 						incomplete.insert(mesh_id);
 					}
@@ -1219,7 +1225,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
 	}
 
 	++LLMeshRepository::sMeshRequestCount;
-	bool ret = true ;
+	bool ret = true;
 	U32 header_size = mMeshHeaderSize[mesh_id];
 	
 	if (header_size > 0)
@@ -1267,7 +1273,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
 			constructUrl(mesh_id, &http_url, &cap_version);
 
 			if (!http_url.empty())
-			{				
+			{
 				LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size);
 				LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
 				if (LLCORE_HTTP_HANDLE_INVALID == handle)
@@ -1313,7 +1319,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
 
 	++LLMeshRepository::sMeshRequestCount;
 	U32 header_size = mMeshHeaderSize[mesh_id];
-	bool ret = true ;
+	bool ret = true;
 	
 	if (header_size > 0)
 	{
@@ -1359,9 +1365,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
 			int cap_version(2);
 			std::string http_url;
 			constructUrl(mesh_id, &http_url, &cap_version);
-
+			
 			if (!http_url.empty())
-			{				
+			{
 				LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size);
 				LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
 				if (LLCORE_HTTP_HANDLE_INVALID == handle)
@@ -1407,7 +1413,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
 
 	++LLMeshRepository::sMeshRequestCount;
 	U32 header_size = mMeshHeaderSize[mesh_id];
-	bool ret = true ;
+	bool ret = true;
 
 	if (header_size > 0)
 	{
@@ -1452,9 +1458,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
 			int cap_version(2);
 			std::string http_url;
 			constructUrl(mesh_id, &http_url, &cap_version);
-
+			
 			if (!http_url.empty())
-			{				
+			{
 				LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size);
 				LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
 				if (LLCORE_HTTP_HANDLE_INVALID == handle)
@@ -1543,11 +1549,11 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params)
 	}
 
 	//either cache entry doesn't exist or is corrupt, request header from simulator	
-	bool retval = true ;
+	bool retval = true;
 	int cap_version(2);
 	std::string http_url;
 	constructUrl(mesh_params.getSculptID(), &http_url, &cap_version);
-
+	
 	if (!http_url.empty())
 	{
 		//grab first 4KB if we're going to bother with a fetch.  Cache will prevent future fetches if a full mesh fits
@@ -1635,9 +1641,9 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
 			int cap_version(2);
 			std::string http_url;
 			constructUrl(mesh_id, &http_url, &cap_version);
-
+			
 			if (!http_url.empty())
-			{				
+			{
 				LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size);
 				LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler);
 				if (LLCORE_HTTP_HANDLE_INVALID == handle)
@@ -1909,7 +1915,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data,
 
 	mOrigin += gAgent.getAtAxis() * scale.magVec();
 
-	mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut") ;
+	mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut");
 
 	mHttpRequest = new LLCore::HttpRequest;
 	mHttpOptions = new LLCore::HttpOptions;
@@ -1977,14 +1983,14 @@ void LLMeshUploadThread::preStart()
 
 void LLMeshUploadThread::discard()
 {
-	LLMutexLock lock(mMutex) ;
+	LLMutexLock lock(mMutex);
 	mDiscarded = true;
 }
 
 bool LLMeshUploadThread::isDiscarded() const
 {
-	LLMutexLock lock(mMutex) ;
-	return mDiscarded ;
+	LLMutexLock lock(mMutex);
+	return mDiscarded;
 }
 
 void LLMeshUploadThread::run()
@@ -2049,6 +2055,14 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures)
 	{
 		LLMeshUploadData data;
 		data.mBaseModel = iter->first;
+
+		if (data.mBaseModel->mSubmodelID)
+		{
+			// These are handled below to insure correct parenting order on creation
+			// due to map walking being based on model address (aka random)
+			continue;
+		}
+
 		LLModelInstance& first_instance = *(iter->second.begin());
 		for (S32 i = 0; i < 5; i++)
 		{
@@ -2086,7 +2100,10 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures)
 				data.mModel[LLModel::LOD_IMPOSTOR], 
 				decomp,
 				mUploadSkin,
-				mUploadJoints);
+				mUploadJoints,
+				FALSE,
+				FALSE,
+				data.mBaseModel->mSubmodelID);
 
 			data.mAssetData = ostr.str();
 			std::string str = ostr.str();
@@ -2120,17 +2137,26 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures)
 			instance_entry["scale"] = ll_sd_from_vector3(scale);
 		
 			instance_entry["material"] = LL_MCODE_WOOD;
-			instance_entry["physics_shape_type"] = (U8)(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL);
+			instance_entry["physics_shape_type"] = data.mModel[LLModel::LOD_PHYSICS].notNull() ? (U8)(LLViewerObject::PHYSICS_SHAPE_PRIM) : (U8)(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL);
 			instance_entry["mesh"] = mesh_index[data.mBaseModel];
 
 			instance_entry["face_list"] = LLSD::emptyArray();
 
-			S32 end = llmin((S32)data.mBaseModel->mMaterialList.size(), data.mBaseModel->getNumVolumeFaces()) ;
+			// We want to be able to allow more than 8 materials...
+			//
+			S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ;
+
 			for (S32 face_num = 0; face_num < end; face_num++)
 			{
 				LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]];
 				LLSD face_entry = LLSD::emptyMap();
-				LLViewerFetchedTexture *texture = material.mDiffuseMap.get();
+
+				LLViewerFetchedTexture *texture = NULL;
+
+				if (material.mDiffuseMapFilename.size())
+				{
+					texture = FindViewerTexture(material);
+				}
 				
 				if ((texture != NULL) &&
 					(textures.find(texture) == textures.end()))
@@ -2145,9 +2171,171 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures)
 					{											
 						LLPointer<LLImageJ2C> upload_file =
 							LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage());
+
+						if (!upload_file.isNull() && upload_file->getDataSize())
+						{
+						texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize());
+					}
+				}
+				}
+
+				if (texture != NULL &&
+					mUploadTextures &&
+					texture_index.find(texture) == texture_index.end())
+				{
+					texture_index[texture] = texture_num;
+					std::string str = texture_str.str();
+					res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end());
+					texture_num++;
+				}
+
+				// Subset of TextureEntry fields.
+				if (texture != NULL && mUploadTextures)
+				{
+					face_entry["image"] = texture_index[texture];
+					face_entry["scales"] = 1.0;
+					face_entry["scalet"] = 1.0;
+					face_entry["offsets"] = 0.0;
+					face_entry["offsett"] = 0.0;
+					face_entry["imagerot"] = 0.0;
+				}
+				face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor);
+				face_entry["fullbright"] = material.mFullbright;
+				instance_entry["face_list"][face_num] = face_entry;
+		    }
+
+			res["instance_list"][instance_num] = instance_entry;
+			instance_num++;
+		}
+	}
+
+	for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter)
+	{
+		LLMeshUploadData data;
+		data.mBaseModel = iter->first;
+
+		if (!data.mBaseModel->mSubmodelID)
+		{
+			// These were handled above already...
+			//
+			continue;
+		}
+
+		LLModelInstance& first_instance = *(iter->second.begin());
+		for (S32 i = 0; i < 5; i++)
+		{
+			data.mModel[i] = first_instance.mLOD[i];
+		}
+
+		if (mesh_index.find(data.mBaseModel) == mesh_index.end())
+		{
+			// Have not seen this model before - create a new mesh_list entry for it.
+			if (model_name.empty())
+			{
+				model_name = data.mBaseModel->getName();
+			}
+
+			if (model_metric.empty())
+			{
+				model_metric = data.mBaseModel->getMetric();
+			}
+
+			std::stringstream ostr;
+			
+			LLModel::Decomposition& decomp =
+				data.mModel[LLModel::LOD_PHYSICS].notNull() ? 
+				data.mModel[LLModel::LOD_PHYSICS]->mPhysics : 
+				data.mBaseModel->mPhysics;
+
+			decomp.mBaseHull = mHullMap[data.mBaseModel];
+
+			LLSD mesh_header = LLModel::writeModel(
+				ostr,  
+				data.mModel[LLModel::LOD_PHYSICS],
+				data.mModel[LLModel::LOD_HIGH],
+				data.mModel[LLModel::LOD_MEDIUM],
+				data.mModel[LLModel::LOD_LOW],
+				data.mModel[LLModel::LOD_IMPOSTOR], 
+				decomp,
+				mUploadSkin,
+				mUploadJoints,
+				FALSE,
+				FALSE,
+				data.mBaseModel->mSubmodelID);
+
+			data.mAssetData = ostr.str();
+			std::string str = ostr.str();
+
+			res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); 
+			mesh_index[data.mBaseModel] = mesh_num;
+			mesh_num++;
+		}
+
+		// For all instances that use this model
+		for (instance_list::iterator instance_iter = iter->second.begin();
+			 instance_iter != iter->second.end();
+			 ++instance_iter)
+		{
+
+			LLModelInstance& instance = *instance_iter;
+		
+			LLSD instance_entry;
+		
+			for (S32 i = 0; i < 5; i++)
+			{
+				data.mModel[i] = instance.mLOD[i];
+			}
+		
+			LLVector3 pos, scale;
+			LLQuaternion rot;
+			LLMatrix4 transformation = instance.mTransform;
+			decomposeMeshMatrix(transformation,pos,rot,scale);
+			instance_entry["position"] = ll_sd_from_vector3(pos);
+			instance_entry["rotation"] = ll_sd_from_quaternion(rot);
+			instance_entry["scale"] = ll_sd_from_vector3(scale);
+		
+			instance_entry["material"] = LL_MCODE_WOOD;
+			instance_entry["physics_shape_type"] = (U8)(LLViewerObject::PHYSICS_SHAPE_NONE);
+			instance_entry["mesh"] = mesh_index[data.mBaseModel];
+
+			instance_entry["face_list"] = LLSD::emptyArray();
+
+			// We want to be able to allow more than 8 materials...
+			//
+			S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ;
+
+			for (S32 face_num = 0; face_num < end; face_num++)
+			{
+				LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]];
+				LLSD face_entry = LLSD::emptyMap();
+
+				LLViewerFetchedTexture *texture = NULL;
+
+				if (material.mDiffuseMapFilename.size())
+				{
+					texture = FindViewerTexture(material);
+				}
+
+				if ((texture != NULL) &&
+					(textures.find(texture) == textures.end()))
+				{
+					textures.insert(texture);
+				}
+
+				std::stringstream texture_str;
+				if (texture != NULL && include_textures && mUploadTextures)
+				{
+					if(texture->hasSavedRawImage())
+					{											
+						LLPointer<LLImageJ2C> upload_file =
+							LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage());
+
+						if (!upload_file.isNull() && upload_file->getDataSize())
+						{
 						texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize());
 					}
 				}
+				}
 
 				if (texture != NULL &&
 					mUploadTextures &&
@@ -2235,7 +2423,7 @@ void LLMeshUploadThread::generateHulls()
 		}
 	}
 		
-	if(has_valid_requests)
+	if (has_valid_requests)
 	{
 		// *NOTE:  Interesting livelock condition on shutdown.  If there
 		// is an upload request in generateHulls() when shutdown starts,
@@ -2340,7 +2528,7 @@ void LLMeshUploadThread::requestWholeModelFee()
 	else
 	{
 		U32 sleep_time(10);
-
+		
 		mHttpRequest->update(0);
 		while (! LLApp::isQuitting() && ! finished() && ! isDiscarded())
 		{
@@ -2435,7 +2623,7 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp
 		// model fee case
 		LLWholeModelFeeObserver* observer(mFeeObserverHandle.get());
 		mWholeModelUploadURL.clear();
-
+		
 		if (! status)
 		{
 			LL_WARNS(LOG_MESH) << "Fee request failed.  Reason:  " << reason
@@ -2465,7 +2653,7 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp
 				LLCoreHttpUtil::responseToLLSD(response, true, body);
 			}
 			dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num));
-
+		
 			if (body["state"].asString() == "upload")
 			{
 				mWholeModelUploadURL = body["uploader"].asString();
@@ -2536,7 +2724,7 @@ void LLMeshRepoThread::notifyLoadedMeshes()
 		LODRequest req = mUnavailableQ.front();
 		mUnavailableQ.pop();
 		mMutex->unlock();
-		
+
 		update_metrics = true;
 		gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD);
 	}
@@ -2672,7 +2860,7 @@ void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header)
 void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response)
 {
 	mProcessed = true;
-	
+
 	unsigned int retries(0U);
 	response->getRetries(NULL, &retries);
 	LLMeshRepository::sHTTPRetryCount += retries;
@@ -2728,7 +2916,7 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo
 				// 200 case, typically
 				offset = 0;
 			}
-
+		
 			// *DEBUG:  To test validation below
 			// offset += 1;
 
@@ -2844,7 +3032,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 				const std::string & lod_name = header_lod[i];
 				lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger());
 			}
-
+		
 			// just in case skin info or decomposition is at the end of the file (which it shouldn't be)
 			lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger());
 			lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger());
@@ -2852,7 +3040,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 			S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id];
 			S32 bytes = lod_bytes + header_bytes; 
 
-
+		
 			// It's possible for the remote asset to have more data than is needed for the local cache
 			// only allocate as much space in the VFS as is needed for the local cache
 			data_size = llmin(data_size, bytes);
@@ -2864,11 +3052,11 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 				++LLMeshRepository::sCacheWrites;
 
 				file.write(data, data_size);
-
+			
 				// zero out the rest of the file 
 				U8 block[MESH_HEADER_SIZE];
 				memset(block, 0, sizeof(block));
-	
+
 				while (bytes-file.tell() > sizeof(block))
 				{
 					file.write(block, sizeof(block));
@@ -2894,8 +3082,8 @@ LLMeshLODHandler::~LLMeshLODHandler()
 			gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD);
 		}
 		LLMeshRepoThread::decActiveLODRequests();
-		}
 	}
+}
 
 void LLMeshLODHandler::processFailure(LLCore::HttpStatus status)
 {
@@ -2913,7 +3101,7 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body
 {
 	if ((! MESH_LOD_PROCESS_FAILED) && gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size))
 	{
-		//good fetch from sim, write to VFS for caching
+		// good fetch from sim, write to VFS for caching
 		LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE);
 
 		S32 offset = mOffset;
@@ -2958,7 +3146,7 @@ void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /*
 {
 	if ((! MESH_SKIN_INFO_PROCESS_FAILED) && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size))
 	{
-		//good fetch from sim, write to VFS for caching
+		// good fetch from sim, write to VFS for caching
 		LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE);
 
 		S32 offset = mOffset;
@@ -3282,7 +3470,7 @@ void LLMeshRepository::notifyLoadedMeshes()
 													 REQUEST2_LOW_WATER_MIN,
 													 REQUEST2_LOW_WATER_MAX);
 	}
-
+	
 	//clean up completed upload threads
 	for (std::vector<LLMeshUploadThread*>::iterator iter = mUploads.begin(); iter != mUploads.end(); )
 	{
@@ -3359,7 +3547,7 @@ void LLMeshRepository::notifyLoadedMeshes()
 
 	//call completed callbacks on finished decompositions
 	mDecompThread->notifyCompleted();
-	
+
 	// For major operations, attempt to get the required locks
 	// without blocking and punt if they're not available.  The
 	// longest run of holdoffs is kept in sMaxLockHoldoffs just
@@ -3378,12 +3566,12 @@ void LLMeshRepository::notifyLoadedMeshes()
 			return;
 		}
 		hold_offs = 0;
-
+		
 		if (gAgent.getRegion())
 		{
 			// Update capability urls
 			static std::string region_name("never name a region this");
-
+			
 			if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived())
 			{
 				region_name = gAgent.getRegion()->getName();
@@ -3399,7 +3587,7 @@ void LLMeshRepository::notifyLoadedMeshes()
 									<< LL_ENDL;
 			}
 		}
-
+		
 		//popup queued error messages from background threads
 		while (!mUploadErrorQ.empty())
 		{
@@ -3754,7 +3942,7 @@ LLSD& LLMeshRepoThread::getMeshHeader(const LLUUID& mesh_id)
 
 
 void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3& scale, bool upload_textures,
-									bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
+								   bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload,
 								   LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer)
 {
 	LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, upload_skin, upload_joints, upload_url, 
@@ -3825,37 +4013,6 @@ void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation,
 	result_rot = quat_rotation; 
 }
 
-bool LLImportMaterial::operator<(const LLImportMaterial &rhs) const
-{
-	if (mDiffuseMap != rhs.mDiffuseMap)
-	{
-		return mDiffuseMap < rhs.mDiffuseMap;
-	}
-
-	if (mDiffuseMapFilename != rhs.mDiffuseMapFilename)
-	{
-		return mDiffuseMapFilename < rhs.mDiffuseMapFilename;
-	}
-
-	if (mDiffuseMapLabel != rhs.mDiffuseMapLabel)
-	{
-		return mDiffuseMapLabel < rhs.mDiffuseMapLabel;
-	}
-
-	if (mDiffuseColor != rhs.mDiffuseColor)
-	{
-		return mDiffuseColor < rhs.mDiffuseColor;
-	}
-
-	if (mBinding != rhs.mBinding)
-	{
-		return mBinding < rhs.mBinding;
-	}
-
-	return mFullbright < rhs.mFullbright;
-}
-
-
 void LLMeshRepository::updateInventory(inventory_data data)
 {
 	LLMutexLock lock(mMeshMutex);
@@ -4253,7 +4410,7 @@ void LLPhysicsDecomp::doDecompositionSingleHull()
 	setMeshData(mesh, true);
 
 	LLCDResult ret = decomp->buildSingleHull() ;
-	if(ret)
+	if (ret)
 	{
 		LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL;
 		make_box(mCurRequest);
@@ -4441,60 +4598,6 @@ void LLPhysicsDecomp::Request::setStatusMessage(const std::string& msg)
 	mStatusMessage = msg;
 }
 
-LLModelInstance::LLModelInstance(LLSD& data)
-{
-	mLocalMeshID = data["mesh_id"].asInteger();
-	mLabel = data["label"].asString();
-	mTransform.setValue(data["transform"]);
-
-	for (U32 i = 0; i < data["material"].size(); ++i)
-	{
-		LLImportMaterial mat(data["material"][i]);
-		mMaterial[mat.mBinding] = mat;
-	}
-}
-
-
-LLSD LLModelInstance::asLLSD()
-{	
-	LLSD ret;
-
-	ret["mesh_id"] = mModel->mLocalID;
-	ret["label"] = mLabel;
-	ret["transform"] = mTransform.getValue();
-	
-	U32 i = 0;
-	for (std::map<std::string, LLImportMaterial>::iterator iter = mMaterial.begin(); iter != mMaterial.end(); ++iter)
-	{
-		ret["material"][i++] = iter->second.asLLSD();
-	}
-
-	return ret;
-}
-
-LLImportMaterial::LLImportMaterial(LLSD& data)
-{
-	mDiffuseMapFilename = data["diffuse"]["filename"].asString();
-	mDiffuseMapLabel = data["diffuse"]["label"].asString();
-	mDiffuseColor.setValue(data["diffuse"]["color"]);
-	mFullbright = data["fullbright"].asBoolean();
-	mBinding = data["binding"].asString();
-}
-
-
-LLSD LLImportMaterial::asLLSD()
-{
-	LLSD ret;
-
-	ret["diffuse"]["filename"] = mDiffuseMapFilename;
-	ret["diffuse"]["label"] = mDiffuseMapLabel;
-	ret["diffuse"]["color"] = mDiffuseColor.getValue();
-	ret["fullbright"] = mFullbright;
-	ret["binding"] = mBinding;
-
-	return ret;
-}
-
 void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp)
 {
 	decomp.mMesh.resize(decomp.mHull.size());
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 39280bea3a5bd4665461af7169c0e1af0e501fb7..688cd01a87c7626dfe8390a86c0705d93a817777 100755
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -90,54 +90,6 @@ class LLTextureUploadData
 	}
 };
 
-class LLImportMaterial
-{
-public:
-	LLPointer<LLViewerFetchedTexture> mDiffuseMap;
-	std::string mDiffuseMapFilename;
-	std::string mDiffuseMapLabel;
-	std::string mBinding;
-	LLColor4 mDiffuseColor;
-	bool mFullbright;
-
-	bool operator<(const LLImportMaterial &params) const;
-
-	LLImportMaterial() 
-		: mFullbright(false) 
-	{ 
-		mDiffuseColor.set(1,1,1,1);
-	}
-
-	LLImportMaterial(LLSD& data);
-
-	LLSD asLLSD();
-};
-
-class LLModelInstance 
-{
-public:
-	LLPointer<LLModel> mModel;
-	LLPointer<LLModel> mLOD[5];
-	
-	std::string mLabel;
-
-	LLUUID mMeshID;
-	S32 mLocalMeshID;
-
-	LLMatrix4 mTransform;
-	std::map<std::string, LLImportMaterial> mMaterial;
-
-	LLModelInstance(LLModel* model, const std::string& label, LLMatrix4& transform, std::map<std::string, LLImportMaterial>& materials)
-		: mModel(model), mLabel(label), mTransform(transform), mMaterial(materials)
-	{
-		mLocalMeshID = -1;
-	}
-
-	LLModelInstance(LLSD& data);
-
-	LLSD asLLSD();
-};
-
 class LLPhysicsDecomp : public LLThread
 {
 public:
@@ -483,6 +435,8 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler
 	// Inherited from LLCore::HttpHandler
 	virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
 
+        LLViewerFetchedTexture* FindViewerTexture(const LLImportMaterial& material);
+
 private:
 	LLHandle<LLWholeModelFeeObserver> mFeeObserverHandle;
 	LLHandle<LLWholeModelUploadObserver> mUploadObserverHandle;
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 4b0e4514a0909b54194dc1990fd4fc4764298576..68713280c0c8484f9d72b3cce3ef85516a342d3f 100755
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -4184,6 +4184,10 @@ void LLRiggedVolume::update(const LLMeshSkinInfo* skin, LLVOAvatar* avatar, cons
 	for (U32 j = 0; j < maxJoints; ++j)
 	{
 		LLJoint* joint = avatar->getJoint(skin->mJointNames[j]);
+        if (!joint)
+        {
+            joint = avatar->getJoint("mPelvis");
+        }
 		if (joint)
 		{
 			mat[j] = skin->mInvBindMatrix[j];
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 a4acd1df78448e3893d0d822a0d27577e9171881..7183b2f1f9d5d0f6081390fca477258f1cf59fcf 100755
--- a/indra/newview/skins/default/xui/en/floater_model_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml
@@ -31,6 +31,7 @@
   <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="mesh_status_invalid_material_list">LOD materials are not a subset of reference model.</string>
+  <string name="phys_status_vertex_limit_exceeded">Some physical hulls exceed vertex limitations.</string>
   <string name="layer_all">All</string> <!-- Text to display in physics layer combo box for "all layers" -->
   <string name="decomposing">Analyzing...</string>
   <string name="simplifying">Simplifying...</string>
@@ -66,7 +67,7 @@
           follows="top|left"
           layout="topleft"
           height="19"
-          max_length_bytes="64"
+          max_length_bytes="63"
           name="description_form"
           prevalidate_callback="ascii"
           top_pad="5"
@@ -1027,19 +1028,19 @@
                   bg_alpha_color="0 0 0 0"
                   bg_opaque_color="0 0 0 0.3"
                   follows="left|top"
-                  height="16"
+                  height="19"
                   layout="topleft"
                   left="18"
                   name="physics info"
-                  top_pad="15"
-                  width="589">
+                  top_pad="12"
+                  width="319">
                     <text
                       follows="top|left"
                       height="15"
                       layout="topleft"
                       left="0"
                       text_color="White"
-                      top_pad="0"
+                      top_pad="3"
                       name="results_text"
                       width="50">
                       Results:
@@ -1077,6 +1078,33 @@
                       Hulls: [HULLS]
                     </text>
                 </panel>
+                <panel
+                 bg_alpha_color="0 0 0 0"
+                 bg_opaque_color="0 0 0 0.3"
+                 follows="left|top"
+                 height="19"
+                 layout="topleft"
+                 left_pad="5"
+                 top_delta="0"
+                 name="physics message"
+                 width="270">
+                     <icon
+                      follows="left|top"
+                      height="16"
+                      left="0"
+                      layout="topleft"
+                      name="physics_status_message_icon"
+                      top_pad="0"
+                      width="16" />
+                     <text
+                      follows="left|top"
+                      height="15"
+                      layout="topleft"
+                      left_pad="2"
+                      name="physics_status_message_text"
+                      width="252"
+                      top_delta="3"/>
+                </panel>
         </panel>
       <!-- MODIFIERS PANEL -->
      <panel