diff --git a/doc/contributions.txt b/doc/contributions.txt
index 9df072e937ebdcaa8f7d86199cb7ab46abf6701e..f451c5732ee51600360326471edbcd5411e05859 100644
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -212,6 +212,8 @@ Dzonatas Sol
 	VWR-1705
 	VWR-1729
 	VWR-1812
+Eddi Decosta
+	SNOW-586
 Eddy Stryker
 	VWR-15
 	VWR-23
@@ -632,6 +634,7 @@ Tharax Ferraris
 	VWR-605
 Thickbrick Sleaford
 	SNOW-207
+	SNOW-586
 	SNOW-743
 	VWR-7109
 	VWR-9287
diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp
index e0e7fb464794b39e67bf69a5ae985c68c98c0f45..6e6e991e84a05047af0587e613290be8ca6094aa 100644
--- a/indra/newview/llface.cpp
+++ b/indra/newview/llface.cpp
@@ -827,6 +827,73 @@ LLVector2 LLFace::surfaceToTexture(LLVector2 surface_coord, LLVector3 position,
 	return tc;
 }
 
+// Returns scale compared to default texgen, and face orientation as calculated
+// by planarProjection(). This is needed to match planar texgen parameters.
+void LLFace::getPlanarProjectedParams(LLQuaternion* face_rot, LLVector3* face_pos, F32* scale) const
+{
+	const LLMatrix4& vol_mat = getWorldMatrix();
+	const LLVolumeFace& vf = getViewerObject()->getVolume()->getVolumeFace(mTEOffset);
+	LLVector3 normal = vf.mVertices[0].mNormal;
+	LLVector3 binormal = vf.mVertices[0].mBinormal;
+	LLVector2 projected_binormal;
+	planarProjection(projected_binormal, normal, vf.mCenter, binormal);
+	projected_binormal -= LLVector2(0.5f, 0.5f); // this normally happens in xform()
+	*scale = projected_binormal.length();
+	// rotate binormal to match what planarProjection() thinks it is,
+	// then find rotation from that:
+	projected_binormal.normalize();
+	F32 ang = acos(projected_binormal.mV[VY]);
+	ang = (projected_binormal.mV[VX] < 0.f) ? -ang : ang;
+	binormal.rotVec(ang, normal);
+	LLQuaternion local_rot( binormal % normal, binormal, normal );
+	*face_rot = local_rot * vol_mat.quaternion();
+	*face_pos = vol_mat.getTranslation();
+}
+
+// Returns the necessary texture transform to align this face's TE to align_to's TE
+bool LLFace::calcAlignedPlanarTE(const LLFace* align_to,  LLVector2* res_st_offset, 
+								 LLVector2* res_st_scale, F32* res_st_rot) const
+{
+	if (!align_to)
+	{
+		return false;
+	}
+	const LLTextureEntry *orig_tep = align_to->getTextureEntry();
+	if ((orig_tep->getTexGen() != LLTextureEntry::TEX_GEN_PLANAR) ||
+		(getTextureEntry()->getTexGen() != LLTextureEntry::TEX_GEN_PLANAR))
+	{
+		return false;
+	}
+
+	LLVector3 orig_pos, this_pos;
+	LLQuaternion orig_face_rot, this_face_rot;
+	F32 orig_proj_scale, this_proj_scale;
+	align_to->getPlanarProjectedParams(&orig_face_rot, &orig_pos, &orig_proj_scale);
+	getPlanarProjectedParams(&this_face_rot, &this_pos, &this_proj_scale);
+
+	// The rotation of "this face's" texture:
+	LLQuaternion orig_st_rot = LLQuaternion(orig_tep->getRotation(), LLVector3::z_axis) * orig_face_rot;
+	LLQuaternion this_st_rot = orig_st_rot * ~this_face_rot;
+	F32 x_ang, y_ang, z_ang;
+	this_st_rot.getEulerAngles(&x_ang, &y_ang, &z_ang);
+	*res_st_rot = z_ang;
+
+	// Offset and scale of "this face's" texture:
+	LLVector3 centers_dist = (this_pos - orig_pos) * ~orig_st_rot;
+	LLVector3 st_scale(orig_tep->mScaleS, orig_tep->mScaleT, 1.f);
+	st_scale *= orig_proj_scale;
+	centers_dist.scaleVec(st_scale);
+	LLVector2 orig_st_offset(orig_tep->mOffsetS, orig_tep->mOffsetT);
+
+	*res_st_offset = orig_st_offset + (LLVector2)centers_dist;
+	res_st_offset->mV[VX] -= (S32)res_st_offset->mV[VX];
+	res_st_offset->mV[VY] -= (S32)res_st_offset->mV[VY];
+
+	st_scale /= this_proj_scale;
+	*res_st_scale = (LLVector2)st_scale;
+	return true;
+}
+
 void LLFace::updateRebuildFlags()
 {
 	if (!mDrawablep->isState(LLDrawable::REBUILD_VOLUME))
diff --git a/indra/newview/llface.h b/indra/newview/llface.h
index 351d48a671995222523fa00071060db925f82cc2..4c975801554dabad9545049fe83de24feaae6e63 100644
--- a/indra/newview/llface.h
+++ b/indra/newview/llface.h
@@ -89,6 +89,9 @@ class LLFace
 	BOOL			hasGeometry()		const	{ return mGeomCount > 0; }
 	LLVector3		getPositionAgent()	const;
 	LLVector2       surfaceToTexture(LLVector2 surface_coord, LLVector3 position, LLVector3 normal);
+	void 			getPlanarProjectedParams(LLQuaternion* face_rot, LLVector3* face_pos, F32* scale) const;
+	bool			calcAlignedPlanarTE(const LLFace* align_to, LLVector2* st_offset,
+										LLVector2* st_scale, F32* st_rot) const;
 	
 	U32				getState()			const	{ return mState; }
 	void			setState(U32 state)			{ mState |= state; }
diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp
index c4bbdb37f873fc62c14b96db3562b99db9b395d2..2c35ae666973e35eebd6328de3f0f8c4576bf751 100644
--- a/indra/newview/llpanelface.cpp
+++ b/indra/newview/llpanelface.cpp
@@ -42,6 +42,7 @@
 #include "llcolorswatch.h"
 #include "llcombobox.h"
 #include "lldrawpoolbump.h"
+#include "llface.h"
 #include "lllineeditor.h"
 #include "llmediaentry.h"
 #include "llresmgr.h"
@@ -75,6 +76,7 @@ BOOL	LLPanelFace::postBuild()
 	childSetCommitCallback("checkbox flip t",&LLPanelFace::onCommitTextureInfo, this);
 	childSetCommitCallback("TexRot",&LLPanelFace::onCommitTextureInfo, this);
 	childSetAction("button apply",&LLPanelFace::onClickApply,this);
+	childSetCommitCallback("checkbox planar align",&LLPanelFace::onCommitPlanarAlign, this);
 	childSetCommitCallback("TexOffsetU",LLPanelFace::onCommitTextureInfo, this);
 	childSetCommitCallback("TexOffsetV",LLPanelFace::onCommitTextureInfo, this);
 	childSetAction("button align",&LLPanelFace::onClickAutoFix,this);
@@ -359,6 +361,93 @@ struct LLPanelFaceSetTEFunctor : public LLSelectedTEFunctor
 	LLPanelFace* mPanel;
 };
 
