Commits

Anonymous committed 481ca18

svn merge -r134817:136516 svn+ssh://svn.lindenlab.com/svn/linden/branches/avatar-pipeline/avatar-pipeline-multiattachments-3 into svn+ssh://svn.lindenlab.com/svn/linden/branches/viewer/viewer-2.0.0-3

For DEV-32867 : Avatar Pipeline Project - Multi-Attachments

This is a viewer-side-only, infrastructure-only merge for multi-attachments. This should not lead to any new functionality; it simply provides backwards compatibility for when multi-attachments are enabled in viewer2.1, so that viewer2.0 users can see viewer2.1 avatars correctly.

Reviewed by: Vir.

Comments (0)

Files changed (20)

indra/llcommon/indra_constants.h

 // group constants
 const S32 MAX_AGENT_GROUPS = 25;
 
+// attachment constants
+const S32 MAX_AGENT_ATTACHMENTS = 38;
+const U8  ATTACHMENT_ADD = 0x80;
+
 // god levels
 const U8 GOD_MAINTENANCE = 250;
 const U8 GOD_FULL = 200;

indra/llprimitive/llprimitive.h

 	bool fromLLSD(LLSD& sd);
 
 	void setSculptTexture(const LLUUID& id) { mSculptTexture = id; }
-	LLUUID getSculptTexture()               { return mSculptTexture; }
+	LLUUID getSculptTexture() const         { return mSculptTexture; }
 	void setSculptType(U8 type)             { mSculptType = type; }
-	U8 getSculptType()                      { return mSculptType; }
+	U8 getSculptType() const                { return mSculptType; }
 };
 
 class LLLightImageParams : public LLNetworkData

indra/newview/llagent.cpp

 		{
 			LLVOAvatar::attachment_map_t::iterator curiter = iter++;
 			LLViewerJointAttachment* attachment = curiter->second;
-			LLViewerObject *attached_object = attachment->getObject();
-			if (attached_object && !attached_object->isDead() && attached_object->mDrawable.notNull())
+			for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+				 attachment_iter != attachment->mAttachedObjects.end();
+				 ++attachment_iter)
 			{
-				// clear any existing "early" movements of attachment
-				attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE);
-				gPipeline.updateMoveNormalAsync(attached_object->mDrawable);
-				attached_object->updateText();
+				LLViewerObject *attached_object = (*attachment_iter);
+				if (attached_object && !attached_object->isDead() && attached_object->mDrawable.notNull())
+				{
+					// clear any existing "early" movements of attachment
+					attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE);
+					gPipeline.updateMoveNormalAsync(attached_object->mDrawable);
+					attached_object->updateText();
+				}
 			}
 		}
 

indra/newview/llagentwearables.cpp

 			S32 attachment_pt = attachments_to_include[i];
 			LLViewerJointAttachment* attachment = get_if_there(mAvatarObject->mAttachmentPoints, attachment_pt, (LLViewerJointAttachment*)NULL);
 			if (!attachment) continue;
-			LLViewerObject* attached_object = attachment->getObject();
-			if (!attached_object) continue;
-			const LLUUID& item_id = attachment->getItemID();
-			if (item_id.isNull()) continue;
-			LLInventoryItem* item = gInventory.getItem(item_id);
-			if (!item) continue;
-			if (!msg_started)
+			for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+				 attachment_iter != attachment->mAttachedObjects.end();
+				 ++attachment_iter)
 			{
-				msg_started = TRUE;
-				msg->newMessage("CreateNewOutfitAttachments");
-				msg->nextBlock("AgentData");
-				msg->addUUID("AgentID", gAgent.getID());
-				msg->addUUID("SessionID", gAgent.getSessionID());
-				msg->nextBlock("HeaderData");
-				msg->addUUID("NewFolderID", folder_id);
+				LLViewerObject *attached_object = (*attachment_iter);
+				if(!attached_object) continue;
+				const LLUUID& item_id = (*attachment_iter)->getItemID();
+				if(item_id.isNull()) continue;
+				LLInventoryItem* item = gInventory.getItem(item_id);
+				if(!item) continue;
+				if(!msg_started)
+				{
+					msg_started = TRUE;
+					msg->newMessage("CreateNewOutfitAttachments");
+					msg->nextBlock("AgentData");
+					msg->addUUID("AgentID", gAgent.getID());
+					msg->addUUID("SessionID", gAgent.getSessionID());
+					msg->nextBlock("HeaderData");
+					msg->addUUID("NewFolderID", folder_id);
+				}
+				msg->nextBlock("ObjectData");
+				msg->addUUID("OldItemID", item_id);
+				msg->addUUID("OldFolderID", item->getParentUUID());
 			}
-			msg->nextBlock("ObjectData");
-			msg->addUUID("OldItemID", item_id);
-			msg->addUUID("OldFolderID", item->getParentUUID());
 		}
 
 		if (msg_started)
 	{
 		LLVOAvatar::attachment_map_t::iterator curiter = iter++;
 		LLViewerJointAttachment* attachment = curiter->second;
-		LLViewerObject* objectp = attachment->getObject();
-		if (objectp)
+		for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+			 attachment_iter != attachment->mAttachedObjects.end();
+			 ++attachment_iter)
 		{
-			LLUUID object_item_id = attachment->getItemID();
-			if (requested_item_ids.find(object_item_id) != requested_item_ids.end())
+			LLViewerObject *objectp = (*attachment_iter);
+			if (objectp)
 			{
-				// Object currently worn, was requested.
-				// Flag as currently worn so we won't have to add it again.
-				current_item_ids.insert(object_item_id);
-			}
-			else
-			{
-				// object currently worn, not requested.
-				objects_to_remove.push_back(objectp);
+				LLUUID object_item_id = objectp->getItemID();
+				if (requested_item_ids.find(object_item_id) != requested_item_ids.end())
+				{
+					// Object currently worn, was requested.
+					// Flag as currently worn so we won't have to add it again.
+					current_item_ids.insert(object_item_id);
+				}
+				else
+				{
+					// object currently worn, not requested.
+					objects_to_remove.push_back(objectp);
+				}
 			}
 		}
 	}
 	{
 		LLVOAvatar::attachment_map_t::iterator curiter = iter++;
 		LLViewerJointAttachment* attachment = curiter->second;
-		LLViewerObject* objectp = attachment->getObject();
-		if (objectp)
-			objects_to_remove.push_back(objectp);
+		for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+			 attachment_iter != attachment->mAttachedObjects.end();
+			 ++attachment_iter)
+		{
+			LLViewerObject *attached_object = (*attachment_iter);
+			if (attached_object)
+			{
+				objects_to_remove.push_back(attached_object);
+			}
+		}
 	}
 	userRemoveMultipleAttachments(objects_to_remove);
 }