+// Functor that aligns a face to mCenterFace
+struct LLPanelFaceSetAlignedTEFunctor : public LLSelectedTEFunctor
+{
+	LLPanelFaceSetAlignedTEFunctor(LLPanelFace* panel, LLFace* center_face) :
+		mPanel(panel),
+		mCenterFace(center_face) {}
+
+	virtual bool apply(LLViewerObject* object, S32 te)
+	{
+		LLFace* facep = object->mDrawable->getFace(te);
+		if (!facep)
+		{
+			return true;
+		}
+
+		bool set_aligned = true;
+		if (facep == mCenterFace)
+		{
+			set_aligned = false;
+		}
+		if (set_aligned)
+		{
+			LLVector2 uv_offset, uv_scale;
+			F32 uv_rot;
+			set_aligned = facep->calcAlignedPlanarTE(mCenterFace, &uv_offset, &uv_scale, &uv_rot);
+			if (set_aligned)
+			{
+				object->setTEOffset(te, uv_offset.mV[VX], uv_offset.mV[VY]);
+				object->setTEScale(te, uv_scale.mV[VX], uv_scale.mV[VY]);
+				object->setTERotation(te, uv_rot);
+			}
+		}
+		if (!set_aligned)
+		{
+			LLPanelFaceSetTEFunctor setfunc(mPanel);
+			setfunc.apply(object, te);
+		}
+		return true;
+	}
+private:
+	LLPanelFace* mPanel;
+	LLFace* mCenterFace;
+};
+
+// Functor that tests if a face is aligned to mCenterFace
+struct LLPanelFaceGetIsAlignedTEFunctor : public LLSelectedTEFunctor
+{
+	LLPanelFaceGetIsAlignedTEFunctor(LLFace* center_face) :
+		mCenterFace(center_face) {}
+
+	virtual bool apply(LLViewerObject* object, S32 te)
+	{
+		LLFace* facep = object->mDrawable->getFace(te);
+		if (!facep)
+		{
+			return false;
+		}
+		if (facep == mCenterFace)
+		{
+			return true;
+		}
+		
+		LLVector2 aligned_st_offset, aligned_st_scale;
+		F32 aligned_st_rot;
+		if ( facep->calcAlignedPlanarTE(mCenterFace, &aligned_st_offset, &aligned_st_scale, &aligned_st_rot) )
+		{
+			const LLTextureEntry* tep = facep->getTextureEntry();
+			LLVector2 st_offset, st_scale;
+			tep->getOffset(&st_offset.mV[VX], &st_offset.mV[VY]);
+			tep->getScale(&st_scale.mV[VX], &st_scale.mV[VY]);
+			F32 st_rot = tep->getRotation();
+			// needs a fuzzy comparison, because of fp errors
+			if (is_approx_equal_fraction(st_offset.mV[VX], aligned_st_offset.mV[VX], 12) && 
+				is_approx_equal_fraction(st_offset.mV[VY], aligned_st_offset.mV[VY], 12) && 
+				is_approx_equal_fraction(st_scale.mV[VX], aligned_st_scale.mV[VX], 12) &&
+				is_approx_equal_fraction(st_scale.mV[VY], aligned_st_scale.mV[VY], 12) &&
+				is_approx_equal_fraction(st_rot, aligned_st_rot, 14))
+			{
+				return true;
+			}
+		}
+		return false;
+	}
+private:
+	LLFace* mCenterFace;
+};
+
 struct LLPanelFaceSendFunctor : public LLSelectedObjectFunctor
 {
 	virtual bool apply(LLViewerObject* object)
@@ -370,8 +459,26 @@ struct LLPanelFaceSendFunctor : public LLSelectedObjectFunctor
 
 void LLPanelFace::sendTextureInfo()
 {
-	LLPanelFaceSetTEFunctor setfunc(this);
-	LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc);
+	if ((bool)childGetValue("checkbox planar align").asBoolean())
+	{
+		struct f1 : public LLSelectedTEGetFunctor<LLFace *>
+		{
+			LLFace* get(LLViewerObject* object, S32 te)
+			{
+				return (object->mDrawable) ? object->mDrawable->getFace(te): NULL;
+			}
+		} get_last_face_func;
+		LLFace* last_face;
+		LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&get_last_face_func, last_face);
+
+		LLPanelFaceSetAlignedTEFunctor setfunc(this, last_face);
+		LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc);
+	}
+	else
+	{
+		LLPanelFaceSetTEFunctor setfunc(this);
+		LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc);
+	}
 
 	LLPanelFaceSendFunctor sendfunc;
 	LLSelectMgr::getInstance()->getSelection()->applyToObjects(&sendfunc);
@@ -497,6 +604,44 @@ void LLPanelFace::getState()
 			}
 		}
 
+
+		// planar align
+		bool align_planar = false;
+		bool identical_planar_aligned = false;
+		bool is_planar = false;
+		{
+			LLCheckBoxCtrl*	cb_planar_align = getChild<LLCheckBoxCtrl>("checkbox planar align");
+			align_planar = (cb_planar_align && cb_planar_align->get());
+			struct f1 : public LLSelectedTEGetFunctor<bool>
+			{
+				bool get(LLViewerObject* object, S32 face)
+				{
+					return (object->getTE(face)->getTexGen() == LLTextureEntry::TEX_GEN_PLANAR);
+				}
+			} func;
+
+			bool texgens_identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, is_planar );
+			bool enabled = (editable && texgens_identical && is_planar);
+			childSetValue("checkbox planar align", align_planar && enabled);
+			childSetEnabled("checkbox planar align", enabled);
+
+			if (align_planar && enabled)
+			{
+				struct f2 : public LLSelectedTEGetFunctor<LLFace *>
+				{
+					LLFace* get(LLViewerObject* object, S32 te)
+					{
+						return (object->mDrawable) ? object->mDrawable->getFace(te): NULL;
+					}
+				} get_te_face_func;
+				LLFace* last_face;
+				LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&get_te_face_func, last_face);
+				LLPanelFaceGetIsAlignedTEFunctor get_is_aligend_func(last_face);
+				// this will determine if the texture param controls are tentative:
+				identical_planar_aligned = LLSelectMgr::getInstance()->getSelection()->applyToTEs(&get_is_aligend_func);
+			}
+		}
+		
 		// Texture scale
 		{
 			childSetEnabled("tex scale",editable);
@@ -510,6 +655,7 @@ void LLPanelFace::getState()
 				}
 			} func;
 			identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, scale_s );