indra/newview/llfloaterinventory.cpp

 {
 	std::set<LLUUID> selected_items;
 	mFolders->getSelectionList(selected_items);
-	LLUUID id = *selected_items.begin();
 
 	std::string joint_name = userdata.asString();
 	LLVOAvatar *avatarp = static_cast<LLVOAvatar*>(gAgent.getAvatarObject());
 	{
 		return true;
 	}
-	LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(id);
 
-	if(item && gInventory.isObjectDescendentOf(id, gInventory.getRootFolderID()))
+	for (std::set<LLUUID>::const_iterator set_iter = selected_items.begin(); 
+		 set_iter != selected_items.end(); 
+		 ++set_iter)
 	{
-		rez_attachment(item, attachmentp);
-	}
-	else if(item && item->isComplete())
-	{
-		// must be in library. copy it to our inventory and put it on.
-		LLPointer<LLInventoryCallback> cb = new RezAttachmentCallback(attachmentp);
-		copy_inventory_item(gAgent.getID(),
-							item->getPermissions().getOwner(),
-							item->getUUID(),
-							LLUUID::null,
-							std::string(),
-							cb);
+		const LLUUID &id = *set_iter;
+		LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(id);
+		if(item && gInventory.isObjectDescendentOf(id, gInventory.getRootFolderID()))
+		{
+			rez_attachment(item, attachmentp);
+		}
+		else if(item && item->isComplete())
+		{
+			// must be in library. copy it to our inventory and put it on.
+			LLPointer<LLInventoryCallback> cb = new RezAttachmentCallback(attachmentp);
+			copy_inventory_item(gAgent.getID(),
+								item->getPermissions().getOwner(),
+								item->getUUID(),
+								LLUUID::null,
+								std::string(),
+								cb);
+		}
 	}
 	gFocusMgr.setKeyboardFocus(NULL);
 

indra/newview/llinventorybridge.cpp

 
 	payload["attachment_point"] = attach_pt;
 
-	if (attachment && attachment->getObject())
+#if !ENABLE_MULTIATTACHMENTS
+	if (attachment && attachment->getNumObjects() > 0)
 	{
 		LLNotifications::instance().add("ReplaceAttachment", LLSD(), payload, confirm_replace_attachment_rez);
 	}
 	else
+#endif
 	{
 		LLNotifications::instance().forceResponse(LLNotification::Params("ReplaceAttachment").payload(payload), 0/*YES*/);
 	}
 
 bool confirm_replace_attachment_rez(const LLSD& notification, const LLSD& response)
 {
+	LLVOAvatar *avatarp = gAgent.getAvatarObject();
+		
+	if (!avatarp->canAttachMoreObjects())
+	{
+		LLSD args;
+		args["MAX_ATTACHMENTS"] = llformat("%d", MAX_AGENT_ATTACHMENTS);
+		LLNotifications::instance().add("MaxAttachmentsOnOutfit", args);
+		return false;
+	}
+
 	S32 option = LLNotification::getSelectedOption(notification, response);
 	if (option == 0/*YES*/)
 	{
 			msg->nextBlockFast(_PREHASH_ObjectData);
 			msg->addUUIDFast(_PREHASH_ItemID, itemp->getUUID());
 			msg->addUUIDFast(_PREHASH_OwnerID, itemp->getPermissions().getOwner());
-			msg->addU8Fast(_PREHASH_AttachmentPt, notification["payload"]["attachment_point"].asInteger());
+			U8 attachment_pt = notification["payload"]["attachment_point"].asInteger();
+#if ENABLE_MULTIATTACHMENTS
+			attachment_pt |= ATTACHMENT_ADD;
+#endif
+			msg->addU8Fast(_PREHASH_AttachmentPt, attachment_pt);
 			pack_permissions_slam(msg, itemp->getFlags(), itemp->getPermissions());
 			msg->addStringFast(_PREHASH_Name, itemp->getName());
 			msg->addStringFast(_PREHASH_Description, itemp->getDescription());
 				// commented out for DEV-32347
 				//items.push_back(std::string("Restore to Last Position"));
 
+				if (!avatarp->canAttachMoreObjects())
+				{
+					disabled_items.push_back(std::string("Object Wear"));
+					disabled_items.push_back(std::string("Attach To"));
+					disabled_items.push_back(std::string("Attach To HUD"));
+				}
 				LLMenuGL* attach_menu = menu.findChildMenuByName("Attach To", TRUE);
 				LLMenuGL* attach_hud_menu = menu.findChildMenuByName("Attach To HUD", TRUE);
 				LLVOAvatar *avatarp = gAgent.getAvatarObject();

indra/newview/llselectmgr.cpp

 		return;
 	}
 
+#if ENABLE_MULTIATTACHMENTS
+	attachment_point |= ATTACHMENT_ADD;
+#endif
 	BOOL build_mode = LLToolMgr::getInstance()->inEdit();
 	// Special case: Attach to default location for this object.
 	if (0 == attachment_point ||

indra/newview/llselectmgr.h

 	static void packDeRezHeader(void* user_data);
 	static void packObjectID(	LLSelectNode* node, void *);
 	static void packObjectIDAsParam(LLSelectNode* node, void *);
-	static void packObjectIDAndRotation(	LLSelectNode* node, void *);
+	static void packObjectIDAndRotation(LLSelectNode* node, void *);
 	static void packObjectLocalID(LLSelectNode* node, void *);
 	static void packObjectClickAction(LLSelectNode* node, void* data);
 	static void packObjectIncludeInSearch(LLSelectNode* node, void* data);

indra/newview/llviewerjointattachment.cpp

 // LLViewerJointAttachment()
 //-----------------------------------------------------------------------------
 LLViewerJointAttachment::LLViewerJointAttachment() :
-mAttachedObject(NULL),
-mVisibleInFirst(FALSE),
-mGroup(0),
-mIsHUDAttachment(FALSE),
-mPieSlice(-1)
+	mVisibleInFirst(FALSE),
+	mGroup(0),
+	mIsHUDAttachment(FALSE),
+	mPieSlice(-1)
 {
 	mValid = FALSE;
 	mUpdateXform = FALSE;
+	mAttachedObjects.clear();
 }
 
 //-----------------------------------------------------------------------------
 	return 0;
 }
 
-void LLViewerJointAttachment::setupDrawable(LLDrawable* drawablep)
+void LLViewerJointAttachment::setupDrawable(LLViewerObject *object)
 {
-	drawablep->mXform.setParent(&mXform); // LLViewerJointAttachment::lazyAttach
-	drawablep->makeActive();
-	LLVector3 current_pos = mAttachedObject->getRenderPosition();
-	LLQuaternion current_rot = mAttachedObject->getRenderRotation();
-	LLQuaternion attachment_pt_inv_rot = ~getWorldRotation();
+	if (!object->mDrawable)
+		return;
+	if (object->mDrawable->isActive())
+	{
+		object->mDrawable->makeStatic(FALSE);
+	}
+
+	object->mDrawable->mXform.setParent(getXform()); // LLViewerJointAttachment::lazyAttach
+	object->mDrawable->makeActive();
+	LLVector3 current_pos = object->getRenderPosition();
+	LLQuaternion current_rot = object->getRenderRotation();
+	LLQuaternion attachment_pt_inv_rot = ~(getWorldRotation());
 
 	current_pos -= getWorldPosition();
 	current_pos.rotVec(attachment_pt_inv_rot);
 
 	current_rot = current_rot * attachment_pt_inv_rot;
 
-	drawablep->mXform.setPosition(current_pos);
-	drawablep->mXform.setRotation(current_rot);
-	gPipeline.markMoved(drawablep);
-	gPipeline.markTextured(drawablep); // face may need to change draw pool to/from POOL_HUD
-	drawablep->setState(LLDrawable::USE_BACKLIGHT);
+	object->mDrawable->mXform.setPosition(current_pos);
+	object->mDrawable->mXform.setRotation(current_rot);
+	gPipeline.markMoved(object->mDrawable);
+	gPipeline.markTextured(object->mDrawable); // face may need to change draw pool to/from POOL_HUD
+	object->mDrawable->setState(LLDrawable::USE_BACKLIGHT);
 	
 	if(mIsHUDAttachment)
 	{
-		for (S32 face_num = 0; face_num < drawablep->getNumFaces(); face_num++)
+		for (S32 face_num = 0; face_num < object->mDrawable->getNumFaces(); face_num++)
 		{
-			drawablep->getFace(face_num)->setState(LLFace::HUD_RENDER);
+			object->mDrawable->getFace(face_num)->setState(LLFace::HUD_RENDER);
 		}
 	}
 
-	LLViewerObject::const_child_list_t& child_list = mAttachedObject->getChildren();
+	LLViewerObject::const_child_list_t& child_list = object->getChildren();
 	for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-		 iter != child_list.end(); iter++)
+		 iter != child_list.end(); ++iter)
 	{
 		LLViewerObject* childp = *iter;
 		if (childp && childp->mDrawable.notNull())
 //-----------------------------------------------------------------------------
 BOOL LLViewerJointAttachment::addObject(LLViewerObject* object)
 {
-	if (mAttachedObject)
+	if (isObjectAttached(object))
 	{
-		llwarns << "Attempted to attach object where an attachment already exists!" << llendl;
-		
-		if (mAttachedObject == object) {
-			llinfos << "(same object re-attached)" << llendl;
-			removeObject(mAttachedObject);
-			// Pass through anyway to let setupDrawable()
-			// re-connect object to the joint correctly
-		}
-		else {
-			llinfos << "(objects differ, removing existing object)" << llendl;
-			// Rather hacky, but no-one can think of something
-			// better to do for this case.
-			gObjectList.killObject(mAttachedObject);
-			// Proceed with new object attachment
-		}
+		llinfos << "(same object re-attached)" << llendl;
+		removeObject(object);
+		// Pass through anyway to let setupDrawable()
+		// re-connect object to the joint correctly
 	}
-	mAttachedObject = object;
-	
-	LLUUID item_id;
 
 	// Find the inventory item ID of the attached object
 	LLNameValue* item_id_nv = object->getNVPair("AttachItemID");
 		const char* s = item_id_nv->getString();
 		if( s )
 		{
-			item_id.set( s );
+			LLUUID item_id;
+			item_id.set(s);
+			object->setItemID(item_id);
 			lldebugs << "getNVPair( AttachItemID ) = " << item_id << llendl;
 		}
 	}
-
-	mItemID = item_id;
-
-	LLDrawable* drawablep = object->mDrawable;
-
-	if (drawablep)
-	{
-		//if object is active, make it static
-		if(drawablep->isActive())
-		{
-			drawablep->makeStatic(FALSE) ;
-		}
-
-		setupDrawable(drawablep);
-	}
-
+	mAttachedObjects.push_back(object);
+	setupDrawable(object);
+	
 	if (mIsHUDAttachment)
 	{
 		if (object->mText.notNull())
 		}
 		LLViewerObject::const_child_list_t& child_list = object->getChildren();
 		for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-			 iter != child_list.end(); iter++)
+			 iter != child_list.end(); ++iter)
 		{
 			LLViewerObject* childp = *iter;
 			if (childp && childp->mText.notNull())
 //-----------------------------------------------------------------------------
 void LLViewerJointAttachment::removeObject(LLViewerObject *object)
 {
+	attachedobjs_vec_t::iterator iter;
+	for (iter = mAttachedObjects.begin();
+		 iter != mAttachedObjects.end();
+		 ++iter)
+	{
+		LLViewerObject *attached_object = (*iter);
+		if (attached_object == object)
+		{
+			break;
+		}
+	}
+	if (iter == mAttachedObjects.end())
+	{
+		llwarns << "Could not find object to detach" << llendl;
+		return;
+	}
+
 	// force object visibile
 	setAttachmentVisibility(TRUE);
 
+	mAttachedObjects.erase(iter);
 	if (object->mDrawable.notNull())
 	{
 		//if object is active, make it static
 		if(object->mDrawable->isActive())
 		{
-			object->mDrawable->makeStatic(FALSE) ;
+			object->mDrawable->makeStatic(FALSE);
 		}
 
 		LLVector3 cur_position = object->getRenderPosition();
 
 	LLViewerObject::const_child_list_t& child_list = object->getChildren();
 	for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-		 iter != child_list.end(); iter++)
+		 iter != child_list.end(); ++iter)
 	{
 		LLViewerObject* childp = *iter;
 		if (childp && childp->mDrawable.notNull())
 		}
 		LLViewerObject::const_child_list_t& child_list = object->getChildren();
 		for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-			 iter != child_list.end(); iter++)
+			 iter != child_list.end(); ++iter)
 		{
 			LLViewerObject* childp = *iter;
 			if (childp->mText.notNull())
 			}
 		}
 	}
-
-	mAttachedObject = NULL;
-	mUpdateXform = FALSE;
-	mItemID.setNull();
+	if (mAttachedObjects.size() == 0)
+	{
+		mUpdateXform = FALSE;
+	}
+	object->setItemID(LLUUID::null);
 }
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 void LLViewerJointAttachment::setAttachmentVisibility(BOOL visible)
 {
-	if (!mAttachedObject || mAttachedObject->mDrawable.isNull() || 
-		!(mAttachedObject->mDrawable->getSpatialBridge()))
-		return;
-
-	if (visible)
+	for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
+		 iter != mAttachedObjects.end();
+		 ++iter)
 	{
-		// Hack to make attachments not visible by disabling their type mask!
-		// This will break if you can ever attach non-volumes! - djs 02/14/03
-		mAttachedObject->mDrawable->getSpatialBridge()->mDrawableType = 
-			mAttachedObject->isHUDAttachment() ? LLPipeline::RENDER_TYPE_HUD : LLPipeline::RENDER_TYPE_VOLUME;
-	}
-	else
-	{
-		mAttachedObject->mDrawable->getSpatialBridge()->mDrawableType = 0;
+		LLViewerObject *attached_obj = (*iter);
+		if (!attached_obj || attached_obj->mDrawable.isNull() || 
+			!(attached_obj->mDrawable->getSpatialBridge()))
+			continue;
+		
+		if (visible)
+		{
+			// Hack to make attachments not visible by disabling their type mask!
+			// This will break if you can ever attach non-volumes! - djs 02/14/03
+			attached_obj->mDrawable->getSpatialBridge()->mDrawableType = 
+				attached_obj->isHUDAttachment() ? LLPipeline::RENDER_TYPE_HUD : LLPipeline::RENDER_TYPE_VOLUME;
+		}
+		else
+		{
+			attached_obj->mDrawable->getSpatialBridge()->mDrawableType = 0;
+		}
 	}
 }
 
 //-----------------------------------------------------------------------------
 void LLViewerJointAttachment::clampObjectPosition()
 {
-	if (mAttachedObject)
+	for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
+		 iter != mAttachedObjects.end();
+		 ++iter)
 	{
-		// *NOTE: object can drift when hitting maximum radius
-		LLVector3 attachmentPos = mAttachedObject->getPosition();
-		F32 dist = attachmentPos.normVec();
-		dist = llmin(dist, MAX_ATTACHMENT_DIST);
-		attachmentPos *= dist;
-		mAttachedObject->setPosition(attachmentPos);
+		if (LLViewerObject *attached_object = (*iter))
+		{
+			// *NOTE: object can drift when hitting maximum radius
+			LLVector3 attachmentPos = attached_object->getPosition();
+			F32 dist = attachmentPos.normVec();
+			dist = llmin(dist, MAX_ATTACHMENT_DIST);
+			attachmentPos *= dist;
+			attached_object->setPosition(attachmentPos);
+		}
 	}
 }
 
 //-----------------------------------------------------------------------------
 void LLViewerJointAttachment::calcLOD()
 {
-	F32 maxarea = mAttachedObject->getMaxScale() * mAttachedObject->getMidScale();
-	LLViewerObject::const_child_list_t& child_list = mAttachedObject->getChildren();
-	for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-		 iter != child_list.end(); iter++)
+	F32 maxarea = 0;
+	for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
+		 iter != mAttachedObjects.end();
+		 ++iter)
 	{
-		LLViewerObject* childp = *iter;
-		F32 area = childp->getMaxScale() * childp->getMidScale();
-		maxarea = llmax(maxarea, area);
+		if (LLViewerObject *attached_object = (*iter))
+		{
+			maxarea = llmax(maxarea,attached_object->getMaxScale() * attached_object->getMidScale());
+			LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
+			for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
+				 iter != child_list.end(); ++iter)
+			{
+				LLViewerObject* childp = *iter;
+				F32 area = childp->getMaxScale() * childp->getMidScale();
+				maxarea = llmax(maxarea, area);
+			}
+		}
 	}
 	maxarea = llclamp(maxarea, .01f*.01f, 1.f);
 	F32 avatar_area = (4.f * 4.f); // pixels for an avatar sized attachment
 	return res;
 }
 