+			identical = align_planar ? identical_planar_aligned : identical;
 			childSetValue("TexScaleU",editable ? llabs(scale_s) : 0);
 			childSetTentative("TexScaleU",LLSD((BOOL)(!identical)));
 			childSetEnabled("TexScaleU",editable);
@@ -528,6 +674,7 @@ void LLPanelFace::getState()
 				}
 			} func;
 			identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, scale_t );
+			identical = align_planar ? identical_planar_aligned : identical;
 
 			childSetValue("TexScaleV",llabs(editable ? llabs(scale_t) : 0));
 			childSetTentative("TexScaleV",LLSD((BOOL)(!identical)));
@@ -549,6 +696,7 @@ void LLPanelFace::getState()
 				}
 			} func;
 			identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, offset_s );
+			identical = align_planar ? identical_planar_aligned : identical;
 			childSetValue("TexOffsetU", editable ? offset_s : 0);
 			childSetTentative("TexOffsetU",!identical);
 			childSetEnabled("TexOffsetU",editable);
@@ -564,6 +712,7 @@ void LLPanelFace::getState()
 				}
 			} func;
 			identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, offset_t );
+			identical = align_planar ? identical_planar_aligned : identical;
 			childSetValue("TexOffsetV", editable ? offset_t : 0);
 			childSetTentative("TexOffsetV",!identical);
 			childSetEnabled("TexOffsetV",editable);
@@ -581,6 +730,7 @@ void LLPanelFace::getState()
 				}
 			} func;
 			identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, rotation );