+BOOL LLViewerJointAttachment::isObjectAttached(const LLViewerObject *viewer_object) const
+{
+	for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
+		 iter != mAttachedObjects.end();
+		 ++iter)
+	{
+		const LLViewerObject* attached_object = (*iter);
+		if (attached_object == viewer_object)
+		{
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+const LLViewerObject *LLViewerJointAttachment::getAttachedObject(const LLUUID &object_id) const
+{
+	for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin();
+		 iter != mAttachedObjects.end();
+		 ++iter)
+	{
+		const LLViewerObject* attached_object = (*iter);
+		if (attached_object->getItemID() == object_id)
+		{
+			return attached_object;
+		}
+	}
+	return NULL;
+}
+
+LLViewerObject *LLViewerJointAttachment::getAttachedObject(const LLUUID &object_id)
+{
+	for (attachedobjs_vec_t::iterator iter = mAttachedObjects.begin();
+		 iter != mAttachedObjects.end();
+		 ++iter)
+	{
+		LLViewerObject* attached_object = (*iter);
+		if (attached_object->getItemID() == object_id)
+		{
+			return attached_object;
+		}
+	}
+	return NULL;
+}

indra/newview/llviewerjointattachment.h

 
 	S32 getGroup() const { return mGroup; }
 	S32 getPieSlice() const { return mPieSlice; }
-	LLViewerObject *getObject() const { return mAttachedObject; }
-	S32	getNumObjects() const { return (mAttachedObject ? 1 : 0); }
-	const LLUUID& getItemID() const { return mItemID; }
+	S32	getNumObjects() const { return mAttachedObjects.size(); }
+
+	void clampObjectPosition();
 
 	//
 	// unique methods
 	BOOL addObject(LLViewerObject* object);
 	void removeObject(LLViewerObject *object);
 
-	void setupDrawable(LLDrawable* drawable);
-	void clampObjectPosition();
+	// 
+	// attachments operations
+	//
+	BOOL isObjectAttached(const LLViewerObject *viewer_object) const;
+	const LLViewerObject *getAttachedObject(const LLUUID &object_id) const;
+	LLViewerObject *getAttachedObject(const LLUUID &object_id);
+
+	// list of attachments for this joint
+	typedef std::vector<LLViewerObject *> attachedobjs_vec_t;
+	attachedobjs_vec_t mAttachedObjects;
 
 protected:
 	void calcLOD();
-	
-protected:
-	// Backlink only; don't make this an LLPointer.
-	LLViewerObject*	mAttachedObject;
+	void setupDrawable(LLViewerObject *object);
+
+private:
 	BOOL			mVisibleInFirst;
 	LLVector3		mOriginalPos;
 	S32				mGroup;
 	BOOL			mIsHUDAttachment;
 	S32				mPieSlice;
-	LLUUID			mItemID;			// Inventory item id of the attached item (null if not in inventory)
 };
 
 #endif // LL_LLVIEWERJOINTATTACHMENT_H

indra/newview/llviewermenu.cpp

 			{
 				LLVOAvatar::attachment_map_t::iterator curiter = iter++;
 				LLViewerJointAttachment* attachment = curiter->second;
-				if (attachment->getObject())
+				if (attachment->getNumObjects() > 0)
 				{
 					new_value = true;
 					break;
 			strings.push_back(avatar->getID().asString());
 			LLUUID invoice;
 			send_generic_message("dumptempassetdata", strings, invoice);
-			LLFloaterReg::showInstance( "avatar_tetures", LLSD(avatar->getID()) );
+			LLFloaterReg::showInstance( "avatar_textures", LLSD(avatar->getID()) );
 		}
 		return true;
 	}
 {
 	if (success)
 	{
-		LLViewerJointAttachment *attachment = (LLViewerJointAttachment *)user_data;
+		const LLViewerJointAttachment *attachment = (LLViewerJointAttachment *)user_data;
 		
 		U8 attachment_id = 0;
 		if (attachment)
 		{
-			for (LLVOAvatar::attachment_map_t::iterator iter = gAgent.getAvatarObject()->mAttachmentPoints.begin();
+			for (LLVOAvatar::attachment_map_t::const_iterator iter = gAgent.getAvatarObject()->mAttachmentPoints.begin();
 				 iter != gAgent.getAvatarObject()->mAttachmentPoints.end(); ++iter)
 			{
 				if (iter->second == attachment)
 class LLAttachmentDetachFromPoint : public view_listener_t
 {
 	bool handleEvent(const LLSD& user_data)
-{
-		LLViewerJointAttachment *attachment = get_if_there(gAgent.getAvatarObject()->mAttachmentPoints, user_data.asInteger(), (LLViewerJointAttachment*)NULL);
-	
-		LLViewerObject* attached_object = attachment ? attachment->getObject() : NULL;
-
-	if (attached_object)
-	{
-		gMessageSystem->newMessage("ObjectDetach");
-		gMessageSystem->nextBlockFast(_PREHASH_AgentData);
-		gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
-		gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
-
-		gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
-		gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, attached_object->getLocalID());
-		gMessageSystem->sendReliable( gAgent.getRegionHost() );
-	}
-		return true;
-}
+	{
+		const LLViewerJointAttachment *attachment = get_if_there(gAgent.getAvatarObject()->mAttachmentPoints, user_data.asInteger(), (LLViewerJointAttachment*)NULL);
+		if (attachment->getNumObjects() > 0)
+		{
+			gMessageSystem->newMessage("ObjectDetach");
+			gMessageSystem->nextBlockFast(_PREHASH_AgentData);
+			gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() );
+			gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
+			
+			for (LLViewerJointAttachment::attachedobjs_vec_t::const_iterator iter = attachment->mAttachedObjects.begin();
+				 iter != attachment->mAttachedObjects.end();
+				 iter++)
+			{
+				LLViewerObject *attached_object = (*iter);
+				gMessageSystem->nextBlockFast(_PREHASH_ObjectData);
+				gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, attached_object->getLocalID());
+			}
+			gMessageSystem->sendReliable( gAgent.getRegionHost() );
+		}
+		return true;
+	}
 };
 
 static bool onEnableAttachmentLabel(LLUICtrl* ctrl, const LLSD& data)
 	LLMenuItemGL* menu = dynamic_cast<LLMenuItemGL*>(ctrl);
 	if (menu)
 	{
-		LLViewerJointAttachment *attachmentp = get_if_there(gAgent.getAvatarObject()->mAttachmentPoints, data["index"].asInteger(), (LLViewerJointAttachment*)NULL);
-	if (attachmentp)
-	{
+		const LLViewerJointAttachment *attachment = get_if_there(gAgent.getAvatarObject()->mAttachmentPoints, data["index"].asInteger(), (LLViewerJointAttachment*)NULL);
+		if (attachment)
+		{
 			label = data["label"].asString();
-		if (attachmentp->getObject())
-		{
-			LLViewerInventoryItem* itemp = gInventory.getItem(attachmentp->getItemID());
-			if (itemp)
+			for (LLViewerJointAttachment::attachedobjs_vec_t::const_iterator attachment_iter = attachment->mAttachedObjects.begin();
+				 attachment_iter != attachment->mAttachedObjects.end();
+				 ++attachment_iter)
 			{
-				label += std::string(" (") + itemp->getName() + std::string(")");
+				const LLViewerObject* attached_object = (*attachment_iter);
+				if (attached_object)
+				{
+					LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getItemID());
+					if (itemp)
+					{
+						label += std::string(" (") + itemp->getName() + std::string(")");
+						break;
+					}
+				}
 			}
 		}
-	}
 		menu->setLabel(label);
-}
+	}
 	return true;
 }
 