+			identical = align_planar ? identical_planar_aligned : identical;
 			childSetValue("TexRot", editable ? rotation * RAD_TO_DEG : 0);
 			childSetTentative("TexRot",!identical);
 			childSetEnabled("TexRot",editable);
@@ -1003,3 +1153,11 @@ void LLPanelFace::setMediaType(const std::string& mime_type)
 {
 }
 
+// static
+void LLPanelFace::onCommitPlanarAlign(LLUICtrl* ctrl, void* userdata)
+{
+	LLPanelFace* self = (LLPanelFace*) userdata;
+	self->getState();
+	self->sendTextureInfo();
+}
+
diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h
index 94c354cf0244d3ae52439172c6f6769a655ed205..42be9b257f9b0e21938d40a14214255dc72f4090 100644
--- a/indra/newview/llpanelface.h
+++ b/indra/newview/llpanelface.h
@@ -85,6 +85,7 @@ class LLPanelFace : public LLPanel
 	static void		onCommitShiny(			LLUICtrl* ctrl, void* userdata);
 	static void		onCommitFullbright(		LLUICtrl* ctrl, void* userdata);
 	static void     onCommitGlow(           LLUICtrl* ctrl, void *userdata);
+	static void		onCommitPlanarAlign(	LLUICtrl* ctrl, void* userdata);
 	
 	static void		onClickApply(void*);
 	static void		onClickAutoFix(void*);
diff --git a/indra/newview/skins/default/xui/en/floater_tools.xml b/indra/newview/skins/default/xui/en/floater_tools.xml
index a96dbe3553487ac379f0f1ce573bc9c111a13c78..86fc1c004fb6417d34b4a5f75feccd4fd8e7b734 100644
--- a/indra/newview/skins/default/xui/en/floater_tools.xml
+++ b/indra/newview/skins/default/xui/en/floater_tools.xml
@@ -2404,7 +2404,7 @@ even though the user gets a free copy.
              layout="topleft"
              name="label shininess"
              left_pad="4"
-             top_pad="-36"
+             top_pad="-37"
              width="90">
                 Shininess
             </text>
@@ -2440,7 +2440,7 @@ even though the user gets a free copy.
              layout="topleft"
              left_pad="4"
              name="label bumpiness"
-             top_pad="-36"
+             top_pad="-37"
              width="90">
                 Bumpiness
             </text>
@@ -2558,6 +2558,17 @@ even though the user gets a free copy.
              top_delta="-4"
              width="120" />
 -->
+            <check_box
+             follows="top|left"
+             height="16"
+             initial_value="false"
+             label="Align planar faces"
+             layout="topleft"
+             left="17"
+             name="checkbox planar align"
+             tool_tip="Align textures on all selected faces with the last selected face. Requires Planar texture mapping."
+             top_delta="26"
+             width="140" />
             <text
              type="string"
              length="1"
@@ -2566,8 +2577,8 @@ even though the user gets a free copy.
              layout="topleft"
              left="10"
              name="tex scale"
-             top_pad="4"
-             width="200">
+             top_pad="2"
+             width="140">
                 Repeats / Face
             </text>
             <spinner
@@ -2580,7 +2591,7 @@ even though the user gets a free copy.
              left="20"
              max_val="100"
              name="TexScaleU"
-             top_pad="6"
+             top_pad="5"
              width="185" />
             <check_box
              height="19"
@@ -2689,7 +2700,7 @@ even though the user gets a free copy.
          bg_alpha_color="DkGray"
          name="Add_Media"
          left="0"
-         height="63"
+         height="47"
          width="290">
             <text
              type="string"
@@ -2775,8 +2786,9 @@ even though the user gets a free copy.
 			 label="Align"
 			 label_selected="Align Media"
 			 layout="topleft"
-			 right="-10"
+			 right="-16"
 			 name="button align"
+			 top_delta="-4"
 			 tool_tip="Align media texture (must load first)"
 			 width="80" />
 		</panel>