-
 class LLAttachmentDetach : public view_listener_t
 {
 	bool handleEvent(const LLSD& userdata)
 		// item is in your inventory
 
 		LLViewerObject*              object         = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject();
-		LLViewerJointAttachment*     attachment_pt  = NULL;
+		LLViewerJointAttachment*     attachment     = NULL;
 		LLInventoryItem*             item           = NULL;
 
-		if ( object )
+		if (object)
 		{
     		S32 attachmentID  = ATTACHMENT_ID_FROM_STATE(object->getState());
-			attachment_pt = get_if_there(gAgent.getAvatarObject()->mAttachmentPoints, attachmentID, (LLViewerJointAttachment*)NULL);
-
-			if ( attachment_pt )
+			attachment = get_if_there(gAgent.getAvatarObject()->mAttachmentPoints, attachmentID, (LLViewerJointAttachment*)NULL);
+
+			if (attachment)
 			{
-				// make sure item is in your inventory (it could be a delayed attach message being sent from the sim)
-				// so check to see if the item is in the inventory already
-				item = gInventory.getItem(attachment_pt->getItemID());
-				
-				if ( !item )
+				for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+					 attachment_iter != attachment->mAttachedObjects.end();
+					 ++attachment_iter)
 				{
-					// Item does not exist, make an observer to enable the pie menu 
-					// when the item finishes fetching worst case scenario 
-					// if a fetch is already out there (being sent from a slow sim)
-					// we refetch and there are 2 fetches
-					LLWornItemFetchedObserver* wornItemFetched = new LLWornItemFetchedObserver();
-					LLInventoryFetchObserver::item_ref_t items; //add item to the inventory item to be fetched
-
-					items.push_back(attachment_pt->getItemID());
-				
-					wornItemFetched->fetchItems(items);
-					gInventory.addObserver(wornItemFetched);
+					// make sure item is in your inventory (it could be a delayed attach message being sent from the sim)
+					// so check to see if the item is in the inventory already
+					item = gInventory.getItem((*attachment_iter)->getItemID());
+					if (!item)
+					{
+						// Item does not exist, make an observer to enable the pie menu 
+						// when the item finishes fetching worst case scenario 
+						// if a fetch is already out there (being sent from a slow sim)
+						// we refetch and there are 2 fetches
+						LLWornItemFetchedObserver* wornItemFetched = new LLWornItemFetchedObserver();
+						LLInventoryFetchObserver::item_ref_t items; //add item to the inventory item to be fetched
+						
+						items.push_back((*attachment_iter)->getItemID());
+						
+						wornItemFetched->fetchItems(items);
+						gInventory.addObserver(wornItemFetched);
+					}
 				}
 			}
 		}
 		bool enable = false;
 		LLVOAvatar::attachment_map_t::iterator found_it = gAgent.getAvatarObject()->mAttachmentPoints.find(user_data.asInteger());
 		if (found_it != gAgent.getAvatarObject()->mAttachmentPoints.end())
-{
-			enable = found_it->second->getObject() != NULL;
+		{
+			enable = found_it->second->getNumObjects() > 0;
 		}
 		return enable;
-}
+	}
 };
 
 class LLAvatarSendIM : public view_listener_t
 		LLVOAvatar::attachment_map_t::iterator curiter = iter++;
 		LLViewerJointAttachment* attachment = curiter->second;
 		S32 key = curiter->first;
-		BOOL visible = (attachment->getObject() != NULL &&
-						attachment->getObject()->mDrawable.notNull() && 
-						!attachment->getObject()->mDrawable->isRenderType(0));
-		LLVector3 pos;
-		if (visible) pos = attachment->getObject()->mDrawable->getPosition();
-		llinfos << "ATTACHMENT " << key << ": item_id=" << attachment->getItemID()
-				<< (attachment->getObject() ? " present " : " absent ")
-				<< (visible ? "visible " : "invisible ")
-				<<  " at " << pos
-				<< " and " << (visible ? attachment->getObject()->getPosition() : LLVector3::zero)
-				<< llendl;
+		for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+			 attachment_iter != attachment->mAttachedObjects.end();
+			 ++attachment_iter)
+		{
+			LLViewerObject *attached_object = (*attachment_iter);
+			BOOL visible = (attached_object != NULL &&
+							attached_object->mDrawable.notNull() && 
+							!attached_object->mDrawable->isRenderType(0));
+			LLVector3 pos;
+			if (visible) pos = attached_object->mDrawable->getPosition();
+			llinfos << "ATTACHMENT " << key << ": item_id=" << attached_object->getItemID()
+					<< (attached_object ? " present " : " absent ")
+					<< (visible ? "visible " : "invisible ")
+					<<  " at " << pos
+					<< " and " << (visible ? attached_object->getPosition() : LLVector3::zero)
+					<< llendl;
+		}
 	}
 }
 

indra/newview/llviewerobject.cpp

 	mJointInfo(NULL),
 	mState(0),
 	mMedia(NULL),
-	mClickAction(0)
+	mClickAction(0),
+	mAttachmentItemID(LLUUID::null)
 {
 	if(!is_global)
 	{

indra/newview/llviewerobject.h

 
 private:	
 	static S32 sNumObjects;
+
+public:
+	const LLUUID &getItemID() const { return mAttachmentItemID; }
+	void setItemID(const LLUUID &id) { mAttachmentItemID = id; }
+private:
+	LLUUID mAttachmentItemID; // ItemID when item is in user inventory.
 };
 
 ///////////////////

indra/newview/llviewerobjectlist.cpp

 				 iter != avatarp->mAttachmentPoints.end(); )
 			{
 				LLVOAvatar::attachment_map_t::iterator curiter = iter++;
-				LLViewerJointAttachment* attachmentp = curiter->second;
-				if (attachmentp->getIsHUDAttachment())
+				LLViewerJointAttachment* attachment = curiter->second;
+				if (attachment->getIsHUDAttachment())
 				{
-					LLViewerObject* objectp = attachmentp->getObject();
-					if (objectp)
+					for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+						 attachment_iter != attachment->mAttachedObjects.end();
+						 ++attachment_iter)
 					{
-						mSelectPickList.insert(objectp);		
-						LLViewerObject::const_child_list_t& child_list = objectp->getChildren();
-						for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-							 iter != child_list.end(); iter++)
+						if (LLViewerObject* attached_object = (*attachment_iter))
 						{
-							LLViewerObject* childp = *iter;
-							if (childp)
+							mSelectPickList.insert(attached_object);
+							LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
+							for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
+								 iter != child_list.end(); iter++)
 							{
-								mSelectPickList.insert(childp);
+								LLViewerObject* childp = *iter;
+								if (childp)
+								{
+									mSelectPickList.insert(childp);
+								}
 							}
 						}
 					}

indra/newview/llvoavatar.cpp

 // Helper functions
 //-----------------------------------------------------------------------------
 static F32 calc_bouncy_animation(F32 x);
-static U32 calc_shame(LLVOVolume* volume, std::set<LLUUID> &textures);
+static U32 calc_shame(const LLVOVolume* volume, std::set<LLUUID> &textures);
 
 //-----------------------------------------------------------------------------
 // LLVOAvatar()
 			continue ;
 		}
 
-		LLViewerObject* object = attachment->getObject();
-		if (object && !object->isHUDAttachment())
-		{
-			LLDrawable* drawable = object->mDrawable;
-			if (drawable)
-			{
-				LLSpatialBridge* bridge = drawable->getSpatialBridge();
-				if (bridge)
+		for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+			 attachment_iter != attachment->mAttachedObjects.end();
+			 ++attachment_iter)
+		{
+			const LLViewerObject* attached_object = (*attachment_iter);
+			if (attached_object && !attached_object->isHUDAttachment())
+			{
+				LLDrawable* drawable = attached_object->mDrawable;
+				if (drawable)
 				{
-					const LLVector3* ext = bridge->getSpatialExtents();
-					LLVector3 distance = (ext[1] - ext[0]);
-					
-					// Only add the prim to spatial extents calculations if it isn't a megaprim.
-					// max_attachment_span calculated at the start of the function 
-					// (currently 5 times our max prim size) 
-					if (distance.mV[0] < max_attachment_span 
-						&& distance.mV[1] < max_attachment_span
-						&& distance.mV[2] < max_attachment_span)
+					LLSpatialBridge* bridge = drawable->getSpatialBridge();
+					if (bridge)
 					{
-						update_min_max(newMin,newMax,ext[0]);
-						update_min_max(newMin,newMax,ext[1]);
+						const LLVector3* ext = bridge->getSpatialExtents();
+						LLVector3 distance = (ext[1] - ext[0]);
+						
+						// Only add the prim to spatial extents calculations if it isn't a megaprim.
+						// max_attachment_span calculated at the start of the function 
+						// (currently 5 times our max prim size) 
+						if (distance.mV[0] < max_attachment_span 
+							&& distance.mV[1] < max_attachment_span
+							&& distance.mV[2] < max_attachment_span)
+						{
+							update_min_max(newMin,newMax,ext[0]);
+							update_min_max(newMin,newMax,ext[1]);
+						}
 					}
 				}
 			}
 		{
 			attachment_map_t::iterator curiter = iter++;
 			LLViewerJointAttachment* attachment = curiter->second;
-			LLViewerObject *attached_object = attachment->getObject();
-
-			BOOL visibleAttachment = visible || (attached_object && 
-												 !(attached_object->mDrawable->getSpatialBridge() &&
-												   attached_object->mDrawable->getSpatialBridge()->getRadius() < 2.0));
-
-			if (visibleAttachment && attached_object && !attached_object->isDead() && attachment->getValid())
-			{
-				// if selecting any attachments, update all of them as non-damped
-				if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() && LLSelectMgr::getInstance()->getSelection()->isAttachment())
+
+			for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+				 attachment_iter != attachment->mAttachedObjects.end();
+				 ++attachment_iter)
+			{
+				LLViewerObject* attached_object = (*attachment_iter);
+				BOOL visibleAttachment = visible || (attached_object && 
+													 !(attached_object->mDrawable->getSpatialBridge() &&
+													   attached_object->mDrawable->getSpatialBridge()->getRadius() < 2.0));
+				
+				if (visibleAttachment && attached_object && !attached_object->isDead() && attachment->getValid())
 				{
-					gPipeline.updateMoveNormalAsync(attached_object->mDrawable);
+					// if selecting any attachments, update all of them as non-damped
+					if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() && LLSelectMgr::getInstance()->getSelection()->isAttachment())
+					{
+						gPipeline.updateMoveNormalAsync(attached_object->mDrawable);
+					}
+					else
+					{
+						gPipeline.updateMoveDampedAsync(attached_object->mDrawable);
+					}
+					
+					LLSpatialBridge* bridge = attached_object->mDrawable->getSpatialBridge();
+					if (bridge)
+					{
+						gPipeline.updateMoveNormalAsync(bridge);
+					}
+					attached_object->updateText();	
 				}
-				else
-				{
-					gPipeline.updateMoveDampedAsync(attached_object->mDrawable);
-				}
-
-				LLSpatialBridge* bridge = attached_object->mDrawable->getSpatialBridge();
-				if (bridge)
-				{
-					gPipeline.updateMoveNormalAsync(bridge);
-				}
-				attached_object->updateText();	
 			}
 		}
 	}
 			{
 				attachment_map_t::iterator curiter = iter++;
 				LLViewerJointAttachment* attachment = curiter->second;
-				if (attachment->getObject())
+
+				for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+					 attachment_iter != attachment->mAttachedObjects.end();
+					 ++attachment_iter)
 				{
-					if(attachment->getObject()->mDrawable->isVisible())
+					if (LLViewerObject *attached_object = (*attachment_iter))
 					{
-						llinfos << attachment->getName() << " visible" << llendl;
-					}
-					else
-					{
-						llinfos << attachment->getName() << " not visible at " << mDrawable->getWorldPosition() << " and radius " << mDrawable->getRadius() << llendl;
+						if(attached_object->mDrawable->isVisible())
+						{
+							llinfos << attachment->getName() << " visible" << llendl;
+						}
+						else
+						{
+							llinfos << attachment->getName() << " not visible at " << mDrawable->getWorldPosition() << " and radius " << mDrawable->getRadius() << llendl;
+						}
 					}
 				}
 			}
 void LLVOAvatar::removeChild(LLViewerObject *childp)
 {
 	LLViewerObject::removeChild(childp);
-	detachObject(childp);
+	if (!detachObject(childp))
+	{
+		llwarns << "Calling detach on non-attached object " << llendl;
+	}
 }
 
 LLViewerJointAttachment* LLVOAvatar::getTargetAttachmentPoint(LLViewerObject* viewer_object)
 //-----------------------------------------------------------------------------
 // attachObject()
 //-----------------------------------------------------------------------------
-LLViewerJointAttachment *LLVOAvatar::attachObject(LLViewerObject *viewer_object)
+const LLViewerJointAttachment *LLVOAvatar::attachObject(LLViewerObject *viewer_object)
 {
 	LLViewerJointAttachment* attachment = getTargetAttachmentPoint(viewer_object);
 
 }
 
 //-----------------------------------------------------------------------------
+// attachObject()
+//-----------------------------------------------------------------------------
+U32 LLVOAvatar::getNumAttachments() const
+{
+	U32 num_attachments = 0;
+	for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin();
+		 iter != mAttachmentPoints.end();
+		 iter++)
+	{
+		const LLViewerJointAttachment *attachment_pt = (*iter).second;
+		num_attachments += attachment_pt->getNumObjects();
+	}
+	return num_attachments;
+}
+
+//-----------------------------------------------------------------------------
+// canAttachMoreObjects()
+//-----------------------------------------------------------------------------
+BOOL LLVOAvatar::canAttachMoreObjects() const
+{
+	return (getNumAttachments() < MAX_AGENT_ATTACHMENTS);
+}
+
+//-----------------------------------------------------------------------------
 // lazyAttach()
 //-----------------------------------------------------------------------------
 void LLVOAvatar::lazyAttach()
 		LLViewerJointAttachment* attachment = curiter->second;
 		if (attachment->getIsHUDAttachment())
 		{
-			LLViewerObject* obj = attachment->getObject();
-			if (obj && obj->mDrawable.notNull())
-			{
-				gPipeline.markMoved(obj->mDrawable);
+			for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+				 attachment_iter != attachment->mAttachedObjects.end();
+				 ++attachment_iter)
+			{
+				const LLViewerObject* attached_object = (*attachment_iter);
+				if (attached_object && attached_object->mDrawable.notNull())
+				{
+					gPipeline.markMoved(attached_object->mDrawable);
+				}
 			}
 		}
 	}
 	{
 		attachment_map_t::iterator curiter = iter++;
 		LLViewerJointAttachment* attachment = curiter->second;
-		// only one object per attachment point for now
-		if (attachment->getObject() == viewer_object)
-		{
-			LLUUID item_id = attachment->getItemID();
+
+		if (attachment->isObjectAttached(viewer_object))
+		{
+			LLUUID item_id = viewer_object->getItemID();
 			attachment->removeObject(viewer_object);
 			if (isSelf())
 			{
 				// the simulator should automatically handle
 				// permission revocation
-
+				
 				stopMotionFromSource(viewer_object->getID());
 				LLFollowCamMgr::setCameraActive(viewer_object->getID(), FALSE);
-
+				
 				LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren();
 				for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
 					 iter != child_list.end(); iter++)
 					LLViewerObject* child_objectp = *iter;
 					// the simulator should automatically handle
 					// permissions revocation
-
+					
 					stopMotionFromSource(child_objectp->getID());
 					LLFollowCamMgr::setCameraActive(child_objectp->getID(), FALSE);
 				}
-
 			}
 			lldebugs << "Detaching object " << viewer_object->mID << " from " << attachment->getName() << llendl;
 			if (isSelf())
 			{
 				// Then make sure the inventory is in sync with the avatar.
-
+				
 				// Update COF contents, don't trigger appearance update.
 				if (gAgent.getAvatarObject() == NULL)
 				{
 				{
 					LLAppearanceManager::dumpCat(LLAppearanceManager::getCOF(),"Removing attachment link:");
 					LLAppearanceManager::removeItemLinks(item_id, false);
-
 				}
-
+				
 				// BAP - needs to change for label to track link.
 				gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
 				gInventory.notifyObservers();
 			return TRUE;
 		}
 	}
-
 	return FALSE;
 }
 
 	{
 		attachment_map_t::const_iterator curiter = iter++;
 		LLViewerJointAttachment* attachment = curiter->second;
-		if (attachment->getIsHUDAttachment() && attachment->getObject())
+		if (attachment->getIsHUDAttachment() && attachment->getNumObjects() > 0)
 		{
 			return TRUE;
 		}
 	{
 		attachment_map_t::const_iterator curiter = iter++;
 		LLViewerJointAttachment* attachment = curiter->second;
-		if (attachment->getIsHUDAttachment() && attachment->getObject())
-		{
-			LLViewerObject* hud_object = attachment->getObject();
-
-			// initialize bounding box to contain identity orientation and center point for attached object
-			bbox.addPointLocal(hud_object->getPosition());
-			// add rotated bounding box for attached object
-			bbox.addBBoxAgent(hud_object->getBoundingBoxAgent());
-			LLViewerObject::const_child_list_t& child_list = hud_object->getChildren();
-			for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-				 iter != child_list.end(); iter++)
-			{
-				LLViewerObject* child_objectp = *iter;
-				bbox.addBBoxAgent(child_objectp->getBoundingBoxAgent());
+		if (attachment->getIsHUDAttachment())
+		{
+			for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+				 attachment_iter != attachment->mAttachedObjects.end();
+				 ++attachment_iter)
+			{
+				const LLViewerObject* attached_object = (*attachment_iter);
+				// initialize bounding box to contain identity orientation and center point for attached object
+				bbox.addPointLocal(attached_object->getPosition());
+				// add rotated bounding box for attached object
+				bbox.addBBoxAgent(attached_object->getBoundingBoxAgent());
+				LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
+				for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
+					 iter != child_list.end(); iter++)
+				{
+					const LLViewerObject* child_objectp = *iter;
+					bbox.addBBoxAgent(child_objectp->getBoundingBoxAgent());
+				}
 			}
 		}
 	}
 		 ++iter)
 	{
 		LLViewerJointAttachment* attachment = iter->second;
-		LLViewerObject* object = attachment->getObject();
-		if (object && !object->isHUDAttachment())
-		{
-			LLDrawable* drawable = object->mDrawable;
-			if (drawable)
-			{
-				shame += 10;
-				LLVOVolume* volume = drawable->getVOVolume();
-				if (volume)
+		for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+			 attachment_iter != attachment->mAttachedObjects.end();
+			 ++attachment_iter)
+		{
+			const LLViewerObject* attached_object = (*attachment_iter);
+			if (attached_object && !attached_object->isHUDAttachment())
+			{
+				const LLDrawable* drawable = attached_object->mDrawable;
+				if (drawable)
 				{
-					shame += calc_shame(volume, textures);
+					shame += 10;
+					const LLVOVolume* volume = drawable->getVOVolume();
+					if (volume)
+					{
+						shame += calc_shame(volume, textures);
+					}
 				}
 			}
 		}
-	}	
+	}
 
 	shame += textures.size() * 5;
 
 }
 
 
-U32 calc_shame(LLVOVolume* volume, std::set<LLUUID> &textures)
+U32 calc_shame(const LLVOVolume* volume, std::set<LLUUID> &textures)
 {
 	if (!volume)
 	{
 	const LLVector3& sc = volume->getScale();
 	scale += (U32) sc.mV[0] + (U32) sc.mV[1] + (U32) sc.mV[2];
 
-	LLDrawable* drawablep = volume->mDrawable;
+	const LLDrawable* drawablep = volume->mDrawable;
 
 	if (volume->isSculpted())
 	{
-		LLSculptParams *sculpt_params = (LLSculptParams *) volume->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
+		const LLSculptParams *sculpt_params = (LLSculptParams *) volume->getParameterEntry(LLNetworkData::PARAMS_SCULPT);
 		LLUUID sculpt_id = sculpt_params->getSculptTexture();
 		textures.insert(sculpt_id);
 	}
 
 	for (S32 i = 0; i < drawablep->getNumFaces(); ++i)
 	{
-		LLFace* face = drawablep->getFace(i);
+		const LLFace* face = drawablep->getFace(i);
 		const LLTextureEntry* te = face->getTextureEntry();
-		LLViewerTexture* img = face->getTexture();
+		const LLViewerTexture* img = face->getTexture();
 
 		textures.insert(img->getID());
 
 	for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
 		 iter != child_list.end(); iter++)
 	{
-		LLViewerObject* child_objectp = *iter;
-		LLDrawable* child_drawablep = child_objectp->mDrawable;
+		const LLViewerObject* child_objectp = *iter;
+		const LLDrawable* child_drawablep = child_objectp->mDrawable;
 		if (child_drawablep)
 		{
-			LLVOVolume* child_volumep = child_drawablep->getVOVolume();
+			const LLVOVolume* child_volumep = child_drawablep->getVOVolume();
 			if (child_volumep)
 			{
 				shame += calc_shame(child_volumep, textures);

indra/newview/llvoavatar.h

 	//--------------------------------------------------------------------
 public:
 	void 				clampAttachmentPositions();
-	virtual LLViewerJointAttachment* attachObject(LLViewerObject *viewer_object);
+	virtual const LLViewerJointAttachment* attachObject(LLViewerObject *viewer_object);
 	BOOL 				detachObject(LLViewerObject *viewer_object);
 	static LLVOAvatar*  findAvatarFromAttachment(LLViewerObject* obj);
 protected:
 	LLBBox 				getHUDBBox() const;
 	void 				rebuildHUD();
 	void 				resetHUDAttachments();
+	BOOL				canAttachMoreObjects() const;
+protected:
+	U32					getNumAttachments() const; // O(N), not O(1)
 
 /**                    Wearables
  **                                                                            **

indra/newview/llvoavatarself.cpp

 	{
 		attachment_map_t::const_iterator curiter = iter++;
 		const LLViewerJointAttachment* attachment = curiter->second;
-		if( attachment->getItemID() == inv_item_id )
+		if (attachment->getAttachedObject(inv_item_id))
 		{
 			return TRUE;
 		}
 			{
 				attachment_map_t::const_iterator curiter = iter++;
 				const LLViewerJointAttachment* attachment = curiter->second;
-				if( attachment->getItemID() == item_id )
+				if (attachment->getAttachedObject(item_id))
 				{
 					return TRUE;
 				}
 //-----------------------------------------------------------------------------
 // getWornAttachment()
 //-----------------------------------------------------------------------------
-LLViewerObject* LLVOAvatarSelf::getWornAttachment( const LLUUID& inv_item_id ) const
+LLViewerObject* LLVOAvatarSelf::getWornAttachment( const LLUUID& inv_item_id )
 {
-	for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); 
+	for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); 
 		 iter != mAttachmentPoints.end(); )
 	{
-		attachment_map_t::const_iterator curiter = iter++;
-		const LLViewerJointAttachment* attachment = curiter->second;
-		if( attachment->getItemID() == inv_item_id )
+		attachment_map_t::iterator curiter = iter++;
+		LLViewerJointAttachment* attachment = curiter->second;
+ 		if (LLViewerObject *attached_object = attachment->getAttachedObject(inv_item_id))
 		{
-			return attachment->getObject();
+			return attached_object;
 		}
 	}
 	return NULL;
 	{
 		attachment_map_t::const_iterator curiter = iter++;
 		const LLViewerJointAttachment* attachment = curiter->second;
-		if( attachment->getItemID() == inv_item_id )
+		if (attachment->getAttachedObject(inv_item_id))
 		{
 			return attachment->getName();
 		}
 }
 
 //virtual
-LLViewerJointAttachment *LLVOAvatarSelf::attachObject(LLViewerObject *viewer_object)
+const LLViewerJointAttachment *LLVOAvatarSelf::attachObject(LLViewerObject *viewer_object)
 {
-	LLViewerJointAttachment *attachment = LLVOAvatar::attachObject(viewer_object);
+	const LLViewerJointAttachment *attachment = LLVOAvatar::attachObject(viewer_object);
 	if (!attachment)
 	{
 		return 0;
 	updateAttachmentVisibility(gAgent.getCameraMode());
 	
 	// Then make sure the inventory is in sync with the avatar.
-	LLViewerInventoryItem *item = gInventory.getItem(attachment->getItemID());
-	if (item)
+
+	// Should just be the last object added
+	if (attachment->isObjectAttached(viewer_object))
 	{
-		LLAppearanceManager::dumpCat(LLAppearanceManager::getCOF(),"Adding attachment link:");
-		LLAppearanceManager::wearItem(item,false);  // Add COF link for item.
+		const LLUUID& attachment_id = viewer_object->getItemID();
+		LLViewerInventoryItem *item = gInventory.getItem(attachment_id);
+		if (item)
+		{
+			LLAppearanceManager::dumpCat(LLAppearanceManager::getCOF(),"Adding attachment link:");
+			LLAppearanceManager::wearItem(item,false);  // Add COF link for item.
+			gInventory.addChangedMask(LLInventoryObserver::LABEL, attachment_id);
+		}
+	}
 
-	}
-	gInventory.addChangedMask(LLInventoryObserver::LABEL, attachment->getItemID());
 	gInventory.notifyObservers();
 
 	return attachment;

indra/newview/llvoavatarself.h

 public:
 	void 				updateAttachmentVisibility(U32 camera_mode);
 	BOOL 				isWearingAttachment(const LLUUID& inv_item_id, BOOL include_linked_items = FALSE) const;
-	LLViewerObject* 	getWornAttachment(const LLUUID& inv_item_id ) const;
+	LLViewerObject* 	getWornAttachment(const LLUUID& inv_item_id );
 	const std::string   getAttachedPointName(const LLUUID& inv_item_id) const;
-	/*virtual*/ LLViewerJointAttachment *attachObject(LLViewerObject *viewer_object);
+	/*virtual*/ const LLViewerJointAttachment *attachObject(LLViewerObject *viewer_object);
 	void				getAllAttachmentsArray(LLDynamicArray<S32>& attachments);
 
 	//--------------------------------------------------------------------

indra/newview/pipeline.cpp

 			 iter != avatarp->mAttachmentPoints.end(); )
 		{
 			LLVOAvatar::attachment_map_t::iterator curiter = iter++;
-			LLViewerJointAttachment* attachmentp = curiter->second;
-			if (attachmentp->getIsHUDAttachment())
-			{
-				LLViewerObject* objectp = attachmentp->getObject();
-				if (objectp)
+			LLViewerJointAttachment* attachment = curiter->second;
+			if (attachment->getIsHUDAttachment())
+			{
+				for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+					 attachment_iter != attachment->mAttachedObjects.end();
+					 ++attachment_iter)
 				{
-					LLDrawable* drawable = objectp->mDrawable;
-					if (drawable->isDead())
+					if (LLViewerObject* attached_object = (*attachment_iter))
 					{
-						continue;
-					}
-
-					for (S32 j = 0; j < drawable->getNumFaces(); ++j)
-					{
-						LLFace* facep = drawable->getFace(j);
-						if (!facep->getPool())
+						LLDrawable* drawable = attached_object->mDrawable;
+						if (drawable->isDead())
 						{
-							facep->renderForSelect(prim_mask);
+							continue;
 						}
-					}
-
-					//render child faces
-					LLViewerObject::const_child_list_t& child_list = objectp->getChildren();
-					for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-						 iter != child_list.end(); iter++)
-					{
-						LLViewerObject* child = *iter;
-						LLDrawable* child_drawable = child->mDrawable;
-						for (S32 l = 0; l < child_drawable->getNumFaces(); ++l)
+							
+						for (S32 j = 0; j < drawable->getNumFaces(); ++j)
 						{
-							LLFace* facep = child_drawable->getFace(l);
+							LLFace* facep = drawable->getFace(j);
 							if (!facep->getPool())
 							{
 								facep->renderForSelect(prim_mask);
 							}
 						}
+							
+						//render child faces
+						LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
+						for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
+							 iter != child_list.end(); iter++)
+						{
+							LLViewerObject* child = *iter;
+							LLDrawable* child_drawable = child->mDrawable;
+							for (S32 l = 0; l < child_drawable->getNumFaces(); ++l)
+							{
+								LLFace* facep = child_drawable->getFace(l);
+								if (!facep->getPool())
+								{
+									facep->renderForSelect(prim_mask);
+								}
+							}
+						}
 					}
-				}	
+				}
 			}
 		}
 
 		iter != avatar->mAttachmentPoints.end();
 		++iter)
 	{
-		LLViewerObject* object = iter->second->getObject();
-		if (object)
-		{
-			markVisible(object->mDrawable->getSpatialBridge(), *LLViewerCamera::getInstance());
+		LLViewerJointAttachment *attachment = iter->second;
+		for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+			 attachment_iter != attachment->mAttachedObjects.end();
+			 ++attachment_iter)
+		{
+			if (LLViewerObject* attached_object = (*attachment_iter))
+			{
+				markVisible(attached_object->mDrawable->getSpatialBridge(), *LLViewerCamera::getInstance());
+			}
 		}
 	}
 

indra/newview/skins/default/xui/en/notifications.xml

 
   <notification
    icon="alertmodal.tga"
+   name="MaxAttachmentsOnOutfit"
+   type="alertmodal">
+Could not attach object.
+Exceeds the attachments limit of [MAX_ATTACHMENTS] objects. Please detach another object first.
+  </notification>
+
+  <notification
+   icon="alertmodal.tga"
    name="CannotWearInfoNotComplete"
    type="alertmodal">
 You can not wear that item because it has not yet loaded. Please try again in a minute.